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

Dependencies:   mbed RFM69 MSF_Time ds3231 TextLCD

Revision:
0:17c0192cb3d7
--- /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);
+    }
+}