Project HOME

Pebbles Heating Controller

Page constructed Sept 2005 (updated Aug 2006, March 2016).

Whitepaper 2006: `Using Simple Pushlogic' by DJ Greaves, D Gordon. At WEBIST 06. PDF.

As part of the Pebbles project, we have created a real heating timer and are installing it in the house of DJ Greaves, where it will be in everyday use.

These days there is nothing novel about a UPnP heating controller that connects to the home network and can be controlled from a web browser. What is special about this device is that its variables are part of a distributed tuple space and that an automated proof technique is used to dynamically check interaction between its internal code and other devices and applications running in the same domain.

The Heating Controller consists of a Pebbles Project Molly that is running the Tuplecore substrate and a Pushlogic interpreter. The physical components of the Heating Controller are:

  • A Molly Card (with processor, flash memory and Ethernet),
  • Power Supply, providing 10 volts unregulated, fused.
  • Three solid state relays, capable of switching 2A at 250VAC, with LED followers on their coil side.
  • Three inputs for thermostats, with LED followers
  • Some hardware interlock logic gates
  • An LED display (8 seven-segment) digits
  • A keypad with a small number of keys with printed legends.

From a software architecture point of view, the Heating Controller is partitioned into the following logical Pebbles:

  • A relay and thermostat pebble, with hardware interlock.
  • A security lamp control pebble.
  • A display and keypad pebble.
  • A Pushlogic timer pebble.
  • Some other pebbles that come with the software (e.g. Pushlogic interpreter, Network Interface Management API and so on..)
These Pebbles have no pro-active behaviour of their own: they are driven by a small Pushlogic Program.

The relay and thermostat pebble exports a pair of tuples: one for sensing and one for control. Two tuples are required so that the thermostats can be at a higher level than the relays. This is because Pushlogic is only able to make connections between tuples of different levels. As the relays are going to be influenced by the thermostats, they must be held in separate tuples. For similar reasons, the display and keypad pebble must have at least two tuples.

Tuples and Fields in each Pebble

Complete Pushlogic Source Code

The canned app of the Heating Controller is as follows (actually now it is in two separate bundles, so this is old):

//
// pushlogic version 0
// Heating Controller

 inout   Heating#Control#Furnace      : {0: 1};
 inout   Heating#Control#Pump         : {0: 1};
 input   Heating#Sense#TankThermostat : {0: 1};
 input   Heating#Sense#RoomThermostat : {0: 1};
 output  HVAC#Control#Light1          : {0: 1};


  // This constraint is physically implemented by the hardware interlock, but
  // it is a good idea to state it in the PushLogic as well.
  // The hardware interlock is mirrored as a software interlock that pushes 
  // back on the Furnace and Pump control fields.

  always (Heating#Sense#RoomThermostat==0 && Heating#Sense#TankThermostat==0) => 
            Heating#Control#Furnace==0 && Heating#Control#Pump== 0;

    // Annunciator leds:
   output Display#leds#HotWater       : {0: 1};
   output Display#leds#CentralHeating : {0: 1};
   output Display#leds#Hour   : {off: 0-23};
   output Display#leds#Minute : {off: 0-59};

   input Keypad#Keys#index    : {0: up down fast_up fast_down};
   input Keypad#Keys#override : {0: 1};

   input Keypad#Keys#ModeSelect : { 0: CurrentTime Heating_On1_Time Heating_On0_Time Heating_Off1_Time Heating_Off0_Time Water_On1_Time Water_On0_Time Water_Off1_Time Water_Off0_Time };
   

   // Communication with the timer Pebble:
   input Timer#Timenow#hour : {0-23},     Timer#Timenow#minute : {0-59};

   // Local state variables    
   local Times#Heating_On0#Hour : {0-23}, Times#Heating_On0#Minute : {0-59};
   local Times#Heating_Off0#Hour: {0-23}, Times#Heating_Off0#Minute: {0-59};
   local Times#Heating_On1#Hour : {0-23}, Times#Heating_On1#Minute : {0-59};
   local Times#Heating_Off1#Hour: {0-23}, Times#Heating_Off1#Minute: {0-59};
   local Times#Water_On0#Hour   : {0-23}, Times#Water_On0#Minute   : {0-59};
   local Times#Water_Off0#Hour  : {0-23}, Times#Water_Off0#Minute  : {0-59};
   local Times#Water_On1#Hour   : {0-23}, Times#Water_On1#Minute   : {0-59};
   local Times#Water_Off1#Hour  : {0-23}, Times#Water_Off1#Minute  : {0-59};


   local HEATING : {0:1}, WATER : {0:1};
   with Timer#Timenow
   {
      macro heating_on =
	(Times#Heating_On0#(Hour, Minute) == #(hour, minute)) || 
	(Times#Heating_On1#(Hour, Minute) == #(hour, minute));

      macro water_on = 
	(Times#Water_On0#(Hour, Minute) == #(hour, minute)) || 
	(Times#Water_On1#(Hour, Minute) == #(hour, minute));

      macro heating_off = 
	(Times#Heating_Off0#(Hour, Minute) == #(hour, minute)) || 
	(Times#Heating_Off1#(Hour, Minute) == #(hour, minute));

      macro water_off = 
	(Times#Water_Off0#(Hour, Minute) == #(hour, minute)) || 
	(Times#Water_Off1#(Hour, Minute) == #(hour, minute));

   } 


   // Front panel override button
   if (^Keypad#Keys#override && Keypad#Keys#override=="1")
 	 HEATING := ! (HEATING);
   else
        // Give priority to off if off and on times are the same.
   	if (heating_off) HEATING:=0; else if (heating_on) HEATING:=1;
   if (water_off) WATER:=0; else if (water_on) WATER:=1;

   // Annunciators
   with Display#leds 
   {
      #HotWater := WATER;
      #CentralHeating := HEATING;
   }
   // Up/Down Buttons
   macro key_up = (Keypad#Keys#index == up) || (Keypad#Keys#index == fast_up);
   macro key_down = (Keypad#Keys#index == down) || (Keypad#Keys#index == fast_down);
   macro key_fast = (Keypad#Keys#index == fast_up) || (Keypad#Keys#index == fast_down);

   if (^Timer#Timenow#second || (key_fast && ^Timer#Timenow#fast_clock) || ^key_up || ^key_down)
   switch (Keypad#Keys#ModeSelect)
     { 
       case Heating_On0_Time:
         with (Times#Heating_On0)
           {
             #Minute := (key_up) ? (#Minute == 59) ? 0 : #Minute + 1 :
               (key_down) ? (#Minute == 0) ? 59 : #Minute - 1 : #Minute;
      	     #Hour := (key_up) ? ((#Minute == 59) ? ((#Hour == 23) ? 0 : #Hour + 1) : #Hour) :
               (key_down) ? ((#Minute == 0) ? ((#Hour == 0) ? 23 : #Hour - 1) : #Hour) : #Hour;
           }
       case Heating_Off0_Time:
         with (Times#Heating_Off0)
           {
             #Minute := (key_up) ? (#Minute == 59) ? 0 : #Minute + 1 :
               (key_down) ? (#Minute == 0) ? 59 : #Minute - 1 : #Minute;
      	     #Hour := (key_up) ? ((#Minute == 59) ? ((#Hour == 23) ? 0 : #Hour + 1) : #Hour) :
               (key_down) ? ((#Minute == 0) ? ((#Hour == 0) ? 23 : #Hour - 1) : #Hour) : #Hour;
           }
       case Heating_On1_Time:
         with (Times#Heating_On1)
           {
             #Minute := (key_up) ? (#Minute == 59) ? 0 : #Minute + 1 :
               (key_down) ? (#Minute == 0) ? 59 : #Minute - 1 : #Minute;
      	     #Hour := (key_up) ? ((#Minute == 59) ? ((#Hour == 23) ? 0 : #Hour + 1) : #Hour) :
               (key_down) ? ((#Minute == 0) ? ((#Hour == 0) ? 23 : #Hour - 1) : #Hour) : #Hour;
           }
       case Heating_Off1_Time:
         with (Times#Heating_Off1)
           {
             #Minute := (key_up) ? (#Minute == 59) ? 0 : #Minute + 1 :
               (key_down) ? (#Minute == 0) ? 59 : #Minute - 1 : #Minute;
      	     #Hour := (key_up) ? ((#Minute == 59) ? ((#Hour == 23) ? 0 : #Hour + 1) : #Hour) :
               (key_down) ? ((#Minute == 0) ? ((#Hour == 0) ? 23 : #Hour - 1) : #Hour) : #Hour;
           }
       case Water_On0_Time:
         with (Times#Water_On0)
           {
             #Minute := (key_up) ? (#Minute == 59) ? 0 : #Minute + 1 :
               (key_down) ? (#Minute == 0) ? 59 : #Minute - 1 : #Minute;
      	     #Hour := (key_up) ? ((#Minute == 59) ? ((#Hour == 23) ? 0 : #Hour + 1) : #Hour) :
               (key_down) ? ((#Minute == 0) ? ((#Hour == 0) ? 23 : #Hour - 1) : #Hour) : #Hour;
           }
       case Water_Off0_Time:
         with (Times#Water_Off0)
           {
             #Minute := (key_up) ? (#Minute == 59) ? 0 : #Minute + 1 :
               (key_down) ? (#Minute == 0) ? 59 : #Minute - 1 : #Minute;
      	     #Hour := (key_up) ? ((#Minute == 59) ? ((#Hour == 23) ? 0 : #Hour + 1) : #Hour) :
               (key_down) ? ((#Minute == 0) ? ((#Hour == 0) ? 23 : #Hour - 1) : #Hour) : #Hour;
           }
       case Water_On1_Time:
         with (Times#Water_On1)
           {
             #Minute := (key_up) ? (#Minute == 59) ? 0 : #Minute + 1 :
               (key_down) ? (#Minute == 0) ? 59 : #Minute - 1 : #Minute;
      	     #Hour := (key_up) ? ((#Minute == 59) ? ((#Hour == 23) ? 0 : #Hour + 1) : #Hour) :
               (key_down) ? ((#Minute == 0) ? ((#Hour == 0) ? 23 : #Hour - 1) : #Hour) : #Hour;
           }
       case Water_Off1_Time:
         with (Times#Water_Off1)
           {
             #Minute := (key_up) ? (#Minute == 59) ? 0 : #Minute + 1 :
               (key_down) ? (#Minute == 0) ? 59 : #Minute - 1 : #Minute;
      	     #Hour := (key_up) ? ((#Minute == 59) ? ((#Hour == 23) ? 0 : #Hour + 1) : #Hour) :
               (key_down) ? ((#Minute == 0) ? ((#Hour == 0) ? 23 : #Hour - 1) : #Hour) : #Hour;
           }
        }

   // LED display
   with (Times) switch (Keypad#Keys#ModeSelect)
   {
      case CurrentTime: 
         Display#leds#(Hour,Minute) := Timer#Timenow#(hour,minute);
      case Heating_Off0_Time: 
         Display#leds#(Hour,Minute) := Times#Heating_Off0#(Hour,Minute);
      case Heating_Off1_Time: 
         Display#leds#(Hour,Minute) := Times#Heating_Off1#(Hour,Minute);
      case Heating_On0_Time: 
         Display#leds#(Hour,Minute) := Times#Heating_On0#(Hour,Minute);
      case Heating_On1_Time: 
         Display#leds#(Hour,Minute) := Times#Heating_On1#(Hour,Minute);
      case Water_Off0_Time: 
         Display#leds#(Hour,Minute) := Times#Water_Off0#(Hour,Minute);
      case Water_Off1_Time: 
         Display#leds#(Hour,Minute) := Times#Water_Off1#(Hour,Minute);
      case Water_On0_Time: 
         Display#leds#(Hour,Minute) := Times#Water_On0#(Hour,Minute);
      case Water_On1_Time: 
         Display#leds#(Hour,Minute) := Times#Water_On1#(Hour,Minute);
      default:
         // Better would be a five second delay before going blank.
         Display#leds#(Hour,Minute) := (off, off);

   }

   // Main output controls 
   with (Heating)
   {
     #Control#Furnace := (HEATING && !(#Sense#RoomThermostat)) || (WATER && !(#Sense#TankThermostat));
     #Control#Pump := (HEATING && !(#Sense#RoomThermostat));
   }
// EOF

As you can see, this code will become a bit shorter when we have implemented arrays in the pushlogic!

The pebble statement can be used at the start of the script (or in a separate file) to redirect field references. Therefore, with the following addition, the Pushlogic program can be hosted on a remote platform, yet still interact with the heating unit's physical I/O devices.

 pebble Heating = "tup:/12.323.44.23/devices/Heating";
 pebble Display = "tup:/12.323.44.23/devices/Display";
 pebble Keypad = "tup:/12.323.44.23/devices/Keypad";
 pebble Timer = $$/Timer;

This all works today and can be demonstrated in FN12.

Security Light Function

The device serves another separate function. It has an additional fused 240V AC switched output that is intended to control a security light. This can be controlled by a separate program, either loaded over the network and hosted on the local Pushlogic execution platform or executed remotely. The application that controls it simulates occupancy of the home.

2016 Decommissioned

Ten years have passed. Here it is, just before removal, installed in the cupboard under the stairs in a house.

An early UPnP Internet-connected domestic central heating controller

In 2016 this unit was taken out of service since commercial Internet-connected devices like Hive Nest and HeatMiser Neo are now available to replace it. These too are UPnP devices in that they use DHCP but they do not publish their API to enable home automation applications.


Project HOME