Andrew Boyson / oldheating

Dependencies:   net 1-wire lpc1768 crypto clock web fram log

Files at this revision

API Documentation at this revision

Comitter:
andrewboyson
Date:
Tue Feb 23 20:35:07 2021 +0000
Parent:
104:46ce1aaf8be7
Child:
106:41ed3ea0bbba
Commit message:
Added ability to set the minimum flow rate and removed the correction to delta T relative to speed. Adding the round circuit time and linked to speed. Next task is to linearize the flow.

Changed in this revision

1-wire.lib Show annotated file Show diff for this revision Revisions of this file
heating/boiler.c Show annotated file Show diff for this revision Revisions of this file
heating/boiler.h Show annotated file Show diff for this revision Revisions of this file
heating/values.c Show annotated file Show diff for this revision Revisions of this file
web-this/boiler/web-boiler-ajax.c Show annotated file Show diff for this revision Revisions of this file
web-this/boiler/web-boiler-html.c Show annotated file Show diff for this revision Revisions of this file
web-this/boiler/web-boiler-query.c Show annotated file Show diff for this revision Revisions of this file
web-this/boiler/web-boiler-script.inc Show annotated file Show diff for this revision Revisions of this file
web-this/boiler/web-boiler-script.js Show annotated file Show diff for this revision Revisions of this file
--- a/1-wire.lib	Wed Feb 10 17:24:36 2021 +0000
+++ b/1-wire.lib	Tue Feb 23 20:35:07 2021 +0000
@@ -1,1 +1,1 @@
-https://os.mbed.com/users/andrewboyson/code/1-wire/#b4e0b4c4e045
+https://os.mbed.com/users/andrewboyson/code/1-wire/#3859fee99d5d
--- a/heating/boiler.c	Wed Feb 10 17:24:36 2021 +0000
+++ b/heating/boiler.c	Tue Feb 23 20:35:07 2021 +0000
@@ -19,14 +19,22 @@
 #define BOILER_CALL_SET FIO2SET(5)
 #define BOILER_CALL_CLR FIO2CLR(5)
 
+#define PUMP_SPEED_CALLING_AUTO_ONLY  -1
+#define PUMP_SPEED_CALLING_AUTO_TWEAK -2
+
+#define MIN_SPEED  40
+#define MAX_SPEED 100
+
 static char*     tankRom; static int iTankRom;
 static char*   outputRom; static int iOutputRom;
 static char*   returnRom; static int iReturnRom;
 
-static int16_t tankSetPoint;       static int iTankSetPoint;
+static int8_t  fullSpeedSecs;      static int iFullSpeedSecs;
+
+static int8_t  tankSetPoint;       static int iTankSetPoint;
 static int16_t tankHysteresis;     static int iTankHysteresis;
 static int16_t runOnResidual16ths; static int iRunOnResidual;
-static uint8_t  runOnTime2s;       static int iRunOnTime;
+static uint8_t runOnTime2s;        static int iRunOnTime;
 
 static int8_t  boilerTarget;       static int iBoilerTarget;
 static int8_t  pumpSpeedCalling;   static int iPumpSpeedCalling;
@@ -36,71 +44,71 @@
 static int16_t rise16thsAt50;      static int iRiseAt50;
 static int16_t rise16thsAt100;     static int iRiseAt100;
 
-uint16_t BoilerGetTankDS18B20Value  () { return DS18B20ValueFromRom(tankRom);   } 
-uint16_t BoilerGetOutputDS18B20Value() { return DS18B20ValueFromRom(outputRom); } 
-uint16_t BoilerGetReturnDS18B20Value() { return DS18B20ValueFromRom(returnRom); } 
-int      BoilerGetTankSetPoint      () { return tankSetPoint;      }
-int      BoilerGetTankHysteresis    () { return tankHysteresis;    } 
-int      BoilerGetRunOnResidual16ths() { return runOnResidual16ths;}
-int      BoilerGetRunOnTime         () { return runOnTime2s << 1;  }
-int      BoilerGetPumpSpeedCalling  () { return pumpSpeedCalling;  }
-int      BoilerGetPumpSpeedRunOn    () { return pumpSpeedRunOn;    }
-int      BoilerGetOutputTarget      () { return boilerTarget;      }
-int      BoilerGetRise16thsAt0      () { return rise16thsAt0;      }
-int      BoilerGetRise16thsAt50     () { return rise16thsAt50;     }
-int      BoilerGetRise16thsAt100    () { return rise16thsAt100;    }
+//Set in main scan
+static int16_t _boilerOutput16ths   = DS18B20_ERROR_VALUE_NOT_SET;
+static int16_t _boilerReturn16ths   = DS18B20_ERROR_VALUE_NOT_SET;
+static int16_t _boilerRtnDel16ths   = DS18B20_ERROR_VALUE_NOT_SET;
+static int16_t _boilerDeltaT16ths   = DS18B20_ERROR_VALUE_NOT_SET;
+static bool    _boilerDeltaTisValid = false;
+
+int16_t  BoilerGetTankDS18B20Value  () { return DS18B20ValueFromRom(tankRom);   } 
+int16_t  BoilerGetOutputDS18B20Value() { return _boilerOutput16ths; } 
+int16_t  BoilerGetReturnDS18B20Value() { return _boilerReturn16ths; }
+int16_t  BoilerGetRtnDelDS18B20Value() { return _boilerRtnDel16ths; }
+int16_t  BoilerGetDeltaTDS18B20Value() { return _boilerDeltaT16ths; } 
+int      BoilerGetFullSpeedSecs     () { return fullSpeedSecs;      }
+int      BoilerGetTankSetPoint      () { return tankSetPoint;       }
+int      BoilerGetTankHysteresis    () { return tankHysteresis;     } 
+int      BoilerGetRunOnDeltaT       () { return runOnResidual16ths; }
+int      BoilerGetRunOnTime         () { return runOnTime2s << 1;   }
+int      BoilerGetPumpSpeedCalling  () { return pumpSpeedCalling;   }
+int      BoilerGetPumpSpeedRunOn    () { return pumpSpeedRunOn;     }
+int      BoilerGetOutputTarget      () { return boilerTarget;       }
+int      BoilerGetMinimumFlow       () { return rise16thsAt0;       }
+int      BoilerGetMidFlowSpeed      () { return rise16thsAt50;      }
+int      BoilerGetFullSpeedDeltaT   () { return rise16thsAt100;     }
 
 static void setTankRom          (char* value) { memcpy(tankRom,      value, 8); FramWrite(iTankRom,         8,  tankRom            ); }
 static void setOutputRom        (char* value) { memcpy(outputRom,    value, 8); FramWrite(iOutputRom,       8,  outputRom          ); }
 static void setReturnRom        (char* value) { memcpy(returnRom,    value, 8); FramWrite(iReturnRom,       8,  returnRom          ); }
-void BoilerSetTankSetPoint      (int   value) { tankSetPoint       = value;     FramWrite(iTankSetPoint,    2, &tankSetPoint       ); }
+void BoilerSetFullSpeedSecs     (int   value) { fullSpeedSecs      = value;     FramWrite(iFullSpeedSecs,   1, &fullSpeedSecs      ); }
+void BoilerSetTankSetPoint      (int   value) { tankSetPoint       = value;     FramWrite(iTankSetPoint,    1, &tankSetPoint       ); }
 void BoilerSetTankHysteresis    (int   value) { tankHysteresis     = value;     FramWrite(iTankHysteresis,  2, &tankHysteresis     ); }
-void BoilerSetRunOnResidual16ths(int   value) { runOnResidual16ths = value;     FramWrite(iRunOnResidual,   2, &runOnResidual16ths ); }
+void BoilerSetRunOnDeltaT       (int   value) { runOnResidual16ths = value;     FramWrite(iRunOnResidual,   2, &runOnResidual16ths ); }
 void BoilerSetRunOnTime         (int   value) { runOnTime2s        = value >> 1;FramWrite(iRunOnTime,       1, &runOnTime2s        ); }
 void BoilerSetPumpSpeedCalling  (int   value) { pumpSpeedCalling   = value;     FramWrite(iPumpSpeedCalling,1, &pumpSpeedCalling   ); }
 void BoilerSetPumpSpeedRunOn    (int   value) { pumpSpeedRunOn     = value;     FramWrite(iPumpSpeedRunOn,  1, &pumpSpeedRunOn     ); }
 void BoilerSetOutputTarget      (int   value) { boilerTarget       = value;     FramWrite(iBoilerTarget,    1, &boilerTarget       ); }
-void BoilerSetRise16thsAt0      (int   value) { rise16thsAt0       = value;     FramWrite(iRiseAt0,         2, &rise16thsAt0       ); }
-void BoilerSetRise16thsAt50     (int   value) { rise16thsAt50      = value;     FramWrite(iRiseAt50,        2, &rise16thsAt50      ); }
-void BoilerSetRise16thsAt100    (int   value) { rise16thsAt100     = value;     FramWrite(iRiseAt100,       2, &rise16thsAt100     ); }
+void BoilerSetMinimumFlow       (int   value) { rise16thsAt0       = value;     FramWrite(iRiseAt0,         2, &rise16thsAt0       ); }
+void BoilerSetMidFlowSpeed      (int   value) { rise16thsAt50      = value;     FramWrite(iRiseAt50,        2, &rise16thsAt50      ); }
+void BoilerSetFullSpeedDeltaT   (int   value) { rise16thsAt100     = value;     FramWrite(iRiseAt100,       2, &rise16thsAt100     ); }
 
 static int calculateBetweenTwoPoints(int x, int xA, int xB, int yA, int yB)
 {
     float m = (float)(yB - yA) / (xB - xA);
     return  yA + m * (x - xA);
 }
-static int oldcalculateBetweenTwoPoints(int point16ths, int pointBig16ths, int pointSmall16ths, int bigSpeed, int width) //width = 1 for 100; 2 for 50 and 4 for 25
-{
-    //Expected to have to compensate the slope for the reduced width but didn't need to.
-    int slope = pointBig16ths - pointSmall16ths;
-    int diff16ths = point16ths - pointSmall16ths;
-    int diffSpeed = (diff16ths * slope * width) >> 8; //points are in 16ths so multiplying them needs a division by 256
-    int requiredSpeed = bigSpeed - diffSpeed;
-    if (requiredSpeed > 100) requiredSpeed = 100;
-    if (requiredSpeed <   0) requiredSpeed = 0;
-    return requiredSpeed;
-}
-
-static int calculatePoint(int targetRise16ths)
+static int calculateSpeedFromDeltaT(int deltaT16ths)
 {
-    if (targetRise16ths > rise16thsAt0) //20
-    {
-        return 0;
-    }
-    else if (targetRise16ths > rise16thsAt50) //15
-    {
-        return calculateBetweenTwoPoints(targetRise16ths, rise16thsAt50, rise16thsAt0, 50, 0);
-        //return oldcalculateBetweenTwoPoints(targetRise16ths, rise16thsAt0, rise16thsAt50, 50, 2);
-    }
-    else if (targetRise16ths > rise16thsAt100) //10
-    {
-        return calculateBetweenTwoPoints(targetRise16ths, rise16thsAt100, rise16thsAt50, 100, 50);
-        //return oldcalculateBetweenTwoPoints(targetRise16ths, rise16thsAt50, rise16thsAt100, 100, 2);
-    }
-    else
-    {
-       return 100;
-    }
+    if (deltaT16ths < rise16thsAt100) return MAX_SPEED; //Needed in case deltaT16ths is negative or zero
+    int flow = MAX_SPEED * rise16thsAt100 / deltaT16ths; //eg for 20 deg ==> 100 * (10 << 4) / (20 << 4) == 50
+    if (flow > MAX_SPEED) flow = MAX_SPEED;
+    if (flow < MIN_SPEED) flow = MIN_SPEED;
+    return flow;
+    
+    //if      (deltaT16ths > rise16thsAt0  ) return MIN_SPEED;
+    //else if (deltaT16ths > rise16thsAt50 ) return calculateBetweenTwoPoints(deltaT16ths, rise16thsAt50,  rise16thsAt0,  MID_SPEED, MIN_SPEED);
+    //else if (deltaT16ths > rise16thsAt100) return calculateBetweenTwoPoints(deltaT16ths, rise16thsAt100, rise16thsAt50, MAX_SPEED, MID_SPEED);
+    //else                                   return MAX_SPEED;
+}
+static int calculateDeltaTFromSpeed(int speed)
+{
+    int deltaT16ths = MAX_SPEED * rise16thsAt100 / speed; //eg for speed = 50 ==> 100 * (10 << 4) / 50 == 20 << 4
+    //if      (speed >= MAX_SPEED) return rise16thsAt100;
+    //else if (speed >  MID_SPEED) return calculateBetweenTwoPoints(speed, MID_SPEED, MAX_SPEED, rise16thsAt50, rise16thsAt100);
+    //else if (speed >  MIN_SPEED) return calculateBetweenTwoPoints(speed, MIN_SPEED, MID_SPEED, rise16thsAt0,  rise16thsAt50 );
+    //else                         return rise16thsAt0;
+    return deltaT16ths;
 }
 
 int BoilerInit()
@@ -127,7 +135,8 @@
                   address = FramLoad( 8,  tankRom,                0); if (address < 0) return -1; iTankRom          = address;
                   address = FramLoad( 8,  outputRom,              0); if (address < 0) return -1; iOutputRom        = address;
                   address = FramLoad( 8,  returnRom,              0); if (address < 0) return -1; iReturnRom        = address;
-    def2 =    65; address = FramLoad( 2, &tankSetPoint,       &def2); if (address < 0) return -1; iTankSetPoint     = address;
+    def1 =   100; address = FramLoad( 1, &fullSpeedSecs,      &def1); if (address < 0) return -1; iFullSpeedSecs    = address;
+    def1 =    65; address = FramLoad( 1, &tankSetPoint,       &def1); if (address < 0) return -1; iTankSetPoint     = address;
     def2 =     5; address = FramLoad( 2, &tankHysteresis,     &def2); if (address < 0) return -1; iTankHysteresis   = address;
     def2 =     2; address = FramLoad( 2, &runOnResidual16ths, &def2); if (address < 0) return -1; iRunOnResidual    = address;
     def1 =   180; address = FramLoad( 1, &runOnTime2s,        &def1); if (address < 0) return -1; iRunOnTime        = address;
@@ -143,35 +152,33 @@
 
     PwmInit(400, 100);
 
-    for (int deltaT = 30; deltaT > 0; deltaT--)
-    {
-        LogF("DeltaT %d ==> speed %d\r\n", deltaT, calculatePoint(deltaT << 4));
-    }
-
     return 0;
 }
+bool BoilerCallEnable = true;
 bool BoilerCall = false;
 static void controlBoilerCall()
 {
-    int tankTemp16ths = DS18B20ValueFromRom(tankRom);
-    if (DS18B20IsValidValue(tankTemp16ths)) //Ignore values which are likely to be wrong
+    if (BoilerCallEnable)
     {
-        int  tankUpper16ths = tankSetPoint   << 4;
-        int hysteresis16ths = tankHysteresis << 4;
-        int  tankLower16ths = tankUpper16ths - hysteresis16ths;
-    
-        if (tankTemp16ths >= tankUpper16ths) BoilerCall = false;
-        if (tankTemp16ths <= tankLower16ths) BoilerCall = true;
+        int tankTemp16ths = DS18B20ValueFromRom(tankRom);
+        if (DS18B20IsValidValue(tankTemp16ths)) //Ignore values which are likely to be wrong
+        {
+            int  tankUpper16ths = tankSetPoint   << 4;
+            int hysteresis16ths = tankHysteresis << 4;
+            int  tankLower16ths = tankUpper16ths - hysteresis16ths;
+        
+            if (tankTemp16ths >= tankUpper16ths) BoilerCall = false;
+            if (tankTemp16ths <= tankLower16ths) BoilerCall = true;
+        }
+    }
+    else
+    {
+        BoilerCall = false;
     }
 }
 bool BoilerPump = false;
 static void controlBoilerPump()
 {
-    int16_t boilerOutput16ths   = DS18B20ValueFromRom(outputRom);
-    int16_t boilerReturn16ths   = DS18B20ValueFromRom(returnRom);
-    int16_t boilerResidual16ths = boilerOutput16ths - boilerReturn16ths;
-    bool    boilerTempsAreValid = DS18B20IsValidValue(boilerOutput16ths) && DS18B20IsValidValue(boilerReturn16ths);
-
     static uint32_t msTimerBoilerPumpRunOn = 0;
     if (BoilerCall)
     {
@@ -181,25 +188,21 @@
     else
     {
         if (MsTimerRelative(msTimerBoilerPumpRunOn,      runOnTime2s * 2000)) BoilerPump = false;
-        if (boilerTempsAreValid && boilerResidual16ths < runOnResidual16ths ) BoilerPump = false;
+        if (_boilerDeltaTisValid && _boilerDeltaT16ths < runOnResidual16ths ) BoilerPump = false;
     }
 }
-int BoilerPumpSpeed = 0;
+int BoilerPumpFlow  = MIN_SPEED;
+int BoilerPumpSpeed = MIN_SPEED;
 int BoilerPumpPwm   = 0;
 static int _autoSpeed = 0;
 static void calculateAutoSpeed()
 {
-    int16_t boilerOutput16ths   = DS18B20ValueFromRom(outputRom);
-    int16_t boilerReturn16ths   = DS18B20ValueFromRom(returnRom);
-    int16_t boilerResidual16ths = boilerOutput16ths - boilerReturn16ths;
-    bool    boilerTempsAreValid = DS18B20IsValidValue(boilerOutput16ths) && DS18B20IsValidValue(boilerReturn16ths);
-    
-    if (!boilerTempsAreValid) return;
+    if (!DS18B20IsValidValue(_boilerReturn16ths)) return;
     
     int target16ths = (int)boilerTarget << 4;
-    int targetRise16ths = target16ths - boilerReturn16ths; //eg 65 - eg 45 = 20*16 16ths
+    int targetRise16ths = target16ths - _boilerReturn16ths; //eg 65 - eg 45 = 20*16 16ths
     
-    _autoSpeed = calculatePoint(targetRise16ths);
+    _autoSpeed = calculateSpeedFromDeltaT(targetRise16ths);
 }
 static void controlBoilerPumpSpeed()
 {
@@ -207,8 +210,8 @@
     calculateAutoSpeed();
     if (BoilerCall)
     {
-        if (pumpSpeedCalling > 100 || pumpSpeedCalling < 0) BoilerPumpSpeed = _autoSpeed;        //Auto
-        else                                                BoilerPumpSpeed = pumpSpeedCalling;  //Manual
+        if (pumpSpeedCalling > MAX_SPEED || pumpSpeedCalling < MIN_SPEED) BoilerPumpSpeed = _autoSpeed;        //Auto
+        else                                                              BoilerPumpSpeed = pumpSpeedCalling;  //Manual
         msTimerReduction = MsTimerCount;
     }
     else
@@ -223,6 +226,10 @@
         }
     }
 }
+static void flowToSpeed()
+{
+    //Do nothing yet
+}
 static void speedToPwm()
 {
     /*
@@ -237,52 +244,118 @@
     Max speed 100 is at fitted = 74; pwm = 10
     Min speed   0 is at fitted =  0; pwm = 84
     */
-    int speed = BoilerPumpSpeed;
-    if (speed <   0) speed =   0;
-    if (speed > 100) speed = 100;
-    speed  *=  74;
-    speed <<=  16;
-    speed  /= 100;
-    speed >>=  16;
-    BoilerPumpPwm = 84 - speed;
+    int pwm = calculateBetweenTwoPoints(BoilerPumpSpeed, MIN_SPEED, MAX_SPEED, 84, 10);
+    if (pwm < 10) pwm = 10;
+    if (pwm > 84) pwm = 84;
+    BoilerPumpPwm = pwm;
 }
 #define TIME_BEFORE_TWEAK_SECS 120
 static void tweakDeltaTs()
 {
+    if (pumpSpeedCalling != PUMP_SPEED_CALLING_AUTO_TWEAK) return;
+    
     static uint32_t msTimerBoilerHeating = 0;
     if (!BoilerCall) msTimerBoilerHeating = MsTimerCount;
     if (!MsTimerRelative(msTimerBoilerHeating, TIME_BEFORE_TWEAK_SECS * 1000)) return;
     
-    int16_t boilerOutput16ths   = DS18B20ValueFromRom(outputRom);
-    int16_t boilerReturn16ths   = DS18B20ValueFromRom(returnRom);
-    int16_t boilerResidual16ths = boilerOutput16ths - boilerReturn16ths;
-    bool    boilerTempsAreValid = DS18B20IsValidValue(boilerOutput16ths) && DS18B20IsValidValue(boilerReturn16ths);
-    if (!boilerTempsAreValid) return;
+    if (!_boilerDeltaTisValid) return;
     
     static int speedLastScan = -1;
     
-    if (speedLastScan == 0 && BoilerPumpSpeed > 0)
-    {
-        if (rise16thsAt0 > boilerResidual16ths) rise16thsAt0--;
-        if (rise16thsAt0 < boilerResidual16ths) rise16thsAt0++;
-    }
-    
-    else if (speedLastScan <= 50 && BoilerPumpSpeed > 50)
+    if (speedLastScan < MAX_SPEED && BoilerPumpSpeed == MAX_SPEED)
     {
-        if (rise16thsAt50 > boilerResidual16ths) rise16thsAt50--;
-        if (rise16thsAt50 < boilerResidual16ths) rise16thsAt50++;
-    }
-    
-    else if (speedLastScan < 100 && BoilerPumpSpeed == 100)
-    {
-        if (rise16thsAt100 > boilerResidual16ths) rise16thsAt100--;
-        if (rise16thsAt100 < boilerResidual16ths) rise16thsAt100++;
+        if (rise16thsAt100 > _boilerDeltaT16ths) rise16thsAt100--;
+        if (rise16thsAt100 < _boilerDeltaT16ths) rise16thsAt100++;
     }
     
     speedLastScan = BoilerPumpSpeed;
 }
+
+#define TIME_BEFORE_DELTA_T_ALARM_SECS 300
+#define DELTA_T_LIMIT (3 << 4)
+static void checkDeltaTs()
+{   
+    static uint32_t msTimerDeltaTNonConform = 0;
+    if (!BoilerCall)
+    {
+        msTimerDeltaTNonConform = MsTimerCount;
+        return;
+    }
+    
+    int expectedDeltaT16ths = calculateDeltaTFromSpeed(BoilerPumpSpeed);
+    
+    bool deltaTisOk = _boilerDeltaTisValid &&
+                      _boilerDeltaT16ths > (expectedDeltaT16ths - DELTA_T_LIMIT) &&
+                      _boilerDeltaT16ths < (expectedDeltaT16ths + DELTA_T_LIMIT);
+    
+    
+    static bool deltaTwasOk = true;
+    
+    /*
+    if (deltaTwasOk != deltaTisOk)
+    {
+        LogTimeF("Boiler delta T ");
+        DS18B20Log(_boilerDeltaT16ths);
+        if (deltaTisOk)
+        {
+            Log(" is inside expected value ");
+        }
+        else
+        {
+            Log(" is outside expected value ");
+        }
+        DS18B20Log(expectedDeltaT16ths);
+        Log("\r\n");
+    }
+    
+    deltaTwasOk = deltaTisOk;
+    */
+    
+    static bool hadAlarm = false;
+    if (deltaTisOk) msTimerDeltaTNonConform = MsTimerCount;
+    bool haveAlarm = MsTimerRelative(msTimerDeltaTNonConform, TIME_BEFORE_DELTA_T_ALARM_SECS * 1000);
+    if (haveAlarm && !hadAlarm)
+    {
+        LogTimeF("Boiler delta T would have tripped after not being ok for %d seconds\r\n", TIME_BEFORE_DELTA_T_ALARM_SECS);
+    }
+    hadAlarm = haveAlarm;
+}
+#define NUMBER_OF_STEPS 10
+static int16_t _returns16ths[NUMBER_OF_STEPS]; //0 is last, 9th is first
+static void delayLine()
+{
+    static uint32_t msTimerDelay = 0;
+    if (BoilerPump)
+    {
+        int msTotal = 1000 * fullSpeedSecs * MAX_SPEED / BoilerPumpSpeed; //speed 10 ==> 10000; speed 100 ==> 1000
+        int msPerStep = msTotal / NUMBER_OF_STEPS;
+        if (MsTimerRelative(msTimerDelay, msPerStep))
+        {
+            for (int i = 0; i < NUMBER_OF_STEPS - 1; i++) _returns16ths[i] = _returns16ths[i + 1];
+            _returns16ths[NUMBER_OF_STEPS - 1] = _boilerReturn16ths;
+            msTimerDelay = MsTimerCount;
+            //LogTimeF("Ms per step = %d, delayed boiler return = ", msPerStep);
+            //DS18B20Log(_returns16ths[0]);
+            //Log("\r\n");
+        }
+    }
+    else
+    {
+        msTimerDelay = MsTimerCount;
+        for (int i = 0; i < NUMBER_OF_STEPS; i++) _returns16ths[i] = DS18B20_ERROR_VALUE_NOT_SET;
+    }
+}
+
 void BoilerMain()
 {
+    delayLine();
+    _boilerOutput16ths   = DS18B20ValueFromRom(outputRom);
+    _boilerReturn16ths   = DS18B20ValueFromRom(returnRom);
+    _boilerRtnDel16ths   = _returns16ths[0];
+    _boilerDeltaTisValid = DS18B20IsValidValue(_boilerOutput16ths) && DS18B20IsValidValue(_boilerRtnDel16ths);
+    if (_boilerDeltaTisValid) _boilerDeltaT16ths   = _boilerOutput16ths - _boilerRtnDel16ths;
+    else                      _boilerDeltaT16ths   = DS18B20_ERROR_VALUE_NOT_SET;
+    
     controlBoilerCall();
     if (BoilerCall) BOILER_CALL_SET;
     else            BOILER_CALL_CLR;
@@ -292,9 +365,10 @@
     else            BOILER_PUMP_CLR;
     
     controlBoilerPumpSpeed();
+    flowToSpeed();
     speedToPwm();
     PwmSet(BoilerPumpPwm);
     
     tweakDeltaTs();
-    
+    checkDeltaTs();
 }
\ No newline at end of file
--- a/heating/boiler.h	Wed Feb 10 17:24:36 2021 +0000
+++ b/heating/boiler.h	Tue Feb 23 20:35:07 2021 +0000
@@ -1,25 +1,30 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-extern int      BoilerGetTankSetPoint      (void); extern void BoilerSetTankSetPoint      (int value);
-extern int      BoilerGetTankHysteresis    (void); extern void BoilerSetTankHysteresis    (int value);
-extern int      BoilerGetRunOnResidual16ths(void); extern void BoilerSetRunOnResidual16ths(int value);
-extern int      BoilerGetRunOnTime         (void); extern void BoilerSetRunOnTime         (int value);
-extern int      BoilerGetPumpSpeedCalling  (void); extern void BoilerSetPumpSpeedCalling  (int value);
-extern int      BoilerGetPumpSpeedRunOn    (void); extern void BoilerSetPumpSpeedRunOn    (int value);
-extern int      BoilerGetOutputTarget      (void); extern void BoilerSetOutputTarget      (int value);
+extern int     BoilerGetFullSpeedSecs     (void); extern void BoilerSetFullSpeedSecs     (int value);
+extern int     BoilerGetFullSpeedDeltaT   (void); extern void BoilerSetFullSpeedDeltaT   (int value);
+extern int     BoilerGetTankSetPoint      (void); extern void BoilerSetTankSetPoint      (int value);
+extern int     BoilerGetTankHysteresis    (void); extern void BoilerSetTankHysteresis    (int value);
+extern int     BoilerGetRunOnDeltaT       (void); extern void BoilerSetRunOnDeltaT       (int value);
+extern int     BoilerGetRunOnTime         (void); extern void BoilerSetRunOnTime         (int value);
+extern int     BoilerGetPumpSpeedCalling  (void); extern void BoilerSetPumpSpeedCalling  (int value);
+extern int     BoilerGetPumpSpeedRunOn    (void); extern void BoilerSetPumpSpeedRunOn    (int value);
+extern int     BoilerGetOutputTarget      (void); extern void BoilerSetOutputTarget      (int value);
 
-extern int      BoilerGetRise16thsAt0      (void); extern void BoilerSetRise16thsAt0      (int value);
-extern int      BoilerGetRise16thsAt50     (void); extern void BoilerSetRise16thsAt50     (int value);
-extern int      BoilerGetRise16thsAt100    (void); extern void BoilerSetRise16thsAt100    (int value);
+extern int     BoilerGetMinimumFlow       (void); extern void BoilerSetMinimumFlow       (int value);
+extern int     BoilerGetMidFlowSpeed      (void); extern void BoilerSetMidFlowSpeed      (int value);
 
-extern uint16_t BoilerGetTankDS18B20Value  (void);
-extern uint16_t BoilerGetOutputDS18B20Value(void);
-extern uint16_t BoilerGetReturnDS18B20Value(void);
+extern int16_t BoilerGetTankDS18B20Value  (void);
+extern int16_t BoilerGetOutputDS18B20Value(void);
+extern int16_t BoilerGetReturnDS18B20Value(void);
+extern int16_t BoilerGetRtnDelDS18B20Value(void);
+extern int16_t BoilerGetDeltaTDS18B20Value(void);
 
-extern int      BoilerPumpSpeed;
-extern int      BoilerPumpPwm;
+extern int     BoilerPumpFlow;
+extern int     BoilerPumpSpeed;
+extern int     BoilerPumpPwm;
 
+extern bool BoilerCallEnable;
 extern bool BoilerCall;
 extern bool BoilerPump;
 
--- a/heating/values.c	Wed Feb 10 17:24:36 2021 +0000
+++ b/heating/values.c	Tue Feb 23 20:35:07 2021 +0000
@@ -91,7 +91,8 @@
     value &= 0x0FFF;
     record |= value; //0000 000A AABB BCCC
     record <<= 12;   //0000 AAAB BBCC C000
-    value = BoilerGetReturnDS18B20Value();
+    //value = BoilerGetReturnDS18B20Value();
+    value = BoilerGetRtnDelDS18B20Value();
     value &= 0x0FFF;
     record |= value; //0000 AAAB BBCC CDDD
     record <<= 12;   //0AAA BBBC CCDD D000
--- a/web-this/boiler/web-boiler-ajax.c	Wed Feb 10 17:24:36 2021 +0000
+++ b/web-this/boiler/web-boiler-ajax.c	Tue Feb 23 20:35:07 2021 +0000
@@ -15,23 +15,27 @@
     HttpAddInt16AsHex(BoilerGetTankDS18B20Value()  ); HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerGetOutputDS18B20Value()); HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerGetReturnDS18B20Value()); HttpAddChar('\n');
+    HttpAddInt16AsHex(BoilerGetRtnDelDS18B20Value()); HttpAddChar('\n');
+    HttpAddInt16AsHex(BoilerGetDeltaTDS18B20Value()); HttpAddChar('\n');
     
     int nibble = 0;
     if (BoilerCall           ) nibble |= 1;
     if (BoilerPump           ) nibble |= 2;
+    if (BoilerCallEnable     ) nibble |= 4;
     HttpAddNibbleAsHex (nibble); HttpAddChar('\n');
     
+    HttpAddInt16AsHex(BoilerGetFullSpeedSecs     ()); HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerGetTankSetPoint      ()); HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerGetTankHysteresis    ()); HttpAddChar('\n');
-    HttpAddInt16AsHex(BoilerGetRunOnResidual16ths()); HttpAddChar('\n');
+    HttpAddInt16AsHex(BoilerGetRunOnDeltaT       ()); HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerGetRunOnTime         ()); HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerPumpSpeed);               HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerPumpPwm);                 HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerGetPumpSpeedCalling  ()); HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerGetPumpSpeedRunOn    ()); HttpAddChar('\n');
     HttpAddByteAsHex (BoilerGetOutputTarget      ()); HttpAddChar('\n');
-    HttpAddInt16AsHex(BoilerGetRise16thsAt0      ()); HttpAddChar('\n');
-    HttpAddInt16AsHex(BoilerGetRise16thsAt50     ()); HttpAddChar('\n');
-    HttpAddInt16AsHex(BoilerGetRise16thsAt100    ()); HttpAddChar('\n');
+    HttpAddInt16AsHex(BoilerGetMinimumFlow       ()); HttpAddChar('\n');
+    HttpAddInt16AsHex(BoilerGetMidFlowSpeed      ()); HttpAddChar('\n');
+    HttpAddInt16AsHex(BoilerGetFullSpeedDeltaT   ()); HttpAddChar('\n');
 }
 
--- a/web-this/boiler/web-boiler-html.c	Wed Feb 10 17:24:36 2021 +0000
+++ b/web-this/boiler/web-boiler-html.c	Tue Feb 23 20:35:07 2021 +0000
@@ -21,25 +21,30 @@
 
     WebAddH2("Pump outputs");
     WebAddAjaxLed("Boiler pump", "ajax-blr-pump-toggle");
-    WebAddAjaxLabelledSuffix("Boiler pump speed (0-100)",      "ajax-blr-pump-speed-html", "%");
+    WebAddAjaxLabelledSuffix("Boiler pump speed (10-100)",     "ajax-blr-pump-speed-html", "%");
     WebAddAjaxLabelledSuffix("Boiler pump pwm &nbsp;(84-10)",  "ajax-blr-pump-pwm-html"  , "%");
     
     WebAddH2("Pump inputs");
-    WebAddAjaxLabelledSuffix("Boiler output",      "ajax-blr-out-html",    "&deg;C");
-    WebAddAjaxLabelledSuffix("Boiler input",       "ajax-blr-rtn-html",    "&deg;C");
-    WebAddAjaxLabelledSuffix("Boiler &Delta;T",    "ajax-blr-rise-html",   "&deg;C");
+    WebAddAjaxLabelledSuffix("Boiler output",        "ajax-blr-out-html",  "&deg;C");
+    WebAddAjaxLabelledSuffix("Boiler input",         "ajax-blr-rtn-html",  "&deg;C");
+    WebAddAjaxLabelledSuffix("Boiler input aligned", "ajax-blr-aln-html",  "&deg;C");
+    WebAddAjaxLabelledSuffix("Boiler &Delta;T",      "ajax-blr-rise-html", "&deg;C");
 
-    WebAddH2("Pump calling parameters");
-    WebAddAjaxInput("Pump speed calling (% or -1)",2, "ajax-pump-speed-calling", "pumpspeedcalling"  );
-    WebAddAjaxInput("Boiler output target (deg)",  2, "ajax-blr-output-target",  "boileroutputtarget");
-    WebAddAjaxInput("Boiler &Delta;T at pump 0",   3, "ajax-pump-rise-at-0"    , "blrriseat0"        );
-    WebAddAjaxInput("Boiler &Delta;T at pump 50",  3, "ajax-pump-rise-at-50"   , "blrriseat50"       );
-    WebAddAjaxInput("Boiler &Delta;T at pump 100", 3, "ajax-pump-rise-at-100"  , "blrriseat100"      );
+    WebAddH2("Pump parameters");
+    WebAddAjaxInput("Full speed circuit time (sec)" , 2, "ajax-full-speed-secs"   , "fullspeedsecs"     );
+    
+    WebAddH2("Pump parameters during boiler call");
+    WebAddAjaxInputToggle("Boiler call enable", "ajax-blr-enable-toggle",      "boilercallenable");
+    WebAddAjaxInput("Calling pump speed (%, A or T)", 2, "ajax-pump-speed-calling", "pumpspeedcalling"  );
+    WebAddAjaxInput("Boiler output target (deg)"    , 2, "ajax-blr-output-target" , "boileroutputtarget");
+    WebAddAjaxInput("Minimum flow (%)"              , 3, "ajax-pump-rise-at-0"    , "blrriseat0"        );
+    WebAddAjaxInput("Mid flow speed"                , 3, "ajax-pump-rise-at-50"   , "blrriseat50"       );
+    WebAddAjaxInput("Full speed &Delta;T"           , 3, "ajax-pump-rise-at-100"  , "blrriseat100"      );
 
-    WebAddH2("Pump run on parameters");
-    WebAddAjaxInput("Run on residual heat (deg)",  2, "ajax-blr-run-on-deg",     "boilerresidual"    );
-    WebAddAjaxInput("Run on max time (sec)",       2, "ajax-blr-run-on-time",    "boilerrunon"       );
-    WebAddAjaxInput("Run on pump speed (%)",       2, "ajax-pump-speed-run-on",  "pumpspeedrunon"    );
+    WebAddH2("Pump parameters during run on");
+    WebAddAjaxInput("Run on min &Delta;T"           , 2, "ajax-blr-run-on-deg",     "boilerresidual"    );
+    WebAddAjaxInput("Run on max time (sec)"         , 2, "ajax-blr-run-on-time",    "boilerrunon"       );
+    WebAddAjaxInput("Run on pump speed (%)"         , 2, "ajax-pump-speed-run-on",  "pumpspeedrunon"    );
 
     WebAddEnd();
     
--- a/web-this/boiler/web-boiler-query.c	Wed Feb 10 17:24:36 2021 +0000
+++ b/web-this/boiler/web-boiler-query.c	Tue Feb 23 20:35:07 2021 +0000
@@ -10,7 +10,19 @@
         char* pName;
         char* pValue;
         pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
-                    
+        
+        
+        if (HttpSameStr(pName, "boilercallenable"))
+        {
+            BoilerCallEnable = !BoilerCallEnable;
+        }
+        
+        if (HttpSameStr(pName, "fullspeedsecs"  ))
+        {
+            int value = HttpQueryValueAsInt(pValue);
+            BoilerSetFullSpeedSecs(value);
+            return;
+        }
         if (HttpSameStr(pName, "tanksetpoint"  ))
         {
             int value = HttpQueryValueAsInt(pValue);
@@ -26,7 +38,7 @@
         if (HttpSameStr(pName, "boilerresidual"))
         {
             double value = HttpQueryValueAsDouble(pValue);
-            BoilerSetRunOnResidual16ths((int)(value * 16));
+            BoilerSetRunOnDeltaT((int)(value * 16));
             return;
         }
         if (HttpSameStr(pName, "boilerrunon"   ))
@@ -37,6 +49,15 @@
         }
         if (HttpSameStr(pName, "pumpspeedcalling"))
         {
+            if (!pValue) return;
+            switch (pValue[0])
+            {
+                case  0 : return;
+                case 'a':
+                case 'A': BoilerSetPumpSpeedCalling(-1); return;
+                case 't':
+                case 'T': BoilerSetPumpSpeedCalling(-2); return;
+            }
             int value = HttpQueryValueAsInt(pValue);
             BoilerSetPumpSpeedCalling(value);
             return;
@@ -56,19 +77,19 @@
         if (HttpSameStr(pName, "blrriseat0"))
         {
             double value = HttpQueryValueAsDouble(pValue);
-            BoilerSetRise16thsAt0((int)(value * 16));
+            BoilerSetMinimumFlow(value);
             return;
         }
         if (HttpSameStr(pName, "blrriseat50"))
         {
             double value = HttpQueryValueAsDouble(pValue);
-            BoilerSetRise16thsAt50((int)(value * 16));
+            BoilerSetMidFlowSpeed(value);
             return;
         }
         if (HttpSameStr(pName, "blrriseat100"))
         {
             double value = HttpQueryValueAsDouble(pValue);
-            BoilerSetRise16thsAt100((int)(value * 16));
+            BoilerSetFullSpeedDeltaT((int)(value * 16));
             return;
         }
     }
--- a/web-this/boiler/web-boiler-script.inc	Wed Feb 10 17:24:36 2021 +0000
+++ b/web-this/boiler/web-boiler-script.inc	Tue Feb 23 20:35:07 2021 +0000
@@ -5,8 +5,12 @@
 "let   tankTemperature  = '';\n"
 "let blrOutTemperature  = '';\n"
 "let blrRtnTemperature  = '';\n"
+"let blrAlnTemperature  = '';\n"
+"let blrDeltaT          = '';\n"
+"let boilerEnable       = false;\n"
 "let boilerCall         = false;\n"
 "let boilerPump         = false;\n"
+"let fullSpeedSecs      = '';\n"
 "let tankSetPoint       = '';\n"
 "let tankHysteresis     = '';\n"
 "let blrRunOnDeg        = '';\n"
@@ -26,20 +30,26 @@
 "      tankTemperature = Ajax.hexToSignedInt16(lines[ 0]);\n"
 "    blrOutTemperature = Ajax.hexToSignedInt16(lines[ 1]);\n"
 "    blrRtnTemperature = Ajax.hexToSignedInt16(lines[ 2]);\n"
-"    boilerCall        = Ajax.hexToBit        (lines[ 3], 0);\n"
-"    boilerPump        = Ajax.hexToBit        (lines[ 3], 1);\n"
-"    tankSetPoint      = Ajax.hexToSignedInt16(lines[ 4]);\n"
-"    tankHysteresis    = Ajax.hexToSignedInt16(lines[ 5]);\n"
-"    blrRunOnDeg       = Ajax.hexToSignedInt16(lines[ 6]);\n"
-"    blrRunOnTime      = Ajax.hexToSignedInt16(lines[ 7]);\n"
-"    blrPumpSpeed      = Ajax.hexToSignedInt16(lines[ 8]);\n"
-"    blrPumpPwm        = Ajax.hexToSignedInt16(lines[ 9]);\n"
-"    pumpSpeedCalling  = Ajax.hexToSignedInt16(lines[10]);\n"
-"    pumpSpeedRunOn    = Ajax.hexToSignedInt16(lines[11]);\n"
-"    blrOutputTarget   = Ajax.hexToSignedInt16(lines[12]);\n"
-"    riseAt0           = Ajax.hexToSignedInt16(lines[13]);\n"
-"    riseAt50          = Ajax.hexToSignedInt16(lines[14]);\n"
-"    riseAt100         = Ajax.hexToSignedInt16(lines[15]);\n"
+"    blrAlnTemperature = Ajax.hexToSignedInt16(lines[ 3]);\n"
+"    blrDeltaT         = Ajax.hexToSignedInt16(lines[ 4]);\n"
+"    boilerCall        = Ajax.hexToBit        (lines[ 5], 0);\n"
+"    boilerPump        = Ajax.hexToBit        (lines[ 5], 1);\n"
+"    boilerEnable      = Ajax.hexToBit        (lines[ 5], 2);\n"
+"    fullSpeedSecs     = Ajax.hexToSignedInt16(lines[ 6]);\n"
+"    tankSetPoint      = Ajax.hexToSignedInt16(lines[ 7]);\n"
+"    tankHysteresis    = Ajax.hexToSignedInt16(lines[ 8]);\n"
+"    blrRunOnDeg       = Ajax.hexToSignedInt16(lines[ 9]);\n"
+"    blrRunOnTime      = Ajax.hexToSignedInt16(lines[10]);\n"
+"    blrPumpSpeed      = Ajax.hexToSignedInt16(lines[11]);\n"
+"    blrPumpPwm        = Ajax.hexToSignedInt16(lines[12]);\n"
+"    pumpSpeedCalling  = Ajax.hexToSignedInt16(lines[13]);\n"
+"    if (pumpSpeedCalling == -1) pumpSpeedCalling = 'A';\n"
+"    if (pumpSpeedCalling == -2) pumpSpeedCalling = 'T';\n"
+"    pumpSpeedRunOn    = Ajax.hexToSignedInt16(lines[14]);\n"
+"    blrOutputTarget   = Ajax.hexToSignedInt16(lines[15]);\n"
+"    riseAt0           = Ajax.hexToSignedInt16(lines[16]);\n"
+"    riseAt50          = Ajax.hexToSignedInt16(lines[17]);\n"
+"    riseAt100         = Ajax.hexToSignedInt16(lines[18]);\n"
 "}\n"
 "function display()\n"
 "{\n"
@@ -47,13 +57,16 @@
 "    elem = Ajax.getElementOrNull('ajax-tank-html'      ); if (elem) elem.textContent = OneWire.DS18B20ToString(tankTemperature);\n"
 "    elem = Ajax.getElementOrNull('ajax-blr-out-html'   ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrOutTemperature);\n"
 "    elem = Ajax.getElementOrNull('ajax-blr-rtn-html'   ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrRtnTemperature);\n"
-"    elem = Ajax.getElementOrNull('ajax-blr-rise-html'  ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrOutTemperature - blrRtnTemperature);\n"
+"    elem = Ajax.getElementOrNull('ajax-blr-aln-html'   ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrAlnTemperature);\n"
+"    elem = Ajax.getElementOrNull('ajax-blr-rise-html'  ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrDeltaT);\n"
 "    \n"
 "    elem = Ajax.getElementOrNull('ajax-blr-pump-speed-html'); if (elem) elem.textContent = blrPumpSpeed;\n"
 "    elem = Ajax.getElementOrNull('ajax-blr-pump-pwm-html'  ); if (elem) elem.textContent = blrPumpPwm;\n"
 "    \n"
-"    elem = Ajax.getElementOrNull('ajax-blr-call-toggle'   ); if (elem) elem.setAttribute('dir', boilerCall ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-blr-pump-toggle'   ); if (elem) elem.setAttribute('dir', boilerPump ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-blr-call-toggle'   ); if (elem) elem.setAttribute('dir', boilerCall   ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-blr-pump-toggle'   ); if (elem) elem.setAttribute('dir', boilerPump   ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-blr-enable-toggle' ); if (elem) elem.setAttribute('dir', boilerEnable ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-full-speed-secs'   ); if (elem) elem.value = fullSpeedSecs;\n"
 "    elem = Ajax.getElementOrNull('ajax-tank-set-point'    ); if (elem) elem.value = tankSetPoint;\n"
 "    elem = Ajax.getElementOrNull('ajax-tank-hysteresis'   ); if (elem) elem.value = tankHysteresis;\n"
 "    elem = Ajax.getElementOrNull('ajax-blr-run-on-deg'    ); if (elem) elem.value = OneWire.DS18B20ToString(blrRunOnDeg);\n"
@@ -63,8 +76,8 @@
 "    elem = Ajax.getElementOrNull('ajax-pump-speed-run-on' ); if (elem) elem.value = pumpSpeedRunOn;\n"
 "    elem = Ajax.getElementOrNull('ajax-blr-output-target' ); if (elem) elem.value = blrOutputTarget;\n"
 "    \n"
-"    elem = Ajax.getElementOrNull('ajax-pump-rise-at-0'    ); if (elem) elem.value = OneWire.DS18B20ToString(riseAt0);\n"
-"    elem = Ajax.getElementOrNull('ajax-pump-rise-at-50'   ); if (elem) elem.value = OneWire.DS18B20ToString(riseAt50);\n"
+"    elem = Ajax.getElementOrNull('ajax-pump-rise-at-0'    ); if (elem) elem.value = riseAt0;\n"
+"    elem = Ajax.getElementOrNull('ajax-pump-rise-at-50'   ); if (elem) elem.value = riseAt50;\n"
 "    elem = Ajax.getElementOrNull('ajax-pump-rise-at-100'  ); if (elem) elem.value = OneWire.DS18B20ToString(riseAt100);\n"
 "}\n"
 "\n"
--- a/web-this/boiler/web-boiler-script.js	Wed Feb 10 17:24:36 2021 +0000
+++ b/web-this/boiler/web-boiler-script.js	Tue Feb 23 20:35:07 2021 +0000
@@ -5,8 +5,12 @@
 let   tankTemperature  = '';
 let blrOutTemperature  = '';
 let blrRtnTemperature  = '';
+let blrAlnTemperature  = '';
+let blrDeltaT          = '';
+let boilerEnable       = false;
 let boilerCall         = false;
 let boilerPump         = false;
+let fullSpeedSecs      = '';
 let tankSetPoint       = '';
 let tankHysteresis     = '';
 let blrRunOnDeg        = '';
@@ -26,20 +30,26 @@
       tankTemperature = Ajax.hexToSignedInt16(lines[ 0]);
     blrOutTemperature = Ajax.hexToSignedInt16(lines[ 1]);
     blrRtnTemperature = Ajax.hexToSignedInt16(lines[ 2]);
-    boilerCall        = Ajax.hexToBit        (lines[ 3], 0);
-    boilerPump        = Ajax.hexToBit        (lines[ 3], 1);
-    tankSetPoint      = Ajax.hexToSignedInt16(lines[ 4]);
-    tankHysteresis    = Ajax.hexToSignedInt16(lines[ 5]);
-    blrRunOnDeg       = Ajax.hexToSignedInt16(lines[ 6]);
-    blrRunOnTime      = Ajax.hexToSignedInt16(lines[ 7]);
-    blrPumpSpeed      = Ajax.hexToSignedInt16(lines[ 8]);
-    blrPumpPwm        = Ajax.hexToSignedInt16(lines[ 9]);
-    pumpSpeedCalling  = Ajax.hexToSignedInt16(lines[10]);
-    pumpSpeedRunOn    = Ajax.hexToSignedInt16(lines[11]);
-    blrOutputTarget   = Ajax.hexToSignedInt16(lines[12]);
-    riseAt0           = Ajax.hexToSignedInt16(lines[13]);
-    riseAt50          = Ajax.hexToSignedInt16(lines[14]);
-    riseAt100         = Ajax.hexToSignedInt16(lines[15]);
+    blrAlnTemperature = Ajax.hexToSignedInt16(lines[ 3]);
+    blrDeltaT         = Ajax.hexToSignedInt16(lines[ 4]);
+    boilerCall        = Ajax.hexToBit        (lines[ 5], 0);
+    boilerPump        = Ajax.hexToBit        (lines[ 5], 1);
+    boilerEnable      = Ajax.hexToBit        (lines[ 5], 2);
+    fullSpeedSecs     = Ajax.hexToSignedInt16(lines[ 6]);
+    tankSetPoint      = Ajax.hexToSignedInt16(lines[ 7]);
+    tankHysteresis    = Ajax.hexToSignedInt16(lines[ 8]);
+    blrRunOnDeg       = Ajax.hexToSignedInt16(lines[ 9]);
+    blrRunOnTime      = Ajax.hexToSignedInt16(lines[10]);
+    blrPumpSpeed      = Ajax.hexToSignedInt16(lines[11]);
+    blrPumpPwm        = Ajax.hexToSignedInt16(lines[12]);
+    pumpSpeedCalling  = Ajax.hexToSignedInt16(lines[13]);
+    if (pumpSpeedCalling == -1) pumpSpeedCalling = 'A';
+    if (pumpSpeedCalling == -2) pumpSpeedCalling = 'T';
+    pumpSpeedRunOn    = Ajax.hexToSignedInt16(lines[14]);
+    blrOutputTarget   = Ajax.hexToSignedInt16(lines[15]);
+    riseAt0           = Ajax.hexToSignedInt16(lines[16]);
+    riseAt50          = Ajax.hexToSignedInt16(lines[17]);
+    riseAt100         = Ajax.hexToSignedInt16(lines[18]);
 }
 function display()
 {
@@ -47,13 +57,16 @@
     elem = Ajax.getElementOrNull('ajax-tank-html'      ); if (elem) elem.textContent = OneWire.DS18B20ToString(tankTemperature);
     elem = Ajax.getElementOrNull('ajax-blr-out-html'   ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrOutTemperature);
     elem = Ajax.getElementOrNull('ajax-blr-rtn-html'   ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrRtnTemperature);
-    elem = Ajax.getElementOrNull('ajax-blr-rise-html'  ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrOutTemperature - blrRtnTemperature);
+    elem = Ajax.getElementOrNull('ajax-blr-aln-html'   ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrAlnTemperature);
+    elem = Ajax.getElementOrNull('ajax-blr-rise-html'  ); if (elem) elem.textContent = OneWire.DS18B20ToString(blrDeltaT);
     
     elem = Ajax.getElementOrNull('ajax-blr-pump-speed-html'); if (elem) elem.textContent = blrPumpSpeed;
     elem = Ajax.getElementOrNull('ajax-blr-pump-pwm-html'  ); if (elem) elem.textContent = blrPumpPwm;
     
-    elem = Ajax.getElementOrNull('ajax-blr-call-toggle'   ); if (elem) elem.setAttribute('dir', boilerCall ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-blr-pump-toggle'   ); if (elem) elem.setAttribute('dir', boilerPump ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-blr-call-toggle'   ); if (elem) elem.setAttribute('dir', boilerCall   ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-blr-pump-toggle'   ); if (elem) elem.setAttribute('dir', boilerPump   ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-blr-enable-toggle' ); if (elem) elem.setAttribute('dir', boilerEnable ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-full-speed-secs'   ); if (elem) elem.value = fullSpeedSecs;
     elem = Ajax.getElementOrNull('ajax-tank-set-point'    ); if (elem) elem.value = tankSetPoint;
     elem = Ajax.getElementOrNull('ajax-tank-hysteresis'   ); if (elem) elem.value = tankHysteresis;
     elem = Ajax.getElementOrNull('ajax-blr-run-on-deg'    ); if (elem) elem.value = OneWire.DS18B20ToString(blrRunOnDeg);
@@ -63,8 +76,8 @@
     elem = Ajax.getElementOrNull('ajax-pump-speed-run-on' ); if (elem) elem.value = pumpSpeedRunOn;
     elem = Ajax.getElementOrNull('ajax-blr-output-target' ); if (elem) elem.value = blrOutputTarget;
     
-    elem = Ajax.getElementOrNull('ajax-pump-rise-at-0'    ); if (elem) elem.value = OneWire.DS18B20ToString(riseAt0);
-    elem = Ajax.getElementOrNull('ajax-pump-rise-at-50'   ); if (elem) elem.value = OneWire.DS18B20ToString(riseAt50);
+    elem = Ajax.getElementOrNull('ajax-pump-rise-at-0'    ); if (elem) elem.value = riseAt0;
+    elem = Ajax.getElementOrNull('ajax-pump-rise-at-50'   ); if (elem) elem.value = riseAt50;
     elem = Ajax.getElementOrNull('ajax-pump-rise-at-100'  ); if (elem) elem.value = OneWire.DS18B20ToString(riseAt100);
 }