First chunk of code for the house automation
[wk-misc.git] / ebus / shutter.c
1 /* shutter.c - Elektor Bus node to control a shutter
2  * Copyright (C) 2011 g10 Code GmbH
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, see <http://www.gnu.org/licenses/>.
16  */
17
18 /* This node is used to control the shutter in a living room.  The
19    shutter is operator by a motor with two coils and limit switches
20    connected to a solid state relays which is controlled by this node.
21    A future version of this controller will also support some sensor
22    to allow pulling down the shutter only to some percentage.  In any
23    case the limit switches of the motor control the endpoints.  The
24    control logic takes down the relays after the time required to pull
25    the shutters up or down plus some extra time.  The S2 and S3
26    switches are used for manually controlling the shutter.  They are
27    interlocked and operate in a toggle on/off way.  The hardware
28    itself is also interlocked so that it is not possible to drive both
29    motors at the same time.
30
31    The used relays are FINDER 34.51.7.012.0010 which feature a high
32    sensitive coil (~18mA) and are able to switch 6A (the datasheet
33    even exlicitly allows motors of up to 185W).  The first relay is
34    used for direction selection with NC connected to the pull-up motor
35    and NO to the pull-down motor.  Our control logic makes sure that
36    this relay is not switched under load.  The second one connects its
37    NO to the first relay and a snubber circuit is used to protect its
38    contacts.  They are both connected to the 12V rail and flyback
39    diodes are used for each.  Two BC547 are used to drive them.
40
41    Historical note: A first version of the shutter rig and this
42    software used two (not interlocked) solid state relays made up from
43    S202S02 opto-triacs along with snubbers and varistors to cope with
44    the voltage peaks induced by the other motor.  However the spikes
45    were obviously too high and after some weeks the triacs bricked
46    themself.  A second try using S202S01s had the same effect.  Better
47    save your money and use mechanical relays.
48  */
49
50 #include "hardware.h"
51
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <avr/io.h>
56 #include <avr/interrupt.h>
57 #include <avr/sleep.h>
58 #include <avr/eeprom.h>
59
60 #include "ebus.h"
61 #include "proto-busctl.h"
62 #include "proto-h61.h"
63
64
65 #define MOTOR_on       (PORTC)  /* Switch motor on.  */
66 #define MOTOR_on_BIT   (PC2)
67 #define MOTOR_down     (PORTC)  /* Switch direction relay to down.  */
68 #define MOTOR_down_BIT (PC3)
69
70
71 #define SCHEDULE_ACTION_NOP   0 /* No operation.  */
72 #define SCHEDULE_ACTION_UP    1 /* Minute + 10s := pull up  */
73 #define SCHEDULE_ACTION_DOWN  5 /* Minute + 50s := pull down  */
74
75
76 /* Allowed action values.  Note that the hardware interlocks both
77    motors.  However to avoid switch the direction under load we first
78    set the direction, wait a moment and then switch the motor on.  */
79 enum __attribute__ ((packed)) action_values {
80   action_none = 0,
81   action_up,
82   action_down
83 };
84
85 enum __attribute__ ((packed)) motor_state_values {
86   motor_state_off = 0,
87   motor_state_pre_off,
88   motor_state_pre_off2,
89   motor_state_pre_up,
90   motor_state_pre_up2,
91   motor_state_up,
92   motor_state_up_ready,
93   motor_state_pre_down,
94   motor_state_pre_down2,
95   motor_state_down,
96   motor_state_down_ready
97 };
98
99 /* Remember the last action time.  */
100 static uint16_t schedule_last_tfound;
101
102 /* The next action to do.  It's value is changed by the commands
103    and a ticker is switching the motor depending on these values.  */
104 static volatile enum action_values next_action;
105
106
107 /* The shutter state byte.  This is directly used for the
108    QueryShutterState response message.  */
109 static volatile byte shutter_state;
110
111 /* Event flag, triggred (surprise) every 10 seconds.  */
112 static volatile byte ten_seconds_event;
113
114
115 \f
116 /* Local prototypes.  */
117 static void init_eeprom (byte force);
118
119
120
121 \f
122 /* This code is called by the 1ms ticker interrupt service routine
123    with the current clock value given in milliseconds from 0..9999. */
124 void
125 ticker_bottom (unsigned int clock)
126 {
127   static volatile enum action_values current_action;
128   static volatile uint16_t action_delay;
129   static volatile enum motor_state_values state = motor_state_off;
130   enum action_values save_action;
131
132   /* Blink the activity LED if the motor is not in off state.  */
133   if (state != motor_state_off)
134     {
135       if (!(clock % 1000))
136         {
137           if ((LED_Collision & _BV(LED_Collision_BIT)))
138             LED_Collision &= ~_BV (LED_Collision_BIT);
139           else
140             LED_Collision |= _BV(LED_Collision_BIT);
141         }
142     }
143
144   /* Get next action.  */
145   save_action = current_action;
146   if (read_key_s2 ())
147     current_action = current_action != action_down ? action_down : action_none;
148   if (read_key_s3 ())
149     current_action = current_action != action_up? action_up : action_none;
150   if (current_action != save_action || next_action)
151     {
152       if (next_action)
153         {
154           current_action = next_action;
155           next_action = 0;
156         }
157       switch (current_action)
158         {
159         case action_none: state = motor_state_pre_off; break;
160         case action_up:   state = motor_state_pre_up; break;
161         case action_down: state = motor_state_pre_down; break;
162         }
163       /* When a new action has been selected we need to limit the
164          action delay to the minimum value so that we don't stick in
165          the long down or up states.  We also need to make sure that
166          there is an action delay so that we enter the state
167          transition switch. */
168       if (!action_delay)
169         action_delay = 1;
170       else if (action_delay > 200)
171         action_delay = 200;
172     }
173
174   if (action_delay && !--action_delay)
175     {
176       switch (state)
177         {
178         case motor_state_off:
179           LED_Collision &= ~_BV (LED_Collision_BIT); /* Clear LED.  */
180           /* Make sure the motor flags are cleared in the state info.  */
181           shutter_state &= 0b00111111;
182           break;
183
184         label_pre_off:
185         case motor_state_pre_off:
186           MOTOR_on &= ~_BV(MOTOR_on_BIT);    /* Switch motor off. */
187           action_delay = 200; /*ms*/
188           state = motor_state_pre_off2;
189           break;
190         case motor_state_pre_off2:
191           MOTOR_down &= ~_BV(MOTOR_down_BIT);/* Switch direction relay off. */
192           action_delay = 200; /*ms*/
193           state = motor_state_off;
194           break;
195
196         case motor_state_pre_up:
197           MOTOR_on &= ~_BV(MOTOR_on_BIT);    /* Switch motor off. */
198           action_delay = 200; /*ms*/
199           state = motor_state_pre_up2;
200           break;
201         case motor_state_pre_up2:
202           MOTOR_down &= ~_BV(MOTOR_down_BIT);/* Switch direction relay off. */
203           action_delay = 200; /*ms*/
204           state = motor_state_up;
205           break;
206         case motor_state_up:
207           MOTOR_on |= _BV(MOTOR_on_BIT);     /* Switch motor on. */
208           shutter_state = 0b11000000;
209           /*                |!------- Direction set to up
210            *                +-------- Motor running.        */
211           action_delay = 25000; /*ms*/
212           state = motor_state_up_ready;
213           break;
214         case motor_state_up_ready:
215           shutter_state = 0b00100000;
216           /*                  | !~~!- Value: 0 = 0% closed
217            *                  +------ State in bits 3..0 is valid.  */
218           goto label_pre_off;
219
220         case motor_state_pre_down:
221           MOTOR_on &= ~_BV(MOTOR_on_BIT);    /* Switch motor off. */
222           action_delay = 200; /*ms*/
223           state = motor_state_pre_down2;
224           break;
225         case motor_state_pre_down2:
226           MOTOR_down |= _BV(MOTOR_down_BIT); /* Switch direction relay on. */
227           action_delay = 200; /*ms*/
228           state = motor_state_down;
229           break;
230         case motor_state_down:
231           MOTOR_on |= _BV(MOTOR_on_BIT);     /* Switch motor on. */
232           shutter_state = 0b10000000;
233           /*                |!------- Direction set to down
234            *                +-------- Motor running.        */
235           action_delay = 25000; /*ms*/
236           state = motor_state_down_ready;
237           break;
238         case motor_state_down_ready:
239           shutter_state = 0b00101111;
240           /*                  | !~~!--- Value: 15 = 100% closed
241            *                  +-------- State in bits 3..0 is valid.  */
242           goto label_pre_off;
243         }
244     }
245
246   if (!clock)
247     {
248       ten_seconds_event = 1;
249       wakeup_main = 1;
250     }
251 }
252
253
254 /* static void */
255 /* send_dbgmsg (const char *string) */
256 /* { */
257 /*   byte msg[16]; */
258
259 /*   msg[0] = PROTOCOL_EBUS_DBGMSG; */
260 /*   msg[1] = config.nodeid_hi; */
261 /*   msg[2] = config.nodeid_lo; */
262 /*   memset (msg+3, 0, 13); */
263 /*   strncpy (msg+3, string, 13); */
264 /*   csma_send_message (msg, 16); */
265 /* } */
266
267 /* static void */
268 /* send_dbgmsg_fmt (const char *format, ...) */
269 /* { */
270 /*   va_list arg_ptr; */
271 /*   char buffer[16]; */
272
273 /*   va_start (arg_ptr, format); */
274 /*   vsnprintf (buffer, 16, format, arg_ptr); */
275 /*   va_end (arg_ptr); */
276 /*   send_dbgmsg (buffer); */
277 /* } */
278
279
280 /* Process scheduled actions.  TIME is the current time.  If
281    FORCED_TLOW is not 0 the scheduler will run the last action between
282    FORCED_TLOW and TIME regardless on whether it has already been run.
283    This feature is required to cope with a changed system time:
284    Consider the shutter shall be closed at 19:00, the current system
285    time is 18:59 and the system time is updated to 19:10 - without
286    that feature the closing at 19:00 would get lost.  */
287 static void
288 process_schedule (uint16_t time, uint16_t forced_tlow)
289 {
290   uint16_t tlow, thigh, t, tfound;
291   byte i;
292
293   if (schedule_last_tfound > time || forced_tlow)
294     schedule_last_tfound = 0;  /* Time wrapped to the next week or forced
295                                   schedule action.  */
296
297   /* We look up to 5 minutes back into the past to cope with lost events.  */
298   time /= 6;
299   time *= 6;
300   tlow = forced_tlow? forced_tlow : time;
301   if (tlow >= 5 * 6)
302     tlow -= 5 * 6;
303   else
304     tlow = 0;
305   if (schedule_last_tfound && schedule_last_tfound > tlow)
306     tlow = schedule_last_tfound;
307   thigh = time + 5;
308
309   /* send_dbgmsg_fmt ("time=%u", time); */
310   /* send_dbgmsg_fmt ("lst=%u", schedule_last_tfound); */
311   /* send_dbgmsg_fmt ("low=%u", tlow); */
312   /* send_dbgmsg_fmt ("hig=%u", thigh); */
313
314   /* Walk the schedule and find the last entry. */
315   for (tfound=0, i=0; i < DIM (ee_data.u.shutterctl.schedule); i++)
316     {
317       t = eeprom_read_word (&ee_data.u.shutterctl.schedule[i]);
318       if (!t)
319         break;
320       if (t > tlow && t <= thigh)
321         tfound = t;
322     }
323   if (tfound)
324     {
325       schedule_last_tfound = tfound;
326       /* send_dbgmsg_fmt ("fnd=%u", ((tfound/6)*6)); */
327       tfound %= 6;
328       /* send_dbgmsg_fmt ("act=%u", tfound); */
329       if (tfound == SCHEDULE_ACTION_UP)
330         next_action = action_up;
331       else if (tfound == SCHEDULE_ACTION_DOWN)
332         next_action = action_down;
333     }
334 }
335
336
337 /* Process a shutter command.  */
338 static void
339 process_shutter_cmd (byte *msg)
340 {
341   uint16_t val16;
342   byte err = 0;
343
344   switch (msg[6])
345     {
346     case P_H61_SHUTTER_QUERY:
347       {
348         msg[1] = msg[3];
349         msg[2] = msg[4];
350         msg[3] = config.nodeid_hi;
351         msg[4] = config.nodeid_lo;
352         msg[5] |= P_BUSCTL_RESPMASK;
353
354         msg[7] = 0; /* No error.  */
355         msg[8] = shutter_state;
356         memset (msg+9, 0, 7);
357         csma_send_message (msg, MSGSIZE);
358       }
359       break;
360
361     case P_H61_SHUTTER_DRIVE:
362       {
363         if (msg[7] > 1 /* Only all shutters or shutter 1 are allowed.  */
364             || msg[9] || msg[10] || msg[11] || msg[12] || msg[13]
365             || msg[14] || msg[15] /* Reserved bytes are not zero.  */
366             || (msg[8] & 0x10)    /* Reserved bit is set.  */
367             || (msg[8] & 0x20)    /* Not yet supported.  */ )
368           err = 1;
369         else if ((msg[8] & 0xc0) == 0xc0)
370           next_action = action_up;
371         else if ((msg[8] & 0xc0) == 0x80)
372           next_action = action_down;
373         else
374           err = 1;
375
376         msg[1] = msg[3];
377         msg[2] = msg[4];
378         msg[3] = config.nodeid_hi;
379         msg[4] = config.nodeid_lo;
380         msg[5] |= P_BUSCTL_RESPMASK;
381         msg[7] = err;
382         msg[8] = shutter_state;
383         memset (msg+9, 0, 7);
384         csma_send_message (msg, MSGSIZE);
385       }
386       break;
387
388     case P_H61_SHUTTER_QRY_TIMINGS:
389       break;
390
391     case P_H61_SHUTTER_UPD_TIMINGS:
392       break;
393
394     case P_H61_SHUTTER_QRY_SCHEDULE:
395       {
396         byte n, i;
397
398         for (i=0; i < DIM (ee_data.u.shutterctl.schedule); i++)
399           {
400             val16 = eeprom_read_word (&ee_data.u.shutterctl.schedule[i]);
401             if (!val16)
402               break;
403           }
404         n = i;
405
406         msg[1] = msg[3];
407         msg[2] = msg[4];
408         msg[3] = config.nodeid_hi;
409         msg[4] = config.nodeid_lo;
410         msg[5] |= P_BUSCTL_RESPMASK;
411         msg[7] = 0; /* We only have a global schedule for now.  */
412         msg[8] = 0; /* No error.  */
413         for (i=0; i < n; i++)
414           {
415             val16 = eeprom_read_word (&ee_data.u.shutterctl.schedule[i]);
416             switch ((val16 % 6))
417               {
418               case SCHEDULE_ACTION_UP:   msg[13] = 0b11000000; break;
419               case SCHEDULE_ACTION_DOWN: msg[13] = 0b10000000; break;
420               default: msg[13] = 0;  break; /* Undefined.  */
421               }
422             val16 /= 6;
423             val16 *= 6;
424             msg[9] = n;
425             msg[10] = i;
426             msg[11] = val16 >> 8;
427             msg[12] = val16;
428             /* msg[13] already set.  */
429             msg[14] = 0;
430             msg[15] = 0;
431             csma_send_message (msg, MSGSIZE);
432           }
433       }
434       break;
435
436     case P_H61_SHUTTER_UPD_SCHEDULE:
437       if (msg[8] || msg[14] || msg[15] || msg[9] != 1
438           || msg[10] >= DIM (ee_data.u.shutterctl.schedule))
439         {
440           /* Bad message or eeprom reset  */
441           if (msg[7] == 0xf0 && msg[9] == 16 && msg[10] == 0xf0
442               && msg[11] == 0xf0 && msg[12] == 0xf0 && msg[13] == 0xf0)
443             {
444               init_eeprom (1);
445             }
446         }
447       else
448         {
449           /* Get time and round down to the full minute.  */
450           val16 = (msg[11] << 8) | msg[12];
451           val16 /= 6;
452           val16 *= 6;
453           /* Include the action.  Note that SCHEDULE_ACTION_NOP is the
454              default.  */
455           if (msg[13] == 0b11000000)
456             val16 += SCHEDULE_ACTION_UP;
457           else if (msg[13] == 0b10000000)
458             val16 += SCHEDULE_ACTION_DOWN;
459
460           eeprom_write_word (&ee_data.u.shutterctl.schedule[msg[10]], val16);
461         }
462       break;
463
464     default:
465       break;
466     }
467 }
468
469
470 /* A new message has been received and we must now parse the message
471    quickly and see what to do.  We need to return as soon as possible,
472    so that the caller may re-enable the receiver.  */
473 static void
474 process_ebus_h61 (byte *msg)
475 {
476   char is_response = !!(msg[5] & P_H61_RESPMASK);
477
478   if (!(msg[1] == config.nodeid_hi || msg[2] == config.nodeid_lo))
479     return; /* Not addressed to us.  */
480
481   switch ((msg[5] & ~P_H61_RESPMASK))
482     {
483     case P_H61_SHUTTER:
484       if (!is_response)
485         process_shutter_cmd (msg);
486       break;
487
488     default:
489       break;
490     }
491 }
492
493
494 /* Process busctl messages.  */
495 static void
496 process_ebus_busctl (byte *msg)
497 {
498   uint16_t val16;
499   byte     val8;
500   char is_response = !!(msg[5] & P_BUSCTL_RESPMASK);
501
502   if (is_response)
503     return;  /* Nothing to do.  */
504   else if (msg[3] == 0xff || msg[4] == 0xff || msg[4] == 0)
505     return ; /* Bad sender address.  */
506   else if (msg[1] == config.nodeid_hi && msg[2] == config.nodeid_lo)
507     ; /* Directed to us.  */
508   else if ((msg[1] == config.nodeid_hi || msg[1] == 0xff) && msg[2] == 0xff)
509     ; /* Broadcast. */
510   else
511     return; /* Not addressed to us.  */
512
513   switch ((msg[5] & ~P_BUSCTL_RESPMASK))
514     {
515     case P_BUSCTL_TIME:
516       {
517         uint16_t t;
518
519         t = get_current_time ();
520         val16 = (msg[7] << 8) | msg[8];
521         val8  = (msg[6] & 0x02)? msg[9] : 0;
522         set_current_fulltime (val16, val8);
523         if (val16 > t)
524           process_schedule (val16, t);
525       }
526       break;
527
528     case P_BUSCTL_QRY_TIME:
529       msg[1] = msg[3];
530       msg[2] = msg[4];
531       msg[3] = config.nodeid_hi;
532       msg[4] = config.nodeid_lo;
533       msg[5] |= P_BUSCTL_RESPMASK;
534       msg[6] = 0; /* fixme: return an error for unknown shutter numbers.  */
535       val16 = get_current_fulltime (&val8);
536       msg[7] = val16 >> 8;
537       msg[8] = val16;
538       msg[9] = val8;
539       memset (msg+10, 0, 6);
540       csma_send_message (msg, MSGSIZE);
541       break;
542
543     case P_BUSCTL_QRY_VERSION:
544       msg[1] = msg[3];
545       msg[2] = msg[4];
546       msg[3] = config.nodeid_hi;
547       msg[4] = config.nodeid_lo;
548       msg[5] |= P_BUSCTL_RESPMASK;
549       msg[6] = eeprom_read_byte (&ee_data.nodetype);
550       msg[7] = 0;
551       memcpy_P (msg+8, PSTR (GIT_REVISION), 7);
552       msg[15] = 0;
553       csma_send_message (msg, MSGSIZE);
554       break;
555
556     default:
557       break;
558     }
559 }
560
561
562
563 /* Init our eeprom data if needed. */
564 static void
565 init_eeprom (byte force)
566 {
567   uint16_t uptime, downtime;
568   byte i;
569
570   if (force || !eeprom_read_word (&ee_data.u.shutterctl.schedule[0]))
571     {
572       /* The schedule is empty - set up reasonable values for every
573          day of the week.  */
574       uptime = (7 * 60 + 30) * 6 + SCHEDULE_ACTION_UP;
575       downtime = (18 * 60 + 15) * 6 + SCHEDULE_ACTION_DOWN;
576       for (i=0; i < 7*2 && i < DIM (ee_data.u.shutterctl.schedule); i++)
577         {
578           if (i==6*2) /* Pull up on Sundays one hour later.  */
579             uptime += 60 * 6;
580           eeprom_write_word (&ee_data.u.shutterctl.schedule[i], uptime);
581           i++;
582           eeprom_write_word (&ee_data.u.shutterctl.schedule[i], downtime);
583           uptime += 24 * 60 * 6;
584           downtime += 24 * 60 * 6;
585         }
586     }
587 }
588
589
590 /*
591     Entry point
592  */
593 int
594 main (void)
595 {
596   byte *msg;
597
598   hardware_setup (NODETYPE_SHUTTER);
599   init_eeprom (0);
600
601   /* Port C configuration changes.  Configure motor ports for output
602      and switch them off. */
603   PORTC &= ~(_BV(MOTOR_down_BIT) | _BV(MOTOR_on_BIT));
604   DDRC  |= _BV(MOTOR_down_BIT) | _BV(MOTOR_on_BIT);
605
606   csma_setup ();
607   onewire_setup ();
608
609   sei (); /* Enable interrupts.  */
610
611   for (;;)
612     {
613       set_sleep_mode (SLEEP_MODE_IDLE);
614       while (!wakeup_main)
615         {
616           cli();
617           if (!wakeup_main)
618             {
619               sleep_enable ();
620               sei ();
621               sleep_cpu ();
622               sleep_disable ();
623             }
624           sei ();
625         }
626       wakeup_main = 0;
627
628       if (ten_seconds_event)
629         {
630           uint16_t t;
631
632           ten_seconds_event = 0;
633
634           t = get_current_time ();
635           if (!(t % 6))
636             {
637               /* Code to run every minute.  */
638               process_schedule (t, 0);
639             }
640         }
641
642
643       msg = csma_get_message ();
644       if (msg)
645         {
646           /* Process the message.  */
647           switch (msg[0])
648             {
649             case PROTOCOL_EBUS_BUSCTL:
650               process_ebus_busctl (msg);
651               break;
652             case PROTOCOL_EBUS_H61:
653               process_ebus_h61 (msg);
654               break;
655             default:
656               /* Ignore all other protocols.  */
657               break;
658             }
659           /* Re-enable the receiver.  */
660           csma_message_done ();
661         }
662     }
663
664 }