├── .gitignore ├── ESP32CSISerial.iml ├── README.md ├── build.gradle └── src └── main ├── AndroidManifest.xml └── java └── com └── stevenmhernandez └── esp32csiserial ├── BaseDataCollectorService.java ├── CSIDataInterface.java ├── ESP32CSISerial.java ├── FileDataCollectorService.java └── UsbService.java /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ESP32CSISerial.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 CSI Serial (for Android) 2 | 3 | This Android library allows your own standard Android application to collect Wi-Fi Channel State Information (CSI). This library should work on any standard installed Android apps on most modern Android devices; no custom Android firmware updates are required. 4 | 5 | ## Requirements 6 | 7 | ESP32 microcontroller programmed with the [ESP32 CSI Toolkit](https://stevenmhernandez.github.io/ESP32-CSI-Tool/). 8 | 9 | USB OTG (On-the-go) Cable (typically micro-usb to micro-usb depending on Android device and ESP32 selected). 10 | 11 | This library allows the app to listen for data on USB OTG from an ESP32 microcontroller running [ESP32 CSI Toolkit](https://stevenmhernandez.github.io/ESP32-CSI-Tool/). CSI data is automatically parsed and returned to your app for further custom processing. 12 | 13 | ## Setup Your Custom Android Application 14 | 15 | `git clone https://github.com/StevenMHernandez/Android-ESP32-CSI.git ESP32CSISerial` 16 | 17 | `File > New > Import Module > {Select cloned ESP32CSISerial directory}` 18 | 19 | In your project perform the following tasks: 20 | 21 | ### `build.gradle` 22 | 23 | ``` 24 | allprojects { 25 | repositories { 26 | ... 27 | maven { url "https://jitpack.io" } // <- Add this line 28 | } 29 | } 30 | ``` 31 | 32 | ### `app/build.gradle` 33 | 34 | ``` 35 | android { 36 | ... 37 | compileOptions { 38 | encoding "UTF-8" 39 | sourceCompatibility JavaVersion.VERSION_1_8 40 | targetCompatibility JavaVersion.VERSION_1_8 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation project(path: ':ESP32CSISerial') 46 | } 47 | ``` 48 | 49 | ### `AndroidManifest.xml` 50 | ``` 51 | 52 | 53 | // <- Add this 54 | // <- Add this 55 | // <- Add this 56 | // <- Add this 57 | 58 | . . . 59 | 60 | 61 | . . . 62 | // <- Add this 63 | 64 | 65 | ``` 66 | 67 | ### `settings.gradle` 68 | 69 | ``` 70 | include ':app', ':ESP32CSISerial' 71 | ``` 72 | 73 | ### Attach to an activity 74 | 75 | In your MainActivity (or any activity you choose) 76 | 77 | ``` 78 | private ESP32CSISerial csiSerial = new ESP32CSISerial(); 79 | 80 | @Override 81 | protected void onCreate(Bundle savedInstanceState) { 82 | . . . 83 | csiSerial.setup(this, "your_project_name"); 84 | csiSerial.onCreate(this); 85 | } 86 | 87 | @Override 88 | protected void onResume() { 89 | . . . 90 | csiSerial.onResume(this); 91 | } 92 | 93 | @Override 94 | protected void onPause() { 95 | . . . 96 | csiSerial.onPause(this); 97 | } 98 | ``` 99 | 100 | 101 | add the interface to your activity: 102 | 103 | ``` 104 | 105 | public class MainActivity extends AppCompatActivity implements CSIDataInterface { 106 | 107 | . . . 108 | 109 | int csiCounter = 0; 110 | @Override 111 | public void addCsi(String csi_string) { 112 | csiCounter++; 113 | homeFragment.homeViewModel.setText(String.valueOf(csiCounter)); 114 | } 115 | } 116 | ``` 117 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | 4 | android { 5 | compileSdkVersion 29 6 | defaultConfig { 7 | minSdkVersion 19 8 | targetSdkVersion 29 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | encoding "UTF-8" 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | } 25 | 26 | allprojects { 27 | repositories { 28 | maven { url "https://jitpack.io" } 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | implementation 'com.github.felHR85:UsbSerial:6.0.6' 35 | } 36 | 37 | sourceCompatibility = "7" 38 | targetCompatibility = "7" 39 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /src/main/java/com/stevenmhernandez/esp32csiserial/BaseDataCollectorService.java: -------------------------------------------------------------------------------- 1 | package com.stevenmhernandez.esp32csiserial; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * This abstract base class allows us to very easily create new methods to store our data. 7 | * For example, if you wish to simple send an HTTP PUT request to a server, create a 8 | * subclass of this abstract class and implement the following methods. 9 | */ 10 | public abstract class BaseDataCollectorService { 11 | /** 12 | * Do any required setup here (i.e. initiate connection to a server or creating a file) 13 | * 14 | * @param context application context if required. 15 | */ 16 | public abstract void setup(Context context); 17 | 18 | /** 19 | * Handle new data (i.e. HTTP PUT request or append to a local file) 20 | * 21 | * @param csi string data direct from the ESP32 serial monitor 22 | */ 23 | public abstract void handle(String csi); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/stevenmhernandez/esp32csiserial/CSIDataInterface.java: -------------------------------------------------------------------------------- 1 | package com.stevenmhernandez.esp32csiserial; 2 | 3 | public interface CSIDataInterface { 4 | public void addCsi(String csi_string); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/stevenmhernandez/esp32csiserial/ESP32CSISerial.java: -------------------------------------------------------------------------------- 1 | package com.stevenmhernandez.esp32csiserial; 2 | 3 | 4 | import android.app.Activity; 5 | import android.content.BroadcastReceiver; 6 | import android.content.ComponentName; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.IntentFilter; 10 | import android.content.ServiceConnection; 11 | import android.os.Bundle; 12 | import android.os.Handler; 13 | import android.os.IBinder; 14 | import android.os.Message; 15 | import android.provider.Settings; 16 | import android.util.Log; 17 | import android.widget.Toast; 18 | 19 | import java.lang.ref.WeakReference; 20 | import java.util.Set; 21 | 22 | public class ESP32CSISerial { 23 | 24 | private final Handler handler = new Handler(); 25 | private CSIDataInterface listener; 26 | private BaseDataCollectorService csiRecorderService = new FileDataCollectorService(); 27 | private String experimentName = null; 28 | private Activity activity; 29 | 30 | public void setup(CSIDataInterface listener, String experimentName) { 31 | this.listener = listener; 32 | this.experimentName = experimentName; 33 | } 34 | 35 | public void onCreate(Activity activity) { 36 | this.activity = activity; 37 | csiRecorderService.setup(activity); 38 | 39 | mHandler = new USBHandler(activity, csiRecorderService, experimentName, listener); 40 | 41 | handler.postDelayed(new Runnable() { 42 | public void run() { 43 | if (usbService != null) { 44 | String setDataString = String.format("SETTIME: %d\n", (int) (System.currentTimeMillis() / 1000)); 45 | usbService.write(setDataString.getBytes()); 46 | } 47 | handler.postDelayed(this, 120000 / 2); 48 | } 49 | }, 10000); //Every 120000 ms (2 minutes) 50 | } 51 | 52 | public void onResume(Activity activity) { 53 | setFilters(); // Start listening notifications from UsbService 54 | startService(UsbService.class, usbConnection, null); // Start UsbService(if it was not started before) and Bind it 55 | } 56 | 57 | public void onPause(Activity activity) { 58 | activity.unregisterReceiver(mUsbReceiver); 59 | activity.unbindService(usbConnection); 60 | } 61 | 62 | public double[] parseCsi(String csi, boolean returnPhases) { 63 | String[] strs = csi.trim().split(" "); 64 | try { 65 | int[] im_ints = new int[strs.length / 2]; 66 | int[] real_ints = new int[strs.length / 2]; 67 | double[] amplitudes = new double[strs.length / 2]; 68 | double[] phases = new double[strs.length / 2]; 69 | 70 | for (int i = 0; i < strs.length; i++) { 71 | if (i % 2 == 0) { 72 | im_ints[(int) Math.floor(i / 2.0)] = Integer.parseInt(strs[i]); 73 | } else { 74 | real_ints[(int) Math.floor(i / 2.0)] = Integer.parseInt(strs[i]); 75 | } 76 | } 77 | 78 | for (int i = 0; i < strs.length / 2; i++) { 79 | amplitudes[i] = Math.sqrt(im_ints[i] ^ 2 + real_ints[i] ^ 2); 80 | phases[i] = Math.atan2(im_ints[i], real_ints[i]); 81 | } 82 | 83 | if (returnPhases) { 84 | return phases; 85 | } else { 86 | return amplitudes; 87 | } 88 | } catch (Exception e) { 89 | Log.e("Parse CSI String", csi); 90 | Log.e("ERROR:", e.getLocalizedMessage()); 91 | } 92 | 93 | return null; 94 | } 95 | 96 | /* 97 | * This handler will be passed to UsbService. Data received from serial port is displayed through this handler 98 | */ 99 | private static class USBHandler extends Handler { 100 | private final WeakReference mActivity; 101 | String experimentName; 102 | CSIDataInterface listener; 103 | BaseDataCollectorService dataService; 104 | public String buffer = ""; 105 | 106 | public USBHandler(Activity activity, BaseDataCollectorService dataService, String experimentName, CSIDataInterface listener) { 107 | mActivity = new WeakReference<>(activity); 108 | this.experimentName = experimentName; 109 | this.dataService = dataService; 110 | this.listener = listener; 111 | } 112 | 113 | public String updateCsiString(Activity activity, String csi) { 114 | String deviceId = Settings.Secure.getString(activity.getContentResolver(), Settings.Secure.ANDROID_ID); 115 | return String.format("%s,%s,%s,%s\n", csi.trim(), deviceId, System.currentTimeMillis(), experimentName); 116 | } 117 | 118 | @Override 119 | public void handleMessage(Message msg) { 120 | switch (msg.what) { 121 | case UsbService.MESSAGE_FROM_SERIAL_PORT: 122 | String data = (String) msg.obj; 123 | 124 | if (data.contains("\r\n")) { 125 | String[] split = data.split("\r\n"); 126 | 127 | String line; 128 | if (split.length > 0) { 129 | line = buffer + split[0]; 130 | } else { 131 | line = buffer; 132 | } 133 | 134 | // Reset Buffer 135 | if (split.length > 1) { 136 | buffer = split[1]; 137 | } else { 138 | buffer = ""; 139 | } 140 | 141 | if (line.contains("CSI_DATA")) { 142 | dataService.handle(updateCsiString(mActivity.get(), line)); 143 | listener.addCsi(line); 144 | } 145 | } else { 146 | buffer += data; 147 | } 148 | 149 | break; 150 | case UsbService.CTS_CHANGE: 151 | Toast.makeText(mActivity.get(), "CTS_CHANGE", Toast.LENGTH_LONG).show(); 152 | break; 153 | case UsbService.DSR_CHANGE: 154 | Toast.makeText(mActivity.get(), "DSR_CHANGE", Toast.LENGTH_LONG).show(); 155 | break; 156 | } 157 | } 158 | } 159 | 160 | 161 | /** 162 | * @See: https://github.com/felHR85/UsbSerial/blob/master/example/src/main/java/com/felhr/serialportexample/MainActivity.java 163 | */ 164 | private UsbService usbService; 165 | private USBHandler mHandler; 166 | 167 | /* 168 | * Notifications from UsbService will be received here. 169 | */ 170 | private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { 171 | @Override 172 | public void onReceive(Context context, Intent intent) { 173 | switch (intent.getAction()) { 174 | case UsbService.ACTION_USB_PERMISSION_GRANTED: // USB PERMISSION GRANTED 175 | Toast.makeText(context, "USB Ready", Toast.LENGTH_SHORT).show(); 176 | break; 177 | case UsbService.ACTION_USB_PERMISSION_NOT_GRANTED: // USB PERMISSION NOT GRANTED 178 | Toast.makeText(context, "USB Permission not granted", Toast.LENGTH_SHORT).show(); 179 | break; 180 | case UsbService.ACTION_NO_USB: // NO USB CONNECTED 181 | Toast.makeText(context, "No USB connected", Toast.LENGTH_SHORT).show(); 182 | break; 183 | case UsbService.ACTION_USB_DISCONNECTED: // USB DISCONNECTED 184 | Toast.makeText(context, "USB disconnected", Toast.LENGTH_SHORT).show(); 185 | break; 186 | case UsbService.ACTION_USB_NOT_SUPPORTED: // USB NOT SUPPORTED 187 | Toast.makeText(context, "USB device not supported", Toast.LENGTH_SHORT).show(); 188 | break; 189 | } 190 | } 191 | }; 192 | 193 | private final ServiceConnection usbConnection = new ServiceConnection() { 194 | @Override 195 | public void onServiceConnected(ComponentName arg0, IBinder arg1) { 196 | usbService = ((UsbService.UsbBinder) arg1).getService(); 197 | usbService.setHandler(mHandler); 198 | } 199 | 200 | @Override 201 | public void onServiceDisconnected(ComponentName arg0) { 202 | usbService = null; 203 | } 204 | }; 205 | 206 | private void startService(Class service, ServiceConnection serviceConnection, Bundle extras) { 207 | if (!UsbService.SERVICE_CONNECTED) { 208 | Intent startServiceIntent = new Intent(this.activity, service); 209 | if (extras != null && !extras.isEmpty()) { 210 | Set keys = extras.keySet(); 211 | for (String key : keys) { 212 | String extra = extras.getString(key); 213 | startServiceIntent.putExtra(key, extra); 214 | } 215 | } 216 | activity.startService(startServiceIntent); 217 | } 218 | Intent bindingIntent = new Intent(this.activity, service); 219 | this.activity.bindService(bindingIntent, serviceConnection, Context.BIND_AUTO_CREATE); 220 | } 221 | 222 | private void setFilters() { 223 | IntentFilter filter = new IntentFilter(); 224 | filter.addAction(UsbService.ACTION_USB_PERMISSION_GRANTED); 225 | filter.addAction(UsbService.ACTION_NO_USB); 226 | filter.addAction(UsbService.ACTION_USB_DISCONNECTED); 227 | filter.addAction(UsbService.ACTION_USB_NOT_SUPPORTED); 228 | filter.addAction(UsbService.ACTION_USB_PERMISSION_NOT_GRANTED); 229 | this.activity.registerReceiver(mUsbReceiver, filter); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/main/java/com/stevenmhernandez/esp32csiserial/FileDataCollectorService.java: -------------------------------------------------------------------------------- 1 | package com.stevenmhernandez.esp32csiserial; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import java.io.File; 7 | import java.io.FileNotFoundException; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | 11 | 12 | public class FileDataCollectorService extends BaseDataCollectorService { 13 | private String LOG_TAG = "FileDataCollectorService"; 14 | private FileOutputStream localBackup = null; 15 | public String filePrefix; 16 | public String fileType; 17 | 18 | public FileDataCollectorService() { 19 | this.filePrefix = "backup"; 20 | this.fileType = "csv"; 21 | } 22 | 23 | public FileDataCollectorService(String filePrefix, String fileType) { 24 | this.filePrefix = filePrefix; 25 | this.fileType = fileType; 26 | } 27 | 28 | public void setup(Context context) { 29 | try { 30 | File test = new File(context.getExternalFilesDir(null), filePrefix + System.currentTimeMillis() + "." + fileType); 31 | localBackup = new FileOutputStream(test, true); 32 | } catch (FileNotFoundException e) { 33 | e.printStackTrace(); 34 | Log.w(LOG_TAG, "FileOutputStream exception: - " + e.toString()); 35 | } 36 | 37 | } 38 | 39 | public void handle(String csi) { 40 | try { 41 | if (localBackup != null) { 42 | localBackup.write(csi.getBytes()); 43 | localBackup.flush(); 44 | } 45 | } catch (IOException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/stevenmhernandez/esp32csiserial/UsbService.java: -------------------------------------------------------------------------------- 1 | package com.stevenmhernandez.esp32csiserial; 2 | 3 | import android.app.PendingIntent; 4 | import android.app.Service; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.hardware.usb.UsbDevice; 10 | import android.hardware.usb.UsbDeviceConnection; 11 | import android.hardware.usb.UsbManager; 12 | import android.os.Binder; 13 | import android.os.Handler; 14 | import android.os.IBinder; 15 | import android.util.Log; 16 | 17 | import com.felhr.usbserial.CDCSerialDevice; 18 | import com.felhr.usbserial.UsbSerialDevice; 19 | import com.felhr.usbserial.UsbSerialInterface; 20 | 21 | import java.io.UnsupportedEncodingException; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | // @See: https://github.com/felHR85/UsbSerial/blob/master/example/src/main/java/com/felhr/serialportexample/UsbService.java 26 | public class UsbService extends Service { 27 | 28 | public static final String TAG = "UsbService"; 29 | 30 | public static final String ACTION_USB_READY = "com.felhr.connectivityservices.USB_READY"; 31 | public static final String ACTION_USB_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"; 32 | public static final String ACTION_USB_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED"; 33 | public static final String ACTION_USB_NOT_SUPPORTED = "com.felhr.usbservice.USB_NOT_SUPPORTED"; 34 | public static final String ACTION_NO_USB = "com.felhr.usbservice.NO_USB"; 35 | public static final String ACTION_USB_PERMISSION_GRANTED = "com.felhr.usbservice.USB_PERMISSION_GRANTED"; 36 | public static final String ACTION_USB_PERMISSION_NOT_GRANTED = "com.felhr.usbservice.USB_PERMISSION_NOT_GRANTED"; 37 | public static final String ACTION_USB_DISCONNECTED = "com.felhr.usbservice.USB_DISCONNECTED"; 38 | public static final String ACTION_CDC_DRIVER_NOT_WORKING = "com.felhr.connectivityservices.ACTION_CDC_DRIVER_NOT_WORKING"; 39 | public static final String ACTION_USB_DEVICE_NOT_WORKING = "com.felhr.connectivityservices.ACTION_USB_DEVICE_NOT_WORKING"; 40 | public static final int MESSAGE_FROM_SERIAL_PORT = 0; 41 | public static final int CTS_CHANGE = 1; 42 | public static final int DSR_CHANGE = 2; 43 | private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; 44 | private static final int BAUD_RATE = 1552000; // BaudRate. Change this value if you need 45 | public static boolean SERVICE_CONNECTED = false; 46 | 47 | private IBinder binder = new UsbBinder(); 48 | 49 | private Context context; 50 | private Handler mHandler; 51 | private UsbManager usbManager; 52 | private UsbDevice device; 53 | private UsbDeviceConnection connection; 54 | private UsbSerialDevice serialPort; 55 | 56 | private boolean serialPortConnected; 57 | /* 58 | * Data received from serial port will be received here. Just populate onReceivedData with your code 59 | * In this particular example. byte stream is converted to String and send to UI thread to 60 | * be treated there. 61 | */ 62 | private UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() { 63 | @Override 64 | public void onReceivedData(byte[] arg0) { 65 | try { 66 | String data = new String(arg0, "UTF-8"); 67 | if (mHandler != null) 68 | mHandler.obtainMessage(MESSAGE_FROM_SERIAL_PORT, data).sendToTarget(); 69 | } catch (UnsupportedEncodingException e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | }; 74 | 75 | /* 76 | * State changes in the CTS line will be received here 77 | */ 78 | private UsbSerialInterface.UsbCTSCallback ctsCallback = new UsbSerialInterface.UsbCTSCallback() { 79 | @Override 80 | public void onCTSChanged(boolean state) { 81 | if(mHandler != null) 82 | mHandler.obtainMessage(CTS_CHANGE).sendToTarget(); 83 | } 84 | }; 85 | 86 | /* 87 | * State changes in the DSR line will be received here 88 | */ 89 | private UsbSerialInterface.UsbDSRCallback dsrCallback = new UsbSerialInterface.UsbDSRCallback() { 90 | @Override 91 | public void onDSRChanged(boolean state) { 92 | if(mHandler != null) 93 | mHandler.obtainMessage(DSR_CHANGE).sendToTarget(); 94 | } 95 | }; 96 | /* 97 | * Different notifications from OS will be received here (USB attached, detached, permission responses...) 98 | * About BroadcastReceiver: http://developer.android.com/reference/android/content/BroadcastReceiver.html 99 | */ 100 | private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { 101 | @Override 102 | public void onReceive(Context arg0, Intent arg1) { 103 | if (arg1.getAction().equals(ACTION_USB_PERMISSION)) { 104 | boolean granted = arg1.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED); 105 | if (granted) // User accepted our USB connection. Try to open the device as a serial port 106 | { 107 | Intent intent = new Intent(ACTION_USB_PERMISSION_GRANTED); 108 | arg0.sendBroadcast(intent); 109 | connection = usbManager.openDevice(device); 110 | new ConnectionThread().start(); 111 | } else // User not accepted our USB connection. Send an Intent to the Main Activity 112 | { 113 | Intent intent = new Intent(ACTION_USB_PERMISSION_NOT_GRANTED); 114 | arg0.sendBroadcast(intent); 115 | } 116 | } else if (arg1.getAction().equals(ACTION_USB_ATTACHED)) { 117 | if (!serialPortConnected) 118 | findSerialPortDevice(); // A USB device has been attached. Try to open it as a Serial port 119 | } else if (arg1.getAction().equals(ACTION_USB_DETACHED)) { 120 | // Usb device was disconnected. send an intent to the Main Activity 121 | Intent intent = new Intent(ACTION_USB_DISCONNECTED); 122 | arg0.sendBroadcast(intent); 123 | if (serialPortConnected) { 124 | serialPort.close(); 125 | } 126 | serialPortConnected = false; 127 | } 128 | } 129 | }; 130 | 131 | /* 132 | * onCreate will be executed when service is started. It configures an IntentFilter to listen for 133 | * incoming Intents (USB ATTACHED, USB DETACHED...) and it tries to open a serial port. 134 | */ 135 | @Override 136 | public void onCreate() { 137 | this.context = this; 138 | serialPortConnected = false; 139 | UsbService.SERVICE_CONNECTED = true; 140 | setFilter(); 141 | usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); 142 | findSerialPortDevice(); 143 | } 144 | 145 | /* MUST READ about services 146 | * http://developer.android.com/guide/components/services.html 147 | * http://developer.android.com/guide/components/bound-services.html 148 | */ 149 | @Override 150 | public IBinder onBind(Intent intent) { 151 | return binder; 152 | } 153 | 154 | @Override 155 | public int onStartCommand(Intent intent, int flags, int startId) { 156 | return Service.START_NOT_STICKY; 157 | } 158 | 159 | @Override 160 | public void onDestroy() { 161 | super.onDestroy(); 162 | serialPort.close(); 163 | unregisterReceiver(usbReceiver); 164 | UsbService.SERVICE_CONNECTED = false; 165 | } 166 | 167 | /* 168 | * This function will be called from MainActivity to write data through Serial Port 169 | */ 170 | public void write(byte[] data) { 171 | if (serialPort != null) 172 | serialPort.write(data); 173 | } 174 | 175 | public void setHandler(Handler mHandler) { 176 | this.mHandler = mHandler; 177 | } 178 | 179 | private void findSerialPortDevice() { 180 | // This snippet will try to open the first encountered usb device connected, excluding usb root hubs 181 | HashMap usbDevices = usbManager.getDeviceList(); 182 | if (!usbDevices.isEmpty()) { 183 | 184 | // first, dump the hashmap for diagnostic purposes 185 | for (Map.Entry entry : usbDevices.entrySet()) { 186 | device = entry.getValue(); 187 | Log.d(TAG, String.format("USBDevice.HashMap (vid:pid) (%X:%X)-%b class:%X:%X name:%s", 188 | device.getVendorId(), device.getProductId(), 189 | UsbSerialDevice.isSupported(device), 190 | device.getDeviceClass(), device.getDeviceSubclass(), 191 | device.getDeviceName())); 192 | } 193 | 194 | for (Map.Entry entry : usbDevices.entrySet()) { 195 | device = entry.getValue(); 196 | int deviceVID = device.getVendorId(); 197 | int devicePID = device.getProductId(); 198 | 199 | // if (deviceVID != 0x1d6b && (devicePID != 0x0001 && devicePID != 0x0002 && devicePID != 0x0003) && deviceVID != 0x5c6 && devicePID != 0x904c) { 200 | if (UsbSerialDevice.isSupported(device)) { 201 | // There is a supported device connected - request permission to access it. 202 | requestUserPermission(); 203 | break; 204 | } else { 205 | connection = null; 206 | device = null; 207 | } 208 | } 209 | if (device==null) { 210 | // There are no USB devices connected (but usb host were listed). Send an intent to MainActivity. 211 | Intent intent = new Intent(ACTION_NO_USB); 212 | sendBroadcast(intent); 213 | } 214 | } else { 215 | Log.d(TAG, "findSerialPortDevice() usbManager returned empty device list." ); 216 | // There is no USB devices connected. Send an intent to MainActivity 217 | Intent intent = new Intent(ACTION_NO_USB); 218 | sendBroadcast(intent); 219 | } 220 | } 221 | 222 | private void setFilter() { 223 | IntentFilter filter = new IntentFilter(); 224 | filter.addAction(ACTION_USB_PERMISSION); 225 | filter.addAction(ACTION_USB_DETACHED); 226 | filter.addAction(ACTION_USB_ATTACHED); 227 | registerReceiver(usbReceiver, filter); 228 | } 229 | 230 | /* 231 | * Request user permission. The response will be received in the BroadcastReceiver 232 | */ 233 | private void requestUserPermission() { 234 | Log.d(TAG, String.format("requestUserPermission(%X:%X)", device.getVendorId(), device.getProductId() ) ); 235 | PendingIntent mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); 236 | usbManager.requestPermission(device, mPendingIntent); 237 | } 238 | 239 | public class UsbBinder extends Binder { 240 | public UsbService getService() { 241 | return UsbService.this; 242 | } 243 | } 244 | 245 | /* 246 | * A simple thread to open a serial port. 247 | * Although it should be a fast operation. moving usb operations away from UI thread is a good thing. 248 | */ 249 | private class ConnectionThread extends Thread { 250 | @Override 251 | public void run() { 252 | serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection); 253 | if (serialPort != null) { 254 | if (serialPort.open()) { 255 | serialPortConnected = true; 256 | serialPort.setBaudRate(BAUD_RATE); 257 | serialPort.setDataBits(UsbSerialInterface.DATA_BITS_8); 258 | serialPort.setStopBits(UsbSerialInterface.STOP_BITS_1); 259 | serialPort.setParity(UsbSerialInterface.PARITY_NONE); 260 | /** 261 | * Current flow control Options: 262 | * UsbSerialInterface.FLOW_CONTROL_OFF 263 | * UsbSerialInterface.FLOW_CONTROL_RTS_CTS only for CP2102 and FT232 264 | * UsbSerialInterface.FLOW_CONTROL_DSR_DTR only for CP2102 and FT232 265 | */ 266 | serialPort.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF); 267 | serialPort.read(mCallback); 268 | serialPort.getCTS(ctsCallback); 269 | serialPort.getDSR(dsrCallback); 270 | 271 | // 272 | // Some Arduinos would need some sleep because firmware wait some time to know whether a new sketch is going 273 | // to be uploaded or not 274 | //Thread.sleep(2000); // sleep some. YMMV with different chips. 275 | 276 | // Everything went as expected. Send an intent to MainActivity 277 | Intent intent = new Intent(ACTION_USB_READY); 278 | context.sendBroadcast(intent); 279 | } else { 280 | // Serial port could not be opened, maybe an I/O error or if CDC driver was chosen, it does not really fit 281 | // Send an Intent to Main Activity 282 | if (serialPort instanceof CDCSerialDevice) { 283 | Intent intent = new Intent(ACTION_CDC_DRIVER_NOT_WORKING); 284 | context.sendBroadcast(intent); 285 | } else { 286 | Intent intent = new Intent(ACTION_USB_DEVICE_NOT_WORKING); 287 | context.sendBroadcast(intent); 288 | } 289 | } 290 | } else { 291 | // No driver for given device, even generic CDC driver could not be loaded 292 | Intent intent = new Intent(ACTION_USB_NOT_SUPPORTED); 293 | context.sendBroadcast(intent); 294 | } 295 | } 296 | } 297 | } 298 | --------------------------------------------------------------------------------