Example #1
0
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
Example #2
0
 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)
Example #3
0
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"
            )
Example #4
0
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 + " !")
Example #5
0
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
Example #6
0
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()
Example #7
0
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)
Example #8
0
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