kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Split StreamInterface from SerialInterface, so it can be a TCPInterface baseclass
rodzic
d510576fa8
commit
1ebc710006
|
@ -16,19 +16,19 @@ import com.hoho.android.usbserial.driver.UsbSerialProber
|
||||||
import com.hoho.android.usbserial.util.SerialInputOutputManager
|
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 {
|
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
|
* 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 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
|
* we do that the system will remember we have accesss
|
||||||
*/
|
*/
|
||||||
val assumePermission = true
|
const val assumePermission = true
|
||||||
|
|
||||||
fun toInterfaceName(deviceName: String) = "s$deviceName"
|
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 {
|
init {
|
||||||
val filter = IntentFilter()
|
val filter = IntentFilter()
|
||||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
|
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
|
||||||
|
@ -105,16 +95,15 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
debug("Closing serial port for good")
|
|
||||||
service.unregisterReceiver(usbReceiver)
|
service.unregisterReceiver(usbReceiver)
|
||||||
onDeviceDisconnect(true)
|
super.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tell MeshService our device has gone away, but wait for it to come back
|
/** 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
|
* @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 {
|
ignoreException {
|
||||||
ioManager?.let {
|
ioManager?.let {
|
||||||
debug("USB device disconnected, but it might come back")
|
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 manager = service.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||||
val device = findSerial(service, address)
|
val device = findSerial(service, address)
|
||||||
|
|
||||||
|
@ -175,101 +164,20 @@ class SerialInterface(private val service: RadioInterfaceService, val address: S
|
||||||
thread.name = "serial reader"
|
thread.name = "serial reader"
|
||||||
thread.start() // No need to keep reference to thread around, we quit by asking the ioManager to quit
|
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)
|
// Now tell clients they can (finally use the api)
|
||||||
service.onConnect()
|
super.connect()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errormsg("Can't find device")
|
errormsg("Can't find device")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleSendToRadio(p: ByteArray) {
|
override fun sendBytes(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()
|
|
||||||
ioManager?.apply {
|
ioManager?.apply {
|
||||||
writeAsync(header)
|
|
||||||
writeAsync(p)
|
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.
|
* Called when [SerialInputOutputManager.run] aborts due to an error.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Ładowanie…
Reference in New Issue