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