Finaly the Aqara temp sensor to work. One worked right out of the box with the custom handler from bspranger. The second sensor took a bit more doing as it attached it self to the samsung smartthings outlet. Once I got it to connect directly to the hub it seems to be stable.
Even got pressure to “work” with action tiles. Added pressure atribute to bsrpangers code. This way I can get webcore to read the temperature. And send it to a virtual device in smarthings which in turn can be read by action tiles. Unfortunatly it has to be read as something else, I chose temperature as it just ads a little degree sign after the pressure. Sent a request to smartthings to add pressure as a capability, while being very friendly stating he forwarded my request to the design team Im not too hopefull this will be added any time soon.
/**************************************************************/
/* Temp test */
/**************************************************************/
/* Author : Akidon */
/* Created : 3.5.2020, 13:05:02 */
/* Modified : 15.5.2020, 13:28:41 */
/* Build : 7 */
/* UI version : v0.3.110.20191009 */
/**************************************************************/
+0msstart
/* Trace started on 18.5.2020, 11:10:41 (about 1 minute ago) */
define
dynamic Pressure1; /* 1006.1 */
end define;
execute
+145mspending
every minute /* #5 */
do
+149ms10ms
with /* #3 */
Temperatur Balkong
do
+151ms8ms
Set variable {Pressure1} = Temperatur Balkong's pressure; /* #4 */
end with;
+160ms30ms
with /* #1 */
{:a32b225724d35e949ee3371fdf3812bc:}
do
+162ms28ms
setTemperature({Pressure1}); /* #2 */
end with;
end every;
end execute;
+203msstop
/* Trace ended on 18.5.2020, 11:10:41 */
/**
* Xiaomi Aqara Temperature Humidity Sensor
* Version 1.3
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Original device handler code by a4refillpad, adapted for use with Aqara model by bspranger
* Additional contributions to code by alecm, alixjg, bspranger, cscheiene, gn0st1c, foz333, jmagnuson, rinkek, ronvandegraaf, snalee, tmleafs, twonk, & veeceeoh
*
* Known issues:
* Xiaomi sensors do not seem to respond to refresh requests
* Inconsistent rendering of user interface text/graphics between iOS and Android devices - This is due to SmartThings, not this device handler
* Pairing Xiaomi sensors can be difficult as they were not designed to use with a SmartThings hub. See
*
*/
metadata {
definition (name: "Xiaomi Aqara Temperature Humidity Sensor", namespace: "bspranger", author: "bspranger") {
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Sensor"
capability "Battery"
capability "Health Check"
attribute "lastCheckin", "String"
attribute "lastCheckinDate", "String"
attribute "maxTemp", "number"
attribute "minTemp", "number"
attribute "maxHumidity", "number"
attribute "minHumidity", "number"
attribute "multiAttributesReport", "String"
attribute "currentDay", "String"
attribute "batteryRuntime", "String"
attribute "pressure", "number"
fingerprint profileId: "0104", deviceId: "5F01", inClusters: "0000, 0003, FFFF, 0402, 0403, 0405", outClusters: "0000, 0004, FFFF", manufacturer: "LUMI", model: "lumi.weather", deviceJoinName: "Xiaomi Aqara Temp Sensor"
command "resetBatteryRuntime"
}
// simulator metadata
simulator {
for (int i = 0; i <= 100; i += 10) {
status "${i}F": "temperature: $i F"
}
for (int i = 0; i <= 100; i += 10) {
status "${i}%": "humidity: ${i}%"
}
}
tiles(scale: 2) {
multiAttributeTile(name:"temperature", type:"generic", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
tileAttribute("device.multiAttributesReport", key: "SECONDARY_CONTROL") {
attributeState("multiAttributesReport", label:'${currentValue}' //icon:"st.Weather.weather12",
)
}
}
valueTile("temperature2", "device.temperature", inactiveLabel: false) {
state "temperature", label:'${currentValue}°', icon:"st.Weather.weather2",
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
valueTile("humidity", "device.humidity", inactiveLabel: false, width: 2, height: 2) {
state "humidity", label:'${currentValue}%', unit:"%", icon:"https://raw.githubusercontent.com/bspranger/Xiaomi/master/images/XiaomiHumidity.png",
backgroundColors:[
[value: 0, color: "#FFFCDF"],
[value: 4, color: "#FDF789"],
[value: 20, color: "#A5CF63"],
[value: 23, color: "#6FBD7F"],
[value: 56, color: "#4CA98C"],
[value: 59, color: "#0072BB"],
[value: 76, color: "#085396"]
]
}
standardTile("pressure", "device.pressure", inactiveLabel: false, decoration:"flat", width: 2, height: 2) {
state "pressure", label:'${currentValue}', icon:"https://raw.githubusercontent.com/bspranger/Xiaomi/master/images/XiaomiPressure.png"
}
valueTile("battery", "device.battery", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}%', unit:"%", icon:"https://raw.githubusercontent.com/bspranger/Xiaomi/master/images/XiaomiBattery.png",
backgroundColors:[
[value: 10, color: "#bc2323"],
[value: 26, color: "#f1d801"],
[value: 51, color: "#44b621"]
]
}
valueTile("spacer", "spacer", decoration: "flat", inactiveLabel: false, width: 1, height: 1) {
state "default", label:''
}
valueTile("lastcheckin", "device.lastCheckin", inactiveLabel: false, decoration:"flat", width: 4, height: 1) {
state "lastcheckin", label:'Last Event:\n ${currentValue}'
}
valueTile("batteryRuntime", "device.batteryRuntime", inactiveLabel: false, decoration:"flat", width: 4, height: 1) {
state "batteryRuntime", label:'Battery Changed: ${currentValue}'
}
main("temperature2")
details(["temperature", "battery", "pressure", "humidity", "spacer", "lastcheckin", "spacer", "spacer", "batteryRuntime", "spacer"])
}
preferences {
//Button Config
input description: "The settings below customize additional infomation displayed in the main tile.", type: "paragraph", element: "paragraph", title: "MAIN TILE DISPLAY"
input name: "displayTempInteger", type: "bool", title: "Display temperature as integer?", description:"NOTE: Takes effect on the next temperature report. High/Low temperatures are always displayed as integers."
input name: "displayTempHighLow", type: "bool", title: "Display high/low temperature?"
input name: "displayHumidHighLow", type: "bool", title: "Display high/low humidity?"
//Temp, Humidity, and Pressure Offsets and Pressure Units
input description: "The settings below allow correction of variations in temperature, humidity, and pressure by setting an offset. Examples: If the sensor consistently reports temperature 5 degrees too warm, enter '-5' for the Temperature Offset. If it reports humidity 3% too low, enter ‘3' for the Humidity Offset. NOTE: Changes will take effect on the NEXT temperature / humidity / pressure report.", type: "paragraph", element: "paragraph", title: "OFFSETS & UNITS"
input "tempOffset", "decimal", title:"Temperature Offset", description:"Adjust temperature by this many degrees", range:"*..*"
input "humidOffset", "number", title:"Humidity Offset", description:"Adjust humidity by this many percent", range: "*..*"
input "pressOffset", "number", title:"Pressure Offset", description:"Adjust pressure by this many units", range: "*..*"
input name:"PressureUnits", type:"enum", title:"Pressure Units", options:["mbar", "kPa", "inHg", "mmHg"], description:"Sets the unit in which pressure will be reported"
input description: "NOTE: The temperature unit (C / F) can be changed in the location settings for your hub.", type: "paragraph", element: "paragraph", title: ""
//Date & Time Config
input description: "", type: "paragraph", element: "paragraph", title: "DATE & CLOCK"
input name: "dateformat", type: "enum", title: "Set Date Format\nUS (MDY) - UK (DMY) - Other (YMD)", description: "Date Format", options:["US","UK","Other"]
input name: "clockformat", type: "bool", title: "Use 24 hour clock?"
//Battery Reset Config
input description: "If you have installed a new battery, the toggle below will reset the Changed Battery date to help remember when it was changed.", type: "paragraph", element: "paragraph", title: "CHANGED BATTERY DATE RESET"
input name: "battReset", type: "bool", title: "Battery Changed?", description: ""
//Battery Voltage Offset
input description: "Only change the settings below if you know what you're doing.", type: "paragraph", element: "paragraph", title: "ADVANCED SETTINGS"
input name: "voltsmax", title: "Max Volts\nA battery is at 100% at __ volts.\nRange 2.8 to 3.4", type: "decimal", range: "2.8..3.4", defaultValue: 3
input name: "voltsmin", title: "Min Volts\nA battery is at 0% (needs replacing)\nat __ volts. Range 2.0 to 2.7", type: "decimal", range: "2..2.7", defaultValue: 2.5
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "${device.displayName}: Parsing description: ${description}"
// Determine current time and date in the user-selected date format and clock style
def now = formatDate()
def nowDate = new Date(now).getTime()
// Any report - temp, humidity, pressure, & battery - results in a lastCheckin event and update to Last Checkin tile
// However, only a non-parseable report results in lastCheckin being displayed in events log
sendEvent(name: "lastCheckin", value: now, displayed: false)
sendEvent(name: "lastCheckinDate", value: nowDate, displayed: false)
// Check if the min/max temp and min/max humidity should be reset
checkNewDay(now)
// getEvent automatically retrieves temp and humidity in correct unit as integer
Map map = zigbee.getEvent(description)
// Send message data to appropriate parsing function based on the type of report
if (map.name == "temperature") {
def temp = parseTemperature(description)
map.value = displayTempInteger ? (int) temp : temp
map.descriptionText = "${device.displayName} temperature is ${map.value}°${temperatureScale}"
map.translatable = true
updateMinMaxTemps(map.value)
} else if (map.name == "humidity") {
map.value = humidOffset ? (int) map.value + (int) humidOffset : (int) map.value
updateMinMaxHumidity(map.value)
} else if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
} else if (description?.startsWith('read attr - raw:')) {
map = parseReadAttr(description)
} else {
log.debug "${device.displayName}: was unable to parse ${description}"
sendEvent(name: "lastCheckin", value: now)
}
if (map) {
log.debug "${device.displayName}: Parse returned ${map}"
return createEvent(map)
} else
return [:]
}
// Calculate temperature with 0.1 precision in C or F unit as set by hub location settings
private parseTemperature(String description) {
def temp = ((description - "temperature: ").trim()) as Float
def offset = tempOffset ? tempOffset : 0
temp = (temp > 100) ? (100 - temp) : temp
temp = (temperatureScale == "F") ? ((temp * 1.8) + 32) + offset : temp + offset
return temp.round(1)
}
// Check catchall for battery voltage data to pass to getBatteryResult for conversion to percentage report
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def catchall = zigbee.parse(description)
log.debug catchall
if (catchall.clusterId == 0x0000) {
def MsgLength = catchall.data.size()
// Original Xiaomi CatchAll does not have identifiers, first UINT16 is Battery
if ((catchall.data.get(0) == 0x01 || catchall.data.get(0) == 0x02) && (catchall.data.get(1) == 0xFF)) {
for (int i = 4; i < (MsgLength-3); i++) {
if (catchall.data.get(i) == 0x21) { // check the data ID and data type
// next two bytes are the battery voltage
resultMap = getBatteryResult((catchall.data.get(i+2)<<8) + catchall.data.get(i+1))
break
}
}
}
}
return resultMap
}
// Parse pressure report or battery report on reset button press
private Map parseReadAttr(String description) {
Map resultMap = [:]
def cluster = description.split(",").find {it.split(":")[0].trim() == "cluster"}?.split(":")[1].trim()
def attrId = description.split(",").find {it.split(":")[0].trim() == "attrId"}?.split(":")[1].trim()
def value = description.split(",").find {it.split(":")[0].trim() == "value"}?.split(":")[1].trim()
// log.debug "${device.displayName}: Parsing read attr: cluster: ${cluster}, attrId: ${attrId}, value: ${value}"
if ((cluster == "0403") && (attrId == "0000")) {
def result = value[0..3]
float pressureval = Integer.parseInt(result, 16)
if (!(settings.PressureUnits)){
settings.PressureUnits = "mbar"
}
// log.debug "${device.displayName}: Converting ${pressureval} to ${PressureUnits}"
switch (PressureUnits) {
case "mbar":
pressureval = (pressureval/10) as Float
pressureval = pressureval.round(1);
break;
case "kPa":
pressureval = (pressureval/100) as Float
pressureval = pressureval.round(2);
break;
case "inHg":
pressureval = (((pressureval/10) as Float) * 0.0295300)
pressureval = pressureval.round(2);
break;
case "mmHg":
pressureval = (((pressureval/10) as Float) * 0.750062)
pressureval = pressureval.round(2);
break;
}
// log.debug "${device.displayName}: Pressure is ${pressureval} ${PressureUnits} before applying the pressure offset."
if (settings.pressOffset) {
pressureval = (pressureval + settings.pressOffset)
}
pressureval = pressureval.round(2);
resultMap = [
name: 'pressure',
value: pressureval,
unit: "${PressureUnits}",
isStateChange: true,
descriptionText : "${device.displayName} Pressure is ${pressureval} ${PressureUnits}"
]
} else if (cluster == "0000" && attrId == "0005") {
def modelName = ""
// Parsing the model name
for (int i = 0; i < value.length(); i+=2) {
def str = value.substring(i, i+2);
def NextChar = (char)Integer.parseInt(str, 16);
modelName = modelName + NextChar
}
log.debug "${device.displayName}: Reported model: ${modelName}"
}
return resultMap
}
// Convert raw 4 digit integer voltage value into percentage based on minVolts/maxVolts range
private Map getBatteryResult(rawValue) {
// raw voltage is normally supplied as a 4 digit integer that needs to be divided by 1000
// but in the case the final zero is dropped then divide by 100 to get actual voltage value
def rawVolts = rawValue / 1000
def minVolts
def maxVolts
if(voltsmin == null || voltsmin == "")
minVolts = 2.5
else
minVolts = voltsmin
if(voltsmax == null || voltsmax == "")
maxVolts = 3.0
else
maxVolts = voltsmax
def pct = (rawVolts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.min(100, Math.round(pct * 100))
def result = [
name: 'battery',
value: roundedPct,
unit: "%",
isStateChange: true,
descriptionText : "${device.displayName} Battery at ${roundedPct}% (${rawVolts} Volts)"
]
return result
}
// If the day of month has changed from that of previous event, reset the daily min/max temp values
def checkNewDay(now) {
def oldDay = ((device.currentValue("currentDay")) == null) ? "32" : (device.currentValue("currentDay"))
def newDay = new Date(now).format("dd")
if (newDay != oldDay) {
resetMinMax()
sendEvent(name: "currentDay", value: newDay, displayed: false)
}
}
// Reset daily min/max temp and humidity values to the current temp/humidity values
def resetMinMax() {
def currentTemp = device.currentValue('temperature')
def currentHumidity = device.currentValue('humidity')
currentTemp = currentTemp ? (int) currentTemp : currentTemp
log.debug "${device.displayName}: Resetting daily min/max values to current temperature of ${currentTemp}° and humidity of ${currentHumidity}%"
sendEvent(name: "maxTemp", value: currentTemp, displayed: false)
sendEvent(name: "minTemp", value: currentTemp, displayed: false)
sendEvent(name: "maxHumidity", value: currentHumidity, displayed: false)
sendEvent(name: "minHumidity", value: currentHumidity, displayed: false)
refreshMultiAttributes()
}
// Check new min or max temp for the day
def updateMinMaxTemps(temp) {
temp = temp ? (int) temp : temp
if ((temp > device.currentValue('maxTemp')) || (device.currentValue('maxTemp') == null))
sendEvent(name: "maxTemp", value: temp, displayed: false)
if ((temp < device.currentValue('minTemp')) || (device.currentValue('minTemp') == null))
sendEvent(name: "minTemp", value: temp, displayed: false)
refreshMultiAttributes()
}
// Check new min or max humidity for the day
def updateMinMaxHumidity(humidity) {
if ((humidity > device.currentValue('maxHumidity')) || (device.currentValue('maxHumidity') == null))
sendEvent(name: "maxHumidity", value: humidity, displayed: false)
if ((humidity < device.currentValue('minHumidity')) || (device.currentValue('minHumidity') == null))
sendEvent(name: "minHumidity", value: humidity, displayed: false)
refreshMultiAttributes()
}
// Update display of multiattributes in main tile
def refreshMultiAttributes() {
def temphiloAttributes = displayTempHighLow ? (displayHumidHighLow ? "Today's High/Low: ${device.currentState('maxTemp')?.value}° / ${device.currentState('minTemp')?.value}°" : "Today's High: ${device.currentState('maxTemp')?.value}° / Low: ${device.currentState('minTemp')?.value}°") : ""
def humidhiloAttributes = displayHumidHighLow ? (displayTempHighLow ? " ${device.currentState('maxHumidity')?.value}% / ${device.currentState('minHumidity')?.value}%" : "Today's High: ${device.currentState('maxHumidity')?.value}% / Low: ${device.currentState('minHumidity')?.value}%") : ""
sendEvent(name: "multiAttributesReport", value: "${temphiloAttributes}${humidhiloAttributes}", displayed: false)
}
//Reset the date displayed in Battery Changed tile to current date
def resetBatteryRuntime(paired) {
def now = formatDate(true)
def newlyPaired = paired ? " for newly paired sensor" : ""
sendEvent(name: "batteryRuntime", value: now)
log.debug "${device.displayName}: Setting Battery Changed to current date${newlyPaired}"
}
// installed() runs just after a sensor is paired using the "Add a Thing" method in the SmartThings mobile app
def installed() {
state.battery = 0
if (!batteryRuntime) resetBatteryRuntime(true)
checkIntervalEvent("installed")
}
// configure() runs after installed() when a sensor is paired
def configure() {
log.debug "${device.displayName}: configuring"
state.battery = 0
if (!batteryRuntime) resetBatteryRuntime(true)
checkIntervalEvent("configured")
return
}
// updated() will run twice every time user presses save in preference settings page
def updated() {
checkIntervalEvent("updated")
if(battReset) {
resetBatteryRuntime()
device.updateSetting("battReset", false)
}
}
private checkIntervalEvent(text) {
// Device wakes up every 1 hours, this interval allows us to miss one wakeup notification before marking offline
log.debug "${device.displayName}: Configured health checkInterval when ${text}()"
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
def formatDate(batteryReset) {
def correctedTimezone = ""
def timeString = clockformat ? "HH:mm:ss" : "h:mm:ss aa"
// If user's hub timezone is not set, display error messages in log and events log, and set timezone to GMT to avoid errors
if (!(location.timeZone)) {
correctedTimezone = TimeZone.getTimeZone("GMT")
log.error "${device.displayName}: Time Zone not set, so GMT was used. Please set up your location in the SmartThings mobile app."
sendEvent(name: "error", value: "", descriptionText: "ERROR: Time Zone not set, so GMT was used. Please set up your location in the SmartThings mobile app.")
}
else {
correctedTimezone = location.timeZone
}
if (dateformat == "US" || dateformat == "" || dateformat == null) {
if (batteryReset)
return new Date().format("MMM dd yyyy", correctedTimezone)
else
return new Date().format("EEE MMM dd yyyy ${timeString}", correctedTimezone)
}
else if (dateformat == "UK") {
if (batteryReset)
return new Date().format("dd MMM yyyy", correctedTimezone)
else
return new Date().format("EEE dd MMM yyyy ${timeString}", correctedTimezone)
}
else {
if (batteryReset)
return new Date().format("yyyy MMM dd", correctedTimezone)
else
return new Date().format("EEE yyyy MMM dd ${timeString}", correctedTimezone)
}
}