Second part about home automation: my usage of a WAGO, previous chapter: Overview
WAGO: Wiring
Before settling on WAGO I had some crazy ideas to do that with a Raspberry Pi and some custom (low voltage) circuits, but my
brother (thanks!) gave me the idea to “just” use such an industrial control system. They are quite expensive, but it is possible
to get used ones on ebay for a more affordable price. And my pretty old WAGO is very reliable, I didn’t have a single issue since
I installed it.
The WAGO system looks like this:
Top green box: Power supply
Below on left: The WAGO itself
On the right: The extension cards
Revising Lights
With a 24V I/O card, e.g. a 16 channel 750-1504 card it
is possible to send a signal as a push button would do it.
Basically:
But the problem is: A push button can be pressed at any time and then the light will be switch on (or off). The WAGO doesn’t know
if the light is currently on or off.
Idea 1: Use a 230V input card to measure if there is currently a voltage on the latching switch. But this idea has several
downsides; I think I/O cards are more expensive, but more importantly: I would need to work with high voltage. High voltage is
more complicated, dangerous and as someone without an electrical degree, I am simply not allowed to work on it.
Idea 2: Use a dual latching switch. These have two inputs and two outputs; they are independent but are always switched at the
same time. Use one input/output pair with 230V to switch to light and the other pair with 24V to signal to the WAGO the current
state of the switch. For WAGO there are 24V input cards, e.g.
750-1405.
Now the circuit looks like this:
What I particularly like about this setup: If the WAGO breaks or is turned off, lighting in the house will continue to work just
fine with the push buttons.
Revising Roller Shutters
First idea is to add the WAGO output in parallel to the switches and then the relays will put current on the up or down input of
the motor. With just one mechanically locked switch it was easy to make sure that never up and down have a current at the same
time, but now the WAGO could send Up and someone presses the Down switch (or is already pressed). Also finding out the current
state of shutter (is it up or down) is difficult if there is a bypass. There is no feedback to measure the current position of the
roller shutter.
One idea is to re-wire the relays in a way that only one current (either Up or Down) can be active; this should also help against
software bugs when I’ll program the WAGO: I might turn on Up and Down in the WAGO at the same time.
The relays get their input from below and have two outputs: Only the left output has power when the relay is off and only the
right output has power when the relay is on (== 24V are on the control input). When just one of the relays is turned on, then
there is a current to Up (or Down) of the motor. But if both are turned on, then the Relay Up cuts power to the Relay Down and
there is only power on the Up input of the motor.
To be honest, I didn’t try it out if this is enough to protect the motor and I don’t know if this is needed at all.
Another part is: I re-wired the Up/Down switches as an input to the WAGO, and connected output channels to the relays:
WAGO: I/O cards
The WAGO itself is a tiny, ancient Linux system and the I/O cards are attached to it. I can (with some limitations) plug in any
I/O card I like.
Default passwords:
web: admin / wago
telnet (no ssh): root / wago
I had no prior knowledge about these systems and I barley know enough to achieve what I wanted and I already forgot most of it.
So, this part is probably flawed and incomplete.
After attaching I/O cards to the system, they need to be configured in a tool called “WAGO-IO-Check 3”, so the WAGO systems knows
what to do with them. There is also some auto-detect which the tool can do, but on my WAGO it needed to be connected via a serial
cable just for that. Other tasks can be done via network. Or I can tell it manually which cards I plugged in, which is what I did:
I only have a few cards.
Pos 02: 750-1405: 16 channel digital input; DC 24V
Pos 03: 750-1405: 16 channel digital input; DC 24V
Pos 04: 750-610: Another connection for power supply; not really needed I think
Pos 05: 750-530: 8 channel digital output; DC 24V; 0.5A
Pos 06: 750-1504: 16 channel digital output; DC 24V; 0.5A
Pos 07: 750-1504: 16 channel digital output; DC 24V; 0.5A
Pos 08: 750-600: Termination module, must be present at end of bus
Spare parts: 2x 750-430, 1x 750-1504
WAGO: Programming
Initially I thought: nice Linux system; is there some Python or C interface to work with these i/o cards? Well, no, I had to learn
something called “Codesys”. It is at least exotic enough that hugos long list of syntax
highlighters doesn’t contain
it. I used “ada” which does some coloring.
It is an integrated IDE that shows an ancient user interface and is quite clunky to use, but at least the nice thing is the
integrated debugger and direct uploading of the program to my WAGO. Still, I would have prefered a proper programming language.
Anyway, the basic idea is: you have a program that checks the state of inputs, sets outputs and then it is repeated in an infinite
loop. I had expected more something like: you get events “input x changed to y” and then you react to it.
There are three parts:
Main program
Inputs and outputs related to light
Inputs and outputs related to roller shutters
Network communication with OpenHAB
Open codesys IDE:
Main
Main program variables:
1
2
3
4
5
6
PROGRAM PLC_PRG
VAR
SHUTTER : ARRAY[1..9]OF SHUTTER_ACTION;
LIGHT : ARRAY[1..20]OF LIGHT_ACTION;
I : BYTE;
END_VAR
Line 3-4: A “LIGHT_ACTION” and a “SHUTTER_ACTION” is a “FUNCTION_BLOCK” which has input and output variables, internal variables and constants. They each have code (see next chapters).
SHUTTER[1].RUNTIME := 543;
SHUTTER[2].RUNTIME := 543;
SHUTTER[3].RUNTIME := 543;
SHUTTER[4].RUNTIME := 543;
SHUTTER[5].RUNTIME := 300;
SHUTTER[6].RUNTIME := 300;
SHUTTER[7].RUNTIME := 300;
SHUTTER[8].RUNTIME := 300;
SHUTTER[9].RUNTIME := 300;
FOR I:=1 TO 9 BY 1DO SHUTTER[I](NET_SOLL_POS := WORD_TO_BYTE(MBCFG_ModbusSlave.SOLL_POS[I]), NET_COMMAND := WORD_TO_BYTE(MBCFG_ModbusSlave.COMMAND[I]));
END_FOR;
SHUTTER[1](CHANNEL := 0, STATE := %IX0.0); (* Kueche *)
SHUTTER[1](CHANNEL := 1, STATE := %IX0.1);
SHUTTER[2](CHANNEL := 0, STATE := %IX0.2); (* Essen *)
SHUTTER[2](CHANNEL := 1, STATE := %IX0.3);
SHUTTER[3](CHANNEL := 0, STATE := %IX0.4); (* Sofa *)
SHUTTER[3](CHANNEL := 1, STATE := %IX0.5);
SHUTTER[4](CHANNEL := 0, STATE := %IX0.6); (* Heizraum *)
SHUTTER[4](CHANNEL := 1, STATE := %IX0.7);
SHUTTER[4](CHANNEL := 2, STATE := %IX0.8);
SHUTTER[4](CHANNEL := 3, STATE := %IX0.9);
SHUTTER[5](CHANNEL := 0, STATE := %IX0.10); (* Kind 1*)
SHUTTER[5](CHANNEL := 1, STATE := %IX0.11);
SHUTTER[6](CHANNEL := 0, STATE := %IX0.12); (* Kind 2*)
SHUTTER[6](CHANNEL := 1, STATE := %IX0.13);
SHUTTER[7](CHANNEL := 0, STATE := %IX0.14); (* Schlafzimmer *)
SHUTTER[7](CHANNEL := 1, STATE := %IX0.15);
SHUTTER[8](CHANNEL := 0, STATE := %IX1.0); (* Bad *)
SHUTTER[8](CHANNEL := 1, STATE := %IX1.1);
SHUTTER[9](CHANNEL := 0, STATE := %IX1.2); (* HWR *)
SHUTTER[9](CHANNEL := 1, STATE := %IX1.3);
%QX0.0 := SHUTTER[1].OUTPUT_ACTION =1;
%QX0.1 := SHUTTER[1].OUTPUT_ACTION =2;
%QX0.2 := SHUTTER[2].OUTPUT_ACTION =1;
%QX0.3 := SHUTTER[2].OUTPUT_ACTION =2;
%QX0.4 := SHUTTER[3].OUTPUT_ACTION =1;
%QX0.5 := SHUTTER[3].OUTPUT_ACTION =2;
%QX0.6 := SHUTTER[4].OUTPUT_ACTION =1;
%QX0.7 := SHUTTER[4].OUTPUT_ACTION =2;
%QX0.8 := SHUTTER[5].OUTPUT_ACTION =1;
%QX0.9 := SHUTTER[5].OUTPUT_ACTION =2;
%QX0.10 := SHUTTER[6].OUTPUT_ACTION =1;
%QX0.11 := SHUTTER[6].OUTPUT_ACTION =2;
%QX0.12 := SHUTTER[7].OUTPUT_ACTION =1;
%QX0.13 := SHUTTER[7].OUTPUT_ACTION =2;
%QX0.14 := SHUTTER[8].OUTPUT_ACTION =1;
%QX0.15 := SHUTTER[8].OUTPUT_ACTION =2;
%QX1.0 := SHUTTER[9].OUTPUT_ACTION =1;
%QX1.1 := SHUTTER[9].OUTPUT_ACTION =2;
FOR I:=1 TO 9 BY 1DO MBCFG_ModbusSlave.POSITION[I] := SHUTTER[I].POSITION;
END_FOR;
Line 1-9: The “RUNTIME” is an estimate how long it takes (in 100ms) for the shutter to fully open or close, e.g. the first shutter is ~54.3s.
Line 11-13: Read from network the target position (0% .. 100%) of the shutter and a NET_COMMAND (will be explained later)
Line 15-34: Read the input channels and hand over the state to the shutter subprogram. E.g. “%IX0.0” is the Down switch, “%IX0.1” the Up switch for the first shutter. The STATE is a boolean.
Line 23-24: This shutter is special: it has two pairs of switches: one in the machine room, the other in the kitchen
Line 36-53: The OUTPUT_ACTION could be 0, then both outputs are off and the shutter is stopped. On 1 only the first output is turned on (down); on 2 the second (up)
Line 55-57: Report current estimated position to network
Line 64-83: Read current state of a light from i/o card
Line 85-104: Enable/disable outputs to turn a light on or off
Line 106-112: Report to network
Line 114: Drive network (modbus) module
Lights
Next part is the function block “LIGHT_ACTION”. As usual it has a header:
1
2
3
4
5
6
7
8
9
10
11
12
13
FUNCTION_BLOCKLIGHT_ACTIONVAR_INPUTSTATE : BOOL; (* an oder aus?*)
NET_COMMAND : BYTE; (*0: AUS, 1: AN, 2: NOOP (benoetigt, um gleiches kommando nochmal zu akzeptieren) *)
END_VAR
VAR_OUTPUT
OUTPUT_ACTION : BOOL := FALSE;
END_VAR
VAR
LAST_NET_COMMAND : BYTE := 2;
IN_PULSE : BOOL := FALSE;
PULSE : TP := (PT := T#100ms);
END_VAR
Line 3: The current state (on/off). A state change can happen with a push button (behind WAGOs back).
Line 4: Command from network (OpenHAB). 0: light shall be OFF, 1: ON, 2: NOOP.
Line 7: State of output channel. To switch the state of the light, this switches to TRUE briefly and then back to simulate a push button.
Line 10-12: Internal variables
Line 10: Remember last given NET_COMMAND.
Modbus just stores the values that have been written by someone (in our case OpenHAB). But a 0 doesn’t mean: the light shall be
OFF forever, because then whenever someone uses a push button, we would immediately turn off the light again. Instead we need to
work trigger-based, i.e. when the command changes to 0, then we have to turn off the light. This should also make it clear, why a
NOOP is needed: We do not get a notification (or event) that the value has been written, but instead at some point the value is
different (or not). Therefore, if OpenHAB would write a 0 again the value doesn’t change and we don’t trigger. So OpenHAB needs to
write a 0 wait a moment and write a 2 (NOOP). Next time it wants to turn off the light (because maybe the push button was used and
the light is on) it can write a 0 again and we will notice it.
Here is the body:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
IN_PULSE := FALSE;
IF NET_COMMAND >=0AND NET_COMMAND <=2AND NET_COMMAND <> LAST_NET_COMMAND THEN LAST_NET_COMMAND := NET_COMMAND;
IF NET_COMMAND =0THENIF STATE THEN (* soll aus sein, ist aber an *)
IN_PULSE := TRUE;
END_IF;
ELSIF NET_COMMAND =1THENIFNOT STATE THEN (* soll an sein, ist aber aus *)
IN_PULSE := TRUE;
END_IF;
END_IF;
END_IF;
PULSE(IN := IN_PULSE);
OUTPUT_ACTION := PULSE.Q;
Line 3: Trigger for NET_COMMAND: It is a valid value and it changed
Line 5: Light shall be OFF …
Line 6: … but light is ON, therefore send a pulse
Line 10-15: Light shall be ON, but is OFF, send pulse
Line 18: A built-in pulse block. TP(IN, PT, Q, ET). When IN is TRUE, Q will be TRUE for PT time (ET counts up the time until PT)
Roller Shutters
The roller shutters are more complicated. The inputs are the state of the up or down switch(es) (maybe several!) and the network
command.
The ideas I came up with:
the last command wins (switch, network)
when the down-switch is turned on, then the roller shutter shall go down to 100%. Same for up-switch.
when going down(up) and the up(down)-switch turns on, then it shall reverse direction to up(down)
if a switch turns from on to off, this means stop
the current position shall be estimated (0%..100%)
the runtime for 0% to 100% (or back) is just an estimate and calibration is needed: when reaching 0 or 100, keep going for a bit
to make sure it always reaches the end position and set position to 0 / 100.
TICK(IN := OUTPUT_ACTION <> 0);
IF TICK.Q THENCASE OUTPUT_ACTION OF1: TICK_COUNT := TICK_COUNT +1;
2: TICK_COUNT := TICK_COUNT -1;
END_CASE;
IF TICK_COUNT <0THEN TICK_COUNT := 0;
TICK_COUNT_EXTRA := TICK_COUNT_EXTRA +1;
END_IF;
IF TICK_COUNT > RUNTIME THEN TICK_COUNT := RUNTIME;
TICK_COUNT_EXTRA := TICK_COUNT_EXTRA +1;
END_IF;
POSITION := INT_TO_BYTE( (TICK_COUNT *100) / RUNTIME);
TICK(IN := FALSE);
TICK(IN := TRUE);
(* pruefen, ob Zielposition erreicht (oder uebererreicht) ist, dann stoppen *)
IF OUTPUT_ACTION =1THEN (* DOWN *)
IF SOLL_POS =100THEN (* nachlaufen lassen, damit wir sicher sind, dass position richtig ist, egal wie sie initialisiert wurde *)
IF TICK_COUNT_EXTRA > RUNTIME THEN (* POSITION ist schon 100*)
OUTPUT_ACTION := 0;
END_IF;
ELSEIF POSITION >= SOLL_POS THEN OUTPUT_ACTION := 0;
END_IF;
END_IF;
ELSIF OUTPUT_ACTION =2THEN (* UP *)
IF SOLL_POS =0THEN (* nachlaufen lassen, damit wir sicher sind, dass position richtig ist, egal wie sie initialisiert wurde *)
IF TICK_COUNT_EXTRA > RUNTIME THEN (* POSITION ist schon 0*)
OUTPUT_ACTION := 0;
END_IF;
ELSEIF POSITION <= SOLL_POS THEN OUTPUT_ACTION := 0;
END_IF;
END_IF;
END_IF;
END_IF;
IF OUTPUT_ACTION =0THEN TICK_COUNT_EXTRA := 0;
END_IF;
Line 39: If we are moving, measure the next 100ms, when they have elapsed TICK.Q in line 41 is TRUE. Remember: this program is
constantly re-executed; multiple times in 100ms.
Line 42-45: TICK_COUNT is increased/decreased by one every 100ms.
Line 47-54: When the end position is reached, TICK_COUNT stays at 0/RUNTIME, but TICK_COUNT_EXTRA is further incremented. This
is the calibration at work.
Line 56: From the current time elapsed we calculate the current position
Line 57-58: Start next tick
Line 61-67,73-77: The end position is reached, keep going for another RUNTIME and then stop. (Initially some of our roller
shutter were half broken, i.e. the motors were stuttering and it took an unpredictable amount of time for a full cycle; now
instead of RUNTIME, a smaller constant like 10 (==1s) should be also fine, but it doesn’t hurt)
Line 68-70,79-81: Stop if target position (that is not an end position) is reached.
Line 86-88: Reset extra ticks when stopped, i.e. at 100% it is possible to stop and go down again for another RUNTIME (extra
ticks); quite useful when the shutters were broken and they were actually not at 100% yet.
Network
Initially I was looking for a way to send/receive TCP or UDP messages, but I had to learn that this is not The Way. Modbus is
something this thing can do. This is some weird protocol from the serial line world that got a second life in the IP world.
Basically it allows you to set and get values (numbers) addressed via some numbers (that have to be in certain ranges). Somewhat
similar to SNMP, but a lot less flexible and limited. It probably makes all sense, when looking at the serial world, but in the IP
world, it is just a strange beast, for me.
Luckily, OpenHAB speaks modbus. Common ground, yeah. But it still needs some clutches.
I picked these addresses:
31000..31009: current roller shutter position (written by WAGO, read by OpenHAB)
41000..41009: target position (read by WAGO, written by OpenHAB)
The 30000 range is for reading from WAGO, the 40000 range is for writing to WAGO. It seems to be always a 16-bit value.
In the IDE this mapping needs to be configured.
The OpenHAB setup to drive this modbus interface correctly is ugly to say the least, but it works. I really hope there is a better
way to do all this, but after I got this to work like this, I lost interest in improving it further. Enough pain :)