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 EventHandler(eventId, eventArg): global owm, apiKey, location if eventId == events.ids.HOURS: # Get weather once/hour if owm == None: apiKey = config.Get("owmApiKey") location = config.Get("owmLocation") if (apiKey != None and location != None): owm = pyowm.OWM(apiKey) # My API key try: obs = owm.weather_at_place(location) # My location except: database.NewEvent(0, "Weather Feed failed!") synopsis.problem("Weather", "Feed failed @ " + str(datetime.now())) return w = obs.get_weather() cloudCover = w.get_clouds() # Percentage cloud cover variables.Set("cloudCover", str(cloudCover), True) outsideTemp = w.get_temperature("celsius")["temp"] # Outside temperature in celsius variables.Set("outsideTemperature", str(outsideTemp), True) windSpeed = w.get_wind()["speed"] variables.Set("windSpeed", str(windSpeed), True) rain = w.get_rain() if rain != {}: rain = 1 # was rain["3h"] # Rain volume in last 3 hours. Unknown units, may be ml(?) else: rain = 0 # No rain variables.Set("rain", str(rain), True) snow = w.get_snow() if snow != {}: snow = 1 # was snow["3h"] # Snow volume in last 3 hours. Unknown units, may be ml(?) else: snow = 0 # No snow variables.Set("snow", str(snow), True) database.NewEvent(0, "Weather now "+str(cloudCover)+"% cloudy") events.Issue(events.ids.WEATHER) # Tell system that we have a new weather report
def EventHandler(eventId, eventArg): global varList if eventId == events.ids.NEWDAY: oldest = database.GetOldestVar() if oldest: log.debug("Oldest variable is " + oldest) log.debug(oldest + "'s date is " + GetTime(oldest)) if (datetime.now() - datetime.strptime( GetTime(oldest), "%Y-%m-%d %H:%M:%S")).days > 30: log.debug("Removing " + oldest + " since it's more than 30 days old") Del(oldest) if eventId == events.ids.SECONDS: away = database.GetAppState("away") if away == None: away = "False" # Assume at home if we don't know any better database.SetAppState("away", away) # Ensure database has our same default Set( "away", away ) # Keep our variable in sync with the value from the database, so now user can update the db via the web and we'll know... oldAway = Get("away") if oldAway == None: oldAway = "Unknown" # Force update if oldAway != away: log.debug("Away state has changed from " + oldAway + " to " + away) if away == "False": database.NewEvent(0, "Arrived Home") else: database.NewEvent(0, "Gone Away") Set( "away", away ) # Keep our variable in sync with the value from the database, so now user can update the db via the web and we'll know...
def EventHandler(eventId, eventArg): global overrideTimeoutMins, currentTargetTemp if eventId == events.ids.MINUTES: name = config.Get("HeatingDevice") if name != None: heatingDevKey = devices.FindDev( name ) # Get heating device every minute to allow for it changing if heatingDevKey != None: # Check that we have a real heating device schedule = config.Get("HeatingSchedule") if schedule != None: scheduledTargetTemp = GetTarget(schedule) if scheduledTargetTemp != None: # We now have a heating device and a schedule to follow or override if overrideTimeoutMins > 0: overrideTimeoutMins = overrideTimeoutMins - 1 if overrideTimeoutMins <= 0: # Just finished override database.NewEvent( heatingDevKey, "Resume " + str(scheduledTargetTemp) + "'C") # For ActivityLog on web page currentTargetTemp = heating.SetTargetTemp( heatingDevKey, scheduledTargetTemp ) # Resume schedule here else: # Still overriding if scheduledTargetTemp != currentTargetTemp: # Check whether schedule in heating device is about to change target database.NewEvent( heatingDevKey, "Overriding scheduled " + str(scheduledTargetTemp) + "'C with " + str(currentTargetTemp) + "C") # For ActivityLog on web page # Un-indent following line to force override temp once/min while overriding, rather than just at change heating.SetTargetTemp( heatingDevKey, currentTargetTemp ) # Re-Set target in heating device (since it's also running the schedule) else: # Not overriding if scheduledTargetTemp != currentTargetTemp: database.NewEvent( heatingDevKey, "Scheduled " + str(scheduledTargetTemp) + "'C") # For ActivityLog on web page #heating.SetTargetTemp(heatingDevKey, scheduledTargetTemp) # Set target in heating device here. (Not needed since it's running the schedule directly) currentTargetTemp = scheduledTargetTemp # else: No scheduled target # else: No HeatingSchedule # else: Despite having a name, there's no associated device variables.Set("TargetTemp", str(currentTargetTemp)) else: # Ignore schedules and overrides if no named heating device synopsis.problem( "NoHeatingDevice", "No HeatingDevice entry in config, needed to resume after override" )
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 do_toggle(self, devId): """toggle name Sends toggle on/off command to named device""" devKey = devices.FindDev(devId) if devKey != None: database.NewEvent(devKey, "Toggle", "UICmd") devcmds.Toggle(devKey)
def EventHandler(eventId, eventArg): global varList if eventId == events.ids.SECONDS: away = database.GetAppState("away") if away == None: away = "False" # Assume at home if we don't know any better database.SetAppState("away", away) # Ensure database has our same default Set("away", away) # Keep our variable in sync with the value from the database, so now user can update the db via the web and we'll know... oldAway = Get("away") if oldAway == None: oldAway = "Unknown" # Force update if oldAway != away: log.debug("Away state has changed from "+oldAway+" to "+away) if away == "False": database.NewEvent(0, "Arrived Home") else: database.NewEvent(0, "Gone Away") Set("away", away) # Keep our variable in sync with the value from the database, so now user can update the db via the web and we'll know...
def Set(devKey, newState=states.present): oldTime, oldState = Get(devKey) if oldState != newState: database.NewEvent(devKey, newState) # For ActivityLog on web page if newState == states.absent: database.LogItem( devKey, "SignalPercentage", 0) # Clear down signal strength when device goes missing database.LogItem(devKey, "Presence", newState)
def Override(devKey, targetC, timeSecs): global overrideTimeoutMins, currentTargetTemp timeMins = int(int(timeSecs) / 60) if timeMins > 0: overrideTimeoutMins = timeMins database.NewEvent( devKey, "Override " + str(targetC) + "'C" ) # For web page. Update event log so I can check my schedule follower works currentTargetTemp = heating.SetTargetTemp( devKey, targetC) # Set target in heating device here
def do_dim(self, line): """dim name percentage Sends level command to named device""" argList = line.split() if len(argList) >= 2: percentage = int(argList[1]) devKey = devices.FindDev(argList[0]) if devKey != None: database.NewEvent(devKey, "Dim", "UICmd") devcmds.Dim(devKey, percentage) else: log.fault("Insufficient Args")
def do_sat(self, line): """sat name sat Sends ColorCtrl command to named device, where 0<sat<100""" argList = line.split() if len(argList) >= 2: sat = int(argList[1]) devKey = devices.FindDev(argList[0]) if devKey != None: database.NewEvent(devKey, "Sat", "UICmd") devcmds.Sat(devKey, sat) else: log.fault("Insufficient Args")
def do_hue(self, line): """hue name hue Sends ColorCtrl command to named device, where 0<hue<360""" argList = line.split() if len(argList) >= 2: hue = int(argList[1]) devKey = devices.FindDev(argList[0]) if devKey != None: database.NewEvent(devKey, "Hue", "UICmd") devcmds.Hue(devKey, hue) else: log.fault("Insufficient Args")
def SetStatus(devIdx, name, value): # For web page global status, statusUpdate for item in status[devIdx]: if item[0] == name: if item[1] == value: return # Bail if value hasn't changed status[devIdx].remove( item) # Remove old tuple if value has changed status[devIdx].append((name, value)) # Add new one regardless if name == "Other" and value != "N/A": log.activity(devIdx, value) statusUpdate = True database.NewEvent(devIdx, name, value)
def HandleSerial(ser): global txBuf, rxBuf while ser.inWaiting(): try: telegesisInLine = str(ser.readline(),'utf-8').rstrip('\r\n') # Rely on timeout=0 to return immediately, either with a line or with None except: database.NewEvent(0, "Serial port problem") vesta.Restart() rxBuf.append(telegesisInLine) # Buffer this for subsequent processing in main thread while len(txBuf): atCmd = txBuf.popleft() wrAtCmd = atCmd + "\r\n" ser.write(wrAtCmd.encode()) log.debug("Tx>"+atCmd)
def EventHandler(eventId, eventArg): global sckLst, sck, cliSck if eventId == events.ids.INIT: sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create socket sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sck.setblocking(0) # accept() is no longer blocking port = 12345 try: sck.bind(('', port)) # Listen on all available interfaces except OSError as err: # "OSError: [Errno 98] Address already in use" database.NewEvent(0, "Socket bind failed with " + err.args[1]) # 0 is always hub vesta.Reboot() sck.listen(0) sckLst = [sck] if eventId == events.ids.SECONDS: if select.select( [sys.stdin], [], [], 0)[0]: # Read from stdin (for working with the console) cmd = sys.stdin.readline() if cmd: Commands().onecmd(cmd) rd, wr, er = select.select( sckLst, [], [], 0) # Read from remote socket (for working with web pages) for s in rd: if s is sck: cliSck, addr = sck.accept() sckLst.append(cliSck) #log.debug("New connection from web page!") else: try: cmd = cliSck.recv(100) except OSError as err: # OSError: [Errno 9] Bad file descriptor" synopsis.problem("Web command failed with ", err.args[1]) cmd = "" # No command if there was a failure if cmd: cmd = cmd.decode() log.debug("Got cmd \"" + cmd + "\" from web page") sys.stdout = open("cmdoutput.txt", "w") # Redirect stdout to file Commands().onecmd(cmd) sys.stdout = sys.__stdout__ # Put stdout back to normal (will hopefully also close the file) f = open("cmdoutput.txt", "r") cmdOut = f.read() cliSck.send(str.encode(cmdOut)) call("rm cmdoutput.txt", shell=True) # Remove cmd output after we've used it
def EventHandler(eventId, eventArg): if eventId == events.ids.TRIGGER: devKey = devices.GetKey( eventArg[1]) # Lookup device from network address in eventArg[1] if devKey != None: devices.NoteMsgDetails(devKey, eventArg) # Note presence now = datetime.now() nowStr = now.strftime("%H:%M") zoneType = database.GetDeviceItem(devKey, "iasZoneType") # Device type if zoneType != None: oldState = database.GetLatestEvent(devKey) if zoneType == zcl.Zone_Type.Contact: if int(eventArg[3], 16) & 1: # Bottom bit indicates alarm1 newState = "opened" else: newState = "closed" if oldState != newState: # NB Might get same state if sensor re-sends, or due to battery report database.UpdateLoggedItem( devKey, "State", newState ) # So that we can access it from the rules later database.NewEvent( devKey, newState ) # For web page. Only update event log when state changes DeviceRun(devKey, "==" + newState ) # See if rule exists (when state changes) #log.debug("Door "+ eventArg[1]+ " "+newState) elif zoneType == zcl.Zone_Type.PIR: if int(eventArg[3], 16) & 1: # Bottom bit indicates alarm1 newState = "active" devices.SetTempVal( devKey, "PirInactive@", datetime.now() + timedelta(seconds=300)) else: newState = "inactive" # Might happen if we get an IAS battery report if oldState != newState: database.UpdateLoggedItem( devKey, "State", newState ) # So that we can access it from the rules later database.NewEvent( devKey, newState ) # For web page. Only update event log when state changes DeviceRun(devKey, "==" + newState) # See if rule exists else: log.debug("DevId: " + eventArg[1] + " zonestatus " + eventArg[3]) else: log.fault("Unknown IAS device type for devId " + eventArg[1]) else: # devKey == None telegesis.Leave( eventArg[1] ) # Tell device to leave the network, since we don't know anything about it elif eventId == events.ids.BUTTON: devKey = devices.GetKey( eventArg[1]) # Lookup device from network address in eventArg[1] if devKey != None: #log.debug("Button "+ eventArg[1]+ " "+eventArg[0]) # Arg[0] holds "ON", "OFF" or "TOGGLE" (Case might be wrong) database.NewEvent(devKey, "pressed") # For web page DeviceRun(devKey, "==" + eventArg[0]) # See if rule exists else: # devKey == None telegesis.Leave( eventArg[1] ) # Tell device to leave the network, since we don't know anything about it elif eventId == events.ids.MULTISTATE: devKey = devices.GetKey( eventArg[1]) # Lookup device from network address in eventArg[1] if devKey != None: database.NewEvent(devKey, "MultiState==" + eventArg[0]) # For web page DeviceRun(devKey, "==" + eventArg[0]) # See if rule exists else: # devKey == None telegesis.Leave( eventArg[1] ) # Tell device to leave the network, since we don't know anything about it
def Reboot(): database.NewEvent(0, "Rebooting...") # 0 is always hub events.Issue(events.ids.SHUTDOWN) # Tell system we're about to shutdown os.system("sudo reboot") # Unrecoverable, so reboot entire machine...
def Restart(): database.NewEvent(0, "Restarting...") # 0 is always hub events.Issue(events.ids.SHUTDOWN) # Tell system we're about to shutdown sys.exit(0) # Stop app, and rely on cron job to restart us
def EventHandler(eventId, eventArg): global oldMins, oldHours if eventId == events.ids.INIT: SetDayInfo() SetSunTimes() if variables.Get("sunrise") != None: variables.Set("morning", variables.Get("sunrise")) variables.Set("evening", variables.Get( "sunset")) # Set up defaults until we get a weather report variables.Set("dark", str(GetDark())) rules.Run("trigger==appstart") database.NewEvent(0, "App started") # 0 is always hub telegesis.SetTime() # Set time up for HA devices to synchronise to queue.EnqueueCmd(0, ["AT+SETATR:000A,0001,05", "OK" ]) # Set Master clock and timezone bits on CICIE queue.EnqueueCmd(0, ["AT+TIMERD", "OK"]) # Set CICIE as time server elif eventId == events.ids.SECONDS: now = datetime.now() if now.minute != oldMins: events.Issue(events.ids.MINUTES, now.minute) oldMins = now.minute # Ready for next time elif eventId == events.ids.MINUTES: now = datetime.now() if now.hour != oldHours: events.Issue(events.ids.HOURS, now.hour) oldHours = now.hour # Ready for next time variables.Set("time", str(now.strftime("%H:%M"))) rules.Run( "time==" + str(now.strftime("%H:%M"))) # Run timed rules once per minute rules.Run("trigger==time") # Run timed rules once per minute # Could alter above to rules.Run("trigger==minute") if variables.Get("sunrise") != None: CheckTimedRule("dawn", now) # Sky getting light before sunrise CheckTimedRule("sunrise", now) # Sun on horizon CheckTimedRule("morning", now) # Now proper daylight (depending on cloud) CheckTimedRule( "evening", now) # No longer proper daylight (depending on cloud) CheckTimedRule("sunset", now) # Sun on horizon CheckTimedRule("dusk", now) # Sky now getting dark after sunset dark = str(GetDark()) # Work out whether "dark" is True or False #log.debug("Old dark = " + variables.Get("dark") + " whereas new dark = " + dark) if dark != variables.Get("dark"): # Compare with previous variables.Set( "dark", dark ) # Update our idea of whether it's dark or light just now rules.Run("dark==" + variables.Get("dark") ) # This will also delete the variable afterwards variables.Set( "dark", dark ) # Re-instate the variable after the rule has deleted it if eventId == events.ids.HOURS: # Could add rules.Run("trigger==hour") if eventArg == 0: # Midnight, time to calculate sunrise and sunset for new day events.Issue(events.ids.NEWDAY) if eventArg == 4: # Re-synch Telegesis clock to local time at 4am every day to cope with DST telegesis.SetTime() # Set time up for HA devices to synchronise to if eventId == events.ids.NEWDAY: # Could add rules.Run("trigger==day") SetSunTimes() log.RollLogs() # Roll the logs, to avoid running out of disc space SetDayInfo() synopsis.BuildPage( ) # Create status page, once/day, based upon reported problems during the previous day synopsis.clearProblems()
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)