From 1ebc710006853621417bcac24e3970e153521570 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 29 Mar 2021 19:10:52 +0800 Subject: [PATCH] Split StreamInterface from SerialInterface, so it can be a TCPInterface baseclass --- .../mesh/service/SerialInterface.kt | 116 ++------------- .../mesh/service/StreamInterface.kt | 132 ++++++++++++++++++ 2 files changed, 144 insertions(+), 104 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/service/StreamInterface.kt diff --git a/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt b/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt index 00b80252..877ddb5a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt @@ -16,19 +16,19 @@ import com.hoho.android.usbserial.driver.UsbSerialProber import com.hoho.android.usbserial.util.SerialInputOutputManager -class SerialInterface(private val service: RadioInterfaceService, val address: String) : Logging, - IRadioInterface, SerialInputOutputManager.Listener { +/** + * An interface that assumes we are talking to a meshtastic device via USB serial + */ +class SerialInterface(service: RadioInterfaceService, address: String) : + StreamInterface(service, address), Logging, SerialInputOutputManager.Listener { companion object : Logging { - private const val START1 = 0x94.toByte() - private const val START2 = 0xc3.toByte() - private const val MAX_TO_FROM_RADIO_SIZE = 512 /** * according to https://stackoverflow.com/questions/12388914/usb-device-access-pop-up-suppression/15151075#15151075 * we should never ask for USB permissions ourselves, instead we should rely on the external dialog printed by the system. If * we do that the system will remember we have accesss */ - val assumePermission = true + const val assumePermission = true fun toInterfaceName(deviceName: String) = "s$deviceName" @@ -85,16 +85,6 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S } } - private val debugLineBuf = kotlin.text.StringBuilder() - - /** The index of the next byte we are hoping to receive */ - private var ptr = 0 - - /** The two halves of our length */ - private var msb = 0 - private var lsb = 0 - private var packetLen = 0 - init { val filter = IntentFilter() filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED) @@ -105,16 +95,15 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S } override fun close() { - debug("Closing serial port for good") service.unregisterReceiver(usbReceiver) - onDeviceDisconnect(true) + super.close() } /** Tell MeshService our device has gone away, but wait for it to come back * * @param waitForStopped if true we should wait for the manager to finish - must be false if called from inside the manager callbacks * */ - fun onDeviceDisconnect(waitForStopped: Boolean) { + override fun onDeviceDisconnect(waitForStopped: Boolean) { ignoreException { ioManager?.let { debug("USB device disconnected, but it might come back") @@ -143,10 +132,10 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S } } - service.onDisconnect(isPermanent = true) // if USB device disconnects it is definitely permantently gone, not sleeping) + super.onDeviceDisconnect(waitForStopped) } - private fun connect() { + override fun connect() { val manager = service.getSystemService(Context.USB_SERVICE) as UsbManager val device = findSerial(service, address) @@ -175,101 +164,20 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S thread.name = "serial reader" thread.start() // No need to keep reference to thread around, we quit by asking the ioManager to quit - // Before telling mesh service, send a few START1s to wake a sleeping device - val wakeBytes = byteArrayOf(START1, START1, START1, START1) - io.writeAsync(wakeBytes) - // Now tell clients they can (finally use the api) - service.onConnect() + super.connect() } } else { errormsg("Can't find device") } } - override fun handleSendToRadio(p: ByteArray) { - // This method is called from a continuation and it might show up late, so check for uart being null - - val header = ByteArray(4) - header[0] = START1 - header[1] = START2 - header[2] = (p.size shr 8).toByte() - header[3] = (p.size and 0xff).toByte() + override fun sendBytes(p: ByteArray) { ioManager?.apply { - writeAsync(header) writeAsync(p) } } - - /** Print device serial debug output somewhere */ - private fun debugOut(b: Byte) { - when (val c = b.toChar()) { - '\r' -> { - } // ignore - '\n' -> { - debug("DeviceLog: $debugLineBuf") - debugLineBuf.clear() - } - else -> - debugLineBuf.append(c) - } - } - - private val rxPacket = ByteArray(MAX_TO_FROM_RADIO_SIZE) - - private fun readChar(c: Byte) { - // Assume we will be advancing our pointer - var nextPtr = ptr + 1 - - fun lostSync() { - errormsg("Lost protocol sync") - nextPtr = 0 - } - - /// Deliver our current packet and restart our reader - fun deliverPacket() { - val buf = rxPacket.copyOf(packetLen) - service.handleFromRadio(buf) - - nextPtr = 0 // Start parsing the next packet - } - - when (ptr) { - 0 -> // looking for START1 - if (c != START1) { - debugOut(c) - nextPtr = 0 // Restart from scratch - } - 1 -> // Looking for START2 - if (c != START2) - lostSync() // Restart from scratch - 2 -> // Looking for MSB of our 16 bit length - msb = c.toInt() and 0xff - 3 -> { // Looking for LSB of our 16 bit length - lsb = c.toInt() and 0xff - - // We've read our header, do one big read for the packet itself - packetLen = (msb shl 8) or lsb - if (packetLen > MAX_TO_FROM_RADIO_SIZE) - lostSync() // If packet len is too long, the bytes must have been corrupted, start looking for START1 again - else if (packetLen == 0) - deliverPacket() // zero length packets are valid and should be delivered immediately (because there won't be a next byte of payload) - } - else -> { - // We are looking at the packet bytes now - rxPacket[ptr - 4] = c - - // Note: we have to check if ptr +1 is equal to packet length (for example, for a 1 byte packetlen, this code will be run with ptr of4 - if (ptr - 4 + 1 == packetLen) { - deliverPacket() - } - } - } - ptr = nextPtr - } - - /** * Called when [SerialInputOutputManager.run] aborts due to an error. */ diff --git a/app/src/main/java/com/geeksville/mesh/service/StreamInterface.kt b/app/src/main/java/com/geeksville/mesh/service/StreamInterface.kt new file mode 100644 index 00000000..ad0f3dcf --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/service/StreamInterface.kt @@ -0,0 +1,132 @@ +package com.geeksville.mesh.service + +import com.geeksville.android.Logging + + +/** + * An interface that assumes we are talking to a meshtastic device over some sort of stream connection (serial or TCP probably) + */ +abstract class StreamInterface(protected val service: RadioInterfaceService, val address: String) : + Logging, + IRadioInterface { + companion object : Logging { + private const val START1 = 0x94.toByte() + private const val START2 = 0xc3.toByte() + private const val MAX_TO_FROM_RADIO_SIZE = 512 + } + + private val debugLineBuf = kotlin.text.StringBuilder() + + /** The index of the next byte we are hoping to receive */ + private var ptr = 0 + + /** The two halves of our length */ + private var msb = 0 + private var lsb = 0 + private var packetLen = 0 + + override fun close() { + debug("Closing stream for good") + onDeviceDisconnect(true) + } + + /** Tell MeshService our device has gone away, but wait for it to come back + * + * @param waitForStopped if true we should wait for the manager to finish - must be false if called from inside the manager callbacks + * */ + protected open fun onDeviceDisconnect(waitForStopped: Boolean) { + service.onDisconnect(isPermanent = true) // if USB device disconnects it is definitely permantently gone, not sleeping) + } + + protected open fun connect() { + // Before telling mesh service, send a few START1s to wake a sleeping device + val wakeBytes = byteArrayOf(START1, START1, START1, START1) + sendBytes(wakeBytes) + + // Now tell clients they can (finally use the api) + service.onConnect() + } + + abstract fun sendBytes(p: ByteArray) + + override fun handleSendToRadio(p: ByteArray) { + // This method is called from a continuation and it might show up late, so check for uart being null + + val header = ByteArray(4) + header[0] = START1 + header[1] = START2 + header[2] = (p.size shr 8).toByte() + header[3] = (p.size and 0xff).toByte() + + sendBytes(header) + sendBytes(p) + } + + + /** Print device serial debug output somewhere */ + private fun debugOut(b: Byte) { + when (val c = b.toChar()) { + '\r' -> { + } // ignore + '\n' -> { + debug("DeviceLog: $debugLineBuf") + debugLineBuf.clear() + } + else -> + debugLineBuf.append(c) + } + } + + private val rxPacket = ByteArray(MAX_TO_FROM_RADIO_SIZE) + + protected fun readChar(c: Byte) { + // Assume we will be advancing our pointer + var nextPtr = ptr + 1 + + fun lostSync() { + errormsg("Lost protocol sync") + nextPtr = 0 + } + + /// Deliver our current packet and restart our reader + fun deliverPacket() { + val buf = rxPacket.copyOf(packetLen) + service.handleFromRadio(buf) + + nextPtr = 0 // Start parsing the next packet + } + + when (ptr) { + 0 -> // looking for START1 + if (c != START1) { + debugOut(c) + nextPtr = 0 // Restart from scratch + } + 1 -> // Looking for START2 + if (c != START2) + lostSync() // Restart from scratch + 2 -> // Looking for MSB of our 16 bit length + msb = c.toInt() and 0xff + 3 -> { // Looking for LSB of our 16 bit length + lsb = c.toInt() and 0xff + + // We've read our header, do one big read for the packet itself + packetLen = (msb shl 8) or lsb + if (packetLen > MAX_TO_FROM_RADIO_SIZE) + lostSync() // If packet len is too long, the bytes must have been corrupted, start looking for START1 again + else if (packetLen == 0) + deliverPacket() // zero length packets are valid and should be delivered immediately (because there won't be a next byte of payload) + } + else -> { + // We are looking at the packet bytes now + rxPacket[ptr - 4] = c + + // Note: we have to check if ptr +1 is equal to packet length (for example, for a 1 byte packetlen, this code will be run with ptr of4 + if (ptr - 4 + 1 == packetLen) { + deliverPacket() + } + } + } + ptr = nextPtr + } +} \ No newline at end of file