Przeglądaj źródła

初始化仓库

wuyunfeng 2 lat temu
commit
70055721b5
78 zmienionych plików z 4389 dodań i 0 usunięć
  1. 0 0
      .gradle/7.3.3/gc.properties
  2. 1 0
      app/.gitignore
  3. 54 0
      app/build.gradle
  4. 139 0
      app/keystore_wuyuqing_3128/keytool-importkeypair
  5. 1 0
      app/keystore_wuyuqing_3128/password.txt
  6. BIN
      app/keystore_wuyuqing_3128/platform.pk8
  7. 24 0
      app/keystore_wuyuqing_3128/platform.x509.pem
  8. BIN
      app/keystore_wuyuqing_3128/rk3128.jks
  9. 7 0
      app/keystore_wuyuqing_3128/rk3128_generate_key.sh
  10. 139 0
      app/keystore_wuyuqing_3128/rk3128_signapk/keytool-importkeypair
  11. BIN
      app/keystore_wuyuqing_3128/rk3128_signapk/platform.pk8
  12. 24 0
      app/keystore_wuyuqing_3128/rk3128_signapk/platform.x509.pem
  13. BIN
      app/keystore_wuyuqing_3128/rk3128_signapk/rk3128.jks
  14. 7 0
      app/keystore_wuyuqing_3128/rk3128_signapk/rk3128_generate_key.sh
  15. BIN
      app/keystore_wuyuqing_3128/rk3128_signapk/signapk.jar
  16. BIN
      app/keystore_wuyuqing_3128/signapk.jar
  17. BIN
      app/libs/log4j-1.2.17.jar
  18. 26 0
      app/src/androidTest/java/com/wdkl/ncs/loragateway/ExampleInstrumentedTest.java
  19. 41 0
      app/src/main/AndroidManifest.xml
  20. 71 0
      app/src/main/java/com/wdkl/ncs/loragateway/Application.java
  21. 303 0
      app/src/main/java/com/wdkl/ncs/loragateway/MainActivity.java
  22. 110 0
      app/src/main/java/com/wdkl/ncs/loragateway/SerialPortActivity.java
  23. 61 0
      app/src/main/java/com/wdkl/ncs/loragateway/SettingsActivity.java
  24. 13 0
      app/src/main/java/com/wdkl/ncs/loragateway/common/Constants.java
  25. 33 0
      app/src/main/java/com/wdkl/ncs/loragateway/receiver/NetworkConnectChangedReceiver.java
  26. 29 0
      app/src/main/java/com/wdkl/ncs/loragateway/tcp/EventBusModel.java
  27. 54 0
      app/src/main/java/com/wdkl/ncs/loragateway/tcp/HexDecoder.java
  28. 26 0
      app/src/main/java/com/wdkl/ncs/loragateway/tcp/HexEncoder.java
  29. 182 0
      app/src/main/java/com/wdkl/ncs/loragateway/tcp/TcpClient.java
  30. 156 0
      app/src/main/java/com/wdkl/ncs/loragateway/tcp/TcpClientHandler.java
  31. 54 0
      app/src/main/java/com/wdkl/ncs/loragateway/utils/AlarmMessageUtil.java
  32. 117 0
      app/src/main/java/com/wdkl/ncs/loragateway/utils/AutoRebootUtil.java
  33. 368 0
      app/src/main/java/com/wdkl/ncs/loragateway/utils/ByteUtil.java
  34. 548 0
      app/src/main/java/com/wdkl/ncs/loragateway/utils/NetUtil.java
  35. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  36. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  37. 107 0
      app/src/main/res/layout/activity_main.xml
  38. 9 0
      app/src/main/res/layout/settings_activity.xml
  39. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  40. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  41. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.webp
  42. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  43. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.webp
  44. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  45. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  46. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  47. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  48. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  49. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  50. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  51. 4 0
      app/src/main/res/values-night/colors.xml
  52. 16 0
      app/src/main/res/values-night/themes.xml
  53. 108 0
      app/src/main/res/values/arrays.xml
  54. 12 0
      app/src/main/res/values/colors.xml
  55. 30 0
      app/src/main/res/values/strings.xml
  56. 16 0
      app/src/main/res/values/themes.xml
  57. 13 0
      app/src/main/res/xml/backup_rules.xml
  58. 19 0
      app/src/main/res/xml/data_extraction_rules.xml
  59. 85 0
      app/src/main/res/xml/root_preferences.xml
  60. 17 0
      app/src/test/java/com/wdkl/ncs/loragateway/ExampleUnitTest.java
  61. 18 0
      build.gradle
  62. 21 0
      gradle.properties
  63. BIN
      gradle/wrapper/gradle-wrapper.jar
  64. 6 0
      gradle/wrapper/gradle-wrapper.properties
  65. 185 0
      gradlew
  66. 89 0
      gradlew.bat
  67. 10 0
      local.properties
  68. 3 0
      serialport/.gitignore
  69. 38 0
      serialport/CMakeLists.txt
  70. 41 0
      serialport/build.gradle
  71. 4 0
      serialport/src/main/AndroidManifest.xml
  72. 247 0
      serialport/src/main/cpp/SerialPort.c
  73. 40 0
      serialport/src/main/cpp/SerialPort.h
  74. 3 0
      serialport/src/main/cpp/gen_SerialPort_h.sh
  75. 302 0
      serialport/src/main/java/android/serialport/SerialPort.java
  76. 128 0
      serialport/src/main/java/android/serialport/SerialPortFinder.java
  77. 3 0
      serialport/src/main/res/values/strings.xml
  78. 17 0
      settings.gradle

+ 0 - 0
.gradle/7.3.3/gc.properties


+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 54 - 0
app/build.gradle

@@ -0,0 +1,54 @@
+plugins {
+    id 'com.android.application'
+}
+
+android {
+    compileSdk 32
+
+    defaultConfig {
+        applicationId "com.wdkl.ncs.loragateway"
+        minSdk rootProject.ext.minSdkVersion
+        targetSdk rootProject.ext.targetSdkVersion
+        versionCode rootProject.ext.versionCode
+        versionName rootProject.ext.versionName
+
+        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
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.appcompat:appcompat:1.3.0'
+    implementation 'com.google.android.material:material:1.4.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+    implementation 'androidx.preference:preference:1.1.1'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+    implementation project(':serialport')
+    implementation 'org.apache.commons:commons-text:1.6'
+    /**
+     * netty
+     */
+    implementation 'io.netty:netty-all:4.1.42.Final'
+
+    implementation 'org.greenrobot:eventbus:3.0.0'
+
+    /**
+     * json
+     */
+    implementation 'com.alibaba:fastjson:1.2.23'
+
+    implementation files('libs/log4j-1.2.17.jar')
+}

+ 139 - 0
app/keystore_wuyuqing_3128/keytool-importkeypair

@@ -0,0 +1,139 @@
+#! /bin/bash
+#
+# This file is part of keytool-importkeypair.
+#
+# keytool-importkeypair is free software: you can redistribute it
+# and/or modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# keytool-importkeypair is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with keytool-importkeypair.  If not, see
+# <http://www.gnu.org/licenses/>.
+#
+
+DEFAULT_KEYSTORE=$HOME/.keystore
+keystore=$DEFAULT_KEYSTORE
+pk8=""
+cert=""
+alias=""
+passphrase=""
+tmpdir=""
+
+scriptname=`basename $0`
+
+usage() {
+cat << EOF
+usage: ${scriptname} [-k keystore] [-p storepass]
+-pk8 pk8 -cert cert -alias key_alias
+
+This script is used to import a key/certificate pair
+into a Java keystore.
+
+If a keystore is not specified then the key pair is imported into
+~/.keystore in the user's home directory.
+
+The passphrase can also be read from stdin.
+EOF
+}
+
+cleanup() {
+if [ ! -z "${tmpdir}" -a -d ${tmpdir} ]; then
+   rm -fr ${tmpdir}
+fi
+}
+
+while [ $# -gt 0 ]; do
+        case $1
+        in
+                -p | --passphrase | -passphrase)
+                        passphrase=$2
+                        shift 2
+        ;;
+                -h | --help)
+                        usage
+                        exit 0
+        ;;
+                -k | -keystore | --keystore)
+                        keystore=$2
+                        shift 2
+        ;;
+                -pk8 | --pk8 | -key | --key)
+                        pk8=$2
+                        shift 2
+        ;;
+                -cert | --cert | -pem | --pem)
+                        cert=$2
+                        shift 2
+        ;;
+                -a | -alias | --alias)
+                        alias=$2
+                        shift 2
+        ;;
+                *)
+                        echo "${scriptname}: Unknown option $1, exiting" 1>&2
+                        usage
+                        exit 1
+        ;;
+        esac
+done
+
+if [ -z "${pk8}" -o -z "${cert}" -o -z "${alias}" ]; then
+   echo "${scriptname}: Missing option, exiting..." 1>&2
+   usage
+   exit 1
+fi
+
+
+for f in "${pk8}" "${cert}"; do
+    if [ ! -f "$f" ]; then
+       echo "${scriptname}: Can't find file $f, exiting..." 1>&2
+       exit 1
+    fi
+done
+
+if [ ! -f "${keystore}" ]; then
+   storedir=`dirname "${keystore}"`
+   if [ ! -d "${storedir}" -o ! -w "${storedir}" ]; then
+      echo "${scriptname}: Can't access ${storedir}, exiting..." 1>&2
+      exit 1
+   fi
+fi
+
+# Create temp directory ofr key and pkcs12 bundle
+tmpdir=`mktemp -q -d "/tmp/${scriptname}.XXXX"`
+
+if [ $? -ne 0 ]; then
+   echo "${scriptname}: Can't create temp directory, exiting..." 1>&2
+   exit 1
+fi
+
+key="${tmpdir}/key"
+p12="${tmpdir}/p12"
+
+if [ -z "${passphrase}" ]; then
+   # Request a passphrase
+  read -p "Enter a passphrase: " -s passphrase
+  echo ""
+fi
+
+# Convert PK8 to PEM KEY
+openssl pkcs8 -inform DER -nocrypt -in "${pk8}" -out "${key}"
+
+# Bundle CERT and KEY
+openssl pkcs12 -export -in "${cert}" -inkey "${key}" -out "${p12}" -password pass:"${passphrase}" -name "${alias}"
+
+# Print cert
+echo -n "Importing \"${alias}\" with "
+openssl x509 -noout -fingerprint -in "${cert}"
+
+# Import P12 in Keystore
+keytool -importkeystore -deststorepass "${passphrase}" -destkeystore "${keystore}" -srckeystore "${p12}" -srcstoretype PKCS12 -srcstorepass "${passphrase}" 
+
+# Cleanup
+cleanup

+ 1 - 0
app/keystore_wuyuqing_3128/password.txt

@@ -0,0 +1 @@
+111111

BIN
app/keystore_wuyuqing_3128/platform.pk8


+ 24 - 0
app/keystore_wuyuqing_3128/platform.x509.pem

@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID+zCCAuOgAwIBAgIJAP8GQTI8+VUSMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g
+VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE
+AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0xNDEyMjMwNjQzNDFaFw00MjA1MTAwNjQzNDFaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G
+A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBAKHY8Fl4XZzpJvgHoezYjhmKPvdq9DGYVc/X
+9NQO5oUlYIA/Ci5rzvljz13KbNve/KxuEu9HN8SzsLg9EBhghOko8JxEg7I8W6uP
+VOoRngNCMvXzjf6av77vxPqphlgq++Y4MIC+fiOxLd+gpYq0p6W7RWxEgrzLHnWi
+CX0dRmWDs+ey2t4f1WKzGoRQQS0Tn21CViThrVEe+zNwANnhErUcvoQB2m4/PQot
+uij7LZNccHJvUOUf5/4wIZd8JOgO3VLwzFO/HhrqUjafCvkpKTjW8oQmHLUz5m40
+ljETGEjqQ6AuAwmaeFT+Bwj1DUaYg+m7AzalJ2aAtHVX0FftRUkCAQOjUDBOMB0G
+A1UdDgQWBBQi+LgbyFfWSoWCbQ+NVDF4ZKTPCjAfBgNVHSMEGDAWgBQi+LgbyFfW
+SoWCbQ+NVDF4ZKTPCjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBH
+1kIQlSBjXRMuQdaDLytr8ZaJXIN1HApg2QA8azYQXOS/B16gwm6tBfh1dL86LL/7
+w09oM9LZv8WwtQyFNjM97vvQvkaOGW/ubhrKOk3+8HTHkEx4n7H6tYGOLdpmWepD
+fBSEFuLwq6yqG6wZFdd7IKO+sPlCxqUpqg40YAb4WOwzDbiuJnswDftP3wIaaJPh
+li6OIjRKyd3Sgw1MtffHOy+WSwqHLkGNgH6GAgvZlvhPA/yim+rjnE9oKV5G6Pyg
+QK7kJJjS/LdeqxE7M7pNRYPhcLT7qhE7MiuBuyqwAMTTBoU8u3lTdOZwNErbRT5t
+SXkgVMffkfN7wBNqpSSY
+-----END CERTIFICATE-----

BIN
app/keystore_wuyuqing_3128/rk3128.jks


+ 7 - 0
app/keystore_wuyuqing_3128/rk3128_generate_key.sh

@@ -0,0 +1,7 @@
+#!/bin/sh
+# 转换系统签名命令
+./keytool-importkeypair -k rk3128.jks -p 111111 -pk8 platform.pk8 -cert platform.x509.pem -alias rk3128
+# rk3128.jks : 签名文件
+# 111111 : 签名文件密码
+# platform.pk8、platform.x509.pem : 系统签名文件
+# rk3128 : 签名文件别名

+ 139 - 0
app/keystore_wuyuqing_3128/rk3128_signapk/keytool-importkeypair

@@ -0,0 +1,139 @@
+#! /bin/bash
+#
+# This file is part of keytool-importkeypair.
+#
+# keytool-importkeypair is free software: you can redistribute it
+# and/or modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# keytool-importkeypair is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with keytool-importkeypair.  If not, see
+# <http://www.gnu.org/licenses/>.
+#
+
+DEFAULT_KEYSTORE=$HOME/.keystore
+keystore=$DEFAULT_KEYSTORE
+pk8=""
+cert=""
+alias=""
+passphrase=""
+tmpdir=""
+
+scriptname=`basename $0`
+
+usage() {
+cat << EOF
+usage: ${scriptname} [-k keystore] [-p storepass]
+-pk8 pk8 -cert cert -alias key_alias
+
+This script is used to import a key/certificate pair
+into a Java keystore.
+
+If a keystore is not specified then the key pair is imported into
+~/.keystore in the user's home directory.
+
+The passphrase can also be read from stdin.
+EOF
+}
+
+cleanup() {
+if [ ! -z "${tmpdir}" -a -d ${tmpdir} ]; then
+   rm -fr ${tmpdir}
+fi
+}
+
+while [ $# -gt 0 ]; do
+        case $1
+        in
+                -p | --passphrase | -passphrase)
+                        passphrase=$2
+                        shift 2
+        ;;
+                -h | --help)
+                        usage
+                        exit 0
+        ;;
+                -k | -keystore | --keystore)
+                        keystore=$2
+                        shift 2
+        ;;
+                -pk8 | --pk8 | -key | --key)
+                        pk8=$2
+                        shift 2
+        ;;
+                -cert | --cert | -pem | --pem)
+                        cert=$2
+                        shift 2
+        ;;
+                -a | -alias | --alias)
+                        alias=$2
+                        shift 2
+        ;;
+                *)
+                        echo "${scriptname}: Unknown option $1, exiting" 1>&2
+                        usage
+                        exit 1
+        ;;
+        esac
+done
+
+if [ -z "${pk8}" -o -z "${cert}" -o -z "${alias}" ]; then
+   echo "${scriptname}: Missing option, exiting..." 1>&2
+   usage
+   exit 1
+fi
+
+
+for f in "${pk8}" "${cert}"; do
+    if [ ! -f "$f" ]; then
+       echo "${scriptname}: Can't find file $f, exiting..." 1>&2
+       exit 1
+    fi
+done
+
+if [ ! -f "${keystore}" ]; then
+   storedir=`dirname "${keystore}"`
+   if [ ! -d "${storedir}" -o ! -w "${storedir}" ]; then
+      echo "${scriptname}: Can't access ${storedir}, exiting..." 1>&2
+      exit 1
+   fi
+fi
+
+# Create temp directory ofr key and pkcs12 bundle
+tmpdir=`mktemp -q -d "/tmp/${scriptname}.XXXX"`
+
+if [ $? -ne 0 ]; then
+   echo "${scriptname}: Can't create temp directory, exiting..." 1>&2
+   exit 1
+fi
+
+key="${tmpdir}/key"
+p12="${tmpdir}/p12"
+
+if [ -z "${passphrase}" ]; then
+   # Request a passphrase
+  read -p "Enter a passphrase: " -s passphrase
+  echo ""
+fi
+
+# Convert PK8 to PEM KEY
+openssl pkcs8 -inform DER -nocrypt -in "${pk8}" -out "${key}"
+
+# Bundle CERT and KEY
+openssl pkcs12 -export -in "${cert}" -inkey "${key}" -out "${p12}" -password pass:"${passphrase}" -name "${alias}"
+
+# Print cert
+echo -n "Importing \"${alias}\" with "
+openssl x509 -noout -fingerprint -in "${cert}"
+
+# Import P12 in Keystore
+keytool -importkeystore -deststorepass "${passphrase}" -destkeystore "${keystore}" -srckeystore "${p12}" -srcstoretype PKCS12 -srcstorepass "${passphrase}" 
+
+# Cleanup
+cleanup

BIN
app/keystore_wuyuqing_3128/rk3128_signapk/platform.pk8


+ 24 - 0
app/keystore_wuyuqing_3128/rk3128_signapk/platform.x509.pem

@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID+zCCAuOgAwIBAgIJAP8GQTI8+VUSMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g
+VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE
+AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0xNDEyMjMwNjQzNDFaFw00MjA1MTAwNjQzNDFaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G
+A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBAKHY8Fl4XZzpJvgHoezYjhmKPvdq9DGYVc/X
+9NQO5oUlYIA/Ci5rzvljz13KbNve/KxuEu9HN8SzsLg9EBhghOko8JxEg7I8W6uP
+VOoRngNCMvXzjf6av77vxPqphlgq++Y4MIC+fiOxLd+gpYq0p6W7RWxEgrzLHnWi
+CX0dRmWDs+ey2t4f1WKzGoRQQS0Tn21CViThrVEe+zNwANnhErUcvoQB2m4/PQot
+uij7LZNccHJvUOUf5/4wIZd8JOgO3VLwzFO/HhrqUjafCvkpKTjW8oQmHLUz5m40
+ljETGEjqQ6AuAwmaeFT+Bwj1DUaYg+m7AzalJ2aAtHVX0FftRUkCAQOjUDBOMB0G
+A1UdDgQWBBQi+LgbyFfWSoWCbQ+NVDF4ZKTPCjAfBgNVHSMEGDAWgBQi+LgbyFfW
+SoWCbQ+NVDF4ZKTPCjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBH
+1kIQlSBjXRMuQdaDLytr8ZaJXIN1HApg2QA8azYQXOS/B16gwm6tBfh1dL86LL/7
+w09oM9LZv8WwtQyFNjM97vvQvkaOGW/ubhrKOk3+8HTHkEx4n7H6tYGOLdpmWepD
+fBSEFuLwq6yqG6wZFdd7IKO+sPlCxqUpqg40YAb4WOwzDbiuJnswDftP3wIaaJPh
+li6OIjRKyd3Sgw1MtffHOy+WSwqHLkGNgH6GAgvZlvhPA/yim+rjnE9oKV5G6Pyg
+QK7kJJjS/LdeqxE7M7pNRYPhcLT7qhE7MiuBuyqwAMTTBoU8u3lTdOZwNErbRT5t
+SXkgVMffkfN7wBNqpSSY
+-----END CERTIFICATE-----

BIN
app/keystore_wuyuqing_3128/rk3128_signapk/rk3128.jks


+ 7 - 0
app/keystore_wuyuqing_3128/rk3128_signapk/rk3128_generate_key.sh

@@ -0,0 +1,7 @@
+#!/bin/sh
+# 转换系统签名命令
+./keytool-importkeypair -k rk3128.jks -p 111111 -pk8 platform.pk8 -cert platform.x509.pem -alias rk3128
+# rk3128.jks : 签名文件
+# 111111 : 签名文件密码
+# platform.pk8、platform.x509.pem : 系统签名文件
+# rk3128 : 签名文件别名

BIN
app/keystore_wuyuqing_3128/rk3128_signapk/signapk.jar


BIN
app/keystore_wuyuqing_3128/signapk.jar


BIN
app/libs/log4j-1.2.17.jar


+ 26 - 0
app/src/androidTest/java/com/wdkl/ncs/loragateway/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.wdkl.ncs.loragateway;
+
+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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.wdkl.ncs.loragateway", appContext.getPackageName());
+    }
+}

+ 41 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.wdkl.ncs.loragateway">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <!-- 允许程序改变网络链接状态 -->
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 允许程序访问访问WIFI网络状态信息 -->
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+
+    <application
+        android:name=".Application"
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.LoraGateway"
+        tools:targetApi="31">
+        <activity
+            android:name=".SettingsActivity"
+            android:exported="false"
+            android:label="@string/title_activity_settings" />
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/title_activity_main"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 71 - 0
app/src/main/java/com/wdkl/ncs/loragateway/Application.java

@@ -0,0 +1,71 @@
+package com.wdkl.ncs.loragateway;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.serialport.SerialPort;
+import android.serialport.SerialPortFinder;
+import android.util.Log;
+
+import androidx.preference.PreferenceManager;
+
+import java.io.IOException;
+import java.security.InvalidParameterException;
+
+public class Application extends android.app.Application {
+
+    public SerialPortFinder mSerialPortFinder = new SerialPortFinder();
+    private SerialPort mSerialPort = null;
+
+    private static Context context;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        context = getApplicationContext();
+    }
+
+    /**
+     * 获取全局上下文
+     */
+    public static Context getContext() {
+        return context;
+    }
+
+    public SerialPort getSerialPort()
+            throws SecurityException, IOException, InvalidParameterException {
+        if (mSerialPort == null) {
+            /* Read serial port parameters */
+            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+            Log.i("Application", "getSerialPort: "+sp.getString("serialDeivce",""));
+            String path = sp.getString("serialDeivce",""); //sp.getString("DEVICE", "");
+            int baudrate = Integer.decode(sp.getString("baudRate", "115200")); //Integer.decode(sp.getString("BAUDRATE", "-1"));
+            int parity = Integer.decode(sp.getString("parity", "0"));
+            int dataBits = Integer.decode(sp.getString("dataBits", "8"));
+            int stopBits = Integer.decode(sp.getString("stopBits", "1"));
+            /* Check parameters */
+            if ((path.length() == 0) || (baudrate == -1)) {
+                throw new InvalidParameterException();
+            }
+
+            /* Open the serial port */
+            //mSerialPort = new SerialPort(new File(path), baudrate, 0);
+
+            SerialPort serialPort = SerialPort //
+                    .newBuilder(path, baudrate) // 串口地址地址,波特率
+                    .parity(parity) // 校验位;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)
+                    .dataBits(dataBits) // 数据位,默认8;可选值为5~8
+                    .stopBits(stopBits) // 停止位,默认1;1:1位停止位;2:2位停止位
+                    .build();
+
+            mSerialPort = serialPort;
+        }
+        return mSerialPort;
+    }
+
+    public void closeSerialPort() {
+        if (mSerialPort != null) {
+            mSerialPort.close();
+            mSerialPort = null;
+        }
+    }
+}

+ 303 - 0
app/src/main/java/com/wdkl/ncs/loragateway/MainActivity.java

@@ -0,0 +1,303 @@
+package com.wdkl.ncs.loragateway;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceManager;
+
+import com.wdkl.ncs.loragateway.common.Constants;
+import com.wdkl.ncs.loragateway.receiver.NetworkConnectChangedReceiver;
+import com.wdkl.ncs.loragateway.tcp.EventBusModel;
+import com.wdkl.ncs.loragateway.tcp.TcpClient;
+import com.wdkl.ncs.loragateway.utils.AlarmMessageUtil;
+import com.wdkl.ncs.loragateway.utils.AutoRebootUtil;
+import com.wdkl.ncs.loragateway.utils.NetUtil;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+import io.netty.util.internal.StringUtil;
+
+public class MainActivity extends SerialPortActivity  {
+    String TAG = "MainActivity";
+    EditText mReception;
+    ConnectivityManager connectivityManager;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        //注册EventBus
+        EventBus.getDefault().register(this);
+        mReception = (EditText) findViewById(R.id.EditTextReception);
+        final Button buttonSetup = (Button) findViewById(R.id.ButtonSetup);
+        buttonSetup.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+//                openNetworkDebug();
+                startActivity(new Intent(MainActivity.this, SettingsActivity.class));
+            }
+        });
+        final Button rebootAppButton = (Button) findViewById(R.id.RebootApp);
+        rebootAppButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                AutoRebootUtil.restartApp();
+            }
+        });
+        this.init();
+//        this.initReceive();
+        NetworkRequest.Builder builder = new NetworkRequest.Builder();
+        NetworkRequest request = builder.build();
+        connectivityManager =(ConnectivityManager) getApplication().getApplicationContext().getSystemService(CONNECTIVITY_SERVICE);
+        connectivityManager.registerNetworkCallback(request,callback);
+    }
+
+    private void init() {
+        TextView settingInfo = (TextView) findViewById(R.id.setttingInfo);
+        StringBuilder sb = new StringBuilder();
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+        Log.i("Application", "getSerialPort: " + sp.getString("serialDeivce", ""));
+        String path = sp.getString("serialDeivce", ""); //sp.getString("DEVICE", "");
+        int baudrate = Integer.decode(sp.getString("baudRate", "115200")); //Integer.decode(sp.getString("BAUDRATE", "-1"));
+        int parity = Integer.decode(sp.getString("parity", "0"));
+        int dataBits = Integer.decode(sp.getString("dataBits", "8"));
+        int stopBits = Integer.decode(sp.getString("stopBits", "1"));
+        String tcpServer = sp.getString("tcpServer", "");
+        Integer tcpServerPort = Integer.decode(sp.getString("tcpServerPort", "5085"));
+        String macAddress = NetUtil.getInstance().getMacAddress();
+        String localIP = NetUtil.getInstance().getLocalIP();
+        sb.append("串口号:" + path + "\n");
+        sb.append("波特率:" + baudrate + "\n");
+        sb.append("校验位:" + parity + "\n");
+        sb.append("数据位:" + dataBits + "\n");
+        sb.append("停止位:" + stopBits + "\n");
+        sb.append("服务器地址:" + tcpServer + "\n");
+        sb.append("服务器端口:" + tcpServerPort + "\n");
+        sb.append("本机MAC地址:" + macAddress + "\n");
+        sb.append("本机IP地址:" + localIP + "\n");
+        settingInfo.setText(sb.toString());
+        Log.e(TAG, "init: "+localIP);
+        TextView tcpstatus = (TextView) findViewById(R.id.serialStatus);
+        if (Constants.SERIAL_PORT_STATUS) {
+            tcpstatus.setText("正常");
+            tcpstatus.setTextColor(getResources().getColor(R.color.green));
+        } else {
+            tcpstatus.setText(Constants.SERIAL_PORT_FAIL_RESON);
+            tcpstatus.setTextColor(getResources().getColor(R.color.red));
+        }
+        //初始化tcp
+        if (Constants.NET_WORK_AVAILABLE) {
+            if(!TcpClient.getInstance().isRunning()) {
+                TcpClient.getInstance().init(tcpServer, tcpServerPort, 60);
+            }else{
+                TcpClient.getInstance().doConnect();
+            }
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        EventBus.getDefault().unregister(this);
+        connectivityManager.unregisterNetworkCallback(callback);
+//        unregisterReceiver(networkConnectChangedReceiver);
+    }
+
+    @Override
+    protected void onDataReceived(byte[] buffer, int size) {
+        runOnUiThread(new Runnable() {
+            public void run() {
+                if (mReception != null) {
+                    byte[] bytes = Arrays.copyOfRange(buffer, 0, size);
+                    String receiveData = bytesToHex(bytes);
+                    String receiveString = new String(bytes, 0, size);
+                    Log.e("Application", "receiveData: " + receiveData);
+                    if (receiveString.contains("\r") || receiveString.contains("\n")) {
+                        Log.e("Application", "有回车键: ");
+                    }
+                    if (receiveData.startsWith("fe")) {
+                        byte[] cardCode = ArrayUtils.subarray(bytes, 5, 9);
+                        //小端字节顺序,需要将数组反转
+                        ArrayUtils.reverse(cardCode);
+                        String hex = bytesToHex(cardCode);
+                        String s = String.format("%010d", Integer.valueOf(hex, 16));
+                        Log.e("Application", "s: " + s);
+                        Log.e("Application", "receiveString: " + receiveString);
+                        Log.e("Application", "receiveData: " + receiveData);
+                    }
+
+                    if (receiveData.startsWith("fd") && receiveData.endsWith("df")) {
+                        String mac = receiveData.substring(2,  receiveData.indexOf("df"));
+                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                        mReception.append(sdf.format(new Date()) + " 收到数据:" + mac + "\n");
+                        //发送tcp消息
+                        if (TcpClient.getInstance().isRunning()) {
+                            TcpClient.getInstance().sendMsg(AlarmMessageUtil.getAlarmMessage(mac));
+                        }
+
+                    }
+
+                }
+            }
+        });
+    }
+
+    private void openNetworkDebug() {
+        try {
+            Process p = Runtime.getRuntime().exec("su");
+            OutputStream op = p.getOutputStream();
+            DataOutputStream os = new DataOutputStream(op);
+//            os.writeBytes("/system/bin/sh\n");
+            os.writeBytes("setprop service.adb.tcp.port 5555\n");
+            os.writeBytes("stop adbd\n");
+            os.writeBytes("start adbd\n");
+            os.writeBytes("exit\n");
+            os.flush();
+            os.close();
+            op.close();
+            Log.i("openNetworkDebug", "打开调试:---- ");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private String bytesToHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
+
+    }
+
+    /**
+     * NFC 读取卡号
+     *
+     * @param bytes
+     * @return
+     */
+    private String bytesToKeyCode(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%c", b));
+        }
+        return sb.toString();
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    public void onMoonEvent(EventBusModel eventBusModel) {
+        switch (eventBusModel.getMsgType()) {
+            case Constants.EVENT_TCP_STATE: {
+                updateTcpStatus();
+                break;
+            }
+            case Constants.NET_WORK_STATUS_CHANGE: {
+//
+                if ((int) eventBusModel.getMsg() > -1) {
+
+                    this.init();
+                }
+                break;
+            }
+            case  Constants.NET_WORK_AVAILABLE_CHANGE:{
+                Log.e("TAG", "onMoonEvent: "+(boolean) eventBusModel.getMsg());
+                if((boolean) eventBusModel.getMsg()){
+                    this.init();
+                }else{
+                    if (TcpClient.getInstance().isRunning()){
+                        TcpClient.getInstance().closeClient();
+                    }
+                }
+                break;
+            }
+        }
+    }
+
+    NetworkConnectChangedReceiver networkConnectChangedReceiver;
+
+    private void initReceive() {
+        //网络状态广播
+        networkConnectChangedReceiver = new NetworkConnectChangedReceiver();
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
+        registerReceiver(networkConnectChangedReceiver, intentFilter);
+    }
+
+
+    private void updateTcpStatus() {
+        TextView tcpstatus = (TextView) findViewById(R.id.tcpStatus);
+        if (Constants.TCP_CONNCETED) {
+            tcpstatus.setText("正常");
+            tcpstatus.setTextColor(getResources().getColor(R.color.green));
+        } else {
+            tcpstatus.setText("未连接");
+            tcpstatus.setTextColor(getResources().getColor(R.color.red));
+        }
+
+    }
+
+    private ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback(){
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            super.onAvailable(network);
+            Log.e(TAG, "onAvailable: ");
+            Constants.NET_WORK_AVAILABLE=true;
+            EventBusModel eventBusModel = new EventBusModel(Constants.NET_WORK_AVAILABLE_CHANGE,Constants.NET_WORK_AVAILABLE);
+            EventBus.getDefault().post(eventBusModel);
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            Log.e(TAG, "onLost: "+NetUtil.getInstance().getNetAvailable());
+            super.onLost(network);
+            if(NetUtil.getInstance().getNetAvailable()){
+                Constants.NET_WORK_AVAILABLE=true;
+            }else{
+                Constants.NET_WORK_AVAILABLE=false;
+            }
+            EventBusModel eventBusModel = new EventBusModel(Constants.NET_WORK_AVAILABLE_CHANGE,Constants.NET_WORK_AVAILABLE);
+            EventBus.getDefault().post(eventBusModel);
+        }
+
+//        @Override
+//        public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+//            super.onCapabilitiesChanged(network, networkCapabilities);
+//            Log.e(TAG, "onCapabilitiesChanged: "+networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+////            if(networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)){
+////                Constants.NET_WORK_AVAILABLE=true;
+////                EventBusModel eventBusModel = new EventBusModel(Constants.NET_WORK_AVAILABLE_CHANGE,Constants.NET_WORK_AVAILABLE);
+////                EventBus.getDefault().post(eventBusModel);
+////            }
+//        }
+    };
+
+}

+ 110 - 0
app/src/main/java/com/wdkl/ncs/loragateway/SerialPortActivity.java

@@ -0,0 +1,110 @@
+package com.wdkl.ncs.loragateway;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.serialport.SerialPort;
+
+import com.wdkl.ncs.loragateway.common.Constants;
+import com.wdkl.ncs.loragateway.tcp.EventBusModel;
+
+import org.greenrobot.eventbus.EventBus;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidParameterException;
+
+public abstract class SerialPortActivity extends Activity {
+
+    protected Application mApplication;
+    protected SerialPort mSerialPort;
+    protected OutputStream mOutputStream;
+    private InputStream mInputStream;
+    private ReadThread mReadThread;
+
+    private class ReadThread extends Thread {
+
+        @Override
+        public void run() {
+            super.run();
+            while (!isInterrupted()) {
+                try {
+                    Thread.sleep(200);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                int size;
+                try {
+                    byte[] buffer = new byte[64];
+                    if (mInputStream == null) return;
+                    if(mInputStream.available()>0) {
+                        size = mInputStream.read(buffer);
+                        if (size > 0) {
+                            onDataReceived(buffer, size);
+                        }
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    return;
+                }
+            }
+        }
+    }
+
+    private void DisplayError(int resourceId) {
+        AlertDialog.Builder b = new AlertDialog.Builder(this);
+        b.setTitle("Error");
+        b.setMessage(resourceId);
+        b.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
+//                startActivity(new Intent(SerialPortActivity.this, SettingsActivity.class));
+//                SerialPortActivity.this.finish();
+            }
+        });
+        b.show();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mApplication = (Application) getApplication();
+        try {
+            mSerialPort = mApplication.getSerialPort();
+            mOutputStream = mSerialPort.getOutputStream();
+            mInputStream = mSerialPort.getInputStream();
+
+			/* Create a receiving thread */
+            mReadThread = new ReadThread();
+            mReadThread.start();
+            Constants.SERIAL_PORT_STATUS=true;
+
+
+        } catch (SecurityException e) {
+            Constants.SERIAL_PORT_STATUS=false;
+            Constants.SERIAL_PORT_FAIL_RESON=getResources().getString(R.string.error_security);
+            DisplayError(R.string.error_security);
+        } catch (IOException e) {
+            Constants.SERIAL_PORT_STATUS=false;
+            Constants.SERIAL_PORT_FAIL_RESON=getResources().getString(R.string.error_unknown);
+            DisplayError(R.string.error_unknown);
+        } catch (InvalidParameterException e) {
+            Constants.SERIAL_PORT_STATUS=false;
+            Constants.SERIAL_PORT_FAIL_RESON=getResources().getString(R.string.error_configuration);
+            DisplayError(R.string.error_configuration);
+        }
+    }
+
+    protected abstract void onDataReceived(final byte[] buffer, final int size);
+
+    @Override
+    protected void onDestroy() {
+        if (mReadThread != null) mReadThread.interrupt();
+        mApplication.closeSerialPort();
+        mSerialPort = null;
+        super.onDestroy();
+    }
+}

+ 61 - 0
app/src/main/java/com/wdkl/ncs/loragateway/SettingsActivity.java

@@ -0,0 +1,61 @@
+package com.wdkl.ncs.loragateway;
+
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.serialport.SerialPortFinder;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceFragmentCompat;
+
+public class SettingsActivity extends AppCompatActivity {
+    private static Application mApplication;
+    private static SerialPortFinder mSerialPortFinder;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        mApplication = (Application) getApplication();
+        mSerialPortFinder = mApplication.mSerialPortFinder;
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.settings_activity);
+        if (savedInstanceState == null) {
+            getSupportFragmentManager()
+                    .beginTransaction()
+                    .replace(R.id.settings, new SettingsFragment())
+                    .commit();
+        }
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            this.finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+
+    public static class SettingsFragment extends PreferenceFragmentCompat {
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            setPreferencesFromResource(R.xml.root_preferences, rootKey);
+
+            final androidx.preference.ListPreference devices = (androidx.preference.ListPreference) findPreference("serialDeivce");
+            String[] entries = mSerialPortFinder.getAllDevices();
+            String[] entryValues = mSerialPortFinder.getAllDevicesPath();
+            devices.setEntries(entries);
+            devices.setEntryValues(entryValues);
+//            devices.setSummary(devices.getValue());
+        }
+
+
+    }
+}

+ 13 - 0
app/src/main/java/com/wdkl/ncs/loragateway/common/Constants.java

@@ -0,0 +1,13 @@
+package com.wdkl.ncs.loragateway.common;
+
+public class Constants {
+
+   public  static final int  EVENT_TCP_STATE = 0x01;
+   public  static final int  EVENT_SERIAL_PORT_STATE = 0x02;
+   public static final int NET_WORK_STATUS_CHANGE = 0x03;
+   public static final int NET_WORK_AVAILABLE_CHANGE = 0x04 ;
+   public static  boolean NET_WORK_AVAILABLE = false;
+   public static Boolean TCP_CONNCETED = false;
+   public static Boolean SERIAL_PORT_STATUS = false;
+   public static String SERIAL_PORT_FAIL_RESON="";
+}

+ 33 - 0
app/src/main/java/com/wdkl/ncs/loragateway/receiver/NetworkConnectChangedReceiver.java

@@ -0,0 +1,33 @@
+package com.wdkl.ncs.loragateway.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.util.Log;
+
+
+import com.wdkl.ncs.loragateway.common.Constants;
+import com.wdkl.ncs.loragateway.tcp.EventBusModel;
+import com.wdkl.ncs.loragateway.utils.NetUtil;
+
+import org.greenrobot.eventbus.EventBus;
+
+public class NetworkConnectChangedReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // TODO Auto-generated method stub
+        // 如果相等的话就说明网络状态发生了变化
+        if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+            int netWorkState = NetUtil.getNetWorkState(context);
+            Log.e("NetworkConnect", "onReceive: "+netWorkState );
+            // 接口回调传过去状态的类型
+            EventBusModel eventBusModel = new EventBusModel(Constants.NET_WORK_STATUS_CHANGE,netWorkState);
+            EventBus.getDefault().post(eventBusModel);
+        }else if(intent.getAction().equals("com.wdkl.loragateway.net.avaiable")){
+            Log.e("NetworkConnect", "onReceive: 收到广播 com.wdkl.loragateway.net.avaiable");
+            EventBusModel eventBusModel = new EventBusModel(Constants.NET_WORK_AVAILABLE_CHANGE,Constants.NET_WORK_AVAILABLE);
+            EventBus.getDefault().post(eventBusModel);
+        }
+    }
+}

+ 29 - 0
app/src/main/java/com/wdkl/ncs/loragateway/tcp/EventBusModel.java

@@ -0,0 +1,29 @@
+package com.wdkl.ncs.loragateway.tcp;
+
+public class EventBusModel {
+
+    private Integer msgType;
+
+    private Object msg;
+
+    public EventBusModel(Integer msgType, Object msg) {
+        this.msgType = msgType;
+        this.msg = msg;
+    }
+
+    public Integer getMsgType() {
+        return msgType;
+    }
+
+    public void setMsgType(Integer msgType) {
+        this.msgType = msgType;
+    }
+
+    public Object getMsg() {
+        return msg;
+    }
+
+    public void setMsg(Object msg) {
+        this.msg = msg;
+    }
+}

+ 54 - 0
app/src/main/java/com/wdkl/ncs/loragateway/tcp/HexDecoder.java

@@ -0,0 +1,54 @@
+package com.wdkl.ncs.loragateway.tcp;
+
+import java.util.List;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+
+public class HexDecoder extends ByteToMessageDecoder {
+
+    @Override
+    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
+        //创建字节数组,buffer.readableBytes可读字节长度
+        byte[] b = new byte[byteBuf.readableBytes()];
+        //复制内容到字节数组b
+        byteBuf.readBytes(b);
+        //字节数组转字符串
+        String str = new String(b);
+
+        // System.out.println(str);
+
+        list.add(bytesToHexString(b));
+    }
+
+    public String bytesToHexString(byte[] bArray) {
+        StringBuffer sb = new StringBuffer(bArray.length);
+        String sTemp;
+        for (int i = 0; i < bArray.length; i++) {
+            sTemp = Integer.toHexString(0xFF & bArray[i]);
+            if (sTemp.length() < 2)
+                sb.append(0);
+            sb.append(sTemp.toUpperCase());
+        }
+        return sb.toString();
+    }
+
+    public static String toHexString1(byte[] b) {
+        StringBuffer buffer = new StringBuffer();
+        for (int i = 0; i < b.length; ++i) {
+            buffer.append(toHexString1(b[i]));
+        }
+        return buffer.toString();
+    }
+
+    public static String toHexString1(byte b) {
+        String s = Integer.toHexString(b & 0xFF);
+        if (s.length() == 1) {
+            return "0" + s;
+        } else {
+            return s;
+        }
+    }
+
+}

+ 26 - 0
app/src/main/java/com/wdkl/ncs/loragateway/tcp/HexEncoder.java

@@ -0,0 +1,26 @@
+package com.wdkl.ncs.loragateway.tcp;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+
+public class HexEncoder extends MessageToByteEncoder<String> {
+
+
+    @Override
+    protected void encode(ChannelHandlerContext channelHandlerContext, String s, ByteBuf byteBuf) throws Exception {
+        //将16进制字符串转为数组
+        byteBuf.writeBytes(hexString2Bytes(s));
+    }
+
+    public static byte[] hexString2Bytes(String src) {
+        int l = src.length() / 2;
+        byte[] ret = new byte[l];
+        for (int i = 0; i < l; i++) {
+            ret[i] = Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
+        }
+        return ret;
+
+    }
+
+}

+ 182 - 0
app/src/main/java/com/wdkl/ncs/loragateway/tcp/TcpClient.java

@@ -0,0 +1,182 @@
+package com.wdkl.ncs.loragateway.tcp;
+
+import android.util.Log;
+
+import java.util.concurrent.TimeUnit;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import io.netty.handler.codec.LengthFieldPrepender;
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.codec.string.StringEncoder;
+import io.netty.handler.timeout.IdleStateHandler;
+import io.netty.util.CharsetUtil;
+
+public class TcpClient {
+    private String TAG = TcpClient.class.getSimpleName();
+
+    private NioEventLoopGroup workGroup;
+    public Channel channel;
+    private Bootstrap bootstrap;
+    //客户端是否启动
+    private volatile boolean isRunning = false;
+    //数据处理
+    private  TcpClientHandler tcpClientHandler = new TcpClientHandler();
+    //重试间隔
+    private Integer retrySeconds = 5;
+    //重试计数
+    private Integer retryTimes = 1;
+
+
+    //单例
+    private static class TcpClientHolder {
+        private static TcpClient instance = new TcpClient();
+    }
+
+    public static TcpClient getInstance() {
+        return TcpClientHolder.instance;
+    }
+
+    //初始化Netty Tcp Client 并连接
+    public void init(String serverIP, Integer serverPort, Integer heartBeatSeconds) {
+        final Integer hbSeconds = heartBeatSeconds;
+        workGroup = new NioEventLoopGroup();
+        bootstrap = new Bootstrap();
+        bootstrap.group(workGroup)
+                .channel(NioSocketChannel.class)
+                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15 * 1000)
+                .option(ChannelOption.SO_KEEPALIVE, true)
+                .handler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    protected void initChannel(SocketChannel socketChannel) throws Exception {
+
+                        socketChannel.pipeline().addLast(new HexDecoder());
+                        socketChannel.pipeline().addLast(new HexEncoder());
+                        socketChannel.pipeline().addLast(new IdleStateHandler(hbSeconds*2, hbSeconds, 0, TimeUnit.SECONDS));//
+                        socketChannel.pipeline().addLast(tcpClientHandler);
+                    }
+                }).remoteAddress(serverIP, serverPort);
+        doConnect();
+        isRunning=true;
+    }
+
+    //独立连接方法,用于重新连接
+    public synchronized void doConnect() {
+        if (channel != null && channel.isActive()) {
+            System.out.println("TcpClient connecting");
+            return;
+        }
+
+        System.out.println("TcpClient connect start");
+        ChannelFuture future = bootstrap.connect().addListener(new ChannelFutureListener() {
+            @Override
+            public void operationComplete(final ChannelFuture channelFuture) throws Exception {
+                if (channelFuture.isSuccess()) {
+                    channel = channelFuture.channel();
+                    retryTimes = 1;
+                    System.out.println("TcpClient connect success");
+                } else {
+                    //连接失败时的处理
+                    System.out.println("TcpClient connect retry : " + retryTimes);
+                    channelFuture.channel().eventLoop().schedule(new Runnable() {
+                        @Override
+                        public void run() {
+                            retryTimes++;
+                            if (retryTimes > 20) { //重试30次还没连成功,等10分钟后再试
+//                                System.out.println("TcpClient 重试" + (retryTimes - 1) + "次,结束");
+//                                channel.close();
+//                                workGroup.shutdownGracefully();
+//                                //todo: 从API获取新的serverIP和serverPort,全新连接
+//                                TcpClient.getInstance().init(Constant.TCP_SERVER_URL, Constant.TCP_PORT, Constant.TCP_HEART_BEAT);
+                                retryTimes=1;
+                                channelFuture.channel().eventLoop().schedule(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        System.out.println("TcpClientHandler 重新连接,第" + retryTimes + "次");
+                                        doConnect();
+                                    }
+                                }, 60*10, TimeUnit.SECONDS);
+
+                            }else{
+                                doConnect();
+                            }
+
+                        }
+                    }, 5, TimeUnit.SECONDS);
+                }
+            }
+        });
+//        try {
+//            future.sync();
+//            future.channel().closeFuture().sync();
+//        } catch (InterruptedException e) {
+//            e.printStackTrace();
+//        } finally {
+        //重试之前不关
+//            try {
+//                Thread.sleep(retrySeconds * retryTimes*1000 + 1000);
+//            } catch (InterruptedException e) {
+//            }
+//            workGroup.shutdownGracefully();
+//        }
+    }
+
+
+    public synchronized void closeClient(){
+        try {
+            if(channel!=null){
+                channel.close();
+            }
+        } finally {
+            workGroup.shutdownGracefully();
+            isRunning=false;
+        }
+    }
+
+    public boolean isRunning() {
+        return isRunning;
+    }
+
+
+    //发送消息,线程安全
+    public synchronized void sendMsg(String content) {
+        Log.e(TAG, "发送的数据 " + content);
+        if (tcpClientHandler != null) {
+            tcpClientHandler.sendMsg(content);
+        }
+    }
+
+
+
+
+
+    public Channel getChannel() {
+        return channel;
+    }
+
+    //测试
+    public static void main(String[] args) {
+
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                TcpClient.getInstance().init("192.168.1.188", 5080, 9);
+            }
+        }).start();
+
+//        Scanner scanner = new Scanner(System.in);
+//        while (true) {
+//            System.out.println("please type : ");
+//            String line = scanner.nextLine();
+//            TcpClient.getInstance().sendMsg(line);
+//        }
+    }
+}

+ 156 - 0
app/src/main/java/com/wdkl/ncs/loragateway/tcp/TcpClientHandler.java

@@ -0,0 +1,156 @@
+package com.wdkl.ncs.loragateway.tcp;
+
+import android.util.Log;
+
+import com.wdkl.ncs.loragateway.common.Constants;
+import com.wdkl.ncs.loragateway.utils.AlarmMessageUtil;
+
+import org.greenrobot.eventbus.EventBus;
+
+import java.util.concurrent.TimeUnit;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import io.netty.util.ReferenceCountUtil;
+
+@ChannelHandler.Sharable
+public class TcpClientHandler extends SimpleChannelInboundHandler<String> {
+    private String TAG = TcpClientHandler.class.getSimpleName();
+
+    private  ChannelHandlerContext ctx;
+    //重连间隔
+    private static Integer retrySeconds = 5;
+    //重连计数
+    private static Integer retryTimes = 0;
+    //总共总连接次数,总连接次数过多可能是网络不稳定
+    private static Integer totalRetryTimes = 0;
+    //是否连接成功
+    private static Boolean connected = false;
+
+    //连接断开变量
+    public static Boolean connecteds = false;
+
+    //连接成功执行的方法
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        super.channelActive(ctx);
+        this.ctx = ctx;
+        connected = true;
+        connecteds = true;
+        retryTimes = 0;
+//        TcpModel tcpModel = DeviceUtil.deviceConnect(Constants.Companion.getMac());
+//        TcpClient.getInstance().sendMsg(tcpModel.toJson());
+        Constants.TCP_CONNCETED=true;
+        EventBus.getDefault().post(new EventBusModel( Constants.EVENT_TCP_STATE,Constants.TCP_CONNCETED));
+    }
+
+    //断开连接
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        super.channelInactive(ctx);
+        connected = false;
+        connecteds = false;
+        System.out.println("TcpClientHandler 失去连接");
+        Constants.TCP_CONNCETED=false;
+        EventBus.getDefault().post(new EventBusModel( Constants.EVENT_TCP_STATE,Constants.TCP_CONNCETED));
+        TcpClient.getInstance().doConnect();
+//        reConnect(ctx);
+    }
+
+    //读取String消息
+    @Override
+    protected void channelRead0(ChannelHandlerContext ctx, String source) throws Exception {
+        //System.out.println("TcpClientHandler from server ===> " + source);
+        if (source.startsWith("A55A01")) {
+            Log.e(TAG, "收到服务器返回的心跳:"  + source);
+        } else {
+//            TcpModel tcpModel = TcpModel.getModelByJson(source);
+//
+//            TcpModel responseTcpModel = DeviceChannel.handleTcpReceived(tcpModel);
+//            if (responseTcpModel != null) {
+//                ctx.writeAndFlush(responseTcpModel.toJson());
+//            } else {
+//
+//            }
+        }
+        ReferenceCountUtil.release(source);
+    }
+
+    //写心跳包。没有消息发送时,每间隔一定时间会由此方法向服务端发送心跳
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+        if (evt instanceof IdleStateEvent) {
+            IdleStateEvent event = (IdleStateEvent) evt;
+            if (event.state() == IdleState.WRITER_IDLE) { //超时未执行写操作,在指定的超时时间内未有写操作,要发送心跳包,告诉服务器连接还存活。服务器收到心跳立马回应,正常客户端收到后执行读操作,
+                Log.i(TAG, "发送心跳: "+AlarmMessageUtil.getHeartBeatMessage());
+                ctx.writeAndFlush(AlarmMessageUtil.getHeartBeatMessage());                //这种情况下不会引发READER_IDLE事件。如果服务器因为网络或其他原因导致回应的心跳,客户端没有收到,在超过写超时时间2个周期后依然没有收到,
+            } else if (event.state() == IdleState.READER_IDLE) { //认为服务不可用,主动断开连接
+//                ctx.channel().close();
+                Log.i(TAG, "TcpClientHandler ===> READER_IDLE");
+                ctx.close();
+//                ctx.close();
+            } else if (event.state() == IdleState.ALL_IDLE) {
+                Log.i(TAG, "TcpClientHandler ===> ALL_IDLE");
+
+            }
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        cause.printStackTrace();
+        //ctx.close();
+        //connected = false;
+        //connecteds = false;
+        System.out.println("TcpClientHandler 失去连接,错误引起");
+//        EventBus.getDefault().post(new MessageEvent(Constants.Companion.getEVENT_TCP_FAIL(),Constants.Companion.getEVENT_TCP_STATE()));
+    }
+
+    //发送消息,不直接调用些方法,调用TcpClient中的发送消息
+    public void sendMsg(String msg) {
+        if (ctx == null) {
+            System.out.println("ctx is null");
+            /*try {
+                Thread.sleep(1000);
+                sendMsg(msg);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }*/
+            return;
+        }
+        ctx.writeAndFlush(msg);
+    }
+
+    //已经连接上,中途失去连接时的处理
+    private void reConnect(final ChannelHandlerContext ctx) {
+        if (totalRetryTimes > 100) {
+            //todo: 存储数据库,并告警
+        }
+        if (connected && (TcpClient.getInstance().channel != null && TcpClient.getInstance().channel.isActive())) {
+            return;
+        }
+        totalRetryTimes++;
+        System.out.println("TcpClientHandler 总计连接次数:" + totalRetryTimes);
+        retryTimes++;
+        if (retryTimes > 30) {
+            //超时3次,其它处理
+            System.out.println("TcpClientHandler 重新连接" + (retryTimes - 1) + "次,结束");
+            retryTimes = 0;
+            //todo: 从API获取新的serverIP和serverPort,全新连接
+            //TcpClient.getInstance().init();
+            return;
+        }
+
+        ctx.channel().eventLoop().schedule(new Runnable() {
+            @Override
+            public void run() {
+                System.out.println("TcpClientHandler 重新连接,第" + retryTimes + "次");
+                TcpClient.getInstance().doConnect();
+                reConnect(ctx);
+            }
+        }, retrySeconds * retryTimes, TimeUnit.SECONDS);
+    }
+}

+ 54 - 0
app/src/main/java/com/wdkl/ncs/loragateway/utils/AlarmMessageUtil.java

@@ -0,0 +1,54 @@
+package com.wdkl.ncs.loragateway.utils;
+
+import android.util.Log;
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Stream;
+
+import io.netty.util.internal.StringUtil;
+
+public class AlarmMessageUtil {
+
+    public static String getHeartBeatMessage() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("a55a01");
+        JSONObject data = new JSONObject();
+        data.put("devId", NetUtil.getInstance().getMacAddress());
+        String dataJsonStr = data.toJSONString();
+        //数据长度16进制字符串
+        Log.i("数据长度", String.format("%04x",dataJsonStr.length()));
+        sb.append(String.format("%04X",dataJsonStr.length()));
+        String s = ByteUtil.str2HexStr(dataJsonStr);
+        sb.append(s);
+        byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
+        int sum = 0;
+        for (int i = 0; i < bytes.length; i++) {
+            sum += bytes[i];
+        }
+        sb.append(Integer.toHexString(sum));
+        return sb.toString();
+    }
+
+    public static String getAlarmMessage(String mac){
+        StringBuilder sb = new StringBuilder();
+        sb.append("a55a02");
+        JSONObject data = new JSONObject();
+        data.put("keycoDe", mac);
+        String dataJsonStr = data.toJSONString();
+        //数据长度16进制字符串
+        Log.i("数据长度", String.format("%04x",dataJsonStr.length()));
+        sb.append(String.format("%04X",dataJsonStr.length()));
+        String s = ByteUtil.str2HexStr(dataJsonStr);
+        sb.append(s);
+        byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
+        int sum = 0;
+        for (int i = 0; i < bytes.length; i++) {
+            sum += bytes[i];
+        }
+        sb.append(Integer.toHexString(sum));
+        return sb.toString();
+    }
+
+}

+ 117 - 0
app/src/main/java/com/wdkl/ncs/loragateway/utils/AutoRebootUtil.java

@@ -0,0 +1,117 @@
+package com.wdkl.ncs.loragateway.utils;
+
+import android.annotation.SuppressLint;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+
+import com.wdkl.ncs.loragateway.Application;
+import com.wdkl.ncs.loragateway.MainActivity;
+
+import java.util.Calendar;
+
+/**
+ * Created by dengzhe on 2018/4/18.
+ * //========自动重启工具类========//
+ */
+
+public class AutoRebootUtil {
+    private static Context context;
+
+    private static Calendar calendar;
+    private static int countDownMinute;
+    private static String currentTime = "0";
+    private static int timeFirst = 11;
+    private static int timeSecond = 17;
+    private static int mt = 50;
+    private static int lt = 55;
+    private static int hour;
+    private static int minute;
+
+
+    public static boolean sys_Reset = false;
+    public static boolean sys_Shutdown = false;
+
+    public static void reboot(Context context) {
+        if (!sys_Reset) {
+            sys_Reset = true;
+            Intent intent = new Intent(Intent.ACTION_REBOOT);
+            intent.putExtra("nowait", 1);
+            intent.putExtra("interval", 1);
+            intent.putExtra("window", 0);
+            context.sendBroadcast(intent);
+        }
+    }
+
+    public static void shutdown(Context context) {
+        if (!sys_Shutdown) {
+            sys_Shutdown = true;
+            Intent intent = new Intent("android.intent.action.ACTION_REQUEST_SHUTDOWN");
+            intent.putExtra("android.intent.extra.KEY_CONFIRM", false);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            context.startActivity(intent);
+        }
+    }
+
+
+    public static void reboot() {
+        if (!sys_Reset) {
+            sys_Reset = true;
+            try {
+                Process proc = Runtime.getRuntime().exec(new String[]{"su", "-c", "reboot "});
+                proc.waitFor();
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+    }
+
+    public static void shutdown() {
+        if (!sys_Shutdown) {
+            sys_Shutdown = true;
+            try {
+                Process proc = Runtime.getRuntime().exec(new String[]{"su", "-c", "reboot -p"});
+                proc.waitFor();
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+    }
+
+
+
+
+
+
+
+
+
+
+    private static boolean restart = false;
+    public static void restartApp() {
+        if (!restart) {
+            try {
+                Intent intent = new Intent(Application.getContext(), MainActivity.class);
+                @SuppressLint("WrongConstant") PendingIntent mPendingIntent = PendingIntent.getActivity(
+                        Application.getContext(), 0, intent,
+                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                AlarmManager mgr = (AlarmManager) Application.getContext().getSystemService(Context.ALARM_SERVICE);
+                mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 2000, mPendingIntent);
+
+//                for (Activity activity : Application.getContext()) {
+//                    if (activity != null) {
+//                        activity.finish();
+//                    }
+//                }
+//                MyApplication.activities.clear();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+            restart = true;
+            android.os.Process.killProcess(android.os.Process.myPid());
+            System.exit(0);
+        }
+    }
+}

+ 368 - 0
app/src/main/java/com/wdkl/ncs/loragateway/utils/ByteUtil.java

@@ -0,0 +1,368 @@
+package com.wdkl.ncs.loragateway.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+
+public class ByteUtil {
+    private static final ByteBuffer buffer = ByteBuffer.allocate(8);
+    /**
+     * int转byte
+     * @param x
+     * @return
+     */
+    public static byte intToByte(int x) {
+        return (byte) x;
+    }
+    /**
+     * byte转int
+     * @param b
+     * @return
+     */
+    public static int byteToInt(byte b) {
+        //Java的byte是有符号,通过 &0xFF转为无符号
+        return b & 0xFF;
+    }
+
+    /**
+     * byte[]转int
+     * @param b
+     * @return
+     */
+    public static int byteArrayToInt(byte[] b) {
+        return   b[3] & 0xFF |
+                (b[2] & 0xFF) << 8 |
+                (b[1] & 0xFF) << 16 |
+                (b[0] & 0xFF) << 24;
+    }
+    public static int byteArrayToInt(byte[] b, int index){
+        return   b[index+3] & 0xFF |
+                (b[index+2] & 0xFF) << 8 |
+                (b[index+1] & 0xFF) << 16 |
+                (b[index+0] & 0xFF) << 24;
+    }
+    /**
+     * int转byte[]
+     * @param a
+     * @return
+     */
+    public static byte[] intToByteArray(int a) {
+        return new byte[] {
+                (byte) ((a >> 24) & 0xFF),
+                (byte) ((a >> 16) & 0xFF),
+                (byte) ((a >> 8) & 0xFF),
+                (byte) (a & 0xFF)
+        };
+    }
+    /**
+     * short转byte[]
+     *
+     * @param b
+     * @param s
+     * @param index
+     */
+    public static void byteArrToShort(byte[] b, short s, int index) {
+        b[index + 1] = (byte) (s >> 8);
+        b[index + 0] = (byte) (s >> 0);
+    }
+    /**
+     * byte[]转short
+     *
+     * @param b
+     * @param index
+     * @return
+     */
+    public static short byteArrToShort(byte[] b, int index) {
+        return (short) (((b[index + 0] << 8) | b[index + 1] & 0xff));
+    }
+    /**
+     * 16位short转byte[]
+     *
+     * @param s
+     *            short
+     * @return byte[]
+     * */
+    public static byte[] shortToByteArr(short s) {
+        byte[] targets = new byte[2];
+        for (int i = 0; i < 2; i++) {
+            int offset = (targets.length - 1 - i) * 8;
+            targets[i] = (byte) ((s >>> offset) & 0xff);
+        }
+        return targets;
+    }
+    /**
+     * byte[]转16位short
+     * @param b
+     * @return
+     */
+    public static short byteArrToShort(byte[] b){
+        return byteArrToShort(b,0);
+    }
+
+    /**
+     * long转byte[]
+     * @param x
+     * @return
+     */
+    public static byte[] longToBytes(long x) {
+        buffer.putLong(0, x);
+        return buffer.array();
+    }
+    /**
+     * byte[]转Long
+     * @param bytes
+     * @return
+     */
+    public static long bytesToLong(byte[] bytes) {
+        buffer.put(bytes, 0, bytes.length);
+        buffer.flip();//need flip
+        return buffer.getLong();
+    }
+    /**
+     * 从byte[]中抽取新的byte[]
+     * @param data - 元数据
+     * @param start - 开始位置
+     * @param end - 结束位置
+     * @return 新byte[]
+     */
+    public static byte[] getByteArr(byte[]data,int start ,int end){
+        byte[] ret=new byte[end-start];
+        for(int i=0;(start+i)<end;i++){
+            ret[i]=data[start+i];
+        }
+        return ret;
+    }
+
+    /**
+     * 流转换为byte[]
+     * @param inStream
+     * @return
+     */
+    public static byte[] readInputStream(InputStream inStream) {
+        ByteArrayOutputStream outStream = null;
+        try {
+            outStream = new ByteArrayOutputStream();
+            byte[] buffer = new byte[1024];
+            byte[] data = null;
+            int len = 0;
+            while ((len = inStream.read(buffer)) != -1) {
+                outStream.write(buffer, 0, len);
+            }
+            data = outStream.toByteArray();
+            return data;
+        }catch (IOException e) {
+            return null;
+        } finally {
+            try {
+                if (outStream != null) {
+                    outStream.close();
+                }
+                if (inStream != null) {
+                    inStream.close();
+                }
+            } catch (IOException e) {
+                return null;
+            }
+        }
+    }
+    /**
+     * byte[]转inputstream
+     * @param b
+     * @return
+     */
+    public static InputStream readByteArr(byte[] b){
+        return new ByteArrayInputStream(b);
+    }
+    /**
+     * byte数组内数字是否相同
+     * @param s1
+     * @param s2
+     * @return
+     */
+    public static boolean isEq(byte[] s1,byte[] s2){
+        int slen=s1.length;
+        if(slen==s2.length){
+            for(int index=0;index<slen;index++){
+                if(s1[index]!=s2[index]){
+                    return false;
+                }
+            }
+            return true;
+        }
+        return  false;
+    }
+    /**
+     * byte数组转换为Stirng
+     * @param s1 -数组
+     * @param encode -字符集
+     * @param err -转换错误时返回该文字
+     * @return
+     */
+    public static String getString(byte[] s1,String encode,String err){
+        try {
+            return new String(s1,encode);
+        } catch (UnsupportedEncodingException e) {
+            return err;
+        }
+    }
+    /**
+     * byte数组转换为Stirng
+     * @param s1-数组
+     * @param encode-字符集
+     * @return
+     */
+    public static String getString(byte[] s1,String encode){
+        return getString(s1,encode,null);
+    }
+    //测试
+    public static void main(String []args){
+        System.err.println(isEq(new byte[]{1,2},new byte[]{1,2}));
+    }
+    /**
+     * 字节数组转16进制字符串
+     * @param b
+     * @return
+     */
+    public static String byteArrToHexString(byte[] b){
+        String result="";
+        for (int i=0; i < b.length; i++) {
+            result += Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring(1);
+        }
+        return result;
+    }
+
+    /**
+     * 16进制字符创转int
+     * @param hexString
+     * @return
+     */
+    public static int hexStringToInt(String hexString){
+        return Integer.parseInt(hexString,16);
+    }
+    /**
+     * 十进制转二进制
+     * @param i
+     * @return
+     */
+    public static String intToBinary(int i){
+        return Integer.toBinaryString(i);
+    }
+
+
+    /**
+     * 16进制字符串转换成byte数组  这个转的多了会报错
+     * */
+    public static byte[] Hex2Bytes(String hexString){
+        byte[] arrB = hexString.getBytes();
+        int iLen = arrB.length;
+        byte[] arrOut = new byte[iLen / 2];
+        String strTmp = null;
+        for (int i = 0; i < iLen; i += 2)
+        {
+            strTmp = new String(arrB, i, 2);
+            arrOut[(i / 2)] = ((byte)Integer.parseInt(strTmp, 16));
+        }
+        return arrOut;
+    }
+
+    /**
+     * 16进制字符串转换成byte数组
+     * @param hexStr
+     * @return
+     */
+    public static byte[] hexToByteArr(String hexStr) {
+       String HexStr = "0123456789abcdef";
+        char[] charArr = hexStr.toCharArray();
+        byte[] btArr = new byte[charArr.length / 2];
+        int index = 0;
+        for (int i = 0; i < charArr.length; i++) {
+            int highBit = HexStr.indexOf(charArr[i]);
+            int lowBit = HexStr.indexOf(charArr[++i]);
+            btArr[index] = (byte) (highBit << 4 | lowBit);
+            index++;
+        }
+        return btArr;
+    }
+
+
+
+
+    /**
+     * 16进制字符串转换成byte数组
+     * */
+    public static byte[] String2Byte(String s) {
+        s = s.replace(" ", "");
+        s = s.replace("#", "");
+        byte[] baKeyword = new byte[s.length() / 2];
+        for (int i = 0; i < baKeyword.length; i++) {
+            try {
+                baKeyword[i] = (byte) (0xff & Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16));
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return baKeyword;
+    }
+
+    public static String hexStr2Str(String hexStr) {
+        String str = "0123456789abcdef";
+        char[] hexs = hexStr.toCharArray();
+        byte[] bytes = new byte[hexStr.length() / 2];
+        int n;
+
+        for (int i = 0; i < bytes.length; i++) {
+            n = str.indexOf(hexs[2 * i]) * 16;
+            n += str.indexOf(hexs[2 * i + 1]);
+            bytes[i] = (byte) (n & 0xff);
+        }
+        return new String(bytes);
+    }
+
+    /**
+     * 字符串转换成为16进制(无需Unicode编码)
+     * @param str
+     * @return
+     */
+    public static String str2HexStr(String str) {
+        char[] chars = "0123456789abcdef".toCharArray();
+        StringBuilder sb = new StringBuilder();
+        byte[] bs = str.getBytes();
+        int bit;
+        for (int i = 0; i < bs.length; i++) {
+            bit = (bs[i] & 0x0f0) >> 4;
+            sb.append(chars[bit]);
+            bit = bs[i] & 0x0f;
+            sb.append(chars[bit]);
+            // sb.append(' ');
+        }
+        return sb.toString().trim();
+    }
+
+    // 16进制表示的字符串转换为字节数组
+    public static byte[] hexStringToByteArray(String hexString) {
+        hexString = hexString.replaceAll(" ", "");
+        int len = hexString.length();
+        byte[] bytes = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节
+            bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
+                    .digit(hexString.charAt(i + 1), 16));
+        }
+        return bytes;
+    }
+    // 字节数组转换为16进制表示的字符串
+    public static String BinaryToHexString(byte[] bytes) {
+        String hexStr = "0123456789ABCDEF";
+        StringBuilder result = new StringBuilder();
+        String hex = "";
+        for (byte b : bytes) {
+            hex = String.valueOf(hexStr.charAt((b & 0xF0) >> 4));
+            hex += String.valueOf(hexStr.charAt(b & 0x0F));
+            result.append(hex).append(" ");
+        }
+        return result.toString();
+    }
+}

+ 548 - 0
app/src/main/java/com/wdkl/ncs/loragateway/utils/NetUtil.java

@@ -0,0 +1,548 @@
+package com.wdkl.ncs.loragateway.utils;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.text.TextUtils;
+
+import com.wdkl.ncs.loragateway.Application;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class NetUtil {
+    private WifiManager wifiManager;
+    private ConnectivityManager connManager;
+    private static NetUtil sInstance = null;
+
+    private static Timer timerNetStatus = null;
+    private static final int SCHEDULE_TIME = 30000;
+    public static boolean NetConn = false;
+    //public static String host = "www.baidu.com";
+    /**
+     * 以太网是否ping成功
+     */
+    public static final String ETHERNETSTATUS = "ethernetStatus";
+
+    public NetUtil() {
+    }
+
+    public static NetUtil getInstance() {
+        if (sInstance == null) {
+            synchronized (NetUtil.class) {
+                if (sInstance == null) {
+                    sInstance = new NetUtil();
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    public void init() {
+        wifiManager = (WifiManager) Application.getContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+        connManager = (ConnectivityManager) Application.getContext().getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+    }
+
+//    public static void startNetCheck() {
+//        if (timerNetStatus != null) {
+//            timerNetStatus.purge();
+//        }
+//        timerNetStatus = new Timer();
+//        timerNetStatus.schedule(new TimerTask() {
+//            @Override
+//            public void run() {
+//                if (!TextUtils.isEmpty(Constants.Companion.getTcpIp())) {
+//                    NetConn = ping(Constants.Companion.getTcpIp(), 2, null);
+//                    Constants.Companion.setNET_AVAILABLE(NetConn);
+//                    EventBus.getDefault().post(new MessageEvent("", Constants.Companion.getEVENT_NETWORK_STATE()));
+//                }
+//            }
+//        }, 10, SCHEDULE_TIME);
+//    }
+
+
+
+    //获取本地ip地址
+    public  String getLocalIP() {
+        try {
+            for (Enumeration<NetworkInterface> enNetI = NetworkInterface.getNetworkInterfaces(); enNetI.hasMoreElements(); ) {
+                NetworkInterface netI = enNetI.nextElement();
+                for (Enumeration<InetAddress> enumIpAddr = netI.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+                    InetAddress inetAddress = enumIpAddr.nextElement();
+                    if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) {
+                        return inetAddress.getHostAddress();
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+
+
+    private static void append(StringBuffer stringBuffer, String text) {
+        if (stringBuffer != null) {
+            stringBuffer.append(text + "\n");
+        }
+    }
+
+    public static boolean isBTConnected() {
+        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (btAdapter != null) {
+            return btAdapter.getState() == BluetoothAdapter.STATE_ON;
+        }
+
+        return false;
+    }
+    /**
+     * 获取网关  Waderson
+     * <p>
+     * 1 WIFI情况下获取网关 2 有线网络下的DHCP模式连接 3 有线网络其他连接方式:比如静态ip、pppoe拨号、ipoe拨号等
+     */
+    public static String getLocalElement(int type) {
+        String e = "";
+        if (1 == type) {
+            //e = getLocalElementByWifi();
+        } else if (2 == type) {
+            e = getLocalElementByDhcp();
+        } else if (3 == type) {
+            e = getLocalElementByIp();
+        }
+        if (!TextUtils.isEmpty(e) && e.length() >= 11 && e.length() <= 15) {
+            return e;
+        } else {
+            return "192.168.101.1";
+        }
+    }
+
+
+    /**
+     * 有线网络下的DHCP模式连接
+     */
+    public static String getLocalElementByDhcp() {
+        BufferedReader bufferedReader = null;
+        String str2 = "";
+        String str3 = "getprop dhcp.eth0.gateway";
+        Process exec;
+        BufferedReader bufferedReader2 = null;
+        try {
+            exec = Runtime.getRuntime().exec(str3);
+            try {
+                bufferedReader2 = new BufferedReader(new InputStreamReader(exec.getInputStream()));
+            } catch (Throwable th3) {
+                if (bufferedReader != null) {
+                    bufferedReader.close();
+                }
+                if (exec != null) {
+                    exec.exitValue();
+                }
+            }
+            try {
+                str3 = bufferedReader2.readLine();
+                if (str3 != null) {
+                    TextUtils.isEmpty(str3);
+                }
+                try {
+                    bufferedReader2.close();
+                } catch (IOException iOException222) {
+                    iOException222.printStackTrace();
+                }
+                if (exec != null) {
+                    try {
+                        exec.exitValue();
+                    } catch (Exception e5) {
+                    }
+                }
+            } catch (IOException e6) {
+                str3 = str2;
+                if (bufferedReader2 != null) {
+                    bufferedReader2.close();
+                }
+                if (exec != null) {
+                    exec.exitValue();
+                }
+                return str3;
+            }
+        } catch (IOException e62) {
+            bufferedReader2 = null;
+            exec = null;
+            str3 = str2;
+            if (bufferedReader2 != null) {
+                try {
+                    bufferedReader2.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (exec != null) {
+                exec.exitValue();
+            }
+            return str3;
+        } catch (Throwable th4) {
+            exec = null;
+            if (bufferedReader != null) {
+                try {
+                    bufferedReader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (exec != null) {
+                exec.exitValue();
+            }
+        }
+        return str3;
+    }
+
+    /**
+     * 有线网络其他连接方式:比如静态ip、pppoe拨号、ipoe拨号等
+     *///TODO 这个方法在主机上获取不到IP
+    public static String getLocalElementByIp() {
+        BufferedReader bufferedReader = null;
+        String result = "";
+        String str2 = "";
+        String str3 = "ip route list table 0";
+        Process exec;
+        BufferedReader bufferedReader2 = null;
+        try {
+            exec = Runtime.getRuntime().exec(str3);
+            try {
+                bufferedReader2 = new BufferedReader(new InputStreamReader(exec.getInputStream()));
+            } catch (Throwable th3) {
+                if (bufferedReader != null) {
+                    bufferedReader.close();
+                }
+                if (exec != null) {
+                    exec.exitValue();
+                }
+            }
+            try {
+                str2 = bufferedReader2.readLine();
+                if (str2 != null) {
+                    str2 = str2.trim();
+                    String[] strings = str2.split("\\s+");
+                    if (strings.length > 3) {
+                        result = strings[2];
+                    }
+                }
+                try {
+                    bufferedReader2.close();
+                } catch (IOException iOException222) {
+                    iOException222.printStackTrace();
+                }
+                if (exec != null) {
+                    try {
+                        exec.exitValue();
+                    } catch (Exception e5) {
+                    }
+                }
+            } catch (IOException e6) {
+                if (bufferedReader2 != null) {
+                    bufferedReader2.close();
+                }
+                if (exec != null) {
+                    exec.exitValue();
+                }
+                return result;
+            }
+        } catch (IOException e62) {
+            bufferedReader2 = null;
+            exec = null;
+            if (bufferedReader2 != null) {
+                try {
+                    bufferedReader2.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (exec != null) {
+                exec.exitValue();
+            }
+            return result;
+        } catch (Throwable th4) {
+            exec = null;
+            if (bufferedReader != null) {
+                try {
+                    bufferedReader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (exec != null) {
+                exec.exitValue();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 得到MAC
+     *
+     * @return String
+     */
+    public String getMacAddress() {
+
+        if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.O){//小于安卓8 走这里
+            String mac = "";
+            try {
+                mac = getLocalMacAddressFromIp();
+                if (TextUtils.isEmpty(mac)) {
+                    mac = getNetworkMac();
+                }
+                if (TextUtils.isEmpty(mac)) {
+                    mac = tryGetWifiMac();
+                }
+            } catch (Exception e) {
+            }
+            return mac;
+        }else {//大于安卓8走这里
+            List<NetworkInterface> interfaces = null;
+            try {
+                interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+                for (NetworkInterface networkInterface : interfaces) {
+                    if (networkInterface != null && TextUtils.isEmpty(networkInterface.getName()) == false) {
+                        if ("wlan0".equalsIgnoreCase(networkInterface.getName())) {
+                            byte[] macBytes = networkInterface.getHardwareAddress();
+                            if (macBytes != null && macBytes.length > 0) {
+                                StringBuilder str = new StringBuilder();
+                                for (byte b : macBytes) {
+                                    str.append(String.format("%02X:", b));
+                                }
+                                if (str.length() > 0) {
+                                    str.deleteCharAt(str.length() - 1);
+                                }
+                                return str.toString();
+                            }
+                        }
+                    }
+                }
+            } catch (SocketException e) {
+                e.printStackTrace();
+            }
+            return "unknown";
+
+        }
+    }
+
+    /**
+     * 兼容安卓5-10 获取Mac地址
+     * @return
+     */
+    public String getMacAddress2() {
+        List<NetworkInterface> interfaces = null;
+        try {
+            interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+            for (NetworkInterface networkInterface : interfaces) {
+                if (networkInterface != null && TextUtils.isEmpty(networkInterface.getName()) == false) {
+                    if ("wlan0".equalsIgnoreCase(networkInterface.getName())) {
+                        byte[] macBytes = networkInterface.getHardwareAddress();
+                        if (macBytes != null && macBytes.length > 0) {
+                            StringBuilder str = new StringBuilder();
+                            for (byte b : macBytes) {
+                                str.append(String.format("%02X:", b));
+                            }
+                            if (str.length() > 0) {
+                                str.deleteCharAt(str.length() - 1);
+                            }
+                            return str.toString();
+                        }
+                    }
+                }
+            }
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+        return "unknown";
+    }
+
+
+    /**
+     * 通过WiFiManager获取mac地址
+     * 这个方法Android 7.0是获取不到的,返回的是null,其实是返回“02:00:00:00:00:00”
+     *
+     * @return
+     */
+    public String tryGetWifiMac() {
+        String mac = null;
+        if (wifiManager != null) {
+            WifiInfo wi = wifiManager.getConnectionInfo();
+            if (wi == null || wi.getMacAddress() == null) {
+                mac = null;
+            }
+            if ("02:00:00:00:00:00".equals(wi.getMacAddress().trim())) {
+                mac = null;
+            } else {
+                mac = wi.getMacAddress().trim();
+            }
+        }
+        return mac;
+    }
+
+    /**
+     * 通过网络接口获取
+     *
+     * @return
+     */
+    public static String getNetworkMac() {
+        try {
+            List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
+            for (NetworkInterface nif : all) {
+                // if (!nif.getName().equalsIgnoreCase("wlan0") && !nif.getName().equalsIgnoreCase("eth0") && !nif.getName().equalsIgnoreCase("eth1"))
+                if (!nif.getName().equalsIgnoreCase("eth0") && !nif.getName().equalsIgnoreCase("eth1") && !nif.getName().equalsIgnoreCase("eth2"))
+                    continue;
+                byte[] macBytes = nif.getHardwareAddress();
+                if (macBytes == null) {
+                    return null;
+                }
+                StringBuilder res1 = new StringBuilder();
+                for (byte b : macBytes) {
+                    res1.append(String.format("%02X:", b));
+                }
+                if (res1.length() > 0) {
+                    res1.deleteCharAt(res1.length() - 1);
+                }
+                return res1.toString();
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+        return null;
+    }
+
+
+    /**
+     * 根据IP地址获取MAC地址
+     *
+     * @return
+     */
+    @SuppressLint("NewApi")
+    public static String getLocalMacAddressFromIp() {
+        String strMacAddr = null;
+        try {
+            // 获得IpD地址
+            InetAddress ip = getLocalInetAddress();
+            byte[] b = NetworkInterface.getByInetAddress(ip)
+                    .getHardwareAddress();
+            StringBuffer buffer = new StringBuffer();
+            for (int i = 0; i < b.length; i++) {
+                if (i != 0) {
+                    buffer.append(':');
+                }
+                String str = Integer.toHexString(b[i] & 0xFF);
+                buffer.append(str.length() == 1 ? 0 + str : str);
+            }
+            strMacAddr = buffer.toString().toUpperCase();
+
+        } catch (Exception e) {
+
+        }
+
+        return strMacAddr;
+    }
+
+    /**
+     * 获取移动设备本地IP
+     *
+     * @return
+     */
+    public static InetAddress getLocalInetAddress() {
+        InetAddress ip = null;
+        try {
+            // 列举
+            Enumeration<NetworkInterface> en_netInterface = NetworkInterface
+                    .getNetworkInterfaces();
+            while (en_netInterface.hasMoreElements()) {// 是否还有元素
+                NetworkInterface ni = (NetworkInterface) en_netInterface
+                        .nextElement();// 得到下一个元素
+                Enumeration<InetAddress> en_ip = ni.getInetAddresses();// 得到一个ip地址的列举
+                while (en_ip.hasMoreElements()) {
+                    ip = en_ip.nextElement();
+                    if (!ip.isLoopbackAddress()
+                            && ip.getHostAddress().indexOf(":") == -1)
+                        break;
+                    else
+                        ip = null;
+                }
+
+                if (ip != null) {
+                    break;
+                }
+            }
+        } catch (SocketException e) {
+
+            e.printStackTrace();
+        }
+        return ip;
+    }
+    /**
+     * 没有连接网络
+     */
+    public static final int NETWORK_NONE = -1;
+    /**
+     * 本地网络
+     */
+    public static final int NETWORK_ETHERNET = 0;
+    /**
+     * 无线网络
+     */
+    public static final int NETWORK_WIFI = 1;
+    public static int getNetWorkState(Context context) {
+        // 得到连接管理器对象
+        ConnectivityManager connectivityManager = (ConnectivityManager) context
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        NetworkInfo activeNetworkInfo = connectivityManager
+                .getActiveNetworkInfo();
+        if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
+
+            if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) {
+                return NETWORK_WIFI;
+            } else if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_ETHERNET)) {
+                return NETWORK_ETHERNET;
+            }
+        } else {
+            return NETWORK_NONE;
+        }
+        return NETWORK_NONE;
+    }
+
+    /**
+     * 获取初始网络情况
+     * @return
+     */
+    public Boolean getNetAvailable(){
+        if (connManager != null) {
+            NetworkInfo networkInfo = connManager.getActiveNetworkInfo();
+            if (networkInfo != null && networkInfo.getState() == NetworkInfo.State.CONNECTED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public int getNetworkType() {
+        if (connManager != null && connManager.getActiveNetworkInfo() != null) {
+            return connManager.getActiveNetworkInfo().getType();
+        }
+
+        return -1;
+    }
+}

Plik diff jest za duży
+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml


+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 107 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+<LinearLayout
+    android:layoutDirection="ltr"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <TextView
+            android:layout_weight="1"
+            android:layout_marginLeft="50dp"
+            android:layout_marginTop="50dp"
+            android:lineSpacingExtra="10dp"
+            android:textColor="@color/material_on_surface_emphasis_medium"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="IP地址及版本号:"
+            android:id="@+id/setttingInfo"></TextView>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_marginLeft="50dp"
+            android:layout_marginBottom="10dp"
+            android:layout_height="wrap_content">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="10dp"
+                android:lineSpacingExtra="10dp"
+                android:text="TCP连接状态:"
+                ></TextView>
+            <TextView
+                android:id="@+id/tcpStatus"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:lineSpacingExtra="10dp"
+                android:textColor="@color/red"
+                android:text="未连接"
+                ></TextView>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_marginLeft="50dp"
+            android:layout_marginBottom="10dp"
+            android:layout_height="wrap_content">
+            <TextView
+                android:layout_width="wrap_content"
+                android:lineSpacingExtra="10dp"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="10dp"
+                android:text="串口状态:"
+                ></TextView>
+            <TextView
+                android:id="@+id/serialStatus"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:lineSpacingExtra="10dp"
+                android:textColor="@color/green"
+                android:text="正常"
+                ></TextView>
+        </LinearLayout>
+        <Button
+            android:id="@+id/ButtonSetup"
+            android:layout_marginLeft="50dp"
+            android:layout_marginRight="50dp"
+            android:textColor="@color/white"
+            android:background="@color/design_default_color_primary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:layout_marginTop="10dp"
+            android:text="通信设置" />
+        <Button
+            android:layout_marginLeft="50dp"
+            android:layout_marginRight="50dp"
+            android:id="@+id/RebootApp"
+            android:layout_marginBottom="50dp"
+            android:layout_marginTop="10dp"
+            android:textColor="@color/white"
+            android:background="@color/teal_200"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="重启应用使配置生效" />
+
+    </LinearLayout>
+    <EditText
+        android:id="@+id/EditTextReception"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_weight="1"
+        android:gravity="top"
+        android:hint="接收区"
+         android:editable="false"
+        android:scrollbarStyle="insideOverlay"
+        android:isScrollContainer="true">
+    </EditText>
+</LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 9 - 0
app/src/main/res/layout/settings_activity.xml

@@ -0,0 +1,9 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/settings"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</LinearLayout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


+ 4 - 0
app/src/main/res/values-night/colors.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="green">#00FF00</color>
+</resources>

+ 16 - 0
app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.LoraGateway" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 108 - 0
app/src/main/res/values/arrays.xml

@@ -0,0 +1,108 @@
+<resources>
+    <!-- Reply Preference -->
+    <string-array name="reply_entries">
+        <item>Reply</item>
+        <item>Reply to all</item>
+    </string-array>
+
+    <string-array name="reply_values">
+        <item>reply</item>
+        <item>reply_all</item>
+    </string-array>
+    <string-array name="stop_bits_entries">
+        <item>1位</item>
+        <item>2位</item>
+        </string-array>
+    <string-array name="stop_bits_values">
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+    <string-array name="parity_entries" >
+        <item>无校验位(NONE)</item>
+        <item>奇校验位(ODD)</item>
+        <item>偶校验位(EVEN)</item>
+    </string-array>
+    <string-array name="parity_values" >
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+    <string-array name="data_bits_entries">
+        <item>5</item>
+        <item>6</item>
+        <item>7</item>
+        <item>8</item>
+    </string-array>
+    <string-array name="data_bits_values">
+        <item>5</item>
+        <item>6</item>
+        <item>7</item>
+        <item>8</item>
+    </string-array>
+    <string-array name="baud_rate_entries">
+        <item>50</item>
+        <item>75</item>
+        <item>110</item>
+        <item>134</item>
+        <item>150</item>
+        <item>200</item>
+        <item>300</item>
+        <item>600</item>
+        <item>1200</item>
+        <item>1800</item>
+        <item>2400</item>
+        <item>4800</item>
+        <item>9600</item>
+        <item>19200</item>
+        <item>38400</item>
+        <item>57600</item>
+        <item>115200</item>
+        <item>230400</item>
+        <item>460800</item>
+        <item>500000</item>
+        <item>576000</item>
+        <item>921600</item>
+        <item>1000000</item>
+        <item>1152000</item>
+        <item>1500000</item>
+        <item>2000000</item>
+        <item>2500000</item>
+        <item>3000000</item>
+        <item>3500000</item>
+        <item>4000000</item>
+        </string-array>
+    <string-array name="baud_rate_values">
+        <item>50</item>
+        <item>75</item>
+        <item>110</item>
+        <item>134</item>
+        <item>150</item>
+        <item>200</item>
+        <item>300</item>
+        <item>600</item>
+        <item>1200</item>
+        <item>1800</item>
+        <item>2400</item>
+        <item>4800</item>
+        <item>9600</item>
+        <item>19200</item>
+        <item>38400</item>
+        <item>57600</item>
+        <item>115200</item>
+        <item>230400</item>
+        <item>460800</item>
+        <item>500000</item>
+        <item>576000</item>
+        <item>921600</item>
+        <item>1000000</item>
+        <item>1152000</item>
+        <item>1500000</item>
+        <item>2000000</item>
+        <item>2500000</item>
+        <item>3000000</item>
+        <item>3500000</item>
+        <item>4000000</item>
+    </string-array>
+
+
+</resources>

+ 12 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+    <color name="green">#00FF00</color>
+    <color name="red">#F44336</color>
+</resources>

+ 30 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,30 @@
+<resources>
+    <string name="app_name">LoraGateway</string>
+    <string name="error_configuration">请配置串口号</string>
+    <string name="error_security">无法读取串口</string>
+    <string name="error_unknown">串口无法打开</string>
+    <string name="title_activity_main">Lora网关</string>
+    <string name="title_activity_settings">系统通信设置</string>
+
+    <!-- Preference Titles -->
+    <string name="messages_header">串口接收设置</string>
+    <string name="sync_header">服务器通信设置</string>
+
+    <!-- Messages Preferences -->
+    <string name="signature_title">Your signature</string>
+    <string name="reply_title">Default reply action</string>
+
+    <!-- Sync Preferences -->
+    <string name="sync_title">Sync email periodically</string>
+    <string name="attachment_title">Download incoming attachments</string>
+    <string name="attachment_summary_on">Automatically download attachments for incoming emails
+    </string>
+    <string name="attachment_summary_off">Only download attachments when manually requested</string>
+    <string name="serial_deivce">串口号</string>
+    <string name="baud_rate">波特率</string>
+    <string name="data_bits">数据位</string>
+    <string name="parity">校验位</string>
+    <string name="stop_bits">停止位</string>
+    <string name="tcp_server">TCP通讯服务器地址</string>
+    <string name="tcp_server_port">服务器端口号</string>
+</resources>

+ 16 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.LoraGateway" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 13 - 0
app/src/main/res/xml/backup_rules.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>

+ 19 - 0
app/src/main/res/xml/data_extraction_rules.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>

+ 85 - 0
app/src/main/res/xml/root_preferences.xml

@@ -0,0 +1,85 @@
+<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceCategory app:title="@string/messages_header">
+
+
+        <ListPreference
+            app:defaultValue="/dev/ttyS2"
+            app:key="serialDeivce"
+            app:title="@string/serial_deivce"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            app:defaultValue="115200"
+            app:entries="@array/baud_rate_entries"
+            app:entryValues="@array/baud_rate_values"
+            app:key="baudRate"
+            app:title="@string/baud_rate"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            app:defaultValue="8"
+            app:entries="@array/data_bits_entries"
+            app:entryValues="@array/data_bits_values"
+            app:key="dataBits"
+            app:title="@string/data_bits"
+            app:useSimpleSummaryProvider="true" />
+
+
+        <ListPreference
+            app:defaultValue="0"
+            app:entries="@array/parity_entries"
+            app:entryValues="@array/parity_values"
+            app:key="parity"
+            app:title="@string/parity"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            app:defaultValue="1"
+            app:entries="@array/stop_bits_entries"
+            app:entryValues="@array/stop_bits_values"
+            app:key="stopBits"
+            app:title="@string/stop_bits"
+            app:useSimpleSummaryProvider="true" />
+
+
+<!--        <EditTextPreference-->
+<!--            app:key="signature"-->
+<!--            app:title="@string/signature_title"-->
+<!--            app:useSimpleSummaryProvider="true" />-->
+
+<!--        <ListPreference-->
+<!--            app:defaultValue="reply"-->
+<!--            app:entries="@array/reply_entries"-->
+<!--            app:entryValues="@array/reply_values"-->
+<!--            app:key="reply"-->
+<!--            app:title="@string/reply_title"-->
+<!--            app:useSimpleSummaryProvider="true" />-->
+
+    </PreferenceCategory>
+
+    <PreferenceCategory app:title="@string/sync_header">
+
+                <EditTextPreference
+                    app:defaultValue="172.28.100.100"
+                    app:key="tcpServer"
+                    app:title="@string/tcp_server"
+                    app:useSimpleSummaryProvider="true" />
+
+
+        <EditTextPreference
+            app:defaultValue="5085"
+            app:key="tcpServerPort"
+            app:title="@string/tcp_server_port"
+            app:useSimpleSummaryProvider="true" />
+
+<!--        <SwitchPreferenceCompat-->
+<!--            app:dependency="sync"-->
+<!--            app:key="attachment"-->
+<!--            app:summaryOff="@string/attachment_summary_off"-->
+<!--            app:summaryOn="@string/attachment_summary_on"-->
+<!--            app:title="@string/attachment_title" />-->
+
+    </PreferenceCategory>
+
+</PreferenceScreen>

+ 17 - 0
app/src/test/java/com/wdkl/ncs/loragateway/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.wdkl.ncs.loragateway;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 18 - 0
build.gradle

@@ -0,0 +1,18 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+    id 'com.android.application' version '7.2.2' apply false
+    id 'com.android.library' version '7.2.2' apply false
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+ext {
+    compileSdkVersion = 29
+
+    minSdkVersion = 21
+    targetSdkVersion = 29
+
+    versionCode = 4
+    versionName = "2.1.4"
+}

+ 21 - 0
gradle.properties

@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Sat Dec 03 13:05:01 CST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME

+ 185 - 0
gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+#      https://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.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 10 - 0
local.properties

@@ -0,0 +1,10 @@
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file should *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=/Users/wcy/Library/Android/sdk

+ 3 - 0
serialport/.gitignore

@@ -0,0 +1,3 @@
+/build
+.externalNativeBuild
+.cxx

+ 38 - 0
serialport/CMakeLists.txt

@@ -0,0 +1,38 @@
+# Sets the minimum version of CMake required to build your native library.
+# This ensures that a certain set of CMake features is available to
+# your build.
+
+cmake_minimum_required(VERSION 3.4.1)
+
+# Specifies a library name, specifies whether the library is STATIC or
+# SHARED, and provides relative paths to the source code. You can
+# define multiple libraries by adding multiple add.library() commands,
+# and CMake builds them for you. When you build your app, Gradle
+# automatically packages shared libraries with your APK.
+
+add_library( # Specifies the name of the library.
+             serial_port
+
+             # Sets the library as a shared library.
+             SHARED
+
+             # Provides a relative path to your source file(s).
+             src/main/cpp/SerialPort.c )
+             
+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} )

+ 41 - 0
serialport/build.gradle

@@ -0,0 +1,41 @@
+apply plugin: 'com.android.library'
+
+
+android {
+    compileSdkVersion rootProject.ext.compileSdkVersion
+
+    defaultConfig {
+        minSdkVersion rootProject.ext.minSdkVersion
+        targetSdkVersion rootProject.ext.targetSdkVersion
+        versionCode rootProject.ext.versionCode
+        versionName rootProject.ext.versionName
+
+        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path 'CMakeLists.txt'
+        }
+    }
+}
+
+dependencies {
+//    compile fileTree(dir: 'libs', include: ['*.jar'])
+//    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+//        exclude group: 'com.android.support', module: 'support-annotations'
+//    })
+//    compile 'com.android.support:appcompat-v7:25.3.0'
+//    testCompile 'junit:junit:4.12'
+
+
+    api "androidx.annotation:annotation:1.1.0"
+}
+
+
+

+ 4 - 0
serialport/src/main/AndroidManifest.xml

@@ -0,0 +1,4 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.serialport">
+    
+</manifest>

+ 247 - 0
serialport/src/main/cpp/SerialPort.c

@@ -0,0 +1,247 @@
+/*
+ * 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 <termios.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <jni.h>
+
+#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:     android_serialport_SerialPort
+ * Method:    open
+ * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
+ */
+JNIEXPORT jobject JNICALL Java_android_serialport_SerialPort_open
+        (JNIEnv *env, jobject thiz, jstring path, jint baudrate, jint dataBits, jint parity,
+         jint stopBits,
+         jint flags) {
+
+    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, "<init>", "()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:     cedric_serial_SerialPort
+ * Method:    close
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_android_serialport_SerialPort_close
+        (JNIEnv *env, jobject thiz) {
+    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);
+}
+

+ 40 - 0
serialport/src/main/cpp/SerialPort.h

@@ -0,0 +1,40 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class android_serialport_SerialPort */
+
+#ifndef _Included_android_serialport_SerialPort
+#define _Included_android_serialport_SerialPort
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     android_serialport_SerialPort
+ * Method:    open
+ * Signature: (Ljava/lang/String;IIIII)Ljava/io/FileDescriptor;
+ */
+JNIEXPORT jobject JNICALL Java_android_serialport_SerialPort_open
+  (JNIEnv *, jobject, jstring, jint, jint, jint, jint, jint);
+
+/*
+ * Class:     android_serialport_SerialPort
+ * Method:    close
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_android_serialport_SerialPort_close
+  (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* Header for class android_serialport_SerialPort_Builder */
+
+#ifndef _Included_android_serialport_SerialPort_Builder
+#define _Included_android_serialport_SerialPort_Builder
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif

+ 3 - 0
serialport/src/main/cpp/gen_SerialPort_h.sh

@@ -0,0 +1,3 @@
+#!/bin/sh
+javah -o SerialPort.h -jni -classpath ../java android.serialport.SerialPort
+

+ 302 - 0
serialport/src/main/java/android/serialport/SerialPort.java

@@ -0,0 +1,302 @@
+/*
+ * 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 android.serialport;
+
+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;
+
+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;
+    private int parity;
+    private int stopBits;
+    private int flags;
+
+    /**
+     * 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;
+    }
+
+    /*
+     * Do not remove or rename the field mFd: it is used by native method close();
+     */
+    private FileDescriptor mFd;
+    private FileInputStream mFileInputStream;
+    private FileOutputStream mFileOutputStream;
+
+    /**
+     * 串口
+     *
+     * @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) throws SecurityException, IOException {
+
+        this.device = device;
+        this.baudrate = baudrate;
+        this.dataBits = dataBits;
+        this.parity = parity;
+        this.stopBits = stopBits;
+        this.flags = flags;
+
+        /* Check access permission */
+        if (!device.canRead() || !device.canWrite()) {
+            try {
+                /* Missing read/write permission, trying to chmod the file */
+                Process su;
+                su = Runtime.getRuntime().exec(sSuPath);
+                String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n";
+                su.getOutputStream().write(cmd.getBytes());
+                if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {
+                    throw new SecurityException();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+                throw new SecurityException();
+            }
+        }
+
+        mFd = open(device.getAbsolutePath(), baudrate, dataBits, parity, stopBits, flags);
+        if (mFd == null) {
+            Log.e(TAG, "native open returns null");
+            throw new IOException();
+        }
+        mFileInputStream = new FileInputStream(mFd);
+        mFileOutputStream = new FileOutputStream(mFd);
+    }
+
+    /**
+     * 串口,默认的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);
+    }
+
+    // Getters and setters
+    @NonNull
+    public InputStream getInputStream() {
+        return mFileInputStream;
+    }
+
+    @NonNull
+    public OutputStream getOutputStream() {
+        return mFileOutputStream;
+    }
+
+    /** 串口设备文件 */
+    @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;
+    }
+
+    // JNI
+    private native FileDescriptor open(String absolutePath, int baudrate, int dataBits, int parity,
+        int stopBits, int flags);
+
+    public native void close();
+
+    /** 关闭流和串口,已经try-catch */
+    public void tryClose() {
+        try {
+            mFileInputStream.close();
+        } catch (IOException e) {
+            //e.printStackTrace();
+        }
+
+        try {
+            mFileOutputStream.close();
+        } catch (IOException e) {
+            //e.printStackTrace();
+        }
+
+        try {
+            close();
+        } catch (Exception e) {
+            //e.printStackTrace();
+        }
+    }
+
+    static {
+        System.loadLibrary("serial_port");
+    }
+
+    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() throws SecurityException, IOException {
+            return new SerialPort(device, baudrate, dataBits, parity, stopBits, flags);
+        }
+    }
+}

+ 128 - 0
serialport/src/main/java/android/serialport/SerialPortFinder.java

@@ -0,0 +1,128 @@
+/*
+ * 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 android.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;
+
+public class SerialPortFinder {
+
+    public class Driver {
+        public Driver(String name, String root) {
+            mDriverName = name;
+            mDeviceRoot = root;
+        }
+
+        private String mDriverName;
+        private String mDeviceRoot;
+        Vector<File> mDevices = null;
+
+        public Vector<File> getDevices() {
+            if (mDevices == null) {
+                mDevices = new Vector<File>();
+                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<Driver> mDrivers = null;
+
+    Vector<Driver> getDrivers() throws IOException {
+        if (mDrivers == null) {
+            mDrivers = new Vector<Driver>();
+            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<String> devices = new Vector<String>();
+        // Parse each driver
+        Iterator<Driver> itdriv;
+        try {
+            itdriv = getDrivers().iterator();
+            while (itdriv.hasNext()) {
+                Driver driver = itdriv.next();
+                Iterator<File> 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<String> devices = new Vector<String>();
+        // Parse each driver
+        Iterator<Driver> itdriv;
+        try {
+            itdriv = getDrivers().iterator();
+            while (itdriv.hasNext()) {
+                Driver driver = itdriv.next();
+                Iterator<File> 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()]);
+    }
+}

+ 3 - 0
serialport/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">SerialPortSource</string>
+</resources>

+ 17 - 0
settings.gradle

@@ -0,0 +1,17 @@
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+        google()
+        mavenCentral()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+rootProject.name = "LoraGateway"
+include ':app'
+include ':serialport'