App Development Tutorial 6

Contents

Tutorial 6: Quicker Timed Behaviours and Interacting with Lists of Devices

The previous tutorial used signals from the clock object to trigger methods. When using that approach, the fastest rate at which we can perform some action is once per minute. If we wanted to perform some action rapidly, like once per second, we would be completely out of luck. Or would we?... The answer is; no, we wouldn't. To trigger actions more rapidly than once per minute we could use timers.

Also if we wanted to interact with several devices of a particular type, we could easily fetch a list of devices and interact with each one using a 'for-loop'. Hence, this tutorial will introduce how to:

  1. Obtain a list of devices, filtered by device type
  2. Connect a signal from each of the devices in this list to a single method
  3. Call a method on an entire list of devices
  4. Use Qt's QTrigger mechanism to generate quick-timed behaviours

Background

The CHAOS platform includes a variety of convenience functions like the ability to fetch a list of devices which match certain criteria and iterating through the devices in a Pythonic way.

Acquire List of Devices of Type

As we already know, self.deviceList is an object containing a list of all devices which were discovered in the home. As presented earlier, we can obtain a reference to a particular device using the getDevice("...") method. Alternatively, we could use the getDevicesOfType("...") method, which would instead return a list of references to all devices which match the device type criteria specified in the string argument. All devices are assigned at least two types, a main type and a sub-type. For example, a light actuator's tooltip shows it's two types;

LightActuatorTypesAndSubtypes.png

We can see that this device has the types "actuator" and "basicActuator". Hence, a call to either self.deviceList.getDevicesOfType("actuator") or self.deviceList.getDevicesOfType("basicActuator") would return a list of devices, containing this device at a minimum. We could also cascade a number of calls to this method together to filter the device list according to a few device types. E.g. self.deviceList.getDevicesOfType("actuator").getDevicesOfType("temperatureSensor") would return a list of any devices which have both actuator AND temperature sensing capabilities.

This list of devices can then be handled using regular Python syntax, like iterating through the entries, or by accessing a particular element.

Signals from a List of Devices

When we have obtained a list of all devices which we are interested in, we can start do things with them, like connecting signals from each device or calling some method on each device.

If we wanted to obtain a list of devices and connect their signals to a particular method, we could first use the getDevicesOfType method to get a list of devices from self.deviceList, then use a 'for-loop' to iterate through them. On each iteration we could connect the device obtained in that iteration to some function.

For example:

class ChaosApp(BaseChaosApp):
        def run(self):
                ...
                #Connect devices to other devices and functions here...
                listOfMotionSensors = self.deviceList.getDevicesOfType("motionSensor")
                for motionSensor in listOfMotionSensors:
                        motionSensor.motionDetected.connect(self.onMotionDetected)
                       
        def onMotionDetected(self):
                self.print("Motion detected somewhere")

In this example, whenever a device of type "motionSensor" triggers, the onMotionDeteted() method is called.

Control of all Devices in a List

In a similar vein, we could iterate through a list of devices and actuate each device to a particular state.

For example:

class ChaosApp(BaseChaosApp):
        def run(self):
                ...
                #Connect devices to other devices and functions here...
                listOfLights = self.deviceList.getDevicesOfType("basicActuator")
                for light in listOfLights:
                        light.turnOff()

would turn off all the lights as soon as the app is run.

Timed Events Using QTimers

Since CHAOS is built on a version of Python with bindings to Qt, it can avail of the full set of features that Qt offers. One such feature is QTimers. The BaseChaosApp class inherits from QObject, so it has built-in support for QTimers. At any point in our CHAOS App we can call the method self.startTimer(mSecs), where mSecs is replaced by the number of milliseconds we want between timer firings. This timer will keep firing every mSecs milliseconds until every we explicitly stop it.

Every time the timer fires, the method timerEvent(self, event) in the BaseChaosApp is called. If we override this method in our app, we can perform some action every mSecs milliseconds. This timer will be fired repeatedly until we call the self.killTimer(timerId) method. We can start numerous timers at the same time, so we must specify the ID of the timer to kill in the timerId parameter. The ID of the started timer can be obtained when calling self.startTimer(mSecs) or it can be obtained within the overridden timerEvent(self, event) method by using event.timerId().

For example, if we wanted to perform some action (in this case simply output a message) some number of seconds after the app launches, we could use:

class ChaosApp(BaseChaosApp):
        def run(self):
                ...
                #Connect devices to other devices and functions here...
                # Start a 5 seconds (5000 milliseconds) timer:
                self.startTimer(5000)
       
        def timerEvent(self, event):
                self.print("QTimer fired")
                # We want to the timer the first time the timerEvent method is called:
                self.killTimer(event.timerId())

Remember that if we did not call self.killTimer(timerId), the timer would continue firing every 5 seconds. In the situation where we launch multiple timers, self.timerEvent(...) will be called when ANY timers are fired. In this case we need to store the ID of each timer as we launch them and then check the value of event.timerId() every time self.timerEvent(...) is called to evaluate which timer has fired. self.startTimer(...) will assign a unique ID to each started timer and return the ID for that timer. We can store this ID in an instance variable to compare with event.timerId() in self.timerEvent(...) to decide what action we need to take. Examples of more sophisticated timer usage can be found in the App Store.

The App

Everything we've learned so far can be combined to create our most sophisticated app yet.

Objective

We want to write an app that monitors the windows and doors for opening and closing during the night and will notify the user with a few blinks of the lights when a potential intruder is detected. The app needs to:

  1. Detect when any door or window opens
  2. Check if it is currently in the monitoring time period in which we are supposed to notify the occupant that there is potential intruder
  3. Flash the lights a number of times in all rooms to passively notify the occupants that something has occurred

Solution

As usual, you should try to create this app for yourself and compare with the solution below by pressing the Expand button.

Example Solution:

from __future__ import print_function
from ChaosApps import BaseChaosApp

class ChaosApp(BaseChaosApp):
        def run(self):
                self.appName='Tutorial 6'
                self.print("Skeleton App '"+self.appName+"' Running")

                #Connect devices to other devices and functions here...
               
                self.startMonitoringTime = 22 # 10pm
                self.stopMonitoringTime = 8   #   8pm
                self.flashesRemaining = 0

                #Connect all door/window sensors to the same function:
                listOfDevices = self.deviceList.getDevicesOfType("openSensor")
               
                for device in listOfDevices:
                        self.print(device)
                        device.stateChanged[bool].connect(self.onDoorOrWindowEvent)

        def onDoorOrWindowEvent(self,isOpened):
                # We only want to notify the occupant if the door has just opened.
                # A closing door is not much of a security risk:
                if isOpened:
                        (year,month,day,hour,minute) = self.clock.getDateTime()
                        # Check if the time is in the range in which we should be monitoring
                        if hour<self.stopMonitoringTime or hour> self.startMonitoringTime:
                                self.flashAllLights()
                       
        def flashAllLights(self):
                self.print("Alarm event detected, flash the lights")
                self.flashesRemaining = 6
                self.startTimer(200)           
               
        def timerEvent(self, event):
               
                for device in self.deviceList.getDevicesOfType("basicActuator"):
                        device.toggleState()

                self.flashesRemaining -= 1
                if self.flashesRemaining <= 0:
                        self.killTimer(event.timerId())
                       
        def initAppGui(self):
                self.print("'" + self.appName + "': CHAOS has asked for the current HTML of the phone app GUI for this CHAOS App")

                htmlString = """appGuiConstructionString"""

                return htmlString

        def appGuiInteractionCallback(self,someData):
                self.print(self.appName + ": Some information has arrived for this CHAOS App from the phone GUI ")

        def stop(self):
                self.print("Disconnecting any created signals and then stopping '" + self.appName + "'")

The highlighted lines are the lines we've added to the skeleton app to achieve this functionality.

A more sophisticated version of this app could get the lights to flash in all rooms except the room where the door or window opening was detected. We don't need to notify the user if they are in the same room where the triggering event originated. In fact, such behaviour could even be perceived as nuisance behaviour. Later tutorials will present how to deduce the name of the sensor whose firing resulted in a method being called.

Wrap Up

In this tutorial we've learned how to create and interact with lists of devices and how to generate quickly timed actions. The next tutorial will move closer to what the user will be presented with by explaining how to generate phone GUI content.

<< - <Prev - Next>