def CommandDev(action, devKey, actList, ruleId): if devKey == None: log.fault("Device " + actList[1] + " from rules not found in devices") synopsis.problem("rules", "Unknown device " + actList[1] + " in rules") else: devices.DelTempVal( devKey, "SwitchOff@") # Kill any extant timers for this device if action == "SwitchOn".lower(): database.NewEvent(devKey, "SwitchOn", "Rule:" + str(ruleId)) devcmds.SwitchOn(devKey) if len(actList) > 3: if actList[2] == "for": SetOnDuration(devKey, int(actList[3], 10)) elif action == "SwitchOff".lower(): database.NewEvent(devKey, "SwitchOff", "Rule:" + str(ruleId)) devcmds.SwitchOff(devKey) elif action == "Toggle".lower(): #database.NewEvent(devKey, "Toggle", "Rule:"+str(ruleId)) # Removed, otherwise Toggle function can't work out old state! devcmds.Toggle(devKey) elif action == "Dim".lower() and actList[2] == "to": database.NewEvent(devKey, "Dim", "Rule:" + str(ruleId)) devcmds.Dim(devKey, float(actList[3])) if len(actList) > 5: if actList[4] == "for": SetDimDuration(devKey, int(actList[5], 10)) elif action == "HueSat".lower( ): # Syntax is "do HueSat <Hue in degrees>,<fractional saturation> database.NewEvent(devKey, "HueSat", "Rule:" + str(ruleId)) devcmds.Colour(devKey, int(actList[3], 10), float(actList[4])) else: log.debug("Unknown action: " + action + " for device: " + actList[1])
def do_off(self, devId): """off name Sends off command to named device""" devKey = devices.FindDev(devId) if devKey != None: database.NewEvent(devKey, "SwitchOff", "UICmd") devcmds.SwitchOff(devKey)
def SetAttrVal(devKey, clstrId, attrId, value): global msp_ota if clstrId == zcl.Cluster.PowerConfig and attrId == zcl.Attribute.Batt_Percentage: SetTempVal(devKey, "GetNextBatteryAfter", datetime.now() + timedelta(seconds=86400)) # Ask for battery every day if value != "FF": try: varVal = int( int(value, 16) / 2 ) # Arrives in 0.5% increments, but drop fractional component except ValueError: varVal = None if varVal != None: log.debug("Battery is " + str(varVal) + "%. Get next reading at " + str(GetTempVal(devKey, "GetNextBatteryAfter"))) database.LogItem(devKey, "BatteryPercentage", varVal) # For web page lowBatt = int(config.Get("lowBattery", "5")) if varVal < lowBatt: devName = database.GetDeviceItem(devKey, "userName") synopsis.problem( devName + "_batt", devName + " low battery (" + str(varVal) + "%)") if clstrId == zcl.Cluster.Temperature and attrId == zcl.Attribute.Celsius: if value != "FF9C" and value != "8000": # Don't know where this value (of -100) comes from, but seems to mean "Illegal temp", although it should be -1'C try: varVal = int(value, 16) / 100 # Arrives in 0.01'C increments database.LogItem(devKey, "TemperatureCelsius", varVal) # For web page except ValueError: log.debug("Bad temperature of " + value) if clstrId == zcl.Cluster.OnOff and attrId == zcl.Attribute.OnOffState: if isnumeric(value, 16): oldState = database.GetLatestLoggedValue(devKey, "State") if int(value, 16) == 0: newState = "SwitchOff" else: newState = "SwitchOn" if oldState != newState: database.UpdateLoggedItem( devKey, "State", newState) # So that we can access it from the rules later database.NewEvent(devKey, newState) Rule(devKey, newState) expectedState = GetTempVal(devKey, "ExpectOnOff") if expectedState != None: if newState != expectedState: if expectedState == "SwitchOn": devcmds.SwitchOn(devKey) # Re-issue command else: # Assume SwitchOff devcmds.SwitchOff(devKey) # Re-issue command else: # We got the expected result DelTempVal(devKey, "ExpectOnOff") if clstrId == zcl.Cluster.Time and attrId == zcl.Attribute.LocalTime: if isnumeric(value, 16): varVal = int(value, 16) # Arrives in Watts, so store it in the same way log.debug("Raw time:" + str(varVal)) timeStr = iottime.FromZigbee(varVal) log.debug("Human time:" + timeStr) database.UpdateLoggedItem(devKey, "Time", timeStr) # Just store latest time string if clstrId == zcl.Cluster.SimpleMetering and attrId == zcl.Attribute.InstantaneousDemand: if isnumeric(value, 16): varVal = int(value, 16) # Arrives in Watts, so store it in the same way inClstr = database.GetDeviceItem( devKey, "inClusters" ) # Assume we have a list of clusters if we get this far if zcl.Cluster.OnOff not in inClstr: # Thus device is powerclamp (has simplemetering but no OnOff) database.UpdateLoggedItem( devKey, "State", str(varVal) + "W" ) # So that we can access it from the rules later, or show it on the web database.UpdateLoggedItem(devKey, "PowerReadingW", varVal) # Just store latest reading if clstrId == zcl.Cluster.SimpleMetering and attrId == zcl.Attribute.CurrentSummationDelivered: if isnumeric(value, 16): varVal = int( value, 16 ) # Arrives in accumulated WattHours, so store it in the same way database.LogItem(devKey, "EnergyConsumedWh", varVal) if clstrId == zcl.Cluster.IAS_Zone and attrId == zcl.Attribute.Zone_Type: database.SetDeviceItem(devKey, "iasZoneType", value) if clstrId == zcl.Cluster.Basic: if attrId == zcl.Attribute.Model_Name: database.SetDeviceItem(devKey, "modelName", value) if attrId == zcl.Attribute.Manuf_Name: database.SetDeviceItem(devKey, "manufName", value) if clstrId == zcl.Cluster.OTA or clstrId == msp_ota: if attrId == zcl.Attribute.firmwareVersion: database.SetDeviceItem(devKey, "firmwareVersion", value) if clstrId == zcl.Cluster.PollCtrl: if attrId == zcl.Attribute.LongPollIntervalQs: varVal = str(float(int(value, 16) / 4)) # Value arrives in units of quarter seconds database.SetDeviceItem( devKey, "longPollInterval", varVal ) # For web page and also to see whether to wait for CheckIn or just send messages (if <6 secs) if clstrId == zcl.Cluster.Thermostat: if attrId == zcl.Attribute.LocalTemp: if isnumeric(value, 16): varVal = int(value, 16) / 100 # Arrives in 0.01'C increments database.LogItem(devKey, "SourceCelsius", varVal) # For web page src = varVal tgt = database.GetLatestLoggedValue(devKey, "TargetCelsius") database.UpdateLoggedItem( devKey, "State", "source " + str(src) + "'C/target " + str(tgt) + "'C") # So that we can show it on the web if attrId == zcl.Attribute.OccupiedHeatingSetPoint: if isnumeric(value, 16): varVal = int(value, 16) / 100 # Arrives in 0.01'C increments database.LogItem(devKey, "TargetCelsius", varVal) # For web page tgt = varVal src = database.GetLatestLoggedValue(devKey, "SourceCelsius") database.UpdateLoggedItem( devKey, "State", "source " + str(src) + "'C/target " + str(tgt) + "'C") # So that we can show it on the web if clstrId == zcl.Cluster.Time: if attrId == zcl.Attribute.Time: if isnumeric(value, 16): varVal = int(value, 16) # Arrives in seconds since 1st Jan 2000 timeStamp = iottime.FromZigbee(varVal) database.LogItem(devKey, "time", str(timeStamp)) # For web page
def EventHandler(eventId, eventArg): global ephemera, globalDevKey, pendingBinding, pendingBindingTimeoutS, pendingRptAttrId, msp_ota if eventId == events.ids.PREINIT: keyList = database.GetAllDevKeys( ) # Get a list of all the device identifiers from the database for i in range( 100): # Fudge to ensure we have enough pendingBinding entries pendingBinding.append( "" ) # Problem is that the devKey indices aren't consecutive (removed devices) and so pendingBindingTimeoutS.append( 0 ) # using the keyList isn't the same as for x in range(maxDevKey) for devKey in keyList: # Hub and devices Init(devKey) # Initialise dictionary and associated ephemera if database.GetDeviceItem(devKey, "nwkId") != "0000": # Ignore hub SetTempVal( devKey, "GetNextBatteryAfter", datetime.now()) # Ask for battery shortly after startup if eventId == events.ids.INIT: msp_ota = config.Get("MSP_OTA") if eventId == events.ids.DEVICE_ANNOUNCE: if len(eventArg) >= 3: eui64 = eventArg[1] nwkId = eventArg[2] devKey = GetKey(nwkId) if devKey == None: # Which will only be the case if we've not seen this short Id before devKey = database.GetDevKey("eui64", eui64) if devKey == None: # Which will be the case if we've not seen the long Id either devKey = Add(nwkId, eui64, eventArg[0]) log.debug("New key for new device is " + str(devKey)) if eventArg[0] == "SED": SetTempVal(devKey, "PollingUntil", datetime.now() + timedelta(seconds=300)) events.Issue( events.ids.NEWDEVICE, devKey ) # Tell everyone that a new device has been seen, so it can be initialised else: # Same long Id, but short Id needs updating after it has changed database.SetDeviceItem(devKey, "nwkId", nwkId) else: NoteMsgDetails(devKey, eventArg) SetTempVal( devKey, "GetNextBatteryAfter", datetime.now() ) # Ask for battery shortly after Device Announce, either new or old one re-joining if eventId == events.ids.CHECKIN: # See if we have anything to ask the device... if len(eventArg) >= 3: endPoint = eventArg[2] seq = "00" # was seq = eventArg[3], but that's the RSSI devKey = GetKey(eventArg[1]) if devKey != None: EnsureInBinding( devKey, zcl.Cluster.PollCtrl ) # Assume CheckIn means PollCtrl must be in binding, so make sure this is up-to-date NoteMsgDetails(devKey, eventArg) if database.GetDeviceItem(devKey, "endPoints") == None: database.SetDeviceItem( devKey, "endPoints", endPoint ) # Note endpoint that CheckIn came from, unless we already know this nwkId = database.GetDeviceItem(devKey, "nwkId") cmdRsp = Check( devKey ) # Check to see if we want to know anything about the device if cmdRsp != None: log.debug("Keep awake for 10 secs so we can send " + cmdRsp[0]) queue.Jump(devKey, [ "AT+RAWZCL:" + nwkId + "," + endPoint + ",0020,11" + seq + "00012800", "DFTREP" ]) # Tell device to enter Fast Poll for 40qs (==10s) SetTempVal(devKey, "PollingUntil", datetime.now() + timedelta(seconds=10)) queue.EnqueueCmd( devKey, cmdRsp) # This will go out after the Fast Poll Set else: SetTempVal( devKey, "PollingUntil", datetime.now() + timedelta(seconds=2) ) # Say that it's polling for a short while, so that we can tell it to stop(!) queue.EnqueueCmd(devKey, [ "AT+RAWZCL:" + nwkId + "," + endPoint + ",0020,11" + seq + "00000100", "DFTREP" ]) # Tell device to stop Poll else: # Unknown device, so assume it's been deleted from our database telegesis.Leave( eventArg[1] ) # Tell device to leave the network, since we don't know anything about it if eventId == events.ids.TRIGGER or eventId == events.ids.BUTTON: if len(eventArg) >= 2: devKey = GetKey( eventArg[1] ) # Lookup device from network address in eventArg[1] if devKey != None: SetTempVal( devKey, "PollingUntil", datetime.now() + timedelta(seconds=1) ) # Say that it's polling for a very short while so that we can try to set up a PollCtrl cluster if eventId == events.ids.RXMSG: if eventArg[0] == "AddrResp" and eventArg[1] == "00" and len( eventArg) >= 3: devKey = GetKey(eventArg[2]) if devKey != None: database.SetDeviceItem(devKey, "eui64", eventArg[1]) elif eventArg[0] == "ActEpDesc" and len(eventArg) >= 3: if "00" == eventArg[2]: devKey = GetKey(eventArg[1]) if devKey != None: database.SetDeviceItem(devKey, "endPoints", eventArg[3]) # Note first endpoint elif eventArg[0] == "SimpleDesc" and len(eventArg) >= 3: if "00" == eventArg[2]: globalDevKey = GetKey( eventArg[1] ) # Is multi-line response, so expect rest of response and use this global index until it's all finished elif "82" == eventArg[2]: # 82 == Invalid endpoint devKey = GetKey(eventArg[1]) events.Issue(events.ids.RXERROR, int( eventArg[2], 16)) # Tell system that we're aborting this command elif eventArg[0] == "InCluster" and len(eventArg) >= 2: if globalDevKey != None: database.SetDeviceItem( globalDevKey, "inClusters", str(eventArg[1:]) ) # Store whole list from arg[1] to arg[n] elif eventArg[0] == "OutCluster" and len(eventArg) >= 2: if globalDevKey != None: NoteMsgDetails( globalDevKey, eventArg ) # Must do this so that we can remove RSSI and LQI if they're there, to avoid these values being interpreted as clusters database.SetDeviceItem( globalDevKey, "outClusters", str(eventArg[1:]) ) # Store whole list from arg[1] to arg[n] globalDevKey = None # We've finished with this global for now elif eventArg[0] == "RESPATTR" and len(eventArg) >= 7: devKey = GetKey(eventArg[1]) if devKey != None: NoteMsgDetails(devKey, eventArg) if len( eventArg ) >= 7: # Check for number of args after possibly removing RSSI and LQI ep = eventArg[2] clusterId = eventArg[3] attrId = eventArg[4] if "00" == eventArg[5]: attrVal = eventArg[6] SetAttrVal(devKey, clusterId, attrId, attrVal) else: SetAttrVal(devKey, clusterId, attrId, "Failed (error " + eventArg[5] + ")") # So that we don't keep asking elif eventArg[0] == "RESPMATTR" and len(eventArg) >= 8: devKey = GetKey(eventArg[1]) if devKey != None: NoteMsgDetails(devKey, eventArg) if len( eventArg ) >= 8: # Check for number of args after possibly removing RSSI and LQI ep = eventArg[2] mfgId = eventArg[3] clusterId = eventArg[4] attrId = eventArg[5] if "00" == eventArg[6]: attrVal = eventArg[7] SetAttrVal(devKey, clusterId, attrId, attrVal) elif eventArg[0] == "REPORTATTR" and len(eventArg) >= 7: devKey = GetKey(eventArg[1]) if devKey != None: ep = eventArg[2] clusterId = eventArg[3] attrId = eventArg[4] attrType = eventArg[5] attrVal = eventArg[6] if clusterId == zcl.Cluster.MultistateInput and attrId == zcl.Attribute.PresentValue: args = [attrVal, eventArg[1]] events.Issue(events.ids.MULTISTATE, args) return # Ignore reports on Basic cluster (eg lumi.sensor_cube when it joins will send this) #if clusterId == zcl.Cluster.Basic: # return # Ignore reports on Basic cluster (eg lumi.sensor_cube when it joins will send this) NoteMsgDetails(devKey, eventArg) EnsureReporting( devKey, clusterId, attrId, attrVal ) # Make sure reports are happening at the correct frequency and update device if not SetAttrVal(devKey, clusterId, attrId, attrVal) NoteReporting(devKey, clusterId, attrId) else: # Unknown device, so assume it's been deleted from our database telegesis.Leave( eventArg[1] ) # Tell device to leave the network, since we don't know anything about it elif eventArg[0] == "Bind" and len( eventArg) >= 2: # Binding Response from device devKey = GetKey(eventArg[1]) if devKey != None: if pendingBinding[devKey]: binding = eval( database.GetDeviceItem(devKey, "binding", "[]")) if pendingBinding[ devKey] not in binding: # Only put it in once, even if we get multiple responses binding.append(pendingBinding[devKey]) database.SetDeviceItem(devKey, "binding", str(binding)) pendingBinding[devKey] = None elif eventArg[0] == "CFGRPTRSP" and len( eventArg) >= 5: # Configure Report Response from device devKey = GetKey(eventArg[1]) status = eventArg[4] if devKey != None and status == "00": clusterId = eventArg[3] attrId = pendingRptAttrId # Need to remember this, since it doesn't appear in CFGRPTRSP NoteReporting(devKey, clusterId, attrId) pendingRptAttrId = None # Ready for the next report elif eventArg[0] == "CWSCHEDULE": heating.ParseCWShedule(eventArg) elif eventArg[0] == "DFTREP": devKey = GetKey(eventArg[1]) NoteMsgDetails(devKey, eventArg) #else: # Unrecognised message, but we still want to extract OOB info # if len(eventArg) >= 2: # devKey = GetKey(eventArg[1]) # Assume this is sensible # if devKey != None: # NoteMsgDetails(devKey, eventArg) #if eventId == events.ids.BUTTON: # devKey = GetKey(eventArg[1]) # Lookup device from network address in eventArg[1] # NoteMsgDetails(devKey, eventArg) if eventId == events.ids.RXERROR: globalDevKey = None # We've finished with this global if we get an error if eventId == events.ids.SECONDS: for devKey in devDict: # Go through devDict, pulling out each entry if devDict[devKey] >= 0: # Make sure device hasn't been deleted if IsListening(devKey): # True if FFD, ZED or Polling devIndex = GetIndexFromKey(devKey) if expRsp[ devIndex] == None: # We don't have a message in flight if queue.IsEmpty(devKey): cmdRsp = Check(devKey) if cmdRsp: queue.EnqueueCmd( devKey, cmdRsp ) # Queue up anything we ought to know cmdRsp = queue.DequeueCmd( devKey) # Pull first item from queue if cmdRsp != None: log.debug("Sending " + str(cmdRsp)) expRsp[devIndex] = cmdRsp[1] # Note response expRspTimeoutS[ devIndex] = 2 # If we've not heard back after 2 seconds, it's probably got lost, so try again telegesis.TxCmd(cmdRsp[0]) # Send command directly else: # We're expecting a response, so time it out expRspTimeoutS[ devIndex] = expRspTimeoutS[devIndex] - eventArg if expRspTimeoutS[devIndex] <= 0: expRsp[devIndex] = None if pendingBinding[ devKey]: # Make sure we timeout pendingBinding pendingBindingTimeoutS[ devKey] = pendingBindingTimeoutS[devKey] - eventArg if pendingBindingTimeoutS[devKey] <= 0: pendingBinding[devKey] = None offAt = GetTempVal(devKey, "SwitchOff@") if offAt: if datetime.now() >= offAt: DelTempVal(devKey, "SwitchOff@") devcmds.SwitchOff(devKey) fadeDownAt = GetTempVal(devKey, "FadeDown@") if fadeDownAt: if datetime.now() >= fadeDownAt: DelTempVal(devKey, "FadeDown@") devcmds.Dim(devKey, 0) devcmds.SwitchOff(devKey) # Switch off after dim command pirOffAt = GetTempVal(devKey, "PirInactive@") if pirOffAt: if datetime.now() >= pirOffAt: DelTempVal(devKey, "PirInactive@") newState = "inactive" database.NewEvent(devKey, newState) Rule(devKey, newState)