Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add check-in message decoding for Aqara WSDCGQ11LM #15

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
249 changes: 174 additions & 75 deletions xiaomi/drivers/xiaomi_aqara_temperature_humidity_sensor.groovy
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
*
*
* Xiaomi Aqara Temperature and Humidity Sensor Driver
*
*
*/


Expand All @@ -24,11 +24,11 @@ metadata {
capability "Battery"
capability "Configuration"
capability "PressureMeasurement"
capability "PushableButton"
capability "RelativeHumidityMeasurement"
capability "Sensor"
capability "TemperatureMeasurement"
capability "VoltageMeasurement"
capability "PushableButton"

attribute "absoluteHumidity", "number"
attribute "healthStatus", "enum", ["offline", "online"]
Expand All @@ -47,18 +47,18 @@ metadata {


preferences {

input name: "infoLogging", type: "bool", title: "Enable logging", defaultValue: true
input name: "debugLogging", type: "bool", title: "Enable debug logging", defaultValue: false
input name: "traceLogging", type: "bool", title: "Enable trace logging", defaultValue: false

}


void testCommand() {

logging("${device} : Test Command", "info")

}


Expand All @@ -76,7 +76,6 @@ void updateSpecifics() {
// Called by updated() method in BirdsLikeWires.library

return

}


Expand All @@ -86,116 +85,216 @@ void processMap(Map map) {

String[] receivedValue = map.value

if (map.cluster == "0402") {
if (map.cluster == "0402") {

// Received temperature data.
String[] temperatureHex = receivedValue[2..3] + receivedValue[0..1]
String temperatureFlippedHex = temperatureHex.join()
logging("${device} : processMap() : temperature ${temperatureFlippedHex}", "trace")
processTemperature(temperatureFlippedHex)

} else if (map.cluster == "0403") {

String[] temperatureHex = receivedValue[2..3] + receivedValue[0..1]
String temperatureFlippedHex = temperatureHex.join()
BigDecimal temperature = hexStrToSignedInt(temperatureFlippedHex)
temperature = temperature.setScale(2, BigDecimal.ROUND_HALF_UP) / 100
// Received pressure data.
String[] pressureHex = receivedValue[2..3] + receivedValue[0..1]
String pressureFlippedHex = pressureHex.join()
logging("${device} : processMap() : pressure ${pressureFlippedHex}", "trace")
processPressure(pressureFlippedHex)

logging("${device} : temperature : ${temperature} from hex value ${temperatureFlippedHex} flipped from ${map.value}", "trace")
} else if (map.cluster == "0405") {

String temperatureScale = location.temperatureScale
if (temperatureScale == "F") {
temperature = (temperature * 1.8) + 32
}
// Received humidity data.
String[] humidityHex = receivedValue[2..3] + receivedValue[0..1]
String humidityFlippedHex = humidityHex.join()
logging("${device} : processMap() : humidity ${humidityFlippedHex}", "trace")
processHumidity(humidityFlippedHex)

if (temperature > 200 || temperature < -200) {
} else if (map.cluster == "0000") {

logging("${device} : Temperature : Value of ${temperature}°${temperatureScale} is unusual. Watch out for batteries failing on this device.", "warn")
if (map.attrId == "0005") {

// Scrounge more value! We can capture a short press of the reset button and make it useful.
logging("${device} : Trigger : Button Pressed", "info")
sendEvent(name: "pushed", value: 1, isStateChange: true)

} else {

logging("${device} : Temperature : ${temperature} °${temperatureScale}", "info")
sendEvent(name: "temperature", value: temperature, unit: "${temperatureScale}")
// processBasic(map)
filterThis(map)

}

} else if (map.cluster == "0403") {
} else {

// Received pressure data.
filterThis(map)

String[] pressureHex = receivedValue[2..3] + receivedValue[0..1]
String pressureFlippedHex = pressureHex.join()
BigDecimal pressure = hexStrToSignedInt(pressureFlippedHex)
pressure = pressure.setScale(1, BigDecimal.ROUND_HALF_UP) / 10
}

BigDecimal lastPressure = device.currentState("pressure") ? device.currentState("pressure").value.toBigDecimal() : 0
}

////////// WORK TO DO - RECORD PREVIOUS PRESSURE AS LASTPRESSURE IF PRESSURE HAS CHANGED OR SOMETHING - TOO TIRED!

// BigDecimal pressurePrevious = device.currentState("pressurePrevious").value.toBigDecimal()
// if (pressurePrevious != null && pressure != lastPressure) {
// endEvent(name: "pressurePrevious", value: lastPressure, unit: "kPa")
// } else if
private void processTemperature(temperatureFlippedHex) {

String pressureDirection = pressure > lastPressure ? "rising" : "falling"
BigDecimal temperature = hexStrToSignedInt(temperatureFlippedHex)
temperature = temperature.setScale(2, BigDecimal.ROUND_HALF_UP) / 100

logging("${device} : pressure : ${pressure} from hex value ${pressureFlippedHex} flipped from ${map.value}", "trace")
logging("${device} : Pressure : ${pressure} kPa", "info")
sendEvent(name: "pressure", value: pressure, unit: "kPa")
sendEvent(name: "pressureDirection", value: "${pressureDirection}")
logging("${device} : temperature : ${temperature} from hex value ${temperatureFlippedHex}", "trace")

} else if (map.cluster == "0405") {
String temperatureScale = location.temperatureScale
if (temperatureScale == "F") {
temperature = (temperature * 1.8) + 32
}

// Received humidity data.
if (temperature > 200 || temperature < -200) {

String[] humidityHex = receivedValue[2..3] + receivedValue[0..1]
String humidityFlippedHex = humidityHex.join()
BigDecimal humidity = hexStrToSignedInt(humidityFlippedHex)
humidity = humidity.setScale(2, BigDecimal.ROUND_HALF_UP) / 100
logging("${device} : Temperature : Value of ${temperature}°${temperatureScale} is unusual. Watch out for batteries failing on this device.", "warn")

logging("${device} : humidity : ${humidity} from hex value ${humidityFlippedHex} flipped from ${map.value}", "trace")
} else {

BigDecimal lastTemperature = device.currentState("temperature") ? device.currentState("temperature").value.toBigDecimal() : 0
logging("${device} : Temperature : ${temperature} °${temperatureScale}", "info")
sendEvent(name: "temperature", value: temperature, unit: "${temperatureScale}")

String temperatureScale = location.temperatureScale
if (temperatureScale == "F") {
lastTemperature = (lastTemperature - 32) / 1.8
}
}
}

BigDecimal numerator = (6.112 * Math.exp((17.67 * lastTemperature) / (lastTemperature + 243.5)) * humidity * 2.1674)
BigDecimal denominator = lastTemperature + 273.15
BigDecimal absoluteHumidity = numerator / denominator
absoluteHumidity = absoluteHumidity.setScale(1, BigDecimal.ROUND_HALF_UP)

String cubedChar = String.valueOf((char)(179))
private void processPressure(pressureFlippedHex,checkin=false) {

if (humidity > 100 || humidity < 0) {
BigDecimal pressure = hexStrToSignedInt(pressureFlippedHex)
if (checkin) {
pressure = pressure / 100
}
pressure = pressure.setScale(1, BigDecimal.ROUND_HALF_UP) / 10
BigDecimal lastPressure = device.currentState("pressure") ? device.currentState("pressure").value.toBigDecimal() : 0

logging("${device} : Humidity : Value of ${humidity} is out of bounds. Watch out for batteries failing on this device.", "warn")
////////// WORK TO DO - RECORD PREVIOUS PRESSURE AS LASTPRESSURE IF PRESSURE HAS CHANGED OR SOMETHING - TOO TIRED!

} else {
// BigDecimal pressurePrevious = device.currentState("pressurePrevious").value.toBigDecimal()
// if (pressurePrevious != null && pressure != lastPressure) {
// endEvent(name: "pressurePrevious", value: lastPressure, unit: "kPa")
// } else if

logging("${device} : Humidity (Relative) : ${humidity} %", "info")
logging("${device} : Humidity (Absolute) : ${absoluteHumidity} g/m${cubedChar}", "info")
sendEvent(name: "humidity", value: humidity, unit: "%")
sendEvent(name: "absoluteHumidity", value: absoluteHumidity, unit: "g/m${cubedChar}")
String pressureDirection = pressure > lastPressure ? "rising" : "falling"

}
logging("${device} : pressure : ${pressure} from hex value ${pressureFlippedHex}", "trace")
logging("${device} : Pressure : ${pressure} kPa", "info")
sendEvent(name: "pressure", value: pressure, unit: "kPa")
sendEvent(name: "pressureDirection", value: "${pressureDirection}")
}

} else if (map.cluster == "0000") {

if (map.attrId == "0005") {
private void processHumidity(humidityFlippedHex) {

// Scrounge more value! We can capture a short press of the reset button and make it useful.
logging("${device} : Trigger : Button Pressed", "info")
sendEvent(name: "pushed", value: 1, isStateChange: true)
BigDecimal humidity = hexStrToSignedInt(humidityFlippedHex)
humidity = humidity.setScale(2, BigDecimal.ROUND_HALF_UP) / 100

} else {
logging("${device} : humidity : ${humidity} from hex value ${humidityFlippedHex}", "trace")

// processBasic(map)
filterThis(map)
BigDecimal lastTemperature = device.currentState("temperature") ? device.currentState("temperature").value.toBigDecimal() : 0

}
String temperatureScale = location.temperatureScale
if (temperatureScale == "F") {
lastTemperature = (lastTemperature - 32) / 1.8
}

} else {
BigDecimal numerator = (6.112 * Math.exp((17.67 * lastTemperature) / (lastTemperature + 243.5)) * humidity * 2.1674)
BigDecimal denominator = lastTemperature + 273.15
BigDecimal absoluteHumidity = numerator / denominator
absoluteHumidity = absoluteHumidity.setScale(1, BigDecimal.ROUND_HALF_UP)

filterThis(map)
String cubedChar = String.valueOf((char)(179))

}
if (humidity > 100 || humidity < 0) {

logging("${device} : Humidity : Value of ${humidity} is out of bounds. Watch out for batteries failing on this device.", "warn")

} else {

logging("${device} : Humidity (Relative) : ${humidity} %", "info")
logging("${device} : Humidity (Absolute) : ${absoluteHumidity} g/m${cubedChar}", "info")
sendEvent(name: "humidity", value: humidity, unit: "%")
sendEvent(name: "absoluteHumidity", value: absoluteHumidity, unit: "g/m${cubedChar}")

}
}


// Adapted from WSDCGQ11LM driver from veeceeoh (https://raw.githubusercontent.com/veeceeoh/xiaomi-hubitat/master/devicedrivers/xiaomi-temperature-humidity-sensor-hubitat.src/xiaomi-temperature-humidity-sensor-hubitat.groovy)
//
// Reverses order of bytes in hex string
private def reverseHexString(hexString) {
def reversed = ""
for (int i = hexString.length(); i > 0; i -= 2) {
reversed += hexString.substring(i - 2, i )
}
return reversed
}


// Adapted from WSDCGQ11LM driver from veeceeoh (https://raw.githubusercontent.com/veeceeoh/xiaomi-hubitat/master/devicedrivers/xiaomi-temperature-humidity-sensor-hubitat.src/xiaomi-temperature-humidity-sensor-hubitat.groovy)
//
// Parse checkin message from lumi.weather device (WSDCGQ11LM) which contains
// a full set of sensor readings.
//
// called from xiaomiDeviceStatus in xiaomi library
//
def parseCheckinMessageSpecifics(hexString) {
logging("Received check-in message","debug")
def result
// First byte of hexString is UINT8 of payload length in bytes, so it is skipped
def strPosition = 2
def strLength = hexString.size() - 2
while (strPosition < strLength) {
def dataTag = Integer.parseInt(hexString[strPosition++..strPosition++], 16) // Each attribute of the check-in message payload is preceded by a unique 1-byte tag value
def dataType = Integer.parseInt(hexString[strPosition++..strPosition++], 16) // After each attribute tag, the following byte gives the data type of the attribute data
def dataLength = DataType.getLength(dataType) // This looks up the length of data for the determined data type
def dataPayload // This is used to collect the payload data of each check-in message attribute
if (dataLength == null || dataLength == -1 || dataLength == 0) { // A length of null or -1 means the data type is probably variable-length, and 0 length is invalid
logging("Check-in message contains unsupported dataType 0x${Integer.toHexString(dataType)} for dataTag 0x${Integer.toHexString(dataTag)} with dataLength $dataLength","debug")
return
} else {
if (strPosition > (strLength - dataLength)) {
logging("Ran out of data before finishing parse of check-in message","debug")
return
}
dataPayload = hexString[strPosition++..(strPosition+=(dataLength * 2) - 1)-1] // Collect attribute tag payload according to data length of its data type
dataPayload = reverseHexString(dataPayload) // Reverse order of bytes for big endian payload
def dataDebug1 = "Check-in message: Found dataTag 0x${Integer.toHexString(dataTag)}"
def dataDebug2 = "dataType 0x${Integer.toHexString(dataType)}, dataLength $dataLength, dataPayload $dataPayload"
switch (dataTag) {
case 0x01: // Battery voltage
logging("$dataDebug1 (battery), $dataDebug2","trace")
//reportBattery(dataPayload, 1000, 2.8, 3.0) // already done in parent call xiaomiDeviceStatus()
break
case 0x05: // RSSI dB
def convertedPayload = Integer.parseInt(dataPayload,16)
logging("$dataDebug1 (RSSI dB), $dataDebug2 ($convertedPayload)","trace")
state.RSSI = convertedPayload
break
case 0x06: // LQI
def convertedPayload = Integer.parseInt(dataPayload,16)
logging("$dataDebug1 (LQI), $dataDebug2 ($convertedPayload)","trace")
state.LQI = convertedPayload
break
case 0x64: // Temperature in Celcius
logging("$dataDebug1 (temperature), $dataDebug2","trace")
processTemperature(dataPayload)
break
case 0x65: // Relative humidity
logging("$dataDebug1 (humidity), $dataDebug2","trace")
processHumidity(dataPayload)
break
case 0x66: // Atmospheric pressure
logging("$dataDebug1 (pressure), $dataDebug2","trace")
processPressure(dataPayload,true)
break
case 0x0A: // ZigBee parent DNI (device network identifier)
logging("$dataDebug1 (ZigBee parent DNI), $dataDebug2","trace")
state.zigbeeParentDNI = dataPayload
break
default:
logging("$dataDebug1 (unknown), $dataDebug2","trace")
}
}
}
}
38 changes: 21 additions & 17 deletions xiaomi/libraries/BirdsLikeWires.xiaomi/BirdsLikeWires.xiaomi.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ void xiaomiDeviceStatus(Map map) {
String modelCheck = "${getDeviceDataByName('model')}"
def dataSize = map.value.size()

logging("${device} check-in message.", "info")
logging("${device} : xiaomiDeviceStatus : Received $dataSize character message.", "debug")

if (modelCheck == "lumi.sen_ill.mgl01") {
Expand Down Expand Up @@ -118,26 +119,29 @@ void xiaomiDeviceStatus(Map map) {

reportBattery(batteryVoltageHex, batteryDivisor, 2.8, 3.0)

// On some devices (buttons for one) there's a wildly inaccurate temperature sensor.
// We may as well throw this out in the log for comedy value as it's rarely reported.
// Who knows. We may learn something.
try {

try {
if (modelCheck == "lumi.weather") {
// decode sensor values, which are part of the checkin message
parseCheckinMessageSpecifics(map.value)
} else {
// On some devices (buttons for one) there's a wildly inaccurate temperature sensor.
// We may as well throw this out in the log for comedy value as it's rarely reported.
// Who knows. We may learn something.

String temperatureValue = "undefined"
String temperatureValue = "undefined"
temperatureValue = map.value[14..15]
BigDecimal temperatureCelsius = hexToBigDecimal(temperatureValue)

logging("${device} : temperatureValue : ${temperatureValue}", "trace")
logging("${device} : temperatureCelsius sensor value : ${temperatureCelsius}", "trace")

temperatureValue = map.value[14..15]
logging("${device} : temperatureValue : ${temperatureValue}", "trace")
logging("${device} : Inaccurate Temperature : $temperatureCelsius °C", "info")
// sendEvent(name: "temperature", value: temperatureCelsius, unit: "C") // No, don't do that. That would be silly.
}
} catch (Exception e) {

BigDecimal temperatureCelsius = hexToBigDecimal(temperatureValue)
logging("${device} : temperatureCelsius sensor value : ${temperatureCelsius}", "trace")
logging("${device} : Inaccurate Temperature : $temperatureCelsius °C", "info")
// sendEvent(name: "temperature", value: temperatureCelsius, unit: "C") // No, don't do that. That would be silly.

} catch (Exception e) {

return

}
return

}
}