All my roller shutters work with electrical motors, so it made sense to hook them up to my z-wave gateway that I had set up in a previous project. This provides me with remote control but also other features like metering (kWh).
This is cool and all, but I'm pretty much a lazy guy. If I can avoid having to press a button 2 times a day, then hell, I don't want to press that button anymore. Even if it requires me a couple of weeks hacking.
SolutionInstead of me having to actively instruct the house to close the roller-shutters, it would be better if the house could automatically do this for me, depending on my user state.That is, I want the roller-shutters to open when I have woken up and they need to close again, in the evening at TV-time, or a little before if it's too dark outside.
User state detectionFor this project, we are still going to manually determine these user-states and how they are detected. So, I will decide when/how the system knows that I have woken up and when it's evening time (compared to an AI that would learn this from behavior).
So, for detecting 'wake-up', I decided to use the motion sensor eye that we installed in a previous project. This sensor is triggered whenever I go up or down the stairs. It is the second sensor that gets activated in the morning (the first being the light button in the bedroom, but that can be used before actually getting up) . So the motion sensor is a prime candidate as a trigger to open the roller-shutters.
The evening is a bit harder. I decided to use one of the lights that's always turned on in the evening as the signal to close the roller-shutters.This is still a simplistic approach. There isn't much intelligence behind it. But at least the roller-shutters are now already more dynamic compared to a clock that would do it's thing every day at the same time (no matter what the season or type of day).
Hardware setupMost of the hardware has already been set up in previous projects: the light button is attached to the Controllino and the motion eye has already been used in a previous automation. Only the roller-shutters are new.
So to set up your hardware:
- Add the motion eye to the z-wave gateway. Just put the pygate in inclusion mode and press 3 times on the button inside the eye.
- Add the roller-shutters to the z-wave gateway. These things usually jump into inclusion mode, when they are turned on for the first time. So just set the pygate in inclusion mode and the roller-shutters will appear automatically when powered on.
This project was written in python, using the (alpha version of the) event library from AllThingsTalk. It allows you to declare functions as rules that get executed when a certain condition has arrived. This is a far more flexible system compared to the rules wizard that only supports basic if-then-else constructs (see this project for an example). To get you started, just take the template project included with the source code. It contains:
- A module called '
rules.py
'. This is where you put all your code.
- '
credentials.py
': change this for your own username and password.
This is my rules.py
(without the imports):
controllino = Device(id='123')
btnLivingAllOn = Sensor(device=controllino, name='59')
lightGangBoven = Actuator(id='123')
TVLighstOnScene = Actuator(id='123')
gateway = Gateway(id='123')
gangAchterBeweging = Device(gateway=gateway, name='zwave_17')
gangBovenBewegingSensor = Sensor(device=gangBovenBeweging, name='48_0_1')
_last_time_rollers_closed = None # don't open them 2 times a day.
_last_time_rollers_opened = None
@When([btnLivingAllOn], lambda: btnLivingAllOn.value == True)
def livinAllLightOn():
global _last_time_rollers_closed
TVLighstOnScene.value = True
now = datetime.datetime.now()
if now.hour > 14 and (_last_time_rollers_closed == None or
_last_time_rollers_closed.day != now.day):
_last_time_rollers_closed = now
close_rollers_for_evening()
@When([gangBovenBewegingSensor])
def gangBoven():
global _last_time_rollers_opened
value = iot.trigger.value #iot.trigger = gangBovenBewegingSensor, small temp hack
lightGangBoven.value = value
now = datetime.datetime.now()
if now.hour > 6 and now.hour < 14
and (_last_time_rollers_opened == None
or _last_time_rollers_opened.day != now.day):
_last_time_rollers_opened = now
open_rollers()
# declare global so that they get loaded 1 time and keep required data in mem (id)
rol_Living = Actuator(id='123')
rol_bed1 = Actuator(id='123')
rol_bed2 = Actuator(id='123')
rol_dres = Actuator(id='123')
rol_door = Actuator(id='123')
def close_rollers_for_evening():
rol_Living.value = 15 #need to recalibrate this thing correctly
rol_bed1.value = 0
rol_bed2.value = 0
rol_dres.value = 0
rol_door.value = 0
def open_rollers():
rol_Living.value = 99
rol_bed1.value = 99
rol_bed2.value = 99
rol_dres.value = 99
rol_door.value = 99
ExplanationDeclarations
In the first section are a bunch of declarations for assets, devices and a gateway. These objects represent instances within the IoT cloud. You can identify an object in various ways:
- Use the ID of the object: every instance in the IoT cloud has a unique ID. This form only requires an ID, no other fields are necessary.
- Use the name of the object: every instance in the IoT cloud has a name that is unique within the namespace of it's parent. For instance, an asset has a name that is unique within the device it belongs to and a device has a name that is unique within the gateway it is attached to. If you use this form, you always need to specify the parent, either as a reference to another object (device or gateway), or is the ID of the gateway or device. You can also specify the name of the device together with the ID of or object for the gateway.
Not shown in this example, but this form of identification also allows you to build queries so that you can work with groups of devices. This is done with factories (for another project).
Side note: The asset names for z-wave devices map to: <command-class>-<function-nr>
.
rules
@When([btnLivingAllOn], lambda: btnLivingAllOn.value == True)
This decorator instructs the function that follows to become a rule that is triggered when the button 'btnLivingAllOn
' is pressed. The Lambda function filters out all button releases (false): the button always sends out a value 'true
' when pressed, followed by 'false
' when the button is released.
This Lambda function might seem a bit strange at first: why not just put it as an 'if
' statement in the function itself: well, there is a small, but subtle difference: the lambda function will make certain that the rule is only executed when the condition transitions from false to true. So when the rule sends out a second event that would make the condition evaluate to true, 2 times in a row, then the second time, the rule is not executed. Think about it like this: you don't want to get an email every time that the device reports battery status below x%, only the first time, but again after it has been re-charged.
def livinAllLightOn():
global _last_time_rollers_closed
TVLighstOnScene.value = True
now = datetime.datetime.now()
if now.hour > 14 and (_last_time_rollers_closed == None or
_last_time_rollers_closed.day != now.day):
_last_time_rollers_closed = now
close_rollers_for_evening()
The rule itself turns on the lights (a scene containing several light sources) when the button is pressed (the button is on the Controllino, the light is z-wave), then it checks if the button is pressed for the first time that day, after 14 hours, if so, the roller shutters are closed. A similar technique is used to open the roller shutters in the morning: this is done when the sensor gets triggered for the first time that day in the morning (after 6 before 14 hours). To get the current state of an asset you can use the 'value
' property. With actuators, you can use the same property to send an actuator command.
Finally, the module 'resources
' has a global called 'trigger
', which contains a reference to the asset that caused the evaluation and execution of the rule.
This is easy: just copy the project on your RPI that also functions as the z-wave gateway and make it auto start at run time (using the cron for example).
About the techniqueWith this technique, we use the mqtt
and http
API services to hook into the event system of AllThingsTalk. The actual code performing the automations is running on our own system, be it a Raspberry Pi or perhaps some cloud server. This has some implications:
- Pro: We have a full programming language at our disposal without restrictions on how it is run. This means we can pretty much do whatever we want. A great improvement in flexibility over the if-then-else approach of rules.
- Con: Perhaps to your surprise, this approach actually requires a lot more data traffic compared to the '
if-then-else
' of the rules wizard. When I pass the sensor in the morning, it first goes to the cloud, comes back to the gateway where the rule is evaluated, then goes back to the cloud to finally come back to the gateway where the actuations are performed.
This approach is ideally suited for building a set of custom services that run on top of the AllThingsTalk core and which are distributed on server(s) in another network or sub system, like in the following diagram:
That said, as you will find out from next projects, this event driven approach in python turns out to be very portable to other models: single vs multi user; local vs cloud execution.
In our setup, where both gateways/devices and services are running in the same local network, more optimal approaches are possible. So our quest is not yet over. Stay tuned!
Comments