Renamed ebusd to housed and shutterctl to shutter.
[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           break;
220
221         case motor_state_pre_down:
222           MOTOR_on &= ~_BV(MOTOR_on_BIT);    /* Switch motor off. */
223           action_delay = 200; /*ms*/
224           state = motor_state_pre_down2;
225           break;
226         case motor_state_pre_down2:
227           MOTOR_down |= _BV(MOTOR_down_BIT); /* Switch direction relay on. */
228           action_delay = 200; /*ms*/
229           state = motor_state_down;
230           break;
231         case motor_state_down:
232           MOTOR_on |= _BV(MOTOR_on_BIT);     /* Switch motor on. */
233           shutter_state = 0b10000000;
234           /*                |!------- Direction set to down
235            *                +-------- Motor running.        */
236           action_delay = 25000; /*ms*/
237           state = motor_state_down_ready;
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           break;
244         }
245     }
246
247   if (!clock)
248     {
249       ten_seconds_event = 1;
250       wakeup_main = 1;
251     }
252 }
253
254
255 /* static void */
256 /* send_dbgmsg (const char *string) */
257 /* { */
258 /*   byte msg[16]; */
259
260 /*   msg[0] = PROTOCOL_EBUS_DBGMSG; */
261 /*   msg[1] = config.nodeid_hi; */
262 /*   msg[2] = config.nodeid_lo; */
263 /*   memset (msg+3, 0, 13); */
264 /*   strncpy (msg+3, string, 13); */
265 /*   csma_send_message (msg, 16); */
266 /* } */
267
268 /* static void */
269 /* send_dbgmsg_fmt (const char *format, ...) */
270 /* { */
271 /*   va_list arg_ptr; */
272 /*   char buffer[16]; */
273
274 /*   va_start (arg_ptr, format); */
275 /*   vsnprintf (buffer, 16, format, arg_ptr); */
276 /*   va_end (arg_ptr); */
277 /*   send_dbgmsg (buffer); */
278 /* } */
279
280
281 /* Process scheduled actions.  TIME is the current time.  If
282    FORCED_TLOW is not 0 the scheduler will run the last action between
283    FORCED_TLOW and TIME regardless on whether it has already been run.
284    This feature is required to cope with a changed system time:
285    Consider the shutter shall be called at 19:00, the current system
286    time is 18:59 and the system time is updated to 19:10 - without
287    that feature the closing at 19:00 would get lost.  */
288 static void
289 process_schedule (uint16_t time, uint16_t forced_tlow)
290 {
291   uint16_t tlow, thigh, t, tfound;
292   byte i;
293
294   if (schedule_last_tfound > time || forced_tlow)
295     schedule_last_tfound = 0;  /* Time wrapped to the next week or forced
296                                   schedule action.  */
297
298   /* We look up to 5 minutes back into the past to cope with lost events.  */
299   time /= 6;
300   time *= 6;
301   tlow = forced_tlow? forced_tlow : time;
302   if (tlow >= 5 * 6)
303     tlow -= 5 * 6;
304   else
305     tlow = 0;
306   if (schedule_last_tfound && schedule_last_tfound > tlow)
307     tlow = schedule_last_tfound;
308   thigh = time + 5;
309
310   /* send_dbgmsg_fmt ("time=%u", time); */
311   /* send_dbgmsg_fmt ("lst=%u", schedule_last_tfound); */
312   /* send_dbgmsg_fmt ("low=%u", tlow); */
313   /* send_dbgmsg_fmt ("hig=%u", thigh); */
314
315   /* Walk the schedule and find the last entry. */
316   for (tfound=0, i=0; i < DIM (ee_data.u.shutterctl.schedule); i++)
317     {
318       t = eeprom_read_word (&ee_data.u.shutterctl.schedule[i]);
319       if (!t)
320         break;
321       if (t > tlow && t <= thigh)
322         tfound = t;
323     }
324   if (tfound)
325     {
326       schedule_last_tfound = tfound;
327       /* send_dbgmsg_fmt ("fnd=%u", ((tfound/6)*6)); */
328       tfound %= 6;
329       /* send_dbgmsg_fmt ("act=%u", tfound); */
330       if (tfound == SCHEDULE_ACTION_UP)
331         next_action = action_up;
332       else if (tfound == SCHEDULE_ACTION_DOWN)
333         next_action = action_down;
334     }
335 }
336
337
338 /* Process a shutter command.  */
339 static void
340 process_shutter_cmd (byte *msg)
341 {
342   uint16_t val16;
343   byte err = 0;
344
345   switch (msg[6])
346     {
347     case P_H61_SHUTTER_QUERY:
348       {
349         msg[1] = msg[3];
350         msg[2] = msg[4];
351         msg[3] = config.nodeid_hi;
352         msg[4] = config.nodeid_lo;
353         msg[5] |= P_BUSCTL_RESPMASK;
354
355         msg[7] = 0; /* No error.  */
356         msg[8] = shutter_state;
357         memset (msg+9, 0, 7);
358         csma_send_message (msg, MSGSIZE);
359       }
360       break;
361
362     case P_H61_SHUTTER_DRIVE:
363       {
364         if (msg[7] > 1 /* Only all shutters or shutter 1 are allowed.  */
365             || msg[9] || msg[10] || msg[11] || msg[12] || msg[13]
366             || msg[14] || msg[15] /* Reserved bytes are not zero.  */
367             || (msg[8] & 0x10)    /* Reserved bit is set.  */
368             || (msg[8] & 0x20)    /* Not yet supported.  */ )
369           err = 1;
370         else if ((msg[8] & 0xc0) == 0xc0)
371           next_action = action_up;
372         else if ((msg[8] & 0xc0) == 0x80)
373           next_action = action_down;
374         else
375           err = 1;
376
377         msg[1] = msg[3];
378         msg[2] = msg[4];
379         msg[3] = config.nodeid_hi;
380         msg[4] = config.nodeid_lo;
381         msg[5] |= P_BUSCTL_RESPMASK;
382         msg[7] = err;
383         msg[8] = shutter_state;
384         memset (msg+9, 0, 7);
385         csma_send_message (msg, MSGSIZE);
386       }
387       break;
388
389     case P_H61_SHUTTER_QRY_TIMINGS:
390       break;
391
392     case P_H61_SHUTTER_UPD_TIMINGS:
393       break;
394
395     case P_H61_SHUTTER_QRY_SCHEDULE:
396       {
397         byte n, i;
398
399         for (i=0; i < DIM (ee_data.u.shutterctl.schedule); i++)
400           {
401             val16 = eeprom_read_word (&ee_data.u.shutterctl.schedule[i]);
402             if (!val16)
403               break;
404           }
405         n = i;
406
407         msg[1] = msg[3];
408         msg[2] = msg[4];
409         msg[3] = config.nodeid_hi;
410         msg[4] = config.nodeid_lo;
411         msg[5] |= P_BUSCTL_RESPMASK;
412         msg[7] = 0; /* We only have a global schedule for now.  */
413         msg[8] = 0; /* No error.  */
414         for (i=0; i < n; i++)
415           {
416             val16 = eeprom_read_word (&ee_data.u.shutterctl.schedule[i]);
417             switch ((val16 % 6))
418               {
419               case SCHEDULE_ACTION_UP:   msg[13] = 0b11000000; break;
420               case SCHEDULE_ACTION_DOWN: msg[13] = 0b10000000; break;
421               default: msg[13] = 0;  break; /* Undefined.  */
422               }
423             val16 /= 6;
424             val16 *= 6;
425             msg[9] = n;
426             msg[10] = i;
427             msg[11] = val16 >> 8;
428             msg[12] = val16;
429             /* msg[13] already set.  */
430             msg[14] = 0;
431             msg[15] = 0;
432             csma_send_message (msg, MSGSIZE);
433           }
434       }
435       break;
436
437     case P_H61_SHUTTER_UPD_SCHEDULE:
438       if (msg[8] || msg[14] || msg[15] || msg[9] != 1
439           || msg[10] >= DIM (ee_data.u.shutterctl.schedule))
440         {
441           /* Bad message or eeprom reset  */
442           if (msg[7] == 0xf0 && msg[9] == 16 && msg[10] == 0xf0
443               && msg[11] == 0xf0 && msg[12] == 0xf0 && msg[13] == 0xf0)
444             {
445               init_eeprom (1);
446             }
447         }
448       else
449         {
450           /* Get time and round down to the full minute.  */
451           val16 = (msg[11] << 8) | msg[12];
452           val16 /= 6;
453           val16 *= 6;
454           /* Include the action.  Note that SCHEDULE_ACTION_NOP is the
455              default.  */
456           if (msg[13] == 0b11000000)
457             val16 += SCHEDULE_ACTION_UP;
458           else if (msg[13] == 0b10000000)
459             val16 += SCHEDULE_ACTION_DOWN;
460
461           eeprom_write_word (&ee_data.u.shutterctl.schedule[msg[10]], val16);
462         }
463       break;
464
465     default:
466       break;
467     }
468 }
469
470
471 /* A new message has been received and we must now parse the message
472    quickly and see what to do.  We need to return as soon as possible,
473    so that the caller may re-enable the receiver.  */
474 static void
475 process_ebus_h61 (byte *msg)
476 {
477   char is_response = !!(msg[5] & P_H61_RESPMASK);
478
479   if (!(msg[1] == config.nodeid_hi || msg[2] == config.nodeid_lo))
480     return; /* Not addressed to us.  */
481
482   switch ((msg[5] & ~P_H61_RESPMASK))
483     {
484     case P_H61_SHUTTER:
485       if (!is_response)
486         process_shutter_cmd (msg);
487       break;
488
489     default:
490       break;
491     }
492 }
493
494
495 /* Process busctl messages.  */
496 static void
497 process_ebus_busctl (byte *msg)
498 {
499   uint16_t val16;
500   byte     val8;
501   char is_response = !!(msg[5] & P_BUSCTL_RESPMASK);
502
503   if (is_response)
504     return;  /* Nothing to do.  */
505   else if (msg[3] == 0xff || msg[4] == 0xff || msg[4] == 0)
506     return ; /* Bad sender address.  */
507   else if (msg[1] == config.nodeid_hi && msg[2] == config.nodeid_lo)
508     ; /* Directed to us.  */
509   else if ((msg[1] == config.nodeid_hi || msg[1] == 0xff) && msg[2] == 0xff)
510     ; /* Broadcast. */
511   else
512     return; /* Not addressed to us.  */
513
514   switch ((msg[5] & ~P_BUSCTL_RESPMASK))
515     {
516     case P_BUSCTL_TIME:
517       {
518         uint16_t t;
519
520         t = get_current_time ();
521         val16 = (msg[7] << 8) | msg[8];
522         val8  = (msg[6] & 0x02)? msg[9] : 0;
523         set_current_fulltime (val16, val8);
524         if (val16 > t)
525           process_schedule (val16, t);
526       }
527       break;
528
529     case P_BUSCTL_QRY_TIME:
530       msg[1] = msg[3];
531       msg[2] = msg[4];
532       msg[3] = config.nodeid_hi;
533       msg[4] = config.nodeid_lo;
534       msg[5] |= P_BUSCTL_RESPMASK;
535       msg[6] = 0; /* fixme: return an error for unknown shutter numbers.  */
536       val16 = get_current_fulltime (&val8);
537       msg[7] = val16 >> 8;
538       msg[8] = val16;
539       msg[9] = val8;
540       memset (msg+10, 0, 6);
541       csma_send_message (msg, MSGSIZE);
542       break;
543
544     default:
545       break;
546     }
547 }
548
549
550
551 /* Init our eeprom data if needed. */
552 static void
553 init_eeprom (byte force)
554 {
555   uint16_t uptime, downtime;
556   byte i;
557
558   if (force || !eeprom_read_word (&ee_data.u.shutterctl.schedule[0]))
559     {
560       /* The schedule is empty - set up reasonable values for every
561          day of the week.  */
562       uptime = (7 * 60 + 30) * 6 + SCHEDULE_ACTION_UP;
563       downtime = (18 * 60 + 15) * 6 + SCHEDULE_ACTION_DOWN;
564       for (i=0; i < 7*2 && i < DIM (ee_data.u.shutterctl.schedule); i++)
565         {
566           if (i==6*2) /* Pull up on Sundays one hour later.  */
567             uptime += 60 * 6;
568           eeprom_write_word (&ee_data.u.shutterctl.schedule[i], uptime);
569           i++;
570           eeprom_write_word (&ee_data.u.shutterctl.schedule[i], downtime);
571           uptime += 24 * 60 * 6;
572           downtime += 24 * 60 * 6;
573         }
574     }
575 }
576
577
578 /*
579     Entry point
580  */
581 int
582 main (void)
583 {
584   byte *msg;
585
586   hardware_setup (NODETYPE_SHUTTER);
587   init_eeprom (0);
588
589   /* Port C configuration changes.  Configure motor ports for output
590      and switch them off. */
591   PORTC &= ~(_BV(MOTOR_down_BIT) | _BV(MOTOR_on_BIT));
592   DDRC  |= _BV(MOTOR_down_BIT) | _BV(MOTOR_on_BIT);
593
594   csma_setup ();
595   onewire_setup ();
596
597   sei (); /* Enable interrupts.  */
598
599   for (;;)
600     {
601       set_sleep_mode (SLEEP_MODE_IDLE);
602       while (!wakeup_main)
603         {
604           cli();
605           if (!wakeup_main)
606             {
607               sleep_enable ();
608               sei ();
609               sleep_cpu ();
610               sleep_disable ();
611             }
612           sei ();
613         }
614       wakeup_main = 0;
615
616       if (ten_seconds_event)
617         {
618           uint16_t t;
619
620           ten_seconds_event = 0;
621
622           t = get_current_time ();
623           if (!(t % 6))
624             {
625               /* Code to run every minute.  */
626               process_schedule (t, 0);
627             }
628         }
629
630
631       msg = csma_get_message ();
632       if (msg)
633         {
634           /* Process the message.  */
635           switch (msg[0])
636             {
637             case PROTOCOL_EBUS_BUSCTL:
638               process_ebus_busctl (msg);
639               break;
640             case PROTOCOL_EBUS_H61:
641               process_ebus_h61 (msg);
642               break;
643             default:
644               /* Ignore all other protocols.  */
645               break;
646             }
647           /* Re-enable the receiver.  */
648           csma_message_done ();
649         }
650     }
651
652 }