Measures the output power from a boiler system and transmits it to open energy monitor system

Dependencies:   mbed RFM69 MSF_Time ds3231 TextLCD

Files at this revision

API Documentation at this revision

Comitter:
dswood
Date:
Fri Jan 07 12:23:44 2022 +0000
Commit message:
Working but still a number of errors

Changed in this revision

BME280_driver.lib Show annotated file Show diff for this revision Revisions of this file
MSF_Time.lib Show annotated file Show diff for this revision Revisions of this file
RFM69.lib Show annotated file Show diff for this revision Revisions of this file
TextLCD.lib Show annotated file Show diff for this revision Revisions of this file
ds3231.lib Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BME280_driver.lib	Fri Jan 07 12:23:44 2022 +0000
@@ -0,0 +1,1 @@
+https://github.com/BoschSensortec/BME280_driver.git/#3d686a326565b9cadb8507f73d7576ec0cc891f2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MSF_Time.lib	Fri Jan 07 12:23:44 2022 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/dswood/code/MSF_Time/#340305453f64
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RFM69.lib	Fri Jan 07 12:23:44 2022 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/dswood/code/RFM69/#37f3683b3648
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TextLCD.lib	Fri Jan 07 12:23:44 2022 +0000
@@ -0,0 +1,1 @@
+https://mbed.org/users/simon/code/TextLCD/#308d188a2d3a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ds3231.lib	Fri Jan 07 12:23:44 2022 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/teams/Maxim-Integrated/code/ds3231/#11630748e2f2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Fri Jan 07 12:23:44 2022 +0000
@@ -0,0 +1,989 @@
+// Solid Fuel Energy monitor
+/* Monitor water temperature and flow
+The energy flow is proportional to the flow of water multiplied by
+the temperature difference between in and out.  Energy flow per second will
+give you output power.
+*/
+#include "mbed.h"
+#include "TextLCD.h"
+#include "MSF_Time.h"
+#include "RFM69.h"
+#include "bme280.h"
+#include "ds3231.h"
+#include "string"
+char Version[]="Version 1.01";
+Serial Mypc(USBTX, USBRX, 115200);  //for debugging
+
+#define RF_freq RF69_433MHZ
+#define Col 20 //display cols
+#define Row 4  // display rows
+TextLCD lcd(D10, D9, D6, D5, D4, D3, TextLCD::LCD20x4  ); // rs, e, d4-d7,Display type
+//DigitalOut led1(LED1); // LED pin used for spi interface - it flashes on each access
+#define My_scl D15
+#define My_sda D14
+Ds3231 rtc(My_sda, My_scl);
+MSF_Time MSF(D2,0);
+//AnalogIn adc_temp(ADC_TEMP);
+//AnalogIn adc_vref(ADC_VREF);
+//AnalogIn adc_vbat(ADC_VBAT);
+AnalogIn adc_Water_Temp(A0);
+AnalogIn adc_Water_Diff(A1);
+AnalogIn adc_Room_Temp(A2);
+//the size of the arrays holding the measured data
+// TM is ten minutes worth of data
+// H hourly data, D day, M Month,  Y for year.
+const int SizeOfTM=13,SizeOfH=14,SizeOfD=14,SizeOfW=7,SizeOfM=12,SizeOfY=12;
+uint64_t TenMins[SizeOfTM],Hour[SizeOfH],Day[SizeOfD],Week[SizeOfW],Month[SizeOfM],Year[SizeOfY];
+//The memory location is in the store and read functions but an offset is needed
+//so each array is sored in a unique location.
+//Each element of data is 64 bit or 2 words or 8 bytes.  So 8 offsets per item. Stored as bytes.
+const int MemOffsetTM=0;
+const int MemOffsetH=SizeOfTM*8;
+const int MemOffsetD=MemOffsetH+(SizeOfH*8);
+const int MemOffsetW=MemOffsetD+(SizeOfD*8);
+const int MemOffsetM=MemOffsetW+(SizeOfW*8);
+const int MemOffsetY=MemOffsetM+(SizeOfM*8);
+struct TimeRecord {
+    int TM;  //  ten mins and the only reason I did not just use tm struct
+    int H;   // hour
+    int D;   // day (week is day divided by seven)
+    int M;   // month
+    int Y;   // year
+};
+struct TimeRecord Current;
+time_t MySeconds;
+InterruptIn Flow_meter(D7);
+unsigned int Flow_duration=0xffff;
+Timer Flow_timer; // Measure time between flow pulses
+Timer Time_Data_timer;  // Measure the length of data pulses
+const float Flow_Detect_Time=10.0; // must be shorter than timer overflow 30s
+const float Display_time=1.0; //used to calc flow too
+float TempS,PressureS,HumidityS,LongAvePressureS,ShortAvePressureS,AvePressureS;
+float Flow,Power,Water_Temp,Room_Temp;
+uint64_t TotalEnergyJ=0,LastEnergy=0;
+int Press;
+float Temp_Diff=0,Temperatur,Pressure,Humidity;
+bool Flow_Detected=false;
+Ticker No_Flow_Detect;
+Ticker Display_Ticker;
+void Flow_Stopped(void);
+float Calc_Flow(int fd);
+float Calc_Power(float Fl, float Td);
+char Timebuff[20];
+char MyBuffer[80];
+bool IsTimeSet=false,RTCValid=false;
+RFM69 MyRadio(D11,D12,D13,PC_5,PC_6);
+bool Sensing=false;
+const int nodeID_emonpwr = 7;
+const int nodeID_emonenv = 19;                                                // emonTx RFM12B node ID
+const int networkGroup = 210;
+const int PulsesPerLitre=288;
+
+I2C SensorI2C(My_sda,My_scl); //I2C sensor bme280
+struct bme280_dev MySensor;
+int8_t rslt = BME280_OK;
+struct bme280_data SensorData;
+uint8_t SensorSettings;
+int8_t SensorSetup();
+int8_t user_i2c_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len);
+int8_t user_i2c_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len);
+int8_t SensorReadAll();
+void user_delay_ms(uint32_t period);
+
+//DigitalOut GreenLED(LED1);
+struct tm * NowTime;
+typedef struct {
+    int16_t Power,Flow, TempOut,TempDiff;
+} PowerPayloadTX;      // create structure - a neat way of packaging data for RF comms
+PowerPayloadTX emonPowerTX;
+typedef struct {
+    int16_t temperature,  pressure, humidity;
+} EnviromentPayloadTX;
+EnviromentPayloadTX emonEnvTX;
+void send_power_rf_data(int ToAddress, int FromAddress);
+bool Flag_send_power_rf_data=false;
+void send_enviro_rf_data(int ToAddress, int FromAddress);
+InterruptIn PageButton(PC_9);
+Timeout DebounceTimer;
+int PageNumber=0;
+const int MaxPages=14;
+bool Debouncing();
+bool DeBounce=false;
+void PageControl();
+void Timed_out();
+void Page1();
+void Page2();
+void Page3();
+void Page4();
+void Page5();
+void Page6();
+void Page7();
+void Page8();
+void Page9();
+void Page10();
+void Page11();
+void Page12();
+void Page13();
+void Page14();
+
+void Rotate(uint64_t Arr[], int size);
+bool Set_RTC_Time(time_t epoch_time);
+void FillTimeRecord(struct TimeRecord *TR);
+bool FlagWriteDataToEEPROM=false;
+
+HAL_StatusTypeDef writeEEPROMByte(uint32_t address, uint8_t data);
+uint8_t readEEPROMByte(uint32_t address) ;
+void ConvertJouleTokWh(uint64_t Joule[], float kWh[], int size);
+void ConvertJouleToKiloJoule(uint64_t Watts[], float kW[], int size);
+void WriteDataToEEPROM();
+void ReadDataFromEEPROM();
+void WriteDataToEEPROM(uint64_t Target[], int size, uint32_t address);
+void ReadDataFromEEPROM(uint64_t Target[], int size, uint32_t address);
+struct ScreenSt {
+    char Memory[Col+1][Row+1];
+    int PersistTime[Row+1];
+    Ticker PersistTicker;
+};
+bool DisplayChanged=false;
+ScreenSt ScreenMemory;
+void DisplayRefresh();
+void PersistDelay();
+void WriteToScreenMemory(char Buff[],int x,int y,int p=0);
+void ClearScreen();
+
+int8_t user_i2c_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len)
+{
+    int i;
+    char addr[1],Buff[len];
+    addr[0]= (char)reg_addr;
+    //MySerial.printf("Read ID %d: addr %d:  len %d\n\r",dev_id,reg_addr,len);
+    int8_t rslt = 0; /* Return 0 for Success, non-zero for failure */
+
+
+    /*
+     * The parameter dev_id can be used as a variable to store the I2C address of the device
+     */
+
+    /*SensorI2C
+     * Data on the bus should be like
+     * |------------+---------------------|
+     * | I2C action | Data                |
+     * |------------+---------------------|
+     * | Start      | -                   |
+     * | Write      | (reg_addr)          |
+     * | Stop       | -                   |
+     * | Start      | -                   |
+     * | Read       | (reg_data[0])       |
+     * | Read       | (....)              |
+     * | Read       | (reg_data[len - 1]) |
+     * | Stop       | -                   |
+     * |------------+---------------------|
+     */
+
+    rslt=SensorI2C.write(dev_id,addr,1,true);
+    rslt+=SensorI2C.read(dev_id,Buff,len);
+    for (i=0; i<len; i++) reg_data[i]=(uint8_t)Buff[i];
+    return rslt;
+}
+int8_t user_i2c_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len)
+{
+
+    int i;
+    char addr[1],Buff[len];
+    addr[0]= (char)reg_addr;
+    for (i=0; i<len; i++) Buff[i]=(char)reg_data[i];
+    int8_t rslt = 0; /* Return 0 for Success, non-zero for failure */
+//MySerial.printf("Write ID %d: addr %d:  len %d\n\r",dev_id,reg_addr,len);
+    /*
+     * The parameter dev_id can be used as a variable to store the I2C address of the device
+     */
+
+    /*
+     * Data on the bus should be like
+     * |------------+---------------------|
+     * | I2C action | Data                |
+     * |------------+---------------------|
+     * | Start      | -                   |
+     * | Write      | (reg_addr)          |
+     * | Write      | (reg_data[0])       |
+     * | Write      | (....)              |
+     * | Write      | (reg_data[len - 1]) |
+     * | Stop       | -                   |
+     * |------------+---------------------|
+     */
+    rslt=SensorI2C.write(dev_id,addr,1,true);
+    rslt+=SensorI2C.write(dev_id,Buff,len);
+    return rslt;
+}
+
+int8_t SensorSetup()
+{
+    uint8_t settings_sel;
+    int8_t rslt;
+    MySensor.dev_id = BME280_I2C_ADDR_PRIM<<1;
+    MySensor.intf = BME280_I2C_INTF;
+    MySensor.read = user_i2c_read;
+    MySensor.write = user_i2c_write;
+    MySensor.delay_ms = user_delay_ms;
+    rslt = bme280_init(&MySensor);
+    /* Recommended mode of operation: Indoor navigation */
+    MySensor.settings.osr_h = BME280_OVERSAMPLING_1X;
+    MySensor.settings.osr_p = BME280_OVERSAMPLING_16X;
+    MySensor.settings.osr_t = BME280_OVERSAMPLING_2X;
+    MySensor.settings.filter = BME280_FILTER_COEFF_16;
+    MySensor.settings.standby_time = BME280_STANDBY_TIME_62_5_MS;
+    settings_sel = BME280_OSR_PRESS_SEL;
+    settings_sel |= BME280_OSR_TEMP_SEL;
+    settings_sel |= BME280_OSR_HUM_SEL;
+    settings_sel |= BME280_STANDBY_SEL;
+    settings_sel |= BME280_FILTER_SEL;
+    rslt = bme280_set_sensor_settings(settings_sel, &MySensor);
+    //Mypc.printf("result %d\n\r",rslt);
+    rslt += bme280_set_sensor_mode(BME280_NORMAL_MODE, &MySensor);
+    //Mypc.printf("result %d\n\r",rslt);
+    MySensor.delay_ms(70); // Should be ready after this.
+    return rslt;
+}
+int8_t SensorReadAll()
+{
+    int8_t rslt;
+    //MySerial.printf("SensorRealAll\n\r");
+    rslt = bme280_get_sensor_data(BME280_ALL, &SensorData, &MySensor);
+    //MySerial.printf("result %d\n\r",rslt);
+    return rslt;
+}
+void user_delay_ms(uint32_t period)
+{
+    /*
+     * Return control or wait,
+     * for a period amount of milliseconds
+     */
+    wait_ms(period);
+}
+
+
+void PersistDelay()
+{
+    int i;
+    for (i=0 ; i<Row; i++)if (ScreenMemory.PersistTime[i]>0)ScreenMemory.PersistTime[i]--;
+}
+
+void WriteToScreenMemory(char Buff[],int x,int y,int p)
+{
+    if (strlen( Buff)==0)return;
+    if (x>Col-1) return;
+    if (y>Row-1)return;
+    int i;
+    for (i=0; i< strlen( Buff); i++) {
+        if ((ScreenMemory.PersistTime[y]==0)||(p>0))ScreenMemory.Memory[x][y]=Buff[i];
+        if (x<Col-1) x++;
+        else {
+            y++;
+            x=0;
+        }
+        if (p>0) ScreenMemory.PersistTime[y]=p;
+        if (y>Row) return;
+    }
+    DisplayChanged=true;
+}
+
+void DisplayRefresh()
+{
+    DisplayChanged=false;
+    lcd.locate(0,0);
+    int x,y;
+    for (y=0; y<Row; y++) {
+        for (x=0; x<Col; x++) {
+            lcd.putc(ScreenMemory.Memory[x][y]);
+        }
+    }
+}
+
+void PageControl()
+{
+    Press++;
+    if (Debouncing()) return; // not the first press within the bounce period
+    PageNumber++;
+    if (PageNumber>(MaxPages-1)) PageNumber=0;
+    ClearScreen();
+}
+void ClearScreen()
+{
+    sprintf(MyBuffer,"                    ");
+    //12345678901234567890
+    WriteToScreenMemory(MyBuffer,0,0);
+    WriteToScreenMemory(MyBuffer,0,1);
+    WriteToScreenMemory(MyBuffer,0,2);
+    WriteToScreenMemory(MyBuffer,0,3); //worlds most inelegant clear screen
+}
+
+void Timed_out()
+{
+    DeBounce=false;// debouncing time is over
+}
+
+bool Debouncing()
+{
+    if (DeBounce)  return true;
+    DeBounce=true;
+    DebounceTimer.attach_us(&Timed_out,250000); //250ms period of debounce
+    return false;
+}
+
+
+void Flow_pulse()
+{
+    float Energy;
+    if (Flow_Detected) { //Flow was detected before
+        Flow_duration=Flow_timer.read_us();  //How long ago was that?
+    }
+    Flow_timer.reset();
+    No_Flow_Detect.attach(&Flow_Stopped, Flow_Detect_Time);//Reset our watchdog timer
+    Flow_Detected=true;
+    Energy=165.0*adc_Water_Diff.read()*4026.4/PulsesPerLitre; // 4.0264 kJ/l @ 50C Constant volume
+    TotalEnergyJ+=(uint64_t)Energy;
+
+}
+void Display()// Display and read the temps and calc power,energy
+{
+    //Mypc.printf("Starting display\n\r");
+    uint64_t TempEnergy;
+    unsigned int Energy;
+    struct TimeRecord temp;
+    Temp_Diff=165*adc_Water_Diff.read(); //20mV per C
+    Water_Temp=165*adc_Water_Temp.read(); //20mV per C
+    Room_Temp=330*adc_Room_Temp.read(); //10mV per C
+    TempEnergy=TotalEnergyJ-LastEnergy;  //Using a TempEnergy to force conversion to 32 bit after the subtraction
+    //Energy=1234;//Fixme delete me
+    LastEnergy=TotalEnergyJ;
+    Energy=TempEnergy;
+
+
+    if (Flow_Detected) {
+        Flow=Calc_Flow(Flow_duration);
+        Power=Energy/Display_time; // Power in watts equals energy per second
+    } else {
+        Flow=0;
+        Power=0;
+    }
+    emonPowerTX.Power=Power;
+    emonPowerTX.Flow=Flow*60; //flow in litres per minute  --per second is always small and rounded to 0
+    emonPowerTX.TempOut=Water_Temp*10;
+    emonPowerTX.TempDiff=Temp_Diff*10;
+    Flag_send_power_rf_data=true;
+    MySeconds=time(null);
+    //SizeOfTM=13,SizeOfH=14,SizeOfD=14,SizeOfW=7,SizeOfM=12,SizeOfY=12;
+    if (RTCValid) {
+        FillTimeRecord(&temp);
+        //Mypc.printf("%dm %dh %dd %dw %dm %dy\n\r",temp.TM,temp.H,temp.D,temp.D/7,temp.M,temp.Y);
+        if (int(Current.TM/10)!=int(temp.TM/10)) {
+            //Mypc.printf("Ten min %d %d\n\r",Current.TM,temp.TM);
+            Current.TM=temp.TM;
+            Rotate(TenMins,SizeOfTM);
+            if ((Current.TM==30) || (Current.TM==0)) {
+                FlagWriteDataToEEPROM=true;
+                Mypc.printf("stored data\n\r");
+            }
+            if (Current.H!=temp.H) {
+                //Mypc.printf("Hour %d %d\n\r",Current.H,temp.H);
+                Rotate(Hour,SizeOfH);
+                Current.H=temp.H;
+                if (Current.D!=temp.D) {
+                    //Mypc.printf("Day %d %d\n\r",Current.D,temp.D);
+                    Rotate(Day,SizeOfD);
+                    if (int(Current.D/7)!=int(temp.D/7)) {
+                        //Mypc.printf("Week %d %d\n\r",Current.D/7,temp.D/7);
+                        Rotate(Week,SizeOfW);
+                    }
+                    Current.D=temp.D;
+                    if (Current.M!=temp.M) {
+                        //Mypc.printf("Month %d %d\n\r",Current.M,temp.M);
+                        Current.M=temp.M;
+                        Rotate(Month,SizeOfM);
+                        if (Current.Y!=temp.Y) {
+                            //Mypc.printf("Year %d %d\n\r",Current.Y,temp.Y);
+                            Current.Y=temp.Y;
+                            Rotate(Year,SizeOfY);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    TenMins[0]+=Energy;
+    Hour[0]+=Energy;
+    Day[0]+=Energy;
+    Week[0]+=Energy;
+    Month[0]+=Energy;
+    Year[0]+=Energy;
+    switch (PageNumber) {
+        case 0:
+            Page1();
+            break;
+        case 1:
+            Page2();
+            break;
+        case 2:
+            Page3();
+            break;
+        case 3:
+            Page4();
+            break;
+        case 4:
+            Page5();
+            break;
+        case 5:
+            Page6();
+            break;
+        case 6:
+            Page7();
+            break;
+        case 7:
+            Page8();
+            break;
+        case 8:
+            Page9();
+            break;
+        case 9:
+            Page10();
+            break;
+        case 10:
+            Page11();
+            break;
+        case 11:
+            Page12();
+            break;
+        case 12:
+            Page13();
+            break;
+        case 13:
+            Page14();
+            break;
+    }
+}
+void Page1()
+{
+
+    sprintf(MyBuffer,"Flw=%3.2f Pwr=%4.0fW ",Flow*60,Power);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"Water Temp=%3.1f%cC   ", Water_Temp,223);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"Water Diff=%3.2f%cC    ", Temp_Diff,223);
+    WriteToScreenMemory(MyBuffer,0,2);
+    if (Sensing) sprintf(MyBuffer,"Rm Temp=%3.1f%cC  %s", TempS,223,MSF.Valid()? "MSF":"   ");
+    else         sprintf(MyBuffer,"Sensor Fail    %s",MSF.Valid()? "MSF":"   ");
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page2()
+{
+    strftime(Timebuff, 20, "%a %d %b %T", localtime(&MySeconds) );
+    if (Sensing) sprintf(MyBuffer,"Room=%2.1f%cC         ",TempS,223);
+    else         sprintf(MyBuffer,"Sensor Fail         ");
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"Humidity=%2.1f%%      ",HumidityS);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"Atm Pressure=%-4.1f ",PressureS);
+    WriteToScreenMemory(MyBuffer,0,2);
+    WriteToScreenMemory(Timebuff,0,3);
+}
+
+void Page3()
+{
+    float kJ[SizeOfTM];
+    ConvertJouleToKiloJoule(TenMins,kJ,SizeOfTM);
+    sprintf(MyBuffer,"Energy kJ in 10mins");
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"E0=%-6.1f,E1=%-7.1f",kJ[0],kJ[1]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"E2=%-6.1f,E3=%-7.1f",kJ[2],kJ[3]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"E6=%-6.1f,E5=%-7.1f",kJ[4],kJ[5]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page4()
+{
+    float kJ[SizeOfTM];
+    ConvertJouleToKiloJoule(TenMins,kJ,SizeOfTM);
+    sprintf(MyBuffer,"kJ/10min  E6=%-7.1f",kJ[6]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"E7=%-6.1f,E8=%-7.1f",kJ[7],kJ[8]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"E9=%-6.1f,E10=%-6.0f",kJ[9],kJ[10]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"E11=%-5.0f,E12=%-5.0f",kJ[11],kJ[12]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page5()
+{
+    float kWh[SizeOfH];
+    ConvertJouleTokWh(Hour,kWh,SizeOfH);
+    sprintf(MyBuffer,"kWh/Hr Hr0=%-4.2f",kWh[0]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"Hr1=%-5.2f,Hr2=%-5.2f",kWh[1],kWh[2]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"Hr3=%-5.2f,Hr4=%-5.2f",kWh[3],kWh[4]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"Hr5=%-5.2f,Hr6=%-5.2f",kWh[5],kWh[6]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page6()
+{
+    //SizeOfTM=13,SizeOfH=14,SizeOfD=14,SizeOfW=7,SizeOfM=12,SizeOfY=12;
+    float kWh[SizeOfH];
+    ConvertJouleTokWh(Hour,kWh,SizeOfH);
+    sprintf(MyBuffer,"Hr7=%-5.2f,Hr8=%-5.2f",kWh[7],kWh[8]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"Hr9=%-5.2f,Hr10=%-5.2f",kWh[9],kWh[10]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"Hr11=%-5.2f",kWh[11]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"Hr12=%-5.2f",kWh[12]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page7()
+{
+    float kWh[SizeOfD];
+    ConvertJouleTokWh(Day,kWh,SizeOfD);
+    sprintf(MyBuffer,"kWh/Day Dy0=%-5.2f",kWh[0]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"Dy1=%-5.2f,Dy2=%-5.2f",kWh[1],kWh[2]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"Dy3=%-5.2f,Dy4=%-5.2f",kWh[3],kWh[4]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"Dy5=%-5.2f,Dy6=%-5.2f",kWh[5],kWh[6]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page8()
+{
+    float kWh[SizeOfD];
+    ConvertJouleTokWh(Day,kWh,SizeOfD);
+    sprintf(MyBuffer,"kWh wk2 Dy0=%-5.2f",kWh[7]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"Dy1=%-5.2f,Dy2=%-5.2f",kWh[8],kWh[9]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"Dy3=%-5.2f,Dy4=%-5.2f",kWh[10],kWh[11]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"Dy5=%-5.2f,Dy6=%-5.2f",kWh[12],kWh[13]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+
+void Page9()
+{
+    float kWh[SizeOfW];
+    ConvertJouleTokWh(Week,kWh,SizeOfW);
+    sprintf(MyBuffer,"kWh/wk wk0=%-5.1f",kWh[0]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"wk1=%-5.1f,wk2=%-5.1f",kWh[1],kWh[2]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"wk3=%-5.1f,wk4=%-5.1f",kWh[3],kWh[4]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"wk5=%-5.1f,wk6=%-5.1f",kWh[5],kWh[6]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page10()
+{
+    float kWh[SizeOfM];
+    ConvertJouleTokWh(Month,kWh,SizeOfM);
+    sprintf(MyBuffer,"kWh/Mth M0=%-5.0f",kWh[0]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"M1=%-5.0f,M2=%-5.0f",kWh[1],kWh[2]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"M3=%-5.0f,M4=%-5.0f",kWh[3],kWh[4]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"M5=%-5.0f,M6=%-5.0f",kWh[5],kWh[6]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page11()
+{
+    float kWh[SizeOfM];
+    ConvertJouleTokWh(Month,kWh,SizeOfM);
+    sprintf(MyBuffer,"kWh/Mth M7=%-5.0f",kWh[7]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"M8=%-5.0f,M9=%-5.0f",kWh[8],kWh[9]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"kWh/M10=%-5.0f",kWh[10]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"kWh/M11=%-5.0f",kWh[11]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page12()
+{
+    float kWh[SizeOfY];
+    ConvertJouleTokWh(Year,kWh,SizeOfY);
+    sprintf(MyBuffer,"kWh/Yr Y0=%-5.0f",kWh[0]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"Y1=%-5.0f,Y2=%-5.0f",kWh[1],kWh[2]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"Y3=%-5.0f,Y4=%-5.0f",kWh[3],kWh[4]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"Y5=%-5.0f,Y6=%-5.0f",kWh[5],kWh[6]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page13()
+{
+    float kWh[SizeOfY];
+    ConvertJouleTokWh(Year,kWh,SizeOfY);
+    sprintf(MyBuffer,"kWh/Yr Y7=%-5.0f",kWh[7]);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"Y8=%-5.0f,Y9=%-5.0f",kWh[8],kWh[9]);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"Y10=%-5.0f",kWh[10]);
+    WriteToScreenMemory(MyBuffer,0,2);
+    sprintf(MyBuffer,"Y11=%-5.0f",kWh[11]);
+    WriteToScreenMemory(MyBuffer,0,3);
+}
+void Page14()
+{
+    strftime(Timebuff, 20, "%a %d %b %T", localtime(&MySeconds) );
+    sprintf(MyBuffer,"Atm Pressure=%f-4.1",PressureS);
+    WriteToScreenMemory(MyBuffer,0,0);
+    sprintf(MyBuffer,"Shrt term rise=%f3.3",AvePressureS-ShortAvePressureS);
+    WriteToScreenMemory(MyBuffer,0,1);
+    sprintf(MyBuffer,"Lng term rise=%f3.3",ShortAvePressureS-LongAvePressureS);
+    WriteToScreenMemory(MyBuffer,0,2);
+    //WriteToScreenMemory(Timebuff,0,3);
+
+    WriteToScreenMemory(Version,0,3);
+}
+
+void Rotate(uint64_t Arr[],int size)
+{
+    for (int i=size-1; i>0 ; i--) {
+        Arr[i]=Arr[i-1];
+    }
+    Arr[0]=0;
+}
+void Flow_Stopped()
+{
+    No_Flow_Detect.detach();
+    Flow_Detected=false;
+}
+
+float Calc_Flow(int fd)
+{
+    //Calculate flow from time in microseconds between pulses
+//288 pulses per litre
+    if (fd==0) { //infinite flow WOW
+        return 123456;
+    } else {
+        return (1000000/(fd*PulsesPerLitre*Display_time)); //Flow in litres per second
+    }
+}
+
+void send_power_rf_data(int ToAddress, int FromAddress)
+{
+    MyRadio.sleep(false); //wakeup
+    MyRadio.setNodeID(FromAddress);
+    int i = 0;
+    while (!MyRadio.canSend() && i<10) {
+        MyRadio.receiveDone();
+        i++;
+    }
+    MyRadio.send(ToAddress, &emonPowerTX, sizeof emonPowerTX);
+    MyRadio.sleep(true);
+
+}
+
+void send_enviro_rf_data(int ToAddress, int FromAddress)
+{
+    MyRadio.sleep(false); //wakeup
+    MyRadio.setNodeID(FromAddress);
+    int i = 0;
+    while (!MyRadio.canSend() && i<10) {
+        MyRadio.receiveDone();
+        i++;
+    }
+    MyRadio.send(ToAddress, &emonEnvTX, sizeof emonEnvTX);
+    MyRadio.sleep(true);
+
+}
+bool Set_RTC_Time(time_t epoch_time)
+{
+    //Taken from here https://os.mbed.com/users/brianclaus/code/ds3231/file/b87f3e7258bb/ds3231.cpp/
+    //  Original code by Brian Claus
+    //  Many thanks for sharing.
+    bool  success = false;
+    //system vars
+    struct tm * sys_time;
+    //RTC vars
+    ds3231_time_t rtc_time = {0,0,0,0,0};
+    ds3231_calendar_t rtc_calendar = {0,0,0,0};
+    Mypc.printf("epoch set to-%d\n\r\n",epoch_time);
+    sys_time = localtime(&epoch_time);
+    set_time(epoch_time);
+    //localtime comes back as 24hour
+
+    rtc_time.mode = 0;
+    rtc_time.hours = sys_time->tm_hour;
+    rtc_time.minutes = sys_time->tm_min;
+    rtc_time.seconds = sys_time->tm_sec;
+
+    rtc_calendar.day  = sys_time->tm_wday + 1;
+    rtc_calendar.date = sys_time->tm_mday;
+    rtc_calendar.month = sys_time->tm_mon + 1;
+    rtc_calendar.year = sys_time->tm_year - 100;
+
+    success = 0==rtc.set_calendar(rtc_calendar);
+    success &= 0==rtc.set_time(rtc_time);
+    wait_us(250);
+
+    return success;
+}
+
+void FillTimeRecord(struct TimeRecord *TR)
+{
+
+    time_t secs;
+    struct tm *CT; //current time
+    secs=time(null);
+    CT=localtime(&secs);
+    TR->TM=CT->tm_min;
+    TR->H=CT->tm_hour;
+    TR->D=CT->tm_yday;
+    TR->M=CT->tm_mon;
+    TR->Y=CT->tm_year;
+}
+void ConvertJouleTokWh(uint64_t Joule[], float kWh[], int size)
+{
+    // Function to convert Joules to kWh
+    // A lot of energy being transfered.  Will this function get hot?
+    float a;
+    int i;
+    for (i=0; i<size; i++) {
+        a=Joule[i];
+        kWh[i]=a/3600000;
+    }
+}
+void ConvertJouleToKiloJoule(uint64_t Joule[], float kJ[], int size)
+{
+    float a;
+    int i;
+    for (i=0; i<size; i++) {
+        a=Joule[i];
+        kJ[i]=a/1000;
+    }
+}
+void ReadDataFromEEPROM(uint64_t Target[], int size, uint32_t address)
+{
+    int count;
+    int data;
+    address = address + 0x08080000;
+    data = *((uint32_t *)address);//high 4 bytes
+    Target[0] = data;
+    //Mypc.printf("Address %d  high data %d\n\r",address,data); //fixme
+    Target[0] = Target[0]<<32;
+    address+=4;
+    data = *((uint32_t *)address);//low 4 bytes
+    //Mypc.printf("Address %d  low data %d\n\r",address,data); //fixme
+    Target[0] |= data;
+    address+=4;
+    for (count=1; count<size; count++) {
+        data = *((uint32_t *)address);
+        //Mypc.printf("Address %d   high data %d\n\r",address,data); //fixme
+        Target[count] =data;
+        Target[count] = Target[count]<<32;
+        address+=4;
+        data = *((uint32_t *)address);
+        Target[count] |= data;
+        //Mypc.printf("Address %d   low data %d\n\r",address,data); //fixme
+        address+=4;
+    }
+}
+void WriteDataToEEPROM(uint64_t Target[], int size, uint32_t address)
+{
+    int count;
+    uint32_t data;
+    address = address + 0x08080000;
+    HAL_FLASHEx_DATAEEPROM_Unlock();
+    data= 0xFFFFFFFF&(Target[0]>>32);//high
+    //   HAL_FLASHEx_DATAEEPROM_ProgramDoubleWord(address , Target[0]);
+    //data=0x000000A0;//fixme
+    HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, address, data);
+    //Mypc.printf("Address %d high data %d\n\r",address,data); //fixme
+    address+=4;
+    data= 0xFFFFFFFF&Target[0];//low
+    //data= 0x000000A0;//fixme
+    HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, address, data);
+    //Mypc.printf("Address %d low data %d\n\r",address,data); //fixme
+    address+=4;
+    for (count=1; count<size; count++) {
+        data= 0xFFFFFFFF&(Target[count]>>32);//high
+        HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, address, data);
+        //Mypc.printf("Address %d   high data %d\n\r",address,data); //fixme
+        address+=4;
+        data= 0xFFFFFFFF&Target[count];//low
+        HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, address, data);
+        //Mypc.printf("Address %d   low data %d\n\r",address,data); //fixme
+        address+=4;
+    }
+    HAL_FLASHEx_DATAEEPROM_Lock();
+}
+void ReadDataFromEEPROM()
+{
+    ReadDataFromEEPROM(TenMins,SizeOfTM,MemOffsetTM);
+    ReadDataFromEEPROM(Hour,SizeOfH,MemOffsetH);
+    ReadDataFromEEPROM(Day,SizeOfD,MemOffsetD);
+    ReadDataFromEEPROM(Week,SizeOfW,MemOffsetW);
+    ReadDataFromEEPROM(Month,SizeOfM,MemOffsetM);
+    ReadDataFromEEPROM(Year,SizeOfY,MemOffsetY);
+}
+void WriteDataToEEPROM()
+{
+    WriteDataToEEPROM(TenMins,SizeOfTM,MemOffsetTM);
+    WriteDataToEEPROM(Hour,SizeOfH,MemOffsetH);
+    WriteDataToEEPROM(Day,SizeOfD,MemOffsetD);
+    WriteDataToEEPROM(Week,SizeOfW,MemOffsetW);
+    WriteDataToEEPROM(Month,SizeOfM,MemOffsetM);
+    WriteDataToEEPROM(Year,SizeOfY,MemOffsetY);
+
+}
+void InitSensor()
+{
+    DigitalInOut pin_sda(My_sda, PIN_INPUT, PullNone, 1);
+    DigitalInOut pin_scl_in(My_scl, PIN_INPUT, PullNone, 1);
+    // Read and verify if recovery is required
+    if (pin_scl_in == 1) {
+        if (pin_sda == 1) {
+            // Return successfuly as SDA and SCL is high
+            if (SensorSetup()==0) Sensing=true;
+            else Sensing=false;
+            return;
+        }
+    } else {
+        // Return as SCL is low and no access to become master.
+        Mypc.printf("I2c clock is being held low.  I2C is cripled\n\r");
+        return ;
+    }
+    DigitalInOut pin_scl_out(My_scl, PIN_OUTPUT, PullNone, 1);
+    Mypc.printf("I2C busy.  Attempt to clock until it has been freed");
+    int count = 40;
+    while ( (!pin_sda) && (count > 0)) {
+        count--;
+        pin_scl_out=!pin_scl_out;
+        wait_us(5);
+    }
+    if (count==0)Mypc.printf("Clocking has not worked");
+    if (SensorSetup()==0) Sensing=true;
+    else Sensing=false;
+
+}
+int main()
+{
+    time_t secs;
+    Mypc.baud(115200);
+    Mypc.printf("Starting main\n\r");
+    ClearScreen();
+    // Uncomment WriteDataToEEPROM to zero data fixme
+    //WriteDataToEEPROM();
+    //Mypc.printf("Stored zeroed data\n\r\n\r");
+    if(SensorSetup()==0) Sensing=true;// This setup taken from Bosch sensor github
+    else Sensing=false;
+    char buffer[8];
+    Flow_meter.fall(&Flow_pulse);
+    Display_Ticker.attach(&Display,Display_time);
+    Flow_timer.start();
+    PageButton.fall(&PageControl);
+    PageButton.mode(PullUp);
+    bool worked=false;
+    int counter;
+    ds3231_cntl_stat_t rtc_control_status = {0,0};
+    rtc.set_cntl_stat_reg(rtc_control_status);
+    wait_us(250);
+    while (!worked) {
+        worked=MyRadio.initialize(RF_freq,nodeID_emonpwr,networkGroup);
+    }
+    Mypc.printf("Radio up\n\r");
+    lcd.printf("Radio up ");
+    MyRadio.sleep(true);
+    ReadDataFromEEPROM();
+
+    Mypc.printf("Starting sensor\n\r");
+    lcd.printf("Starting sensor ");
+    InitSensor();
+    SensorReadAll();
+    TempS=SensorData.temperature;
+    PressureS=SensorData.pressure/100;
+    HumidityS=SensorData.humidity;
+    LongAvePressureS=PressureS; //init value
+    ShortAvePressureS=PressureS;
+    AvePressureS=PressureS;
+    if ((PressureS>825.0) && (PressureS<1300.0))Sensing=true;
+    else Sensing=false;
+    Mypc.printf("%f- %f- %f   \n\r",TempS,PressureS,HumidityS);
+    lcd.printf("%f- %f- %f  ",TempS,PressureS,HumidityS);
+
+    wait(1);
+    //Set_RTC_Time(1533992221);
+    ClearScreen();
+    Mypc.printf("Starting loop\n\r");
+    while (true) {
+        //Mypc.printf("Tick\n\r");
+        /*  RTCValid simply check that the time is not old and fairly recent
+        so thr RTC has been set in the past
+        IsTimeSet is true is MSF has been received and was put into the RTC
+        */
+        if (!RTCValid) {
+            secs=rtc.get_epoch();
+            wait_us(250);
+            if (secs>1572611760) {
+                set_time(secs);
+                RTCValid=true; // Real time clock has a real time in it
+                FillTimeRecord(&Current);
+                Mypc.printf("%dm %dh %dd %dw %dm %dy\n\r",Current.TM,Current.H,Current.D,Current.D/7,Current.M,Current.Y);
+            }
+        }
+        if ((!IsTimeSet)&&(MSF.Valid())) {
+            secs=MSF.Read();
+            if (secs>1572611760) {
+                IsTimeSet=Set_RTC_Time(secs);  //Set external RTC + internal rtc
+                // I see this is done by Set_RTC_Time //set_time(secs);                //Set internal RTC
+                Mypc.printf("Time set %d\n\r",secs);
+                RTCValid=true; // Real time clock has a real time in it
+                FillTimeRecord(&Current);
+                Mypc.printf("%dm %dh %dd %dw %dm %dy\n\r",Current.TM,Current.H,Current.D,Current.D/7,Current.M,Current.Y);
+            } else {
+                RTCValid=false; // Time is not right
+                IsTimeSet=false;
+                Mypc.printf("Time UNset\n\r");
+            }
+        }
+        secs=time(null);// Read time and set if 02:10 from msf time
+        strftime(buffer,8,"%T",localtime(&secs));
+        if (strncmp(buffer,"02:10:00",8)==0) { //set the time each day
+            //printf("compare %s",buffer);
+            wait(1);  //  wait one second to stop this being called twice Assumes RTC clock is not that bad
+            IsTimeSet=false;
+        }
+        if (SensorReadAll()==0) {
+            Sensing=true;
+            TempS=SensorData.temperature;
+            PressureS=SensorData.pressure/100;
+            HumidityS=SensorData.humidity;
+            AvePressureS=(PressureS+AvePressureS)/2;
+            ShortAvePressureS=(ShortAvePressureS*0.9)+(AvePressureS*0.1);
+            LongAvePressureS=(LongAvePressureS*0.99)+(ShortAvePressureS*0.01);
+        } else InitSensor();
+
+//Mypc.printf("tick\n\r");
+        for (counter=1 ; counter<111; counter++) {//delay + display update if necessary
+            wait_ms(10);
+            if (DisplayChanged)DisplayRefresh();
+            if (Flag_send_power_rf_data) {
+                Flag_send_power_rf_data=false;
+                send_power_rf_data(0,nodeID_emonpwr);
+                wait_ms(25);// Delay between transmissions - modems are slow
+            }
+            if (FlagWriteDataToEEPROM) {  //Do we update eeprom?
+                FlagWriteDataToEEPROM=false;
+                WriteDataToEEPROM();
+            }
+        }
+        if (Sensing) {
+            emonEnvTX.temperature=10*TempS;// times ten so we can divide by 10 at the other end and get 1 decimal place
+            emonEnvTX.pressure=10*PressureS;
+            emonEnvTX.humidity=10*HumidityS;
+            send_enviro_rf_data(0, nodeID_emonenv);
+        }
+        //Mypc.printf("Long=%f1.3 Short=%f1.3 \n\r",LongAvePressureS,ShortAvePressureS);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Fri Jan 07 12:23:44 2022 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/mbed_official/code/mbed/builds/65be27845400
\ No newline at end of file