diff --git a/devicetypes/intraix/billion-smart-meter.src/billion-smart-meter.groovy b/devicetypes/intraix/billion-smart-meter.src/billion-smart-meter.groovy new file mode 100644 index 0000000..c459b24 --- /dev/null +++ b/devicetypes/intraix/billion-smart-meter.src/billion-smart-meter.groovy @@ -0,0 +1,199 @@ +/** + * Copyright 2015 Intraix + * + * 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. + * + * Billion Smart Meter + * + * Author: Intraix + * Date: 2015-09-03 + */ +metadata { + definition(name: "Billion Smart Meter", namespace: "intraix", author: "Intraix") { + capability "Actuator" + capability "Sensor" + capability "Configuration" + capability "Refresh" + capability "Switch" + capability "Energy Meter" + capability "Power Meter" + + // Custom attiributes for capabilities not supported by ST + attribute "voltage", "number" + attribute "current", "number" + attribute "frequency", "number" + attribute "powerFactor", "number" + attribute "apparentPower", "number" + + fingerprint profileId: "0104", inClusters: "0000,0003,0006,0702", outClusters: "" + } + + // simulator metadata + simulator { + // status messages + status "on": "on/off: 1" + status "off": "on/off: 0" + + // reply messages + reply "zcl on-off on": "on/off: 1" + reply "zcl on-off off": "on/off: 0" + } + + // UI tile definitions + tiles { + standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { + state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff" + state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn" + state "turningOn", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff" + state "turningOff", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn" + } + standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 1, height: 1) { + state "default", label: "", action: "refresh.refresh", icon: "st.secondary.refresh" + } + valueTile("energy", "device.energy", decoration: "flat", width: 1, height: 1) { + state "energy", label: '${currentValue} kWh' + } + valueTile("power", "device.power", decoration: "flat", width: 1, height: 1) { + state "power", label: '${currentValue} W' + } + valueTile("voltage", "device.voltage", decoration: "flat", width: 1, height: 1) { + state "voltage", label: '${currentValue} V' + } + valueTile("current", "device.current", decoration: "flat", width: 1, height: 1) { + state "current", label: '${currentValue} A' + } + valueTile("frequency", "device.frequency", decoration: "flat", width: 1, height: 1) { + state "frequency", label: '${currentValue} Hz' + } + valueTile("powerFactor", "device.powerFactor", decoration: "flat", width: 1, height: 1) { + state "powerFactor", label: 'Power Factor ${currentValue}' + } + valueTile("apparentPower", "device.apparentPower", decoration: "flat", width: 1, height: 1) { + state "apparentPower", label: '${currentValue} VA' + } + main(["switch", "energy", "power", "voltage", "current", "frequency", "powerFactor", "apparentPower"]) + details(["switch", "energy", "power", "voltage", "current", "frequency", "powerFactor", "apparentPower", "refresh"]) + } +} + +// Parse incoming device messages to generate events +def parse(String description) { + log.debug "Parse description $description" + def events = [] + + if (description?.startsWith("catchall:")) { + def msg = zigbee.parse(description) + log.trace msg + log.trace "data: $msg.data" + } else if (description?.startsWith("read attr -")) { + def descMap = parseDescriptionAsMap(description) + log.debug "Read attr: $description" + if (descMap.cluster == "0006" && descMap.attrId == "0000") { + def value = descMap.value.endsWith("01") ? "on" : "off" + def event = createEvent(name: "switch", value: value) + events.add(event) + } else if (descMap.cluster == "0702" && descMap.attrId == "8000") { + // Payload length is 21 bytes, which is 42 characters + def payload = descMap.raw.substring(descMap.raw.length() - 42, descMap.raw.length()) + log.trace "payload is $payload" + + // Decode the various parameters from the payload + def voltage = Integer.parseInt(payload.substring(0, 4), 16) / 100 + log.trace "voltage is $voltage" + def voltageEvent = createEvent(name: "voltage", value: voltage) + events.add(voltageEvent) + + def current = Integer.parseInt(payload.substring(4, 8), 16) / 100 + log.trace "current is $current" + def currentEvent = createEvent(name: "current", value: current) + events.add(currentEvent) + + def frequency = Integer.parseInt(payload.substring(8, 12), 16) / 100 + log.trace "frequency is $frequency" + def frequencyEvent = createEvent(name: "frequency", value: frequency) + events.add(frequencyEvent) + + def powerFactor = Integer.parseInt(payload.substring(12, 14), 16) / 100 + log.trace "powerFactor is $powerFactor" + def powerFactorEvent = createEvent(name: "powerFactor", value: powerFactor) + events.add(powerFactorEvent) + + def activePower = Integer.parseInt(payload.substring(14, 22), 16) / 100 + log.trace "activePower is $activePower" + def powerEvent = createEvent(name: "power", value: activePower) + events.add(powerEvent) + + def apparentPower = Integer.parseInt(payload.substring(22, 30), 16) / 100 + log.trace "apparentPower is $apparentPower" + def apparentPowerEvent = createEvent(name: "apparentPower", value: apparentPower) + events.add(apparentPowerEvent) + + def mainEnergy = Integer.parseInt(payload.substring(30, 42), 16) / 1000 + log.trace "mainEnergy is $mainEnergy" + def energyEvent = createEvent(name: "energy", value: mainEnergy) + events.add(energyEvent) + } + } else if (description?.startsWith("on/off:")) { + log.debug "Switch command" + def value = description?.endsWith(" 1") ? "on" : "off" + def event = createEvent(name: "switch", value: value) + events.add(event) + } + + log.debug "Parse returned ${events}" + return events +} + +def parseDescriptionAsMap(description) { + (description - "read attr - ").split(",").inject([:]) { map, param -> + def nameAndValue = param.split(":") + map += [(nameAndValue[0].trim()): nameAndValue[1].trim()] + } +} + +// Commands to device +def on() { + // Fire event for on since meter doesn't suppport zigbee bind + sendEvent(name: "switch", value: "on") + 'zcl on-off on' +} + +def off() { + // Fire event for off since meter doesn't suppport zigbee bind + sendEvent(name: "switch", value: "off") + 'zcl on-off off' +} + +def configure() { + meterConfig() + onOffConfig() + refresh() +} + +// Meter reporting, min inteval 1 min and reporting interval if no activity as 4 min +// min change in value is 01 +def meterConfig() { + [ + "zcl global send-me-a-report 0x0702 0x8000 0x41 60 240 {01}", + "send 0x${device.deviceNetworkId} 1 1", "delay 1500", + ] +} + +def onOffConfig() { + [ + "zcl global send-me-a-report 6 0 0x10 300 600 {01}", + "send 0x${device.deviceNetworkId} 1 1", "delay 1500", + ] +} + +def refresh() { + [ + "st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500", + "st rattr 0x${device.deviceNetworkId} 1 0x0702 0x8000" + ] +} \ No newline at end of file