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(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 + '}' } } }