Recent site activity

Controller Design & State Machine

Heating Controller State

The controller tracks sensors, zones, valves, time-periods and demand values. 

Loosely, Zones represent separate controllers (in the control theory sense), sensors can be soft-mapped as controller inputs. TimePeriods provide demand signals to each controller that switch in real time following a schedule. Values are assigned to take corrective action from the demand output from the controller.

e.g. one could map all sensors as average inputs for a single zone, that has every valve configured as it's output. Or use all "downstairs" sensors as an input to one of two controllers (up, down) etc. etc.
  • Sensor - each sensor has 
    • a 16 byte address, 
    • last value & update time
    • allocation to zone
  • Valves - each value has a 
    • pair of bytes to represent the desired and achieved position (0= closed, 1=open), 
    • a count-down timer to detect stuck valves, and 
    • allocation to a zone
  • Zone
    • string to represent a room name, 
    • state: 
      • 0: manual_off

      • 2: manual_temp_time
      • 3: auto
      • 4: sad_stuck_open 
      • 5: sad_stuck_closed
      • 6: sad_dead_sensor 
    • zone countdown time (for manual_temp_countdown)
    • zone_demand 
  • TimePeriods
    • period_switchtime - end time of the period
    • period_dowmask - bit flag of days_of_week that can use this period
    • period_zone_demand - a vector of demand values for each zone within the period
  • TZ Calendar
    • schedule of DST changes
To help with the pseduo-code functionality descriptions, here is a representation of the state as variables:

long[16]  sensor_address1
long[16]  sensor_address2
short[16]  sensor_lasttime
short[16]  sensor_lastyymmdd
short[16] sensor_lastvalue
byte[16]  sensor_zone        -1 unallocated, otherwise zone allocated 0..15
byte      sensor_count
byte      sensor_next

long      sensor_lastscan

byte[16]  valve_demand
byte[16]  valve_position
byte[16]  valve_timeout
byte[16]  value_zone        -1 unallocated, otherwise zone allocate 0..15

string[16] zone_name
byte[16]   zone_state
short[16]  zone_demand
short[16]  zone_temp
short[16]  zone_countdown

short[8]     period_switchtime
byte[8]      period_dowmask
short[8][16] period_zone_demand
byte         period_count

??        tz_yymmdd_switch        date to apply switch
??        tz_utc_adjust           +1 or 0

// live state
byte        live_period
short       live_time
byte        live_dow
??          live_yymmdd
??          live_utclocal_shift

At each rollover of the seconds counter (i.e. every minute):
  • increment the minutes-since-midnight counter
  • check for date rollover, set live_yymmdd
    • check for DST switch
      • set calendar_pos = 0
      • while (live_yymmdd > calendar_switch[calendar_pos]) increment calendar_pos
      • apply the local clock offset from calendar_utc_adjust[calendar_pos] to live_utclocal_shift
  • check for time-period switch
    • update live_dow to the current day-of-week, initialise check_limit = 100
    • while ((period_end[live_period] <= live_time || period_dow[live_period] does not permit live_dow ) && check_limit > 0) 
      • increment live_period, decrement check_limit
      • if (live_period == period_count) set live_period = 0
    • if (check_limit == 0) "go into scheduling error state"? set live_period = 0
    • for each zone, 
      • copy the period_zone_demand[live_period][z] to zone_demand[z] if zone_state[z] == auto
  • check for stuck valves
    • decrement each valve_timeout counter that is greater than zero, where the valve_demand is different to valve_position
    • if a valve_timeout[v] reaches zero, lookup the zone z = , set the zone_state[z] to sad_stuck_closed or sad_stuck_open (if operating)
  • check for dead sensor
    • if a sensor hasn't updated in an hour, and the sensor is allocated, set the corresponding zone status to sad_dead_sensor (if operating)
      • Usability note: corrective action is to 
        • 1) remove sensor from zone, then 
        • 2) set the zone to manual_off, manual_temp_time (or auto if another sensor exists for the zone), which all clear the sad state
        • 3) re-allocate the valve to take output from a nearby zone as a substitute !
  • update the sensor measurement
    • if last asynchronous (conversion successful) perform
      • sensor_lasttime[sensor_next] = live_time
      • sensor_lastyymmdd[sensor_next] = live_yymmdd
      • sensor_lastvalue[sensor_next] = (conversion_result)
    • increment sensor_next
    • if sensor_next == sensor_count   then sensor_next = 0
    • send a conversion request on the I2C bus for sensor_address[sensor_next]
  • copy sensor values to zone_temp, taking average where more than one sensor is active for a given zone (many values will not have changed)