Add connect, pause, sync

This commit is contained in:
Patrick Moessler 2019-10-25 02:08:13 +02:00
parent 173e6690c2
commit eaa6d72a3e
8 changed files with 339 additions and 79 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -0,0 +1,89 @@
package de.asaril.bletail
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Context
import android.content.Intent
import android.util.Log
import no.nordicsemi.android.ble.callback.SuccessCallback
class BleConnectionHandler(main: MainActivity) {
private var ble: UartManager? = null
private val activity: MainActivity = main
var connected = false
private set
private fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
fun connect(callback: ((Boolean) -> Unit)?) {
ble = UartManager(activity)
ble!!.setGattCallbacks(DefaultUartManagerCallbacks())
val bluetoothManager: BluetoothManager =
activity.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
if (!bluetoothAdapter.isEnabled) {
val enableIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
val requestEnableBt = 1
activity.startActivityForResult(enableIntent, requestEnableBt)
}
bluetoothAdapter.bluetoothLeScanner.startScan(object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
Log.i("BLEtail", "Remote device name: " + result.device.name)
if (result.device.name == "Bluefruit52") {
bluetoothAdapter.bluetoothLeScanner.stopScan(this)
ble!!.connect(result.device)
.retry(3, 100)
.useAutoConnect(true)
.done {
connected = true
if (callback != null) {
callback(connected)
}
}
.fail { _: BluetoothDevice, _: Int ->
connected = false
if (callback != null) {
callback(connected)
}
}
.enqueue()
}
}
})
}
fun disconnect(callback: ((Boolean) -> Unit)?) {
ble!!.disconnect()
.done {
connected = false
if (callback != null) {
callback(connected)
}
}
.enqueue()
}
fun sendToBleUart(msgRaw: ByteArray) {
if (connected) {
val arr: ByteArray = CobsUtils.encode(msgRaw)
Log.i("BLEtail", "tx:" + arr.toHexString())
ble?.send(arr)
} else {
Log.w("BLEtail", "tx failed (not connected):" + msgRaw.toHexString())
}
}
}

View file

@ -1,18 +1,9 @@
package de.asaril.bletail
import CobsUtils
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.text.InputType
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.SeekBar
@ -23,56 +14,46 @@ import kotlinx.android.synthetic.main.activity_main.*
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import android.view.MenuItem
import android.view.View
class MainActivity : AppCompatActivity() {
private val LED_COUNT: Int = 40
private var ble: UartManager? = null
private var ble: BleConnectionHandler? = null
private var wasConnected = false
private var segments: MutableList<SegmentCardView>? = null
private var paused = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
ble = BleConnectionHandler(this)
segments = MutableList<SegmentCardView>(5, init = {
SegmentCardView(this, it, 0, LED_COUNT - 1, Fx.Mode.THEATER_CHASE_RAINBOW, Color(), Color(), Color(), 0x0800, false)
})
for (s in segments!!) {
s.sendCallback = this::sendToBleUart
s.sendCallback = { bytes: ByteArray -> ble!!.sendToBleUart(bytes) }
}
ble = UartManager(this)
ble!!.setGattCallbacks(DefaultUartManagerCallbacks())
val bluetoothManager: BluetoothManager =
getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
if (!bluetoothAdapter.isEnabled) {
val enableIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
val requestEnableBt = 1
startActivityForResult(enableIntent, requestEnableBt)
}
bluetoothAdapter.bluetoothLeScanner.startScan(object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
Log.i("BLEtail", "Remote device name: " + result.device.name)
if (result.device.name == "Bluefruit52") {
bluetoothAdapter.bluetoothLeScanner.stopScan(this)
ble!!.connect(result.device)
.retry(3, 100)
.useAutoConnect(true)
.enqueue()
ble!!.connect { connected: Boolean ->
if (connected) {
val storedState = File(getExternalFilesDir(null)!!, "_stored.json")
val loaded = storedState.exists() and loadFile(storedState)
if (!loaded) {
val defaultFile = File(getExternalFilesDir(null)!!, "default.json")
if (defaultFile.exists()) {
loadFile(defaultFile)
}
}
}
})
updateConnection(connected)
}
brightness!!.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(var1: SeekBar, var2: Int, var3: Boolean) {
@ -90,15 +71,49 @@ class MainActivity : AppCompatActivity() {
}
override fun onResume() {
if (wasConnected) {
ble?.connect { c -> updateConnection(c) }
}
super.onResume()
}
override fun onPause() {
saveFile("_stored")
super.onPause()
}
override fun onStop() {
wasConnected = (ble?.connected) ?: false
ble?.disconnect { c -> updateConnection(c) }
super.onStop()
}
override fun onDestroy() {
segments = null
ble!!.disconnect()
ble!!.disconnect { c -> updateConnection(c) }
val storedState = File(getExternalFilesDir(null)!!, "_stored.json")
if (storedState.exists()) {
storedState.deleteOnExit()
}
super.onDestroy()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main_menu, menu)
val connected = (ble != null) and (ble!!.connected)
menu.findItem(R.id.action_connection).apply {
if (connected) {
icon = getDrawable(R.drawable.ic_bluetooth_connected_black_24dp)
title = getString(R.string.disconnect)
} else {
icon = getDrawable(R.drawable.ic_bluetooth_disabled_black_24dp)
title = getString(R.string.connect)
}
}
return true
}
@ -107,28 +122,30 @@ class MainActivity : AppCompatActivity() {
val m = Fx.setNumSegments_msg.newBuilder()
m.numSegments = count
r.setNumSegments = m.build()
sendToBleUart(r.build().toByteArray())
if (ble != null) {
ble!!.sendToBleUart(r.build().toByteArray())
val segLength: Int = LED_COUNT / count
val segmentListLayout = findViewById<LinearLayout>(R.id.segmentList)
val segLength: Int = LED_COUNT / count
val segmentListLayout = findViewById<LinearLayout>(R.id.segmentList)
for (i in 0 until count) {
segments!![i].start = segLength * i
segments!![i].end = if (i == count - 1) {
LED_COUNT - 1
} else {
(segLength * (i + 1) - 1)
for (i in 0 until count) {
segments!![i].start = segLength * i
segments!![i].end = if (i == count - 1) {
LED_COUNT - 1
} else {
(segLength * (i + 1) - 1)
}
segments!![i].sendSetSegment()
}
segments!![i].sendSetSegment()
}
while (segmentListLayout.childCount > count) {
val ind = segmentListLayout.childCount - 1
segmentListLayout.removeViewAt(ind)
}
while (segmentListLayout.childCount < count) {
val ind = segmentListLayout.childCount
segmentListLayout.addView(segments!![ind], ind)
while (segmentListLayout.childCount > count) {
val ind = segmentListLayout.childCount - 1
segmentListLayout.removeViewAt(ind)
}
while (segmentListLayout.childCount < count) {
val ind = segmentListLayout.childCount
segmentListLayout.addView(segments!![ind], ind)
}
}
}
@ -141,8 +158,9 @@ class MainActivity : AppCompatActivity() {
val r: Fx.Root.Builder = Fx.Root.newBuilder()
r.halt = Fx.halt_msg.newBuilder().build()
val msgRaw = r.build().toByteArray()
sendToBleUart(msgRaw)
ble!!.disconnect()
ble?.sendToBleUart(msgRaw)
ble?.disconnect(null)
finish()
}
}
builder.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() }
@ -154,7 +172,7 @@ class MainActivity : AppCompatActivity() {
val m = Fx.setBrightness_msg.newBuilder()
m.brightness = value
r.setBrightness = m.build()
sendToBleUart(r.build().toByteArray())
ble?.sendToBleUart(r.build().toByteArray())
}
fun segCountClick(item: MenuItem) {
@ -169,13 +187,6 @@ class MainActivity : AppCompatActivity() {
)
}
fun sendToBleUart(msgRaw: ByteArray) {
val arr: ByteArray = CobsUtils.encode(msgRaw)
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
Log.i("BLEtail", "tx:" + arr.toHexString())
ble?.send(arr)
}
fun serialize(): JSONObject {
val j = JSONObject()
@ -189,21 +200,32 @@ class MainActivity : AppCompatActivity() {
return j
}
fun deserialize(j: JSONObject) {
fun deserialize(j: JSONObject): Boolean {
brightness.progress = j.getInt("brightness")
val jsegs = j.getJSONArray("segments")
updateSegmentCards(jsegs.length())
for (i in 0 until jsegs.length()) {
val segNum = jsegs.length()
if (segNum < 1) {
return false
}
updateSegmentCards(segNum)
for (i in 0 until segNum) {
val jseg = jsegs[i] as JSONObject
val idx = jseg.getInt("index")
assert(idx < segments!!.size)
segments!![idx].deserialize(jseg)
}
return true
}
private fun loadFile(file: File) {
private fun loadFile(file: File): Boolean {
val j = JSONObject(file.readText())
deserialize(j)
val loaded = deserialize(j)
if ((!loaded) and (file.name == "_stored.json")) {
// remove broken stored state
file.delete()
return false
}
return loaded
}
private fun saveFile(filename: String) {
@ -240,5 +262,99 @@ class MainActivity : AppCompatActivity() {
builder.show()
}
fun pauseClick(item: MenuItem) {
if ((ble != null) && (ble!!.connected)) {
this.paused = !this.paused
sendPause(item)
}
}
private fun sendPause(item: MenuItem) {
val r = Fx.Root.newBuilder()
if (paused) {
val m = Fx.pause_msg.newBuilder()
r.pause = m.build()
item.icon = getDrawable(android.R.drawable.ic_media_play)
item.title = getString(R.string.resume)
} else {
val m = Fx.resume_msg.newBuilder()
r.resume = m.build()
item.icon = getDrawable(android.R.drawable.ic_media_pause)
item.title = getString(R.string.pause)
}
ble?.sendToBleUart(r.build().toByteArray())
}
fun updateConnection(connected: Boolean) {
(findViewById<View?>(R.id.action_connection) as MenuItem?)?.apply {
if (connected) {
icon = getDrawable(R.drawable.ic_bluetooth_connected_black_24dp)
title = getString(R.string.disconnect)
} else {
icon = getDrawable(R.drawable.ic_bluetooth_disabled_black_24dp)
title = getString(R.string.connect)
}
}
}
fun connectionClick(item: MenuItem) {
val callback = { connected: Boolean ->
item.apply {
if (connected) {
icon = getDrawable(R.drawable.ic_bluetooth_connected_black_24dp)
title = getString(R.string.disconnect)
} else {
icon = getDrawable(R.drawable.ic_bluetooth_disabled_black_24dp)
title = getString(R.string.connect)
}
}
Unit
}
if (ble != null) {
if (ble!!.connected) {
ble!!.disconnect(callback)
} else {
ble!!.connect(callback)
}
}
}
fun stopClick(item: MenuItem) {
if ((ble != null) && (ble!!.connected)) {
paused = true
sendPause(item)
val r = Fx.Root.newBuilder()
val m = Fx.strip_off_msg.newBuilder()
r.stripOff = m.build()
ble?.sendToBleUart(r.build().toByteArray())
}
val storedState = File(getExternalFilesDir(null)!!, "_stored.json")
if (storedState.exists()) {
storedState.deleteOnExit()
}
}
fun syncClick(item: MenuItem) {
if ((ble != null) && (ble!!.connected) && (segments != null)) {
var r = Fx.Root.newBuilder()
val m = Fx.stop_msg.newBuilder()
r.stop = m.build()
ble?.sendToBleUart(r.build().toByteArray())
for (i in 0 until segments!!.size) {
segments!![i].speed = segments!![0].speed
segments!![i].updateView()
}
r = Fx.Root.newBuilder()
val m2 = Fx.start_msg.newBuilder()
r.start = m2.build()
ble?.sendToBleUart(r.build().toByteArray())
}
}
}

View file

@ -266,17 +266,22 @@ class SegmentCardView : CardView {
assert(this.index == j.getInt("index"))
this.start = j.getInt("start")
this.end = j.getInt("end")
this.mode = Fx.Mode.forNumber(j.getInt("mode"))
val jcolors = j.getJSONArray("colors")
this.colors[0] = Color.valueOf(jcolors[0] as Int)
this.colors[1] = Color.valueOf(jcolors[1] as Int)
this.colors[2] = Color.valueOf(jcolors[2] as Int)
this.speed = j.getInt("speed")
this.reverse = j.getBoolean("reverse")
// set mode last as this will trigger setSegment
this.mode = Fx.Mode.forNumber(j.getInt("mode"))
updateView()
sendSetSegment()
}
private fun updateView() {
fun updateView() {
viewCol0!!.setColorFilter(colors[0].toArgb())
viewCol1!!.setColorFilter(colors[1].toArgb())
viewCol2!!.setColorFilter(colors[2].toArgb())

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M7,12l-2,-2 -2,2 2,2 2,-2zM17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88zM19,10l-2,2 2,2 2,-2 -2,-2z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M13,5.83l1.88,1.88 -1.6,1.6 1.41,1.41 3.02,-3.02L12,2h-1v5.03l2,2v-3.2zM5.41,4L4,5.41 10.59,12 5,17.59 6.41,19 11,14.41V22h1l4.29,-4.29 2.3,2.29L20,18.59 5.41,4zM13,18.17v-3.76l1.88,1.88L13,18.17z"/>
</vector>

View file

@ -9,14 +9,28 @@
android:iconTint="@android:color/secondary_text_dark"
android:title="@string/load"
android:onClick="loadBtnClick"
app:showAsAction="ifRoom" />
app:showAsAction="always" />
<item
android:id="@+id/saveBtn"
android:title="@string/save"
android:icon="@android:drawable/ic_menu_save"
android:iconTint="@android:color/primary_text_dark"
android:onClick="saveBtnClick"
app:showAsAction="ifRoom"/>
app:showAsAction="always"/>
<item
android:id="@+id/action_pause"
android:icon="@android:drawable/ic_media_pause"
android:onClick="pauseClick"
android:title="@string/pause"
app:showAsAction="always" />
<item
android:id="@+id/action_connection"
android:icon="@drawable/ic_bluetooth_disabled_black_24dp"
android:onClick="connectionClick"
android:title="@string/connect"
app:showAsAction="ifRoom" />
<item
android:id="@+id/segCountMenu"
@ -48,8 +62,20 @@
</menu>
</item>
<item
android:id="@+id/action_halt"
android:id="@+id/action_sync"
android:icon="@android:drawable/ic_popup_sync"
android:onClick="syncClick"
android:title="@string/action_sync"
app:showAsAction="collapseActionView" />
<item
android:id="@+id/action_stop"
android:icon="@android:drawable/ic_lock_power_off"
android:onClick="stopClick"
android:title="@string/action_stop"
app:showAsAction="collapseActionView" />
<item
android:id="@+id/action_halt"
android:icon="@android:drawable/ic_delete"
android:onClick="powerOffClick"
android:title="@string/action_halt"
app:showAsAction="collapseActionView" />

View file

@ -1,6 +1,6 @@
<resources>
<string name="app_name">BLEtail</string>
<string name="action_halt">Halt</string>
<string name="action_halt">Shutdown</string>
<string name="action_settings">Settings</string>
<string name="seg3">3</string>
<string name="seg5">5</string>
@ -15,4 +15,10 @@
<string name="brightness">Brightness</string>
<string name="load">Load</string>
<string name="save">Save</string>
<string name="connect">Connect</string>
<string name="disconnect">Disconnect</string>
<string name="pause">Pause</string>
<string name="resume">Resume</string>
<string name="action_stop">Stop (Off)</string>
<string name="action_sync">Sync</string>
</resources>