def CheckReporting(devKey, reporting, field, cluster, attrId, attrType, defVal): global pendingRptAttrId rpt = cluster+":"+attrId if pendingRptAttrId == None and rpt not in reporting: pendingRptAttrId = attrId devRpt = database.GetDeviceItem(devKey, field, defVal) rptList = devRpt.split(",") # Explode the CSV line to a list of min, max, delta if "-1" == rptList[0]: return # Don't configure this attribute for reporting if min==-1 log.debug("Update device reporting for "+database.GetDeviceItem(devKey, "userName")+"'s "+field) log.debug("Reporting was "+reporting+" which didn't include " + rpt) if attrType == zcl.AttributeTypes.Uint8 or attrType == zcl.AttributeTypes.Sint8: deltaLen = 2 # Need to know number of digits to use in deltaHex elif attrType == zcl.AttributeTypes.Uint16 or attrType == zcl.AttributeTypes.Sint16: deltaLen = 4 # Need to know number of digits to use in deltaHex elif attrType == zcl.AttributeTypes.Uint24 or attrType == zcl.AttributeTypes.Sint24: deltaLen = 6 # Need to know number of digits to use in deltaHex elif attrType == zcl.AttributeTypes.Uint32 or attrType == zcl.AttributeTypes.Sint32: deltaLen = 8 # Need to know number of digits to use in deltaHex elif attrType == zcl.AttributeTypes.Uint48: deltaLen = 12 # Need to know number of digits to use in deltaHex else: deltaLen = 0 # Don't know format, so fail if deltaLen != 0: deltaLenFmt = "%0."+str(deltaLen)+"X" # Work out how many digits to use given the attribute type minHex = "%0.4X" % int(rptList[0]) # Convert each decimal item to a hex string if int(rptList[1]) == -1: maxHex = "FFFF" # To disable this report else: maxHex = "%0.4X" % int(rptList[1]) deltaHex = deltaLenFmt % int(rptList[2]) # Use the correct number of leading zeros for delta's hex representation nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") return ("AT+CFGRPT:"+nwkId+","+ep+",0,"+cluster+",0,"+attrId+","+attrType+","+minHex+","+maxHex+","+deltaHex, "CFGRPTRSP") return None
def TxReadAttrRsp(devKey, clstrId, attrId, attrType, attrVal): nwkId = database.GetDeviceItem(devKey, "nwkId") if nwkId == None: return # Make sure it's a real device before continuing (it may have just been deleted) ep = database.GetDeviceItem(devKey, "endPoints") cmdRsp = ReadAttrRsp(nwkId, ep, clstrId, attrId, attrType, attrVal) queue.EnqueueCmd(devKey, cmdRsp) # Queue up command for sending via devices.py
def Hue(devKey, hueDegree): nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") if nwkId and ep: hueStr = format(int(float(hueDegree / 360) * 254), 'X').zfill(2) queue.EnqueueCmd(devKey, [ "AT+CCMVTOHUE:" + nwkId + "," + ep + ",0," + hueStr + ",00,0001", "DFTREP" ]) # Fade over 100ms (in sec/10)
def Sat(devKey, satPercentage): nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") if nwkId and ep: satStr = format(int(float(satPercentage / 100) * 254), 'X').zfill(2) queue.EnqueueCmd(devKey, [ "AT+CCMVTOSAT:" + nwkId + "," + ep + ",0," + satStr + ",0001", "DFTREP" ]) # Fade over 100ms (in sec/10)
def Prod(devKey): # Ask device a question, just to provoke a response nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") if nwkId != None and ep != None: log.debug("Prodding devKey " + str(devKey) + " (nwkId:" + nwkId + ")") cmdRsp = telegesis.ReadAttr( nwkId, ep, zcl.Cluster.Basic, zcl.Attribute.Model_Name ) # Get Basic's Device Name in order to prod it into life queue.EnqueueCmd(devKey, cmdRsp)
def Hue(devKey, hueDegree): protocol = database.GetDeviceItem(devKey, "protocol") nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") if protocol == "ZigbeeHA" and nwkId != None and ep != None: hueStr = format(int(float(hueDegree / 360) * 254), 'X').zfill(2) queue.EnqueueCmd(devKey, [ "AT+CCMVTOHUE:" + nwkId + "," + ep + ",0," + hueStr + ",00,0001", "DFTREP" ]) # Fade over 100ms (in sec/10)
def Sat(devKey, satPercentage): protocol = database.GetDeviceItem(devKey, "protocol") nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") if protocol == "ZigbeeHA" and nwkId != None and ep != None: satStr = format(int(float(satPercentage / 100) * 254), 'X').zfill(2) queue.EnqueueCmd(devKey, [ "AT+CCMVTOSAT:" + nwkId + "," + ep + ",0," + satStr + ",0001", "DFTREP" ]) # Fade over 100ms (in sec/10)
def CheckThermostat(devKey): nwkId = database.GetDeviceItem(devKey, "nwkId") if nwkId == None: return None # Make sure it's a real device before continuing (it may have just been deleted) inClstr = database.GetDeviceItem( devKey, "inClusters") # Assume we have a list of clusters if we get this far if zcl.Cluster.Thermostat not in inClstr: return None # Not a thermostat return nwkId
def SetBinding(devKey, cluster, ourEp): global pendingBinding, pendingBindingTimeoutS nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") eui = database.GetDeviceItem(devKey, "eui64") if None != ep and None != eui: pendingBinding[devKey] = cluster pollFreq = float(database.GetDeviceItem(devKey, "longPollInterval", 0)) # See if the device is a fast poller pendingBindingTimeoutS[devKey] = pollFreq+10 # Allow LongPollInterval and add 10 seconds to that. Or just 10 seconds if an FFD return ("AT+BIND:"+nwkId+",3,"+eui+","+ep+","+cluster+","+database.GetDeviceItem(0, "eui64")+","+ourEp, "Bind")
def Identify( devKey, durationS ): # Duration in seconds to flash the device's LED. Use duration=0 to stop. nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") if nwkId and ep: durationStr = format(int(durationS), 'X').zfill(4) queue.EnqueueCmd(devKey, [ "AT+IDENTIFY:" + nwkId + "," + ep + ",0," + durationStr, "DFTREP" ]) # Identify for selected time
def SwitchOff(devKey): nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") if nwkId and ep: #database.UpdateLoggedItem(devKey, "State", "SwitchOff") # So that we can access it from the rules later devices.DelTempVal( devKey, "SwitchOff@" ) # Remove any pending "Off" events if we're turning the device off directly devices.SetTempVal(devKey, "JustSentOnOff", "True") devices.SetTempVal(devKey, "ExpectOnOff", "SwitchOff") queue.EnqueueCmd(devKey, ["AT+RONOFF:" + nwkId + "," + ep + ",0,0", "OK" ]) # Assume FFD if it supports OnOff cluster database.SetDeviceItem(devKey, "dimLevel", 0) # Set dimness to 0
def Dim(devKey, level): nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") if nwkId and ep: if level > 1: # Assume it's a percentage level = level / 100 # Convert to a fraction levelStr = format(int(level * 254), 'X').zfill(2) queue.EnqueueCmd(devKey, [ "AT+LCMVTOLEV:" + nwkId + "," + ep + ",0,1," + levelStr + ",000A", "DFTREP" ]) # Fade over 1 sec (in 10ths) if level == 0: SwitchOff(devKey) database.SetDeviceItem(devKey, "dimLevel", level * 100) # Assume the LevelCtrl command above works
def IsListening(devKey): type = database.GetDeviceItem(devKey, "devType") if type == "SED": pollFreq = database.GetDeviceItem( devKey, "longPollInterval") # See if the device is a fast poller if pollFreq != None: if float(pollFreq) < 7.6: return True pollTime = GetTempVal(devKey, "PollingUntil") if pollTime != None: if datetime.now() < pollTime: return True return False else: # Assume not sleepy return True # These devices are always awake and listening
def EnsureReporting(devKey, clstrId, attrId, attrVal): # Check when this attr last reported and update device's reporting if necessary if isnumeric(attrVal, 16): newVal = int(attrVal, 16) # Assume value arrives in hex else: return if clstrId == zcl.Cluster.SimpleMetering and attrId == zcl.Attribute.InstantaneousDemand: prevItem = database.GetLatestLoggedItem(devKey, "PowerReadingW") # Check when we got last reading field = "powerReporting" #elif clstrId == zcl.Cluster.SimpleMetering and attrId == zcl.Attribute.CurrentSummationDelivered: # These energy* fields don't work, so interpolate power over time # prevItem = database.GetLatestLoggedItem(devKey, "EnergyConsumedWh") # field = "energyConsumedReporting" #elif clstrId == zcl.Cluster.SimpleMetering and attrId == zcl.Attribute.CurrentSummationReceived: # prevItem = database.GetLatestLoggedItem(devKey, "EnergyGeneratedWh") # field = "energyGeneratedReporting" else: # Don't know how often it should report. Could add temperature and battery return if prevItem != None: prevTime = prevItem[1] prevVal = prevItem[0] minMaxDelta = database.GetDeviceItem(devKey, field) # Look up min and max for this item if minMaxDelta != None: confList = minMaxDelta.split(",") min = int(confList[0]) max = int(confList[1]) delta = int(confList[2]) change = abs(newVal - int(prevVal)) secsSinceLastReport = (datetime.now() - datetime.strptime(prevTime, "%Y-%m-%d %H:%M:%S")).seconds # Work out time between now and prevTime in seconds log.debug("Prev report "+str(secsSinceLastReport)+" s ago, min="+str(min)+" for devKey "+str(devKey)+" with old val="+str(prevVal)+" vs new val of "+str(newVal)) if max == -1 or secsSinceLastReport < min or secsSinceLastReport > max: # Check whether min>secsSincelastReport>max or max==-1 Config(devKey, field) # Re-configure device elif secsSinceLastReport < max-(max/10) and change<delta: # Check delta if not too close to max Config(devKey, field) # Re-configure device
def EnsureInBinding(devKey, clusterId): # Put clusterId in binding if not there already entry = database.GetDeviceItem(devKey, "binding", "[]") binding = eval(entry) #log.debug("Binding is "+str(binding)) if clusterId not in binding: binding.append(clusterId) database.SetDeviceItem(devKey, "binding", str(binding))
def Remove(devKey): if IsListening(devKey): nwkId = database.GetDeviceItem(devKey, "nwkId") if nwkId: telegesis.Leave(nwkId) # Tell device to leave the network immediately (assuming it's listening) database.RemoveDevice(devKey) devDict[devKey] = -1 # Remove link between the key and its index (but don't remove the entry in the dict)
def Config(devKey, field): # Read newly changed field from database for device and use this to update actual device ASAP log.debug("Updating reporting for " + field) reporting = database.GetDeviceItem( devKey, "reporting" ) # See if we're expecting this report, and note it in the reporting table if field == "batteryReporting": rptToUpdate = zcl.Cluster.PowerConfig + ":" + zcl.Attribute.Batt_Percentage elif field == "temperatureReporting": rptToUpdate = zcl.Cluster.Temperature + ":" + zcl.Attribute.Celsius elif field == "powerReporting": rptToUpdate = zcl.Cluster.SimpleMetering + ":" + zcl.Attribute.InstantaneousDemand elif field == "energyConsumedReporting": rptToUpdate = zcl.Cluster.SimpleMetering + ":" + zcl.Attribute.CurrentSummationDelivered elif field == "energyGeneratedReporting": rptToUpdate = zcl.Cluster.SimpleMetering + ":" + zcl.Attribute.CurrentSummationReceived else: rptToUpdate = None if reporting != None and rptToUpdate != None: reportList = eval(reporting) if rptToUpdate in reportList: reportList.remove( rptToUpdate ) # Remove newly changed item from list so that Check() routine will spot this and update the reporting accordingly updatedReporting = str(reportList) database.SetDeviceItem( devKey, "reporting", updatedReporting) # Ready for Check() to send new item
def SetDaySchedule(devKey, scheduleType="Heating", dayOfWeek="Sun"): nwkId = CheckThermostat(devKey) if nwkId == None: return # Make sure it's a real thermostat device before continuing ep = database.GetDeviceItem(devKey, "endPoints") frameCtl = "11" seqId = "00" dayOfWeekIndex = iottime.GetDowIndex(dayOfWeek) log.debug("From " + dayOfWeek + " get value of " + str(dayOfWeekIndex)) dayBit = 2**dayOfWeekIndex # ** is "raise to the power". Assumes dayOfWeek is a int where 0=Sunday, 1=Monday, etc. scheduleStr = database.GetSchedule(scheduleType, dayOfWeek) try: scheduleList = eval(scheduleStr) except: return # Bad list from database numSetpoints = len(scheduleList) scheduleStr = "" for index in range(0, numSetpoints): timeTemp = scheduleList[index] # Get each time & temp from schedule timeStr = timeTemp[0] tempStr = timeTemp[1] time = datetime.strptime(timeStr, "%H:%M") minsSinceMidnight = (time.hour * 60) + time.minute htonMins = telegesis.ByteSwap(minsSinceMidnight) htonTemp = telegesis.ByteSwap(int(float(tempStr) * 100)) scheduleStr = scheduleStr + "{:04X}".format(htonMins) scheduleStr = scheduleStr + "{:04X}".format(htonTemp) cmdRsp = ("AT+RAWZCL:" + nwkId + "," + ep + "," + zcl.Cluster.Thermostat + "," + frameCtl + seqId + zcl.Commands.SetSchedule + "{:02X}".format(numSetpoints) + "{:02X}".format(dayBit) + "01" + scheduleStr, "CWSCHEDULE") # Set heating(01) schedule queue.EnqueueCmd(devKey, cmdRsp) # Queue up command for sending via devices.py
def ParseCWShedule(eventArg): global thermoDevKey if eventArg[0] != "CWSCHEDULE" or len(eventArg) < 5: return devKey = devices.GetKey(eventArg[1]) if devKey == None: return devices.NoteMsgDetails(devKey, eventArg) ep = eventArg[2] numSetpoints = int(eventArg[3], 16) dayOfWeekBitMap = int(eventArg[4], 16) dayOfWeekIndex = int(math.log(dayOfWeekBitMap, 2)) dayOfWeek = iottime.GetDow(dayOfWeekIndex) # Assume eventArg[5] holds "01" for heating schedule if len(eventArg) < 5 + ( 2 * numSetpoints ): # Sanity check that we have all the bits of schedule we expect return newSchedule = [] for index in range(0, numSetpoints): minutesSinceMidnight = eventArg[6 + ( index * 2)] # Value in hex as number of minutes since midnight targetTemp = eventArg[6 + ( index * 2) + 1] # Value in hex as targetTemp in units of 1/100'C secs = int(minutesSinceMidnight, 16) * 60 timeOfDay = time.strftime("%H:%M", time.gmtime(secs)) scheduleTemp = int(targetTemp, 16) / 100 newSchedule.append((timeOfDay, scheduleTemp)) username = database.GetDeviceItem(devKey, "userName") log.debug("Schedule from " + username + " for " + dayOfWeek + " is " + str(newSchedule)) scheduleType = username # was config.Get("HeatingSchedule", "Heating") database.SetSchedule(scheduleType, dayOfWeek, str( newSchedule)) # Update the database from the Thermostat/boiler device
def DeviceRun(devKey, restOfRule): # Run rule for specified device userName = database.GetDeviceItem(devKey, "userName") Run(userName + restOfRule) groupList = database.GetGroupsWithDev( devKey ) # Check if device is in any groups and run appropriate rules for each group for name in groupList: Run(name + restOfRule)
def SetTime(devKey): protocol = database.GetDeviceItem(devKey, "protocol") timeVal = datetime.now() timeVal = time.mktime(timeVal.timetuple()) zbTime = iottime.ToZigbee(timeVal) timeStr = format(int(zbTime), 'X').zfill(8) if protocol == "ZigbeeHA": telegesis.TxWriteAttr(devKey, zcl.Cluster.Time, zcl.Attribute.Time, zcl.AttributeTypes.UtcTime, timeStr)
def SwitchOn(devKey): nwkId = database.GetDeviceItem(devKey, "nwkId") ep = database.GetDeviceItem(devKey, "endPoints") dimLevel = database.GetDeviceItem(devKey, "dimLevel") inClstr = database.GetDeviceItem( devKey, "inClusters") # For checking whether we have LevelCtrl if nwkId and ep: #database.UpdateLoggedItem(devKey, "State", "SwitchOn") # So that we can access it from the rules later devices.SetTempVal(devKey, "JustSentOnOff", "True") devices.SetTempVal(devKey, "ExpectOnOff", "SwitchOn") queue.EnqueueCmd(devKey, ["AT+RONOFF:" + nwkId + "," + ep + ",0,1", "OK" ]) # Assume FFD if it supports OnOff cluster if zcl.Cluster.LevelCtrl in inClstr and dimLevel != 100: # Queue up a dimming command if available and we're at a different dimness queue.EnqueueCmd( devKey, ["AT+LCMVTOLEV:" + nwkId + "," + ep + ",0,1,FE,0001", "OK" ]) # Ensure fully bright ready to be turned on database.SetDeviceItem( devKey, "dimLevel", 100) # Assume the LevelCtrl command above works
def NoteReporting(devKey, clusterId, attrId): reporting = database.GetDeviceItem(devKey, "reporting") # See if we're expecting this report, and note it in the reporting table newRpt = clusterId+":"+attrId if reporting != None: reportList = eval(reporting) if newRpt not in reportList: reportList.append(newRpt) reporting = str(reportList) else: reporting = "['"+newRpt+"']" database.SetDeviceItem(devKey, "reporting", reporting) # Ready for next time log.debug("New reporting entry of "+reporting+" with cluster:"+clusterId+" and attrId:"+attrId+" for devKey:"+str(devKey)) EnsureInBinding(devKey, clusterId) # Assume reportable items must be in binding for us to receive them, so make sure this is up-to-date
def Check(): # Expected to be called infrequently - ie once/minute keyList = database.GetAllDevKeys( ) # Get a list of all the device identifiers from the database notHeardFromList = [] for devKey in keyList: # Element 0 is hub, rest are devices if database.GetDeviceItem(devKey, "nwkId") != "0000": # Ignore hub lastSeen, presence = Get(devKey) if datetime.now() > lastSeen + timedelta( seconds=900 ) and "SED" != database.GetDeviceItem( devKey, "devType" ): # More than 15 minutes since we last heard from device, and it's listening notHeardFromList.append( devKey) # Make a list of devices to query if presence != states.absent and datetime.now( ) > lastSeen + timedelta( seconds=1800 ): # More than 30 minutes since we last heard from device Set(devKey, states.absent) #if presence != states.present: # notHeardFromList.append(devKey) # Make a list of devices to query if notHeardFromList != []: log.debug("List of missing (or nearly missing) devices: " + str(notHeardFromList)) numDevs = len(notHeardFromList) if numDevs > 3: for i in range(3): # Prod 3 random devices from list devKey = random.choice(notHeardFromList) devcmds.Prod( devKey ) # Ask device a question, just to provoke a response notHeardFromList.remove( devKey ) # Having prodded it, make sure we don't prod it again else: # Prod each device in list for devKey in notHeardFromList: devcmds.Prod( devKey ) # Ask device a question, just to provoke a response
def GetDaySchedule( devKey, dayOfWeek="Sun"): # Ask Thermostat/Boiler device for its schedule global thermoDevKey nwkId = CheckThermostat(devKey) if nwkId == None: return # Make sure it's a real thermostat device before continuing thermoDevKey = devKey ep = database.GetDeviceItem(devKey, "endPoints") frameCtl = "11" seqId = "00" dayOfWeekIndex = iottime.GetDowIndex(dayOfWeek) dayBit = 2**dayOfWeekIndex # ** is "raise to the power". Assumes dayOfWeek is a int where 0=Sunday, 1=Monday, etc. cmdRsp = ("AT+RAWZCL:" + nwkId + "," + ep + "," + zcl.Cluster.Thermostat + "," + frameCtl + seqId + zcl.Commands.GetSchedule + "{:02X}".format(dayBit) + "01", "CWSCHEDULE" ) # Get heating(01) schedule queue.EnqueueCmd(devKey, cmdRsp) # Queue up command for sending via devices.py
def EventHandler(eventId, eventArg): global ser, txBuf, rxBuf if eventId == events.ids.INIT: serPort = config.Get("tty", '/dev/ttyUSB0') serSpeed = config.Get("baud", '19200') ser = serial.Serial(serPort, int(serSpeed), timeout=0) ser.flushInput() if database.GetDevicesCount()==0: # If we have no devices yet, then... devices.Add("0000", "N/A", "COO") # ...make sure we add this device as the first item before we try to use it! queue.EnqueueCmd(0, ["ATS63=0007", "OK"]) # Request RSSI & LQI on every received message, also disable automatic checkIn responses queue.EnqueueCmd(0, ["ATS0F=0400", "OK"]) # Use 0600 to set bit 9 (bit 10 already set) to get rawzcl responses so we can see schedule responses from thermostat if database.GetDeviceItem(0, "modelName") == None: queue.EnqueueCmd(0, ["ATI", "OK"]) # Request our EUI, as well as our Telegesis version queue.EnqueueCmd(0, ["AT+N", "OK"]) # Get network information, to see whether to start new network or use existing one elif eventId == events.ids.SECONDS: HandleSerial(ser) while len(rxBuf): Parse(rxBuf.popleft()) elif eventId == events.ids.RADIO_INFO: print(ourChannel+","+ourPowLvl+","+ourPan+","+ourExtPan) # Formatted to make it easy to extract in php elif eventId == events.ids.INFO: print("TxBuf: ", str(txBuf))
def GetAvailability(devKey): # Over last 24 hours lastSeen, presence = Get(devKey) userName = database.GetDeviceItem(devKey, "userName") if userName == None: return "Unknown name for device" if lastSeen < datetime.now() - timedelta( hours=24): # Check if any change in last 24 hours if presence == states.present: availability = "" # Perfect availability for the whole time else: # Assume absent availability = userName + " has been missing all day" else: # There's been some changes in presence in the last 24 hours entries = database.GetLoggedItemsSinceTime( devKey, "Presence", "datetime('now', '-1 day')") #log.debug(userName+"'s presence for last 24 hours;"+str(entries)) if entries == None: availability = "No presence for " + userName elif len( entries ) == 1: # The device has changed only once in the last 24 hours lastTwoEntries = database.GetLastNLoggedItems( devKey, "Presence", 2) # Get last 2 entries if len(lastTwoEntries ) == 1: # The device has changed only once ever availability = "" # Perfect availability for the whole time else: # Check when it changed, and what it changed to to work out availability = "" # Hasn't changed enough to be a worry elif len(entries) > 1: availability = userName + "'s availability changed " + str( len(entries) ) + " times in last 24 hours and is now " + presence # Changeable else: if presence == states.present: availability = "" # Perfect availability for the whole time else: # Assume absent or unknown availability = userName + " has not been heard from all day" return availability
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 BuildPage(): upTime = datetime.now() - iottime.appStartTime absUrl = config.Get("vestaURL", "") log.debug("Building status page") txt = open("synopsis.txt", "w") # Create text file for txt html = open( "synopsis.html", "w" ) # Create local html file, so we can copy it for Apache to serve up, or mail directly html.write("\n<html><head>") # Blank line at start html.write( "<META HTTP-EQUIV=\"CACHE-CONTROL\" CONTENT=\"NO-CACHE, no-store, must-revalidate\">" ) # Try to force browser to discard any previous version of this page html.write("</head><body>") html.write("<center><h1>Vesta Status</h1>") txt.write("Vesta Status\n\n") writeLine( "At " + datetime.now().strftime("%H:%M") + " on " + datetime.now().strftime("%Y/%m/%d"), html, txt) writeLine("Uptime: %d days, %.2d:%.2d" % (upTime.days, upTime.seconds // 3600, (upTime.seconds // 60) % 60), html, txt) # Cribbed from "uptime" command writeLine("", html, txt) # Just newline keyList = database.GetAllDevKeys( ) # Get a list of all the device identifiers from the database noProblems = True restartCount = database.CountEvents(0, "App started", "date('now', '-1 days')") if restartCount > 0: noProblems = False writeLine( "App restarted " + str(restartCount) + " times in the last 24 hours", html, txt) else: # Don't check availablilty of devices if app has restarted for devKey in keyList: # Element 0 is hub, rest are devices if database.GetDeviceItem(devKey, "nwkId") != "0000": # Ignore hub availability = presence.GetAvailability(devKey) if availability != "": # If device missing even only occasionally, tell user (Empty string means "fine") noProblems = False writeLine(availability, html, txt) dbSize = database.GetFileSize() if (dbSize / len(keyList)) > (30 * 1024): # Arbitrary limit of 30K per device noProblems = False problem( "dbSize", "Database file size is " + "{0:.2f}".format(dbSize / (1024 * 1024)) + "MB which is " + "{0:.0f}".format((dbSize / len(keyList)) / 1024) + "KB per device") errList = glob.glob( "/home/pi/Vesta/*_err.log") # Get list of all error logs numLogs = len(errList) if numLogs: noProblems = False if numLogs == 1: problem("error_logs", "1 error log") else: problem("error_logs", str(numLogs) + " error logs") if len(issues) > 0: noProblems = False for items in issues.values(): writeLine(items, html, txt) if noProblems: writeLine("Everything OK!", html, txt) writeLine("", html, txt) # Just newline writeLine("(Vesta v" + vesta.GetVersion() + ")", html, txt) html.write("<br><center><a href=\"" + absUrl + "/vesta/index.php\"><img src=\"" + absUrl + "/vesta/vestaLogo.png\" width=32 height=32 title=\"Home\"></a>") html.write("</body></html>") html.close() txt.close() os.system( "sudo cp synopsis.html /var/www/html/vesta" ) # So vesta.php can refer to it. Will overrwrite any previous status
def Check(devKey): global pendingBinding, msp_ota if devKey == 0: return # We don't need anything from the hub nwkId = database.GetDeviceItem(devKey, "nwkId") if None == nwkId: return # Make sure it's a real device before continuing (it may have just been deleted) ep = database.GetDeviceItem(devKey, "endPoints") if None == ep: return (["AT+ACTEPDESC:" + nwkId + "," + nwkId, "ActEpDesc"]) eui = database.GetDeviceItem(devKey, "eui64") if None == eui: return (["AT+EUIREQ:" + nwkId + "," + nwkId, "AddrResp"]) inClstr = database.GetDeviceItem( devKey, "inClusters", "[]") # Assume we have a list of clusters if we get this far if "[]" == inClstr: return ([ "AT+SIMPLEDESC:" + nwkId + "," + nwkId + "," + ep, "OutCluster" ]) outClstr = database.GetDeviceItem(devKey, "outClusters", "[]") binding = database.GetDeviceItem(devKey, "binding" "[]") if str(pendingBinding[devKey] ) == "": # Only try to add one binding per device at once if zcl.Cluster.PollCtrl in inClstr and zcl.Cluster.PollCtrl not in binding: return SetBinding( devKey, zcl.Cluster.PollCtrl, "01") # 01 is our endpoint we want CHECKIN messages to come to if zcl.Cluster.OnOff in outClstr and zcl.Cluster.OnOff not in binding: # If device sends OnOff commands (eg a Button) return SetBinding( devKey, zcl.Cluster.OnOff, "0A" ) # 0A is our endpoint we want messages to come to (so that we get TOGGLE, ON and OFF commands) if zcl.Cluster.Temperature in inClstr and zcl.Cluster.Temperature not in binding: return SetBinding( devKey, zcl.Cluster.Temperature, "01" ) # 01 is our endpoint we want Temperature reports to come to if zcl.Cluster.SimpleMetering in inClstr and zcl.Cluster.SimpleMetering not in binding: return SetBinding( devKey, zcl.Cluster.SimpleMetering, "01" ) # 01 is our endpoint we want SimpleMetering messages to come to if zcl.Cluster.Thermostat in inClstr and zcl.Cluster.Thermostat not in binding: return SetBinding( devKey, zcl.Cluster.Thermostat, "01" ) # 01 is our endpoint we want Thermostat messages to come to if zcl.Cluster.IAS_Zone in inClstr: if None == database.GetDeviceItem(devKey, "iasZoneType"): return telegesis.ReadAttr( nwkId, ep, zcl.Cluster.IAS_Zone, zcl.Attribute.Zone_Type ) # Get IAS device type (PIR or contact, etc.) if zcl.Cluster.Basic in inClstr: if None == database.GetDeviceItem(devKey, "modelName"): return telegesis.ReadAttr( nwkId, ep, zcl.Cluster.Basic, zcl.Attribute.Model_Name) # Get Basic's Device Name if None == database.GetDeviceItem(devKey, "manufName"): return telegesis.ReadAttr( nwkId, ep, zcl.Cluster.Basic, zcl.Attribute.Manuf_Name) # Get Basic's Manufacturer Name if zcl.Cluster.PowerConfig in inClstr and "SED" == database.GetDeviceItem( devKey, "devType"): checkBatt = GetTempVal(devKey, "GetNextBatteryAfter") if checkBatt != None: if datetime.now() > checkBatt: log.debug("Now = " + str(datetime.now()) + " and checkBatt = " + str(checkBatt)) return telegesis.ReadAttr( nwkId, ep, zcl.Cluster.PowerConfig, zcl.Attribute.Batt_Percentage) # Get Battery percentage if zcl.Cluster.PollCtrl in inClstr: if None == database.GetDeviceItem(devKey, "longPollInterval"): return telegesis.ReadAttr( nwkId, ep, zcl.Cluster.PollCtrl, zcl.Attribute. LongPollIntervalQs) # Get Poll Control's Long poll interval if zcl.Cluster.OTA in outClstr: if None == database.GetDeviceItem(devKey, "firmwareVersion"): return ("AT+READCATR:" + nwkId + "," + ep + ",0," + zcl.Cluster.OTA + "," + zcl.Attribute.firmwareVersion, "RESPATTR" ) # Get OTA's Version number as a string of hex digits if msp_ota != None and msp_ota in outClstr: if None == database.GetDeviceItem(devKey, "firmwareVersion"): return ("AT+READMCATR:" + nwkId + "," + ep + ",0," + config.Get(mfgId) + "," + msp_ota + "," + zcl.Attribute.firmwareVersion, "RESPMATTR" ) # Get OTA's Version number as a string of hex digits reporting = database.GetDeviceItem(devKey, "reporting", "[]") if zcl.Cluster.PowerConfig in binding and "SED" == database.GetDeviceItem( devKey, "devType"): atCmd = CheckReporting( devKey, reporting, "batteryReporting", zcl.Cluster.PowerConfig, zcl.Attribute.Batt_Percentage, zcl.AttributeTypes.Uint8, "43200,43200,2" ) # Default temperature reporting is "Every 12 hours" if atCmd != None: return atCmd if zcl.Cluster.Temperature in binding: atCmd = CheckReporting( devKey, reporting, "temperatureReporting", zcl.Cluster.Temperature, zcl.Attribute.Celsius, zcl.AttributeTypes.Uint16, "300,3600,100" ) # Default temperature reporting is "between 5 mins and 1 hr, or +/- 1.00'C" if atCmd != None: return atCmd if zcl.Cluster.SimpleMetering in binding: atCmd = CheckReporting( devKey, reporting, "powerReporting", zcl.Cluster.SimpleMetering, zcl.Attribute.InstantaneousDemand, zcl.AttributeTypes.Sint24, "-1,-1,10" ) # Default power reporting is "between 5 seconds and 15 minutes, or +/- 10W" if atCmd != None: return atCmd atCmd = CheckReporting( devKey, reporting, "energyConsumedReporting", zcl.Cluster.SimpleMetering, zcl.Attribute.CurrentSummationDelivered, zcl.AttributeTypes.Uint48, "-1,-1,100" ) # Default energy consumed reporting is "between 1 minute and 15 minutes, or +100Wh" if atCmd != None: return atCmd atCmd = CheckReporting( devKey, reporting, "energyGeneratedReporting", zcl.Cluster.SimpleMetering, zcl.Attribute.CurrentSummationReceived, zcl.AttributeTypes.Uint48, "-1,-1,0" ) # Default energy generated reporting is "never" (-1 as max) if atCmd != None: return atCmd if zcl.Cluster.Thermostat in binding: atCmd = CheckReporting( devKey, reporting, "targetTempReporting", zcl.Cluster.Thermostat, zcl.Attribute.OccupiedHeatingSetPoint, zcl.AttributeTypes.Sint16, "60,900,100" ) # Default target temperature reporting is "between 1 minute and 15 minutes, or +/-1.00'C" if atCmd != None: return atCmd if GetTempVal(devKey, "JustSentOnOff"): DelTempVal(devKey, "JustSentOnOff") return telegesis.ReadAttr( nwkId, ep, zcl.Cluster.OnOff, zcl.Attribute.OnOffState) # Get OnOff state after sending toggle return None