├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── themes.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── bass │ │ │ │ └── easyserialport │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── keepReceive │ │ │ │ ├── CustomEasyPortDataHandle.kt │ │ │ │ ├── KeepReceiveDemo1.kt │ │ │ │ └── KeepReceiveDemo2.kt │ │ │ │ ├── waitRsp │ │ │ │ └── WaitRspDemo1.kt │ │ │ │ └── other │ │ │ │ └── OtherApiDemo.kt │ │ └── AndroidManifest.xml │ └── test │ │ └── kotlin │ │ └── com │ │ └── bass │ │ └── easyserialport │ │ └── ExampleUnitTest.kt ├── proguard-rules.pro └── build.gradle ├── easySerial ├── .gitignore ├── proguard-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── cpp │ │ ├── SerialPort.h │ │ ├── CMakeLists.txt │ │ └── SerialPort.c │ │ └── java │ │ └── com │ │ └── bass │ │ └── easySerial │ │ ├── interfaces │ │ ├── EasyReadDataCallBack.kt │ │ └── EasyReceiveCallBack.kt │ │ ├── enums │ │ ├── StopBit.kt │ │ ├── Parity.kt │ │ ├── FlowCon.kt │ │ ├── DataBit.kt │ │ └── BaudRate.kt │ │ ├── bean │ │ ├── ErgodicBean.kt │ │ ├── DriverBean.kt │ │ └── WaitResponseBean.kt │ │ ├── handle │ │ └── EasyPortDataHandle.kt │ │ ├── wrapper │ │ ├── BaseEasySerialPort.kt │ │ ├── EasyKeepReceivePort.kt │ │ └── EasyWaitRspPort.kt │ │ ├── extend │ │ ├── EasyOtherExt.kt │ │ ├── EasyLogger.kt │ │ └── EasyDataConversionExt.kt │ │ ├── util │ │ ├── EasySerialFinderUtil.kt │ │ └── ErgodicPortUtil.kt │ │ ├── SerialPort.kt │ │ └── EasySerialBuilder.kt └── build.gradle ├── jitpack.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── local.properties ├── gradle.properties ├── gradlew.bat ├── gradlew ├── LICENSE └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /easySerial/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .cxx 3 | .gradle -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 3 | before_install: 4 | - yes | sdkmanager "cmake;3.10.2" -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | EasySerialPort 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BASS-HY/EasySerial/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /easySerial/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class com.bass.easySerial.bean.**{*;} 2 | -keep class com.bass.easySerial.enums.**{*;} 3 | -keep class com.bass.easySerial.SerialPort{*;} -------------------------------------------------------------------------------- /easySerial/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/test/kotlin/com/bass/easyserialport/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.bass.easyserialport 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * Create by BASS 7 | * on 2022/11/5 11:54. 8 | */ 9 | class ExampleUnitTest { 10 | 11 | @Test 12 | fun test() {} 13 | 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Oct 29 17:45:37 GMT+08:00 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | #org.gradle.java.home=F\:\\jdk-19 -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | maven { url "https://jitpack.io" } 7 | } 8 | } 9 | rootProject.name = "EasySerialPort" 10 | include ':app' 11 | include ':easySerial' 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | ## This file is automatically generated by Android Studio. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file should *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | # 7 | # Location of the SDK. This is only used by Gradle. 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | sdk.dir=D\:\\Sdk -------------------------------------------------------------------------------- /app/src/main/java/com/bass/easyserialport/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.bass.easyserialport 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.bass.easySerial.extend.conver2ByteArray 6 | import com.bass.easySerial.extend.conver2HexString 7 | 8 | class MainActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_main) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/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 -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /easySerial/src/main/cpp/SerialPort.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class android_serialport_api_SerialPort */ 4 | 5 | #ifndef _Included_android_serialport_api_SerialPort 6 | #define _Included_android_serialport_api_SerialPort 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | /* 11 | * Method: open 12 | * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; 13 | */ 14 | JNIEXPORT jobject JNICALL Java_com_bass_easySerial_SerialPort_open 15 | (JNIEnv *, jobject, jstring, jint, jint); 16 | 17 | JNIEXPORT jobject JNICALL Java_com_bass_easySerial_SerialPort_open2 18 | (JNIEnv *, jobject, jstring, jint, jint, jint, jint, jint, jint); 19 | 20 | /* 21 | * Method: close 22 | * Signature: ()V 23 | */ 24 | JNIEXPORT void JNICALL Java_com_bass_easySerial_SerialPort_close 25 | (JNIEnv *, jobject); 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif 30 | #endif 31 | -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/interfaces/EasyReadDataCallBack.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.interfaces 18 | 19 | /** 20 | * 接收串口读取的数据 21 | */ 22 | internal interface EasyReadDataCallBack { 23 | 24 | /** 25 | * 串口返回的数据 26 | * @param bytes 为串口读取到的数据的副本 27 | */ 28 | suspend fun receiveData(bytes: ByteArray) 29 | 30 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/enums/StopBit.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.enums 18 | 19 | /** 20 | * @Description 串口停止位定义 21 | */ 22 | @Suppress("unused") 23 | enum class StopBit(val stopBit: Int) { 24 | BEmpty(-1), 25 | 26 | /** 27 | * 1位停止位 28 | */ 29 | B1(1), 30 | 31 | /** 32 | * 2位停止位 33 | */ 34 | B2(2); 35 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/bean/ErgodicBean.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.bean 18 | 19 | /** 20 | * Create by BASS 21 | * on 2021/12/24 16:15. 22 | */ 23 | internal data class ErgodicBean( 24 | val path: String, 25 | val baudRate: Int, 26 | val flags: Int, 27 | val dataBits: Int = -1, 28 | val stopBits: Int = -1, 29 | val parity: Int = 0 30 | ) -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/enums/Parity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.enums 18 | 19 | /** 20 | * @Description 串口校验位定义 21 | */ 22 | @Suppress("unused") 23 | enum class Parity(val parity: Int) { 24 | /** 25 | * 无奇偶校验 26 | */ 27 | NONE(0), 28 | 29 | /** 30 | * 奇校验 31 | */ 32 | ODD(1), 33 | 34 | /** 35 | * 偶校验 36 | */ 37 | EVEN(2); 38 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/enums/FlowCon.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.enums 18 | 19 | /** 20 | * @Description 串口流控定义 21 | */ 22 | @Suppress("unused") 23 | enum class FlowCon(val flowCon: Int) { 24 | /** 25 | * 不使用流控 26 | */ 27 | NONE(0), 28 | 29 | /** 30 | * 硬件流控 31 | */ 32 | HARD(1), 33 | 34 | /** 35 | * 软件流控 36 | */ 37 | SOFT(2); 38 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/interfaces/EasyReceiveCallBack.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.interfaces 18 | 19 | /** 20 | * Create by BASS 21 | * on 2022/8/11 13:42. 22 | * 永久接收的串口返回处理后的串口数据给监听者 23 | */ 24 | interface EasyReceiveCallBack { 25 | 26 | /** 27 | * 返回处理后的串口数据给监听者 28 | * @param dataList 处理后的串口数据 29 | */ 30 | suspend fun receiveData(dataList: List) 31 | 32 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/enums/DataBit.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.enums 18 | 19 | /** 20 | * @Description 串口数据位定义 21 | */ 22 | @Suppress("unused") 23 | enum class DataBit(val dataBit: Int) { 24 | CSEmpty(-1), 25 | 26 | /** 27 | * 5位数据位 28 | */ 29 | CS5(5), 30 | 31 | /** 32 | * 6位数据位 33 | */ 34 | CS6(6), 35 | 36 | /** 37 | * 7位数据位 38 | */ 39 | CS7(7), 40 | 41 | /** 42 | * 8位数据位 43 | */ 44 | CS8(8); 45 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | applicationId "com.bass.easyserialport" 11 | minSdk 21 12 | targetSdk 32 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | kotlinOptions { 30 | jvmTarget = '1.8' 31 | } 32 | } 33 | 34 | dependencies { 35 | 36 | implementation 'androidx.core:core-ktx:1.7.0' 37 | implementation 'androidx.appcompat:appcompat:1.4.1' 38 | implementation 'com.google.android.material:material:1.4.0' 39 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 40 | 41 | testImplementation 'junit:junit:4.13.2' 42 | 43 | implementation project(':easySerial') 44 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/bean/DriverBean.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.bean 18 | 19 | import java.io.File 20 | import java.util.* 21 | 22 | /** 23 | * Create by BASS 24 | * on 2022/6/29 14:07. 25 | */ 26 | internal data class DriverBean(val driverName: String, val deviceRoot: String) { 27 | val devices by lazy { 28 | Vector().apply { 29 | val dev = File("/dev") 30 | val files = dev.listFiles() ?: return@apply 31 | files.forEach { 32 | if (it.absolutePath.startsWith(deviceRoot, true)) add(it) 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/bean/WaitResponseBean.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.bean 18 | 19 | /** 20 | * 写入后等待返回的数据类 21 | * @param bytes 串口返回的数据 22 | * @param size 读取到的数据大小 23 | */ 24 | data class WaitResponseBean(val bytes: ByteArray, val size: Int) { 25 | override fun equals(other: Any?): Boolean { 26 | if (other !is WaitResponseBean) return false 27 | if (size != other.size) return false 28 | if (bytes.size != other.bytes.size) return false 29 | for (index in bytes.indices) { 30 | if (bytes[index] != other.bytes[index]) return false 31 | } 32 | return true 33 | } 34 | 35 | override fun hashCode(): Int { 36 | return super.hashCode() 37 | } 38 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | 25 | android.disableAutomaticComponentCreation=true -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/enums/BaudRate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.enums 18 | 19 | /** 20 | * @Description 串口波特率定义 21 | */ 22 | @Suppress("unused") 23 | enum class BaudRate(val baudRate: Int) { 24 | B0(0), 25 | B50(50), 26 | B75(75), 27 | B110(110), 28 | B134(134), 29 | B150(150), 30 | B200(200), 31 | B300(300), 32 | B600(600), 33 | B1200(1200), 34 | B1800(1800), 35 | B2400(2400), 36 | B4800(4800), 37 | B9600(9600), 38 | B19200(19200), 39 | B38400(38400), 40 | B57600(57600), 41 | B115200(115200), 42 | B230400(230400), 43 | B460800(460800), 44 | B500000(500000), 45 | B576000(576000), 46 | B921600(921600), 47 | B1000000(1000000), 48 | B1152000(1152000), 49 | B1500000(1500000), 50 | B2000000(2000000), 51 | B2500000(2500000), 52 | B3000000(3000000), 53 | B3500000(3500000), 54 | B4000000(4000000); 55 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/handle/EasyPortDataHandle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.handle 18 | 19 | import kotlinx.coroutines.sync.Mutex 20 | import kotlinx.coroutines.sync.withLock 21 | 22 | /** 23 | * Create by BASS 24 | * on 2022/6/1 15:33. 25 | * 数据处理类 继承此类,对串口返回的数据进行处理,将处理后的数据返回给监听者 26 | */ 27 | abstract class EasyPortDataHandle { 28 | 29 | private val mutex = Mutex()//同步锁,防止处理数据的过程太慢,而输入过快,导致接收端数据紊乱的问题 30 | 31 | internal suspend fun receivePortData(byteArray: ByteArray): List { 32 | mutex.withLock { return portData(byteArray) } 33 | } 34 | 35 | /** 36 | * 数据处理方法 37 | * @param byteArray 串口收到的原始数据 38 | * @return 返回自定义处理后的数据,此数据将被派发到各个监听者 39 | */ 40 | abstract suspend fun portData(byteArray: ByteArray): List 41 | 42 | /** 43 | * 串口关闭时会回调此方法 44 | * 如果您需要,可重写此方法,在此方法中做释放资源的操作 45 | */ 46 | open fun close() {} 47 | } -------------------------------------------------------------------------------- /easySerial/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | id 'maven-publish' 6 | } 7 | 8 | group = rootProject.ext.jitpackGroupId 9 | version = rootProject.ext.jitpackVersion 10 | 11 | android { 12 | compileSdk 31 13 | 14 | defaultConfig { 15 | minSdk 14 16 | targetSdk 31 17 | versionCode 1 18 | versionName "1.0" 19 | 20 | //将混淆文件合并到宿主APP中 21 | consumerProguardFiles 'proguard-rules.pro' 22 | 23 | externalNativeBuild { 24 | cmake { 25 | cppFlags '' 26 | abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86' 27 | } 28 | } 29 | } 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { jvmTarget = '1.8' } 37 | 38 | externalNativeBuild { 39 | cmake { 40 | path file('src/main/cpp/CMakeLists.txt') 41 | version '3.10.2' 42 | } 43 | } 44 | } 45 | 46 | dependencies { 47 | //协程 48 | api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2' 49 | } 50 | 51 | afterEvaluate { 52 | publishing { 53 | publications { 54 | // Creates a Maven publication called "release". 55 | release(MavenPublication) { 56 | from components.release 57 | groupId = rootProject.ext.jitpackGroupId 58 | artifactId = rootProject.ext.jitpackArtifactId 59 | version = rootProject.ext.jitpackVersion 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/bass/easyserialport/keepReceive/CustomEasyPortDataHandle.kt: -------------------------------------------------------------------------------- 1 | package com.bass.easyserialport.keepReceive 2 | 3 | import com.bass.easySerial.extend.conver2HexString 4 | import com.bass.easySerial.handle.EasyPortDataHandle 5 | import java.util.regex.Pattern 6 | 7 | /** 8 | * Create by BASS 9 | * on 2022/10/29 23:05. 10 | * 自定义的数据解析规则; 11 | */ 12 | class CustomEasyPortDataHandle : EasyPortDataHandle() { 13 | 14 | private val stringList = mutableListOf()//用于记录数据 15 | private val stringBuilder = StringBuilder()//用于记录数据 16 | private val pattern = Pattern.compile("(AT)(.*?)(\r\n)")//用于匹配数据 17 | 18 | /** 19 | * 数据处理方法 20 | * 21 | * @param byteArray 串口收到的原始数据 22 | * @return 返回自定义处理后的数据,此数据将被派发到各个监听者 23 | * 24 | * 25 | * 我们可以在这里做很多事情,比如有时候串口返回的数据并不是完整的数据, 26 | * 它可能有分包返回的情况,我们需要自行凑成一个完整的数据后再返回给监听者, 27 | * 在数据不完整的时候我们直接返回空数据集给监听者,告知他们这不是一个完整的数据; 28 | * 29 | * 在这里我们做个演示,假设数据返回是以AT开头,换行符为结尾的数据是正常的数据; 30 | * 31 | */ 32 | override suspend fun portData(byteArray: ByteArray): List { 33 | //清除之前记录的匹配成功的数据 34 | stringList.clear() 35 | 36 | //将串口数据转为16进制字符串 37 | val hexString = byteArray.conver2HexString() 38 | //记录本次读取到的串口数据 39 | stringBuilder.append(hexString) 40 | 41 | while (true) {//循环匹配,直到匹配完所有的数据 42 | //寻找记录中符合规则的数据 43 | val matcher = pattern.matcher(stringBuilder) 44 | //没有寻找到符合规则的数据,则返回Null 45 | if (!matcher.find()) break 46 | //寻找到符合规则的数据,记录匹配成功的数据,并将其从StringBuilder中删除 47 | val group = matcher.group() 48 | stringList.add(group) 49 | stringBuilder.delete(matcher.start(), matcher.end()) 50 | } 51 | 52 | //返回记录的匹配成功的数据 53 | return stringList.toList() 54 | } 55 | 56 | /** 57 | * 串口关闭时会回调此方法 58 | * 如果您需要,可重写此方法,在此方法中做释放资源的操作 59 | */ 60 | override fun close() { 61 | stringBuilder.clear() 62 | stringList.clear() 63 | } 64 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/wrapper/BaseEasySerialPort.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.wrapper 18 | 19 | import com.bass.easySerial.EasySerialBuilder 20 | import com.bass.easySerial.SerialPort 21 | 22 | /** 23 | * Create by BASS 24 | * on 2021/12/23 17:47. 25 | * 串口通信的基类 26 | */ 27 | @Suppress("unused", "SpellCheckingInspection") 28 | abstract class BaseEasySerialPort internal constructor(protected val serialPort: SerialPort) { 29 | 30 | protected var customMaxReadSize = 64//串口每次从数据流中读取的最大字节数 31 | 32 | /** 33 | * 获取串口的名称 34 | * 如:/dev/ttyS4 35 | */ 36 | fun getPortPath() = serialPort.getDevicePath() 37 | 38 | /** 39 | * 强转成 [EasyKeepReceivePort] 40 | * @exception ClassCastException 如果类型不匹配,则抛出异常 41 | */ 42 | @Suppress("UNCHECKED_CAST") 43 | @Throws(ClassCastException::class) 44 | fun cast2KeepReceivePort(): EasyKeepReceivePort { 45 | return this as EasyKeepReceivePort 46 | } 47 | 48 | /** 49 | * 强转成[EasyWaitRspPort] 50 | * @exception ClassCastException 如果类型不匹配,则抛出异常 51 | */ 52 | @Throws(ClassCastException::class) 53 | fun cast2WaitRspPort(): EasyWaitRspPort { 54 | return this as EasyWaitRspPort 55 | } 56 | 57 | /** 58 | * 调用此方法将关闭串口 59 | */ 60 | open suspend fun close() { 61 | //关闭串口 62 | serialPort.closeSerial() 63 | //移除串口类实例,下次才可再创建 64 | EasySerialBuilder.remove(this) 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/extend/EasyOtherExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:Suppress("unused") 18 | 19 | package com.bass.easySerial.extend 20 | 21 | import kotlinx.coroutines.suspendCancellableCoroutine 22 | import kotlinx.coroutines.withTimeoutOrNull 23 | 24 | 25 | /** 26 | * Create by BASS 27 | * on 2022/1/11 10:13. 28 | */ 29 | 30 | //tryCatch的内联函数 31 | internal suspend inline fun tryCatchSuspend(crossinline func: suspend () -> Unit) { 32 | try { 33 | func() 34 | } catch (e: Exception) { 35 | logE(e) 36 | } 37 | } 38 | 39 | /** 40 | * tryCatch的内联函数带有返回值 41 | * @param catchValue 捕获异常后的返回值 42 | */ 43 | internal suspend inline fun tryCatchSuspend( 44 | catchValue: T, 45 | crossinline func: suspend () -> T 46 | ): T { 47 | return try { 48 | func() 49 | } catch (e: Exception) { 50 | logE(e) 51 | catchValue 52 | } 53 | } 54 | 55 | //tryCatch的内联函数 56 | internal inline fun tryCatch(crossinline func: () -> Unit) { 57 | try { 58 | func() 59 | } catch (e: Exception) { 60 | logE(e) 61 | } 62 | } 63 | 64 | /** 65 | * tryCatch的内联函数带有返回值 66 | * @param catchValue 捕获异常后的返回值 67 | */ 68 | internal inline fun tryCatch(catchValue: T, crossinline func: () -> T): T { 69 | return try { 70 | func() 71 | } catch (e: Exception) { 72 | logE(e) 73 | catchValue 74 | } 75 | } 76 | 77 | //操作超时设定 78 | internal suspend inline fun blockWithTimeoutOrNull(timeOut: Long, crossinline func: () -> T?) = 79 | withTimeoutOrNull(timeOut) { 80 | suspendCancellableCoroutine { 81 | it.resumeWith(Result.success(func())) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/extend/EasyLogger.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.extend 18 | 19 | import android.util.Log 20 | import com.bass.easySerial.EasySerialBuilder 21 | import java.nio.charset.Charset 22 | 23 | internal const val TAG = "EasyPort" 24 | internal val encoder by lazy { Charset.forName("gbk").newEncoder() } 25 | 26 | @Suppress("UNUSED") 27 | internal fun logD(d: String) { 28 | if (EasySerialBuilder.showLog) Log.d(TAG, d) 29 | } 30 | 31 | @Suppress("UNUSED") 32 | internal fun logE(e: String) { 33 | if (EasySerialBuilder.showLog) Log.e(TAG, e) 34 | } 35 | 36 | internal fun logE(e: Exception) { 37 | if (EasySerialBuilder.showLog) Log.e(TAG, e.stackTraceToString()) 38 | } 39 | 40 | internal fun logE(e: Throwable) { 41 | if (EasySerialBuilder.showLog) Log.e(TAG, e.stackTraceToString()) 42 | } 43 | 44 | @Suppress("UNUSED") 45 | internal fun logI(i: String) { 46 | if (EasySerialBuilder.showLog) Log.i(TAG, i) 47 | } 48 | 49 | internal fun logPortSendData(bytes: ByteArray?) { 50 | if (!EasySerialBuilder.showLog) return 51 | val decodeToString = bytes?.decodeToString() 52 | if (encoder.canEncode(decodeToString)) 53 | logI("串口发起通信:${decodeToString}") 54 | else 55 | logI("串口发起通信:${bytes?.conver2HexStringWithBlank()}") 56 | } 57 | 58 | internal fun logPortReceiveData(bytes: ByteArray?) { 59 | if (!EasySerialBuilder.showLog) return 60 | try { 61 | val decodeToString = bytes?.decodeToString() 62 | if (tryCatch(false) { encoder.canEncode(decodeToString) }) logI("串口接收到数据:${decodeToString}") 63 | else logI("串口接收到数据:${bytes?.conver2HexStringWithBlank()}") 64 | } catch (e: Exception) { 65 | logI("串口接收到数据(无法转化为字符串或16进制形式):$bytes") 66 | } 67 | } -------------------------------------------------------------------------------- /easySerial/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 BASS 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # For more information about using CMake with Android Studio, read the 18 | # documentation: https://d.android.com/studio/projects/add-native-code.html 19 | 20 | # Sets the minimum version of CMake required to build the native library. 21 | 22 | cmake_minimum_required(VERSION 3.10.2) 23 | 24 | # Declares and names the project. 25 | 26 | project("EasySerial") 27 | 28 | # Creates and names a library, sets it as either STATIC 29 | # or SHARED, and provides the relative paths to its source code. 30 | # You can define multiple libraries, and CMake builds them for you. 31 | # Gradle automatically packages shared libraries with your APK. 32 | 33 | add_library( # Sets the name of the library. 34 | EasySerial 35 | 36 | # Sets the library as a shared library. 37 | SHARED 38 | 39 | # Provides a relative path to your source file(s). 40 | SerialPort.c 41 | SerialPort.h) 42 | 43 | # Searches for a specified prebuilt library and stores the path as a 44 | # variable. Because CMake includes system libraries in the search path by 45 | # default, you only need to specify the name of the public NDK library 46 | # you want to add. CMake verifies that the library exists before 47 | # completing its build. 48 | 49 | find_library( # Sets the name of the path variable. 50 | log-lib 51 | 52 | # Specifies the name of the NDK library that 53 | # you want CMake to locate. 54 | log) 55 | 56 | # Specifies libraries CMake should link to your target library. You 57 | # can link multiple libraries, such as libraries you define in this 58 | # build script, prebuilt third-party libraries, or system libraries. 59 | 60 | target_link_libraries( # Specifies the target library. 61 | EasySerial 62 | 63 | # Links the target library to the log library 64 | # included in the NDK. 65 | ${log-lib}) -------------------------------------------------------------------------------- /app/src/main/java/com/bass/easyserialport/keepReceive/KeepReceiveDemo1.kt: -------------------------------------------------------------------------------- 1 | package com.bass.easyserialport.keepReceive 2 | 3 | import android.util.Log 4 | import com.bass.easySerial.EasySerialBuilder 5 | import com.bass.easySerial.enums.* 6 | import com.bass.easySerial.extend.conver2HexString 7 | import com.bass.easySerial.interfaces.EasyReceiveCallBack 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.launch 11 | 12 | /** 13 | * Create by BASS 14 | * on 2022/10/29 22:24. 15 | * 演示1: 16 | * 创建一个永久接收的串口(串口开启失败返回Null) 17 | * 不做自定义返回数据的处理 18 | */ 19 | @Suppress("unused") 20 | class KeepReceiveDemo1 { 21 | 22 | private val tag = "KeepReceiveDemo1" 23 | 24 | /** 25 | * 演示1: 26 | * 创建一个永久接收的串口(串口开启失败返回Null) 27 | */ 28 | fun createPort() { 29 | //创建一个串口,串口返回的数据默认为ByteArray类型; 30 | val tempPort = 31 | EasySerialBuilder.createKeepReceivePort("/dev/ttyS4", BaudRate.B4800) 32 | 33 | //我们还可以这样创建串口,串口返回的数据默认为ByteArray类型; 34 | val tempPort2 = EasySerialBuilder.createKeepReceivePort( 35 | "/dev/ttyS4", 36 | BaudRate.B4800, DataBit.CS8, StopBit.B1, 37 | Parity.NONE, 0, FlowCon.NONE 38 | ) 39 | 40 | //串口可能会开启失败,在这里做简单判断; 41 | val port = tempPort ?: return 42 | 43 | //设置串口每次从数据流中读取的最大字节数,默认为64个字节; 44 | //注意:此方法一定要在监听串口返回之前设置,否则设置无效; 45 | port.setMaxReadSize(64) 46 | 47 | //设置数据的读取间隔,即上一次读取完数据后,隔多少秒后读取下一次数据; 48 | //默认为10毫秒,读取时间越短,CPU的占用会越高,请合理配置此设置; 49 | port.setReadInterval(100) 50 | 51 | //监听串口返回的数据; 第一种写法;须注意,此回调处于协程之中; 52 | val dataCallBack1 = port.addDataCallBack { 53 | //处理项目逻辑; 54 | // 此处示范将串口数据转化为16进制字符串; 55 | if (it.isEmpty()) return@addDataCallBack 56 | val hexString = it.last().conver2HexString() 57 | Log.d(tag, "接收到串口数据:$hexString") 58 | } 59 | //在我们不再需要使用的时候,可以移除串口监听; 60 | port.removeDataCallBack(dataCallBack1) 61 | 62 | //监听串口返回的数据,第二种写法;须注意,此回调处于协程之中; 63 | val dataCallBack2 = object : EasyReceiveCallBack { 64 | override suspend fun receiveData(dataList: List) { 65 | //处理项目逻辑; 66 | //此处示范将串口数据转化为16进制字符串; 67 | if (dataList.isEmpty()) return 68 | val hexString = dataList.last().conver2HexString() 69 | Log.d(tag, "接收到串口数据:$hexString") 70 | } 71 | 72 | } 73 | port.addDataCallBack(dataCallBack2) 74 | //在我们不再需要使用的时候,可以移除串口监听; 75 | port.removeDataCallBack(dataCallBack2) 76 | 77 | //使用完毕关闭串口,关闭串口须在协程中关闭,关闭时会阻塞当前协程,直到关闭处理完成; 78 | //这个过程并不会耗费太长时间,一般为1ms-4ms; 79 | CoroutineScope(Dispatchers.IO).launch { port.close() } 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bass/easyserialport/keepReceive/KeepReceiveDemo2.kt: -------------------------------------------------------------------------------- 1 | package com.bass.easyserialport.keepReceive 2 | 3 | import android.util.Log 4 | import com.bass.easySerial.EasySerialBuilder 5 | import com.bass.easySerial.enums.* 6 | import com.bass.easySerial.interfaces.EasyReceiveCallBack 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.launch 10 | 11 | /** 12 | * Create by BASS 13 | * on 2022/10/29 23:00. 14 | * 演示2: 15 | * 创建一个永久接收的串口(串口开启失败返回Null) 16 | * 自定义回调的数据类型 17 | */ 18 | @Suppress("unused") 19 | class KeepReceiveDemo2 { 20 | 21 | private val tag = "KeepReceiveDemo2" 22 | 23 | /** 24 | * 演示2: 25 | * 创建一个永久接收的串口(串口开启失败返回Null) 26 | */ 27 | fun createPort() { 28 | //创建一个串口,串口返回的数据类型,我们自定义为String类型; 29 | val tempPort = 30 | EasySerialBuilder.createKeepReceivePort("/dev/ttyS4", BaudRate.B4800) 31 | 32 | //我们还可以这样创建串口,串口返回的数据类型,我们自定义为String类型; 33 | val tempPort2 = EasySerialBuilder.createKeepReceivePort( 34 | "/dev/ttyS4", 35 | BaudRate.B4800, DataBit.CS8, StopBit.B1, 36 | Parity.NONE, 0, FlowCon.NONE 37 | ) 38 | 39 | //串口可能会开启失败,在这里做简单判断; 40 | val port = tempPort ?: return 41 | 42 | //设置串口每次从数据流中读取的最大字节数,默认为64个字节; 43 | //注意:此方法一定要在监听串口返回之前设置,否则设置无效; 44 | port.setMaxReadSize(64) 45 | 46 | //设置数据的读取间隔,即上一次读取完数据后,隔多少秒后读取下一次数据; 47 | //默认为10毫秒,读取时间越短,CPU的占用会越高,请合理配置此设置; 48 | port.setReadInterval(100) 49 | 50 | //因为我们设置数据返回类型不再是默认的ByteArray类型,所以我们需要设置自定义的数据解析规则; 51 | port.setDataHandle(CustomEasyPortDataHandle()) 52 | 53 | //监听串口返回的数据; 第一种写法;须注意,此回调处于协程之中; 54 | val dataCallBack1 = port.addDataCallBack { 55 | //返回的数据集内没有数据,则表明没有匹配成功的数据; 56 | //我们这里不处理没有匹配成功的情况; 57 | if (it.isEmpty()) return@addDataCallBack 58 | //处理项目逻辑; 59 | //此处演示直接将转化后的数据类型打印出来; 60 | Log.d(tag, "接收到串口数据:$it") 61 | } 62 | //在我们不再需要使用的时候,可以移除串口监听; 63 | port.removeDataCallBack(dataCallBack1) 64 | 65 | //监听串口返回的数据,第二种写法;须注意,此回调处于协程之中; 66 | val dataCallBack2 = object : EasyReceiveCallBack { 67 | override suspend fun receiveData(dataList: List) { 68 | //返回的数据集内没有数据,则表明没有匹配成功的数据; 69 | //我们这里不处理没有匹配成功的情况; 70 | if (dataList.isEmpty()) return 71 | //处理项目逻辑; 72 | //此处演示直接将转化后的数据类型打印出来; 73 | dataList.forEach { Log.d(tag, "接收到串口数据:$it") } 74 | } 75 | } 76 | port.addDataCallBack(dataCallBack2) 77 | //在我们不再需要使用的时候,可以移除串口监听; 78 | port.removeDataCallBack(dataCallBack2) 79 | 80 | //使用完毕关闭串口,关闭串口须在协程中关闭,关闭时会阻塞当前协程,直到关闭处理完成; 81 | //这个过程并不会耗费太长时间,一般为1ms-4ms; 82 | CoroutineScope(Dispatchers.IO).launch { port.close() } 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/util/EasySerialFinderUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.util 18 | 19 | import com.bass.easySerial.bean.DriverBean 20 | import com.bass.easySerial.extend.logD 21 | import com.bass.easySerial.extend.tryCatch 22 | import java.io.File 23 | import java.io.FileReader 24 | import java.io.LineNumberReader 25 | import java.util.* 26 | 27 | /** 28 | * Create by BASS 29 | * on 2022/6/29 14:05. 30 | */ 31 | @Suppress("unused") 32 | object EasySerialFinderUtil { 33 | 34 | /** 35 | * 获取所有的串口号 36 | */ 37 | fun getAllDevicesPath(): MutableList { 38 | val devices = Vector() 39 | // Parse each driver 40 | val iterator = getDrivers().iterator() 41 | tryCatch { 42 | while (iterator.hasNext()) { 43 | val bean = iterator.next() 44 | val fileIterator: Iterator = bean.devices.iterator() 45 | while (fileIterator.hasNext()) { 46 | val device = fileIterator.next().absolutePath 47 | devices.add(device) 48 | } 49 | } 50 | } 51 | return devices.toMutableList() 52 | } 53 | 54 | fun getAllDevices(): MutableList { 55 | val devices = Vector() 56 | tryCatch { 57 | val iterator = getDrivers().iterator() 58 | while (iterator.hasNext()) { 59 | val driver = iterator.next() 60 | val driverIterator: Iterator = driver.devices.iterator() 61 | while (driverIterator.hasNext()) { 62 | val device = driverIterator.next().name 63 | val value = String.format("%s (%s)", device, driver.driverName) 64 | devices.add(value) 65 | } 66 | } 67 | } 68 | return devices.toMutableList() 69 | } 70 | 71 | private fun getDrivers() = Vector().apply { 72 | tryCatch { 73 | val reader = LineNumberReader(FileReader("/proc/tty/drivers")) 74 | val regex = Regex(" +") 75 | var line: String? 76 | while (reader.readLine().also { line = it } != null) { 77 | // Since driver name may contain spaces, we do not extract driver name with split() 78 | val driverName = line!!.substring(0, 0x15).trim { it <= ' ' } 79 | val typeArray = line!!.split(regex).toTypedArray() 80 | if (typeArray.size >= 5 && typeArray[typeArray.size - 1] == "serial") { 81 | logD("Found new driver " + driverName + " on " + typeArray[typeArray.size - 4]) 82 | add(DriverBean(driverName, typeArray[typeArray.size - 4])) 83 | } 84 | } 85 | reader.close() 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bass/easyserialport/waitRsp/WaitRspDemo1.kt: -------------------------------------------------------------------------------- 1 | package com.bass.easyserialport.waitRsp 2 | 3 | import android.util.Log 4 | import com.bass.easySerial.EasySerialBuilder 5 | import com.bass.easySerial.enums.* 6 | import com.bass.easySerial.extend.conver2ByteArray 7 | import com.bass.easySerial.extend.conver2HexString 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.launch 11 | 12 | /** 13 | * Create by BASS 14 | * on 2022/10/30 0:21. 15 | * 演示1: 16 | * 创建一个发送后再接收的串口(串口开启失败返回Null) 17 | */ 18 | @Suppress("unused") 19 | class WaitRspDemo1 { 20 | 21 | private val tag = "WaitRspDemo1" 22 | 23 | fun createPort() { 24 | //创建一个发送后再接收的串口 25 | val tempPort = EasySerialBuilder.createWaitRspPort("/dev/ttyS4", BaudRate.B4800) 26 | 27 | //我们还可以这样创建串口 28 | val tempPort2 = EasySerialBuilder.createWaitRspPort( 29 | "/dev/ttyS4", 30 | BaudRate.B4800, DataBit.CS8, StopBit.B1, 31 | Parity.NONE, 0, FlowCon.NONE 32 | ) 33 | 34 | //串口可能会开启失败,在这里做简单判断; 35 | val port = tempPort ?: return 36 | 37 | //设置串口每次从数据流中读取的最大字节数,默认为64个字节; 38 | //对于不可读取字节数的串口,必须在调用[writeWaitRsp]或[writeAllWaitRsp]之前调用,否则设置无效; 39 | //在请求时不指定接收数据的最大字节数时,将会使用这里配置的字节大小; 40 | port.setMaxReadSize(64) 41 | 42 | //设置数据的读取间隔,即上一次读取完数据后,隔多少秒后读取下一次数据; 43 | //默认为10毫秒,读取时间越短,CPU的占用会越高,请合理配置此设置; 44 | port.setReadInterval(100) 45 | 46 | //假设几个串口命令: 47 | val orderByteArray1 = "1A FF FF".conver2ByteArray() 48 | val orderByteArray2 = "2B FF FF".conver2ByteArray() 49 | val orderByteArray3 = "3C FF FF".conver2ByteArray() 50 | 51 | //发送串口命令,并等待返回的示例1: 52 | CoroutineScope(Dispatchers.IO).launch { 53 | //此方法我们必须在协程作用域中调用,默认等待返回时间为200ms, 54 | //即调用此函数,将会阻塞200ms,并将此期间接收到的串口数据返回给调用方; 55 | val rspBean = port.writeWaitRsp(orderByteArray1) 56 | //此外,我们也可以指定等待时间,如下示例: 57 | val rspBean2 = port.writeWaitRsp(orderByteArray1, timeOut = 500) 58 | //还可以,指定本次请求接收的数据的最大字节数,如下示例: 59 | val rspBean3 = port.writeWaitRsp(orderByteArray1, bufferSize = 64) 60 | 61 | //讲解一下返回的数据: 62 | rspBean.bytes 63 | //串口返回的数据,此字节数组的大小为我们setBufferSize()时输入的字节大小; 64 | //需要注意的是,字节数组内的字节并不全是串口返回的数据; 65 | //我们假设串口返回了4个字节的数据,那么其余的60个字节都是0; 66 | //那我们怎么知道收到了多少个字节呢? 67 | rspBean.size 68 | //以上便是串口返回的字节长度,所以我们取串口返回的实际字节数组可以这样取: 69 | val portBytes = rspBean.bytes.copyOf(rspBean.size) 70 | //插句题外话,我们也提供了直接将读取到的字节转为16进制字符串的方法: 71 | val hexString = rspBean.bytes.conver2HexString(rspBean.size) 72 | 73 | //在获取到后做我们自己的业务逻辑 74 | Log.d(tag, "接收到数据:${hexString}") 75 | } 76 | 77 | //发送串口命令,并等待返回的示例2: 78 | //有时候,我们可能需要连续向串口输出命令,并等待其返回,对此我们也提供了便捷的方案: 79 | CoroutineScope(Dispatchers.IO).launch { 80 | //此方法我们必须在协程作用域中调用 81 | //调用此方法我们内部将按照顺序一个一个请求并收集结果,将结果返回 82 | val rspBeanList1 = 83 | port.writeAllWaitRsp(orderByteArray1, orderByteArray2, orderByteArray3) 84 | //或者是指定每个请求的超时时间: 85 | val rspBeanList2 = 86 | port.writeAllWaitRsp(200, orderByteArray1, orderByteArray2, orderByteArray3) 87 | //又或者,即指定每个请求的超时时间,也指定每个请求接收数据的最大字节数: 88 | val rspBeanList3 = 89 | port.writeAllWaitRsp(200, 64, orderByteArray1, orderByteArray2, orderByteArray3) 90 | //以下返回的数据与请求一一对应: 91 | val rspBean1 = rspBeanList1[0]//orderByteArray1 92 | val rspBean2 = rspBeanList1[1]//orderByteArray2 93 | val rspBean3 = rspBeanList1[2]//orderByteArray3 94 | 95 | //在获取到后做我们自己的业务逻辑 96 | Log.d(tag, "接收到数据:${rspBean1.bytes.conver2HexString(rspBean1.size)}") 97 | Log.d(tag, "接收到数据:${rspBean2.bytes.conver2HexString(rspBean2.size)}") 98 | Log.d(tag, "接收到数据:${rspBean3.bytes.conver2HexString(rspBean3.size)}") 99 | } 100 | 101 | //发送串口命令,示例3: 102 | //在同一个串口中,我们有些需要等待串口的数据返回,有些是不需要的,在不需要串口数据返回的情况下, 103 | //我们可以直接调用写入即可: 104 | CoroutineScope(Dispatchers.IO).launch { 105 | //此方法我们必须在协程作用域中调用 106 | port.write(orderByteArray1) 107 | } 108 | 109 | //使用完毕关闭串口,关闭串口须在协程中关闭,关闭时会阻塞当前协程,直到关闭处理完成; 110 | //这个过程并不会耗费太长时间,一般为1ms-4ms; 111 | CoroutineScope(Dispatchers.IO).launch { port.close() } 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/wrapper/EasyKeepReceivePort.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.wrapper 18 | 19 | import com.bass.easySerial.SerialPort 20 | import com.bass.easySerial.handle.EasyPortDataHandle 21 | import com.bass.easySerial.interfaces.EasyReadDataCallBack 22 | import com.bass.easySerial.interfaces.EasyReceiveCallBack 23 | import com.bass.easySerial.extend.logPortReceiveData 24 | import kotlinx.coroutines.CoroutineScope 25 | import kotlinx.coroutines.Dispatchers 26 | import kotlinx.coroutines.launch 27 | import kotlinx.coroutines.sync.Mutex 28 | import kotlinx.coroutines.sync.withLock 29 | import java.util.concurrent.CopyOnWriteArrayList 30 | 31 | /** 32 | * Create by BASS 33 | * on 2021/12/24 9:00. 34 | * 35 | * 通过 **[com.bass.easySerial.EasySerialBuilder.createKeepReceivePort]** 生成本类对象 36 | * 37 | * 接收数据:永远保持接收状态(耗费cpu); 适合:串口主动传输到客户端 38 | * 发送数据:不阻塞,不需要等待结果返回 39 | * 40 | * @param CallBackType 返回的数据类型 41 | */ 42 | @Suppress("UNUSED") 43 | class EasyKeepReceivePort internal constructor(serialPort: SerialPort) : 44 | BaseEasySerialPort(serialPort), EasyReadDataCallBack { 45 | 46 | private val callBackList by lazy { CopyOnWriteArrayList>() }//监听数据返回 47 | private var dataHandle: EasyPortDataHandle? = null//数据处理类 48 | private val openReceiveMutex by lazy { Mutex() }//开启串口接收的同步锁,防止多次开启 49 | private var isStart = false//标志是否已经开启了数据监听 50 | 51 | @Suppress("UNCHECKED_CAST") 52 | //监听串口数据 53 | override suspend fun receiveData(bytes: ByteArray) { 54 | CoroutineScope(Dispatchers.IO).launch {//开启顶层协程,不阻塞串口的读取 55 | logPortReceiveData(bytes)//输出获取到的数据 56 | dataHandle?.apply {//自定义了数据处理,则处理数据 57 | val dataList = receivePortData(bytes) 58 | callBackList.forEach { it.receiveData(dataList) }//处理完成数据后发生给监听者 59 | } ?: run {//没有自定义数据处理,则直接返回原始数据 60 | try { 61 | callBackList.forEach { it.receiveData(listOf(bytes as CallBackType)) } 62 | } catch (e: Exception) { 63 | throw RuntimeException( 64 | "如果您没有设置数据处理方法,则传入的CallBackType类型应当为ByteArray,否则将无法进行数据转换", 65 | e.cause 66 | ) 67 | } 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * 添加串口数据监听,可在不同地方添加多个监听 74 | * @param callBack 监听 75 | */ 76 | fun addDataCallBack(callBack: EasyReceiveCallBack) { 77 | callBackList.add(callBack) 78 | start()//开启串口接收 79 | } 80 | 81 | /** 82 | * 添加串口数据监听,可在不同地方添加多个监听 83 | * @param callBack 监听 84 | */ 85 | fun addDataCallBack(callBack: suspend (List) -> Unit): EasyReceiveCallBack { 86 | val receiveCallBack = object : EasyReceiveCallBack { 87 | override suspend fun receiveData(dataList: List) { 88 | callBack(dataList) 89 | } 90 | } 91 | callBackList.add(receiveCallBack) 92 | start()//开启串口接收 93 | return receiveCallBack 94 | } 95 | 96 | /** 97 | * 移除指定的串口监听 98 | */ 99 | fun removeDataCallBack(callBack: EasyReceiveCallBack) { 100 | callBackList.remove(callBack) 101 | } 102 | 103 | /** 104 | * 设置数据处理方法,接收到串口数据后会对数据进行处理后返回 105 | * @param dataHandle 自定义的数据处理类 106 | */ 107 | fun setDataHandle(dataHandle: EasyPortDataHandle): EasyKeepReceivePort { 108 | this.dataHandle = dataHandle 109 | return this 110 | } 111 | 112 | /** 113 | * 设置串口每次从数据流中读取的最大字节数; 114 | * 必须在调用[addDataCallBack]之前设置,否则设置无效; 115 | * @param max 指定串口每次从数据流中读取的最大字节数; 116 | */ 117 | fun setMaxReadSize(max: Int): EasyKeepReceivePort { 118 | customMaxReadSize = max 119 | return this 120 | } 121 | 122 | /** 123 | * 设置串口数据读取的间隔 单位为毫秒; 124 | * 默认为10毫秒,读取时间越短,CPU的占用会越高,请合理配置此设置; 125 | * @param interval 间隔时间(毫秒) 126 | */ 127 | fun setReadInterval(interval: Long): EasyKeepReceivePort { 128 | serialPort.setReadInterval(interval) 129 | return this 130 | } 131 | 132 | /** 133 | * 写入一个数据 134 | * @param byteArray 写入的数据 135 | */ 136 | fun write(byteArray: ByteArray) { 137 | serialPort.write(byteArray) 138 | } 139 | 140 | //开始接收数据 141 | private fun start() { 142 | if (openReceiveMutex.isLocked || isStart) return 143 | CoroutineScope(Dispatchers.IO).launch { 144 | openReceiveMutex.withLock { 145 | if (isStart) return@launch//已经开启了则不再开启 146 | serialPort.setReadDataCallBack(this@EasyKeepReceivePort) 147 | serialPort.startRead(customMaxReadSize) 148 | isStart = true 149 | } 150 | } 151 | } 152 | 153 | override suspend fun close() { 154 | callBackList.clear() 155 | dataHandle?.close() 156 | dataHandle = null 157 | super.close() 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/util/ErgodicPortUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.util 18 | 19 | import android.util.Log 20 | import com.bass.easySerial.SerialPort 21 | import com.bass.easySerial.bean.ErgodicBean 22 | import com.bass.easySerial.enums.BaudRate 23 | import com.bass.easySerial.enums.DataBit 24 | import com.bass.easySerial.enums.Parity 25 | import com.bass.easySerial.enums.StopBit 26 | import com.bass.easySerial.extend.conver2ByteArray 27 | import com.bass.easySerial.wrapper.EasyWaitRspPort 28 | import kotlinx.coroutines.CoroutineScope 29 | import kotlinx.coroutines.Dispatchers 30 | import kotlinx.coroutines.launch 31 | import java.io.File 32 | 33 | /** 34 | * Create by BASS 35 | * on 2021/12/15 14:42. 36 | * 用于循环查找需要的串口,此方法处于实验阶段,暂未开放; 37 | */ 38 | @Suppress("UNUSED") 39 | internal object ErgodicPortUtil { 40 | 41 | private var bytes2: ByteArray? = null 42 | private val successList by lazy { mutableListOf() } 43 | private val baudRates by lazy { intArrayOf(BaudRate.B4800.baudRate, BaudRate.B9600.baudRate) } 44 | private val nBits by lazy { intArrayOf( 45 | DataBit.CS5.dataBit, 46 | DataBit.CS6.dataBit, 47 | DataBit.CS7.dataBit, 48 | DataBit.CS8.dataBit 49 | ) } 50 | private val nEvent by lazy { arrayOf(Parity.NONE, Parity.ODD, Parity.EVEN) } 51 | private val mStop by lazy { intArrayOf(StopBit.B1.stopBit, StopBit.B2.stopBit) } 52 | 53 | /** 54 | * @param hexStr 16进制串口查询指令 55 | */ 56 | fun ergodic(hexStr: String) { 57 | bytes2 = hexStr.conver2ByteArray() 58 | CoroutineScope(Dispatchers.IO).launch { 59 | for (path in EasySerialFinderUtil.getAllDevicesPath()) { 60 | for (baudRate in baudRates) { 61 | for (nBit in nBits) { 62 | for (event in nEvent) { 63 | for (stop in mStop) { 64 | try { 65 | Log.i( 66 | "遍历串口", 67 | "本次尝试-->path: $path baudRate:$baudRate nBit:$nBit event:$event stop:$stop" 68 | ) 69 | bytes2?.let { 70 | val serialPort = SerialPort( 71 | File(path), 72 | baudRate, 73 | 0, 74 | nBit, 75 | stop, 76 | event.parity 77 | ) 78 | val easyWaitRspPort = EasyWaitRspPort(serialPort) 79 | val dataList = easyWaitRspPort.writeWaitRsp(it) 80 | if (dataList.size > 0) { 81 | Log.w( 82 | "遍历串口", 83 | "成功!!! path:$path baudRate:$baudRate nBit:$nBit event:$event stop:$stop \nreturned: ${it.contentToString()}" 84 | ) 85 | ErgodicBean( 86 | path, 87 | baudRate, 88 | 0, 89 | nBit, 90 | stop, 91 | event.parity 92 | ).apply { 93 | successList.add(this) 94 | } 95 | } else { 96 | Log.e( 97 | "遍历串口", 98 | "本次失败 path: $path baudRate:$baudRate nBit:$nBit event:$event stop:$stop" 99 | ) 100 | } 101 | easyWaitRspPort.close() 102 | } 103 | } catch (e: Exception) { 104 | Log.w( 105 | "遍历串口", 106 | "异常 path: $path baudRate:$baudRate nBit:$nBit event:$event stop:$stop" 107 | ) 108 | Log.w("遍历串口", "原因:${e.cause}") 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } 115 | Log.i("遍历串口", "成功如下:\n") 116 | successList.forEach { 117 | Log.i( 118 | "遍历串口", 119 | "path: ${it.path} baudRate:${it.baudRate} nBit:${it.dataBits} event:${it.parity} stop:${it.stopBits}" 120 | ) 121 | } 122 | 123 | } 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/java/com/bass/easyserialport/other/OtherApiDemo.kt: -------------------------------------------------------------------------------- 1 | package com.bass.easyserialport.other 2 | 3 | import android.util.Log 4 | import com.bass.easySerial.EasySerialBuilder 5 | import com.bass.easySerial.enums.BaudRate 6 | import com.bass.easySerial.extend.* 7 | import com.bass.easySerial.util.EasySerialFinderUtil 8 | import com.bass.easySerial.wrapper.BaseEasySerialPort 9 | import kotlinx.coroutines.CoroutineScope 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.launch 12 | 13 | /** 14 | * Create by BASS 15 | * on 2022/10/31 23:42. 16 | * 其他API的调用示例 17 | */ 18 | class OtherApiDemo { 19 | 20 | private val tag = "OtherApiDemo" 21 | 22 | /** 23 | * 获取串口对象,示例 24 | */ 25 | fun getPortDemo() { 26 | //1.获取串口对象 27 | //每一个串口只会创建一个实例,我们在内部缓存了串口实例,即一处创建,到处可取; 28 | //如果此串口还未创建,则将获取到Null; 29 | val tempPort: BaseEasySerialPort? = EasySerialBuilder.get("dev/ttyS4") 30 | 31 | val serialPort = tempPort ?: return 32 | 33 | //获取到实例后,我们仅可以调用close()方法关闭串口 34 | //此方法必须在协程作用域中调用 35 | CoroutineScope(Dispatchers.IO).launch { serialPort.close() } 36 | 37 | //如果你明确知道当前串口属于哪种类型,那么你可以进行类型强转后使用更多特性。如: 38 | val easyWaitRspPort = serialPort.cast2WaitRspPort() 39 | CoroutineScope(Dispatchers.IO).launch { 40 | val rspBean = easyWaitRspPort.writeWaitRsp("00 FF AA".conver2ByteArray()) 41 | } 42 | //或者是: 43 | val keepReceivePort = serialPort.cast2KeepReceivePort() 44 | keepReceivePort.write("00 FF AA".conver2ByteArray()) 45 | } 46 | 47 | /** 48 | * 设置串口不读取字节数,示例: 49 | */ 50 | fun addNoAvailableDemo() { 51 | //如果你发现,串口无法收到数据,但是可正常写入数据,使用串口调试工具可正常收发, 52 | //那么你应当试试如下将串口设置为无法读取字节数: 53 | EasySerialBuilder.addNoAvailableDevicePath("dev/ttyS4") 54 | //设置完后再开启串口,否则设置不生效; 55 | //也可以直接这么写: 56 | EasySerialBuilder.addNoAvailableDevicePath("dev/ttyS4") 57 | .createWaitRspPort("dev/ttyS4", BaudRate.B4800) 58 | 59 | //对于`addNoAvailableDevicePath()`方法,需要讲解一下内部串口数据读取的实现了, 60 | //在读取数据时,会先调用`inputStream.available()` 来判断流中有多少个可读字节,但在部分串口中,即使有数据, 61 | //`available()`读取到的依旧是0,这就导致了无法读取到数据的情况,当调用`addNoAvailableDevicePath()`后, 62 | //我们将不再判断流中的可读字节数,而是直接调用`inputStream.read()`方法; 63 | //当你使用此方法后,请勿重复开启\关闭串口, 因为这样可能会导致串口无法再工作; 64 | } 65 | 66 | /** 67 | * 串口日志打印开关 68 | */ 69 | fun showLogDemo() { 70 | //是否打印串口通信日志 true为打印日志,false为不打印; 71 | //建议在Release版本中不打印串口日志; 72 | //打印的日志的 tag = "EasyPort"; 73 | EasySerialBuilder.isShowLog(true) 74 | } 75 | 76 | /** 77 | * 获取本设备所有的串口名称,示例 78 | */ 79 | fun getAllPortNameDemo() { 80 | val allDevicesPath: MutableList = EasySerialFinderUtil.getAllDevicesPath() 81 | allDevicesPath.forEach { 82 | Log.d(tag, "串口名称: $it") 83 | } 84 | } 85 | 86 | /** 87 | * 判断当前是否有串口正在使用,示例 88 | */ 89 | fun hasPortWorkingDemo() { 90 | val hasPortWorking: Boolean = EasySerialBuilder.hasPortWorking() 91 | Log.d(tag, "当前是否有串口正在使用: $hasPortWorking") 92 | } 93 | 94 | /** 95 | * 数据转化,示例 96 | */ 97 | fun conversionDemo() { 98 | /** ----------- 16进制字符串转为字节数组 start------------------*/ 99 | val hexString = "00 FF CA FA" 100 | //将16进制字符串 转为 字节数组 101 | val hexByteArray1 = hexString.conver2ByteArray() 102 | //将16进制字符串从第0位截取到第4位("00 FF") 转为 字节数组 103 | val hexByteArray2 = hexString.conver2ByteArray(4) 104 | //将16进制字符串从第2位截取到第4位(" FF") 转为 字节数组 105 | val hexByteArray3 = hexString.conver2ByteArray(2, 4) 106 | /** ----------- 16进制字符串转为字节数组 end ------------------*/ 107 | 108 | 109 | /** ----------- 字节数组转为16进制字符串 start------------------*/ 110 | val byteArray = byteArrayOf(0, -1, 10)// 此字节数组=="00FF0A" 111 | //将字节数组 转为 16进制字符串 112 | val hexStr1 = byteArray.conver2HexString()//结果为:"00FF0A" 113 | //将字节数组取1位 转为 16进制字符串 114 | val hexStr2 = byteArray.conver2HexString(1)//结果为:"00" 115 | //将字节数组取2位 转为 16进制字符串 并设置字母为小写 116 | val hexStr3 = byteArray.conver2HexString(2, false)//结果为:"00ff" 117 | //将字节数组取第2位到第3位 转为 16进制字符串 并设置字母为小写 118 | val hexStr4 = byteArray.conver2HexString(1, 2, false)//结果为:"ff0a" 119 | //将字节数组取第0位 转为 16进制字符串 并设置字母为小写 120 | val hexStr5 = byteArray.conver2HexString(0, 0, false)//结果为:"00" 121 | 122 | //将字节数组 转为 16进制字符串 16进制之间用空格分隔 123 | val hexStr6 = byteArray.conver2HexStringWithBlank()//结果为:"00 FF 0A" 124 | //将字节数组取2位 转为 16进制字符串 16进制之间用空格分隔 125 | val hexStr7 = byteArray.conver2HexStringWithBlank(2)//结果为:"00 FF" 126 | //将字节数组取2位 转为 16进制字符串 并设置字母为小写 127 | val hexStr8 = byteArray.conver2HexStringWithBlank(2, false)//结果为:"00 ff" 128 | //将字节数组取第2位到第3位 转为 16进制字符串 并设置字母为小写 129 | val hexStr9 = byteArray.conver2HexStringWithBlank(1, 2, false)//结果为:"ff 0a" 130 | //将字节数组取第2位 转为 16进制字符串 并设置字母为小写 131 | val hexStr10 = byteArray.conver2HexStringWithBlank(1, 1, false)//结果为:"ff" 132 | /** ----------- 字节数组转为16进制字符串 end ------------------*/ 133 | 134 | 135 | /** ----------- 字节数组转为字符数组 start------------------*/ 136 | val byteArray2 = 137 | byteArrayOf('H'.code.toByte(), 'A'.code.toByte(), 'H'.code.toByte(), 'A'.code.toByte()) 138 | //将字节数组 转为 字符数组 139 | val charArray1 = byteArray2.conver2CharArray()//即:"HAHA" 140 | //将字节数组取1位 转为 字符数组 141 | val charArray2 = byteArray2.conver2CharArray(1)//即:"H" 142 | //将字节数组取第2位到第3位 转为 字符数组 143 | val charArray3 = byteArray2.conver2CharArray(2, 3)//即:"HA" 144 | //将字节数组第2位 转为 字符数组 145 | val charArray4 = byteArray2.conver2CharArray(2, 2)//即:"H" 146 | /** ----------- 字节数组转为字符数组 end ------------------*/ 147 | 148 | 149 | /** ----------- 字节数组计算CRC值 start------------------*/ 150 | val byteArray3 = byteArrayOf(1, 3, 0, 0, 0, 9) 151 | //计算字节数组的CRC值: 152 | val crc1 = byteArray3.getCRC() 153 | //将字节数组取2位,计算CRC值: 154 | val crc2 = byteArray3.getCRC(2) 155 | //将字节数组取第2位到第3位,计算CRC值: 156 | val crc3 = byteArray3.getCRC(2, 3) 157 | 158 | //将crc转为字节的计算示例: 159 | val highByte = (crc1 and 0xFF).toByte()//高位 160 | val lowByte = (crc1 shr 8 and 0xFF).toByte()//低位 161 | /** ----------- 字节数组计算CRC值 end ------------------*/ 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/SerialPort.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial 18 | 19 | import com.bass.easySerial.enums.DataBit 20 | import com.bass.easySerial.extend.* 21 | import com.bass.easySerial.extend.blockWithTimeoutOrNull 22 | import com.bass.easySerial.extend.logPortSendData 23 | import com.bass.easySerial.extend.tryCatch 24 | import com.bass.easySerial.extend.tryCatchSuspend 25 | import com.bass.easySerial.interfaces.EasyReadDataCallBack 26 | import kotlinx.coroutines.* 27 | import java.io.* 28 | 29 | /** 30 | * 串口对象创建类 31 | * @param device 串口文件 32 | * @param baudRate 波特率 33 | * @param flags 校验位 34 | * @param dataBits 数据位 取值 5、6、7、8 35 | * @param stopBits 停止位 取值1 或者 2 36 | * @param parity 校验类型 取值 0(NONE), 1(ODD), 2(EVEN) 37 | * @param flowCon 流控 38 | */ 39 | class SerialPort( 40 | device: File, baudRate: Int, flags: Int, 41 | dataBits: Int, stopBits: Int, parity: Int, flowCon: Int = 0 42 | ) { 43 | private val mFd: FileDescriptor?//文件描述符 44 | internal val isNoAvailable: Boolean//是否为不可读取字节数的串口 45 | private val devicePath: String 46 | private var mFileInputStream: FileInputStream? = null//串口读取流 47 | private var mFileOutputStream: FileOutputStream? = null//串口写入流 48 | private var readJob: Job? = null//重复读取的协程 49 | private var readDataCallBack: EasyReadDataCallBack? = null//串口读取数据观察者 50 | private var readInterval = 10L//串口数据读取的间隔 单位为毫秒 51 | 52 | 53 | companion object { 54 | init { 55 | System.loadLibrary("EasySerial") 56 | } 57 | } 58 | 59 | init { 60 | //判断串口名称是否为不可读取字节数的串口 61 | devicePath = device.absolutePath 62 | isNoAvailable = 63 | EasySerialBuilder.noAvailableList.find { it == devicePath }?.let { true } ?: false 64 | //检查访问权限 65 | if (!device.canRead() || !device.canWrite()) { 66 | try { 67 | /* Missing read/write permission, trying to chmod the file */ 68 | val su: Process = Runtime.getRuntime().exec("/system/bin/su") 69 | val cmd = "chmod 666 ${device.absolutePath}\nexit\n" 70 | su.outputStream.write(cmd.toByteArray()) 71 | if (su.waitFor() != 0 || !device.canRead() || !device.canWrite()) throw SecurityException() 72 | } catch (e: Exception) { 73 | e.printStackTrace() 74 | throw SecurityException() 75 | } 76 | } 77 | mFd = if (dataBits == DataBit.CSEmpty.dataBit) open(device.absolutePath, baudRate, flags) 78 | else open2(device.absolutePath, baudRate, stopBits, dataBits, parity, flowCon, flags) 79 | if (mFd == null) { 80 | logE("native open returns null") 81 | throw IOException() 82 | } 83 | mFileInputStream = FileInputStream(mFd) 84 | mFileOutputStream = FileOutputStream(mFd) 85 | } 86 | 87 | //开始永久接收 88 | internal fun startRead(maxReadSize: Int) { 89 | readJob?.let { return } 90 | readJob = CoroutineScope(Dispatchers.IO).launch { 91 | val buffer = ByteArray(maxReadSize) 92 | var size: Int 93 | while (isActive) { 94 | try { 95 | mFileInputStream ?: throw NullPointerException("串口输入流未被初始化!!!") 96 | mFileInputStream?.let { 97 | size = if (isNoAvailable) noAvailableHandle(buffer, it) 98 | else availableHandle(buffer, it) 99 | //4.判断读取到的字节大小 返回读取到的字节的副本 100 | if (size > 0) readDataCallBack?.receiveData(buffer.copyOf(size)) 101 | } 102 | delay(readInterval) 103 | } catch (e: Exception) { 104 | logE(e) 105 | readJob = null 106 | break 107 | } 108 | } 109 | } 110 | } 111 | 112 | //关闭永久接收 113 | internal suspend fun closeRead() { 114 | withTimeoutOrNull(200) { tryCatchSuspend { readJob?.cancelAndJoin() } } 115 | readJob = null 116 | readDataCallBack = null 117 | } 118 | 119 | //设置串口读取的间隔 120 | internal fun setReadInterval(readInterval: Long) { 121 | this.readInterval = readInterval 122 | } 123 | 124 | //监听串口数据 125 | internal fun setReadDataCallBack(readDataCallBack: EasyReadDataCallBack?) { 126 | this.readDataCallBack = readDataCallBack 127 | } 128 | 129 | //直接写入 130 | internal fun write(bytes: ByteArray?) { 131 | logPortSendData(bytes) 132 | tryCatch { bytes?.let { mFileOutputStream?.write(it) } } 133 | } 134 | 135 | //关闭串口以及输入输出流 136 | internal suspend fun closeSerial() { 137 | closeRead() 138 | tryCatch { mFileInputStream?.close() } 139 | tryCatch { mFileOutputStream?.close() } 140 | close() 141 | } 142 | 143 | //获取串口的名称 如:/dev/ttyS4 144 | internal fun getDevicePath(): String = devicePath 145 | 146 | //不允许读取字节数的处理 147 | private fun noAvailableHandle(buffer: ByteArray, inputStream: FileInputStream): Int { 148 | return inputStream.read(buffer) 149 | } 150 | 151 | //允许读取字节数的处理 152 | private suspend fun availableHandle(buffer: ByteArray, inputStream: FileInputStream): Int { 153 | //1.获取可读的字节数 154 | var availableSize = tryCatchSuspend(0) { 155 | blockWithTimeoutOrNull(5) { inputStream.available() } ?: 0 156 | } 157 | //2.判断可读的字节数是否合规 158 | if (availableSize < 0) availableSize = 0 159 | else if (availableSize > buffer.size) availableSize = buffer.size 160 | //3.读取超时限定 读取指定长度的数据,如果不指定长度,在无法读取时可能会阻塞协程 161 | return blockWithTimeoutOrNull(10) { 162 | inputStream.read(buffer, 0, availableSize) 163 | } ?: 0 164 | } 165 | 166 | private external fun open(path: String, baudRate: Int, flags: Int): FileDescriptor? 167 | private external fun open2( 168 | path: String, 169 | baudRate: Int, 170 | stopBits: Int, 171 | dataBits: Int, 172 | parity: Int, 173 | flowCon: Int, 174 | flags: Int 175 | ): FileDescriptor? 176 | 177 | private external fun close() 178 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/wrapper/EasyWaitRspPort.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bass.easySerial.wrapper 18 | 19 | import com.bass.easySerial.SerialPort 20 | import com.bass.easySerial.bean.WaitResponseBean 21 | import com.bass.easySerial.interfaces.EasyReadDataCallBack 22 | import com.bass.easySerial.extend.logPortReceiveData 23 | import kotlinx.coroutines.* 24 | import kotlinx.coroutines.sync.Mutex 25 | import kotlinx.coroutines.sync.withLock 26 | import java.nio.ByteBuffer 27 | 28 | /** 29 | * 通过 **[com.bass.easySerial.EasySerialBuilder.createWaitRspPort]** 生成本类对象 30 | * 31 | * 接收数据:写入后立即等待数据返回 32 | * 发送数据:写入一次数据,就阻塞写入时的协程,等待返回一次数据,带有超时限制; 33 | * 多个协程同时写入将会排队执行上诉过程; 34 | */ 35 | @Suppress("UNUSED") 36 | class EasyWaitRspPort internal constructor(serialPort: SerialPort) : 37 | BaseEasySerialPort(serialPort) { 38 | 39 | private val mutex by lazy { Mutex() }//同步锁 防止上一次写入过程未结束 下一次就开始 40 | 41 | /** 42 | * 设置串口每次从数据流中读取的最大字节数; 43 | * 对于不可读取字节数的串口,必须在调用[writeWaitRsp]或[writeAllWaitRsp]之前调用,否则设置无效; 44 | * 在请求时不指定接收数据的最大字节数时,将会使用这里配置的字节大小; 45 | * @param max 指定串口每次从数据流中读取的最大字节数; 46 | */ 47 | fun setMaxReadSize(max: Int): EasyWaitRspPort { 48 | customMaxReadSize = max 49 | return this 50 | } 51 | 52 | /** 53 | * 设置串口数据读取的间隔 单位为毫秒; 54 | * 默认为10毫秒,读取时间越短,CPU的占用会越高,请合理配置此设置; 55 | * @param interval 间隔时间(毫秒) 56 | */ 57 | fun setReadInterval(interval: Long): EasyWaitRspPort { 58 | serialPort.setReadInterval(interval) 59 | return this 60 | } 61 | 62 | /** 63 | * 写入数据 不阻塞等待结果返回 64 | * @param order 写入的数据 65 | */ 66 | suspend fun write(order: ByteArray) { 67 | mutex.withLock { serialPort.write(order) } 68 | } 69 | 70 | /** 71 | * 写入数据并等待返回 72 | * 写入后将阻塞写入的协程,并等待结果返回 73 | * @param order 写入的数据 74 | * @param timeOut 每次读取的超时时间,默认200ms 75 | * @param bufferSize 接收数据的最大字节数,默认为全局配置的最大字节数 76 | * @return 返回读取到的数据 77 | */ 78 | suspend fun writeWaitRsp( 79 | order: ByteArray, 80 | timeOut: Long = 200, 81 | bufferSize: Int = customMaxReadSize 82 | ): WaitResponseBean { 83 | mutex.withLock { 84 | return writeTimeOut(order, timeOut, bufferSize) 85 | } 86 | } 87 | 88 | /** 89 | * 写入数据并等待返回 90 | * 写入后将阻塞写入的协程,并等待结果返回 91 | */ 92 | suspend fun writeAllWaitRsp(vararg orderList: ByteArray): MutableList { 93 | return writeAllWaitRsp(timeOut = 200, bufferSize = customMaxReadSize, orderList = orderList) 94 | } 95 | 96 | /** 97 | * 写入数据并等待返回 98 | * 写入后将阻塞写入的协程,并等待结果返回 99 | */ 100 | suspend fun writeAllWaitRsp( 101 | timeOut: Long = 200, 102 | vararg orderList: ByteArray 103 | ): MutableList { 104 | return writeAllWaitRsp( 105 | timeOut = timeOut, 106 | bufferSize = customMaxReadSize, 107 | orderList = orderList 108 | ) 109 | } 110 | 111 | /** 112 | * 写入数据并等待返回 113 | * 写入后将阻塞写入的协程,并等待结果返回 114 | * @param timeOut 每次读取的超时时间,默认200ms 115 | * @param bufferSize 每个请求接收数据的最大字节数,默认为全局配置的最大字节数 116 | * @param orderList 写入的数据,可同时写入多个 117 | * @return 返回读取到的数据 118 | */ 119 | suspend fun writeAllWaitRsp( 120 | timeOut: Long = 200, 121 | bufferSize: Int = customMaxReadSize, 122 | vararg orderList: ByteArray 123 | ): MutableList { 124 | mutex.withLock { 125 | val rspList = mutableListOf() 126 | orderList.forEach { rspList.add(writeTimeOut(it, timeOut, bufferSize)) } 127 | return rspList 128 | } 129 | } 130 | 131 | /** 132 | * 阻塞读取数据 133 | * @param orderBytes 要发送的指令 134 | * @param timeOut 读取超时限定 135 | * @param bufferSize 接收数据的最大字节数 136 | * @return 返回读取到的数据以及数据大小 137 | */ 138 | private suspend fun writeTimeOut( 139 | orderBytes: ByteArray, 140 | timeOut: Long, 141 | bufferSize: Int 142 | ): WaitResponseBean { 143 | val pair = if (serialPort.isNoAvailable) noAvailableHandle(orderBytes, timeOut, bufferSize) 144 | else availableHandle(orderBytes, timeOut, bufferSize) 145 | if (pair.second > 0) logPortReceiveData(pair.first)//显示读取到的数据的日志 146 | return WaitResponseBean(pair.first, pair.second) 147 | } 148 | 149 | //可读取字节数的处理 150 | private suspend fun availableHandle( 151 | orderBytes: ByteArray, 152 | timeOut: Long, 153 | bufferSize: Int 154 | ): Pair { 155 | val blockByteBuffer = ByteBuffer.allocate(bufferSize)//读取到的数据 156 | var size = 0//读取到的数据大小 157 | serialPort.closeRead()//关闭读取 158 | //监听返回 159 | serialPort.setReadDataCallBack(object : EasyReadDataCallBack { 160 | override suspend fun receiveData(bytes: ByteArray) { 161 | if (size >= bufferSize) return 162 | if (size + bytes.size > bufferSize) { 163 | blockByteBuffer.put(bytes, 0, bufferSize - size) 164 | size = bufferSize 165 | } else { 166 | size += bytes.size 167 | blockByteBuffer.put(bytes)//将返回的数据加入缓存中 168 | } 169 | } 170 | }) 171 | serialPort.startRead(customMaxReadSize)//开始读取串口返回的数据 172 | serialPort.write(orderBytes)//开始写入数据 173 | delay(timeOut)//等待读取结束 174 | serialPort.closeRead()//关闭读取 175 | return Pair(blockByteBuffer.array(), size) 176 | } 177 | 178 | //不可读取字节数的处理 179 | private suspend fun noAvailableHandle( 180 | orderBytes: ByteArray, 181 | timeOut: Long, 182 | bufferSize: Int 183 | ): Pair { 184 | val blockByteBuffer = ByteBuffer.allocate(bufferSize)//读取到的数据 185 | var size = 0//读取到的数据大小 186 | serialPort.setReadDataCallBack(null)//重置监听 187 | serialPort.startRead(customMaxReadSize)//开始读取串口返回的数据 188 | //监听返回 189 | serialPort.setReadDataCallBack(object : EasyReadDataCallBack { 190 | override suspend fun receiveData(bytes: ByteArray) { 191 | if (size >= bufferSize) return 192 | if (size + bytes.size > bufferSize) { 193 | blockByteBuffer.put(bytes, 0, bufferSize - size) 194 | size = bufferSize 195 | } else { 196 | size += bytes.size 197 | blockByteBuffer.put(bytes)//将返回的数据加入缓存中 198 | } 199 | } 200 | }) 201 | serialPort.write(orderBytes)//开始写入数据 202 | delay(timeOut)//等待读取结束 203 | serialPort.setReadDataCallBack(null) 204 | return Pair(blockByteBuffer.array(), size) 205 | } 206 | 207 | override suspend fun close() { 208 | mutex.withLock { super.close() } 209 | } 210 | 211 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/EasySerialBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:Suppress("UNUSED") 18 | 19 | package com.bass.easySerial 20 | 21 | import com.bass.easySerial.enums.* 22 | import com.bass.easySerial.extend.logE 23 | import com.bass.easySerial.wrapper.BaseEasySerialPort 24 | import com.bass.easySerial.wrapper.EasyWaitRspPort 25 | import com.bass.easySerial.wrapper.EasyKeepReceivePort 26 | import java.io.File 27 | import java.io.IOException 28 | import java.security.InvalidParameterException 29 | import java.util.concurrent.ConcurrentHashMap 30 | 31 | /** 32 | * Create by BASS 33 | * on 2021/12/24 9:00. 34 | * 创建串口以及设置全局配置 35 | */ 36 | object EasySerialBuilder { 37 | 38 | //保存所有生成的串口 39 | private val serialPortMap by lazy { ConcurrentHashMap() } 40 | 41 | //不可读取字节数的串口名称 42 | internal val noAvailableList by lazy { mutableListOf() } 43 | 44 | //是否显示日志 45 | internal var showLog = true 46 | 47 | /** 48 | * 创建等待结果返回的串口对象 49 | * 写入后将阻塞写入的协程,并等待结果返回 50 | */ 51 | fun createWaitRspPort(path: String, baudRate: BaudRate, flags: Int = 0) = 52 | initWaitResponse(path, baudRate, flags) 53 | 54 | /** 55 | * 创建等待结果返回的串口对象 56 | */ 57 | fun createWaitRspPort( 58 | path: String, 59 | baudRate: BaudRate, 60 | dataBits: DataBit, 61 | stopBits: StopBit, 62 | parity: Parity, 63 | flags: Int = 0, 64 | flowCon: FlowCon = FlowCon.NONE 65 | ) = initWaitResponse(path, baudRate, flags, dataBits, stopBits, parity, flowCon) 66 | 67 | //开始创建串口对象 68 | private fun initWaitResponse( 69 | path: String, 70 | baudRate: BaudRate, 71 | flags: Int, 72 | dataBits: DataBit = DataBit.CSEmpty, 73 | stopBits: StopBit = StopBit.BEmpty, 74 | parity: Parity = Parity.NONE, 75 | flowCon: FlowCon = FlowCon.NONE 76 | ): EasyWaitRspPort? { 77 | try { 78 | serialPortMap.forEach { 79 | if (it.key == path) return it.value as EasyWaitRspPort 80 | } 81 | if (path.isEmpty() || baudRate.baudRate == -1) throw InvalidParameterException() 82 | 83 | val serialPort = SerialPort( 84 | File(path), 85 | baudRate.baudRate, 86 | flags, 87 | dataBits.dataBit, 88 | stopBits.stopBit, 89 | parity.parity, 90 | flowCon.flowCon 91 | ) 92 | val serialPortChat = EasyWaitRspPort(serialPort) 93 | serialPortMap[path] = serialPortChat 94 | return serialPortChat 95 | } catch (e: SecurityException) { 96 | logE("You do not have read/write permission to the serialPort.") 97 | return null 98 | } catch (e: IOException) { 99 | logE("The serial port can not be opened for an unknown reason.") 100 | return null 101 | } catch (e: InvalidParameterException) { 102 | logE("Please configure your serial port first.") 103 | return null 104 | } 105 | } 106 | 107 | //------------------------ 创建一个可以永远接收的串口 ------------------------------- 108 | /** 109 | * 创建一个永远保持接收的串口对象 110 | * 写入时不会发送阻塞 111 | */ 112 | fun createKeepReceivePort(path: String, baudRate: BaudRate, flags: Int = 0) = 113 | initReceive(path, baudRate, flags) 114 | 115 | /** 116 | * 创建一个永远保持接收的串口对象 117 | * 写入时不会发送阻塞 118 | */ 119 | fun createKeepReceivePort( 120 | path: String, 121 | baudRate: BaudRate, 122 | dataBits: DataBit, 123 | stopBits: StopBit, 124 | parity: Parity, 125 | flags: Int = 0, 126 | flowCon: FlowCon = FlowCon.NONE 127 | ) = initReceive(path, baudRate, flags, dataBits, stopBits, parity, flowCon) 128 | 129 | //开始创建串口对象 130 | @Suppress("UNCHECKED_CAST") 131 | private fun initReceive( 132 | path: String, 133 | baudRate: BaudRate, 134 | flags: Int, 135 | dataBits: DataBit = DataBit.CSEmpty, 136 | stopBits: StopBit = StopBit.BEmpty, 137 | parity: Parity = Parity.NONE, 138 | flowCon: FlowCon = FlowCon.NONE 139 | ): EasyKeepReceivePort? { 140 | try { 141 | serialPortMap.forEach { 142 | if (it.key == path) return it.value as EasyKeepReceivePort 143 | } 144 | if (path.isEmpty() || baudRate.baudRate == -1) throw InvalidParameterException() 145 | 146 | val serialPort = SerialPort( 147 | File(path), 148 | baudRate.baudRate, 149 | flags, 150 | dataBits.dataBit, 151 | stopBits.stopBit, 152 | parity.parity, 153 | flowCon.flowCon 154 | ) 155 | val serialPortChat = EasyKeepReceivePort(serialPort) 156 | serialPortMap[path] = serialPortChat 157 | return serialPortChat 158 | } catch (e: SecurityException) { 159 | logE("You do not have read/write permission to the serialPort.") 160 | return null 161 | } catch (e: IOException) { 162 | logE("The serial port can not be opened for an unknown reason.") 163 | return null 164 | } catch (e: InvalidParameterException) { 165 | logE("Please configure your serial port first.") 166 | return null 167 | } 168 | } 169 | 170 | /** 171 | * 获取指定接口已经存在的实例 172 | * @param path 串口名称 173 | * @return 返回串口对象 如果没有生成 那么将返回 Null 174 | */ 175 | fun get(path: String): BaseEasySerialPort? { 176 | serialPortMap.forEach { 177 | if (it.key == path) return it.value 178 | } 179 | return null 180 | } 181 | 182 | /** 183 | * 判断是否有串口正在工作 184 | */ 185 | fun hasPortWorking() = serialPortMap.size > 0 186 | 187 | /** 188 | * 添加不可读取字节数的串口名称 189 | * 部分串口会有无法读取字节数的问题,如果你确定串口配置是无误的却没有接收到数据, 190 | * 那么你可以尝试调用此方法后再创建串口 191 | * 一定要先调用此方法,再调用创建串口的方法,此方法才能生效 192 | * @param devicePath 例如:/dev/ttyS4 193 | */ 194 | fun addNoAvailableDevicePath(devicePath: String): EasySerialBuilder { 195 | noAvailableList.find { it == devicePath } ?: noAvailableList.add(devicePath) 196 | return this 197 | } 198 | 199 | /** 200 | * 移除不可读取字节数的串口名称 201 | * @param devicePath 例如:/dev/ttyS4 202 | */ 203 | fun removeNoAvailableDevicePath(devicePath: String): EasySerialBuilder { 204 | noAvailableList.remove(devicePath) 205 | return this 206 | } 207 | 208 | /** 209 | * 设置是否打印日志 210 | * @param show true为打印日志,否则反之,默认为打印日志 211 | */ 212 | fun isShowLog(show: Boolean): EasySerialBuilder { 213 | showLog = show 214 | return this 215 | } 216 | 217 | //串口关闭时移除实例,只被内部调用,不对外开放 218 | internal fun remove(serialPortChat: BaseEasySerialPort) { 219 | serialPortMap.forEach { 220 | if (it.value === serialPortChat) { 221 | serialPortMap.remove(it.key) 222 | return@forEach 223 | } 224 | } 225 | } 226 | } -------------------------------------------------------------------------------- /easySerial/src/main/java/com/bass/easySerial/extend/EasyDataConversionExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 BASS 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:Suppress("unused", "SpellCheckingInspection") 18 | 19 | package com.bass.easySerial.extend 20 | 21 | /** 22 | * 字符串转16进制字节数组 23 | * 从字符串第0位截取到[endIndex]位,将其转化位字节数组后返回 24 | * @param endIndex 默认为字符串的最后一个字符的下标 25 | * @return 16进制字符串的字节数组 26 | */ 27 | fun String.conver2ByteArray(endIndex: Int = length): ByteArray { 28 | if (isEmpty() || endIndex < 2) return ByteArray(0) 29 | //移除空格 30 | val hexStr = substring(0, if (endIndex != length) endIndex + 1 else endIndex).replace(" ", "") 31 | if (hexStr.isEmpty() || hexStr.length % 2 != 0) return ByteArray(0) 32 | //开始转化 33 | val byteArray = ByteArray(hexStr.length / 2) 34 | for (i in byteArray.indices) { 35 | val subStr = hexStr.substring(2 * i, 2 * i + 2) 36 | byteArray[i] = subStr.toInt(16).toByte() 37 | } 38 | return byteArray 39 | } 40 | 41 | /** 42 | * 字符串转16进制字节数组 43 | * 从字符串第[startIndex]位截取到[endIndex]位,将其转化位字节数组后返回 44 | * @param startIndex 转化的字符串的起始位置下标 45 | * @param endIndex 转化的字符串的结束位置下标 46 | * @return 16进制字符串的字节数组 47 | */ 48 | fun String.conver2ByteArray(startIndex: Int, endIndex: Int): ByteArray { 49 | if (isEmpty() || startIndex == endIndex || startIndex < 0) return ByteArray(0) 50 | //移除空格 51 | val hexStr = substring(startIndex, endIndex + 1).replace(" ", "") 52 | if (hexStr.isEmpty() || hexStr.length % 2 != 0) return ByteArray(0) 53 | //开始转化 54 | val byteArray = ByteArray(hexStr.length / 2) 55 | for (i in byteArray.indices) { 56 | val subStr = hexStr.substring(2 * i, 2 * i + 2) 57 | byteArray[i] = subStr.toInt(16).toByte() 58 | } 59 | return byteArray 60 | } 61 | 62 | 63 | /** 64 | * 将buteArray转化为16进制的String 65 | * 每个16进制数之间用空格分割 66 | * @param size 要转化的字节长度 默认为全部转化 67 | * @param isUppercase 是否开启大写 默认开启 68 | */ 69 | fun ByteArray.conver2HexStringWithBlank( 70 | size: Int = this.size, 71 | isUppercase: Boolean = true 72 | ): String { 73 | if (size == 0) return "" 74 | val builder = StringBuilder() 75 | for (index in 0 until size) { 76 | val hexString = Integer.toHexString(this[index].toInt() and 0xFF) 77 | val coverHex = 78 | if (hexString.length == 1) "0${if (isUppercase) hexString.uppercase() else hexString}${if (index == size - 1) "" else " "}" 79 | else "${if (isUppercase) hexString.uppercase() else hexString}${if (index == size - 1) "" else " "}" 80 | builder.append(coverHex) 81 | } 82 | return builder.toString() 83 | } 84 | 85 | /** 86 | * 将buteArray转化为16进制的String 87 | * 每个16进制数之间用空格分割 88 | * @param startIndex 读取的字节数组的起始下标 89 | * @param endIndex 读取的字节数组的结束下标 90 | * @param isUppercase 是否开启大写 默认开启 91 | */ 92 | fun ByteArray.conver2HexStringWithBlank( 93 | startIndex: Int, 94 | endIndex: Int, 95 | isUppercase: Boolean = true 96 | ): String { 97 | if (isEmpty() || startIndex < 0 || endIndex < 0) return "" 98 | val builder = StringBuilder() 99 | for (index in startIndex..endIndex) { 100 | val hexString = Integer.toHexString(this[index].toInt() and 0xFF) 101 | val coverHex = 102 | if (hexString.length == 1) "0${if (isUppercase) hexString.uppercase() else hexString}${if (index == endIndex) "" else " "}" 103 | else "${if (isUppercase) hexString.uppercase() else hexString}${if (index == endIndex) "" else " "}" 104 | builder.append(coverHex) 105 | } 106 | return builder.toString() 107 | } 108 | 109 | 110 | /** 111 | * 将byteArray转化为16进制的String 112 | * @param size 要转化的字节长度 默认为全部转化 113 | * @param isUppercase 是否开启大写 默认开启 114 | */ 115 | fun ByteArray.conver2HexString(size: Int = this.size, isUppercase: Boolean = true): String { 116 | if (size == 0) return "" 117 | val builder = StringBuilder() 118 | for (index in 0 until size) { 119 | val hexString = Integer.toHexString(this[index].toInt() and 0xFF) 120 | val coverHex = 121 | if (hexString.length == 1) "0${if (isUppercase) hexString.uppercase() else hexString}" 122 | else if (isUppercase) hexString.uppercase() 123 | else hexString 124 | builder.append(coverHex) 125 | } 126 | return builder.toString() 127 | } 128 | 129 | /** 130 | * 将buteArray转化为16进制的String 131 | * @param startIndex 读取的字节数组的起始下标 132 | * @param endIndex 读取的字节数组的结束下标 133 | * @param isUppercase 是否开启大写 默认开启 134 | */ 135 | fun ByteArray.conver2HexString( 136 | startIndex: Int, 137 | endIndex: Int, 138 | isUppercase: Boolean = true 139 | ): String { 140 | if (isEmpty() || startIndex < 0 || endIndex < 0) return "" 141 | val builder = StringBuilder() 142 | for (index in startIndex..endIndex) { 143 | val hexString = Integer.toHexString(this[index].toInt() and 0xFF) 144 | val coverHex = 145 | if (hexString.length == 1) "0${if (isUppercase) hexString.uppercase() else hexString}" 146 | else if (isUppercase) hexString.uppercase() 147 | else hexString 148 | builder.append(coverHex) 149 | } 150 | return builder.toString() 151 | } 152 | 153 | /** 154 | * 将buteArray转化为CharArray 155 | * @param size 要转化的字节长度 默认为全部转化 156 | */ 157 | fun ByteArray.conver2CharArray(size: Int = this.size): CharArray { 158 | if (size <= 0) return CharArray(0) 159 | return CharArray(size) { this[it].toInt().toChar() } 160 | } 161 | 162 | /** 163 | * 将buteArray转化为CharArray 164 | * @param startIndex 读取的字节数组的起始下标 165 | * @param endIndex 读取的字节数组的结束下标 166 | */ 167 | fun ByteArray.conver2CharArray(startIndex: Int, endIndex: Int): CharArray { 168 | if (isEmpty() || startIndex < 0 || endIndex < 0) return CharArray(0) 169 | val size = (endIndex - startIndex) + 1 170 | return CharArray(size) { this[it + startIndex].toInt().toChar() } 171 | } 172 | 173 | /** 174 | * byteArray计算CRC值 175 | */ 176 | fun ByteArray.getCRC(size: Int = this.size): Int { 177 | // 预置 1 个 16 位的寄存器为十六进制0xFFFF, 称此寄存器为 CRC寄存器。 178 | var crc = 0xFFFF 179 | for (i in 0 until size) { 180 | // 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器 181 | crc = crc and 0xFF00 or (crc and 0x00FF) xor (this[i].toInt() and 0xFF) 182 | repeat(8) { 183 | // 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位 184 | if (crc and 0x0001 > 0) {// 如果移出位为 1, CRC寄存器与多项式A001进行异或 185 | crc = crc shr 1 186 | crc = crc xor 0xA001 187 | } else crc = crc shr 1// 如果移出位为 0,再次右移一位 188 | } 189 | } 190 | return crc 191 | } 192 | 193 | /** 194 | * byteArray计算CRC值 195 | */ 196 | fun ByteArray.getCRC(startIndex: Int, endIndex: Int): Int { 197 | if (isEmpty() || startIndex < 0 || endIndex < 0) throw RuntimeException("计算CRC时发生错误,请检查参数") 198 | // 预置 1 个 16 位的寄存器为十六进制0xFFFF, 称此寄存器为 CRC寄存器。 199 | var crc = 0xFFFF 200 | for (i in startIndex..endIndex) { 201 | // 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器 202 | crc = crc and 0xFF00 or (crc and 0x00FF) xor (this[i].toInt() and 0xFF) 203 | repeat(8) { 204 | // 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位 205 | if (crc and 0x0001 > 0) {// 如果移出位为 1, CRC寄存器与多项式A001进行异或 206 | crc = crc shr 1 207 | crc = crc xor 0xA001 208 | } else crc = crc shr 1// 如果移出位为 0,再次右移一位 209 | } 210 | } 211 | return crc 212 | } -------------------------------------------------------------------------------- /easySerial/src/main/cpp/SerialPort.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009-2011 Cedric Priscal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * BASS Modified this file. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "SerialPort.h" 29 | 30 | #include "android/log.h" 31 | 32 | static const char *TAG = "EasySerial"; 33 | #define LogD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args) 34 | #define LogE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) 35 | 36 | static speed_t getBaudRate(jint baudRate) { 37 | switch (baudRate) { 38 | case 0: 39 | return B0; 40 | case 50: 41 | return B50; 42 | case 75: 43 | return B75; 44 | case 110: 45 | return B110; 46 | case 134: 47 | return B134; 48 | case 150: 49 | return B150; 50 | case 200: 51 | return B200; 52 | case 300: 53 | return B300; 54 | case 600: 55 | return B600; 56 | case 1200: 57 | return B1200; 58 | case 1800: 59 | return B1800; 60 | case 2400: 61 | return B2400; 62 | case 4800: 63 | return B4800; 64 | case 9600: 65 | return B9600; 66 | case 19200: 67 | return B19200; 68 | case 38400: 69 | return B38400; 70 | case 57600: 71 | return B57600; 72 | case 115200: 73 | return B115200; 74 | case 230400: 75 | return B230400; 76 | case 460800: 77 | return B460800; 78 | case 500000: 79 | return B500000; 80 | case 576000: 81 | return B576000; 82 | case 921600: 83 | return B921600; 84 | case 1000000: 85 | return B1000000; 86 | case 1152000: 87 | return B1152000; 88 | case 1500000: 89 | return B1500000; 90 | case 2000000: 91 | return B2000000; 92 | case 2500000: 93 | return B2500000; 94 | case 3000000: 95 | return B3000000; 96 | case 3500000: 97 | return B3500000; 98 | case 4000000: 99 | return B4000000; 100 | default: 101 | return -1; 102 | } 103 | } 104 | 105 | /* 106 | * Class: cedric_serial_SerialPort 107 | * Method: close 108 | * Signature: ()V 109 | */ 110 | JNIEXPORT void JNICALL 111 | Java_com_bass_easySerial_SerialPort_close(JNIEnv *env, jobject thiz) { 112 | jclass SerialPortClass = (*env)->GetObjectClass(env, thiz); 113 | jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor"); 114 | 115 | jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); 116 | jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I"); 117 | 118 | jobject mFd = (*env)->GetObjectField(env, thiz, mFdID); 119 | jint descriptor = (*env)->GetIntField(env, mFd, descriptorID); 120 | 121 | LogD("close(fd = %d)", descriptor); 122 | close(descriptor); 123 | } 124 | 125 | 126 | JNIEXPORT jobject JNICALL 127 | Java_com_bass_easySerial_SerialPort_open(JNIEnv *env, jobject thiz, jstring path, jint baudRate, 128 | jint flags) { 129 | int fd;// Linux串口文件句柄(本次整个函数最终的关键成果) 130 | speed_t speed;// 波特率类型的值 131 | jobject mFileDescriptor; // 文件句柄(最终返回的成果) 132 | 133 | //检查参数,获取波特率参数信息 [先确定好波特率] 134 | { 135 | speed = getBaudRate(baudRate); 136 | if (speed == -1) { 137 | LogE("无效的波特率"); 138 | return NULL; 139 | } 140 | } 141 | 142 | //第一步:打开串口 143 | { 144 | jboolean isCopy; 145 | const char *path_utf = (*env)->GetStringUTFChars(env, path, &isCopy); 146 | LogD("打开串口 路径是:%s flags:0x%x", path_utf, O_RDWR | flags); 147 | fd = open(path_utf, O_RDWR | flags);// 打开串口的函数,O_RDWR(读 和 写) 148 | LogD("打开串口 open() fd = %d", fd); 149 | (*env)->ReleaseStringUTFChars(env, path, path_utf);// 释放操作 150 | if (fd == -1) { 151 | LogE("无法打开端口"); 152 | return NULL; 153 | } 154 | } 155 | 156 | //第二步:获取和设置终端属性-配置串口设备 157 | /* TCSANOW:不等数据传输完毕就立即改变属性。 158 | TCSADRAIN:等待所有数据传输结束才改变属性。 159 | TCSAFLUSH:清空输入输出缓冲区才改变属性。 160 | 注意:当进行多重修改时,应当在这个函数之后再次调用 tcgetattr() 来检测是否所有修改都成功实现。 161 | */ 162 | { 163 | struct termios cfg; 164 | LogD("执行配置串口中..."); 165 | if (tcgetattr(fd, &cfg)) { 166 | LogE("配置串口tcgetattr() 失败"); 167 | close(fd); 168 | return NULL; 169 | } 170 | 171 | cfmakeraw(&cfg);// 将串口设置成原始模式,并且让fd(文件句柄 对串口可读可写) 172 | cfsetispeed(&cfg, speed);// 设置串口读取波特率 173 | cfsetospeed(&cfg, speed);// 设置串口写入波特率 174 | 175 | if (tcsetattr(fd, TCSANOW, &cfg)) { // 根据上面的配置,再次获取串口属性 176 | LogE("再配置串口tcgetattr() 失败"); 177 | close(fd); 178 | return NULL; 179 | } 180 | } 181 | 182 | //第三步:构建FileDescriptor.java对象,并赋予丰富串口相关的值 183 | { 184 | jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor"); 185 | jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "", "()V"); 186 | jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I"); 187 | //反射生成FileDescriptor对象,并赋值 (fd==Linux串口文件句柄) FileDescriptor的构造函数实例化 188 | mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor); 189 | (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint) fd);// 这里的fd,就是打开串口的关键成果 190 | } 191 | return mFileDescriptor;// 把最终的成果,返回会Java层 192 | } 193 | 194 | JNIEXPORT jobject JNICALL 195 | Java_com_bass_easySerial_SerialPort_open2(JNIEnv *env, jobject thiz, jstring path, jint baudRate, 196 | jint stopBits, jint dataBits, 197 | jint parity, jint flowCon, jint flags) { 198 | int fd; 199 | speed_t speed; 200 | jobject mFileDescriptor; 201 | 202 | /* Check arguments */ 203 | { 204 | speed = getBaudRate(baudRate); 205 | if (speed == -1) { 206 | LogE("无效的波特率"); 207 | return NULL; 208 | } 209 | } 210 | 211 | /* Opening device */ 212 | { 213 | jboolean isCopy; 214 | const char *path_utf = (*env)->GetStringUTFChars(env, path, &isCopy); 215 | LogD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags); 216 | fd = open(path_utf, O_RDWR | flags); 217 | LogD("open() fd = %d", fd); 218 | (*env)->ReleaseStringUTFChars(env, path, path_utf); 219 | if (fd == -1) { 220 | LogE("无法打开端口"); 221 | return NULL; 222 | } 223 | } 224 | 225 | /* Configure device */ 226 | { 227 | struct termios cfg; 228 | LogD("执行配置串口中..."); 229 | if (tcgetattr(fd, &cfg)) { 230 | LogE("配置串口tcgetattr() 失败"); 231 | close(fd); 232 | return NULL; 233 | } 234 | 235 | cfmakeraw(&cfg); 236 | cfsetispeed(&cfg, speed); 237 | cfsetospeed(&cfg, speed); 238 | 239 | cfg.c_cflag &= ~CSIZE; 240 | switch (dataBits) { 241 | case 5: 242 | cfg.c_cflag |= CS5; //Use 5-bit data bits 243 | break; 244 | case 6: 245 | cfg.c_cflag |= CS6; //Use 6-bit data bits 246 | break; 247 | case 7: 248 | cfg.c_cflag |= CS7; //Use 7-bit data bits 249 | break; 250 | default: 251 | cfg.c_cflag |= CS8; //Use 8-bit data bits 252 | break; 253 | } 254 | 255 | switch (parity) { 256 | case 0: 257 | cfg.c_cflag &= ~PARENB; // None parity 258 | break; 259 | case 1: 260 | cfg.c_cflag |= (PARODD | PARENB); // Odd parity 261 | break; 262 | case 2: 263 | cfg.c_iflag &= ~(IGNPAR | PARMRK); // Even parity 264 | cfg.c_iflag |= INPCK; 265 | cfg.c_cflag |= PARENB; 266 | cfg.c_cflag &= ~PARODD; 267 | break; 268 | default: 269 | cfg.c_cflag &= ~PARENB; 270 | break; 271 | } 272 | 273 | switch (stopBits) { 274 | case 1: 275 | cfg.c_cflag &= ~CSTOPB; // 1 bit stop bit 276 | break; 277 | case 2: 278 | cfg.c_cflag |= CSTOPB; // 2 bit stop bit 279 | break; 280 | default: 281 | break; 282 | } 283 | 284 | // hardware flow control 285 | switch (flowCon) { 286 | case 0: 287 | cfg.c_cflag &= ~CRTSCTS; // None flow control 288 | break; 289 | case 1: 290 | cfg.c_cflag |= CRTSCTS; // Hardware flow control 291 | break; 292 | case 2: 293 | cfg.c_cflag |= IXON | IXOFF | IXANY; // Software flow control 294 | break; 295 | default: 296 | cfg.c_cflag &= ~CRTSCTS; 297 | break; 298 | } 299 | 300 | 301 | if (tcsetattr(fd, TCSANOW, &cfg)) { 302 | LogE("再配置串口tcgetattr() 失败"); 303 | close(fd); 304 | return NULL; 305 | } 306 | } 307 | 308 | /* Create a corresponding file descriptor */ 309 | { 310 | jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor"); 311 | jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "", "()V"); 312 | jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I"); 313 | mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor); 314 | (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint) fd); 315 | } 316 | 317 | return mFileDescriptor; 318 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 BASS 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 一、前言 2 | 3 | -------------------------------------------------------------------------- 4 | 5 | 1. **如果你的项目需要使用串口通信,并且项目使用了kotlin,那么这个SDK是你不能错过的;** 6 | 2. **如果你使用过串口通信,那么这个SDK最吸引你的地方应当是可在写入时同步阻塞等待数据返回,并且我们支持超时设置;如果你没有使用过串口通信,这个SDK也可以让你快熟的进行串口通信;** 7 | 3. **此SDK用于Android端的串口通信,此项目的C代码移植于谷歌官方串口库[android-serialport-api](https://code.google.com/archive/p/android-serialport-api/) 8 | ,以及[GeekBugs-Android-SerialPort](https://github.com/GeekBugs/Android-SerialPort) 9 | ;在此基础上,我进行了封装,目的是让开发者可以更加快速的进行串口通信;** 10 | 4. **此SDK可以创建2种不同作用的串口对象,一个是保持串口接收的串口对象,一个是写入并同步等待数据返回的串口对象;** 11 | 5. **当前仅支持Kotlin项目使用,对于java调用做的并不完美;** 12 | 6. **此SDK本人已经在多个项目中实践使用,并在github上进行开源,如果你在使用中有任何问题,请在issue中向我提出;** 13 | 7. **本SDK不向您保证任何稳定性及可靠性,您将自行承担使用本SDK后可能带来的任何后果;** 14 | 15 | -------------------------------------------------------------------------- 16 | 17 | # 二、SDK的使用介绍 18 | 19 | -------------------------------------------------------------------------- 20 | 21 | ## 引入库 22 | 23 | 在Build.gradle(:project)下导入jitpack库 24 | 25 | ```groovy 26 | allprojects { 27 | repositories { 28 | maven { url 'https://jitpack.io' } 29 | } 30 | } 31 | ``` 32 | 33 | 在新版gradle中,在settings.gradle下导入jitpack库 34 | 35 | ```groovy 36 | dependencyResolutionManagement { 37 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 38 | repositories { 39 | maven { url "https://jitpack.io" } 40 | } 41 | } 42 | ``` 43 | 44 | 在Build.gradle(:module)中添加: 45 | 46 | ```groovy 47 | dependencies { 48 | implementation 'com.github.BASS-HY:EasySerial:1.0.0' 49 | } 50 | ``` 51 | 52 | ## EasyKeepReceivePort的使用 53 | 54 | **1.创建一个永久接收的串口(串口开启失败返回Null); 55 | 演示:不做自定义返回数据的处理;** 56 | 57 | A). 创建一个串口,串口返回的数据默认为ByteArray类型; 58 | 59 | ```Kotlin 60 | val port = EasySerialBuilder.createKeepReceivePort("/dev/ttyS4", BaudRate.B4800) 61 | ``` 62 | 63 | 我们还可以这样创建串口,串口返回的数据默认为ByteArray类型; 64 | 65 | ```Kotlin 66 | val port = EasySerialBuilder.createKeepReceivePort( 67 | "/dev/ttyS4", 68 | BaudRate.B4800, DataBit.CS8, StopBit.B1, 69 | Parity.NONE, 0, FlowCon.NONE 70 | ) 71 | ``` 72 | 73 | B). 设置串口每次从数据流中读取的最大字节数,默认为64个字节; 74 | 75 | 1. 注意:必须在调用[addDataCallBack]之前设置,否则设置无效; 76 | 77 | ```Kotlin 78 | port.setMaxReadSize(64) 79 | ``` 80 | 81 | C).设置数据的读取间隔; 82 | 83 | 1. 即上一次读取完数据后,隔多少秒后读取下一次数据; 84 | 2. 默认为10毫秒,读取时间越短,CPU的占用会越高,请合理配置此设置; 85 | 86 | ```Kotlin 87 | port.setReadInterval(100) 88 | ``` 89 | 90 | D). 监听串口返回的数据; 91 | 第一种写法;须注意,此回调处于协程之中; 92 | 93 | ```Kotlin 94 | val dataCallBack = port.addDataCallBack { 95 | //处理项目逻辑; 96 | // 此处示范将串口数据转化为16进制字符串; 97 | if (it.isEmpty()) return@addDataCallBack 98 | val hexString = it.last().conver2HexString() 99 | Log.d(tag, "接收到串口数据:$hexString") 100 | } 101 | port.addDataCallBack(dataCallBack) 102 | ``` 103 | 104 | 第二种写法;须注意,此回调处于协程之中; 105 | 106 | ```Kotlin 107 | val dataCallBack = object : EasyReceiveCallBack { 108 | override suspend fun receiveData(dataList: List) { 109 | //处理项目逻辑; 110 | //此处示范将串口数据转化为16进制字符串; 111 | if (dataList.isEmpty()) return 112 | val hexString = dataList.last().conver2HexString() 113 | Log.d(tag, "接收到串口数据:$hexString") 114 | } 115 | 116 | } 117 | port.addDataCallBack(dataCallBack) 118 | ``` 119 | 120 | E). 移除串口监听; 121 | 122 | ```Kotlin 123 | port.removeDataCallBack(dataCallBack) 124 | ``` 125 | 126 | F). 关闭串口; 127 | 使用完毕关闭串口,关闭串口须在作用域中关闭,关闭时会阻塞当前协程,直到关闭处理完成; 这个过程并不会耗费太长时间,一般为1ms-4ms; 128 | 129 | ```Kotlin 130 | CoroutineScope(Dispatchers.IO).launch { port.close() } 131 | ``` 132 | 133 | **2. 创建一个永久接收的串口(串口开启失败返回Null); 134 | 演示:自定义回调的数据类型,在接收到串口数据后对数据进行一次处理,再将数据返回给串口数据监听者;** 135 | 136 | A). 创建一个串口,串口返回的数据类型,我们自定义为String类型; 137 | 138 | ```Kotlin 139 | val port = EasySerialBuilder.createKeepReceivePort("/dev/ttyS4", BaudRate.B4800) 140 | ``` 141 | 142 | 我们还可以这样创建串口,串口返回的数据类型,我们自定义为String类型; 143 | 144 | ```Kotlin 145 | val port = EasySerialBuilder.createKeepReceivePort( 146 | "/dev/ttyS4", 147 | BaudRate.B4800, DataBit.CS8, StopBit.B1, 148 | Parity.NONE, 0, FlowCon.NONE 149 | ) 150 | ``` 151 | 152 | B). 设置串口每次从数据流中读取的最大字节数,默认为64个字节; 153 | 注意:必须在调用[addDataCallBack]之前设置,否则设置无效; 154 | 155 | ```Kotlin 156 | port.setMaxReadSize(64) 157 | ``` 158 | 159 | C).设置数据的读取间隔; 160 | 161 | 1. 即上一次读取完数据后,隔多少秒后读取下一次数据; 162 | 2. 默认为10毫秒,读取时间越短,CPU的占用会越高,请合理配置此设置; 163 | 164 | ```Kotlin 165 | port.setReadInterval(100) 166 | ``` 167 | 168 | D).因为我们设置数据返回类型不再是默认的ByteArray类型,所以我们需要设置自定义的数据解析规则; 169 | 170 | ```Kotlin 171 | port.setDataHandle(CustomEasyPortDataHandle()) 172 | ``` 173 | 174 | 接下来我们创建一个自定义解析类,并将其命令为CustomEasyPortDataHandle; 175 | 176 | ```Kotlin 177 | class CustomEasyPortDataHandle : EasyPortDataHandle() { 178 | 179 | private val stringList = mutableListOf()//用于记录数据 180 | private val stringBuilder = StringBuilder()//用于记录数据 181 | private val pattern = Pattern.compile("(AT)(.*?)(\r\n)")//用于匹配数据 182 | 183 | /** 184 | * 数据处理方法 185 | * 186 | * @param byteArray 串口收到的原始数据 187 | * @return 返回自定义处理后的数据,此数据将被派发到各个监听者 188 | * 189 | * 190 | * 我们可以在这里做很多事情,比如有时候串口返回的数据并不是完整的数据, 191 | * 它可能有分包返回的情况,我们需要自行凑成一个完整的数据后再返回给监听者, 192 | * 在数据不完整的时候我们直接返回空数据集给监听者,告知他们这不是一个完整的数据; 193 | * 194 | * 在这里我们做个演示,假设数据返回是以AT开头,换行符为结尾的数据是正常的数据; 195 | * 196 | */ 197 | override suspend fun portData(byteArray: ByteArray): List { 198 | //清除之前记录的匹配成功的数据 199 | stringList.clear() 200 | 201 | //将串口数据转为16进制字符串 202 | val hexString = byteArray.conver2HexString() 203 | //记录本次读取到的串口数据 204 | stringBuilder.append(hexString) 205 | 206 | while (true) {//循环匹配,直到匹配完所有的数据 207 | //寻找记录中符合规则的数据 208 | val matcher = pattern.matcher(stringBuilder) 209 | //没有寻找到符合规则的数据,则返回Null 210 | if (!matcher.find()) break 211 | //寻找到符合规则的数据,记录匹配成功的数据,并将其从StringBuilder中删除 212 | val group = matcher.group() 213 | stringList.add(group) 214 | stringBuilder.delete(matcher.start(), matcher.end()) 215 | } 216 | 217 | //返回记录的匹配成功的数据 218 | return stringList.toList() 219 | } 220 | 221 | /** 222 | * 串口关闭时会回调此方法 223 | * 如果您需要,可重写此方法,在此方法中做释放资源的操作 224 | */ 225 | override fun close() { 226 | stringBuilder.clear() 227 | stringList.clear() 228 | } 229 | } 230 | ``` 231 | 232 | E). 监听串口返回的数据; 233 | 此时,我们监听到的数据为我们一开始设置的String类型; 234 | 235 | ```kotlin 236 | val dataCallBack = object : EasyReceiveCallBack { 237 | override suspend fun receiveData(dataList: List) { 238 | //返回的数据集内没有数据,则表明没有匹配成功的数据; 239 | //我们这里不处理没有匹配成功的情况; 240 | if (dataList.isEmpty()) return 241 | //处理项目逻辑; 242 | //此处演示直接将转化后的数据类型打印出来; 243 | dataList.forEach { Log.d(tag, "接收到串口数据:$it") } 244 | } 245 | 246 | } 247 | ``` 248 | 249 | F). 移除串口监听,关闭串口与之前的一致; 250 | 251 | ```kotlin 252 | port.addDataCallBack(dataCallBack)//添加串口数据监听 253 | port.removeDataCallBack(dataCallBack)//移除串口数据监听 254 | CoroutineScope(Dispatchers.IO).launch { port.close() }//关闭串口 255 | ``` 256 | 257 | -------------------------------------------------------------------------- 258 | 259 | ## EasyWaitRspPort的使用 260 | 261 | A). 创建一个发送后再接收的串口(串口开启失败返回Null); 262 | 263 | ```kotlin 264 | val port = EasySerialBuilder.createWaitRspPort("/dev/ttyS4", BaudRate.B4800) 265 | ``` 266 | 267 | 我们还可以这样创建串口: 268 | 269 | ```kotlin 270 | val port = EasySerialBuilder.createWaitRspPort( 271 | "/dev/ttyS4", 272 | BaudRate.B4800, DataBit.CS8, StopBit.B1, 273 | Parity.NONE, 0, FlowCon.NONE 274 | ) 275 | ``` 276 | 277 | B). 设置串口每次从数据流中读取的最大字节数,默认为64个字节; 278 | 279 | 1. 对于不可读取字节数的串口,必须在调用[writeWaitRsp]或[writeAllWaitRsp]之前调用,否则设置无效; 280 | 2. 在请求时不指定接收数据的最大字节数时,将会使用这里配置的字节大小; 281 | 282 | ```Kotlin 283 | port.setMaxReadSize(64) 284 | ``` 285 | 286 | C).设置数据的读取间隔; 287 | 288 | 1. 即上一次读取完数据后,隔多少秒后读取下一次数据; 289 | 2. 默认为10毫秒,读取时间越短,CPU的占用会越高,请合理配置此设置; 290 | 291 | ```Kotlin 292 | port.setReadInterval(100) 293 | ``` 294 | 295 | D). 接下来演示发送串口命令的3种方法 296 | 297 | **1. 调用写入函数,并阻塞等待200ms,阻塞完成之后将会返回此期间接收到的串口数据;** 298 | 需要注意的是,此方法需要在协程作用域中调用; 299 | 300 | ```kotlin 301 | val rspBean: WaitResponseBean = port.writeWaitRsp(orderByteArray1) 302 | ``` 303 | 304 | 此外,我们也可以在调用此函数时指定等待时长,此处我们演示等待500ms: 305 | 306 | ```kotlin 307 | val rspBean: WaitResponseBean = port.writeWaitRsp(orderByteArray1, timeOut = 500) 308 | ``` 309 | 310 | 还可以,指定本次请求接收的数据的最大字节数,如下示例: 311 | 312 | ```kotlin 313 | val rspBean: WaitResponseBean = port.writeWaitRsp(orderByteArray1, bufferSize = 64) 314 | ``` 315 | 316 | ```rspBean```是一个封装的数据类,我们来讲解一下: 317 | 318 | ```kotlin 319 | rspBean.bytes 320 | ``` 321 | 322 | 这是串口返回的数据,此字节数组的大小为调用写入时指定的bufferSize的大小; 323 | 需要注意的是,字节数组内的字节并不全是串口返回的数据; 324 | 325 | 默认的bufferSize大小为64,我们假设串口返回了4个字节的数据,那么其余的60个字节都是0; 326 | 327 | 那我们怎么知道实际收到了多少个字节呢?这就需要用到数据类内的另一个数据: 328 | 329 | ```kotlin 330 | rspBean.size 331 | ``` 332 | 333 | 这是串口实际读取的字节长度,所以我们取串口返回的实际字节数组可以这样取: 334 | 335 | ```kotlin 336 | val portBytes = rspBean.bytes.copyOf(rspBean.size) 337 | ``` 338 | 339 | 插句题外话,我们也提供了直接将读取到的字节转为16进制字符串的方法: 340 | 341 | ```kotlin 342 | val hexString = rspBean.bytes.conver2HexString(rspBean.size) 343 | ``` 344 | 345 | **2. 有时候,我们可能需要连续向串口输出命令,并等待其返回,对此我们也提供了便捷的方案:** 346 | 347 | ```kotlin 348 | val rspBeanList: MutableList = 349 | port.writeAllWaitRsp(orderByteArray1, orderByteArray2, orderByteArray3) 350 | ``` 351 | 352 | 或者是指定每个请求的超时时间: 353 | 354 | ```kotlin 355 | val rspBeanList: MutableList = 356 | port.writeAllWaitRsp(200, orderByteArray1, orderByteArray2, orderByteArray3) 357 | ``` 358 | 359 | 又或者,即指定每个请求的超时时间,也指定每个请求接收数据的最大字节数: 360 | 361 | ```kotlin 362 | val rspBeanList: MutableList = 363 | port.writeAllWaitRsp(200, 64, orderByteArray1, orderByteArray2, orderByteArray3) 364 | ``` 365 | 366 | 同样的,此方法也是需要在协程作用域中调用; 367 | 368 | 我们来讲解一下函数参数: 369 | 370 | 第1个参数:单个写入命令的阻塞等待时长,注意,这是单个的,并非所有命令的总阻塞时长; 371 | 372 | 第2个参数:一个数组类型,即我们想写入并等待返回的所有指令集; 373 | 374 | ```rspBeanList```为多个```WaitResponseBean```的集合,我们在上面已经讲解了```WaitResponseBean``` 375 | ,此处不再赘述;集合中的数据与请求是一一对应: 376 | 377 | ```kotlin 378 | val rspBean1: WaitResponseBean = rspBeanList[0]//orderByteArray1 379 | val rspBean2: WaitResponseBean = rspBeanList[1]//orderByteArray2 380 | val rspBean3: WaitResponseBean = rspBeanList[2]//orderByteArray3 381 | ``` 382 | 383 | **3. 在同一个串口中,我们有些需要等待串口的数据返回,有些是不需要的,在不需要串口数据返回的情况下,我们可以直接调用写入即可:** 384 | 385 | ```kotlin 386 | port.write(orderByteArray1) 387 | ``` 388 | 389 | 相同的,此方法必须在协程作用域中调用; 390 | 391 | E). 关闭串口: 392 | 使用完毕关闭串口,关闭串口须在协程作用域中关闭,关闭时会阻塞当前协程,直到关闭处理完成; 这个过程并不会耗费太长时间,一般为1ms-4ms; 393 | 394 | ```Kotlin 395 | CoroutineScope(Dispatchers.IO).launch { port.close() } 396 | ``` 397 | 398 | -------------------------------------------------------------------------- 399 | 400 | ## 其他API的使用介绍 401 | 402 | **1. 获取串口对象:** 403 | 每一个串口只会创建一个实例,我们在内部缓存了串口实例,即一处创建,到处可取;如果此串口还未创建,则将获取到Null; 404 | 405 | ```kotlin 406 | val serialPort: BaseEasySerialPort? = EasySerialBuilder.get("dev/ttyS4") 407 | ``` 408 | 409 | 获取到实例后,我们仅可以调用close()方法关闭串口; 410 | 此方法必须在协程作用域中调用; 411 | 412 | ```kotlin 413 | CoroutineScope(Dispatchers.IO).launch { serialPort.close() } 414 | ``` 415 | 416 | 如果你明确知道当前串口属于哪种类型,那么你可以进行类型强转后使用更多特性。如: 417 | 418 | ```kotlin 419 | val easyWaitRspPort = serialPort.cast2WaitRspPort() 420 | CoroutineScope(Dispatchers.IO).launch { 421 | val rspBean = easyWaitRspPort.writeWaitRsp("00 FF AA".conver2ByteArray()) 422 | } 423 | ``` 424 | 425 | 或者是: 426 | 427 | ```kotlin 428 | val keepReceivePort = serialPort.cast2KeepReceivePort() 429 | keepReceivePort.write("00 FF AA".conver2ByteArray()) 430 | ``` 431 | 432 | **2. 设置串口不读取字节数:** 433 | 如果你发现,串口无法收到数据,但是可正常写入数据,使用串口调试工具可正常收发,那么你应当试试如下将串口设置为无法读取字节数: 434 | 435 | ```kotlin 436 | EasySerialBuilder.addNoAvailableDevicePath("dev/ttyS4") 437 | ``` 438 | 439 | 设置完后再开启串口,否则设置不生效;也可以直接这么写: 440 | 441 | ```kotlin 442 | EasySerialBuilder.addNoAvailableDevicePath("dev/ttyS4") 443 | .createWaitRspPort("dev/ttyS4", BaudRate.B4800) 444 | ``` 445 | 446 | 对于`addNoAvailableDevicePath()`方法,需要讲解一下内部串口数据读取的实现了; 447 | 在读取数据时,会先调用`inputStream.available()` 来判断流中有多少个可读字节,但在部分串口中,即使有数据, 448 | `available()`读取到的依旧是0,这就导致了无法读取到数据的情况; 449 | 当调用`addNoAvailableDevicePath()`后, 我们将不再判断流中的可读字节数,而是直接调用`inputStream.read()`方法; 450 | 当你使用此方法后,请勿重复开启\关闭串口, 因为这样可能会导致串口无法再工作; 451 | 452 | **3. 获取本设备所有的串口名称:** 453 | 454 | ```kotlin 455 | val allDevicesPath: MutableList = EasySerialFinderUtil.getAllDevicesPath() 456 | allDevicesPath.forEach { Log.d(tag, "串口名称: $it") } 457 | ``` 458 | 459 | **4. 判断当前是否有串口正在使用:** 460 | 461 | ```kotlin 462 | val hasPortWorking: Boolean = EasySerialBuilder.hasPortWorking() 463 | ``` 464 | 465 | **5. 串口日志打印开关:** 466 | 是否打印串口通信日志 true为打印日志,false为不打印; 467 | 建议在Release版本中不打印串口日志; 468 | 打印的日志的 tag = "EasyPort"; 469 | 470 | ```kotlin 471 | EasySerialBuilder.isShowLog(true) 472 | ``` 473 | 474 | **6. 数据转化:** 475 | A). 16进制字符串转为字节数组: 476 | 477 | ```kotlin 478 | val hexString = "00 FF CA FA" 479 | //将16进制字符串 转为 字节数组 480 | val hexByteArray1 = hexString.conver2ByteArray() 481 | //将16进制字符串从第0位截取到第4位("00 FF") 转为 字节数组 482 | val hexByteArray2 = hexString.conver2ByteArray(4) 483 | //将16进制字符串从第2位截取到第4位(" FF") 转为 字节数组 484 | val hexByteArray3 = hexString.conver2ByteArray(2, 4) 485 | ``` 486 | 487 | B). 字节数组转为16进制字符串: 488 | 489 | ```kotlin 490 | val byteArray = byteArrayOf(0, -1, 10)// 此字节数组=="00FF0A" 491 | //将字节数组 转为 16进制字符串 492 | val hexStr1 = byteArray.conver2HexString()//结果为:"00FF0A" 493 | //将字节数组取1位 转为 16进制字符串 494 | val hexStr2 = byteArray.conver2HexString(1)//结果为:"00" 495 | //将字节数组取2位 转为 16进制字符串 并设置字母为小写 496 | val hexStr3 = byteArray.conver2HexString(2, false)//结果为:"00ff" 497 | //将字节数组取第2位到第3位 转为 16进制字符串 并设置字母为小写 498 | val hexStr4 = byteArray.conver2HexString(1, 2, false)//结果为:"ff0a" 499 | //将字节数组取第0位 转为 16进制字符串 并设置字母为小写 500 | val hexStr5 = byteArray.conver2HexString(0, 0, false)//结果为:"00" 501 | 502 | //将字节数组 转为 16进制字符串 16进制之间用空格分隔 503 | val hexStr6 = byteArray.conver2HexStringWithBlank()//结果为:"00 FF 0A" 504 | //将字节数组取2位 转为 16进制字符串 16进制之间用空格分隔 505 | val hexStr7 = byteArray.conver2HexStringWithBlank(2)//结果为:"00 FF" 506 | //将字节数组取2位 转为 16进制字符串 并设置字母为小写 507 | val hexStr8 = byteArray.conver2HexStringWithBlank(2, false)//结果为:"00 ff" 508 | //将字节数组取第2位到第3位 转为 16进制字符串 并设置字母为小写 509 | val hexStr9 = byteArray.conver2HexStringWithBlank(1, 2, false)//结果为:"ff 0a" 510 | //将字节数组取第2位 转为 16进制字符串 并设置字母为小写 511 | val hexStr10 = byteArray.conver2HexStringWithBlank(1, 1, false)//结果为:"ff" 512 | ``` 513 | 514 | C). 字节数组转为字符数组: 515 | 516 | ```kotlin 517 | val byteArray2 = 518 | byteArrayOf('H'.code.toByte(), 'A'.code.toByte(), 'H'.code.toByte(), 'A'.code.toByte()) 519 | //将字节数组 转为 字符数组 520 | val charArray1 = byteArray2.conver2CharArray()//即:"HAHA" 521 | //将字节数组取1位 转为 字符数组 522 | val charArray2 = byteArray2.conver2CharArray(1)//即:"H" 523 | //将字节数组取第2位到第3位 转为 字符数组 524 | val charArray3 = byteArray2.conver2CharArray(2, 3)//即:"HA" 525 | //将字节数组第2位 转为 字符数组 526 | val charArray4 = byteArray2.conver2CharArray(2, 2)//即:"H" 527 | ``` 528 | 529 | D). 字节数组计算CRC值: 530 | 531 | ```kotlin 532 | val byteArray3 = byteArrayOf(1, 3, 0, 0, 0, 9) 533 | //计算字节数组的CRC值: 534 | val crc1 = byteArray3.getCRC() 535 | //将字节数组取2位,计算CRC值: 536 | val crc2 = byteArray3.getCRC(2) 537 | //将字节数组取第2位到第3位,计算CRC值: 538 | val crc3 = byteArray3.getCRC(2, 3) 539 | 540 | //将crc转为字节的计算示例: 541 | val highByte = (crc1 and 0xFF).toByte()//高位 542 | val lowByte = (crc1 shr 8 and 0xFF).toByte()//低位 543 | ``` 544 | 545 | # 三、传送门 546 | 547 | 1. [github地址](https://github.com/BASS-HY/EasySerial) 548 | 2. [CSDN地址](https://blog.csdn.net/weixin_45379305/article/details/127719263?spm=1001.2014.3001.5501) 549 | 3. 如果有什么意见和建议都可以在github或者在CSDN中向我提出噢,我也会一直完善此项目的; 550 | 551 | # 四、鸣谢 552 | 553 | 此项目移植于谷歌官方串口库[android-serialport-api](https://code.google.com/archive/p/android-serialport-api/) 554 | ,以及[GeekBugs-Android-SerialPort](https://github.com/GeekBugs/Android-SerialPort) 555 | ,综合了这俩个库的C代码,对其进行的封装; 556 | 557 | # 五、许可证 558 | 559 | ``` 560 | Copyright 2022 BASS 561 | 562 | Licensed under the Apache License, Version 2.0 (the "License"); 563 | you may not use this file except in compliance with the License. 564 | You may obtain a copy of the License at 565 | 566 | http://www.apache.org/licenses/LICENSE-2.0 567 | 568 | Unless required by applicable law or agreed to in writing, software 569 | distributed under the License is distributed on an "AS IS" BASIS, 570 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 571 | See the License for the specific language governing permissions and 572 | limitations under the License. 573 | ``` --------------------------------------------------------------------------------