diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..603b140
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..36ae69d
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,56 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ applicationId "com.kitsprout.main"
+ minSdkVersion 24
+ targetSdkVersion 29
+ versionCode 2
+ versionName "1.1"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ externalNativeBuild {
+ cmake {
+ cppFlags ""
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ android.applicationVariants.all { variant ->
+ variant.outputs.all {
+ def project = 'kSerialBLE'
+ def SEP = '-'
+ def version = variant.versionName
+ outputFileName = project + SEP + version + ".apk"
+ }
+ }
+
+ externalNativeBuild {
+ cmake {
+ path "src/main/jni/CMakeLists.txt"
+ version "3.10.2"
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ implementation 'com.google.android.material:material:1.1.0'
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3f627d2
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/kitsprout/ks/KBluetooth.java b/app/src/main/java/com/kitsprout/ks/KBluetooth.java
new file mode 100644
index 0000000..cd6b62b
--- /dev/null
+++ b/app/src/main/java/com/kitsprout/ks/KBluetooth.java
@@ -0,0 +1,250 @@
+package com.kitsprout.ks;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+import android.view.Menu;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Set;
+import java.util.UUID;
+
+public class KBluetooth {
+
+ private static final String TAG = "KSBLE";
+ private static final UUID btUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+
+ private static boolean connectStatus;
+ private static BluetoothAdapter adapter;
+ private static BluetoothDevice[] deviceList;
+ private static BluetoothSocket socket;
+ private static InputStream inputStream;
+ private static OutputStream outputStream;
+
+ public static BluetoothDevice device;
+
+ public static boolean startup(Context context) {
+ connectStatus = false;
+
+ Log.d(TAG, "kBluetooth startup ...");
+
+ // get the bluetooth adapter
+ adapter = getAdapter();
+ if (adapter == null) {
+ Log.e(TAG, " getAdapter() ... error");
+ return false;
+ }
+ Log.d(TAG, " getAdapter() ... ok");
+
+ // get bluetooth paired devices
+ deviceList = getPairedDevice(adapter);
+ Log.d(TAG, " getPairedDevice() ... ok");
+ // select print device list
+ for (int i = 0; i < deviceList.length; i++) {
+ Log.d(TAG, " [" + (i+1) + "] "
+ + deviceList[i].getName() +
+ " (" + deviceList[i].getAddress() + ")");
+ }
+
+ // listening bluetooth broadcasts
+ BroadcastReceiver btReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
+ connectStatus = true;
+ Log.d(TAG, "onReceive() ... connected");
+ } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
+ if (connectStatus) {
+ connectStatus = false;
+ disconnect();
+ }
+ Log.d(TAG, "onReceive() ... disconnected");
+ }
+ }
+ };
+ IntentFilter btConnectFilter = new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
+ IntentFilter btDisconnectFilter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ context.registerReceiver(btReceiver, btConnectFilter);
+ context.registerReceiver(btReceiver, btDisconnectFilter);
+
+ return true;
+ }
+
+ private static BluetoothAdapter getAdapter() {
+// Log.d(TAG, "getAdapter()");
+ BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter == null) {
+ Log.e(TAG, "device doesn't support bluetooth");
+ return null;
+ }
+ return btAdapter;
+ }
+
+ private static BluetoothDevice[] getPairedDevice(BluetoothAdapter btAdapter) {
+// Log.d(TAG, "getPairedDevice()");
+ BluetoothDevice[] btDeviceList = null;
+ Set btDevicesList = btAdapter.getBondedDevices();
+ if (btDevicesList.size() > 0) {
+ int cnt = 0;
+ btDeviceList = new BluetoothDevice[btDevicesList.size()];
+ for (BluetoothDevice device : btDevicesList) {
+ btDeviceList[cnt++] = device;
+ }
+ }
+ return btDeviceList;
+ }
+
+ private static BluetoothSocket getSocket(BluetoothDevice device) {
+// Log.d(TAG, "getSocket()");
+ BluetoothSocket btSocket = null;
+ try {
+ btSocket = device.createRfcommSocketToServiceRecord(btUUID);
+ } catch (IOException e) {
+ Log.e(TAG, "getSocket() method failed", e);
+ }
+ return btSocket;
+ }
+
+ private static BluetoothDevice getDevice(BluetoothAdapter btAdapter, String address) {
+// Log.d(TAG, "getDevice()");
+ return btAdapter.getRemoteDevice(address);
+ }
+
+ public static String[] getDeviceName() {
+// Log.d(TAG, "getDeviceName()");
+ String[] deviceName = new String[deviceList.length];
+ for (int i = 0; i < deviceList.length; i++) {
+ String name = deviceList[i].getName();
+ if (deviceList[i].getName().length() > 13) {
+ name = name.substring(0, 10) + "...";
+ }
+ deviceName[i] = String.format("%s (%s)", name, deviceList[i].getAddress());
+ }
+ return deviceName;
+ }
+
+ public static void updatePairedDeviceList() {
+// Log.d(TAG, "updatePairedDevice()");
+ deviceList = getPairedDevice(adapter);
+ }
+
+ public static boolean connect(String address) {
+ Log.d(TAG, String.format("connect() ... mac: %s", address));
+ socket = getSocket(getDevice(adapter, address));
+ if (socket == null) {
+ return false;
+ }
+ adapter.cancelDiscovery();
+ try {
+ // Connect to the remote device through the socket
+ socket.connect();
+ inputStream = socket.getInputStream();
+ outputStream = socket.getOutputStream();
+ } catch (IOException connectException) {
+ // Unable to connect; close the socket and return
+ try {
+ socket.close();
+ Log.d(TAG, "connect() ... failed");
+ } catch (IOException e) {
+ Log.e(TAG, "connect() ... failed ... could not open the client socket", e);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isConnected() {
+ return connectStatus;
+ }
+
+ public static boolean disconnect() {
+ Log.d(TAG, String.format("disconnect() ... mac: %s", device.getAddress()));
+ try {
+ socket.close();
+ } catch (IOException e) {
+ Log.e(TAG, "disconnect() ... failed ... could not close the client socket", e);
+ return false;
+ }
+ return true;
+ }
+
+ public static final int DISCONNECT_FAILED = -2; // disconnect
+ public static final int DISCONNECTED = -1; // disconnect
+ public static final int UNCHANE = 0; // do nothing
+ public static final int CONNECTED = 1; // connect
+ public static final int CONNECT_FAILED = 2; // disconnect
+ public static int select(int index) {
+ int state = UNCHANE;
+ if ((index > 0) && (index <= deviceList.length)) {
+ // connect
+ device = deviceList[index - 1];
+ if (!connectStatus) {
+ connectStatus = connect(device.getAddress());
+ if (connectStatus) {
+ state = CONNECTED;
+ } else {
+ state = CONNECT_FAILED;
+ }
+ } else {
+ state = UNCHANE;
+ }
+ } else {
+ // disconnect
+ if (connectStatus) {
+ connectStatus = false;
+ if (disconnect()) {
+ state = DISCONNECTED;
+ } else {
+ state = DISCONNECT_FAILED;
+ }
+ }
+ }
+ return state;
+ }
+
+ public static int send(byte[] data) {
+ Log.d(TAG, "send()");
+ try {
+ outputStream.write(data);
+ } catch (IOException e) {
+ Log.e(TAG, "OutputStream write() method failed", e);
+ }
+ return data.length;
+ }
+
+ public static byte[] receive() {
+ try {
+ int bytesAvailable = inputStream.available();
+ if (bytesAvailable > 0) {
+ byte[] receiveBytes = new byte[bytesAvailable];
+ if (inputStream.read(receiveBytes) != -1) {
+ return receiveBytes;
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "InputStream read() method failed", e);
+ return null;
+ }
+ return new byte[0];
+ }
+
+ public static Menu updateDeviceMenu(Menu menu) {
+ updatePairedDeviceList();
+ menu.clear();
+ menu.add(0, 0, 0, "Disconnect");
+ String[] deviceName = getDeviceName();
+ for (int i = 1; i <= deviceName.length; i++) {
+ menu.add(0, i, i, deviceName[i - 1]);
+ }
+ return menu;
+ }
+
+}
diff --git a/app/src/main/java/com/kitsprout/ks/KSerial.java b/app/src/main/java/com/kitsprout/ks/KSerial.java
new file mode 100644
index 0000000..2fed698
--- /dev/null
+++ b/app/src/main/java/com/kitsprout/ks/KSerial.java
@@ -0,0 +1,260 @@
+package com.kitsprout.ks;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class KSerial {
+
+ // form JNI
+ static {
+ System.loadLibrary("libkserial");
+ }
+ private static native byte[] pack(int[] param, int type, int lens, double[] data);
+ private static native int unpackBuffer(byte[] buf, int lens);
+ private static native int getPacketCount();
+ private static native int getPacketType(int index);
+ private static native int getPacketBytes(int index);
+ private static native int[] getPacketParam(int index);
+ private static native double[] getPacketData(int index);
+
+ // log tag
+ private static final String TAG = "KSERIAL";
+
+ // default maximum receive buffer size
+ private static final int maxPacketBufferSize = 8 * 1024; // 8 kB
+
+ // receive packet buffer and index
+ private int packetBufferIndex;
+ private byte[] packetBuffer;
+
+ // maximum record buffer size
+ private int saveMaxBufferSize;
+ private ArrayList pklist;
+
+ private double packetTimeUnit;
+ private long packetTotalCount;
+ private int parameter16;
+
+ // timestamp and packet frequency
+ private boolean timestampInited;
+ private boolean enablePacketTime;
+ private long[] firstTimestamp;
+ private long[] lastTimestamp;
+ private long systemTimestampCount;
+ private long systemLastTimestamp;
+ private double frequency;
+ private double frequencyFiltered;
+
+ // lost rate
+ private boolean enableLostRate;
+ private long lostCount;
+
+ public static class KPacket {
+ public int type;
+ public int nbyte;
+ public int[] param;
+ public double[] data;
+ }
+
+ public KSerial(int saveMaxBufferSize, double timeUnit) {
+ this.saveMaxBufferSize = saveMaxBufferSize;
+ this.pklist = new ArrayList();
+ this.packetTotalCount = 0;
+ this.timestampInited = false;
+ this.firstTimestamp = new long[2];
+ this.lastTimestamp = new long[2];
+ this.resetPacketBuffer();
+ this.setTimeUnit(timeUnit);
+ }
+
+ private static final String[] typeNum2str = {
+ "uint8", "uint16", "uint32", "uint64",
+ "int8", "int16", "int32", "int64",
+ "R0", "half", "float", "double",
+ "R1", "R2", "R3", "R4",
+ };
+ public static String typeConvert(int type) {
+ if ((type < 0) || (type > 15)) {
+ return "";
+ }
+ return typeNum2str[type];
+ }
+ public static int typeConvert(String type) {
+ return 0;
+ }
+ private static int typeSize(int type) {
+ return 0;
+ }
+ private static int typeSize(String type) {
+ return 0;
+ }
+
+ private void resetPacketBuffer() {
+ packetBufferIndex = 0;
+ packetBuffer = new byte[maxPacketBufferSize];
+ }
+
+ private void setTimeUnit(double timeUnit) {
+ if (timeUnit > 0) {
+ // use packet time
+ enablePacketTime = true;
+ packetTimeUnit = timeUnit;
+ } else {
+ // use system time
+ enablePacketTime = false;
+ packetTimeUnit = 0;
+ }
+ }
+
+ private long getPacketTimestamp(KPacket packet) {
+ return (long)(packet.data[0] * 1000 + packet.data[1]);
+ }
+
+ public int getPacketParameterU16(KPacket packet) {
+ parameter16 = (packet.param[1] & 0x0000FFFF) * 256 | packet.param[0];
+ return parameter16;
+ }
+
+ private long getPacketLost(KPacket[] packets) {
+ long count = 0;
+ for (KPacket packet : packets) {
+ int lastCount = parameter16;
+ int differenceCount = getPacketParameterU16(packet) - lastCount;
+ if ((differenceCount != 1) && (differenceCount != -65535)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private double getPacketFrequency(KPacket lastPacket, int count) {
+ if (!timestampInited) {
+ timestampInited = true;
+ firstTimestamp[0] = System.currentTimeMillis();
+ lastTimestamp[0] = firstTimestamp[0];
+ if (enablePacketTime) {
+ firstTimestamp[1] = getPacketTimestamp(lastPacket);
+ lastTimestamp[1] = firstTimestamp[1];
+ }
+ frequency = 0;
+ } else {
+ packetTotalCount = packetTotalCount + count;
+ long systemTimestamp = System.currentTimeMillis();
+ if (enablePacketTime) {
+ long packetTimestamp = getPacketTimestamp(lastPacket);
+ double dt = (packetTimestamp - lastTimestamp[1]) * packetTimeUnit;
+ lastTimestamp[1] = packetTimestamp;
+ frequency = count / dt;
+ } else {
+ double dt = (systemTimestamp - systemLastTimestamp) * 0.001;
+ systemTimestampCount = systemTimestampCount + count;
+ if (dt > 1.2) {
+ frequency = systemTimestampCount / dt;
+ systemTimestampCount = 0;
+ systemLastTimestamp = systemTimestamp;
+ }
+ }
+ lastTimestamp[0] = systemTimestamp;
+ }
+ return frequency;
+ }
+
+ public byte[] setPacket(KPacket[] packets) {
+// int byteCount = 0;
+// List packetList = new ArrayList();
+// for (KPacket packet : packets) {
+// byte[] packBytes = pack(packet.param, packet.type, packet.data.length, packet.data);
+// packetList.add(packBytes);
+// byteCount += packBytes.length;
+// }
+// return packetList.toArray();
+ return new byte[0];
+ }
+
+ public byte[] setPacket(KPacket packets) {
+ return pack(packets.param, packets.type, packets.data.length, packets.data);
+ }
+
+ public KPacket[] getPacket(byte[] receiveBytes) {
+ // update packet buffer
+ System.arraycopy(receiveBytes, 0, packetBuffer, packetBufferIndex, receiveBytes.length);
+ packetBufferIndex += receiveBytes.length;
+ // unpack receive buffer
+ int newPacketBufferIndex = unpackBuffer(packetBuffer, packetBufferIndex);
+ int packetCount = getPacketCount();
+ // receive packet
+ KPacket[] pk = new KPacket[packetCount];
+ if (packetCount > 0) {
+ for (int i = 0; i < packetCount; i++) {
+ // get packet info and data form jni
+ KPacket packet = new KPacket();
+ packet.type = getPacketType(i);
+ packet.nbyte = getPacketBytes(i);
+ packet.param = getPacketParam(i);
+ packet.data = getPacketData(i);
+ // record packet
+ if (saveMaxBufferSize > 0) {
+ if (pklist.size() >= saveMaxBufferSize) {
+ pklist.remove(0); // remove oldest packet
+ }
+ pklist.add(packet);
+ }
+ pk[i] = packet;
+ }
+ // get frequency
+ frequency = getPacketFrequency(pk[packetCount-1], packetCount);
+ // get lostCount
+ if (enableLostRate) {
+ lostCount += getPacketLost(pk);
+ }
+ // remove unpack data from receive buffer
+ packetBufferIndex -= newPacketBufferIndex;
+ System.arraycopy(packetBuffer, newPacketBufferIndex, packetBuffer, 0, packetBufferIndex);
+// Log.d(TAG, String.format("time=%.2f, freq=%.2f, packetTotalCount=%d, pklist.size()=%d", getTimes(), getFrequency(0), packetTotalCount, pklist.size()));
+ }
+
+ return pk;
+ }
+
+ public double getFrequency(double weighting) {
+ if (weighting > 0) {
+ frequencyFiltered = (1 - weighting) * frequencyFiltered + weighting * frequency;
+ } else {
+ frequencyFiltered = frequency;
+ }
+ return frequencyFiltered;
+ }
+
+ public double getTimes() {
+ if (enablePacketTime) {
+ return (lastTimestamp[1] - firstTimestamp[1]) * packetTimeUnit;
+ } else {
+ return (lastTimestamp[0] - firstTimestamp[0]) * 0.001;
+ }
+ }
+
+ // need to set parameter byte to counter (16-bit)
+ public void enableLostRateDetection(boolean cmd) {
+ enableLostRate = cmd;
+ lostCount -= 1;
+ }
+
+ public long getLostCount() {
+ return lostCount;
+ }
+
+ public long getPacketTotalCount() {
+ return packetTotalCount;
+ }
+
+ public long getSavePacketCount() {
+ return pklist.size();
+ }
+
+ public ArrayList getSavePacketBuffer() {
+ return pklist;
+ }
+
+}
diff --git a/app/src/main/java/com/kitsprout/main/MainActivity.java b/app/src/main/java/com/kitsprout/main/MainActivity.java
new file mode 100644
index 0000000..90ee9e9
--- /dev/null
+++ b/app/src/main/java/com/kitsprout/main/MainActivity.java
@@ -0,0 +1,181 @@
+package com.kitsprout.main;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.kitsprout.ks.KBluetooth;
+import com.kitsprout.ks.KSerial;
+import com.kitsprout.ks.KSerial.KPacket;
+
+import java.util.Locale;
+
+public class MainActivity extends AppCompatActivity {
+
+ private Menu bluetoothDeviceMenu = null;
+
+ private TextView bluetoothRecvText;
+ private TextView bluetoothRecvBufferText;
+ private EditText bluetoothSendText;
+ private Button bluetoothSendButton;
+ private Button bluetoothConnectStatus;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ // layout
+ bluetoothRecvText = findViewById(R.id.textViewBluetoothRecv);
+ bluetoothRecvBufferText = findViewById(R.id.textViewBluetoothRecvBuffer);
+ bluetoothSendText = findViewById(R.id.editTextBluetoothSend);
+ bluetoothSendButton = findViewById(R.id.buttonBluetoothSend);
+ bluetoothConnectStatus = findViewById(R.id.buttonBluetoothConnectStatus);
+ changeConnectStatusColor(false);
+
+ // bluetooth startup
+ if (!KBluetooth.startup(this)) {
+ Log.e("KS_DBG", "bluetoothStartup ... error");
+ }
+ bluetoothRecvText.setText(getPacketString(0, 0, 0, 0, 0, new KPacket[0]));
+
+ // keep screen on
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ bluetoothDeviceMenu = menu;
+ return super.onCreateOptionsMenu(bluetoothDeviceMenu);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ bluetoothDeviceMenu = KBluetooth.updateDeviceMenu(menu);
+ return super.onPrepareOptionsMenu(bluetoothDeviceMenu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ int state = KBluetooth.select(id);
+ if (state == KBluetooth.DISCONNECTED) {
+// bluetoothRecvBufferText.setText("");
+ changeConnectStatusColor(false);
+ Toast.makeText(this, "DISCONNECT SUCCESS", Toast.LENGTH_SHORT).show();
+ } else if (state == KBluetooth.DISCONNECT_FAILED) {
+ Toast.makeText(this, "DISCONNECT FAILED", Toast.LENGTH_SHORT).show();
+ } else if (state == KBluetooth.CONNECT_FAILED) {
+ Toast.makeText(this, "CONNECT FAILED", Toast.LENGTH_SHORT).show();
+ } else if (state == KBluetooth.CONNECTED) {
+ bluetoothBeginListening();
+ Toast.makeText(this, "CONNECT SUCCESS", Toast.LENGTH_SHORT).show();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void changeConnectStatusColor(boolean enable) {
+ if (enable) {
+ bluetoothSendButton.setTextColor(Color.BLACK);
+ bluetoothConnectStatus.setBackgroundColor(Color.RED);
+ } else {
+ bluetoothSendButton.setTextColor(Color.GRAY);
+ bluetoothConnectStatus.setBackgroundColor(Color.parseColor("#DCDCDC"));
+ }
+ }
+
+ public void OnClickBluetoothSendData(View view) {
+ if (KBluetooth.isConnected()) {
+ int lens = KBluetooth.send(bluetoothSendText.getText().toString().getBytes());
+ Log.d("KS_DBG", String.format("OnClickBluetoothSendData() ... lens = %d", lens));
+ } else {
+ Log.d("KS_DBG", "OnClickBluetoothSendData() ... without connect");
+ }
+ }
+
+ KSerial ks;
+ Thread bluetoothRecvThread;
+ void bluetoothBeginListening() {
+ Log.d("KS_DBG", "bluetoothBeginListening()");
+ final Handler handler = new Handler();
+ ks = new KSerial(32*1024, 0.001);
+ ks.enableLostRateDetection(true);
+ bluetoothRecvThread = new Thread(new Runnable() {
+ public void run() {
+ changeConnectStatusColor(true);
+ while (!Thread.currentThread().isInterrupted() && KBluetooth.isConnected()) {
+ byte[] receiveBytes = KBluetooth.receive();
+ if (receiveBytes != null) {
+ int bytesAvailable = receiveBytes.length;
+ if (bytesAvailable > 0) {
+ KPacket[] pk = ks.getPacket(receiveBytes);
+ for (KSerial.KPacket KPacket : pk) {
+ Log.d("KSERIAL", String.format("%6d,%.0f,%d,%d",
+ ks.getPacketParameterU16(KPacket), ks.getFrequency(0), bytesAvailable, pk.length));
+ }
+ if (pk.length > 0) {
+ // show information
+ final String recvByteString = getPacketHexString(ks.setPacket(pk[pk.length-1]));
+ final String recvBufferString = getPacketString(ks.getFrequency(0), ks.getTimes(), bytesAvailable, ks.getLostCount(), ks.getPacketTotalCount(), pk);
+ handler.post(new Runnable() {
+ public void run() {
+ bluetoothRecvText.setText(recvBufferString);
+ bluetoothRecvBufferText.setText(recvByteString);
+ }
+ });
+ }
+ }
+ } else {
+ KBluetooth.disconnect();
+ break;
+ }
+ }
+ changeConnectStatusColor(false);
+ }
+ });
+ bluetoothRecvThread.start();
+ }
+
+ private String getPacketHexString(byte[] buf) {
+ StringBuilder logString;
+ logString = new StringBuilder("");
+ for (byte b : buf) {
+ logString.append(String.format(Locale.ENGLISH, " %02X", b));
+ }
+ return logString.toString();
+ }
+
+ private String getPacketString(double freq, double time, int bytesAvailable, long lostCount, long bytesTotal, KPacket[] packet) {
+ int idx = packet.length - 1;
+ StringBuilder logString;
+ logString = new StringBuilder(String.format(Locale.ENGLISH, "Freq: %.2f Hz\n", freq));
+ logString.append(String.format(Locale.ENGLISH, "Time: %.3f sec\n", time));
+ logString.append(String.format(Locale.ENGLISH, "Recv: %d\n", bytesAvailable));
+ logString.append(String.format(Locale.ENGLISH, "Lost: %d\n", lostCount));
+ if (idx < 0) {
+ return logString.toString();
+ }
+ logString.append("\n");
+ logString.append(String.format(Locale.ENGLISH, "Total: %d (%d)\n", bytesTotal, packet.length));
+ logString.append(String.format(Locale.ENGLISH, "Type: %s\n", KSerial.typeConvert(packet[idx].type)));
+ logString.append(String.format(Locale.ENGLISH, "Lens: %d (%d bytes)\n", packet[idx].data.length, packet[idx].nbyte));
+ logString.append(String.format(Locale.ENGLISH, "Param: %02X, %02X\n", packet[idx].param[0], packet[idx].param[1]));
+ logString.append("\n");
+ for (int i = 0; i < packet[idx].data.length; i++) {
+ logString.append(String.format(Locale.ENGLISH, "Data[%d]: %.0f\n", i, packet[idx].data[i]));
+ }
+ return logString.toString();
+ }
+
+}
diff --git a/app/src/main/jni/CMakeLists.txt b/app/src/main/jni/CMakeLists.txt
new file mode 100644
index 0000000..8074c46
--- /dev/null
+++ b/app/src/main/jni/CMakeLists.txt
@@ -0,0 +1,46 @@
+# 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.
+ libkserial
+ # Sets the library as a shared library.
+ SHARED
+ # Provides a relative path to your source file(s).
+ kSerial.c kSerialJNI.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.
+ libkserial
+ # Links the target library to the log library
+ # included in the NDK.
+ ${log-lib}
+)
diff --git a/app/src/main/jni/kSerial.c b/app/src/main/jni/kSerial.c
new file mode 100644
index 0000000..cadedcd
--- /dev/null
+++ b/app/src/main/jni/kSerial.c
@@ -0,0 +1,632 @@
+/**
+ * __ ____
+ * / /__ _ __ / __/ __
+ * / //_/(_)/ /_ / / ___ ____ ___ __ __ / /_
+ * / ,< / // __/_\ \ / _ \ / __// _ \/ // // __/
+ * /_/|_|/_/ \__//___// .__//_/ \___/\_,_/ \__/
+ * /_/ github.com/KitSprout
+ *
+ * @file kSerial.c
+ * @author KitSprout
+ * @date Mar-2020
+ * @brief kSerial packet format :
+ * byte 1 : header 'K' (75) [HK]
+ * byte 2 : header 'S' (83) [HS]
+ * byte 3 : data bytes (12-bit) [L ]
+ * byte 4 : data type [T ]
+ * byte 5 : parameter 1 [P1]
+ * byte 6 : parameter 2 [P2]
+ * byte 7 : checksum [CK]
+ * ...
+ * byte L-1 : data [DN]
+ * byte L : finish '\r' (13) [ER]
+ */
+
+/* Includes --------------------------------------------------------------------------------*/
+#include "kSerial.h"
+#if KSERIAL_SEND_ENABLE || KSERIAL_RECV_ENABLE
+#include
+#include
+#include "serial.h"
+#endif
+
+/* Define ----------------------------------------------------------------------------------*/
+/* Macro -----------------------------------------------------------------------------------*/
+
+#if KSERIAL_SEND_ENABLE
+#ifndef kSerial_Send
+#define kSerial_Send(__DATA, __LENS) Serial_SendData(&s, __DATA, __LENS)
+#endif
+#endif
+#if KSERIAL_RECV_ENABLE
+#define kSerial_Recv(__DATA, __LENS) Serial_RecvData(&s, __DATA, __LENS)
+#define kSerial_RecvByte() Serial_RecvByte(&s)
+#define kSerial_RecvFlush() Serial_Flush(&s)
+#endif
+#if KSERIAL_SEND_ENABLE || KSERIAL_RECV_ENABLE
+#define kSerial_Delay(__MS) Serial_Delay(__MS)
+#endif
+
+/* Typedef ---------------------------------------------------------------------------------*/
+/* Variables -------------------------------------------------------------------------------*/
+
+#if KSERIAL_SEND_ENABLE
+uint8_t ksSendBuff[KS_MAX_SEND_BUFF_SIZE] = {0};
+#endif
+#if KSERIAL_RECV_ENABLE
+uint8_t ksRecvBuff[KS_MAX_RECV_BUFF_SIZE] = {0};
+#endif
+
+/* Prototypes ------------------------------------------------------------------------------*/
+/* Functions -------------------------------------------------------------------------------*/
+
+/**
+ * @brief kSerial_GetTypeSize
+ */
+uint32_t kSerial_GetTypeSize( uint32_t type )
+{
+ type &= 0x0F;
+ if ((type > KS_F64) || (type == KS_R0))
+ {
+ return (0);
+ }
+ else
+ {
+ return (1 << (type & 0x03));
+ }
+}
+
+/**
+ * @brief kSerial_CheckHeader
+ */
+uint32_t kSerial_CheckHeader( const uint8_t *packet, void *param, uint32_t *type, uint32_t *nbyte )
+{
+ uint32_t checksum = 0;
+
+ if ((packet[0] == 'K') && (packet[1] == 'S'))
+ {
+ *type = packet[3] & 0x0F;
+ *nbyte = (((uint32_t)packet[3] << 4) & 0x0F00) | packet[2];
+ for (uint32_t i = 2; i < 6; i++)
+ {
+ checksum += packet[i];
+ }
+ checksum &= 0xFF;
+ if (packet[6] == checksum)
+ {
+ ((uint8_t*)param)[0] = packet[4];
+ ((uint8_t*)param)[1] = packet[5];
+ return KS_OK;
+ }
+ }
+
+ return KS_ERROR;
+}
+
+/**
+ * @brief kSerial_CheckEnd
+ */
+uint32_t kSerial_CheckEnd( const uint8_t *packet, const uint32_t nbyte )
+{
+ if (packet[nbyte + 8 - 1] == '\r')
+ {
+ return KS_OK;
+ }
+
+ return KS_ERROR;
+}
+
+/**
+ * @brief kSerial_Check
+ */
+uint32_t kSerial_Check( const uint8_t *packet, const uint32_t bufsize, void *param, uint32_t *type, uint32_t *nbyte )
+{
+ uint8_t checksum = 0;
+
+ if ((packet[0] == 'K') && (packet[1] == 'S'))
+ {
+ *type = packet[3] & (uint8_t)0x0F;
+ *nbyte = (((uint32_t)packet[3] << 4) & 0x0F00) | packet[2];
+ checksum = packet[2] + packet[3] + packet[4] + packet[5];
+ if (checksum == packet[6])
+ {
+ uint32_t lastidx = *nbyte + 8 - 1;
+ if ((lastidx < bufsize) && (packet[lastidx] == '\r'))
+ {
+ ((uint8_t*)param)[0] = packet[4];
+ ((uint8_t*)param)[1] = packet[5];
+ return KS_OK;
+ }
+ }
+ }
+
+ return KS_ERROR;
+}
+
+/**
+ * @brief kSerial_GetBytesData
+ */
+void kSerial_GetBytesData( const uint8_t *packet, void *pdata, const uint32_t nbyte )
+{
+ for (uint32_t i = 0; i < nbyte; i++)
+ {
+ ((uint8_t*)pdata)[i] = packet[7 + i];
+ }
+}
+
+/**
+ * @brief kSerial_Pack
+ */
+uint32_t kSerial_Pack( uint8_t *packet, const void *param, const uint32_t type, const uint32_t lens, const void *pdata )
+{
+ uint32_t lensHiBit;
+ uint32_t packetDataBytes; // in bytes
+ uint32_t checksum = 0;
+ uint32_t typeSize = kSerial_GetTypeSize(type);
+
+ packetDataBytes = (typeSize > 1) ? (lens * typeSize) : (lens);
+ lensHiBit = (packetDataBytes & 0x0F00) >> 4;
+
+ packet[0] = 'K'; /* header 'K' */
+ packet[1] = 'S'; /* header 'S' */
+ packet[2] = packetDataBytes; /* data bytes */
+ packet[3] = lensHiBit | (type & 0x0F); /* data type */
+
+ if (param != NULL)
+ {
+ packet[4] = ((uint8_t*)param)[0]; /* parameter 1 */
+ packet[5] = ((uint8_t*)param)[1]; /* parameter 2 */
+ }
+ else
+ {
+ packet[4] = 0;
+ packet[5] = 0;
+ }
+ for (uint32_t i = 2; i < 6; i++)
+ {
+ checksum += packet[i];
+ }
+ packet[6] = checksum; /* checksum */
+
+ for (uint32_t i = 0; i < packetDataBytes; i++)
+ {
+ packet[7 + i] = ((uint8_t*)pdata)[i]; /* data ...... */
+ }
+ packet[7 + packetDataBytes] = '\r'; /* finish '\r' */
+
+ return (packetDataBytes + 8);
+}
+
+/**
+ * @brief kSerial_Unpack
+ */
+uint32_t kSerial_Unpack( const uint8_t *packet, const uint32_t bufsize, void *param, uint32_t *type, uint32_t *nbyte, void *pdata )
+{
+ uint32_t status;
+
+ status = kSerial_Check(packet, bufsize, param, type, nbyte);
+ if (status == KS_OK)
+ {
+ for (uint32_t i = 0; i < *nbyte; i++)
+ {
+ ((uint8_t*)pdata)[i] = packet[7 + i];
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief kSerial_UnpackBuffer
+ */
+#include
+uint32_t kSerial_UnpackBuffer( const uint8_t *buffer, const uint32_t buffersize, kserial_packet_t *packet, uint32_t *packetcnt )
+{
+ uint32_t offset = 0;
+ uint32_t newindex = 0;
+ uint32_t typeSize;
+
+ *packetcnt = 0;
+ while ((buffersize - offset) > 7) // min packet bytes = 8
+ {
+ uint8_t param[2] = {0};
+ uint32_t type = 0;
+ uint32_t nbyte = 0;
+ uint32_t status = kSerial_Check(&buffer[offset], buffersize - offset, param, &type, &nbyte);
+ if (status == KS_OK)
+ {
+ packet[*packetcnt].param[0] = param[0];
+ packet[*packetcnt].param[1] = param[1];
+ packet[*packetcnt].type = type;
+ packet[*packetcnt].nbyte = nbyte;
+ typeSize = kSerial_GetTypeSize(type);
+ if (typeSize != 0)
+ {
+ packet[*packetcnt].lens = packet[*packetcnt].nbyte / typeSize;
+ }
+ packet[*packetcnt].data = (void *)calloc(packet[*packetcnt].nbyte, sizeof(uint8_t));
+ kSerial_GetBytesData(&buffer[offset], packet[*packetcnt].data, packet[*packetcnt].nbyte);
+ offset += nbyte + 8;
+ newindex = offset;
+ (*packetcnt)++;
+ }
+ else
+ {
+ offset++;
+ }
+ }
+
+ return newindex;
+}
+
+/**
+ * @brief kSerial_SendPacket
+ */
+uint32_t kSerial_SendPacket( void *param, void *sdata, const uint32_t lens, const uint32_t type )
+{
+#if KSERIAL_SEND_ENABLE
+ uint32_t nbytes;
+ nbytes = kSerial_Pack(ksSendBuff, param, type, lens, sdata);
+ kSerial_Send(ksSendBuff, nbytes);
+ // TODO: fix return
+ return nbytes;
+#else
+ return KS_ERROR;
+#endif
+}
+
+/**
+ * @brief kSerial_RecvPacket
+ */
+uint32_t kSerial_RecvPacket( void *param, void *rdata, uint32_t *lens, uint32_t *type )
+{
+#if KSERIAL_RECV_ENABLE
+ static uint32_t index = 0;
+ static uint32_t bytes = 0;
+ static uint32_t point = 0;
+
+ uint32_t state;
+ uint32_t typeSize;
+
+ ksRecvBuff[point] = kSerial_RecvByte();
+ if (point > 6)
+ {
+ if ((ksRecvBuff[point - 7] == 'K') && (ksRecvBuff[point - 6] == 'S'))
+ {
+ index = point - 7;
+ bytes = ((((uint32_t)ksRecvBuff[index + 3] << 4) & 0x0F00) | ksRecvBuff[index + 2]) + 8;
+ }
+ if ((point - index + 1) == bytes)
+ {
+ state = kSerial_Unpack(&ksRecvBuff[index], param, type, lens, rdata);
+ if (state == KS_OK)
+ {
+ point = 0;
+ index = 0;
+ bytes = 0;
+ typeSize = kSerial_GetTypeSize(*type);
+ if (typeSize != 0)
+ {
+ *lens /= typeSize;
+ }
+ return KS_OK;
+ }
+ }
+ }
+ if (++point >= KS_MAX_RECV_BUFF_SIZE)
+ {
+ point = 0;
+ }
+ return KS_ERROR;
+#else
+ return KS_ERROR;
+#endif
+}
+
+/**
+ * @brief kSerial_Read
+ */
+uint32_t kSerial_Read( kserial_t *ks )
+{
+#if KSERIAL_RECV_ENABLE
+ uint32_t available = 0;
+ uint32_t nbyte;
+ uint32_t newindex;
+
+ do
+ { // add rx data to packet buffer
+ nbyte = kSerial_Recv(&ks->buffer[ks->count], ks->size - ks->count);
+ if (nbyte)
+ {
+ available = 1;
+ ks->count += nbyte;
+ }
+ }
+ while (nbyte);
+
+ ks->pkcnt = 0;
+ if (available)
+ {
+ newindex = kSerial_UnpackBuffer(ks->buffer, ks->count, ks->packet, &ks->pkcnt);
+ if (ks->pkcnt)
+ {
+ // update packet buffer
+ ks->count -= newindex;
+ memcpy(ks->buffer, &ks->buffer[newindex], ks->count);
+ memset(&ks->buffer[ks->count], 0, ks->size - ks->count);
+ }
+ }
+ // TODO: fix return
+ return ks->pkcnt;
+#else
+ return KS_ERROR;
+#endif
+}
+
+/**
+ * @brief kSerial_ReadFlush
+ */
+void kSerial_ReadFlush( kserial_t *ks )
+{
+#if KSERIAL_RECV_ENABLE
+ kSerial_RecvFlush();
+ memset(ks->buffer, 0, ks->size);
+ ks->count = 0;
+#endif
+}
+
+/**
+ * @brief kSerial_GetPacketData
+ */
+void kSerial_GetPacketData( kserial_packet_t *ksp, void *pdata, const uint32_t index )
+{
+ if (pdata != NULL)
+ {
+ memcpy(pdata, ksp[index].data, ksp[index].nbyte);
+ }
+ free(ksp[index].data);
+}
+
+/**
+ * @brief kSerial_TwiWriteReg
+ * Send packet ['K', 'S', 1, R1, slaveAddress(8-bit), regAddress, ck, regData, '\r']
+ */
+uint32_t kSerial_TwiWriteReg( const uint8_t slaveAddr, const uint8_t regAddr, const uint8_t regData )
+{
+#if KSERIAL_TWI_ENABLE
+ uint8_t param[2] = {slaveAddr << 1, regAddr};
+ uint32_t type = KS_R1;
+ uint32_t nbytes;
+
+ kSerial_RecvFlush();
+
+ nbytes = kSerial_Pack(ksSendBuff, param, type, 1, ®Data);
+ kSerial_Send(ksSendBuff, nbytes);
+#if 0
+ klogd("[W] param = %02X, %02X, type = %d, bytes = %d, data = %02X\n", param[0], param[1], type, nbytes, wdata);
+#endif
+ return nbytes;
+#else
+ return KS_ERROR;
+#endif
+}
+
+/**
+ * @brief kSerial_TwiReadReg
+ * Send packet ['K', 'S', 1, R1, slaveAddress(8-bit)+1, regAddress, ck, 1, '\r']
+ * Recv packet ['K', 'S', 1, R1, slaveAddress(8-bit)+1, regAddress, ck, regData, '\r']
+ */
+uint32_t kSerial_TwiReadReg( const uint8_t slaveAddr, const uint8_t regAddr, uint8_t *regData )
+{
+#if KSERIAL_TWI_ENABLE
+ uint8_t param[2] = {(slaveAddr << 1) + 1, regAddr};
+ uint32_t type = KS_R1;
+ uint32_t nbytes;
+ uint32_t status;
+ uint32_t singleRead = 1;
+
+ kSerial_RecvFlush();
+
+ nbytes = kSerial_Pack(ksSendBuff, param, type, 1, &singleRead);
+ kSerial_Send(ksSendBuff, nbytes);
+
+ nbytes = 0;
+ while (nbytes == 0)
+ {
+ kSerial_Delay(100);
+ nbytes = kSerial_Recv(ksRecvBuff, KS_MAX_RECV_BUFF_SIZE);
+ }
+
+ // TODO: check i2cbuff first 'KS'
+ status = kSerial_Unpack(ksRecvBuff, param, &type, &nbytes, ksSendBuff);
+ if (status == KS_OK)
+ {
+ for (uint32_t i = 0; i < nbytes; i++)
+ {
+ regData[i] = ksSendBuff[i];
+ }
+#if 0
+ klogd("[R] param = %02X, %02X, type = %d, bytes = %d, data =", param[0], param[1], type, nbytes + 8);
+ for (uint32_t i = 0; i < nbytes; i++)
+ {
+ klogd(" %02X", i2cbuff[1][i]);
+ }
+ klogd("\n");
+#endif
+ }
+ return status;
+#else
+ return KS_ERROR;
+#endif
+}
+
+/**
+ * @brief kSerial_TwiReadRegs
+ * Send packet ['K', 'S', 1, R1, slaveAddress(8-bit)+1, regAddress, ck, lens, '\r']
+ * Recv packet ['K', 'S', lens, R1, slaveAddress(8-bit)+1, regAddress, ck, regData ..., '\r']
+ */
+uint32_t kSerial_TwiReadRegs( const uint8_t slaveAddr, const uint8_t regAddr, uint8_t *regData, const uint8_t lens )
+{
+#if KSERIAL_TWI_ENABLE
+ uint8_t param[2] = {(slaveAddr << 1) + 1, regAddr};
+ uint32_t type = KS_R1;
+ uint32_t nbytes;
+ uint32_t status;
+
+ kSerial_RecvFlush();
+
+ nbytes = kSerial_Pack(ksSendBuff, param, type, 1, &lens);
+ kSerial_Send(ksSendBuff, nbytes);
+
+ nbytes = 0;
+ while (nbytes == 0)
+ {
+ kSerial_Delay(100);
+ nbytes = kSerial_Recv(ksRecvBuff, KS_MAX_RECV_BUFF_SIZE);
+ }
+
+ // TODO: check i2cbuff first 'KS'
+ status = kSerial_Unpack(ksRecvBuff, param, &type, &nbytes, ksSendBuff);
+ if (status == KS_OK)
+ {
+ for (uint32_t i = 0; i < nbytes; i++)
+ {
+ regData[i] = ksSendBuff[i];
+ }
+#if 0
+ klogd("[R] param = %02X, %02X, type = %d, bytes = %d, data =", param[0], param[1], type, nbytes + 8);
+ for (uint32_t i = 0; i < nbytes; i++)
+ {
+ klogd(" %02X", i2cbuff[1][i]);
+ }
+ klogd("\n");
+#endif
+ }
+ return status;
+#else
+ return KS_ERROR;
+#endif
+}
+
+/**
+ * @brief kSerial_TwiCheck
+ * Send packet ['K', 'S', 1, R1, 1, 0, ck, 1, '\r']
+ * Recv packet ['K', 'S', 1, R1, 1, 0, ck, regData, '\r']
+ */
+uint32_t kSerial_TwiCheck( void )
+{
+#if KSERIAL_TWI_ENABLE
+ uint8_t val;
+ if (kSerial_TwiReadReg(0x00, 0x00, &val) != KS_OK)
+ {
+ return KS_ERROR;
+ }
+ return KS_OK;
+#else
+ return KS_ERROR;
+#endif
+}
+
+/**
+ * @brief kSerial_TwiScanDevice
+ * Send packet ['K', 'S', 0, R2, 0xAB, 0, ck, '\r']
+ * Recv packet ['K', 'S', lens, R2, 0xAB, 0, ck, address ..., '\r']
+ */
+uint32_t kSerial_TwiScanDevice( uint8_t *slaveAddr )
+{
+#if KSERIAL_TWI_ENABLE
+ uint8_t param[2] = {0xAB, 0};
+ uint32_t type = KS_R2;
+ uint32_t nbytes;
+ uint32_t status;
+ uint32_t count;
+
+ kSerial_RecvFlush();
+
+ nbytes = kSerial_Pack(ksSendBuff, param, type, 0, NULL);
+ kSerial_Send(ksSendBuff, nbytes);
+
+ Serial_Delay(100);
+ nbytes = kSerial_Recv(ksRecvBuff, KS_MAX_RECV_BUFF_SIZE);
+
+ // TODO: check i2cbuff first 'KS'
+ status = kSerial_Unpack(ksRecvBuff, param, &type, &count, ksSendBuff);
+ if (status == KS_OK)
+ {
+ for (uint32_t i = 0; i < count; i++)
+ {
+ slaveAddr[i] = ksSendBuff[i];
+ }
+#if 0
+ printf(" >> i2c device list (found %d device)\n\n", count);
+ printf(" ");
+ for (uint32_t i = 0; i < count; i++)
+ {
+ printf(" %02X", slaveAddr[i]);
+ }
+ printf("\n\n");
+#endif
+ }
+ else
+ {
+ return 0xFF;
+ }
+ return count;
+#else
+ return KS_ERROR;
+#endif
+}
+
+/**
+ * @brief kSerial_TwiScanRegister
+ * Send packet ['K', 'S', 0, R2, 0xCB, slaveAddress, ck, '\r']
+ * Recv packet ['K', 'S', 256, R2, 0xCB, slaveAddress, ck, address ..., '\r']
+ */
+uint32_t kSerial_TwiScanRegister( const uint8_t slaveAddr, uint8_t reg[256] )
+{
+#if KSERIAL_TWI_ENABLE
+ uint8_t param[2] = {0xCB, slaveAddr << 1};
+ uint32_t type = KS_R2;
+ uint32_t nbytes;
+ uint32_t status;
+
+ kSerial_RecvFlush();
+
+ nbytes = kSerial_Pack(ksSendBuff, param, type, 0, NULL);
+ kSerial_Send(ksSendBuff, nbytes);
+
+ Serial_Delay(100);
+ nbytes = kSerial_Recv(ksRecvBuff, KS_MAX_RECV_BUFF_SIZE);
+
+ // TODO: check i2cbuff first 'KS'
+ status = kSerial_Unpack(ksRecvBuff, param, &type, &nbytes, ksSendBuff);
+ if (status == KS_OK)
+ {
+ for (uint32_t i = 0; i < 256; i++)
+ {
+ reg[i] = ksSendBuff[i];
+ }
+#if 0
+ printf("\n");
+ printf(" >> i2c device register (address 0x%02X)\n\n", slaveAddr);
+ printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
+ for (uint32_t i = 0; i < 256; i += 16)
+ {
+ printf(" %02X: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
+ i,
+ reg[i + 0], reg[i + 1], reg[i + 2], reg[i + 3],
+ reg[i + 4], reg[i + 5], reg[i + 6], reg[i + 7],
+ reg[i + 8], reg[i + 9], reg[i + 10], reg[i + 11],
+ reg[i + 12], reg[i + 13], reg[i + 14], reg[i + 15]
+ );
+ }
+ printf("\n\n");
+#endif
+ }
+ return status;
+#else
+ return KS_ERROR;
+#endif
+}
+
+/*************************************** END OF FILE ****************************************/
diff --git a/app/src/main/jni/kSerial.h b/app/src/main/jni/kSerial.h
new file mode 100644
index 0000000..d5f899a
--- /dev/null
+++ b/app/src/main/jni/kSerial.h
@@ -0,0 +1,113 @@
+/**
+ * __ ____
+ * / /__ _ __ / __/ __
+ * / //_/(_)/ /_ / / ___ ____ ___ __ __ / /_
+ * / ,< / // __/_\ \ / _ \ / __// _ \/ // // __/
+ * /_/|_|/_/ \__//___// .__//_/ \___/\_,_/ \__/
+ * /_/ github.com/KitSprout
+ *
+ * @file kSerial.h
+ * @author KitSprout
+ * @date Jan-2020
+ * @brief
+ *
+ */
+
+/* Define to prevent recursive inclusion ---------------------------------------------------*/
+#ifndef __KSERIAL_H
+#define __KSERIAL_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* Includes --------------------------------------------------------------------------------*/
+#include
+#include "kStatus.h"
+
+/* Define ----------------------------------------------------------------------------------*/
+
+#ifndef KSERIAL_SEND_ENABLE
+#define KSERIAL_SEND_ENABLE (0U)
+#ifndef KS_MAX_SEND_BUFF_SIZE
+#define KS_MAX_SEND_BUFF_SIZE (4096 + 32)
+#endif
+#endif
+
+#ifndef KSERIAL_RECV_ENABLE
+#define KSERIAL_RECV_ENABLE (0U)
+#ifndef KS_MAX_RECV_BUFF_SIZE
+#define KS_MAX_RECV_BUFF_SIZE (4096 + 1024 + 32)
+#endif
+#endif
+
+#ifndef KSERIAL_TWI_ENABLE
+#define KSERIAL_TWI_ENABLE (0U)
+#endif
+#if KSERIAL_TWI_ENABLE
+#if !(KSERIAL_SEND_ENABLE && KSERIAL_RECV_ENABLE)
+#error "Need to enable send and recv"
+#endif
+#endif
+
+/* Macro -----------------------------------------------------------------------------------*/
+/* Typedef ---------------------------------------------------------------------------------*/
+
+typedef struct
+{
+ uint8_t param[2];
+ uint32_t type;
+ uint32_t lens;
+ uint32_t nbyte;
+ void *data;
+
+} kserial_packet_t;
+
+typedef struct
+{
+ uint32_t size;
+ uint32_t count;
+ uint8_t *buffer;
+
+ uint32_t pkcnt;
+ kserial_packet_t *packet;
+
+} kserial_t;
+
+/* Extern ----------------------------------------------------------------------------------*/
+/* Functions -------------------------------------------------------------------------------*/
+
+uint32_t kSerial_GetTypeSize( uint32_t type );
+
+uint32_t kSerial_CheckHeader( const uint8_t *packet, void *param, uint32_t *type, uint32_t *nbyte );
+uint32_t kSerial_CheckEnd( const uint8_t *packet, const uint32_t nbyte );
+uint32_t kSerial_Check( const uint8_t *packet, const uint32_t lens, void *param, uint32_t *type, uint32_t *nbyte );
+void kSerial_GetBytesData( const uint8_t *packet, void *pdata, const uint32_t nbyte );
+
+uint32_t kSerial_Pack( uint8_t *packet, const void *param, const uint32_t type, const uint32_t lens, const void *pdata );
+uint32_t kSerial_Unpack( const uint8_t *packet, const uint32_t bufsize, void *param, uint32_t *type, uint32_t *nbyte, void *pdata );
+uint32_t kSerial_UnpackBuffer( const uint8_t *buffer, const uint32_t buffersize, kserial_packet_t *packet, uint32_t *packetcnt );
+void kSerial_GetPacketData( kserial_packet_t *ksp, void *pdata, const uint32_t index );
+
+uint32_t kSerial_SendPacket( void *param, void *sdata, const uint32_t lens, const uint32_t type );
+uint32_t kSerial_RecvPacket( void *param, void *rdata, uint32_t *lens, uint32_t *type );
+
+uint32_t kSerial_Read( kserial_t *ks );
+void kSerial_ReadFlush( kserial_t *ks );
+void kSerial_GetPacketData( kserial_packet_t *ksp, void *pdata, const uint32_t index );
+
+uint32_t kSerial_TwiWriteReg( const uint8_t slaveAddr, const uint8_t regAddr, const uint8_t regData );
+uint32_t kSerial_TwiReadReg( const uint8_t slaveAddr, const uint8_t regAddr, uint8_t *regData );
+uint32_t kSerial_TwiReadRegs( const uint8_t slaveAddr, const uint8_t regAddr, uint8_t *regData, const uint8_t lens );
+uint32_t kSerial_TwiCheck( void );
+uint32_t kSerial_TwiScanDevice( uint8_t *slaveAddr );
+uint32_t kSerial_TwiScanRegister( const uint8_t slaveAddr, uint8_t reg[256] );
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/*************************************** END OF FILE ****************************************/
diff --git a/app/src/main/jni/kSerialJNI.cpp b/app/src/main/jni/kSerialJNI.cpp
new file mode 100644
index 0000000..45f7776
--- /dev/null
+++ b/app/src/main/jni/kSerialJNI.cpp
@@ -0,0 +1,286 @@
+#include
+
+#include "kSerial.h"
+
+#include
+#define LOG_TAG "KS_LIB"
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+extern "C"
+{
+
+#define MAX_KSERIAL_PACKET_LENS (1024)
+uint8_t kspackbuf[1032] = {0};
+uint32_t kspacketcnt = 0;
+kserial_packet_t kspacket[MAX_KSERIAL_PACKET_LENS] = {0};
+
+JNIEXPORT jbyteArray
+Java_com_kitsprout_ks_KSerial_pack(
+ JNIEnv* env, jclass clazz, jintArray jparam, jint jtype, jint jlens, jdoubleArray jdata) {
+ uint8_t param[2] = {0};
+ uint32_t type = (uint32_t) jtype;
+ uint32_t lens = (uint32_t) jlens;
+ uint32_t packetTotalBytes;
+ int *pkparam = env->GetIntArrayElements(jparam, nullptr);
+ double *pkdata = env->GetDoubleArrayElements(jdata, nullptr);
+
+ param[0] = (uint8_t) pkparam[0];
+ param[1] = (uint8_t) pkparam[1];
+
+ switch (type) {
+ case KS_I8: {
+ int8_t data[1024];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = (int8_t) pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ case KS_I16: {
+ int16_t data[512];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = (int16_t) pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ case KS_I32: {
+ int32_t data[256];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = (int32_t) pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ case KS_I64: {
+ int64_t data[128];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = (int64_t) pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ case KS_R0:
+ case KS_R1:
+ case KS_R2:
+ case KS_R3:
+ case KS_R4:
+ case KS_U8: {
+ uint8_t data[1024];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = (uint8_t) pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ case KS_U16: {
+ uint16_t data[512];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = (uint16_t) pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ case KS_U32: {
+ uint32_t data[256];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = (uint32_t) pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ case KS_U64: {
+ uint64_t data[128];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = (uint64_t) pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+// case KS_F16: {
+// half data[512];
+// for (uint32_t i = 0; i < lens; i++) {
+// data[i] = (half) pkdata[i];
+// }
+// break;
+// }
+ case KS_F32: {
+ float data[256];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = (float) pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ case KS_F64: {
+ double data[128];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = pkdata[i];
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ default: {
+ uint8_t data[1024];
+ for (uint32_t i = 0; i < lens; i++) {
+ data[i] = 0;
+ }
+ packetTotalBytes = kSerial_Pack(kspackbuf, param, type, lens, data);
+ break;
+ }
+ }
+ jbyteArray jpacket = env->NewByteArray(packetTotalBytes);
+ env->SetByteArrayRegion(jpacket, 0, packetTotalBytes, (jbyte*)kspackbuf);
+
+ return jpacket;
+}
+
+JNIEXPORT jint
+Java_com_kitsprout_ks_KSerial_unpackBuffer(
+ JNIEnv* env, jclass clazz, jbyteArray jbuf, jint jlens) {
+ const uint32_t pkbufsize = (uint32_t) jlens;
+ uint8_t *pkbuf = (uint8_t *)env->GetByteArrayElements(jbuf, nullptr);
+ uint32_t newindex = kSerial_UnpackBuffer(pkbuf, pkbufsize, kspacket, &kspacketcnt);
+ return newindex;
+}
+
+JNIEXPORT jint
+Java_com_kitsprout_ks_KSerial_getPacketCount(
+ JNIEnv* env, jclass clazz) {
+ return kspacketcnt;
+}
+
+JNIEXPORT jint
+Java_com_kitsprout_ks_KSerial_getPacketType(
+ JNIEnv* env, jclass clazz, jint index) {
+ return kspacket[index].type;
+}
+
+JNIEXPORT jint
+Java_com_kitsprout_ks_KSerial_getPacketBytes(
+ JNIEnv* env, jclass clazz, jint index) {
+
+ return kspacket[index].nbyte;
+}
+
+JNIEXPORT jintArray
+Java_com_kitsprout_ks_KSerial_getPacketParam(
+ JNIEnv* env, jclass clazz, jint index) {
+ jintArray jparam = env->NewIntArray(2);
+ int param[2];
+ param[0] = kspacket[index].param[0];
+ param[1] = kspacket[index].param[1];
+ env->SetIntArrayRegion(jparam, 0, 2, param);
+ return jparam;
+}
+
+JNIEXPORT jdoubleArray
+Java_com_kitsprout_ks_KSerial_getPacketData(
+ JNIEnv* env, jclass clazz, jint index) {
+
+ jdoubleArray jdata = env->NewDoubleArray(kspacket[index].lens);
+ double data[kspacket[index].lens];
+ switch (kspacket[index].type) {
+ case KS_I8: {
+ int8_t pdata[kspacket[index].lens];
+ kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = (double)pdata[i];
+ }
+ break;
+ }
+ case KS_I16: {
+ int16_t pdata[kspacket[index].lens];
+ kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = (double)pdata[i];
+ }
+ break;
+ }
+ case KS_I32: {
+ int32_t pdata[kspacket[index].lens];
+ kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = (double)pdata[i];
+ }
+ break;
+ }
+ case KS_I64: {
+ int64_t pdata[kspacket[index].lens];
+ kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = (double)pdata[i];
+ }
+ break;
+ }
+ case KS_R0:
+ case KS_R1:
+ case KS_R2:
+ case KS_R3:
+ case KS_R4:
+ case KS_U8: {
+ uint8_t pdata[kspacket[index].lens];
+ kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = (double)pdata[i];
+ }
+ break;
+ }
+ case KS_U16: {
+ uint16_t pdata[kspacket[index].lens];
+ kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = (double)pdata[i];
+ }
+ break;
+ }
+ case KS_U32: {
+ uint32_t pdata[kspacket[index].lens];
+ kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = (double)pdata[i];
+ }
+ break;
+ }
+ case KS_U64: {
+ uint64_t pdata[kspacket[index].lens];
+ kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = (double)pdata[i];
+ }
+ break;
+ }
+// case KS_F16: {
+// float16_t pdata[kspacket[index].lens];
+// for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+// kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+// data[i] = (double)pdata[i];
+// }
+// break;
+// }
+ case KS_F32: {
+ float pdata[kspacket[index].lens];
+ kSerial_GetPacketData(kspacket, pdata, (uint32_t)index);
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = (double)pdata[i];
+ }
+ break;
+ }
+ case KS_F64: {
+ kSerial_GetPacketData(kspacket, data, (uint32_t)index);
+ break;
+ }
+ default: {
+ for (uint32_t i = 0; i < kspacket[index].lens; i++) {
+ data[i] = 0;
+ }
+ break;
+ }
+ }
+ env->SetDoubleArrayRegion(jdata, 0, kspacket[index].lens, data);
+
+ return jdata;
+}
+
+}
diff --git a/app/src/main/jni/kStatus.h b/app/src/main/jni/kStatus.h
new file mode 100644
index 0000000..005fbb7
--- /dev/null
+++ b/app/src/main/jni/kStatus.h
@@ -0,0 +1,74 @@
+/**
+ * __ ____
+ * / /__ _ __ / __/ __
+ * / //_/(_)/ /_ / / ___ ____ ___ __ __ / /_
+ * / ,< / // __/_\ \ / _ \ / __// _ \/ // // __/
+ * /_/|_|/_/ \__//___// .__//_/ \___/\_,_/ \__/
+ * /_/ github.com/KitSprout
+ *
+ * @file kStatus.h
+ * @author KitSprout
+ * @date Mar-2020
+ * @brief
+ *
+ */
+
+/* Define to prevent recursive inclusion ---------------------------------------------------*/
+#ifndef __KSTATUS_H
+#define __KSTATUS_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* Includes --------------------------------------------------------------------------------*/
+/* Define ----------------------------------------------------------------------------------*/
+
+#ifndef NULL
+#ifdef __cplusplus
+#define NULL 0
+#else
+#define NULL ((void *)0)
+#endif
+#endif
+
+#ifndef KSSTATUS
+#define KSSTATUS
+#define KS_OK (0U)
+#define KS_ERROR (1U)
+#define KS_BUSY (2U)
+#define KS_TIMEOUT (3U)
+#endif
+
+#ifndef KSUNIT
+#define KSUNIT
+#define KS_U8 (0x0) /* 4'b 0000 */
+#define KS_U16 (0x1) /* 4'b 0001 */
+#define KS_U32 (0x2) /* 4'b 0010 */
+#define KS_U64 (0x3) /* 4'b 0011 */
+#define KS_I8 (0x4) /* 4'b 0100 */
+#define KS_I16 (0x5) /* 4'b 0101 */
+#define KS_I32 (0x6) /* 4'b 0110 */
+#define KS_I64 (0x7) /* 4'b 0111 */
+#define KS_F16 (0x9) /* 4'b 1001 */
+#define KS_F32 (0xA) /* 4'b 1010 */
+#define KS_F64 (0xB) /* 4'b 1011 */
+#define KS_R0 (0x8) /* 4'b 1000 */
+#define KS_R1 (0xC) /* 4'b 1100 */
+#define KS_R2 (0xD) /* 4'b 1101 */
+#define KS_R3 (0xE) /* 4'b 1110 */
+#define KS_R4 (0xF) /* 4'b 1111 */
+#endif
+
+/* Macro -----------------------------------------------------------------------------------*/
+/* Typedef ---------------------------------------------------------------------------------*/
+/* Extern ----------------------------------------------------------------------------------*/
+/* Functions -------------------------------------------------------------------------------*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/*************************************** END OF FILE ****************************************/
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape.xml b/app/src/main/res/drawable/shape.xml
new file mode 100644
index 0000000..e9186e1
--- /dev/null
+++ b/app/src/main/res/drawable/shape.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..d8ec51b
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..9718a5c
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-ldpi/ic_launcher.png b/app/src/main/res/mipmap-ldpi/ic_launcher.png
new file mode 100644
index 0000000..92e2ddd
Binary files /dev/null and b/app/src/main/res/mipmap-ldpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..469f9e2
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..c5b69b2
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f37b11d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..36a42f7
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..030098f
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #6200EE
+ #3700B3
+ #03DAC5
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..6189de9
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+kSerialBLE
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..09c8bdf
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..a88f623
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,29 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.6.3'
+
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..199d16e
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1240266
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Apr 17 10:36:45 CST 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..c74ba89
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name='kSerialBLE'
+include ':app'