├── .gitignore ├── README.md ├── app-debug.apk ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sjl │ │ └── deviceconnector │ │ └── test │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── sjl │ │ │ └── deviceconnector │ │ │ └── test │ │ │ ├── activity │ │ │ ├── BaseActivity.java │ │ │ ├── BluetoothListActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── SerialPortListActivity.java │ │ │ ├── SerialPortSettingActivity.java │ │ │ ├── UsbListActivity.java │ │ │ └── WifiSettingActivity.java │ │ │ ├── adapter │ │ │ ├── BluetoothLeDataAdapter.java │ │ │ ├── BluetoothListAdapter.java │ │ │ ├── SerialPortListAdapter.java │ │ │ └── UsbListAdapter.java │ │ │ ├── app │ │ │ ├── AppConstant.java │ │ │ └── MyApplication.java │ │ │ ├── entity │ │ │ ├── ConnectWay.java │ │ │ ├── MessageEvent.java │ │ │ ├── SerialPortInfo.java │ │ │ └── WifiInfo.java │ │ │ ├── service │ │ │ ├── BleService.java │ │ │ ├── BluetoothService.java │ │ │ ├── ReadThread.java │ │ │ └── WifiService.java │ │ │ ├── util │ │ │ ├── MessageEventUtils.java │ │ │ ├── SPUtils.java │ │ │ ├── SpSettingUtils.java │ │ │ └── TUtils.java │ │ │ └── widget │ │ │ └── LoadingDialog.java │ └── res │ │ ├── drawable-xxhdpi │ │ └── ic_close_cancel.png │ │ ├── drawable │ │ ├── half_black_round_12dp_bg.xml │ │ ├── ic_send_white_24dp.xml │ │ ├── list_item_bg_white.xml │ │ └── white_round_8dp_bg.xml │ │ ├── layout │ │ ├── ble_data_dialog.xml │ │ ├── ble_data_recycle_item.xml │ │ ├── bluetooth_list_activity.xml │ │ ├── bluetooth_list_recycle_item.xml │ │ ├── load_dialog.xml │ │ ├── main_activity.xml │ │ ├── serial_port_list_activity.xml │ │ ├── serial_port_list_recycle_item.xml │ │ ├── serial_port_setting_activity.xml │ │ ├── usb_list_activity.xml │ │ ├── usb_list_recycle_item.xml │ │ └── wifi_setting_activity.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── file_paths.xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── com │ └── sjl │ └── deviceconnector │ └── test │ └── ExampleUnitTest.java ├── build.gradle ├── config.gradle ├── deviceconnector ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sjl │ │ └── deviceconnector │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── sjl │ │ │ └── deviceconnector │ │ │ ├── DeviceContext.java │ │ │ ├── ErrorCode.java │ │ │ ├── Waiter.java │ │ │ ├── device │ │ │ ├── bluetooth │ │ │ │ ├── BluetoothHelper.java │ │ │ │ ├── ble │ │ │ │ │ ├── AdStructureParser.java │ │ │ │ │ ├── BluetoothGattWrap.java │ │ │ │ │ ├── BluetoothLeClient.java │ │ │ │ │ ├── BluetoothLeNotifyListener.java │ │ │ │ │ ├── BluetoothLeServiceListener.java │ │ │ │ │ ├── IBluetoothLeClient.java │ │ │ │ │ ├── request │ │ │ │ │ │ ├── BluetoothLeRequest.java │ │ │ │ │ │ ├── CharacteristicReadRequest.java │ │ │ │ │ │ ├── CharacteristicWriteRequest.java │ │ │ │ │ │ ├── DescriptorReadRequest.java │ │ │ │ │ │ ├── DescriptorWriteRequest.java │ │ │ │ │ │ ├── IndicateRequest.java │ │ │ │ │ │ ├── MtuRequest.java │ │ │ │ │ │ ├── NotifyRequest.java │ │ │ │ │ │ └── RemoteRssiRequest.java │ │ │ │ │ └── response │ │ │ │ │ │ └── BluetoothLeResponse.java │ │ │ │ └── scanner │ │ │ │ │ ├── AbstractBluetoothScanner.java │ │ │ │ │ ├── BluetoothClassicScanner.java │ │ │ │ │ ├── BluetoothLowEnergyScanner.java │ │ │ │ │ └── BluetoothScanner.java │ │ │ ├── serialport │ │ │ │ └── SerialPortHelper.java │ │ │ └── usb │ │ │ │ └── UsbHelper.java │ │ │ ├── entity │ │ │ ├── AdStructure.java │ │ │ ├── BluetoothScanResult.java │ │ │ └── SerialPortConfig.java │ │ │ ├── exception │ │ │ └── ProviderTimeoutException.java │ │ │ ├── listener │ │ │ ├── BluetoothScanListener.java │ │ │ ├── ConnectedListener.java │ │ │ ├── ReceiverObservable.java │ │ │ ├── UsbPermissionListener.java │ │ │ └── UsbPlugListener.java │ │ │ ├── provider │ │ │ ├── BaseConnectProvider.java │ │ │ ├── BaseIoConnectProvider.java │ │ │ ├── BluetoothConnectProvider.java │ │ │ ├── BluetoothLeConnectProvider.java │ │ │ ├── IConnectProvider.java │ │ │ ├── SerialPortConnectProvider.java │ │ │ ├── SocketConnectProvider.java │ │ │ ├── UsbComConnectProvider.java │ │ │ ├── UsbConnectProvider.java │ │ │ └── WifiConnectProvider.java │ │ │ └── util │ │ │ ├── BluetoothUtils.java │ │ │ ├── ByteUtils.java │ │ │ └── LogUtils.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── sjl │ └── deviceconnector │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot ├── 1.jpg ├── 10.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg └── 9.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /build 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeviceConnector 2 | 3 | Android 设备连接者,提供Android上位机和外设的连接通讯通道,支持多种连接方式选择,如串口、Usb Com、Usb、经典蓝牙、低功耗蓝牙、Wifi的连接, 4 | DeviceConnector框架上层调用一致,底层不同实现,方便使用者切换连接方式。为简化上层调用和方便数据处理, 全部连接采用同步操作,即一发一收(一问一答)的方式通讯 5 | 6 | **应用场景:** 7 | 8 | 基于硬件协议编写应用层硬件通讯(供应商没有提供SDK的情况),App示例可作为调试工具使用 9 | 10 | # 效果图 11 | 12 | [安装包下载](app-debug.apk) 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | # 依赖引入 22 | 23 | 步骤 1. 在工程跟目录build.gradle文件下添加仓库 24 | 25 | 26 | allprojects { 27 | repositories { 28 | ... 29 | maven { url 'https://jitpack.io' } 30 | } 31 | } 32 | 33 | 步骤2. 添加依赖 34 | 35 | dependencies { 36 | //1.1.0-RC2之前 37 | implementation 'com.github.kellysong:DeviceConnector:1.1.0-RC2' 38 | implementation 'com.github.mik3y:usb-serial-for-android:3.4.3' 39 | implementation 'com.github.licheedev:Android-SerialPort-API:2.0.0' 40 | implementation 'androidx.annotation:annotation:1.2.0' 41 | 42 | //1.1.0之后 43 | implementation 'com.github.kellysong:DeviceConnector:1.1.4' 44 | } 45 | 46 | # API调用 47 | 48 | 在Application onCreate()初始化 49 | 50 | 51 | DeviceContext.init(this,false); 52 | 53 | **1.初始化实例** 54 | 55 | //串口 56 | BaseConnectProvider baseConnectProvider = new SerialPortConnectProvider(SerialPortConfig serialPortConfig); 57 | 58 | //Usb Com 59 | BaseConnectProvider baseConnectProvider = new UsbComConnectProvider(int vendorId, int productId, SerialPortConfig serialPortConfig); 60 | //or 61 | BaseConnectProvider baseConnectProvider = new UsbComConnectProvider(UsbDevice usbDevice, SerialPortConfig serialPortConfig); 62 | 63 | //Usb 64 | BaseConnectProvider baseConnectProvider = new UsbConnectProvider(int vendorId, int productId); 65 | //or 66 | BaseConnectProvider baseConnectProvider = new UsbConnectProvider(UsbDevice usbDevice); 67 | //默认蓝牙 Com 68 | BaseConnectProvider baseConnectProvider = new BluetoothConnectProvider(BluetoothDevice bluetoothDevice); 69 | BaseConnectProvider baseConnectProvider = new BluetoothConnectProvider(String address); 70 | //指定蓝牙服务UUID 71 | BaseConnectProvider baseConnectProvider = new BluetoothConnectProvider(BluetoothDevice bluetoothDevice, String uuid); 72 | BaseConnectProvider baseConnectProvider = new BluetoothConnectProvider(String address, String uuid); 73 | 74 | //Wifi 75 | BaseConnectProvider baseConnectProvider = new WifiConnectProvider(String host, int port, int connectTimeout, int readTimeout); 76 | 77 | //特别地:蓝牙Ble 78 | BluetoothLeConnectProvider connectProvider = new BluetoothLeConnectProvider(BluetoothDevice bluetoothDevice); 79 | BluetoothLeConnectProvider connectProvider = new BluetoothLeConnectProvider(String address); 80 | 81 | **2.打开连接** 82 | 83 | baseConnectProvider.open(); 84 | 85 | **3.写和读数据** 86 | 87 | //只写 88 | baseConnectProvider.write(byte[] sendParams, int timeout); 89 | //只读 90 | baseConnectProvider.read(byte[] buffer, int timeout); 91 | //写和读(通用型api) 92 | baseConnectProvider.read(byte[] sendParams, byte[] buffer, int timeout); 93 | 94 | **3.1 蓝牙Ble通讯** 95 | 96 | 蓝牙Ble,与其它连接方式不好统一,故做特殊处理 97 | 98 | //特征写请求,其它请求创建不同的实例发送即可 99 | CharacteristicWriteRequest bluetoothLeRequest = new CharacteristicWriteRequest(); 100 | bluetoothLeRequest.setService(UUID_SERVICE); 101 | bluetoothLeRequest.setCharacter(UUID_CHARACTER_WRITE); 102 | bluetoothLeRequest.setBytes(sendData); 103 | BluetoothLeResponse response = new BluetoothLeResponse(); 104 | bluetoothLeRequest.sendRequest(bluetoothLeRequest,response,5*1000); 105 | 106 | //蓝牙ble监听服务端(也叫从机/外围设备/peripheral)数据,分两步操作 107 | 108 | //1.监听通知信息 109 | bluetoothLeConnectProvider.setBluetoothLeNotifyListener(new BluetoothLeNotifyListener() { 110 | @Override 111 | public void onNotify(UUID serviceId, UUID characterId, byte[] value) { 112 | 113 | } 114 | }); 115 | //2.开启通知 116 | NotifyRequest notifyRequest = new NotifyRequest(); 117 | notifyRequest.setService(UUID_SERVICE); 118 | notifyRequest.setCharacter(UUID_CHARACTER_READ); 119 | notifyRequest.setEnable(true); 120 | BluetoothLeResponse response = new BluetoothLeResponse(); 121 | bluetoothLeConnectProvider.sendRequest(notifyRequest,response,5*1000); 122 | //3. 修改mtu(子线程执行) 123 | MtuRequest mtuRequest = new MtuRequest(); 124 | mtuRequest.setMtu(240); 125 | //ble包的最大值,实际以返回的mtu作为参考值 126 | BluetoothLeResponse response = new BluetoothLeResponse(); 127 | bluetoothLeConnectProvider.mtuRequest(notifyRequest,response,5*1000); 128 | 129 | **4.关闭连接** 130 | 131 | baseConnectProvider.close(); 132 | 133 | # 混淆 134 | 135 | -keep class android.serialport.**{*;} 136 | -keep class * implements com.hoho.android.usbserial.driver.UsbSerialDriver { 137 | 138 | public static java.util.Map getSupportedDevices(); 139 | } 140 | 141 | # 注意事项 142 | 143 | 1. 检查设备支持的连接方式 144 | 2. 检查连接参数配置是否正确 145 | 3. 发送数据时,检查数据是否符合硬件协议要求,此外注意是否需要在报文数据尾部追加报文结束符/r(0x0A)或/n(0x0D)或/r/n(0x0A 0x0D)或其它校验码 146 | 4. 传统串口连接需要Root,免Root连接建议使用Usb Com 147 | 5. Usb Com和Usb连接之前需要先申请Usb权限,再调用open,不然出现首次连接失败;需要注意的是,正常申请一次权限就可以,但是Usb设备被拔出或者应用卸载了,连接之前需要再次申请权限 148 | 6. Usb连接传输模式、传输速度的不同,要注意发送数据包的大小,这里的包指一次传输数据的大小,包大小受限于端点的最大包大小 149 | 7. Usb Com和Usb连接如果的read()方法的逻辑需要改变或者需要一次发送,多次循环读取才能把数据读取完整的话,建议继承**连接提供者**,覆写read()方法进行一次写多次读;V1.1.0-RC2版本开始支持上层多次读,无须继承覆写read()方法 150 | 8. 蓝牙搜索在Android 6.0以上需要申请定位权限;高版本蓝牙连接会弹出蓝牙配对授权窗口,建议在蓝牙设置里面进行配对和取消配对,配对一次即可或者直接通过蓝牙地址直连; 151 | 9. Wifi连接需要网络权限 152 | 10. App示例中,使用手机就可以模拟测试Wifi连接和蓝牙连接,前提是先启动服务;串口和USB测试需要相应的设备连接 153 | 11. 已集成网络、蓝牙(包括ble)、Usb定位权限(权限申请由调用者申请) 154 | 12. 提高ble蓝牙连接成功率的建议:1.扫描到后连接 2.蓝牙断开再次连接,等待蓝牙断开连接回调后再次尝试 2.在连接失败或者断开连接之后,关闭之前进行缓存刷新(框架已经处理) 155 | 13. 针对有输入输出流的连接者(串口、Wifi、经典蓝牙),需要在外部处理,建议拿到输入流之后在外部启动线程监听,这时候不能使用read方法,需要调用者自己实现 156 | 157 | 158 | # License 159 | 160 | Copyright 2022 Song Jiali 161 | 162 | Licensed under the Apache License, Version 2.0 (the "License"); 163 | you may not use this file except in compliance with the License. 164 | You may obtain a copy of the License at 165 | 166 | http://www.apache.org/licenses/LICENSE-2.0 167 | 168 | Unless required by applicable law or agreed to in writing, software 169 | distributed under the License is distributed on an "AS IS" BASIS, 170 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 171 | See the License for the specific language governing permissions and 172 | limitations under the License. -------------------------------------------------------------------------------- /app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/app-debug.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion '29.0.3' 6 | 7 | defaultConfig { 8 | applicationId "com.sjl.deviceconnector.test" 9 | minSdkVersion 17 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0.0" 13 | multiDexEnabled true 14 | 15 | Properties properties = new Properties() 16 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 17 | def UUID_SERVICE = properties.getProperty('UUID_SERVICE') 18 | if (UUID_SERVICE == null){ 19 | buildConfigField "String", "uuid_service","\"0000fff1-0000-1000-8000-00805f9b34fb\"" 20 | }else { 21 | buildConfigField "String", "uuid_service", UUID_SERVICE 22 | } 23 | def UUID_CHARACTER_READ = properties.getProperty('UUID_CHARACTER_READ') 24 | if (UUID_CHARACTER_READ == null){ 25 | buildConfigField "String", "uuid_character_read","\"0000ff11-0000-1000-8000-00805f9b34fb\"" 26 | }else { 27 | buildConfigField "String", "uuid_character_read",UUID_CHARACTER_READ 28 | } 29 | def UUID_CHARACTER_WRITE = properties.getProperty('UUID_CHARACTER_WRITE') 30 | if (UUID_CHARACTER_WRITE == null){ 31 | buildConfigField "String", "uuid_character_write","\"0000ff12-0000-1000-8000-00805f9b34fb\"" 32 | }else { 33 | buildConfigField "String", "uuid_character_write", UUID_CHARACTER_WRITE 34 | } 35 | 36 | } 37 | buildTypes { 38 | release { 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 41 | } 42 | } 43 | viewBinding { 44 | enabled = true 45 | } 46 | compileOptions { 47 | sourceCompatibility JavaVersion.VERSION_1_8 48 | targetCompatibility JavaVersion.VERSION_1_8 49 | } 50 | } 51 | 52 | 53 | 54 | dependencies { 55 | implementation fileTree(include: ['*.jar'], dir: 'libs') 56 | testImplementation 'junit:junit:4.12' 57 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 58 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' 59 | implementation 'com.github.kellysong.my-common:base-core:2.3.4' 60 | implementation rootProject.ext.dependencies["autosize"] 61 | implementation rootProject.ext.dependencies["aviLoading"] 62 | implementation rootProject.ext.debugDependencies["leakcanary"] 63 | //设备连接库 64 | implementation project(':deviceconnector') 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in F:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/sjl/deviceconnector/test/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test; 2 | 3 | import android.content.Context; 4 | import androidx.test.platform.app.InstrumentationRegistry; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static junit.framework.TestCase.assertEquals; 11 | 12 | @RunWith(AndroidJUnit4.class) 13 | public class ApplicationTest { 14 | @Test 15 | public void useAppContext() { 16 | // Context of the app under test. 17 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 18 | 19 | assertEquals("com.sjl.deviceconnector.test", appContext.getPackageName()); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 45 | 46 | 49 | 50 | 53 | 56 | 57 | 58 | 63 | 66 | 67 | 70 | 74 | 77 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.view.LayoutInflater; 9 | 10 | 11 | import com.sjl.deviceconnector.test.entity.MessageEvent; 12 | import com.sjl.deviceconnector.test.util.TUtils; 13 | import com.sjl.deviceconnector.test.widget.LoadingDialog; 14 | 15 | import org.greenrobot.eventbus.EventBus; 16 | import org.greenrobot.eventbus.Subscribe; 17 | import org.greenrobot.eventbus.ThreadMode; 18 | 19 | import java.lang.reflect.Method; 20 | 21 | import androidx.appcompat.app.AppCompatActivity; 22 | import androidx.viewbinding.ViewBinding; 23 | 24 | /** 25 | * 基类Activity 26 | * 27 | * @author Kelly 28 | * @version 1.0.0 29 | * @filename BaseActivity 30 | * @time 2021/9/11 14:40 31 | * @copyright(C) 2021 song 32 | */ 33 | public abstract class BaseActivity extends AppCompatActivity { 34 | protected VB viewBinding; 35 | protected Context mContext; 36 | protected LoadingDialog loadingDialog; 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | try { 41 | Method inflate = TUtils.getClass(getClass()).getDeclaredMethod("inflate", LayoutInflater.class); 42 | viewBinding = (VB) inflate.invoke(null, getLayoutInflater()); 43 | setContentView(viewBinding.getRoot()); 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | mContext = this; 48 | loadingDialog = new LoadingDialog(this); 49 | initView(); 50 | initListener(); 51 | initData(); 52 | 53 | } 54 | 55 | 56 | protected abstract void initView(); 57 | 58 | protected abstract void initListener(); 59 | 60 | protected abstract void initData(); 61 | 62 | @Override 63 | public void onStart() { 64 | super.onStart(); 65 | EventBus.getDefault().register(this); 66 | } 67 | 68 | @Override 69 | public void onStop() { 70 | super.onStop(); 71 | EventBus.getDefault().unregister(this); 72 | } 73 | 74 | @Subscribe(threadMode = ThreadMode.MAIN) 75 | public void onMessageEvent(MessageEvent event) { 76 | _onMessageEvent(event); 77 | } 78 | 79 | protected void _onMessageEvent(MessageEvent event) { 80 | } 81 | 82 | 83 | public void openActivity(Class clz) { 84 | openActivity(clz, null); 85 | } 86 | 87 | public void openActivity(Class clz, Bundle bundle) { 88 | Intent intent = new Intent(mContext, clz); 89 | if (bundle != null) { 90 | intent.putExtras(bundle); 91 | } 92 | mContext.startActivity(intent); 93 | } 94 | 95 | 96 | 97 | /** 98 | * 判断Activity是否Destroy 99 | * 100 | * @param mActivity 101 | * @return 102 | */ 103 | public static boolean isDestroy(Activity mActivity) { 104 | if (mActivity == null || mActivity.isFinishing() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && mActivity.isDestroyed())) { 105 | return true; 106 | } else { 107 | return false; 108 | } 109 | } 110 | protected void showLoading() { 111 | if (loadingDialog != null) { 112 | loadingDialog.show(); 113 | } 114 | } 115 | 116 | protected void hideLoading() { 117 | if (loadingDialog != null) { 118 | loadingDialog.hide(); 119 | } 120 | } 121 | @Override 122 | protected void onDestroy() { 123 | super.onDestroy(); 124 | //取消监听 125 | hideLoading(); 126 | loadingDialog = null; 127 | viewBinding = null; 128 | } 129 | 130 | } 131 | 132 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/activity/SerialPortListActivity.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.view.View; 6 | 7 | import com.chad.library.adapter.base.BaseQuickAdapter; 8 | import com.chad.library.adapter.base.listener.OnItemClickListener; 9 | import com.sjl.deviceconnector.device.serialport.SerialPortHelper; 10 | import com.sjl.deviceconnector.test.adapter.SerialPortListAdapter; 11 | import com.sjl.deviceconnector.test.databinding.BluetoothListActivityBinding; 12 | import com.sjl.deviceconnector.test.databinding.SerialPortListActivityBinding; 13 | 14 | import java.util.List; 15 | 16 | import androidx.recyclerview.widget.LinearLayoutManager; 17 | 18 | 19 | /** 20 | * 串口列表 21 | * 22 | * @author Kelly 23 | * @version 1.0.0 24 | * @filename SerialPortListActivity 25 | * @time 2022/7/25 15:05 26 | * @copyright(C) 2022 song 27 | */ 28 | public class SerialPortListActivity extends BaseActivity { 29 | 30 | 31 | @Override 32 | protected void initView() { 33 | 34 | } 35 | 36 | @Override 37 | protected void initListener() { 38 | 39 | viewBinding.toolbar.setNavigationOnClickListener(new View.OnClickListener() { 40 | @Override 41 | public void onClick(View v) { 42 | finish(); 43 | } 44 | }); 45 | } 46 | 47 | @Override 48 | protected void initData() { 49 | List deviceList = SerialPortHelper.getDeviceList(); 50 | SerialPortListAdapter serialPortListAdapter = new SerialPortListAdapter(deviceList); 51 | serialPortListAdapter.setOnItemClickListener(new OnItemClickListener() { 52 | @Override 53 | public void onItemClick(BaseQuickAdapter adapter, View view, int position) { 54 | String item = serialPortListAdapter.getItem(position); 55 | Intent intent = new Intent(); 56 | intent.putExtra(MainActivity.EXTRA_DEVICE_ITEM, item); 57 | setResult(Activity.RESULT_OK, intent); 58 | finish(); 59 | } 60 | }); 61 | viewBinding.recyclerView.setLayoutManager(new LinearLayoutManager(mContext)); 62 | viewBinding.recyclerView.setAdapter(serialPortListAdapter); 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/activity/SerialPortSettingActivity.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.activity; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | 6 | import com.sjl.deviceconnector.test.app.MyApplication; 7 | import com.sjl.deviceconnector.test.R; 8 | import com.sjl.deviceconnector.test.databinding.SerialPortSettingActivityBinding; 9 | import com.sjl.deviceconnector.test.entity.SerialPortInfo; 10 | import com.sjl.deviceconnector.test.util.SpSettingUtils; 11 | 12 | import androidx.appcompat.app.AlertDialog; 13 | 14 | /** 15 | * 串口,保存到内存中 16 | * 17 | * @author Kelly 18 | * @version 1.0.0 19 | * @filename SerialPortSettingActivity 20 | * @time 2022/6/9 18:00 21 | * @copyright(C) 2022 song 22 | */ 23 | public class SerialPortSettingActivity extends BaseActivity implements View.OnClickListener { 24 | TextView tvBaudRate; 25 | TextView tvDataBits; 26 | TextView tvStopBits; 27 | TextView tvParity; 28 | 29 | private int baudRate = 115200; 30 | private int dataBits = 8; 31 | private int stopBits = 1; 32 | private int parity = 0; 33 | 34 | @Override 35 | protected void initView() { 36 | tvBaudRate = viewBinding.tvBaudRate; 37 | tvDataBits = viewBinding.tvDataBits; 38 | tvStopBits = viewBinding.tvStopBits; 39 | tvParity = viewBinding.tvParity; 40 | } 41 | 42 | @Override 43 | protected void initListener() { 44 | viewBinding.itemBaudRate.setOnClickListener(this); 45 | viewBinding.itemDataBits.setOnClickListener(this); 46 | viewBinding.itemStopBits.setOnClickListener(this); 47 | viewBinding.itemParity.setOnClickListener(this); 48 | viewBinding.toolbar.setNavigationOnClickListener(new View.OnClickListener() { 49 | @Override 50 | public void onClick(View v) { 51 | finish(); 52 | } 53 | }); 54 | } 55 | 56 | @Override 57 | protected void initData() { 58 | initDefaultValue(); 59 | } 60 | 61 | private void initDefaultValue() { 62 | final SerialPortInfo serialPortInfo = MyApplication.getSerialPortInfo(); 63 | tvBaudRate.setText(String.valueOf(serialPortInfo.getBaudRate())); 64 | tvDataBits.setText(String.valueOf(serialPortInfo.getDataBits())); 65 | if (serialPortInfo.getStopBits() == 3) { 66 | tvStopBits.setText("1.5"); 67 | } else { 68 | tvStopBits.setText(String.valueOf(serialPortInfo.getStopBits())); 69 | } 70 | int parity = serialPortInfo.getParity(); 71 | final String[] values = getResources().getStringArray(R.array.parity); 72 | tvParity.setText(values[parity]); 73 | 74 | 75 | } 76 | 77 | 78 | 79 | @Override 80 | public void onClick(View v) { 81 | final SerialPortInfo serialPortInfo = MyApplication.getSerialPortInfo(); 82 | 83 | switch (v.getId()) { 84 | 85 | case R.id.itemBaudRate: { 86 | final String[] values = getResources().getStringArray(R.array.baudRates); 87 | int pos = java.util.Arrays.asList(values).indexOf(String.valueOf(baudRate)); 88 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 89 | builder.setTitle("波特率"); 90 | builder.setSingleChoiceItems(values, pos, (dialog, which) -> { 91 | dialog.dismiss(); 92 | baudRate = Integer.parseInt(values[which]); 93 | tvBaudRate.setText(values[which]); 94 | serialPortInfo.setBaudRate(baudRate); 95 | saveSerialPortInfo(serialPortInfo); 96 | }); 97 | builder.create().show(); 98 | break; 99 | } 100 | case R.id.itemDataBits: { 101 | final String[] values = getResources().getStringArray(R.array.dataBits); 102 | int pos = java.util.Arrays.asList(values).indexOf(String.valueOf(dataBits)); 103 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 104 | builder.setTitle("数据位"); 105 | builder.setSingleChoiceItems(values, pos, (dialog, which) -> { 106 | dialog.dismiss(); 107 | dataBits = Integer.parseInt(values[which]); 108 | tvDataBits.setText(values[which]); 109 | serialPortInfo.setDataBits(dataBits); 110 | saveSerialPortInfo(serialPortInfo); 111 | }); 112 | builder.create().show(); 113 | break; 114 | } 115 | case R.id.itemStopBits: { 116 | final String[] values = getResources().getStringArray(R.array.stopBits); 117 | int pos = 0; 118 | if (stopBits == 1) { 119 | pos = 0; 120 | } else if (stopBits == 3) { 121 | pos = 1; 122 | } else if (stopBits == 2) { 123 | pos = 2; 124 | } 125 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 126 | builder.setTitle("停止位"); 127 | builder.setSingleChoiceItems(values, pos, (dialog, which) -> { 128 | dialog.dismiss(); 129 | if (values[which].equals("1.5")) { 130 | stopBits = 3; 131 | } else { 132 | stopBits = Integer.parseInt(values[which]); 133 | } 134 | tvStopBits.setText(values[which]); 135 | 136 | serialPortInfo.setStopBits(stopBits); 137 | saveSerialPortInfo(serialPortInfo); 138 | }); 139 | builder.create().show(); 140 | break; 141 | } 142 | case R.id.itemParity: { 143 | final String[] values = getResources().getStringArray(R.array.parity); 144 | 145 | int pos = parity; 146 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 147 | builder.setTitle("校验位"); 148 | builder.setSingleChoiceItems(values, pos, (dialog, which) -> { 149 | dialog.dismiss(); 150 | parity = which; 151 | tvParity.setText(values[which]); 152 | serialPortInfo.setParity(which); 153 | saveSerialPortInfo(serialPortInfo); 154 | }); 155 | builder.create().show(); 156 | break; 157 | } 158 | default: 159 | break; 160 | } 161 | 162 | } 163 | 164 | private void saveSerialPortInfo(SerialPortInfo serialPortInfo) { 165 | SpSettingUtils.saveSerialPortInfo(serialPortInfo); 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/activity/UsbListActivity.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.hardware.usb.UsbDevice; 6 | import android.view.View; 7 | import android.widget.Toast; 8 | 9 | import com.chad.library.adapter.base.BaseQuickAdapter; 10 | import com.chad.library.adapter.base.listener.OnItemClickListener; 11 | import com.sjl.deviceconnector.device.usb.UsbHelper; 12 | import com.sjl.deviceconnector.listener.UsbPermissionListener; 13 | import com.sjl.deviceconnector.test.adapter.UsbListAdapter; 14 | import com.sjl.deviceconnector.test.databinding.UsbListActivityBinding; 15 | 16 | import java.util.List; 17 | 18 | import androidx.recyclerview.widget.LinearLayoutManager; 19 | 20 | 21 | /** 22 | * USB列表 23 | * 24 | * @author Kelly 25 | * @version 1.0.0 26 | * @filename UsbListActivity 27 | * @time 2022/7/25 12:20 28 | * @copyright(C) 2022 song 29 | */ 30 | public class UsbListActivity extends BaseActivity { 31 | 32 | 33 | @Override 34 | protected void initView() { 35 | 36 | } 37 | 38 | @Override 39 | protected void initListener() { 40 | 41 | viewBinding.toolbar.setNavigationOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | finish(); 45 | } 46 | }); 47 | } 48 | 49 | @Override 50 | protected void initData() { 51 | List deviceList = UsbHelper.getDeviceList(); 52 | UsbListAdapter usbListAdapter = new UsbListAdapter(deviceList); 53 | usbListAdapter.setOnItemClickListener(new OnItemClickListener() { 54 | @Override 55 | public void onItemClick(BaseQuickAdapter adapter, View view, int position) { 56 | UsbDevice item = usbListAdapter.getItem(position); 57 | UsbHelper.getInstance().requestPermission(item, new UsbPermissionListener() { 58 | @Override 59 | public void onGranted(UsbDevice usbDevice) { 60 | Intent intent = new Intent(); 61 | intent.putExtra(MainActivity.EXTRA_DEVICE_ITEM, item); 62 | setResult(Activity.RESULT_OK, intent); 63 | finish(); 64 | } 65 | 66 | @Override 67 | public void onDenied(UsbDevice usbDevice) { 68 | Toast.makeText(mContext, "Usb设备授权失败:" + usbDevice.getVendorId() + ":" + usbDevice.getProductId(), Toast.LENGTH_LONG).show(); 69 | } 70 | }); 71 | 72 | } 73 | }); 74 | viewBinding.recyclerView.setLayoutManager(new LinearLayoutManager(mContext)); 75 | viewBinding.recyclerView.setAdapter(usbListAdapter); 76 | 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/activity/WifiSettingActivity.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.activity; 2 | 3 | import android.text.Editable; 4 | import android.text.InputType; 5 | import android.text.TextUtils; 6 | import android.text.TextWatcher; 7 | import android.text.method.DigitsKeyListener; 8 | import android.view.View; 9 | 10 | import com.sjl.deviceconnector.test.app.MyApplication; 11 | import com.sjl.deviceconnector.test.databinding.WifiSettingActivityBinding; 12 | import com.sjl.deviceconnector.test.entity.WifiInfo; 13 | import com.sjl.deviceconnector.test.util.SpSettingUtils; 14 | 15 | /** 16 | * Wifi设置 17 | * 18 | * @author Kelly 19 | * @version 1.0.0 20 | * @filename WifiSettingActivity 21 | * @time 2022/7/25 15:26 22 | * @copyright(C) 2022 song 23 | */ 24 | public class WifiSettingActivity extends BaseActivity implements TextWatcher { 25 | 26 | 27 | @Override 28 | protected void initView() { 29 | 30 | } 31 | 32 | @Override 33 | protected void initListener() { 34 | viewBinding.toolbar.setNavigationOnClickListener(new View.OnClickListener() { 35 | @Override 36 | public void onClick(View v) { 37 | finish(); 38 | } 39 | }); 40 | viewBinding.etIp.addTextChangedListener(this); 41 | viewBinding.etPort.addTextChangedListener(this); 42 | 43 | } 44 | 45 | @Override 46 | protected void initData() { 47 | initDefaultValue(); 48 | } 49 | 50 | private void initDefaultValue() { 51 | final WifiInfo wifiInfo = MyApplication.getWifiInfo(); 52 | String ip = wifiInfo.getIp(); 53 | int port = wifiInfo.getPort(); 54 | viewBinding.etIp.setText(ip); 55 | viewBinding.etIp.setInputType(InputType.TYPE_CLASS_NUMBER); 56 | String digits = "0123456789."; 57 | viewBinding.etIp.setKeyListener(DigitsKeyListener.getInstance(digits)); 58 | if (!TextUtils.isEmpty(ip)){ 59 | viewBinding.etIp.setSelection(ip.length()); 60 | } 61 | if (port != 0){ 62 | viewBinding.etPort.setText(String.valueOf(port)); 63 | viewBinding.etPort.setSelection(String.valueOf(port).length()); 64 | } 65 | 66 | } 67 | 68 | 69 | 70 | 71 | @Override 72 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 73 | 74 | } 75 | 76 | @Override 77 | public void onTextChanged(CharSequence s, int start, int before, int count) { 78 | final WifiInfo wifiInfo = MyApplication.getWifiInfo(); 79 | String ip = viewBinding.etIp.getText().toString().trim(); 80 | if (!TextUtils.isEmpty(ip)) { 81 | wifiInfo.setIp(ip); 82 | } 83 | String port = viewBinding.etPort.getText().toString().trim(); 84 | if (!TextUtils.isEmpty(port)) { 85 | wifiInfo.setPort(Integer.parseInt(port)); 86 | } 87 | SpSettingUtils.saveSysWifiInfo(wifiInfo); 88 | } 89 | 90 | @Override 91 | public void afterTextChanged(Editable s) { 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/adapter/BluetoothLeDataAdapter.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.adapter; 2 | 3 | 4 | import com.chad.library.adapter.base.BaseQuickAdapter; 5 | import com.chad.library.adapter.base.viewholder.BaseViewHolder; 6 | import com.sjl.deviceconnector.entity.AdStructure; 7 | import com.sjl.deviceconnector.test.R; 8 | import com.sjl.deviceconnector.util.ByteUtils; 9 | 10 | import java.util.List; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | 15 | /** 16 | * ble raw data适配器 17 | * 18 | * @author Kelly 19 | * @version 1.0.0 20 | * @filename BluetoothLeDataAdapter 21 | * @time 2023/3/17 21:04 22 | * @copyright(C) 2023 song 23 | */ 24 | public class BluetoothLeDataAdapter extends BaseQuickAdapter { 25 | public BluetoothLeDataAdapter(@Nullable List data) { 26 | super(R.layout.ble_data_recycle_item, data); 27 | } 28 | 29 | @Override 30 | protected void convert(@NonNull BaseViewHolder helper, AdStructure item) { 31 | 32 | helper.setText(R.id.tv_len, String.valueOf(item.length)) 33 | .setText(R.id.tv_type, String.format("%02X", item.type & 0xff)) 34 | .setText(R.id.tv_value, ByteUtils.byteArrToHexString(item.data)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/adapter/BluetoothListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.adapter; 2 | 3 | import android.bluetooth.BluetoothDevice; 4 | 5 | import com.chad.library.adapter.base.BaseQuickAdapter; 6 | import com.chad.library.adapter.base.viewholder.BaseViewHolder; 7 | import com.sjl.deviceconnector.entity.BluetoothScanResult; 8 | import com.sjl.deviceconnector.test.R; 9 | 10 | import java.util.Collections; 11 | import java.util.Comparator; 12 | import java.util.List; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | 17 | /** 18 | * TODO 19 | * 20 | * @author Kelly 21 | * @version 1.0.0 22 | * @filename BluetoothListAdapter 23 | * @time 2022/7/25 10:56 24 | * @copyright(C) 2022 song 25 | */ 26 | public class BluetoothListAdapter extends BaseQuickAdapter { 27 | private boolean scanFlag; 28 | public BluetoothListAdapter(@Nullable List data) { 29 | super(R.layout.bluetooth_list_recycle_item, data); 30 | addChildClickViewIds(R.id.tv_raw_data); 31 | } 32 | 33 | @Override 34 | protected void convert(@NonNull BaseViewHolder helper, BluetoothScanResult item) { 35 | String deviceName = item.getName(); 36 | 37 | helper.setText(R.id.tv_title_name,deviceName).setText(R.id.tv_address,item.getAddress()); 38 | if (item.getBondState() == BluetoothDevice.BOND_BONDED) { 39 | helper.setText(R.id.tv_state,"已配对"); 40 | }else { 41 | helper.setText(R.id.tv_state,"未配对"); 42 | } 43 | helper.setText(R.id.tv_rssi,String.format("Rssi: %d", item.getRssi())); 44 | if (!scanFlag){ //经典蓝牙 45 | helper.setGone(R.id.tv_raw_data,true); 46 | }else { 47 | helper.setGone(R.id.tv_raw_data,false); 48 | } 49 | } 50 | 51 | public void addNewData(BluetoothScanResult device) { 52 | List datas = getData(); 53 | if (datas == null || datas.size() == 0){ 54 | addData(device); 55 | return; 56 | } 57 | boolean exist = false; 58 | for (BluetoothScanResult bluetoothDevice:datas){ 59 | if (bluetoothDevice.getAddress().equals(device.getAddress())){ 60 | exist = true; 61 | } 62 | } 63 | if (!exist){ 64 | addData(device); 65 | } 66 | //按信号强弱从大到小排序 67 | Collections.sort(getData(), new Comparator() { 68 | @Override 69 | public int compare(BluetoothScanResult o1, BluetoothScanResult o2) { 70 | return o2.getRssi()- o1.getRssi(); 71 | } 72 | }); 73 | notifyDataSetChanged(); 74 | 75 | } 76 | 77 | public void setScanFlag(boolean scanFlag) { 78 | this.scanFlag = scanFlag; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/adapter/SerialPortListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.adapter; 2 | 3 | 4 | import com.chad.library.adapter.base.BaseQuickAdapter; 5 | import com.chad.library.adapter.base.viewholder.BaseViewHolder; 6 | import com.sjl.deviceconnector.test.R; 7 | 8 | import java.util.List; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | 13 | /** 14 | * TODO 15 | * 16 | * @author Kelly 17 | * @version 1.0.0 18 | * @filename SerialPortListAdapter 19 | * @time 2022/7/25 14:29 20 | * @copyright(C) 2022 song 21 | */ 22 | public class SerialPortListAdapter extends BaseQuickAdapter { 23 | public SerialPortListAdapter(@Nullable List data) { 24 | super(R.layout.serial_port_list_recycle_item, data); 25 | } 26 | 27 | @Override 28 | protected void convert(@NonNull BaseViewHolder helper, String item) { 29 | helper.setText(R.id.tv_title_name,item); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/adapter/UsbListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.adapter; 2 | 3 | import android.hardware.usb.UsbDevice; 4 | import android.text.TextUtils; 5 | 6 | import com.chad.library.adapter.base.BaseQuickAdapter; 7 | import com.chad.library.adapter.base.viewholder.BaseViewHolder; 8 | import com.sjl.core.util.log.LogUtils; 9 | import com.sjl.deviceconnector.test.R; 10 | 11 | import java.util.List; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | 16 | /** 17 | * TODO 18 | * 19 | * @author Kelly 20 | * @version 1.0.0 21 | * @filename DeviceListAdapter 22 | * @time 2022/6/7 12:01 23 | * @copyright(C) 2022 song 24 | */ 25 | public class UsbListAdapter extends BaseQuickAdapter { 26 | public UsbListAdapter(@Nullable List data) { 27 | super(R.layout.usb_list_recycle_item, data); 28 | } 29 | 30 | @Override 31 | protected void convert(@NonNull BaseViewHolder helper, UsbDevice item) { 32 | String path = TextUtils.isEmpty(item.getDeviceName()) ? "Unknown" : item.getDeviceName(); 33 | String manufacturerName = "Unknown"; 34 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { 35 | manufacturerName = TextUtils.isEmpty(item.getManufacturerName()) ? "Unknown" : item.getManufacturerName(); 36 | } 37 | String productName = "Unknown"; 38 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { 39 | productName = TextUtils.isEmpty(item.getProductName()) ? "Unknown" : item.getProductName(); 40 | } 41 | String driver = manufacturerName; 42 | 43 | 44 | helper.setText(R.id.tv_driver, driver) 45 | .setText(R.id.tv_path, path) 46 | .setText(R.id.tv_name, productName) 47 | .setText(R.id.tv_manufacturer, manufacturerName) 48 | .setText(R.id.tv_vendorId, String.valueOf(item.getVendorId())) 49 | .setText(R.id.tv_productId, String.valueOf(item.getProductId())); 50 | LogUtils.i("UsbDevice:" + item.toString()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/app/AppConstant.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.app; 2 | 3 | 4 | 5 | /** 6 | * 常量 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename AppConstant 11 | * @time 2022/6/7 14:20 12 | * @copyright(C) 2022 song 13 | */ 14 | public interface AppConstant { 15 | 16 | /** 17 | * 页面传递参数key 18 | */ 19 | interface Extras { 20 | 21 | 22 | } 23 | 24 | 25 | /** 26 | * 消息事件代码 27 | */ 28 | interface MessageEventCode { 29 | int LOG = 1000; 30 | 31 | } 32 | 33 | 34 | /** 35 | * 本地配置的key,都放在这 36 | */ 37 | interface SpParams { 38 | String SERIAL_PORT_INFO= "serial_port_info"; 39 | String WIFI_INFO = "wifi_info"; 40 | 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/app/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.app; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.text.TextUtils; 8 | 9 | import com.sjl.core.app.BaseApplication; 10 | import com.sjl.core.util.log.LogUtils; 11 | import com.sjl.deviceconnector.DeviceContext; 12 | import com.sjl.deviceconnector.entity.SerialPortConfig; 13 | import com.sjl.deviceconnector.test.entity.SerialPortInfo; 14 | import com.sjl.deviceconnector.test.entity.WifiInfo; 15 | import com.sjl.deviceconnector.test.util.SpSettingUtils; 16 | 17 | import java.util.concurrent.ExecutorService; 18 | import java.util.concurrent.Executors; 19 | 20 | import androidx.multidex.MultiDex; 21 | import me.jessyan.autosize.AutoSizeConfig; 22 | 23 | /** 24 | * TODO 25 | * 26 | * @author Kelly 27 | * @version 1.0.0 28 | * @filename MyApplication 29 | * @time 2022/6/2 15:53 30 | * @copyright(C) 2022 song 31 | */ 32 | public class MyApplication extends BaseApplication { 33 | private static final ExecutorService executorService = Executors.newCachedThreadPool(); 34 | private static final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); 35 | 36 | 37 | @Override 38 | public void onCreate() { 39 | super.onCreate(); 40 | int sw = getResources().getConfiguration().smallestScreenWidthDp; 41 | if (sw >=200 && sw <= 500) { 42 | //大部分屏幕是360 43 | sw= 360; 44 | } 45 | AutoSizeConfig.getInstance().setDesignWidthInDp(sw); 46 | initLogConfig(true); 47 | DeviceContext.init(this,true); 48 | } 49 | 50 | 51 | 52 | public static SerialPortInfo getSerialPortInfo() { 53 | return SpSettingUtils.getSerialPortInfo(); 54 | } 55 | 56 | public static WifiInfo getWifiInfo() { 57 | return SpSettingUtils.getWifiInfo(); 58 | } 59 | 60 | public static ExecutorService getExecutor() { 61 | return executorService; 62 | } 63 | 64 | public static Handler getMainThreadExecutor() { 65 | return mainThreadHandler; 66 | } 67 | 68 | 69 | public static SerialPortConfig getSerialPortConfig(SerialPortInfo serialPortInfo) { 70 | if (serialPortInfo == null) { 71 | return null; 72 | } 73 | SerialPortConfig.Builder builder; 74 | if (TextUtils.isEmpty(serialPortInfo.getDevicePath())){//说明是Usb Com,只需要参数即可,串口路径不需要 75 | builder = SerialPortConfig.newBuilder(serialPortInfo.getBaudRate()); 76 | }else { 77 | builder = SerialPortConfig.newBuilder(serialPortInfo.getDevicePath(),serialPortInfo.getBaudRate()); 78 | } 79 | 80 | builder.dataBits(serialPortInfo.getDataBits()); 81 | builder.parity(serialPortInfo.getParity()); 82 | builder.stopBits(serialPortInfo.getStopBits()); 83 | return builder.build(); 84 | } 85 | 86 | 87 | 88 | @Override 89 | protected void attachBaseContext(Context base) { 90 | super.attachBaseContext(base); 91 | MultiDex.install(this); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/entity/ConnectWay.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.entity; 2 | 3 | /** 4 | * 连接方式枚举 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename ConnectWay 9 | * @time 2022/7/25 11:50 10 | * @copyright(C) 2022 song 11 | */ 12 | public enum ConnectWay { 13 | SERIAL_PORT("串口"), 14 | USB_COM("Usb Com"), 15 | USB("Usb"), 16 | BLUETOOTH("Bluetooth Classic"), 17 | WIFI("Wifi"), 18 | BLUETOOTH_LOW_ENERGY("Bluetooth Low Energy"); 19 | 20 | private final String name; 21 | 22 | ConnectWay(String name) { 23 | this.name = name; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/entity/MessageEvent.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.entity; 2 | 3 | /** 4 | * TODO 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename MessageEvent 9 | * @time 2022/6/7 15:26 10 | * @copyright(C) 2022 song 11 | */ 12 | public class MessageEvent { 13 | private int code; 14 | private String flag; 15 | private T event; 16 | 17 | public MessageEvent(int code, String flag, T event) { 18 | this.code = code; 19 | this.flag = flag; 20 | this.event = event; 21 | } 22 | 23 | public int getCode() { 24 | return code; 25 | } 26 | 27 | public String getFlag() { 28 | return flag; 29 | } 30 | 31 | public T getEvent() { 32 | return event; 33 | } 34 | 35 | public static class Builder { 36 | private int code; 37 | private String flag; 38 | private T event; 39 | 40 | 41 | public Builder setCode(int code) { 42 | this.code = code; 43 | return this; 44 | } 45 | 46 | public Builder setFlag(String flag) { 47 | this.flag = flag; 48 | return this; 49 | } 50 | 51 | public Builder setEvent(T event) { 52 | this.event = event; 53 | return this; 54 | } 55 | 56 | public MessageEvent create() { 57 | return new MessageEvent(code, flag, event); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/entity/SerialPortInfo.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.entity; 2 | 3 | 4 | /** 5 | * 串口信息 6 | * 7 | * @author Kelly 8 | * @version 1.0.0 9 | * @filename SerialPortInfo 10 | * @time 2022/7/25 17:38 11 | * @copyright(C) 2022 song 12 | */ 13 | public class SerialPortInfo { 14 | private String devicePath; 15 | private int baudRate = 115200; 16 | private int dataBits = 8; 17 | private int stopBits = 1; 18 | private int parity = 0; 19 | 20 | public String getDevicePath() { 21 | return devicePath; 22 | } 23 | 24 | public void setDevicePath(String devicePath) { 25 | this.devicePath = devicePath; 26 | } 27 | 28 | public int getBaudRate() { 29 | return baudRate; 30 | } 31 | 32 | public void setBaudRate(int baudRate) { 33 | this.baudRate = baudRate; 34 | } 35 | 36 | public int getDataBits() { 37 | return dataBits; 38 | } 39 | 40 | public void setDataBits(int dataBits) { 41 | this.dataBits = dataBits; 42 | } 43 | 44 | public int getStopBits() { 45 | return stopBits; 46 | } 47 | 48 | public void setStopBits(int stopBits) { 49 | this.stopBits = stopBits; 50 | } 51 | 52 | public int getParity() { 53 | return parity; 54 | } 55 | 56 | public void setParity(int parity) { 57 | this.parity = parity; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/entity/WifiInfo.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.entity; 2 | 3 | /** 4 | * Wifi信息 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename WifiInfo 9 | * @time 2022/7/25 17:39 10 | * @copyright(C) 2022 song 11 | */ 12 | public class WifiInfo { 13 | private String ip = "127.0.0.1"; 14 | private int port = 8806; 15 | 16 | public String getIp() { 17 | return ip; 18 | } 19 | 20 | public void setIp(String ip) { 21 | this.ip = ip; 22 | } 23 | 24 | public int getPort() { 25 | return port; 26 | } 27 | 28 | public void setPort(int port) { 29 | this.port = port; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/service/ReadThread.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.service; 2 | 3 | 4 | 5 | import com.sjl.core.util.log.LogUtils; 6 | import com.sjl.deviceconnector.provider.BaseIoConnectProvider; 7 | import com.sjl.deviceconnector.test.util.MessageEventUtils; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | /** 13 | * 客户端监听服务端数据的线程 14 | *

注意:单独监听就不能使用baseConnectProvider.read接口

15 | * @author Kelly 16 | * @version 1.0.0 17 | * @filename ReadThread 18 | * @time 2024/3/4 18:23 19 | * @copyright(C) 2024 song 20 | */ 21 | public class ReadThread extends Thread { 22 | private BaseIoConnectProvider connectProvider; 23 | private boolean running; 24 | 25 | public ReadThread(BaseIoConnectProvider connectProvider) { 26 | this.connectProvider = connectProvider; 27 | } 28 | 29 | @Override 30 | public void run() { 31 | running = true; 32 | LogUtils.i("Start read thread."); 33 | InputStream inputStream = connectProvider.getInputStream(); 34 | while (running) { 35 | if (inputStream == null) { 36 | return; 37 | } 38 | try { 39 | byte[] readData = new byte[128]; 40 | int size = inputStream.read(readData); 41 | if (size > 0) { 42 | synchronized (this){ 43 | byte[] tempBuffer = new byte[size]; 44 | System.arraycopy(readData, 0, tempBuffer, 0, tempBuffer.length); 45 | MessageEventUtils.sendLog("收到服务端数据:" + new String(tempBuffer)); 46 | } 47 | } 48 | } catch (Exception e) { 49 | LogUtils.e("Data read exception", e); 50 | } 51 | } 52 | LogUtils.w("The read thread stop."); 53 | } 54 | 55 | public void close(){ 56 | running = false; 57 | try { 58 | connectProvider.getInputStream().close(); 59 | } catch (IOException e) { 60 | 61 | } 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/util/MessageEventUtils.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.util; 2 | 3 | import com.sjl.deviceconnector.test.entity.MessageEvent; 4 | 5 | import org.greenrobot.eventbus.EventBus; 6 | 7 | /** 8 | * TODO 9 | * 10 | * @author Kelly 11 | * @version 1.0.0 12 | * @filename MessageEventUtils 13 | * @time 2022/7/26 20:29 14 | * @copyright(C) 2022 song 15 | */ 16 | public class MessageEventUtils { 17 | 18 | public static void sendLog(String log) { 19 | MessageEvent.Builder stringBuilder = new MessageEvent.Builder(); 20 | stringBuilder.setCode(1000); 21 | stringBuilder.setEvent(log); 22 | EventBus.getDefault().post(stringBuilder.create()); 23 | } 24 | 25 | public static void sendData(int connectWay,String data) { 26 | MessageEvent.Builder stringBuilder = new MessageEvent.Builder(); 27 | stringBuilder.setCode(connectWay); 28 | stringBuilder.setEvent(data); 29 | EventBus.getDefault().post(stringBuilder.create()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/util/SPUtils.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.util; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | 7 | import com.sjl.deviceconnector.test.app.MyApplication; 8 | 9 | /** 10 | * 偏好参数工具类 11 | */ 12 | public class SPUtils { 13 | 14 | private static final String FILE_NAME = "app_config"; 15 | private static final SharedPreferences sp; 16 | 17 | static { 18 | sp = MyApplication.getContext().getSharedPreferences(FILE_NAME, 19 | Context.MODE_PRIVATE); 20 | } 21 | 22 | /** 23 | * 保存 24 | * 25 | * @param key 26 | * @param object 27 | */ 28 | public static void put(String key, Object object) { 29 | SharedPreferences.Editor editor = sp.edit(); 30 | if (object instanceof String) { 31 | editor.putString(key, (String) object); 32 | } else if (object instanceof Integer) { 33 | editor.putInt(key, (Integer) object); 34 | } else if (object instanceof Boolean) { 35 | editor.putBoolean(key, (Boolean) object); 36 | } else if (object instanceof Float) { 37 | editor.putFloat(key, (Float) object); 38 | } else if (object instanceof Long) { 39 | editor.putLong(key, (Long) object); 40 | } else { 41 | editor.putString(key, object.toString()); 42 | } 43 | 44 | editor.commit(); 45 | } 46 | 47 | /** 48 | * 取值 49 | * @param key 50 | * @param defaultObject 51 | * @return 52 | */ 53 | public static Object get(String key, Object defaultObject) { 54 | if (defaultObject instanceof String) { 55 | return sp.getString(key, (String) defaultObject); 56 | } else if (defaultObject instanceof Integer) { 57 | return sp.getInt(key, (Integer) defaultObject); 58 | } else if (defaultObject instanceof Boolean) { 59 | return sp.getBoolean(key, (Boolean) defaultObject); 60 | } else if (defaultObject instanceof Float) { 61 | return sp.getFloat(key, (Float) defaultObject); 62 | } else if (defaultObject instanceof Long) { 63 | return sp.getLong(key, (Long) defaultObject); 64 | } 65 | return null; 66 | } 67 | 68 | /** 69 | * 移除key 70 | * @param key 71 | */ 72 | public static void remove(String key) { 73 | sp.edit().remove(key).commit(); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/util/SpSettingUtils.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.util; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.text.TextUtils; 7 | 8 | import com.google.gson.Gson; 9 | import com.sjl.deviceconnector.entity.SerialPortConfig; 10 | import com.sjl.deviceconnector.test.app.AppConstant; 11 | import com.sjl.deviceconnector.test.app.MyApplication; 12 | import com.sjl.deviceconnector.test.entity.SerialPortInfo; 13 | import com.sjl.deviceconnector.test.entity.WifiInfo; 14 | 15 | 16 | /** 17 | * TODO 18 | * 19 | * @author Kelly 20 | * @version 1.0.0 21 | * @filename SpSettingUtils 22 | * @time 2022/7/2 19:16 23 | * @copyright(C) 2022 song 24 | */ 25 | public class SpSettingUtils { 26 | 27 | private static Gson gson = new Gson(); 28 | 29 | private SpSettingUtils() { 30 | } 31 | 32 | 33 | public static void saveSerialPortInfo(SerialPortInfo serialPortInfo) { 34 | String s = gson.toJson(serialPortInfo); 35 | SPUtils.put(AppConstant.SpParams.SERIAL_PORT_INFO, s); 36 | } 37 | 38 | public static SerialPortInfo getSerialPortInfo() { 39 | SerialPortInfo serialPortInfo; 40 | String serialPortConfigStr = (String) SPUtils.get(AppConstant.SpParams.SERIAL_PORT_INFO, ""); 41 | if (!TextUtils.isEmpty(serialPortConfigStr)){ 42 | serialPortInfo = new Gson().fromJson(serialPortConfigStr, SerialPortInfo.class); 43 | }else { 44 | serialPortInfo = new SerialPortInfo(); 45 | } 46 | return serialPortInfo; 47 | } 48 | 49 | 50 | public static void saveSysWifiInfo(WifiInfo wifiInfo) { 51 | String s = gson.toJson(wifiInfo); 52 | SPUtils.put(AppConstant.SpParams.WIFI_INFO, s); 53 | } 54 | 55 | public static WifiInfo getWifiInfo() { 56 | WifiInfo wifiInfo; 57 | String s = (String) SPUtils.get(AppConstant.SpParams.WIFI_INFO, ""); 58 | if (!TextUtils.isEmpty(s)){ 59 | wifiInfo = new Gson().fromJson(s, WifiInfo.class); 60 | }else { 61 | wifiInfo = new WifiInfo(); 62 | } 63 | return wifiInfo; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/util/TUtils.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.util; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | 5 | /** 6 | * TODO 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename TUtils 11 | * @time 2022/6/7 11:47 12 | * @copyright(C) 2022 song 13 | */ 14 | public class TUtils { 15 | 16 | public static Class getClass(Class clz) { 17 | ParameterizedType type = (ParameterizedType) clz.getGenericSuperclass(); 18 | Class cls = (Class) type.getActualTypeArguments()[0]; 19 | return cls; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/sjl/deviceconnector/test/widget/LoadingDialog.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test.widget; 2 | 3 | 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.Window; 10 | import android.view.WindowManager; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | 14 | import com.sjl.deviceconnector.test.R; 15 | import com.wang.avi.AVLoadingIndicatorView; 16 | 17 | /** 18 | * TODO 19 | * 20 | * @author Kelly 21 | * @version 1.0.0 22 | * @filename LoadingDialog 23 | * @time 2022/6/13 15:55 24 | * @copyright(C) 2022 song 25 | */ 26 | public class LoadingDialog { 27 | AVLoadingIndicatorView mAvLoadingIndicatorView; 28 | Dialog mLoadingDialog; 29 | ImageView ivClose; 30 | TextView tvMsg; 31 | LoadingListener loadingListener; 32 | public LoadingDialog(Context context) { 33 | View view = LayoutInflater.from(context).inflate( 34 | R.layout.load_dialog, null); 35 | mAvLoadingIndicatorView = view.findViewById(R.id.indicator); 36 | tvMsg = view.findViewById(R.id.tvMsg); 37 | ivClose = view.findViewById(R.id.iv_close); 38 | 39 | mLoadingDialog = new Dialog(context,R.style.LoadingDialog); 40 | mLoadingDialog.setCancelable(false); 41 | mLoadingDialog.setContentView(view); 42 | mLoadingDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { 43 | @Override 44 | public void onDismiss(DialogInterface dialog) { 45 | if (loadingListener != null){ 46 | loadingListener.onDismiss(LoadingDialog.this); 47 | } 48 | } 49 | }); 50 | Window window = mLoadingDialog.getWindow(); 51 | WindowManager.LayoutParams params = window.getAttributes(); 52 | params.width = context.getResources().getDimensionPixelSize(R.dimen.dp_160); 53 | params.height = context.getResources().getDimensionPixelSize(R.dimen.dp_100); 54 | window.setAttributes(params); 55 | 56 | } 57 | 58 | 59 | public void show() { 60 | mLoadingDialog.show(); 61 | mAvLoadingIndicatorView.setVisibility(View.VISIBLE);//内部隐藏了,复用显示时,需要再次显示 62 | mAvLoadingIndicatorView.show(); 63 | 64 | } 65 | 66 | 67 | public void hide() { 68 | if (mLoadingDialog != null && mLoadingDialog.isShowing()) { 69 | mLoadingDialog.dismiss(); 70 | if (mAvLoadingIndicatorView != null) { 71 | mAvLoadingIndicatorView.hide(); 72 | } 73 | removeLoadingListener(); 74 | tvMsg.setText(mLoadingDialog.getContext().getString(R.string.loading_processing)); 75 | } 76 | } 77 | 78 | public void removeLoadingListener() { 79 | this.loadingListener = null; 80 | ivClose.setVisibility(View.GONE); 81 | } 82 | 83 | public void setLoadingListener(LoadingListener loadingListener) { 84 | this.loadingListener = loadingListener; 85 | ivClose.setVisibility(View.VISIBLE); 86 | ivClose.setOnClickListener(new View.OnClickListener() { 87 | @Override 88 | public void onClick(View v) { 89 | ivClose.setVisibility(View.GONE); 90 | if (loadingListener != null){ 91 | loadingListener.onClickClose(LoadingDialog.this,tvMsg); 92 | } 93 | } 94 | }); 95 | } 96 | 97 | public interface LoadingListener{ 98 | /** 99 | * 点击关闭按钮的时候触发 100 | * @param loadingDialog 101 | * @param tvMsg 提示文本控件 102 | */ 103 | void onClickClose(LoadingDialog loadingDialog, TextView tvMsg); 104 | 105 | /** 106 | * 关闭加载框的时候触发 107 | * @param loadingDialog 108 | */ 109 | void onDismiss(LoadingDialog loadingDialog); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_close_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/app/src/main/res/drawable-xxhdpi/ic_close_cancel.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/half_black_round_12dp_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_item_bg_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/white_round_8dp_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/ble_data_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 28 | 37 | 38 | 42 | 47 | 48 | 53 | 54 | 60 | 61 | 62 | 66 | 67 | 73 | 74 | -------------------------------------------------------------------------------- /app/src/main/res/layout/ble_data_recycle_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 22 | 23 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bluetooth_list_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 17 | 18 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bluetooth_list_recycle_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 21 | 29 | 30 | 46 | 47 | 57 | 69 | -------------------------------------------------------------------------------- /app/src/main/res/layout/load_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 28 | 29 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/serial_port_list_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/serial_port_list_recycle_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/serial_port_setting_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 26 | 27 | 28 | 34 | 35 | 43 | 44 | 53 | 54 | 59 | 60 | 61 | 66 | 67 | 75 | 76 | 85 | 86 | 91 | 92 | 93 | 98 | 99 | 107 | 108 | 117 | 118 | 123 | 124 | 125 | 130 | 131 | 139 | 140 | 149 | 150 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /app/src/main/res/layout/usb_list_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 13 | 14 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/usb_list_recycle_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 24 | 25 | 26 | 35 | 36 | 45 | 46 | 55 | 56 | 65 | 66 | 75 | 76 | 89 | 90 | 103 | 104 | 115 | 116 | 127 | 128 | 139 | -------------------------------------------------------------------------------- /app/src/main/res/layout/wifi_setting_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 27 | 28 | 29 | 35 | 36 | 45 | 46 | 58 | 59 | 64 | 65 | 66 | 71 | 72 | 81 | 82 | 95 | 96 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2400 5 | 3600 6 | 4800 7 | 9600 8 | 14400 9 | 19200 10 | 28800 11 | 38400 12 | 57600 13 | 115200 14 | 230400 15 | 460800 16 | 921600 17 | 18 | 19 | 20 | 5 21 | 6 22 | 7 23 | 8 24 | 25 | 26 | 27 | 1 28 | 1.5 29 | 2 30 | 31 | 32 | None 33 | Odd 34 | Even 35 | Mark 36 | Space 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #fff 7 | #000 8 | #F00 9 | #ffcccccc 10 | 11 | #FF7F50 12 | #FF0000 13 | 14 | #00CD66 15 | #548B54 16 | 17 | #ff222222 18 | #ff808080 19 | #ffe0e0e0 20 | #3A3A3B 21 | #82CAFF 22 | #00FF00 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeviceConnector 3 | 正在取消... 4 | 加载中... 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 15 | 16 | 24 | 25 | 26 | 31 | 32 | 33 | 43 | 44 | 45 | 52 | 53 | 54 | 55 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/test/java/com/sjl/deviceconnector/test/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.test; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | apply from: "config.gradle" 3 | 4 | buildscript { 5 | repositories { 6 | jcenter() 7 | mavenCentral() 8 | google() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.6.3' 12 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | jcenter() 21 | maven { url 'https://jitpack.io' } 22 | google() 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | android = [ 3 | compileSdkVersion: 29, 4 | buildToolsVersion: "29.0.3", 5 | minSdkVersion : 17, 6 | targetSdkVersion : 29, 7 | versionCode : 8, 8 | versionName : "1.1.4", 9 | applicationId : "com.sjl.deviceconnector.test" 10 | ] 11 | 12 | 13 | version = [ 14 | appcompatVersion : "1.2.0", 15 | recyclerviewVersion : "1.1.0", 16 | constraintlayoutVersion: "2.0.4", 17 | material : "1.3.0", 18 | multidex : "2.0.1", 19 | roomVersion : "2.2.5", 20 | aviLoadingVersion : "2.1.3", 21 | autosizeVersion : "1.1.2", 22 | 23 | leakcanaryVersion : "2.1", 24 | buglySDKVersion : "3.2.422", 25 | buglyNDKVersion : "3.7.5", 26 | buglyUpgradeVersion : "2.1" 27 | ] 28 | 29 | dependencies = [ 30 | "appcompat" : "androidx.appcompat:appcompat:${version.appcompatVersion}", 31 | "recyclerview" : "androidx.recyclerview:recyclerview:${version.recyclerviewVersion}", 32 | "constraintlayout" : "androidx.constraintlayout:constraintlayout:${version.constraintlayoutVersion}", 33 | 34 | room : "androidx.room:room-runtime:${version.roomVersion}", 35 | multidex : "androidx.multidex:multidex:${version.multidex}", 36 | aviLoading : "com.wang.avi:library:${version.aviLoadingVersion}", 37 | autosize : "me.jessyan:autosize:${version.autosizeVersion}", 38 | 39 | 40 | 41 | "bugly" : "com.tencent.bugly:crashreport:${version.buglySDKVersion}", 42 | "bugly_nativecrashreport": "com.tencent.bugly:nativecrashreport:${version.buglyNDKVersion}", 43 | "bugly_upgrade" : "com.tencent.bugly:crashreport_upgrade:${version.buglyUpgradeVersion}", 44 | ] 45 | annotationProcessor = [ 46 | "room-compiler": "androidx.room:room-compiler:${version.roomVersion}" 47 | ] 48 | 49 | debugDependencies = [ 50 | "leakcanary": "com.squareup.leakcanary:leakcanary-android:${version.leakcanaryVersion}" 51 | ] 52 | releaseDependencies = [ 53 | ] 54 | } -------------------------------------------------------------------------------- /deviceconnector/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /deviceconnector/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven'//this 3 | group='com.github.kellysong'//this 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion '29.0.3' 8 | 9 | 10 | defaultConfig { 11 | minSdkVersion 17 12 | targetSdkVersion 29 13 | versionCode 4 14 | versionName "1.1.0" 15 | 16 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 17 | 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | } 31 | 32 | dependencies { 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 35 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' 36 | implementation 'org.jetbrains:annotations:15.0' 37 | api 'com.github.mik3y:usb-serial-for-android:3.4.3' 38 | api 'com.github.licheedev:Android-SerialPort-API:2.0.0' 39 | api 'androidx.annotation:annotation:1.2.0' 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /deviceconnector/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /deviceconnector/src/androidTest/java/com/sjl/deviceconnector/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector; 2 | 3 | import android.content.Context; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | import androidx.test.ext.junit.runners.AndroidJUnit4; 9 | import androidx.test.platform.app.InstrumentationRegistry; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() throws Exception { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.sjl.deviceconnector", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /deviceconnector/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/DeviceContext.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | 6 | import com.sjl.deviceconnector.util.LogUtils; 7 | 8 | /** 9 | * 设备上下文 10 | * 11 | * @author Kelly 12 | */ 13 | public class DeviceContext { 14 | private static Context context; 15 | 16 | private static Handler mMainHandler; 17 | 18 | public static void init(Context context,boolean debug) { 19 | DeviceContext.context = context; 20 | LogUtils.init(debug); 21 | } 22 | 23 | public static Context getContext() { 24 | checkContext(); 25 | return context; 26 | } 27 | 28 | private static void checkContext() { 29 | if (context == null) { 30 | throw new RuntimeException("DeviceContext未初始化"); 31 | } 32 | } 33 | 34 | public static Handler mainHandler() { 35 | checkContext(); 36 | if (mMainHandler == null){ 37 | mMainHandler = new Handler(context.getMainLooper()); 38 | } 39 | return mMainHandler; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/ErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector; 2 | 3 | /** 4 | * 通信传输错误码 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename ErrorCode.java 9 | * @time 2018/4/16 14:29 10 | * @copyright(C) 2018 song 11 | */ 12 | public class ErrorCode { 13 | /** 14 | * 成功 15 | */ 16 | public static final int ERROR_OK = 0; 17 | /** 18 | * 通信超时 19 | */ 20 | public static final int ERROR_TIMEOUT = -1; 21 | /** 22 | * 数据发送错误 23 | */ 24 | public static final int ERROR_SEND = -2; 25 | /** 26 | * 数据接收错误或者丢包 27 | */ 28 | public static final int ERROR_RECEIVE = -3; 29 | /** 30 | * 数据校验错误 31 | */ 32 | public static final int ERROR_CHECK = -4; 33 | /** 34 | * 未连接 35 | */ 36 | public static final int ERROR_NOT_CONNECTED = -5; 37 | /** 38 | * 已连接 39 | */ 40 | public static final int ERROR_CONNECTED = -6; 41 | /** 42 | * 数据为空 43 | */ 44 | public static final int ERROR_DATA_NULL = -7; 45 | 46 | /** 47 | * 取消操作 48 | */ 49 | public static final int ERROR_CANCEL = -8; 50 | 51 | /** 52 | * 不支持该功能或接口 53 | */ 54 | public static final int ERROR_NOT_SUPPORTED = -9; 55 | /** 56 | * 未初始化 57 | */ 58 | public static final int ERROR_NOT_INIT = -10; 59 | 60 | /** 61 | * 设备未找到或不存在 62 | */ 63 | public static final int ERROR_DEVICE_NOT_FIND = -11; 64 | /** 65 | * 设备驱动未找到 66 | */ 67 | public static final int ERROR_DEVICE_DRIVER_NOT_FIND = -12; 68 | /** 69 | * 无权限 70 | */ 71 | public static final int ERROR_NO_PERMISSION = -14; 72 | /** 73 | * 权限拒绝 74 | */ 75 | public static final int ERROR_PERMISSION_FAIL = -15; 76 | /** 77 | * 打开失败或连接失败 78 | */ 79 | public static final int ERROR_OPEN_FAIL = -16; 80 | 81 | 82 | /** 83 | * 数据通信失败或失败 84 | */ 85 | public static final int ERROR_FAIL = -99; 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/Waiter.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector; 2 | 3 | /** 4 | * TODO 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename Waiter 9 | * @time 2023/3/4 9:55 10 | * @copyright(C) 2023 song 11 | */ 12 | public class Waiter { 13 | 14 | public void waitForTimeout(Object toWaitOn, long timeoutMillis) throws InterruptedException { 15 | toWaitOn.wait(timeoutMillis); 16 | } 17 | 18 | public void notifyAll(Object toNotify) { 19 | toNotify.notifyAll(); 20 | } 21 | } -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/BluetoothHelper.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth; 2 | 3 | import android.bluetooth.BluetoothDevice; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | 9 | import com.sjl.deviceconnector.DeviceContext; 10 | import com.sjl.deviceconnector.device.bluetooth.scanner.AbstractBluetoothScanner; 11 | import com.sjl.deviceconnector.device.bluetooth.scanner.BluetoothClassicScanner; 12 | import com.sjl.deviceconnector.listener.BluetoothScanListener; 13 | import com.sjl.deviceconnector.listener.ConnectedListener; 14 | import com.sjl.deviceconnector.listener.ReceiverObservable; 15 | import com.sjl.deviceconnector.util.LogUtils; 16 | 17 | /** 18 | * 蓝牙辅助类 19 | * 20 | * @author Kelly 21 | * @version 1.0.0 22 | * @filename BluetoothHelper 23 | * @time 2022/7/26 15:55 24 | * @copyright(C) 2022 song 25 | */ 26 | public class BluetoothHelper implements ReceiverObservable { 27 | private BroadcastReceiver mBroadcastReceiver; 28 | 29 | private AbstractBluetoothScanner bluetoothScanner; 30 | protected ConnectedListener connectedListener; 31 | private static final int DEFAULT_SCAN_TIME = 8 * 1000; 32 | /** 33 | * 扫描时间,单位毫秒 34 | */ 35 | private int mScanTime = DEFAULT_SCAN_TIME; 36 | 37 | private BluetoothHelper() { 38 | bluetoothScanner = new BluetoothClassicScanner(); 39 | } 40 | 41 | public static BluetoothHelper getInstance() { 42 | return BluetoothHelperHolder.singleton; 43 | } 44 | 45 | 46 | private static final class BluetoothHelperHolder { 47 | private static BluetoothHelper singleton = new BluetoothHelper(); 48 | } 49 | 50 | /** 51 | * 连接状态监听 52 | * 53 | * @param connectedListener 54 | */ 55 | public void setConnectedListener(ConnectedListener connectedListener) { 56 | this.connectedListener = connectedListener; 57 | } 58 | 59 | 60 | /** 61 | * 设置蓝牙扫描策略 62 | * 63 | * @param bluetoothScanner 64 | */ 65 | public void setBluetoothScanner(AbstractBluetoothScanner bluetoothScanner) { 66 | this.bluetoothScanner = bluetoothScanner; 67 | } 68 | 69 | /** 70 | * 设置扫描时间 71 | * 72 | * @param scanTime 单位毫秒 73 | */ 74 | public void setScanTime(int scanTime) { 75 | if (scanTime < 2 * 1000) { 76 | this.mScanTime = DEFAULT_SCAN_TIME; 77 | return; 78 | } 79 | this.mScanTime = scanTime; 80 | } 81 | 82 | /** 83 | * 蓝牙扫描,需要先注册广播 84 | * 85 | * @param bluetoothScanListener 86 | */ 87 | public void startScan(BluetoothScanListener bluetoothScanListener) { 88 | startScan(null, bluetoothScanListener); 89 | } 90 | 91 | /** 92 | * 根据蓝牙地址扫描指定设备 93 | * 94 | * @param address 95 | * @param bluetoothScanListener 96 | */ 97 | public void startScan(String address, BluetoothScanListener bluetoothScanListener) { 98 | bluetoothScanner.setBluetoothScanListener(bluetoothScanListener); 99 | bluetoothScanner.setAddress(address); 100 | bluetoothScanner.startScan(); 101 | DeviceContext.mainHandler().postDelayed(new Runnable() { 102 | @Override 103 | public void run() { 104 | bluetoothScanner.stopScan(); 105 | } 106 | }, mScanTime); 107 | } 108 | 109 | 110 | /** 111 | * 取消扫描 112 | */ 113 | public void stopScan() { 114 | DeviceContext.mainHandler().removeCallbacksAndMessages(null); 115 | bluetoothScanner.stopScan(); 116 | } 117 | 118 | 119 | @Override 120 | public void registerReceiver() { 121 | Context context = DeviceContext.getContext(); 122 | IntentFilter intent = new IntentFilter(); 123 | 124 | intent.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); 125 | intent.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 126 | // intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);// 配对开始时,配对成功时 127 | // intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);// 搜索模式改变 128 | // intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);// 蓝牙开关状态 129 | mBroadcastReceiver = new MyBroadcastReceiver(); 130 | context.registerReceiver(mBroadcastReceiver, intent); 131 | } 132 | 133 | 134 | @Override 135 | public void unregisterReceiver() { 136 | Context context = DeviceContext.getContext(); 137 | if (mBroadcastReceiver != null) { 138 | context.unregisterReceiver(mBroadcastReceiver); 139 | mBroadcastReceiver = null; 140 | } 141 | this.connectedListener = null; 142 | } 143 | 144 | 145 | private final class MyBroadcastReceiver extends BroadcastReceiver { 146 | @Override 147 | public void onReceive(Context context, Intent intent) { 148 | String action = intent.getAction(); 149 | if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) { 150 | BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 151 | String name = device.getName(); 152 | LogUtils.i("发现蓝牙设备连接成功,name:" + name); 153 | if (connectedListener != null) { 154 | connectedListener.onResult(device, true); 155 | } 156 | 157 | } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) { 158 | BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 159 | String name = device.getName(); 160 | LogUtils.w("发现蓝牙设备断开连接,name:" + name); 161 | if (connectedListener != null) { 162 | connectedListener.onResult(device, false); 163 | } 164 | } 165 | } 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/AdStructureParser.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble; 2 | 3 | import com.sjl.deviceconnector.entity.AdStructure; 4 | import com.sjl.deviceconnector.entity.BluetoothScanResult; 5 | import com.sjl.deviceconnector.util.ByteUtils; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | /** 13 | * AdStructure解析器 14 | * 15 | * @author Kelly 16 | * @version 1.0.0 17 | * @filename AdStructureParser 18 | * @time 2023/3/17 21:13 19 | * @copyright(C) 2023 song 20 | */ 21 | public class AdStructureParser { 22 | 23 | /** 24 | * 获取原始数据(16进制字符串) 25 | * @param scanRecordBytes 26 | * @return 27 | */ 28 | public static String getHexRawData(byte[] scanRecordBytes) { 29 | return ByteUtils.byteArrToHexString(getRawData(scanRecordBytes)); 30 | } 31 | 32 | /** 33 | * 获取原始数据(字节数组) 34 | * @param scanRecordBytes 35 | * @return 36 | */ 37 | public static byte[] getRawData(byte[] scanRecordBytes) { 38 | int to = 0; 39 | for (int i = scanRecordBytes.length - 1 ; i >= 0; i--) { 40 | if (scanRecordBytes[i] != 0) { 41 | to = i; 42 | break; 43 | } 44 | } 45 | return Arrays.copyOfRange(scanRecordBytes, 0, to + 1); 46 | } 47 | 48 | /** 49 | * 根据AdStructure规则解析 50 | * 51 | * @param item 52 | * @return 53 | * @see AdStructure 54 | */ 55 | public static List parse(BluetoothScanResult item) { 56 | return parse(item.getScanRecordBytes()); 57 | } 58 | 59 | /** 60 | * 根据AdStructure规则解析 61 | * 62 | * @param scanRecordBytes 63 | * @return 64 | * @see AdStructure 65 | */ 66 | public static List parse(byte[] scanRecordBytes) { 67 | if (scanRecordBytes == null) { 68 | return Collections.EMPTY_LIST; 69 | } 70 | List adStructureList = new ArrayList(); 71 | AdStructure adStructure; 72 | for (int i = 0; i < scanRecordBytes.length; ) { 73 | if (scanRecordBytes.length - i >= 2) { 74 | 75 | byte length = scanRecordBytes[i]; 76 | int from = i + 2; 77 | if (length > 0 && from < scanRecordBytes.length) { 78 | adStructure = new AdStructure(); 79 | byte type = scanRecordBytes[i + 1]; 80 | int endIndex = from + length - 1; 81 | if (endIndex >= scanRecordBytes.length) { 82 | endIndex = scanRecordBytes.length - 1; 83 | } 84 | adStructure.length = length; 85 | adStructure.type = type; 86 | byte[] data = Arrays.copyOfRange(scanRecordBytes, from, endIndex); 87 | adStructure.data = data; 88 | i += length + 1; 89 | adStructureList.add(adStructure); 90 | } else { 91 | break; 92 | } 93 | } else { 94 | break; 95 | } 96 | } 97 | 98 | return adStructureList; 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/BluetoothGattWrap.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble; 2 | 3 | import android.bluetooth.BluetoothGatt; 4 | import android.bluetooth.BluetoothGattCharacteristic; 5 | import android.bluetooth.BluetoothGattService; 6 | import android.os.Build; 7 | 8 | import com.sjl.deviceconnector.util.LogUtils; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.HashMap; 13 | import java.util.LinkedHashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.UUID; 17 | 18 | import androidx.annotation.RequiresApi; 19 | 20 | /** 21 | * TODO 22 | * 23 | * @author Kelly 24 | * @version 1.0.0 25 | * @filename BluetoothGattWrap 26 | * @time 2023/3/4 12:56 27 | * @copyright(C) 2023 song 28 | */ 29 | public class BluetoothGattWrap { 30 | /** 31 | * 服务列表 32 | */ 33 | private List mGattServiceList = new ArrayList<>(); 34 | /** 35 | * 服务id和特征映射列表 36 | */ 37 | private Map> mDeviceProfile = new LinkedHashMap<>(); 38 | 39 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 40 | public void addServices(List services) { 41 | clear(); 42 | mGattServiceList.addAll(services); 43 | Map> newProfiles = new HashMap>(); 44 | 45 | for (BluetoothGattService service : services) { 46 | UUID serviceUUID = service.getUuid(); 47 | Map map = newProfiles.get(serviceUUID); 48 | 49 | if (map == null) { 50 | LogUtils.i("发现 service uuid " + serviceUUID); 51 | map = new HashMap(); 52 | newProfiles.put(service.getUuid(), map); 53 | } 54 | 55 | List characters = service.getCharacteristics(); 56 | 57 | for (BluetoothGattCharacteristic character : characters) { 58 | UUID characterUUID = character.getUuid(); 59 | LogUtils.i("character uuid : " + characterUUID); 60 | map.put(character.getUuid(), character); 61 | } 62 | } 63 | mDeviceProfile.clear(); 64 | mDeviceProfile.putAll(newProfiles); 65 | } 66 | 67 | public void clear() { 68 | mGattServiceList.clear(); 69 | } 70 | 71 | /** 72 | * 根据服务id获取 BluetoothGattService 73 | * 74 | * @param serviceId 75 | * @return 76 | */ 77 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 78 | public BluetoothGattService getService(UUID serviceId) { 79 | if (serviceId == null) { 80 | return null; 81 | } 82 | 83 | List services = getServices(); 84 | for (BluetoothGattService service : services) { 85 | if (service.getUuid().equals(serviceId)) { 86 | return service; 87 | } 88 | } 89 | return null; 90 | } 91 | 92 | /** 93 | * 根据服务id和特征id获取BluetoothGattCharacteristic 94 | * 95 | * @param serviceId 96 | * @param characterId 97 | * @return 98 | */ 99 | public BluetoothGattCharacteristic getCharacter(UUID serviceId, UUID characterId) { 100 | BluetoothGattCharacteristic characteristic = null; 101 | 102 | Map characters = mDeviceProfile.get(serviceId); 103 | if (characters != null) { 104 | characteristic = characters.get(characterId); 105 | } 106 | return characteristic; 107 | } 108 | 109 | /** 110 | * 先根据服务id和特征id获取BluetoothGattCharacteristic,没有再从bluetoothGatt获取 111 | * 112 | * @param serviceId 113 | * @param characterId 114 | * @param bluetoothGatt 115 | * @return 116 | */ 117 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 118 | public BluetoothGattCharacteristic getCharacter(UUID serviceId, UUID characterId, BluetoothGatt bluetoothGatt) { 119 | BluetoothGattCharacteristic characteristic = getCharacter(serviceId, characterId); 120 | if (characteristic == null) { 121 | if (bluetoothGatt != null) { 122 | BluetoothGattService gattService = bluetoothGatt.getService(serviceId); 123 | if (gattService != null) { 124 | characteristic = gattService.getCharacteristic(characterId); 125 | } 126 | } 127 | } 128 | return characteristic; 129 | } 130 | 131 | 132 | public List getServices() { 133 | return mGattServiceList; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/BluetoothLeClient.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble; 2 | 3 | import android.bluetooth.BluetoothGatt; 4 | import android.bluetooth.BluetoothGattCharacteristic; 5 | import android.bluetooth.BluetoothGattDescriptor; 6 | import android.os.Build; 7 | 8 | import com.sjl.deviceconnector.util.LogUtils; 9 | 10 | import java.util.UUID; 11 | 12 | 13 | /** 14 | * Ble客户端实现 15 | * 16 | * @author Kelly 17 | * @version 1.0.0 18 | * @filename BluetoothLeClient 19 | * @time 2023/3/4 13:52 20 | * @copyright(C) 2023 song 21 | */ 22 | public class BluetoothLeClient implements IBluetoothLeClient { 23 | public static final byte[] EMPTY_BYTES = new byte[]{}; 24 | /** 25 | * 系统提供接受通知自带的UUID 26 | */ 27 | public static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); 28 | 29 | private BluetoothGattWrap mBluetoothGattWrap; 30 | private BluetoothGatt mBluetoothGatt; 31 | 32 | public BluetoothLeClient(BluetoothGattWrap mBluetoothGattWrap) { 33 | this.mBluetoothGattWrap = mBluetoothGattWrap; 34 | } 35 | 36 | public void setBluetoothGatt(BluetoothGatt bluetoothGatt) { 37 | this.mBluetoothGatt = bluetoothGatt; 38 | } 39 | 40 | @Override 41 | public boolean readCharacteristic(UUID serviceId, UUID characterId) { 42 | BluetoothGattCharacteristic characteristic = mBluetoothGattWrap.getCharacter(serviceId, characterId); 43 | 44 | if (characteristic == null) { 45 | return false; 46 | } 47 | 48 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 49 | if (!mBluetoothGatt.readCharacteristic(characteristic)) { 50 | return false; 51 | } 52 | } 53 | return true; 54 | } 55 | 56 | @Override 57 | public boolean writeCharacteristic(UUID serviceId, UUID characterId, byte[] value) { 58 | BluetoothGattCharacteristic characteristic = mBluetoothGattWrap.getCharacter(serviceId, characterId); 59 | 60 | if (characteristic == null) { 61 | return false; 62 | } 63 | 64 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 65 | characteristic.setValue(value != null ? value : EMPTY_BYTES); 66 | if (!mBluetoothGatt.writeCharacteristic(characteristic)) { 67 | return false; 68 | } 69 | } 70 | return true; 71 | } 72 | 73 | @Override 74 | public boolean readDescriptor(UUID serviceId, UUID characterId, UUID descriptorId) { 75 | BluetoothGattCharacteristic characteristic = mBluetoothGattWrap.getCharacter(serviceId, characterId); 76 | 77 | if (characteristic == null) { 78 | return false; 79 | } 80 | 81 | BluetoothGattDescriptor gattDescriptor; 82 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { 83 | gattDescriptor = characteristic.getDescriptor(descriptorId); 84 | if (gattDescriptor == null) { 85 | LogUtils.e("gattDescriptor为空"); 86 | return false; 87 | } 88 | if (!mBluetoothGatt.readDescriptor(gattDescriptor)) { 89 | return false; 90 | } 91 | } 92 | 93 | return true; 94 | } 95 | 96 | @Override 97 | public boolean writeDescriptor(UUID serviceId, UUID characterId, UUID descriptorId, byte[] value) { 98 | BluetoothGattCharacteristic characteristic = mBluetoothGattWrap.getCharacter(serviceId, characterId); 99 | 100 | if (characteristic == null) { 101 | return false; 102 | } 103 | 104 | BluetoothGattDescriptor gattDescriptor; 105 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { 106 | gattDescriptor = characteristic.getDescriptor(descriptorId); 107 | if (gattDescriptor == null) { 108 | LogUtils.e("gattDescriptor为空"); 109 | return false; 110 | } 111 | gattDescriptor.setValue(value != null ? value : EMPTY_BYTES); 112 | if (!mBluetoothGatt.writeDescriptor(gattDescriptor)) { 113 | return false; 114 | } 115 | } 116 | return true; 117 | } 118 | 119 | @Override 120 | public boolean setCharacteristicNotification(UUID serviceId, UUID characterId, boolean enable) { 121 | BluetoothGattCharacteristic characteristic = mBluetoothGattWrap.getCharacter(serviceId, characterId); 122 | if (characteristic == null) { 123 | return false; 124 | } 125 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 126 | if (!mBluetoothGatt.setCharacteristicNotification(characteristic, enable)) { 127 | LogUtils.e("setCharacteristicNotification失败"); 128 | return false; 129 | } 130 | BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG); 131 | 132 | if (descriptor == null) { 133 | LogUtils.e("获取自带的descriptor为空"); 134 | return false; 135 | } 136 | 137 | byte[] value = enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE; 138 | 139 | if (!descriptor.setValue(value)) { 140 | return false; 141 | } 142 | 143 | if (!mBluetoothGatt.writeDescriptor(descriptor)) { 144 | return false; 145 | } 146 | } 147 | 148 | return true; 149 | } 150 | 151 | @Override 152 | public boolean setCharacteristicIndication(UUID serviceId, UUID characterId, boolean enable) { 153 | BluetoothGattCharacteristic characteristic = mBluetoothGattWrap.getCharacter(serviceId, characterId); 154 | if (characteristic == null) { 155 | return false; 156 | } 157 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 158 | if (!mBluetoothGatt.setCharacteristicNotification(characteristic, enable)) { 159 | LogUtils.e("setCharacteristicNotification失败"); 160 | return false; 161 | } 162 | BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG); 163 | 164 | if (descriptor == null) { 165 | LogUtils.e("获取自带的descriptor为空"); 166 | return false; 167 | } 168 | 169 | byte[] value = enable ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE; 170 | 171 | if (!descriptor.setValue(value)) { 172 | return false; 173 | } 174 | 175 | if (!mBluetoothGatt.writeDescriptor(descriptor)) { 176 | return false; 177 | } 178 | } 179 | 180 | return true; 181 | } 182 | 183 | @Override 184 | public boolean readRemoteRssi() { 185 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 186 | if (!mBluetoothGatt.readRemoteRssi()) { 187 | return false; 188 | } 189 | } 190 | return true; 191 | } 192 | 193 | @Override 194 | public boolean requestMtu(int mtu) { 195 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 196 | if (!mBluetoothGatt.requestMtu(mtu)) { 197 | return false; 198 | } 199 | } 200 | return true; 201 | } 202 | 203 | 204 | } 205 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/BluetoothLeNotifyListener.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * TODO 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename BluetoothLeNotifyListener 11 | * @time 2023/3/4 17:59 12 | * @copyright(C) 2023 song 13 | */ 14 | public interface BluetoothLeNotifyListener { 15 | void onNotify(UUID serviceId, UUID characterId, byte[] value); 16 | } 17 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/BluetoothLeServiceListener.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble; 2 | 3 | import android.bluetooth.BluetoothGattService; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 服务发现回调 9 | * 10 | * @author Kelly 11 | * @version 1.0.0 12 | * @filename BluetoothLeServiceListener 13 | * @time 2023/5/17 14:28 14 | * @copyright(C) 2023 song 15 | */ 16 | public interface BluetoothLeServiceListener { 17 | /** 18 | * 服务发现回调 19 | * @param status 0成功时,bluetoothGattServices有值 20 | * @param bluetoothGattServices 21 | */ 22 | void onServicesDiscovered(int status, List bluetoothGattServices); 23 | } 24 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/IBluetoothLeClient.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * ble客户端接口 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename IBluetoothLeClient 11 | * @time 2023/3/4 10:00 12 | * @copyright(C) 2023 song 13 | */ 14 | public interface IBluetoothLeClient { 15 | 16 | 17 | /** 18 | * 读取特征值 19 | * 20 | * @param serviceId 21 | * @param characterId 22 | * @return 23 | */ 24 | boolean readCharacteristic(UUID serviceId, UUID characterId); 25 | 26 | /** 27 | * 写入特征值 28 | * 29 | * @param serviceId 30 | * @param characterId 31 | * @param value 32 | * @return 33 | */ 34 | boolean writeCharacteristic(UUID serviceId, UUID characterId, byte[] value); 35 | 36 | /** 37 | * 读取描述 38 | * 39 | * @param serviceId 40 | * @param characterId 41 | * @param descriptorId 42 | * @return 43 | */ 44 | boolean readDescriptor(UUID serviceId, UUID characterId, UUID descriptorId); 45 | 46 | /** 47 | * 写入描述 48 | * 49 | * @param serviceId 50 | * @param characterId 51 | * @param descriptorId 52 | * @param value 53 | * @return 54 | */ 55 | boolean writeDescriptor(UUID serviceId, UUID characterId, UUID descriptorId, byte[] value); 56 | 57 | 58 | /** 59 | * 设置特征通知 60 | * 61 | * @param serviceId 62 | * @param characterId 63 | * @param enable 64 | * @return 65 | */ 66 | boolean setCharacteristicNotification(UUID serviceId, UUID characterId, boolean enable); 67 | 68 | /** 69 | * 设置特征知识 70 | * 71 | * @param serviceId 72 | * @param characterId 73 | * @param enable 74 | * @return 75 | */ 76 | boolean setCharacteristicIndication(UUID serviceId, UUID characterId, boolean enable); 77 | 78 | /** 79 | * 读取Rssi 80 | * 81 | * @return 82 | */ 83 | boolean readRemoteRssi(); 84 | 85 | /** 86 | * 设置mtu 87 | * 88 | * @param mtu 89 | * @return 90 | */ 91 | boolean requestMtu(int mtu); 92 | 93 | } 94 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/request/BluetoothLeRequest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.request; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * Ble请求基类 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename BluetoothLeRequest 11 | * @time 2023/3/4 12:15 12 | * @copyright(C) 2023 song 13 | */ 14 | public class BluetoothLeRequest { 15 | 16 | 17 | /** 18 | * 通讯的服务id 19 | */ 20 | private UUID service; 21 | /** 22 | * 通讯的特征id 23 | */ 24 | private UUID character; 25 | 26 | 27 | public UUID getService() { 28 | return service; 29 | } 30 | 31 | public void setService(UUID service) { 32 | this.service = service; 33 | } 34 | 35 | public UUID getCharacter() { 36 | return character; 37 | } 38 | 39 | public void setCharacter(UUID character) { 40 | this.character = character; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/request/CharacteristicReadRequest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.request; 2 | 3 | /** 4 | * 特征读请求 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename CharacteristicReadRequest 9 | * @time 2023/3/4 12:41 10 | * @copyright(C) 2023 song 11 | */ 12 | public class CharacteristicReadRequest extends BluetoothLeRequest { 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/request/CharacteristicWriteRequest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.request; 2 | 3 | /** 4 | * 特征写请求 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename CharacteristicWriteRequest 9 | * @time 2023/3/4 12:41 10 | * @copyright(C) 2023 song 11 | */ 12 | public class CharacteristicWriteRequest extends BluetoothLeRequest { 13 | 14 | 15 | /** 16 | * 待发送的数据 17 | */ 18 | private byte[] bytes; 19 | 20 | public byte[] getBytes() { 21 | return bytes; 22 | } 23 | 24 | public void setBytes(byte[] bytes) { 25 | this.bytes = bytes; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/request/DescriptorReadRequest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.request; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * 描述符读请求 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename DescriptorReadRequest 11 | * @time 2023/3/4 14:19 12 | * @copyright(C) 2023 song 13 | */ 14 | public class DescriptorReadRequest extends BluetoothLeRequest { 15 | 16 | /** 17 | * 通讯的描述符id 18 | */ 19 | private UUID descriptor; 20 | 21 | 22 | public UUID getDescriptor() { 23 | return descriptor; 24 | } 25 | 26 | public void setDescriptor(UUID descriptor) { 27 | this.descriptor = descriptor; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/request/DescriptorWriteRequest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.request; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * 描述符写请求 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename DescriptorWriteRequest 11 | * @time 2023/3/4 12:41 12 | * @copyright(C) 2023 song 13 | */ 14 | public class DescriptorWriteRequest extends BluetoothLeRequest { 15 | /** 16 | * 通讯的描述符id 17 | */ 18 | private UUID descriptor; 19 | /** 20 | * 待发送的数据 21 | */ 22 | private byte[] bytes; 23 | 24 | public byte[] getBytes() { 25 | return bytes; 26 | } 27 | 28 | public void setBytes(byte[] bytes) { 29 | this.bytes = bytes; 30 | } 31 | 32 | public UUID getDescriptor() { 33 | return descriptor; 34 | } 35 | 36 | public void setDescriptor(UUID descriptor) { 37 | this.descriptor = descriptor; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/request/IndicateRequest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.request; 2 | 3 | /** 4 | * 特性值指示请求 5 | *

indicate:译为“指示”,它是服务器给客户端发送数据的方式。它在使用上比notify多一个应答的步骤。

6 | * 7 | * @author Kelly 8 | * @version 1.0.0 9 | * @filename IndicateRequest 10 | * @time 2023/3/4 14:32 11 | * @copyright(C) 2023 song 12 | */ 13 | public class IndicateRequest extends BluetoothLeRequest { 14 | /** 15 | * true开启通知,false关闭 16 | */ 17 | private boolean enable; 18 | 19 | public boolean isEnable() { 20 | return enable; 21 | } 22 | 23 | public void setEnable(boolean enable) { 24 | this.enable = enable; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/request/MtuRequest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.request; 2 | 3 | /** 4 | * mtu设置请求 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename MtuRequest 9 | * @time 2023/3/4 14:33 10 | * @copyright(C) 2023 song 11 | */ 12 | public class MtuRequest extends BluetoothLeRequest { 13 | private int mtu; 14 | 15 | public int getMtu() { 16 | return mtu; 17 | } 18 | 19 | /** 20 | * 21 | * 设置MTU,不同的蓝牙版本最大MTU不同,蓝牙4.2的最大MTU=247Byte,蓝牙5.0的最大MTU=512Byte。 22 | *

蓝牙一般默认支持的MTU长度是23个字节,一有效的最大MTU还需要减去协议Byte、Opcode和Handler,实际传输数据就是20字节。

23 | * 24 | *

通过gatt.requestMtu(mtu)。会触发onMtuChanged回调。这里mtu 的范围在23 ~ 512之间,目前市面上Android版本高的手机(支持蓝牙5.0)基本上都是247。也就是设置mtu = 512,回调中的mtu可能还是247,真本事因为手机做了限制

25 | * 26 | * 27 | * @param mtu 23字节~512字节 28 | */ 29 | public void setMtu(int mtu) { 30 | this.mtu = mtu; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/request/NotifyRequest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.request; 2 | 3 | /** 4 | * 特性值通知请求(单向) 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename NotifyRequest 9 | * @time 2023/3/4 14:33 10 | * @copyright(C) 2023 song 11 | */ 12 | public class NotifyRequest extends BluetoothLeRequest { 13 | /** 14 | * true开启通知,false关闭 15 | */ 16 | private boolean enable; 17 | 18 | public boolean isEnable() { 19 | return enable; 20 | } 21 | 22 | public void setEnable(boolean enable) { 23 | this.enable = enable; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/request/RemoteRssiRequest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.request; 2 | 3 | /** 4 | * 远程Rssi请求 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename RemoteRssiRequest 9 | * @time 2023/3/4 14:33 10 | * @copyright(C) 2023 song 11 | */ 12 | public class RemoteRssiRequest extends BluetoothLeRequest { 13 | } 14 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/ble/response/BluetoothLeResponse.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.ble.response; 2 | 3 | import java.util.Arrays; 4 | import java.util.UUID; 5 | 6 | /** 7 | * Ble响应 8 | * 9 | * @author Kelly 10 | * @version 1.0.0 11 | * @filename BluetoothLeResponse 12 | * @time 2023/3/4 15:18 13 | * @copyright(C) 2023 song 14 | */ 15 | public class BluetoothLeResponse { 16 | private int code; 17 | private byte[] data; 18 | private int rssi = -1; 19 | private int mtu = -1; 20 | 21 | public int getCode() { 22 | return code; 23 | } 24 | 25 | public void setCode(int code) { 26 | this.code = code; 27 | } 28 | 29 | public byte[] getData() { 30 | return data; 31 | } 32 | 33 | public void setData(byte[] data) { 34 | this.data = data; 35 | } 36 | 37 | public int getRssi() { 38 | return rssi; 39 | } 40 | 41 | public void setRssi(int rssi) { 42 | this.rssi = rssi; 43 | } 44 | 45 | public int getMtu() { 46 | return mtu; 47 | } 48 | 49 | public void setMtu(int mtu) { 50 | this.mtu = mtu; 51 | } 52 | 53 | public void copy(BluetoothLeResponse buffer) { 54 | this.code = buffer.code; 55 | this.data = buffer.data; 56 | this.rssi = buffer.rssi; 57 | this.mtu = buffer.mtu; 58 | 59 | } 60 | 61 | public void reset() { 62 | this.code = 0; 63 | this.data = null; 64 | this.rssi = -1; 65 | this.mtu = -1; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "{" + 71 | "code=" + code + 72 | ", data=" + Arrays.toString(data) + 73 | ", rssi=" + rssi + 74 | ", mtu=" + mtu + 75 | '}'; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/scanner/AbstractBluetoothScanner.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.scanner; 2 | 3 | import com.sjl.deviceconnector.entity.BluetoothScanResult; 4 | import com.sjl.deviceconnector.listener.BluetoothScanListener; 5 | 6 | /** 7 | * TODO 8 | * 9 | * @author Kelly 10 | * @version 1.0.0 11 | * @filename AbstractBluetoothScanner 12 | * @time 2023/3/3 14:58 13 | * @copyright(C) 2023 song 14 | */ 15 | public abstract class AbstractBluetoothScanner implements BluetoothScanner{ 16 | 17 | protected BluetoothScanListener bluetoothScanListener; 18 | protected String address; 19 | 20 | 21 | 22 | /** 23 | * 扫描监听 24 | * @param bluetoothScanListener 25 | */ 26 | public void setBluetoothScanListener(BluetoothScanListener bluetoothScanListener) { 27 | this.bluetoothScanListener = bluetoothScanListener; 28 | } 29 | 30 | public void setAddress(String address) { 31 | this.address = address; 32 | } 33 | 34 | 35 | public void removeListener(){ 36 | this.address = null; 37 | this.bluetoothScanListener = null; 38 | } 39 | 40 | protected void notifyDeviceFounded(BluetoothScanResult device) { 41 | if (bluetoothScanListener != null) { 42 | bluetoothScanListener.onDeviceFound(device); 43 | } 44 | } 45 | 46 | public void notifyScanFinish() { 47 | if (bluetoothScanListener != null) { 48 | bluetoothScanListener.onScanFinish(); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/scanner/BluetoothClassicScanner.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.scanner; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothDevice; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.text.TextUtils; 10 | 11 | import com.sjl.deviceconnector.DeviceContext; 12 | import com.sjl.deviceconnector.entity.BluetoothScanResult; 13 | import com.sjl.deviceconnector.listener.ReceiverObservable; 14 | import com.sjl.deviceconnector.util.BluetoothUtils; 15 | import com.sjl.deviceconnector.util.LogUtils; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | * 经典蓝牙扫描 21 | * 22 | * @author Kelly 23 | * @version 1.0.0 24 | * @filename BluetoothClassicScanner 25 | * @time 2023/3/3 14:47 26 | * @copyright(C) 2023 song 27 | */ 28 | public class BluetoothClassicScanner extends AbstractBluetoothScanner implements ReceiverObservable { 29 | private BroadcastReceiver mBroadcastReceiver; 30 | 31 | @Override 32 | public void startScan() { 33 | List bondedDevices = BluetoothUtils.getBondedDevices(); 34 | for (BluetoothDevice bluetoothDevice : bondedDevices) { 35 | if (bluetoothDevice.getAddress().equals(address)) { 36 | notifyDeviceFounded(new BluetoothScanResult(bluetoothDevice,-1, (byte[]) null)); 37 | notifyScanFinish(); 38 | break; 39 | } 40 | } 41 | registerReceiver(); 42 | BluetoothAdapter bluetoothAdapter = BluetoothUtils.getBluetoothAdapter(); 43 | if (bluetoothAdapter.isDiscovering()) { 44 | bluetoothAdapter.cancelDiscovery(); 45 | } 46 | bluetoothAdapter.startDiscovery(); 47 | } 48 | 49 | @Override 50 | public void stopScan() { 51 | unregisterReceiver(); 52 | BluetoothAdapter bluetoothAdapter = BluetoothUtils.getBluetoothAdapter(); 53 | if (bluetoothAdapter != null && bluetoothAdapter.isDiscovering()) { 54 | bluetoothAdapter.cancelDiscovery(); 55 | } 56 | notifyScanFinish(); 57 | removeListener(); 58 | } 59 | 60 | 61 | 62 | @Override 63 | public void registerReceiver() { 64 | Context context = DeviceContext.getContext(); 65 | IntentFilter intent = new IntentFilter(); 66 | intent.addAction(BluetoothDevice.ACTION_FOUND);// 搜索蓝压设备,每搜到一个设备发送一条广播 67 | intent.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);// 扫描完成 68 | intent.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); 69 | intent.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 70 | // intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);// 配对开始时,配对成功时 71 | // intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);// 搜索模式改变 72 | // intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);// 蓝牙开关状态 73 | mBroadcastReceiver = new ScanBroadcastReceiver(); 74 | context.registerReceiver(mBroadcastReceiver, intent); 75 | } 76 | 77 | 78 | @Override 79 | public void unregisterReceiver() { 80 | Context context = DeviceContext.getContext(); 81 | if (mBroadcastReceiver != null) { 82 | context.unregisterReceiver(mBroadcastReceiver); 83 | mBroadcastReceiver = null; 84 | } 85 | address = null; 86 | } 87 | 88 | 89 | private final class ScanBroadcastReceiver extends BroadcastReceiver { 90 | @Override 91 | public void onReceive(Context context, Intent intent) { 92 | String action = intent.getAction(); 93 | if (BluetoothDevice.ACTION_FOUND.equals(action)) { 94 | BluetoothDevice device = (BluetoothDevice) intent.getExtras().get(BluetoothDevice.EXTRA_DEVICE); 95 | int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); 96 | 97 | LogUtils.i("BluetoothDevice:" + device.getName() + ",address:" + device.getAddress()); 98 | notifyDeviceFounded(new BluetoothScanResult(device, rssi, (byte[]) null)); 99 | 100 | if (!TextUtils.isEmpty(address) && address.equals(device.getAddress())) { 101 | stopScan(); 102 | } 103 | } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { 104 | LogUtils.w("蓝牙扫描完毕"); 105 | notifyScanFinish(); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/scanner/BluetoothLowEnergyScanner.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.scanner; 2 | 3 | 4 | import android.annotation.SuppressLint; 5 | import android.bluetooth.BluetoothAdapter; 6 | import android.bluetooth.BluetoothDevice; 7 | import android.bluetooth.le.BluetoothLeScanner; 8 | import android.bluetooth.le.ScanCallback; 9 | import android.bluetooth.le.ScanResult; 10 | import android.bluetooth.le.ScanSettings; 11 | import android.os.Build; 12 | import android.text.TextUtils; 13 | 14 | import com.sjl.deviceconnector.entity.BluetoothScanResult; 15 | import com.sjl.deviceconnector.util.BluetoothUtils; 16 | import com.sjl.deviceconnector.util.LogUtils; 17 | 18 | import java.util.List; 19 | 20 | 21 | /** 22 | * ble扫描 23 | *

Android4.3开始,开始支持BLE功能,但只支持Central Mode(中心模式,可连接外围设备)

24 | *

Android5.0开始,开始支持Peripheral Mode(外围模式,可被中心设备连接)

25 | * 26 | * @author Kelly 27 | * @version 1.0.0 28 | * @filename BluetoothLowEnergyScanner 29 | * @time 2023/3/3 14:49 30 | * @copyright(C) 2023 song 31 | */ 32 | public class BluetoothLowEnergyScanner extends AbstractBluetoothScanner { 33 | 34 | 35 | @Override 36 | public void startScan() { 37 | //android 5.0后 38 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 39 | BluetoothLeScanner bluetoothLeScanner = BluetoothUtils.getBluetoothLeScanner(); 40 | //创建ScanSettings的build对象用于设置参数 41 | /*ScanSettings.Builder builder = new ScanSettings.Builder() 42 | //设置低功耗模式 43 | .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY); 44 | //android 6.0添加设置回调类型、匹配模式等 45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 46 | //定义回调类型 47 | builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES); 48 | //设置蓝牙LE扫描滤波器硬件匹配的匹配模式 49 | builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY); 50 | } 51 | 52 | //芯片组支持批处理芯片上的扫描 53 | if (BluetoothUtils.getBluetoothAdapter().isOffloadedScanBatchingSupported()) { 54 | //不开启批处理扫描模式,即不回调onBatchScanResults 55 | builder.setReportDelay(0L); 56 | } 57 | ScanSettings scanSettings = builder.build();*/ 58 | bluetoothLeScanner.startScan(mScanCallback); 59 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 60 | BluetoothUtils.getBluetoothAdapter().startLeScan(mLeScanCallback); 61 | } else { 62 | throw new RuntimeException("系统版本过低,不支持ble,当前系统版本为:" + Build.VERSION.SDK_INT); 63 | } 64 | 65 | } 66 | 67 | @Override 68 | public void stopScan() { 69 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 70 | BluetoothLeScanner bluetoothLeScanner = BluetoothUtils.getBluetoothLeScanner(); 71 | if (bluetoothLeScanner != null){ 72 | bluetoothLeScanner.stopScan(mScanCallback); 73 | } 74 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 75 | BluetoothAdapter bluetoothAdapter = BluetoothUtils.getBluetoothAdapter(); 76 | if (bluetoothAdapter != null){ 77 | bluetoothAdapter.stopLeScan(mLeScanCallback); 78 | } 79 | 80 | } 81 | notifyScanFinish(); 82 | removeListener(); 83 | } 84 | 85 | /** 86 | * 5.0以上扫描回调 87 | */ 88 | @SuppressLint("NewApi") 89 | private final ScanCallback mScanCallback = new ScanCallback() { 90 | 91 | @Override 92 | public void onScanResult(int callbackType, ScanResult result) { 93 | BluetoothScanResult bluetoothScanResult = new BluetoothScanResult(result.getDevice(), result.getRssi(), result.getScanRecord()); 94 | bluetoothScanResult.setScanRecordBytes(result.getScanRecord().getBytes()); 95 | 96 | notifyDeviceFounded(bluetoothScanResult); 97 | 98 | if (!TextUtils.isEmpty(address) && address.equals(bluetoothScanResult.getAddress())) { 99 | stopScan(); 100 | } 101 | } 102 | 103 | @Override 104 | public void onBatchScanResults(List results) { 105 | LogUtils.e("onBatchScanResults:" + results.size()); 106 | } 107 | 108 | @Override 109 | public void onScanFailed(int errorCode) { 110 | LogUtils.e("扫描失败errorCode:" + errorCode); 111 | } 112 | 113 | }; 114 | 115 | /** 116 | * 4.3-5.0以下扫描回调 117 | */ 118 | @SuppressLint("NewApi") 119 | private final BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { 120 | 121 | @Override 122 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { 123 | BluetoothScanResult bluetoothScanResult = new BluetoothScanResult(device, rssi, scanRecord); 124 | 125 | notifyDeviceFounded(bluetoothScanResult); 126 | 127 | if (!TextUtils.isEmpty(address) && address.equals(bluetoothScanResult.getAddress())) { 128 | stopScan(); 129 | } 130 | } 131 | 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/bluetooth/scanner/BluetoothScanner.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.bluetooth.scanner; 2 | 3 | import com.sjl.deviceconnector.listener.BluetoothScanListener; 4 | 5 | /** 6 | * TODO 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename BluetoothScanner 11 | * @time 2023/3/3 14:33 12 | * @copyright(C) 2023 song 13 | */ 14 | public interface BluetoothScanner { 15 | 16 | /** 17 | * 开始扫描 18 | */ 19 | void startScan(); 20 | 21 | /** 22 | * 停止扫描 23 | */ 24 | void stopScan(); 25 | } 26 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/device/serialport/SerialPortHelper.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.device.serialport; 2 | 3 | import android.serialport.SerialPortFinder; 4 | 5 | import com.sjl.deviceconnector.util.LogUtils; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | /** 12 | * 串口辅助类 13 | * 14 | * @author Kelly 15 | * @version 1.0.0 16 | * @filename SerialPortHelper 17 | * @time 2022/7/25 11:56 18 | * @copyright(C) 2022 song 19 | */ 20 | public class SerialPortHelper { 21 | private static List deviceList = new ArrayList<>(); 22 | 23 | /** 24 | * 判断串口是否存在(打开不存在的串口导致阻塞卡死) 25 | * @param devicePath 26 | * @return 27 | */ 28 | public static boolean isExistDevice(String devicePath) { 29 | getDeviceList(); 30 | return deviceList.contains(devicePath); 31 | } 32 | 33 | /** 34 | * 获取串口设备列表 35 | * @return 36 | */ 37 | public static List getDeviceList() { 38 | if (deviceList == null || deviceList.size() == 0){ 39 | SerialPortFinder serialPortFinder = new SerialPortFinder(); 40 | String[] allDevicesPath = serialPortFinder.getAllDevicesPath(); 41 | deviceList.addAll(Arrays.asList(allDevicesPath)); 42 | LogUtils.i("SerialPortDevicesPath:" + Arrays.asList(allDevicesPath).toString()); 43 | } 44 | return deviceList; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/entity/AdStructure.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.entity; 2 | 3 | import com.sjl.deviceconnector.util.ByteUtils; 4 | 5 | /** 6 | * 广播数据单元 7 | * 8 | *

广播包中包含若干个广播数据单元,广播数据单元也称为 AD Structure。

9 | *

广播数据单元 = 长度值Length + AD type + AD Data。

10 | *

长度值Length只占一个字节,并且位于广播数据单元的第一个字节;AD type也是一个字节;AD Data长度为Length-1

11 | * 12 | * @author Kelly 13 | * @version 1.0.0 14 | * @filename AdStructure 15 | * @time 2023/3/12 20:28 16 | * @copyright(C) 2023 song 17 | */ 18 | public class AdStructure { 19 | /** 20 | * 广播中声明的长度 21 | */ 22 | public int length; 23 | 24 | /** 25 | * 广播中声明的type 26 | *

type = 0x01 表示设备LE物理连接。

27 | *

type = 0x09 表示设备的全名(转为ASCII码)

> 28 | *

type = 0x03 表示完整的16bit UUID。

29 | *

type = 0xFF 表示厂商数据。前两个字节表示厂商ID,后面的为厂商数据,具体由用户自行定义。

30 | *

type = 0x16 结合Type = 0x03看,表示16 bit UUID对应的数据,具体由用户自行定义。

31 | * 32 | */ 33 | public int type; 34 | 35 | /** 36 | * 广播中的数据部分 37 | */ 38 | public byte[] data; 39 | 40 | 41 | @Override 42 | public String toString() { 43 | return "AdStructure{" + 44 | "length=0x" +String.format("%02X", length & 0xff) +"->"+length+ 45 | ", type=0x" + String.format("%02X", type & 0xff) +"->"+type+ 46 | ", data=0x" + ByteUtils.byteArrToHexString(data) + 47 | '}'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/entity/BluetoothScanResult.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.entity; 2 | 3 | import android.bluetooth.BluetoothDevice; 4 | import android.bluetooth.le.ScanRecord; 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | import android.text.TextUtils; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * TODO 13 | * 14 | * @author Kelly 15 | * @version 1.0.0 16 | * @filename BluetoothScanResult 17 | * @time 2023/3/3 14:52 18 | * @copyright(C) 2023 song 19 | */ 20 | public class BluetoothScanResult { 21 | 22 | private BluetoothDevice device; 23 | 24 | /** 25 | * 可理解成设备的信号值。该数值是一个负数,越大则信号越强。 26 | */ 27 | private int rssi; 28 | 29 | /** 30 | * 远程设备提供的广播数据的内容。 31 | */ 32 | private byte[] scanRecordBytes; 33 | /** 34 | * 远程设备提供的广播数据的内容。 35 | */ 36 | private ScanRecord scanRecord; 37 | 38 | 39 | 40 | public BluetoothScanResult(BluetoothDevice device, int rssi, byte[] scanRecordBytes) { 41 | this.device = device; 42 | this.rssi = rssi; 43 | this.scanRecordBytes = scanRecordBytes; 44 | } 45 | 46 | public BluetoothScanResult(BluetoothDevice device, int rssi, ScanRecord scanRecord) { 47 | this.device = device; 48 | this.rssi = rssi; 49 | this.scanRecord = scanRecord; 50 | } 51 | 52 | public BluetoothDevice getDevice() { 53 | return device; 54 | } 55 | 56 | public void setDevice(BluetoothDevice device) { 57 | this.device = device; 58 | } 59 | 60 | public String getName() { 61 | String name = getDevice().getName(); 62 | return TextUtils.isEmpty(name) || TextUtils.equals("null", name) ? "Unknown" : name; 63 | } 64 | 65 | public String getAddress() { 66 | String address = getDevice().getAddress(); 67 | return TextUtils.isEmpty(address) ? "--" : address; 68 | } 69 | 70 | 71 | public int getBondState() { 72 | return getDevice().getBondState(); 73 | } 74 | 75 | public int getRssi() { 76 | return rssi; 77 | } 78 | 79 | public void setRssi(int rssi) { 80 | this.rssi = rssi; 81 | } 82 | 83 | public byte[] getScanRecordBytes() { 84 | return scanRecordBytes; 85 | } 86 | 87 | public void setScanRecordBytes(byte[] scanRecordBytes) { 88 | this.scanRecordBytes = scanRecordBytes; 89 | } 90 | 91 | public ScanRecord getScanRecord() { 92 | return scanRecord; 93 | } 94 | 95 | public void setScanRecord(ScanRecord scanRecord) { 96 | this.scanRecord = scanRecord; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/entity/SerialPortConfig.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.entity; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * 串口配置 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename SerialPortConfig 11 | * @time 2020/11/9 15:59 12 | * @copyright(C) 2020 song 13 | */ 14 | public class SerialPortConfig { 15 | 16 | private File device; 17 | private int baudRate; 18 | private int dataBits; 19 | private int parity; 20 | private int stopBits; 21 | private int flags; 22 | 23 | public SerialPortConfig(Builder builder) { 24 | this.device = builder.device; 25 | this.baudRate = builder.baudRate; 26 | this.dataBits = builder.dataBits; 27 | this.parity = builder.parity; 28 | this.stopBits = builder.stopBits; 29 | this.flags = builder.flags; 30 | } 31 | 32 | public File getDevice() { 33 | return device; 34 | } 35 | 36 | public int getBaudRate() { 37 | return baudRate; 38 | } 39 | 40 | public int getDataBits() { 41 | return dataBits; 42 | } 43 | 44 | public int getParity() { 45 | return parity; 46 | } 47 | 48 | public int getStopBits() { 49 | return stopBits; 50 | } 51 | 52 | public int getFlags() { 53 | return flags; 54 | } 55 | 56 | public static Builder newBuilder(int baudrate) { 57 | return new Builder(baudrate); 58 | } 59 | public static Builder newBuilder(File device, int baudrate) { 60 | return new Builder(device, baudrate); 61 | } 62 | 63 | public static Builder newBuilder(String devicePath, int baudrate) { 64 | return new Builder(devicePath, baudrate); 65 | } 66 | 67 | public final static class Builder { 68 | 69 | private File device; 70 | private int baudRate; 71 | private int dataBits = 8; 72 | private int parity = 0; 73 | private int stopBits = 1; 74 | private int flags = 0; 75 | 76 | private Builder(int baudRate) { 77 | this.baudRate = baudRate; 78 | } 79 | 80 | private Builder(File device, int baudRate) { 81 | this.device = device; 82 | this.baudRate = baudRate; 83 | } 84 | 85 | private Builder(String devicePath, int baudrate) { 86 | this(new File(devicePath), baudrate); 87 | } 88 | 89 | /** 90 | * 波特率 91 | * 92 | * @param baudRate 93 | * @return 94 | */ 95 | public Builder baudRate(int baudRate) { 96 | this.baudRate = baudRate; 97 | return this; 98 | } 99 | 100 | /** 101 | * 数据位 102 | * 103 | * @param dataBits 默认8,可选值为5~8 104 | * @return 105 | */ 106 | public Builder dataBits(int dataBits) { 107 | this.dataBits = dataBits; 108 | return this; 109 | } 110 | 111 | /** 112 | * 校验位 113 | * 114 | * @param parity 0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN) 115 | * @return 116 | */ 117 | public Builder parity(int parity) { 118 | this.parity = parity; 119 | return this; 120 | } 121 | 122 | /** 123 | * 停止位 124 | * 125 | * @param stopBits 默认1;1:1位停止位;2:2位停止位 126 | * @return 127 | */ 128 | public Builder stopBits(int stopBits) { 129 | this.stopBits = stopBits; 130 | return this; 131 | } 132 | 133 | /** 134 | * 标志 135 | * 136 | * @param flags 默认0 137 | * @return 138 | */ 139 | public Builder flags(int flags) { 140 | this.flags = flags; 141 | return this; 142 | } 143 | 144 | 145 | public SerialPortConfig build(){ 146 | return new SerialPortConfig(this); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/exception/ProviderTimeoutException.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.exception; 2 | 3 | /** 4 | * 连接提供者超时异常处理 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename ProviderTimeoutException 9 | * @time 2022/7/23 11:27 10 | * @copyright(C) 2022 song 11 | */ 12 | public class ProviderTimeoutException extends RuntimeException{ 13 | 14 | public ProviderTimeoutException() { 15 | } 16 | 17 | public ProviderTimeoutException(String message) { 18 | super(message); 19 | } 20 | 21 | public ProviderTimeoutException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/listener/BluetoothScanListener.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.listener; 2 | 3 | import android.bluetooth.BluetoothDevice; 4 | 5 | import com.sjl.deviceconnector.entity.BluetoothScanResult; 6 | 7 | /** 8 | * 蓝牙扫描监听 9 | * 10 | * @author Kelly 11 | * @version 1.0.0 12 | * @filename BluetoothScanListener 13 | * @time 2022/8/4 16:26 14 | * @copyright(C) 2022 song 15 | */ 16 | public interface BluetoothScanListener { 17 | void onDeviceFound(BluetoothScanResult bluetoothScanResult); 18 | 19 | void onScanFinish(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/listener/ConnectedListener.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.listener; 2 | 3 | 4 | /** 5 | * 设备连接状态监听 6 | * 7 | * @author Kelly 8 | * @version 1.0.0 9 | * @filename ConnectedListener 10 | * @time 2022/8/4 16:14 11 | * @copyright(C) 2022 song 12 | */ 13 | public interface ConnectedListener { 14 | 15 | void onResult(T device,boolean connected); 16 | } 17 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/listener/ReceiverObservable.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.listener; 2 | 3 | /** 4 | * 广播观察者 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename ReceiverObservable 9 | * @time 2022/8/4 16:19 10 | * @copyright(C) 2022 song 11 | */ 12 | public interface ReceiverObservable { 13 | /** 14 | * 注册广播 15 | */ 16 | void registerReceiver(); 17 | 18 | /** 19 | * 反注册广播 20 | */ 21 | void unregisterReceiver(); 22 | } 23 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/listener/UsbPermissionListener.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.listener; 2 | 3 | import android.hardware.usb.UsbDevice; 4 | 5 | /** 6 | * USB设备权限申请回调 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename UsbPermissionListener 11 | * @time 2022/6/7 14:31 12 | * @copyright(C) 2022 song 13 | */ 14 | public interface UsbPermissionListener { 15 | 16 | void onGranted(UsbDevice usbDevice); 17 | 18 | void onDenied(UsbDevice usbDevice); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/listener/UsbPlugListener.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.listener; 2 | 3 | import android.hardware.usb.UsbDevice; 4 | 5 | /** 6 | * USB设备插拔回调 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename UsbPlugListener 11 | * @time 2022/6/9 11:51 12 | * @copyright(C) 2022 song 13 | */ 14 | public interface UsbPlugListener { 15 | 16 | void onAttached(UsbDevice usbDevice); 17 | 18 | void onDetached(UsbDevice usbDevice); 19 | } 20 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/provider/BaseConnectProvider.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.provider; 2 | 3 | 4 | import android.util.Printer; 5 | 6 | import com.sjl.deviceconnector.ErrorCode; 7 | import com.sjl.deviceconnector.util.LogUtils; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.Closeable; 11 | import java.io.InputStream; 12 | import java.net.SocketTimeoutException; 13 | 14 | /** 15 | * 通用方法基类 16 | * 17 | * @author Kelly 18 | */ 19 | public abstract class BaseConnectProvider implements IConnectProvider { 20 | 21 | /** 22 | * 报文打印接口 23 | */ 24 | protected Printer mLogging; 25 | /** 26 | * 连接状态 27 | */ 28 | protected boolean mConnectState = false; 29 | /** 30 | * 每次读取等待超时时间,防止漏读,单位:毫秒 31 | */ 32 | private int readWaitTimeout = 100; 33 | 34 | @Override 35 | public int getState() { 36 | return mConnectState ? ErrorCode.ERROR_OK : ErrorCode.ERROR_NOT_CONNECTED; 37 | } 38 | 39 | /** 40 | * 重载多一个,适合发送不需处理返回值的命令 41 | * 42 | * @param sendParams 43 | * @return 44 | */ 45 | public int write(byte[] sendParams) { 46 | return write(sendParams, 0); 47 | } 48 | 49 | 50 | /** 51 | * 读取字节 52 | * 53 | * @param inStream 54 | * @param timeout 55 | * @return 56 | * @throws Exception 57 | */ 58 | protected byte[] readStream(InputStream inStream, int timeout) throws Exception { 59 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 60 | readStreamWithRecursion(output, inStream, timeout); 61 | output.close(); 62 | return output.toByteArray(); 63 | } 64 | 65 | 66 | /** 67 | * 读取字符串 68 | * 69 | * @param inStream 70 | * @return 71 | * @throws Exception 72 | */ 73 | protected String readString(InputStream inStream, int timeout) throws Exception { 74 | return new String(readStream(inStream, timeout)); 75 | } 76 | 77 | 78 | /** 79 | * 递归读取流 80 | * 81 | * @param output 82 | * @param inStream 83 | * @param timeout 单位毫秒 84 | * @return 85 | * @throws Exception 86 | */ 87 | private void readStreamWithRecursion(ByteArrayOutputStream output, InputStream inStream, int timeout) throws Exception { 88 | long start = System.currentTimeMillis(); 89 | while (inStream.available() == 0) { 90 | if ((System.currentTimeMillis() - start) > timeout) {//超时退出 91 | LogUtils.w("超时读取....."); 92 | throw new SocketTimeoutException("超时读取"); 93 | } 94 | } 95 | byte[] buffer = new byte[128]; 96 | int read = inStream.read(buffer); 97 | output.write(buffer, 0, read); 98 | final int wait = readWaitTimeout; 99 | //二次读取等待,防止漏读 100 | long startWait = System.currentTimeMillis(); 101 | boolean checkExist = false; 102 | while (System.currentTimeMillis() - startWait <= wait) { 103 | int a = inStream.available(); 104 | if (a > 0) { 105 | checkExist = true; 106 | break; 107 | } 108 | 109 | } 110 | if (checkExist) { 111 | if (!checkMessage(buffer, read)) { 112 | readStreamWithRecursion(output, inStream, timeout); 113 | } 114 | } 115 | 116 | } 117 | 118 | 119 | /** 120 | * 验证读取消息 121 | * 122 | * @param buffer 123 | * @param read 124 | * @return true验证,false不验证 125 | */ 126 | private boolean checkMessage(byte[] buffer, int read) { 127 | if (read <= buffer.length) { 128 | return checkRule(buffer, read); 129 | } 130 | return false; 131 | } 132 | 133 | /** 134 | * 报文验证规则,需要验证子类覆写 135 | * 136 | * @param buffer 137 | * @param read 138 | * @return 139 | */ 140 | protected boolean checkRule(byte[] buffer, int read) { 141 | return false; 142 | } 143 | 144 | 145 | public void setReadWaitTimeout(int readWaitTimeout) { 146 | this.readWaitTimeout = readWaitTimeout; 147 | } 148 | 149 | 150 | /** 151 | * 关闭流 152 | * 153 | * @param x 154 | */ 155 | public static void close(Closeable x) { 156 | if (x != null) { 157 | try { 158 | x.close(); 159 | } catch (Exception e) { 160 | // skip 161 | } 162 | } 163 | } 164 | 165 | 166 | /** 167 | * 通讯报文打印 168 | * 169 | * @param printer 170 | */ 171 | public void setCmdLogging(Printer printer) { 172 | mLogging = printer; 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/provider/BaseIoConnectProvider.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.provider; 2 | 3 | 4 | import android.util.Printer; 5 | 6 | import com.sjl.deviceconnector.ErrorCode; 7 | import com.sjl.deviceconnector.exception.ProviderTimeoutException; 8 | import com.sjl.deviceconnector.util.ByteUtils; 9 | import com.sjl.deviceconnector.util.LogUtils; 10 | 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | 14 | /** 15 | * IO通用方法基类 16 | * 17 | * @author Kelly 18 | */ 19 | public abstract class BaseIoConnectProvider extends BaseConnectProvider { 20 | protected InputStream mInputStream; 21 | protected OutputStream mOutputStream; 22 | 23 | @Override 24 | public synchronized int read(byte[] buffer, int timeout) { 25 | final Printer logging = mLogging; 26 | try { 27 | byte[] bytes = readStream(mInputStream, timeout); 28 | if (logging != null) { 29 | logging.println("<<<<< 收:" + ByteUtils.byteArrToHexString(bytes)); 30 | } 31 | int realLength = bytes.length; 32 | System.arraycopy(bytes, 0, buffer, 0, bytes.length); 33 | return realLength; 34 | } catch (Exception e) { 35 | LogUtils.e("读取数据异常", e); 36 | if (e instanceof ProviderTimeoutException) { 37 | return ErrorCode.ERROR_TIMEOUT;//读取超时 38 | } else { 39 | return ErrorCode.ERROR_RECEIVE;//接收数据失败 40 | } 41 | } 42 | } 43 | 44 | 45 | @Override 46 | public synchronized int read(byte[] sendParams, byte[] buffer, int timeout) { 47 | final Printer logging = mLogging; 48 | if (logging != null) { 49 | LogUtils.i(">>>>> 发:" + ByteUtils.byteArrToHexString(sendParams)); 50 | } 51 | int ret = write(sendParams, timeout); 52 | if (ret == ErrorCode.ERROR_OK) { 53 | return read(buffer,timeout); 54 | } else { 55 | return ErrorCode.ERROR_SEND;//发送数据失败 56 | } 57 | } 58 | 59 | 60 | @Override 61 | public synchronized int write(byte[] sendParams, int timeout) { 62 | int i = ErrorCode.ERROR_TIMEOUT; 63 | try { 64 | if (mOutputStream != null && getState() == ErrorCode.ERROR_OK) { 65 | mOutputStream.write(sendParams); 66 | mOutputStream.flush(); 67 | i = ErrorCode.ERROR_OK; 68 | } 69 | return i; 70 | } catch (Exception e) { 71 | LogUtils.e("write error.", e); 72 | } 73 | return i; 74 | } 75 | 76 | 77 | /** 78 | * 获取输出流 79 | * 80 | * @return 81 | */ 82 | public OutputStream getOutputStream() { 83 | return mOutputStream; 84 | } 85 | 86 | /** 87 | * 获取输入流 88 | * 89 | * @return 90 | */ 91 | public InputStream getInputStream() { 92 | return mInputStream; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/provider/BluetoothConnectProvider.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.provider; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothDevice; 5 | import android.bluetooth.BluetoothSocket; 6 | 7 | import com.sjl.deviceconnector.ErrorCode; 8 | import com.sjl.deviceconnector.util.LogUtils; 9 | 10 | import java.util.UUID; 11 | 12 | /** 13 | * 蓝牙连接提供者(经典蓝牙) 14 | *

连接流程:打开蓝牙开关->搜索蓝牙->配对蓝牙->连接蓝牙->读写数据->断开蓝牙->关闭蓝牙开关

15 | * @author Kelly 16 | * @version 1.0.0 17 | * @filename BluetoothConnectProvider.java 18 | * @time 2018/4/13 8:43 19 | * @copyright(C) 2018 song 20 | */ 21 | public class BluetoothConnectProvider extends BaseIoConnectProvider { 22 | /** 23 | * SPP服务UUID号 24 | */ 25 | public final static String BLUETOOTH_UUID = "00001101-0000-1000-8000-00805F9B34FB"; 26 | private final BluetoothDevice mBluetoothDevice; 27 | private BluetoothSocket mBluetoothSocket; 28 | private String uuid = BLUETOOTH_UUID; 29 | 30 | 31 | /** 32 | * 初始化一个蓝牙提供者 33 | * 34 | * @param address 蓝牙mac地址 35 | */ 36 | public BluetoothConnectProvider(String address) { 37 | this(address, BLUETOOTH_UUID); 38 | } 39 | 40 | /** 41 | * 初始化一个蓝牙提供者 42 | * 43 | * @param bluetoothDevice 蓝牙设备 44 | */ 45 | public BluetoothConnectProvider(BluetoothDevice bluetoothDevice) { 46 | this(bluetoothDevice, BLUETOOTH_UUID); 47 | } 48 | 49 | /** 50 | * 根据指定蓝牙服务UUID,初始化一个蓝牙提供者 51 | * 52 | * @param address 53 | * @param uuid 54 | */ 55 | public BluetoothConnectProvider(String address, String uuid) { 56 | BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter(); 57 | if (defaultAdapter == null) { 58 | throw new NullPointerException("设备不支持蓝牙"); 59 | } 60 | this.mBluetoothDevice = defaultAdapter.getRemoteDevice(address); 61 | this.uuid = uuid; 62 | } 63 | 64 | /** 65 | * 根据指定蓝牙服务UUID,初始化一个蓝牙提供者 66 | * 67 | * @param bluetoothDevice 68 | * @param uuid 69 | */ 70 | public BluetoothConnectProvider(BluetoothDevice bluetoothDevice, String uuid) { 71 | this.mBluetoothDevice = bluetoothDevice; 72 | this.uuid = uuid; 73 | } 74 | 75 | 76 | 77 | @Override 78 | public int open() { 79 | int state = getState(); 80 | if (state == ErrorCode.ERROR_OK) { 81 | return state; 82 | } 83 | try { 84 | mBluetoothSocket = mBluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(uuid)); 85 | mBluetoothSocket.connect(); 86 | mConnectState = true; 87 | mInputStream = mBluetoothSocket.getInputStream(); 88 | mOutputStream = mBluetoothSocket.getOutputStream(); 89 | return ErrorCode.ERROR_OK; 90 | } catch (Exception e) { 91 | LogUtils.e("蓝牙连接异常", e); 92 | close(); 93 | return ErrorCode.ERROR_FAIL; 94 | } 95 | } 96 | 97 | @Override 98 | public void close() { 99 | mConnectState = false; 100 | close(getOutputStream()); 101 | close(getInputStream()); 102 | close(mBluetoothSocket); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/provider/IConnectProvider.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.provider; 2 | 3 | /** 4 | * 通用连接提供者接口 5 | * 6 | * @author Kelly 7 | */ 8 | public interface IConnectProvider { 9 | 10 | /** 11 | * 打开连接 12 | * 13 | * @return 0连接成功,-1连接失败 14 | */ 15 | int open(); 16 | 17 | 18 | /** 19 | * 获取连接状态 20 | * 21 | * @return 0连接成功,-1连接失败 22 | */ 23 | int getState(); 24 | 25 | /** 26 | * 写数据(多次写的场景) 27 | * 28 | * @param sendParams 发送命令 29 | * @param timeout 超时时间,单位ms 30 | * @return 0 写成功,-1写失败 31 | */ 32 | int write(byte[] sendParams, int timeout); 33 | 34 | 35 | /** 36 | * 读数据(多次读的场景) 37 | * 38 | * @param buffer 临时缓冲区 39 | * @param timeout 超时时间,单位ms 40 | * @return >0读取数据成功(代表数据长度),-1读取超时,-2数据发送错误,-3数据接收错误,更多错误请看{@link com.sjl.deviceconnector.ErrorCode)} 41 | */ 42 | int read(byte[] buffer, int timeout); 43 | 44 | 45 | /** 46 | * 写和读数据(通用型) 47 | * 48 | * @param sendParams 发送命令 49 | * @param buffer 临时缓冲区 50 | * @param timeout 超时时间,单位ms 51 | * @return >0读取数据成功(代表数据长度),-1读取超时,-2数据发送错误,-3数据接收错误,更多错误请看{@link com.sjl.deviceconnector.ErrorCode)} 52 | */ 53 | int read(byte[] sendParams, byte[] buffer, int timeout); 54 | 55 | 56 | /** 57 | * 关闭连接 58 | */ 59 | void close(); 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/provider/SerialPortConnectProvider.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.provider; 2 | 3 | import android.serialport.SerialPort; 4 | 5 | import com.sjl.deviceconnector.ErrorCode; 6 | import com.sjl.deviceconnector.entity.SerialPortConfig; 7 | import com.sjl.deviceconnector.util.LogUtils; 8 | 9 | /** 10 | * 串口连接提供者 11 | * 12 | * @author Kelly 13 | * @version 1.0.0 14 | * @filename SerialPortConnectProvider 15 | * @time 2020/10/30 11:31 16 | * @copyright(C) 2020 song 17 | */ 18 | public class SerialPortConnectProvider extends BaseIoConnectProvider { 19 | private final SerialPortConfig serialPortConfig; 20 | private SerialPort mSerialPort; 21 | 22 | /** 23 | * 初始化串口连接提供者 24 | * 25 | * @param serialPortConfig 串口配置 26 | */ 27 | public SerialPortConnectProvider(SerialPortConfig serialPortConfig) { 28 | this.serialPortConfig = serialPortConfig; 29 | } 30 | 31 | 32 | @Override 33 | public int open() { 34 | int state = getState(); 35 | if (state == ErrorCode.ERROR_OK) { 36 | return state; 37 | } 38 | try { 39 | mSerialPort = SerialPort.newBuilder(serialPortConfig.getDevice(), serialPortConfig.getBaudRate()) 40 | .parity(serialPortConfig.getParity()) 41 | .flags(serialPortConfig.getFlags()) 42 | .dataBits(serialPortConfig.getDataBits()) 43 | .stopBits(serialPortConfig.getStopBits()).build(); 44 | mOutputStream = mSerialPort.getOutputStream(); 45 | mInputStream = mSerialPort.getInputStream(); 46 | mConnectState = true; 47 | } catch (Exception e) { 48 | LogUtils.e("打开串口失败", e); 49 | close(); 50 | return ErrorCode.ERROR_FAIL; 51 | } 52 | return ErrorCode.ERROR_OK; 53 | } 54 | 55 | 56 | @Override 57 | public void close() { 58 | mConnectState = false; 59 | close(getOutputStream()); 60 | close(getInputStream()); 61 | if (mSerialPort != null) { 62 | mSerialPort.close(); 63 | mSerialPort = null; 64 | } 65 | } 66 | 67 | 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/provider/SocketConnectProvider.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.provider; 2 | 3 | import android.os.Build; 4 | 5 | import com.sjl.deviceconnector.ErrorCode; 6 | 7 | import java.net.InetSocketAddress; 8 | import java.net.Socket; 9 | import java.net.SocketAddress; 10 | 11 | /** 12 | * Socket连接提供者,可作为TCP通讯使用 13 | * 14 | * @author Kelly 15 | * @version 1.0.0 16 | * @filename SocketConnectProvider.java 17 | * @time 2018/4/13 8:40 18 | * @copyright(C) 2018 song 19 | */ 20 | public class SocketConnectProvider extends BaseIoConnectProvider { 21 | private Socket mSocket; 22 | private String ip; 23 | private int port; 24 | private int connectTimeout, readTimeout; 25 | 26 | 27 | /** 28 | * 初始化Socket连接提供者 29 | * 30 | * @param ip ip地址 31 | * @param port 端口号 32 | * @param connectTimeout 连接超时时间,毫秒 33 | * @param readTimeout 读取超时时间,毫秒 34 | */ 35 | public SocketConnectProvider(String ip, int port, int connectTimeout, int readTimeout) { 36 | this.ip = ip; 37 | this.port = port; 38 | this.connectTimeout = connectTimeout < 0 ? 10 * 1000 : connectTimeout; 39 | this.readTimeout = readTimeout < 0 ? 10 * 1000 : readTimeout; 40 | } 41 | 42 | 43 | 44 | 45 | @Override 46 | public int open() { 47 | int state = getState(); 48 | if (state == ErrorCode.ERROR_OK) { 49 | return state; 50 | } 51 | mSocket = new Socket(); 52 | SocketAddress address = new InetSocketAddress(ip, port);//socket连接地址 53 | try { 54 | mSocket.connect(address, connectTimeout); 55 | mSocket.setSoTimeout(readTimeout); 56 | mInputStream = mSocket.getInputStream(); 57 | mOutputStream = mSocket.getOutputStream(); 58 | mConnectState = true; 59 | return ErrorCode.ERROR_OK; 60 | } catch (Exception e) { 61 | close(); 62 | return ErrorCode.ERROR_FAIL; 63 | } 64 | 65 | } 66 | 67 | @Override 68 | public void close() { 69 | mConnectState = false; 70 | close(getOutputStream()); 71 | close(getInputStream()); 72 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 73 | close(mSocket); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/provider/UsbComConnectProvider.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.provider; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.hardware.usb.UsbDevice; 6 | import android.hardware.usb.UsbDeviceConnection; 7 | import android.hardware.usb.UsbManager; 8 | 9 | import com.hoho.android.usbserial.driver.CommonUsbSerialPort; 10 | import com.hoho.android.usbserial.driver.UsbSerialDriver; 11 | import com.hoho.android.usbserial.driver.UsbSerialPort; 12 | import com.hoho.android.usbserial.driver.UsbSerialProber; 13 | import com.hoho.android.usbserial.util.SerialInputOutputManager; 14 | import com.sjl.deviceconnector.DeviceContext; 15 | import com.sjl.deviceconnector.ErrorCode; 16 | import com.sjl.deviceconnector.device.usb.UsbHelper; 17 | import com.sjl.deviceconnector.entity.SerialPortConfig; 18 | import com.sjl.deviceconnector.util.LogUtils; 19 | 20 | /** 21 | * Usb Com连接提供者(基于usb-serial-for-android驱动库封装) 22 | * 23 | * @author Kelly 24 | * @version 1.0.0 25 | * @filename UsbComConnectProvider 26 | * @time 2020/10/30 11:31 27 | * @copyright(C) 2020 song 28 | */ 29 | public class UsbComConnectProvider extends BaseConnectProvider { 30 | private SerialInputOutputManager usbIoManager; 31 | private UsbSerialPort usbSerialPort; 32 | private final UsbDevice mUsbDevice; 33 | private final SerialPortConfig serialPortConfig; 34 | 35 | /** 36 | * 初始化Usb Com连接提供者 37 | * 38 | * @param vendorId 产商id 39 | * @param productId 产品id 40 | * @param serialPortConfig 串口配置参数 41 | */ 42 | public UsbComConnectProvider(int vendorId, int productId, SerialPortConfig serialPortConfig) { 43 | this(UsbHelper.getDevice(vendorId, productId), serialPortConfig); 44 | } 45 | 46 | /** 47 | * 初始化Usb Com连接提供者 48 | * 49 | * @param usbDevice 50 | */ 51 | public UsbComConnectProvider(UsbDevice usbDevice, SerialPortConfig serialPortConfig) { 52 | this.mUsbDevice = usbDevice; 53 | this.serialPortConfig = serialPortConfig; 54 | } 55 | 56 | 57 | @SuppressLint("WrongConstant") 58 | @Override 59 | public int open() { 60 | int state = getState(); 61 | if (state == ErrorCode.ERROR_OK) { 62 | return state; 63 | } 64 | if (mUsbDevice == null) { 65 | LogUtils.e("connection failed: device not found"); 66 | return ErrorCode.ERROR_DEVICE_NOT_FIND; 67 | } 68 | UsbManager usbManager = (UsbManager) DeviceContext.getContext().getSystemService(Context.USB_SERVICE); 69 | UsbSerialDriver usbSerialDriver = UsbSerialProber.getDefaultProber().probeDevice(mUsbDevice); 70 | if (usbSerialDriver == null) { 71 | LogUtils.e("connection failed: device driver not found"); 72 | return ErrorCode.ERROR_DEVICE_DRIVER_NOT_FIND; 73 | } 74 | UsbDeviceConnection usbConnection = usbManager.openDevice(usbSerialDriver.getDevice()); 75 | if (usbConnection == null && !usbManager.hasPermission(usbSerialDriver.getDevice())) { 76 | LogUtils.e("connection failed: open failed"); 77 | //触发一次权限申请,正常使用需要在外部申请usb权限 78 | UsbHelper.getInstance().requestPermission(mUsbDevice); 79 | return ErrorCode.ERROR_NO_PERMISSION; 80 | 81 | } 82 | if (!usbManager.hasPermission(usbSerialDriver.getDevice())) { 83 | LogUtils.e("connection failed: permission denied"); 84 | return ErrorCode.ERROR_PERMISSION_FAIL; 85 | } 86 | try { 87 | usbSerialPort = usbSerialDriver.getPorts().get(0); 88 | usbSerialPort.open(usbConnection); 89 | usbSerialPort.setParameters(serialPortConfig.getBaudRate(), serialPortConfig.getDataBits(), serialPortConfig.getStopBits(), serialPortConfig.getParity()); 90 | try { 91 | usbSerialPort.purgeHwBuffers(true, true); 92 | } catch (Exception e) { 93 | e.printStackTrace(); 94 | } 95 | LogUtils.e("connected"); 96 | mConnectState = true; 97 | return ErrorCode.ERROR_OK; 98 | } catch (Exception e) { 99 | LogUtils.e("connection failed", e); 100 | close(); 101 | } 102 | return ErrorCode.ERROR_FAIL; 103 | } 104 | 105 | 106 | @Override 107 | public synchronized int write(byte[] sendParams, int timeout) { 108 | int state = getState(); 109 | if (state != ErrorCode.ERROR_OK) { 110 | return state; 111 | } 112 | try { 113 | usbSerialPort.write(sendParams, timeout); 114 | return ErrorCode.ERROR_OK; 115 | } catch (Exception e) { 116 | return ErrorCode.ERROR_FAIL; 117 | } 118 | } 119 | 120 | @Override 121 | public synchronized int read(byte[] buffer, int timeout) { 122 | int state = getState(); 123 | if (state != ErrorCode.ERROR_OK) { 124 | return state; 125 | } 126 | try { 127 | int len = usbSerialPort.read(buffer, timeout); 128 | return len; 129 | } catch (Exception e) { 130 | LogUtils.e("read failed", e); 131 | return ErrorCode.ERROR_FAIL; 132 | } 133 | } 134 | 135 | @Override 136 | public synchronized int read(byte[] sendParams, byte[] buffer, int timeout) { 137 | int ret = write(sendParams, timeout); 138 | if (ret == ErrorCode.ERROR_OK) { 139 | return read(buffer, timeout); 140 | } else { 141 | return ret; 142 | } 143 | 144 | } 145 | 146 | @Override 147 | public void close() { 148 | mConnectState = false; 149 | if (usbIoManager != null) { 150 | usbIoManager.stop(); 151 | usbIoManager = null; 152 | } 153 | close(usbSerialPort); 154 | 155 | } 156 | 157 | /** 158 | * 设置发送缓冲区大小 159 | * 160 | * @param bufferSize 大小字节数 161 | */ 162 | public void setWriteBufferSize(int bufferSize) { 163 | if (usbSerialPort != null && usbSerialPort instanceof CommonUsbSerialPort){ 164 | CommonUsbSerialPort commonUsbSerialPort = (CommonUsbSerialPort) usbSerialPort; 165 | commonUsbSerialPort.setWriteBufferSize(bufferSize); 166 | } 167 | } 168 | 169 | public UsbSerialPort getUsbSerialPort() { 170 | return usbSerialPort; 171 | } 172 | 173 | public int getMaxWriteBuffer() { 174 | return usbSerialPort.getWriteEndpoint().getMaxPacketSize(); 175 | } 176 | 177 | public int getMaxReadBuffer() { 178 | return usbSerialPort.getReadEndpoint().getMaxPacketSize(); 179 | } 180 | 181 | 182 | } 183 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/provider/WifiConnectProvider.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.provider; 2 | 3 | 4 | 5 | /** 6 | * Wifi连接提供者 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename WifiConnectProvider.java 11 | * @time 2018/4/13 8:45 12 | * @copyright(C) 2018 song 13 | */ 14 | public class WifiConnectProvider extends SocketConnectProvider { 15 | 16 | 17 | /** 18 | * 初始化Wifi连接提供者 19 | * 20 | * @param host ip地址 21 | * @param port 端口号 22 | * @param connectTimeout 连接超时时间,毫秒 23 | * @param readTimeout 读取超时时间,毫秒 24 | */ 25 | public WifiConnectProvider(String host, int port, int connectTimeout, int readTimeout) { 26 | super(host, port, connectTimeout, readTimeout); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/util/BluetoothUtils.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.util; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothDevice; 5 | import android.bluetooth.le.BluetoothLeScanner; 6 | import android.os.Build; 7 | 8 | import com.sjl.deviceconnector.entity.BluetoothScanResult; 9 | import com.sjl.deviceconnector.listener.BluetoothScanListener; 10 | 11 | import java.lang.reflect.Method; 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | import androidx.annotation.RequiresApi; 18 | 19 | /** 20 | * TODO 21 | * 22 | * @author Kelly 23 | * @version 1.0.0 24 | * @filename BluetoothUtils 25 | * @time 2023/3/3 15:04 26 | * @copyright(C) 2023 song 27 | */ 28 | public class BluetoothUtils { 29 | 30 | 31 | /** 32 | * 蓝牙适配器 33 | * 34 | * @return 35 | */ 36 | public static BluetoothAdapter getBluetoothAdapter() { 37 | return BluetoothAdapter.getDefaultAdapter(); 38 | } 39 | 40 | /** 41 | * 蓝牙适配器 42 | * 43 | * @return 44 | */ 45 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 46 | public static BluetoothLeScanner getBluetoothLeScanner() { 47 | BluetoothLeScanner bluetoothLeScanner = getBluetoothAdapter().getBluetoothLeScanner(); 48 | return bluetoothLeScanner; 49 | } 50 | 51 | /** 52 | * 判断蓝牙开关是否启用 53 | * 54 | * @return 55 | */ 56 | public static boolean isEnabled() { 57 | return getBluetoothAdapter().isEnabled(); 58 | } 59 | 60 | /** 61 | * 打开蓝牙 62 | */ 63 | public static void enable() { 64 | getBluetoothAdapter().enable(); 65 | } 66 | 67 | /** 68 | * 关闭蓝牙 69 | */ 70 | public static void disable() { 71 | getBluetoothAdapter().disable(); 72 | } 73 | 74 | /** 75 | * 获取已配对的蓝牙设备 76 | * 77 | * @return 78 | */ 79 | public static List getBondedDevices() { 80 | Set bondedDevices = getBluetoothAdapter().getBondedDevices(); 81 | if (bondedDevices == null) { 82 | return Collections.emptyList(); 83 | } 84 | return new ArrayList(bondedDevices); 85 | } 86 | 87 | /** 88 | * 获取已配对的蓝牙设备 89 | * 90 | * @return 91 | */ 92 | public static List wrapBondedDevices() { 93 | List bondedDevices = getBondedDevices(); 94 | 95 | List arrayList = new ArrayList(bondedDevices.size()); 96 | for (BluetoothDevice bd:bondedDevices) { 97 | arrayList.add(new BluetoothScanResult(bd,-1, (byte[]) null)); 98 | } 99 | return arrayList; 100 | } 101 | 102 | 103 | /** 104 | * 取消配对 105 | * 106 | * @param device 107 | * @return 108 | * @throws Exception 109 | */ 110 | public static boolean cancelBond(BluetoothDevice device) throws Exception { 111 | Method createBondMethod = device.getClass().getMethod("removeBond"); 112 | Boolean returnValue = (Boolean) createBondMethod.invoke(device); 113 | return returnValue.booleanValue(); 114 | } 115 | 116 | /** 117 | * 配对蓝牙设备 118 | * 119 | * @param device 120 | * @return 121 | * @throws Exception 122 | */ 123 | public static boolean createBond(BluetoothDevice device) throws Exception { 124 | Method createBondMethod = device.getClass().getMethod("createBond"); 125 | Boolean returnValue = (Boolean) createBondMethod.invoke(device); 126 | return returnValue.booleanValue(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/util/ByteUtils.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.util; 2 | 3 | /** 4 | * TODO 5 | * 6 | * @author Kelly 7 | * @version 1.0.0 8 | * @filename ByteUtils 9 | * @time 2020/8/29 19:32 10 | * @copyright(C) 2020 song 11 | */ 12 | public class ByteUtils { 13 | /** 14 | * 16进制字符串转字节数组 15 | * 16 | * @param hex 17 | * @return 18 | */ 19 | public static byte[] hexStringToByteArr(String hex) { 20 | int l = hex.length() / 2; 21 | byte[] ret = new byte[l]; 22 | for (int i = 0; i < l; i++) { 23 | ret[i] = (byte) Integer.valueOf(hex.substring(i * 2, i * 2 + 2), 16).byteValue(); 24 | } 25 | return ret; 26 | } 27 | 28 | /** 29 | * 字节数组转16进制字符串 30 | * @param b 31 | * @return 32 | */ 33 | public static String byteArrToHexString(byte[] b) { 34 | StringBuilder result = new StringBuilder(); 35 | for (int i = 0; i < b.length; i++) { 36 | result.append(Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1)); 37 | } 38 | return result.toString().toUpperCase(); 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /deviceconnector/src/main/java/com/sjl/deviceconnector/util/LogUtils.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector.util; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * TODO 7 | * 8 | * @author Kelly 9 | * @version 1.0.0 10 | * @filename LogUtils 11 | * @time 2020/8/29 18:56 12 | * @copyright(C) 2020 song 13 | */ 14 | public class LogUtils { 15 | private static final String TAG = "DEVICE_CONNECTOR"; 16 | 17 | private static boolean debug = false; 18 | 19 | private LogUtils() { 20 | 21 | } 22 | public static void init(boolean debug){ 23 | LogUtils.debug = debug; 24 | } 25 | 26 | public static void i(String str) { 27 | if (!debug){ 28 | return; 29 | } 30 | Log.i(TAG, str); 31 | } 32 | 33 | public static void w(String str) { 34 | if (!debug){ 35 | return; 36 | } 37 | Log.w(TAG, str); 38 | } 39 | 40 | public static void e(String str) { 41 | if (!debug){ 42 | return; 43 | } 44 | Log.e(TAG, str); 45 | } 46 | 47 | public static void e(String str, Exception e) { 48 | if (!debug){ 49 | return; 50 | } 51 | Log.e(TAG, str, e); 52 | } 53 | 54 | public static boolean isDebug() { 55 | return debug; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /deviceconnector/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | deviceconnector 3 | 4 | -------------------------------------------------------------------------------- /deviceconnector/src/test/java/com/sjl/deviceconnector/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.sjl.deviceconnector; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | android.enableJetifier=true 20 | android.useAndroidX=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /screenshot/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/1.jpg -------------------------------------------------------------------------------- /screenshot/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/10.jpg -------------------------------------------------------------------------------- /screenshot/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/2.jpg -------------------------------------------------------------------------------- /screenshot/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/3.jpg -------------------------------------------------------------------------------- /screenshot/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/4.jpg -------------------------------------------------------------------------------- /screenshot/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/5.jpg -------------------------------------------------------------------------------- /screenshot/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/6.jpg -------------------------------------------------------------------------------- /screenshot/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/7.jpg -------------------------------------------------------------------------------- /screenshot/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/8.jpg -------------------------------------------------------------------------------- /screenshot/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kellysong/DeviceConnector/ad281aeeaf4a1aff7a745812a87f722d4f35359f/screenshot/9.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':deviceconnector' --------------------------------------------------------------------------------