181 lines
6.2 KiB
Kotlin
181 lines
6.2 KiB
Kotlin
package com.logitech.vc.kirbytest
|
|
|
|
import com.google.gson.annotations.SerializedName
|
|
import kotlin.math.roundToInt
|
|
|
|
|
|
object DecoderIaq {
|
|
private const val INVALID_CO2 = 16383
|
|
private const val INVALID_VOC = 2047
|
|
private const val INVALID_HUMIDITY = 1023
|
|
private const val INVALID_TEMPERATURE = 2047
|
|
private const val INVALID_PRESSURE = 65535
|
|
private const val INVALID_OCCUPANCY = 3
|
|
private const val INVALID_PM25 = 1023
|
|
private const val INVALID_PM10 = 1023
|
|
|
|
private val supportedMessageTypes = listOf<Number>(0, 1, 5)
|
|
|
|
fun parseMeasurement(input: String): Uplink? {
|
|
|
|
val inputBytes = hexStringToByteArray(input)
|
|
val msgType = inputBytes[0].toInt() and 0xFF ushr 4
|
|
|
|
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
|
|
measurement.co2 = if (co2 == INVALID_CO2) null else co2
|
|
|
|
val voc = parseUnsignedInt(inputBytes, 2, 4) ushr 3 and INVALID_VOC
|
|
measurement.voc = if (co2 == INVALID_VOC) null else voc
|
|
|
|
val humidity =
|
|
parseUnsignedInt(inputBytes, 3, 5) ushr 1 and INVALID_HUMIDITY
|
|
measurement.humidity = if (humidity == INVALID_HUMIDITY) null else humidity / 10
|
|
|
|
val temperature =
|
|
parseUnsignedInt(inputBytes, 4, 7) ushr 6 and INVALID_TEMPERATURE
|
|
measurement.temperature =
|
|
if (temperature == INVALID_TEMPERATURE) null else ((temperature / 10.0 - 40) * 10.0).roundToInt() / 10.0
|
|
|
|
val pressure =
|
|
parseUnsignedInt(inputBytes, 6, 9) ushr 6 and INVALID_PRESSURE
|
|
measurement.pressure =
|
|
if (pressure == INVALID_PRESSURE) null else (30000 + 19000.0 * pressure / 13107).roundToInt()
|
|
|
|
val occupancy =
|
|
parseUnsignedInt(inputBytes, 8, 9) 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
|
|
measurement.pm25 = if (pm25 == INVALID_PM25) null else pm25
|
|
|
|
val pm10 = parseUnsignedInt(inputBytes, 9, 11) and INVALID_PM10
|
|
measurement.pm10 = if (pm10 == INVALID_PM10) null else pm10
|
|
}
|
|
return measurement
|
|
}
|
|
|
|
|
|
interface Uplink {
|
|
var deviceId: String?
|
|
var msgType: Number?
|
|
}
|
|
|
|
data class Measurement(
|
|
@SerializedName("bleAddress")
|
|
override var deviceId: String? = null,
|
|
override var msgType: Number? = null,
|
|
var co2: Number? = null,
|
|
var voc: Number? = null,
|
|
var humidity: Number? = null,
|
|
var temperature: Number? = null,
|
|
var pressure: Number? = null,
|
|
var occupancy: Number? = null,
|
|
var pm25: Number? = null,
|
|
var pm10: Number? = null
|
|
) : Uplink {
|
|
override fun toString(): String {
|
|
return "M{" +
|
|
"type=" + msgType +
|
|
", co2=" + co2 +
|
|
", voc=" + voc +
|
|
", hum=" + humidity +
|
|
", temp=" + temperature +
|
|
", press=" + pressure +
|
|
", pm25=" + pm25 +
|
|
", pm10=" + pm10 +
|
|
", 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 +
|
|
'}'
|
|
}
|
|
}
|
|
} |