Central Heating controller using the real time clock, PHY module for internet, 1-wire interface for temperature sensors, a system log and a configuration file

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

/media/uploads/andrewboyson/heating.sch

/media/uploads/andrewboyson/heating.brd

/media/uploads/andrewboyson/eagle.epf

Files at this revision

API Documentation at this revision

Comitter:
andrewboyson
Date:
Fri Apr 23 08:36:42 2021 +0000
Parent:
105:1899f7ed17ec
Commit message:
Not working, crashes.

Changed in this revision

fram.lib Show annotated file Show diff for this revision Revisions of this file
fram/fram.c Show diff for this revision Revisions of this file
fram/fram.h Show diff for this revision Revisions of this file
fram/spi.c Show diff for this revision Revisions of this file
fram/spi.h 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
net.lib Show annotated file Show diff for this revision Revisions of this file
settings/settings.c Show annotated file Show diff for this revision Revisions of this file
settings/settings.h 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fram.lib	Fri Apr 23 08:36:42 2021 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/andrewboyson/code/fram/#e8f4aff306cd
--- a/fram/fram.c	Tue Feb 23 20:35:07 2021 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-#include <string.h>
-#include <stdbool.h>
-
-#include "spi.h"
-#include "log.h"
-#include "fram.h"
-
-#define WREN  0x06 // Set Write Enable Latch   0000 0110B
-#define WRDI  0x04 // Reset Write Enable Latch 0000 0100B
-#define RDSR  0x05 // Read Status Register     0000 0101B
-#define WRSR  0x01 // Write Status Register    0000 0001B
-#define READ  0x03 // Read Memory Code         0000 0011B
-#define WRITE 0x02 // Write Memory Code        0000 0010B
-#define RDID  0x9F // Read Device ID           1001 1111B
-
-#define FRAM_ID     0x047f0302
-#define FRAM_MAGIC  42          //Magic number to show that the FRAM has been intialised
-
-bool FramEmpty; //Set in FramInit
-int  FramUsed;  //Initialised in FramInit and used by FramAllocate to remember the next available location
-
-int FramAllocate(int size) //Allocates a number of bytes in FRAM 
-{
-    int start = FramUsed;
-    int used = FramUsed + size;
-    if (used > FRAM_SIZE - 1)
-    {
-        Log("FramAllocate - No more room in FRAM\r\n");
-        return -1;
-    }
-    FramUsed = used;
-    return start;
-}
-int FramInit()
-{
-    //Configure SPI
-    SpiInit();
-    
-    //Check if the FRAM is connected and working properly
-    SpiChipSelect(0);
-    SpiTransfer(RDID);
-    int id = 0;
-    id = (id << 8) + SpiTransfer(0);
-    id = (id << 8) + SpiTransfer(0);
-    id = (id << 8) + SpiTransfer(0);
-    id = (id << 8) + SpiTransfer(0);
-    SpiChipSelect(1);
-    if (id != FRAM_ID)
-    {
-        LogF("FramInit - Expected FRAM id %08x but got %08x\r\n", FRAM_ID, id);
-        return -1;
-    }
-    
-    //Check the first byte to see if the FRAM is initialised and zero if not
-    SpiChipSelect(0);
-    SpiTransfer(READ);
-    SpiTransfer(0);
-    SpiTransfer(0);
-    char magic = SpiTransfer(0);
-    SpiChipSelect(1);
-    FramEmpty = magic != FRAM_MAGIC;
-    if (FramEmpty)
-    {
-        LogF("FramInit - Byte 0 value %d is not the magic value %d so initialising FRAM to zero\r\n", magic, FRAM_MAGIC);
-        SpiChipSelect(0);
-        SpiTransfer(WREN);
-        SpiChipSelect(1); SpiChipSelect(1); SpiChipSelect(1); SpiChipSelect(1); SpiChipSelect(1); SpiChipSelect(1);
-        SpiChipSelect(0);
-        SpiTransfer(WRITE);
-        SpiTransfer(0);
-        SpiTransfer(0);
-        SpiTransfer(FRAM_MAGIC);                           //Set the magic number in byte 0
-        for(int i = 1; i < FRAM_SIZE; i++) SpiTransfer(0); //Zero all other locations
-        SpiChipSelect(1);
-    }
-    FramUsed = 1; //Set the next available location to one past the byte used for the magic number
-    return 0;
-}
-void FramWrite(int address, int len, void* pVoid)
-{
-    if (address + len > FRAM_SIZE - 1 || address < 0)
-    {
-        Log("FramWrite - Invalid FRAM address\r\n");
-        return;
-    }
-    SpiChipSelect(0);
-    SpiTransfer(WREN);
-    SpiChipSelect(1); SpiChipSelect(1); SpiChipSelect(1); SpiChipSelect(1); SpiChipSelect(1); SpiChipSelect(1); //Deselect must be at least 60ns. One operation at 96MHz is about 10ns
-    SpiChipSelect(0);
-    SpiTransfer(WRITE);
-    SpiTransfer(address >> 8);
-    SpiTransfer(address & 0xFF);
-    char* p = (char*)pVoid;
-    for (int i = 0; i < len; i++) SpiTransfer(*p++);
-    SpiChipSelect(1);
-}
-void FramRead(int address, int len, void* pVoid)
-{
-    if (address + len > FRAM_SIZE - 1 || address < 0)
-    {
-        Log("FramRead - Invalid FRAM address\r\n");
-        return;
-    }
-    SpiChipSelect(0);
-    SpiTransfer(READ);
-    SpiTransfer(address >> 8);
-    SpiTransfer(address & 0xFF);
-    char* p = (char*)pVoid;
-    for (int i = 0; i < len; i++) *p++ = SpiTransfer(0);
-    SpiChipSelect(1);
-}
-
-int FramLoad(int len, void* pValue, void* pDefault)
-{
-    int address = FramAllocate(len);
-    if (address >= 0) 
-    {
-        if (FramEmpty)
-        {
-            if (pDefault)
-            {
-                int defaultHasContent = 0;
-                char* pFrom = (char*)pDefault;
-                char* pTo   = (char*)pValue;
-                for (int i = 0; i < len; i++)
-                {
-                    if (*pFrom) defaultHasContent = 1;
-                    *pTo++ = *pFrom++;
-                }
-                if (defaultHasContent) FramWrite(address, len, pValue);
-            }
-            else //pDefault is NULL so zero the setting
-            {
-                memset(pValue, 0, len);
-            }
-        }
-        else
-        {
-            FramRead(address, len, pValue);
-        }
-    }
-    return address;
-}
--- a/fram/fram.h	Tue Feb 23 20:35:07 2021 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-#include <stdbool.h>
-
-extern bool FramEmpty;
-extern int  FramUsed;
-extern int  FramInit(void);
-extern int  FramAllocate(int size);
-extern void FramWrite(int address, int len, void* pVoid);
-extern void FramRead (int address, int len, void* pVoid);
-extern int  FramLoad (int len, void* pValue, void* pDefault);
-
-#define FRAM_SIZE   8192
--- a/fram/spi.c	Tue Feb 23 20:35:07 2021 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-#include "gpio.h"
-#include "log.h"
-
-#define CS_DIR FIO0DIR(6)
-#define CS_SET FIO0SET(6)
-#define CS_CLR FIO0CLR(6)
-
-//SSP1
-#define CR0     (*((volatile unsigned *) 0x40030000))
-#define CR1     (*((volatile unsigned *) 0x40030004))
-#define DR      (*((volatile unsigned *) 0x40030008))
-#define SR      (*((volatile unsigned *) 0x4003000C))
-#define CPSR    (*((volatile unsigned *) 0x40030010))
-
-void SpiInit(void)
-{
-    //Configure
-    CR0 |= 7 << 0; //3:0 8 bit transfer
-    CR0 |= 0 << 4; //5:4 SPI
-    CR0 |= 0 << 6; //7:6 Mode 0
-    CR0 |= 0 << 8; //divide by 1
-
-    //Set prescaler bps = PCLK / PS ==> PS = PCLK / bps ==> PS = 96/16 = 6
-    CPSR = 6; //Bit 0 must be 0. 6 ==> 16 bps which is within the 20MHz allowed by the FRAM
-    
-    //Select the function of the ssel pin: P0.6
-    CS_SET;     //Deselect the output == CS = 1
-    CS_DIR = 1; //Set the direction to 1 == output
-    
-    //Enable operation
-    CR1 |= 2; //Enable the SSP controller
-}
-void SpiChipSelect(int value)
-{
-    if (value) CS_SET;
-    else       CS_CLR;
-}
-void SpiWrite(char byte)
-{
-    DR = byte; //This loads the next frame in the TX FIFO
-}
-int  SpiBusy(void)
-{
-    return SR & 0x10; //bit 4 is BSY. This bit is 0 if the SSPn controller is idle, or 1 if it is currently sending/receiving a frame and/or the Tx FIFO is not empty.
-}
-char SpiRead(void)
-{
-    return DR & 0xFF; //This reads the oldest frame in the RX FIFO
-}
-char SpiTransfer(char byte)
-{
-    SpiWrite(byte);
-    while(SpiBusy()) /*spin until not busy, at 16 bits per us or 2 bytes per us should be only 48 operations*/;
-    return SpiRead();
-}
--- a/fram/spi.h	Tue Feb 23 20:35:07 2021 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-extern void SpiInit(void);
-extern void SpiChipSelect(int value);
-extern void SpiWrite(char byte);
-extern int  SpiBusy(void);
-extern char SpiRead(void);
-extern char SpiTransfer(char byte);
\ No newline at end of file
--- a/heating/boiler.c	Tue Feb 23 20:35:07 2021 +0000
+++ b/heating/boiler.c	Fri Apr 23 08:36:42 2021 +0000
@@ -22,7 +22,6 @@
 #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;
@@ -33,16 +32,16 @@
 
 static int8_t  tankSetPoint;       static int iTankSetPoint;
 static int16_t tankHysteresis;     static int iTankHysteresis;
-static int16_t runOnResidual16ths; static int iRunOnResidual;
+static int16_t _runOnDelta16ths; static int iRunOnResidual;
 static uint8_t runOnTime2s;        static int iRunOnTime;
 
 static int8_t  boilerTarget;       static int iBoilerTarget;
 static int8_t  pumpSpeedCalling;   static int iPumpSpeedCalling;
-static int8_t  pumpSpeedRunOn;     static int iPumpSpeedRunOn;
+static int8_t  _rampDownTime;     static int _iRampDownTime;
 
-static int16_t rise16thsAt0;       static int iRiseAt0;
-static int16_t rise16thsAt50;      static int iRiseAt50;
-static int16_t rise16thsAt100;     static int iRiseAt100;
+static int8_t  _minSpeed;             static int _iMinSpeed;
+static int8_t  _midSpeedPwm;          static int _iMidSpeedPwm;
+static int16_t _fullSpeedDeltaT16ths; static int _iFullSpeedDeltaT;
 
 //Set in main scan
 static int16_t _boilerOutput16ths   = DS18B20_ERROR_VALUE_NOT_SET;
@@ -52,36 +51,36 @@
 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;     }
+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 _runOnDelta16ths;      }
+int      BoilerGetRunOnTime         () { return runOnTime2s << 1;      }
+int      BoilerGetPumpSpeedCalling  () { return pumpSpeedCalling;      }
+int      BoilerGetRampDownTime      () { return _rampDownTime;        }
+int      BoilerGetOutputTarget      () { return boilerTarget;          }
+int      BoilerGetMinSpeed          () { return _minSpeed;             }
+int      BoilerGetMidSpeedPwm       () { return _midSpeedPwm;          }
+int      BoilerGetFullSpeedDeltaT   () { return _fullSpeedDeltaT16ths; }
 
-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 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 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 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 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 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 BoilerSetRunOnDeltaT       (int   value) { _runOnDelta16ths      = value;     FramWrite(iRunOnResidual,   2, &_runOnDelta16ths     ); }
+void BoilerSetRunOnTime         (int   value) { runOnTime2s           = value >> 1;FramWrite(iRunOnTime,       1, &runOnTime2s          ); }
+void BoilerSetPumpSpeedCalling  (int   value) { pumpSpeedCalling      = value;     FramWrite(iPumpSpeedCalling,1, &pumpSpeedCalling     ); }
+void BoilerSetRampDownTime      (int   value) { _rampDownTime         = value;     FramWrite(_iRampDownTime,   1, &_rampDownTime        ); }
+void BoilerSetOutputTarget      (int   value) { boilerTarget          = value;     FramWrite(iBoilerTarget,    1, &boilerTarget         ); }
+void BoilerSetMinSpeed          (int   value) { _minSpeed             = value;     FramWrite(_iMinSpeed,       1, &_minSpeed            ); }
+void BoilerSetMidSpeedPwm       (int   value) { _midSpeedPwm          = value;     FramWrite(_iMidSpeedPwm,    1, &_midSpeedPwm         ); }
+void BoilerSetFullSpeedDeltaT   (int   value) { _fullSpeedDeltaT16ths = value;     FramWrite(_iFullSpeedDeltaT,2, &_fullSpeedDeltaT16ths); }
 
 static int calculateBetweenTwoPoints(int x, int xA, int xB, int yA, int yB)
 {
@@ -90,24 +89,15 @@
 }
 static int calculateSpeedFromDeltaT(int deltaT16ths)
 {
-    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;
+    if (deltaT16ths < _fullSpeedDeltaT16ths) return MAX_SPEED;   //Needed in case deltaT16ths is negative or zero
+    int speed = MAX_SPEED * _fullSpeedDeltaT16ths / deltaT16ths; //eg for 20 deg ==> 100 * (10 << 4) / (20 << 4) == 50
+    if (speed > MAX_SPEED) speed = MAX_SPEED;
+    if (speed < _minSpeed) speed = _minSpeed;
+    return 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;
+    int deltaT16ths = MAX_SPEED * _fullSpeedDeltaT16ths / speed; //eg for speed = 50 ==> 100 * (10 << 4) / 50 == 20 << 4
     return deltaT16ths;
 }
 
@@ -132,20 +122,22 @@
     uint8_t def1;
     int16_t def2;
     int32_t def4;
-                  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;
-    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;
-    def1 =   100; address = FramLoad( 1, &pumpSpeedCalling,   &def1); if (address < 0) return -1; iPumpSpeedCalling = address;
-    def1 =    10; address = FramLoad( 1, &pumpSpeedRunOn,     &def1); if (address < 0) return -1; iPumpSpeedRunOn   = address;
-    def1 =    65; address = FramLoad( 1, &boilerTarget,       &def1); if (address < 0) return -1; iBoilerTarget     = address;
-    def2 = 10<<4; address = FramLoad( 2, &rise16thsAt0,       &def2); if (address < 0) return -1; iRiseAt0          = address;
-    def2 = 15<<4; address = FramLoad( 2, &rise16thsAt50,      &def2); if (address < 0) return -1; iRiseAt50         = address;
-    def2 = 20<<4; address = FramLoad( 2, &rise16thsAt100,     &def2); if (address < 0) return -1; iRiseAt100        = address;
+                  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;
+    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, &_runOnDelta16ths,      &def2); if (address < 0) return -1; iRunOnResidual    = address;
+    def1 =   180; address = FramLoad( 1, &runOnTime2s,           &def1); if (address < 0) return -1; iRunOnTime        = address;
+    def1 =   100; address = FramLoad( 1, &pumpSpeedCalling,      &def1); if (address < 0) return -1; iPumpSpeedCalling = address;
+    def1 =    10; address = FramLoad( 1, &_rampDownTime,        &def1); if (address < 0) return -1; _iRampDownTime   = address;
+    def1 =    65; address = FramLoad( 1, &boilerTarget,          &def1); if (address < 0) return -1; iBoilerTarget     = address;
+    def1 =    50; address = FramLoad( 1, &_minSpeed,             &def1); if (address < 0) return -1; _iMinSpeed        = address;
+                            FramAllocate(1);
+    def1 =    50; address = FramLoad( 1, &_midSpeedPwm,          &def1); if (address < 0) return -1; _iMidSpeedPwm     = address;
+                            FramAllocate(1);
+    def2 = 10<<4; address = FramLoad( 2, &_fullSpeedDeltaT16ths, &def2); if (address < 0) return -1; _iFullSpeedDeltaT = address;
     
     BOILER_PUMP_DIR = 1; //Set the direction to 1 == output
     BOILER_CALL_DIR = 1; //Set the direction to 1 == output
@@ -187,12 +179,12 @@
     }
     else
     {
-        if (MsTimerRelative(msTimerBoilerPumpRunOn,      runOnTime2s * 2000)) BoilerPump = false;
-        if (_boilerDeltaTisValid && _boilerDeltaT16ths < runOnResidual16ths ) BoilerPump = false;
+        if (MsTimerRelative(msTimerBoilerPumpRunOn,    runOnTime2s * 2000)) BoilerPump = false;
+        if (_boilerDeltaTisValid && _boilerDeltaT16ths < _runOnDelta16ths ) BoilerPump = false;
     }
 }
-int BoilerPumpFlow  = MIN_SPEED;
-int BoilerPumpSpeed = MIN_SPEED;
+int BoilerPumpFlow  = MAX_SPEED;
+int BoilerPumpSpeed = MAX_SPEED;
 int BoilerPumpPwm   = 0;
 static int _autoSpeed = 0;
 static void calculateAutoSpeed()
@@ -210,28 +202,29 @@
     calculateAutoSpeed();
     if (BoilerCall)
     {
-        if (pumpSpeedCalling > MAX_SPEED || pumpSpeedCalling < MIN_SPEED) BoilerPumpSpeed = _autoSpeed;        //Auto
-        else                                                              BoilerPumpSpeed = pumpSpeedCalling;  //Manual
+        if (pumpSpeedCalling < 0) BoilerPumpSpeed = _autoSpeed;        //Auto
+        else                      BoilerPumpSpeed = pumpSpeedCalling;  //Manual
         msTimerReduction = MsTimerCount;
     }
     else
     {
-        if (BoilerPumpSpeed > pumpSpeedRunOn)
+        if (BoilerPumpSpeed > _minSpeed)
         {
-            if (MsTimerRepetitive(&msTimerReduction, 250)) BoilerPumpSpeed--;
+            int msPerUnit = 1000 * _rampDownTime / (MAX_SPEED - _minSpeed);
+            if (MsTimerRepetitive(&msTimerReduction, msPerUnit)) BoilerPumpSpeed--;
         }
         else
         {
-            BoilerPumpSpeed = pumpSpeedRunOn;
+            BoilerPumpSpeed = _minSpeed;
         }
     }
+    if (BoilerPumpSpeed < _minSpeed) BoilerPumpSpeed = _minSpeed;
+    if (BoilerPumpSpeed > MAX_SPEED) BoilerPumpSpeed = MAX_SPEED;
 }
-static void flowToSpeed()
+static int speedToPwm(int speed)
 {
-    //Do nothing yet
-}
-static void speedToPwm()
-{
+    #define MAX_SPEED_PWM 10
+    #define MIN_SPEED_PWM 84
     /*
     PWM input signal [%] Pump status
     ≤ 10 Maximum speed
@@ -244,10 +237,15 @@
     Max speed 100 is at fitted = 74; pwm = 10
     Min speed   0 is at fitted =  0; pwm = 84
     */
-    int pwm = calculateBetweenTwoPoints(BoilerPumpSpeed, MIN_SPEED, MAX_SPEED, 84, 10);
-    if (pwm < 10) pwm = 10;
-    if (pwm > 84) pwm = 84;
-    BoilerPumpPwm = pwm;
+    if (speed <= _minSpeed) return MIN_SPEED_PWM;
+    if (speed >= MAX_SPEED) return MAX_SPEED_PWM;
+    int midSpeed = (_minSpeed + MAX_SPEED) / 2;
+    if (speed < midSpeed) return calculateBetweenTwoPoints(speed, _minSpeed,  midSpeed, MIN_SPEED_PWM,   _midSpeedPwm);
+    else                  return calculateBetweenTwoPoints(speed,  midSpeed, MAX_SPEED,  _midSpeedPwm,  MAX_SPEED_PWM);
+    //int pwm = calculateBetweenTwoPoints(BoilerPumpSpeed, _minSpeed, 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()
@@ -264,8 +262,8 @@
     
     if (speedLastScan < MAX_SPEED && BoilerPumpSpeed == MAX_SPEED)
     {
-        if (rise16thsAt100 > _boilerDeltaT16ths) rise16thsAt100--;
-        if (rise16thsAt100 < _boilerDeltaT16ths) rise16thsAt100++;
+        if (_fullSpeedDeltaT16ths > _boilerDeltaT16ths) _fullSpeedDeltaT16ths--;
+        if (_fullSpeedDeltaT16ths < _boilerDeltaT16ths) _fullSpeedDeltaT16ths++;
     }
     
     speedLastScan = BoilerPumpSpeed;
@@ -365,8 +363,7 @@
     else            BOILER_PUMP_CLR;
     
     controlBoilerPumpSpeed();
-    flowToSpeed();
-    speedToPwm();
+    BoilerPumpPwm = speedToPwm(BoilerPumpSpeed);
     PwmSet(BoilerPumpPwm);
     
     tweakDeltaTs();
--- a/heating/boiler.h	Tue Feb 23 20:35:07 2021 +0000
+++ b/heating/boiler.h	Fri Apr 23 08:36:42 2021 +0000
@@ -8,11 +8,11 @@
 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     BoilerGetRampDownTime      (void); extern void BoilerSetRampDownTime      (int value);
 extern int     BoilerGetOutputTarget      (void); extern void BoilerSetOutputTarget      (int value);
 
-extern int     BoilerGetMinimumFlow       (void); extern void BoilerSetMinimumFlow       (int value);
-extern int     BoilerGetMidFlowSpeed      (void); extern void BoilerSetMidFlowSpeed      (int value);
+extern int     BoilerGetMinSpeed          (void); extern void BoilerSetMinSpeed          (int value);
+extern int     BoilerGetMidSpeedPwm       (void); extern void BoilerSetMidSpeedPwm       (int value);
 
 extern int16_t BoilerGetTankDS18B20Value  (void);
 extern int16_t BoilerGetOutputDS18B20Value(void);
--- a/heating/values.c	Tue Feb 23 20:35:07 2021 +0000
+++ b/heating/values.c	Fri Apr 23 08:36:42 2021 +0000
@@ -4,7 +4,7 @@
 
 #include "tftp.h"
 #include "dnslabel.h"
-#include "fram.h"
+//#include "fram.h"
 #include "clk.h"
 #include "clktime.h"
 #include "mstimer.h"
@@ -15,44 +15,42 @@
 #include "hot-water.h"
 #include "log.h"
 #include "net.h"
+#include "settings.h"
 
 bool ValuesTrace = false;
 
-static char    serverName[DNS_MAX_LABEL_LENGTH+1]; static int iServerName;
-static char      fileName[DNS_MAX_LABEL_LENGTH+1]; static int iFileName;
-static int32_t writeSize;                          static int iWriteSize;
-static int32_t readInterval;                       static int iReadInterval;
-static int64_t startTime;                          static int iStartTime;
-static int32_t count;                              static int iCount;
-                                                   static int iData;
+static char    serverName[DNS_MAX_LABEL_LENGTH+1];
+static char    fileName  [DNS_MAX_LABEL_LENGTH+1];
+static int32_t writeSize;
+static int32_t readInterval;
+static int64_t startTime;
+static int32_t count;
 
 char*   ValuesGetServerName   (              ) { return         serverName;                    }
-char*   ValuesGetFileName     (              ) { return           fileName;                    }
-int     ValuesGetWriteSize    (              ) { return (int)    writeSize;                    }
-int     ValuesGetReadInterval (              ) { return (int)     readInterval;                }
+char*   ValuesGetFileName     (              ) { return         fileName;                      }
+int     ValuesGetWriteSize    (              ) { return (int)   writeSize;                     }
+int     ValuesGetReadInterval (              ) { return (int)   readInterval;                  }
 void    ValuesGetStartTm      (struct tm* ptm) { ClkTimeToTmUtc(startTime, ptm);               }
 int64_t ValuesGetStartTime    (              ) { return startTime >> CLK_TIME_ONE_SECOND_SHIFT;}
-int     ValuesGetCount        (              ) { return (int)        count;                    }
+int     ValuesGetCount        (              ) { return (int)   count;                         }
 
-void ValuesSetServerName    (char*   value) { DnsLabelCopy(serverName, value); FramWrite(iServerName  , DNS_MAX_LABEL_LENGTH,  serverName   ); }
-void ValuesSetFileName      (char*   value) { DnsLabelCopy(  fileName, value); FramWrite(iFileName    , DNS_MAX_LABEL_LENGTH,    fileName   ); }
-void ValuesSetWriteSize     (int     value) { writeSize     =          value ; FramWrite(iWriteSize   ,                    4, &writeSize    ); }
-void ValuesSetReadInterval  (int     value) { readInterval  =          value ; FramWrite(iReadInterval,                    4, &readInterval ); }
-static void    setStartTime (int64_t value) { startTime     =          value ; FramWrite(iStartTime   ,                    8, &startTime    ); }
-static void    setCount     (int     value) { count         =          value ; FramWrite(iCount       ,                    4, &count        ); }
+void ValuesSetServerName    (char*   value) { DnsLabelCopy(serverName, value); SetValuesServerName  ( value); }
+void ValuesSetFileName      (char*   value) { DnsLabelCopy(  fileName, value); SetValuesFileName    ( value); }
+void ValuesSetWriteSize     (int     value) { writeSize     =          value ; SetValuesWriteSize   (&value); }
+void ValuesSetReadInterval  (int     value) { readInterval  =          value ; SetValuesReadInterval(&value); }
+static void    setStartTime (int64_t value) { startTime     =          value ; SetValuesStartTime   (&value); }
+static void    setCount     (int     value) { count         =          value ; SetValuesCount       (&value); }
 
 static int readValuesFromFram()
 {
     int address;
     int32_t def4;
-    
-                 address = FramLoad( DNS_MAX_LABEL_LENGTH+1,  serverName  , NULL ); if (address < 0) return -1; iServerName   = address;
-                 address = FramLoad( DNS_MAX_LABEL_LENGTH+1,    fileName  , NULL ); if (address < 0) return -1; iFileName     = address;
-    def4 =  100; address = FramLoad(                      4, &writeSize   , &def4); if (address < 0) return -1; iWriteSize    = address;
-    def4 =   15; address = FramLoad(                      4, &readInterval, &def4); if (address < 0) return -1; iReadInterval = address;
-                 address = FramLoad(                      8, &startTime   , NULL ); if (address < 0) return -1; iStartTime    = address;
-                 address = FramLoad(                      4, &count       , NULL ); if (address < 0) return -1; iCount        = address;
-                 address = FramAllocate(                  1                      ); if (address < 0) return -1; iData         = address;
+    GetValuesServerName  (   serverName);
+    GetValuesFileName    (     fileName);
+    GetValuesWriteSize   (   &writeSize);
+    GetValuesReadInterval(&readInterval);
+    GetValuesStartTime   (   &startTime);
+    GetValuesCount       (       &count);
     return 0;
 }
 
@@ -64,13 +62,13 @@
 static int nextByteOfWriteStream()
 {
     int byteAfterData = count * 8;
-    if (writeIndex >= byteAfterData || writeIndex + iData >= FRAM_SIZE)
+    if (writeIndex >= byteAfterData)
     {
         setCount(0);
         return -1;
     }
     char c;
-    FramRead(writeIndex + iData, 1, &c);
+    GetValuesData(writeIndex, &c);
     writeIndex++;
     return c;
 }
@@ -105,9 +103,13 @@
     record |= BoilerCall   << 1;
     record |= BoilerPump   << 2;   //AAAB BBCC CDDD EEEF
     
-    if (count == 0) setStartTime(ClkNowTai());
+    if (count <= 0)
+    {
+        count = 0;
+        setStartTime(ClkNowTai());
+    }
     
-    FramWrite(iData + 8 * count, 8, &record);
+    SetValuesData(count, &record);
     setCount(count + 1);
 }
 
--- a/net.lib	Tue Feb 23 20:35:07 2021 +0000
+++ b/net.lib	Fri Apr 23 08:36:42 2021 +0000
@@ -1,1 +1,1 @@
-https://os.mbed.com/users/andrewboyson/code/net/#47a953ab571b
+https://os.mbed.com/users/andrewboyson/code/net/#bd5b123143ca
--- a/settings/settings.c	Tue Feb 23 20:35:07 2021 +0000
+++ b/settings/settings.c	Fri Apr 23 08:36:42 2021 +0000
@@ -85,6 +85,35 @@
 static int iHttp;
 static int iTftp;
 
+//Values
+static int iValuesServerName;
+void SetValuesServerName(char* value) { FramWrite(iValuesServerName, DNS_MAX_LABEL_LENGTH, value); }
+void GetValuesServerName(char* value) { FramRead (iValuesServerName, DNS_MAX_LABEL_LENGTH, value); value[DNS_MAX_LABEL_LENGTH] = 0; }
+
+static int iValuesFileName;
+void SetValuesFileName(char* value) { FramWrite(iValuesFileName, DNS_MAX_LABEL_LENGTH, value); }
+void GetValuesFileName(char* value) { FramRead (iValuesFileName, DNS_MAX_LABEL_LENGTH, value); value[DNS_MAX_LABEL_LENGTH] = 0; }
+
+static int iValuesWriteSize;
+void SetValuesWriteSize(int* pValue) { FramWrite(iValuesWriteSize, 4, pValue); }
+void GetValuesWriteSize(int* pValue) { FramRead (iValuesWriteSize, 4, pValue); }
+
+static int iValuesReadInterval;
+void SetValuesReadInterval(int* pValue) { FramWrite(iValuesReadInterval, 4, pValue); }
+void GetValuesReadInterval(int* pValue) { FramRead (iValuesReadInterval, 4, pValue); }
+
+static int iValuesStartTime;
+void SetValuesStartTime(int64_t* pValue) { FramWrite(iValuesStartTime, 8, pValue); }
+void GetValuesStartTime(int64_t* pValue) { FramRead (iValuesStartTime, 8, pValue); }
+
+static int iValuesCount;
+void SetValuesCount(int* pValue) { FramWrite(iValuesCount, 4, pValue); }
+void GetValuesCount(int* pValue) { FramRead (iValuesCount, 4, pValue); }
+
+static int iValuesData;
+void GetValuesData(int index, char* pValue) { FramRead (iValuesData +     index, 1, pValue); }
+void SetValuesData(int count, void* pValue) { FramWrite(iValuesData + 8 * count, 8, pValue); }
+
 //Clock settings
 void SetClockSlewDivisor      (int  value) { ClkGovSlewDivisor       = value; FramWrite(iClkGovSlewDivisor,       4, &ClkGovSlewDivisor      ); }
 void SetClockSlewMaxMs        (int  value) { ClkGovSlewChangeMaxMs   = value; FramWrite(iClkGovSlewMaxMs,         4, &ClkGovSlewChangeMaxMs  ); }
@@ -146,8 +175,6 @@
 void ChgTraceHttp      () {       WebTrace        =       !WebTrace       ; FramWrite(iHttp,       1,       &WebTrace       ); }
 void ChgTraceTftp      () {      TftpTrace        =      !TftpTrace       ; FramWrite(iTftp,       1,      &TftpTrace       ); }
 
-
-
 static int iServerName;
 static int iInitialInterval;
 static int iNormalInterval;
@@ -181,6 +208,7 @@
 {
     int address;
     int32_t def4;
+    int64_t def8;
     char b;
     
     def4 =       10; address = FramLoad( 4, &ClkGovSlewDivisor,       &def4);    if (address < 0) return -1; iClkGovSlewDivisor       = address;
@@ -230,5 +258,24 @@
     address = FramLoad( 1, &b, NULL);       WebTrace         = b; if (address < 0) return -1; iHttp        = address;
     address = FramLoad( 1, &b, NULL);      TftpTrace         = b; if (address < 0) return -1; iTftp        = address;
 
+    address = FramAllocate(DNS_MAX_LABEL_LENGTH); if (address < 0) return -1; iValuesServerName   = address;
+    FramAllocate(1); //Spare
+    address = FramAllocate(DNS_MAX_LABEL_LENGTH); if (address < 0) return -1; iValuesFileName     = address;
+    FramAllocate(1); //Spare
+    address = FramAllocate(                   4); if (address < 0) return -1; iValuesWriteSize    = address;
+    address = FramAllocate(                   4); if (address < 0) return -1; iValuesReadInterval = address;
+    address = FramAllocate(                   8); if (address < 0) return -1; iValuesStartTime    = address;
+    address = FramAllocate(                   4); if (address < 0) return -1; iValuesCount        = address;
+    address = FramAllocate(                   1); if (address < 0) return -1; iValuesData         = address;
+    if (FramEmpty)
+    {
+        b    =   0; FramWrite(iValuesServerName  , 1, &b   );
+        b    =   0; FramWrite(iValuesFileName    , 1, &b   );
+        def4 = 100; FramWrite(iValuesWriteSize   , 4, &def4);
+        def4 =  15; FramWrite(iValuesReadInterval, 4, &def4);
+        def8 =   0; FramWrite(iValuesStartTime   , 8, &def8);
+        def4 =   0; FramWrite(iValuesCount       , 4, &def4);
+    }
     return 0;
+    
 }
--- a/settings/settings.h	Tue Feb 23 20:35:07 2021 +0000
+++ b/settings/settings.h	Fri Apr 23 08:36:42 2021 +0000
@@ -56,6 +56,20 @@
 extern void SetNtpClientOffsetMs        ( int   value);
 extern void SetNtpClientMaxDelayMs      ( int   value);
 
+extern void SetValuesServerName  (char*     value);
+extern void GetValuesServerName  (char*     value);
+extern void SetValuesFileName    (char*     value);
+extern void GetValuesFileName    (char*     value);
+extern void SetValuesWriteSize   (int*     pValue);
+extern void GetValuesWriteSize   (int*     pValue);
+extern void SetValuesReadInterval(int*     pValue);
+extern void GetValuesReadInterval(int*     pValue);
+extern void SetValuesStartTime   (int64_t* pValue);
+extern void GetValuesStartTime   (int64_t* pValue);
+extern void SetValuesCount       (int*     pValue);
+extern void GetValuesCount       (int*     pValue);
+extern void GetValuesData(int index, char* pValue);
+extern void SetValuesData(int count, void* pValue);
 
 extern int  SettingsNtpInit(void);
 extern int  SettingsInit(void);
\ No newline at end of file
--- a/web-this/boiler/web-boiler-ajax.c	Tue Feb 23 20:35:07 2021 +0000
+++ b/web-this/boiler/web-boiler-ajax.c	Fri Apr 23 08:36:42 2021 +0000
@@ -32,10 +32,10 @@
     HttpAddInt16AsHex(BoilerPumpSpeed);               HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerPumpPwm);                 HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerGetPumpSpeedCalling  ()); HttpAddChar('\n');
-    HttpAddInt16AsHex(BoilerGetPumpSpeedRunOn    ()); HttpAddChar('\n');
+    HttpAddInt16AsHex(BoilerGetRampDownTime      ()); HttpAddChar('\n');
     HttpAddByteAsHex (BoilerGetOutputTarget      ()); HttpAddChar('\n');
-    HttpAddInt16AsHex(BoilerGetMinimumFlow       ()); HttpAddChar('\n');
-    HttpAddInt16AsHex(BoilerGetMidFlowSpeed      ()); HttpAddChar('\n');
+    HttpAddInt16AsHex(BoilerGetMinSpeed          ()); HttpAddChar('\n');
+    HttpAddInt16AsHex(BoilerGetMidSpeedPwm       ()); HttpAddChar('\n');
     HttpAddInt16AsHex(BoilerGetFullSpeedDeltaT   ()); HttpAddChar('\n');
 }
 
--- a/web-this/boiler/web-boiler-html.c	Tue Feb 23 20:35:07 2021 +0000
+++ b/web-this/boiler/web-boiler-html.c	Fri Apr 23 08:36:42 2021 +0000
@@ -21,7 +21,7 @@
 
     WebAddH2("Pump outputs");
     WebAddAjaxLed("Boiler pump", "ajax-blr-pump-toggle");
-    WebAddAjaxLabelledSuffix("Boiler pump speed (10-100)",     "ajax-blr-pump-speed-html", "%");
+    WebAddAjaxLabelledSuffix("Boiler pump speed (<span id='ajax-min-speed-text'></span>-100)",     "ajax-blr-pump-speed-html", "%");
     WebAddAjaxLabelledSuffix("Boiler pump pwm &nbsp;(84-10)",  "ajax-blr-pump-pwm-html"  , "%");
     
     WebAddH2("Pump inputs");
@@ -32,19 +32,19 @@
 
     WebAddH2("Pump parameters");
     WebAddAjaxInput("Full speed circuit time (sec)" , 2, "ajax-full-speed-secs"   , "fullspeedsecs"     );
+    WebAddAjaxInput("Minimum speed (%)"             , 2, "ajax-min-speed-value"   , "blrriseat0"        );
+    WebAddAjaxInput("Mid speed pwm"                 , 2, "ajax-pump-rise-at-50"   , "blrriseat50"       );
     
     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 parameters during run on");
-    WebAddAjaxInput("Run on min &Delta;T"           , 2, "ajax-blr-run-on-deg",     "boilerresidual"    );
+    WebAddAjaxInput("Run on min &Delta;T"           , 3, "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"    );
+    WebAddAjaxInput("Ramp down time (sec)"          , 2, "ajax-pump-speed-run-on",  "pumpspeedrunon"    );
 
     WebAddEnd();
     
--- a/web-this/boiler/web-boiler-query.c	Tue Feb 23 20:35:07 2021 +0000
+++ b/web-this/boiler/web-boiler-query.c	Fri Apr 23 08:36:42 2021 +0000
@@ -65,7 +65,7 @@
         if (HttpSameStr(pName, "pumpspeedrunon"))
         {
             int value = HttpQueryValueAsInt(pValue);
-            BoilerSetPumpSpeedRunOn(value);
+            BoilerSetRampDownTime(value);
             return;
         }
         if (HttpSameStr(pName, "boileroutputtarget"))
@@ -77,13 +77,13 @@
         if (HttpSameStr(pName, "blrriseat0"))
         {
             double value = HttpQueryValueAsDouble(pValue);
-            BoilerSetMinimumFlow(value);
+            BoilerSetMinSpeed(value);
             return;
         }
         if (HttpSameStr(pName, "blrriseat50"))
         {
             double value = HttpQueryValueAsDouble(pValue);
-            BoilerSetMidFlowSpeed(value);
+            BoilerSetMidSpeedPwm(value);
             return;
         }
         if (HttpSameStr(pName, "blrriseat100"))
--- a/web-this/boiler/web-boiler-script.inc	Tue Feb 23 20:35:07 2021 +0000
+++ b/web-this/boiler/web-boiler-script.inc	Fri Apr 23 08:36:42 2021 +0000
@@ -20,7 +20,7 @@
 "let pumpSpeedCalling   = '';\n"
 "let pumpSpeedRunOn     = '';\n"
 "let blrOutputTarget    = '';\n"
-"let riseAt0            = '';\n"
+"let minSpeed           = '';\n"
 "let riseAt50           = '';\n"
 "let riseAt100          = '';\n"
 "\n"
@@ -47,7 +47,7 @@
 "    if (pumpSpeedCalling == -2) pumpSpeedCalling = 'T';\n"
 "    pumpSpeedRunOn    = Ajax.hexToSignedInt16(lines[14]);\n"
 "    blrOutputTarget   = Ajax.hexToSignedInt16(lines[15]);\n"
-"    riseAt0           = Ajax.hexToSignedInt16(lines[16]);\n"
+"    minSpeed          = Ajax.hexToSignedInt16(lines[16]);\n"
 "    riseAt50          = Ajax.hexToSignedInt16(lines[17]);\n"
 "    riseAt100         = Ajax.hexToSignedInt16(lines[18]);\n"
 "}\n"
@@ -76,7 +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 = riseAt0;\n"
+"    elem = Ajax.getElementOrNull('ajax-min-speed-value'   ); if (elem) elem.value = minSpeed;\n"
+"    elem = Ajax.getElementOrNull('ajax-min-speed-text'    ); if (elem) elem.textContent = minSpeed;\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"
--- a/web-this/boiler/web-boiler-script.js	Tue Feb 23 20:35:07 2021 +0000
+++ b/web-this/boiler/web-boiler-script.js	Fri Apr 23 08:36:42 2021 +0000
@@ -20,7 +20,7 @@
 let pumpSpeedCalling   = '';
 let pumpSpeedRunOn     = '';
 let blrOutputTarget    = '';
-let riseAt0            = '';
+let minSpeed           = '';
 let riseAt50           = '';
 let riseAt100          = '';
 
@@ -47,7 +47,7 @@
     if (pumpSpeedCalling == -2) pumpSpeedCalling = 'T';
     pumpSpeedRunOn    = Ajax.hexToSignedInt16(lines[14]);
     blrOutputTarget   = Ajax.hexToSignedInt16(lines[15]);
-    riseAt0           = Ajax.hexToSignedInt16(lines[16]);
+    minSpeed          = Ajax.hexToSignedInt16(lines[16]);
     riseAt50          = Ajax.hexToSignedInt16(lines[17]);
     riseAt100         = Ajax.hexToSignedInt16(lines[18]);
 }
@@ -76,7 +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 = riseAt0;
+    elem = Ajax.getElementOrNull('ajax-min-speed-value'   ); if (elem) elem.value = minSpeed;
+    elem = Ajax.getElementOrNull('ajax-min-speed-text'    ); if (elem) elem.textContent = minSpeed;
     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);
 }