diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..34ca3da --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..e1e76f7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..74c3861 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1ce180d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/SerialPort/.gitignore b/SerialPort/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/SerialPort/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/SerialPort/build.gradle b/SerialPort/build.gradle new file mode 100644 index 0000000..42fdabe --- /dev/null +++ b/SerialPort/build.gradle @@ -0,0 +1,48 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdk 30 + + defaultConfig { + minSdk 16 + targetSdk 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + externalNativeBuild { + cmake { + cppFlags '' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + externalNativeBuild { + cmake { + path file('src/main/cpp/CMakeLists.txt') + version '3.10.2' + } + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.3.0' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' +} \ No newline at end of file diff --git a/SerialPort/consumer-rules.pro b/SerialPort/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/SerialPort/proguard-rules.pro b/SerialPort/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/SerialPort/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/SerialPort/src/androidTest/java/top/lyoun/serialport/ExampleInstrumentedTest.java b/SerialPort/src/androidTest/java/top/lyoun/serialport/ExampleInstrumentedTest.java new file mode 100644 index 0000000..87661e7 --- /dev/null +++ b/SerialPort/src/androidTest/java/top/lyoun/serialport/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package top.lyoun.serialport; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("top.lyoun.serialport.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/SerialPort/src/main/AndroidManifest.xml b/SerialPort/src/main/AndroidManifest.xml new file mode 100644 index 0000000..36bb686 --- /dev/null +++ b/SerialPort/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/SerialPort/src/main/cpp/CMakeLists.txt b/SerialPort/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..dc335e7 --- /dev/null +++ b/SerialPort/src/main/cpp/CMakeLists.txt @@ -0,0 +1,83 @@ + +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.10.2) + +# Declares and names the project. + +project("SerialPort") + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +add_library( # Sets the name of the library. + serial_port + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + SerialPort.c) + +add_library( # Sets the name of the library. + welcome + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + welcome.c) + + +add_library( # Sets the name of the library. + hello + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + hello.c) + + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log ) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( # Specifies the target library. + serial_port + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) + +target_link_libraries( # Specifies the target library. + hello + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) + +target_link_libraries( # Specifies the target library. + welcome + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) \ No newline at end of file diff --git a/SerialPort/src/main/cpp/SerialPort.c b/SerialPort/src/main/cpp/SerialPort.c new file mode 100644 index 0000000..93e97dc --- /dev/null +++ b/SerialPort/src/main/cpp/SerialPort.c @@ -0,0 +1,312 @@ +/* + * Copyright 2009-2011 Cedric Priscal + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "SerialPort.h" + +#include "android/log.h" + +static const char *TAG = "serial_port"; +#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args) +#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args) +#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) + +static speed_t getBaudrate(jint baudrate) { + switch (baudrate) { + case 0: + return B0; + case 50: + return B50; + case 75: + return B75; + case 110: + return B110; + case 134: + return B134; + case 150: + return B150; + case 200: + return B200; + case 300: + return B300; + case 600: + return B600; + case 1200: + return B1200; + case 1800: + return B1800; + case 2400: + return B2400; + case 4800: + return B4800; + case 9600: + return B9600; + case 19200: + return B19200; + case 38400: + return B38400; + case 57600: + return B57600; + case 115200: + return B115200; + case 230400: + return B230400; + case 460800: + return B460800; + case 500000: + return B500000; + case 576000: + return B576000; + case 921600: + return B921600; + case 1000000: + return B1000000; + case 1152000: + return B1152000; + case 1500000: + return B1500000; + case 2000000: + return B2000000; + case 2500000: + return B2500000; + case 3000000: + return B3000000; + case 3500000: + return B3500000; + case 4000000: + return B4000000; + default: + return -1; + } +} + +/* + * Class: top_lyoun_serialport_SerialPort + * Method: open + * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; + */ +JNIEXPORT jobject JNICALL Java_top_lyoun_serialport_SerialPort_open + (JNIEnv *env, jobject thiz, jstring path, jint baudrate, jint dataBits, jint parity, + jint stopBits, + jint flags) { + +#if defined(__arm__) +#if defined(__ARM_ARCH_7A__) +#if defined(__ARM_NEON__) +#if defined(__ARM_PCS_VFP) +#define ABI "armeabi-v7a/NEON (hard-float)" +#else +#define ABI "armeabi-v7a/NEON" +#endif +#else +#if defined(__ARM_PCS_VFP) +#define ABI "armeabi-v7a (hard-float)" +#else +#define ABI "armeabi-v7a" +#endif +#endif +#else +#define ABI "armeabi" +#endif +#elif defined(__i386__) +#define ABI "x86" +#elif defined(__x86_64__) +#define ABI "x86_64" +#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ +#define ABI "mips64" +#elif defined(__mips__) +#define ABI "mips" +#elif defined(__aarch64__) +#define ABI "arm64-v8a" +#else +#define ABI "unknown" +#endif + + int fd; + speed_t speed; + jobject mFileDescriptor; + + /* Check arguments */ + { + speed = getBaudrate(baudrate); + if (speed == -1) { + /* TODO: throw an exception */ + LOGE("Invalid baudrate"); + return NULL; + } + } + + /* Opening device */ + { + jboolean iscopy; + const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy); + LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags); + fd = open(path_utf, O_RDWR | flags); + LOGD("open() fd = %d", fd); + (*env)->ReleaseStringUTFChars(env, path, path_utf); + if (fd == -1) { + /* Throw an exception */ + LOGE("Cannot open port"); + /* TODO: throw an exception */ + return NULL; + } + } + + /* Configure device */ + { + struct termios cfg; + LOGD("Configuring serial port"); + if (tcgetattr(fd, &cfg)) { + LOGE("tcgetattr() failed"); + close(fd); + /* TODO: throw an exception */ + return NULL; + } + + cfmakeraw(&cfg); + cfsetispeed(&cfg, speed); + cfsetospeed(&cfg, speed); + + + cfg.c_cflag &= ~CSIZE; + switch (dataBits) { + case 5: + cfg.c_cflag |= CS5; //使用5位数据位 + break; + case 6: + cfg.c_cflag |= CS6; //使用6位数据位 + break; + case 7: + cfg.c_cflag |= CS7; //使用7位数据位 + break; + case 8: + cfg.c_cflag |= CS8; //使用8位数据位 + break; + default: + cfg.c_cflag |= CS8; + break; + } + + switch (parity) { + case 0: + cfg.c_cflag &= ~PARENB; //无奇偶校验 + break; + case 1: + cfg.c_cflag |= (PARODD | PARENB); //奇校验 + break; + case 2: + cfg.c_iflag &= ~(IGNPAR | PARMRK); // 偶校验 + cfg.c_iflag |= INPCK; + cfg.c_cflag |= PARENB; + cfg.c_cflag &= ~PARODD; + break; + default: + cfg.c_cflag &= ~PARENB; + break; + } + + switch (stopBits) { + case 1: + cfg.c_cflag &= ~CSTOPB; //1位停止位 + break; + case 2: + cfg.c_cflag |= CSTOPB; //2位停止位 + break; + default: + cfg.c_cflag &= ~CSTOPB; //1位停止位 + break; + } + + if (tcsetattr(fd, TCSANOW, &cfg)) { + LOGE("tcsetattr() failed"); + close(fd); + /* TODO: throw an exception */ + return NULL; + } + } + + /* Create a corresponding file descriptor */ + { + jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor"); + jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "", "()V"); + jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I"); + mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor); + (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint) fd); + } + + return mFileDescriptor; +} + +/* + * Class: top_lyoun_serialport_SerialPort + * Method: close + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_top_lyoun_serialport_SerialPort_close + (JNIEnv *env, jobject thiz) { + +#if defined(__arm__) +#if defined(__ARM_ARCH_7A__) +#if defined(__ARM_NEON__) +#if defined(__ARM_PCS_VFP) +#define ABI "armeabi-v7a/NEON (hard-float)" +#else +#define ABI "armeabi-v7a/NEON" +#endif +#else +#if defined(__ARM_PCS_VFP) +#define ABI "armeabi-v7a (hard-float)" +#else +#define ABI "armeabi-v7a" +#endif +#endif +#else +#define ABI "armeabi" +#endif +#elif defined(__i386__) +#define ABI "x86" +#elif defined(__x86_64__) +#define ABI "x86_64" +#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ +#define ABI "mips64" +#elif defined(__mips__) +#define ABI "mips" +#elif defined(__aarch64__) +#define ABI "arm64-v8a" +#else +#define ABI "unknown" +#endif + + jclass SerialPortClass = (*env)->GetObjectClass(env, thiz); + jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor"); + + jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); + jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I"); + + jobject mFd = (*env)->GetObjectField(env, thiz, mFdID); + jint descriptor = (*env)->GetIntField(env, mFd, descriptorID); + + LOGD("close(fd = %d)", descriptor); + close(descriptor); +} + diff --git a/SerialPort/src/main/cpp/SerialPort.h b/SerialPort/src/main/cpp/SerialPort.h new file mode 100644 index 0000000..b5045de --- /dev/null +++ b/SerialPort/src/main/cpp/SerialPort.h @@ -0,0 +1,40 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class top_lyoun_serialport_SerialPort */ + +#ifndef _Included_top_lyoun_serialport_SerialPort +#define _Included_top_lyoun_serialport_SerialPort +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: top_lyoun_serialport_SerialPort + * Method: open + * Signature: (Ljava/lang/String;IIIII)Ljava/io/FileDescriptor; + */ +JNIEXPORT jobject JNICALL Java_top_lyoun_serialport_SerialPort_open + (JNIEnv *, jobject, jstring, jint, jint, jint, jint, jint); + +/* + * Class: top_lyoun_serialport_SerialPort + * Method: close + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_top_lyoun_serialport_SerialPort_close + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif +/* Header for class top_lyoun_serialport_SerialPort_Builder */ + +#ifndef _Included_top_lyoun_serialport_SerialPort_Builder +#define _Included_top_lyoun_serialport_SerialPort_Builder +#ifdef __cplusplus +extern "C" { +#endif +#ifdef __cplusplus +} +#endif +#endif diff --git a/SerialPort/src/main/cpp/hello.c b/SerialPort/src/main/cpp/hello.c new file mode 100644 index 0000000..9f91a7a --- /dev/null +++ b/SerialPort/src/main/cpp/hello.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include + +/* This is a trivial JNI example where we use a native method + * to return a new VM String. See the corresponding Java source + * file located at: + * + * hello-jni/app/src/main/java/com/example/hellojni/HelloJni.java + */ +JNIEXPORT jstring JNICALL +Java_top_lyoun_serialport_Hello_stringFromJNI( JNIEnv* env, jobject thiz ) +{ +#if defined(__arm__) + #if defined(__ARM_ARCH_7A__) + #if defined(__ARM_NEON__) + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a/NEON (hard-float)" + #else + #define ABI "armeabi-v7a/NEON" + #endif + #else + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a (hard-float)" + #else + #define ABI "armeabi-v7a" + #endif + #endif + #else + #define ABI "armeabi" + #endif +#elif defined(__i386__) +#define ABI "x86" +#elif defined(__x86_64__) +#define ABI "x86_64" +#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ +#define ABI "mips64" +#elif defined(__mips__) +#define ABI "mips" +#elif defined(__aarch64__) +#define ABI "arm64-v8a" +#else +#define ABI "unknown" +#endif + + return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI "."); +} + + +JNIEXPORT jstring JNICALL +Java_top_lyoun_serialport_Hello_otherInfo( JNIEnv* env, jobject thiz ) +{ +#if defined(__arm__) + #if defined(__ARM_ARCH_7A__) + #if defined(__ARM_NEON__) + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a/NEON (hard-float)" + #else + #define ABI "armeabi-v7a/NEON" + #endif + #else + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a (hard-float)" + #else + #define ABI "armeabi-v7a" + #endif + #endif + #else + #define ABI "armeabi" + #endif +#elif defined(__i386__) +#define ABI "x86" +#elif defined(__x86_64__) + #define ABI "x86_64" +#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ +#define ABI "mips64" +#elif defined(__mips__) +#define ABI "mips" +#elif defined(__aarch64__) +#define ABI "arm64-v8a" +#else +#define ABI "unknown" +#endif + + return (*env)->NewStringUTF(env, "我是其他信息! "); +} \ No newline at end of file diff --git a/SerialPort/src/main/cpp/welcome.c b/SerialPort/src/main/cpp/welcome.c new file mode 100644 index 0000000..de679ae --- /dev/null +++ b/SerialPort/src/main/cpp/welcome.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include + +/* This is a trivial JNI example where we use a native method + * to return a new VM String. See the corresponding Java source + * file located at: + * + * hello-jni/app/src/main/java/com/example/hellojni/HelloJni.java + */ +JNIEXPORT jstring JNICALL +Java_top_lyoun_serialport_Welcome_welcome( JNIEnv* env, jobject thiz ) +{ +#if defined(__arm__) + #if defined(__ARM_ARCH_7A__) + #if defined(__ARM_NEON__) + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a/NEON (hard-float)" + #else + #define ABI "armeabi-v7a/NEON" + #endif + #else + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a (hard-float)" + #else + #define ABI "armeabi-v7a" + #endif + #endif + #else + #define ABI "armeabi" + #endif +#elif defined(__i386__) +#define ABI "x86" +#elif defined(__x86_64__) +#define ABI "x86_64" +#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ +#define ABI "mips64" +#elif defined(__mips__) +#define ABI "mips" +#elif defined(__aarch64__) +#define ABI "arm64-v8a" +#else +#define ABI "unknown" +#endif + + return (*env)->NewStringUTF(env, "Welcome JNI Android ! "); +} + diff --git a/SerialPort/src/main/java/top/lyoun/serialport/ByteUtils.java b/SerialPort/src/main/java/top/lyoun/serialport/ByteUtils.java new file mode 100644 index 0000000..a6d0b2d --- /dev/null +++ b/SerialPort/src/main/java/top/lyoun/serialport/ByteUtils.java @@ -0,0 +1,59 @@ +package top.lyoun.serialport; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 13:41 + * Desc: ByteUtils + */ +public class ByteUtils { + + //判断是否是奇数:true: 奇数 , false: 偶数 + public final static int isOdd(int num) { + return num & 0x1; + } + + /** + * 十六进制字符串转byte数组,当hex为小于256时,byte数组长度为1, + * 当hex为大于256,小于65536时,byte数组长度为2,以此类推 + * 功能已验证,success + * @param hexString + * @return byte[] + */ + public final static byte[] hexToBytes(String hexString) { + if (hexString == null || hexString.equals("")) { + return null; + } + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) ((charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])) & 0xFF); + } + return d; + } + + /** + * 字符转byte + * 功能已验证,success + * @param c char + * @return byte + */ + private final static byte charToByte(char c) { + return (byte) "0123456789ABCDEF".indexOf(c); + } + + //十六进制转整数 + public final static int HexToInt(String inHex) { + if (inHex == null || inHex.equals("")) { + return -1; + } + try { + return Integer.parseInt(inHex, 16); + } catch (NumberFormatException e) { + e.printStackTrace(); + return -1; + } + } +} diff --git a/SerialPort/src/main/java/top/lyoun/serialport/Hello.java b/SerialPort/src/main/java/top/lyoun/serialport/Hello.java new file mode 100644 index 0000000..ca3262e --- /dev/null +++ b/SerialPort/src/main/java/top/lyoun/serialport/Hello.java @@ -0,0 +1,18 @@ +package top.lyoun.serialport; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 12:35 + * Desc: Hello + */ +public class Hello { + + static { + System.loadLibrary("hello"); + } + + public native String stringFromJNI(); + + public native String otherInfo(); + +} diff --git a/SerialPort/src/main/java/top/lyoun/serialport/SerialPort.java b/SerialPort/src/main/java/top/lyoun/serialport/SerialPort.java new file mode 100644 index 0000000..c1c9c1c --- /dev/null +++ b/SerialPort/src/main/java/top/lyoun/serialport/SerialPort.java @@ -0,0 +1,498 @@ +/* + * Copyright 2009 Cedric Priscal + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.lyoun.serialport; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import top.lyoun.serialport.listener.OnSerialDataListener; +import top.lyoun.serialport.listener.OnSerialOpenListener; +import top.lyoun.serialport.listener.Status; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 12:35 + * Desc: SerialPort 串口读写工具类 + */ +public final class SerialPort { + + private static final String TAG = "SerialPort"; + + public static final String DEFAULT_SU_PATH = "/system/bin/su"; + + private static String sSuPath = DEFAULT_SU_PATH; + private File device; //串口设备文件 + private int baudrate; //波特率 + private int dataBits; //数据位;默认8,可选值为5~8 + private int parity; //奇偶校验;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN) + private int stopBits; //停止位;默认1;1:1位停止位;2:2位停止位 + private int flags; //默认0 + + /* + * Do not remove or rename the field mFd: it is used by native method close(); + */ + private FileDescriptor mFd; + private FileInputStream mFileInputStream; + private FileOutputStream mFileOutputStream; + + private OnSerialOpenListener mSerialOpenListener; + private OnSerialDataListener mSerialDataListener; + public boolean isOpen = false; //串口是否打开标记 + + private HandlerThread mSendingHandlerThread; + private Handler mSendingHandler; + private SerialReadThread mSerialReadThread; + + static { + System.loadLibrary("serial_port"); + } + + // JNI + private native FileDescriptor open(String absolutePath, int baudrate, int dataBits, int parity, + int stopBits, int flags); + + public native void close(); + + + /** + * 串口 + * + * @param device 串口设备文件 + * @param baudrate 波特率 + * @param dataBits 数据位;默认8,可选值为5~8 + * @param parity 奇偶校验;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN) + * @param stopBits 停止位;默认1;1:1位停止位;2:2位停止位 + * @param flags 默认0 + * @throws SecurityException + * @throws IOException + */ + public SerialPort(@NonNull File device, int baudrate, int dataBits, int parity, int stopBits, + int flags) { + + this.device = device; + this.baudrate = baudrate; + this.dataBits = dataBits; + this.parity = parity; + this.stopBits = stopBits; + this.flags = flags; + } + + /** + * 串口,默认的8n1 + * + * @param device 串口设备文件 + * @param baudrate 波特率 + * @throws SecurityException + * @throws IOException + */ + public SerialPort(@NonNull File device, int baudrate) throws SecurityException, IOException { + this(device, baudrate, 8, 0, 1, 0); + } + + /** + * 串口 + * + * @param device 串口设备文件 + * @param baudrate 波特率 + * @param dataBits 数据位;默认8,可选值为5~8 + * @param parity 奇偶校验;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN) + * @param stopBits 停止位;默认1;1:1位停止位;2:2位停止位 + * @throws SecurityException + * @throws IOException + */ + public SerialPort(@NonNull File device, int baudrate, int dataBits, int parity, int stopBits) + throws SecurityException, IOException { + this(device, baudrate, dataBits, parity, stopBits, 0); + } + + + /** + * 设置串口打开监听 + */ + public void setOnSerialOpenListener(OnSerialOpenListener listener){ + if (listener != null) { + mSerialOpenListener = listener; + } + } + + /** + * 设置串口数据收发监听 + */ + public void setOnSerialDataListener(OnSerialDataListener listener){ + if(listener != null){ + mSerialDataListener = listener; + } + } + + /** + * 打开串口 + * + * @return 串口打开状态 true:打开 false:打开失败 + */ + public boolean open() { + isOpen = openSafe(device,baudrate, dataBits, parity, stopBits, flags); + return isOpen; + } + + /** 关闭流和串口,已经try-catch */ + public void tryClose() { + stopSendThread(); + stopReceivedThread(); + closeSafe(); + isOpen = false; + + Log.i(TAG, "tryClose: 串口关闭成功!" + device); + } + + /** + * 发送数据 + * + * @param bytes 发送的字节 + * @return 发送状态 true:发送成功 false:发送失败 + */ + public boolean sendBytes(byte[] bytes) { + if (null != mSendingHandler) { + Message message = Message.obtain(); + message.obj = bytes; + return mSendingHandler.sendMessage(message); + } + return false; + } + + /** + * 发送Hex + * + * @param hex 16进制文本 + */ + public void sendHex(String hex) { + byte[] hexArray = ByteUtils.hexToBytes(hex); + sendBytes(hexArray); + } + + /** + * 发送文本 + * + * @param txt 文本 + */ + public void sendTxt(String txt) { + byte[] txtArray = txt.getBytes(); + sendBytes(txtArray); + } + + /** + * 开启发送消息线程 + */ + private void startSendThread() { + mSendingHandlerThread = new HandlerThread("mSendingHandlerThread"); + mSendingHandlerThread.start(); + + mSendingHandler = new Handler(mSendingHandlerThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + byte[] sendBytes = (byte[]) msg.obj; + if (null != mFileOutputStream && null != sendBytes && sendBytes.length > 0) { + try { + mFileOutputStream.write(sendBytes); + if (null != mSerialDataListener) { + mSerialDataListener.onDataSend(sendBytes); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }; + } + + /** + * 停止发送消息线程 + */ + private void stopSendThread() { + mSendingHandler = null; + if (null != mSendingHandlerThread) { + mSendingHandlerThread.interrupt(); + mSendingHandlerThread.quit(); + mSendingHandlerThread = null; + } + } + + /** + * 开启接收消息的线程 + */ + private void startReadThread() { + mSerialReadThread = new SerialReadThread(mFileInputStream) { + @Override + public void onDataReceived(byte[] bytes) { + if (null != mSerialDataListener) { + mSerialDataListener.onDataReceived(bytes); + } + } + }; + mSerialReadThread.start(); + } + + /** + * 停止接收消息的线程 + */ + private void stopReceivedThread() { + if (null != mSerialReadThread) { + mSerialReadThread.release(); + } + } + + /** + * 检查文件权限 + * + * @param device 文件 + * @return 权限修改是否成功 + */ + private boolean chmod777(File device) { + if (null == device || !device.exists()) { + return false; + } + try { + Process su = Runtime.getRuntime().exec(sSuPath); + String cmd = "chmod 777" + device.getAbsolutePath() + "\n" + "exit\n"; + su.getOutputStream().write(cmd.getBytes()); + if (0 == su.waitFor() && device.canRead() && device.canWrite() && device.canExecute()) { + return true; + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + + } + return false; + } + + + private boolean openSafe(File device, int baudrate, int dataBits, int parity, + int stopBits, int flags) { + if (null == device || !device.exists()) { + Log.e(TAG, "openSafe: 文件不存在或文件不能为空!"); + return false; + } + Log.i(TAG, String.format("SerialPort: %s: %d,%d,%d,%d,%d,%d", device.getPath(), baudrate, dataBits, parity, stopBits, flags)); + if (!device.canRead() || !device.canWrite()) { + boolean chmod777 = chmod777(device); + if (!chmod777) { + Log.e(TAG, device.getPath() + " : 没有读写权限"); + if (null != mSerialOpenListener) { + mSerialOpenListener.onFail(device, Status.NO_READ_WRITE_PERMISSION); + } + return false; + } + } + try { + mFd = open(device.getAbsolutePath(), baudrate, dataBits, parity, stopBits, flags); + mFileInputStream = new FileInputStream(mFd); + mFileOutputStream = new FileOutputStream(mFd); + startSendThread(); + startReadThread(); + if (null != mSerialOpenListener) { + mSerialOpenListener.onSuccess(device); + } + Log.i(TAG, device.getPath() + " : 串口已经打开"); + return true; + } catch (Exception e) { + e.printStackTrace(); + if (null != mSerialOpenListener) { + mSerialOpenListener.onFail(device, Status.OPEN_FAIL); + } + } + return false; + } + + private void closeSafe() { + if (null != mFd) { + close(); + mFd = null; + } + if (null != mFileInputStream) { + try { + mFileInputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + mFileInputStream = null; + } + + if (null != mFileOutputStream) { + try { + mFileOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + mFileOutputStream = null; + } + } + + // Getters and setters + @NonNull + public InputStream getInputStream() { + return mFileInputStream; + } + + @NonNull + public OutputStream getOutputStream() { + return mFileOutputStream; + } + + /** + * Set the su binary path, the default su binary path is {@link #DEFAULT_SU_PATH} + * + * @param suPath su binary path + */ + public static void setSuPath(@Nullable String suPath) { + if (suPath == null) { + return; + } + sSuPath = suPath; + } + + /** + * Get the su binary path + * + * @return + */ + @NonNull + public static String getSuPath() { + return sSuPath; + } + + /** 串口设备文件 */ + @NonNull + public File getDevice() { + return device; + } + + /** 波特率 */ + public int getBaudrate() { + return baudrate; + } + + /** 数据位;默认8,可选值为5~8 */ + public int getDataBits() { + return dataBits; + } + + /** 奇偶校验;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN) */ + public int getParity() { + return parity; + } + + /** 停止位;默认1;1:1位停止位;2:2位停止位 */ + public int getStopBits() { + return stopBits; + } + + public int getFlags() { + return flags; + } + + public static Builder newBuilder(File device, int baudrate) { + return new Builder(device, baudrate); + } + + public static Builder newBuilder(String devicePath, int baudrate) { + return new Builder(devicePath, baudrate); + } + + public final static class Builder { + + private File device; + private int baudrate; + private int dataBits = 8; + private int parity = 0; + private int stopBits = 1; + private int flags = 0; + + private Builder(File device, int baudrate) { + this.device = device; + this.baudrate = baudrate; + } + + private Builder(String devicePath, int baudrate) { + this(new File(devicePath), baudrate); + } + + /** + * 数据位 + * + * @param dataBits 默认8,可选值为5~8 + * @return + */ + public Builder dataBits(int dataBits) { + this.dataBits = dataBits; + return this; + } + + /** + * 校验位 + * + * @param parity 0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN) + * @return + */ + public Builder parity(int parity) { + this.parity = parity; + return this; + } + + /** + * 停止位 + * + * @param stopBits 默认1;1:1位停止位;2:2位停止位 + * @return + */ + public Builder stopBits(int stopBits) { + this.stopBits = stopBits; + return this; + } + + /** + * 标志 + * + * @param flags 默认0 + * @return + */ + public Builder flags(int flags) { + this.flags = flags; + return this; + } + + /** + * 打开并返回串口 + * + * @return + * @throws SecurityException + * @throws IOException + */ + public SerialPort build() { + return new SerialPort(device, baudrate, dataBits, parity, stopBits, flags); + } + } +} diff --git a/SerialPort/src/main/java/top/lyoun/serialport/SerialPortFinder.java b/SerialPort/src/main/java/top/lyoun/serialport/SerialPortFinder.java new file mode 100644 index 0000000..facc1d3 --- /dev/null +++ b/SerialPort/src/main/java/top/lyoun/serialport/SerialPortFinder.java @@ -0,0 +1,133 @@ +/* + * Copyright 2009 Cedric Priscal + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.lyoun.serialport; + +import android.util.Log; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.LineNumberReader; +import java.util.Iterator; +import java.util.Vector; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 13:22 + * Desc: SerialPortFinder + */ +public class SerialPortFinder { + + public class Driver { + public Driver(String name, String root) { + mDriverName = name; + mDeviceRoot = root; + } + + private String mDriverName; + private String mDeviceRoot; + Vector mDevices = null; + + public Vector getDevices() { + if (mDevices == null) { + mDevices = new Vector(); + File dev = new File("/dev"); + + File[] files = dev.listFiles(); + + if (files != null) { + int i; + for (i = 0; i < files.length; i++) { + if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) { + Log.d(TAG, "Found new device: " + files[i]); + mDevices.add(files[i]); + } + } + } + } + return mDevices; + } + + public String getName() { + return mDriverName; + } + } + + private static final String TAG = "SerialPort"; + + private Vector mDrivers = null; + + Vector getDrivers() throws IOException { + if (mDrivers == null) { + mDrivers = new Vector(); + LineNumberReader r = new LineNumberReader(new FileReader("/proc/tty/drivers")); + String l; + while ((l = r.readLine()) != null) { + // Issue 3: + // Since driver name may contain spaces, we do not extract driver name with split() + String drivername = l.substring(0, 0x15).trim(); + String[] w = l.split(" +"); + if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) { + Log.d(TAG, "Found new driver " + drivername + " on " + w[w.length - 4]); + mDrivers.add(new Driver(drivername, w[w.length - 4])); + } + } + r.close(); + } + return mDrivers; + } + + public String[] getAllDevices() { + Vector devices = new Vector(); + // Parse each driver + Iterator itdriv; + try { + itdriv = getDrivers().iterator(); + while (itdriv.hasNext()) { + Driver driver = itdriv.next(); + Iterator itdev = driver.getDevices().iterator(); + while (itdev.hasNext()) { + String device = itdev.next().getName(); + String value = String.format("%s (%s)", device, driver.getName()); + devices.add(value); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return devices.toArray(new String[devices.size()]); + } + + public String[] getAllDevicesPath() { + Vector devices = new Vector(); + // Parse each driver + Iterator itdriv; + try { + itdriv = getDrivers().iterator(); + while (itdriv.hasNext()) { + Driver driver = itdriv.next(); + Iterator itdev = driver.getDevices().iterator(); + while (itdev.hasNext()) { + String device = itdev.next().getAbsolutePath(); + devices.add(device); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return devices.toArray(new String[devices.size()]); + } +} diff --git a/SerialPort/src/main/java/top/lyoun/serialport/SerialReadThread.java b/SerialPort/src/main/java/top/lyoun/serialport/SerialReadThread.java new file mode 100644 index 0000000..2b37d5b --- /dev/null +++ b/SerialPort/src/main/java/top/lyoun/serialport/SerialReadThread.java @@ -0,0 +1,61 @@ +package top.lyoun.serialport; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 13:22 + * Desc: SerialReadThread + */ +public abstract class SerialReadThread extends Thread{ + + private static final String TAG = "SerialReadThread"; + + private InputStream mInputStream; + private byte[] mReceivedBuffer; + + public SerialReadThread(InputStream inputStream) { + mInputStream = inputStream; + mReceivedBuffer = new byte[1024]; + } + + @Override + public void run() { + super.run(); + while (!isInterrupted()) { + try { + if (null == mInputStream) { + return; + } + int size = mInputStream.read(mReceivedBuffer); + if (0 >= size) { + return; + } + byte[] receivedBytes = new byte[size]; + System.arraycopy(mReceivedBuffer, 0, receivedBytes, 0, size); + onDataReceived(receivedBytes); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public abstract void onDataReceived(byte[] bytes); + + /** + * 释放 + */ + public void release() { + interrupt(); + + if (null != mInputStream) { + try { + mInputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + mInputStream = null; + } + } +} diff --git a/SerialPort/src/main/java/top/lyoun/serialport/Welcome.java b/SerialPort/src/main/java/top/lyoun/serialport/Welcome.java new file mode 100644 index 0000000..7403c9d --- /dev/null +++ b/SerialPort/src/main/java/top/lyoun/serialport/Welcome.java @@ -0,0 +1,15 @@ +package top.lyoun.serialport; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 12:35 + * Desc: Welcome + */ +public class Welcome { + + static { + System.loadLibrary("welcome"); + } + + public native String welcome(); +} diff --git a/SerialPort/src/main/java/top/lyoun/serialport/listener/OnSerialDataListener.java b/SerialPort/src/main/java/top/lyoun/serialport/listener/OnSerialDataListener.java new file mode 100644 index 0000000..d2c02d2 --- /dev/null +++ b/SerialPort/src/main/java/top/lyoun/serialport/listener/OnSerialDataListener.java @@ -0,0 +1,13 @@ +package top.lyoun.serialport.listener; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 12:36 + * Desc: 监控串口数据收发 + */ +public interface OnSerialDataListener { + + void onDataReceived(byte[] bytes); + + void onDataSend(byte[] bytes); +} diff --git a/SerialPort/src/main/java/top/lyoun/serialport/listener/OnSerialOpenListener.java b/SerialPort/src/main/java/top/lyoun/serialport/listener/OnSerialOpenListener.java new file mode 100644 index 0000000..b16a012 --- /dev/null +++ b/SerialPort/src/main/java/top/lyoun/serialport/listener/OnSerialOpenListener.java @@ -0,0 +1,15 @@ +package top.lyoun.serialport.listener; + +import java.io.File; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 12:34 + * Desc: 监控串口打开状态 + */ +public interface OnSerialOpenListener { + + void onSuccess(File device); + + void onFail(File device, Status status); +} diff --git a/SerialPort/src/main/java/top/lyoun/serialport/listener/Status.java b/SerialPort/src/main/java/top/lyoun/serialport/listener/Status.java new file mode 100644 index 0000000..82972ef --- /dev/null +++ b/SerialPort/src/main/java/top/lyoun/serialport/listener/Status.java @@ -0,0 +1,13 @@ +package top.lyoun.serialport.listener; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 12:35 + * Desc: Status + */ +public enum Status { + + NO_READ_WRITE_PERMISSION, //没有文件读写权限 + OPEN_FAIL, //串口打开失败 + +} diff --git a/SerialPort/src/test/java/top/lyoun/serialport/ExampleUnitTest.java b/SerialPort/src/test/java/top/lyoun/serialport/ExampleUnitTest.java new file mode 100644 index 0000000..c4dd04c --- /dev/null +++ b/SerialPort/src/test/java/top/lyoun/serialport/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package top.lyoun.serialport; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..6036604 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 30 + + defaultConfig { + applicationId "top.lyoun.androidserialhelper" + minSdk 16 + targetSdk 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + viewBinding { + enabled = true + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'com.google.android.material:material:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + + //引入本工程目录下的SerialPort模块 + implementation project(path: ':SerialPort') +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/top/lyoun/androidserialhelper/ExampleInstrumentedTest.java b/app/src/androidTest/java/top/lyoun/androidserialhelper/ExampleInstrumentedTest.java new file mode 100644 index 0000000..ef73c32 --- /dev/null +++ b/app/src/androidTest/java/top/lyoun/androidserialhelper/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package top.lyoun.androidserialhelper; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("top.lyoun.androidserialhelper", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5f5f111 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/top/lyoun/androidserialhelper/Application.java b/app/src/main/java/top/lyoun/androidserialhelper/Application.java new file mode 100644 index 0000000..69da430 --- /dev/null +++ b/app/src/main/java/top/lyoun/androidserialhelper/Application.java @@ -0,0 +1,41 @@ +package top.lyoun.androidserialhelper; + +import java.security.PublicKey; + +import top.lyoun.serialport.SerialPort; +import top.lyoun.serialport.SerialPortFinder; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 14:11 + * Desc: Application + */ +public class Application extends android.app.Application { + + public SerialPortFinder mSerialPortFinder = + new SerialPortFinder(); + + private SerialPort mSerial1; + + public SerialPort getSerial1(){ + if(mSerial1 == null){ + mSerial1 = SerialPort.newBuilder( + "/dev/ttymxc1", + 115200) + .build(); + } + + return mSerial1; + } + + //关闭串口 + public void closeSerial(){ + closeSerial1(); + } + + public void closeSerial1(){ + if(mSerial1 != null){ + mSerial1.tryClose(); + } + } +} diff --git a/app/src/main/java/top/lyoun/androidserialhelper/MainActivity.java b/app/src/main/java/top/lyoun/androidserialhelper/MainActivity.java new file mode 100644 index 0000000..7e99a82 --- /dev/null +++ b/app/src/main/java/top/lyoun/androidserialhelper/MainActivity.java @@ -0,0 +1,216 @@ +package top.lyoun.androidserialhelper; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import java.io.File; +import java.lang.reflect.Array; +import java.util.Arrays; + +import top.lyoun.androidserialhelper.databinding.ActivityMainBinding; +import top.lyoun.serialport.Hello; +import top.lyoun.serialport.SerialPort; +import top.lyoun.serialport.Welcome; +import top.lyoun.serialport.listener.OnSerialDataListener; +import top.lyoun.serialport.listener.OnSerialOpenListener; +import top.lyoun.serialport.listener.Status; + +/** + * Author: LyounJAP + * Date: 2022/1/15 0015 12:36 + * Desc: MainActivity + */ +public class MainActivity extends AppCompatActivity { + + private static final String TAG = "MainActivity"; + + private ActivityMainBinding mViewBinding; + + Hello mHello; + Welcome mWelcome; + + Application mApplication; + SerialPort mSerial1; + + StringBuffer sb = new StringBuffer(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mViewBinding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(mViewBinding.getRoot()); + initView(); + mHello = new Hello(); + mWelcome = new Welcome(); + + //获取串口实例 + mApplication = (Application) getApplication(); + mSerial1 = mApplication.getSerial1(); + + //读取设备列表 + Log.i(TAG, "allDevices: " + Arrays.toString(mApplication.mSerialPortFinder.getAllDevices())); + Log.i(TAG, "allDevicesPath: " + Arrays.toString(mApplication.mSerialPortFinder.getAllDevicesPath())); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if(mApplication != null){ + mApplication.closeSerial(); + } + } + + void initView(){ + + mViewBinding.reset.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mViewBinding.text1.setText("text1"); + mViewBinding.text2.setText("text2"); + } + }); + + mViewBinding.getvalue.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mViewBinding.text1.setText("" + mHello.stringFromJNI()); + mViewBinding.text2.setText("" + mWelcome.welcome()); + } + }); + + mViewBinding.openserialBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + openSerial(); + } + }); + + mViewBinding.closeserialBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + closeSerial1(); + mViewBinding.serialstatusTx.setText("串口已关闭!"); + } + }); + + mViewBinding.sendBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + sendText(mViewBinding.msget.getText().toString()); + } + }); + + mViewBinding.clearBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + sb = new StringBuffer(); + mViewBinding.content.setText(""); + } + }); + } + + //打开串口 + void openSerial(){ + if(mSerial1 == null){ + Log.i(TAG, "openSerial: 串口对象不能为空!"); + return; + } + + if(mSerial1.isOpen){ + Log.i(TAG, "openSerial: 检测到当前串口已开启,是否再重复开启?" ); + } + + //设置串口打开监听 + mSerial1.setOnSerialOpenListener(new OnSerialOpenListener() { + @Override + public void onSuccess(File device) { + Log.i(TAG, "onSuccess: 串口打开成功!" + device); + mViewBinding.serialstatusTx.post(new Runnable() { + @Override + public void run() { + mViewBinding.serialstatusTx.setText("串口打开成功!" + device); + } + }); + } + + @Override + public void onFail(File device, Status status) { + String tips = ""; + if(Status.NO_READ_WRITE_PERMISSION == status){ + tips = "串口文件没有读写权限!" + device; + } + if(Status.OPEN_FAIL == status){ + tips = "串口打开失败!" + device; + } + Log.e(TAG, "onFail: " + tips); + String finalTips = tips; + mViewBinding.serialstatusTx.post(new Runnable() { + @Override + public void run() { + mViewBinding.serialstatusTx.setText(finalTips); + } + }); + } + }); + + //设置串口数据读写监听 + mSerial1.setOnSerialDataListener(new OnSerialDataListener() { + @Override + public void onDataReceived(byte[] bytes) { + Log.i(TAG, "mSerial1.onDataReceived: " + Arrays.toString(bytes)); + sb.append("read: ").append(Arrays.toString(bytes)).append("\n"); + + mViewBinding.content.post(new Runnable() { + @Override + public void run() { + mViewBinding.content.setText(sb.toString() + ""); + } + }); + } + + @Override + public void onDataSend(byte[] bytes) { + Log.i(TAG, "mSerial1.onDataSend: " + Arrays.toString(bytes)); + sb.append("send: ").append(Arrays.toString(bytes)).append("\n"); + + mViewBinding.content.post(new Runnable() { + @Override + public void run() { + mViewBinding.content.setText(sb.toString() + ""); + } + }); + } + }); + + mSerial1.open(); + + Log.i(TAG, "openSerial: 串口开启" + ((mSerial1.isOpen) ? "成功!" : "失败!")); + } + + //关闭串口 + void closeSerial1(){ + if(mApplication != null){ + mApplication.closeSerial1(); + } + } + + //发送数据 + void sendText(String text){ + if(text == null || "".equals(text.trim())){ + return; + } + if(mSerial1 == null || !mSerial1.isOpen){ + Log.i(TAG, "sendText: 串口尚未打开,发送数据失败!" + mSerial1); + return; + } + + mSerial1.sendTxt(text); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..7464c06 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,97 @@ + + + + + + + +