def SwitchOff(devIdx): devId = GetVal(devIdx, "devId") ep = GetVal(devIdx, "EP") if devId and ep: DelTempVal( devIdx, "SwitchOff@" ) # Remove any pending "Off" events if we're turning the device off directly telegesis.TxCmd( ["AT+LCMVTOLEV:" + devId + "," + ep + ",0,0,FE,0001", "OK"]) # Ensure fully bright ready to be turned on later SetTempVal(devIdx, "JustSentOnOff", "True") telegesis.TxCmd(["AT+RONOFF:" + devId + "," + ep + ",0,0", "OK"]) # Assume FFD if it supports OnOff cluster
def SwitchOn(devIdx): devId = GetVal(devIdx, "devId") ep = GetVal(devIdx, "EP") if devId and ep: SetTempVal(devIdx, "JustSentOnOff", "True") telegesis.TxCmd(["AT+RONOFF:" + devId + "," + ep + ",0,1", "OK"]) # Assume FFD if it supports OnOff cluster
def Dim(devIdx, levelFraction): devId = GetVal(devIdx, "devId") ep = GetVal(devIdx, "EP") if devId and ep: levelStr = format(int(levelFraction * 254), 'X') telegesis.TxCmd([ "AT+LCMVTOLEV:" + devId + "," + ep + ",0,1," + levelStr + ",000A", "OK" ]) # Fade over 1 sec (in 10ths)
def Toggle(devIdx): devId = GetVal(devIdx, "devId") ep = GetVal(devIdx, "EP") if devId and ep: DelTempVal( devIdx, "SwitchOff@" ) # Remove any pending "Off" events if we're handling the device directly SetTempVal(devIdx, "JustSentOnOff", "True") telegesis.TxCmd(["AT+RONOFF:" + devId + "," + ep + ",0", "OK"]) # Assume FFD if it supports OnOff cluster
def SendPendingCommand(): global info devIdx = 0 for device in info: if IsListening(devIdx): # True if FFD, ZED or Polling offTime = GetTempVal(devIdx, "SwitchOff@") if offTime != None: if datetime.now() > offTime: SwitchOff(devIdx) cmdRsp = Check(devIdx, True) # Automatically consume any pending command if cmdRsp != None: log.log("Sending " + str(cmdRsp)) telegesis.TxCmd(cmdRsp) # Send command directly devIdx = devIdx + 1
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)
def do_at(self, line): """at cmd "Sends AT command to Telegesis stick""" telegesis.TxCmd(["AT" + line, "OK"])
def do_open(self, line): """open Opens network (for 60s) to allow new device to join""" telegesis.TxCmd(["AT+PJOIN", "OK"])
def EventHandler(eventId, eventArg): global info, dirty, globalDevIdx, pendingBinding, pendingRptAttrId, statusUpdate if eventId == events.ids.INIT: try: with open(devFilename, "r") as f: try: info = eval( f.read()) # Load previous cache of devices into info[] log.log("Loaded list from file") except: log.fault("Unusable device list from file - discarding!") info = [] except OSError: info = [] dirty = False # Info[] is initialised now devIdx = 0 database.ClearDevices() rowId = database.NewDevice("0000") database.UpdateDevice(rowId, "UserName", "Hub") for devices in info: ephemera.append([]) # Initialise parallel ephemeral device list InitDevStatus(devIdx) # Initialise parallel device status CopyDevToDB(devIdx) SetTempVal( devIdx, "LastSeen", datetime.now() ) # Mark it as "seen at start up" so we don't start looking for it immediately devIdx = devIdx + 1 CheckAllAttrs() # Set up any useful variables for the loaded devices if eventId == events.ids.DEVICE_ANNOUNCE: devId = eventArg[2] devIdx = GetIdx(devId) if devIdx == None: # Which will only be the case if this device is actually new, but it may have just reset and announced devIdx = InitDev(devId) SetUserNameFromDevIdx( devIdx, "(New) " + devId) # Default username of network ID, since that's unique SetVal(devIdx, "DevType", eventArg[0]) # SED, FFD or ZED SetVal(devIdx, "EUI", eventArg[1]) if eventArg[0] == "SED": SetTempVal(devIdx, "PollingUntil", datetime.now() + timedelta(seconds=300)) else: NoteEphemera(devIdx, eventArg) if eventId == events.ids.CHECKIN: # See if we have anything to ask the device... endPoint = eventArg[2] seq = "00" # was seq = eventArg[3], but that's the RSSI devIdx = GetIdx(eventArg[1]) if devIdx != None: NoteEphemera(devIdx, eventArg) if GetVal(devIdx, "EP") == None: SetVal( devIdx, "EP", endPoint ) # Note endpoint that CheckIn came from, unless we already know this devId = GetVal(devIdx, "devId") cmdRsp = Check( devIdx, False ) # Check to see if we want to know anything about the device if cmdRsp != None: log.log("Want to know " + str(cmdRsp)) telegesis.TxCmd([ "AT+RAWZCL:" + devId + "," + endPoint + ",0020,11" + seq + "00012800", "OK" ]) # Tell device to enter Fast Poll for 40qs (==10s) SetTempVal(devIdx, "PollingUntil", datetime.now() + timedelta(seconds=10)) telegesis.TxCmd( cmdRsp ) # This will go out after the Fast Poll Set - but possibly ought to go out as part of SECONDS handler..? else: #log.log("Don't want to know anything about "+GetUserNameFromDevIdx(devIdx)) telegesis.TxCmd([ "AT+RAWZCL:" + devId + "," + endPoint + ",0020,11" + seq + "00000100", "OK" ]) # Tell device to stop Poll if eventId == events.ids.RXMSG: if eventArg[0] == "AddrResp" and eventArg[1] == "00": devIdx = GetIdx(eventArg[2]) if devIdx != None: SetVal(devIdx, "EUI", eventArg[3]) elif eventArg[0] == "ActEpDesc": if "00" == eventArg[2]: devIdx = GetIdx(eventArg[1]) if devIdx != None: SetVal(devIdx, "EP", eventArg[3]) # Note first endpoint elif eventArg[0] == "SimpleDesc": if "00" == eventArg[2]: globalDevIdx = GetIdx( 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 devIdx = GetIdx(eventArg[1]) DelVal(devIdx, "EP") events.Issue(events.ids.RXERROR, int( eventArg[2], 16)) # Tell system that we're aborting this command elif eventArg[0] == "InCluster": if globalDevIdx != None: SetVal(globalDevIdx, "InCluster", eventArg[1:]) # Store whole list from arg[1] to arg[n] elif eventArg[0] == "OutCluster": if globalDevIdx != None: NoteEphemera(globalDevIdx, eventArg) SetVal(globalDevIdx, "OutCluster", eventArg[1:]) # Store whole list from arg[1] to arg[n] globalDevIdx = None # We've finished with this global for now if eventArg[0] == "RESPATTR": devIdx = GetIdx(eventArg[1]) if devIdx != None: NoteEphemera(devIdx, eventArg) ep = eventArg[2] clusterId = eventArg[3] attrId = eventArg[4] if "00" == eventArg[5]: attrVal = eventArg[6] else: attrVal = "Error:" + eventArg[ 5] # So that we don't ask for the same attribute again later SetAttrVal(devIdx, clusterId, attrId, attrVal) if eventArg[0] == "REPORTATTR": devIdx = GetIdx(eventArg[1]) if devIdx != None: ep = eventArg[2] clusterId = eventArg[3] attrId = eventArg[4] attrType = eventArg[5] attrVal = eventArg[6] NoteEphemera(devIdx, eventArg) SetAttrVal(devIdx, clusterId, attrId, attrVal) reporting = GetVal( devIdx, "Reporting" ) # See if we're expecting this report, and note it in the reporting table if reporting != None: newRpt = clusterId + ":" + attrId if newRpt not in reporting: reporting.append(newRpt) SetVal(devIdx, "Reporting", reporting) else: SetVal(devIdx, "Reporting", []) # Ready for next time if eventArg[0] == "Bind": # Binding Response from device devIdx = GetIdx(eventArg[1]) if devIdx != None: if pendingBinding != None: binding = GetVal(devIdx, "Binding") binding.append(pendingBinding) SetVal(devIdx, "Binding", binding) pendingBinding = None if eventArg[0] == "CFGRPTRSP": # Configure Report Response from device devIdx = GetIdx(eventArg[1]) status = eventArg[4] if devIdx != None and status == "00": clusterId = eventArg[3] attrId = pendingRptAttrId # Need to remember this, since it doesn't appear in CFGRPTRSP reporting = GetVal(devIdx, "Reporting") newRpt = clusterId + ":" + attrId if newRpt not in reporting: reporting.append(newRpt) SetVal(devIdx, "Reporting", reporting) if eventId == events.ids.BUTTON: devIdx = GetIdx( eventArg[1]) # Lookup device from network address in eventArg[1] NoteEphemera(devIdx, eventArg) if eventId == events.ids.RXERROR: globalDevIdx = None # We've finished with this global if we get an error if eventId == events.ids.SECONDS: SendPendingCommand() for devIdx, devInfo in enumerate( info ): # See if any devices are timing out, and turn them off if necessary offAt = GetVal(devIdx, "SwitchOff@") if offAt: if now >= offAt: SwitchOff(devIdx) if dirty: with open(devFilename, 'wt') as f: pprint(info, stream=f) # Was print(info, file=f) # Save devices list directly to file dirty = False # Don't save again until needed if statusUpdate: SaveStatus() statusUpdate = False if eventId == events.ids.MINUTES: SaveStatus() # For app UpTime CheckPresence() # For all devices