First chunk of code for the house automation
authorWerner Koch <wk@gnupg.org>
Thu, 10 Nov 2011 17:31:50 +0000 (18:31 +0100)
committerWerner Koch <wk@gnupg.org>
Thu, 10 Nov 2011 17:31:50 +0000 (18:31 +0100)
22 files changed:
ebus/.gitignore [new file with mode: 0644]
ebus/Makefile
ebus/README [new file with mode: 0644]
ebus/csma.c [new file with mode: 0644]
ebus/doorbell.c [new file with mode: 0644]
ebus/ebus.h [new file with mode: 0644]
ebus/hardware.c [new file with mode: 0644]
ebus/hardware.h [new file with mode: 0644]
ebus/housectl.c
ebus/hsd-date.c [new file with mode: 0644]
ebus/hsd-misc.h [new file with mode: 0644]
ebus/hsd-time.c [new file with mode: 0644]
ebus/hsd-time.h [new file with mode: 0644]
ebus/i2c-lcd.c [new file with mode: 0644]
ebus/i2c.c [new file with mode: 0644]
ebus/onewire.c [new file with mode: 0644]
ebus/proto-busctl.h [new file with mode: 0644]
ebus/proto-dbgmsg.h [new file with mode: 0644]
ebus/proto-h61.h [new file with mode: 0644]
ebus/protocol.h [new file with mode: 0644]
ebus/shutter.c
ebus/testnode.c [new file with mode: 0644]

diff --git a/ebus/.gitignore b/ebus/.gitignore
new file mode 100644 (file)
index 0000000..b8306a3
--- /dev/null
@@ -0,0 +1,8 @@
+revision.h
+housed
+housectl
+*.hex
+*.elf
+*.o
+
+ebusdump
index 65825e2..eabd0be 100644 (file)
@@ -27,12 +27,14 @@ HOSTCFLAGS = -Wall -Wno-pointer-sign -g -O2
 sources = ebus.h hardware.c hardware.h protocol.h csma.c \
          ebusdump.c testnode.c shutter.c proto-busctl.h proto-h61.h \
          proto-dbgmsg.h onewire.c i2c.c i2c-lcd.c \
-         housed.c housectl.c
+         housed.c housectl.c hsd-misc.h hsd-time.c hsd-time.h
 
 all: housed housectl testnode.hex shutter.hex doorbell.hex
 
 common_node_obj = hardware.o csma.o onewire.o
 
+common_hsd_obj = hsd-time.o
+
 .PHONY: FORCE
 
 FORCE:
@@ -62,6 +64,9 @@ onewire.o: hardware.h ebus.h
 i2c.o: hardware.h ebus.h
 i2c-lcd.o: hardware.h ebus.h
 
+hsd-time.o: hsd-time.h hsd-misc.h
+ebusctl.o: hsd-time.h hsd-misc.h
+
 testnode.elf : testnode.o $(common_node_obj)
        $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)
 
@@ -79,5 +84,5 @@ doorbell.elf : doorbell.o $(common_node_obj) i2c.o i2c-lcd.o
 housed : housed.c protocol.h proto-busctl.h proto-h61.h
        $(HOSTCC) $(HOSTCFLAGS) -o $@ housed.c
 
-housectl : housectl.c protocol.h proto-busctl.h proto-h61.h
-       $(HOSTCC) $(HOSTCFLAGS) -o $@ housectl.c
+housectl : housectl.c protocol.h proto-busctl.h proto-h61.h $(common_hsd_obj)
+       $(HOSTCC) $(HOSTCFLAGS) -o $@ housectl.c $(common_hsd_obj)
diff --git a/ebus/README b/ebus/README
new file mode 100644 (file)
index 0000000..c315945
--- /dev/null
@@ -0,0 +1,75 @@
+Running an CSMA/CD protocol on the Elektor Bus.              -*- org -*-
+
+* Installation instructions
+
+  Just run "make" to build the code.  To write the flash you use this:
+
+     avrdude -c usbasp -pm88 -U flash:w:PROGRAM.hex
+
+  Please make sure to set the FUSE bits correctly:
+
+     lfuse = 0xFF (16MHz crystal)
+     hfuse = 0xD7 (ie. set EESAVE))
+
+  you may do this by using these commands:
+
+    avrdude -c usbasp -pm88 -v -B 4 -U lfuse:w:0xFF:m
+    avrdude -c usbasp -pm88 -v -B 4 -U hfuse:w:0xD7:m
+
+  You also need to assign an different node-id to each node in the
+  same collision domain by storing them in the EEPROM.  Due to
+  restriction of RS485 protocol more than 32 nodes are not possible
+  with most modern drivers.  Do allow for easier switching we suggest
+  to use only nodes ids in the range 1..30.  If more buses will
+  eventually be attached the high bits may be used to identify the
+  bus.  Such a simple scheme allows easy routing between buses by
+  direct mapping of the node ids.
+
+  For example, setting the node-id 17 (0x11) may be done with:
+
+    avrdude -c usbasp -pm88 -U eeprom:w:0,0,0,17:m
+
+* Helper tools
+
+  The tool ebusdump.c may be used on a Unix hosts to display the
+  stats.  Assuming the RS-485 converter is attached to /dev/ttyUSB1
+  you would do this:
+
+    stty </dev/ttyUSB1 speed 9600 raw
+    ./ebusdump </dev/ttyUSB1 --top
+
+  Instead of of showing the stats page, ebusdump may also be used to
+  dump the activity by usingit without the --top option.
+
+  This code sends status information to allow analyzing the bus
+  behaviour under load.  The keys S2 and S3 are used to change the
+  send interval of those messages in discrete steps.  With 9600 baud
+  you will notice many collisions in particular if the interval has
+  been set down to 25 by pressing the S3 button 4 times; the interval
+  is shown in the ebusdump output.
+
+* The protocol
+
+  In contrast to the original Elektor Bus protocol, this version uses
+  implements a CSMA/CD protocol with an explicit framing similar to
+  PPP (RFC-1662).  The original 0xAA byte has been replaced by a
+  protocol ID to allow the use of several protocols on the same bus:
+
+    +-----------+--------+---------------------+---------+----------+
+    |SYNC(0x7e) | PROTID | PAYLOAD (15 octets) | CRC(msb)| CRC(lsb) |
+    +-----------+--------+---------------------+---------+----------+
+
+  Except for the SYNC byte all other octets are byte stuffed and
+  masked so that the sync byte value always indicates the start of a
+  frame.  The CRC is computed over PROTID and PAYLOAD after
+  de-stuffing.  In contrast to PPP we don't need a trailing flag to
+  indicate the end of the frame because we use a fixed length payload.
+  The CRC uses the polynom x^16+x^12+x^5+1 and an initial value of
+  0xffff (CRC-CCID).  The CRC is sent in network byte order.
+
+  For a description of the PROTID, see the file protocol.h.
+
+* Future work
+
+  - Add a framework to register actual applications.
+  - Use a simple send queue to allow receiving urgent data.
diff --git a/ebus/csma.c b/ebus/csma.c
new file mode 100644 (file)
index 0000000..fb2f153
--- /dev/null
@@ -0,0 +1,566 @@
+/* csma.c - CMSA code for an Elektor Bus Node using an ATmega88
+ * 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/>.
+ *
+ * 2011-06-01 wk  Initial code.
+ * 2011-08-22 wk  Changed to a CSMA/CD protocol
+ * 2011-09-05 wk  Modularized code.
+ */
+
+#include "hardware.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <util/delay.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/interrupt.h>
+#include <avr/eeprom.h>
+#include <avr/sleep.h>
+#include <util/crc16.h>
+
+#include "ebus.h"
+
+
+
+\f
+/* We use timer 0 to measure the time for one octet.  With 8n1 an
+   octet is represented by 10 bits on the wire which takes the time
+   Tc.
+
+   |   Baud |    Tc |    Tc |   Tc |
+   |        |  (ms) | c/256 | c/64 |
+   |--------+-------+-------+------|
+   |   9600 |  1.04 |    65 |      |
+   |  19200 |  0.52 |    33 |      |
+   |  38400 |  0.26 |       |   65 |
+   |  57600 |  0.17 |       |   43 |
+   |  76800 |  0.13 |       |   33 |
+   | 115200 | 0.087 |       |   22 |
+*/
+#if BAUD == 9600
+# define T_x_PSCALE 256
+# define T_c_CMPVAL  65
+#elif BAUD == 19200
+# define T_x_PSCALE 256
+# define T_c_CMPVAL  33
+#elif BAUD == 38400
+# define T_x_PSCALE  64
+# define T_c_CMPVAL  65
+#elif BAUD == 57600
+# define T_x_PSCALE  64
+# define T_c_CMPVAL  43
+#elif BAUD == 76800
+# define T_x_PSCALE  64
+# define T_c_CMPVAL  33
+#elif BAUD == 115200
+# define T_x_PSCALE  64
+# define T_c_CMPVAL  22
+#else
+# error Specified baud rate not supported
+#endif
+
+
+static volatile unsigned int frames_sent;
+static volatile unsigned int frames_received;
+static volatile unsigned int collision_count;
+static volatile unsigned int overflow_count;
+
+/* Flag indicating that a PCINT2 was triggered.  */
+volatile byte rx_pin_level_change;
+
+/* The buffer filled by an ISR with the message.  */
+static volatile byte rx_buffer[MSGSIZE];
+
+/* The buffer with the currently sent message.  We need to store it to
+   send retries.  It is also used by the receiver to detect collisions.  */
+static volatile byte tx_buffer[MSGSIZE];
+static volatile uint16_t tx_buffer_crc;
+
+/* Flag set if we do not want to receive but check our own sending for
+   collisions.  */
+static volatile byte check_sending;
+
+/* If true RX_BUFFER has a new message.  This flag is set by the ISR
+   and must be cleared by the main fucntion so that the ISR can
+   continue to work.  */
+static volatile byte rx_ready;
+
+
+
+\f
+
+/* Reset the gap timer (timer 0).  Note that this resets both
+   timers.  */
+static void
+reset_retry_timer (void)
+{
+  TCCR0B = 0x00;   /* Stop clock.  */
+  TIFR0  = _BV(OCF0A) | _BV(OCF0B);  /* Clear the flags.  */
+  TCNT0  = 0x00;   /* Clear timer/counter register.  */
+
+  /* Start the clock.  */
+#if T_x_PSCALE == 256
+  TCCR0B = 0x04;   /* Set prescaler to clk/256.  */
+#else
+  TCCR0B = 0x03;   /* Set prescaler to clk/64.  */
+#endif
+}
+
+
+static inline int
+t_c_reached_p (void)
+{
+  return !!(TIFR0 & _BV(OCF0A));
+}
+
+
+static void
+wait_t_c_reached (void)
+{
+  set_sleep_mode (SLEEP_MODE_IDLE);
+  while (!t_c_reached_p ())
+    {
+      cli();
+      if (!t_c_reached_p ())
+        {
+          sleep_enable ();
+          sei ();
+          sleep_cpu ();
+          sleep_disable ();
+        }
+      sei ();
+    }
+}
+
+
+/* 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 volatile byte *msg)
+{
+  int idx;
+  uint16_t crc = 0xffff;
+
+  for (idx=0; idx < MSGSIZE; idx++)
+    crc = _crc_ccitt_update (crc, msg[idx]);
+
+  return crc;
+}
+
+
+\f
+/*
+   Interrupt service routines
+ */
+
+
+/* UART tx complete interrupt service routine. */
+ISR (USART_TX_vect)
+{
+  /* Nothing to do.  */
+}
+
+
+/* UART receive complete interrupt service routine.  Note that we
+   always listen on the bus and thus also receive our own transmits.
+   This is useful so that we can easily detect collisions.  */
+ISR (USART_RX_vect)
+{
+  static byte sentinel;
+  static byte receiving;
+  static byte idx;
+  static byte escape;
+  static uint16_t crc;
+  byte c;
+
+  c = UDR0;
+
+  if (sentinel)
+    return;
+  sentinel = 1;
+  sei ();
+
+  if (c == FRAMESYNCBYTE)
+    {
+      if (receiving)
+        {
+          /* Received sync byte while receiving - either a collision
+             or the message was too short and the next frame started.  */
+          LED_Collision |= _BV(LED_Collision_BIT);
+          receiving = 0;
+          collision_count++;
+          if (check_sending)
+            check_sending = 2;
+        }
+      else
+        {
+          receiving = 1;
+          idx = escape = 0;
+        }
+    }
+  else if (!receiving)
+    ; /* No sync seen, thus skip this octet.  */
+  else if (rx_ready && !check_sending)
+    {
+      /* Overflow.  The previous message has not yet been processed.  */
+      receiving = 0;
+      overflow_count++;
+    }
+  else if (c == FRAMEESCBYTE && !escape)
+    escape = 1;
+  else
+    {
+      if (escape)
+        {
+          escape = 0;
+          c ^= FRAMEESCMASK;
+        }
+
+      if (check_sending)
+        {
+          if (idx < MSGSIZE)
+            {
+              if (tx_buffer[idx++] != c)
+                {
+                  LED_Collision |= _BV(LED_Collision_BIT);
+                  receiving = 0;
+                  collision_count++;
+                  check_sending = 2;  /* Tell the tx code.  */
+                  idx = 0;
+                }
+            }
+          else if (idx == MSGSIZE)
+            {
+              crc = c << 8;
+              idx++;
+            }
+          else /* idx == MSGSIZE + 1 */
+            {
+              crc |= c;
+              if (crc != tx_buffer_crc)
+                {
+                  LED_Collision |= _BV(LED_Collision_BIT);
+                  receiving = 0;
+                  collision_count++;
+                  check_sending = 2;  /* Tell the tx code.  */
+                  idx = 0;
+                }
+              else if (check_sending == 1)
+                {
+                  check_sending = 0; /* All checked. */
+                  receiving = 0;
+                }
+            }
+        }
+      else if (idx == 0 && (c & PROTOCOL_MSGLEN_MASK) != PROTOCOL_MSGLEN_16)
+        {
+          /* Protocol length mismatch - ignore this message.  */
+          /* Switch a lit collision LED off. */
+          LED_Collision &= ~_BV(LED_Collision_BIT);
+          /* Prepare for the next frame.  */
+          receiving = 0;
+        }
+      else if (idx < MSGSIZE)
+        rx_buffer[idx++] = c;
+      else if (idx == MSGSIZE)
+        {
+          crc = c << 8;
+          idx++;
+        }
+      else /* idx == MSGSIZE + 1 */
+        {
+          crc |= c;
+          if (crc != compute_crc (rx_buffer))
+            {
+              LED_Collision |= _BV(LED_Collision_BIT);
+              collision_count++;
+              if (check_sending)
+                check_sending = 2;
+            }
+          else
+            {
+              frames_received++;
+              /* Switch a lit collision LED off. */
+              LED_Collision &= ~_BV(LED_Collision_BIT);
+              /* Tell the mainloop that there is something to process.  */
+              rx_ready = 1;
+              wakeup_main = 1;
+            }
+          /* Prepare for the next frame.  */
+          receiving = 0;
+        }
+    }
+
+  sentinel = 0;
+}
+
+
+/* Pin Change Interrupt Request 2 handler.  */
+ISR (PCINT2_vect)
+{
+  rx_pin_level_change = 1;
+  reset_retry_timer ();
+  rx_pin_level_change = 0;
+}
+
+
+/* Send out the raw byte C.  */
+static void
+send_byte_raw (const byte c)
+{
+  /* Wait until transmit buffer is empty.  */
+  while (!bit_is_set (UCSR0A, UDRE0))
+    ;
+  /* Send the byte.  */
+  UDR0 = c;
+}
+
+
+/* Send byte C with byte stuffing.  */
+static void
+send_byte (const byte c)
+{
+  if (c == FRAMESYNCBYTE || c == FRAMEESCBYTE)
+    {
+      send_byte_raw (FRAMEESCBYTE);
+      send_byte_raw ((c ^ FRAMEESCMASK));
+    }
+  else
+    send_byte_raw (c);
+}
+
+
+static int
+bus_idle_p (void)
+{
+  rx_pin_level_change = 0;
+
+  reset_retry_timer ();
+
+  PCMSK2  = _BV(PCINT16);  /* We only want to use this pin.  */
+  PCICR  |= _BV(PCIE2);
+
+  wait_t_c_reached ();
+
+  PCMSK2 &= ~_BV(PCINT16);
+  PCICR  &= ~_BV(PCIE2);
+
+  return !rx_pin_level_change;
+}
+
+
+static void
+wait_bus_idle (void)
+{
+  while (!bus_idle_p ())
+    ;
+}
+
+
+/* Send a message.  This function does the framing and and retries
+   until the message has been sent.  */
+void
+csma_send_message (const byte *data, byte datalen)
+{
+  byte idx;
+  byte resend = 0;
+  byte backoff = 0;
+
+  memcpy ((byte*)tx_buffer, data, datalen);
+  if (datalen < MSGSIZE)
+    memset ((byte*)tx_buffer+datalen, 0, MSGSIZE - datalen);
+
+  tx_buffer_crc = compute_crc (tx_buffer);
+
+  do
+    {
+      if (resend)
+        {
+          int nloops;
+
+          /* Exponential backoff up to 32.  For the first two resents
+             we randomly use one or two wait loops; the 3rd and 4th
+             time 1 to 4, then 1 to 8, then 1 to 16 and finally stick
+             to 1 to 32. */
+          if (backoff < 8)
+            backoff++;
+
+          do
+            {
+              wait_bus_idle ();
+              nloops = (rand () % (1 << backoff/2)) + 1;
+
+              while (nloops--)
+                {
+                  reset_retry_timer ();
+                  wait_t_c_reached ();
+                }
+            }
+          while (!bus_idle_p ());
+
+          /* Switch a lit collision LED off.  We do this here to give
+             a feedback on the used delay.  */
+          LED_Collision &= ~_BV(LED_Collision_BIT);
+
+          resend = 0;
+        }
+      else
+        wait_bus_idle ();
+
+      check_sending = 1;
+
+      /* Switch TX LED on.  */
+      LED_Transmit |= _BV(LED_Transmit_BIT);
+
+      /* Before sending we need to clear the TXC bit by setting it.  */
+      UCSR0A |= _BV(TXC0);
+
+      /* Enable the LT1785 driver output (DE).  */
+      PORTD |= _BV(2);
+
+      send_byte_raw (FRAMESYNCBYTE);
+      for (idx=0; idx < MSGSIZE; idx++)
+        {
+          send_byte (tx_buffer[idx]);
+          if (check_sending == 2)
+            {
+              /* Collision detected - stop sending as soon as
+                 possible.  We even disable the driver output right
+                 now so that we won't clobber the bus any further with
+                 data in the tx queue.  */
+              PORTD &= ~_BV(2);  /* Disable LT1785 driver output.  */
+              resend++;
+              break;
+            }
+        }
+
+      if (!resend)
+        {
+          send_byte ((tx_buffer_crc >> 8));
+          send_byte (tx_buffer_crc);
+        }
+
+      /* Wait until transmit is complete.  */
+      while (!bit_is_set (UCSR0A, TXC0))
+        ;
+      UCSR0A |= _BV(TXC0);
+
+      /* Now disable the LT1785 driver output (DE).  It is important
+         to do that as soon as possible.  */
+      PORTD &= ~_BV(2);
+
+      /* Wait until the receiver received and checked all our octets.  */
+      if (check_sending == 1)
+        {
+          byte tccount = 0;
+
+          reset_retry_timer ();
+          while (check_sending == 1)
+            {
+              set_sleep_mode (SLEEP_MODE_IDLE);
+              cli();
+              if (t_c_reached_p ())
+                {
+                  if (++tccount > 3)
+                    {
+                      /* Nothing received for 4*Tc - assume receiver
+                         is clogged due to collisions.  */
+                      check_sending = 2;
+                    }
+                  else
+                    reset_retry_timer ();
+                }
+              else if (check_sending == 1)
+                {
+                  sleep_enable ();
+                  sei ();
+                  sleep_cpu ();
+                  sleep_disable ();
+                }
+              sei ();
+            }
+        }
+      if (check_sending == 2)
+        resend++;
+
+      check_sending = 0;
+
+      /* Switch TX LED off.  */
+      LED_Transmit &= ~_BV(LED_Transmit_BIT);
+
+    }
+  while (resend);
+
+  /* Switch a lit collision LED off. */
+  LED_Collision &= ~_BV(LED_Collision_BIT);
+
+  frames_sent++;
+}
+
+
+/* Return a message or NULL if none available.  The caller must call
+   csma_message_done after a message has been processed.  */
+byte *
+csma_get_message (void)
+{
+  if (rx_ready)
+    return (byte*)rx_buffer;
+  return NULL;
+}
+
+
+void
+csma_message_done (void)
+{
+  rx_ready = 0;
+}
+
+
+unsigned int
+csma_get_stats (int what)
+{
+  switch (what)
+    {
+    case 1: return frames_received;
+    case 2: return frames_sent;
+    case 3: return collision_count;
+    case 4: return overflow_count;
+    default: return 0;
+    }
+}
+
+
+
+/* Initialize the CSMA code.  This must be done after the
+   initialization of the general hardware code.  */
+void
+csma_setup (void)
+{
+  /* Timer 0: We use this timer to measure the time for one octet on
+     the wire.  */
+  TCCR0A = 0x00;   /* Select normal mode.  */
+#if T_x_PSCALE == 256
+  TCCR0B = 0x04;   /* Set prescaler to clk/256.  */
+#else
+  TCCR0B = 0x03;   /* Set prescaler to clk/64.  */
+#endif
+  TIMSK0 = 0x00;   /* No interrupts so that we need to clear the TIFR0
+                      flags ourself.  */
+  OCR0A  = T_c_CMPVAL; /* Use this for Tc.  */
+  OCR0B  = 0;
+}
diff --git a/ebus/doorbell.c b/ebus/doorbell.c
new file mode 100644 (file)
index 0000000..8d44917
--- /dev/null
@@ -0,0 +1,191 @@
+/* doorbell.c - Elektor Bus node to control a a doorbell
+ * 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 doorbell and to display the
+   current time and temperature.  A PCF8574 I2C bus expander is used
+   to drive an ST7036 controlled LCD display in 4 bit mode.  */
+
+#include "hardware.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <util/delay.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/sleep.h>
+
+
+#include "ebus.h"
+#include "proto-busctl.h"
+#include "proto-h61.h"
+
+
+/* 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)
+{
+  /* Every 10 seconds send out a temperature reading.  */
+  if (!clock)
+    {
+      wakeup_main = 1;
+    }
+}
+
+
+/* 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))
+    {
+    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:
+      /* Fixme: Implement */
+      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;
+      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;
+    }
+}
+
+
+/*
+    Entry point
+ */
+int
+main (void)
+{
+  byte *msg;
+
+  hardware_setup (NODETYPE_DOORBELL);
+
+  csma_setup ();
+  onewire_setup ();
+  i2c_setup ();
+  lcd_setup ();
+
+  sei (); /* Enable interrupts.  */
+
+  lcd_init ();
+  for (;;)
+    {
+      for (;;)
+        {
+          if (read_key_s2 ())
+            lcd_backlight (1);
+          else if (read_key_s3 ())
+            lcd_backlight (0);
+          lcd_home ();
+          lcd_putc ('a');
+          LED_Collision |= _BV(LED_Collision_BIT);
+          _delay_ms (100);
+          LED_Collision &= ~_BV(LED_Collision_BIT);
+          _delay_ms (100);
+          lcd_puts_P ("Hello World!");
+          LED_Collision |= _BV(LED_Collision_BIT);
+          _delay_ms (100);
+          LED_Collision &= ~_BV(LED_Collision_BIT);
+        }
+
+      set_sleep_mode (SLEEP_MODE_IDLE);
+      while (!wakeup_main)
+        {
+          cli();
+          if (!wakeup_main)
+            {
+              sleep_enable ();
+              sei ();
+              sleep_cpu ();
+              sleep_disable ();
+            }
+          sei ();
+        }
+      wakeup_main = 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 ();
+        }
+
+    }
+
+}
diff --git a/ebus/ebus.h b/ebus/ebus.h
new file mode 100644 (file)
index 0000000..a1417ae
--- /dev/null
@@ -0,0 +1,99 @@
+/* ebus.h - Global definitions for an Elektor Bus Node
+ * 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/>.
+ */
+
+#ifndef EBUS_H
+#define EBUS_H
+
+#include <avr/pgmspace.h>
+#include "protocol.h"
+#include "revision.h"
+
+
+/* Typedefs.  */
+typedef unsigned char byte;
+
+
+/* We only support 16 byte long messages.  */
+#define MSGSIZE 16
+
+
+/* For fast access we copy some of the config data into the RAM.  */
+struct
+{
+  byte nodeid_hi;
+  byte nodeid_lo;
+} config;
+
+
+/* Set to one (e.g. the timer int) to wakeup the main loop.  */
+volatile char wakeup_main;
+
+
+/*-- hardware.c --*/
+void hardware_setup (byte nodetype);
+byte read_key_s2 (void);
+byte read_key_s3 (void);
+uint16_t get_current_time (void);
+uint16_t get_current_fulltime (byte *r_deci);
+void set_current_fulltime (uint16_t tim, byte deci);
+
+
+/*-- csma.c --*/
+void csma_setup (void);
+unsigned int csma_get_stats (int what);
+void csma_send_message (const byte *data, byte datalen);
+byte *csma_get_message (void);
+void csma_message_done (void);
+
+/*-- onewire.c --*/
+void onewire_setup (void);
+void onewire_enable (void);
+void onewire_disable (void);
+void onewire_write_byte (uint8_t c);
+uint8_t onewire_read_byte (void);
+void onewire_wait_for_one (void);
+
+/*-- i2c.c --*/
+void i2c_setup (void);
+byte i2c_start_mt (byte address);
+byte i2c_send_byte (byte value);
+byte i2c_send_data (byte *data, byte datalen);
+void i2c_stop (void);
+
+
+/*-- i2c-lcd.c --*/
+void lcd_setup (void);
+void lcd_init (void);
+void lcd_backlight (uint8_t onoff);
+void lcd_putc (uint8_t c);
+void lcd_clear (void);
+void lcd_home (void);
+void lcd_gotoxy (uint8_t x, uint8_t y);
+void lcd_puts (const char *s);
+void _lcd_puts_P (const char *progmem_s);
+#define lcd_puts_P(s)  _lcd_puts_P (PSTR ((s)))
+
+
+/*-- Callbacks to be implemented by each node  --*/
+void ticker_bottom (unsigned int clock);  /* Called by hardware.c. */
+
+
+/* Helper macros.  */
+#define DIM(v)              (sizeof(v)/sizeof((v)[0]))
+
+
+#endif /*EBUS_H*/
diff --git a/ebus/hardware.c b/ebus/hardware.c
new file mode 100644 (file)
index 0000000..5ea26c3
--- /dev/null
@@ -0,0 +1,220 @@
+/* hardware.c - Hardware related code for an Elektor Bus Node
+ * 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/>.
+ */
+
+#include "hardware.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <util/delay.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/interrupt.h>
+#include <avr/eeprom.h>
+#include <avr/sleep.h>
+#include <util/crc16.h>
+
+#include "ebus.h"
+
+/* UART defs.  */
+#define UBRR_VAL   ((F_CPU+8*BAUD)/(16*BAUD)-1)
+#define BAUD_REAL  (F_CPU/(16*(UBRR_VAL+1)))
+#define BAUD_ERROR ((1000*BAUD_REAL)/BAUD)
+#if (BAUD_ERROR < 990 || BAUD_ERROR > 1010)
+# error computed baud rate out of range
+#endif
+
+
+
+\f
+/* EEPROM layout.  We keep configuration data at the start of the
+   eeprom but copy it on startup into the RAM for easier access.  */
+struct __attribute__ ((packed)) ee_config_s
+{
+  uint16_t  reserved;
+  byte      nodeid_hi;
+  byte      nodeid_lo;
+  byte      reserved2[12];
+};
+
+struct ee_config_s ee_config EEMEM = {0};
+struct ee_data_s ee_data EEMEM = {NODETYPE_UNDEFINED, 0, {{0}}};
+
+/* End EEPROM.  */
+
+
+/* The current time measured in quantities of 10 seconds with
+   explicit roll over after 7 days.  */
+static volatile uint16_t current_time;
+
+/* Milliseconds in the 10 second period.  */
+static volatile uint16_t current_clock;
+
+
+
+\f
+/* Read key S2.  Return true once at the first debounced leading edge
+   of the depressed key.  For correct operation this function needs to
+   be called at fixed intervals. */
+byte
+read_key_s2 (void)
+{
+  static uint16_t state;
+
+  state <<= 1;
+  state |= !KEY_S2;
+  state |= 0xf800; /* To work on 10 continuous depressed readings.  */
+  return (state == 0xfc00); /* Only the transition shall return true.  */
+}
+
+byte
+read_key_s3 (void)
+{
+  static uint16_t state;
+
+  state <<= 1;
+  state |= !KEY_S3;
+  state |= 0xf800; /* To work on 10 continuous depressed readings.  */
+  return (state == 0xfc00); /* Only the transition shall return true.  */
+}
+
+
+/* Return the current time.  Time is measured as the count of 10
+   second periods passed in a 7 day period.  */
+uint16_t
+get_current_time (void)
+{
+  return current_time;
+}
+
+
+uint16_t
+get_current_fulltime (byte *r_deci)
+{
+  uint16_t hi, lo;
+
+  cli ();
+  hi = current_time;
+  lo = current_clock;
+  sei ();
+  *r_deci = lo/100;
+  return hi;
+}
+
+
+void
+set_current_fulltime (uint16_t tim, byte deci)
+{
+  cli ();
+  current_time = tim;
+  current_clock = deci * 100;
+  sei ();
+}
+
+
+\f
+/*
+   Interrupt service routines
+ */
+
+
+/* 1ms ticker interrupt service routine. */
+ISR (TIMER2_COMPA_vect)
+{
+  uint16_t clockval;
+
+  current_clock++;
+
+  if (current_clock >= 10000)
+    {
+      /* 10 seconds passed.  Bump the current time.  */
+      current_time++;
+      if (current_time == (uint16_t)1440 * 6 * 7 )
+        current_time = 0; /* Weekly roll-over.  */
+      current_clock = 0;
+    }
+
+  clockval = current_clock;
+  ticker_bottom (clockval);
+}
+
+
+/* Setup for some parts of the hardware.  The caller needs to pass the
+   node type so that the EEPROM will be erased if it does not match
+   the node type. */
+void
+hardware_setup (byte nodetype)
+{
+  byte value;
+
+  /* Port D configuration:
+     PIND.7 = Inp: KEY_S3
+     PIND.6 = Out: LED_Transmit
+     PIND.5 = Inp: KEY_S2
+     PIND.4 = Out: LED_Collision
+     PIND.3 = Out: LT1785-pin2 = !RE
+     PIND.2 = Out: LT1785-pin3 = DE
+     PIND.1 = TXD: LT1785-pin4 = DI
+     PIND.0 = RXD: LT1785-pin1 = RO
+  */
+  PORTD = _BV(7) | _BV(5);  /* Enable pull-ups.  */
+  DDRD  = (_BV(6) | _BV(4) | _BV(3) | _BV(2) | _BV(1));
+
+#if (KEY_S3_BIT != 7 || KEY_S2_BIT != 5                 \
+     || LED_Transmit_BIT != 6 || LED_Collision_BIT != 4)
+# error mismatch with hardware.h
+#endif
+
+  /* Set the UART: 8n1, async, rx and tx on, rx int enabled.
+     Baud rate set to the value computed from BAUD.  */
+  UCSR0A = 0x00;
+  UCSR0B = _BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0);
+  UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
+  UBRR0H = (UBRR_VAL >> 8) & 0x0f;
+  UBRR0L = (UBRR_VAL & 0xff);
+
+  /* Timer 2: 1ms ticker.  */
+  TCCR2A = 0x02;   /* Select CTC mode.  */
+  TCCR2B = 0x04;   /* Set prescaler to 64.  */
+  TCNT2 = 0x00;    /* Clear timer/counter register.  */
+  OCR2A  = 249;    /* Compare value for 1ms.  Note: This is one less
+                      than the naively computed value 250.  */
+  TIMSK2 = 0x02;   /* Set OCIE2A.  */
+
+  /* Copy some configuration data into the RAM.  */
+  config.nodeid_hi = eeprom_read_byte (&ee_config.nodeid_hi);
+  config.nodeid_lo = eeprom_read_byte (&ee_config.nodeid_lo);
+
+
+  /* Lacking any better way to seed rand we use the node id.  A better
+     way would be to use the low bit of an ADC to gather some entropy.
+     However the node id is supposed to be unique and should thus be
+     sufficient.  */
+  srand (config.nodeid_lo);
+
+  /* Clear the node specific eeprom if the node type changed.  */
+  value = eeprom_read_byte (&ee_data.nodetype);
+  if (value != nodetype)
+    {
+      int i;
+      uint32_t dw = 0;
+
+      for (i=0; i < sizeof ee_data; i += sizeof dw)
+        eeprom_write_dword ((uint32_t*)(&ee_data.nodetype+i), dw);
+      eeprom_write_byte (&ee_data.nodetype, nodetype);
+    }
+
+}
diff --git a/ebus/hardware.h b/ebus/hardware.h
new file mode 100644 (file)
index 0000000..ed74647
--- /dev/null
@@ -0,0 +1,96 @@
+/* hardware.h - Hardware definitons for an Elektor Bus Node
+ * 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/>.
+ */
+
+#ifndef HARDWARE_H
+#define HARDWARE_H
+
+#include <stdint.h>
+
+/* UART defs.  */
+#define BAUD      19200ul
+
+/* Clock frequency in Hz. */
+#define F_CPU 16000000UL
+
+/* Key and LED definitions.  */
+#define KEY_S2_PIN        (PIND)
+#define KEY_S2_BIT        (PIND5)
+#define KEY_S3_PIN        (PIND)
+#define KEY_S3_BIT        (PIND7)
+#define LED_Collision     (PORTD)
+#define LED_Collision_BIT (4)
+#define LED_Transmit      (PORTD)
+#define LED_Transmit_BIT  (6)
+
+#define KEY_S2    bit_is_set (KEY_S2_PIN, KEY_S2_BIT)
+#define KEY_S3    bit_is_set (KEY_S3_PIN, KEY_S3_BIT)
+
+/* Plugin hardware definitions.  */
+#define OW_Bus_DDR   (DDRC)     /* The 1-Wire Bus.  */
+#define OW_Bus_PORT  (PORTC)
+#define OW_Bus_PIN   (PINC)
+#define OW_Bus_BIT   (0)
+
+
+/* Node type values */
+#define NODETYPE_UNDEFINED  0
+#define NODETYPE_SHUTTER    1
+#define NODETYPE_DOORBELL   2
+
+
+/* EEPROM layout for all node types.  */
+struct __attribute__ ((packed)) ee_data_s
+{
+  uint8_t nodetype;
+  uint8_t reserved;
+  union
+  {
+    uint8_t raw[110];
+
+    struct __attribute__ ((packed))
+    {
+      /* We may store up to 16 up/down actions in the schedule table.
+         An entry with value zero indicated the end of the table.  The
+         granularity is 1 minute with the 10-seconds values used to
+         indicate the action:
+           minute + 0  := no action
+           minute + 10 := pull-up
+           minute + 20 := rfu
+           minute + 30 := rfu
+           minute + 40 := rfu
+           minute + 50 := pull-down
+      */
+      uint16_t schedule[16];
+    } shutterctl;
+
+    struct __attribute__ ((packed))
+    {
+      unsigned char foo;
+    } doorbell;
+  } u;
+};
+
+extern struct ee_data_s ee_data;
+
+
+/* In particular F_CPU is required for other system headers to work
+   correctly; thus do a simple check first.  */
+#if defined(_STDLIB_H_) || defined(_UTIL_DELAY_H_)
+# error file not included prior to other header files
+#endif
+
+#endif /*HARDWARE_H*/
index 1262f2f..a073011 100644 (file)
@@ -32,6 +32,9 @@
 #include "proto-busctl.h"
 #include "proto-h61.h"
 
+#include "hsd-misc.h"
+#include "hsd-time.h"
+
 
 #define PGM           "housectl"
 #define PGM_VERSION   "0.0"
@@ -626,6 +629,7 @@ main (int argc, char **argv )
              "query-version\n"
              "query-shutter-state\n"
              "query-shutter-schedule\n"
+             "set-shutter-schedule SLOT TIMESPEC\n"
              "reset-shutter-eeprom\n"
              "drive-shutter up|down\n"
              ,stdout);
@@ -640,6 +644,8 @@ main (int argc, char **argv )
     cmd_query_shutter_state (fp);
   else if (!strcmp (cmd, "query-shutter-schedule"))
     cmd_query_shutter_schedule (fp);
+  else if (!strcmp (cmd, "set-shutter-schedule"))
+    cmd_set_shutter_schedule (fp);
   else if (!strcmp (cmd, "reset-shutter-eeprom"))
     cmd_reset_shutter_eeprom (fp);
   else if (!strcmp (cmd, "drive-shutter"))
diff --git a/ebus/hsd-date.c b/ebus/hsd-date.c
new file mode 100644 (file)
index 0000000..7643035
--- /dev/null
@@ -0,0 +1,50 @@
+/* hsd-date.c - Date functions for housed and housectl
+ * 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 <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <time.h>
+
+
+/* Take a time string and convert it into an ebus style time.  Ebus
+   time is the number of 10 second intervals since Monday 0:00 local
+   time. (uint16_t)(-1) is returned on error.
+
+   Supported formats are:
+
+     hh:mm:sx    - Hour, minute and 10 seconds on Monday
+     w hh:mm:sx  - Ditto with weekday specified
+     ndhh:mm:sx  - Ditto with weekday give as Monday (0) to  Sunday (6)
+
+   with
+
+     hh = Hour
+     mm = Minutes
+     sx = Seconds with the lower digit ignored
+     w  = Weekday name (mo,tu,we,th,fr,sa,su)
+                    or (mon,tue,wed,thu,fri,sat,sun)
+          in any capitalization.
+ */
+uint16_t
+timestr_to_ebustime (const char *string)
+{
+
+}
diff --git a/ebus/hsd-misc.h b/ebus/hsd-misc.h
new file mode 100644 (file)
index 0000000..a7ab484
--- /dev/null
@@ -0,0 +1,51 @@
+/* hsd-misc.c - Miscellaneous macros for housed.c and housectl.c
+ * 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/>.
+ */
+
+#ifndef HSD_MISC_H
+#define HSD_MISC_H
+
+static inline int
+ascii_isspace (int a)
+{
+  switch (a)
+    {
+    case ' ': case '\n': case '\r':
+    case '\t': case '\f': case '\v':
+      return 1;
+    default:
+      return 0;
+    }
+}
+
+/*-- Macros to replace ctype ones to avoid locale problems. --*/
+#define spacep(p)   (*(p) == ' ' || *(p) == '\t')
+#define digitp(p)   (*(p) >= '0' && *(p) <= '9')
+#define hexdigitp(a) (digitp (a)                     \
+                      || (*(a) >= 'A' && *(a) <= 'F')  \
+                      || (*(a) >= 'a' && *(a) <= 'f'))
+
+
+/* The atoi macros assume that the buffer has only valid digits. */
+#define atoi_1(p)   (*(p) - '0' )
+#define atoi_2(p)   ((atoi_1(p) * 10) + atoi_1((p)+1))
+#define atoi_4(p)   ((atoi_2(p) * 100) + atoi_2((p)+2))
+#define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
+                     *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
+#define xtoi_4(p)   ((xtoi_2(p) * 256) + xtoi_2((p)+2))
+
+#endif /*HSD_MISC_H*/
diff --git a/ebus/hsd-time.c b/ebus/hsd-time.c
new file mode 100644 (file)
index 0000000..7d17acb
--- /dev/null
@@ -0,0 +1,54 @@
+/* hsd-time.c - Timee functions for housed and housectl
+ * 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/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <time.h>
+
+#include "hsd-time.h"
+
+/* Take a time string and convert it into an ebus style time.  Ebus
+   time is the number of 10 second intervals since Monday 0:00 local
+   time. (uint16_t)(-1) is returned on error.
+
+   Supported formats are:
+
+     hh:mm:sx    - Hour, minute and 10 seconds on Monday
+     w hh:mm:sx  - Ditto with weekday specified
+     ndhh:mm:sx  - Ditto with weekday give as Monday (0) to  Sunday (6)
+
+   with
+
+     hh = Hour
+     mm = Minutes
+     sx = Seconds with the lower digit ignored
+     w  = Weekday name (mo,tu,we,th,fr,sa,su)
+                    or (mon,tue,wed,thu,fri,sat,sun)
+          in any capitalization.
+ */
+uint16_t
+timestr_to_ebustime (const char *string)
+{
+
+
+
+
+}
diff --git a/ebus/hsd-time.h b/ebus/hsd-time.h
new file mode 100644 (file)
index 0000000..12489bd
--- /dev/null
@@ -0,0 +1,24 @@
+/* hsd-time.c - Time functions for housed and housectl
+ * 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/>.
+ */
+
+#ifndef HSD_TIME_H
+#define HSD_TIME_H
+
+uint16_t timestr_to_ebustime (const char *string);
+
+
+#endif /*HSD_TIME_H*/
diff --git a/ebus/i2c-lcd.c b/ebus/i2c-lcd.c
new file mode 100644 (file)
index 0000000..5c10993
--- /dev/null
@@ -0,0 +1,258 @@
+/* i2c-lcd.c - LCD driver for an PCF8574 attached LCD
+ * 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 mode is used to drive an ST7036 controlled LCD display via a
+   PCF8574 I2C bus expander.  The port assignments are:
+
+   | 8574 | ST7036     |
+   |------+------------|
+   | P0   | DB4 (31)   |
+   | P1   | DB5 (30)   |
+   | P2   | DB6 (29)   |
+   | P3   | DB7 (28)   |
+   | P4   | E   (36)   |
+   | P5   | RS  (39)   |
+   | P6   | !Backlight |
+   |------+------------|
+
+   The 8574 is wired to I2C address 0x40.  The backlight is connected
+   to an NPN transistor with the base pulled up by an 4k7 resistor and
+   pulled low by writing a one to P6.
+ */
+
+#include "hardware.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <util/delay.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/interrupt.h>
+
+#include "ebus.h"
+
+
+/* Display definitions.  */
+#define LCD_ADDRESS       0x40  /* Address of the PCF8574.  */
+
+#define LCD_E_BIT         0x10
+#define LCD_RS_BIT        0x20
+#define LCD_BACKLIGHT_BIT 0x40  /* Switch backlight off.  */
+
+
+/* The status of the backlight. */
+static byte backlight_off;
+
+
+/* Send a byte to the 8574.  */
+static void
+_lcd_send_byte (uint8_t value)
+{
+  /* The LCD copies the data on the falling edge of the E pin.  Thus
+     we send the byte with the bit for the E pin set and then again
+     with the E bit cleared.  We don't need to use a delay because the
+     minumum required E pulse time is 350ns and due to the 100kHz I2C
+     clock we need 90us to send one byte to the PCF8574.n  */
+  if (backlight_off)
+    value |= LCD_BACKLIGHT_BIT;
+  else
+    value &= ~LCD_BACKLIGHT_BIT;
+  i2c_send_byte (value | LCD_E_BIT);
+  i2c_send_byte (value & ~LCD_E_BIT);
+}
+
+void
+lcd_backlight (uint8_t onoff)
+{
+  backlight_off = !onoff;
+  i2c_start_mt (LCD_ADDRESS);
+  if (backlight_off)
+    i2c_send_byte (LCD_BACKLIGHT_BIT);
+  else
+    i2c_send_byte (0);
+  i2c_stop ();
+}
+
+
+/* Write high nibble of DATA.  */
+static void
+_lcd_write_high (uint8_t data, uint8_t write_ctrl)
+{
+  data >>= 4;
+  if (write_ctrl)
+    data &= ~LCD_RS_BIT;  /* Clear RS.  */
+  else
+    data |= LCD_RS_BIT;   /* Set RS.  */
+  _lcd_send_byte (data);
+
+}
+
+/* Write low nibble of DATA.  */
+static void
+_lcd_write_low (uint8_t data, uint8_t write_ctrl)
+{
+  data &= 0x0f;
+  if (write_ctrl)
+    data &= ~LCD_RS_BIT;  /* Clear RS.  */
+  else
+    data |= LCD_RS_BIT;   /* Set RS.  */
+  _lcd_send_byte (data);
+}
+
+
+static void
+_lcd_write (uint8_t data, uint8_t write_ctrl)
+{
+  _lcd_write_high (data, write_ctrl);
+  _lcd_write_low (data, write_ctrl);
+}
+
+
+/* Write a data byte to the display.  */
+void
+lcd_putc (uint8_t c)
+{
+  i2c_start_mt (LCD_ADDRESS);
+  _lcd_write (c, 0);
+  i2c_stop ();
+}
+
+
+/* Clear the display.  */
+void
+lcd_clear (void)
+{
+  i2c_start_mt (LCD_ADDRESS);
+  _lcd_write (0x01, 1);
+  i2c_stop ();
+}
+
+
+/* Got to the home position.  */
+void
+lcd_home (void)
+{
+  i2c_start_mt (LCD_ADDRESS);
+  _lcd_write (0x02, 1);
+  i2c_stop ();
+}
+
+
+/* Set the next data write position to X,Y.  */
+void
+lcd_gotoxy (uint8_t x, uint8_t y)
+{
+  i2c_start_mt (LCD_ADDRESS);
+  _lcd_write (0x80 | ((y? 0x40:0) + x), 1);
+  i2c_stop ();
+}
+
+
+/* uint8_t */
+/* lcd_getc (void) */
+/* { */
+/*   _lcd_waitbusy (); */
+/*   return _lcd_read (0); */
+/* } */
+
+void
+lcd_puts (const char *s)
+{
+  uint8_t c;
+
+  i2c_start_mt (LCD_ADDRESS);
+  while ((c = *s++))
+    _lcd_write (c, 0);
+  i2c_stop ();
+}
+
+
+void
+_lcd_puts_P (const char *progmem_s)
+{
+  uint8_t c;
+
+  i2c_start_mt (LCD_ADDRESS);
+  while ((c = pgm_read_byte (progmem_s++)))
+    _lcd_write (c, 0);
+  i2c_stop ();
+}
+
+
+
+/* Initialize the LCD code.  This must be run with interrupts enabled.  */
+void
+lcd_init (void)
+{
+  /* Wait for the internal reset to complete.  */
+  _delay_ms (40);
+
+  i2c_start_mt (LCD_ADDRESS);
+
+  /* Call Function_set three times with a delay of at least 1.6ms and
+     26.4us.  Because we need 90us to send the command to the PCF8574
+     an explicit delay of the latter value is not required.  */
+  _lcd_write_high (0x30, 1);
+  _delay_ms (2);
+  _lcd_write_high (0x30, 1);
+  _lcd_write_high (0x30, 1);
+
+  LED_Transmit |= _BV(LED_Transmit_BIT);
+  /* Now call Function_set to select 4 bit mode.  */
+  _lcd_write_high (0x20, 1);
+
+  /* Do the actual Function_set: 4 bit, 2 lines, table 1. */
+  _lcd_write (0x29, 1);
+
+  /* Bias_Set: 1/4, 3 lines.  */
+  _lcd_write (0x1d, 1);
+
+  /* Power control: icon display off, booster off.  */
+  _lcd_write (0x50, 1);
+
+  /* Follower control: on, ratio=4  */
+  _lcd_write (0x6c, 1);
+
+  /* Set contrast.  */
+  _lcd_write (0x77, 1);
+
+  /* Switch back to instruction table 0.  */
+  _lcd_write (0x28, 1);
+
+  /* Display on/off: Display on, cursor on, blinking on.  */
+  /*                 (bit 2)     (bit 1)     (bit 0)        */
+  _lcd_write (0x0f, 1);
+
+  /* Clear display.  */
+  _lcd_write (0x01, 1);
+
+  /* Entry mode: auto-increment, shift off.  */
+  _lcd_write (0x06, 1);
+
+  i2c_stop ();
+}
+
+
+/* Initialize the LCD code.  This must be done after the
+   initialization of the general hardware code and expects that the
+   I2C code has been initialized.  Note that lcd_init needs to be run
+   after this function.  */
+void
+lcd_setup (void)
+{
+}
diff --git a/ebus/i2c.c b/ebus/i2c.c
new file mode 100644 (file)
index 0000000..173ba48
--- /dev/null
@@ -0,0 +1,215 @@
+/* i2c.c - I2C implementation for AVR
+ * 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/>.
+ */
+
+/*
+   Note that Atmel's term for I2C is TWI which stands for Two-Wire
+   interface.
+ */
+
+#include "hardware.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <util/delay.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/interrupt.h>
+
+#include "ebus.h"
+
+/* Send the START condition.  Returns 0 on success.  */
+static byte
+send_start (void)
+{
+ retry:
+  /* Clear the interrupt flag, set send_staart and enable TWI operation.  */
+  TWCR = _BV (TWINT) | _BV (TWSTA) | _BV (TWEN);
+  /* Wait for completion.  */
+  while (!(TWCR & _BV (TWINT)))
+    ;
+  /* Check the status value.  */
+  switch (TWSR & 0xF8)
+    {
+    case 0x08:  /* Start condition has been transmitted.  */
+    case 0x10:  /* Re-start condition has been transmitted.  */
+      break;
+    case 0x38:  /* Arbitration lost.  */
+      goto retry;
+    default:    /* Error or unexpected status code.  */
+      return 1;
+    }
+  return 0;
+}
+
+
+/* Send the STOP condition.  */
+static void
+send_stop (void)
+{
+  /* Clear the interrupt flag, set send_stop and enable TWI operation.  */
+  TWCR = _BV (TWINT) | _BV (TWSTO) | _BV (TWEN);
+  /* The MCU won't set TWINT after after sending the stop; thus we may
+     not wait for it.  */
+}
+
+
+/* Send the slave ADDRESS.  Returns 0 on success, 1 on general error,
+   2 to request a restart.  */
+static byte
+send_sla (byte address)
+{
+  /* Load address into the data register.  */
+  TWDR = address;
+  /* Clear the interrupt flag and enable TWI operation.  */
+  TWCR = _BV (TWINT) | _BV (TWEN);
+  /* Wait for completion.  */
+  while (!(TWCR & _BV (TWINT)))
+    ;
+  /* Check the status value.  */
+  switch (TWSR & 0xF8)
+    {
+    case 0x18:  /* SLA+W has been transmitted.  */
+      break;
+    case 0x20:  /* NACK received.  */
+      LED_Collision |= _BV(LED_Collision_BIT);
+      return 2;
+    case 0x38:  /* Arbitration lost.  */
+      return 2;
+    default:    /* Unexpected status code.  */
+      return 1;
+    }
+  return 0;
+}
+
+
+/* Send a data byte.  Returns 0 on success, 1 on general error, 2 on
+   requesting a restart, 3 on receiving a NACK.  */
+static byte
+send_data (byte data)
+{
+  /* Load data into the data register.  */
+  TWDR = data;
+  /* Clear the interrupt flag and enable TWI operation.  */
+  TWCR = _BV (TWINT) | _BV (TWEN);
+  /* Wait for completion.  */
+  while (!(TWCR & _BV (TWINT)))
+    ;
+  /* Check the status value.  */
+  switch (TWSR & 0xF8)
+    {
+    case 0x28:  /* Data has been transmitted.  */
+      break;
+    case 0x30:  /* NACK received.  */
+      return 3;
+    case 0x38:  /* Arbitration lost.  */
+      return 2;
+    default:    /* Unexpected status code.  */
+      return 1;
+    }
+  return 0;
+}
+
+
+/* Start in master transmitter mode.  ADDRESS is the slave address in
+   the range 0 to 254; the low bit is ignored but should be passed as
+   zero.  Returns 0 on success, 1 on general error, 2 for a bad
+   address.  */
+byte
+i2c_start_mt (byte address)
+{
+  /* We need to send an SLA+W; thus clear the LSB.  */
+  address &= 0xf7;
+
+  if (send_start ())
+    {
+      LED_Transmit |= _BV(LED_Transmit_BIT);
+      return 1;
+    }
+  switch (send_sla (address))
+    {
+    case 0:
+      break;
+    case 2:
+      return 2; /* NACK on SLA - assume bad address.  */
+    default:
+      return 1;
+    }
+  return 0;
+}
+
+/* Send byte VALUE.  Returns 1 if successfully sent; 0 on error.  */
+byte
+i2c_send_byte (byte value)
+{
+  if (send_data (value))
+    return 0;
+  return 1;
+}
+
+
+/* Send DATALEN bytes of DATA.  Returns number of bytes successfully
+   sent.  */
+byte
+i2c_send_data (byte *data, byte datalen)
+{
+  byte idx;
+
+  for (idx=0; idx < datalen; idx++)
+    {
+      if (send_data (data[idx]))
+        return idx;
+    }
+  return idx;
+}
+
+
+
+void
+i2c_stop (void)
+{
+  send_stop ();
+}
+
+
+
+/* Initialize the I2C code.  This must be done after the
+   initialization of the general hardware code.  Note that using I2C
+   switches port C pins 4 and 5 to their alternate functions: PC5 is
+   SCL and PC4 is SCD.  */
+void
+i2c_setup (void)
+{
+  /* We use external pull-ups thus disable the internal pull-ups. */
+  PORTC &= ~(_BV(4) | _BV(5));
+
+  /* Set the clock close to 100kHz:
+       F_scl = F_cpu / (16 + 2 * TWBR * Prescaler)
+       |    F_cpu | pre | TWBR | F_scl |
+       |----------+-----+------+-------|
+       | 16000000 |   1 |   73 | 98765 |
+       #+TBLFM: $4=$1 / (16 + (2 * $3 * $2));%.0f
+
+     Note that the prescaler is controlled by bits 1 and 0 of the
+     TWSR; bits 7 to 3 make up the TWI status register. */
+#if F_CPU != 16000000ul
+# error Please adjust the bit rate register.
+#endif
+  TWSR = 0;  /* Prescaler to 1.  */
+  TWBR = 73; /* Bit rate register to 73.  */
+
+}
diff --git a/ebus/onewire.c b/ebus/onewire.c
new file mode 100644 (file)
index 0000000..2b8308c
--- /dev/null
@@ -0,0 +1,172 @@
+/* onewire.c - 1-Wire (iButton) master implementation for AVR
+ * 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/>.
+ */
+
+#include "hardware.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <util/delay.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <avr/interrupt.h>
+#include <avr/eeprom.h>
+#include <avr/sleep.h>
+#include <util/crc16.h>
+
+#include "ebus.h"
+
+
+static int
+write_reset (void)
+{
+  /* Drive low for 480us.  */
+  OW_Bus_PORT &= ~_BV(OW_Bus_BIT);
+  _delay_us (480);
+  /* Drive high via pull-up for 480us.  */
+  OW_Bus_PORT |= _BV(OW_Bus_BIT);  /* Enable pull-up.  */
+  OW_Bus_DDR  &= ~_BV(OW_Bus_BIT); /* Configure as input.  */
+  _delay_us (480);
+  OW_Bus_DDR  |= _BV(OW_Bus_BIT);  /* Configure as output.  */
+  return 0;
+}
+
+
+static void
+write_one (void)
+{
+  /* Drive low for 10us (1 <= T_low1 < 15).  */
+  OW_Bus_PORT &= ~_BV(OW_Bus_BIT);
+  _delay_us (10);
+  /* Drive high for 80us (60us <= T_slot < 120us) 60+(120-60)/2-T_low1.  */
+  OW_Bus_PORT |= _BV(OW_Bus_BIT);
+  _delay_us (80);
+  /* Note that this includes enough time for recovery.  */
+}
+
+static void
+write_zero (void)
+{
+  /* Drive low for 90us (60 <= T_low0 < 120).  */
+  OW_Bus_PORT &= ~_BV(OW_Bus_BIT);
+  _delay_us (90);
+  /* Drive high again.  */
+  OW_Bus_PORT |= _BV(OW_Bus_BIT);
+}
+
+
+static uint8_t
+read_bit (void)
+{
+  uint8_t c;
+
+  OW_Bus_PORT |= _BV(1);
+
+  /* Drive low for at least 1us; we use 2us.  */
+  OW_Bus_PORT &= ~_BV(OW_Bus_BIT);
+  _delay_us (2);
+  /* Drive high via pull-up to that the slave may pull it down.  */
+  OW_Bus_PORT |= _BV(OW_Bus_BIT);  /* Enable pull-up.  */
+  OW_Bus_DDR  &= ~_BV(OW_Bus_BIT); /* Configure as input.  */
+  _delay_us (10);
+  c = !!bit_is_set (OW_Bus_PIN, OW_Bus_BIT);
+  _delay_us (78);  /* This makes the slot 90us.  */
+  OW_Bus_DDR  |= _BV(OW_Bus_BIT);  /* Configure as output.  */
+
+  return c;
+}
+
+
+/* Power up the bus if not done and reset the bus.  */
+void
+onewire_enable (void)
+{
+  OW_Bus_PORT |= _BV(OW_Bus_BIT);  /* Set high.  */
+  OW_Bus_DDR  |= _BV(OW_Bus_BIT);  /* Configure as output.  */
+  write_reset ();
+}
+
+
+/* Power down the bus etc.  */
+void
+onewire_disable (void)
+{
+
+
+}
+
+
+void
+onewire_write_byte (uint8_t c)
+{
+  uint8_t i;
+  uint8_t mask;
+
+  for (mask=1, i=0; i < 8; mask <<= 1, i++)
+    if ((c & mask))
+      write_one ();
+    else
+      write_zero ();
+  _delay_us (20);
+}
+
+
+uint8_t
+onewire_read_byte (void)
+{
+  uint8_t c, i;
+  uint8_t mask;
+
+  OW_Bus_PORT |= _BV(1);
+
+  c = 0;
+  for (mask=1, i=0; i < 8; mask <<= 1, i++)
+    if (read_bit ())
+      c |= mask;
+  OW_Bus_PORT &= ~_BV(1);
+
+  return c;
+}
+
+
+void
+onewire_wait_for_one (void)
+{
+  while (!read_bit ())
+    ;
+}
+
+
+
+
+/* Initialize the 1-Wire code.  This must be done after the
+   initialization of the general hardware code.  Note that this will
+   only prepare the machinery, for real use onewire_enable needs to be
+   called before a real action.  */
+void
+onewire_setup (void)
+{
+  /* Internal pullup needs to be enabled.  Startup as output.  */
+  OW_Bus_PORT |= _BV(OW_Bus_BIT);  /* Enable pull-up.  */
+  OW_Bus_DDR  |= _BV(OW_Bus_BIT);
+
+  /* For debugging.  */
+  OW_Bus_PORT &= ~_BV(1);
+  OW_Bus_DDR  |= _BV(1);
+
+
+}
diff --git a/ebus/proto-busctl.h b/ebus/proto-busctl.h
new file mode 100644 (file)
index 0000000..bf222ea
--- /dev/null
@@ -0,0 +1,98 @@
+/* proto-busctl.h - Definition of the BusControl protocol.
+ * 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/>.
+ */
+
+#ifndef PROTO_BUSCTL_H
+#define PROTO_BUSCTL_H
+
+/* Description of the BusControl protocol.
+
+   This protocol is used on the ebus for generic tasks.  All messages
+   use the common header:
+
+   byte  0 - protocol PROTOCOL_EBUS_BUSCTL (0x81)
+   byte  1 - receiver node-id high
+   byte  2 - receiver node-id low
+   byte  3 - sender   node-id high
+   byte  4 - sender   node-id low
+   byte  5 - command, see below
+   byte  6...15 - command dependent
+
+   A sender node-id high of 0 is valid and indicates that the sender
+   is on the current bus segment.  A sender node-id low of 0 is
+   reserved for a bus controller node (which is currently not
+   defined).  A sender with node-id low or high set to 0xff is
+   invalid.
+
+   List of Commands.  Bit 7 is the RESPONSE bit and used for
+   corresponding response messages.  The command itself is given by
+   bits 6 to 0:
+
+* 0x01 := Time Broadcast
+
+  byte 6 - Control byte
+          bit 2 - Daylight Saving Time active
+          bit 1 - Decile seconds given.
+          bit 0 - Exact time given.  If this bit is not set, the time
+                  may be somewhat off due to collision retries.
+  byte 7,8 - Number of 10 second periods passed this week.
+  byte 9   - Decile seconds within this period.  Values are 0 to 99
+             representing 0.0 to 9.9 seconds.
+  byte 10..15 - rfu
+
+  No reponses are defined or expected.
+
+  Note that the usual operation is to broadcast the current time from
+  a sender connected to an NTP server.  Thus it is common to see
+  receiver node-ids of (0xff,0xff).
+
+  The time on the bus is the local time.  This makes it easier to
+  display and modify times.  This function shall be called by a master
+  around the daylight switching hour to make sure the bus gets updated
+  to the right time.
+
+* 0x02 := Query Time
+
+  byte 6..15 - rfu, must be 0.
+
+  Response format:
+
+  byte 6   - rfu
+  byte 7,8 - Number of 10 second periods passed this week.
+  byte 9   - Decile seconds within this period.  Values are 0 to 99
+             representing 0.0 to 9.9 seconds.
+
+* 0x03 := Query Version
+
+  byte 6..15 - rfu, must be 0.
+
+  Response format:
+
+  byte 6   - nodetype (NODETYPE_xxxx)
+  byte 7   - reserved
+  byte 8..14 - GIT revision string or "unknown".
+  byte 15   - reserved
+*/
+
+#include "protocol.h"
+
+#define P_BUSCTL_RESPMASK    0x80 /* The response mask for the commands.  */
+#define P_BUSCTL_TIME        0x01 /* Time Broadcast.  */
+#define P_BUSCTL_QRY_TIME    0x02 /* Query Time.  */
+#define P_BUSCTL_QRY_VERSION 0x03 /* Query software version.  */
+
+
+#endif /*PROTO_BUSCTL_H*/
diff --git a/ebus/proto-dbgmsg.h b/ebus/proto-dbgmsg.h
new file mode 100644 (file)
index 0000000..6c8e13c
--- /dev/null
@@ -0,0 +1,35 @@
+/* proto-dbgmsg.h - Definition of the debug message protocol.
+ * 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/>.
+ */
+
+#ifndef PROTO_DBGMSG_H
+#define PROTO_DBGMSG_H
+
+/* Description of the Debug Message protocol.
+
+   This protocol is used to send debug strings to the bus.
+
+   byte  0 - protocol PROTOCOL_EBUS_DBGMSG
+   byte  1 - sender   node-id high
+   byte  2 - sender   node-id low
+   byte  3...15 - string
+
+*/
+
+#include "protocol.h"
+
+
+#endif /*PROTO_DBGMSG_H*/
diff --git a/ebus/proto-h61.h b/ebus/proto-h61.h
new file mode 100644 (file)
index 0000000..fa60881
--- /dev/null
@@ -0,0 +1,181 @@
+/* proto-h61.h - Definition of the H61 protocol.
+ * 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/>.
+ */
+
+#ifndef PROTO_H61_H
+#define PROTO_H61_H
+
+/* Description of the H/61 protocol.
+
+   This protocol is used for domotik control of a detached house.  The
+   message is similar to the
+
+   byte  0 - protocol PROTOCOL_EBUS_H61 (0x86)
+   byte  1 - receiver node-id high
+   byte  2 - receiver node-id low
+   byte  3 - sender   node-id high
+   byte  4 - sender   node-id low
+   byte  5 - command, see below
+   byte  6...15 - command dependent
+
+   List of Commands.  Bit 7 is the RESPONSE bit and used for
+   corrsponding response messages.  The command itself is given by
+   bits 6 to 0:
+
+* 0x10 := Shutter control:
+
+  byte 6 - Subcommand for the shutter control:
+
+** 0x00 - Query shutter state
+
+   byte 7..15 - rfu must be 0.
+
+*** Response message
+
+   byte  7 - error flag (0 for okay).  Only set on response from the
+             drive shutter subcommand.
+   byte  8 - Shutter 1 state:
+             bit 7 - Currently driving
+             bit 6 - Currently pulling up
+             bit 5 - State in bits 3...0 is valid.
+             bit 4 - rfu
+             bit 3..0 - Percentage closed
+                       (0 = open, 31 = 100% closed)
+   byte  9 - Shutter 2 state
+   byte 10 - Shutter 3 state
+   byte 11 - Shutter 4 state
+   byte 12 - Shutter 5 state
+   byte 13 - Shutter 6 state
+   byte 14 - Shutter 7 state
+   byte 15 - Shutter 8 state
+
+** 0x01 - Drive shutter
+
+   byte 7 - Shutter number to drive
+            (0 = all shutters controlled by node)
+   byte 8 - Shutter control byte
+            bit 7 - Drive
+            bit 6 - Direction is "up".
+            bit 5 - Drive to percentage given in bits 3..0.
+                    (Bit 6 must be zero.)
+            bit 4 - rfu
+            bit 3..0 = Drive to this closed state
+                       (0 = open, 15 = 100% closed)
+
+            Undefined values will do nothing.
+   byte  9..15 - rfu
+
+
+*** Response message
+
+    The response is the same as the response from the query shutter
+    state subcommand.  If there was an error in the command block the
+    error flag in the response block will be set.  However the
+    subcommand in the response message is 0x01 and not 0x00.
+
+** 0x02 - Query shutter timings
+
+   The time required to pull the drive up or down is stored in the
+   EEPROM.  This command allows to read out the value.
+
+   byte 7 - Shutter number
+   byte 8..15 - rfu, must be 0.
+
+*** Response message
+
+   byte 7 - Shutter number
+   byte 8 - Time for a complete pull up in seconds.
+   byte 9 - Time for a complete pull down in seconds.
+   byte 10..15 - rfu
+
+** 0x03 - Update shutter timings
+
+   The time required to pull the drive up or down is stored in th
+   EEPROM.  This command allows an easy adjustment.
+
+   byte 7 - Shutter number
+   byte 8 - Time for a complete pull up in seconds.
+   byte 9 - Time for a complete pull down in seconds.
+   byte 10..15 - rfu, must be 0
+
+*** Response message
+
+    The response message is the same as Query Shutter timings.
+
+*** 0x04 - Query Shutter Schedule
+
+    byte 7 - Shutter number
+
+*** Response message
+
+   The device answers with multiple response messages returning all
+   stored schedules.
+
+   byte 7 - Shutter number
+   byte 8 - Error flag (only used with ChangeShutterSchedule)
+   byte 9 - Number of items
+   byte 10 - Item number
+   byte 11,12 - Time
+   byte 13    - New state (cf. Drive Shutter)
+   byte 14..15 - rfu, must be 0
+
+** 0x05 - Update Shutter Schedule
+
+   byte 7 - Shutter number
+   byte 8 - rfu, must be 0
+   byte 9 - constant 1.
+   byte 10 - Item number
+   byte 11,12 - Time
+   byte 13    - Action (cf. Drive Shutter)
+
+   byte 14..15 - rfu, must be 0
+
+   After one message old messages are destroyed.  On error the device
+   may or may not use the old values.  Using an undefined action may
+   be used to delete an entry.
+
+   A factory reset of the schedule may be done using these parameters:
+   Shutter number: 0xf0
+   Byte 9: 16
+   Item number: 0xf0
+   time = 0xf0f0
+   action = 0xf0
+
+*** Response message
+
+   There is no response message.  It is suggested to use Query Shutter
+   State to check that the settings are correct.
+
+*/
+
+#include "protocol.h"
+
+#define P_H61_RESPMASK 0x80  /* The response mask for the commands.  */
+#define P_H61_SHUTTER  0x10  /* ShutterControl.  */
+#define P_H61_ADCREAD  0x21  /* Read analog sensor.  */
+
+
+/* Subcommands of P_H61_SHUTTER.  */
+#define P_H61_SHUTTER_QUERY        0x00
+#define P_H61_SHUTTER_DRIVE        0x01
+#define P_H61_SHUTTER_QRY_TIMINGS  0x02
+#define P_H61_SHUTTER_UPD_TIMINGS  0x03
+#define P_H61_SHUTTER_QRY_SCHEDULE 0x04
+#define P_H61_SHUTTER_UPD_SCHEDULE 0x05
+
+
+
+#endif /*PROTO_H61_H*/
diff --git a/ebus/protocol.h b/ebus/protocol.h
new file mode 100644 (file)
index 0000000..0016b59
--- /dev/null
@@ -0,0 +1,70 @@
+/* protocol.h - Protocol definitons for an Elektor Bus Node
+ * 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/>.
+ */
+
+#ifndef PROTOCOL_H
+#define PROTOCOL_H
+
+/* The sync byte which indicates the start of a new message.  If this
+   byte is used inside the message it needs to be escaped and XORed
+   with the escape mask.  This ensures that a sync byte will always be
+   the start of a new frame.  Note that this is similar to the PPP
+   (rfc-1662) framing format.  */
+#define FRAMESYNCBYTE 0x7e
+#define FRAMEESCBYTE  0x7d
+#define FRAMEESCMASK  0x20
+
+/* The protocol id describes the format of the payload; ie. the actual
+   message.  It is used instead of the 0xaa byte of the original (May
+   2011) ElektorBus protocol.  Defined values:
+
+   | Bit 7 .. 6 | Bit 5 .. 0   |
+   |------------+--------------|
+   | Msglen     | ProtocolType |
+   |------------+--------------|
+
+   | Msglen | Description          |
+   |--------+----------------------|
+   |      0 | 48 byte + 2 byte CRC |
+   |      1 | 32 byte + 2 byte CRC |
+   |      2 | 16 byte + 2 byte CRC |
+   |      3 | RFU                  |
+   |        |                      |
+
+   | ProtocolType  | Description            | Allowed Msglen codes  |
+   |---------------+------------------------+-----------------------|
+   | 0x00          | Not used               | -                     |
+   | 0x01 ... 0x2f | Assigned values        |                       |
+   | 0x01          | BusControl             | 2,1,0                 |
+   | 0x06          | H/61 protocol          | 2                     |
+   | 0x1f          | DebugMessage           | 2,1,0                 |
+   | 0x2a          | ElektorBus Application | 2                     |
+   | 0x30 ... 0x37 | Experimental protocols |                       |
+   | 0x38 ... 0x3e | RFU                    |                       |
+   | 0x3f          | Not used               |                       |
+ */
+#define PROTOCOL_MSGLEN_MASK  0xc0
+#define PROTOCOL_TYPE_MASK    0x3f
+#define PROTOCOL_MSGLEN_48    0x00
+#define PROTOCOL_MSGLEN_32    0x40
+#define PROTOCOL_MSGLEN_16    0x80
+
+#define PROTOCOL_EBUS_BUSCTL (PROTOCOL_MSGLEN_16 | 0x01)
+#define PROTOCOL_EBUS_H61    (PROTOCOL_MSGLEN_16 | 0x06)
+#define PROTOCOL_EBUS_DBGMSG (PROTOCOL_MSGLEN_16 | 0x1f)
+#define PROTOCOL_EBUS_TEST   (PROTOCOL_MSGLEN_16 | 0x31)
+
+#endif /*PROTOCOL_H*/
index d546886..041b1ef 100644 (file)
@@ -281,7 +281,7 @@ ticker_bottom (unsigned int clock)
    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
+   Consider the shutter shall be closed 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
diff --git a/ebus/testnode.c b/ebus/testnode.c
new file mode 100644 (file)
index 0000000..dcd44b8
--- /dev/null
@@ -0,0 +1,270 @@
+/* testnode.c - Elektor Bus test Node
+ * 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 continuously sends messages with status information.  S2
+   and S3 are used to change the send interval.  */
+
+#include "hardware.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/sleep.h>
+#include <util/delay.h>
+
+#include <util/crc16.h>
+
+#include "ebus.h"
+#include "proto-dbgmsg.h"
+
+
+static volatile unsigned short send_interval;
+
+static volatile byte send_flag;
+
+
+
+/* This code is called by the 1ms ticker interrupt service routine
+   with the current clock value given in milliseconds from 0..999. */
+void
+ticker_bottom (unsigned int clock)
+{
+
+  /* Run the main loop every N ms.  */
+  if (send_interval && !(clock % send_interval))
+    {
+      send_flag = 1;
+      wakeup_main = 1;
+    }
+
+  if (read_key_s2 ())
+    {
+      switch (send_interval)
+        {
+        case 1000:
+          /* Disable sending but send one more frame to show the
+             new sending interval.  */
+          send_interval = 0;
+          send_flag = 1;
+          wakeup_main = 1;
+          break;
+        case  500: send_interval = 1000; break;
+        case  250: send_interval =  500; break;
+        case  125: send_interval =  250; break;
+        case  100: send_interval =  125; break;
+        case   50: send_interval =  100; break;
+        case   25: send_interval =   50; break;
+        }
+    }
+  if (read_key_s3 ())
+    {
+      switch (send_interval)
+        {
+        case 0:    send_interval = 1000; break;
+        case 1000: send_interval = 500; break;
+        case  500: send_interval = 250; break;
+        case  250: send_interval = 125; break;
+        case  125: send_interval = 100; break;
+        case  100: send_interval =  50; break;
+        case   50: send_interval =  25; break;
+        }
+    }
+}
+
+
+static void
+send_msg (void)
+{
+  byte msg[MSGSIZE];
+  int idx = 0;
+  unsigned int val;
+
+  msg[idx++] = PROTOCOL_EBUS_TEST;
+  msg[idx++] = 0;
+  msg[idx++] = config.nodeid_lo;
+  msg[idx++] = 0;
+  msg[idx++] = (val = get_current_time ()) >> 8;
+  msg[idx++] = val;
+  msg[idx++] = (val = csma_get_stats (1)) >> 8;
+  msg[idx++] = val;
+  msg[idx++] = (val = csma_get_stats (2)) >> 8;
+  msg[idx++] = val;
+  msg[idx++] = (val = csma_get_stats (3)) >> 8;
+  msg[idx++] = val;
+  msg[idx++] = (val = csma_get_stats (4)) >> 8;
+  msg[idx++] = val;
+  msg[idx++] = send_interval >> 8;
+  msg[idx++] = send_interval;
+
+  csma_send_message (msg, MSGSIZE);
+}
+
+
+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);
+}
+
+
+
+/* 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_test (void)
+{
+  /* Is this a broadcast or is it directed to us.  If not we don't
+     need to care about it.  */
+
+}
+
+
+
+/*
+    Entry point
+ */
+int
+main (void)
+{
+  byte *msg;
+  hardware_setup (NODETYPE_UNDEFINED);
+  csma_setup ();
+  onewire_setup ();
+
+  send_interval = 1000;
+
+  sei (); /* Enable interrupts.  */
+
+
+    /* { */
+    /*   byte i, crc, buf[9]; */
+
+    /*   send_dbgmsg_fmt ("serial..."); */
+    /*   onewire_enable (); */
+    /*   onewire_write_byte (0x33); /\* Read ROM.  *\/ */
+    /*   for (i=0; i < 8; i++) */
+    /*     buf[i] = onewire_read_byte (); */
+    /*   _delay_ms (1); */
+    /*   crc = 0; */
+    /*   for (i=0; i < 7; i++) */
+    /*     crc = _crc_ibutton_update (crc, buf[i]); */
+
+    /*   send_dbgmsg_fmt ("%02x %02x%02x%02x %02x", */
+    /*                    buf[0], buf[1], buf[2], buf[3], buf[7]); */
+    /*   send_dbgmsg_fmt ("%02x%02x%02x %s", */
+    /*                    buf[4], buf[5], buf[6], */
+    /*                    buf[7] == crc? "ok":"bad"); */
+
+    /*   _delay_ms (2000); */
+    /* } */
+
+  for (;;)
+    {
+      byte i, crc, buf[9];
+
+      onewire_enable ();
+      onewire_write_byte (0xcc); /* Skip ROM.  */
+      onewire_write_byte (0x44); /* Convert T.  */
+      onewire_wait_for_one ();
+      _delay_ms (100);
+      onewire_enable ();         /* Reset */
+      onewire_write_byte (0xcc); /* Skip ROM.  */
+      onewire_write_byte (0xbe); /* Read scratchpad.  */
+      for (i=0; i < 9; i++)
+        buf[i] = onewire_read_byte ();
+
+      crc = 0;
+      for (i=0; i < 8; i++)
+        crc = _crc_ibutton_update (crc, buf[i]);
+
+      if (buf[8] == crc)
+        {
+          int16_t tread = (buf[1] << 8) | buf[0];
+          int16_t t;
+
+          t = (tread*100 - 25 + ((16 - buf[6])*100 / 16)) / 20;
+
+          send_dbgmsg_fmt ("t=%d (%d)", t, tread);
+        }
+
+      _delay_ms (5000);
+    }
+
+
+
+  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;
+
+      msg = csma_get_message ();
+      if (msg)
+        {
+          /* Process the message.  */
+          switch ((msg[0] & PROTOCOL_TYPE_MASK))
+            {
+            case PROTOCOL_EBUS_TEST:
+              process_ebus_test ();
+              break;
+            default:
+              /* Ignore all other protocols.  */
+              break;
+            }
+          /* Re-enable the receiver.  */
+          csma_message_done ();
+        }
+
+      if (send_flag)
+        {
+          send_msg ();
+          send_flag = 0;
+        }
+    }
+}