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
|
||||
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -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