My time switch was conceived as the ultimate solution to any time switch needs that I might ever have. So I overengineered it a little bit. Of course it uses my DCF77 library. You can find the code together with the other example code for my libary at github.
With regard to its “real” features it sports 100 trigger points that may be explicit times or patterns. It allows them to be combined in up to 100 “groups”. For its output it uses pins 2-17 for a total of 16 independent channels. It may be controlled / programmed over the serial line. Its timer setup is always persisted to EEPROM.
Setup the Timeswitch
Step one is to hook a DCF77 module to pin A5 (== digital 19). Check if the constant dcf77_inverted_samples constant in the code fits to your module. The best way to figure this out is with the Swiss Army Debug Helper.
Now upload the timeswitch sketch and set the serial monitor to 115200 baud. It should show something like the output below.
DCF77 Timeswitch V1.0 (c) Udo Klein 2015 www.blinkenlight.net Sample Pin: 19 Inverted Mode: 1 Analog Mode: 1 Monitor Pin: 18 Initializing... ID: uk Version: 20150201 CRC: 0000 CRC ok s 00 A0, C x0000, C b0000 0000 0000 0000, D 0000000, T 00:00:00 + ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ s 99 A0, C x0000, C b0000 0000 0000 0000, D 0000000, T 00:00:00 + 122222222222222211111111111111111111111111111111111111111111 111111111111111122222222222222222222222222222222222222222222 222222222222222222222222222222222222222222222222222222222222 222222222222222222222222222222222222222222222233333334444444 444444444444444444444444444444444444444444444444444444444444 444444444444444444444444444444444444444444444444444444444445 556666677777777888888888888888888888888888888888888888888887 777777777777777777777777777777777777777777777777777777777778 synced: 2015-01-30 2 21:39:43 UTC+01 Channels: 0000 0000 0000 0000 synced: 2015-01-30 2 21:39:44 UTC+01 Channels: 0000 0000 0000 0000 synced: 2015-01-30 2 21:39:45 UTC+01 Channels: 0000 0000 0000 0000
The output displays my copyright. Then it shows the pin setup. It also shows the version ID of the data stored in EEPROM. Then it displays its currently loaded control code. In the example above the code shows that the memory is / was initialized and there is no control code in the time switch yet. Thus all LEDs should be off.
Then you may notice that it starts ticking numbers, one per second. These numbers indicate how many of the decoder stages are already locked. This gives a hint about how long it will take till the switch gets hold of the proper time. Sooner or later it will then show “synced” followed by the time and the output channel state.
You may use the serial monitor to send a “?” or “h” to the time switch to get some help. Notice that all commands must be terminated by either a newline or a “;”. So if you do not want to terminate them set your serial monitor to send line endings (use the drop down box at the bottom right). All commands are case insensitive. White space is always optional. Sending “h;” will give you the following output.
hH, ?: this help function Output control: P pause all output channels B break - set ouput to zero R run - also recompute CRC16 Alarm control: Erase erase all alarm settings from EEPROM D display alarm settings D nn display alarm settings for alarm nn D nn- display alarm settings for alarm nn-99 D -mm display alarm settings for alarm 00-mm D nn-mm display alarm settings for alarm nn-mm V w dd:mm:ss View how the output would be at weekday w for the given time S nn set trigger nn Options may be separated by commas A [0|1] Activation: 0 --> deactivate, 1 --> activate C xnnnn Channels encoded hexadecimal, up to 4 digits C bnnnn Channels encoded binary, up to 16 digits If the maximum number of digits is not used, the channel setting must be terminated by a comma. D ddddddd Days, starting from Monday, one binary digit per Day T Time in BCD * as Jokers are allowed + Continue current group, only admissible as final option # Close current group, only admissible as final option If the same options is used several times for the same set statement the last one wins. Examples: s 00 a1 activate channel 00 without altering its other settings s 01 a0 deactivate channel 01 without altering ... s 02 a1 xab23 activate channel 02 and set output to 1010101100100011 without ... s 03 a1 0 b1010101100100011 activate channel 03 set output to xAB23 w/o ... s 04 a1 b1, activate channel 04 and set output to x0001 w/o ... s 05 a1 ab, d1110000 t14:15:16 + activate channel 04 and set output to x000B, trigger on Monday, Tuesday, Wednesday at 14:15:16 and do not close current group s06 a1,xb,d1110000 t**:15:16 + as above but trigger every hour s07 a1,xb,d1110000 t**:*5:16 + as above but trigger every ten minutes S08 A1,XB,D1110000 T14:5*:*1 + as above but trigger whenever time matches S09 A1,XB,D1110000 T14:**:** # as above but close current group s10 t1*:15:16 not supported - for hours only the double joker ** is supported s11 t*4:15:16 see above
Lets start with the output control options. You may notice that the clock keeps running while you issue commands. This may mess up your output. Both on the serial monitor as well as for the IO lines. Hence you may stop any output while interacting with the clock. This is what the “pause” and “break” commands are for.
Stop any clock output and freeze the current output state.
Stop any clock output and reset the output state.
Continue to run the clock. This also recomputes the EEPROM CRC checksum. The clock will resume into the output state it would have if it would have been running with the current EEPROM content before. With other words: even if a newly introduced trigger has already “passed” it may influence the output immediately. That is the time switch will not wait till a trigger fires and then adapt its output. It will always backtrack and figure out what the output states would be if the clock had been running indefinitely.
The simplest of the command control commands is “Erase”. Notice that this is not “E” but “erase”. Either you spell it out completely or you will get an error. If you send “erase” to the time switch it will reset all EEPROM contents.
The display command “D” can be used to display the trigger settings. “D” will display all triggers. “D” followed by a number range will display all triggers for the range. E.g. D 1-23 would display triggers 1-13. If the start or end is omitted it will be defaulted to 0 (for the start) or 99 (for the end).
The output is always in such a format that it could be fed into the set command that I will describe below.
For example D 1-3 might output
d1-3; s 01 A0, C x0000, C b0000 0000 0000 0000, D 0000000, T 00:00:00 + s 02 A0, C x0000, C b0000 0000 0000 0000, D 0000000, T 00:00:00 + s 03 A0, C x0000, C b0000 0000 0000 0000, D 0000000, T 00:00:00 +
As already mentioned the meaning will become obvious once the set command is explained.
There is one additional trick. Obviously “D-” would default to “D 0-99”. However as this could already be achieved by sending “D” to the time switch it will output the triggers in a more compact form. That is it will output trigger 0, trigger 99, and all active triggers. For all inactive triggers it will only output the group end markers (also explained with the set command). This is mostly useful if only a small number of triggers is active.
d-; s 00 A0, C x0000, C b0000 0000 0000 0000, D 0000000, T 00:00:00 + ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ s 99 A0, C x0000, C b0000 0000 0000 0000, D 0000000, T 00:00:00 +
View / Verify
The view command “V” is useful to verify the output of the time switch for a given point in time. The command requires the weekday and the desired time. With regard to the question if the week starts on Sunday or on Monday I stick to ISO 8601. Thus Monday == 1 and Sunday == 7.
v 7 12:34:56;
Alarm Test: 7 12:34:56 Channels: 0000 0000 0000 0000
shows that on Sunday 12:34:56 all channels will be 0 (= low = inactive).
The set command “s” is the most complex and most important command of all. It is used to define the trigger times as well as the desired output states. Lets start very simple. Suppose for the sake of example we want trigger to fire on Monday – Friday 8:00 and set channels 0 and 2 to high, all other channels to low.
So we set
s 00 D 1111100; s 00 T 08:00:00; s 00 C b101; s 00 A 1;
The first command tells the clock that trigger 00 will fire on Monday – Friday but not on Saturday and Sunday. The second line tell that it will trigger at 08:00. The third line tells it to activate channels 0 and 2 and deactivate all other channels if triggered. Finally the last line tells it to activate the trigger. Inactive triggers will be ignored by the clock except for determining group ends. More on groups below. Right now we assume we have only one group. So if this is the only trigger channels 0 and 2 will be activated immediately unless there is some other trigger that tells when to deactivate the channels again.
The next trigger will be set in a similar fashion but now we combine the command options into one command
s01 D111111, T 08:05:00, Cb, A1;
This tells the time switch to trigger at 8:05 every day and set all channel to 0. “Cb” is valid because leading zeros may always be ommited. If you wonder why you set channels with “Cb” – “C” is for channels and “b” is for binary. With “Cx” you may set channels in hexadecimal. Sometimes this is more convenient. E.g. “Cxffff” is more convenient than “Cb1111111111111111”. By the way: whitespace is optional everywhere. So if you prefer you could also request “s00 Cb 1111 1111 1111 1111”.
Lets see how the result of the input so far.
d-1; s 00 A1, C x0005, C b0000 0000 0000 0101, D 1111100, T 08:00:00 + s 01 A1, C x0000, C b0000 0000 0000 0000, D 1111111, T 08:05:00 + v 2 08:02:03; Alarm Test: 2 08:02:03 Channels: 0000 0000 0000 0101 v 6 08:03:03; Alarm Test: 6 08:03:03 Channels: 0000 0000 0000 0000
Notice the use of the verify command. Also notice the output of the display command. As I already stated its output may be fed directly to the time switch. As you can see it outputs both the binary and the hexadecimal channel representation. Hmmm. This means you can feed contradicting options into the same command. If you really want to the clock will always follow the “last one wins” rule. Whatever you feed into it the latest input or input option will always win.
As we see with the verify command the clock will activate the channels as desired. But what if we want them to activate the first 5 minutes of every hour? Luckily the clock also supports “jokers”. To trigger for the first 5 minutes of every hour from Monday till Friday we issue the following.
s 00 D 1111100, T **:00:00; s 01 D 1111100, T **:05:00;
Now suppose that we want to have another set of triggers that activate channel 1 on Monday 08:00:00 till 09:00:00.
s 02 A1, Cb10, D 1000000, T 08:00:00 + s 03 A1, Cx, D 1000000, T 09:00:00 +
To make it clear this now implies the following setup
d-3; s 00 A1, C x0005, C b0000 0000 0000 0101, D 1111100, T **:00:00 + s 01 A1, C x0000, C b0000 0000 0000 0000, D 1111100, T **:05:00 + s 02 A1, C x0002, C b0000 0000 0000 0010, D 1000000, T 08:00:00 + s 03 A1, C x0000, C b0000 0000 0000 0000, D 1000000, T 09:00:00 + Now what will happen on Monday at 08:00:00? Triggers 0 and 3 will both match. So what would the output be? Well, the last one that matches best wins. Thus v 1 08:00:00 Alarm Test: 1 08:00:00 Channels: 0000 0000 0000 0010
This is definitely not what we want. What we intended was that trigger 0 and 1 as well as trigger 2 and 3 belong into different groups. To indicate this we add a group end marker to trigger 1.
Notice the different output of the display command.
d-3; s 00 A1, C x0005, C b0000 0000 0000 0101, D 1111100, T **:00:00 + s 01 A1, C x0000, C b0000 0000 0000 0000, D 1111100, T **:00:05 # s 02 A1, C x0002, C b0000 0000 0000 0010, D 1000000, T 08:00:00 + s 03 A1, C x0000, C b0000 0000 0000 0000, D 1000000, T 09:00:00 +
Lets verify what we now get on Monday 08:00:00.
v 1 08:00:00; Alarm Test: 1 08:00:00 Channels: 0000 0000 0000 0111
Now both groups trigger independently. The output of both groups is then aggregated by binary "or". That is active outputs will win over inactive outputs.
To illustrate this a little bit more here is the setup to use the time switch to drive a binary clock.
s 00 A1, C x0000, D 1111111, T **:*0:00 +; s 01 A1, C x0001, D 1111111, T **:*1:00 +; s 02 A1, C x0002, D 1111111, T **:*2:00 +; s 03 A1, C x0003, D 1111111, T **:*3:00 +; s 04 A1, C x0004, D 1111111, T **:*4:00 +; s 05 A1, C x0005, D 1111111, T **:*5:00 +; s 06 A1, C x0006, D 1111111, T **:*6:00 +; s 07 A1, C x0007, D 1111111, T **:*7:00 +; s 08 A1, C x0008, D 1111111, T **:*8:00 +; s 09 A1, C x0009, D 1111111, T **:*9:00 #; s 20 A1, C x0000, D 1111111, T **:00:00 +; s 21 A1, C x0010, D 1111111, T **:10:00 +; s 22 A1, C x0020, D 1111111, T **:20:00 +; s 23 A1, C x0030, D 1111111, T **:30:00 +; s 24 A1, C x0040, D 1111111, T **:40:00 +; s 25 A1, C x0050, D 1111111, T **:50:00 #; s 31 A1, C x0000, D 1111111, T 00:00:00 +; s 31 A1, C x0100, D 1111111, T 01:00:00 +; s 32 A1, C x0200, D 1111111, T 02:00:00 +; s 33 A1, C x0300, D 1111111, T 03:00:00 +; s 34 A1, C x0400, D 1111111, T 04:00:00 +; s 35 A1, C x0500, D 1111111, T 05:00:00 +; s 36 A1, C x0600, D 1111111, T 06:00:00 +; s 37 A1, C x0700, D 1111111, T 07:00:00 +; s 38 A1, C x0800, D 1111111, T 08:00:00 +; s 39 A1, C x0900, D 1111111, T 09:00:00 +; s 40 A1, C x1000, D 1111111, T 10:00:00 +; s 41 A1, C x1100, D 1111111, T 11:00:00 +; s 42 A1, C x1200, D 1111111, T 12:00:00 +; s 43 A1, C x1300, D 1111111, T 13:00:00 +; s 44 A1, C x1400, D 1111111, T 14:00:00 +; s 45 A1, C x1500, D 1111111, T 15:00:00 +; s 46 A1, C x1600, D 1111111, T 16:00:00 +; s 47 A1, C x1700, D 1111111, T 17:00:00 +; s 48 A1, C x1800, D 1111111, T 18:00:00 +; s 49 A1, C x1900, D 1111111, T 19:00:00 +; s 50 A1, C x2000, D 1111111, T 20:00:00 +; s 51 A1, C x2100, D 1111111, T 21:00:00 +; s 52 A1, C x2200, D 1111111, T 22:00:00 +; s 53 A1, C x2300, D 1111111, T 23:00:00 #;
As you can see I arranged the triggers in 3 groups. Two groups for the minutes and one for the hours.
Tips and Tricks
Here are some tricks to deal with special requirements that neatly illustrate how the grouping and matching logic can be pushed to its limits.
Deactivate a whole group
Sometimes it might be desirable to temporarily disable a group. Deactivating all triggers of the group is somewhat cumbersome though. In such cases it is appropriate to always terminate a group with a dedicated inactive trigger that always fires. E.g. for the second group in the binary clock example like so
s 20 A1, C x0000, D 1111111, T **:00:00 +; s 21 A1, C x0010, D 1111111, T **:10:00 +; s 22 A1, C x0020, D 1111111, T **:20:00 +; s 23 A1, C x0030, D 1111111, T **:30:00 +; s 24 A1, C x0040, D 1111111, T **:40:00 +; s 25 A1, C x0050, D 1111111, T **:50:00 +; s 26 A0, C x0000, D 1111111, T **:**:** #;
This does not alter the normal behaviour of the group. However once trigger 26 gets activated it will match any time. Since it is also the last trigger of this group it will overwrite any other trigger of this group. Since it will output 0 this effectively deactivates all other triggers of this group.
Activate all channels
E.g. for output testing it might be desirable to activate all outputs. This can be achieved by
s 99 A1, C xffff, D 1111111, T **:**:**;
Since 99 is always the last trigger and high channel output always wins this will do the trick.
Match Leap Seconds
The challenge with leap seconds is that I will not allow second 60 as a valid input for the parser. This is because I assume that in 99.99% of the cases leap seconds will be considered to special to be handled explicitly. But what if we want to trigger at a leap second? Here is my solution.
s 00 A1, Cb1, D 1111111, T **:59:** +; s 01 A1, Cb0, D 1111111, T **:59:0* +; s 02 A1, Cb0, D 1111111, T **:59:1* +; s 03 A1, Cb0, D 1111111, T **:59:2* +; s 04 A1, Cb0, D 1111111, T **:59:3* +; s 05 A1, Cb0, D 1111111, T **:59:4* +; s 06 A1, Cb0, D 1111111, T **:59:5* +; s 07 A1, Cb0, D 1111111, T **:05:00 +;
Why does this work? Trigger 00 wil match any second of minute 59. This includes second 60 (the leap second). Triggers 1-6 combined match the seconds of minute 59 which do not start with a 6. Since they are processed after trigger 0 they have highere precedence.Thus the whole construction will never trigger except at a leap second. Trigger 7 was included in the example as a reminder to not forget to include a trigger for switching off again.
The only caveat with this logic is that it will not work for leap seconds that get removed. However this has never happened so far. And since the earth's rotation is slowing down this is very likely not going to happen in the near future.
Generate 1s Pulses at Dedicated Times
Sometimes it is desirable to only generate a pulse instead of switching on or off. The naive way would be to issue two triggers with a time difference of 1s. However it is much easier to put all pulse times into a group with a trigger that matches everything. For example the following would issue one 1s pulse on channel 0 every hour at minute 30 on monday. It would also issue an additional 1s pulse on channel 1 every day at 12:00:00.
s 00 A1, Cb00, D 1111111, T **:**:** +; s 01 A1, Cb01, D 1000000, T **:30:00 +; s 02 A1, Cb10, D 1111111, T 12:00:00 #;
Of course the same trick might be used to issue low pulses instead of high pulses. It may also be used to issue longer pulses. For example the code below would issue negative pulses of 10 second duration at the same times the previous example issue positive 1 second pulses.
s 00 A1, Cb11, D 1111111, T **:**:** +; s 01 A1, Cb10, D 1000000, T **:30:0* +; s 02 A1, Cb01, D 1111111, T 12:00:0* #;