ebus: Switch to a 10ms ticker
[wk-misc.git] / ebus / shutter.c
index d431ba1..f781a07 100644 (file)
@@ -56,6 +56,7 @@
 #include <avr/interrupt.h>
 #include <avr/sleep.h>
 #include <avr/eeprom.h>
+#include <util/crc16.h>  /* For _crc_ibutton_update.  */
 
 #include "ebus.h"
 #include "proto-busctl.h"
 enum __attribute__ ((packed)) action_values {
   action_none = 0,
   action_up,
-  action_down
+  action_down,
+  action_up_key,
+  action_down_key
 };
 
 enum __attribute__ ((packed)) motor_state_values {
   motor_state_off = 0,
   motor_state_pre_off,
   motor_state_pre_off2,
+  motor_state_pre_off3,
   motor_state_pre_up,
   motor_state_pre_up2,
   motor_state_up,
@@ -96,20 +100,59 @@ enum __attribute__ ((packed)) motor_state_values {
   motor_state_down_ready
 };
 
+
+\f
+/*
+   Communication between ISR and main loop.
+ */
+
+/* Event flag triggered (surprise) every second.  */
+static volatile byte one_second_event;
+
+/* Event flag trigger by key S2.  */
+static volatile byte key_s2_event;
+
+/* Event flag trigger by key S3.  */
+static volatile byte key_s3_event;
+
+/* The motor action delay counter and its event flag.  */
+static volatile uint16_t motor_action_delay;
+static volatile uint16_t motor_action_event;
+
+/* Sensor action delay counter and its event flag.  */
+static volatile uint16_t sensor_action_delay;
+static volatile byte sensor_action_event;
+
+
+\f
+/*
+   Global state of the main loop.
+ */
+
 /* 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 current state of the motor state machine.  */
+static enum motor_state_values motor_state;
 
 /* The shutter state byte.  This is directly used for the
    QueryShutterState response message.  */
-static volatile byte shutter_state;
+static byte shutter_state;
 
-/* Event flag, triggerd (surprise) every 10 seconds.  */
-static volatile byte ten_seconds_event;
+/* A structure to control the sensor actions.  This is not used by an
+   ISR thus no need for volatile.  */
+static struct
+{
+  /* If set a sensor read out has been requested.  The value gives the
+     number of read out tries left.  If it is down to zero an error
+     message will be returned.  */
+  byte active;
+  /* The address to send the response to.  If a second client requests
+     a readout, we won't record the address but broadcast th
+     result.  */
+  byte addr_hi;
+  byte addr_lo;
+} sensor_ctrl;
 
 
 \f
@@ -119,168 +162,201 @@ 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. */
+/* This code is called by the 10ms ticker interrupt service routine
+   with the current clock value given in 10 millisecond increments
+   from 0..999. */
 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;
+  if (!(clock % 100))
+    {
+      one_second_event = 1;
+      wakeup_main = 1;
+    }
 
-  /* Blink the activity LED if the motor is not in off state.  */
-  if (state != motor_state_off)
+  if (!key_s2_event && read_key_s2 ())
     {
-      if (!(clock % 1000))
-        {
-          if ((LED_Collision & _BV(LED_Collision_BIT)))
-            LED_Collision &= ~_BV (LED_Collision_BIT);
-          else
-            LED_Collision |= _BV(LED_Collision_BIT);
-        }
+      key_s2_event = 1;
+      wakeup_main = 1;
     }
 
-  /* 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 (!key_s3_event && read_key_s3 ())
     {
-      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;
+      key_s3_event = 1;
+      wakeup_main = 1;
+    }
+
+  if (motor_action_delay && !--motor_action_delay)
+    {
+      motor_action_event = 1;
+      wakeup_main = 1;
+    }
+
+  if (sensor_action_delay && !--sensor_action_delay)
+    {
+      sensor_action_event = 1;
+      wakeup_main = 1;
+    }
+}
+
+
+static void
+send_dbgmsg (const char *string)
+{
+  byte msg[16];
+
+  if (config.debug_flags)
+    {
+      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); */
+/* } */
+
+
+/* Trigger a new motor action.  */
+static void
+trigger_action (enum action_values action)
+{
+  static enum action_values last_action;
+
+ again:
+  switch (action)
+    {
+    case action_none: motor_state = motor_state_pre_off;  break;
+    case action_up:   motor_state = motor_state_pre_up;   break;
+    case action_down: motor_state = motor_state_pre_down; break;
+
+    case action_up_key:
+      action = last_action == action_up? action_none : action_up;
+      goto again;
+    case action_down_key:
+      action = last_action == action_down? action_none : action_down;
+      goto again;
+
+    default:
+      return;/* Avoid setting a new delay for unknown states.  */
     }
 
-  if (action_delay && !--action_delay)
+  last_action = action;
+
+  /* Now force a new transaction using the ticker interrupt.  We set
+     the delay to 10 ms so that the motor_action_event will be
+     triggered within the next 10 milliseconds.  There is no need to
+     disable interrupts; the worst what could happen is a double
+     triggered action and that action is anyway a motor off.  Using
+     this indirect method avoids a race between motor_action_delay and
+     motor_action_event.  */
+  motor_action_delay = 1;
+}
+
+
+/* The main state machine for the shutter motors.  The return value is
+   delay to be taken before the next call to this function.  */
+static uint16_t
+motor_action (void)
+{
+  uint16_t newdelay;
+
+  /* Perform the state transitions.  */
+  do
     {
-      switch (state)
+      newdelay = 0;
+      switch (motor_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;
+          newdelay = MILLISEC (200);
+          motor_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;
+          newdelay = MILLISEC (200);
+          motor_state = motor_state_pre_off3;
+          break;
+        case motor_state_pre_off3:
+          LED_Collision &= ~_BV (LED_Collision_BIT); /* Clear LED.  */
+          /* Make sure the motor flags are cleared in the state info.  */
+          shutter_state &= 0b00111111;
+          motor_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;
+          newdelay = MILLISEC (200);
+          motor_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;
+          newdelay = MILLISEC (200);
+          motor_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;
+          newdelay = MILLISEC (25000);
+          motor_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;
+          motor_state = motor_state_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;
+          newdelay = MILLISEC (200);
+          motor_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;
+          newdelay = MILLISEC (200);
+          motor_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;
+          newdelay = MILLISEC (25000);
+          motor_state = motor_state_down_ready;
           break;
         case motor_state_down_ready:
           shutter_state = 0b00101111;
           /*                  | !~~!--- Value: 15 = 100% closed
            *                  +-------- State in bits 3..0 is valid.  */
-          goto label_pre_off;
+          motor_state = motor_state_pre_off;
+          break;
         }
     }
+  while (!newdelay && motor_state != motor_state_off);
 
-  if (!clock)
-    {
-      ten_seconds_event = 1;
-      wakeup_main = 1;
-    }
+  return newdelay;
 }
 
 
-static void
-send_dbgmsg (const char *string)
-{
-  byte msg[16];
-
-  if (config.debug_flags)
-    {
-      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.
@@ -335,14 +411,15 @@ process_schedule (uint16_t time, uint16_t forced_tlow)
       /* send_dbgmsg_fmt ("act=%u", tfound); */
       if (tfound == SCHEDULE_ACTION_UP)
         {
-          next_action = action_up;
           send_dbgmsg ("sch-act up");
+          trigger_action (action_up);
         }
       else if (tfound == SCHEDULE_ACTION_DOWN)
         {
-          next_action = action_down;
           send_dbgmsg ("sch-act dn");
+          trigger_action (action_down);
         }
+
     }
 }
 
@@ -381,13 +458,13 @@ process_shutter_cmd (byte *msg)
           err = 1;
         else if ((msg[8] & 0xc0) == 0xc0)
           {
-            next_action = action_up;
             send_dbgmsg ("bus-act up");
+            trigger_action (action_up);
           }
         else if ((msg[8] & 0xc0) == 0x80)
           {
-            next_action = action_down;
             send_dbgmsg ("bus-act dn");
+            trigger_action (action_down);
           }
         else
           err = 1;
@@ -483,29 +560,33 @@ process_shutter_cmd (byte *msg)
 static void
 process_sensor_cmd (byte *msg)
 {
-  /* uint16_t val16; */
-  /* byte err = 0; */
-
   switch (msg[6])
     {
     case P_H61_SENSOR_TEMPERATURE:
-      {
-        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] = (1 << 4 | 1); /* Group 1 of 1.  */
-        msg[8] = 0;
-        msg[9] = 0;
-        msg[10] = 0x80; /* No sensor.  */
-        msg[11] = 0;
-        msg[12] = 0x80;
-        msg[13] = 0;
-        msg[14] = 0x80;
-        msg[15] = 0;
-        memset (msg+10, 0, 6);
-        csma_send_message (msg, MSGSIZE);
+      if (sensor_ctrl.active)
+        {
+          /* A second client request, if it is a different one switch
+             to broadcast mode.  */
+          if (msg[3] != sensor_ctrl.addr_hi || msg[4] != sensor_ctrl.addr_lo)
+            {
+              sensor_ctrl.addr_hi = 0xff;
+              sensor_ctrl.addr_lo = 0xff;
+            }
+        }
+      else
+        {
+          sensor_ctrl.active = 5;    /* Number of tries.  */
+          sensor_ctrl.addr_hi = msg[3];
+          sensor_ctrl.addr_lo = msg[4];
+
+          /* Trigger the read out machinery.  */
+          onewire_enable ();
+          onewire_write_byte (0xcc); /* Skip ROM.  */
+          onewire_write_byte (0x44); /* Convert T.  */
+          /* Now we need to wait at least 750ms to read the value from
+             the scratchpad.  */
+          sensor_action_delay = MILLISEC (900);
+          sensor_action_event = 0;
       }
       break;
 
@@ -515,6 +596,78 @@ process_sensor_cmd (byte *msg)
 }
 
 
+/* Try to read the result from the sensor and send it back.  This
+   function shall be called at least 750ms after the first conversion
+   message.  */
+static void
+send_sensor_result (void)
+{
+  byte i, crc;
+  int16_t t;
+  byte msg[16]; /* Used to read the scratchpad and to build the message.  */
+
+  if (!sensor_ctrl.active)
+    return;
+
+  onewire_enable ();         /* Reset */
+  onewire_write_byte (0xcc); /* Skip ROM.  */
+  onewire_write_byte (0xbe); /* Read scratchpad.  */
+  for (i=0; i < 9; i++)
+    msg[i] = onewire_read_byte ();
+
+  crc = 0;
+  for (i=0; i < 8; i++)
+    crc = _crc_ibutton_update (crc, msg[i]);
+
+  if (msg[8] == crc)
+    {
+      t = (msg[1] << 8) | msg[0];
+      t = (t*100 - 25 + ((16 - msg[6])*100 / 16)) / 20;
+    }
+  else
+    {
+      t = 0x7fff;  /* Read error */
+    }
+
+  if (t != 0x7fff || !--sensor_ctrl.active)
+    {
+      /* Success or read error with the counter at zero.  */
+      msg[0] = PROTOCOL_EBUS_H61;
+      msg[1] = sensor_ctrl.addr_hi;
+      msg[2] = sensor_ctrl.addr_lo;
+      msg[3] = config.nodeid_hi;
+      msg[4] = config.nodeid_lo;
+      msg[5] = (P_H61_SENSOR | P_H61_RESPMASK);
+      msg[6] = P_H61_SENSOR_TEMPERATURE;
+      msg[7] = (1 << 4 | 1); /* Group 1 of 1.  */
+      msg[8] = (t >> 8); /* Sensor no. 0.  */
+      msg[9] = t;
+      msg[10] = 0x80; /* No sensor no. 1.  */
+      msg[11] = 0;
+      msg[12] = 0x80; /* No sensor no. 2.  */
+      msg[13] = 0;
+      msg[14] = 0x80; /* No sensor no. 3.  */
+      msg[15] = 0;
+      csma_send_message (msg, MSGSIZE);
+
+      sensor_ctrl.active = 0;  /* Ready. */
+      onewire_disable ();
+    }
+  else
+    {
+      send_dbgmsg ("sens #4");
+      /* Better issue the read command again.  */
+      onewire_enable ();         /* Reset.     */
+      onewire_write_byte (0xcc); /* Skip ROM.  */
+      onewire_write_byte (0x44); /* Convert T. */
+      /* Try again ...  */
+      sensor_action_delay = MILLISEC (1100);
+      sensor_action_event = 0;
+    }
+}
+
+
+
 /* 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.  */
@@ -606,10 +759,37 @@ process_ebus_busctl (byte *msg)
       csma_send_message (msg, MSGSIZE);
       break;
 
+      /* FIXME: Better move this into hardware.c */
+    /* case P_BUSCTL_QRY_NAME: */
+    /*   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] = eeprom_read_byte (&ee_data.nodetype); */
+    /*   msg[7] = 0; */
+    /*   eeprom_read_block (msg+8, config */
+    /*   memcpy_P (msg+8, PSTR (GIT_REVISION), 7); */
+    /*   msg[15] = 0; */
+    /*   csma_send_message (msg, MSGSIZE); */
+    /*   break; */
+
     case P_BUSCTL_SET_DEBUG:
       set_debug_flags (msg[6]);
       break;
 
+    case P_BUSCTL_QRY_DEBUG:
+      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] = config.debug_flags;
+      msg[7] = config.reset_flags;
+      memset (msg+8, 0, 8);
+      csma_send_message (msg, MSGSIZE);
+      break;
+
     default:
       break;
     }
@@ -652,6 +832,7 @@ init_eeprom (byte force)
 int
 main (void)
 {
+  static int ten_seconds_counter;
   byte *msg;
 
   hardware_setup (NODETYPE_SHUTTER);
@@ -684,17 +865,64 @@ main (void)
         }
       wakeup_main = 0;
 
-      if (ten_seconds_event)
+      if (key_s2_event)
+        {
+          send_dbgmsg ("key-act down");
+          trigger_action (action_down_key);
+          key_s2_event = key_s3_event = 0;
+        }
+
+      if (key_s3_event)
         {
-          uint16_t t;
+          send_dbgmsg ("key-act up");
+          trigger_action (action_up_key);
+          key_s3_event = 0;
+        }
+
+      if (motor_action_event)
+        {
+          motor_action_event = 0;
+          motor_action_delay = motor_action ();
+        }
+
+      if (sensor_action_event)
+        {
+          sensor_action_event = 0;
+          send_sensor_result ();
+        }
+
+      if (one_second_event)
+        {
+          one_second_event = 0;
+
+          /*
+             Code to run once a second.
+           */
+
+          /* Blink the activity LED if the motor is not in off state.  */
+          if (motor_state != motor_state_off)
+            {
+              if ((LED_Collision & _BV(LED_Collision_BIT)))
+                LED_Collision &= ~_BV (LED_Collision_BIT);
+              else
+                LED_Collision |= _BV(LED_Collision_BIT);
+            }
 
-          ten_seconds_event = 0;
 
-          t = get_current_time ();
-          if (!(t % 6))
+          if (++ten_seconds_counter == 10)
             {
-              /* Code to run every minute.  */
-              process_schedule (t, 0);
+              /*
+                 Code to run once every 10 seconds.
+               */
+              uint16_t t;
+
+              ten_seconds_counter = 0;
+              t = get_current_time ();
+              if (!(t % 6))
+                {
+                  /* Code to run every minute.  */
+                  process_schedule (t, 0);
+                }
             }
         }