From 302ab8c069827c399cdd2664c79126eda71fe2f9 Mon Sep 17 00:00:00 2001
From: endian11 <Dana_Lee1016@126.com>
Date: 星期一, 30 十二月 2019 18:00:27 +0800
Subject: [PATCH] Merge branch 'master' of https://gitee.com/endian11/DriveJudge

---
 lib/src/main/cpp/common/apptimer.h                               |   23 
 lib/src/main/cpp/native-lib.h                                    |   28 
 im_lib/.gitignore                                                |    1 
 lib/src/main/cpp/CMakeLists.txt                                  |   63 
 lib/src/main/cpp/test_items/park_bottom.cpp                      |  380 ++
 lib/src/main/cpp/utils/crc16.cpp                                 |   66 
 lib/src/main/cpp/mcu/mcu_if.h                                    |   16 
 lib/src/main/cpp/test_items/error_list.h                         |   17 
 lib/src/main/cpp/native-lib.cpp                                  |  464 +++
 lib/src/main/cpp/test_items/error_list.cpp                       |  207 +
 lib/src/main/cpp/rtk_module/parse_gps.cpp                        |  273 ++
 lib/src/main/java/com/anyun/exam/lib/util/DESUtil.java           |  124 
 lib/build.gradle                                                 |   15 
 im_lib/src/main/java/com/anyun/im_lib/im/IMSClientBootstrap.java |   61 
 lib/src/main/cpp/driver_test.h                                   |   52 
 lib/src/main/java/com/anyun/exam/lib/util/ByteUtil.java          |   16 
 lib/src/main/cpp/utils/num.cpp                                   |   76 
 lib/src/main/cpp/common/net.h                                    |   17 
 lib/src/main/cpp/rtk_module/parse_gps.h                          |   39 
 lib/src/main/cpp/common/net.cpp                                  |  482 +++
 im_lib/consumer-rules.pro                                        |    0 
 lib/src/main/cpp/mcu/mcu_if.cpp                                  |  306 ++
 lib/src/main/cpp/driver_test.cpp                                 |  616 ++++
 lib/src/main/cpp/test_items/stop_and_start.cpp                   |  227 +
 lib/src/main/cpp/Geometry.cpp                                    |  390 +++
 lib/src/main/cpp/utils/num.h                                     |   13 
 lib/src/main/java/com/anyun/exam/lib/util/NetUtils.java          |  221 +
 lib/src/main/cpp/rtk_module/rtk.cpp                              |  421 +++
 lib/src/main/cpp/test_items/driving_curve.h                      |   17 
 lib/src/main/cpp/Geometry.h                                      |   58 
 lib/src/main/java/com/anyun/exam/lib/util/Speaker.java           |   44 
 lib/src/main/cpp/test_items/park_edge.cpp                        |  274 ++
 lib/src/main/cpp/defs.h                                          |   61 
 lib/src/main/cpp/utils/crc16.h                                   |   12 
 lib/src/main/cpp/common/serial_port.cpp                          |  306 ++
 lib/src/main/cpp/rtk_platform/platform.cpp                       |  361 ++
 lib/src/main/cpp/test_items/driving_curve.cpp                    |  140 +
 lib/src/main/cpp/rtk_module/rtk.h                                |   22 
 lib/src/main/cpp/rtk_platform/parse_net.cpp                      |  994 +++++++
 lib/src/main/cpp/test_items/park_edge.h                          |   19 
 im_lib/src/main/java/com/anyun/im_lib/IMSClientFactory.java      |   16 
 lib/src/main/cpp/test_items/park_bottom.h                        |   18 
 lib/src/main/cpp/common/serial_port.h                            |   30 
 .idea/codeStyles/Project.xml                                     |  134 
 lib/src/main/cpp/test_items/turn_a90.cpp                         |  172 +
 lib/src/main/cpp/common/apptimer.cpp                             |  188 +
 lib/src/main/cpp/jni_log.h                                       |   28 
 lib/src/main/cpp/rtk_platform/platform.h                         |   25 
 lib/src/main/cpp/test_items/turn_a90.h                           |   17 
 lib/src/main/cpp/rtk_platform/parse_net.h                        |   55 
 lib/src/main/cpp/test_items/stop_and_start.h                     |   16 
 lib/src/main/java/com/anyun/exam/lib/RemoteService.java          |   75 
 52 files changed, 7,670 insertions(+), 26 deletions(-)

diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 30aa626..ae78c11 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,29 +1,113 @@
 <component name="ProjectCodeStyleConfiguration">
   <code_scheme name="Project" version="173">
-    <Objective-C-extensions>
-      <file>
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
-      </file>
-      <class>
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
-      </class>
-      <extensions>
-        <pair source="cpp" header="h" fileNamingConvention="NONE" />
-        <pair source="c" header="h" fileNamingConvention="NONE" />
-      </extensions>
-    </Objective-C-extensions>
+    <codeStyleSettings language="XML">
+      <arrangement>
+        <rules>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:android</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:id</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>style</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>ANDROID_ATTRIBUTE_ORDER</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>.*</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+        </rules>
+      </arrangement>
+    </codeStyleSettings>
   </code_scheme>
 </component>
\ No newline at end of file
diff --git a/im_lib/.gitignore b/im_lib/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/im_lib/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/im_lib/consumer-rules.pro b/im_lib/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/im_lib/consumer-rules.pro
diff --git a/im_lib/src/main/java/com/anyun/im_lib/IMSClientFactory.java b/im_lib/src/main/java/com/anyun/im_lib/IMSClientFactory.java
new file mode 100644
index 0000000..d3f69dc
--- /dev/null
+++ b/im_lib/src/main/java/com/anyun/im_lib/IMSClientFactory.java
@@ -0,0 +1,16 @@
+package com.anyun.im_lib;
+
+import com.anyun.im_lib.interf.IMSClientInteface;
+import com.anyun.im_lib.netty.NettyTcpClient;
+
+/**
+ * MyApplication2
+ * Created by lzw on 2019/12/2. 13:13:44
+ * 閭锛�632393724@qq.com
+ * All Rights Saved! Chongqing AnYun Tech co. LTD
+ */
+public class IMSClientFactory {
+    public static IMSClientInteface getIMSClient() {
+        return NettyTcpClient.getInstance();
+    }
+}
diff --git a/im_lib/src/main/java/com/anyun/im_lib/im/IMSClientBootstrap.java b/im_lib/src/main/java/com/anyun/im_lib/im/IMSClientBootstrap.java
new file mode 100644
index 0000000..d4d001c
--- /dev/null
+++ b/im_lib/src/main/java/com/anyun/im_lib/im/IMSClientBootstrap.java
@@ -0,0 +1,61 @@
+package com.anyun.im_lib.im;
+
+import android.util.Log;
+
+import com.anyun.im_lib.IMSClientFactory;
+import com.anyun.im_lib.interf.IMSClientInteface;
+
+import java.util.Vector;
+
+import static android.content.ContentValues.TAG;
+
+/**
+ * MyApplication2
+ * Created by lzw on 2019/12/2. 11:56:55
+ * 閭锛�632393724@qq.com
+ * All Rights Saved! Chongqing AnYun Tech co. LTD
+ */
+public class IMSClientBootstrap {
+
+    private static final IMSClientBootstrap INSTANCE= new IMSClientBootstrap();
+    private IMSClientInteface imsClient;
+
+    /**鏍囪IMSClientBootstrap鏄惁宸茬粡鍒濆鍖�**/
+    private boolean isAlive;
+
+    /**
+     * 
+     * @param userId
+     * @param token
+     * @param hosts
+     * @param appStatus
+     */
+    public synchronized void init(String userId,String token,String hosts,int appStatus){
+        if (!isAlive){
+            Vector<String> serverUrlList = convertHosts(hosts);
+            if (serverUrlList == null || serverUrlList.size() ==0){
+                Log.i(TAG, "init IMLibClientBootstrap error,ims hosts is null");
+                return;
+            }
+            isAlive = true;
+            Log.i(TAG, "init IMLibClientBootstrap ,server="+hosts);
+        }
+        if (null != imsClient){
+            imsClient.close();
+        }
+        //鍒濆鍖朓MSClientInteface
+        imsClient = IMSClientFactory.getIMSClient();
+        updateAppStatus(appStatus);
+//        imsClient.init(serverUrlList,new OnEventListener(userId,token),new IMSConnectStatusCallback());
+    }
+
+
+
+    private void updateAppStatus(int appStatus) {
+    }
+
+    private Vector<String> convertHosts(String hosts) {
+        //TODO
+        return null;
+    }
+}
diff --git a/lib/build.gradle b/lib/build.gradle
index b7ce1a3..fb084b3 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -12,6 +12,16 @@
 
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
 
+        externalNativeBuild {
+            cmake {
+                cppFlags ""
+            }
+        }
+        ndk {
+            // Specifies the ABI configurations of your native
+            // libraries Gradle should build and package with your APK.
+            abiFilters 'armeabi-v7a','arm64-v8a'
+        }
     }
 
     buildTypes {
@@ -21,6 +31,11 @@
         }
     }
 
+    externalNativeBuild {
+        cmake {
+            path "src/main/cpp/CMakeLists.txt"
+        }
+    }
 }
 
 dependencies {
diff --git a/lib/src/main/cpp/CMakeLists.txt b/lib/src/main/cpp/CMakeLists.txt
new file mode 100644
index 0000000..9cebae4
--- /dev/null
+++ b/lib/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,63 @@
+# For more information about using CMake with Android Studio, read the
+# documentation: https://d.android.com/studio/projects/add-native-code.html
+
+# Sets the minimum version of CMake required to build the native library.
+
+cmake_minimum_required(VERSION 3.4.1)
+
+# Creates and names a library, sets it as either STATIC
+# or SHARED, and provides the relative paths to its source code.
+# You can define multiple libraries, and CMake builds them for you.
+# Gradle automatically packages shared libraries with your APK.
+
+add_library( # Sets the name of the library.
+        native-lib
+
+        # Sets the library as a shared library.
+        SHARED
+
+        # Provides a relative path to your source file(s).
+        native-lib.cpp
+        common/serial_port.cpp
+        common/net.cpp
+        common/apptimer.cpp
+        rtk_platform/parse_net.cpp
+        rtk_platform/platform.cpp
+        rtk_module/parse_gps.cpp
+        Geometry.cpp
+        driver_test.cpp
+        mcu/mcu_if.cpp
+        test_items/error_list.cpp
+        test_items/park_edge.cpp
+        test_items/park_bottom.cpp
+        test_items/stop_and_start.cpp
+        test_items/driving_curve.cpp
+        test_items/turn_a90.cpp
+        rtk_module/rtk.cpp
+
+        utils/crc16.cpp
+        utils/num.cpp)
+
+# Searches for a specified prebuilt library and stores the path as a
+# variable. Because CMake includes system libraries in the search path by
+# default, you only need to specify the name of the public NDK library
+# you want to add. CMake verifies that the library exists before
+# completing its build.
+
+find_library( # Sets the name of the path variable.
+        log-lib
+
+        # Specifies the name of the NDK library that
+        # you want CMake to locate.
+        log)
+
+# Specifies libraries CMake should link to your target library. You
+# can link multiple libraries, such as libraries you define in this
+# build script, prebuilt third-party libraries, or system libraries.
+
+target_link_libraries( # Specifies the target library.
+        native-lib
+
+        # Links the target library to the log library
+        # included in the NDK.
+        ${log-lib})
\ No newline at end of file
diff --git a/lib/src/main/cpp/Geometry.cpp b/lib/src/main/cpp/Geometry.cpp
new file mode 100644
index 0000000..39531c1
--- /dev/null
+++ b/lib/src/main/cpp/Geometry.cpp
@@ -0,0 +1,390 @@
+//
+// Created by YY on 2019/4/30.
+//
+
+#include "defs.h"
+#include "Geometry.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <cmath>
+#include <jni.h>
+#include <malloc.h>
+#include <initializer_list>
+
+#include "jni_log.h"
+
+using namespace std;
+
+const double EPSILON = 1e-6;
+
+inline bool isEqual(double a, double b)
+{
+    return (fabs(a - b) <= EPSILON);
+}
+
+inline double toRadians(double degree)
+{
+    return (degree * M_PI) / 180.0;
+}
+
+inline double toDegree(double radians)
+{
+    return (radians * 180.0) / M_PI;
+}
+
+void MakeLine(Line *line, const PointF *p1, const PointF *p2)
+{
+    line->X1 = p1->X;
+    line->Y1 = p1->Y;
+    line->X2 = p2->X;
+    line->Y2 = p2->Y;
+}
+
+void MakePolygon(Polygon *polygon, std::initializer_list<PointF> point_set)
+{
+    int n = 0;
+
+    polygon->point = (PointF *)malloc(point_set.size() * sizeof(PointF));
+
+    for (auto ptr : point_set) {
+        polygon->point[n++] = ptr;
+    }
+    polygon->num = n;
+}
+
+void MakeHidePoint(PointF *point, const PointF *bp, const Line *bl)
+{
+    point->X = (bl->X1 + bl->X2) - bp->X;
+    point->Y = (bl->Y1 + bl->Y2) - bp->Y;
+}
+
+void CleanPolygon(Polygon *polygon)
+{
+    if (polygon->point != NULL) {
+        free(polygon->point);
+        polygon->point = NULL;
+    }
+    polygon->num = 0;
+}
+
+Relation IntersectionOf(const Polygon *polygon1, const Polygon *polygon2)
+{
+    bool inside = false, outside = false, tangent = false;
+
+    if (polygon1->num == 0 || polygon2->num == 0) {
+        return GM_None;
+    }
+
+    for (int idx = 0; idx < polygon1->num; ++idx) {
+        Relation relation = IntersectionOf(polygon1->point[idx], polygon2);
+
+        if (relation == GM_Containment) {
+            inside = true;
+        } else if (relation == GM_None) {
+            outside = true;
+        } else {
+            tangent = true;
+        }
+
+        if (inside && outside) {
+            break;
+        }
+    }
+
+    if (inside && outside) {
+        return GM_Intersection;
+    } else if (tangent) {
+        return GM_Tangent;
+    } else if (inside) {
+        return GM_Containment;
+    }
+    return GM_None;
+}
+
+Relation IntersectionOf(Line line, const Polygon *polygon)
+{
+    if (polygon->num == 0) {
+        return GM_None;
+    }
+
+    if (polygon->num == 1) {
+        return IntersectionOf(polygon->point[0], line);
+    }
+
+    bool tangent = false;
+    for (int index = 0; index < polygon->num; index++) {
+        int index2 = (index + 1) % polygon->num;
+        Line line2;
+
+        line2.X1 = polygon->point[index].X;
+        line2.Y1 = polygon->point[index].Y;
+        line2.X2 = polygon->point[index2].X;
+        line2.Y2 = polygon->point[index2].Y;
+
+//        LOGD("line1(%d %d - %d %d) line2(%d %d - %d %d)", line.X1, line.Y1, line.X2, line.Y2,
+//             line2.X1, line2.Y1, line2.X2, line2.Y2);
+
+        Relation relation = IntersectionOf(line, line2);
+
+//        LOGD("relation = %d", relation);
+
+        if (relation == GM_Intersection) {
+            return relation;
+        }
+        if (relation == GM_Tangent) {
+            tangent = true;
+        }
+    }
+
+    PointF point2;
+
+    point2.X = line.X1;
+    point2.Y = line.Y1;
+
+    return tangent ? GM_Tangent : IntersectionOf(point2, polygon);
+}
+
+Relation IntersectionOf(PointF point, const Polygon *polygon)
+{
+    switch (polygon->num)
+    {
+        case 0:
+            return GM_None;
+        case 1:
+            if (isEqual(polygon->point[0].X, point.X) &&
+                isEqual(polygon->point[0].Y, point.Y)) {
+                return GM_Tangent;
+            }
+            else {
+                return GM_None;
+            }
+        case 2: {
+            Line line2;
+
+            line2.X1 = polygon->point[0].X;
+            line2.Y1 = polygon->point[0].Y;
+            line2.X2 = polygon->point[1].X;
+            line2.Y2 = polygon->point[1].Y;
+            return IntersectionOf(point, line2);
+        }
+        default:
+            break;
+    }
+
+    int counter = 0;
+    int i;
+    PointF p1;
+    int n = polygon->num;
+
+    p1.X = polygon->point[0].X;
+    p1.Y = polygon->point[0].Y;
+
+    if (isEqual(point.X, p1.X) && isEqual(point.Y, p1.Y)) {
+        return GM_Tangent;
+    }
+
+    for (i = 1; i <= n; i++) {
+        PointF p2;
+
+        p2.X = polygon->point[i % n].X;
+        p2.Y = polygon->point[i % n].Y;
+
+        if (isEqual(point.X, p2.X) && isEqual(point.Y, p2.Y)) {
+            return GM_Tangent;
+        }
+
+        if (point.Y > fmin(p1.Y, p2.Y)) {
+            if (point.Y <= fmax(p1.Y, p2.Y)) {
+                if (point.X <= fmax(p1.X, p2.X)) {
+                    if (p1.Y != p2.Y) {
+                        double xinters = (point.Y - p1.Y) * (p2.X - p1.X) / (p2.Y - p1.Y) + p1.X;
+
+                        if (isEqual(p1.X, p2.X) || point.X <= xinters)
+                            counter++;
+                    }
+                }
+            }
+        }
+        p1 = p2;
+    }
+
+    return (counter % 2 == 1) ? GM_Containment : GM_None;
+}
+
+Relation IntersectionOf(PointF point, Line line)
+{
+    double bottomY = fmin(line.Y1, line.Y2);
+    double topY = fmax(line.Y1, line.Y2);
+    bool heightIsRight = point.Y >= bottomY &&
+                         point.Y <= topY;
+    //Vertical line, slope is divideByZero error!
+    if (isEqual(line.X1, line.X2)) {
+        if (isEqual(point.X, line.X1) && heightIsRight) {
+            return GM_Tangent;
+        } else {
+            return GM_None;
+        }
+    }
+
+    double slope = (line.X2 - line.X1) / (line.Y2 - line.Y1);
+    bool onLine = isEqual(line.Y1 - point.Y, slope * (line.X1 - point.X));
+
+    if (onLine && heightIsRight) {
+        return GM_Tangent;
+    } else {
+        return GM_None;
+    }
+}
+
+Relation IntersectionOf(Line line1, Line line2)
+{
+    //  Fail if either line segment is zero-length.
+    if ((isEqual(line1.X1, line1.X2) && isEqual(line1.Y1, line1.Y2)) || (isEqual(line2.X1, line2.X2) && isEqual(line2.Y1, line2.Y2)))
+        return GM_None;
+
+    if ((isEqual(line1.X1, line2.X1) && isEqual(line1.Y1, line2.Y1)) || (isEqual(line1.X2, line2.X1) && isEqual(line1.Y2, line2.Y1)))
+        return GM_Intersection;
+
+    if ((isEqual(line1.X1, line2.X2) && isEqual(line1.Y1, line2.Y2)) || (isEqual(line1.X2, line2.X2) && isEqual(line1.Y2, line2.Y2)))
+        return GM_Intersection;
+
+    //  (1) Translate the system so that point A is on the origin.
+    line1.X2 -= line1.X1; line1.Y2 -= line1.Y1;
+    line2.X1 -= line1.X1; line2.Y1 -= line1.Y1;
+    line2.X2 -= line1.X1; line2.Y2 -= line1.Y1;
+
+    //  Discover the length of segment A-B.
+    double distAB = sqrt(line1.X2 * line1.X2 + line1.Y2 * line1.Y2);
+
+    //  (2) Rotate the system so that point B is on the positive X axis.
+    double theCos = line1.X2 / distAB;
+    double theSin = line1.Y2 / distAB;
+    double newX = line2.X1 * theCos + line2.Y1 * theSin;
+
+    line2.Y1 = line2.Y1 * theCos - line2.X1 * theSin;
+    line2.X1 = newX;
+    newX = line2.X2 * theCos + line2.Y2 * theSin;
+    line2.Y2 = line2.Y2 * theCos - line2.X2 * theSin;
+    line2.X2 = newX;
+
+    //  Fail if segment C-D doesn't cross line A-B.
+    if ((line2.Y1 < 0 && line2.Y2 < 0) || (line2.Y1 >= 0 && line2.Y2 >= 0)) {
+        return GM_None;
+    }
+
+    //  (3) Discover the position of the intersection point along line A-B.
+    double posAB = line2.X2 + (line2.X1 - line2.X2) * line2.Y2 / (line2.Y2 - line2.Y1);
+
+    //  Fail if segment C-D crosses line A-B outside of segment A-B.
+    if (posAB < 0 || posAB > distAB) {
+        return GM_None;
+    }
+    //  (4) Apply the discovered position to line A-B in the original coordinate system.
+    return GM_Intersection;
+}
+
+double DistanceOf(PointF point1, PointF point2)
+{
+    return sqrt((point1.X-point2.X)*(point1.X-point2.X) + (point1.Y-point2.Y)*(point1.Y-point2.Y));
+}
+
+double DistanceOf(PointF point, Line line)
+{
+//    float a = sqrt((point.X-line.X1)*(point.X-line.X1) + (point.Y-line.Y1)*(point.Y-line.Y1));
+//    float b = sqrt((point.X-line.X2)*(point.X-line.X2) + (point.Y-line.Y2)*(point.Y-line.Y2));
+    double c = sqrt((line.X1-line.X2)*(line.X1-line.X2) + (line.Y1-line.Y2)*(line.Y1-line.Y2));
+
+//    float p = (a+b+c)/2;
+
+//    dis = 2 * sqrt(p*(p-a)*(p-b)*(p-c)) / c;
+
+    return fabs(point.X*line.Y1 + point.Y*line.X2 + line.X1*line.Y2 - line.X2*line.Y1 - line.X1*point.Y - point.X*line.Y2) / c;
+}
+
+/**********************************************************
+ * base 鍜� dest鐨勭浜岀偣閲嶅悎鏃跺舰鎴愮殑澶硅
+ * @param base
+ * @param dest
+ * @return
+ */
+double CalculateAngle(Line base, Line dest)
+{
+    double angle = 0;
+
+    double dx = base.X2 - dest.X2;
+    double dy = base.Y2 - dest.Y2;
+
+    dest.X1 += dx;
+    dest.Y1 += dy;
+
+    double c2 = pow((dest.X1 - base.X1), 2) + pow((dest.Y1 - base.Y1), 2);
+    double a2 = pow((base.X1 - base.X2), 2) + pow((base.Y1 - base.Y2), 2);
+    double b2 = pow((dest.X1 - base.X2), 2) + pow((dest.Y1 - base.Y2), 2);
+
+    angle = acos((a2 + b2 - c2) / (2 * sqrt(a2) * sqrt(b2)));
+
+    return toDegree(angle);
+}
+
+PointF rotatePoint(PointF oldPoint, PointF centre, double degree) {
+    PointF newPoint;
+    newPoint.X = (oldPoint.X-centre.X)*cos(toRadians(degree)) - (oldPoint.Y-centre.Y)*sin(toRadians(degree)) + centre.X;
+    newPoint.Y = (oldPoint.X-centre.X)*sin(toRadians(degree)) + (oldPoint.Y-centre.Y)*cos(toRadians(degree)) + centre.Y;
+    return newPoint;
+}
+
+// All in
+bool InsidePolygon(const Polygon *t1, const Polygon *t2) {
+    for (int i = 0; i < t1->num; ++i) {
+        if (IntersectionOf(t1->point[i], t2) != GM_Containment) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// Any part inside
+bool PartInsidePolygon(const Polygon *t1, const Polygon *t2) {
+    for (int i = 0; i < t1->num; ++i) {
+        if (IntersectionOf(t1->point[i], t2) == GM_Containment) {
+            return true;
+        }
+    }
+    return false;
+}
+
+// All out
+bool OutsidePolygon(const Polygon *t1, const Polygon *t2) {
+    for (int i = 0; i < t1->num; ++i) {
+        if (IntersectionOf(t1->point[i], t2) != GM_None) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/***************************************************************
+ * @brief p3浣嶄簬鐢眕1->p2鏋勬垚鐨勫皠绾匡紝宸︿晶杩樻槸鍙充晶锛屽悓鍚戯紝鍙嶅悜
+ * @param p1
+ * @param p2
+ * @param p3
+ * @return 0 - straight, 1 - left, -1 - right, 2 - front, -2 - back
+ */
+int IntersectionOfLine(PointF p1, PointF p2, PointF p3)
+{
+    double lr = (p1.X-p3.X)*(p2.Y-p3.Y) - (p1.Y-p3.Y)*(p2.X-p3.X);
+
+    if (fabs(lr) <= EPSILON) {
+        double fb = (p2.X-p1.X)*(p3.X-p1.X) + (p2.Y-p1.Y)*(p3.Y-p1.Y);
+        if (fabs(fb) <= EPSILON)
+            return 0;
+        else if (fb > 0)
+            return 2;
+        else
+            return -2;
+    } else if (lr > 0) {
+        return 1;
+    } else {
+        return -1;
+    }
+}
diff --git a/lib/src/main/cpp/Geometry.h b/lib/src/main/cpp/Geometry.h
new file mode 100644
index 0000000..12db160
--- /dev/null
+++ b/lib/src/main/cpp/Geometry.h
@@ -0,0 +1,58 @@
+//
+// Created by YY on 2019/4/30.
+//
+
+#ifndef GUI_GEOMETRY_H
+#define GUI_GEOMETRY_H
+
+#include <stdint.h>
+#include <initializer_list>
+
+enum Relation
+{
+    GM_None,
+    GM_Tangent,
+    GM_Intersection,
+    GM_Containment
+};
+
+typedef struct PointF_ {
+    double X;
+    double Y;
+} PointF;
+
+typedef struct Line_ {
+    double X1;
+    double Y1;
+    double X2;
+    double Y2;
+} Line;
+
+typedef struct Polygon_ {
+    int num;
+    PointF *point;
+} Polygon;
+
+inline double toRadians(double degree);
+inline double toDegree(double radians);
+inline bool isEqual(double a, double b);
+void MakeLine(Line *line, const PointF *p1, const PointF *p2);
+void MakePolygon(Polygon *polygon, std::initializer_list<PointF> point_set);
+void CleanPolygon(Polygon *polygon);
+void MakeHidePoint(PointF *point, const PointF *bp, const Line *bl);
+
+Relation IntersectionOf(const Polygon *polygon1, const Polygon *polygon2);
+Relation IntersectionOf(Line line, const Polygon *polygon);
+Relation IntersectionOf(PointF point, const Polygon *polygon);
+Relation IntersectionOf(PointF point, Line line);
+Relation IntersectionOf(Line line1, Line line2);
+double DistanceOf(PointF point1, PointF point2);
+double DistanceOf(PointF point, Line line);
+double CalculateAngle(Line base, Line dest);
+PointF rotatePoint(PointF oldPoint, PointF centre, double degree);
+bool InsidePolygon(const Polygon *t1, const Polygon *t2);
+bool PartInsidePolygon(const Polygon *t1, const Polygon *t2);
+bool OutsidePolygon(const Polygon *t1, const Polygon *t2);
+int IntersectionOfLine(PointF p1, PointF p2, PointF p3);
+
+#endif //GUI_GEOMETRY_H
diff --git a/lib/src/main/cpp/common/apptimer.cpp b/lib/src/main/cpp/common/apptimer.cpp
new file mode 100644
index 0000000..b37cd86
--- /dev/null
+++ b/lib/src/main/cpp/common/apptimer.cpp
@@ -0,0 +1,188 @@
+//POSIX.1b Timer
+#include <jni.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <errno.h>
+#include <time.h>
+#include "../jni_log.h"
+#include "apptimer.h"
+
+#define MAX_TIMER    32
+
+static struct {
+    timer_t timerId;
+
+    void (*func)(union sigval sig);
+
+    int value;
+    uint8_t *user_data;
+} AppTimer[MAX_TIMER];
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static int createTimer(timer_t *timerId, void (*func)(union sigval sig),
+                       int value,
+                       uint8_t *usr_data, int usr_data_length,
+                       uint8_t **usr_data_ptr) {
+    struct sigevent sev;
+    pthread_attr_t attr;
+
+    // Register printMsg to SIGALRM
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//detached
+
+    memset(&sev, 0, sizeof(sev));
+
+    sev.sigev_notify = SIGEV_THREAD;
+    sev.sigev_notify_function = func;
+    sev.sigev_notify_attributes = &attr;
+
+    if (usr_data != NULL && usr_data_length != 0) {
+        if ((sev.sigev_value.sival_ptr = malloc(usr_data_length)) != NULL) {
+            *usr_data_ptr = (uint8_t *) sev.sigev_value.sival_ptr;
+            memcpy(sev.sigev_value.sival_ptr, usr_data, usr_data_length);            //Copy usr data
+            sev.sigev_value.sival_int = usr_data_length;
+        } else {
+            return -1;
+        }
+    } else {
+        sev.sigev_value.sival_int = value;
+    }
+
+    /* create timer */
+    if (timer_create(CLOCK_REALTIME, &sev, timerId) == -1) {
+        return -1;
+    }
+//    LOGD("timer_create\n");
+    return 0;
+}
+
+static int setTimer(timer_t timerId, int timeMSec) {
+    struct itimerspec its;
+
+    /* Start the timer */
+    its.it_value.tv_sec = timeMSec / 1000;
+    its.it_value.tv_nsec = (timeMSec % 1000) * 1000000;
+
+    its.it_interval.tv_sec = 0;
+    its.it_interval.tv_nsec = 0;
+
+    if (timer_settime(timerId, 0, &its, NULL) == -1) {
+        return -1;
+    }
+//    LOGD("timer_settime\n");
+    return 0;
+}
+
+void AppTimer_Init(void) {
+    memset(AppTimer, 0, sizeof(AppTimer));
+    pthread_mutex_init(&mutex, NULL);
+}
+
+void AppTimer_add(void (*func)(union sigval), int timeMS) {
+    int i;
+//	LOGD("AppTimer_add\n");
+    pthread_mutex_lock(&mutex);
+    for (i = 0; i < MAX_TIMER; i++) {
+        if (AppTimer[i].func == NULL) {
+            if (createTimer(&(AppTimer[i].timerId), func, 0, NULL, 0, &(AppTimer[i].user_data)) == 0) {
+                AppTimer[i].func = func;
+
+                if (setTimer(AppTimer[i].timerId, timeMS) !=
+                    0) {            //Set timer fail, delele it
+                    timer_delete(AppTimer[i].timerId);
+                    if (AppTimer[i].user_data != NULL) {
+                        free(AppTimer[i].user_data);
+                        AppTimer[i].user_data = NULL;
+                    }
+                    AppTimer[i].func = NULL;
+                }
+            }
+            break;
+        }
+    }
+    pthread_mutex_unlock(&mutex);
+}
+
+void AppTimer_add(void (*func)(union sigval), int timeMS, uint8_t *data, int length) {
+    int i;
+//	LOGD("AppTimer_add\n");
+    pthread_mutex_lock(&mutex);
+    for (i = 0; i < MAX_TIMER; i++) {
+        if (AppTimer[i].func == NULL) {
+            if (createTimer(&(AppTimer[i].timerId), func, 0, data, length, &(AppTimer[i].user_data)) == 0) {
+                AppTimer[i].func = func;
+
+                if (setTimer(AppTimer[i].timerId, timeMS) !=
+                    0) {            //Set timer fail, delele it
+                    timer_delete(AppTimer[i].timerId);
+                    if (AppTimer[i].user_data != NULL) {
+                        free(AppTimer[i].user_data);
+                        AppTimer[i].user_data = NULL;
+                    }
+                    AppTimer[i].func = NULL;
+                }
+            }
+            break;
+        }
+    }
+    pthread_mutex_unlock(&mutex);
+}
+
+void AppTimer_add(void (*func)(union sigval), int timeMS, int value) {
+    int i;
+//	LOGD("AppTimer_add\n");
+    pthread_mutex_lock(&mutex);
+    for (i = 0; i < MAX_TIMER; i++) {
+        if (AppTimer[i].func == NULL) {
+            if (createTimer(&(AppTimer[i].timerId), func, value, NULL, 0, &(AppTimer[i].user_data)) == 0) {
+                AppTimer[i].func = func;
+
+                if (setTimer(AppTimer[i].timerId, timeMS) !=
+                    0) {            //Set timer fail, delele it
+                    timer_delete(AppTimer[i].timerId);
+                    if (AppTimer[i].user_data != NULL) {
+                        free(AppTimer[i].user_data);
+                        AppTimer[i].user_data = NULL;
+                    }
+                    AppTimer[i].func = NULL;
+                }
+            }
+            break;
+        }
+    }
+    pthread_mutex_unlock(&mutex);
+}
+
+void AppTimer_delete(void (*func)(union sigval)) {
+    int i;
+//	LOGD("AppTimer_delete\n");
+    pthread_mutex_lock(&mutex);
+    for (i = 0; i < MAX_TIMER; i++) {
+        if (AppTimer[i].func == func) {
+            timer_delete(AppTimer[i].timerId);
+            if (AppTimer[i].user_data != NULL) {
+                free(AppTimer[i].user_data);
+                AppTimer[i].user_data = NULL;
+            }
+            AppTimer[i].func = NULL;
+        }
+    }
+    pthread_mutex_unlock(&mutex);
+}
+
+uint32_t AppTimer_GetTickCount(void)
+{
+    struct timespec ts;
+
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+
+    return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
+}
diff --git a/lib/src/main/cpp/common/apptimer.h b/lib/src/main/cpp/common/apptimer.h
new file mode 100644
index 0000000..89dab30
--- /dev/null
+++ b/lib/src/main/cpp/common/apptimer.h
@@ -0,0 +1,23 @@
+#ifndef _APPTIMER_H_
+#define _APPTIMER_H_
+
+#include <stdint.h>
+#include <signal.h>
+
+#define D_SEC(n)		((n)*1000UL)
+#define D_MIN(n)		((n)*1000UL*60UL)
+#define D_HOUR(n)		((n)*1000UL*60UL*60UL)
+
+extern void AppTimer_Init(void);
+
+extern void AppTimer_add(void (*func)(union sigval), int timeMS);
+
+extern void AppTimer_add(void (*func)(union sigval), int timeMS, uint8_t *data, int length);
+
+extern void AppTimer_add(void (*func)(union sigval), int timeMS, int value);
+
+extern void AppTimer_delete(void (*func)(union sigval));
+
+extern uint32_t AppTimer_GetTickCount(void);
+
+#endif
diff --git a/lib/src/main/cpp/common/net.cpp b/lib/src/main/cpp/common/net.cpp
new file mode 100644
index 0000000..1ccea8c
--- /dev/null
+++ b/lib/src/main/cpp/common/net.cpp
@@ -0,0 +1,482 @@
+//
+// Created by YY on 2019/9/29.
+//
+
+#include <netdb.h>
+#include <cstring>
+#include <netinet/tcp.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <cstdio>
+#include <cerrno>
+#include <sys/select.h>
+#include <arpa/inet.h>
+#include <regex>
+#include <pthread.h>
+
+#include "net.h"
+#include "../jni_log.h"
+
+using namespace std;
+
+/*************************************************************
+	host_name:domain name
+	net_addr:return numbers-and-dots notation
+*/
+int GetHostIP(const char *host_name, char *net_addr)
+{
+    struct hostent *hptr;
+    char **pptr;
+    char str[32];
+    bool found_first = false;
+
+    if ((hptr = gethostbyname(host_name)) == NULL) {
+        LOGE("gethostbyname error\n");
+        return -1;
+    }
+
+    LOGD("official hostname: %s\n", hptr->h_name);
+
+    for (pptr = hptr->h_aliases; *pptr != NULL; pptr++) {
+        LOGD("alias:%s\n", *pptr);
+    }
+
+    switch(hptr->h_addrtype)
+    {
+        case AF_INET:
+        {
+            for (pptr = hptr->h_addr_list; *pptr != NULL; pptr++) {
+                LOGD("addrsss:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
+                if (!found_first) {
+                    strcpy(net_addr, str);
+                    found_first = true;
+                }
+            }
+            break;
+        }
+        case AF_INET6:
+        default:
+            LOGD("unknown address type\n");
+            break;
+    }
+
+    if (found_first == true) {
+        return 0;
+    }
+    return -2;
+}
+
+static bool is_domain_name(const char *ip)
+{
+    regex pattern0("^((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}$");
+    string target(ip);
+
+    return !regex_match(target, pattern0);
+}
+
+static int socket_set_keepalive(int fd)
+{
+    int alive, idle, cnt, intv;
+
+//    int value, value_len;
+//    getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &value_len);
+//    LOGD("keepalive 0 %d", value);
+
+//    value = 128;
+//    setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value));
+//    LOGD("keepalive 1 %d", value);
+
+//    getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &value_len);
+//    LOGD("keepalive 1 %d", value);
+
+
+    /* Set: use keepalive on fd, default 0 */
+    alive = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &alive, sizeof(alive)) != 0)
+    {
+        LOGE("TCP Set keepalive error");
+        return -1;
+    }
+    /* 20 Seconds not data, send keeplive packet, default 7200 */
+    idle = 20;
+    if (setsockopt (fd, SOL_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)) != 0)
+    {
+        LOGE("TCP Set keepalive idle error");
+        return -1;
+    }
+    /* If not recv respond, After 5 seconds retry, default 75 */
+    intv = 5;
+    if (setsockopt (fd, SOL_TCP, TCP_KEEPINTVL, &intv, sizeof(intv)) != 0)
+    {
+        LOGE("TCP Set keepalive intv error");
+        return -1;
+    }
+    /* If try 9 times and fail, we consider the tcp is disconnected, default 9 */
+    cnt = 9;
+    if (setsockopt (fd, SOL_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt)) != 0)
+    {
+        LOGE("TCP Set keepalive cnt error");
+        return -1;
+    }
+
+/*    int timeout = 10000;        // 10绉�
+    if (setsockopt (fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout)) != 0)
+    {
+        LOGE("TCP Set keepalive timeout error");
+        return -1;
+    }*/
+
+    LOGD("TCP Set keepalive OK");
+
+    return 0;
+}
+
+static int tcp_connect(char *ip, uint16_t port)
+{
+    struct sockaddr_in server_sockaddr;
+    int soc;
+    int ret, fds_ret;
+    struct timeval tv;
+    fd_set rdfds;
+    long arg;
+    int error_value;
+    socklen_t error_value_len;
+
+    LOGI("%s", __func__);
+
+    error_value_len = sizeof( error_value );
+
+    if((soc = socket(PF_INET, SOCK_STREAM, 0)) == -1)
+    {
+        LOGE("%s: socket", __func__);
+        return -1;
+    }
+
+    server_sockaddr.sin_family = AF_INET;
+    server_sockaddr.sin_addr.s_addr = inet_addr(ip);//htonl(ip);//inet_addr("192.168.1.37");
+    server_sockaddr.sin_port = htons(port);
+    bzero(&server_sockaddr.sin_zero, 8);
+
+    // Set non-blocking
+    if( (arg = fcntl(soc, F_GETFL, NULL)) < 0)
+    {
+        LOGE("%s: fcntl( F_GETFL ) error", __func__);
+        goto TCP_CONNECT_1;
+    }
+
+    if( fcntl(soc, F_SETFL, arg | O_NONBLOCK) < 0)
+    {
+        LOGE( "%s: fcntl( F_SETFL ) error", __func__);
+        goto TCP_CONNECT_1;
+    }
+
+    ret = connect(soc, (struct sockaddr*)&server_sockaddr, sizeof(struct sockaddr));
+
+    if(ret < 0)
+    {
+        if(errno != EINPROGRESS)		//It usually return this error!
+        {
+            /*
+             * connect error: Connection refused
+             *
+             * perror( "connect error" );
+             */
+            goto TCP_CONNECT_1;
+        }
+    }
+    else if(ret == 0)
+    {
+        goto TCP_CONNECT_0;
+    }
+
+    tv.tv_sec = 10;		//Connect Timeout
+    tv.tv_usec = 0;
+    FD_ZERO(&rdfds);
+    FD_SET(soc, &rdfds);
+
+    fds_ret = select(soc + 1, NULL, &rdfds, NULL, &tv);
+
+    if(fds_ret < 0)
+    {
+        LOGE( "%s: TCP select error", __func__);
+        goto TCP_CONNECT_1;
+    }
+    else if(fds_ret == 0)
+    {
+        LOGE("%s: TCP Connect Timeout %ld", __func__, tv.tv_sec);
+        goto TCP_CONNECT_1;
+    }
+    else if(FD_ISSET(soc, &rdfds))
+    {
+        if(getsockopt(soc, SOL_SOCKET, SO_ERROR, &error_value, &error_value_len) < 0)
+        {
+            goto TCP_CONNECT_1;
+        }
+    }
+    else
+    {
+        LOGE("%s: some error occur in tcp_connect()", __func__);
+        goto TCP_CONNECT_1;
+    }
+
+    TCP_CONNECT_0:
+//    if ( fcntl( soc, F_SETFL, arg ) < 0 )
+//    {
+//        perror( "fcntl( F_SETFL ) error" );
+//        goto TCP_CONNECT_1;
+//    }
+
+    if ( error_value != 0 )
+    {
+        /*
+         * Error: Connection refused
+         *
+         * fprintf( stderr, "Error: %s", strerror( error_value ) );
+         */
+        goto TCP_CONNECT_1;
+    }
+
+    if(socket_set_keepalive(soc) != 0)
+    {
+        goto TCP_CONNECT_1;
+    }
+
+    arg &= ~O_NONBLOCK;
+    if( fcntl(soc, F_SETFL, arg) < 0)
+    {
+        LOGE( "%s: fcntl( F_SETFL ) error", __func__);
+        goto TCP_CONNECT_1;
+    }
+
+    LOGI("%s: tcp connected %s: %d", __func__, ip, port);
+
+    return( soc );
+
+    TCP_CONNECT_1:
+    close( soc );
+    return( -1 );
+}
+
+static int udp_connect(const char *ip, uint16_t port) {
+    struct sockaddr_in server_sockaddr;
+    int soc;
+    int opt;
+    int arg;
+
+    if ((soc = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
+        return -1;
+    }
+
+    /*Enable send broadcast packet*/
+    opt = 1;
+    if (setsockopt(soc, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)) != 0) {
+        perror("setsockopt");
+        close(soc);
+        return -1;
+    }
+
+    // Set non-blocking
+    if( (arg = fcntl(soc, F_GETFL, NULL)) < 0 ) {
+        close(soc);
+        return -1;
+    }
+
+    if ( fcntl(soc, F_SETFL, arg | O_NONBLOCK) < 0 ) {
+        close(soc);
+        return -1;
+    }
+
+    server_sockaddr.sin_family = AF_INET;
+    server_sockaddr.sin_addr.s_addr = inet_addr(ip);//htonl(INADDR_ANY);
+    server_sockaddr.sin_port = htons(port);
+    bzero(&server_sockaddr.sin_zero, 8);
+
+    return soc;
+}
+
+int WriteTCP(int fd, const uint8_t * buf, uint32_t len)
+{
+    int ret = 0;
+    int fds_ret;
+    struct timeval tv;
+    fd_set rdfds;
+
+    if (fd < 0)
+        return -2;
+
+    /*******************************************************
+    TCP send with nonblock, if occur failure(such as line disconnect...),
+    will send timeout, so terminate TCP.
+    */
+    tv.tv_sec = 5;
+    tv.tv_usec = 0;
+
+    FD_ZERO(&rdfds); //clean
+    FD_SET(fd, &rdfds); //set
+
+    fds_ret = select(fd + 1, NULL, &rdfds, NULL, &tv);
+
+    if (fds_ret < 0) {
+        LOGE("tcp error send select error");
+        return -1;
+    } else if(fds_ret == 0) {
+        LOGE("tcp error Occur failure(such as line disconnect)");
+        //Occur failure(such as line disconnect)
+        ret = -1;
+    } else if(FD_ISSET(fd, &rdfds)) {
+        ret = send(fd, buf, len, 0);
+        if(ret == -1) {
+            LOGE("tcp error TCP Send Error");
+        }
+    } else {
+        LOGE("tcp error tcp send has error\n");
+    }
+
+    return ret;
+}
+
+int ReadTCP(int fd, uint8_t * buf, uint32_t len)
+{
+    if (fd < 0) {
+        return -2;
+    }
+
+    int recvLen = recv(fd, buf, len, 0);        //read socket data from server
+
+    if (recvLen == 0) {
+        LOGW("tcp error TCP disconnected 0 errno = %d", errno);
+        return -1;
+    } else if (recvLen < 0) {
+        if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
+            return 0;
+        } else {
+            LOGW("tcp error TCP disconnected errno = %d", errno);;
+            return -1;
+        }
+    }
+
+    return recvLen;
+}
+
+int ConnectTCP(const char *ip, uint16_t port)
+{
+    char net_addr[32] = {0};
+
+    if (is_domain_name(ip)) {
+        if (GetHostIP(ip, net_addr) != 0) {
+            return -3;
+        }
+    } else {
+        strcpy(net_addr, ip);
+    }
+
+    return tcp_connect(net_addr, port);
+}
+
+void DisconnectTCP(int fd)
+{
+    LOGI("DisconnectTCP fd = %d", fd);
+    if (fd >= 0) {
+        shutdown(fd, SHUT_RDWR);
+        close(fd);
+    }
+}
+
+int EstablishUDP(const char *ip, uint16_t port)
+{
+    char net_addr[32] = {0};
+
+    if (is_domain_name(ip)) {
+        if (GetHostIP(ip, net_addr) != 0) {
+            return -3;
+        }
+    } else {
+        strcpy(net_addr, ip);
+    }
+
+    return udp_connect(net_addr, port);
+}
+
+void RemoveUDP(int fd)
+{
+    close(fd);
+}
+
+int WriteUDP(int fd, char *ip, uint16_t port, const uint8_t * buf, uint32_t len)
+{
+    int ret = 0;
+    int fds_ret;
+    struct timeval tv;
+    fd_set rdfds;
+
+    if (fd < 0)
+        return -2;
+
+    /*******************************************************
+    TCP send with nonblock, if occur failure(such as line disconnect...),
+    will send timeout, so terminate TCP.
+    */
+    tv.tv_sec = 5;
+    tv.tv_usec = 0;
+
+    FD_ZERO(&rdfds); //clean
+    FD_SET(fd, &rdfds); //set
+
+    fds_ret = select(fd + 1, NULL, &rdfds, NULL, &tv);
+
+    if (fds_ret < 0)
+    {
+        LOGE("UDP send select error");
+        return -1;
+    }
+    else if(fds_ret == 0)
+    {
+        LOGE("Occur failure(such as line disconnect)");
+        ret = -1;
+    }
+    else if(FD_ISSET(fd, &rdfds))
+    {
+        struct sockaddr_in server_sockaddr;
+
+        server_sockaddr.sin_family = AF_INET;
+        server_sockaddr.sin_addr.s_addr = inet_addr(ip);//htonl(INADDR_ANY);
+        server_sockaddr.sin_port = htons(port);
+        bzero(&server_sockaddr.sin_zero, 8);
+
+        ret = sendto(fd, buf, len, 0, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr_in));
+
+        if(ret == -1)
+        {
+            LOGE("UDP Send Error");
+        }
+    }
+    else
+    {
+        LOGE("UDP send has error\n");
+    }
+
+    return ret;
+}
+
+int ReadUDP(int fd, uint8_t * buf, uint32_t len)
+{
+    struct sockaddr_in from_addr;
+    socklen_t from_len;
+
+    int recvLen = recvfrom(fd, buf, len, 0,
+                           (struct sockaddr *) &from_addr,
+                           &from_len);//read socket data from server//
+
+    if (recvLen == 0) {
+        return 0;
+    } else if (recvLen < 0) {
+        if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
+            return 0;
+        } else {
+            LOGE("UDP ERROR!! = %d", errno);
+            return -1;
+        }
+    }
+
+    return recvLen;
+}
diff --git a/lib/src/main/cpp/common/net.h b/lib/src/main/cpp/common/net.h
new file mode 100644
index 0000000..48a7c7a
--- /dev/null
+++ b/lib/src/main/cpp/common/net.h
@@ -0,0 +1,17 @@
+//
+// Created by YY on 2019/9/29.
+//
+
+#ifndef RTKBASESTATION_NET_H
+#define RTKBASESTATION_NET_H
+
+int WriteTCP(int fd, const uint8_t * buf, uint32_t len);
+int ReadTCP(int fd, uint8_t * buf, uint32_t len);
+int ConnectTCP(const char *ip, uint16_t port);
+void DisconnectTCP(int fd);
+int EstablishUDP(const char *ip, uint16_t port);
+void RemoveUDP(int fd);
+int WriteUDP(int fd, char *ip, uint16_t port, const uint8_t * buf, uint32_t len);
+int ReadUDP(int fd, uint8_t * buf, uint32_t len);
+
+#endif //RTKBASESTATION_NET_H
diff --git a/lib/src/main/cpp/common/serial_port.cpp b/lib/src/main/cpp/common/serial_port.cpp
new file mode 100644
index 0000000..a41832b
--- /dev/null
+++ b/lib/src/main/cpp/common/serial_port.cpp
@@ -0,0 +1,306 @@
+//
+// Created by YY on 2017/9/5.
+//
+
+#include <jni.h>
+#include <string>
+
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <termios.h>
+#include <android/log.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include "../jni_log.h"
+#include "serial_port.h"
+
+using namespace std;
+
+static volatile int serial_port_fd[2] = {0, 0};
+static pthread_mutex_t mutex[2] = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER};
+
+int setRTS(int fd, int level)
+{
+    int status;
+
+    if (ioctl(fd, TIOCMGET, &status) == -1) {
+        LOGD("setRTS(): TIOCMGET");
+        return 0;
+    }
+    if (level)
+        status |= TIOCM_RTS;
+    else
+        status &= ~TIOCM_RTS;
+
+    if (ioctl(fd, TIOCMSET, &status) == -1) {
+        LOGD("setRTS(): TIOCMSET");
+        return 0;
+    }
+    return 1;
+}
+
+/*********************************************************************
+ * PUBLIC FUNCTIONS
+ */
+static int SetSerialPort(int fd, int speed, int databits, char parity, int stopbits, int flowctrl) {
+    int status = 0;
+    struct termios opt;
+    int speed_arr[] = {B921600, B576000, B500000, B460800, B230400, B115200, B38400, B19200,
+                       B9600, B4800, B2400, B1200, B300};
+    int name_arr[] = {921600, 576000, 500000, 460800, 230400, 115200, 38400, 19200, 9600, 4800,
+                      2400, 1200, 300};
+
+    status = tcgetattr(fd, &opt);
+    if (status != 0) {
+        goto SET_SERIAL_PORT_END;
+    }
+
+    status = tcflush(fd, TCIOFLUSH);
+    if (status != 0) {
+        goto SET_SERIAL_PORT_END;
+    }
+    //Set baud
+    for (int i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {
+        if (speed == name_arr[i]) {
+            cfsetispeed(&opt, speed_arr[i]);
+            cfsetospeed(&opt, speed_arr[i]);
+            break;
+        }
+    }
+
+    //Set databit
+    opt.c_cflag &= ~CSIZE;
+
+    if (databits == 7) {
+        opt.c_cflag |= CS7;
+    } else {
+        opt.c_cflag |= CS8;
+    }
+    //Set parity
+    switch (parity) {
+        case 'E':
+        case 'e':
+            opt.c_cflag |= PARENB;        /*Enable parity*/
+            opt.c_cflag &= ~PARODD;
+            opt.c_iflag |= INPCK;        /*Enable pairty checking*/
+            break;
+        case 'O':
+        case 'o':
+            opt.c_cflag |= PARENB | PARODD;
+            opt.c_iflag |= INPCK;
+            break;
+
+        case 'N':
+        case 'n':
+        default:
+            opt.c_cflag &= ~PARENB;
+            opt.c_iflag &= ~INPCK;
+            break;
+    }
+    //Set stop
+    if (stopbits == 2) {
+        opt.c_cflag |= CSTOPB;
+    } else {
+        opt.c_cflag &= ~CSTOPB;
+    }
+
+    opt.c_cflag |= CLOCAL | CREAD;
+
+    //Set raw mode
+    opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);                /*Input*/
+    opt.c_oflag &= ~OPOST;                                /*Output*/
+    opt.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL);            //Disable XON/XOFF flowcontrol
+    opt.c_iflag &= ~(ICRNL | INLCR);                        //Disable 0x0d and 0x0a convert
+
+    opt.c_cc[VTIME] = 0;//150; //15 seconds 150x100ms = 15s
+    opt.c_cc[VMIN] = 1;        //Recv one char, read() will return
+
+    if (flowctrl) {
+        opt.c_cflag |= CRTSCTS;
+    } else {
+        opt.c_cflag &= ~CRTSCTS;                        //Disable hard flowcontrol
+    }
+
+    tcflush(fd, TCIOFLUSH);
+
+    status = tcsetattr(fd, TCSANOW, &opt);
+
+    SET_SERIAL_PORT_END:
+    if (status != 0) {
+        LOGD("Serial Port set fail");
+    } else {
+        LOGD("Serial Port set success");
+    }
+
+//    setRTS(fd, 1);
+
+    return status;
+}
+
+static int OpenSerialPort(const char *name) {
+    int uart_fd;
+
+//    uart_fd = open(name, O_RDWR /*| O_NONBLOCK/*| O_NOCTTY | O_NDELAY*/);
+    uart_fd = open(name, O_RDWR | O_NOCTTY);
+    LOGD("open serial port fd = %d", uart_fd);
+    return uart_fd;
+}
+
+static void CloseSerialPort(int fd) {
+    close(fd);
+}
+
+int WriteSerialPort(int id, const void *buf, int len) {
+    int ret = -1;
+    int fds_ret;
+    int fd = GetSerialPort(id);
+
+    struct timeval tv;
+    fd_set wrfds;
+
+    if (fd <= 0) {
+        return -1;
+    }
+
+    tv.tv_sec = 1;
+    tv.tv_usec = 0;
+
+    FD_ZERO(&wrfds); //clean
+    FD_SET(fd, &wrfds); //set
+
+    pthread_mutex_lock(&mutex[id]);
+
+    fds_ret = select(fd + 1, NULL, &wrfds, NULL, &tv);
+
+    if (fds_ret < 0) {
+        LOGE("Serial port select error\n");
+    }
+    else if(fds_ret == 0) {
+        LOGE("Serial port write timeout\n");
+    }
+    else if(FD_ISSET(fd, &wrfds)) {
+        ret = write(fd, buf, len);      //serial write data
+        if (ret != len ) {
+            LOGE("Serial Port Error\n");
+            tcflush(fd, TCOFLUSH);
+        }
+    }
+    else {
+        LOGE("Serial Port error 2\n");
+    }
+
+    pthread_mutex_unlock(&mutex[id]);
+
+    return ret;
+}
+
+int GetSerialPort(int id) {
+    return serial_port_fd[id];
+}
+
+int InitSerialPort(int id, int baud, int dataBits, char parity, int stopBits, int flowctrl)
+{
+    char name[32];
+
+    if (id == UART_0) {
+        strcpy(name, "/dev/ttyHSL0");
+    } else if (id == UART_1) {
+        strcpy(name, "/dev/ttyHSL1");
+    } else {
+        return -1;
+    }
+
+    UninitSerialPort(id);
+
+    serial_port_fd[id] = OpenSerialPort(name);
+    if (serial_port_fd[id] <= 0) {
+        return -1;
+    }
+
+    if (SetSerialPort(serial_port_fd[id], baud, dataBits, parity, stopBits, flowctrl) != 0) {
+        return -2;
+    }
+    pthread_mutex_init(&mutex[id], NULL);
+
+    return 0;
+}
+
+void UninitSerialPort(int id)
+{
+    if (serial_port_fd[id] > 0) {
+        CloseSerialPort(serial_port_fd[id]);
+        pthread_mutex_destroy(&mutex[id]);
+        serial_port_fd[id] = 0;
+    }
+}
+
+int ReadSerialPort(int id, uint8_t *out, uint16_t length)
+{
+    if (serial_port_fd[id] <= 0) return 0;
+    return read(serial_port_fd[id], out, length);
+}
+
+//extern "C"
+//JNIEXPORT jint JNICALL
+//Java_com_example_yy_jnicallback_MyService_InitSerialPort(
+//        JNIEnv *env,
+//        jobject /* this */,
+//        jstring name,
+//        jint baud,
+//        jint dataBits,
+//        jbyte parity,
+//        jint stopBits) {
+//
+//    const char *s = env->GetStringUTFChars(name, 0);
+//    char item_value[128];
+//    strcpy(item_value, s);
+//    env->ReleaseStringUTFChars(name, s);
+//    LOGD("serial port = %s", item_value);
+//
+//    if (serial_port_fd > 0) {
+//        CloseSerialPort(serial_port_fd);
+//        pthread_mutex_destroy(&mutex);
+//        serial_port_fd = 0;
+//    }
+//
+//    serial_port_fd = OpenSerialPort(item_value);
+//    if (serial_port_fd <= 0) {
+//        return -1;
+//    }
+//
+//    if (SetSerialPort(serial_port_fd, baud, dataBits, parity, stopBits) != 0) {
+//        return -2;
+//    }
+//    pthread_mutex_init(&mutex, NULL);
+//    return 0;
+//}
+//
+//extern "C"
+//JNIEXPORT void JNICALL
+//Java_com_example_yy_jnicallback_MyService_MonitSerialPort(
+//        JNIEnv *env,
+//        jobject /* this */) {
+//    if (serial_port_fd > 0) {
+//        uint8_t UartRxBuf[4096];
+//
+///*        uint8_t pkt[] = {0x7E, 0x80, 0x02, 0x00, 0x00, 0x26, 0x00, 0x00, 0x01, 0x38, 0x20, 0x20,
+//                         0x55, 0x45, 0x04, 0x4E,
+//                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x61, 0x63,
+//                         0x0C, 0x06, 0xEA, 0x04,
+//                         0xFE, 0x00, 0x00, 0x01, 0x63, 0x00, 0xB4, 0x13, 0x03, 0x05, 0x18, 0x18,
+//                         0x52, 0x01, 0x04, 0x00,
+//                         0x00, 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, 0x4D, 0x7E};
+//        WriteSerialPort(serial_port_fd, pkt, sizeof(pkt));*/
+//
+//        int length = read(serial_port_fd, UartRxBuf, sizeof(UartRxBuf));
+//
+//        if (length > 0) {
+//            Parse(DATA_ACCESS_MCU, UartRxBuf, length);
+//        }
+//    }
+//}
diff --git a/lib/src/main/cpp/common/serial_port.h b/lib/src/main/cpp/common/serial_port.h
new file mode 100644
index 0000000..e20f27c
--- /dev/null
+++ b/lib/src/main/cpp/common/serial_port.h
@@ -0,0 +1,30 @@
+//
+// Created by YY on 2017/9/5.
+//
+
+#ifndef JNICALLBACK_SERIAL_PORT_H
+#define JNICALLBACK_SERIAL_PORT_H
+
+#include <stdint.h>
+
+#define UART_0            0
+#define UART_1            1
+
+struct serial_config {
+    char name[32];
+    int baud;
+    int data_bit;
+    char verify_bit;
+    int stop_bit;
+    int flow_ctrl;
+};
+
+int InitSerialPort(int id, int baud, int dataBits, char parity, int stopBits, int flowctrl);
+void UninitSerialPort(int id);
+
+int GetSerialPort(int id);
+int ReadSerialPort(int id, uint8_t *out, uint16_t length);
+int WriteSerialPort(int id, const void *buf, int len);
+int setRTS(int fd, int level);
+
+#endif //JNICALLBACK_SERIAL_PORT_H
diff --git a/lib/src/main/cpp/defs.h b/lib/src/main/cpp/defs.h
new file mode 100644
index 0000000..fba169e
--- /dev/null
+++ b/lib/src/main/cpp/defs.h
@@ -0,0 +1,61 @@
+#ifndef _DEFS_H_
+#define _DEFS_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define ENABLE_DEBUG
+
+#ifdef ENABLE_DEBUG
+
+#define xENABLE_DEBUG_MAIN
+#define ENABLE_DEBUG_BT
+#define xENABLE_DEBUG_EX_OBD
+#define xENABLE_DEBUG_CV520
+
+#endif/*ENABLE_DEBUG*/
+
+/* takes a byte out of a uint32_t : var - uint32_t,  ByteNum - byte to take out (0 - 3) */
+#define BREAK_UINT32( var, ByteNum ) \
+          (uint8_t)((uint32_t)(((var) >>((ByteNum) * 8)) & 0x00FF))
+
+#define BUILD_UINT32(Byte0, Byte1, Byte2, Byte3) \
+          ((uint32_t)((uint32_t)((Byte0) & 0x00FF) \
+          + ((uint32_t)((Byte1) & 0x00FF) << 8) \
+          + ((uint32_t)((Byte2) & 0x00FF) << 16) \
+          + ((uint32_t)((Byte3) & 0x00FF) << 24)))
+
+#define BUILD_UINT16(loByte, hiByte) \
+          ((uint16_t)(((loByte) & 0x00FF) + (((hiByte) & 0x00FF) << 8)))
+
+#define HI_UINT16(a) (((a) >> 8) & 0xFF)
+#define LO_UINT16(a) ((a) & 0xFF)
+
+#ifndef BV
+#define BV(n)      (1ul << (n))
+#endif
+
+#ifndef ABS
+#define ABS(n)     (((n) < 0) ? -(n) : (n))
+#endif
+
+#ifndef MAX
+#define MAX(a, b)   ((a >= b) ? (a) : (b))
+#endif
+
+#ifndef MIN
+#define MIN(a, b)   ((a < b) ? (a) : (b))
+#endif
+
+const double GLB_EPSILON = 1e-6;
+
+//#define htonl(a)	((((uint32_t)(a) & 0xff000000) >> 24) | \
+//                    (((uint32_t)(a) & 0x00ff0000) >> 8) | \
+//                    (((uint32_t)(a) & 0x0000ff00) << 8) | \
+//                    (((uint32_t)(a) & 0x000000ff) << 24))
+//#define htons(a)	((((uint16_t)(a) & 0xff00) >> 8) | (((uint16_t)(a) & 0x00ff) << 8))
+//#define ntohl(a)	htonl(a)
+//#define ntohs(a)	htons(a)
+
+
+#endif
diff --git a/lib/src/main/cpp/driver_test.cpp b/lib/src/main/cpp/driver_test.cpp
new file mode 100644
index 0000000..a2259d3
--- /dev/null
+++ b/lib/src/main/cpp/driver_test.cpp
@@ -0,0 +1,616 @@
+//
+// Created by YY on 2019/10/21.
+// Units note: distance - metre
+//             speed - metre per second
+//             angle - DEGREES
+//
+
+#include <cstdlib>
+#include <cmath>
+#include <semaphore.h>
+#include <pthread.h>
+#include <cstring>
+#include <vector>
+#include "driver_test.h"
+#include "defs.h"
+#include "Geometry.h"
+#include "common/apptimer.h"
+#include "jni_log.h"
+#include "test_items/park_edge.h"
+#include "native-lib.h"
+#include "test_items/park_bottom.h"
+#include "test_items/park_edge.h"
+#include "test_items/error_list.h"
+#include "test_items/turn_a90.h"
+#include "test_items/driving_curve.h"
+#include "test_items/stop_and_start.h"
+
+using namespace std;
+
+#define RTK_INVALID         0
+#define RTK_SINGLE_POINT   1
+#define RTK_DIFF            2
+#define RTK_FIX            3
+#define RTK_FLOAT            4
+
+#define CAR_MODEL_POINT_NUM             32
+
+enum {
+    TEST_NONE,
+    TEST_PARK_EDGE,
+    TEST_PARK_BOTTOM,
+    TEST_TUNE_90,
+    TEST_S_CURVE,
+    TEST_SLOPE,
+    TEST_SIMPLE,
+    TEST_END
+};
+
+static bool TestStart = false;
+static int TestItem = TEST_NONE;
+static int CarInArea = 0;
+int errs = 0;
+
+vector<int> ErrorList;
+
+static Polygon theParkEdgeMap;         // 渚т綅鍋滆溅鍦板浘
+static Polygon theTurn90Map;
+static Polygon theSSMap;
+
+#define CAR_COORD_STORE_SIZE        10
+
+struct car_coord_ {
+    uint32_t uptime;
+    double azimuth;
+    PointF coord;
+};
+
+struct car_desc_ {
+    double distance;            // 璺濈涓诲ぉ绾跨殑璺濈
+    double angle;               // 浠庝腑杞寸嚎閫嗘椂閽堝舰鎴愮殑瑙掑害
+};
+
+struct car_desc_ *CarDesc = NULL;
+
+struct car_coord_ *CarCoord = NULL;
+static double currSpeed = 0, currAzimuth = 0;
+static PointF currCoord;
+static int prevDirect = 0, currDirect = 0;
+
+static int car_coord_num = 0, car_coord_in = 0;
+
+#define MOV_AVG_SIZE                1
+#define RTK_HISTORY_SIZE            1000
+#define CAR_MODEL_CACHE_SIZE      10
+
+struct rtk_info_ {
+    int qf;
+    time_t uptime;
+    double azimuth;
+    PointF coord;
+};
+
+static struct rtk_info_ *RTKHistory = NULL;
+static int rtk_history_num = 0, rtk_history_in = 0;
+static pthread_mutex_t add_rtk_history_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static carModelDesc_t *carModelDescFile = NULL;
+
+static car_model_cache_t carModelCache[CAR_MODEL_CACHE_SIZE];
+static int carModelCacheIn, carModelCacheNum;
+
+static void UpdateCarBodyCoord(double azimuth, PointF coord);
+static void UpdateCarBodyCoord(double azimuth, PointF coord, car_model_cache_t *carModel, int &carModelIn, int &carModelNum);
+static bool FrontTireEnterArea(const Polygon *car, const Polygon *map);
+
+void DriverTestInit(void)
+{
+    carModelDescFile = (carModelDesc_t *)malloc(sizeof(carModelDesc_t));
+
+    carModelDescFile->body_num = 6;
+    carModelDescFile->body[0] = 0;
+    carModelDescFile->body[1] = 1;
+    carModelDescFile->body[2] = 2;
+    carModelDescFile->body[3] = 3;
+    carModelDescFile->body[4] = 4;
+    carModelDescFile->body[5] = 5;
+
+    carModelDescFile->front_left_tire[0] = 1;
+    carModelDescFile->front_left_tire[1] = 1;
+    carModelDescFile->front_right_tire[0] = 5;
+    carModelDescFile->front_right_tire[1] = 5;
+    carModelDescFile->rear_left_tire[0] = 2;
+    carModelDescFile->rear_left_tire[1] = 2;
+    carModelDescFile->rear_right_tire[0] = 4;
+    carModelDescFile->rear_right_tire[1] = 4;
+
+    memset(&carModelCache, 0, sizeof(carModelCache));
+    carModelCacheIn = carModelCacheNum = 0;
+
+    CarDesc = (struct car_desc_ *) malloc(sizeof(struct car_desc_) * 6);
+    CarDesc[0].distance = 0.2465;
+    CarDesc[0].angle = 0;
+
+    CarDesc[1].distance = 0.2635;
+    CarDesc[1].angle = 20.7;
+
+    CarDesc[2].distance = 0.14;
+    CarDesc[2].angle = 138.9;
+
+    CarDesc[3].distance = 0.1055;
+    CarDesc[3].angle = 180.0;
+
+    CarDesc[4].distance = 0.14;
+    CarDesc[4].angle = 221.1;
+
+    CarDesc[5].distance = 0.2635;
+    CarDesc[5].angle = 339.3;
+
+    memset(&theParkEdgeMap, 0, sizeof(theParkEdgeMap));
+    theParkEdgeMap.num = 8;
+    theParkEdgeMap.point = (PointF *)malloc(theParkEdgeMap.num * sizeof(PointF));
+
+    theParkEdgeMap.point[0].Y = 28.013;
+    theParkEdgeMap.point[0].X = -11.9669;
+
+    theParkEdgeMap.point[1].Y = 27.137;
+    theParkEdgeMap.point[1].X = -11.5114;
+
+    theParkEdgeMap.point[2].Y = 27.5039;
+    theParkEdgeMap.point[2].X = -10.8069;
+
+    theParkEdgeMap.point[3].Y = 26.4212;
+    theParkEdgeMap.point[3].X = -10.2969;
+
+    theParkEdgeMap.point[4].Y = 26.8894;
+    theParkEdgeMap.point[4].X = -9.2102;
+
+    theParkEdgeMap.point[5].Y = 28.0027;
+    theParkEdgeMap.point[5].X = -9.6513;
+
+    theParkEdgeMap.point[6].Y = 28.3797;
+    theParkEdgeMap.point[6].X = -8.9758;
+
+    theParkEdgeMap.point[7].Y = 29.3232;
+    theParkEdgeMap.point[7].X = -9.5057;
+
+    memset(&theTurn90Map, 0, sizeof(theTurn90Map));
+    theTurn90Map.num = 6;
+    theTurn90Map.point = (PointF *)malloc(theTurn90Map.num * sizeof(PointF));
+
+    theTurn90Map.point[0].Y = 10;
+    theTurn90Map.point[0].X = 10;
+
+    theTurn90Map.point[1].Y = 12.5;
+    theTurn90Map.point[1].X = 10;
+
+    theTurn90Map.point[2].Y = 12.5;
+    theTurn90Map.point[2].X = 12.5;
+
+    theTurn90Map.point[3].Y = 15;
+    theTurn90Map.point[3].X = 12.5;
+
+    theTurn90Map.point[4].Y = 15;
+    theTurn90Map.point[4].X = 15;
+
+    theTurn90Map.point[5].Y = 10;
+    theTurn90Map.point[5].X = 15;
+
+    memset(&theSSMap, 0, sizeof(theSSMap));
+    theSSMap.num = 9;
+    theSSMap.point = (PointF *)malloc(theSSMap.num * sizeof(PointF));
+
+    theSSMap.point[0].Y = 10;
+    theSSMap.point[0].X = 10;
+
+    theSSMap.point[8].Y = 10;
+    theSSMap.point[8].X = 15;
+
+    theSSMap.point[1].Y = 10;
+    theSSMap.point[1].X = 11.5;
+
+    theSSMap.point[4].Y = 10;
+    theSSMap.point[4].X = 12.5;
+
+    theSSMap.point[5].Y = 10;
+    theSSMap.point[5].X = 13.5;
+
+    theSSMap.point[2].Y = 12;
+    theSSMap.point[2].X = 11.5;
+
+    theSSMap.point[3].Y = 12;
+    theSSMap.point[3].X = 12.5;
+
+    theSSMap.point[6].Y = 12;
+    theSSMap.point[6].X = 13.5;
+
+    theSSMap.point[7].Y = 12;
+    theSSMap.point[7].X = 15;
+
+    pthread_mutex_init(&add_rtk_history_mutex, NULL);
+
+    RTKHistory = (struct rtk_info_ *) malloc(RTK_HISTORY_SIZE * sizeof(struct rtk_info_));
+    rtk_history_num = rtk_history_in = 0;
+
+    CarCoord = (struct car_coord_ *) malloc(CAR_COORD_STORE_SIZE * sizeof(struct car_coord_));
+    car_coord_num = car_coord_in = 0;
+
+    TestStart = true;
+    TestItem = TEST_NONE;
+
+    ErrorList.clear();
+
+    TextSpeak("寮�濮嬫祴璇�");
+}
+
+void UpdateRTKInfo(struct rtk_info *s)
+{
+    struct tm test_tm;
+
+    struct timeval tv;
+    struct timezone tz;
+
+    gettimeofday(&tv,&tz);
+
+    memset(&test_tm, 0, sizeof(test_tm));
+
+    test_tm.tm_year = 2000 + s->YY - 1900;
+    test_tm.tm_mon = s->MM - 1;
+    test_tm.tm_mday = s->DD;
+    test_tm.tm_hour = s->hh;
+    test_tm.tm_min = s->mm;
+    test_tm.tm_sec = s->ss;
+
+    pthread_mutex_lock(&add_rtk_history_mutex);
+    /*if (s->qf == 3)*/ {
+        RTKHistory[rtk_history_in].qf = RTK_FIX;//s->qf;
+        RTKHistory[rtk_history_in].uptime = mktime(&test_tm) - tz.tz_minuteswest*60;
+        RTKHistory[rtk_history_in].azimuth = s->heading;
+        RTKHistory[rtk_history_in].coord.X = s->x;
+        RTKHistory[rtk_history_in].coord.Y = s->y;
+
+        rtk_history_in = (rtk_history_in + 1) % RTK_HISTORY_SIZE;
+        if (rtk_history_num < RTK_HISTORY_SIZE)
+            rtk_history_num++;
+    }
+    pthread_mutex_unlock(&add_rtk_history_mutex);
+
+//    DEBUG("UpdateRTKInfo qf = %d tm %ld", s->qf, mktime(&test_tm) - tz.tz_minuteswest*60);
+}
+
+void UpdateCarCoord(void)
+{
+    double azimuth;
+    PointF coord;
+
+    // 璁$畻鏈�杩戠殑鍑犱釜锛屾粦鍔ㄥ钩鍧�
+    int cnt = 0, valid_cnt = 0;
+    int s, sm;
+    time_t prev_time = 0;
+    time_t last_rtk_time = 0;
+
+    pthread_mutex_lock(&add_rtk_history_mutex);
+    s = rtk_history_in;
+    sm = rtk_history_num;
+    pthread_mutex_unlock(&add_rtk_history_mutex);
+
+    DEBUG("UpdateCarCoord rtk_history_in %d rtk_history_num %d car_coord_num %d", s, sm, car_coord_num);
+
+    // 濡傛灉鍑虹幇QF涓嶆槸鍥哄畾瑙c�丟PS鎶ユ枃涓㈠け鐨勬儏鍐碉紝灏辨寜涓婃鐨勮椹剁姸鎬佸仛鐩寸嚎琛岄┒
+    for (; cnt < sm && valid_cnt < MOV_AVG_SIZE; cnt++) {
+        if (s == 0) {
+            s = RTK_HISTORY_SIZE - 1;
+        } else {
+            s--;
+        }
+
+        if (cnt == 0) {
+            last_rtk_time = RTKHistory[s].uptime;
+        }
+
+        if (RTKHistory[s].uptime - last_rtk_time <= 10) {
+            if (RTKHistory[s].qf == RTK_FIX) {
+                if (valid_cnt != 0) {
+                    azimuth += RTKHistory[s].azimuth;
+                    coord.X += RTKHistory[s].coord.X;
+                    coord.Y += RTKHistory[s].coord.Y;
+                } else {
+                    azimuth = RTKHistory[s].azimuth;
+                    coord.X = RTKHistory[s].coord.X;
+                    coord.Y = RTKHistory[s].coord.Y;
+                }
+                valid_cnt++;
+            }
+        } else {
+            break;
+        }
+        prev_time = RTKHistory[s].uptime;
+    }
+
+    if (valid_cnt >= MOV_AVG_SIZE) {
+        azimuth /= MOV_AVG_SIZE;
+        coord.X /= MOV_AVG_SIZE;
+        coord.Y /= MOV_AVG_SIZE;
+
+        CarCoord[car_coord_in].uptime = AppTimer_GetTickCount();
+
+        CarCoord[car_coord_in].azimuth = azimuth;
+        CarCoord[car_coord_in].coord = coord;
+        car_coord_in = (car_coord_in + 1) % CAR_COORD_STORE_SIZE;
+        if (car_coord_num < CAR_COORD_STORE_SIZE)
+            car_coord_num++;
+    } else if (car_coord_num >= 3) {
+        // 鎸変笂娆¤繍琛岃建杩规帹绠椾竴姝�
+        int p1 = ((car_coord_in-1)+CAR_COORD_STORE_SIZE)%CAR_COORD_STORE_SIZE;
+        uint32_t tm = AppTimer_GetTickCount() -  CarCoord[p1].uptime;
+        double distance = currSpeed * tm / 1000;
+
+        PointF xy;
+
+        xy.X = CarCoord[p1].coord.X + distance * sin(toRadians(CarCoord[p1].azimuth)) * currDirect;
+        xy.Y = CarCoord[p1].coord.Y + distance * sin(toRadians(CarCoord[p1].azimuth)) * currDirect;
+
+        CarCoord[car_coord_in].uptime = AppTimer_GetTickCount();
+        CarCoord[car_coord_in].azimuth =  CarCoord[p1].azimuth;
+        CarCoord[car_coord_in].coord = xy;
+        car_coord_in = (car_coord_in + 1) % CAR_COORD_STORE_SIZE;
+        if (car_coord_num < CAR_COORD_STORE_SIZE)
+            car_coord_num++;
+    } else {
+        return;
+    }
+
+    // 璁$畻杞﹁締杞粨鐐广�侀�熷害銆佸墠杩涘悗閫�
+    if (car_coord_num >= 3) {
+        int p1 = ((car_coord_in-1)+CAR_COORD_STORE_SIZE)%CAR_COORD_STORE_SIZE;
+        int p2 = ((car_coord_in-2)+CAR_COORD_STORE_SIZE)%CAR_COORD_STORE_SIZE;
+        int p3 = ((car_coord_in-3)+CAR_COORD_STORE_SIZE)%CAR_COORD_STORE_SIZE;
+
+        double speed1 = sqrt(pow(CarCoord[p1].coord.X - CarCoord[p2].coord.X, 2) + pow(CarCoord[p1].coord.Y - CarCoord[p2].coord.Y, 2)) * 1000 /
+                (double)(CarCoord[p1].uptime - CarCoord[p2].uptime);
+        double speed2 = sqrt(pow(CarCoord[p2].coord.X - CarCoord[p3].coord.X, 2) + pow(CarCoord[p2].coord.Y - CarCoord[p3].coord.Y, 2)) * 1000 /
+                        (double)(CarCoord[p2].uptime - CarCoord[p3].uptime);
+
+        currSpeed = speed1;
+        currAzimuth = CarCoord[p1].azimuth;
+        currCoord = CarCoord[p1].coord;
+
+        DEBUG("鏂瑰悜 spd1 %f spd2 %f", speed1, speed2);
+
+        if (speed1 < 0.05 && speed2 < 0.05) {
+            // 鍋滆溅
+            currDirect = 0;
+//            TextSpeak("鍋�");
+        } else if (speed1 < 0.05) {
+             currDirect = prevDirect;
+        } else {
+            // 鍒ゆ柇鍓嶈繘杩樻槸鍚庨��
+            double deg = 0.0;
+
+            if (fabs(CarCoord[p1].coord.Y-CarCoord[p2].coord.Y) <= GLB_EPSILON) {
+                if (CarCoord[p1].coord.X > CarCoord[p2].coord.X) {
+                    deg = 90;
+                } else {
+                    deg = 270;
+                }
+
+                DEBUG("鏂瑰悜 deg %f p1x %f p2x %f", deg, CarCoord[p1].coord.X, CarCoord[p2].coord.X);
+            } else if (fabs(CarCoord[p1].coord.X - CarCoord[p2].coord.X) <= GLB_EPSILON) {
+                if (CarCoord[p1].coord.Y > CarCoord[p2].coord.Y) {
+                    deg = 0;
+                } else {
+                    deg = 180;
+                }
+            } else {
+                deg = atan(fabs(CarCoord[p1].coord.X - CarCoord[p2].coord.X) /
+                           fabs(CarCoord[p1].coord.Y - CarCoord[p2].coord.Y));
+
+                deg = toDegree(deg);
+
+                if (CarCoord[p1].coord.X > CarCoord[p2].coord.X &&
+                    CarCoord[p1].coord.Y > CarCoord[p2].coord.Y) {
+
+                } else if (CarCoord[p1].coord.X < CarCoord[p2].coord.X &&
+                           CarCoord[p1].coord.Y > CarCoord[p2].coord.Y) {
+                    deg = 360 - deg;
+                } else if (CarCoord[p1].coord.X < CarCoord[p2].coord.X &&
+                           CarCoord[p1].coord.Y < CarCoord[p2].coord.Y) {
+                    deg = 180 + deg;
+                } else if (CarCoord[p1].coord.X > CarCoord[p2].coord.X &&
+                           CarCoord[p1].coord.Y < CarCoord[p2].coord.Y) {
+                    deg = 180 - deg;
+                }
+
+                DEBUG("鏂瑰悜 deg %f p1x %f p2x %f p1y %f p2y %f", deg, CarCoord[p1].coord.X,
+                      CarCoord[p2].coord.X, CarCoord[p1].coord.Y, CarCoord[p2].coord.Y);
+            }
+
+            deg = fabs(CarCoord[p1].azimuth - deg);
+            if (deg > 180) {
+                deg = 360 - deg;
+            }
+            if (deg < 90) {
+                // 鍓嶈繘
+                currDirect = 1;
+//                    TextSpeak("杩�");
+            } else {
+                // 鍚庨��
+                currDirect = -1;
+//                    TextSpeak("閫�");
+            }
+
+            prevDirect = currDirect;
+        }
+
+        DEBUG("speed = %f, azimuth = %f coord.X = %f coord.Y = %f dir = %d", speed1, CarCoord[p1].azimuth,
+              CarCoord[p1].coord.X, CarCoord[p1].coord.Y, currDirect);
+
+//        UpdateCarBodyCoord(CarCoord[p1].azimuth, CarCoord[p1].coord);
+        UpdateCarBodyCoord(CarCoord[p1].azimuth, CarCoord[p1].coord, carModelCache, carModelCacheIn, carModelCacheNum);
+//        for (int i = 0; i < theCarModel.num; ++i) {
+//            DEBUG("n = %d X = %f Y = %f", i, theCarModel.point[i].X, theCarModel.point[i].Y);
+//        }
+
+
+        int c1 = ((carModelCacheIn-1)+CAR_MODEL_CACHE_SIZE)%CAR_MODEL_CACHE_SIZE;
+        Polygon py;
+
+        py.num = carModelCache[c1].point_num;
+        py.point = carModelCache[c1].points;
+
+        DrawScreen(&theSSMap, &py);
+
+        if (!TestStart) return;
+
+        if (CarInArea == 0) {
+//            if (FrontTireEnterArea(&py, &theParkEdgeMap)) {
+//                CarInArea = TEST_PARK_BOTTOM;
+//                TestItem = TEST_PARK_BOTTOM;
+//                StartParkBottom();
+//            }
+
+            CarInArea = TEST_SLOPE;
+            TestItem = TEST_SLOPE;
+            StartSAS();
+        }
+
+        switch (TestItem) {
+            case TEST_NONE: {
+                break;
+            }
+            case TEST_PARK_BOTTOM: {
+                errs = TestParkBottom(ErrorList, &theParkEdgeMap, GetCarModelCache(0), currSpeed, currDirect);
+
+                if (errs != 0) {
+                    StopParkBottom();
+                    TestItem = TEST_NONE;
+                }
+                break;
+            }
+            case TEST_PARK_EDGE: {
+                errs = TestParkEdge(ErrorList, &theParkEdgeMap, GetCarModelCache(0), currSpeed, currDirect);
+
+                if (errs != 0) {
+                    StopParkEdge();
+                    TestItem = TEST_NONE;
+                }
+                break;
+            }
+
+            case TEST_TUNE_90: {
+                errs = TestTurnA90(ErrorList, &theTurn90Map, GetCarModelCache(0), currSpeed, currDirect, currAzimuth);
+
+                if (errs != 0) {
+                    StopTurnA90();
+                    TestItem = TEST_NONE;
+                }
+
+                break;
+            }
+
+            case TEST_SLOPE: {
+                errs = TestSAS(ErrorList, &theSSMap, GetCarModelCache(0), currSpeed, currDirect);
+
+                if (errs != 0) {
+                    StopSAS();
+                    TestItem = TEST_NONE;
+                }
+
+                break;
+            }
+            default:
+                break;
+        }
+
+        if (ErrorList.size() > 0) {
+            vector<int>::iterator it = ErrorList.end();
+            it--;
+            error_list_t list = GetErrorList(*it);
+
+            int scr = 0;
+
+            for (vector<int>::iterator it1 = ErrorList.begin(); it1 != ErrorList.end(); ++it1) {
+                error_list_t list = GetErrorList(*it1);
+
+                scr += list.dec_score;
+            }
+
+            char buff[256];
+
+            sprintf(buff, "%s, 鎬昏鎵e垎 %d", list.text_desc, scr);
+
+            TextOsd(1, buff);
+        }
+    }
+}
+
+car_model_cache_t *GetCarModelCache(int node)
+{
+    if (node < carModelCacheNum) {
+        node = ((carModelCacheIn-node-1)+CAR_MODEL_CACHE_SIZE)%CAR_MODEL_CACHE_SIZE;
+        return &carModelCache[node];
+    }
+    return NULL;
+}
+
+/*******************************************************************
+ * @brief 鐢变富澶╃嚎鍧愭爣璁$畻杞﹁韩鐐瑰潗鏍�
+ * @param azimuth
+ * @param coord
+ */
+static void UpdateCarBodyCoord(double azimuth, PointF coord, car_model_cache_t *carModel, int &in, int &num)
+{
+    carModel[in].uptime = AppTimer_GetTickCount();
+    carModel[in].desc = carModelDescFile;
+    carModel[in].point_num = 6;
+
+    if (carModel[in].points == NULL) {
+        carModel[in].points = (PointF *) malloc(sizeof(PointF) * carModel[in].point_num);
+    }
+
+    for (int i = 0; i < 6; ++i) {
+        double tx = coord.X + CarDesc[i].distance*sin(toRadians(azimuth));
+        double ty = coord.Y + CarDesc[i].distance*cos(toRadians(azimuth));
+
+        carModel[in].points[i].X = (tx-coord.X)*cos(toRadians(CarDesc[i].angle)) -
+                                 (ty-coord.Y)*sin(toRadians(CarDesc[i].angle)) + coord.X;
+        carModel[in].points[i].Y = (tx-coord.X)*sin(toRadians(CarDesc[i].angle)) +
+                                 (ty-coord.Y)*cos(toRadians(CarDesc[i].angle)) + coord.Y;
+    }
+
+    in = (in + 1) % CAR_MODEL_CACHE_SIZE;
+    if (num < CAR_MODEL_CACHE_SIZE) {
+        num++;
+    }
+}
+
+static void UpdateCarBodyCoord(double azimuth, PointF coord)
+{
+//    static double az = 0;
+//
+//    az += 2.0;
+//
+//    for (int i = 0; i < theCarModel.num; ++i) {
+//        double tx = coord.X + CarDesc[i].distance*sin(toRadians(azimuth));
+//        double ty = coord.Y + CarDesc[i].distance*cos(toRadians(azimuth));
+//
+//        theCarModel.point[i].X = (tx-coord.X)*cos(toRadians(CarDesc[i].angle)) -
+//                                 (ty-coord.Y)*sin(toRadians(CarDesc[i].angle)) + coord.X;
+//        theCarModel.point[i].Y = (tx-coord.X)*sin(toRadians(CarDesc[i].angle)) +
+//                                 (ty-coord.Y)*cos(toRadians(CarDesc[i].angle)) + coord.Y;
+//    }
+//    UpdateParkTest(&theCarModel, &theTireModel, speed);
+//    UpdatePark2Test(&theCarModel, &theTireModel, speed);
+//    UpdateTuneAngle90Test(&theCarModel, 0);
+    //UpdateSlopeTest(&theCarModel, speed, direct);
+}
+
+static bool FrontTireEnterArea(const Polygon *car, const Polygon *map)
+{
+    if (IntersectionOf(car->point[0], map) == GM_Containment) {
+        return true;
+    }
+    return false;
+}
+
+
diff --git a/lib/src/main/cpp/driver_test.h b/lib/src/main/cpp/driver_test.h
new file mode 100644
index 0000000..6979f04
--- /dev/null
+++ b/lib/src/main/cpp/driver_test.h
@@ -0,0 +1,52 @@
+//
+// Created by YY on 2019/10/21.
+//
+
+#ifndef RTKDRIVERTEST_DRIVER_TEST_H
+#define RTKDRIVERTEST_DRIVER_TEST_H
+
+#include "Geometry.h"
+
+struct rtk_info {
+    int YY;
+    int MM;
+    int DD;
+    int hh;
+    int mm;
+    int ss;
+    int dss;
+    int qf;
+    double heading;
+    double x;
+    double y;
+};
+
+#define TIRE_OUTSIDE    0
+#define TIRE_INSIDE     1
+
+// 鎬绘暟
+// 涓酱绾垮墠鐐�,id 0锛屼腑杞寸嚎鍚庣偣id
+// 宸﹀寘鍥达紝 鍙冲寘鍥�
+// 宸﹀墠杞紝澶栧唴
+typedef struct {
+    int body_num;
+    int body[64];
+    int front_left_tire[2];
+    int front_right_tire[2];
+    int rear_left_tire[2];
+    int rear_right_tire[2];
+} carModelDesc_t;
+
+typedef struct {
+    uint32_t uptime;
+    int point_num;
+    PointF *points;
+    carModelDesc_t *desc;
+} car_model_cache_t;
+
+void DriverTestInit(void);
+void UpdateRTKInfo(struct rtk_info *s);
+void UpdateCarCoord(void);
+car_model_cache_t *GetCarModelCache(int node);
+
+#endif //RTKDRIVERTEST_DRIVER_TEST_H
diff --git a/lib/src/main/cpp/jni_log.h b/lib/src/main/cpp/jni_log.h
new file mode 100644
index 0000000..85b3dc2
--- /dev/null
+++ b/lib/src/main/cpp/jni_log.h
@@ -0,0 +1,28 @@
+//
+// Created by YY on 2017/8/29.
+//
+
+#ifndef JNI_LOG_H
+#define JNI_LOG_H
+
+#include <android/log.h>
+
+#define DTAG    "JNI_DEBUG"
+
+#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,DTAG,__VA_ARGS__) // 瀹氫箟LOGD绫诲瀷
+#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,DTAG,__VA_ARGS__) // 瀹氫箟LOGI绫诲瀷
+#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,DTAG,__VA_ARGS__) // 瀹氫箟LOGW绫诲瀷
+#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,DTAG,__VA_ARGS__) // 瀹氫箟LOGE绫诲瀷
+#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,DTAG,__VA_ARGS__) // 瀹氫箟LOGF绫诲瀷
+
+#define LOGMCUD(...)  __android_log_print(ANDROID_LOG_DEBUG,"MCU_DEBUG",__VA_ARGS__) // 瀹氫箟LOGD绫诲瀷
+
+#define ENABLE_DEBUG_PROTOCOL
+
+#ifdef ENABLE_DEBUG_PROTOCOL
+#define DEBUG(...)                      LOGD(__VA_ARGS__)
+#else
+#define DEBUG(...)
+#endif
+
+#endif //JNI_LOG_H
diff --git a/lib/src/main/cpp/mcu/mcu_if.cpp b/lib/src/main/cpp/mcu/mcu_if.cpp
new file mode 100644
index 0000000..585503c
--- /dev/null
+++ b/lib/src/main/cpp/mcu/mcu_if.cpp
@@ -0,0 +1,306 @@
+//
+// Created by YY on 2019/12/21.
+//
+
+#include <cstring>
+#include <pthread.h>
+#include "mcu_if.h"
+#include "../common/apptimer.h"
+#include "../utils/crc16.h"
+#include "../defs.h"
+#include "../jni_log.h"
+#include "../common/serial_port.h"
+
+#define MCU_UART            UART_1
+
+enum parse_status_t {
+    SYNC_HEAD_ONE,
+    SYNC_HEAD_TWO,
+    GET_ID_HI,
+    GET_ID_LO,
+    GET_LENGTH_HI,
+    GET_LENGTH_LO,
+    GET_PAYLOAD,
+    GET_CRC16_HI,
+    GET_CRC16_LO
+};
+
+#define ID_CM_APP_BOOT          0x0001
+#define ID_MC_MCU_BOOT          0x8001
+#define ID_CM_INQ_MCU_VER       0x0002
+#define ID_MC_MCU_VER           0x8002
+#define ID_CM_RW_INFO           0x0003
+#define ID_MC_RW_INFO_RSP       0x8003
+#define ID_CM_MCU_DFU_REQ       0x0004
+#define ID_MC_MCU_DFU_RSP       0x8004
+#define ID_CM_MCU_DFU_DATA       0x0005
+#define ID_CM_MCU_DFU_DATA_CMP  0x0006
+#define ID_MC_MCU_DFU_DATA_RSP  0x8006
+#define ID_MC_CAR_INFO           0x8007
+#define ID_MC_RTK_DATA          0x8008
+#define ID_CM_RTK_DATA            0x0008
+#define ID_CM_READ_RFCARD         0x0009
+#define ID_MC_RFCARD_RSP        0x8009
+
+static parse_status_t parse_status;
+
+static struct {
+    uint16_t id;
+    uint16_t length;
+    uint16_t rx_len;
+    uint8_t buffer[4096 + 4];
+    uint16_t crc16;
+}McuPkt;
+
+static void *UartThread1(void *p);
+static void ParseMcuTimeout(union sigval sig);
+static void McuCommandEntry(uint16_t id, const uint8_t *data, int lenth);
+
+void ParseMcuInit(void)
+{
+    parse_status = SYNC_HEAD_ONE;
+    AppTimer_delete(ParseMcuTimeout);
+
+    SendMcuCommand(ID_CM_APP_BOOT, NULL, 0);
+}
+
+#define PARSE_BUFF_SIZE         4096
+
+static void *UartThread1(void *p) {
+    struct serial_config *cfg = (struct serial_config *) p;
+
+    int res = InitSerialPort(MCU_UART, cfg->baud, cfg->data_bit, cfg->verify_bit, cfg->stop_bit, cfg->flow_ctrl);
+    DEBUG("Serial %s open %d", cfg->name, res);
+    uint8_t RxBuf[PARSE_BUFF_SIZE];
+    int RxBufLen = 0;
+
+    if (res == 0)
+        ParseMcuInit();
+
+    while (res == 0) {
+        int ul = ReadSerialPort(MCU_UART, (uint8_t *)RxBuf + RxBufLen, sizeof(RxBuf) - RxBufLen);
+        RxBufLen += ul;
+
+        /*{
+            static char buffd[16384];
+
+            buffd[0] = 0;
+            int i = 0;
+            for (i = 0; i < ul; i++) {
+                if ((i % 32) == 0) {
+                    sprintf(buffd + strlen(buffd), "\n");
+                }
+                sprintf(buffd + strlen(buffd), "%02X ", RxBuf[i]);
+                if (strlen(buffd) > 800) {
+                    DEBUG("%s <- %s...", "UART", buffd);
+                    buffd[0] = 0;
+                }
+            }
+            if (strlen(buffd) > 0)
+                DEBUG("%s <- %s", "UART", buffd);
+        }*/
+
+        if (RxBufLen > 0) {
+            DEBUG("RECV LEN %d", RxBufLen);
+            ParseMcu(RxBuf, RxBufLen);
+            RxBufLen = 0;
+        }
+    }
+    if (res == 0) {
+        UninitSerialPort(MCU_UART);
+    }
+    pthread_exit(NULL);
+}
+
+void ParseMcu(const uint8_t *data, int length)
+{
+    int x = 0;
+    while (x < length) {
+        uint8_t c = data[x];
+        switch (parse_status) {
+            case SYNC_HEAD_ONE:
+                if (c == 0x55) {
+                    parse_status = SYNC_HEAD_TWO;
+                    AppTimer_delete(ParseMcuTimeout);
+                    AppTimer_add(ParseMcuTimeout, D_SEC(5));
+                }
+                x++;
+                break;
+            case SYNC_HEAD_TWO:
+                if (c == 0xAA) {
+                    parse_status = GET_ID_HI;
+                } else if (c != 0x55) {
+                    parse_status = SYNC_HEAD_ONE;
+                }
+                x++;
+                break;
+            case GET_ID_HI:
+                McuPkt.id = c;
+                parse_status = GET_ID_LO;
+                x++;
+                break;
+            case GET_ID_LO:
+                McuPkt.id <<= 8;
+                McuPkt.id += c;
+                parse_status = GET_LENGTH_HI;
+                x++;
+                break;
+            case GET_LENGTH_HI:
+                McuPkt.rx_len = 0;
+                McuPkt.length = c;
+                parse_status = GET_LENGTH_LO;
+                x++;
+                break;
+            case GET_LENGTH_LO:
+                McuPkt.length <<= 8;
+                McuPkt.length += c;
+                parse_status = GET_PAYLOAD;
+
+                if (McuPkt.length >= 1500) {
+                    DEBUG("Pkt Too large!");
+                    parse_status = SYNC_HEAD_ONE;
+                    AppTimer_delete(ParseMcuTimeout);
+                }
+
+                McuPkt.buffer[0] = HI_UINT16(McuPkt.id);
+                McuPkt.buffer[1] = LO_UINT16(McuPkt.id);
+                McuPkt.buffer[2] = HI_UINT16(McuPkt.length);
+                McuPkt.buffer[3] = LO_UINT16(McuPkt.length);
+                x++;
+                break;
+            case GET_PAYLOAD:
+                if (length - x >= McuPkt.length - McuPkt.rx_len) {
+                    memcpy(McuPkt.buffer + 4 + McuPkt.rx_len, data + x, McuPkt.length - McuPkt.rx_len);
+                    x += McuPkt.length - McuPkt.rx_len;
+                    McuPkt.rx_len = McuPkt.length;
+                    parse_status = GET_CRC16_HI;
+                } else {
+                    memcpy(McuPkt.buffer + 4 + McuPkt.rx_len, data + x, length - x);
+                    McuPkt.rx_len += length - x;
+                    x = length;
+                }
+                break;
+            case GET_CRC16_HI:
+                McuPkt.crc16 = c;
+                parse_status = GET_CRC16_LO;
+                x++;
+                break;
+            case GET_CRC16_LO: {
+                McuPkt.crc16 <<= 8;
+                McuPkt.crc16 += c;
+
+                uint16_t crc16 = CRCCCITT(McuPkt.buffer, McuPkt.length + 4, 0, 0);
+
+                DEBUG("mcuif crc16 but 0x%04X exp 0x%04X", McuPkt.crc16, crc16);
+
+                if (McuPkt.crc16 == crc16) {
+                    McuCommandEntry(McuPkt.id, McuPkt.buffer + 4, McuPkt.length);
+                }
+
+                parse_status = SYNC_HEAD_ONE;
+                AppTimer_delete(ParseMcuTimeout);
+                x++;
+                break;
+            }
+            default:
+                break;
+        }
+    }
+}
+
+void SendMcuCommand(uint16_t id, const uint8_t *data, int length)
+{
+    uint8_t buffer[2048];
+    int x = 0;
+
+    buffer[x++] = 0x55;
+    buffer[x++] = 0xAA;
+    buffer[x++] = HI_UINT16(id);
+    buffer[x++] = LO_UINT16(id);
+    buffer[x++] = HI_UINT16(length);
+    buffer[x++] = LO_UINT16(length);
+
+    if (data != NULL && length > 0) {
+        memcpy(buffer + x, data, length);
+        x += length;
+    }
+
+    uint16_t crc16 =  CRCCCITT(buffer + 2, length + 4, 0, 0);
+    buffer[x++] = HI_UINT16(crc16);
+    buffer[x++] = LO_UINT16(crc16);
+
+    WriteSerialPort(MCU_UART, buffer, x);
+}
+
+void ConfigMCU(void)
+{
+    // TODO
+    static struct serial_config serialConfig;
+
+    strcpy(serialConfig.name, "/dev/ttyHSL1");
+    serialConfig.baud = 115200;
+    serialConfig.data_bit = 8;
+    serialConfig.verify_bit = 'N';
+    serialConfig.stop_bit = 1;
+    serialConfig.flow_ctrl = 0;
+
+    pthread_t pid;
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//detached
+    pthread_create(&pid, &attr, UartThread1, &serialConfig);
+}
+
+void SendRtkToMcu(const uint8_t *data, int length)
+{
+    SendMcuCommand(ID_CM_RTK_DATA, data, length);
+}
+
+static void ParseMcuTimeout(union sigval sig) {
+    AppTimer_delete(ParseMcuTimeout);
+    parse_status = SYNC_HEAD_ONE;
+}
+
+static void sendrtk(union sigval sig) {
+    uint8_t data[486];
+
+    memset(data, 0x86, sizeof(data));
+
+    SendMcuCommand(ID_CM_RTK_DATA, data, sizeof(data));
+
+    SendMcuCommand(ID_CM_READ_RFCARD, NULL, 0);
+
+    AppTimer_delete(sendrtk);
+    AppTimer_add(sendrtk, D_SEC(1));
+}
+
+static void McuCommandEntry(uint16_t id, const uint8_t *data, int lenth)
+{
+    static int ii = 0;
+
+    switch (id) {
+        case ID_MC_MCU_BOOT:
+            DEBUG("MCU BOOT");
+            break;
+        case ID_MC_MCU_VER:
+            DEBUG("MCU VER");
+            break;
+        case ID_MC_RW_INFO_RSP:
+            break;
+        case ID_MC_MCU_DFU_RSP:
+            break;
+        case ID_MC_MCU_DFU_DATA_RSP:
+            break;
+        case ID_MC_CAR_INFO:
+            DEBUG("ID_MC_CAR_INFO");
+            break;
+        case ID_MC_RTK_DATA:
+            DEBUG("ID_MC_RTK_DATA");
+            break;
+        case ID_MC_RFCARD_RSP:
+            DEBUG("ID_MC_RFCARD_RSP");
+            break;
+        default:
+            break;
+    }
+}
diff --git a/lib/src/main/cpp/mcu/mcu_if.h b/lib/src/main/cpp/mcu/mcu_if.h
new file mode 100644
index 0000000..a324a3d
--- /dev/null
+++ b/lib/src/main/cpp/mcu/mcu_if.h
@@ -0,0 +1,16 @@
+//
+// Created by YY on 2019/12/21.
+//
+
+#ifndef RTKDRIVERTEST_MCU_IF_H
+#define RTKDRIVERTEST_MCU_IF_H
+
+#include <cstdint>
+
+void ConfigMCU(void);
+void SendRtkToMcu(const uint8_t *data, int length);
+void ParseMcuInit(void);
+void ParseMcu(const uint8_t *data, int length);
+void SendMcuCommand(uint16_t id, const uint8_t *data, int length);
+
+#endif //RTKDRIVERTEST_MCU_IF_H
diff --git a/lib/src/main/cpp/native-lib.cpp b/lib/src/main/cpp/native-lib.cpp
new file mode 100644
index 0000000..85f60cf
--- /dev/null
+++ b/lib/src/main/cpp/native-lib.cpp
@@ -0,0 +1,464 @@
+#include <jni.h>
+#include <string>
+#include <pthread.h>
+#include <unistd.h>
+#include "common/serial_port.h"
+#include "jni_log.h"
+#include "common/net.h"
+#include "common/apptimer.h"
+#include "rtk_platform/parse_net.h"
+#include "native-lib.h"
+#include "Geometry.h"
+#include "rtk_platform/platform.h"
+#include "rtk_module/rtk.h"
+#include "mcu/mcu_if.h"
+#include "driver_test.h"
+
+static JavaVM *sg_jvm = NULL;
+static jobject sg_obj = NULL;
+
+const char *RTK_PLATFORM_IP = "47.93.80.84";
+const int RTK_PLATFORM_PORT = 12125;
+const uint8_t phone[] = {0x20,0x19,0x10,0x15,0x00,0x00,0x00,0x02};
+
+#define IMEI_LENGTH         15
+
+char * GetImei(void)
+{
+    JNIEnv *env;
+    static char imei[IMEI_LENGTH + 1] = {0};
+
+    LOGD("JNI_GetImei");
+
+    if (strlen(imei) == 0) {
+        bool ready_in_java_env = false;
+
+        if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+            // Attach涓荤嚎绋�
+            if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+                LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+                return imei;
+            }
+        } else {
+            ready_in_java_env = true;
+        }
+
+        jclass cls = env->GetObjectClass(sg_obj);
+        jmethodID fun = env->GetMethodID(cls, "javaGetImei", "()Ljava/lang/String;");
+
+        jstring ret = (jstring) env->CallObjectMethod(sg_obj, fun);
+
+        if (ret != NULL) {
+            const char *pret = env->GetStringUTFChars(ret, JNI_FALSE);
+            strcpy(imei, pret);
+        }
+
+        env->DeleteLocalRef(cls);
+
+        if (!ready_in_java_env) {
+            //Detach涓荤嚎绋�
+            if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+                LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+                return imei;
+            }
+        }
+    }
+    return imei;
+}
+
+int DESEncrypt(const uint8_t *key, int key_length,
+        const uint8_t *plaintext, int plaintext_length,
+        uint8_t **ciphertext)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+    int ciphertext_length = 0;
+    *ciphertext = NULL;
+
+    LOGD("JNI_DESEncrypt");
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return 0;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "javaDESEncrypt", "([B[B)[B");
+
+    jbyteArray jni_key = env->NewByteArray(key_length);
+    jbyteArray jni_plaintext = env->NewByteArray(plaintext_length);
+
+    env->SetByteArrayRegion(jni_key, 0, key_length, (jbyte *) key);
+    env->SetByteArrayRegion(jni_plaintext, 0, plaintext_length, (jbyte *) plaintext);
+
+    jbyteArray jni_ciphertext = (jbyteArray) env->CallObjectMethod(sg_obj, fun, jni_plaintext, jni_key);
+    if (jni_ciphertext != NULL) {
+        ciphertext_length = env->GetArrayLength(jni_ciphertext);
+        uint8_t *buffer = (uint8_t *)malloc(ciphertext_length);
+        env->GetByteArrayRegion(jni_ciphertext, 0, ciphertext_length, (jbyte *) buffer);
+        *ciphertext = buffer;
+    }
+
+    env->DeleteLocalRef(jni_key);
+    env->DeleteLocalRef(jni_plaintext);
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+    return ciphertext_length;
+}
+
+void SetPlatformKey(const uint8_t *key, int length)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+
+    LOGD("JNI_SetPlatformKey");
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jbyteArray jni_cmd = env->NewByteArray(length);
+    env->SetByteArrayRegion(jni_cmd, 0, length, (jbyte *) key);
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "SetPlatformKey", "([B)V");
+
+    env->CallVoidMethod(sg_obj, fun, jni_cmd);
+
+    env->DeleteLocalRef(jni_cmd);
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+}
+
+void DelPlatformKey(void)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "DeletePlatformKey", "()V");
+
+    env->CallVoidMethod(sg_obj, fun);
+
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+}
+
+int GetPlatformKey(uint8_t *pkey)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+    uint8_t key[64] = {0};
+    int key_len = 0;
+
+    LOGD("JNI_GetPlatformKey");
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return 0;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "GetPlatformKey", "()[B");
+
+    jbyteArray jni_cmd = (jbyteArray) env->CallObjectMethod(sg_obj, fun);
+
+    if (jni_cmd != NULL) {
+        key_len = env->GetArrayLength(jni_cmd);
+        if (key_len <= sizeof(key)) {
+            env->GetByteArrayRegion(jni_cmd, 0, key_len, (jbyte *) key);
+            memcpy(pkey, key, key_len);
+        } else {
+            key_len = 0;
+        }
+    }
+
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+    return key_len;
+}
+
+void SetSharedValue(const char *key, int value)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+
+    LOGD("JNI_SetSharedValue");
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "SetSharedValue", "(Ljava/lang/String;I)V");
+
+    env->CallVoidMethod(sg_obj, fun, env->NewStringUTF(key), value);
+
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+}
+
+int GetSharedValue(const char *key)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+    int value = 0;
+
+    LOGD("JNI_GetSharedValue");
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return value;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "GetSharedValue", "(Ljava/lang/String;)I");
+
+    value = env->CallIntMethod(sg_obj, fun, env->NewStringUTF(key));
+
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+    return value;
+}
+
+void TextSpeak(const char *text)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "TextSpeak", "(Ljava/lang/String;)V");
+
+    std::string cstext = text;
+    env->CallVoidMethod(sg_obj, fun, env->NewStringUTF(cstext.c_str()));
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+}
+
+void TextOsd(int type, const char *text)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "TextOsd", "(ILjava/lang/String;)V");
+
+    std::string cstext = text;
+    env->CallVoidMethod(sg_obj, fun, type, env->NewStringUTF(cstext.c_str()));
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+}
+
+void DrawScreen(const Polygon *map, const Polygon *car)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "DrawMap", "([[D[[D)V");
+
+    jclass doubleArrCls = env->FindClass("[D");
+    jobjectArray retmap = env->NewObjectArray(map->num, doubleArrCls, NULL);
+    jobjectArray retcar = env->NewObjectArray(car->num, doubleArrCls, NULL);
+
+    for (int i = 0; i < map->num; ++i) {
+        jdoubleArray doubleArr = env->NewDoubleArray(2);
+        jdouble tmp[2] = {map->point[i].X, 0 - map->point[i].Y};
+
+        env->SetDoubleArrayRegion(doubleArr, 0, 2, tmp);
+        env->SetObjectArrayElement(retmap, i, doubleArr);
+        env->DeleteLocalRef(doubleArr);
+    }
+
+    for (int i = 0; i < car->num; ++i) {
+        jdoubleArray doubleArr = env->NewDoubleArray(2);
+        jdouble tmp[2] = {car->point[i].X, 0 - car->point[i].Y};
+
+        env->SetDoubleArrayRegion(doubleArr, 0, 2, tmp);
+        env->SetObjectArrayElement(retcar, i, doubleArr);
+        env->DeleteLocalRef(doubleArr);
+    }
+
+    env->CallVoidMethod(sg_obj, fun, retmap, retcar);
+    env->DeleteLocalRef(retmap);
+    env->DeleteLocalRef(retcar);
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+}
+
+void DisplayText(const char *string)
+{
+    DEBUG("DisplayText: %s", string);
+}
+
+void CCL(int who)
+{
+    JNIEnv *env;
+    bool ready_in_java_env = false;
+    static int c[2] = {0};
+
+    if (sg_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+        // Attach涓荤嚎绋�
+        if (sg_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
+            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
+            return;
+        }
+    } else {
+        ready_in_java_env = true;
+    }
+
+    jclass cls = env->GetObjectClass(sg_obj);
+    jmethodID fun = env->GetMethodID(cls, "CCL", "(II)V");
+
+    env->CallVoidMethod(sg_obj, fun, c[who], who);
+
+    c[who] = !c[who];
+
+    env->DeleteLocalRef(cls);
+
+    if (!ready_in_java_env) {
+        //Detach涓荤嚎绋�
+        if (sg_jvm->DetachCurrentThread() != JNI_OK) {
+            LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
+        }
+    }
+}
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_anyun_exam_lib_RemoteService_startNative(JNIEnv *env, jobject thiz) {
+    // TODO: implement startNative()
+    // 淇濆瓨鍏ㄥ眬JVM浠ヤ究鍦ㄥ瓙绾跨▼涓娇鐢�
+    env->GetJavaVM(&sg_jvm);
+    // 涓嶈兘鐩存帴璧嬪��(g_obj = ojb)
+    sg_obj = env->NewGlobalRef(thiz);
+
+    AppTimer_Init();
+
+    ConfigMCU();
+    DriverTestInit();
+    ConfigRTKModule();
+
+    InitPlatform(phone, RTK_PLATFORM_IP, RTK_PLATFORM_PORT);
+}
\ No newline at end of file
diff --git a/lib/src/main/cpp/native-lib.h b/lib/src/main/cpp/native-lib.h
new file mode 100644
index 0000000..578dbf9
--- /dev/null
+++ b/lib/src/main/cpp/native-lib.h
@@ -0,0 +1,28 @@
+//
+// Created by YY on 2019/10/14.
+//
+
+#ifndef RTKBASESTATION_NATIVE_LIB_H
+#define RTKBASESTATION_NATIVE_LIB_H
+
+#include <cstdint>
+#include "Geometry.h"
+
+char * GetImei(void);
+
+int DESEncrypt(const uint8_t *key, int key_length,
+               const uint8_t *plaintext, int plaintext_length,
+               uint8_t **ciphertext);
+
+void SetPlatformKey(const uint8_t *key, int length);
+void DelPlatformKey(void);
+int GetPlatformKey(uint8_t *pkey);
+void SetSharedValue(const char *key, int value);
+int GetSharedValue(const char *key);
+void TextSpeak(const char *text);
+void TextOsd(int type, const char *text);
+void DrawScreen(const Polygon *map, const Polygon *car);
+void CCL(int who);
+void DisplayText(const char *string);
+
+#endif //RTKBASESTATION_NATIVE_LIB_H
diff --git a/lib/src/main/cpp/rtk_module/parse_gps.cpp b/lib/src/main/cpp/rtk_module/parse_gps.cpp
new file mode 100644
index 0000000..43ec7ba
--- /dev/null
+++ b/lib/src/main/cpp/rtk_module/parse_gps.cpp
@@ -0,0 +1,273 @@
+/*********************************************************
+YY add
+Parse u-blox GPS nmea data
+*/
+#include "parse_gps.h"
+#include "../jni_log.h"
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef const uint8_t *ptr;
+
+static ptr skip1(ptr s, ptr e)
+{
+	if(s)
+		while(s != e && (*s == ' ' || *s == '\t' || *s == ',' || *s == ';'))
+			++s;
+	return s;
+}
+
+static ptr match2(ptr s, ptr e)
+{
+	return s && s != e && (*s == '\r' || *s == '\n') ? s+1 : 0;
+}
+
+static ptr matchChar(ptr s, ptr e, uint8_t c)
+{
+	return s && s != e && toupper(*s) == (int) c ? s+1 : 0;
+}
+
+static ptr skipQuote(ptr p, ptr e)
+{
+	return p && p != e && *p == '"' ? p+1 : p;
+}
+
+static ptr getString(ptr p, ptr e, struct nmea_sequence *res)
+{
+	p = skipQuote(p, e);
+	if(p)
+	{
+		ptr s = p;
+		while(p != e && (isalnum(*p) || *p==' ' || *p=='+' || *p=='^' || *p=='-' || *p=='_' || *p=='#' || *p=='.' || *p=='/' || *p==':')) ++p;
+		if(p != s)
+		{
+			res->data = s;
+			res->length = p - s;
+			return skipQuote(p, e);
+		}
+	}
+	res->data = 0;
+	res->length = 0;
+
+	return (p != 0 && (*p == ',' || *p == '*')) ? p : 0;
+}
+
+static ptr getHexNumber(ptr p, ptr e, ptr start)
+{
+	if(p)
+	{
+		ptr s = p;
+		uint16_t r = 0;
+		uint8_t check = 0;
+
+		while(p != e && isalnum(*p)) {
+			if(isdigit(*p)) {
+				r = (r << 4) + (*p++ - '0');
+			}
+			else {
+				r = (r << 4) + (toupper(*p++) - 'A' + 10);
+			}
+		}
+		if(p != s) {
+
+			while(start != s-1) {
+				check ^= *start++;
+			}
+
+			if (check != r) DEBUG("crc error exp %02X but %02X", check, r);
+		}
+
+		return ( p != s && check == r )? p : 0;
+	}
+	return 0;
+}
+
+static ptr getHexNumber2(ptr p, ptr e, ptr start)
+{
+	if(p)
+	{
+		ptr s = p;
+		uint32_t r = 0;
+
+		while(p != e && isalnum(*p)) {
+			if(isdigit(*p)) {
+				r = (r << 4) + (*p++ - '0');
+			}
+			else {
+				r = (r << 4) + (toupper(*p++) - 'A' + 10);
+			}
+		}
+		if(p != s) {
+
+		}
+
+		return ( p != s )? p : 0;
+	}
+	return 0;
+}
+
+static ptr findEndOfPacket(ptr s, ptr e)
+{
+	if(s == e) return 0;
+
+	if(*s == '\r' || *s == '\n')
+	{
+		return s+1;
+	}
+	else
+	{
+		/* expecting ... <cr> */
+		ptr p = s;
+		while(p != e && (*p == ' ' || *p == '\t')) ++p;
+		while(p != e && *p != '\r') ++p;
+		return p == e ? 0 : p+1;
+	}
+}
+
+static ptr getNMEA(ptr s, ptr e, struct nmea *res)
+{
+	res->nmea_num = 0;
+	while(s && s != e && *s != '\r' && *s != '\n' && *s != '*') {
+		if(s = matchChar(s, e, ',')) {
+			struct nmea_sequence dat;
+
+//			LOGD("g = %d", res->nmea_num);
+			s = getString(s, e, &dat);
+			if(res->nmea_num < MAX_NAEA_NUM) {
+				res->nmea_value[res->nmea_num] = dat;
+				res->nmea_num += 1;
+			}
+		}
+		else {
+			s = 0;
+		}
+	}
+	return s;
+}
+
+static ptr getPARAMS(ptr s, ptr e, struct nmea *res)
+{
+	res->nmea_num = 0;
+
+	while (s && s != e && *s != '\r' && *s != '\n') {
+		while(s != e && (*s == ' ' || *s == '\t')) ++s;
+
+		struct nmea_sequence dat;
+		s = getString(s, e, &dat);
+		if(res->nmea_num < MAX_NAEA_NUM) {
+			res->nmea_value[res->nmea_num] = dat;
+			res->nmea_num += 1;
+		}
+
+		while (s != e && (*s == ',')) ++s;
+	}
+
+	return s;
+}
+
+static ptr getNMEA2(ptr s, ptr e, struct nmea *res)
+{
+    bool first = true;
+
+	res->nmea_num = 0;
+
+	while(s && s != e && *s != '\r' && *s != '\n' && *s != '*') {
+		if((first && (s=matchChar(s, e, ';'))) || (s=matchChar(s, e, ','))) {
+		    first = false;
+
+			struct nmea_sequence dat;
+//			LOGD("g = %d", res->nmea_num);
+			s = getString(s, e, &dat);
+
+			if(res->nmea_num < MAX_NAEA_NUM) {
+				res->nmea_value[res->nmea_num] = dat;
+				res->nmea_num += 1;
+			}
+		}
+		else {
+			s = 0;
+		}
+	}
+	return s;
+}
+
+static ptr findSemicolon(ptr s, ptr e)
+{
+	while (s && s != e && *s != ';') {
+		s++;
+	}
+	return s;
+}
+
+ptr parseGPS(ptr s, ptr e)
+{
+	ptr p;
+	for(; (p = findEndOfPacket(s, e)) != 0; s = p)
+	{
+		if(p == s+1)
+		{
+			/* Silently discard one character; no packets are that short */
+			continue;
+		}
+		else
+		{//GPGGA   GPGSA   GPGSV    GPRMC    GPVTG    GPGLL    GPZDA
+			ptr start = s;
+			struct nmea nmeas;
+
+			if(match2(getHexNumber(matchChar(getNMEA(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '$'), e, 'G'), e, 'P'), e, 'G'), e, 'G'), e, 'A'), e, &nmeas), e, '*'), e, start), e)) {
+				handleGPGGA(&nmeas);
+				continue;
+			}
+			if(match2(getHexNumber(matchChar(getNMEA(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '$'), e, 'G'), e, 'P'), e, 'G'), e, 'S'), e, 'A'), e, &nmeas), e, '*'), e, start), e)) {
+				handleGPGSA(&nmeas);
+				continue;
+			}
+			if(match2(getHexNumber(matchChar(getNMEA(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '$'), e, 'G'), e, 'P'), e, 'G'), e, 'S'), e, 'V'), e, &nmeas), e, '*'), e, start), e)) {
+				handleGPGSV(&nmeas);
+				continue;
+			}
+			if(match2(getHexNumber(matchChar(getNMEA(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '$'), e, 'G'), e, 'P'), e, 'R'), e, 'M'), e, 'C'), e, &nmeas), e, '*'), e, start), e)) {
+				handleGPRMC(&nmeas);
+				continue;
+			}
+			if(match2(getHexNumber(matchChar(getNMEA(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '$'), e, 'G'), e, 'P'), e, 'V'), e, 'T'), e, 'G'), e, &nmeas), e, '*'), e, start), e)) {
+				handleGPVTG(&nmeas);
+				continue;
+			}
+			if(match2(getHexNumber(matchChar(getNMEA(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '$'), e, 'G'), e, 'P'), e, 'G'), e, 'L'), e, 'L'), e, &nmeas), e, '*'), e, start), e)) {
+				handleGPGLL(&nmeas);
+				continue;
+			}
+			if(match2(getHexNumber(matchChar(getNMEA(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '$'), e, 'G'), e, 'P'), e, 'Z'), e, 'D'), e, 'A'), e, &nmeas), e, '*'), e, start), e)) {
+				handleGPZDA(&nmeas);
+				continue;
+			}
+			if(match2(getHexNumber(matchChar(getNMEA(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '$'), e, 'P'), e, 'T'), e, 'N'), e, 'L'), e, ','), e, 'P'), e, 'J'), e, 'K'), e, &nmeas), e, '*'), e, start), e)) {
+				handlePJK(&nmeas);
+				continue;
+			}
+			if(match2(getHexNumber(matchChar(getNMEA(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '$'), e, 'G'), e, 'P'), e, 'T'), e, 'R'), e, 'A'), e, &nmeas), e, '*'), e, start), e)) {
+				handleGPTRA(&nmeas);
+				continue;
+			}
+			if (match2(getHexNumber2(matchChar(getNMEA2(findSemicolon(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(start = matchChar(skip1(s, e), e, '#'), e, 'B'), e, 'E'), e, 'S'), e, 'T'), e, 'P'), e, 'O'), e, 'S'), e, 'A'), e), e, &nmeas), e, '*'), e, start), e)) {
+				handleBESTPOSA(&nmeas);
+				continue;
+			}
+			if (match2(getPARAMS(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(skip1(s, e), e, 'P'), e, 'J'), e, 'K'), e, ' '), e, 'P'), e, 'A'), e, 'R'), e, 'A'), e, 'M'), e, 'E'), e, 'T'), e, 'E'), e, 'R'), e, ':'), e, &nmeas), e)) {
+				handlePJKParam(&nmeas);
+				continue;
+			}
+			if (match2(getPARAMS(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(matchChar(skip1(s, e), e, 'S'), e, 'T'), e, 'A'), e, 'R'), e, 'T'), e, 'T'), e, 'I'), e, 'M'), e, 'E'), e, ':'), e, &nmeas), e)) {
+				handleRTKRebootComp(&nmeas);
+				continue;
+			}
+			handleUnrecognisedNMEA(s, p-s);
+		}
+	}
+
+	return s;
+}
diff --git a/lib/src/main/cpp/rtk_module/parse_gps.h b/lib/src/main/cpp/rtk_module/parse_gps.h
new file mode 100644
index 0000000..7064785
--- /dev/null
+++ b/lib/src/main/cpp/rtk_module/parse_gps.h
@@ -0,0 +1,39 @@
+#ifndef _PARSE_GPS_H_
+#define _PARSE_GPS_H_
+
+#include <stdint.h>
+
+#define MAX_NAEA_NUM				32
+
+struct nmea_sequence
+{
+  const uint8_t *data;
+  uint16_t length;
+};
+
+struct nmea
+{
+	uint16_t nmea_num;
+	struct nmea_sequence nmea_value[MAX_NAEA_NUM];
+};
+
+const uint8_t *parseGPS(const uint8_t *s, const uint8_t *e);
+
+void handleUnrecognisedNMEA(const uint8_t *data, uint16_t length);
+
+void handleGPGGA(const struct nmea *);
+
+void handleGPGSA(const struct nmea *);
+
+void handleGPGSV(const struct nmea *);
+void handleGPRMC(const struct nmea *);
+void handleGPVTG(const struct nmea *);
+void handleGPGLL(const struct nmea *);
+void handleGPZDA(const struct nmea *);
+void handlePJK(const struct nmea *);
+void handleGPTRA(const struct nmea *);
+void handleBESTPOSA(const struct nmea *);
+void handlePJKParam(const struct nmea *s);
+void handleRTKRebootComp(const struct nmea *s);
+
+#endif
diff --git a/lib/src/main/cpp/rtk_module/rtk.cpp b/lib/src/main/cpp/rtk_module/rtk.cpp
new file mode 100644
index 0000000..28d7bf8
--- /dev/null
+++ b/lib/src/main/cpp/rtk_module/rtk.cpp
@@ -0,0 +1,421 @@
+//
+// Created by YY on 2019/12/23.
+//
+
+#include <cstring>
+#include <cstdio>
+#include <pthread.h>
+#include <cmath>
+#include <cstdlib>
+#include <cctype>
+#include "rtk.h"
+#include "parse_gps.h"
+#include "../common/serial_port.h"
+#include "../jni_log.h"
+#include "../driver_test.h"
+#include "../utils/num.h"
+#include "../defs.h"
+#include "../common/apptimer.h"
+#include "../rtk_platform/platform.h"
+#include "../native-lib.h"
+
+#define RTK_MODULE_UART         UART_0
+
+#define PARSE_BUFF_SIZE         4096
+
+const static char FACTORY[] = "FRESET\r\n";
+const static char REBOOT[] = "RESET\r\n";
+const static char INQ_PJK_PARAM[] = "LOG PJKPARA\r\n";
+const static char AY_PJKPARAM[] = "set pjkpara 6378137 298.257223563 29.51245330924 106.4553361945 0 0\r\n";
+const static char UNLOGALL[] = "unlogall\r\n";
+const static char IFCOM2[] = "interfacemode com2 auto auto on\r\n";
+const static char SAVECONFIG[] = "saveconfig\r\n";
+const static char *PJKITEMS[] = {"gptra", "ptnlpjk"};
+const static char *GPSITEMS[] = {"gpgga", "gprmc", "gpvtg"};
+
+static gpsStatus_t gpsStatus;
+
+static int tra_hh, tra_mm, tra_ss, tra_dss;
+struct rtk_info CurrRTKInfo;
+static time_t recved_gprmc_time = 0, recved_gpgga_time = 0;;
+
+static void CheckPjkParam(void);
+static void CheckPjkParamTimeout(union sigval sig);
+
+static void *UartThread(void *p);
+
+void ConfigRTKModule(void)
+{
+    // TODO
+
+    memset(&CurrRTKInfo, 0, sizeof(CurrRTKInfo));
+    tra_hh = tra_mm = tra_ss = tra_dss = 0;
+    memset(&gpsStatus, 0, sizeof(gpsStatus));
+
+    static struct serial_config serialConfig;
+
+    strcpy(serialConfig.name, "/dev/ttyHSL0");
+    serialConfig.baud = 115200;
+    serialConfig.data_bit = 8;
+    serialConfig.verify_bit = 'N';
+    serialConfig.stop_bit = 1;
+    serialConfig.flow_ctrl = 0;
+
+    pthread_t pid;
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//detached
+    pthread_create(&pid, &attr, UartThread, &serialConfig);
+}
+
+void FactorySettings(void)
+{
+    WriteSerialPort(RTK_MODULE_UART, FACTORY, strlen(FACTORY));
+}
+
+void RebootModule(void)
+{
+    WriteSerialPort(RTK_MODULE_UART, REBOOT, strlen(REBOOT));
+}
+
+void handleRTKRebootComp(const struct nmea *s)
+{
+    DEBUG("RTK Reboot complete!!");
+    SetAYFactoryParam(5);
+}
+
+void handlePJKParam(const struct nmea *s) {
+//PJK Parameter: A:6378137.000, 1/F:298.257223563, B0:0.000000deg, L0:120.000000, N0:0.000, E0:500000.000.
+//PJK Parameter: A:6378137.000, 1/F:298.257223563, B0:29.512453deg, L0:106.455336, N0:0.000, E0:0.000.
+    bool setparam = true;
+
+    const char DP1[] = "A:6378137.000";
+    const char DP2[] = "1/F:298.257223563";
+    const char DP3[] = "B0:29.512453deg";
+    const char DP4[] = "L0:106.455336";
+    const char DP5[] = "N0:0.000";
+    const char DP6[] = "E0:0.000";
+
+    AppTimer_delete(CheckPjkParamTimeout);
+
+    for (int i = 0; i < s->nmea_num; ++i) {
+        char out[64] = {0};
+
+        memcpy(out, s->nmea_value[i].data, s->nmea_value[i].length);
+
+        DEBUG("handlePJKParam = %s", out);
+    }
+
+    if (s->nmea_num != 6) return;
+
+    if (memcmp(s->nmea_value[0].data, DP1, strlen(DP1))) {
+        setparam = true;
+    }
+    if (memcmp(s->nmea_value[1].data, DP2, strlen(DP2))) {
+        setparam = true;
+    }
+    if (memcmp(s->nmea_value[2].data, DP3, strlen(DP3))) {
+        setparam = true;
+    }
+    if (memcmp(s->nmea_value[3].data, DP4, strlen(DP4))) {
+        setparam = true;
+    }
+    if (memcmp(s->nmea_value[4].data, DP5, strlen(DP5))) {
+        setparam = true;
+    }
+    if (memcmp(s->nmea_value[5].data, DP6, strlen(DP6))) {
+        setparam = true;
+    }
+
+    if (setparam) {
+        SetAYFactoryParam(5);
+    }
+}
+
+void SetAYFactoryParam(int freq)
+{
+    DisplayText("閰嶇疆RTK妯″潡");
+
+    WriteSerialPort(RTK_MODULE_UART, UNLOGALL, strlen(UNLOGALL));
+    WriteSerialPort(RTK_MODULE_UART, IFCOM2, strlen(IFCOM2));
+
+    if (freq == 0)
+        freq = 5;
+
+    for (int i = 0; i < sizeof(PJKITEMS)/ sizeof(PJKITEMS[0]); ++i) {
+        char cmd[64];
+        sprintf(cmd, "log com1 %s ontime %0.1f\r\n", PJKITEMS[i], 1.0/(double)freq);
+        WriteSerialPort(RTK_MODULE_UART, cmd, strlen(cmd));
+    }
+
+    for (int i = 0; i <  sizeof(GPSITEMS)/ sizeof(GPSITEMS[0]); ++i) {
+        char cmd[64];
+        sprintf(cmd, "log com1 %s ontime 1\r\n", GPSITEMS[i]);
+        WriteSerialPort(RTK_MODULE_UART, cmd, strlen(cmd));
+    }
+
+    WriteSerialPort(RTK_MODULE_UART, AY_PJKPARAM, strlen(AY_PJKPARAM));
+//    WriteSerialPort(RTK_MODULE_UART, SAVECONFIG, strlen(SAVECONFIG));
+}
+
+void GetGpsStatus(gpsStatus_t &data)
+{
+    data = gpsStatus;
+}
+
+static void *UartThread(void *p) {
+    struct serial_config *cfg = (struct serial_config *) p;
+
+    int res = InitSerialPort(RTK_MODULE_UART, cfg->baud, cfg->data_bit, cfg->verify_bit, cfg->stop_bit, cfg->flow_ctrl);
+    DEBUG("Serial %s open %d", cfg->name, res);
+    uint8_t RxBuf[PARSE_BUFF_SIZE];
+    int RxBufLen = 0;
+
+    if (res == 0) {
+        CheckPjkParam();
+    }
+
+    while (res == 0) {
+        int ul = ReadSerialPort(RTK_MODULE_UART, (uint8_t *)RxBuf + RxBufLen, sizeof(RxBuf) - RxBufLen);
+        RxBufLen += ul;
+
+        {
+            static char buffd[16384];
+
+            buffd[0] = 0;
+            int i = 0;
+            for (i = 0; i < ul; i++) {
+                if ((i % 32) == 0) {
+                    sprintf(buffd + strlen(buffd), "\n");
+                }
+                sprintf(buffd + strlen(buffd), "%02X ", RxBuf[i]);
+                if (strlen(buffd) > 800) {
+                    DEBUG("%s <- %s...", "UART", buffd);
+                    buffd[0] = 0;
+                }
+            }
+            if (strlen(buffd) > 0)
+                DEBUG("%s <- %s", "UART", buffd);
+        }
+
+        if (RxBufLen > 0) {
+            const uint8_t *ptr = parseGPS(RxBuf, RxBuf + RxBufLen);
+            if(ptr != RxBuf) {
+                memcpy(RxBuf, ptr, RxBufLen - (ptr - RxBuf));
+                RxBufLen -= ptr - RxBuf;
+            } else if(RxBufLen == PARSE_BUFF_SIZE) {        //濉弧浜嗭紝涓旀病鏈変竴涓猏r锛岄兘鎶涘純
+                DEBUG("Parse GPS error");
+                RxBufLen = 0;
+            }
+        }
+    }
+    if (res == 0) {
+        UninitSerialPort(RTK_MODULE_UART);
+    }
+    pthread_exit(NULL);
+}
+
+
+void handleUnrecognisedNMEA(const uint8_t *data, uint16_t length) {
+//    char buff[4096] = {0};
+//    memcpy(buff, data, MIN(length, 3000));
+//    DEBUG("handleUnrecognisedNMEA: %s", buff);
+}
+
+void handleGPGGA(const struct nmea *s)
+{
+    DEBUG("handleGPGGA num = %d", s->nmea_num);
+    if (s->nmea_num >= 10) {
+        double lat1, lat2, lon1, lon2, alt;
+        int qf;
+
+        qf = str2int(s->nmea_value[5].data, s->nmea_value[5].length);
+
+        if (qf > 0) {
+            str2float(&lat1, s->nmea_value[1].data, 2);
+            str2float(&lat2, s->nmea_value[1].data+2, s->nmea_value[1].length-2);
+
+            str2float(&lon1, s->nmea_value[3].data, 3);
+            str2float(&lon2, s->nmea_value[3].data+3, s->nmea_value[3].length-3);
+
+            lat1 = lat1 + lat2/60.0;
+            lon1 = lon1 + lon2/60.0;
+
+            str2float(&alt, s->nmea_value[8].data, s->nmea_value[8].length);
+
+            gpsStatus.gps_status = 1;
+            gpsStatus.latitude = (uint32_t)(lat1 * 1000000);
+            gpsStatus.longitude = (uint32_t)(lon1 * 1000000);
+
+//            char buff1[32] = {0};
+//            char buff2[32] = {0};
+//            char buff3[32] = {0};
+//
+//            memcpy(buff1, s->nmea_value[1].data, s->nmea_value[1].length);
+//            memcpy(buff2, s->nmea_value[3].data, s->nmea_value[3].length);
+//            memcpy(buff3, s->nmea_value[8].data, s->nmea_value[8].length);
+//
+//            DEBUG("%s %s %s: lat = %ld  lon = %ld alt = %f", buff1, buff2, buff3,  gpsStatus.latitude,  gpsStatus.longitude, alt);
+
+            gpsStatus.altitude = (int) fabs(alt);
+
+            recved_gpgga_time = AppTimer_GetTickCount();
+
+            if (abs(recved_gpgga_time - recved_gprmc_time) < 500) {
+                RequestRtkDownload(gpsStatus.latitude, gpsStatus.longitude, gpsStatus.altitude,
+                                   gpsStatus.bcd_time, 1);
+            }
+        } else {
+            gpsStatus.gps_status = 0;
+        }
+    }
+}
+
+void handleGPGSA(const struct nmea *s) {
+}
+
+void handleGPGSV(const struct nmea *s) {
+}
+
+void handleGPRMC(const struct nmea *s)
+{
+    DEBUG("handleGPRMC num = %d", s->nmea_num);
+    if (s->nmea_num >= 9) {
+
+        int hh = str2int(s->nmea_value[0].data, 2);
+        int mm = str2int(s->nmea_value[0].data + 2, 2);
+        int ss = str2int(s->nmea_value[0].data + 4, 2);
+
+        int DD = str2int(s->nmea_value[8].data, 2);
+        int MM = str2int(s->nmea_value[8].data + 2, 2);
+        int YY = str2int(s->nmea_value[8].data + 4, 2);
+
+        gpsStatus.bcd_time[0] = ((YY/10)<<4) + (YY%10);
+        gpsStatus.bcd_time[1] = ((MM/10)<<4) + (MM%10);
+        gpsStatus.bcd_time[2] = ((DD/10)<<4) + (DD%10);
+        gpsStatus.bcd_time[3] = ((hh/10)<<4) + (hh%10);
+        gpsStatus.bcd_time[4] = ((ss/10)<<4) + (ss%10);
+        gpsStatus.bcd_time[5] = ((mm/10)<<4) + (mm%10);
+
+        recved_gprmc_time = AppTimer_GetTickCount();
+
+        if (abs(recved_gpgga_time - recved_gprmc_time) < 500) {
+            RequestRtkDownload(gpsStatus.latitude, gpsStatus.longitude, gpsStatus.altitude,
+                    gpsStatus.bcd_time, 1);
+        }
+    }
+}
+
+const char *GPS_SOL_OK = "SOL_COMPUTED";
+const char *GPS_SOL_OBS = "INSUFFICIENT_OBS";
+const char *GPS_SOL_CS = "COLD_START";
+
+const char *GPS_POS_NONE = "NONE";
+const char *GPS_POS_FIX = "FIXEDPOS";
+const char *GPS_POS_SIG = "SINGLE";
+const char *GPS_POS_PDIFF = "PSRDIFF";
+const char *GPS_POS_NARF = "NARROW_FLOAT";
+const char *GPS_POS_WIDI = "WIDE_INT";
+const char *GPS_POS_NARI = "NARROW_INT";
+const char *GPS_POS_SWIDL = "SUPER WIDE-LANE";
+
+void handleBESTPOSA(const struct nmea *s) {
+    DEBUG("handleBESTPOSA num = %d", s->nmea_num);
+    double lat, lon;
+
+    if (memcmp(s->nmea_value[0].data, GPS_SOL_OK, s->nmea_value[0].length)) {
+        str2float(&lat, s->nmea_value[2].data, s->nmea_value[2].length);
+        str2float(&lon, s->nmea_value[3].data, s->nmea_value[3].length);
+    }
+}
+
+void handleGPVTG(const struct nmea *s) {
+}
+
+void handleGPGLL(const struct nmea *s) {
+}
+
+void handleGPZDA(const struct nmea *s) {
+}
+
+void handlePJK(const struct nmea *s) {
+    DEBUG("handlePJK num = %d", s->nmea_num);
+
+    CurrRTKInfo.hh = str2int(s->nmea_value[0].data, 2);
+    CurrRTKInfo.mm = str2int(s->nmea_value[0].data + 2, 2);
+    CurrRTKInfo.ss = str2int(s->nmea_value[0].data + 4, 2);
+    CurrRTKInfo.dss = str2int(s->nmea_value[0].data + 7, 2);
+
+    CurrRTKInfo.MM = str2int(s->nmea_value[1].data, 2);
+    CurrRTKInfo.DD = str2int(s->nmea_value[1].data + 2, 2);
+    CurrRTKInfo.YY = str2int(s->nmea_value[1].data + 4, 2);
+
+    CurrRTKInfo.qf = str2int(s->nmea_value[6].data, s->nmea_value[6].length);
+
+    // NOTE: RTK妯″潡鏄互鍗楀寳鍚戜负X杞达紝瑗夸笢鍚戜负Y杞达紝鎴戜滑浜ゆ崲涓嬶紝浠ョ鍚堜竴鑸�昏緫
+    str2float(&CurrRTKInfo.y, s->nmea_value[2].data, s->nmea_value[2].length);
+    str2float(&CurrRTKInfo.x, s->nmea_value[4].data, s->nmea_value[4].length);
+
+
+//    const double by1 = 28.013;
+//    const double bx1 = -11.9669;
+//
+//    const double by2 = 29.3232;
+//    const double bx2 = -9.5057;
+//
+//    static double xx = -10.9669, yy = 28.013;
+//
+//    CurrRTKInfo.y = yy;
+//    CurrRTKInfo.x = xx;
+//
+//    if (forwardx) {
+//        xx += 0.02;
+//        yy += 0.02 * (by2 - by1) / (bx2 - bx1);
+//    } else {
+//        xx -= 0.02;
+//        yy -= 0.02 * (by2 - by1) / (bx2 - bx1);
+//    }
+
+    if (CurrRTKInfo.hh == tra_hh && CurrRTKInfo.mm == tra_mm && CurrRTKInfo.ss == tra_ss && CurrRTKInfo.dss == tra_dss) {
+        UpdateRTKInfo(&CurrRTKInfo);
+//        up_num++;
+        /*if ((up_num % 5) == 0)*/ {
+//            NewMgrEvent(DRIVER_UPDATE_EVT);
+        }
+    }
+}
+
+void handleGPTRA(const struct nmea *s) {
+    DEBUG("handleGPTRA num = %d", s->nmea_num);
+
+    tra_hh = str2int(s->nmea_value[0].data, 2);
+    tra_mm = str2int(s->nmea_value[0].data + 2, 2);
+    tra_ss = str2int(s->nmea_value[0].data + 4, 2);
+    tra_dss = str2int(s->nmea_value[0].data + 7, 2);
+
+    str2float(&CurrRTKInfo.heading, s->nmea_value[1].data, s->nmea_value[1].length);
+
+//    CurrRTKInfo.heading = 60;
+
+    if (CurrRTKInfo.hh == tra_hh && CurrRTKInfo.mm == tra_mm && CurrRTKInfo.ss == tra_ss && CurrRTKInfo.dss == tra_dss) {
+        UpdateRTKInfo(&CurrRTKInfo);
+//        up_num++;
+        /*if ((up_num % 5) == 0)*/ {
+//            NewMgrEvent(DRIVER_UPDATE_EVT);
+        }
+    }
+}
+
+static void CheckPjkParam(void)
+{
+    WriteSerialPort(RTK_MODULE_UART, INQ_PJK_PARAM, strlen(INQ_PJK_PARAM));
+
+    AppTimer_delete(CheckPjkParamTimeout);
+    AppTimer_add(CheckPjkParamTimeout, D_SEC(3));
+}
+
+static void CheckPjkParamTimeout(union sigval sig) {
+    AppTimer_delete(CheckPjkParamTimeout);
+
+    DEBUG("RTK Module failure!!");
+}
diff --git a/lib/src/main/cpp/rtk_module/rtk.h b/lib/src/main/cpp/rtk_module/rtk.h
new file mode 100644
index 0000000..72d80f8
--- /dev/null
+++ b/lib/src/main/cpp/rtk_module/rtk.h
@@ -0,0 +1,22 @@
+//
+// Created by YY on 2019/12/23.
+//
+
+#ifndef RTKDRIVERTEST_RTK_H
+#define RTKDRIVERTEST_RTK_H
+
+typedef struct {
+    uint16_t gps_status;
+    uint32_t latitude;
+    uint32_t longitude;
+    uint16_t altitude;
+    uint8_t bcd_time[6];
+}gpsStatus_t;
+
+void ConfigRTKModule(void);
+void FactorySettings(void);
+void RebootModule(void);
+void SetAYFactoryParam(int freq);
+void GetGpsStatus(gpsStatus_t &data);
+
+#endif //RTKDRIVERTEST_RTK_H
diff --git a/lib/src/main/cpp/rtk_platform/parse_net.cpp b/lib/src/main/cpp/rtk_platform/parse_net.cpp
new file mode 100644
index 0000000..4925d8e
--- /dev/null
+++ b/lib/src/main/cpp/rtk_platform/parse_net.cpp
@@ -0,0 +1,994 @@
+//
+// Created by YY on 2019/10/8.
+//
+
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <malloc.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include "../jni_log.h"
+#include "../defs.h"
+#include "../native-lib.h"
+#include "parse_net.h"
+#include "../common/apptimer.h"
+#include "../common/serial_port.h"
+#include "../common/net.h"
+#include "platform.h"
+#include "../mcu/mcu_if.h"
+
+using namespace std;
+
+#define PKT_HEAD_CHAR           0x7E
+#define MAX_CONTENT_SIZE        1100
+#define MAX_MESSAGE_DATA        1023
+#define PKT_SYNC_HEAD           0x1
+#define PKT_7D                   0x02
+
+#define DEVICE_SERIAL_NUM_SIZE  16
+#define PHONE_NUM_SIZE          8
+#define DEFAULT_MAX_RESEND_CNT          3
+
+#define DEFAULT_SHORT_RESEND_INTERVAL           D_SEC(1)
+#define DEFAULT_LONG_RESEND_INTERVAL           D_SEC(2)
+
+typedef struct {
+    uint16_t length:10;
+    uint16_t encrypt:3;
+    uint16_t is_multi_msg:1;
+} message_attrib_t;
+
+typedef struct {
+    uint8_t version;
+    uint16_t id;
+    union {
+        message_attrib_t a;
+        uint16_t b;
+    } attrib;
+    uint8_t phone_num[PHONE_NUM_SIZE];
+    uint16_t seq;
+    uint8_t reserve;
+    uint16_t multi_msg_num;
+    uint16_t multi_msg_seq;
+
+    uint8_t resend;             //娑堟伅閲嶄紶娆℃暟锛屽鏋滄秷鎭棤闇�杩滅搴旂瓟锛屽氨璁剧疆涓�0锛屽惁鍒欏氨搴斾负澶т簬0鐨勫��
+    uint32_t resend_interval;
+} message_t;
+
+typedef struct {
+    uint16_t type : 1;
+    uint16_t need_rsp : 1;
+    uint16_t reserve : 2;
+    uint16_t encrypt : 4;
+}ex_message_attrib_t;
+
+#define EXTERNAL_ENCRYPT_SIZE           256
+
+typedef struct {
+    uint8_t type;
+    uint16_t id;
+    union {
+        ex_message_attrib_t a;
+        uint16_t b;
+    }attrib;
+    uint16_t seq;
+    uint8_t sn[16];
+    uint32_t length;
+    uint8_t encrypt[EXTERNAL_ENCRYPT_SIZE];             // 鍗曠墖鏈哄拰骞冲彴鐨勬墿灞曟秷鎭兘瑕佸鐞嗭紝鏁呮寜杈冮暱鐨勭畻
+} ex_message_t;
+
+typedef struct message_tx_table_ {
+    uint8_t access;
+    uint16_t id;
+    uint16_t seq;
+    uint8_t curr_cnt;
+    uint8_t max_try_cnt;
+    uint32_t resend_interval;
+    uint32_t time_out;
+    uint32_t length;
+    struct message_tx_table_ *prev;
+    struct message_tx_table_ *next;
+    uint8_t data[0];
+
+} message_tx_table_t;
+
+#define ID_CP_COMMON_RSP        0x0001
+#define ID_PC_COMMON_RSP      0x8001
+#define ID_CP_HEARTBEAT             0x0002
+#define ID_CP_DEVICE_REG            0x0100
+#define ID_PC_DEVICE_REG_RSP    0x8100
+#define ID_CP_LOGIN_REQ         0x0101
+#define ID_CP_RTK_UPLOAD        0x0301
+#define ID_CP_RTK_START_REQ     0x0401
+#define ID_PC_RTK_DOWNLOAD      0x8401
+#define ID_CP_RTK_END_REQ       0x0402
+
+
+const uint8_t PKT_RESEVER = 0x3C;
+
+static message_tx_table_t * message_tx_table_head[DATA_ACCESS_END];
+
+static uint8_t packet_parse_status[DATA_ACCESS_END];
+static uint8_t packet_buffer[DATA_ACCESS_END][MAX_CONTENT_SIZE];
+static uint16_t packet_buffer_length[DATA_ACCESS_END];
+
+struct largeMessage_t{
+    uint16_t currSeq;
+    uint32_t length;
+    uint8_t *data;
+}largeMessage[DATA_ACCESS_END];
+
+static uint8_t PhoneNumber[PHONE_NUM_SIZE] = {0};
+static pthread_mutex_t seq_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t tx_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void ParseTimeout(union sigval sig);
+static uint32_t GetResendTimeout(uint8_t access, uint8_t curr_resend_cnt, uint32_t base_time);
+static uint16_t GetMessageSeq(uint8_t access);
+static void PacketEntry(uint8_t access, const uint8_t *data, uint16_t length);
+static void MessageEntry(uint8_t access, const message_t *msg, const uint8_t *data, uint32_t length);
+static void MakeMessage(message_t *msg, uint8_t version, uint16_t id, uint8_t encrypt, const uint8_t *phone_number, uint8_t reserve, uint8_t resend, uint32_t resend_intval);
+
+static void SendMessage(uint8_t access, const message_t *srcMsg, const uint8_t *data, uint32_t length);
+static void SendPacket(uint8_t access, const message_t *msg, const uint8_t *data, uint16_t id);
+static message_tx_table_t *FindTxQueue(message_tx_table_t **head, uint16_t id, uint16_t seq);
+static message_tx_table_t *FindTxQueue(message_tx_table_t **head, uint32_t tm);
+static message_tx_table_t *FindTxQueue(message_tx_table_t **head, message_tx_table_t *item);
+static void AddTxQueue(message_tx_table_t **head, uint8_t access, uint16_t id, uint16_t seq, const uint8_t *data, uint32_t length, uint8_t resend, uint32_t resend_intval);
+static void RemoveTxQueue(message_tx_table_t **head, uint16_t id, uint16_t seq);
+static void RemoveTxQueue(message_tx_table_t **head, uint16_t id);
+static void RemoveAllTxQueue(message_tx_table_t **head);
+static int SendQueue(message_tx_table_t *item);
+static void ResendItemTimeout(message_tx_table_t **head, uint32_t tm);
+static uint32_t GetResentTimeoutTxQueue(message_tx_table_t **head);
+
+static void *TxQueueMgrThread(void *p);
+static void TriggerResendTxQueue(union sigval sig);
+
+/*
+7E 80 89 00 40 87 00 00 01 37 20 20 55 68 00 76 00 9A 41 11 00 22 00 77 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 2C 00 00 02 E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 04 38 36 39 30 37 34 30 33 30 39 34 38 37 30 32 F2 0F 4B 93 32 B6 CE 5C
+55 98 64 20 D1 7A C6 B7 DB E4 93 15 C9 59 5C F4 54 63 11 5C 81 EE F8 35 52 12 64 A5 51 40 B8 E5
+D7 4D B8 EF 5F C2 2F D4 E5 EA 20 41 E5 C9 F6 BA 64 04 5B 5B 1D F3 9A 09 9D 7E
+ */
+
+void Parse(uint8_t access, const uint8_t *buff, uint16_t length) {
+    /*{
+        static char buffd[16384];
+
+        buffd[0] = 0;
+        DEBUG("Parse access = %s Length = %d", access ? "TCP" : "UART", length);
+        int i = 0;
+        for (i = 0; i < length; i++) {
+            if ((i % 32) == 0) {
+                sprintf(buffd + strlen(buffd), "\n");
+            }
+            sprintf(buffd + strlen(buffd), "%02X ", buff[i]);
+            if (strlen(buffd) > 800) {
+                DEBUG("%s <- %s...", access ? "TCP" : "UART" , buffd);
+                buffd[0] = 0;
+            }
+        }
+        if (strlen(buffd) > 0)
+            DEBUG("%s <- %s", access ? "TCP" : "UART" , buffd);
+    }*/
+
+    for (uint16_t i = 0; i < length; i++) {
+        unsigned char dat = buff[i];
+        if (packet_parse_status[access] & PKT_SYNC_HEAD) {
+            if (dat == PKT_HEAD_CHAR) {
+                packet_parse_status[access] &= ~PKT_SYNC_HEAD;
+                packet_parse_status[access] &= ~PKT_7D;
+                packet_buffer_length[access] = 0;
+
+                AppTimer_add(ParseTimeout, D_SEC(5), access);
+            }
+        } else {
+            if (dat == PKT_HEAD_CHAR) {
+                if (packet_buffer_length[access] == 0) {
+                    /* 鍑虹幇0闀垮害杞借嵎鐨勬儏鍐碉紝寰�寰�鏄崟鐗囨満杩囨潵鐨勬暟鎹甗7E..]..7E锛�7E.....7E鐨勬嫭鍙峰唴涓㈠け浜嗭紝鍜屽悗涓�涓�7E澶寸粨鍚堝湪涓�璧�*/
+                    packet_parse_status[access] &= ~PKT_7D;
+
+                    AppTimer_add(ParseTimeout, D_SEC(5), access);
+                } else {
+                    AppTimer_delete(ParseTimeout);
+                    packet_parse_status[access] = PKT_SYNC_HEAD;
+                    PacketEntry(access, packet_buffer[access], packet_buffer_length[access]);
+                }
+                continue;
+            } else if (dat == 0x7D && !(packet_parse_status[access] & PKT_7D)) {
+                packet_parse_status[access] |= PKT_7D;
+                continue;
+            }
+
+            if (packet_parse_status[access] & PKT_7D) {
+                packet_parse_status[access] &= ~PKT_7D;
+                dat = 0x7D + dat - 1;
+            }
+
+            if (packet_buffer_length[access] < MAX_CONTENT_SIZE) {
+                packet_buffer[access][ packet_buffer_length[access] ] = dat;
+                packet_buffer_length[access]++;
+            } else {                                // Too much, discard
+                DEBUG("Parse error: pkt too large!");
+                AppTimer_delete(ParseTimeout);
+                packet_parse_status[access] = PKT_SYNC_HEAD;
+            }
+        }
+    }
+}
+
+static void ParseTimeout(union sigval sig) {
+    AppTimer_delete(ParseTimeout);
+    packet_parse_status[sig.sival_int] = PKT_SYNC_HEAD;
+
+    DEBUG("ParseTimeout %d", sig.sival_int);
+}
+
+static uint32_t GetResendTimeout(uint8_t access, uint8_t curr_resend_cnt, uint32_t base_time)
+{
+    if (access == DATA_ACCESS_MCU) {
+        return base_time;
+    }
+
+    //T2 = T1 * (n + 1), 棣栨鏃堕棿璁や负鏄�1绉�
+    uint32_t t = base_time;
+    for (int i = 0; i <= curr_resend_cnt; i++) {
+        t = t * (i + 1);
+    }
+
+    return t;
+}
+
+static uint16_t GetMessageSeq(uint8_t access)
+{
+    static uint16_t TxSeq[DATA_ACCESS_END] = {0};
+    uint16_t seq;
+
+    pthread_mutex_lock(&seq_mutex);
+    seq = TxSeq[access];
+    TxSeq[access]++;
+    pthread_mutex_unlock(&seq_mutex);
+
+    return seq;
+}
+
+static void PacketEntry(uint8_t access, const uint8_t *data, uint16_t length) {
+    uint16_t i, x = 0;
+    uint8_t checkByte = 0;
+    message_t theMessage;
+
+    for (i = 0; i < length; i++) {
+        checkByte ^= data[i];
+    }
+
+    if (checkByte != 0 || length <= 16) {
+        DEBUG("recv pkt error, checkByte: 0x%02X, length: %d", checkByte, length);
+        return;
+    }
+
+
+    length--;               // Ignore check byte
+    theMessage.version = data[x++];
+    theMessage.id = BUILD_UINT16(data[x + 1], data[x]);
+    x += 2;
+    theMessage.attrib.b = BUILD_UINT16(data[x + 1], data[x]);
+    x += 2;
+    memcpy(theMessage.phone_num, data + x, PHONE_NUM_SIZE);
+    x += PHONE_NUM_SIZE;
+    theMessage.seq = BUILD_UINT16(data[x + 1], data[x]);
+    x += 2;
+
+    theMessage.reserve = data[x++];
+    theMessage.resend = DEFAULT_MAX_RESEND_CNT;
+
+    if (theMessage.attrib.a.is_multi_msg) {
+        if (x + 4 <= length) {
+            theMessage.multi_msg_num = BUILD_UINT16(data[x + 1], data[x]);
+            theMessage.multi_msg_seq = BUILD_UINT16(data[x + 3], data[x + 2]);
+            x += 4;
+
+            if (theMessage.multi_msg_seq == 1) {
+                // First multi-msg
+                largeMessage[access].currSeq = 1;
+                largeMessage[access].length = 0;
+
+                if (largeMessage[access].data != NULL) {
+                    largeMessage[access].data = (uint8_t *) realloc(largeMessage[access].data,
+                                                                    theMessage.multi_msg_num * MAX_MESSAGE_DATA);
+                } else {
+                    largeMessage[access].data = (uint8_t *) malloc(
+                            theMessage.multi_msg_num * MAX_MESSAGE_DATA);
+                }
+
+                memcpy(largeMessage[access].data, data + x, theMessage.attrib.a.length);
+                largeMessage[access].length = theMessage.attrib.a.length;
+                CommonRespend(access, theMessage.seq, theMessage.id, COMMON_RESP_SUCCESS);
+            } else if (largeMessage[access].data != NULL &&
+                       theMessage.multi_msg_seq != largeMessage[access].currSeq + 1) {
+                //璇锋眰閲嶄紶鍒嗗寘?
+                CommonRespend(access, theMessage.seq, theMessage.id, COMMON_RESP_FAIL);
+                return;
+            } else if (largeMessage[access].data != NULL){
+                largeMessage[access].currSeq = theMessage.multi_msg_seq;
+
+                memcpy(largeMessage[access].data + largeMessage[access].length, data + x, theMessage.attrib.a.length);
+                largeMessage[access].length += theMessage.attrib.a.length;
+
+                CommonRespend(access, theMessage.seq, theMessage.id, COMMON_RESP_SUCCESS);
+
+                if (theMessage.multi_msg_seq == theMessage.multi_msg_num) {
+                    DEBUG("澶氬寘鎺ユ敹瀹屾瘯 %d %d\n", theMessage.multi_msg_seq, largeMessage[access].length);
+                    MessageEntry(access, &theMessage, largeMessage[access].data, largeMessage[access].length);
+                    free(largeMessage[access].data);
+                    largeMessage[access].data = NULL;
+                }
+            }
+        } else {
+            // Length error
+            return;
+        }
+    } else {
+        MessageEntry(access, &theMessage, data + x, theMessage.attrib.a.length);
+    }
+}
+
+static void MessageEntry(uint8_t access, const message_t *msg, const uint8_t *data, uint32_t length)
+{
+    DEBUG("MessageEntry[%d] id = 0x%04X, seq = %d, length = %d", access, msg->id, msg->seq, length);
+
+    switch (msg->id) {
+        case ID_PC_COMMON_RSP:
+            if (length == 5) {
+                uint16_t seq = BUILD_UINT16(data[1], data[0]);
+                uint16_t id = BUILD_UINT16(data[3], data[2]);
+                uint8_t res = data[4];
+
+                DEBUG("ID_PC_COMMON_RSP seq = %d, id = 0x%04X res = %d", seq, id, res);
+
+                RemoveTxQueue(&message_tx_table_head[DATA_ACCESS_PLATFORM], id);
+
+                if (id == ID_CP_LOGIN_REQ) {
+                    DeviceLoginCallback(res);
+                }
+            }
+            break;
+        case ID_PC_DEVICE_REG_RSP: {
+            DEBUG("ID_PC_DEVICE_REG_RSP");
+            RemoveTxQueue(&message_tx_table_head[DATA_ACCESS_PLATFORM], ID_CP_DEVICE_REG);
+            if (length >= 3) {
+                if (data[2] == 0 && length == 11) {
+                    DeviceRegisterCallback(0, data + 3, 8);
+                } else {
+                    DeviceRegisterCallback(data[2], 0, 0);
+                }
+            }
+            break;
+        }
+        case ID_PC_RTK_DOWNLOAD:
+            DEBUG("ID_PC_RTK_DOWNLOAD");
+            // 姹囨姤缁欏崟鐗囨満
+            if (length > 0) {
+                SendRtkToMcu(data, length);
+            }
+            break;
+        default:
+            break;
+    }
+
+    CommonRespend(access, msg->seq, msg->id, COMMON_RESP_FAIL);
+}
+
+static void MakeMessage(message_t *msg, uint8_t version, uint16_t id, uint8_t encrypt, const uint8_t *phone_number, uint8_t reserve, uint8_t resend, uint32_t resend_intval)
+{
+    msg->version = version;
+    msg->id = id;
+    msg->attrib.b = 0;
+    msg->attrib.a.encrypt = encrypt;
+    memcpy(msg->phone_num, phone_number, PHONE_NUM_SIZE);
+    msg->reserve = reserve;
+    msg->resend = resend;
+    msg->resend_interval = resend_intval;
+}
+
+static void SendMessage(uint8_t access, const message_t *srcMsg, const uint8_t *data, uint32_t length)
+{
+    uint16_t total_num = (length + MAX_MESSAGE_DATA - 1)  / MAX_MESSAGE_DATA;
+    uint32_t x = 0;
+    message_t msg = *srcMsg;
+
+    if (length == 0) {
+        total_num = 1;
+    }
+
+    msg.attrib.a.is_multi_msg = (total_num > 1 ? 1 : 0);
+
+    DEBUG("length = %d, total_num %d\n", length, total_num);
+
+    for (uint16_t curr_num = 1; curr_num <= total_num; curr_num++) {
+        uint32_t load = 0;
+
+        DEBUG("curr_num %d\n", curr_num);
+
+        msg.seq = GetMessageSeq(access);
+
+        if (msg.attrib.a.is_multi_msg) {
+            msg.multi_msg_num = total_num;
+            msg.multi_msg_seq = curr_num;
+        }
+
+        load = (length - x > MAX_MESSAGE_DATA) ? MAX_MESSAGE_DATA : (length - x);
+        msg.attrib.a.length = load;
+
+        DEBUG("load %d\n", load);
+
+        SendPacket(access, &msg, data + x, msg.id);
+        x += load;
+    }
+}
+
+static void SendPacket(uint8_t access, const message_t *msg, const uint8_t *data, uint16_t id) {
+    uint8_t buffer[MAX_CONTENT_SIZE];
+    uint8_t buffer2[MAX_CONTENT_SIZE*2];
+
+    uint16_t x = 0;
+
+    DEBUG("SendPacket len = %d", msg->attrib.a.length);
+
+    buffer[x++] = msg->version;
+    buffer[x++] = HI_UINT16(msg->id);
+    buffer[x++] = LO_UINT16(msg->id);
+    buffer[x++] = HI_UINT16(msg->attrib.b);
+    buffer[x++] = LO_UINT16(msg->attrib.b);
+    memcpy(buffer + x, msg->phone_num, PHONE_NUM_SIZE);
+    x += PHONE_NUM_SIZE;
+    buffer[x++] = HI_UINT16(msg->seq);
+    buffer[x++] = LO_UINT16(msg->seq);
+    buffer[x++] = msg->reserve;
+    if (msg->attrib.a.is_multi_msg) {
+        buffer[x++] = HI_UINT16(msg->multi_msg_num);
+        buffer[x++] = LO_UINT16(msg->multi_msg_num);
+        buffer[x++] = HI_UINT16(msg->multi_msg_seq);
+        buffer[x++] = LO_UINT16(msg->multi_msg_seq);
+    }
+
+    memcpy(buffer + x, data, msg->attrib.a.length);
+    x += msg->attrib.a.length;
+
+    uint8_t checkByte = 0;
+    for (int i = 0; i < x; i++) {
+        checkByte ^= buffer[i];
+    }
+    buffer[x++] = checkByte;
+
+    uint32_t y = 0;
+    buffer2[y++] = PKT_HEAD_CHAR;
+    for (int i = 0; i < x; i++) {
+        if (buffer[i] == 0x7E) {
+            buffer2[y++] = 0x7D;
+            buffer2[y++] = 0x02;
+        } else if (buffer[i] == 0x7D) {
+            buffer2[y++] = 0x7D;
+            buffer2[y++] = 0x01;
+        } else {
+            buffer2[y++] = buffer[i];
+        }
+    }
+    buffer2[y++] = PKT_HEAD_CHAR;
+
+    AddTxQueue(&message_tx_table_head[access], access, id, msg->seq, buffer2, y, msg->resend, msg->resend_interval);
+}
+
+
+static message_tx_table_t *FindTxQueue(message_tx_table_t **head, uint16_t id, uint16_t seq)
+{
+    if (head == NULL) {
+        return NULL;
+    }
+    for (message_tx_table_t *ptr = *head; ptr != NULL; ptr = ptr->next) {
+        if (ptr->id == id && ptr->seq == seq) {
+            return ptr;
+        }
+    }
+    return NULL;
+}
+
+static message_tx_table_t *FindTxQueue(message_tx_table_t **head, uint32_t tm)
+{
+    if (head == NULL) {
+        return NULL;
+    }
+
+    for (message_tx_table_t *ptr = *head; ptr != NULL; ptr = ptr->next) {
+        if (ptr->time_out <= tm) {
+            return ptr;
+        }
+    }
+    return NULL;
+}
+
+static message_tx_table_t *FindTxQueue(message_tx_table_t **head, message_tx_table_t *item)
+{
+    if (head == NULL) {
+        return NULL;
+    }
+    for (message_tx_table_t *ptr = *head; ptr != NULL; ptr = ptr->next) {
+        if (ptr == item) {
+            return ptr;
+        }
+    }
+    return NULL;
+}
+
+static void AddTxQueue(message_tx_table_t **head, uint8_t access, uint16_t id, uint16_t seq, const uint8_t *data, uint32_t length, uint8_t resend, uint32_t resend_intval)
+{
+    message_tx_table_t *new_item;
+
+    if (length == 0 || head == NULL) {
+        return;
+    }
+
+    DEBUG("AddTxQueue id = 0x%X, seq = %d, len = %d", id, seq, length);
+
+    pthread_mutex_lock(&tx_queue_mutex);
+
+    // If the item is exist, skip it
+    if (FindTxQueue(head, id, seq) != NULL) {
+        DEBUG("宸茬粡鏈変簡锛屾垜浠笉骞蹭簡");
+        goto ATQ_END;
+    }
+
+    if ((new_item = (message_tx_table_t *)malloc(sizeof(message_tx_table_t) + length))== NULL) {         // Not enough memory!
+        LOGE("Not enough memory!");
+        goto ATQ_END;
+    }
+
+    new_item->next = NULL;
+    new_item->access = access;
+    new_item->id = id;
+    new_item->seq = seq;
+    new_item->length = length;
+    new_item->curr_cnt = 0;
+    new_item->max_try_cnt = 1 + resend;         // Zero means unlimited
+    new_item->resend_interval = resend_intval;
+    new_item->time_out = resend_intval + AppTimer_GetTickCount();
+    memcpy(new_item->data, data, length);
+
+    if (*head == NULL) {
+        new_item->prev = NULL;
+        *head = new_item;
+
+        DEBUG("############### Create head node ##################");
+
+    } else {
+        message_tx_table_t *ptr = *head;
+        while (ptr->next != NULL) {
+            ptr = ptr->next;
+        }
+
+        ptr->next = new_item;
+        new_item->prev = ptr;
+    }
+
+    SendQueue(new_item);
+
+    if (new_item->max_try_cnt > 0 && new_item->curr_cnt >= new_item->max_try_cnt) {
+        if (new_item == *head) {
+            if (new_item->next == NULL) {
+                *head = NULL;
+                DEBUG("****************** Delete all ******************");
+            } else {
+                *head = new_item->next;
+                new_item->next->prev = NULL;
+            }
+        } else {
+            if (new_item->next != NULL) {
+                new_item->next->prev = new_item->prev;
+            }
+            new_item->prev->next = new_item->next;
+        }
+        free(new_item);
+    }
+
+    ATQ_END:
+    pthread_mutex_unlock(&tx_queue_mutex);
+
+    uint32_t tim = GetResentTimeoutTxQueue(&message_tx_table_head[DATA_ACCESS_PLATFORM]);
+
+    DEBUG("NEXT ====  %ld", tim);
+
+    AppTimer_delete(TriggerResendTxQueue);
+    if (tim != uint32_t (-1)) {
+        AppTimer_add(TriggerResendTxQueue, tim);
+    }
+}
+
+static void RemoveTxQueue(message_tx_table_t **head, uint16_t id, uint16_t seq)
+{
+    if (head == NULL)
+        return;
+
+    message_tx_table_t *ptr = *head;
+
+    pthread_mutex_lock(&tx_queue_mutex);
+    while ( ptr != NULL ) {
+        if (ptr->id == id && ptr->seq == seq) {
+            // delete
+            message_tx_table_t *temp = ptr;
+
+            if (ptr == *head) {
+                if (ptr->next == NULL) {
+                    DEBUG("****************** RemoveTxQueue Delete all ******************");
+                    ptr = *head = NULL;
+                } else {
+                    *head = ptr->next;
+                    ptr->next->prev = NULL;
+                    ptr = *head;
+                }
+            } else {
+                if (ptr->next != NULL) {
+                    ptr->next->prev = ptr->prev;
+                }
+                ptr->prev->next = ptr->next;
+                ptr = ptr->next;
+            }
+
+            free(temp);
+        } else {
+            ptr = ptr->next;
+        }
+    }
+    pthread_mutex_unlock(&tx_queue_mutex);
+}
+
+static void RemoveTxQueue(message_tx_table_t **head, uint16_t id)
+{
+    if (head == NULL)
+        return;
+
+    message_tx_table_t *ptr = *head;
+
+    pthread_mutex_lock(&tx_queue_mutex);
+    while ( ptr != NULL ) {
+        if (ptr->id == id) {
+            // delete
+            message_tx_table_t *temp = ptr;
+            DEBUG("****************** 鎴戜滑鎵惧埌浜嗭紝 寮勬瀹� ******************");
+
+            if (ptr == *head) {
+                if (ptr->next == NULL) {
+                    DEBUG("****************** RemoveTxQueue Delete all ******************");
+                    ptr = *head = NULL;
+                } else {
+                    *head = ptr->next;
+                    ptr->next->prev = NULL;
+                    ptr = *head;
+                }
+            } else {
+                if (ptr->next != NULL) {
+                    ptr->next->prev = ptr->prev;
+                }
+                ptr->prev->next = ptr->next;
+                ptr = ptr->next;
+            }
+
+            free(temp);
+        } else {
+            ptr = ptr->next;
+        }
+    }
+    pthread_mutex_unlock(&tx_queue_mutex);
+}
+
+static void RemoveAllTxQueue(message_tx_table_t **head)
+{
+    if (head == NULL) {
+        return;
+    }
+
+    pthread_mutex_lock(&tx_queue_mutex);
+    for (message_tx_table_t *ptr = *head, *next; ptr != NULL; ptr = next) {
+        next = ptr->next;
+        free(ptr);
+    }
+    *head = NULL;
+    pthread_mutex_unlock(&tx_queue_mutex);
+}
+
+static int SendQueue(message_tx_table_t *item)
+{
+    DEBUG("SendQueue id = 0x%04X, seq = %d, length = %d", item->id, item->seq, item->length);
+    if (item != NULL) {
+        if (item->access == DATA_ACCESS_MCU) {
+            if (WriteSerialPort(GetSerialPort(UART_1), item->data, item->length) != item->length) {
+                item->time_out = 100 + AppTimer_GetTickCount();
+                LOGE("鍙戝線涓插彛鍑洪敊浜�");
+//                return -1;
+            }
+        } else if(item->access == DATA_ACCESS_PLATFORM) {
+            if (WritePlatform(item->data, item->length) != item->length) {
+                item->time_out = D_SEC(3) + AppTimer_GetTickCount();
+                LOGE("鍙戝線缃戠粶鍑洪敊浜� seq = %d", item->seq);
+//                return -2;
+            }
+        } else {
+//            return 0;
+        }
+
+#ifdef ENABLE_DEBUG_PROTOCOL
+        /*{
+            static char buff[16384];
+
+            buff[0] = 0;
+
+            int i = 0;
+            for (i = 0; i < item->length; i++) {
+                if ((i % 32) == 0) {
+                    sprintf(buff + strlen(buff), "\n");
+                }
+                sprintf(buff + strlen(buff), "%02X ", item->data[i]);
+
+                if (strlen(buff) > 800) {
+                    DEBUG("%s -> %s...", item->access == DATA_ACCESS_MCU ? "UART" : "TCP" , buff);
+                    buff[0] = 0;
+                }
+            }
+
+            if (strlen(buff) > 0) {
+                DEBUG("%s -> %s", item->access == DATA_ACCESS_MCU ? "UART" : "TCP", buff);
+            }
+        }*/
+#endif
+        // 绯荤粺灞傜‘璁ゅ彂閫佹垚鍔�
+        item->curr_cnt += 1;
+        item->time_out = GetResendTimeout(item->access, item->curr_cnt - 1, item->resend_interval) + AppTimer_GetTickCount();
+    }
+    return 0;
+}
+
+static void ResendItemTimeout(message_tx_table_t **head, uint32_t tm)
+{
+    if (head == NULL)
+        return;
+
+    message_tx_table_t *ptr = *head;
+
+    while ( ptr != NULL ) {
+        if (ptr->time_out <= tm) {
+            // send
+            LOGE("閲嶅彂 id 0x%04X seq %d", ptr->id, ptr->seq);
+            int ret = SendQueue(ptr);
+            if (ret < 0 && ret != -3) {
+                break;
+            }
+            if (ptr->max_try_cnt > 0 && ptr->curr_cnt >= ptr->max_try_cnt) {
+                // delete
+                DEBUG("Delete item %d", ptr->curr_cnt);
+                message_tx_table_t *temp = ptr;
+
+                pthread_mutex_lock(&tx_queue_mutex);
+                if (ptr == *head) {
+                    if (ptr->next == NULL) {
+                        DEBUG("****************** Delete all 2 ******************");
+                        ptr = *head = NULL;
+                    } else {
+                        *head = ptr->next;
+                        ptr->next->prev = NULL;
+                        ptr = *head;
+                    }
+                } else {
+                    if (ptr->next != NULL) {
+                        ptr->next->prev = ptr->prev;
+                    }
+                    ptr->prev->next = ptr->next;
+                    ptr = ptr->next;
+                }
+                pthread_mutex_unlock(&tx_queue_mutex);
+
+                free(temp);
+            } else {
+                ptr = ptr->next;
+            }
+        } else {
+            ptr = ptr->next;
+        }
+    }
+}
+
+static uint32_t GetResentTimeoutTxQueue(message_tx_table_t **head)
+{
+    uint32_t resentTime = uint32_t (-1);
+    uint32_t now = AppTimer_GetTickCount();
+
+    if (head == NULL)
+        return resentTime;
+
+    pthread_mutex_lock(&tx_queue_mutex);
+    message_tx_table_t *ptr = *head;
+
+    while ( ptr != NULL ) {
+        if (ptr->time_out <= now) {
+            resentTime = 0;
+            break;
+        } else if (ptr->time_out - now < resentTime) {
+            resentTime = ptr->time_out - now;
+        }
+        ptr = ptr->next;
+    }
+    pthread_mutex_unlock(&tx_queue_mutex);
+
+    return resentTime;
+}
+
+static sem_t sem_tx_mgr;
+
+static void *TxQueueMgrThread(void *p) {
+    while (true) {
+        sem_wait(&sem_tx_mgr);
+        uint32_t tim = AppTimer_GetTickCount();
+
+        DEBUG("TxQueueMgrThread %ld", tim);
+
+        // Check send queue, and resend
+        ResendItemTimeout(&message_tx_table_head[DATA_ACCESS_PLATFORM], tim);
+
+        tim = GetResentTimeoutTxQueue(&message_tx_table_head[DATA_ACCESS_PLATFORM]);
+
+        DEBUG("NEXT ====  %ld", tim);
+        AppTimer_delete(TriggerResendTxQueue);
+        if (tim != uint32_t (-1)) {
+            AppTimer_add(TriggerResendTxQueue, tim);
+        }
+    }
+    pthread_exit(NULL);
+}
+
+static void TriggerResendTxQueue(union sigval sig) {
+    AppTimer_delete(TriggerResendTxQueue);
+    sem_post(&sem_tx_mgr);
+}
+
+void PlatformTxInit(const uint8_t *phone)
+{
+    //2019101500000001
+    memcpy(PhoneNumber, phone, PHONE_NUM_SIZE);
+
+/*    PhoneNumber[0] = 0x20;
+    PhoneNumber[1] = 0x19;
+    PhoneNumber[2] = 0x10;
+    PhoneNumber[3] = 0x15;
+    PhoneNumber[4] = 0x00;
+    PhoneNumber[5] = 0x00;
+    PhoneNumber[6] = 0x00;
+    PhoneNumber[7] = 0x02;*/
+
+    pthread_mutex_init(&seq_mutex, NULL);
+    pthread_mutex_init(&tx_queue_mutex, NULL);
+    sem_init(&sem_tx_mgr, 0, 0);
+
+    pthread_t pid;
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//detached
+    pthread_create(&pid, &attr, TxQueueMgrThread, NULL);
+}
+
+void CommonRespend(uint8_t access, uint16_t seq, uint16_t id, uint8_t value)
+{
+    uint8_t data[5];
+
+    data[0] = HI_UINT16(seq);
+    data[1] = LO_UINT16(seq);
+    data[2] = HI_UINT16(id);
+    data[3] = LO_UINT16(id);
+    data[4] = value;
+
+    message_t msg;
+    MakeMessage(&msg, MESSAGE_VERSION_B2016, ID_CP_COMMON_RSP, PROTOCOL_ENCRYPT_NONE,
+                PhoneNumber, PKT_RESEVER, 0, DEFAULT_SHORT_RESEND_INTERVAL);
+
+    SendMessage(access, &msg, data, sizeof(data));
+}
+
+void SendHeartBeat(uint8_t access)
+{
+    message_t msg;
+    MakeMessage(&msg, MESSAGE_VERSION_B2016, ID_CP_HEARTBEAT, PROTOCOL_ENCRYPT_NONE,
+                PhoneNumber, PKT_RESEVER, 0, DEFAULT_SHORT_RESEND_INTERVAL);
+    SendMessage(access, &msg, NULL, 0);
+}
+
+void SendDeviceRegister(uint16_t province, uint16_t city, const uint8_t *device_model,
+        int device_model_length, const uint8_t *device_sn, const char *imei)
+{
+    uint8_t data[64] = {0};
+    int x = 0;
+
+    data[x++] = HI_UINT16(province);
+    data[x++] = LO_UINT16(province);
+    data[x++] = HI_UINT16(city);
+    data[x++] = LO_UINT16(city);
+    memcpy(data + x, device_model, device_model_length);
+    x += 20;
+    memcpy(data + x, device_sn, 16);
+    x += 16;
+    memcpy(data + x, imei, strlen(imei));
+    x += 15;
+    message_t msg;
+    MakeMessage(&msg, MESSAGE_VERSION_B2016, ID_CP_DEVICE_REG, PROTOCOL_ENCRYPT_NONE,
+                PhoneNumber, PKT_RESEVER, 0, DEFAULT_SHORT_RESEND_INTERVAL);
+    SendMessage(DATA_ACCESS_PLATFORM, &msg, data, x);
+}
+
+void SendDeviceLogin(const uint8_t *data, int length)
+{
+    message_t msg;
+    MakeMessage(&msg, MESSAGE_VERSION_B2016, ID_CP_LOGIN_REQ, PROTOCOL_ENCRYPT_NONE,
+                PhoneNumber, PKT_RESEVER, 0, DEFAULT_SHORT_RESEND_INTERVAL);
+    SendMessage(DATA_ACCESS_PLATFORM, &msg, data, length);
+}
+
+void SendRTKReport(uint8_t gps_status, uint32_t latitude, uint32_t longitude, uint16_t altitude,
+        const uint8_t *bcd_time, const uint8_t *rtk, int rtk_length)
+{
+    uint8_t data[MAX_CONTENT_SIZE];
+    int x = 0;
+
+    data[x++] = gps_status;
+    data[x++] = BREAK_UINT32(latitude, 3);
+    data[x++] = BREAK_UINT32(latitude, 2);
+    data[x++] = BREAK_UINT32(latitude, 1);
+    data[x++] = BREAK_UINT32(latitude, 0);
+    data[x++] = BREAK_UINT32(longitude, 3);
+    data[x++] = BREAK_UINT32(longitude, 2);
+    data[x++] = BREAK_UINT32(longitude, 1);
+    data[x++] = BREAK_UINT32(longitude, 0);
+    data[x++] = HI_UINT16(altitude);
+    data[x++] = LO_UINT16(altitude);
+    memcpy(data + x, bcd_time, 6);
+    x += 6;
+    memcpy(data + x, rtk, rtk_length);
+    x += rtk_length;
+
+    message_t msg;
+    MakeMessage(&msg, MESSAGE_VERSION_B2016, ID_CP_RTK_UPLOAD, PROTOCOL_ENCRYPT_NONE,
+                PhoneNumber, PKT_RESEVER, 0, DEFAULT_SHORT_RESEND_INTERVAL);
+    SendMessage(DATA_ACCESS_PLATFORM, &msg, data, x);
+}
+
+void SendRTKStart(uint32_t latitude, uint32_t longitude, uint16_t altitude,
+                    const uint8_t *bcd_time, uint16_t rtk_pkt_interval)
+{
+    uint8_t data[18];
+    int x = 0;
+
+    data[x++] = BREAK_UINT32(latitude, 3);
+    data[x++] = BREAK_UINT32(latitude, 2);
+    data[x++] = BREAK_UINT32(latitude, 1);
+    data[x++] = BREAK_UINT32(latitude, 0);
+    data[x++] = BREAK_UINT32(longitude, 3);
+    data[x++] = BREAK_UINT32(longitude, 2);
+    data[x++] = BREAK_UINT32(longitude, 1);
+    data[x++] = BREAK_UINT32(longitude, 0);
+    data[x++] = HI_UINT16(altitude);
+    data[x++] = LO_UINT16(altitude);
+    memcpy(data + x, bcd_time, 6);
+    x += 6;
+    data[x++] = HI_UINT16(rtk_pkt_interval);
+    data[x++] = LO_UINT16(rtk_pkt_interval);
+
+    message_t msg;
+    MakeMessage(&msg, MESSAGE_VERSION_B2016, ID_CP_RTK_START_REQ, PROTOCOL_ENCRYPT_NONE,
+                PhoneNumber, PKT_RESEVER, 0, DEFAULT_SHORT_RESEND_INTERVAL);
+    SendMessage(DATA_ACCESS_PLATFORM, &msg, data, x);
+}
+
+void SendRTKStop(void)
+{
+    message_t msg;
+    MakeMessage(&msg, MESSAGE_VERSION_B2016, ID_CP_RTK_END_REQ, PROTOCOL_ENCRYPT_NONE,
+                PhoneNumber, PKT_RESEVER, 0, DEFAULT_SHORT_RESEND_INTERVAL);
+    SendMessage(DATA_ACCESS_PLATFORM, &msg, NULL, 0);
+}
diff --git a/lib/src/main/cpp/rtk_platform/parse_net.h b/lib/src/main/cpp/rtk_platform/parse_net.h
new file mode 100644
index 0000000..179b96c
--- /dev/null
+++ b/lib/src/main/cpp/rtk_platform/parse_net.h
@@ -0,0 +1,55 @@
+//
+// Created by YY on 2019/10/8.
+//
+
+#ifndef RTKBASESTATION_PARSE_NET_H
+#define RTKBASESTATION_PARSE_NET_H
+
+enum
+{
+    DATA_ACCESS_MCU = 0,
+    DATA_ACCESS_PLATFORM,
+    DATA_ACCESS_PLATFORM_2,
+    DATA_ACCESS_PLATFORM_LOG,
+    DATA_ACCESS_END
+};
+
+#define MESSAGE_VERSION_JT808          0
+#define MESSAGE_VERSION_A2013          1
+#define MESSAGE_VERSION_B2016          128
+
+#define PROTOCOL_ENCRYPT_NONE           0
+#define PROTOCOL_ENCRYPT_RSA2048        1
+
+#define COMMON_RESP_SUCCESS           0
+#define COMMON_RESP_FAIL           1
+#define COMMON_RESP_ERROR           2
+#define COMMON_RESP_NOT_SUPPORT     3
+
+
+#define EX_MESSAGE_TYPE_INTERNAL       0x9A
+#define EX_MESSAGE_TYPE_EXTERNAL       0x13
+
+#define EX_MESSAGE_DOWNLOAD         0x8900
+#define EX_MESSAGE_UPLOAD           0x0900
+
+#define EX_MESSAGE_ATTRIB_RESEND        1           //鏄惁鏄噸浼犲寘
+#define EX_MESSAGE_ATTRIB_INDICATE     2            //璇ュ寘鏄惁闇�瑕佽繙绔簲绛�
+#define EX_MESSAGE_ATTRIB_SHA1          4
+#define EX_MESSAGE_ATTRIB_SHA256        8
+
+void PlatformTxInit(const uint8_t *phone);
+void Parse(uint8_t access, const uint8_t *buff, uint16_t length);
+void CommonRespend(uint8_t access, uint16_t seq, uint16_t id, uint8_t value);
+
+void SendHeartBeat(uint8_t access);
+void SendDeviceRegister(uint16_t province, uint16_t city, const uint8_t *device_model,
+                    int device_model_length, const uint8_t *device_sn, const char *imei);
+void SendDeviceLogin(const uint8_t *data, int length);
+void SendRTKReport(uint8_t gps_status, uint32_t latitude, uint32_t longitude, uint16_t altitude,
+               const uint8_t *bcd_time, const uint8_t *rtk, int rtk_length);
+void SendRTKStart(uint32_t latitude, uint32_t longitude, uint16_t altitude,
+                    const uint8_t *bcd_time, uint16_t rtk_pkt_interval);
+void SendRTKStop(void);
+
+#endif //RTKBASESTATION_PARSE_NET_H
diff --git a/lib/src/main/cpp/rtk_platform/platform.cpp b/lib/src/main/cpp/rtk_platform/platform.cpp
new file mode 100644
index 0000000..99751dd
--- /dev/null
+++ b/lib/src/main/cpp/rtk_platform/platform.cpp
@@ -0,0 +1,361 @@
+//
+// Created by YY on 2019/12/27.
+//
+
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <pthread.h>
+#include <semaphore.h>
+#include "platform.h"
+#include "../jni_log.h"
+#include "../common/net.h"
+#include "../native-lib.h"
+#include "../common/apptimer.h"
+#include "parse_net.h"
+#include "../defs.h"
+#include "../rtk_module/rtk.h"
+
+#define PARSE_BUFF_SIZE         4096
+
+const char *VK_REG = "platform_register";
+
+struct platformSocket {
+    char domain_name[32];
+    int port;
+};
+
+static struct deviceInfo_ {
+    uint16_t province;
+    uint16_t city;
+    uint8_t device_model[21];
+    uint8_t device_sn[17];
+    char imei[16];
+}deviceInfo;
+
+struct platformSocketInfo {
+    char domain_name[32];
+    int port;
+};
+
+static struct platformStatus_ {
+    uint8_t platformKey[64];
+    int platformKeyLength;
+    uint32_t connected : 1;
+    uint32_t registed : 1;
+    uint32_t login : 1;
+    uint32_t downloadRtk : 1;
+} platformStatus;
+
+struct platformSocket exceptSocket, currSocket;
+static uint32_t eventMask;
+static sem_t sem_status_changed;
+
+static bool requestPlatformSendRtk = false;
+static int platform_tcp_fd = 0;
+
+static pthread_mutex_t platform_tx_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t events_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void ConnectPlatform(const char *domain_name, int port);
+static void ConnectPlatformLater(union sigval sig);
+
+static void *PlatformDataListenThread(void *p);
+static void *StatusListenThread(void *p);
+
+static void RegisterPlatform(void);
+static void RegisterPlatformTimeout(union sigval sig);
+
+static void LoginPlatform(void);
+static void LoginPlatformTimeout(union sigval sig);
+static void TriggerHeartbeat(union sigval sig);
+
+void InitPlatform(const uint8_t *phone, const char *domain_name, int port)
+{
+    pthread_mutex_init(&platform_tx_mutex, NULL);
+    pthread_mutex_init(&events_mutex, NULL);
+
+    eventMask = 0;
+    platform_tcp_fd = -1;
+    memset(&currSocket, 0, sizeof(currSocket));
+    strcpy(exceptSocket.domain_name, domain_name);
+    exceptSocket.port = port;
+
+    sem_init(&sem_status_changed, 0, 0);
+
+    strcpy(deviceInfo.imei, GetImei());
+    deviceInfo.province = 53;
+    deviceInfo.city = 100;
+    strcpy((char *)deviceInfo.device_model, "RTKBaseStation");
+    strcpy((char *)deviceInfo.device_sn, "2019101500000001");
+
+    PlatformTxInit(phone);
+
+    pthread_t pid;
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//detached
+    pthread_create(&pid, &attr, StatusListenThread, NULL);
+
+    strcpy(deviceInfo.imei, GetImei());
+
+    ConnectPlatform(domain_name, port);
+}
+
+void PlatformStatusChanged(uint32_t event)
+{
+    pthread_mutex_lock(&events_mutex);
+    eventMask |= event;
+    pthread_mutex_unlock(&events_mutex);
+    sem_post(&sem_status_changed);
+}
+
+static void ConnectPlatform(const char *domain_name, int port)
+{
+    DEBUG("ConnectPlatform %s: %d", domain_name, port);
+    // TODO
+    struct platformSocketInfo *ptr = (struct platformSocketInfo *)malloc(sizeof(struct platformSocketInfo));
+
+    strcpy(ptr->domain_name, domain_name);
+    ptr->port = port;
+
+    pthread_t platform_pid;
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//detached
+    pthread_create(&platform_pid, &attr, PlatformDataListenThread, ptr);
+
+    char out[64];
+    sprintf(out, "杩炴帴骞冲彴 %s:%d...", domain_name, port);
+    DisplayText(out);
+}
+
+static void ConnectPlatformLater(union sigval sig) {
+    AppTimer_delete(ConnectPlatformLater);
+    ConnectPlatform(exceptSocket.domain_name, exceptSocket.port);
+}
+
+static void *StatusListenThread(void *p) {
+    while (true) {
+        sem_wait(&sem_status_changed);
+
+        uint32_t events;
+        pthread_mutex_lock(&events_mutex);
+        events = eventMask;
+        eventMask = 0;
+        pthread_mutex_unlock(&events_mutex);
+
+        if (events & PLATFORM_CONNECT_EVT) {
+            char out[64];
+            sprintf(out, "骞冲彴杩炴帴鎴愬姛 %s:%d", currSocket.domain_name, currSocket.port);
+            DisplayText(out);
+
+            platformStatus.connected = 1;
+            if (!platformStatus.registed || platformStatus.platformKeyLength == 0) {
+                RegisterPlatform();
+            } else if (!platformStatus.login) {
+                LoginPlatform();
+            }
+        }
+
+        if (events & PLATFORM_DISCONNECT_EVT) {
+            char out[64];
+            sprintf(out, "骞冲彴鏂紑 %s:%d", currSocket.domain_name, currSocket.port);
+            DisplayText(out);
+
+            AppTimer_delete(ConnectPlatformLater);
+            AppTimer_add(ConnectPlatformLater, D_SEC(2));
+
+            platformStatus.login = 0;
+            platformStatus.connected = 0;
+
+            AppTimer_delete(TriggerHeartbeat);
+            AppTimer_delete(RegisterPlatformTimeout);
+            AppTimer_delete(LoginPlatformTimeout);
+        }
+        if (events & PLATFORM_REGISTER_EVT) {
+            DEBUG("PLATFORM_REGISTER_EVT");
+            platformStatus.platformKeyLength = GetPlatformKey(platformStatus.platformKey);
+            platformStatus.registed = GetSharedValue(VK_REG);
+            platformStatus.login = 0;
+            DEBUG("platformStatus.platformKeyLength = %d", platformStatus.platformKeyLength);
+
+            if (platformStatus.registed && platformStatus.platformKeyLength > 0) {
+                LoginPlatform();
+            }
+        }
+        if (events & PLATFORM_LOGIN_EVT) {
+            DEBUG("PLATFORM_LOGIN_EVT");
+            platformStatus.login = 1;
+            requestPlatformSendRtk = true;
+            AppTimer_delete(TriggerHeartbeat);
+            AppTimer_add(TriggerHeartbeat, D_SEC(30));
+        }
+    }
+}
+
+static void *PlatformDataListenThread(void *p) {
+    struct platformSocket *ptr = (struct platformSocket *)p;
+
+    uint8_t RxBuf[PARSE_BUFF_SIZE];
+
+    int fds_ret;
+    struct timeval tv;
+    fd_set rdfds;
+    fd_set exfds;
+
+    int fd = -1;
+    int RxBufLen = 0;
+
+    fd = ConnectTCP(ptr->domain_name, ptr->port);
+
+    pthread_mutex_lock(&platform_tx_mutex);
+    platform_tcp_fd = fd;
+
+    currSocket = *ptr;
+
+    pthread_mutex_unlock(&platform_tx_mutex);
+
+    if (fd > 0) {
+        PlatformStatusChanged(PLATFORM_CONNECT_EVT);
+    }
+
+    while (fd > 0) {
+        tv.tv_sec = 5;
+        tv.tv_usec = 0;
+        FD_ZERO(&rdfds); //clean
+        FD_SET(fd, &rdfds); //set
+
+        fds_ret = select(fd + 1, &rdfds, NULL, NULL, &tv);
+
+        if (fds_ret < 0) {
+            break;
+        } else if(fds_ret == 0) {
+            //Occur failure(such as line disconnect)
+        } else if(FD_ISSET(fd, &rdfds)) {
+            RxBufLen = ReadTCP(fd, RxBuf, sizeof(RxBuf));
+
+            if (RxBufLen < 0) {
+                break;
+            } else if (RxBufLen > 0) {
+                Parse(DATA_ACCESS_PLATFORM, RxBuf, RxBufLen);
+            }
+        }
+    }
+
+    pthread_mutex_lock(&platform_tx_mutex);
+    if (platform_tcp_fd > 0) {
+        DisconnectTCP(platform_tcp_fd);
+        platform_tcp_fd = -1;
+    }
+    pthread_mutex_unlock(&platform_tx_mutex);
+    free(ptr);
+
+    PlatformStatusChanged(PLATFORM_DISCONNECT_EVT);
+    pthread_exit(NULL);
+}
+
+int WritePlatform(const uint8_t * buf, uint32_t len)
+{
+    int ret = -1;
+
+    pthread_mutex_lock(&platform_tx_mutex);
+    if (platform_tcp_fd > 0) {
+        ret = WriteTCP(platform_tcp_fd, buf, len);
+    }
+    pthread_mutex_unlock(&platform_tx_mutex);
+
+    return ret;
+}
+
+static void RegisterPlatformTimeout(union sigval sig)
+{
+    DEBUG("RegisterPlatformTimeout");
+    AppTimer_delete(RegisterPlatformTimeout);
+    RegisterPlatform();
+}
+
+static void RegisterPlatform(void)
+{
+    AppTimer_delete(RegisterPlatformTimeout);
+    AppTimer_add(RegisterPlatformTimeout, D_SEC(15));
+    SendDeviceRegister(deviceInfo.province, deviceInfo.city, deviceInfo.device_model,
+                       strlen((char *)deviceInfo.device_model), deviceInfo.device_sn, deviceInfo.imei);
+}
+
+static void TriggerHeartbeat(union sigval sig) {
+    AppTimer_delete(TriggerHeartbeat);
+
+    if (platformStatus.login && platformStatus.connected) {
+        SendHeartBeat(DATA_ACCESS_PLATFORM);
+        AppTimer_add(TriggerHeartbeat, D_SEC(30));
+    }
+}
+
+static void LoginPlatformTimeout(union sigval sig)
+{
+    AppTimer_delete(LoginPlatformTimeout);
+    LoginPlatform();
+}
+
+static void LoginPlatform(void)
+{
+    uint32_t tim = time(NULL);
+    uint8_t data[12];
+    uint8_t *ciphertext;
+
+    data[0] = BREAK_UINT32(tim, 3);
+    data[1] = BREAK_UINT32(tim, 2);
+    data[2] = BREAK_UINT32(tim, 1);
+    data[3] = BREAK_UINT32(tim, 0);
+
+    AppTimer_delete(LoginPlatformTimeout);
+    AppTimer_add(LoginPlatformTimeout, D_SEC(15));
+
+    DESEncrypt(platformStatus.platformKey, platformStatus.platformKeyLength, data, 4, &ciphertext);
+
+    if (ciphertext != NULL) {
+        memcpy(data + 4, ciphertext, 8);
+        SendDeviceLogin(data, sizeof(data));
+    }
+}
+
+void DeviceRegisterCallback(uint8_t res, const uint8_t *data, int length)
+{
+    AppTimer_delete(RegisterPlatformTimeout);
+    if (res != 0) {
+
+    } else {
+        TextSpeak("缁堢娉ㄥ唽鎴愬姛");
+        DisplayText("缁堢娉ㄥ唽鎴愬姛");
+        SetPlatformKey(data, length);
+        SetSharedValue(VK_REG, 1);
+        PlatformStatusChanged(PLATFORM_REGISTER_EVT);
+    }
+}
+
+void DeviceLoginCallback(uint8_t res)
+{
+    AppTimer_delete(LoginPlatformTimeout);
+    if (res != 0) {
+
+    } else {
+        TextSpeak("缁堢鐧诲綍鎴愬姛");
+        DisplayText("缁堢鐧诲綍鎴愬姛");
+        PlatformStatusChanged(PLATFORM_LOGIN_EVT);
+    }
+}
+
+void RequestRtkDownload(uint32_t latitude, uint32_t longitude, uint16_t altitude,
+                        const uint8_t *bcd_time, uint16_t rtk_pkt_interval)
+{
+    if (requestPlatformSendRtk) {
+        requestPlatformSendRtk = false;
+        SendRTKStart(latitude, longitude, altitude, bcd_time, rtk_pkt_interval);
+    }
+}
+
+void StopRtkDownload(void)
+{
+    SendRTKStop();
+}
diff --git a/lib/src/main/cpp/rtk_platform/platform.h b/lib/src/main/cpp/rtk_platform/platform.h
new file mode 100644
index 0000000..03e0ca6
--- /dev/null
+++ b/lib/src/main/cpp/rtk_platform/platform.h
@@ -0,0 +1,25 @@
+//
+// Created by YY on 2019/12/27.
+//
+
+#ifndef RTKDRIVERTEST_PLATFORM_H
+#define RTKDRIVERTEST_PLATFORM_H
+
+#define PLATFORM_CONNECT_EVT                         0x0001
+#define PLATFORM_DISCONNECT_EVT                      0x0002
+#define PLATFORM_REGISTER_EVT                        0x0004
+#define PLATFORM_LOGIN_EVT                           0x0008
+#define RTK_UPDATE_EVT                               0x0010
+#define GPS_UPDATE_EVT                              0x0020
+
+void InitPlatform(const uint8_t *phone, const char *domain_name, int port);
+void PlatformStatusChanged(uint32_t event);
+int WritePlatform(const uint8_t * buf, uint32_t len);
+void DeviceRegisterCallback(uint8_t res, const uint8_t *data, int length);
+void DeviceLoginCallback(uint8_t res);
+
+void RequestRtkDownload(uint32_t latitude, uint32_t longitude, uint16_t altitude,
+                        const uint8_t *bcd_time, uint16_t rtk_pkt_interval);
+void StopRtkDownload(void);
+
+#endif //RTKDRIVERTEST_PLATFORM_H
diff --git a/lib/src/main/cpp/test_items/driving_curve.cpp b/lib/src/main/cpp/test_items/driving_curve.cpp
new file mode 100644
index 0000000..c924ced
--- /dev/null
+++ b/lib/src/main/cpp/test_items/driving_curve.cpp
@@ -0,0 +1,140 @@
+//
+// Created by YY on 2019/11/4.
+//
+
+#include "driving_curve.h"
+#include "../driver_test.h"
+#include "../common/apptimer.h"
+
+#include <vector>
+
+using namespace std;
+
+enum {
+    DRIVING_ON_CURVE
+};
+
+const uint32_t STOP_CAR_TIME = D_SEC(2);
+
+static bool carStopEvent = false;
+static bool DCTesting = false;
+static int currTarget;
+static uint32_t stopTimepoint = 0;
+
+static bool CrashRedLine(const Polygon *left, const Polygon *right, const car_model_cache_t *car);
+static bool ExitTestArea(const Polygon *left, const Polygon *right, const car_model_cache_t *car);
+
+void StartDrivingCurve(void)
+{
+    DCTesting = true;
+    carStopEvent = false;
+    currTarget = DRIVING_ON_CURVE;
+}
+
+void StopDrivingCurve(void)
+{
+    DCTesting = false;
+}
+
+int TestDrivingCurve(vector<int>&err, const Polygon *map, const Polygon *map2, const car_model_cache_t *car, double speed, int run_status)
+{
+    int status = 0;
+
+    if (!DCTesting)
+        return -2;
+
+    if (currTarget == DRIVING_ON_CURVE) {
+        if (CrashRedLine(map, map2, car)) {
+            // 杞﹁疆鍘嬬嚎
+            err.push_back(27);
+            status = -1;
+        }
+
+        if (ExitTestArea(map, map2, car)) {
+            // 娴嬭瘯瀹屾垚
+            status = 1;
+        }
+
+        if (run_status != 0) {
+            if (carStopEvent && AppTimer_GetTickCount() - stopTimepoint > D_SEC(2)) {
+                // 涓�斿仠杞�
+                err.push_back(28);
+            }
+            carStopEvent = false;
+        } else {
+            carStopEvent = true;
+            stopTimepoint = AppTimer_GetTickCount();
+        }
+    }
+
+    if (status != 0) {
+        StopDrivingCurve();
+    }
+
+    return status;
+}
+
+// 杞﹁疆鏄惁鍘嬭竟绾�
+static bool CrashRedLine(const Polygon *left, const Polygon *right, const car_model_cache_t *car)
+{
+    bool ret = false;
+
+    car_model_cache_t *prev_car = GetCarModelCache(1);
+    if (prev_car == NULL)
+        return false;
+
+    // 鎸�4涓疆瀛愮殑澶栦晶璁$畻
+    Line front_left_tire_track, front_right_tire_track, rear_left_tire_track, rear_right_tire_track;
+    MakeLine(&front_left_tire_track,  &car->points[car->desc->front_left_tire[TIRE_OUTSIDE]],  &prev_car->points[car->desc->front_left_tire[TIRE_OUTSIDE]]);
+    MakeLine(&front_right_tire_track,  &car->points[car->desc->front_right_tire[TIRE_OUTSIDE]],  &prev_car->points[car->desc->front_right_tire[TIRE_OUTSIDE]]);
+    MakeLine(&rear_left_tire_track,  &car->points[car->desc->rear_left_tire[TIRE_OUTSIDE]],  &prev_car->points[car->desc->rear_left_tire[TIRE_OUTSIDE]]);
+    MakeLine(&rear_right_tire_track,  &car->points[car->desc->rear_right_tire[TIRE_OUTSIDE]],  &prev_car->points[car->desc->rear_right_tire[TIRE_OUTSIDE]]);
+
+    Line line;
+
+    line.X1 = left->point[0].X;
+    line.Y1 = left->point[0].Y;
+    for (int i = 1; i < left->num; ++i) {
+        line.X2 = left->point[i].X;
+        line.Y2 = left->point[i].Y;
+
+        if (IntersectionOf(line, front_left_tire_track) == GM_Intersection ||
+                IntersectionOf(line, front_right_tire_track) == GM_Intersection ||
+                IntersectionOf(line, rear_left_tire_track) == GM_Intersection ||
+                IntersectionOf(line, rear_right_tire_track) == GM_Intersection) {
+            ret = true;
+            break;
+        }
+        line.X1 = line.X2;
+        line.Y1 = line.Y2;
+    }
+
+    line.X1 = right->point[0].X;
+    line.Y1 = right->point[0].Y;
+    for (int i = 1; !ret && i < right->num; ++i) {
+        line.X2 = right->point[i].X;
+        line.Y2 = right->point[i].Y;
+
+        if (IntersectionOf(line, front_left_tire_track) == GM_Intersection ||
+            IntersectionOf(line, front_right_tire_track) == GM_Intersection ||
+            IntersectionOf(line, rear_left_tire_track) == GM_Intersection ||
+            IntersectionOf(line, rear_right_tire_track) == GM_Intersection) {
+            ret = true;
+            break;
+        }
+        line.X1 = line.X2;
+        line.Y1 = line.Y2;
+    }
+
+    return ret;
+}
+
+// 鏁翠釜杞﹁締閮借椹剁璇ユ祴璇曞尯鍩�
+static bool ExitTestArea(const Polygon *left, const Polygon *right, const car_model_cache_t *car)
+{
+    for (int i = 0; i < car->point_num; ++i) {
+        if (IntersectionOfLine(left->point[0], right->point[0], car->points[i]) != 1)
+            return false;
+    }
+    return true;
+}
diff --git a/lib/src/main/cpp/test_items/driving_curve.h b/lib/src/main/cpp/test_items/driving_curve.h
new file mode 100644
index 0000000..3628bd4
--- /dev/null
+++ b/lib/src/main/cpp/test_items/driving_curve.h
@@ -0,0 +1,17 @@
+//
+// Created by YY on 2019/11/4.
+//
+
+#ifndef RTKDRIVERTEST_DRIVING_CURVE_H
+#define RTKDRIVERTEST_DRIVING_CURVE_H
+
+#include "../driver_test.h"
+#include <vector>
+
+using namespace std;
+
+void StartDrivingCurve(void);
+void StopDrivingCurve(void);
+int TestDrivingCurve(vector<int>&err, const Polygon *map, const Polygon *map2, const car_model_cache_t *car, double speed, int run_status);
+
+#endif //RTKDRIVERTEST_DRIVING_CURVE_H
diff --git a/lib/src/main/cpp/test_items/error_list.cpp b/lib/src/main/cpp/test_items/error_list.cpp
new file mode 100644
index 0000000..a03740d
--- /dev/null
+++ b/lib/src/main/cpp/test_items/error_list.cpp
@@ -0,0 +1,207 @@
+#include "error_list.h"
+
+//
+// Created by YY on 2019/10/31.
+//
+const error_list_t errorList[] = {
+        {
+                .id = 0,
+                .text_desc = "",
+                .item = 0,
+                .dec_score = 0
+        },
+        {
+                .id = 1,
+                .text_desc = "涓嶆寜瑙勫畾浣跨敤瀹夊叏甯�",
+                .item = 1,
+                .dec_score = 100
+        },
+        {
+                .id = 2,
+                .text_desc = "涓嶆寜鑰冭瘯鍛樻寚浠よ椹�",
+                .item = 1,
+                .dec_score = 100
+        },
+        {
+                .id = 3,
+                .text_desc = "鍚姩鍙戝姩鏈烘椂鎸′綅鏈疆浜庣┖妗o紙椹昏溅鎸★級",
+                .item = 1,
+                .dec_score = 100
+        },
+        {
+                .id = 4,
+                .text_desc = "鍙戝姩鏈哄惎鍔ㄥ悗锛屼笉鍙婃椂鏉惧紑鍚姩寮�鍏�",
+                .item = 1,
+                .dec_score = 10
+        },
+        {
+                .id = 5,
+                .text_desc = "鍥犳搷浣滀笉褰撻�犳垚鍙戝姩鏈虹唲鐏竴娆�",
+                .item = 1,
+                .dec_score = 10
+        },
+        {
+                .id = 6,
+                .text_desc = "涓嶆寜瑙勫畾绾胯矾銆侀『搴忚椹�",
+                .item = 2,
+                .dec_score = 100
+        },
+        {
+                .id = 7,
+                .text_desc = "杞﹁韩鍑虹嚎",
+                .item = 2,
+                .dec_score = 100
+        },
+        {
+                .id = 8,
+                .text_desc = "鍊掑簱涓嶅叆",
+                .item = 2,
+                .dec_score = 100
+        },
+        {
+                .id = 9,
+                .text_desc = "鍦ㄥ�掕溅鍓嶏紝鏈皢涓や釜鍓嶈疆瑙﹀湴鐐瑰潎椹惰繃鎺у埗绾�",
+                .item = 2,
+                .dec_score = 100
+        },
+        {
+                .id = 10,
+                .text_desc = "椤圭洰瀹屾垚鏃堕棿瓒呰繃210绉�",
+                .item = 2,
+                .dec_score = 100
+        },
+        {
+                .id = 11,
+                .text_desc = "涓�斿仠杞︽椂闂磋秴杩�2绉�",
+                .item = 2,
+                .dec_score = 5
+        },
+        {
+                .id = 12,
+                .text_desc = "杞﹁締鍋滄鍚庯紝鍓嶄繚闄╂潬鏈畾浜庢々鏉嗙嚎涓婏紝涓斿墠鍚庤秴鍑�50cm",
+                .item = 3,
+                .dec_score = 100
+        },
+        {
+                .id = 13,
+                .text_desc = "琛岄┒涓溅杞帇绾�",
+                .item = 3,
+                .dec_score = 100
+        },
+        {
+                .id = 14,
+                .text_desc = "杞﹁締鍋滄鍚庯紝杞﹁韩璺濈璺竟绾胯秴鍑�50cm",
+                .item = 3,
+                .dec_score = 100
+        },
+        {
+                .id = 15,
+                .text_desc = "璧锋鏃堕棿瓒呰繃30s",
+                .item = 3,
+                .dec_score = 100
+        },
+        {
+                .id = 16,
+                .text_desc = "璧锋鍚庢簻澶т簬30cm",
+                .item = 3,
+                .dec_score = 100
+        },
+        {
+                .id = 17,
+                .text_desc = "杞﹁締鍋滄鍚庯紝鍓嶄繚闄╂潬鏈畾浜庢々鏉嗙嚎涓婏紝涓斿墠鍚庝笉瓒呭嚭50cm",
+                .item = 3,
+                .dec_score = 10
+        },
+        {
+                .id = 18,
+                .text_desc = "杞﹁締鍋滄鍚庯紝杞﹁韩璺濈璺竟缂樼嚎瓒呭嚭30cm锛屾湭瓒呭嚭50cm",
+                .item = 3,
+                .dec_score = 10
+        },
+        {
+                .id = 19,
+                .text_desc = "鍋滆溅鍚庯紝鏈媺绱ч┗杞﹀埗鍔ㄥ櫒",
+                .item = 3,
+                .dec_score = 10
+        },
+        {
+                .id = 20,
+                .text_desc = "璧锋鏃惰溅杈嗗悗婧滆窛绂�10cm~30cm",
+                .item = 3,
+                .dec_score = 10
+        },
+        {
+                .id = 21,
+                .text_desc = "杞﹁締鍏ュ簱鍋滄鍚庯紝杞﹁韩鍑虹嚎",
+                .item = 4,
+                .dec_score = 100
+        },
+        {
+                .id = 22,
+                .text_desc = "椤圭洰瀹屾垚鏃堕棿瓒呰繃90s",
+                .item = 4,
+                .dec_score = 100
+        },
+        {
+                .id = 23,
+                .text_desc = "琛岄┒涓溅杞Е杞ц溅閬撹竟绾�",
+                .item = 4,
+                .dec_score = 10
+        },
+        {
+                .id = 24,
+                .text_desc = "琛岄┒涓溅韬Е纰拌溅浣嶈竟绾�",
+                .item = 4,
+                .dec_score = 10
+        },
+        {
+                .id = 25,
+                .text_desc = "鍑哄簱鏃朵笉浣跨敤鎴栭敊璇娇鐢ㄨ浆鍚戠伅",
+                .item = 4,
+                .dec_score = 10
+        },
+        {
+                .id = 26,
+                .text_desc = "涓�斿仠杞︽椂闂磋秴杩�2绉�",
+                .item = 4,
+                .dec_score = 5
+        },
+        {
+                .id = 27,
+                .text_desc = "杞﹁疆杞ч亾璺竟缂樼嚎",
+                .item = 5,
+                .dec_score = 100
+        },
+        {
+                .id = 28,
+                .text_desc = "涓�斿仠杞︽椂闂磋秴杩�2绉�",
+                .item = 5,
+                .dec_score = 100
+        },
+        {
+                .id = 29,
+                .text_desc = "杞﹁疆杞ч亾璺竟缂樼嚎",
+                .item = 6,
+                .dec_score = 100
+        },
+        {
+                .id = 30,
+                .text_desc = "杞集鏃朵笉浣跨敤鎴栭敊璇娇鐢ㄨ浆鍚戠伅锛岃浆寮悗涓嶅叧闂浆鍚戠伅",
+                .item = 6,
+                .dec_score = 10
+        },
+        {
+                .id = 31,
+                .text_desc = "涓�斿仠杞︽椂闂磋秴杩�2绉�",
+                .item = 6,
+                .dec_score = 5
+        }
+};
+
+error_list_t GetErrorList(int index)
+{
+        if (index >= sizeof(errorList) / sizeof(errorList[0]))
+                return errorList[0];
+
+        return errorList[index];
+}
diff --git a/lib/src/main/cpp/test_items/error_list.h b/lib/src/main/cpp/test_items/error_list.h
new file mode 100644
index 0000000..ab50b4c
--- /dev/null
+++ b/lib/src/main/cpp/test_items/error_list.h
@@ -0,0 +1,17 @@
+//
+// Created by YY on 2019/10/29.
+//
+
+#ifndef RTKDRIVERTEST_ERROR_LIST_H
+#define RTKDRIVERTEST_ERROR_LIST_H
+
+typedef struct {
+    int id;
+    int item;
+    char *text_desc;
+    int dec_score;
+}error_list_t;
+
+error_list_t GetErrorList(int index);
+
+#endif //RTKDRIVERTEST_ERROR_LIST_H
diff --git a/lib/src/main/cpp/test_items/park_bottom.cpp b/lib/src/main/cpp/test_items/park_bottom.cpp
new file mode 100644
index 0000000..d7f1422
--- /dev/null
+++ b/lib/src/main/cpp/test_items/park_bottom.cpp
@@ -0,0 +1,380 @@
+//
+// Created by YY on 2019/10/23.
+//
+
+#include "park_bottom.h"
+#include "../common/apptimer.h"
+#include "../Geometry.h"
+#include "../native-lib.h"
+#include "../jni_log.h"
+#include "../driver_test.h"
+#include <vector>
+
+using namespace std;
+
+enum {
+    NONE,
+    FIRST_TOUCH_CTRL_LINE,
+    FIRST_PARK,
+    SECOND_TOUCH_CTRL_LINE,
+    SECOND_PARK,
+    THIRD_TOUCH_CTRL_LINE
+};
+
+const int PARK_TIMEOUT = 210;
+
+static bool PBTesting = false;
+static bool trigLeaveTestAreaDetect = false;
+static bool leaveTestArea = false;
+static bool stopCar2S = false;
+
+static int currTarget;
+static bool leftTireCrossLeftLine, leftTireCrossRightLine, rightTireCrossLeftLine, rightTireCrossRightLine;
+static char first_ctrl_line_id;
+static bool carStopEvent;               // 涓�斿仠杞︽爣璁�
+static bool carParkSuccess;             // 鏄惁鍋滃湪搴撲綅
+static bool parkTimeout;
+
+static void StopCarTimeout(union sigval sig);
+static void LeaveTestAreaLongtime(union sigval sig);
+static void CrossCtrlLine(const Polygon *map, const car_model_cache_t *car);
+static bool EnterParking(const Polygon *map, const car_model_cache_t *car);
+static void ParkBottomTimeout(union sigval sig);
+static bool CrashRedLine(const Polygon *map, const car_model_cache_t *car);
+
+void StartParkBottom(void)
+{
+    stopCar2S = false;
+    trigLeaveTestAreaDetect = false;
+    leaveTestArea = false;
+    PBTesting = true;
+    parkTimeout = false;
+    first_ctrl_line_id = 0;
+    currTarget = FIRST_TOUCH_CTRL_LINE;
+    leftTireCrossLeftLine = leftTireCrossRightLine = rightTireCrossLeftLine = rightTireCrossRightLine = false;
+
+    TextOsd(0, "ParkBottom");
+}
+
+void StopParkBottom(void)
+{
+    PBTesting = false;
+    AppTimer_delete(StopCarTimeout);
+    AppTimer_delete(ParkBottomTimeout);
+    AppTimer_delete(LeaveTestAreaLongtime);
+    currTarget = NONE;
+
+    TextOsd(0, "ParkBottom End");
+}
+
+int TestParkBottom(vector<int>&err, const Polygon *map, const car_model_cache_t *car, double speed, int run_status)
+{
+    int status = 0;
+
+    if (!PBTesting)
+        return -2;
+
+    DEBUG("TestParkBottom speed %f dir %d", speed, run_status);
+
+
+    if (currTarget > FIRST_TOUCH_CTRL_LINE) {
+        // 鏄惁瓒呮椂
+        if (parkTimeout) {
+            // 涓嶅悎鏍硷細鍔ㄤ綔瓒呮椂
+            err.push_back(10);
+            status = -1;
+        }
+        // 鏄惁鍘嬬嚎
+        if (CrashRedLine(map, car)) {
+            // 涓嶅悎鏍硷細杞﹁韩鍑虹嚎
+            err.push_back(7);
+            status = -1;
+        }
+
+        if (trigLeaveTestAreaDetect) {
+            if (IntersectionOf(car->points[car->desc->front_left_tire[TIRE_OUTSIDE]], map) == GM_Containment &&
+                IntersectionOf(car->points[car->desc->front_right_tire[TIRE_OUTSIDE]], map) == GM_Containment) {
+                trigLeaveTestAreaDetect = false;
+                AppTimer_delete(LeaveTestAreaLongtime);
+            }
+        }
+    }
+
+    if (currTarget == FIRST_TOUCH_CTRL_LINE ||
+        currTarget == SECOND_TOUCH_CTRL_LINE ||
+        currTarget == THIRD_TOUCH_CTRL_LINE) {
+        if (run_status > 0) {
+            if (stopCar2S && currTarget != FIRST_TOUCH_CTRL_LINE) {
+                // 鎵�5鍒嗭細涓�斿仠杞﹁秴杩�2绉�
+                err.push_back(11);
+            }
+
+            if (!((leftTireCrossLeftLine && rightTireCrossLeftLine) ||
+                    (leftTireCrossRightLine && rightTireCrossRightLine))) {
+                DEBUG("CrossCtrlLine");
+                CrossCtrlLine(map, car);
+            } else if (currTarget == FIRST_TOUCH_CTRL_LINE || currTarget == SECOND_TOUCH_CTRL_LINE) {
+                // 璺ㄨ繃鎺у埗绾垮悗锛岃溅杈嗘寔缁悜鍓嶈椹讹紝澶勭悊杩欎簺涔辨悶鎯呭喌
+                // 鏁翠釜杞﹂兘绂诲紑娴嬭瘯鍖哄悗锛屽鏋滄寔缁�15绉掞紝杩樻病鍥炲埌娴嬭瘯鍖猴紝灏卞拷鐣ヨ娴嬭瘯鎴栬�呮窐姹�
+                if (leaveTestArea) {
+                    if (currTarget == FIRST_TOUCH_CTRL_LINE) {
+                        status = -2;
+                        TextOsd(0, "鏉ラ亾鍦虹帺鐨�");
+                        DEBUG("鏉ラ亾鍦虹帺鐨�");
+                    } else {
+                        // 涓嶅悎鏍硷細鏈寜瑙勫畾绾胯矾琛岄┒锛堢洿鎺ヨ窇鍑烘祴璇曞尯浜嗭級
+                        err.push_back(6);
+                        status = -1;
+                        TextOsd(0, "鐩存帴璺戝嚭娴嬭瘯鍖轰簡");
+                        DEBUG("鐩存帴璺戝嚭娴嬭瘯鍖轰簡");
+                    }
+                } else if (!trigLeaveTestAreaDetect) {
+                    Polygon car_body;
+
+                    car_body.num = car->desc->body_num;
+                    car_body.point = (PointF *) malloc(sizeof(PointF) * car_body.num);
+                    for (int i = 0; i < car_body.num; ++i) {
+                        car_body.point[i] = car->points[car->desc->body[i]];
+                    }
+
+                    if (IntersectionOf(map, &car_body) == GM_None) {
+                        trigLeaveTestAreaDetect = true;
+                        AppTimer_delete(LeaveTestAreaLongtime);
+                        AppTimer_add(LeaveTestAreaLongtime, D_SEC(15));
+                        DEBUG("寮�濮嬬鍦鸿鏃�");
+                    }
+
+                    free(car_body.point);
+                }
+            }
+
+            if (currTarget == THIRD_TOUCH_CTRL_LINE) {
+                char the_ctrl_line_crossed = 0;
+
+                if (leftTireCrossLeftLine && rightTireCrossLeftLine) {
+                    the_ctrl_line_crossed = 'L';
+                } else if (leftTireCrossRightLine && rightTireCrossRightLine) {
+                    the_ctrl_line_crossed = 'R';
+                }
+
+                if (the_ctrl_line_crossed != 0 && the_ctrl_line_crossed == first_ctrl_line_id) {
+                    // 椤圭洰瀹屾垚
+                    status = 1;
+                } else if (the_ctrl_line_crossed != 0) {
+                    // 涓嶅悎鏍硷細鏈寜瑙勫畾绾胯矾琛岄┒锛堟湭鍥炲埌璧峰鐐癸級
+                    err.push_back(6);
+                    status = -1;
+                }
+            }
+            if (carStopEvent)
+                AppTimer_delete(StopCarTimeout);
+            carStopEvent = false;
+            stopCar2S = false;
+        } else if (run_status < 0) {
+            // 宸﹀彸鍊掑簱澶х翰骞舵湭瑕佹眰璋佸厛瀹屾垚锛屾晠浠ュ厛瓒婅繃鐨勬帶鍒剁嚎涓哄噯锛屼笅娆″緱瓒婅繃鍙﹀涓�鏉�
+            char the_ctrl_line_crossed = 0;
+
+            if (leftTireCrossLeftLine && rightTireCrossLeftLine) {
+                the_ctrl_line_crossed = 'L';
+            } else if (leftTireCrossRightLine && rightTireCrossRightLine) {
+                the_ctrl_line_crossed = 'R';
+            }
+
+            if (first_ctrl_line_id > 0 && first_ctrl_line_id == the_ctrl_line_crossed) {
+                // 涓嶅悎鏍硷細鏈寜瑙勫畾绾胯矾琛岄┒锛堣瘯鍥惧仛2娆″悓鏂瑰悜鐨勫�掑簱锛�
+                err.push_back(6);
+                status = -1;
+            } else if (the_ctrl_line_crossed > 0 && first_ctrl_line_id == 0) {
+                first_ctrl_line_id = the_ctrl_line_crossed;
+                // 椤圭洰姝e紡寮�濮嬶紝210绉掑唴瀹屾垚
+                AppTimer_delete(ParkBottomTimeout);
+                AppTimer_add(ParkBottomTimeout, D_SEC(PARK_TIMEOUT));
+                currTarget = FIRST_PARK;
+                carParkSuccess = false;
+                parkTimeout = false;
+                leftTireCrossLeftLine = leftTireCrossRightLine = rightTireCrossLeftLine = rightTireCrossRightLine = false;
+
+                TextOsd(0, "绗竴娆″�掑簱");
+            } else if (the_ctrl_line_crossed > 0) {
+                currTarget = SECOND_PARK;
+                carParkSuccess = false;
+                leftTireCrossLeftLine = leftTireCrossRightLine = rightTireCrossLeftLine = rightTireCrossRightLine = false;
+                TextOsd(0, "绗簩娆″�掑簱");
+            } else if (currTarget != THIRD_TOUCH_CTRL_LINE) {
+                // 涓嶅悎鏍硷細鍊掕溅鍓嶏紝2鍓嶈疆娌¢┒杩囨帶鍒剁嚎
+                err.push_back(9);
+                status = -1;
+            }
+            if (carStopEvent)
+                AppTimer_delete(StopCarTimeout);
+            carStopEvent = false;
+            stopCar2S = false;
+        } else {
+            if (!carStopEvent) {
+                AppTimer_delete(StopCarTimeout);
+                AppTimer_add(StopCarTimeout, D_SEC(2));
+            }
+            carStopEvent = true;
+        }
+    } else if (currTarget == FIRST_PARK || currTarget == SECOND_PARK) {
+        if (run_status < 0) {
+            if (stopCar2S) {
+                // 鎵�5鍒嗭細涓�斿仠杞�
+                err.push_back(11);
+            }
+
+            carStopEvent = false;
+            stopCar2S = false;
+        } else if (run_status == 0) {
+            // 绔嬪嵆妫�鏌ユ槸鍚﹀仠杞﹀埌浣嶏紝涔熻鏄腑閫斿仠杞︼紝鍏堜笉绠★紝寰呭彂鐢熷墠杩涗簨浠跺悗锛屽啀鏂畾鏄惁鍋滆溅鍒颁綅
+            if (!carStopEvent) {
+                carStopEvent = true;
+                carParkSuccess = EnterParking(map, car);
+                AppTimer_delete(StopCarTimeout);
+                AppTimer_add(StopCarTimeout, D_SEC(2));
+            }
+        } else {
+            if (carStopEvent) {
+                if (!carParkSuccess) {
+                    // 涓嶅悎鏍硷細鍊掑簱涓嶅叆
+                    err.push_back(8);
+                    status = -1;
+                } else if (currTarget == FIRST_PARK) {
+                    currTarget = SECOND_TOUCH_CTRL_LINE;
+                    TextOsd(0, "杩囧彟涓�鏍规帶鍒剁嚎");
+                } else {
+                    currTarget = THIRD_TOUCH_CTRL_LINE;
+                    TextOsd(0, "鍐嶈繃绗竴鏍规帶鍒剁嚎");
+                }
+            }
+            carStopEvent = false;
+            stopCar2S = false;
+        }
+    }
+
+    if (status != 0) {
+        StopParkBottom();
+    }
+
+    return status;
+}
+
+static void StopCarTimeout(union sigval sig) {
+    AppTimer_delete(StopCarTimeout);
+
+    stopCar2S = true;
+}
+
+static void LeaveTestAreaLongtime(union sigval sig) {
+    AppTimer_delete(LeaveTestAreaLongtime);
+    leaveTestArea = true;
+    trigLeaveTestAreaDetect = false;
+}
+
+static void ParkBottomTimeout(union sigval sig) {
+    AppTimer_delete(ParkBottomTimeout);
+    parkTimeout = true;
+}
+
+// 妫�娴�2鍓嶈疆鏄惁姝e悜瓒婅繃宸﹀彸鎺у埗绾�
+static void CrossCtrlLine(const Polygon *map, const car_model_cache_t *car)
+{
+    Line leftCtrlLine, rightCtrlLine;
+    Line track1;
+    PointF p1, p2;
+
+    car_model_cache_t *prev_car = GetCarModelCache(1);
+    if (prev_car == NULL)
+        return;
+
+    MakeLine(&leftCtrlLine, &map->point[0], &map->point[1]);
+    MakeLine(&rightCtrlLine, &map->point[6], &map->point[7]);
+
+    // 宸﹀墠杞紝鍙栬疆瀹界殑涓偣
+    p1.X = (car->points[car->desc->front_left_tire[TIRE_OUTSIDE]].X + car->points[car->desc->front_left_tire[TIRE_INSIDE]].X) / 2;
+    p1.Y = (car->points[car->desc->front_left_tire[TIRE_OUTSIDE]].Y + car->points[car->desc->front_left_tire[TIRE_INSIDE]].Y) / 2;
+
+    p2.X = (prev_car->points[prev_car->desc->front_left_tire[TIRE_OUTSIDE]].X + prev_car->points[prev_car->desc->front_left_tire[TIRE_INSIDE]].X) / 2;
+    p2.Y = (prev_car->points[prev_car->desc->front_left_tire[TIRE_OUTSIDE]].Y + prev_car->points[prev_car->desc->front_left_tire[TIRE_INSIDE]].Y) / 2;
+
+    MakeLine(&track1, &p1, &p2);
+    
+    if (IntersectionOf(track1, leftCtrlLine) == GM_Intersection &&
+        IntersectionOf(p1, map) == GM_None) {
+        leftTireCrossLeftLine = true;
+    }
+
+    if (IntersectionOf(track1, rightCtrlLine) == GM_Intersection &&
+        IntersectionOf(p1, map) == GM_None) {
+        leftTireCrossRightLine = true;
+    }
+
+    // 鍙冲墠杞�
+    p1.X = (car->points[car->desc->front_right_tire[TIRE_OUTSIDE]].X + car->points[car->desc->front_right_tire[TIRE_INSIDE]].X) / 2;
+    p1.Y = (car->points[car->desc->front_right_tire[TIRE_OUTSIDE]].Y + car->points[car->desc->front_right_tire[TIRE_INSIDE]].Y) / 2;
+
+    p2.X = (prev_car->points[prev_car->desc->front_right_tire[TIRE_OUTSIDE]].X + prev_car->points[prev_car->desc->front_right_tire[TIRE_INSIDE]].X) / 2;
+    p2.Y = (prev_car->points[prev_car->desc->front_right_tire[TIRE_OUTSIDE]].Y + prev_car->points[prev_car->desc->front_right_tire[TIRE_INSIDE]].Y) / 2;
+
+    MakeLine(&track1, &p1, &p2);
+
+    if (IntersectionOf(track1, leftCtrlLine) == GM_Intersection &&
+        IntersectionOf(p1, map) == GM_None) {
+        rightTireCrossLeftLine = true;
+    }
+    if (IntersectionOf(track1, rightCtrlLine) == GM_Intersection &&
+        IntersectionOf(p1, map) == GM_None) {
+        rightTireCrossRightLine = true;
+    }
+}
+
+static bool EnterParking(const Polygon *map, const car_model_cache_t *car) {
+    bool succ = false;
+
+    Polygon parking;
+    Polygon car_body;
+
+    car_body.num = car->desc->body_num;
+    car_body.point = (PointF *) malloc(sizeof(PointF) * car_body.num);
+    for (int i = 0; i < car_body.num; ++i) {
+        car_body.point[i] = car->points[car->desc->body[i]];
+    }
+
+    MakePolygon(&parking, {map->point[2], map->point[3], map->point[4], map->point[5]});
+
+    if (IntersectionOf(&car_body, &parking) == GM_Containment) {
+        succ = true;
+    }
+
+    CleanPolygon(&parking);
+    free(car_body.point);
+
+    return succ;
+}
+
+static bool CrashRedLine(const Polygon *map, const car_model_cache_t *car)
+{
+    bool ret = false;
+
+    Line red_line;
+    const int red_lines[][2] = {{0, 7}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 6}};
+
+    Polygon car_body;
+
+    car_body.num = car->desc->body_num;
+    car_body.point = (PointF *) malloc(sizeof(PointF) * car_body.num);
+    for (int i = 0; i < car_body.num; ++i) {
+        car_body.point[i] = car->points[car->desc->body[i]];
+    }
+
+    for (int i = 0; i < sizeof(red_lines) / sizeof(red_lines[0]); ++i) {
+        MakeLine(&red_line, &map->point[red_lines[i][0]], &map->point[red_lines[i][1]]);
+        if (IntersectionOf(red_line, &car_body) != GM_None) {
+            ret = true;
+            break;
+        }
+    }
+
+    free(car_body.point);
+    return ret;
+}
diff --git a/lib/src/main/cpp/test_items/park_bottom.h b/lib/src/main/cpp/test_items/park_bottom.h
new file mode 100644
index 0000000..7fc8606
--- /dev/null
+++ b/lib/src/main/cpp/test_items/park_bottom.h
@@ -0,0 +1,18 @@
+//
+// Created by YY on 2019/10/23.
+//
+
+#ifndef RTKDRIVERTEST_PARK_BOTTOM_H
+#define RTKDRIVERTEST_PARK_BOTTOM_H
+
+#include "../Geometry.h"
+#include "../driver_test.h"
+#include <vector>
+
+using namespace std;
+
+void StartParkBottom(void);
+void StopParkBottom(void);
+int TestParkBottom(vector<int>&err, const Polygon *map, const car_model_cache_t *car, double speed, int run_status);
+
+#endif //RTKDRIVERTEST_PARK_BOTTOM_H
diff --git a/lib/src/main/cpp/test_items/park_edge.cpp b/lib/src/main/cpp/test_items/park_edge.cpp
new file mode 100644
index 0000000..205013e
--- /dev/null
+++ b/lib/src/main/cpp/test_items/park_edge.cpp
@@ -0,0 +1,274 @@
+//
+// Created by YY on 2019/10/23.
+//
+
+#include "park_edge.h"
+#include "../Geometry.h"
+#include "../driver_test.h"
+#include "../common/apptimer.h"
+#include "../native-lib.h"
+
+#include <vector>
+
+using namespace std;
+
+enum {
+    ARRIVED_START,
+    PARK_CAR,
+    START_CAR
+};
+
+const int PARK_TIMEOUT = 90;
+const uint32_t STOP_CAR_TIME = D_SEC(2);
+
+static int prev_run_status;
+static int runStatusBeforeStop;
+static uint32_t stopTimepoint = 0;
+static bool parkTimeout;
+static bool occurCrashRedLine1, occurCrashRedLine2;
+static bool PETesting = false;
+static int currTarget;
+static bool carStopEvent;               // 涓�斿仠杞︽爣璁�
+static bool carParkSuccess;             // 鏄惁鍋滃湪搴撲綅
+static int leaveParkCnt;                // 杞﹁締绂诲紑搴撲綅鍒氬彂鐢熺殑鏃讹紝妫�鏌ユ槸鍚﹀紑鍚浆鍚戠伅
+
+static void ParkEdgeTimeout(union sigval sig);
+static bool CrashRedLine1(const Polygon *map, const car_model_cache_t *car);
+static bool CrashRedLine2(const Polygon *map, const car_model_cache_t *car);
+static bool EnterParking(const Polygon *map, const car_model_cache_t *car);
+static bool ExitParkArea(const Polygon *map, const car_model_cache_t *car);
+static bool ExitTestArea(const Polygon *map, const car_model_cache_t *car);
+
+bool EnterParkEdgeArea(const Polygon *car, const Polygon *tire, const Polygon *map)
+{
+    if (IntersectionOf(tire->point[0], map) == GM_Containment &&
+        IntersectionOf(tire->point[1], map) == GM_Containment &&
+        IntersectionOf(tire->point[4], map) == GM_Containment &&
+        IntersectionOf(tire->point[5], map) == GM_Containment) {
+        return true;
+    }
+    return false;
+}
+
+void StartParkEdge(void)
+{
+    prev_run_status = 0;
+    parkTimeout = false;
+    occurCrashRedLine1 = occurCrashRedLine2 = false;        // 杩欎釜绉戠洰瑙勫畾鐗规畩鐐癸紝鍙戠敓涓�娆℃墸10鍒嗭紝鑰屼笉鐩存帴娣樻卑
+    PETesting = true;
+    currTarget = ARRIVED_START;
+}
+
+void StopParkEdge(void)
+{
+    AppTimer_delete(ParkEdgeTimeout);
+    PETesting = false;
+}
+
+int TestParkEdge(vector<int>&err, const Polygon *map, const car_model_cache_t *car, double speed, int run_status)
+{
+    int status = 0;
+
+    if (!PETesting)
+        return 0;
+
+    if (currTarget >= PARK_CAR) {
+        if (CrashRedLine1(map, car)) {
+            if (!occurCrashRedLine1) {
+                occurCrashRedLine1 = true;
+                // 杞﹁疆鍘嬭竟绾匡紝姣忔鎵�10鍒�
+                err.push_back(23);
+            }
+        } else {
+            occurCrashRedLine1 = false;
+        }
+
+        if (CrashRedLine2(map, car)) {
+            if (!occurCrashRedLine2) {
+                occurCrashRedLine2 = true;
+                // 杞﹁韩鍘嬪簱浣嶇嚎锛屾瘡娆℃墸10鍒�
+                err.push_back(24);
+            }
+        } else {
+            occurCrashRedLine2 = false;
+        }
+
+        if (parkTimeout) {
+            // 瓒呮椂90绉掞紝涓嶅悎鏍�
+            err.push_back(22);
+            status = -1;
+        }
+
+        if (prev_run_status != run_status) {
+            if (run_status == 0) {
+                // 杞﹀仠浜�
+                runStatusBeforeStop = prev_run_status;
+                stopTimepoint = AppTimer_GetTickCount();
+            } else {
+                // 杞﹀姩浜嗭紝涓斿拰鍋滆溅鍓嶇殑杩愯鐘舵�佷竴鑷�
+                if (runStatusBeforeStop == run_status && AppTimer_GetTickCount() - stopTimepoint > STOP_CAR_TIME) {
+                    // 涓�斿仠杞︼紝鎵�5鍒�
+                    err.push_back(26);
+                }
+            }
+        }
+    }
+
+    if (currTarget == ARRIVED_START) {
+        if (ExitTestArea(map, car)) {
+            // 鐩存帴椹剁娴嬭瘯鍖哄煙
+            status = -2;
+        } else
+        if (run_status < 0) {
+            AppTimer_add(ParkEdgeTimeout, D_SEC(PARK_TIMEOUT));
+            currTarget = PARK_CAR;
+            TextOsd(0, "寮�濮嬪�掑簱");
+        }
+    } else if (currTarget == PARK_CAR) {
+        if (run_status < 0) {
+            carStopEvent = false;
+        } else if (run_status == 0) {
+            // 绔嬪嵆妫�鏌ユ槸鍚﹀仠杞﹀埌浣嶏紝涔熻鏄腑閫斿仠杞︼紝鍏堜笉绠★紝寰呭彂鐢熷墠杩涗簨浠跺悗锛屽啀鏂畾鏄惁鍋滆溅鍒颁綅
+            if (!carStopEvent) {
+                carStopEvent = true;
+                carParkSuccess = EnterParking(map, car);
+            }
+        } else {
+            if (carStopEvent) {
+                if (!carParkSuccess) {
+                    // 涓嶅悎鏍硷細杞﹁韩鍑虹嚎
+                    err.push_back(21);
+                    status = -1;
+                }
+            }
+            carStopEvent = false;
+            leaveParkCnt = 0;
+            currTarget = START_CAR;
+            TextOsd(0, "寮�濮嬪嚭搴�");
+        }
+    } else if (currTarget == START_CAR) {
+        if (run_status > 0) {
+            leaveParkCnt++;
+
+            if (leaveParkCnt == 1) {
+                // 鏈紑鍚浆鍚戠伅锛屾墸10鍒�
+                err.push_back(25);
+            }
+
+            if (ExitParkArea(map, car)) {
+                // 椤圭洰瀹屾垚
+                status = 1;
+                TextOsd(0, "椤圭洰瀹屾垚");
+            }
+        }
+    }
+
+    if (status != 0) {
+        StopParkEdge();
+    }
+
+    prev_run_status = run_status;
+    return status;
+}
+
+static void ParkEdgeTimeout(union sigval sig) {
+    AppTimer_delete(ParkEdgeTimeout);
+    parkTimeout = true;
+}
+
+// 杞﹁疆鏄惁鍘嬮亾璺竟绾�
+static bool CrashRedLine1(const Polygon *map, const car_model_cache_t *car)
+{
+    bool ret = false;
+
+    Line red_line;
+    const int red_lines[][2] = {{0, 7}, {1, 2}, {5, 6}};
+
+    Line frontAxle, rearAxle;
+
+    MakeLine(&frontAxle, &car->points[car->desc->front_left_tire[TIRE_OUTSIDE]], &car->points[car->desc->front_right_tire[TIRE_OUTSIDE]]);
+    MakeLine(&rearAxle, &car->points[car->desc->rear_left_tire[TIRE_OUTSIDE]], &car->points[car->desc->rear_right_tire[TIRE_OUTSIDE]]);
+
+    for (int i = 0; i < sizeof(red_lines) / sizeof(red_lines[0]); ++i) {
+        MakeLine(&red_line, &map->point[red_lines[i][0]], &map->point[red_lines[i][1]]);
+        if (IntersectionOf(red_line, frontAxle) == GM_Intersection ||
+            IntersectionOf(red_line, rearAxle) == GM_Intersection) {
+            ret = true;
+            break;
+        }
+    }
+
+    return ret;
+}
+
+// 杞﹁韩鏄惁鍘嬪簱浣嶇嚎
+static bool CrashRedLine2(const Polygon *map, const car_model_cache_t *car)
+{
+    bool ret = false;
+
+    Line red_line;
+    const int red_lines[][2] = {{2, 3}, {3, 4}, {4, 5}};
+
+    Polygon car_body;
+
+    car_body.num = car->desc->body_num;
+    car_body.point = (PointF *) malloc(sizeof(PointF) * car_body.num);
+    for (int i = 0; i < car_body.num; ++i) {
+        car_body.point[i] = car->points[car->desc->body[i]];
+    }
+
+    for (int i = 0; i < sizeof(red_lines) / sizeof(red_lines[0]); ++i) {
+        MakeLine(&red_line, &map->point[red_lines[i][0]], &map->point[red_lines[i][1]]);
+        if (IntersectionOf(red_line, &car_body) != GM_None) {
+            ret = true;
+            break;
+        }
+    }
+
+    free(car_body.point);
+    return ret;
+}
+
+static bool EnterParking(const Polygon *map, const car_model_cache_t *car) {
+    bool succ = false;
+
+    Polygon parking;
+    Polygon car_body;
+
+    car_body.num = car->desc->body_num;
+    car_body.point = (PointF *) malloc(sizeof(PointF) * car_body.num);
+    for (int i = 0; i < car_body.num; ++i) {
+        car_body.point[i] = car->points[car->desc->body[i]];
+    }
+
+    MakePolygon(&parking, {map->point[2], map->point[3], map->point[4], map->point[5]});
+
+    if (IntersectionOf(&car_body, &parking) == GM_Containment) {
+        succ = true;
+    }
+
+    CleanPolygon(&parking);
+    free(car_body.point);
+
+    return succ;
+}
+
+// 鏁翠釜杞﹁締閮借椹惰繃鍓嶅簱浣嶇嚎
+static bool ExitParkArea(const Polygon *map, const car_model_cache_t *car)
+{
+    for (int i = 0; i < car->point_num; ++i) {
+        if (IntersectionOfLine(map->point[4], map->point[5], car->points[i]) != -1)
+            return false;
+    }
+    return true;
+}
+
+// 鏁翠釜杞﹁締閮借椹惰繃鏈�鍓嶇鎺у埗绾�
+static bool ExitTestArea(const Polygon *map, const car_model_cache_t *car)
+{
+    for (int i = 0; i < car->point_num; ++i) {
+        if (IntersectionOfLine(map->point[6], map->point[7], car->points[i]) != -1)
+            return false;
+    }
+    return true;
+}
diff --git a/lib/src/main/cpp/test_items/park_edge.h b/lib/src/main/cpp/test_items/park_edge.h
new file mode 100644
index 0000000..0465566
--- /dev/null
+++ b/lib/src/main/cpp/test_items/park_edge.h
@@ -0,0 +1,19 @@
+//
+// Created by YY on 2019/10/23.
+//
+
+#ifndef RTKDRIVERTEST_PARK_EDGE_H
+#define RTKDRIVERTEST_PARK_EDGE_H
+
+#include "../Geometry.h"
+#include "../driver_test.h"
+#include <vector>
+
+using namespace std;
+
+bool EnterParkEdgeArea(const Polygon *car, const Polygon *tire, const Polygon *map);
+void StartParkEdge(void);
+void StopParkEdge(void);
+int TestParkEdge(vector<int>&err, const Polygon *map, const car_model_cache_t *car, double speed, int run_status);
+
+#endif //RTKDRIVERTEST_PARK_EDGE_H
diff --git a/lib/src/main/cpp/test_items/stop_and_start.cpp b/lib/src/main/cpp/test_items/stop_and_start.cpp
new file mode 100644
index 0000000..6b8fa0e
--- /dev/null
+++ b/lib/src/main/cpp/test_items/stop_and_start.cpp
@@ -0,0 +1,227 @@
+//
+// Created by YY on 2019/10/31.
+//
+
+#include <cstdlib>
+#include <vector>
+#include <cmath>
+
+#include "stop_and_start.h"
+#include "../driver_test.h"
+#include "../jni_log.h"
+#include "../common/apptimer.h"
+
+using namespace std;
+
+enum
+{
+    STOP_CAR,
+    START_CAR
+};
+
+const double STOP_DISTANCE_THRESHOLD_RED = 0.5;
+const double EDGE_DISTANCE_THRESHOLD_RED = 0.5;
+const double EDGE_DISTANCE_THRESHOLD_YELLOW = 0.3;
+const double SLIDE_DISTANCE_THRESHOLD_RED = 0.3;
+const double SLIDE_DISTANCE_THRESHOLD_YELLOW = 0.1;
+const int CAR_START_TIMEOUT = 30;
+const double EPSILON = 1e-3;
+
+static bool SASTesting = false;
+
+static double slideDistance;
+static bool startCarTimeout;
+static int currTarget;
+static PointF stopPoint;
+static int startCarConfirm;             // 璧锋鏃讹紝鎸佺画鍓嶈繘涓�灏忔鎵嶇畻
+
+static void StartCarTimeout(union sigval sig);
+static bool CrashRedLine(const Polygon *map, const car_model_cache_t *car);
+static double DistanceOfHead2Stopline(const Polygon *map, const car_model_cache_t *car);
+static double DistanceOfTire2Edge(const Polygon *map, const car_model_cache_t *car);
+static bool ExitTestArea(const Polygon *map, const car_model_cache_t *car);
+
+void StartSAS(void)
+{
+    SASTesting = true;
+    slideDistance = 0.0;
+    startCarTimeout = false;
+    currTarget = STOP_CAR;
+}
+
+void StopSAS(void)
+{
+    SASTesting = false;
+    AppTimer_delete(StartCarTimeout);
+}
+
+int TestSAS(vector<int>&err, const Polygon *map, const car_model_cache_t *car, double speed, int run_status)
+{
+    int status = 0;
+
+    if (!SASTesting)
+        return -2;
+
+    if (currTarget >= STOP_CAR) {
+        if (CrashRedLine(map, car)) {
+            // 杞﹁疆鍘嬬嚎
+            err.push_back(13);
+            status = -1;
+        }
+    }
+
+    if (currTarget == STOP_CAR) {
+        if (run_status == 0) {
+            double dis1 = DistanceOfHead2Stopline(map, car);
+            double dis2 = DistanceOfTire2Edge(map, car);
+
+            if (dis1 > STOP_DISTANCE_THRESHOLD_RED) {
+                // 璺濈鍋滄绾垮墠鍚庤秴鍑�50鍘樼背
+                err.push_back(12);
+                status = -1;
+            } else if (fabs(dis1) > EPSILON) {
+                // 鍓嶄繚闄╂病鏈変綅浜庡仠姝㈠甫鍐咃紝浣嗘病鏈夎秴鍑�50鍘樼背
+                err.push_back(17);
+            }
+
+            if (dis2 > EDGE_DISTANCE_THRESHOLD_RED) {
+                // 璺濈杈圭嚎瓒呭嚭50鍘樼背
+                err.push_back(14);
+                status = -1;
+            } else if (dis2 > EDGE_DISTANCE_THRESHOLD_YELLOW) {
+                // 璺濈杈圭嚎瓒呭嚭30鍘樼背
+                err.push_back(18);
+            }
+
+            // 妫�鏌ユ槸鍚︽媺浣忔墜鍒�
+
+            AppTimer_delete(StartCarTimeout);
+            AppTimer_add(StartCarTimeout, D_SEC(CAR_START_TIMEOUT));
+            slideDistance = 0.0;
+            stopPoint = car->points[0];
+            startCarConfirm = 0;
+            currTarget = START_CAR;
+        } else if (run_status > 0) {
+            if (ExitTestArea(map, car)) {
+                // 杞﹁締鐩存帴椹剁娴嬭瘯鍖猴紝鐩存帴娣樻卑
+                err.push_back(12);
+                status = -1;
+            }
+        }
+    } else if (currTarget == START_CAR) {
+        if (startCarTimeout) {
+            startCarTimeout = false;
+            //璧锋鏃堕棿瓒呰繃30绉�
+            err.push_back(15);
+            status = -1;
+        }
+
+        if (run_status > 0) {
+            startCarConfirm++;
+            if (startCarConfirm == 2) {
+                AppTimer_delete(StartCarTimeout);           // 璧锋瀹屾垚
+            }
+
+            if (slideDistance > SLIDE_DISTANCE_THRESHOLD_YELLOW) {
+                // 鍚庢粦瓒呰繃10鍘樼背锛屼絾娌¤秴杩�30鍘樼背
+                err.push_back(20);
+            }
+        } else if (run_status < 0) {
+            // 鍚庢粦浜�
+            slideDistance = DistanceOf(stopPoint, car->points[0]);
+            if (slideDistance > SLIDE_DISTANCE_THRESHOLD_RED) {
+                // 鍚庢粦瓒呰繃30鍘樼背
+                err.push_back(16);
+                status = -1;
+            }
+        }
+
+        if (ExitTestArea(map, car)) {
+            // 娴嬭瘯瀹屾垚浜�
+            status = 1;
+        }
+    }
+
+    if (status != 0) {
+        StopSAS();
+    }
+
+    return status;
+}
+
+static void StartCarTimeout(union sigval sig) {
+    AppTimer_delete(StartCarTimeout);
+    startCarTimeout = true;
+}
+
+// 杞﹁疆鏄惁鍘嬭竟绾�
+static bool CrashRedLine(const Polygon *map, const car_model_cache_t *car)
+{
+    bool ret = false;
+
+    Line red_line;
+    const int red_lines[][2] = {{0, 8}};
+
+    Line frontAxle, rearAxle;
+
+    MakeLine(&frontAxle, &car->points[car->desc->front_left_tire[TIRE_OUTSIDE]], &car->points[car->desc->front_right_tire[TIRE_OUTSIDE]]);
+    MakeLine(&rearAxle, &car->points[car->desc->rear_left_tire[TIRE_OUTSIDE]], &car->points[car->desc->rear_right_tire[TIRE_OUTSIDE]]);
+
+    for (int i = 0; i < sizeof(red_lines) / sizeof(red_lines[0]); ++i) {
+        MakeLine(&red_line, &map->point[red_lines[i][0]], &map->point[red_lines[i][1]]);
+        if (IntersectionOf(red_line, frontAxle) == GM_Intersection ||
+                IntersectionOf(red_line, rearAxle) == GM_Intersection) {
+            ret = true;
+            break;
+        }
+    }
+
+    return ret;
+}
+
+static double DistanceOfHead2Stopline(const Polygon *map, const car_model_cache_t *car)
+{
+    double dis = 0.0;
+
+    int rel1 = IntersectionOfLine(map->point[4], map->point[3], car->points[0]);
+    int rel2 = IntersectionOfLine(map->point[5], map->point[6], car->points[0]);
+
+    if (rel1 == 1) {
+        Line line1;
+
+        MakeLine(&line1, &map->point[4], &map->point[3]);
+
+        dis = DistanceOf(car->points[0], line1);
+    } else if (rel2 == -1) {
+        Line line2;
+
+        MakeLine(&line2, &map->point[5], &map->point[6]);
+
+        dis = DistanceOf(car->points[0], line2);
+    }
+
+    return dis;
+}
+
+static double DistanceOfTire2Edge(const Polygon *map, const car_model_cache_t *car)
+{
+    Line edge;
+
+    MakeLine(&edge,  &map->point[0], &map->point[8]);
+
+    double l1 = DistanceOf(car->points[car->desc->front_right_tire[TIRE_OUTSIDE]], edge);
+
+    double l2 = DistanceOf(car->points[car->desc->rear_right_tire[TIRE_OUTSIDE]], edge);
+
+    return (l1+l2)/2.0;
+}
+
+// 鏁翠釜杞﹁締閮借椹剁璇ユ祴璇曞尯鍩�
+static bool ExitTestArea(const Polygon *map, const car_model_cache_t *car)
+{
+    for (int i = 0; i < car->point_num; ++i) {
+        if (IntersectionOfLine(map->point[8], map->point[7], car->points[i]) != -1)
+            return false;
+    }
+    return true;
+}
diff --git a/lib/src/main/cpp/test_items/stop_and_start.h b/lib/src/main/cpp/test_items/stop_and_start.h
new file mode 100644
index 0000000..dcb90f3
--- /dev/null
+++ b/lib/src/main/cpp/test_items/stop_and_start.h
@@ -0,0 +1,16 @@
+//
+// Created by YY on 2019/10/31.
+//
+
+#ifndef RTKDRIVERTEST_STOP_AND_START_H
+#define RTKDRIVERTEST_STOP_AND_START_H
+
+#include "../driver_test.h"
+
+using namespace std;
+
+void StartSAS(void);
+void StopSAS(void);
+int TestSAS(vector<int>&err, const Polygon *map, const car_model_cache_t *car, double speed, int run_status);
+
+#endif //RTKDRIVERTEST_STOP_AND_START_H
diff --git a/lib/src/main/cpp/test_items/turn_a90.cpp b/lib/src/main/cpp/test_items/turn_a90.cpp
new file mode 100644
index 0000000..5c17947
--- /dev/null
+++ b/lib/src/main/cpp/test_items/turn_a90.cpp
@@ -0,0 +1,172 @@
+//
+// Created by YY on 2019/11/4.
+//
+
+#include "turn_a90.h"
+#include "../Geometry.h"
+#include "../driver_test.h"
+#include "../common/apptimer.h"
+#include "../jni_log.h"
+
+#include <vector>
+
+using namespace std;
+
+enum {
+    TURN_ANGLE_90,
+    TURN_ANGLE_90_CMP
+};
+
+const uint32_t STOP_CAR_TIME = D_SEC(2);
+
+static bool TA90Testing;
+static bool carStopEvent;
+static bool checked;
+static int currTarget;
+static int azimuth;
+static uint32_t stopTimepoint = 0;
+
+static bool CrashRedLine(const Polygon *map, const car_model_cache_t *car);
+static bool ExitTestArea(const Polygon *map, const car_model_cache_t *car);
+static bool Turned(const Polygon *map, const car_model_cache_t *car);
+
+void StartTurnA90(double heading)
+{
+    azimuth = (int) heading;
+    checked = false;
+    TA90Testing = true;
+    carStopEvent = false;
+
+    currTarget = TURN_ANGLE_90;
+}
+
+void StopTurnA90(void)
+{
+    TA90Testing = false;
+}
+
+int TestTurnA90(vector<int>&err, const Polygon *map, const car_model_cache_t *car, double speed, int run_status, double heading)
+{
+    int status = 0;
+
+    if (!TA90Testing)
+        return -2;
+
+    DEBUG("TestTurnA90 %d", run_status);
+
+    if (CrashRedLine(map, car)) {
+        // 鍘嬬嚎浜�
+        err.push_back(29);
+        status = -1;
+        DEBUG("閿欒 鍘嬬嚎");
+    }
+
+    if (run_status != 0) {
+        if (carStopEvent)
+            DEBUG("TURN_ANGLE_90 鍋滆溅鏃堕棿 %ld", AppTimer_GetTickCount() - stopTimepoint);
+
+        if (carStopEvent && AppTimer_GetTickCount() - stopTimepoint > STOP_CAR_TIME) {
+            // 涓�斿仠杞�
+            err.push_back(31);
+            DEBUG("閿欒 鍋滆溅1");
+        }
+        carStopEvent = false;
+    } else if (!carStopEvent){
+        carStopEvent = true;
+        stopTimepoint = AppTimer_GetTickCount();
+    }
+
+    if (ExitTestArea(map, car)) {
+        // 娴嬭瘯缁撴潫
+        status = 1;
+    }
+
+    if (currTarget == TURN_ANGLE_90) {
+        // 杞悜鐏紑鍚�
+        int az = (int) heading;
+
+        if (abs(az - azimuth) > 180) {
+            az = 360 - abs(az-azimuth);
+        } else {
+            az = abs(az - azimuth);
+        }
+
+        if (az >= 10 && !checked) {
+            checked = true;
+            // 杞悜鐏湭寮�鍚�
+            err.push_back(30);
+            DEBUG("閿欒 鐏病鐪�");
+        }
+
+        if (Turned(map, car)) {
+            currTarget = TURN_ANGLE_90_CMP;
+            checked = false;
+        }
+        DEBUG("TURN_ANGLE_90 %d %d",run_status, az);
+    } else {
+        // 鍏抽棴杞悜鐏�
+        Line line;
+
+        MakeLine(&line, &map->point[1], &map->point[2]);
+
+        // 澶т簬2.5绫冲悗妫�鏌ヨ溅鐏�
+        if (!checked && DistanceOf(car->points[0], line) >= 2.5) {
+            checked = true;
+            // 杞悜鐏湭鍏抽棴
+            err.push_back(30);
+            DEBUG("閿欒 鐏病绠�");
+        }
+        DEBUG("TURN_ANGLE_90_CMP");
+    }
+
+    if (status != 0) {
+        StopTurnA90();
+    }
+
+    return status;
+}
+
+// 杞﹁疆鏄惁鍘嬭竟绾�
+static bool CrashRedLine(const Polygon *map, const car_model_cache_t *car)
+{
+    bool ret = false;
+
+    Line red_line;
+    const int red_lines[][2] = {{0, 5}, {5, 4}, {1, 2}, {2, 3}};
+
+    Line frontAxle, rearAxle;
+
+    // 浠呯湅杞﹁疆澶栦晶
+    MakeLine(&frontAxle, &car->points[car->desc->front_left_tire[TIRE_OUTSIDE]], &car->points[car->desc->front_right_tire[TIRE_OUTSIDE]]);
+    MakeLine(&rearAxle, &car->points[car->desc->rear_left_tire[TIRE_OUTSIDE]], &car->points[car->desc->rear_right_tire[TIRE_OUTSIDE]]);
+
+    for (int i = 0; i < sizeof(red_lines) / sizeof(red_lines[0]); ++i) {
+        MakeLine(&red_line, &map->point[red_lines[i][0]], &map->point[red_lines[i][1]]);
+        if (IntersectionOf(red_line, frontAxle) == GM_Intersection ||
+            IntersectionOf(red_line, rearAxle) == GM_Intersection) {
+            ret = true;
+            break;
+        }
+    }
+
+    return ret;
+}
+
+// 鏁翠釜杞﹁締閮借椹剁璇ユ祴璇曞尯鍩�
+static bool ExitTestArea(const Polygon *map, const car_model_cache_t *car)
+{
+    for (int i = 0; i < car->point_num; ++i) {
+        if (IntersectionOfLine(map->point[3], map->point[4], car->points[i]) != 1)
+            return false;
+    }
+    return true;
+}
+
+static bool Turned(const Polygon *map, const car_model_cache_t *car)
+{
+    for (int i = 0; i < car->point_num; ++i) {
+        if (IntersectionOfLine(map->point[1], map->point[2], car->points[i]) != 1)
+            return false;
+    }
+    return true;
+}
diff --git a/lib/src/main/cpp/test_items/turn_a90.h b/lib/src/main/cpp/test_items/turn_a90.h
new file mode 100644
index 0000000..66ccb66
--- /dev/null
+++ b/lib/src/main/cpp/test_items/turn_a90.h
@@ -0,0 +1,17 @@
+//
+// Created by YY on 2019/11/4.
+//
+
+#ifndef RTKDRIVERTEST_TURN_A90_H
+#define RTKDRIVERTEST_TURN_A90_H
+
+#include "../driver_test.h"
+#include <vector>
+
+using namespace std;
+
+void StartTurnA90(double heading);
+void StopTurnA90(void);
+int TestTurnA90(vector<int>&err, const Polygon *map, const car_model_cache_t *car, double speed, int run_status, double heading);
+
+#endif //RTKDRIVERTEST_TURN_A90_H
diff --git a/lib/src/main/cpp/utils/crc16.cpp b/lib/src/main/cpp/utils/crc16.cpp
new file mode 100644
index 0000000..c7a7998
--- /dev/null
+++ b/lib/src/main/cpp/utils/crc16.cpp
@@ -0,0 +1,66 @@
+//
+// Created by YY on 2019/9/10.
+//
+
+#include <cstdio>
+#include "crc16.h"
+
+const static unsigned short crc_table [256] = {
+        0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5,
+        0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b,
+        0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210,
+        0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
+        0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,
+        0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401,
+        0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b,
+        0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
+        0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6,
+        0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
+        0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5,
+        0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
+        0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969,
+        0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96,
+        0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
+        0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
+        0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03,
+        0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd,
+        0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6,
+        0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
+        0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,
+        0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb,
+        0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1,
+        0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
+        0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,
+        0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2,
+        0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb,
+        0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
+        0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447,
+        0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
+        0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2,
+        0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
+        0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9,
+        0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827,
+        0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
+        0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
+        0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0,
+        0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d,
+        0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07,
+        0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
+        0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba,
+        0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
+        0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
+};
+
+unsigned short CRCCCITT(unsigned char *data, size_t length, unsigned short seed, unsigned short final)
+{
+    size_t count;
+    unsigned int crc = seed;
+    unsigned int temp;
+
+    for (count = 0; count < length; ++count) {
+        temp = (*data++ ^ (crc >> 8)) & 0xff;
+        crc = crc_table[temp] ^ (crc << 8);
+    }
+
+    return (unsigned short)(crc ^ final);
+}
diff --git a/lib/src/main/cpp/utils/crc16.h b/lib/src/main/cpp/utils/crc16.h
new file mode 100644
index 0000000..aee6513
--- /dev/null
+++ b/lib/src/main/cpp/utils/crc16.h
@@ -0,0 +1,12 @@
+//
+// Created by YY on 2019/9/10.
+//
+
+#ifndef ANYUNPROJECT_CRC16_H
+#define ANYUNPROJECT_CRC16_H
+
+#include <cstdio>
+
+unsigned short CRCCCITT(unsigned char *data, size_t length, unsigned short seed, unsigned short final);
+
+#endif //ANYUNPROJECT_CRC16_H
diff --git a/lib/src/main/cpp/utils/num.cpp b/lib/src/main/cpp/utils/num.cpp
new file mode 100644
index 0000000..fe14678
--- /dev/null
+++ b/lib/src/main/cpp/utils/num.cpp
@@ -0,0 +1,76 @@
+//
+// Created by YY on 2019/12/23.
+//
+
+#include "num.h"
+
+long str2int(const uint8_t *s, uint16_t length)
+{
+    uint16_t i;
+    long n = 0;
+    bool sign = false;
+
+    for(i = 0; i < length; i++)
+    {
+
+        if(s[i] >= '0' && s[i] <= '9') {
+            n = n*10 + s[i] - '0';
+        } else if (i == 0 && s[i]=='-') {
+            sign = true;
+        }
+    }
+
+    if (sign) {
+        n = 0-n;
+    }
+
+    return n;
+}
+
+bool str2float(double *f, const uint8_t *s, uint16_t length)
+{
+    bool sign = false;
+    bool getDecimal = false;
+    uint16_t i;
+    uint64_t m = 0, n = 1;
+    double x;
+
+    *f = 0.0;
+
+    for(i = 0; i < length; i++)
+    {
+        if(s[i] == '-' || s[i] == '+') {
+            if(i == 0) {
+                if (s[i] == '-') {
+                    sign = true;
+                }
+            }
+            else {
+                return false;
+            }
+        }
+        else if(s[i] == '.') {
+            if(getDecimal == true) {
+                return false;
+            }
+            getDecimal = true;
+        }
+        else if(s[i] >= '0' && s[i] <= '9') {
+            m = m*10 + s[i] - '0';
+            if(getDecimal == true) {
+                n *= 10;
+            }
+        }
+        else {
+            return false;
+        }
+    }
+
+    x = (double)m / (double)n;
+    if(sign) {
+        x = 0.0 - x;
+    }
+    *f = x;
+
+    return true;
+}
diff --git a/lib/src/main/cpp/utils/num.h b/lib/src/main/cpp/utils/num.h
new file mode 100644
index 0000000..45e1937
--- /dev/null
+++ b/lib/src/main/cpp/utils/num.h
@@ -0,0 +1,13 @@
+//
+// Created by YY on 2019/12/23.
+//
+
+#ifndef RTKDRIVERTEST_NUM_H
+#define RTKDRIVERTEST_NUM_H
+
+#include <cstdint>
+
+long str2int(const uint8_t *s, uint16_t length);
+bool str2float(double *f, const uint8_t *s, uint16_t length);
+
+#endif //RTKDRIVERTEST_NUM_H
diff --git a/lib/src/main/java/com/anyun/exam/lib/RemoteService.java b/lib/src/main/java/com/anyun/exam/lib/RemoteService.java
index 87f49db..57565de 100644
--- a/lib/src/main/java/com/anyun/exam/lib/RemoteService.java
+++ b/lib/src/main/java/com/anyun/exam/lib/RemoteService.java
@@ -2,10 +2,15 @@
 
 import android.app.Service;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.util.Base64;
 import android.util.Log;
+
+import com.anyun.exam.lib.util.DESUtil;
+import com.anyun.exam.lib.util.NetUtils;
 
 import androidx.annotation.Nullable;
 
@@ -18,8 +23,8 @@
  * All Rights Saved! Chongqing AnYun Tech co. LTD
  */
 public class RemoteService extends Service {
-
     private static final String TAG = "RemoteService";
+    private final static String LOAD_KEY = "RTKBaseStation";
     /**鏈嶅姟鏄惁閿�姣佹爣蹇�*/
     private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
     private RemoteCallbackList<IListenerInterface> mListenerList = new RemoteCallbackList();
@@ -54,6 +59,7 @@
     public void onCreate() {
         super.onCreate();
         Log.i(TAG,"onCreate()");
+        startNative();
         new Thread(new Worker()).start();
     }
 
@@ -95,4 +101,71 @@
             mListenerList.finishBroadcast();
         }
     }
+
+    public String javaGetImei() {
+        return NetUtils.getDeviceId(getApplicationContext());
+    }
+
+    public String javaDESEncrypt(String plaintext, String key) {
+        try {
+            byte []des = DESUtil.encrypt(plaintext.getBytes("utf-8"), key);
+            return Base64.encodeToString(des, Base64.DEFAULT);
+        } catch (Exception e) {
+
+        }
+        return null;
+    }
+
+    public byte[] javaDESEncrypt(byte []plaintext, byte []key) {
+        try {
+            return DESUtil.encrypt(plaintext, key);
+        } catch (Exception e) {
+
+        }
+        return null;
+    }
+
+    public void SetPlatformKey(byte []key) {
+        String params = Base64.encodeToString(key, Base64.DEFAULT);
+
+        SharedPreferences sharedPreferences = getSharedPreferences(LOAD_KEY, MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPreferences.edit();
+        editor.putString("platform_key", params);
+        editor.commit();
+    }
+
+    public byte[] GetPlatformKey() {
+        SharedPreferences sharedPreferences = getSharedPreferences(LOAD_KEY, MODE_PRIVATE);
+        String params = sharedPreferences.getString("platform_key", "");
+        if (params.equals("")) {
+            return null;
+        }
+        return Base64.decode(params, Base64.DEFAULT);
+    }
+
+    public void DeletePlatformKey() {
+        SharedPreferences sharedPreferences = getSharedPreferences(LOAD_KEY, MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPreferences.edit();
+        editor.putString("platform_key", "");
+        editor.commit();
+    }
+
+    public void SetSharedValue(String key, int value) {
+        SharedPreferences sharedPreferences = getSharedPreferences(LOAD_KEY, MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPreferences.edit();
+        editor.putInt(key, value);
+        editor.commit();
+    }
+
+    public int GetSharedValue(String key) {
+        SharedPreferences sharedPreferences = getSharedPreferences(LOAD_KEY, MODE_PRIVATE);
+        return sharedPreferences.getInt(key, 0);
+    }
+
+    // Used to load the 'native-lib' library on application startup.
+    static {
+        System.loadLibrary("native-lib");
+    }
+
+    public native void startNative();
 }
diff --git a/lib/src/main/java/com/anyun/exam/lib/util/ByteUtil.java b/lib/src/main/java/com/anyun/exam/lib/util/ByteUtil.java
new file mode 100644
index 0000000..0929b60
--- /dev/null
+++ b/lib/src/main/java/com/anyun/exam/lib/util/ByteUtil.java
@@ -0,0 +1,16 @@
+package com.anyun.exam.lib.util;
+
+public class ByteUtil {
+    public static String byte2hex(byte [] buffer){
+        StringBuilder h = new StringBuilder();
+        for(int i = 0; i < buffer.length; i++){
+            String temp = Integer.toHexString(buffer[i] & 0xFF).toUpperCase();
+            if(temp.length() == 1){
+                temp = "0" + temp;
+            }
+            h.append(temp);
+            h.append(" ");
+        }
+        return h.toString();
+    }
+}
diff --git a/lib/src/main/java/com/anyun/exam/lib/util/DESUtil.java b/lib/src/main/java/com/anyun/exam/lib/util/DESUtil.java
new file mode 100644
index 0000000..96c3b5a
--- /dev/null
+++ b/lib/src/main/java/com/anyun/exam/lib/util/DESUtil.java
@@ -0,0 +1,124 @@
+package com.anyun.exam.lib.util;
+
+import android.util.Base64;
+
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+
+public class DESUtil {
+    private static String password = "safeluck";
+
+    /**
+     *
+     * @Method: encrypt
+     * @Description: 鍔犲瘑鏁版嵁
+     * @param data
+     * @return
+     * @throws Exception
+     * @date 2016骞�7鏈�26鏃�
+     */
+    public static String encrypt(String data, String pwd) {  //瀵箂tring杩涜BASE64Encoder杞崲
+        byte[] bt = encryptByKey(data.getBytes(), pwd);
+        return Base64.encodeToString(bt, Base64.DEFAULT);
+    }
+
+    public static byte[] encrypt(byte[] message, String key) throws Exception {
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        DESKeySpec desKeySpec = new DESKeySpec(key.getBytes("UTF-8"));
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        IvParameterSpec iv = new IvParameterSpec(key.getBytes("UTF-8"));
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
+        return cipher.doFinal(message);
+    }
+
+    public static byte[] encrypt(byte[] message, byte[] key) throws Exception {
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        DESKeySpec desKeySpec = new DESKeySpec(key);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        IvParameterSpec iv = new IvParameterSpec(key);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
+        return cipher.doFinal(message);
+    }
+
+    public static byte[] decrypt2(byte[] message, String key) throws Exception {
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        DESKeySpec desKeySpec = new DESKeySpec(key.getBytes("UTF-8"));
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        IvParameterSpec iv = new IvParameterSpec(key.getBytes("UTF-8"));
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
+        return cipher.doFinal(message);
+    }
+
+    /**
+     *
+     * @Method: encrypt
+     * @Description: 瑙e瘑鏁版嵁
+     * @param data
+     * @return
+     * @throws Exception
+     * @date 2016骞�7鏈�26鏃�
+     */
+    public static String decryptor(String data, String pwd) throws Exception {  //瀵箂tring杩涜BASE64Encoder杞崲
+        byte[] bt = decrypt(Base64.decode(data, Base64.DEFAULT), pwd);
+        String strs = new String(bt);
+        return strs;
+    }
+    /**
+     * 鍔犲瘑
+     * @param datasource byte[]
+     * @param password String
+     * @return byte[]
+     */
+    private static byte[] encryptByKey(byte[] datasource, String key) {
+        try{
+            SecureRandom random = new SecureRandom();
+
+            DESKeySpec desKey = new DESKeySpec(key.getBytes());
+            //鍒涘缓涓�涓瘑鍖欏伐鍘傦紝鐒跺悗鐢ㄥ畠鎶奃ESKeySpec杞崲鎴�
+            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+            SecretKey securekey = keyFactory.generateSecret(desKey);
+            //Cipher瀵硅薄瀹為檯瀹屾垚鍔犲瘑鎿嶄綔
+            Cipher cipher = Cipher.getInstance("DES");
+            //鐢ㄥ瘑鍖欏垵濮嬪寲Cipher瀵硅薄
+            cipher.init(Cipher.ENCRYPT_MODE, securekey);
+            //鐜板湪锛岃幏鍙栨暟鎹苟鍔犲瘑
+            //姝e紡鎵ц鍔犲瘑鎿嶄綔
+            return cipher.doFinal(datasource);
+        }catch(Throwable e){
+            e.printStackTrace();
+        }
+        return null;
+    }
+    /**
+     * 瑙e瘑
+     * @param src byte[]
+     * @param password String
+     * @return byte[]
+     * @throws Exception
+     */
+    private static byte[] decrypt(byte[] src, String key) throws Exception {
+        // DES绠楁硶瑕佹眰鏈変竴涓彲淇′换鐨勯殢鏈烘暟婧�
+        SecureRandom random = new SecureRandom();
+        // 鍒涘缓涓�涓狣ESKeySpec瀵硅薄
+        DESKeySpec desKey = new DESKeySpec(key.getBytes());
+        // 鍒涘缓涓�涓瘑鍖欏伐鍘�
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        // 灏咲ESKeySpec瀵硅薄杞崲鎴怱ecretKey瀵硅薄
+        SecretKey securekey = keyFactory.generateSecret(desKey);
+        // Cipher瀵硅薄瀹為檯瀹屾垚瑙e瘑鎿嶄綔
+        Cipher cipher = Cipher.getInstance("DES");
+        // 鐢ㄥ瘑鍖欏垵濮嬪寲Cipher瀵硅薄
+        cipher.init(Cipher.DECRYPT_MODE, securekey, random);
+        // 鐪熸寮�濮嬭В瀵嗘搷浣�
+        return cipher.doFinal(src);
+    }
+}
+
diff --git a/lib/src/main/java/com/anyun/exam/lib/util/NetUtils.java b/lib/src/main/java/com/anyun/exam/lib/util/NetUtils.java
new file mode 100644
index 0000000..17b798d
--- /dev/null
+++ b/lib/src/main/java/com/anyun/exam/lib/util/NetUtils.java
@@ -0,0 +1,221 @@
+package com.anyun.exam.lib.util;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.telephony.TelephonyManager;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
+/**
+ * Created by YY on 2017/10/27.
+ */
+public class NetUtils {
+    /**
+     * 鍒ゆ柇缃戠粶鎯呭喌
+     *
+     * @param context  涓婁笅鏂�
+     * @return false 琛ㄧず娌℃湁缃戠粶 true 琛ㄧず鏈夌綉缁�
+     */
+    public static boolean isNetworkAvalible(Context context) {
+        // 鑾峰緱缃戠粶鐘舵�佺鐞嗗櫒
+        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        if (connectivityManager == null) {
+            return false;
+        } else {
+            // 寤虹珛缃戠粶鏁扮粍
+            NetworkInfo[] net_info = connectivityManager.getAllNetworkInfo();
+
+            if (net_info != null) {
+                for (int i = 0; i < net_info.length; i++) {
+                    // 鍒ゆ柇鑾峰緱鐨勭綉缁滅姸鎬佹槸鍚︽槸澶勪簬杩炴帴鐘舵��
+                    if (net_info[i].getState() == State.CONNECTED) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 鍒ゆ柇缃戠粶鏄惁杩炴帴
+     **/
+    public static boolean netState(Context context) {
+        ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        // 鑾峰彇浠h〃鑱旂綉鐘舵�佺殑NetWorkInfo瀵硅薄
+        NetworkInfo networkInfo = connManager.getActiveNetworkInfo();
+        // 鑾峰彇褰撳墠鐨勭綉缁滆繛鎺ユ槸鍚﹀彲鐢�
+        boolean available = false;
+        try {
+            available = networkInfo.isAvailable();
+        } catch (Exception e) {
+//            e.printStackTrace();
+            return false;
+        }
+        if (available) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 鍦ㄨ繛鎺ュ埌缃戠粶鍩虹涔嬩笂,鍒ゆ柇璁惧鏄惁鏄疭IM缃戠粶杩炴帴
+     *
+     * @param context
+     * @return
+     */
+    public static boolean IsMobileNetConnect(Context context) {
+        try {
+            ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+            State state = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState();
+            if (State.CONNECTED == state)
+                return true;
+            else
+                return false;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    //杩斿洖鍊� -1锛氭病鏈夌綉缁�  1锛歐IFI缃戠粶2锛歸ap缃戠粶3锛歯et缃戠粶
+    public static int GetNetype(Context context) {
+        int netType = -1;
+        ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
+        if(networkInfo==null) {
+            return netType;
+        }
+        int nType = networkInfo.getType();
+        if(nType== ConnectivityManager.TYPE_MOBILE) {
+            if(networkInfo.getExtraInfo().toLowerCase().equals("cmnet")) {
+                netType = 3;
+            }
+            else {
+                netType = 2;
+            }
+        }
+        else if(nType== ConnectivityManager.TYPE_WIFI) {
+            netType = 1;
+        }
+        return netType;
+    }
+
+    public static boolean hasSimCard(Context context) {
+        TelephonyManager telMgr = (TelephonyManager)
+                context.getSystemService(Context.TELEPHONY_SERVICE);
+        boolean result = true;
+        switch (telMgr.getSimState()) {
+            case TelephonyManager.SIM_STATE_ABSENT:
+                result = false; // 娌℃湁SIM鍗�
+                break;
+            case TelephonyManager.SIM_STATE_UNKNOWN:
+                result = false;
+                break;
+        }
+        return result;
+    }
+
+    public static TelephonyManager getTelephonyManager(Context context) {
+        // 鑾峰彇telephony绯荤粺鏈嶅姟锛岀敤浜庡彇寰桽IM鍗″拰缃戠粶鐩稿叧淇℃伅
+        TelephonyManager mTelephonyManager = (TelephonyManager) context
+                    .getSystemService(Context.TELEPHONY_SERVICE);
+        return mTelephonyManager;
+    }
+
+    public static String getIccid(Context context) {
+        TelephonyManager mTelephonyManager = (TelephonyManager) context
+                .getSystemService(Context.TELEPHONY_SERVICE);
+        try {
+            return mTelephonyManager.getSimSerialNumber();
+        } catch (SecurityException e) {
+
+        }
+        return null;
+    }
+
+    /**
+     * 鍞竴鐨勮澶嘔D锛� GSM鎵嬫満鐨� IMEI 鍜� CDMA鎵嬫満鐨� MEID. Return null if device ID is not
+     * 鍙栧緱鎵嬫満IMEI
+     * available.
+     */
+    public static String getDeviceId(Context context) {
+        String mDeviceId = getTelephonyManager(context).getImei();// String
+        return mDeviceId;
+    }
+
+    /**
+     * 鍙栧緱IMEI SV
+     * 璁惧鐨勮蒋浠剁増鏈彿锛� 杩斿洖绉诲姩缁堢鐨勮蒋浠剁増鏈紝渚嬪锛欸SM鎵嬫満鐨処MEI/SV鐮併�� 渚嬪锛歵he IMEI/SV(software version)
+     * for GSM phones. Return null if the software version is not available.
+     */
+    public static String getDeviceSoftwareVersion(Context context) {
+        String mDeviceSoftwareVersion = getTelephonyManager(context).getDeviceSoftwareVersion();// String
+        return mDeviceSoftwareVersion;
+    }
+
+    /**
+     * 鍙栧緱鎵嬫満IMSI
+     * 杩斿洖鐢ㄦ埛鍞竴鏍囪瘑锛屾瘮濡侴SM缃戠粶鐨処MSI缂栧彿 鍞竴鐨勭敤鎴稩D锛� 渚嬪锛欼MSI(鍥介檯绉诲姩鐢ㄦ埛璇嗗埆鐮�) for a GSM phone.
+     * 闇�瑕佹潈闄愶細READ_PHONE_STATE
+     */
+    public static String getSubscriberId(Context context) {
+        String mSubscriberId = getTelephonyManager(context).getSubscriberId();// String
+        return mSubscriberId;
+    }
+
+    public static String getIPAddress(Context context) {
+        NetworkInfo info = ((ConnectivityManager) context
+                .getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
+        if (info != null && info.isConnected()) {
+            if (info.getType() == ConnectivityManager.TYPE_MOBILE) {//褰撳墠浣跨敤2G/3G/4G缃戠粶
+                try {
+                    //Enumeration<NetworkInterface> en=NetworkInterface.getNetworkInterfaces();
+                    for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
+                        NetworkInterface intf = en.nextElement();
+                        for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+                            InetAddress inetAddress = enumIpAddr.nextElement();
+                            if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
+                                return inetAddress.getHostAddress();
+                            }
+                        }
+                    }
+                } catch (SocketException e) {
+                    e.printStackTrace();
+                }
+
+            } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {//褰撳墠浣跨敤鏃犵嚎缃戠粶
+                WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+                WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+                String ipAddress = intIP2StringIP(wifiInfo.getIpAddress());//寰楀埌IPV4鍦板潃
+                return ipAddress;
+            }
+        } else {
+            //褰撳墠鏃犵綉缁滆繛鎺�,璇峰湪璁剧疆涓墦寮�缃戠粶
+        }
+        return null;
+    }
+
+    /**
+     * 灏嗗緱鍒扮殑int绫诲瀷鐨処P杞崲涓篠tring绫诲瀷
+     *
+     * @param ip
+     * @return
+     */
+    public static String intIP2StringIP(int ip) {
+        return (ip & 0xFF) + "." +
+                ((ip >> 8) & 0xFF) + "." +
+                ((ip >> 16) & 0xFF) + "." +
+                (ip >> 24 & 0xFF);
+    }
+}
diff --git a/lib/src/main/java/com/anyun/exam/lib/util/Speaker.java b/lib/src/main/java/com/anyun/exam/lib/util/Speaker.java
new file mode 100644
index 0000000..4cfb739
--- /dev/null
+++ b/lib/src/main/java/com/anyun/exam/lib/util/Speaker.java
@@ -0,0 +1,44 @@
+package com.anyun.exam.lib.util;
+
+import android.content.Context;
+import android.speech.tts.TextToSpeech;
+import android.util.Log;
+
+import java.util.Locale;
+
+public class Speaker {
+    private Context context;
+    private TextToSpeech tts;
+
+    final public String TAG = Speaker.class.getCanonicalName();
+
+    public Speaker(final Context context) {
+        // TODO Auto-generated constructor stub
+        this.context = context;
+        tts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
+            @Override
+            public void onInit(int status) {
+                Log.d(TAG, "onInit TextToSpeech.SUCCESS");
+
+                // TODO Auto-generated method stub
+                if (status == TextToSpeech.SUCCESS)
+                {
+                    Log.d(TAG, "TextToSpeech.SUCCESS");
+                    int result = tts.setLanguage(Locale.CHINA);
+
+                    if (result == TextToSpeech.LANG_MISSING_DATA
+                            || result == TextToSpeech.LANG_NOT_SUPPORTED)
+                    {
+                        Log.d(TAG, "TextToSpeech.LANG_NOT_SUPPORTED");
+                    }
+                }
+            }
+        });
+    }
+
+    public void speak(String text) {
+        Log.d(TAG, "SPEAK");
+//            tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
+        tts.speak(text, TextToSpeech.QUEUE_ADD, null, "speech");
+    }
+}

--
Gitblit v1.8.0