In the previous tutorial we learned how to obtain a reference to a device and perform some action when the device emits a signal. In this tutorial we will progress onto the reception of more complex signals and controlling another device based on the contents of the signals.
This tutorial will cover:
In the last tutorial we worked with a motion sensor type device. Signals from that type of device do not contain any data, they just indicate to the connected methods that the sensor has just fired. In this tutorial we shall use a sensor which includes data in it's signals.
If we look at the door sensor on the main entrance door in the home simulator, we can see that this device is named "livingRoomDoorSensor" and it emits a signal referred to as stateChanged(bool):
This indicates that this device's stateChanged signal conveys a single boolean argument to the connected method. Compared to what we've previously encountered, there is a slightly different syntax for defining connections from signals which contain arguments.
As per the previous tutorial, connections from signals which don't contain arguments look like:
whereas connections from signals which contain arguments look like:
The difference is that when the signal contains arguments the signal name must be appended with a list of expected argument types in square brackets. In this case, the stateChanged signal contains a single boolean argument, but it could contain any number of python data types, separated by commas. As long as the slot or method that the signal is connected to has a matching set of input arguments, the signal connection will be successful.
In the code snippet above we can see that the stateChanged signal, containing a boolean argument, is connected to the onSensorStateChanged method. That method must be created to accept the same number and types of arguments that the connected signal contains. An example of the definition of this method is:
In this case, the variable newState will be passed the boolean value which the emitted stateChanged signal contained. The arguments in the method can be given any names as long as the number of arguments, excluding the self argument, is the same as in the connected signal. For more background on how signals and slots behave in this Python-Qt framework feel free to read Signals and Slots in PySide | Qt Wiki | Qt Project.
At any point in our app, we can call a method on any device to actuate, or change the state of, the device. To achieve this we must get a reference to the device, then call the relevant method on that reference.
If we consider the light actuator in the living room:
we can see that the device's name is "livingRoomLight" and it contains a method named turnOn(). Hence, to turn on this device we can use the code:
As a more functional example, if we wanted to turn on the living room light every time the living room door is opened, we could use the code:
The previous code snippet is a perfectly functioning example. However, it has the one disadvantage that every time the door opens, we have to re-fetch the reference to the light object before actuating the light object. A better approach would be to fetch the reference to the light object once; when the app starts, and store it in an instance variable to be used again and again. Modifying the code to use this approach would give:
The only changes in this piece of code compared to the previous one are highlighted. Even though the changes are minor, this prevents unnecessary repeated fetching of references to a the same device by storing the reference in the instance variable self.light when the app starts. This instance variable is available for the lifetime of the app and can be accessed in any methods in the app.
Remember that if a variable is not prepended with a self. it will "go out of scope" at the end of the method and will need to be created next time it is needed. Therefore, it is good practice to declare all devices which you expect to use in other methods throughout the app as instance variables in the run() method.
Let's develop a quick app to formalise what we've covered so far.
We want to write an app that:
To test, you can click the door sensor icon in the home simulator to toggle it's state from closed to opened and vice-versa. If you want to manually deactivate the light to test the app again, just click on the light icon.
Try programming the app for yourself, and when you're happy with it, compare it to the example solution.
The highlighted lines are the lines we've added to the skeleton app to achieve this functionality.
This tutorial has discussed how to intercept signals from devices which contain data and actuate other devices based on those signals. The next tutorial will be a quick one, explaining how we can save time and connect a signal from one device directly to a method or slot in another device.