/* * * Hildebrand Glow MQTT Driver * */ @Field String driverVersion = "v1.11 (26th August 2023)" #include BirdsLikeWires.library import groovy.transform.Field @Field boolean debugMode = false @Field int reportIntervalMinutes = 1 @Field int checkEveryMinutes = 1 @Field int occasionalUpdateMinutes = 10 metadata { definition (name: "Hildebrand Glow", namespace: "BirdsLikeWires", author: "Andrew Davison", importUrl: "https://raw.githubusercontent.com/birdslikewires/hubitat/master/hildebrand/drivers/glow_mqtt.groovy") { capability "SignalStrength" command "disconnect" attribute "healthStatus", "enum", ["offline", "online"] if (debugMode) { command "testCommand" } } } preferences { input name: "cloudActive", type: "bool", title: "Use Hildebrand Cloud MQTT Broker", defaultValue: true input name: "cloudId", type: "text", title: "Device ID:", required: true input name: "mqttBroker", type: "text", title: "Local MQTT Broker Address:", required: false input name: "mqttUser", type: "text", title: "MQTT Username:", required: false input name: "mqttPass", type: "password", title: "MQTT Password:", required: false 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") } void configureSpecifics() { // Called by main configure() method in BirdsLikeWires.library device.name = "Hildebrand Glow" state.occasionalUpdated = 0 removeDataValue("firmware") removeDataValue("mac") removeDataValue("model") removeDataValue("smetsversion") removeDataValue("zigbee") // Create child devices. fetchChild("BirdsLikeWires","Hildebrand Glow Meter","Electricity") fetchChild("BirdsLikeWires","Hildebrand Glow Meter","Gas") } void updateSpecifics() { // Called by updated() method in BirdsLikeWires.library disconnect() if (settings?.cloudActive) { state.mqttBroker = "glowmqtt.energyhive.com" state.mqttTopic = settings?.cloudId ? "SMART/HILD/${cloudId}" : "" state.parsePasses = 1 // Cloud MQTT is received as 1 message and would need parsing in one pass. } else { state.mqttBroker = settings?.mqttBroker state.mqttTopic = "glow/#" state.parsePasses = 3 // Local MQTT is received as 3 seperate messages (STATE plus two SENSORS), parsed sequentially. } state.parseCounter = 0 // We update some variables only every occasionalUpdateMinutes, so we must count our parse() passes. schedule("0/10 * * * * ? *", mqttConnect) } void disconnect() { // Disconnect from broker. interfaces.mqtt.disconnect() logging("${device} : MQTT : Disconnected from broker ${state.mqttBroker} (${state.mqttTopic}).", "info") } void uninstalled() { // Runs when device is removed. interfaces.mqtt.disconnect() logging("${device} : Uninstalled", "info") } void parse(String description) { updateHealthStatus() checkDriver() // The parse is expected to run state.parsePasses number of times. Integer parseMax = state.parsePasses Integer parseCount = state.parseCounter + 1 logging("${device} : Parse (Pass $parseCount/$parseMax) : $description", "trace") // Some values should only be updated every occasionalUpdateMinutes to reduce chatter and hub load. Boolean updateOccasional = false Integer updateCount = 0 long occasionalUpdateMillis = occasionalUpdateMinutes * 60000 long occasionalElapsedMillis = state.updatedHealthStatus - state.occasionalUpdated if (occasionalElapsedMillis > occasionalUpdateMillis || state.occasionalUpdated == 0) { updateOccasional = true logging("${device} : Parse : Threshold of $occasionalUpdateMinutes minutes reached. Updating occasional values!", "debug") } try { def msg = interfaces.mqtt.parseMessage(description) logging("${device} : Topic : ${msg.topic}", "trace") logging("${device} : Payload : ${msg.payload}", "trace") def json = new groovy.json.JsonSlurper().parseText(msg.payload) logging("${device} : JSON : ${json}", "debug") def eleMeter = fetchChild("BirdsLikeWires","Hildebrand Glow Meter","Electricity") def gasMeter = fetchChild("BirdsLikeWires","Hildebrand Glow Meter","Gas") if (settings?.cloudActive) { logging("${device} : Format : Cloud", "debug") // State int lqi = Integer.parseInt(json.pan.lqi, 16) sendEvent(name: "lqi", value: lqi) int rssi = Integer.parseInt(json.pan.rssi, 16) String twosComplement = Integer.toHexString((-1 * rssi)); rssi = zigbee.convertHexToInt(twosComplement[-2..-1]) * -1 sendEvent(name: "rssi", value: rssi) // Electricity BigDecimal cumulative = Integer.parseInt(json.elecMtr."0702"."00"."00", 16) / 1000 cumulative = cumulative.setScale(3, BigDecimal.ROUND_HALF_UP) eleMeter.parse([[name:"energy", value:cumulative]]) int power = Integer.parseInt(json.elecMtr."0702"."04"."00", 16) eleMeter.parse([[name:"power", value:power]]) //logging("${device} : cumulative : $cumulative", "warn") if (updateOccasional) { // State updateDataValue("model", "${json.hversion}") updateDataValue("smetsversion", "${json.smetsVer}") updateDataValue("zigbee", "${json.zbSoftVer}") // Electricity String eleSupplier = json.elecMtr."0708"."01"."01" eleMeter.setState([[name:"supplier", value:eleSupplier]]) // Gas String gasSupplier = json.gasMtr."0708"."01"."01" gasMeter.setState([[name:"supplier", value:gasSupplier]]) } } else { logging("${device} : Format : Local", "debug") if (msg.topic.indexOf('STATE') >= 0) { sendEvent(name: "lqi", value: "${json.han.lqi}") sendEvent(name: "rssi", value: "${json.han.rssi}") if (updateOccasional) { updateDataValue("firmware", "${json.software}") updateDataValue("mac", "${json.ethmac}") updateDataValue("model", "${json.hardware}") updateDataValue("smetsversion", "${json.smetsversion}") updateDataValue("zigbee", "${json.zigbee}") } } else if (msg.topic.indexOf('electricitymeter') >= 0) { BigDecimal cumulativeExport = json.electricitymeter.energy.export.cumulative String cumulativeExportUnits = json.electricitymeter.energy.export.units eleMeter.parse([[name:"export", value:cumulativeExport, unit: "${cumulativeExportUnits}"]]) BigDecimal cumulative = json.electricitymeter.energy.import.cumulative String cumulativeUnits = json.electricitymeter.energy.import.units eleMeter.parse([[name:"energy", value:cumulative, unit: "${cumulativeUnits}"]]) BigDecimal power = json.electricitymeter.power.value * 1000 power = power.intValue() eleMeter.parse([[name:"power", value:power, unit: "W"]]) if (updateOccasional) { eleMeter.setState([[name:"energyUnit", value:"${cumulativeUnits}"]]) eleMeter.setState([[name:"exportUnit", value:"${cumulativeExportUnits}"]]) eleMeter.setState([[name:"powerUnit", value:"W"]]) BigDecimal day = json.electricitymeter.energy.import.day BigDecimal week = json.electricitymeter.energy.import.week BigDecimal month = json.electricitymeter.energy.import.month eleMeter.parse([[name:"day", value:day]]) eleMeter.parse([[name:"week", value:week]]) eleMeter.parse([[name:"month", value:month]]) String mpan = json.electricitymeter.energy.import.mpan eleMeter.setState([[name:"mpan", value:mpan]]) BigDecimal unitrate = json.electricitymeter.energy.import.price.unitrate BigDecimal unitratePence = unitrate * 100 unitratePence = unitratePence.setScale(2, BigDecimal.ROUND_HALF_UP) eleMeter.parse([[name:"unitrate", value:unitratePence]]) eleMeter.setState([[name:"unitrateUnits", value:"p/kWh"]]) BigDecimal standingcharge = json.electricitymeter.energy.import.price.standingcharge BigDecimal standingchargePence = standingcharge * 100 standingchargePence = standingchargePence.setScale(2, BigDecimal.ROUND_HALF_UP) eleMeter.parse([[name:"standingcharge", value:standingchargePence]]) eleMeter.setState([[name:"standingchargeUnits", value:"p/day"]]) String supplier = json.electricitymeter.energy.import.supplier eleMeter.setState([[name:"supplier", value:supplier]]) } } else if (msg.topic.indexOf('gasmeter') >= 0) { if (updateOccasional) { // Gas meters only send data every 30 minutes via the electricity meter, so there's no point being verbose. BigDecimal volume = json.gasmeter.energy.import.cumulativevol gasMeter.parse([[name:"volume", value:volume]]) String volUnits = json.gasmeter.energy.import.cumulativevolunits gasMeter.setState([[name:"volumeUnits", value:"${volUnits}"]]) BigDecimal cumulative = json.gasmeter.energy.import.cumulative gasMeter.parse([[name:"energy", value:cumulative]]) String units = json.gasmeter.energy.import.units gasMeter.setState([[name:"energyUnit", value:"${units}"]]) BigDecimal day = json.gasmeter.energy.import.day BigDecimal week = json.gasmeter.energy.import.week BigDecimal month = json.gasmeter.energy.import.month gasMeter.parse([[name:"day", value:day]]) gasMeter.parse([[name:"week", value:week]]) gasMeter.parse([[name:"month", value:month]]) String mprn = json.gasmeter.energy.import.mprn gasMeter.setState([[name:"mprn", value:mprn]]) BigDecimal unitrate = json.gasmeter.energy.import.price.unitrate BigDecimal unitratePence = unitrate * 100 unitratePence = unitratePence.setScale(2, BigDecimal.ROUND_HALF_UP) gasMeter.parse([[name:"unitrate", value:unitratePence]]) gasMeter.setState([[name:"unitrateUnits", value:"p/kWh"]]) BigDecimal standingcharge = json.gasmeter.energy.import.price.standingcharge BigDecimal standingchargePence = standingcharge * 100 standingchargePence = standingchargePence.setScale(2, BigDecimal.ROUND_HALF_UP) gasMeter.parse([[name:"standingcharge", value:standingchargePence]]) gasMeter.setState([[name:"standingchargeUnits", value:"p/day"]]) String supplier = json.gasmeter.energy.import.supplier gasMeter.setState([[name:"supplier", value:supplier]]) } } } } catch (Exception e) { logging("${device} : Parse : ${e.message}", "error") } if (parseCount == parseMax) { state.parseCounter = 0 if (updateOccasional) { state.occasionalUpdated = state.updatedHealthStatus } } else { state.parseCounter = parseCount } }