├── 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 | ```
--------------------------------------------------------------------------------