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