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:
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.
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;
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.
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.
In this example, whenever a device of type "motionSensor" triggers, the onMotionDeteted() method is called.
In a similar vein, we could iterate through a list of devices and actuate each device to a particular state.
would turn off all the lights as soon as the app is run.
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:
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.
Everything we've learned so far can be combined to create our most sophisticated app yet.
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:
As usual, you should try to create this app for yourself and compare with the solution below by pressing the Expand button.
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.
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.