/* ===== pfod Command for Garage Heater v1 ==== pfodApp msg {.} --> {,~Garage Heater v1`1000~V13|!L`1~(Heater is ~~Off) \Running)~t|!E~|A`0~Start / Stop~~\ (Working...)~t|!C~|F<-2>`0~Increment Power Level~~ \ (Working...)~t|I<-2>`0~Cycle Display ~~ \ (Working...)~t|K<-2>`0~Manual / Auto~~ \ (Working...)~t|!B~|!H<-2>`775~Inlet Air Temp: ~ (Deg C)`1023`0~1023.0~0~t|!N<-2>`775~Outlet Air Temp: ~ (Deg C)`1023`0~1023.0~0~t|!G<-4>`4~Power Level: ~`6`1~6~1~ |!D<-2>`7~Fuel Level: ~ Litres`10`0~10~0~ } Smoothing: Reads Windy repeatedly from an analog input, calculating a running average and printing it to the computer. Keeps ten readings in an array and continually averages them. Define the number of samples to keep track of. The higher the number, the more the readings will be smoothed, but the slower the output will respond to the input. Using a constant rather than a normal variable lets us use this value to determine the size of the readings array. //////////////////////////////////////////////////////////////////////////////// Screen items - top to bottom: L Label Heater is... 1=Off, 2=Starting, 3=Running, 4=Cooling Down 5= PreHeating Windy 6= No Fuel E Label Low Fuel!!!! M Label This will take... A Button Start/Stop O Label Blank P Label Blank Q Label Blank R Label Blank U Slider (Slider) Time to cool down V Slider (Slider) Time to warm up T Slider (Slider) Time to stabilise Windy S Label Blank C Label Blank F Button Inc Power Level I Button Cycle Display K Button Manual/Auto B Label Blank G Slider (Text) Heater Power Level H Slider (Text) Inlet Air Temp N Slider (Text) Outlet Air Temp J Slider Intake Air Flow D Slider Fuel Level //////////////////////////////////////////////////////////////////////////////// */ #include // Included for the power sequence #include #include #include #include #include // Include the one-wire comms library #include // Include the Temp Sensors library #include "HX711.h" // Lib for the HX711 amp & loadcell #include // V2.31 or higher int swap01(int); // method prototype for slider end swaps pfodSecurity parser("V14"); // create a parser with menu version string to handle the pfod messages pfodESP8266BufferedClient bufferedClient; //#define DEBUG; // Use to send o/p to Serial (disconnect relay board first) #define WLAN_SSID "**********" // cannot be longer than 32 characters! #define WLAN_PASS "**********" #define ONE_WIRE_BUS 12 // Data wire(s) connecteded into Pin 2 on the Arduino (any D I/O will work) #define Data_Out_Pin 13 // Can be other Digital I/O pins #define Serial_Clock_Pin 14 #define pfodSecurityCode "" //Means no pfod password // Hard coded variables that can be changed:///////////////////////////////////////////////////// int Thresh = 10; // Threshold differences to include a hysteresis loop (stops flickering between fuel colour levels) int LowLevel = 500; // 'Low' fuel level (x 100) int MediumLevel = 1000; // 'Medium' fuel level (x 100) int FuelMin = 50; // Min fuel level to run (in litres x 100) int TempDiff = 20; // Difference between in & out temps (x10) (to check if the heater is running) int PreHeatFlow; // Flow rate to decide when it's warmed up & to stop flashing the running light int PreHeatPwr1 = 60; // Different 'warmed-up' flow rates for different power levels int PreHeatPwr2 = 80; int PreHeatPwr3 = 100; int PreHeatPwr4 = 130; int PreHeatPwr5 = 150; //long zero_factor = 608129; // This is the tare weight (platform & empty drum) (was 621807) //long zero_factor = 608129; // This is the tare weight (platform & empty Blue Barrel) long zero_factor = 746844; // This is the tare weight (platform & empty Blue Barrel + 6.24l min fuel to stop filter drawing air //float calibration_factor = 22372; // Figs obtained from the 'Load Cell Calibration' sketch on 30/1/21 with 10l drum float calibration_factor = 20920; // Figs obtained from the 'Load Cell Calibration' sketch on 30/1/21 with 60l barrel float MinFlowRate = 15; // Threshold where the 'running' & 'Shutting Down' lights switch off float ZeroWind = 0.0074128125; // Adjust to get zero reading with zero wind. 0.0001 = about 1.0 l/m /* Increasing the number decreases the speed. Fourth digit from dec point is enough! */ const char staticIP[] = "192.168.0.91"; // Leave it as "" for DHCP, but DHCP is not recommended. const int portNo = 4989; // What TCP port to listen on for connections. const int numReadings = 20; // To average Windy i/p (A0) const int Is_Running_pin = 15; // name the output pin for 'Running LED' const int Cool_Down_pin = 16; // name the output pin for 'Stopped LED' const int cmd_A_pin = 5; // name the output pin for 'Start / Stop' const int cmd_F_pin = 4; // name the output pin for 'Increment Power Level' const int cmd_I_pin = 2; // name the output pin for 'Cycle Display' const int cmd_K_pin = 1; // name the output pin for 'Manual / Auto' const int cmd_J_pin = A0; // name the pin for 'Intake Air Flow' const float zeroWindAdjustment = .4; // Windy: negative numbers yield smaller wind speeds and vice versa. unsigned long cmd_F_pin_On_Off[] = {500, 1000, 500, 1000, 500, 1000, 1000}; // Power sequence (in ms) // off, on, off, on, off, on, off unsigned long cmd_A_PULSE_LENGTH = 2500; // 2.5 secs unsigned long cmd_F_PULSE_LENGTH = 1000; // 1.0 secs unsigned long cmd_I_PULSE_LENGTH = 200; // 0.2 secs - a long press doesn't work with this one (cycle display) unsigned long cmd_K_PULSE_LENGTH = 2500; // 2.5 secs unsigned long cmd_J_ADC_READ_INTERVAL = 1000; // 1sec, edit this to change adc read interval unsigned long WindyReadInterval = 400; // Time in mS between reading Windy unsigned long CoolMillis; // Timer to flash the cooling LED unsigned long CoolInterval = 250; // Flashes LED every 1/4 second unsigned long PreHeatMillis; // Timer to flash running light unsigned long PreHeatInterval = 250; // Flashes LED every 1/4 second unsigned long KillMillis; // Timer to pulse the on/ff button if no fuel unsigned long KillMillisInterval = 3000; // Time to 'press' the on/off button unsigned long WindyHeatMillis; // Timer for pre-heating Windy unsigned long WindyHeatInterval = 30000; // Time to wait for Windy to be stable (needs 5 - 10 secs to warm up + array averaging update time = 30 secs) unsigned long CoolSlideMillis; // Timer for the cool down slider to increment unsigned long CoolSlideInterval = 1000; // Update the slider every sec unsigned long WarmSlideMillis; // Timer for the warm up slider to increment unsigned long WarmSlideInterval = 1000; // Update the slider every sec const size_t Number_of_Delays = 7; // can be smaller than the number of elements in cmd_F_pin_On_Off but not larger ////////////////////////////////////////////////////////////////////////////////////////////////// bool cmd_F_pin_On = false; bool Done = true; // Flags when the power sequence is finished bool Enabled = false; // Flag for enabling 3 buttons and lots of formatting bool cmd_A_pulseRunning = false; // true when cmd_A pulse running bool Is_Running_var = 0; // Variable used to set the pin output state (1=off) bool cmd_F_pulseRunning = false; // true when cmd_F pulse running bool InletThermoOK = true; // Flag if inlet thermo is not found bool OutletThermoOK = true; // Flag if outlet thermo is not found bool cmd_I_pulseRunning = false; // true when cmd_I pulse running bool clearPlot = false; // set by the {@} response code bool CheckNeeded = true; // Single temp diff check after reboot to see if heater is off or on bool NoFuel = false; // Flag so the heater won't start if there's low fuel bool cmd_K_pulseRunning = false; // true when cmd_K pulse running bool Dead = false; // 'No fuel' calls KillIt, 'Dead ensures it only runs once bool HoldFuelLevel = false; // Once triggered by no fuel, holds the timer open to shut down bool PreHeatWindy = true; // Windy needs to stabilise int readings[numReadings]; // the readings from Windy int readIndex = 0; // the index of the current reading int ArrayTotal = 0; // the running total int WindyAverage = 0; // the average Windy reading int cmd_L_var = 5; // name the variable for '(Heater is' 0=Off 1=Starting 2=Running 3= Cooling Down 4= Warming Up 5= PreHeating Windy 6= No Fuel) int cmd_A_var; // name the variable for 'Start / Stop' 0= 1= (Working Hard...) int Cool_Down_var = 0; // Variable used to set the pin output state (1 = off) int cmd_F_var; // name the variable for 'Increment Power Level' 0= 1= (Working Hard...) int cmd_I_var; // name the variable for 'Cycle Display' 0= 1= (Working Hard...) int cmd_K_var; // name the variable for 'Manual / Auto' 0= 1= (Working Hard...) int cmd_H_var; // name the variable for 'Inlet Air Temp:' int cmd_N_var; // name the variable for 'Outlet Air Temp:' int cmd_G_var = 0; // name the variable for 'Power Level:' int cmd_D_var; // name the variable for 'Fuel Level:' int cmd_T_var = 0; // name the variable for 'Windy Stable Time' int cmd_J_var; // name the variable for 'Intake Air Flow' int TempCtimes100; int CoolSlideVal = 0; // Level of the cooling down slider int WarmSlideVal = 0; // Level of the warming up slider int TempCount = 0; // Counts the number of times 'No Fuel' is flagged (currently 100) before firing NoFuel & shutting down heater float Reading = 0; float TMP_Therm_ADunits; //temp termistor value from wind sensor float RV_Wind_ADunits; //RV output from wind sensor float RV_Wind_Volts; float zeroWind_ADunits; float zeroWind_volts; float WindSpeed_MPH; float FlowRate; float WindyTempC; unsigned long cmd_A_pulseStartTime = 0; // the time when cmd_A pulse started unsigned long cmd_F_pulseStartTime = 0; // the time when cmd_F pulse started unsigned long cmd_I_pulseStartTime = 0; // the time when cmd_I pulse started unsigned long cmd_K_pulseStartTime = 0; // the time when cmd_K pulse started unsigned long plot_mSOffset = 0; // set by {@} response unsigned long lastMillis; WiFiServer server(portNo); WiFiClient client; WiFiClient* clientPtr; // non-null if have connected client OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) DallasTemperature sensors(&oneWire); // Pass the oneWire reference to Dallas Temperature. HX711 scale; DeviceAddress inletThermometer = { 0x28, 0xAA, 0x55, 0x51, 0x55, 0x14, 0x01, 0x5A }; // Fitted Thermo's sensor addresses asigned manually. DeviceAddress outletThermometer = { 0x28, 0xD0, 0x84, 0xE8, 0x5E, 0x14, 0x01, 0x5D }; size_t stepIdx = 0; // Which delay above it's looking at millisDelay cmd_F_pin_Delays; // the delay object pfodDelay cmd_J_adcTimer; // ADC timer void setup() { // Explicitly set the ESP8266 to be a WiFi-client, otherwise, it by default, // would try to act as both a client and an access-point and could cause // network-issues with your other WiFi-devices on your WiFi-network. WiFi.mode(WIFI_STA); EEPROM.begin(512); // only use 20bytes for pfodSecurity but reserve 512 (pfodWifiConfig uses more) cmd_A_var = 1; // i.e. Output is OFF pinMode(cmd_A_pin, OUTPUT); // output for 'Start / Stop' is initially LOW, digitalWrite(cmd_A_pin, cmd_A_var); // set output cmd_F_var = 1; pinMode(cmd_F_pin, OUTPUT); // output for 'Increment Power Level' is initially LOW, digitalWrite(cmd_F_pin, cmd_F_var); // set output cmd_I_var = 1; pinMode(cmd_I_pin, OUTPUT); // output for 'Cycle Display' is initially LOW, digitalWrite(cmd_I_pin, cmd_I_var); // set output cmd_K_var = 1; pinMode(cmd_K_pin, OUTPUT); // output for 'Manual / Auto' is initially LOW, digitalWrite(cmd_K_pin, cmd_K_var); // set output pinMode(Is_Running_pin, OUTPUT); // output for 'Running LED' is initially LOW, digitalWrite(Is_Running_pin, Is_Running_var); // set output (LED off) pinMode(Cool_Down_pin, OUTPUT); // output for 'Running LED' is initially LOW, digitalWrite(Cool_Down_pin, Cool_Down_var); // set output (LED off) startSequence(); // Just to set the variable 'justFinished' to true so the power sequence runs when called the first time #ifdef DEBUG Serial.begin(115200); Serial.println(); #endif if (*staticIP != '\0') { //Initialise wifi module IPAddress ip(pfodESP8266Utils::ipStrToNum(staticIP)); IPAddress gateway(ip[0], ip[1], ip[2], 1); // set gatway to ...192.168.0.1 #ifdef DEBUG Serial.print(F("Setting gateway to: ")); Serial.println(gateway); #endif IPAddress subnet(255, 255, 255, 0); WiFi.config(ip, gateway, subnet); } WiFi.begin(WLAN_SSID, WLAN_PASS); while (WiFi.status() != WL_CONNECTED) { delay(500); #ifdef DEBUG Serial.print("."); #endif } #ifdef DEBUG Serial.println(); Serial.println("WiFi connected"); #endif server.begin(); // Start the server #ifdef DEBUG Serial.println("Server started"); #endif #ifdef DEBUG Serial.println(WiFi.localIP()); // Print the IP address #endif cmd_J_adcTimer.start(cmd_J_ADC_READ_INTERVAL); // start ADC timer sensors.begin(); // Start up the temp sensors library scale.begin(Data_Out_Pin, Serial_Clock_Pin); // Start the scale library sensors.setResolution(10); // Sets the resolution to 0.1 of a degree scale.set_offset(zero_factor); // Subtract the tare weight (offset) from the measured weight scale.set_scale(calibration_factor); // Adjust the readings returned to litres (0.832 Kg/Litre) if (!sensors.getAddress(inletThermometer, 0)) InletThermoOK = false; //Check for sensors available and set flag if not OK else InletThermoOK = true; if (!sensors.getAddress(outletThermometer, 1)) OutletThermoOK = false; else OutletThermoOK = true; for (int thisReading = 0; thisReading < numReadings; thisReading++) { // initialize all Windy readings to 0: readings[thisReading] = 0; } WindyHeatMillis = millis(); // Start Windy pre-heat timer } void loop() { if (clientPtr && (!clientPtr->connected())) { closeConnection(parser.getPfodAppStream()); } if (server.hasClient()) { // new connection if (!clientPtr) { // no existing connection client = server.available(); // get any new client clientPtr = &client; // have connected client parser.connect(bufferedClient.connect(clientPtr), F(pfodSecurityCode)); // sets new io stream to read from and write to EEPROM.commit(); // does nothing if nothing to do } else { // already have client so just stop the new one WiFiClient newClient = server.available(); // get any new client newClient.stop(); } } if (clientPtr) { uint8_t cmd = parser.parse(); // parse incoming data from connection // parser returns non-zero when a pfod command is fully parsed if (cmd != 0) { // have parsed a complete msg { to } uint8_t* pfodFirstArg = parser.getFirstArg(); // may point to \0 if no arguments in this msg. pfod_MAYBE_UNUSED(pfodFirstArg); // may not be used, just suppress warning long pfodLongRtn; // used for parsing long return arguments, if any pfod_MAYBE_UNUSED(pfodLongRtn); // may not be used, just suppress warning if ('.' == cmd) { // pfodApp has connected and sent {.} , it is asking for the main menu if (!parser.isRefresh()) { sendMainMenu(); // send back the menu designed } else { sendMainMenuUpdate(); // menu is cached just send update } // handle {@} request } else if ('@' == cmd) { // pfodApp requested 'current' time plot_mSOffset = millis(); // capture current millis as offset rawdata timestamps clearPlot = true; // clear plot on reconnect as have new plot_mSOffset parser.print(F("{@`0}")); // return `0 as 'current' raw data milliseconds // now handle commands returned from button/sliders } else if ('A' == cmd) { // user clicked 'Start / Stop' button if (cmd_L_var == 0) { cmd_L_var = 1; // Used to change Heater is off/on thingie when the stop/start button is clicked Done = false; // Enable the Power Sequence (Increments 3 times) } if (cmd_L_var == 2) { Enabled = false; Done = true; cmd_L_var = 3; // Set to cooling down cmd_F_var = 0; // Set the power increment button to 0 cmd_G_var = 0; // Set the power level to 0 SetRunningLights(); } parser.parseLong(pfodFirstArg, &pfodLongRtn); // parse first arg as a long cmd_A_var = (int)pfodLongRtn; // set variable digitalWrite(cmd_A_pin, cmd_A_var); // set output if (cmd_A_var == 0) { cmd_A_pulseStartTime = millis(); // Pulse the output (3s) cmd_A_pulseRunning = true; } else { cmd_A_pulseRunning = false; } sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if ('F' == cmd) { // user clicked 'Increment Power Level' button parser.parseLong(pfodFirstArg, &pfodLongRtn); // parse first arg as a long cmd_F_var = (int)pfodLongRtn; // set variable if (cmd_G_var <= 6) { // Keep to the Max value of 6 and Min value of 1 cmd_G_var = cmd_G_var++; digitalWrite(cmd_F_pin, cmd_F_var); // set output } if (cmd_G_var > 6) { cmd_G_var = 1; digitalWrite(cmd_F_pin, cmd_F_var); // set output } if (cmd_F_var == 0) { cmd_F_pulseStartTime = millis(); // Pulse the output (1s) cmd_F_pulseRunning = true; } else { cmd_F_pulseRunning = false; } sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if ('I' == cmd) { // user clicked the 'Cycle Display' button parser.parseLong(pfodFirstArg, &pfodLongRtn); // parse first arg as a long cmd_I_var = (int)pfodLongRtn; // set variable digitalWrite(cmd_I_pin, cmd_I_var); // set output if (cmd_I_var == 0) { cmd_I_pulseStartTime = millis(); // Pulse the output (1s) cmd_I_pulseRunning = true; } else { cmd_I_pulseRunning = false; cmd_I_var = 1; } sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if ('K' == cmd) { // user clicked the 'Manual / Auto' button parser.parseLong(pfodFirstArg, &pfodLongRtn); // parse first arg as a long cmd_K_var = (int)pfodLongRtn; // set variable digitalWrite(cmd_K_pin, cmd_K_var); // set output if (cmd_K_var == 0) { cmd_K_pulseStartTime = millis(); // high pulse cmd_K_pulseRunning = true; } else { cmd_K_pulseRunning = false; } sendMainMenuUpdate(); // always send back a pfod msg otherwise pfodApp will disconnect. } else if ('!' == cmd) { // CloseConnection command closeConnection(parser.getPfodAppStream()); } else { // unknown command parser.print(F("{}")); // always send back a pfod msg otherwise pfodApp will disconnect. } } cmd_A_checkPulse(); cmd_F_checkPulse(); cmd_I_checkPulse(); cmd_K_checkPulse(); } if (CheckNeeded) OnOffCheck(); // Check to see if the heater is off or on & set the app accordingly if (cmd_G_var == 1) PreHeatFlow = PreHeatPwr1; // Sets trigger levels for '...Warming up' dependent on selected power level if (cmd_G_var == 2) PreHeatFlow = PreHeatPwr2; if (cmd_G_var == 3) PreHeatFlow = PreHeatPwr3; if (cmd_G_var == 4) PreHeatFlow = PreHeatPwr4; if (cmd_G_var == 5 || cmd_G_var == 6) PreHeatFlow = PreHeatPwr5; if (millis() - lastMillis > WindyReadInterval) { // Averaging Code & read Windy every 200 ms ArrayTotal = ArrayTotal - readings[readIndex]; // subtract the last reading: ReadWindy(); // Get a new Windy reading readings[readIndex] = FlowRate; // Add it to the array ArrayTotal = ArrayTotal + readings[readIndex]; // add the reading to the total: readIndex = readIndex + 1; // advance to the next position in the array: if (readIndex >= numReadings) { // if we're at the end of the array... readIndex = 0; // ...wrap around to the beginning: } WindyAverage = ArrayTotal / numReadings; // calculate the average: } sensors.requestTemperatures(); // call sensors.requestTemperatures() to issue a global temperature request to all devices on the bus printTemperature(inletThermometer); // Call function to print sensor temps printTemperature(outletThermometer); Reading = scale.get_units(); // Get the scales reading if (Reading < 0) Reading = 0; // Stops it displaying -ve litres Reading = Reading * 100; //Can only Tx integers, so scaled up to loose the dec. points cmd_D_var = Reading; if (cmd_D_var < FuelMin || HoldFuelLevel) { // Test for enough fuel TempCount++; // Filters out spurious NoFuel readings if (TempCount > 100) { NoFuel = true; HoldFuelLevel = true; TempCount = 0; if (!Dead) KillIt(); // Don't kill it twice! } } if (cmd_D_var > (FuelMin + Thresh)) { NoFuel = false; HoldFuelLevel = false; Dead = false; } if (!cmd_A_pulseRunning) { if (!Done) MaxPower(); // Power relay fires 3 times to inc default level from 3 to 6 } if (Enabled && WindyAverage < PreHeatFlow && !PreHeatWindy) cmd_L_var = 4; // Shows 'Heater is Warming Up' text if (Enabled && WindyAverage >= PreHeatFlow && !PreHeatWindy) cmd_L_var = 2; // Shows 'Heater is Running' text if (PreHeatWindy) cmd_L_var = 5; SetRunningLights(); } void closeConnection(Stream *io) { (void)(io); // unused // add any special code here to force connection to be dropped parser.closeConnection(); // nulls io stream bufferedClient.stop(); // clears client reference clientPtr->stop(); clientPtr = NULL; } void cmd_A_checkPulse() { if (cmd_A_pulseRunning && ((millis() - cmd_A_pulseStartTime) > cmd_A_PULSE_LENGTH)) { cmd_A_pulseRunning = false; // timer finished cmd_A_var = 1; // return output to HIGH digitalWrite(cmd_A_pin, cmd_A_var); // update output pin } } void cmd_F_checkPulse() { if (cmd_F_pulseRunning && ((millis() - cmd_F_pulseStartTime) > cmd_F_PULSE_LENGTH)) { cmd_F_pulseRunning = false; // timer finished cmd_F_var = 1; // return output to HIGH digitalWrite(cmd_F_pin, cmd_F_var); // update output pin } } void cmd_I_checkPulse() { if (cmd_I_pulseRunning && ((millis() - cmd_I_pulseStartTime) > cmd_I_PULSE_LENGTH)) { cmd_I_pulseRunning = false; // timer finished cmd_I_var = 1; // return output to HIGH digitalWrite(cmd_I_pin, cmd_I_var); // update output pin } } void cmd_K_checkPulse() { if (cmd_K_pulseRunning && ((millis() - cmd_K_pulseStartTime) > cmd_K_PULSE_LENGTH)) { cmd_K_pulseRunning = false; // timer finished cmd_K_var = 1; // return output to HIGH digitalWrite(cmd_K_pin, cmd_K_var); // update output pin } } void cmd_J_readADC() { if (cmd_J_adcTimer.justFinished()) { cmd_J_adcTimer.repeat(); // restart timer, without drift cmd_J_var = analogRead(cmd_J_pin); // read ADC input } } void sendMainMenu() { // !! Remember to change the parser version string // every time you edit this method parser.print(F("{,")); // start a Menu screen pfod message // send menu background, format, prompt, refresh and version parser.print(F("<-2>~Garage Heater v8`500")); parser.sendVersion(); // send the menu version // send menu items parser.print(F("|!L")); parser.print('`'); parser.print(cmd_L_var); // output the current value parser.print(F("~Heater is ~~Ready to Start\\Starting\\Running\\Cooling Down\\Warming Up\\Waiting...\\Not Available~t")); // Note the \\ inside the "'s to send \ ... parser.print(F("|!E")); parser.print(F("~")); parser.print(F("|!M")); parser.print(F("~")); parser.print(F("|A")); parser.print('`'); parser.print(cmd_A_var); // output the current state 0 Low or 1 High parser.print(F("~Start~~ (Working Hard...)\\~t")); parser.print(F("|!O")); parser.print(F("~")); parser.print(F("|!P")); parser.print(F("~")); parser.print(F("|!Q")); parser.print(F("~")); parser.print(F("|!V<-2>")); parser.print('`'); parser.print(WarmSlideVal); // output the current time the heater has been warming (Display on a slider only) parser.print(F("~Warming Up... ~`155000`0~3~0~s")); // 155000 = 2min 35 secs in milliseconds to change from warming up to running parser.print(F("~")); parser.print(F("|!U<-2>")); parser.print('`'); parser.print(CoolSlideVal); // output the current time the heater has been cooling (Display on a slider only) parser.print(F("~Cooling... ~`360000`0~6~0~s")); // 360000mS Eq to max cool time of 6 mins parser.print(F("~")); parser.print(F("|!T<-2>")); parser.print('`'); parser.print(cmd_T_var); // output the current averaged ADC reading (Display on a slider only) parser.print(F("~Stabilising... ~`30000`0~30~0~s")); parser.print(F("~")); parser.print(F("|!R")); parser.print(F("~")); parser.print(F("|!S")); parser.print(F("~")); parser.print(F("|!C")); parser.print(F("~")); parser.print(F("|F!<-2>")); parser.print('`'); parser.print(cmd_F_var); // output the current state 0 Low or 1 High parser.print(F("~Increment Power Level~~ (Working Hard...)\\~t")); parser.print(F("|I!<-2>")); parser.print('`'); parser.print(cmd_I_var); // output the current state 0 Low or 1 High parser.print(F("~Cycle Display ~~ (Working Hard...)\\~t")); parser.print(F("|K!<-2>")); parser.print('`'); parser.print(cmd_K_var); // output the current state 0 Low or 1 High parser.print(F("~Manual / Auto~~ (Working Hard...)\\~t")); parser.print(F("|!B")); parser.print(F("~")); parser.print(F("|!G><-2>")); parser.print('`'); parser.print(cmd_G_var); // output the current value parser.print(F("~Heater Power Level: ~`6`0~6~0~t")); parser.print(F("|!H<-2>")); parser.print('`'); parser.print(cmd_H_var); // output the current value parser.print(F("~Inlet Air Temp: ~ \302\260C`1200`-100~120~-10~t")); // The '\302\260' bit prints a degree symbol parser.print(F("|!N<-2>")); parser.print('`'); parser.print(cmd_N_var); // output the current value parser.print(F("~Outlet Air Temp: ~ \302\260C`1200`-100~120~-10~t")); parser.print(F("|!J<-2>")); parser.print('`'); parser.print(WindyAverage); // output the current averaged ADC reading parser.print(F("~Intake Air Flow ~ litres/min`240`0~240~0~")); parser.print(F("|!D<-2>")); parser.print('`'); parser.print(cmd_D_var); // output the current value parser.print(F("~Fuel Level: ~ Litres`6500`0~65~0~ ")); parser.print(F("}")); // close pfod message } void sendMainMenuUpdate() { parser.print(F("{;")); // start an Update Menu pfod message // send menu items if (cmd_L_var != 0 && cmd_L_var != 5) { parser.print(F("<-2>~Garage Heater v8`500")); // This one works... parser.print(F("|!L")); } else { parser.print(F("<-2>~Garage Heater v8`500")); parser.print(F("|!L")); } parser.print(F("|!L")); parser.print('`'); parser.print(cmd_L_var); // output the current values if (cmd_L_var == 0) { parser.print(F("|!E")); parser.print(F("~ ")); parser.print(F("|!M-")); parser.print(F("|!O")); parser.print(F("|!P")); parser.print(F("|!Q")); parser.print(F("|!T-")); parser.print(F("|!U-")); parser.print(F("|!V-")); parser.print(F("|!R")); parser.print(F("|!C")); if (NoFuel) { // Hides the stop/start button & displays text if no fuel parser.print(F("|!L-")); parser.print(F("|A-")); parser.print(F("|!R")); parser.print(F("|!C")); parser.print(F("|!M+<-2>")); parser.print(F("~(Cannot Start - No Fuel!)")); } else { parser.print(F("|!L")); parser.print(F("|!C")); parser.print(F("|!R-")); parser.print(F("|A")); parser.print('`'); parser.print(cmd_A_var); // output the current state 0 Low or 1 High parser.print(F("~Start~~ (Working Hard...)\\~t")); parser.print(F("|!M-")); } parser.print(F("|F-")); parser.print(F("|I-")); parser.print(F("|K-")); } else if (cmd_L_var == 1) { parser.print(F("|!E")); parser.print(F("~ ")); parser.print(F("|!L")); // Sets a green(ish) background for 'Heater is Starting' parser.print(F("|!M<-2>")); // Hide the buttons, and show packer labels parser.print(F("~(Increasing Power Level...)")); parser.print(F("|!O")); parser.print(F("|!C")); parser.print(F("|!P")); parser.print(F("|!Q")); parser.print(F("|!T-")); parser.print(F("|!U-")); parser.print(F("|!V-")); parser.print(F("|!R")); parser.print(F("|A-")); parser.print(F("|F-")); parser.print(F("|I-")); parser.print(F("|K-")); } else if (cmd_L_var == 2) { parser.print(F("|!E")); parser.print(F("|!L")); // Sets a dark blue background for 'Heater is Running' parser.print(F("|!M-")); parser.print(F("|!O-")); // Hide all the labels, show all the buttons parser.print(F("|!P-")); parser.print(F("|!Q-")); parser.print(F("|!T-")); parser.print(F("|!U-")); parser.print(F("|!V-")); parser.print(F("|!R-")); parser.print(F("|!S-")); parser.print(F("|!C-")); parser.print(F("|A")); parser.print('`'); parser.print(cmd_A_var); // output the current state 0 Low or 1 High parser.print(F("~Stop~~ (Working Hard...)\\~t")); parser.print(F("|F")); parser.print('`'); parser.print(cmd_F_var); // output the current state 0 Low or 1 High parser.print(F("~Increment Power Level~~ (Working Hard...)\\~t")); parser.print(F("|I")); parser.print('`'); parser.print(cmd_I_var); // output the current state 0 Low or 1 High parser.print(F("~Cycle Display ~~ (Working Hard...)\\~t")); parser.print(F("|K")); parser.print('`'); parser.print(cmd_K_var); // output the current state 0 Low or 1 High parser.print(F("~Manual / Auto~~ (Working Hard...)\\~t")); } else if (cmd_L_var == 3) { parser.print(F("|!L")); // Sets a light blue background for 'Heater is Cooling Down' parser.print(F("|!M<-2>")); // Hide the buttons, and show packer labels if (!Dead) parser.print(F("~(This takes up to 6 minutes...)")); else parser.print(F("~(Auto-Shutdown: No Fuel Left!!!)")); parser.print(F("|!E")); parser.print(F("|!O")); parser.print(F("|!P")); parser.print(F("|!Q")); parser.print(F("|!T-")); parser.print(F("|!V-")); parser.print(F("|!C-")); parser.print(F("|!U")); parser.print('`'); parser.print(CoolSlideVal); parser.print(F("|!R")); parser.print(F("|A-")); parser.print(F("|F-")); parser.print(F("|I-")); parser.print(F("|K-")); } else if (cmd_L_var == 4) { parser.print(F("|!L")); // Sets a yellow background for 'Heater is Warming Up' parser.print(F("|!M<-2>")); // Hide the buttons, and show packer labels parser.print(F("~(This takes about 3 minutes...)")); parser.print(F("|!O")); parser.print(F("|!P")); parser.print(F("|!Q-")); parser.print(F("|!E")); parser.print(F("|!T-")); parser.print(F("|!U-")); parser.print(F("|!C-")); parser.print(F("|!V")); parser.print('`'); parser.print(WarmSlideVal); parser.print(F("|!R")); parser.print(F("|A-")); parser.print(F("|F-")); parser.print(F("|I-")); parser.print(F("|K-")); } else if (cmd_L_var == 5) { parser.print(F("|!L")); // Sets a grey background for 'Heater is Waiting...' parser.print(F("|!M<-2>")); // Hide the buttons, and show packer labels parser.print(F("~Flow Sensor Stabilising (Approx 30s)")); parser.print(F("|!O")); parser.print(F("|!P")); parser.print(F("|!Q-")); parser.print(F("|!E")); parser.print(F("|!U-")); parser.print(F("|!V-")); parser.print(F("|!C")); parser.print(F("|!T")); parser.print('`'); parser.print(cmd_T_var); // output the current stabilising time left parser.print(F("|!R")); parser.print(F("|A-")); parser.print(F("|F-")); parser.print(F("|I-")); parser.print(F("|K-")); } parser.print(F("|!B")); parser.print(F("|!G")); parser.print('`'); parser.print(cmd_G_var); // output the current value if (!Done) { // Shows the power level slider when warming up, or text otherwise parser.print(F("~Increasing Power... ~ `6`0~6~0~s")); } else { if (cmd_G_var == 6) parser.print(F("~Heater Power Level: ~ (Max)`6`0~6~0~t")); else if (cmd_G_var == 1) { parser.print(F("~Heater Power Level: ~ (Min)`6`0~6~0~t")); } else if (cmd_G_var == 0) { parser.print(F("~Heater Power Level: ~ (Off)`6`0~6~0~t")); } else { parser.print(F("~Heater Power Level: ~ of 6`6`0~6~0~t")); } } if (InletThermoOK) { // Changes background colour & text if thermos are u/s if (!OutletThermoOK) parser.print(F("|!H<-2>")); parser.print(F("|!H<-2>")); parser.print('`'); parser.print(cmd_H_var); // output the current inlet temp parser.print(F("~Inlet Air Temp: ~ \302\260C`1200`-100~120~-10~t")); // The '\302\260' bit prints a degree symbol } else { parser.print(F("|!H<-2>")); parser.print('`'); parser.print(cmd_H_var); // output the current inlet temp parser.print(F("~Inlet Thermometer Not Found. All Default Temps Set To: ~ \302\260C`1000`-100~100~-10~t")); } if (OutletThermoOK) { if (!InletThermoOK) parser.print(F("|!N<-2>")); parser.print(F("|!N<-2>")); parser.print('`'); parser.print(cmd_N_var); // output the current outlet temp parser.print(F("~Outlet Air Temp: ~ \302\260C`1200`-100~120~-10~t")); } else { parser.print(F("|!N<-2>")); parser.print('`'); parser.print(cmd_N_var); // output the current outlet temp parser.print(F("~Outlet Thermometer Not Found. All Default Temps Set To: ~ \302\260C`1000`-100~100~-10~t")); } parser.print(F("|!J")); parser.print('`'); parser.print(WindyAverage); // output the current averaged ADC reading for Windy if (!Enabled && cmd_L_var == 0) cmd_G_var = 0; // If the Heater is off (L), the power level (G) = 0 if (cmd_D_var <= LowLevel - Thresh) { // Sets low/medium/high fuel level background colours if (cmd_L_var != 0) { parser.print(F("|!D+<-2>")); // The + after D makes the text flash parser.print('`'); parser.print(cmd_D_var); parser.print(F("|!E+<-2>")); parser.print(F("~Low Fuel")); } else if (cmd_L_var == 0) { if (NoFuel) { parser.print(F("|!D+<-2>")); // The + after D makes the text flash parser.print('`'); parser.print(cmd_D_var); parser.print(F("|!E+<-2>")); parser.print(F("~Fuel Tank Empty")); } else { parser.print(F("|!D+<-2>")); // The + after D makes the text flash parser.print('`'); parser.print(cmd_D_var); parser.print(F("|!E+<-2>")); parser.print(F("~Low Fuel")); } } } else if (cmd_D_var > LowLevel && cmd_D_var <= MediumLevel - Thresh) { if (cmd_L_var != 0 && cmd_L_var != 5) { parser.print(F("|!D<-2>")); parser.print('`'); parser.print(cmd_D_var); parser.print(F("|!E<-2>")); parser.print(F("~ ")); } else if (cmd_L_var == 0 || cmd_L_var == 5) { parser.print(F("|!D<-2>")); parser.print('`'); parser.print(cmd_D_var); parser.print(F("|!E<-2>")); parser.print(F("~ ")); parser.print('`'); parser.print(cmd_D_var); } } else if (cmd_D_var > MediumLevel) { if (cmd_L_var != 0 && cmd_L_var != 5) { parser.print(F("|!D<-2>")); parser.print('`'); parser.print(cmd_D_var); parser.print(F("|!E<-2>")); parser.print(F("~ ")); } else if (cmd_L_var == 0 || cmd_L_var == 5) { parser.print(F("|!D<-2>")); parser.print('`'); parser.print(cmd_D_var); parser.print(F("|!E<-2>")); parser.print(F("~ ")); } } parser.print(F("}")); // close pfod message // ============ end of menu =========== } int swap01(int in) { return (in == 0) ? 1 : 0; } void printTemperature(DeviceAddress deviceAddress) { // Function to print the temperature for each device if (InletThermoOK && OutletThermoOK) { float tempC = sensors.getTempC(deviceAddress); if (deviceAddress == outletThermometer) { // tempC = tempC + 0.5; // Compensates for the 0.5 deg difference in readings cmd_N_var = tempC * 10; } else { cmd_H_var = tempC * 10; WindyTempC = tempC; } } else { cmd_N_var = 0; // Set all temps to 0 if either thermo fails cmd_H_var = 0; WindyTempC = 0; } } void startSequence() { // Really only sets the 'justFinished' variable stepIdx = 0; cmd_F_pin_Delays.start(cmd_F_pin_On_Off[stepIdx]); digitalWrite(cmd_F_pin, HIGH); // TURN led off for first step cmd_F_pin_On = false; } void MaxPower() { // Fires the power relay 3 times if (cmd_F_pin_Delays.justFinished()) { // don't combine this test with any other condition cmd_F_pin_On = !cmd_F_pin_On; // on delay timed out, toggle relay pin if (cmd_F_pin_On && stepIdx != 6) { digitalWrite(cmd_F_pin, LOW); // turn led on } else { digitalWrite(cmd_F_pin, HIGH); // turn led off } if (stepIdx == 1)cmd_G_var = 4; if (stepIdx == 3)cmd_G_var = 5; if (stepIdx == 5)cmd_G_var = 6; stepIdx++; if (stepIdx >= Number_of_Delays) { stepIdx = 0; // repeat sequence cmd_I_var = 0; CycleDisplay(); Enabled = true; // Enable the 3 buttons Done = true; // We're finished now } cmd_F_pin_Delays.start(cmd_F_pin_On_Off[stepIdx]); } cmd_F_var = 1; } void OnOffCheck() { // New check using wind if (millis() - WindyHeatMillis < WindyHeatInterval) { // Waiting for Windy to stabilise cmd_T_var = (millis() - WindyHeatMillis); // Progress bar for for showing time to stabilise cmd_L_var = 5; // Set the text 'Heater is Waiting...' CheckNeeded = true; // Keep checking until the timer runs out PreHeatWindy = true; } else PreHeatWindy = false; if (WindyAverage < MinFlowRate && !PreHeatWindy) { cmd_L_var = 0; // Set the text 'Heater is Off' cmd_G_var = 0; // Set the Power Level to Min (0) cmd_T_var = 0; Enabled = false; CheckNeeded = false; } if (WindyAverage > MinFlowRate && !PreHeatWindy) { cmd_L_var = 2; // Set the text 'Heater is Running' cmd_G_var = 6; // Set the Power Level to Max (6) cmd_T_var = 0; Enabled = true; CheckNeeded = false; } } void CycleDisplay() { digitalWrite(cmd_I_pin, cmd_I_var); // set output if (cmd_I_var == 0) { cmd_I_pulseStartTime = millis(); // Pulse the output (1s) cmd_I_pulseRunning = true; } else { cmd_I_pulseRunning = false; } } void SetRunningLights() { if (Enabled || (WindyAverage > MinFlowRate)) { if (Enabled && WindyAverage < PreHeatFlow) { if (millis() - WarmSlideMillis > WarmSlideInterval) { WarmSlideVal = WarmSlideVal + 1000; WarmSlideMillis = millis(); } if (millis() - PreHeatMillis > PreHeatInterval) { Is_Running_var = !Is_Running_var; digitalWrite(Is_Running_pin, Is_Running_var); PreHeatMillis = millis(); } } else { WarmSlideMillis = 0; WarmSlideVal = 0; Is_Running_var = 1; digitalWrite(Is_Running_pin, Is_Running_var); } } else { Is_Running_var = 0; digitalWrite(Is_Running_pin, Is_Running_var); } if (!Enabled && (WindyAverage > MinFlowRate)) { if (millis() - CoolSlideMillis > CoolSlideInterval) { CoolSlideVal = CoolSlideVal + 1000; CoolSlideMillis = millis(); } if (millis() - CoolMillis > CoolInterval) { Cool_Down_var = !Cool_Down_var; digitalWrite(Cool_Down_pin, Cool_Down_var); CoolMillis = millis(); } } if (!Enabled && WindyAverage < MinFlowRate && Done) { if (!PreHeatWindy) { cmd_L_var = 0; CoolMillis = 0; CoolSlideVal = 0; // Reset the cooling down slider to 0 before 'cooling down' } } if (cmd_L_var != 3) { Cool_Down_var = 0; digitalWrite(Cool_Down_pin, Cool_Down_var); } } void ReadWindy() { TMP_Therm_ADunits = (-7.8 * WindyTempC) + 645.99; // This formula finds what the ADunits on the wind sensor are using my own temp probefollows RV_Wind_ADunits = analogRead(cmd_J_pin); RV_Wind_Volts = (RV_Wind_ADunits * 0.0048828125); // these are all derived from regressions from raw data as such they depend on a lot of experimental factors // such as accuracy of temp sensors, and voltage at the actual wind sensor, (wire losses) which were unaccouted for. TempCtimes100 = (0.005 * ((float)TMP_Therm_ADunits * (float)TMP_Therm_ADunits)) - (16.862 * (float)TMP_Therm_ADunits) + 9075.4; zeroWind_ADunits = -0.0006 * ((float)TMP_Therm_ADunits * (float)TMP_Therm_ADunits) + 1.0727 * (float)TMP_Therm_ADunits + 47.172; // 13.0C 553 482.39 zeroWind_volts = (zeroWind_ADunits * ZeroWind) - zeroWindAdjustment; // With my temp probe // zeroWind_volts = (zeroWind_ADunits * 0.0048828125) - zeroWindAdjustment; // Original // This from a regression from data in the form of // Vraw = V0 + b * WindSpeed ^ c // V0 is zero wind at a particular temperature // The constants b and c were determined by some Excel wrangling with the solver. if (RV_Wind_Volts - zeroWind_volts > 0) WindSpeed_MPH = pow(((RV_Wind_Volts - zeroWind_volts) / .2300) , 2.7265); else WindSpeed_MPH = 0; FlowRate = ((PI * pow (0.05, 2) * WindSpeed_MPH * 26.8224 * 1000) / 60); // Pi*r squared * MPH * 26.8224 (to meters/Min) x 1000 (cubic meters/min to litres/min) cmd_J_var = FlowRate; #ifdef DEBUG // Serial.print("Flow Rate "); // Serial.print(WindyAverage); // Serial.println(" l/m "); // Serial.print(Enabled); // Serial.print(" Done is "); // Serial.println(Done); #endif lastMillis = millis(); } void KillIt() { // Auto-switch off when too low on fuel // Start the timer if (cmd_A_var == 1) { KillMillis = millis(); // Start the timer (cmd_A_var = 0); // Switch on the relay digitalWrite(cmd_A_pin, cmd_A_var); } if (millis() - KillMillis > KillMillisInterval) { // When the timer runs out... cmd_A_var = 1; digitalWrite(cmd_A_pin, cmd_A_var); // switch off the relay. Dead = true; // Don't kill it again! Enabled = false; HoldFuelLevel = false; cmd_L_var = 3; // Set it to Cooling Down cmd_G_var = 0; // Set power level to 0 SetRunningLights(); } } //////////////////////////////////////////////////////////////////