feat: add device status uplink decoder
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
package com.logitech.vc.kirbytest
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.apache.commons.codec.DecoderException
|
||||
import org.apache.commons.codec.binary.Hex
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
object DecoderIaq {
|
||||
private const val INVALID_CO2 = 16383
|
||||
private const val INVALID_VOC = 2047
|
||||
@@ -17,18 +14,70 @@ object DecoderIaq {
|
||||
private const val INVALID_PM25 = 1023
|
||||
private const val INVALID_PM10 = 1023
|
||||
|
||||
private val supportedMessageTypes = listOf<Number>(0, 1)
|
||||
private val supportedMessageTypes = listOf<Number>(0, 1, 5)
|
||||
|
||||
fun parseMeasurement(input: String): Uplink? {
|
||||
|
||||
fun parseMeasurement(input: String): Measurement? {
|
||||
val measurement = Measurement()
|
||||
val inputBytes = hexStringToByteArray(input)
|
||||
val msgType = inputBytes[0].toInt() and 0xFF ushr 4
|
||||
|
||||
if(!supportedMessageTypes.contains(msgType)) {
|
||||
Log.i("Decoder", "Invalid message type: $msgType")
|
||||
if (!supportedMessageTypes.contains(msgType)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (msgType < 3) {
|
||||
return parseIaq(msgType, inputBytes)
|
||||
} else if (msgType == 5) {
|
||||
return parseDeviceStatus(msgType, inputBytes)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseDeviceStatus(msgType: Number, inputBytes: ByteArray): DeviceStatus {
|
||||
val status = DeviceStatus(msgType = msgType)
|
||||
|
||||
val fwMajor = parseUnsignedIntFromBytes(inputBytes, 4, 8)
|
||||
val fwMinor = parseUnsignedIntFromBytes(inputBytes, 12, 8)
|
||||
val fwPatch = parseUnsignedIntFromBytes(inputBytes, 20, 8)
|
||||
val fwTweak = parseUnsignedIntFromBytes(inputBytes, 28, 8)
|
||||
|
||||
status.firmware = "$fwMajor.$fwMinor.$fwPatch"
|
||||
if(fwTweak > 0u) {
|
||||
status.firmware += "-$fwTweak"
|
||||
}
|
||||
|
||||
status.operationMode = parseUnsignedIntFromBytes(inputBytes, 36, 4).toInt()
|
||||
val batteryVoltage = parseUnsignedIntFromBytes(inputBytes, 40, 16).toInt()
|
||||
if (batteryVoltage == 0) {
|
||||
status.usbPowered = true
|
||||
} else {
|
||||
status.batteryVoltage = batteryVoltage / 1000f
|
||||
}
|
||||
|
||||
|
||||
if (inputBytes.size > 7) {
|
||||
val errors = inputBytes[7].toInt()
|
||||
val errorCo2 = (errors shr 0) and 1
|
||||
val errorVoc = (errors shr 1) and 1
|
||||
val errorPm = (errors shr 2) and 1
|
||||
val errorRadar = (errors shr 3) and 1
|
||||
val errorTemp = (errors shr 4) and 1
|
||||
|
||||
status.errorCo2 = errorCo2 == 1
|
||||
status.errorVoc = errorVoc == 1
|
||||
status.errorPm = errorPm == 1
|
||||
status.errorRadar = errorRadar == 1
|
||||
status.errorTemp = errorTemp == 1
|
||||
}
|
||||
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
|
||||
private fun parseIaq(msgType: Number, inputBytes: ByteArray): Measurement {
|
||||
val measurement = Measurement()
|
||||
measurement.msgType = msgType
|
||||
|
||||
val co2 = parseUnsignedInt(inputBytes, 0, 3) ushr 6 and INVALID_CO2
|
||||
@@ -55,10 +104,6 @@ object DecoderIaq {
|
||||
parseUnsignedInt(inputBytes, 8, 9) ushr 4 and INVALID_OCCUPANCY
|
||||
measurement.occupancy = if (occupancy == INVALID_OCCUPANCY) null else occupancy
|
||||
|
||||
val occupancy2 =
|
||||
parseUnsignedInt(inputBytes, 10, 11) ushr 4 and INVALID_OCCUPANCY
|
||||
measurement.occupancy = if (occupancy == INVALID_OCCUPANCY) null else occupancy
|
||||
|
||||
if (msgType == 0) {
|
||||
val pm25 =
|
||||
parseUnsignedInt(inputBytes, 8, 10) ushr 2 and INVALID_PM25
|
||||
@@ -70,28 +115,16 @@ object DecoderIaq {
|
||||
return measurement
|
||||
}
|
||||
|
||||
private fun parseUnsignedInt(bytes: ByteArray, startIncl: Int, endExcl: Int): Int {
|
||||
val section = bytes.copyOfRange(startIncl, min(bytes.size, endExcl))
|
||||
var unsignedInt = 0
|
||||
for (i in section.indices) {
|
||||
unsignedInt = unsignedInt shl 8
|
||||
unsignedInt = unsignedInt or (section[i].toInt() and 0xFF)
|
||||
}
|
||||
return unsignedInt
|
||||
|
||||
interface Uplink {
|
||||
var deviceId: String?
|
||||
var msgType: Number?
|
||||
}
|
||||
|
||||
private fun hexStringToByteArray(encoded: String): ByteArray {
|
||||
return try {
|
||||
Hex.decodeHex(encoded)
|
||||
} catch (e: DecoderException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
data class Measurement (
|
||||
data class Measurement(
|
||||
@SerializedName("bleAddress")
|
||||
var deviceId: String? = null,
|
||||
var msgType: Number? = null,
|
||||
override var deviceId: String? = null,
|
||||
override var msgType: Number? = null,
|
||||
var co2: Number? = null,
|
||||
var voc: Number? = null,
|
||||
var humidity: Number? = null,
|
||||
@@ -100,9 +133,7 @@ object DecoderIaq {
|
||||
var occupancy: Number? = null,
|
||||
var pm25: Number? = null,
|
||||
var pm10: Number? = null
|
||||
) {
|
||||
|
||||
|
||||
) : Uplink {
|
||||
override fun toString(): String {
|
||||
return "M{" +
|
||||
"type=" + msgType +
|
||||
@@ -116,6 +147,35 @@ object DecoderIaq {
|
||||
", occ=" + occupancy +
|
||||
'}'
|
||||
}
|
||||
}
|
||||
|
||||
data class DeviceStatus(
|
||||
@SerializedName("bleAddress")
|
||||
override var deviceId: String? = null,
|
||||
override var msgType: Number? = null,
|
||||
var firmware: String? = null,
|
||||
var operationMode: Number? = null,
|
||||
var batteryVoltage: Number? = null,
|
||||
var usbPowered: Boolean = false,
|
||||
var errorCo2: Boolean = false,
|
||||
var errorVoc: Boolean = false,
|
||||
var errorPm: Boolean = false,
|
||||
var errorRadar: Boolean = false,
|
||||
var errorTemp: Boolean = false,
|
||||
) : Uplink {
|
||||
override fun toString(): String {
|
||||
return "S{" +
|
||||
"type=" + msgType +
|
||||
", fw=" + firmware +
|
||||
", op=" + operationMode +
|
||||
", usb=" + usbPowered +
|
||||
", batt=" + batteryVoltage +
|
||||
", errTemp=" + errorTemp +
|
||||
", errCo2=" + errorCo2 +
|
||||
", errVoc=" + errorVoc +
|
||||
", errPm=" + errorPm +
|
||||
", errRad=" + errorRadar +
|
||||
'}'
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user