I want to send some files to an FTP server from Android. I have the server IP address, username, and password. I tried to connect to it from Filezilla and it works, however, if I try to connect from Android it fails. I get status code 530 from ftpClient.getReplyCode().
According to https://en.wikipedia.org/wiki/List_of_FTP_server_return_codes the status code means that the login didn't work. ftpClient.login returns false.
So I tried mounting an FTP server with node.js and could connect and upload files perfectly. Then I tried to connect to another test server ftp://test.rebex.net/ using username: demo and password: password and the login works too (uploading files fails because its a test account).
But why do I fail to log in to that specific server from Android but not from Filezilla?
My code:
uploading_to_local_server = true;
FTPClient ftpClient = new FTPClient();
try {
if(port == 0){
ftpClient.connect(ftpserver);
}else {
ftpClient.connect(ftpserver, port);
}
ftpClient.setSoTimeout(10000);
ftpClient.enterLocalPassiveMode();
Log.d(TAG,"FTP. TRYING TO LOGIN ");
Boolean login_response = ftpClient.login(ftp_usr,ftp_psw);
mreplyCode = ftpClient.getReplyCode();
Log.d(TAG,"FTP. RESPONSE " + mreplyCode);
if (login_response) {
Log.d(TAG, "FTP. VIDEOS TO UPLOAD: LOGGED SUCCESFULLY");
//Logged.
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
ftpClient.setFileTransferMode(FTP.BINARY_FILE_TYPE);
//iterating through the videos
String video_upload_result = "Uknown Error";
ArrayList<HashMap<String, String>> list_videos_to_upload = dataBaseHelper.getVideosLocalserverH();
for (int counter = 0; counter < list_videos_to_upload.size(); counter++) {
video_upload_result = "Uknown Error";
//list_videos_to_upload.get(counter).put("imei", IMEI);
String video_path = list_videos_to_upload.get(counter).get("dir_route");
String video_name = list_videos_to_upload.get(counter).get("video_name");
String vid_id = list_videos_to_upload.get(counter).get("id");
String sync_status = list_videos_to_upload.get(counter).get("sync");
Log.d(TAG, "VIDEOS TO UPLOAD: " + video_path + " ID: " + vid_id);
//Log.d(TAG, "WIFI STATUS: " + mWifi.isConnected() + " DATA STATUS: " + wData.isConnected());
video_upload_result = try_upload_video_to_ftpserver(ftpClient, video_name, remote_path, video_path);
Log.d(TAG, "VIDEOS TO UPLOAD: " + video_path + " RESULT: " + video_upload_result);
if(video_upload_result.equals("Succes")) {
Log.d(TAG, "TESTING UPLOAD: VIDEO ID: "+ vid_id);
dataBaseHelper.modVideos_Localserver(Collections.singletonList(vid_id));
}else if(video_upload_result.equals(video_path + ": open failed: ENOENT (No such file or directory)")){
Log.d(TAG, "TESTING UPLOAD: ERROR CATCHED. FILE NOT FOUND: "+ vid_id);
dataBaseHelper.modVideos_Localserver(Collections.singletonList(vid_id));
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Log.d(TAG, "Error Sleeping thread");
e.printStackTrace();
}
}
}
ftpClient.logout();
ftpClient.disconnect();
}catch (IOException e) {
e.printStackTrace();
}
Log.d(TAG, "UPLOADING LOOP ENDED: ");
uploading_to_local_server = false;
EDIT:
Finally Managed to make it work.
Turns out the server only accepted connections through ftps.
So I changed:
FTPClient ftpClient = new FTPClient();
for
FTPSClient ftpClient = new FTPSClient("TLS", false);
and I managed to connect to the server but still coudnĀ“t upload videos because the server also only accepts encrypted data, so I added:
ftpClient.execPROT("P"); // encrypt data channel
And now I can finally upload videos to that server.
After connect to FTP, and then login to FTP.
If to login is success, then you need to let localPassiveMode:
ftpClient.enterLocalPassiveMode();
Then you can upload file to FTP server.
Thank you
I'm developing an app that sends data over a WiFi intranet connection and all the data is sent in one package on Android 5 but in several packages in Android 8.0.1
So for the code below in Android 5 I get "This is a test" and in Android 8.0.1
"T" (one packet)
"hi"
" i"
"s"
" a"
" test"
Does anybody would know what is happening?
This is part of the code
if(!Connected)
{
//myClient.addListener();
myClient = new Socket(serverName, port);
myClient.setTcpNoDelay(true);
//myClient.on("new message", onNewMessage);
Connected=true;
inFromServer = myClient.getInputStream();
in = new DataInputStream(inFromServer);
}
if(Connected)
{
//updateText.setText("Just connected to " + client.getRemoteSocketAddress());
OutputStream outToServer = myClient.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
if(msgToSend.isEmpty())
{
out.writeChars("This is a test\r\n");
//out.writeBytes("This is a test\r\n");
}
else
{
out.writeChars(msgToSend);
//out.writeBytes(msgToSend);
}
out.flush();
while(waitingData)
{
if(in.available()>0)
{
XChar = in.readByte();
if(XChar>=32)
{
inBuffer+= Byte.toString(XChar);
}
else
{
waitingData= false;
}
}
}
}
I have a client server model where the client runs on android. It establishes its tls sockets using the following code:.
(Everything the client does to login and relogin)
public class LoginAsync extends AsyncTask<Boolean, String, Boolean>
protected Boolean doInBackground(Boolean... params)
{
try
{
//only handle 1 login request at a time
synchronized(loginLock)
{
if(tryingLogin)
{
Utils.logcat(Const.LOGW, tag, "already trying a login. ignoring request");
onPostExecute(false);
return false;
}
tryingLogin = true;
}
//http://stackoverflow.com/a/34228756
//check if server is available first before committing to anything
// otherwise this process will stall. host not available trips timeout exception
Socket diag = new Socket();
diag.connect(new InetSocketAddress(Vars.serverAddress, Vars.commandPort), TIMEOUT);
diag.close();
//send login command
Vars.commandSocket = Utils.mkSocket(Vars.serverAddress, Vars.commandPort, Vars.expectedCertDump);
String login = Utils.currentTimeSeconds() + "|login|" + uname + "|" + passwd;
Vars.commandSocket.getOutputStream().write(login.getBytes());
//read response
byte[] responseRaw = new byte[Const.BUFFERSIZE];
int length = Vars.commandSocket.getInputStream().read(responseRaw);
//on the off chance the socket crapped out right from the get go, now you'll know
if(length < 0)
{
Utils.logcat(Const.LOGE, tag, "Socket closed before a response could be read");
onPostExecute(false);
return false;
}
//there's actual stuff to process, process it!
String loginresp = new String(responseRaw, 0, length);
Utils.logcat(Const.LOGD, tag, loginresp);
//process login response
String[] respContents = loginresp.split("\\|");
if(respContents.length != 4)
{
Utils.logcat(Const.LOGW, tag, "Server response imporoperly formatted");
onPostExecute(false); //not a legitimate server response
return false;
}
if(!(respContents[1].equals("resp") && respContents[2].equals("login")))
{
Utils.logcat(Const.LOGW, tag, "Server response CONTENTS imporperly formated");
onPostExecute(false); //server response doesn't make sense
return false;
}
long ts = Long.valueOf(respContents[0]);
if(!Utils.validTS(ts))
{
Utils.logcat(Const.LOGW, tag, "Server had an unacceptable timestamp");
onPostExecute(false);
return false;
}
Vars.sessionid = Long.valueOf(respContents[3]);
//establish media socket
Vars.mediaSocket = Utils.mkSocket(Vars.serverAddress, Vars.mediaPort, Vars.expectedCertDump);
String associateMedia = Utils.currentTimeSeconds() + "|" + Vars.sessionid;
Vars.mediaSocket.getOutputStream().write(associateMedia.getBytes());
Intent cmdListenerIntent = new Intent(Vars.applicationContext, CmdListener.class);
Vars.applicationContext.startService(cmdListenerIntent);
onPostExecute(true);
return true;
}
catch (CertificateException c)
{
Utils.logcat(Const.LOGE, tag, "server certificate didn't match the expected");
onPostExecute(false);
return false;
}
catch (Exception i)
{
Utils.dumpException(tag, i);
onPostExecute(false);
return false;
}
}
with the mksocket utility function being:
public static Socket mkSocket(String host, int port, final String expected64) throws CertificateException
{
TrustManager[] trustOnlyServerCert = new TrustManager[]
{new X509TrustManager()
{
#Override
public void checkClientTrusted(X509Certificate[] chain, String alg)
{
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String alg) throws CertificateException
{
//Get the certificate encoded as ascii text. Normally a certificate can be opened
// by a text editor anyways.
byte[] serverCertDump = chain[0].getEncoded();
String server64 = Base64.encodeToString(serverCertDump, Base64.NO_PADDING & Base64.NO_WRAP);
//Trim the expected and presented server ceritificate ascii representations to prevent false
// positive of not matching because of randomly appended new lines or tabs or both.
server64 = server64.trim();
String expected64Trimmed = expected64.trim();
if(!expected64Trimmed.equals(server64))
{
throw new CertificateException("Server certificate does not match expected one.");
}
}
#Override
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
}
};
try
{
SSLContext context;
context = SSLContext.getInstance("TLSv1.2");
context.init(new KeyManager[0], trustOnlyServerCert, new SecureRandom());
SSLSocketFactory mkssl = context.getSocketFactory();
Socket socket = mkssl.createSocket(host, port);
socket.setKeepAlive(true);
return socket;
}
catch (Exception e)
{
dumpException(tag, e);
return null;
}
}
Here is the command listener service that gets started on successful login:
public class CmdListener extends IntentService
protected void onHandleIntent(Intent workIntent)
{
// don't want this to catch the login resposne
Utils.logcat(Const.LOGD, tag, "command listener INTENT SERVICE started");
while(inputValid)
{
String logd = ""; //accumulate all the diagnostic message together to prevent multiple entries of diagnostics in log ui just for cmd listener
try
{//the async magic here... it will patiently wait until something comes in
byte[] rawString = new byte[Const.BUFFERSIZE];
int length = Vars.commandSocket.getInputStream().read(rawString);
if(length < 0)
{
throw new Exception("input stream read failed");
}
String fromServer = new String(rawString, 0, length);
String[] respContents = fromServer.split("\\|");
logd = logd + "Server response raw: " + fromServer + "\n";
//check for properly formatted command
if(respContents.length != 4)
{
Utils.logcat(Const.LOGW, tag, "invalid server response");
continue;
}
//verify timestamp
long ts = Long.valueOf(respContents[0]);
if(!Utils.validTS(ts))
{
Utils.logcat(Const.LOGW, tag, "Rejecting server response for bad timestamp");
continue;
}
//just parse and process commands here. not much to see
}
catch (IOException e)
{
Utils.logcat(Const.LOGE, tag, "Command socket closed...");
Utils.dumpException(tag, e);
inputValid = false;
}
catch(NumberFormatException n)
{
Utils.logcat(Const.LOGE, tag, "string --> # error: ");
}
catch(NullPointerException n)
{
Utils.logcat(Const.LOGE, tag, "Command socket null pointer exception");
inputValid = false;
}
catch(Exception e)
{
Utils.logcat(Const.LOGE, tag, "Other exception");
inputValid = false;
}
}
//only 1 case where you don't want to restart the command listener: quitting the app.
//the utils.quit function disables BackgroundManager first before killing the sockets
//that way when this dies, nobody will answer the command listener dead broadcast
Utils.logcat(Const.LOGE, tag, "broadcasting dead command listner");
try
{
Intent deadBroadcast = new Intent(Const.BROADCAST_BK_CMDDEAD);
sendBroadcast(deadBroadcast);
}
catch (Exception e)
{
Utils.logcat(Const.LOGE, tag, "couldn't broadcast dead command listener... leftover broadacast from java socket stupidities?");
Utils.dumpException(tag, e);
}
}
And here is the background manager that signs you in when you switch from wifi to lte, lte to wifi, or when you come out of the subway from nothing to lte:
public class BackgroundManager extends BroadcastReceiver
{
private static final String tag = "BackgroundManager";
#Override
public void onReceive(final Context context, Intent intent)
{
if(Vars.applicationContext == null)
{
//sometimes intents come in when the app is in the process of shutting down so all the contexts won't work.
//it's shutting down anyways. no point of starting something
return;
}
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if(Vars.uname == null || Vars.passwd == null)
{
//if the person hasn't logged in then there's no way to start the command listener
// since you won't have a command socket to listen on
Utils.logcat(Const.LOGW, tag, "user name and password aren't available?");
}
String action = intent.getAction();
if(action.equals(ConnectivityManager.CONNECTIVITY_ACTION))
{
manager.cancel(Vars.pendingRetries);
new KillSocketsAsync().execute();
if(Utils.hasInternet())
{
//internet reconnected case
Utils.logcat(Const.LOGD, tag, "internet was reconnected");
new LoginAsync(Vars.uname, Vars.passwd).execute();
}
else
{
Utils.logcat(Const.LOGD, tag, "android detected internet loss");
}
//command listener does a better of job of figuring when the internet died than android's connectivity manager.
//android's connectivity manager doesn't always get subway internet loss
}
else if (action.equals(Const.BROADCAST_BK_CMDDEAD))
{
String loge = "command listener dead received\n";
//cleanup the pending intents and make sure the old sockets are gone before making new ones
manager.cancel(Vars.pendingRetries);
new KillSocketsAsync().execute(); //make sure everything is good and dead
//all of this just to address the stupid java socket issue where it might just endlessly die/reconnect
//initialize the quick dead count and timestamp if this is the first time
long now = System.currentTimeMillis();
long deadDiff = now - Vars.lastDead;
Vars.lastDead = now;
if(deadDiff < Const.QUICK_DEAD_THRESHOLD)
{
Vars.quickDeadCount++;
loge = loge + "Another quick death (java socket stupidity) occured. Current count: " + Vars.quickDeadCount + "\n";
}
//with the latest quick death, was it 1 too many? if so restart the app
//https://stackoverflow.com/questions/6609414/how-to-programatically-restart-android-app
if(Vars.quickDeadCount == Const.QUICK_DEAD_MAX)
{
loge = loge + "Too many quick deaths (java socket stupidities). Restarting the app\n";
Utils.logcat(Const.LOGE, tag, loge);
//self restart, give it a 5 seconds to quit
Intent selfStart = new Intent(Vars.applicationContext, InitialServer.class);
int pendingSelfId = 999;
PendingIntent selfStartPending = PendingIntent.getActivity(Vars.applicationContext, pendingSelfId, selfStart, PendingIntent.FLAG_CANCEL_CURRENT);
manager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+Const.RESTART_DELAY, selfStartPending);
//hopefully 5 seconds will be enough to get out
Utils.quit();
return;
}
else
{ //app does not need to restart. still record the accumulated error messages
Utils.logcat(Const.LOGE, tag, loge);
}
//if the network is dead then don't bother
if(!Utils.hasInternet())
{
Utils.logcat(Const.LOGD, tag, "No internet detected from commnad listener dead");
return;
}
new LoginAsync(Vars.uname, Vars.passwd).execute();
}
else if (action.equals(Const.ALARM_ACTION_RETRY))
{
Utils.logcat(Const.LOGD, tag, "login retry received");
//no point of a retry if there is no internet to try on
if(!Utils.hasInternet())
{
Utils.logcat(Const.LOGD, tag, "no internet for sign in retry");
manager.cancel(Vars.pendingRetries);
return;
}
new LoginAsync(Vars.uname, Vars.passwd).execute();
}
else if(action.equals(Const.BROADCAST_LOGIN_BG))
{
boolean ok = intent.getBooleanExtra(Const.BROADCAST_LOGIN_RESULT, false);
Utils.logcat(Const.LOGD, tag, "got login result of: " + ok);
Intent loginResult = new Intent(Const.BROADCAST_LOGIN_FG);
loginResult.putExtra(Const.BROADCAST_LOGIN_RESULT, ok);
context.sendBroadcast(loginResult);
if(!ok)
{
Utils.setExactWakeup(Const.RETRY_FREQ, Vars.pendingRetries);
}
}
}
}
The server is on a select system call to listen to its established sockets. It accepts new sockets using this code (C on Linux)
incomingCmd = accept(cmdFD, (struct sockaddr *) &cli_addr, &clilen);
if(incomingCmd < 0)
{
string error = "accept system call error";
postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, error, SELF, ERRORLOG, DONTKNOW, relatedKey));
perror(error.c_str());
goto skipNewCmd;
}
string ip = inet_ntoa(cli_addr.sin_addr);
//setup ssl connection
SSL *connssl = SSL_new(sslcontext);
SSL_set_fd(connssl, incomingCmd);
returnValue = SSL_accept(connssl);
//in case something happened before the incoming connection can be made ssl.
if(returnValue <= 0)
{
string error = "Problem initializing new command tls connection from " + ip;
postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, error, SELF, ERRORLOG, ip, relatedKey));
SSL_shutdown(connssl);
SSL_free(connssl);
shutdown(incomingCmd, 2);
close(incomingCmd);
}
else
{
//add the new socket descriptor to the client self balancing tree
string message = "new command socket from " + ip;
postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, message, SELF, INBOUNDLOG, ip, relatedKey));
clientssl[incomingCmd] = connssl;
sdinfo[incomingCmd] = SOCKCMD;
failCount[incomingCmd] = 0;
}
The problem I'm having is when the client reconnects to the server from an ip address it has used recently, the socket on the client always seems to die after creation. If I retry again, it dies again. The only way to get it to connect is for the android app to kill and restart itself.
Example: on wifi at home with address 192.168.1.101. Connection ok. Switch to LTE on address 24.157.18.90. Reconnects me to the server ok. Come back home and get 192.168.1.101. The socket always dies until the app kills itself. Or if while I'm outside, I loose LTE because I take the subway, when I come out, I get the same problem. Note that each time, it will make a new socket. It will not somehow try to salvage the old one. The socket creation also seems to succeed. It's just as soon as the client wants to do a read on it, java says the socket is closed.
I put all the relevant code in its unobfuscated original form since it's my hobby project. I am out of ideas why this happens.
// http://stackoverflow.com/a/34228756
//check if server is available first before committing to anything
// otherwise this process will stall. host not available trips timeout exception
Socket diag = new Socket();
diag.connect(new InetSocketAddress(Vars.serverAddress, Vars.commandPort), TIMEOUT);
diag.close();
It is caused by these three pointless lines of code. The server gets a connection and an immediate read() result of zero.
There is no value in establishing a connection only to close it and then assume you can open another one. You should use the conection you just established. In general the correct way to establish whether any resource is available is to try to use it in the normal way. Techniques like the above are indistinguishable from attempts to predict the future.
In My Android application I get xmpp connection initially using server_nm and 5222 port on login and done single chat message send and receive,but at time of file transfer, How I configure connection with proxy_server and 7777 port which use for file transfer?
public class XmppManager {
private static final int packetReplyTimeout = 500; // millis
private String server=server_nm;
private int port=5222;
private ConnectionConfiguration config;
private XMPPConnection connection;
String group_name,userid;
private ChatManager chatManager;
private MessageListener messageListener;
private Handler mHandler;
PacketFilter filter;
Message message;
Context context;
public static boolean IBB_ONLY = (System.getProperty("ibb") != null);//true;
protected static final String STREAM_DATA_FIELD_NAME = "stream-method";
private StreamNegotiator byteStreamTransferManager;
private StreamNegotiator inbandTransferManager;
// constructor
public XmppManager(String server, int port) {
this.server = server;
this.port = port;
}
// to initialize connection
public void init(Context c) throws XMPPException {
System.out.println(String.format(
"Initializing connection to server %1$s port %2$d", server,
port));
SmackConfiguration.setPacketReplyTimeout(50000);
config = new ConnectionConfiguration(server, port);
config.setSASLAuthenticationEnabled(false);
config.setSecurityMode(SecurityMode.disabled);
// group chat invitation
ProviderManager.getInstance().addExtensionProvider("x",
"http://jabber.org/protocol/muc#user",
new GroupChatInvitation.Provider());
// offline msgtime
ProviderManager.getInstance().addExtensionProvider("x",
"jabber:x:delay", new DelayInformationProvider());
//file transfer method
ProviderManager.getInstance().addIQProvider("query","http://jabber.org/protocol/bytestreams", new BytestreamsProvider());
ProviderManager.getInstance().addIQProvider("query","http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
ProviderManager.getInstance().addIQProvider("query","http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());
ProviderManager.getInstance().addIQProvider("si","http://jabber.org/protocol/si", new StreamInitiationProvider());
ProviderManager.getInstance().addIQProvider("open","http://jabber.org/protocol/ibb", new OpenIQProvider());
ProviderManager.getInstance().addIQProvider("close","http://jabber.org/protocol/ibb", new CloseIQProvider());
ProviderManager.getInstance().addExtensionProvider("data","http://jabber.org/protocol/ibb", new DataPacketProvider());
// v-card integration
ProviderManager.getInstance().addIQProvider("vCard", "vcard-temp", new VCardProvider());
connection = new XMPPConnection(config);
connection.connect();
System.out.println("Connected: " + connection.isConnected());
chatManager = connection.getChatManager();
context = c;
Intent i = new Intent(context, MyService.class);
context.startService(i);
}
// login to xmpp server
public void performLogin(String username, String password,String resource)
throws XMPPException {
if (connection != null && connection.isConnected()) {
System.out.println("connection on presence send" + connection.isConnected() + " " + username + " " + password);
connection.login(username, password,resource);
setStatus(true, "Online");
Presence presence = new Presence(Presence.Type.available);
connection.sendPacket(presence);
}
}
//using this code I try to send file
public void sendfileafterresponse(String filenm,String filereceiverid )
{
FileTransferNegotiator.IBB_ONLY = true;
this.setServiceEnabled(connection, true);
FileTransferManager manager = new FileTransferManager(connection);
OutgoingFileTransfer transfer = manager.createOutgoingFileTransfer(filereceiverid + "#chat.bobl.us/" + filereceiverid);
File file = new File(filenm);
try {
System.out.println("file send to" + filenm + filereceiverid);
transfer.sendFile(file, "test_file");
} catch (XMPPException e) {
e.printStackTrace();
}
while(!transfer.isDone()) {
if(transfer.getStatus().equals(Status.error)) {
System.out.println("ERROR!!! " + transfer.getError());
} else if (transfer.getStatus().equals(Status.cancelled)
|| transfer.getStatus().equals(Status.refused)) {
System.out.println("Cancelled!!! " + transfer.getError());
}
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(transfer.getStatus().equals(Status.refused) || transfer.getStatus().equals(Status.error)
|| transfer.getStatus().equals(Status.cancelled)){
System.out.println("refused cancelled error " + transfer.getError());
} else {
System.out.println("Success");
}
}
}
}
If you still want to transfer files via smack directly, you basically have too options, as far as I understand the API.
1.
First you can use In Band transmission, that is enabled via
FileTransferNegotiator.IBB_ONLY = true;
This sends your file as base64 encoded message, the same way you are sending a normal message packet.
The second option is to use a streaming proxy. This one is way faster since it does not need to encode / decode the file to base64 but transmitts the data directly. Both devices connnect to the proxy server and share data on this.
To configure a streaming proxy you have to configure it on the server side and add the Providers to your client side connection (this should be done automatically on the latest asmack library).
I configured such a setup using the ejabberd service and mod_proxy65 Mod_proxy65
add to your /etc/ejabberd/ejabberd.yml, your ip adress and host should match the service you are using.
host: "proxy1.example.org"
name: "File Transfer Proxy"
ip: "200.150.100.1"
port: 7777
Then allow the firewall to communicate on this port, and you should be good to go.
However, I figured out, that the file transfer only is functional, if both clients are in the presence "available" if any of them is in another state, the sender receives "service- unavailabe" errors.
Don't rely on XMPP file transfer using aSmack/Smack as it is buly. Instead you can create a plugin which will upload files to the server and after successfully uploading the files send the absolute url of the file to the recipient so that he/she can download the file from there.
Hope this works !
I'm wondering if anybody can help me figure out what is causing the data I am sending to become corrupt.
My setup is currently an Arduino pro mini with a HM-10 bluetooth module connected (I have also tried HM-11 Module too) and an Android application to receive the bluetooth data.
Module setup: http://letsmakerobots.com/node/38009
If I send data with big enough intervals then the data is fine, but if I send the data continuously I see messages getting mixed up and lost. To test this I send "$0.1,0.2,0.3,0.4,0.5" to the Android application from the Arduino, sometimes the stream of data appears to send fine but other times it is really quite scrambled. Please see the below graphs that demonstrate this:
Good case:
Bad case:
Arduino code:
String inputString = ""; //Hold the incoming data.
boolean stringComplete = false; //Determines if the string is complete.
boolean realtime = false;
void setup()
{
Serial.begin(9600);
delay(500);
Serial.print("AT+START");
delay(500);
}
void loop()
{
if(stringComplete)
{
if(inputString.equals("rStart"))
{
Serial.println("$startACK");
realtime = true;
}
else if(inputString.equals("stop"))
{
Serial.println("$stopACK");
realtime = false;
}
else{
Serial.print(inputString);
}
inputString = "";
stringComplete = false;
}
if(realtime)
{
Serial.println("$0.1,0.2,0.3,0.4,0.5,0.6");
delay(10);
}
}
void serialEvent() {
while (Serial.available())
{
// get the new byte:
char inChar = (char)Serial.read();
if (inChar == '\n')
{
stringComplete = true;
}
else
{
inputString += inChar;
}
}
}
The Android side just receives the data and then parses it in an IntentService:
#Override
protected void onHandleIntent(Intent intent) {
//Incoming command.
String rawData = intent.getStringExtra(DataProcessingIntentService.REQUEST);
//Append our new data to our data helper.
Log.i(this.getClass().getName(), "Previous Raw: (" + DataProcessingHelper.getInstance().getData() + ")");
DataProcessingHelper.getInstance().appendData(rawData);
Log.i(this.getClass().getName(), "New Raw: (" + DataProcessingHelper.getInstance().getData() + ")");
commandStartIndex = DataProcessingHelper.getInstance().getData().indexOf("$");
commandEndIndex = DataProcessingHelper.getInstance().getData().indexOf("\n");
//Set this as the data starting point.
if(commandStartIndex != -1){
DataProcessingHelper.getInstance().offsetData(commandStartIndex);
}
//Ensure that a command has been found and that the end index is after the starting index.
if(commandStartIndex != -1 && commandEndIndex > commandStartIndex){
//Remove the command structure from the command.
command = DataProcessingHelper.getInstance().getData().substring(commandStartIndex+1, commandEndIndex-1); //Remove the \r\n end command.
DataProcessingHelper.getInstance().offsetData(commandEndIndex+1);
if(command.length() > 1){
//Split the data out of the comand.
splitData = command.split(",");
Log.i(this.getClass().getName(), "Broadcasting the processed data. (" + command + ")");
//Broadcast data.
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(DataProcessingIntentService.RESPONSE);
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
broadcastIntent.putExtra(DataProcessingIntentService.RESPONSE, splitData);
sendBroadcast(broadcastIntent);
}else{
Log.e(this.getClass().getName(), "Command is less than 1 character long!");
}
}
}
Thank you for any help!
I have now figured out what was causing this problem. It appears that BLE only supports a maximum of 20 bytes per a transaction. The time between these transactions is different depending on what you are using. I'm currently using notifications which means that I can send 20 bytes every 7.5 milliseconds maximum. I have opted for 10 milliseconds to be safe. I will now need to look into breaking up packets into 20 bytes maximum to ensure no data corruption.