def Run( trigger ): # Run through the rules looking to see if we have a match for the trigger rules = database.GetRules( trigger) # Get a list of all rules that mention trigger log.debug("Running rule: " + trigger) delVar = False if "==" in trigger: sep = trigger.index("==") triggerType = trigger[:sep] triggerVal = trigger[sep + 2:] oldVal = variables.Get(triggerType) if oldVal == None: # Check to see if this variable already exists and leave it alone if so variables.Set(triggerType, triggerVal) delVar = True for line in rules: ruleId = line[0] rule = ' '.join( line[1].split() ) # Compact multiple spaces into single ones and make each line into a rule ruleList = rule.split(" ") # Make each rule into a list if ruleList[0].lower() == "if": doIndex = FindItemInList("do", ruleList) # Safely look for "do" if doIndex != None: if ParseCondition( ruleList[1:doIndex], trigger ) == True: # Parse condition from element 1 (ie immediately after "if") to "do" Action(ruleList[doIndex + 1:], ruleId) # Do action # else skip rest of line # else assume the line is a comment and skip it # end of rules if delVar: variables.Del( triggerType) # Make sure we don't re-run the same trigger
def do_set(self, line): """set name value Set named variable to value""" if " " in line: argList = line.split(" ") name = argList[0] val = argList[1] variables.Set(name, val) else: variables.Del(line)
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 Run( trigger ): # Run through the rules looking to see if we have a match for the trigger rulesFile = Path(rulesFilename) if rulesFile.is_file(): with open(rulesFilename) as rules: log.log("Running rule: " + trigger) if "==" in trigger: sep = trigger.index("==") triggerType = trigger[:sep] triggerVal = trigger[sep + 2:] variables.Set(triggerType, triggerVal) for line in rules: rule = ' '.join( line.split() ) # Compact multiple spaces into single ones and make each line into a rule ruleList = rule.split(" ") # Make each rule into a list if ruleList[0] == "if": doIndex = FindItemInList("do", ruleList) # Safely look for "do" if doIndex != None: if ParseCondition( ruleList[1:doIndex], trigger ) == True: # Parse condition from element 1 to "do" Action(ruleList[doIndex + 1:]) # Do action # else skip rest of line elif ruleList[0] == "do": Action(ruleList[1:]) # else assume the line is a comment and skip it # end of rules variables.Del( triggerType) # Make sure we don't re-run the same trigger else: shell.exec("touch " + rulesFilename) shell.exec("chmod 666 " + rulesFilename) log.fault("Made new " + rulesFilename + " !")
def Action(actList, ruleId): log.debug("Action with: " + str(actList)) action = actList[0].lower() if action == "Log".lower(): log.debug("Rule says Log event for " + ' '.join(actList[1:])) elif action == "Play".lower(): call(["omxplayer", "-o", actList[1], actList[2]]) elif action == "Event".lower(): if actList[1].lower() == "TimeOfDay".lower(): events.IssueEvent(events.ids.TIMEOFDAY, actList[2]) elif actList[1].lower() == "Alarm".lower(): events.IssueEvent(events.ids.ALARM, actList[2]) # Could have other events here... elif action == "synopsis": # Was status emailAddress = config.Get("emailAddress") log.debug("About to send synopsis to " + emailAddress) if emailAddress != None: synopsis.BuildPage() # Create synopsis page on demand with open("synopsis.txt", "r") as fh: # Plain text of email emailText = fh.readlines() text = ''.join(emailText) with open("synopsis.html", "r") as fh: # HTML of email emailHtml = fh.readlines() html = ''.join(emailHtml) sendmail.email("Vesta Status", text, html) # See sendmail.py else: synopsis.problem( "NoEmail", "No emailAddress entry in config, needed to send synopsis") elif action == "email": # All args are body of the text. Fixed subject and email address emailAddress = config.Get("emailAddress") if emailAddress != None: emailBody = [] for item in actList[1:]: emailBody.append(item) plainText = " ".join(emailBody) log.debug("Sending email with '" + plainText + "'") result = sendmail.email("Vesta Alert!", plainText, None) if result != 0: synopsis.problem( "Email", "sendmail.email() failed with code " + str(result) + " when trying to send:" + plainText) else: synopsis.problem("NoEmail", "No emailAddress entry in config") elif action == "override": # Syntax is "Override <targetDevice> <targetDegC> <durationSecs>" devKey = devices.FindDev(actList[1]) target = actList[2] timeSecs = actList[3] if devKey != None: schedule.Override(devKey, target, timeSecs) elif action == "set": # Set a named variable to a value expression = "".join( actList[1:] ) # First recombine actList[1] onwards, with no spaces. Now expression should be of the form "<var>=<val>" if "--" in expression: sep = expression.index("--") varName = expression[:sep] varVal = variables.Get(varName) if isNumber(varVal): newVal = str(eval(varVal + "-1")) variables.Set(varName, newVal) Run( varName + "==" + newVal ) # Recurse! to see if any rules need running now that we've set a variable else: log.fault(varName + " not a number at " + expression) elif "++" in expression: sep = expression.index("++") varName = expression[:sep] varVal = variables.Get(varName) if isNumber(varVal): newVal = str(eval(varVal + "+1")) variables.Set(varName, newVal) Run( varName + "==" + newVal ) # Recurse! to see if any rules need running now that we've set a variable else: log.fault(varName + " not a number at " + expression) elif "=" in expression: sep = expression.index("=") varName = expression[:sep] varVal = expression[sep + 1:] variables.Set(varName, varVal) Run( varName + "==" + varVal ) # Recurse! to see if any rules need running now that we've set a variable else: log.fault("Badly formatted rule at " + expression) elif action == "unset": # Remove a named variable variables.Del(actList[1]) else: # Must be a command for a device, or group of devices if len(actList) >= 2: # Check that we have a second arg... name = actList[1] # Second arg is name if database.IsGroupName(name): # Check if name is a groupName devKeyList = GetGroupDevs(name) for devKey in devKeyList: CommandDev(action, devKey, actList, ruleId) # Command each device in list else: devKey = database.GetDevKey("userName", name) CommandDev(action, devKey, actList, ruleId) # Command one device
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 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 if database.GetDeviceItem(devKey, "protocol") != None: database.SetDeviceItem(devKey, "protocol", "ZigbeeHA") # Ensure all unknown devices are ZigbeeHA 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.NEWDAY: keyList = database.GetAllDevKeys() # Get a list of all the device identifiers from the database for devKey in keyList: # Hub and devices if GetTempVal(devKey, "wattSeconds"): SetTempVal(devKey, "wattSeconds", 0) # Clear down any accumulated energy at midnight SetTempVal(devKey, "lastRecorded_ws", 0) energyToday_kWh = variables.Get("energyToday_kWh") # Get today's energy consumed according to PowerClamp variables.Set("energyYesterday_kWh", energyToday_kWh) # Copy Today's energy into Yesterday's for comparison variables.Set("energyToday_kWh", "0") # Every midnight, clear down previous day's energy use if eventId == events.ids.MINUTES: keyList = database.GetAllDevKeys() # Get a list of all the device identifiers from the database for devKey in keyList: # Hub and devices recordEnergyMins = database.GetDeviceItem(devKey, "recordEnergyMins") if recordEnergyMins: minsSinceEnergy = GetTempVal(devKey, "minsSinceEnergy") if minsSinceEnergy == None: minsSinceEnergy = 0 minsSinceEnergy += 1 wattSeconds = GetTempVal(devKey, "wattSeconds") if minsSinceEnergy >= int(recordEnergyMins) and wattSeconds: # Make a recording of energy now, if we have any minsSinceEnergy = 0 lastRecorded_ws = GetTempVal(devKey, "lastRecorded_ws") if lastRecorded_ws == None: lastRecorded_ws = 0 energy_kWh = '%.3f' % ((wattSeconds - lastRecorded_ws) / 3600000) # Get kWh to 3 decimal places devName = database.GetDeviceItem(devKey, "userName") with open(log.logdir+'/'+devName+'_kWh.csv', 'a') as fh: fh.write(str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+','+energy_kWh+'\n') # Append a line to the end of the csv file SetTempVal(devKey, "lastRecorded_ws", wattSeconds) # Make a note of last wattSeconds, so we can calculate energy next time SetTempVal(devKey, "minsSinceEnergy", minsSinceEnergy) 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) 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 protocol = database.GetDeviceItem(devKey, "Protocol") if protocol == "ZigbeeHA" and 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 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): zbTime = int(value, 16) log.debug("Raw time:"+str(zbTime)) timeStr = iottime.FromZigbee(zbTime) 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): watts = int(value, 16) # Arrives in Watts, so store it in the same way if watts > 8000000: # Means that it is a 24-bit signed value, so generated rather than consumed (eg solar power, etc.) watts = 0-(16777216 - watts) # 0-() to make the value negative so it's clear it's generated power newPowerTime = datetime.now() oldPowerTime = GetTempVal(devKey, "oldPowerTime") if oldPowerTime: elapsedPowerTime = newPowerTime - oldPowerTime wattSeconds = GetTempVal(devKey, "wattSeconds") if wattSeconds == None: wattSeconds = 0 wattSeconds += elapsedPowerTime.seconds * watts SetTempVal(devKey, "wattSeconds", wattSeconds) database.LogItem(devKey, "EnergyConsumedWh", wattSeconds / 3600) # Fills database too fast! 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) energyToday_kWh = '%.1f' % (wattSeconds / 3600000) # Get kWh used to 1 decimal place variables.Set("energyToday_kWh", energyToday_kWh) database.UpdateLoggedItem(devKey, "State", str(watts)+"W") # So that we can access it from the rules later, or show it on the web SetTempVal(devKey, "oldPowerTime", newPowerTime) # Ready for next power reading database.UpdateLoggedItem(devKey, "PowerReadingW", watts) # 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