/* ===== Blebox Hubitat Integration Driver 2021 Updates Copyright 2021, Dave Gutheinz This driver has been created on the basis of multisensor driver developed by David Gutheiz 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. DISCLAIMER: The author of this integration is not associated with blebox. This code uses the blebox open API documentation for development and is intended for integration into the Hubitat Environment. This driver is based on Blebox multisensor driver developed by David Gutheinz @devegut ===== Hiatory ===== */ // ===== Definitions, Installation and Updates ===== def driverVer() { return "2.0.6" } def apiLevel() { return 20220505 } // bleBox latest API Level, 5.5.2022 metadata { definition (name: "bleBox luxSensor", namespace: "ultrasmart", author: "Mariusz Jarczak", importUrl: "https://raw.githubusercontent.com/MarioHudds/hubitat/refs/heads/master/bleBox%20luxSensor" ) { capability "Refresh" capability "IlluminanceMeasurement" attribute "commsError", "bool" // Illumination attribute "illuminance", "number" attribute "illuminanceAvg", "number" attribute "illuminanceMax", "number" attribute "luxName", "string" } preferences { input ("statusLed", "bool", title: "Enable the Status LED", defaultValue: true) input ("nameSync", "enum", title: "Synchronize Names", defaultValue: "none", options: ["none": "Don't synchronize", "device" : "bleBox device name master", "hub" : "Hubitat label master"]) input ("refreshInterval", "enum", title: "Device Refresh Interval (minutes)", options: ["1", "5", "15", "30"], defaultValue: "30") input ("debug", "bool", title: "Enable debug logging", defaultValue: true) input ("descriptionText", "bool", title: "Enable description text logging", defaultValue: true) } } def installed() { logInfo("Installing...") sendGetCmd("/api/settings/state", "updateSensorData") runIn(5, updated) } def updated() { logInfo("Updating...") unschedule() state.errorCount = 0 updateDataValue("driverVersion", driverVer()) // Check apiLevel and provide state warning when old. if (apiLevel() > getDataValue("apiLevel")) { state.apiNote = "Device api level should be 20220505"// apiLevel } else { state.remove("apiNote") } // update data based on preferences switch(refreshInterval) { case "1" : runEvery1Minute(refresh); break case "5" : runEvery5Minutes(refresh); break case "15" : runEvery15Minutes(refresh); break default: runEvery30Minutes(refresh) } logInfo("Refresh interval set for every ${refreshInterval} minute(s).") if (debug) { runIn(1800, debugOff) } logInfo("Debug logging is: ${debug}.") logInfo("Description text logging is ${descriptionText}.") setDevice() runIn(2, refresh) } def setDevice() { logDebug("setDevice: statusLed: ${statusLed}, nameSync = ${nameSync}") // Led def command = "/api/settings/set" def cmdText = """{"settings":{""" def ledEnabled = 1 if (statusLed == false) { ledEnabled = 0 } cmdText = cmdText + """"statusLed":{"enabled":${ledEnabled}}""" // Name if (nameSync == "hub") { cmdText = cmdText + ""","deviceName":"${device.label}"}}""" } cmdText = cmdText + """}}""" sendPostCmd(command, cmdText, "updateDeviceSettings") } def updateDeviceSettings(response) { def cmdResponse = parseInput(response) logDebug("updateDeviceSettings: ${cmdResponse}") // Work around for null response due to shutter box returning null. if (cmdResponse == null) { if (state.nullResp == true) { return } state.nullResp = true pauseExecution(1000) sendGetCmd("/api/settings/state", "updateDeviceSettings") return } state.nullResp = false // Capture Data def ledEnabled def deviceName if (cmdResponse.settings) { ledEnabled = cmdResponse.settings.statusLed.enabled deviceName = cmdResponse.settings.deviceName } else { logWarn("updateSettings: Setting data not read properly. Check apiLevel.") return } def settingsUpdate = [:] // Led Status def statusLed = true if (ledEnabled == 0) { statusLed = false } device.updateSetting("statusLed",[type:"bool", value: statusLed]) settingsUpdate << ["statusLed": statusLed] // Name - only update if syncing name if (nameSync != "none") { device.setLabel(deviceName) settingsUpdate << ["HubitatName": deviceName] device.updateSetting("nameSync",[type:"enum", value:"none"]) } logInfo("updateDeviceSettings: ${settingsUpdate}") } // ===== Commands and Parse Returns ===== def refresh() { logDebug("refresh.") sendGetCmd("/state", "updateSensorData") } def updateSensorData(response) { def cmdResponse = parseInput(response) logDebug("updateSensorData: cmdResponse = ${cmdResponse}") if (!cmdResponse?.multiSensor?.sensors) { logWarn "updateSensorData: No sensor data found." return } def sensorData = cmdResponse.multiSensor.sensors def illuminanceValues = [:] sensorData.each { sensor -> if (sensor.type == "illuminance") { illuminanceValues.illuminance = sensor.value } else if (sensor.type == "illuminanceAvg") { illuminanceValues.illuminanceAvg = sensor.value } else if (sensor.type == "illuminanceMax") { illuminanceValues.illuminanceMax = sensor.value } } if (illuminanceValues) { def sensorNumber = 1 // Assuming we only get data for a single lux sensor ID:0 def luxName = "Lux Sensor 1" // Default lux sensor name def scaledIlluminance def scaledIlluminanceAvg def scaledIlluminanceMax if (illuminanceValues.illuminance != null) { if (illuminanceValues.illuminance > 0 && illuminanceValues.illuminance <= 1000) { scaledIlluminance = String.format("%.1f", (illuminanceValues.illuminance / 100)) } else { scaledIlluminance = Math.round(illuminanceValues.illuminance / 100).toInteger() } } if (illuminanceValues.illuminanceAvg != null) { if (illuminanceValues.illuminanceAvg > 0 && illuminanceValues.illuminanceAvg <= 1000) { scaledIlluminanceAvg = String.format("%.1f", (illuminanceValues.illuminanceAvg / 100)) } else { scaledIlluminanceAvg = Math.round(illuminanceValues.illuminanceAvg / 100).toInteger() } } if (illuminanceValues.illuminanceMax != null) { if (illuminanceValues.illuminanceMax > 0 && illuminanceValues.illuminanceMax <= 1000) { scaledIlluminanceMax = String.format("%.1f", (illuminanceValues.illuminanceMax / 100)) } else { scaledIlluminanceMax = Math.round(illuminanceValues.illuminanceMax / 100).toInteger() } } if (illuminanceValues.illuminance != null) { sendEvent(name: "illuminance", value: scaledIlluminance, unit: "lux") sendEvent(name: "illuminanceAvg", value: scaledIlluminanceAvg, unit: "lux") sendEvent(name: "illuminanceMax", value: scaledIlluminanceMax, unit: "lux") sendEvent(name: "luxName", value: luxName) logDebug("updateSensorData: Sensor ${sensorNumber}, illuminance: ${scaledIlluminance}, avg: ${scaledIlluminanceAvg}, max: ${scaledIlluminanceMax}, name: ${luxName}") } else { logWarn("updateSensorData: illuminance data not available.") } } } // ===== Communications ===== private sendGetCmd(command, action){ logDebug("sendGetCmd: ${command} / ${action} / ${getDataValue("deviceIP")}") runIn(3, setCommsError) sendHubCommand(new hubitat.device.HubAction("GET ${command} HTTP/1.1\r\nHost: ${getDataValue("deviceIP")}\r\n\r\n", hubitat.device.Protocol.LAN, null,[callback: action])) } private sendPostCmd(command, body, action){ logDebug("sendPostCmd: ${command} / ${body} / ${action} / ${getDataValue("deviceIP")}") runIn(3, setCommsError) def parameters = [ method: "POST", path: command, protocol: "hubitat.device.Protocol.LAN", body: body, headers: [ Host: getDataValue("deviceIP") ]] sendHubCommand(new hubitat.device.HubAction(parameters, null, [callback: action])) } def parseInput(response) { unschedule(setCommsError) state.errorCount = 0 if (device.currentValue("commsError") == "true") { sendEvent(name: "commsError", value: false) } try { if(response.body == null) { return } def jsonSlurper = new groovy.json.JsonSlurper() return jsonSlurper.parseText(response.body) } catch (error) { logWarn "parseInput: Error attempting to parse: ${error}." } } def setCommsError() { logDebug("setCommsError") if (state.errorCount < 3) { state.errorCount+= 1 } else if (state.errorCount == 3) { state.errorCount += 1 sendEvent(name: "commsError", value: true) logWarn "setCommsError: Three consecutive communications errors." } } // ===== Utility Methods ===== def logTrace(msg) { log.trace "${device.label} ${driverVer()} ${msg}" } def logInfo(msg) { if (descriptionText == true) { log.info "${device.label} ${driverVer()} ${msg}" } } def logDebug(msg){ if(debug == true) { log.debug "${device.label} ${driverVer()} ${msg}" } } def debugOff() { device.updateSetting("debug", [type:"bool", value: false]) logInfo("debugLogOff: Debug logging is off.") } def logWarn(msg){ log.warn "${device.label} ${driverVer()} ${msg}" } // end-of-fileOK.