Renamed ebusd to housed and shutterctl to shutter.
authorWerner Koch <wk@gnupg.org>
Mon, 24 Oct 2011 11:34:57 +0000 (13:34 +0200)
committerWerner Koch <wk@gnupg.org>
Mon, 24 Oct 2011 12:00:23 +0000 (14:00 +0200)
Also did some minor improvements.

ebus/ebusd.c [deleted file]
ebus/ebusdump.c
ebus/ebusnode1.c
ebus/housed.c [new file with mode: 0644]
ebus/shutter.c [new file with mode: 0644]

diff --git a/ebus/ebusd.c b/ebus/ebusd.c
deleted file mode 100644 (file)
index cff501c..0000000
+++ /dev/null
@@ -1,311 +0,0 @@
-/* ebusd.c - Ebus control daemon
- * Copyright (C) 2011 Werner Koch (dd9jn)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-#include <signal.h>
-#include <stdint.h>
-#include <errno.h>
-#include <termios.h>
-#include <unistd.h>
-#include <sys/ioctl.h>
-
-
-#define PGM           "ebusd"
-#define PGM_VERSION   "0.0"
-#define PGM_BUGREPORT "wk@gnupg.org"
-
-/* Option flags. */
-static int verbose;
-static int debug;
-
-/* Error counter.  */
-static int any_error;
-
-
-/* Print diagnostic message and exit with failure. */
-static void
-die (const char *format, ...)
-{
-  va_list arg_ptr;
-
-  fflush (stdout);
-  fprintf (stderr, "%s: ", PGM);
-
-  va_start (arg_ptr, format);
-  vfprintf (stderr, format, arg_ptr);
-  va_end (arg_ptr);
-  putc ('\n', stderr);
-
-  exit (1);
-}
-
-
-/* Print diagnostic message. */
-static void
-err (const char *format, ...)
-{
-  va_list arg_ptr;
-
-  any_error = 1;
-
-  fflush (stdout);
-  fprintf (stderr, "%s: ", PGM);
-
-  va_start (arg_ptr, format);
-  vfprintf (stderr, format, arg_ptr);
-  va_end (arg_ptr);
-  putc ('\n', stderr);
-}
-
-
-/* Print a info message message. */
-static void
-inf (const char *format, ...)
-{
-  va_list arg_ptr;
-
-  if (verbose)
-    {
-      fprintf (stderr, "%s: ", PGM);
-
-      va_start (arg_ptr, format);
-      vfprintf (stderr, format, arg_ptr);
-      va_end (arg_ptr);
-      putc ('\n', stderr);
-    }
-}
-
-
-static void
-dump_mcbits (int fd)
-{
-  int mcbits;
-
-  if (ioctl (fd, TIOCMGET, &mcbits))
-    err ("TIOCMGET failed: %s\n", strerror (errno));
-  else
-    inf ("mc: %3s %3s %3s %3s %3s %3s %3s %3s %3s",
-         (mcbits & TIOCM_LE )? "LE":"",
-         (mcbits & TIOCM_DTR)? "DTR":"",
-         (mcbits & TIOCM_DSR)? "DSR":"",
-         (mcbits & TIOCM_CAR)? "DCD":"",
-         (mcbits & TIOCM_RNG)? "RI":"",
-         (mcbits & TIOCM_RTS)? "RTS":"",
-         (mcbits & TIOCM_CTS)? "CTS":"",
-         (mcbits & TIOCM_ST )? "TX2":"",
-         (mcbits & TIOCM_SR )? "RX2":"");
-}
-
-
-static FILE *
-open_line (const char *fname)
-{
-  FILE *fp;
-  int fd;
-  struct termios term;
-
-  fp = fopen (fname, "r+");
-  if (!fp || (fd = fileno (fp)) == -1)
-    die ("can't open `%s': %s", fname, strerror (errno));
-
-  if (tcgetattr (fd, &term))
-    die ("tcgetattr(%d) failed: %s", fd, strerror (errno));
-
-  term.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
-                    | INLCR | IGNCR | ICRNL | IXON);
-  term.c_oflag &= ~OPOST;
-  term.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
-  term.c_cflag &= ~(CSIZE | PARENB);
-  term.c_cflag |= CS8;
-
-  if (cfsetospeed (&term, B9600) || cfsetispeed (&term, B9600))
-    die ("setting terminal speed to 9600 failed: %s", strerror (errno));
-
-  if (tcsetattr (fd, TCSANOW, &term ) )
-    die ("tcsetattr(%d) failed: %s", fd, strerror (errno));
-
-  inf ("connected to '%s' at 9600bps", fname);
-  dump_mcbits (fd);
-  /* { */
-  /*   int mcbits; */
-
-  /*   for (;;) */
-  /*     { */
-  /*       mcbits = TIOCM_RTS; */
-  /*       if (ioctl (fd, TIOCMBIC, &mcbits)) */
-  /*         err ("TIOCMBIC(RTS) failed: %s\n", strerror (errno)); */
-  /*       mcbits = TIOCM_RTS; */
-  /*       if (ioctl (fd, TIOCMBIS, &mcbits)) */
-  /*         err ("TIOCMBIS(RTS) failed: %s\n", strerror (errno)); */
-  /*     } */
-  /* } */
-  /* dump_mcbits (fd); */
-
-  return fp;
-}
-
-
-static uint16_t
-crc_ccitt_update (uint16_t crc, uint8_t data)
-{
-  data ^= (crc & 0xff);
-  data ^= data << 4;
-
-  return ((((uint16_t)data << 8) | ((crc >> 8)& 0xff)) ^ (uint8_t)(data >> 4)
-          ^ ((uint16_t)data << 3));
-}
-
-
-/* Compute the CRC for MSG.  MSG must be of MSGSIZE.  The CRC used is
-   possible not the optimal CRC for our message length.  However we
-   have a convenient inline function for it.  */
-static uint16_t
-compute_crc (const unsigned char *msg)
-{
-  int idx;
-  uint16_t crc = 0xffff;
-
-  for (idx=0; idx < 16; idx++)
-    crc = crc_ccitt_update (crc, msg[idx]);
-
-  return crc;
-}
-
-
-static void
-process (FILE *fp)
-{
-  unsigned char buffer[16+2];
-  int idx, synced, esc;
-  int c, i;
-
-  esc = synced = idx = 0;
-  while ((c=getc (fp)) != EOF)
-    {
-      if (c == 0x7e)
-        {
-          esc = 0;
-          synced = 1;
-          idx = 0;
-        }
-      else if (c == 0x7d && !esc)
-        esc = 1;
-      else if (synced)
-        {
-          if (esc)
-            {
-              esc = 0;
-              c ^= 0x20;
-            }
-
-          if (idx < sizeof buffer)
-            buffer[idx++] = c;
-
-          if (idx == sizeof buffer)
-            {
-              unsigned int crc = compute_crc (buffer);
-              for (i=0; i < sizeof buffer; i++)
-                printf ("%s%02x", i? " ":"", buffer[i]);
-              if ((crc >> 8) == buffer[16] && (crc&0xff) == buffer[17])
-                fputs (" ok", stdout);
-              else
-                fputs (" bad", stdout);
-              putchar ('\n');
-              fflush (stdout);
-            }
-        }
-    }
-}
-
-
-
-static int
-show_usage (int ex)
-{
-  fputs ("Usage: " PGM " DEVICE\n"
-         "Control an attached ebus\n\n"
-         "  --verbose      enable extra informational output\n"
-         "  --debug        enable additional debug output\n"
-         "  --help         display this help and exit\n\n"
-         "Report bugs to " PGM_BUGREPORT ".\n",
-         ex? stderr:stdout);
-  exit (ex);
-}
-
-
-int
-main (int argc, char **argv )
-{
-  int last_argc = -1;
-  FILE *fp;
-
-  if (argc)
-    {
-      argc--; argv++;
-    }
-  while (argc && last_argc != argc )
-    {
-      last_argc = argc;
-      if (!strcmp (*argv, "--"))
-        {
-          argc--; argv++;
-          break;
-        }
-      else if (!strcmp (*argv, "--version"))
-        {
-          fputs (PGM " " PGM_VERSION "\n", stdout);
-          exit (0);
-        }
-      else if (!strcmp (*argv, "--help"))
-        {
-          show_usage (0);
-        }
-      else if (!strcmp (*argv, "--verbose"))
-        {
-          verbose = 1;
-          argc--; argv++;
-        }
-      else if (!strcmp (*argv, "--debug"))
-        {
-          verbose = debug = 1;
-          argc--; argv++;
-        }
-      else if (!strncmp (*argv, "--", 2))
-        show_usage (1);
-    }
-
-  if (argc != 1)
-    show_usage (1);
-
-  setvbuf (stdout, NULL, _IOLBF, 0);
-
-  fp = open_line (*argv);
-  process (fp);
-  fclose (fp);
-
-  return any_error? 1:0;
-}
-
-/*
-Local Variables:
-compile-command: "cc -Wall -o ebusd ebusd.c"
-End:
-*/
index a8ff1f4..6b66d3a 100644 (file)
@@ -176,7 +176,11 @@ main (int argc, char **argv )
                       break;
                     case 5:
                       value |= c;
-                      printf (" t:%6u", value);
+                      printf (" t:%ud%02uh%02um%02us",
+                              (value/6/60/24),
+                              (value/6/60 % 24),
+                              (value/6 % 60),
+                              (value % 6) * 10);
                       break;
                     case 7:
                       value |= c;
index a439782..36d6fbf 100644 (file)
@@ -215,9 +215,6 @@ volatile unsigned int overflow_count;
 /* Flag indicating that a PCINT2 was triggered.  */
 volatile byte rx_pin_level_change;
 
-/* Set to one (e.g. the timer int) to wakeup the main loop.  */
-volatile char wakeup_main;
-
 /* The buffer filled by an ISR with the message.  */
 static volatile byte rx_buffer[MSGSIZE];
 
diff --git a/ebus/housed.c b/ebus/housed.c
new file mode 100644 (file)
index 0000000..b45ecff
--- /dev/null
@@ -0,0 +1,821 @@
+/* housed.c - A house control daemon
+ * Copyright (C) 2011 Werner Koch (dd9jn)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <signal.h>
+#include <stdint.h>
+#include <errno.h>
+#include <time.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include "protocol.h"
+#include "proto-busctl.h"
+#include "proto-h61.h"
+
+
+#define PGM           "housed"
+#define PGM_VERSION   "0.0"
+#define PGM_BUGREPORT "wk@gnupg.org"
+
+typedef unsigned char byte;
+
+/* Option flags. */
+static int verbose;
+static int debug;
+static int line_speed = 19200;
+
+/* Error counter.  */
+static int any_error;
+
+
+/* Print diagnostic message and exit with failure. */
+static void
+die (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  fflush (stdout);
+  fprintf (stderr, "%s: ", PGM);
+
+  va_start (arg_ptr, format);
+  vfprintf (stderr, format, arg_ptr);
+  va_end (arg_ptr);
+  putc ('\n', stderr);
+
+  exit (1);
+}
+
+
+/* Print diagnostic message. */
+static void
+err (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  any_error = 1;
+
+  fflush (stdout);
+  fprintf (stderr, "%s: ", PGM);
+
+  va_start (arg_ptr, format);
+  vfprintf (stderr, format, arg_ptr);
+  va_end (arg_ptr);
+  putc ('\n', stderr);
+}
+
+
+/* Print a info message message. */
+static void
+inf (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  if (verbose)
+    {
+      fprintf (stderr, "%s: ", PGM);
+
+      va_start (arg_ptr, format);
+      vfprintf (stderr, format, arg_ptr);
+      va_end (arg_ptr);
+      putc ('\n', stderr);
+    }
+}
+
+
+static void
+dump_mcbits (int fd)
+{
+  int mcbits;
+
+  if (ioctl (fd, TIOCMGET, &mcbits))
+    err ("TIOCMGET failed: %s\n", strerror (errno));
+  else
+    inf ("mc: %3s %3s %3s %3s %3s %3s %3s %3s %3s",
+         (mcbits & TIOCM_LE )? "LE":"",
+         (mcbits & TIOCM_DTR)? "DTR":"",
+         (mcbits & TIOCM_DSR)? "DSR":"",
+         (mcbits & TIOCM_CAR)? "DCD":"",
+         (mcbits & TIOCM_RNG)? "RI":"",
+         (mcbits & TIOCM_RTS)? "RTS":"",
+         (mcbits & TIOCM_CTS)? "CTS":"",
+         (mcbits & TIOCM_ST )? "TX2":"",
+         (mcbits & TIOCM_SR )? "RX2":"");
+}
+
+
+static FILE *
+open_line (const char *fname)
+{
+  FILE *fp;
+  int fd;
+  struct termios term;
+  speed_t speed;
+
+  switch (line_speed)
+    {
+    case 300   : speed = B300   ; break;
+    case 600   : speed = B600   ; break;
+    case 1200  : speed = B1200  ; break;
+    case 2400  : speed = B2400  ; break;
+    case 4800  : speed = B4800  ; break;
+    case 9600  : speed = B9600  ; break;
+    case 19200 : speed = B19200 ; break;
+    case 38400 : speed = B38400 ; break;
+    case 57600 : speed = B57600 ; break;
+    case 115200: speed = B115200; break;
+    default:
+      die ("unsupported line speed %d given", line_speed);
+    }
+
+  fp = fopen (fname, "r+");
+  if (!fp || (fd = fileno (fp)) == -1)
+    die ("can't open `%s': %s", fname, strerror (errno));
+
+  if (tcgetattr (fd, &term))
+    die ("tcgetattr(%d) failed: %s", fd, strerror (errno));
+
+  term.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
+                    | INLCR | IGNCR | ICRNL | IXON);
+  term.c_oflag &= ~OPOST;
+  term.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+  term.c_cflag &= ~(CSIZE | PARENB);
+  term.c_cflag |= CS8;
+
+  if (cfsetospeed (&term, speed) || cfsetispeed (&term, speed))
+    die ("setting terminal speed to %d failed: %s",
+         line_speed, strerror (errno));
+
+  if (tcsetattr (fd, TCSANOW, &term ) )
+    die ("tcsetattr(%d) failed: %s", fd, strerror (errno));
+
+  inf ("connected to '%s' at %dbps", fname, line_speed);
+  dump_mcbits (fd);
+  /* { */
+  /*   int mcbits; */
+
+  /*   for (;;) */
+  /*     { */
+  /*       mcbits = TIOCM_RTS; */
+  /*       if (ioctl (fd, TIOCMBIC, &mcbits)) */
+  /*         err ("TIOCMBIC(RTS) failed: %s\n", strerror (errno)); */
+  /*       mcbits = TIOCM_RTS; */
+  /*       if (ioctl (fd, TIOCMBIS, &mcbits)) */
+  /*         err ("TIOCMBIS(RTS) failed: %s\n", strerror (errno)); */
+  /*     } */
+  /* } */
+  /* dump_mcbits (fd); */
+
+  return fp;
+}
+
+
+static uint16_t
+crc_ccitt_update (uint16_t crc, uint8_t data)
+{
+  data ^= (crc & 0xff);
+  data ^= data << 4;
+
+  return ((((uint16_t)data << 8) | ((crc >> 8)& 0xff)) ^ (uint8_t)(data >> 4)
+          ^ ((uint16_t)data << 3));
+}
+
+
+/* Compute the CRC for MSG.  MSG must be of MSGLEN.  The CRC used is
+   possible not the optimal CRC for our message length.  However on
+   the AVR we have a convenient inline function for it.  */
+static uint16_t
+compute_crc (const unsigned char *msg, size_t msglen)
+{
+  int idx;
+  uint16_t crc = 0xffff;
+
+  for (idx=0; idx < msglen; idx++)
+    crc = crc_ccitt_update (crc, msg[idx]);
+
+  return crc;
+}
+
+
+/* Log a decoded protocol message. */
+static void
+logmsg_start (const char *text)
+{
+  fprintf (stdout, "[%s]", text);
+}
+
+static void
+logmsg_end ()
+{
+  fprintf (stdout, "\n");
+  fflush (stdout);
+}
+
+static void
+logmsg_addr (byte *msg, size_t msglen)
+{
+  fprintf (stdout, " %02x.%02x->%02x.%02x",
+           msg[3], msg[4], msg[1], msg[2]);
+
+  if (msg[3] == 0xff || msg[4] == 0xff)
+    fputs ("[bad_sender_addr]", stdout);
+}
+
+static void
+logmsg_fmt (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  va_start (arg_ptr, format);
+  putc (' ', stdout);
+  vfprintf (stdout, format, arg_ptr);
+  va_end (arg_ptr);
+}
+
+static void
+logmsg_time (unsigned int value, unsigned int decile)
+{
+  unsigned int day, hour, min, sec;
+
+  day = (value/6/60/24);
+  hour= (value/6/60 % 24);
+  min = (value/6 % 60);
+  sec = (value % 6) * 10;
+  sec += decile/10;
+
+  logmsg_fmt ("%ud%02u:%02u:%02u.%u", day, hour, min, sec, decile%10);
+}
+
+
+/* Return the current ebus time for broadcasting.  The time is defined
+   as number of 10 second periods passed since Monday 0:00.  */
+static unsigned int
+mk_ebus_time (unsigned int *r_decile, unsigned int *r_dst)
+{
+  struct tm *tp;
+  time_t atime = time (NULL);
+  unsigned int result;
+
+  /* Get the local time and convert it to a Monday...Sunday week.  */
+  /* Fixme: We can't return fractions of a second.  Need to use
+     clock_gettime or wait for the full second.  */
+  tp = localtime (&atime);
+  if (!tp->tm_wday)
+    tp->tm_wday = 6;
+  else
+    tp->tm_wday--;
+
+  result = (tp->tm_wday * 24 * 60 * 6
+            + tp->tm_hour * 60 * 6
+            + tp->tm_min * 6 + tp->tm_sec/10);
+  if (r_decile)
+    *r_decile = (tp->tm_sec % 10) * 10;
+  if (r_dst)
+    *r_dst = !!tp->tm_isdst;
+  return result;
+}
+
+
+
+
+\f
+/* Process test messages.  */
+static void
+process_ebus_test (byte *msg, size_t msglen)
+{
+  logmsg_start ("test");
+  logmsg_fmt ("from %02x.%02x mode %02x", msg[1], msg[2], msg[3]);
+  logmsg_time ((msg[4] << 8)|msg[5], 0);
+  logmsg_fmt ("nrx %u", ((msg[6] << 8)|msg[7]));
+  logmsg_fmt ("ntx %u", ((msg[8] << 8)|msg[9]));
+  logmsg_fmt ("col %u", ((msg[10] << 8)|msg[11]));
+  logmsg_fmt ("ovf %u", ((msg[12] << 8)|msg[13]));
+  logmsg_fmt ("int %u", ((msg[14] << 8)|msg[15]));
+  logmsg_end ();
+}
+
+
+/* Process debug messages.  */
+static void
+process_ebus_dbgmsg (byte *msg, size_t msglen)
+{
+  logmsg_start ("dbgmsg");
+  logmsg_fmt ("%02x.%02x => %.13s", msg[1], msg[2], msg+3);
+  logmsg_end ();
+}
+
+\f
+static void
+p_busctl_time (byte *msg, size_t msglen, int have_decile)
+{
+  unsigned int value, decile;
+
+  value = (msg[7] << 8 | msg[8]);
+  decile = (have_decile || (msg[6] & 0x02))? msg[9]: 0;
+
+  logmsg_time (value, decile);
+  logmsg_fmt ("(%lu %u%s%s%s)",
+              value, decile,
+              (msg[6] & 0x04)? " dst":"",
+              (msg[6] & 0x02)? " decile":"",
+              (msg[6] & 0x01)? " exact":"");
+}
+
+
+/* Process busctl messages.  */
+static void
+process_ebus_busctl (byte *msg, size_t msglen)
+{
+  char is_response = !!(msg[5] & P_BUSCTL_RESPMASK);
+
+  logmsg_start ("busctl");
+  logmsg_addr (msg, msglen);
+
+  switch ((msg[5] & ~P_BUSCTL_RESPMASK))
+    {
+    case P_BUSCTL_TIME:
+      logmsg_fmt ("%s:TimeBroadcast", is_response?"Rsp":"Cmd");
+      if (is_response)
+        logmsg_fmt ("[invalid:response_flag_set]");
+      else
+        p_busctl_time (msg, msglen, 0);
+      break;
+
+    case P_BUSCTL_QRY_TIME:
+      logmsg_fmt ("%s:QueryTime", is_response?"Rsp":"Cmd");
+      if (is_response)
+        p_busctl_time (msg, msglen, 1);
+      break;
+
+    default:
+      logmsg_fmt ("%s:%02x", is_response?"Rsp":"Cmd", msg[5]);
+      break;
+    }
+  logmsg_end ();
+}
+
+
+static void
+p_h61_adcread_cmd (byte *msg, size_t msglen)
+{
+  logmsg_fmt ("[not_yet_supported]");
+}
+
+static void
+p_h61_adcread_rsp (byte *msg, size_t msglen)
+{
+  logmsg_fmt ("sensor_%d: %u (%02x%02x)",
+              msg[6],
+              (msg[7] << 8 | msg[8]),
+              msg[7], msg[8]);
+
+}
+
+
+static void
+p_h61_shutter_cmd (byte *msg, size_t msglen)
+{
+  switch (msg[6])
+    {
+    case P_H61_SHUTTER_QRY_SCHEDULE:
+      logmsg_fmt ("QrySchedule(%u)", msg[7]);
+      break;
+    case P_H61_SHUTTER_UPD_SCHEDULE:
+      logmsg_fmt ("UpdSchedule(%u): %u[%u] action 0x%02x ",
+                  msg[7], msg[10], msg[9], msg[13]);
+      logmsg_time ((msg[11] << 8)|msg[12], 0);
+      break;
+    default:
+      logmsg_fmt ("Subcommand_%u", msg[6]);
+      break;
+    }
+}
+
+static void
+p_h61_shutter_rsp (byte *msg, size_t msglen)
+{
+  switch (msg[6])
+    {
+    case P_H61_SHUTTER_QRY_SCHEDULE:
+      logmsg_fmt ("QrySchedule(%u): %u[%u] action 0x%02x ",
+                  msg[7], msg[10], msg[9], msg[13]);
+      logmsg_time ((msg[11] << 8)|msg[12], 0);
+      break;
+    default:
+      logmsg_fmt ("Subcommand_%u", msg[6]);
+      break;
+    }
+}
+
+
+/* Process H/61 messages.  */
+static void
+process_ebus_h61 (byte *msg, size_t msglen)
+{
+  char is_response = !!(msg[5] & P_H61_RESPMASK);
+
+  logmsg_start ("h61");
+  logmsg_addr (msg, msglen);
+
+  switch ((msg[5] & ~P_H61_RESPMASK))
+    {
+    case P_H61_ADCREAD:
+      logmsg_fmt ("%s:AdcRead", is_response?"Rsp":"Cmd");
+      if (is_response)
+        p_h61_adcread_rsp (msg, msglen);
+      else
+        p_h61_adcread_cmd (msg, msglen);
+       break;
+
+    case P_H61_SHUTTER:
+      logmsg_fmt ("%s:Shutter", is_response?"Rsp":"Cmd");
+      if (is_response)
+        p_h61_shutter_rsp (msg, msglen);
+      else
+        p_h61_shutter_cmd (msg, msglen);
+       break;
+
+    default:
+      logmsg_fmt ("%s:%02x", is_response?"Rsp":"Cmd", msg[5]);
+      break;
+    }
+  logmsg_end ();
+}
+
+
+static void
+process (FILE *fp)
+{
+  unsigned char buffer[48+2];
+  int idx, synced, esc;
+  int c, i;
+  int msglen = 0;
+
+  esc = synced = idx = 0;
+  while ((c=getc (fp)) != EOF)
+    {
+      if (c == FRAMESYNCBYTE)
+        {
+          esc = 0;
+          synced = 1;
+          idx = 0;
+        }
+      else if (c == FRAMEESCBYTE && !esc)
+        esc = 1;
+      else if (synced)
+        {
+          if (esc)
+            {
+              esc = 0;
+              c ^= FRAMEESCMASK;
+            }
+
+          if (!idx)
+            {
+              switch ((c & PROTOCOL_MSGLEN_MASK))
+                {
+                case PROTOCOL_MSGLEN_48: msglen = 48; break;
+                case PROTOCOL_MSGLEN_32: msglen = 32; break;
+                case PROTOCOL_MSGLEN_16: msglen = 16; break;
+                default:
+                  err ("reserved message length value encountered");
+                  synced = 0;
+                  continue;
+                }
+              buffer[idx++] = c;
+            }
+          else if (idx < msglen + 2)
+            {
+              buffer[idx++] = c;
+              if (idx == msglen + 2)
+                {
+                  unsigned int crc;
+                  int crcok;
+
+                  crc = compute_crc (buffer, msglen);
+                  crcok = ((crc >> 8) == buffer[msglen]
+                           && (crc&0xff) == buffer[msglen+1]);
+
+                  if (debug)
+                    {
+                      for (i=0; i < msglen + 2; i++)
+                        printf ("%s%02x", i? " ":"", buffer[i]);
+                      fputs (crcok? " ok":" bad", stdout);
+                      putchar ('\n');
+                      fflush (stdout);
+                    }
+                  if (crcok)
+                    {
+                      switch ((buffer[0] & 0xff))
+                        {
+                        case PROTOCOL_EBUS_BUSCTL:
+                          process_ebus_busctl (buffer, msglen);
+                          break;
+                        case PROTOCOL_EBUS_H61:
+                          process_ebus_h61 (buffer, msglen);
+                          break;
+                        case PROTOCOL_EBUS_DBGMSG:
+                          process_ebus_dbgmsg (buffer, msglen);
+                          break;
+                        case PROTOCOL_EBUS_TEST:
+                          process_ebus_test (buffer, msglen);
+                          break;
+                        default:
+                          /* Ignore all other protocols.  */
+                          break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+/* Send out the raw byte C.  */
+static void
+send_byte_raw (FILE *fp, byte c)
+{
+  putc (c, fp);
+}
+
+
+/* Send byte C with byte stuffing.  */
+static void
+send_byte (FILE *fp, byte c)
+{
+  if (c == FRAMESYNCBYTE || c == FRAMEESCBYTE)
+    {
+      send_byte_raw (fp, FRAMEESCBYTE);
+      send_byte_raw (fp, (c ^ FRAMEESCMASK));
+    }
+  else
+    send_byte_raw (fp, c);
+}
+
+static void
+footest_1 (FILE *fp)
+{
+  byte msg[16];
+  unsigned int crc;
+  int idx;
+
+  msg[0] = PROTOCOL_EBUS_BUSCTL;
+  msg[1] = 0xff;
+  msg[2] = 0xff;
+  msg[3] = 0x01;
+  msg[4] = 0x01;
+  msg[5] = P_BUSCTL_QRY_TIME;
+  memset (msg+6, 0, 10);
+  crc = compute_crc (msg, 16);
+
+  send_byte_raw (fp, FRAMESYNCBYTE);
+  for (idx=0; idx < 16; idx++)
+    send_byte (fp, msg[idx]);
+  send_byte (fp, crc >> 8);
+  send_byte (fp, crc);
+  fflush (fp);
+}
+
+static void
+footest_2 (FILE *fp)
+{
+  byte msg[16];
+  unsigned int crc;
+  int idx;
+
+  msg[0] = PROTOCOL_EBUS_H61;
+  msg[1] = 0x10;
+  msg[2] = 0x05;
+  msg[3] = 0x01;
+  msg[4] = 0x01;
+  msg[5] = P_H61_SHUTTER;
+  msg[6] = P_H61_SHUTTER_QRY_SCHEDULE;
+  msg[7] = 1;
+  memset (msg+8, 0, 8);
+  crc = compute_crc (msg, 16);
+
+  send_byte_raw (fp, FRAMESYNCBYTE);
+  for (idx=0; idx < 16; idx++)
+    send_byte (fp, msg[idx]);
+  send_byte (fp, crc >> 8);
+  send_byte (fp, crc);
+  fflush (fp);
+}
+
+static void
+footest_3 (FILE *fp)
+{
+  byte msg[16];
+  unsigned int crc;
+  int idx;
+  unsigned int tim, dec, dst;
+
+  tim = mk_ebus_time (&dec, &dst);
+  msg[0] = PROTOCOL_EBUS_BUSCTL;
+  msg[1] = 0xff;
+  msg[2] = 0xff;
+  msg[3] = 0x01;
+  msg[4] = 0x01;
+  msg[5] = P_BUSCTL_TIME;
+  msg[6] = 0x03;  /* Decile given, exact time */
+  if (dst)
+    msg[6] |= 0x04;
+  msg[7] = tim >> 8;
+  msg[8] = tim;
+  msg[9] = dec;
+  memset (msg+10, 0, 6);
+  crc = compute_crc (msg, 16);
+
+  send_byte_raw (fp, FRAMESYNCBYTE);
+  for (idx=0; idx < 16; idx++)
+    send_byte (fp, msg[idx]);
+  send_byte (fp, crc >> 8);
+  send_byte (fp, crc);
+  fflush (fp);
+}
+
+
+static void
+footest_4 (FILE *fp)
+{
+  byte msg[16];
+  unsigned int crc;
+  int idx;
+  unsigned char   item = 2;
+  unsigned int     tim = 3 * 60 * 6;
+  unsigned char action = 0x80;
+
+  msg[0] = PROTOCOL_EBUS_H61;
+  msg[1] = 0x10;
+  msg[2] = 0x05;
+  msg[3] = 0x01;
+  msg[4] = 0x01;
+  msg[5] = P_H61_SHUTTER;
+  msg[6] = P_H61_SHUTTER_UPD_SCHEDULE;
+  msg[7] = 0;
+  msg[8] = 0;
+  msg[9] = 1;
+  msg[10] = item;
+  msg[11] = tim >> 8;
+  msg[12] = tim;
+  msg[13] = action;
+  msg[14] = 0;
+  msg[15] = 0;
+  crc = compute_crc (msg, 16);
+
+  send_byte_raw (fp, FRAMESYNCBYTE);
+  for (idx=0; idx < 16; idx++)
+    send_byte (fp, msg[idx]);
+  send_byte (fp, crc >> 8);
+  send_byte (fp, crc);
+  fflush (fp);
+}
+
+
+static void
+send_reset_shutter_eeprom (FILE *fp)
+{
+  byte msg[16];
+  unsigned int crc;
+  int idx;
+
+  msg[0] = PROTOCOL_EBUS_H61;
+  msg[1] = 0x10;
+  msg[2] = 0x05;
+  msg[3] = 0x01;
+  msg[4] = 0x01;
+  msg[5] = P_H61_SHUTTER;
+  msg[6] = P_H61_SHUTTER_UPD_SCHEDULE;
+  msg[7] = 0xf0;
+  msg[8] = 0;
+  msg[9] = 16;
+  msg[10] = 0xf0;
+  msg[11] = 0xf0;
+  msg[12] = 0xf0;
+  msg[13] = 0xf0;
+  msg[14] = 0;
+  msg[15] = 0;
+  crc = compute_crc (msg, 16);
+
+  send_byte_raw (fp, FRAMESYNCBYTE);
+  for (idx=0; idx < 16; idx++)
+    send_byte (fp, msg[idx]);
+  send_byte (fp, crc >> 8);
+  send_byte (fp, crc);
+  fflush (fp);
+}
+
+
+static int
+show_usage (int ex)
+{
+  fputs ("Usage: " PGM " DEVICE\n"
+         "Control an attached ebus\n\n"
+         "  --speed N      Use given speed\n"
+         "  --verbose      Enable extra informational output\n"
+         "  --debug        Enable additional debug output\n"
+         "  --help         Display this help and exit\n\n"
+         "Report bugs to " PGM_BUGREPORT ".\n",
+         ex? stderr:stdout);
+  exit (ex);
+}
+
+
+int
+main (int argc, char **argv )
+{
+  int last_argc = -1;
+  int testmode = 0;
+  FILE *fp;
+
+  if (argc)
+    {
+      argc--; argv++;
+    }
+  while (argc && last_argc != argc )
+    {
+      last_argc = argc;
+      if (!strcmp (*argv, "--"))
+        {
+          argc--; argv++;
+          break;
+        }
+      else if (!strcmp (*argv, "--version"))
+        {
+          fputs (PGM " " PGM_VERSION "\n", stdout);
+          exit (0);
+        }
+      else if (!strcmp (*argv, "--help"))
+        {
+          show_usage (0);
+        }
+      else if (!strcmp (*argv, "--speed"))
+        {
+          argc--; argv++;
+          if (argc)
+            {
+              line_speed = atoi (*argv);
+              argc--; argv++;
+            }
+        }
+      else if (!strcmp (*argv, "--verbose"))
+        {
+          verbose = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--debug"))
+        {
+          verbose = debug = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--test"))
+        {
+          argc--; argv++;
+          if (argc)
+            {
+              testmode = atoi (*argv);
+              argc--; argv++;
+            }
+        }
+      else if (!strncmp (*argv, "--", 2))
+        show_usage (1);
+    }
+
+  if (argc != 1)
+    show_usage (1);
+
+  setvbuf (stdout, NULL, _IOLBF, 0);
+
+  fp = open_line (*argv);
+  switch (testmode)
+    {
+    case 1: footest_1 (fp); break;
+    case 2: footest_2 (fp); break;
+    case 3: footest_3 (fp); break;
+    case 4: footest_4 (fp); break;
+    case 5: send_reset_shutter_eeprom (fp); break;
+    default: process (fp); break;
+    }
+  fclose (fp);
+
+  return any_error? 1:0;
+}
diff --git a/ebus/shutter.c b/ebus/shutter.c
new file mode 100644 (file)
index 0000000..4aee6a6
--- /dev/null
@@ -0,0 +1,652 @@
+/* shutter.c - Elektor Bus node to control a shutter
+ * Copyright (C) 2011 g10 Code GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This node is used to control the shutter in a living room.  The
+   shutter is operator by a motor with two coils and limit switches
+   connected to a solid state relays which is controlled by this node.
+   A future version of this controller will also support some sensor
+   to allow pulling down the shutter only to some percentage.  In any
+   case the limit switches of the motor control the endpoints.  The
+   control logic takes down the relays after the time required to pull
+   the shutters up or down plus some extra time.  The S2 and S3
+   switches are used for manually controlling the shutter.  They are
+   interlocked and operate in a toggle on/off way.  The hardware
+   itself is also interlocked so that it is not possible to drive both
+   motors at the same time.
+
+   The used relays are FINDER 34.51.7.012.0010 which feature a high
+   sensitive coil (~18mA) and are able to switch 6A (the datasheet
+   even exlicitly allows motors of up to 185W).  The first relay is
+   used for direction selection with NC connected to the pull-up motor
+   and NO to the pull-down motor.  Our control logic makes sure that
+   this relay is not switched under load.  The second one connects its
+   NO to the first relay and a snubber circuit is used to protect its
+   contacts.  They are both connected to the 12V rail and flyback
+   diodes are used for each.  Two BC547 are used to drive them.
+
+   Historical note: A first version of the shutter rig and this
+   software used two (not interlocked) solid state relays made up from
+   S202S02 opto-triacs along with snubbers and varistors to cope with
+   the voltage peaks induced by the other motor.  However the spikes
+   were obviously too high and after some weeks the triacs bricked
+   themself.  A second try using S202S01s had the same effect.  Better
+   save your money and use mechanical relays.
+ */
+
+#include "hardware.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/sleep.h>
+#include <avr/eeprom.h>
+
+#include "ebus.h"
+#include "proto-busctl.h"
+#include "proto-h61.h"
+
+
+#define MOTOR_on       (PORTC)  /* Switch motor on.  */
+#define MOTOR_on_BIT   (PC2)
+#define MOTOR_down     (PORTC)  /* Switch direction relay to down.  */
+#define MOTOR_down_BIT (PC3)
+
+
+#define SCHEDULE_ACTION_NOP   0 /* No operation.  */
+#define SCHEDULE_ACTION_UP    1 /* Minute + 10s := pull up  */
+#define SCHEDULE_ACTION_DOWN  5 /* Minute + 50s := pull down  */
+
+
+/* Allowed action values.  Note that the hardware interlocks both
+   motors.  However to avoid switch the direction under load we first
+   set the direction, wait a moment and then switch the motor on.  */
+enum __attribute__ ((packed)) action_values {
+  action_none = 0,
+  action_up,
+  action_down
+};
+
+enum __attribute__ ((packed)) motor_state_values {
+  motor_state_off = 0,
+  motor_state_pre_off,
+  motor_state_pre_off2,
+  motor_state_pre_up,
+  motor_state_pre_up2,
+  motor_state_up,
+  motor_state_up_ready,
+  motor_state_pre_down,
+  motor_state_pre_down2,
+  motor_state_down,
+  motor_state_down_ready
+};
+
+/* Remember the last action time.  */
+static uint16_t schedule_last_tfound;
+
+/* The next action to do.  It's value is changed by the commands
+   and a ticker is switching the motor depending on these values.  */
+static volatile enum action_values next_action;
+
+
+/* The shutter state byte.  This is directly used for the
+   QueryShutterState response message.  */
+static volatile byte shutter_state;
+
+/* Event flag, triggred (surprise) every 10 seconds.  */
+static volatile byte ten_seconds_event;
+
+
+\f
+/* Local prototypes.  */
+static void init_eeprom (byte force);
+
+
+
+\f
+/* This code is called by the 1ms ticker interrupt service routine
+   with the current clock value given in milliseconds from 0..9999. */
+void
+ticker_bottom (unsigned int clock)
+{
+  static volatile enum action_values current_action;
+  static volatile uint16_t action_delay;
+  static volatile enum motor_state_values state = motor_state_off;
+  enum action_values save_action;
+
+  /* Blink the activity LED if the motor is not in off state.  */
+  if (state != motor_state_off)
+    {
+      if (!(clock % 1000))
+        {
+          if ((LED_Collision & _BV(LED_Collision_BIT)))
+            LED_Collision &= ~_BV (LED_Collision_BIT);
+          else
+            LED_Collision |= _BV(LED_Collision_BIT);
+        }
+    }
+
+  /* Get next action.  */
+  save_action = current_action;
+  if (read_key_s2 ())
+    current_action = current_action != action_down ? action_down : action_none;
+  if (read_key_s3 ())
+    current_action = current_action != action_up? action_up : action_none;
+  if (current_action != save_action || next_action)
+    {
+      if (next_action)
+        {
+          current_action = next_action;
+          next_action = 0;
+        }
+      switch (current_action)
+        {
+        case action_none: state = motor_state_pre_off; break;
+        case action_up:   state = motor_state_pre_up; break;
+        case action_down: state = motor_state_pre_down; break;
+        }
+      /* When a new action has been selected we need to limit the
+         action delay to the minimum value so that we don't stick in
+         the long down or up states.  We also need to make sure that
+         there is an action delay so that we enter the state
+         transition switch. */
+      if (!action_delay)
+        action_delay = 1;
+      else if (action_delay > 200)
+        action_delay = 200;
+    }
+
+  if (action_delay && !--action_delay)
+    {
+      switch (state)
+        {
+        case motor_state_off:
+          LED_Collision &= ~_BV (LED_Collision_BIT); /* Clear LED.  */
+          /* Make sure the motor flags are cleared in the state info.  */
+          shutter_state &= 0b00111111;
+          break;
+
+        label_pre_off:
+        case motor_state_pre_off:
+          MOTOR_on &= ~_BV(MOTOR_on_BIT);    /* Switch motor off. */
+          action_delay = 200; /*ms*/
+          state = motor_state_pre_off2;
+          break;
+        case motor_state_pre_off2:
+          MOTOR_down &= ~_BV(MOTOR_down_BIT);/* Switch direction relay off. */
+          action_delay = 200; /*ms*/
+          state = motor_state_off;
+          break;
+
+        case motor_state_pre_up:
+          MOTOR_on &= ~_BV(MOTOR_on_BIT);    /* Switch motor off. */
+          action_delay = 200; /*ms*/
+          state = motor_state_pre_up2;
+          break;
+        case motor_state_pre_up2:
+          MOTOR_down &= ~_BV(MOTOR_down_BIT);/* Switch direction relay off. */
+          action_delay = 200; /*ms*/
+          state = motor_state_up;
+          break;
+        case motor_state_up:
+          MOTOR_on |= _BV(MOTOR_on_BIT);     /* Switch motor on. */
+          shutter_state = 0b11000000;
+          /*                |!------- Direction set to up
+           *                +-------- Motor running.        */
+          action_delay = 25000; /*ms*/
+          state = motor_state_up_ready;
+          break;
+        case motor_state_up_ready:
+          shutter_state = 0b00100000;
+          /*                  | !~~!- Value: 0 = 0% closed
+           *                  +------ State in bits 3..0 is valid.  */
+          goto label_pre_off;
+          break;
+
+        case motor_state_pre_down:
+          MOTOR_on &= ~_BV(MOTOR_on_BIT);    /* Switch motor off. */
+          action_delay = 200; /*ms*/
+          state = motor_state_pre_down2;
+          break;
+        case motor_state_pre_down2:
+          MOTOR_down |= _BV(MOTOR_down_BIT); /* Switch direction relay on. */
+          action_delay = 200; /*ms*/
+          state = motor_state_down;
+          break;
+        case motor_state_down:
+          MOTOR_on |= _BV(MOTOR_on_BIT);     /* Switch motor on. */
+          shutter_state = 0b10000000;
+          /*                |!------- Direction set to down
+           *                +-------- Motor running.        */
+          action_delay = 25000; /*ms*/
+          state = motor_state_down_ready;
+        case motor_state_down_ready:
+          shutter_state = 0b00101111;
+          /*                  | !~~!--- Value: 15 = 100% closed
+           *                  +-------- State in bits 3..0 is valid.  */
+          goto label_pre_off;
+          break;
+        }
+    }
+
+  if (!clock)
+    {
+      ten_seconds_event = 1;
+      wakeup_main = 1;
+    }
+}
+
+
+/* static void */
+/* send_dbgmsg (const char *string) */
+/* { */
+/*   byte msg[16]; */
+
+/*   msg[0] = PROTOCOL_EBUS_DBGMSG; */
+/*   msg[1] = config.nodeid_hi; */
+/*   msg[2] = config.nodeid_lo; */
+/*   memset (msg+3, 0, 13); */
+/*   strncpy (msg+3, string, 13); */
+/*   csma_send_message (msg, 16); */
+/* } */
+
+/* static void */
+/* send_dbgmsg_fmt (const char *format, ...) */
+/* { */
+/*   va_list arg_ptr; */
+/*   char buffer[16]; */
+
+/*   va_start (arg_ptr, format); */
+/*   vsnprintf (buffer, 16, format, arg_ptr); */
+/*   va_end (arg_ptr); */
+/*   send_dbgmsg (buffer); */
+/* } */
+
+
+/* Process scheduled actions.  TIME is the current time.  If
+   FORCED_TLOW is not 0 the scheduler will run the last action between
+   FORCED_TLOW and TIME regardless on whether it has already been run.
+   This feature is required to cope with a changed system time:
+   Consider the shutter shall be called at 19:00, the current system
+   time is 18:59 and the system time is updated to 19:10 - without
+   that feature the closing at 19:00 would get lost.  */
+static void
+process_schedule (uint16_t time, uint16_t forced_tlow)
+{
+  uint16_t tlow, thigh, t, tfound;
+  byte i;
+
+  if (schedule_last_tfound > time || forced_tlow)
+    schedule_last_tfound = 0;  /* Time wrapped to the next week or forced
+                                  schedule action.  */
+
+  /* We look up to 5 minutes back into the past to cope with lost events.  */
+  time /= 6;
+  time *= 6;
+  tlow = forced_tlow? forced_tlow : time;
+  if (tlow >= 5 * 6)
+    tlow -= 5 * 6;
+  else
+    tlow = 0;
+  if (schedule_last_tfound && schedule_last_tfound > tlow)
+    tlow = schedule_last_tfound;
+  thigh = time + 5;
+
+  /* send_dbgmsg_fmt ("time=%u", time); */
+  /* send_dbgmsg_fmt ("lst=%u", schedule_last_tfound); */
+  /* send_dbgmsg_fmt ("low=%u", tlow); */
+  /* send_dbgmsg_fmt ("hig=%u", thigh); */
+
+  /* Walk the schedule and find the last entry. */
+  for (tfound=0, i=0; i < DIM (ee_data.u.shutterctl.schedule); i++)
+    {
+      t = eeprom_read_word (&ee_data.u.shutterctl.schedule[i]);
+      if (!t)
+        break;
+      if (t > tlow && t <= thigh)
+        tfound = t;
+    }
+  if (tfound)
+    {
+      schedule_last_tfound = tfound;
+      /* send_dbgmsg_fmt ("fnd=%u", ((tfound/6)*6)); */
+      tfound %= 6;
+      /* send_dbgmsg_fmt ("act=%u", tfound); */
+      if (tfound == SCHEDULE_ACTION_UP)
+        next_action = action_up;
+      else if (tfound == SCHEDULE_ACTION_DOWN)
+        next_action = action_down;
+    }
+}
+
+
+/* Process a shutter command.  */
+static void
+process_shutter_cmd (byte *msg)
+{
+  uint16_t val16;
+  byte err = 0;
+
+  switch (msg[6])
+    {
+    case P_H61_SHUTTER_QUERY:
+      {
+        msg[1] = msg[3];
+        msg[2] = msg[4];
+        msg[3] = config.nodeid_hi;
+        msg[4] = config.nodeid_lo;
+        msg[5] |= P_BUSCTL_RESPMASK;
+
+        msg[7] = 0; /* No error.  */
+        msg[8] = shutter_state;
+        memset (msg+9, 0, 7);
+        csma_send_message (msg, MSGSIZE);
+      }
+      break;
+
+    case P_H61_SHUTTER_DRIVE:
+      {
+        if (msg[7] > 1 /* Only all shutters or shutter 1 are allowed.  */
+            || msg[9] || msg[10] || msg[11] || msg[12] || msg[13]
+            || msg[14] || msg[15] /* Reserved bytes are not zero.  */
+            || (msg[8] & 0x10)    /* Reserved bit is set.  */
+            || (msg[8] & 0x20)    /* Not yet supported.  */ )
+          err = 1;
+        else if ((msg[8] & 0xc0) == 0xc0)
+          next_action = action_up;
+        else if ((msg[8] & 0xc0) == 0x80)
+          next_action = action_down;
+        else
+          err = 1;
+
+        msg[1] = msg[3];
+        msg[2] = msg[4];
+        msg[3] = config.nodeid_hi;
+        msg[4] = config.nodeid_lo;
+        msg[5] |= P_BUSCTL_RESPMASK;
+        msg[7] = err;
+        msg[8] = shutter_state;
+        memset (msg+9, 0, 7);
+        csma_send_message (msg, MSGSIZE);
+      }
+      break;
+
+    case P_H61_SHUTTER_QRY_TIMINGS:
+      break;
+
+    case P_H61_SHUTTER_UPD_TIMINGS:
+      break;
+
+    case P_H61_SHUTTER_QRY_SCHEDULE:
+      {
+        byte n, i;
+
+        for (i=0; i < DIM (ee_data.u.shutterctl.schedule); i++)
+          {
+            val16 = eeprom_read_word (&ee_data.u.shutterctl.schedule[i]);
+            if (!val16)
+              break;
+          }
+        n = i;
+
+        msg[1] = msg[3];
+        msg[2] = msg[4];
+        msg[3] = config.nodeid_hi;
+        msg[4] = config.nodeid_lo;
+        msg[5] |= P_BUSCTL_RESPMASK;
+        msg[7] = 0; /* We only have a global schedule for now.  */
+        msg[8] = 0; /* No error.  */
+        for (i=0; i < n; i++)
+          {
+            val16 = eeprom_read_word (&ee_data.u.shutterctl.schedule[i]);
+            switch ((val16 % 6))
+              {
+              case SCHEDULE_ACTION_UP:   msg[13] = 0b11000000; break;
+              case SCHEDULE_ACTION_DOWN: msg[13] = 0b10000000; break;
+              default: msg[13] = 0;  break; /* Undefined.  */
+              }
+            val16 /= 6;
+            val16 *= 6;
+            msg[9] = n;
+            msg[10] = i;
+            msg[11] = val16 >> 8;
+            msg[12] = val16;
+            /* msg[13] already set.  */
+            msg[14] = 0;
+            msg[15] = 0;
+            csma_send_message (msg, MSGSIZE);
+          }
+      }
+      break;
+
+    case P_H61_SHUTTER_UPD_SCHEDULE:
+      if (msg[8] || msg[14] || msg[15] || msg[9] != 1
+          || msg[10] >= DIM (ee_data.u.shutterctl.schedule))
+        {
+          /* Bad message or eeprom reset  */
+          if (msg[7] == 0xf0 && msg[9] == 16 && msg[10] == 0xf0
+              && msg[11] == 0xf0 && msg[12] == 0xf0 && msg[13] == 0xf0)
+            {
+              init_eeprom (1);
+            }
+        }
+      else
+        {
+          /* Get time and round down to the full minute.  */
+          val16 = (msg[11] << 8) | msg[12];
+          val16 /= 6;
+          val16 *= 6;
+          /* Include the action.  Note that SCHEDULE_ACTION_NOP is the
+             default.  */
+          if (msg[13] == 0b11000000)
+            val16 += SCHEDULE_ACTION_UP;
+          else if (msg[13] == 0b10000000)
+            val16 += SCHEDULE_ACTION_DOWN;
+
+          eeprom_write_word (&ee_data.u.shutterctl.schedule[msg[10]], val16);
+        }
+      break;
+
+    default:
+      break;
+    }
+}
+
+
+/* A new message has been received and we must now parse the message
+   quickly and see what to do.  We need to return as soon as possible,
+   so that the caller may re-enable the receiver.  */
+static void
+process_ebus_h61 (byte *msg)
+{
+  char is_response = !!(msg[5] & P_H61_RESPMASK);
+
+  if (!(msg[1] == config.nodeid_hi || msg[2] == config.nodeid_lo))
+    return; /* Not addressed to us.  */
+
+  switch ((msg[5] & ~P_H61_RESPMASK))
+    {
+    case P_H61_SHUTTER:
+      if (!is_response)
+        process_shutter_cmd (msg);
+      break;
+
+    default:
+      break;
+    }
+}
+
+
+/* Process busctl messages.  */
+static void
+process_ebus_busctl (byte *msg)
+{
+  uint16_t val16;
+  byte     val8;
+  char is_response = !!(msg[5] & P_BUSCTL_RESPMASK);
+
+  if (is_response)
+    return;  /* Nothing to do.  */
+  else if (msg[3] == 0xff || msg[4] == 0xff || msg[4] == 0)
+    return ; /* Bad sender address.  */
+  else if (msg[1] == config.nodeid_hi && msg[2] == config.nodeid_lo)
+    ; /* Directed to us.  */
+  else if ((msg[1] == config.nodeid_hi || msg[1] == 0xff) && msg[2] == 0xff)
+    ; /* Broadcast. */
+  else
+    return; /* Not addressed to us.  */
+
+  switch ((msg[5] & ~P_BUSCTL_RESPMASK))
+    {
+    case P_BUSCTL_TIME:
+      {
+        uint16_t t;
+
+        t = get_current_time ();
+        val16 = (msg[7] << 8) | msg[8];
+        val8  = (msg[6] & 0x02)? msg[9] : 0;
+        set_current_fulltime (val16, val8);
+        if (val16 > t)
+          process_schedule (val16, t);
+      }
+      break;
+
+    case P_BUSCTL_QRY_TIME:
+      msg[1] = msg[3];
+      msg[2] = msg[4];
+      msg[3] = config.nodeid_hi;
+      msg[4] = config.nodeid_lo;
+      msg[5] |= P_BUSCTL_RESPMASK;
+      msg[6] = 0; /* fixme: return an error for unknown shutter numbers.  */
+      val16 = get_current_fulltime (&val8);
+      msg[7] = val16 >> 8;
+      msg[8] = val16;
+      msg[9] = val8;
+      memset (msg+10, 0, 6);
+      csma_send_message (msg, MSGSIZE);
+      break;
+
+    default:
+      break;
+    }
+}
+
+
+
+/* Init our eeprom data if needed. */
+static void
+init_eeprom (byte force)
+{
+  uint16_t uptime, downtime;
+  byte i;
+
+  if (force || !eeprom_read_word (&ee_data.u.shutterctl.schedule[0]))
+    {
+      /* The schedule is empty - set up reasonable values for every
+         day of the week.  */
+      uptime = (7 * 60 + 30) * 6 + SCHEDULE_ACTION_UP;
+      downtime = (18 * 60 + 15) * 6 + SCHEDULE_ACTION_DOWN;
+      for (i=0; i < 7*2 && i < DIM (ee_data.u.shutterctl.schedule); i++)
+        {
+          if (i==6*2) /* Pull up on Sundays one hour later.  */
+            uptime += 60 * 6;
+          eeprom_write_word (&ee_data.u.shutterctl.schedule[i], uptime);
+          i++;
+          eeprom_write_word (&ee_data.u.shutterctl.schedule[i], downtime);
+          uptime += 24 * 60 * 6;
+          downtime += 24 * 60 * 6;
+        }
+    }
+}
+
+
+/*
+    Entry point
+ */
+int
+main (void)
+{
+  byte *msg;
+
+  hardware_setup (NODETYPE_SHUTTER);
+  init_eeprom (0);
+
+  /* Port C configuration changes.  Configure motor ports for output
+     and switch them off. */
+  PORTC &= ~(_BV(MOTOR_down_BIT) | _BV(MOTOR_on_BIT));
+  DDRC  |= _BV(MOTOR_down_BIT) | _BV(MOTOR_on_BIT);
+
+  csma_setup ();
+  onewire_setup ();
+
+  sei (); /* Enable interrupts.  */
+
+  for (;;)
+    {
+      set_sleep_mode (SLEEP_MODE_IDLE);
+      while (!wakeup_main)
+        {
+          cli();
+          if (!wakeup_main)
+            {
+              sleep_enable ();
+              sei ();
+              sleep_cpu ();
+              sleep_disable ();
+            }
+          sei ();
+        }
+      wakeup_main = 0;
+
+      if (ten_seconds_event)
+        {
+          uint16_t t;
+
+          ten_seconds_event = 0;
+
+          t = get_current_time ();
+          if (!(t % 6))
+            {
+              /* Code to run every minute.  */
+              process_schedule (t, 0);
+            }
+        }
+
+
+      msg = csma_get_message ();
+      if (msg)
+        {
+          /* Process the message.  */
+          switch (msg[0])
+            {
+            case PROTOCOL_EBUS_BUSCTL:
+              process_ebus_busctl (msg);
+              break;
+            case PROTOCOL_EBUS_H61:
+              process_ebus_h61 (msg);
+              break;
+            default:
+              /* Ignore all other protocols.  */
+              break;
+            }
+          /* Re-enable the receiver.  */
+          csma_message_done ();
+        }
+    }
+
+}