Intergating new protocol
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@ class WorkOrderApi(
|
||||
|
||||
suspend fun fetchAbleWorkOrders(woID: String, action: String): List<WorkOrderDto> {
|
||||
return client.get("$baseUrl/${Endpoints.ABLE_WO}") {
|
||||
parameter("woID", woID)
|
||||
parameter("wo_id", woID)
|
||||
parameter("action", action)
|
||||
}.body()
|
||||
}
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ class BleManager {
|
||||
}
|
||||
suspend fun sendCommand(peripheral: Peripheral, command: String) {
|
||||
Napier.d(">>>>> Sent to uart: $command")
|
||||
val cmd = command.replace("\\r\\n", "\r\n")
|
||||
val cmd = command.replace("\\n", "\n")
|
||||
peripheral.write(RX_CHAR, cmd.encodeToByteArray())
|
||||
}
|
||||
fun observeRx(peripheral: Peripheral): Flow<String> =
|
||||
|
||||
+48
-33
@@ -171,11 +171,12 @@ class ScannerViewModel(
|
||||
|
||||
bleManager.scanDevices()
|
||||
.catch {
|
||||
Napier.d(">>>>> bleManager.scanDevices catch block")
|
||||
_isScanning.value = false
|
||||
stopCleanupJob()
|
||||
}
|
||||
.collect { advertisement ->
|
||||
if (advertisement.name == null) return@collect
|
||||
// if (advertisement.name == null) return@collect
|
||||
val bleId: String = advertisement.identifier.toString()
|
||||
if(_autoConnect.value && isBonded(bleId)){
|
||||
connect(advertisement)
|
||||
@@ -285,9 +286,10 @@ class ScannerViewModel(
|
||||
fun sendCommand(peripheral: Peripheral, command: String) {
|
||||
val deviceId = peripheral.identifier.toString()
|
||||
viewModelScope.launch {
|
||||
bleManager.sendCommand(peripheral, Helper.buildUartCommand(command))
|
||||
appendHistory(deviceId, "Sent: $command")
|
||||
logRepository.appendLog(title = "Communicate - Sent", content = command, deviceId)
|
||||
val data = Helper.buildUartCommand(command)
|
||||
bleManager.sendCommand(peripheral, data)
|
||||
appendHistory(deviceId, "Sent: $data")
|
||||
logRepository.appendLog(title = "Communicate - Sent", content = data, deviceId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +363,7 @@ class ScannerViewModel(
|
||||
_connectedDevices.value -= deviceIdentifier
|
||||
}
|
||||
}
|
||||
fun fetchWorkOrders(peripheral: Peripheral, woID: String = "0", action: String = "next") {
|
||||
private fun fetchWorkOrders(peripheral: Peripheral, woID: String = "0", action: String = "next") {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val orders = scannerRepository.getAbleWorkOrders(woID = woID, action = action)
|
||||
@@ -379,52 +381,46 @@ class ScannerViewModel(
|
||||
sendCommand(peripheral, Helper.CMD_EMPTY_WO)
|
||||
return@launch
|
||||
}
|
||||
val formatted = workOrders.joinToString(separator = "\r\n") { wo ->
|
||||
val formatted = workOrders.joinToString(separator = "\n") { wo ->
|
||||
val torque = wo.services?.values?.firstOrNull()?.torqueRequired ?: 0.0
|
||||
val actionCode = if (wo.status.lowercase() == "open") 0 else 1
|
||||
val wheelsMask = wo.computeWheelsNuts()
|
||||
"$${wo.id},${actionCode},${wo.make},${wo.licensePlate},$torque,ft-lb,$wheelsMask*"
|
||||
"${Helper.WO_RES};${wo.id};${actionCode};${wo.make};${wo.licensePlate};$torque;ft-lb;$wheelsMask"
|
||||
}
|
||||
|
||||
sendCommand(peripheral, formatted)
|
||||
}
|
||||
}
|
||||
private fun handleMessage(peripheral: Peripheral, msg: String, deviceIdentifier: String) {
|
||||
appendHistory(deviceIdentifier, "Received: $msg")
|
||||
logRepository.appendLog(title = "Communicate - Received", content = msg, deviceId = deviceIdentifier)
|
||||
val decoded = Helper.decodeRxData(msg) ?: return
|
||||
val data = decoded.data
|
||||
val woId = decoded.id
|
||||
val cmd = data.take(4)
|
||||
val action = if (cmd == "back") "back" else "next"
|
||||
private fun acceptWorkOrder(peripheral: Peripheral, type: Int, woId: String, deviceIdentifier: String) {
|
||||
viewModelScope.launch {
|
||||
when (cmd) {
|
||||
"requ", "next", "back" -> fetchWorkOrders(peripheral, woId, action)
|
||||
"conf" -> {
|
||||
var command = Helper.CMD_RES_OK(Helper.Command.ACCEPT_WO.name)
|
||||
try {
|
||||
val res = scannerRepository.confirmWorkOrder(woId, deviceIdentifier, 0)
|
||||
var command = Helper.CMD_NOT_AVAILABLE_WO
|
||||
val res = scannerRepository.confirmWorkOrder(woId, deviceIdentifier, type)
|
||||
if (res)
|
||||
command = Helper.CMD_RECEIVED_WO
|
||||
command = Helper.CMD_RES_OK(Helper.Command.ACCEPT_WO.name)
|
||||
sendCommand(peripheral, command)
|
||||
} catch (e: Exception) {
|
||||
Napier.e(">>>>> Failed to handle message: $msg", e)
|
||||
sendCommand(peripheral, Helper.CMD_NOT_AVAILABLE_WO)
|
||||
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
|
||||
Napier.e(">>>>> Failed to handle message data", e)
|
||||
sendCommand(peripheral, command)
|
||||
logRepository.appendLog(
|
||||
title = "HTTP",
|
||||
content = "${e.message}".substringBefore("[url=")
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
/** Send torque, re-torque or cancel case */
|
||||
val bleData = data.trim(' ').trimEnd('*').split(",")
|
||||
}
|
||||
private fun finishWorkOrder(peripheral: Peripheral, type: Int, woId: String, data: String, deviceIdentifier: String) {
|
||||
val bleData = data.trim(' ').split(";")
|
||||
val nuts: Int = bleData[Helper.BLE_NUTS_POS].toInt()
|
||||
val torqueData: List<String> = bleData.subList(Helper.BLE_TORQUE_POS, bleData.size)
|
||||
val wheelTorqueData = Helper.parseWheelTorqueData(torqueData, nuts)
|
||||
val dto = SendMeasureDto(
|
||||
deviceId = deviceIdentifier,
|
||||
type = 0, // tbu
|
||||
type = type,
|
||||
nuts = nuts,
|
||||
torqueData = wheelTorqueData
|
||||
)
|
||||
Napier.d(">>>>> DTO $dto")
|
||||
viewModelScope.launch {
|
||||
when (val act = bleData.firstOrNull()) {
|
||||
Helper.TorqueAction.TORQUE -> {
|
||||
try {
|
||||
@@ -434,7 +430,7 @@ class ScannerViewModel(
|
||||
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
|
||||
} finally {
|
||||
// Currently, always send UPLOADED_WO regardless of success or failure
|
||||
sendCommand(peripheral, Helper.CMD_UPLOADED_WO)
|
||||
sendCommand(peripheral, Helper.CMD_RES_OK(Helper.Command.FINISH_WO.name))
|
||||
}
|
||||
}
|
||||
Helper.TorqueAction.RE_TORQUE -> {
|
||||
@@ -445,7 +441,7 @@ class ScannerViewModel(
|
||||
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
|
||||
} finally {
|
||||
// Currently, always send UPLOADED_WO regardless of success or failure
|
||||
sendCommand(peripheral, Helper.CMD_UPLOADED_WO)
|
||||
sendCommand(peripheral, Helper.CMD_RES_OK(Helper.Command.FINISH_WO.name))
|
||||
}
|
||||
}
|
||||
Helper.TorqueAction.CANCEL -> {
|
||||
@@ -456,16 +452,35 @@ class ScannerViewModel(
|
||||
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
|
||||
} finally {
|
||||
// Currently, always send UPLOADED_WO regardless of success or failure
|
||||
sendCommand(peripheral, Helper.CMD_UPLOADED_WO)
|
||||
sendCommand(peripheral, Helper.CMD_RES_OK(Helper.Command.FINISH_WO.name))
|
||||
}
|
||||
}
|
||||
else -> Napier.w(">>>>> Unknown action: $act")
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun handleMessage(peripheral: Peripheral, msg: String, deviceIdentifier: String) {
|
||||
appendHistory(deviceIdentifier, "Received: $msg")
|
||||
logRepository.appendLog(title = "Communicate - Received", content = msg, deviceId = deviceIdentifier)
|
||||
val decoded = Helper.decodeRxData(msg) ?: return
|
||||
Napier.d(">>>>> decoded rx message: $decoded")
|
||||
val cmd = Helper.Command.fromString(decoded.command)
|
||||
val type = Helper.TypeOfDevice.fromString(decoded.type)
|
||||
if(type != null) {
|
||||
val woId = decoded.woId
|
||||
val data = decoded.data
|
||||
when (cmd) {
|
||||
Helper.Command.START_WO -> fetchWorkOrders(peripheral)
|
||||
Helper.Command.NEXT_WO -> fetchWorkOrders(peripheral, woId, "next")
|
||||
Helper.Command.PREVIOUS_WO -> fetchWorkOrders(peripheral, woId, "back")
|
||||
Helper.Command.ACCEPT_WO -> acceptWorkOrder(peripheral, type.code, woId, deviceIdentifier)
|
||||
Helper.Command.FINISH_WO -> {
|
||||
finishWorkOrder(peripheral, type.code, woId, data, deviceIdentifier)
|
||||
}
|
||||
null -> Napier.w("Unknown command: $cmd")
|
||||
}
|
||||
} else Napier.w("Unknown type: $type")
|
||||
}
|
||||
|
||||
fun addBondedDevice(id: String, name: String) {
|
||||
viewModelScope.launch {
|
||||
deviceRepository.saveDevice(id, name)
|
||||
|
||||
+58
-17
@@ -1,37 +1,78 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.utils
|
||||
|
||||
object Helper {
|
||||
const val CMD_EMPTY_WO = $$"$e*\r\n"
|
||||
const val CMD_RECEIVED_WO = $$"$r*\r\n"
|
||||
const val CMD_NOT_AVAILABLE_WO = $$"$n*\r\n"
|
||||
const val CMD_UPLOADED_WO = $$"$u*\r\n"
|
||||
const val END_LINE_FEED = "#\r\n"
|
||||
const val START_CHARACTER ="$"
|
||||
const val END_LINE_FEED = "#\n"
|
||||
const val SECURITY_CRC16 = "00cr16"
|
||||
const val CMD_EMPTY_WO = "START_WO;empty\n$SECURITY_CRC16"
|
||||
const val WO_RES = "WO_RES"
|
||||
val CMD_RES_OK: (String) -> String = { cmd -> "$cmd;OK\n$SECURITY_CRC16" }
|
||||
val CMD_RES_NG: (String) -> String = { cmd -> "$cmd;NG\n$SECURITY_CRC16" }
|
||||
|
||||
const val BLE_NUTS_POS = 4
|
||||
const val BLE_NUTS_POS = 2
|
||||
const val BLE_TORQUE_POS = BLE_NUTS_POS + 1
|
||||
enum class Command {
|
||||
START_WO,
|
||||
NEXT_WO,
|
||||
PREVIOUS_WO,
|
||||
ACCEPT_WO,
|
||||
FINISH_WO;
|
||||
companion object {
|
||||
fun fromString(code: String): Command? =
|
||||
try { valueOf(code) } catch (e: IllegalArgumentException) { null }
|
||||
}
|
||||
}
|
||||
enum class TypeOfDevice(val code: Int) {
|
||||
WHEEL(0),
|
||||
OIL_FILTER(1),
|
||||
DRAIN_PLUG(2);
|
||||
|
||||
companion object {
|
||||
fun fromString(type: String): TypeOfDevice? = when (type) {
|
||||
"wheel" -> WHEEL
|
||||
"oil_filter" -> OIL_FILTER
|
||||
"drain_plug" -> DRAIN_PLUG
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
object TorqueAction {
|
||||
const val TORQUE = "0"
|
||||
const val RE_TORQUE = "1"
|
||||
const val CANCEL = "2"
|
||||
}
|
||||
data class RxMessage(
|
||||
val id: String,
|
||||
val data: String
|
||||
val command: String,
|
||||
val type: String, // Type of device
|
||||
val woId: String = "0",
|
||||
val data: String,
|
||||
val securityCode: String,
|
||||
val crc16: String
|
||||
)
|
||||
|
||||
fun buildUartCommand(cmd: String): String {
|
||||
return "$cmd$END_LINE_FEED"
|
||||
return "$START_CHARACTER$cmd$END_LINE_FEED"
|
||||
}
|
||||
|
||||
fun decodeRxData(msg: String): RxMessage? {
|
||||
if (!msg.startsWith("$")) return null
|
||||
val payload = msg.drop(1).trimEnd('*', ' ', '\r', '\n')
|
||||
val parts = payload.split(",", limit = 2)
|
||||
/** Check header and footer*/
|
||||
if (!msg.startsWith("$") && !msg.endsWith("#\n")) return null
|
||||
/** Remove $ and trim end */
|
||||
val payload = msg.drop(1).trimEnd('*', '#',' ', '\r', '\n')
|
||||
val parts = payload.split(";", limit = 3)
|
||||
if(parts.size < 2) return null
|
||||
val woID = parts[0]
|
||||
val data = parts[1]
|
||||
return RxMessage(woID, data)
|
||||
val cmd = parts.first()
|
||||
var woId = "0"
|
||||
var data = ""
|
||||
val type = parts[1].substringBefore("\n")
|
||||
/** Tail include security code and crc16 */
|
||||
val tail = parts.last().substringAfterLast("\n")
|
||||
val security = tail.take(2)
|
||||
val crc16 = tail.drop(2).take(4)
|
||||
if(parts.size == 3) {
|
||||
val payloadParts = parts.last().substringBeforeLast("\n").split(";")
|
||||
woId = payloadParts.firstOrNull() ?: "0"
|
||||
data = payloadParts.drop(1).joinToString(";") // exclude woId
|
||||
}
|
||||
return RxMessage(cmd, type, woId, data, security, crc16)
|
||||
}
|
||||
|
||||
fun parseWheelTorqueData(torqueData: List<String>, nutsPerWheel: Int): Map<String, List<Double>> {
|
||||
|
||||
+4
@@ -2,6 +2,7 @@ package com.digitoolsolutions.app.torquevaultkmp
|
||||
|
||||
import androidx.compose.ui.window.ComposeUIViewController
|
||||
import com.digitoolsolutions.app.torquevaultkmp.di.initKoin
|
||||
import com.juul.kable.CentralManager
|
||||
import io.github.aakira.napier.DebugAntilog
|
||||
import io.github.aakira.napier.Napier
|
||||
|
||||
@@ -9,6 +10,9 @@ fun MainViewController() = ComposeUIViewController(
|
||||
configure = {
|
||||
// Add logger lib
|
||||
Napier.base(DebugAntilog()) // NoOpAntilog() for release
|
||||
CentralManager.configure {
|
||||
stateRestoration = true
|
||||
}
|
||||
initKoin()
|
||||
}
|
||||
) { App() }
|
||||
@@ -23,7 +23,10 @@
|
||||
<string>The app requires Bluetooth to connect and manage Torque devices.</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>The app requires Bluetooth to connect and manage Torque devices.</string>
|
||||
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>bluetooth-central</string>
|
||||
</array>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
|
||||
Reference in New Issue
Block a user