import groovy.json.JsonSlurper /* * App to create and update virtual devices for those in a UPB Network with a friendly name * * Uses HTTP messages to retrieve data (JSON encoded) from the voice bridge configuration software * * Intial release: 11-Sep-22 */ definition( name: "UPB App", namespace: "UPB", author: "UPB Voice Bridge", description: "Creates virtual devices that connect to voice enabled UPB devices.", category: "Convenience", iconUrl: "", iconX2Url: "", iconX3Url: "") preferences { section { input name: "ComputerIP", type: "text", title: "Configuration computer IP address", required: true input name: "WorksWith", type: "enum", title: "Used for UPB messaging", required: true, options: ["PIM-R via iTach Flex", "PulseWorx Gateway"], defaultValue: "PIM-R via iTach Flex" input name: "PIMIP", type: "text", title: "iTach Flex IP address. Leave as 0.0.0.0 when using PulseWorx Gateway", required: true, defaultValue: "0.0.0.0" input name: "GatewayIP", type: "text", title: "PulseWorx Gateway IP Address. Leave as 0.0.0.0 when using PIM-R", required: true, defaultValue: "0.0.0.0" input name: "GatewayPort", type: "text", title: "PulseWorx Gateway Communication port", required: true, defaultValue: 80 input name: "NetworkId", type: "text", title: "UPB Network ID", required: true input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true input name: "DoGet", type: "bool", title: "Get UPB device configuration from Voice Bridge software", defaultValue: false } } // //----------------------------------------------------------------------------- // Standard stuff //----------------------------------------------------------------------------- // void installed() { if (logEnable) log.debug ("In Installed"); updated(); } void uninstalled() { // unsubscribe to all events unsubscribe(); } void eventHandler(evt) { } void GetDevices() { if (logEnable) log.debug ("In GetDevices"); } // //----------------------------------------------------------------------------- // When settings are updated //----------------------------------------------------------------------------- // void SyncConfiguration() { settings.ComputerIP = settings.ComputerIP.trim(); settings.GatewayIP = settings.GatewayIP.trim(); try { String jsonText = ""; String s = ""; Boolean done = false; int block = 0; while (done == false) { if (block == 0) s = "http://" + settings.ComputerIP + "/api/v1/GetFileStart?name=voice.dat"; else s = "http://" + settings.ComputerIP + "/api/v1/GetFileNext"; httpGet(s) { resp -> if (resp.success) { if (resp.data == "failed") { done = true; } else { if (resp.data != null) jsonText = jsonText + resp.data.text; else done = true; if (logEnable) log.debug ("block ${block} done ${done}"); } } } block++; } def jsonSlurper = new groovy.json.JsonSlurper(); def voiceInfo = jsonSlurper.parseText(jsonText); int mapSize = voiceInfo.size(); def map = voiceInfo.get('network'); def syncTime; syncTime = map.get('now'); def networkId; networkId = map.get('id'); def worksWith; worksWith = map.get('UPBXmt'); app.updateSetting("NetworkId", [value: networkId, type: "text"]); app.updateSetting("WorksWith", [value: worksWith, type: "text"]); def devs = app.getChildDevices(); devs.each { it.UpdateConnectedAt(""); } def roomName; def deviceName; def unitId; def friendlyName; def isDim; def channel; def onScene; def onDirect; def offScene; def offDirect; def twoPartName; def sceneId; def sourceId; int ctAdded; int ctRemoved; int ctUpdated; ctAdded = 0; ctRemoved = 0; ctUpdated = 0; def objectList = voiceInfo.get('devices'); objectList.each { def objectMap = it; if (logEnable) log.debug ("${objectMap.get('id')} ${objectMap.get('r')} ${objectMap.get('n')}"); enable = objectMap.get('e'); if (enable != 0) { roomName = objectMap.get('r'); deviceName = objectMap.get('n'); unitId = objectMap.get('id'); channel = objectMap.get('ch'); friendlyName = objectMap.get('v'); isDim = objectMap.get('dim'); onScene = objectMap.get('onl'); onDirect = objectMap.get('ond'); offScene = objectMap.get('offl'); offDirect = objectMap.get('offd'); twoPartName = roomName + "-" + deviceName; dev = getChildDevice("UPB_${unitId}_${channel}") if (dev == NULL) { ctAdded = ctAdded + 1; if (isDim == 1) addChildDevice("UPB", "UPB Dim Object", "UPB_${unitId}_${channel}", null, [label: friendlyName, name: twoPartName]); else addChildDevice("UPB", "UPB Non-Dim Object", "UPB_${unitId}_${channel}", null, [label: friendlyName, name: twoPartName]); dev = getChildDevice("UPB_${unitId}_${channel}"); dev.sendEvent(name: "switch", value: "off", isStateChange: false); } else { ctUpdated = ctUpdated + 1; if (logEnable) log.debug ("Found device updating"); } dev.UpdateUPBId (unitId); dev.UpdateDeviceChannel (channel); dev.UpdateConnectedAt (syncTime); dev.UpdateDeviceLabel (friendlyName); if (onScene == 0) dev.UpdateOnSceneControl(0, 0, 0); else dev.UpdateOnSceneControl(1, onScene, onDirect); if (offScene == 0) dev.UpdateOffSceneControl(0, 0, 0); else dev.UpdateOffSceneControl(1, offScene, offDirect); } } objectList = voiceInfo.get('scenes'); objectList.each { def objectMap = it; if (logEnable) log.debug ("${objectMap.get('id')} ${objectMap.get('n')}"); enable = objectMap.get('e'); if (enable != 0) { sceneName = objectMap.get('n'); sceneId = objectMap.get('id'); friendlyName = objectMap.get('v'); sourceId = objectMap.get('cid'); dev = getChildDevice("UPBS_${sceneId}") if (dev == NULL) { ctAdded = ctAdded + 1; addChildDevice("UPB", "UPB Scene", "UPBS_${sceneId}", null, [label: friendlyName, name: sceneName]); dev = getChildDevice("UPBS_${sceneId}") } else { ctUpdated = ctUpdated + 1; if (logEnable) log.debug ("Found updating"); } dev.UpdateUPBId (sceneId); dev.UpdateConnectedAt (syncTime); dev.UpdateDeviceLabel (friendlyName); dev.UpdateSourceId (sourceId); } } // Create the Command Connector if needed // Can't use the Device Network Id since that is the IP address of the Computer to get updates def connectorName; found = false; upbName = "UPB Command Connector"; devs = app.getChildDevices(); devs.each { connectorName = it.getName(); if (connectorName == upbName) { it.UpdateConnectedAt (syncTime); found = true; } } if (found == false) { ip = settings.ComputerIP; dot1 = ip.indexOf("."); dot2 = ip.indexOf(".",dot1+1); dot3 = ip.indexOf(".",dot2+1); int o1 = Integer.parseInt(ip.substring(0,dot1)); int o2 = Integer.parseInt(ip.substring(dot1+1,dot2)); int o3 = Integer.parseInt(ip.substring(dot2+1,dot3)); int o4 = 0; colon = ip.indexOf(":"); if (colon != -1) { o4 = Integer.parseInt(ip.substring(dot3+1,colon)); } else { o4 = Integer.parseInt(ip.substring(dot3+1)); } computerHexIP = Integer.toHexString(o4 + (o3*256) + (o2*256*256) + (o1*256*256*256)).toUpperCase(); if (computerHexIP.length() < 8) computerHexIP = "0" + computerHexIP; addChildDevice("UPB", "UPB Command Connector", computerHexIP, null, [name: upbName]); dev = getChildDevice(computerHexIP) dev.UpdateConnectedAt (syncTime); } def marker; devs = app.getChildDevices(); devs.each { marker = it.GetConnectedAt(); if (marker == NULL) { if (logEnable) log.debug ("Empty marker so delete ${it.getName}"); ctRemoved = ctRemoved + 1; deleteChildDevice(it.deviceNetworkId); } } log.info ("Devices added:${ctAdded} updated:${ctUpdated} removed:${ctRemoved}"); } catch (Exception e) { log.warn "Sync GetFile failed: ${e.message}"; } } void updated() { if (logEnable) log.debug ("In Updated [${settings.GatewayIP}]"); if (logEnable) log.debug ("In Updated [${settings.DoGet}]"); if (settings.DoGet) { if (logEnable) log.debug ("In DoGet"); SyncConfiguration(); } app.updateSetting("DoGet", [value: false, type: "bool"]); unsubscribe() } // //----------------------------------------------------------------------------- // Used by the drivers //----------------------------------------------------------------------------- // def GetConfigurationIP () { return (settings.ComputerIP); } def GetPIMIP () { return (settings.PIMIP); } def GetGatewayIP () { return (settings.GatewayIP); } def GetWorksWith () { if (settings.WorksWith == "PulseWorx Gateway") return (1); else return (0); } void ProcessStateUpdate(Integer isDevice, Integer id, Integer channel, Integer percent) { if (logEnable) log.debug ("In ProcessStateUpdate ${isDevice} ${id} ${channel} ${percent}"); if (isDevice) { dev = getChildDevice("UPB_${id}_{channel}") if (dev != NULL) { if (logEnable) log.debug ("Process state update found device"); if (level == 0) dev.sendEvent(name: "switch", value: "off", isStateChange: true); else dev.sendEvent(name: "switch", value: "on", isStateChange: true); dev.sendEvent(name: "level", value: level, isStateChange: true); } } else { dev = getChildDevice("UPBS_${id}") if (dev != NULL) { if (logEnable) log.debug ("Process state update found scene"); if (level == 0) dev.sendEvent(name: "switch", value: "off", isStateChange: true); else dev.sendEvent(name: "switch", value: "on", isStateChange: true); } } } def BuildGotoCommand (Integer did, Integer level, Integer channel) { byte[] command = [0x0a, 0x04, 0x00, 0x00, 0xff, 0x22, 0x00, 0xff, 0x00]; command[2] = Integer.parseInt(settings.NetworkId); command[3] = did; command[6] = level; command[8] = channel; int sum = 0; for (int iByte = 0; iByte < 9; iByte++) { sum = sum + command[iByte]; } sum = -sum; csumHex = Integer.toHexString(sum); csumHex = csumHex.substring(csumHex.length() - 2); UPBCmd = hubitat.helper.HexUtils.byteArrayToHexString(command); UPBCmd = 'T' + UPBCmd + csumHex + 'Z'; UPBCmd = UPBCmd.toUpperCase(); return (UPBCmd); } def BuildSceneActivateCommand (Integer did, Integer sid) { byte[] command = [0x87, 0x04, 0x00, 0x00, 0xff, 0x20]; command[2] = Integer.parseInt(settings.NetworkId); command[3] = did; command[4] = sid; int sum = 0; for (int iByte = 0; iByte < 6; iByte++) { sum = sum + command[iByte]; } sum = -sum; csumHex = Integer.toHexString(sum); csumHex = csumHex.substring(csumHex.length() - 2); UPBCmd = hubitat.helper.HexUtils.byteArrayToHexString(command); UPBCmd = 'T' + UPBCmd + csumHex + 'Z'; UPBCmd = UPBCmd.toUpperCase(); return (UPBCmd); } def BuildSceneDeactivateCommand (Integer did, Integer sid) { byte[] command = [0x87, 0x04, 0x00, 0x00, 0xff, 0x21]; command[2] = Integer.parseInt(settings.NetworkId); command[3] = did; command[4] = sid; int sum = 0; for (int iByte = 0; iByte < 6; iByte++) { sum = sum + command[iByte]; } sum = -sum; csumHex = Integer.toHexString(sum); csumHex = csumHex.substring(csumHex.length() - 2); UPBCmd = hubitat.helper.HexUtils.byteArrayToHexString(command); UPBCmd = 'T' + UPBCmd + csumHex + 'Z'; UPBCmd = UPBCmd.toUpperCase(); return (UPBCmd); }