Welcome! Log In Create A New Profile

Advanced

Independence Model version...

Posted by Independence 
Re: TEC1-12710 TEG (?) Devices
August 21, 2020 07:22PM
Independence Wrote:
-------------------------------------------------------
> Update on the TEC1-12710 TEG devices:
> After leaving them running for 5 days, while they are still running, they are generating 40 ma less
> than when I first started. The 'battery' is now supply 290 ma during normal operating temperatures,

Well, after running another 8 days, the 'battery' is now supplying 345 ma, so these TEGs have lost about 95 ma of current since they were first turned up. So they are now supplying about 405 ma at 6.4V. The biggest issue is that they appear to be degrading slightly everyday. I don't know if its due to the temperature, but we're been getting cooler nights now and the cat chamber has only been getting up to about 175 C. That may also have an effect on the generation, but I've also measured it during the day with higher temperatures, and it hasn't been substantially different.

I'd be interested in Ziggy's results with his $20 TEGs compared to these $9 TEGs....
Re: TEC1-12710 TEG (?) Devices
September 05, 2020 08:36PM
Well, after 29 days of running 12 hours a night, here's the quick summary of the cheap $9 TEC1-12710 devices I purchased:

Day 1: Produced about 500 ma of current
Day 5: Produced about 455 ma of current
Day 8: Produced about 405 ma of current
Day 29: Produced about 310 ma of current

Graphically, they look like:



Not that many data points, but It's not a good trend and I suspect they are just disintegrating with the heat. I'm just going to leave them in there and keep checking them. I'm curious if they will bottom out or continue to degrade.
Mark II
October 30, 2020 07:20PM
Well, the Liberty project with the Wemos D1 controller went so well that I decided to re-spin the Independence board with the same controller. Mosquito season is well over, but here are the results, though I will be unable to fully test it till next year. However, it's basically the same code base and tests out fine on the bench. Like the Liberty project, I've decided to post everything here for reference. All usual disclaimers apply, of course.

The basic difference was that the Independence required more i/o and A/D converters than the Wemos D1 has, so some i2c peripherals had to be added. Unfortunately, the Wemos D1 doesn't have hardware assist for i2c, so it can be subject to more code latency, despite its' faster speed. I believe there is a newer Wemos esp32 device that HAS hardware i2c, but that's for another day. Anyway, here's the schematic for the current version:



The only changes are the replacement of the previous controller chip with the Wemos controller, along with the added MCP23017 I/O expander and the ADS1115 ADC chips. I also removed the transistors driving the LEDs as the Wemos chip could drive them directly. All the other parts are still derived from the original board, with the exception of a couple of pull-up resistors, decoupling capacitors, and new resistor values for the LEDs.

This is version 1.3 of the schematic and here is the corresponding PCB:



The Gerber files necessary to produce this board are here. As mentioned in the Liberty forum, you basically upload the zip file to jlcpcb.com and for less than $25, will get 5 PCBs in about a week.

And here is how it looks in real life:



The board uses the same BME280 temperature/humidity module as the Liberty, via the i2c header. I also included a 1wire interface for use with the previous 18B20 device, but it's not currently in the code.

That's it for the hardware, the software will follow in the next post.
Re: Mark II
October 30, 2020 07:55PM
The software for the Liberty was basically derived from the Independence, but since I had enhanced it, instead of using the original code, I used the Liberty code and added back in the Independence specific functions. The start ups are slightly different, and the voltage thresholds are also different, but those can be handled mostly be adjusting some constants in the code. I DID add OTA firmware update to both code base so now you can update the firmware wirelessly. Here's how the main web screen now looks:



Compared to the original Independence screen, the major differences are the addition of the firmware upgrade link, and the run time counter. I think the run time counter will really be useful next year to track the gas consumption of the unit. Given that I run it with gas mostly during dusk and dawn, it will give me a better idea of how much propane is left, without going out and weighing it.

The OTA upgrade code was lifted from examples and uses libraries that I didn't dig into. On the bench, it worked fine, but I would still leave a USB cable connected to the Wemos and bring it outside the unit so you don't have to disassemble to connect to it, if OTA fails.

When you click on the firmware upgrade link, this is what you get:



You basically click on Choose File to select the compiled binary that Arduino produces, and then click on Update. The progress % will progress to 100%, whereupon the unit will re-start (hopefully) with the new code.

There is now also a selection between Battery mode and AC mode. The Independence is designed as a battery unit and the TEGs are supposed to power it while it is running continuously. The batteries are only used to start the unit. However, because I only run gas on my units during dusk and dawn, I've hooked up AC power to it and during fan mode times, it is fully powered by AC. If there is a need for a battery powered unit, I included the option to set it in the web interface. When enabled, battery mode basically disables the forward diode to allow the battery to charge a bit from the TEGs. It also includes some code that checks for battery voltage getting high, at which point, it enables the diode again, preventing any further charging. In the original re-spin, where I had two controller chips, I suspect that the TEGs couldn't quite keep up with powering the unit and charging, so the battery was probably slowly discharging. With this new single controller, the TEGs might be able to at least keep it neutral.

Anyway, here is the code that goes with the V1.3 PCB, with all the usual disclaimers:

/*

  * ESP8266 Wemos D1 mini experimental controller
  * Remember to set to 160 MHz CPU speed
  * Don't enable mqtt unless there is a server to connect to. The connect routine blocks for a while.
  * 
  * ** This is experimental and untested code. It is posted for reference only. Any use is at your own risk! **

      Failed states:

        1 red blink - Failed to ignite
        2 red blinks - Failed to reach operating temp in allocated time
        3 red blinks - Under temperature
        4 red blinks - Over temperature
        5 red blinks - Low battery
        6 red blinks - Unknown error

      Status LEDs based on Thermistor voltage:
        Startup to ADLOW switch - solid Yellow/Orange
          3.3v to 2.5 v  - 1 blink Orange
          2.5v to 2.0v   - 2 blink
          2.0v to 1.5v   - 3 blink
          1.35v to 1.5v  - 4 blink
        Run Mode - Green LED
          1.35v to 1.2v  - No blink Green
          1.2v to 1.0v   - 1 blink
          0.8v to 1.0v   - 2 blink
          0.6v to 0.8v   - 3 blink
          0.4v to 0.6v   - 4 blink
          0.2v to 0.4v   - 5 blink
          1.35v to 1.7v  - 10 blink
        Cooldown Mode
          5 blinks Yellow/Orange
        Fan Mode
          Alternate Green and Yellow/Orange


      10/30/2020  - V1.0 - First version, includes OTA updater. For use on PCB V1.3
 * 
 */

// Set the specific variables below first!!

//Define the client
#define CLIENT  "MM1 Wemos"

// Client specific configurations below
#define VERSION "V1.0 Oct 30th 2020 " CLIENT
#define URL "/"
#define CLIENTID "esp8266" CLIENT
#define OTAMENU                 // Comment this out if you don't want firmware upgrade on menu. 
#define LOW_BATTERY 4.4

//SSID and Password of your WiFi router
const char* ssid = "<your SSID>";
const char* password = "<Your WiFi password>";

//mqtt information
const char* mqttServer = "<your MQTT server>";
const int mqttPort = 1883;
const char* mqttUser = "";
const char* mqttPassword = "";

// End of user definable items

#include <EEPROM.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <ESP8266WebServer.h>
#include <ADS1115_WE.h> 
#include <Adafruit_MCP23017.h>
#include <SparkFunBME280.h>

BME280 mySensor;
Adafruit_MCP23017 mcp;
#define I2C_ADDRESS 0x48
ADS1115_WE adc(I2C_ADDRESS);

WiFiClient espClient;
PubSubClient client(espClient);
 
//Declare a global object variable from the ESP8266WebServer class.
ESP8266WebServer server(80); //Server on port 80

// This is the upgrade screen html script

const char* upgradeIndex = 
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
CLIENT " OTA Firmware Updater<BR>"
"Current Version: " VERSION " <BR>Compiled: " __DATE__ " " __TIME__ "<BR><BR>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>Progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('Progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')" 
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";


// Use some macros to normalize Wemos and MCP23017 pin actions. Also defines pin functions

#define LEDSOFF     digitalWrite(D3, LOW);digitalWrite(D4, LOW)
#define GREENLEDON  digitalWrite(D3, LOW);digitalWrite(D4, HIGH)
#define GREENLEDOFF digitalWrite(D3, LOW);digitalWrite(D4, LOW)
#define REDLEDON    digitalWrite(D3, HIGH);digitalWrite(D4, LOW)
#define REDLEDOFF   digitalWrite(D3, LOW);digitalWrite(D4, LOW)
#define YELLEDON    digitalWrite(D3, HIGH);digitalWrite(D4, HIGH)
#define YELLEDOFF   digitalWrite(D3, LOW);digitalWrite(D4, LOW)
#define IGNITORON   mcp.digitalWrite(0, HIGH)
#define IGNITOROFF  mcp.digitalWrite(0, LOW)
#define GETIGNITOR  mcp.digitalRead(0)
#define SOLENOIDON  mcp.digitalWrite(1, HIGH)
#define SOLENOIDOFF mcp.digitalWrite(1, LOW)
#define GETSOLENOID mcp.digitalRead(1)
#define TURN12VON   mcp.digitalWrite(4, HIGH)
#define TURN12VOFF  mcp.digitalWrite(4, LOW)
#define RELAYON     mcp.digitalWrite(5, HIGH)
#define RELAYOFF    mcp.digitalWrite(5, LOW)
#define GETRELAY    mcp.digitalRead(5)
#define ADC10K      mcp.pinMode(3, INPUT);mcp.pinMode(2, OUTPUT)
#define ADC100K     mcp.pinMode(3, OUTPUT);mcp.pinMode(2, INPUT)
#define GETSWITCH   digitalRead(D5)

#define FAN         D6                  // PWM pin for the fan

// MQTT related definitions and variables

#define MQTT_POLL   60000      // Default amount of time between sending 1 min
#define CONNECT_TM  2000       // Check for connection every 2 secs

int mqtt_state;
unsigned long int mqtt_timer, connect_timer, mqtt_poll_time = MQTT_POLL;

//ADC Variables
float adc0, adc1, adc2;
int adc_state;

// Debounce time in ms
#define SW_DBNC_TM  300
unsigned long int switch_dbnc;

//Thermistor definitions and variables

// Voltage thresholds for Thermistor

#define TH_UNDER    1.7       // Under temperature
#define TH_OVER     0.2       // Over temperature
#define TH_GREEN    1.35      // Run mode
#define IGN_DETECT  0.2       // Ignition detect difference

// Define states of themistor
#define HIGHSTATE 0
#define TRANSLOW  1
#define TRANSHI   2
#define LOWSTATE  3

char *thermistor_states[] = {"High", ">Low", ">High", "Low"};
byte thermistor_state, thermistor_block, thermistor_cnt, thermistor_dbnc;
float thermistor, thermistor_pre, thermistor_temp;
unsigned long int thermistor_timer;

// Generic definitions
#define ON        1
#define OFF       0
#define DISABLED  0
#define ENABLED   1

// Define LED states for servicing. Set via set_led(STATE, #blinks)
// Yellow and Orange are the same, depending on machine, but code uses Yellow

#define LEDOFF    0   // All LEDs off
#define LEDYELLOW 1   // Solid Yellow
#define LEDRED    2   // Solid Red
#define LEDGREEN  3   // Solid Green
#define YELBLNK   4   // Yellow with Off blinks
#define GREENBLNK 5   // Green with Off blinks
#define REDBLNK   6   // Red with On blinks
#define GREENYEL  7   // Alternate between 

// Blink times, states, and variables
#define LED_BLNK_ON   300   // ms of ON blink time
#define LED_BLNK_OFF  300   // ms of OFF blink time
#define LED_BLNK_WT   3000  // Wait time between blinks
#define LED_BLNK_ALT  1000  // ms between alternating green/yell

// Blink States
#define BLNK_START  0
#define BLNK_ON     1
#define BLNK_OFF    2
#define BLNK_PAUSE  3

byte led_state, led_blink_state, led_mode, led_blinks, led_blink_cnt;
unsigned long int led_timer;

// Define LED indicator states based on thermistor voltage. Stored in led_mode to keep track of current mode so as not to set more than once

#define WARM0     1         // Startup to ADLOW switch - solid Yel
#define WARM1     2         // 3.3v to 2.5 v  - 1 blink
#define WARM2     3         // 2.5v to 2.0v   - 2 blink
#define WARM3     4         // 2.0v to 1.5v   - 3 blink
#define WARM4     5         // 1.35v to 1.5v   - 3 blink
#define RUN1      6         // 1.35v to 1.2v  - No blink  - Green
#define RUN2      7         // 1.2v to 1.0v   - 1 blink
#define RUN3      8         // 0.8v to 1.0v   - 2 blink
#define RUN4      9         // 0.6v to 0.8v   - 3 blink
#define RUN5      10        // 0.4v to 0.6v   - 4 blink
#define RUN6      11        // 0.2v to 0.4v   - 5 blink
#define RUN7      12        // 1.35v to 1.7v  - 10 blink

// Some timer values in ms

#define PURGE_TM    10000       // Amount of time to purge
#define TO_IGNITOR  25000       // Before ignitor
#define PRE_GAS     4000        // Ignitor on before gas
#define IGNITOR_WT  15000       // Amount of time to fire ignitor 15 secs
#define NO_IGNITE   240000      // Maximum time to wait for ignition 4 mins
#define NORUN       3600000     // Max wait till run mode 1 hour

// Fan PWM definitions, need to validate normal running speed
#define FAN_NORMAL  700
#define FAN_FAST    921         // Cooldown fan speed
#define FAN_90      921         // 90% fan speed
#define FAN_80      819         // 80% fan speed
#define FAN_65      665         // 65%
#define FAN_FULL    1023        // Full speed

// States for the main running routine. Not all states used in all versions

#define OFFSTATE  0         // Starting state, Switch is off
#define PURGE     1         // Purge state to clear out any gas
#define WAITSTART 2         // Wait a while before starting ignitor
#define IGNITE    3         // Start the ignitor
#define GASON     4         // Turn on gas
#define CKIGNITE  5         // Check for ignite
#define WARMUP    6         // Wait for running temperature
#define RUN       7         // Running mode
#define POWER_OFF 8
#define COOLDOWN  9
#define FAIL      10
#define DONOTHING 11
#define FANONLY   12
#define PRE_HIB   14
#define HIBERNATE 15        // Equivalent to OFF state with the switch ON. Power save mode set via remote interface
#define RUN_OVER  16        // These run states are only used to indicate conditions, not real states
#define RUN_UNDER 17

char *main_states[] = {"Off", "Purge", "Wait Ign", "Ignite", "Gas On", "Ck Ignite", "Warmup", "Run", "Power Off", "Cool Down", "Fail", "Do Nothing", "Fan Mode", "null", "Pre-Hib", "Hibernate"};

// Error codes, also corresponds to number of red blinks

#define NO_ERROR    0
#define NO_IGN      1
#define NO_TEMP     2
#define UNDER_TEMP  3
#define OVER_TEMP   4
#define LOW_BAT     5
#define UNKNOWN_ERROR 6

char *error_codes[] = {"None", "No Ignition", "Didn't reach temperature", "Under Temperature", "Over Temperature", "Low Battery", "Unknown Error"};
int error_code;

int main_state, next_state, previous_state, fan_speed, flag1, flag2, auto_print, terminal_flag;
int days, hours, mins, tracking_state, batterymode, charger_state, tempflag;
float temperature, humidity, battery, teg;
unsigned long int main_timer, ignitor_timer, temperature_timer, tracking_timer, eeprom_save_timer, temp_timer;

//----------------------------------
// End of definitions, start of code
//----------------------------------

// One time setup code

void setup(void){
  Serial.begin(115200);
  EEPROM.begin(8);

  Wire.setClock(400000);          // Set to fastest speed
  Wire.begin();                   // Start i2c and initialize devices
  mcp.begin();                    // Initialize the i2c GPIO expander

// Initialize all the I/O first
  
  pinMode(D3, OUTPUT); pinMode(D4, OUTPUT); pinMode(D5, INPUT_PULLUP); pinMode(D6, OUTPUT);
  mcp.pinMode(0, OUTPUT); mcp.digitalWrite(0, LOW);
  mcp.pinMode(1, OUTPUT); mcp.digitalWrite(1, LOW);
  mcp.pinMode(2, INPUT); mcp.digitalWrite(2, HIGH);
  mcp.pinMode(3, OUTPUT); mcp.digitalWrite(3, HIGH);  
  mcp.pinMode(4, OUTPUT); mcp.digitalWrite(4, LOW);
  mcp.pinMode(5, OUTPUT); mcp.digitalWrite(5, LOW);  
  REDLEDOFF;
  GREENLEDOFF;
  IGNITOROFF;
  SOLENOIDOFF;
  ADC100K;
  TURN12VOFF;
  analogWrite(FAN, 0); pinMode(FAN, OUTPUT);                  // Fan off

  WiFi.begin(ssid, password);     //Connect to your WiFi router
  Serial.println("");
  
  // Wait for connection. This blocks so if it can't connect, the rest of the program won't run. 
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");    
  }
 
  //If connection successful show IP address in serial monitor
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //IP address assigned to your ESP

// Set up the web server handler routines for each URL
 
  server.on("/", handleRoot);         //Which routine to handle at root location. This is main display page
  server.on("/normal", normal);       // Send normal command
  server.on("/fan", fan);             // Send fan command
  server.on("/hibernate", hibernate); // Send hibernate command
  server.on("/mqtt", mqtt);           // Toggle mqtt state
  server.on("/decmqtt", decmqtt);     // Decrease mqtt time
  server.on("/decmqtt", decmqtt);     // Decrease mqtt time
  server.on("/togglebat", togglebat); // Toggle battery mode
  server.on("/optiona", optiona);     // Send options
  server.on("/optionb", optionb);     // Send options
  server.on("/optionc", optionc);     // Send options
  server.on("/optiond", optiond);     // Send options

// Set up the OTA upgrade code. This code is taken from the upgrade example file

  server.on("/upgrade", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", upgradeIndex);
  });
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
      if (!Update.begin(maxSketchSpace)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
    yield();
  });
  
  server.begin();                  //Start webserver
  Serial.println("HTTP server started");
  
// Setup address of temp sensor and initialize
  mySensor.setI2CAddress(0x76);
  if (mySensor.beginI2C() == false) //Begin communication over I2C
  {
    Serial.println("BME280 temp sensor did not respond");
  }

// Setup and start the ADC via i2c  
  if(!adc.init()){
    Serial.println("ADS1115 not connected!");
  }
  adc.setVoltageRange_mV(ADS1115_RANGE_6144);   // Set widest measurement range 
  adc.setCompareChannels(ADS1115_COMP_0_GND);   // Start with first channel
  adc.startSingleMeasurement();                 // Kick off first measurement; it is read in arrears and in non-blocking mode as part of service_adc() routine

// Setup MQTT

  client.setServer(mqttServer, mqttPort);
  mqtt_timer = connect_timer = millis();

// Take care of other initializations

  main_timer = switch_dbnc = thermistor_timer = temperature_timer = millis();
  main_state = adc_state = thermistor_state = 0;

  batterymode = EEPROM.read(1);     // Read the stored battery mode
  if (batterymode != OFF && batterymode != ON) {  // Check for uninitialized memory
     batterymode = OFF;
     EEPROM.write(1, batterymode);   // If uninitialized, set it to AC mode
     EEPROM.commit();
  }
  if (batterymode == ON)            // If in battery mode, turn off the relay to allow charging
    RELAYOFF;
  else                              // Otherwise, turn it on to enable reverse diode for AC supply
    RELAYON;

  days = EEPROM.read(2);            // Recover the current gas runtime
  hours = EEPROM.read(3);
  mins = EEPROM.read(4);
}


//----------------------------
// Main code loop starts here
//----------------------------

void loop(void){

// Service all the ancillary functions. Should all be non-blocking

  server.handleClient();          //Handle web client requests
  service_adc();
  service_thermistor();
  service_led();
  service_mqtt();
  service_temperature(); 
  track_runtime(); 
  if (batterymode)                // Only service charger if in battery mode
    service_charger();
  service_terminal();             // Console may be removed for production


// Now service the main state machine

  switch (main_state) {

    case OFFSTATE:                              // Comes here when off to wait for ON button or Wifi control

      if (GETSWITCH == HIGH) {
        switch_dbnc = millis();
      } 
      else if (millis() - switch_dbnc >= SW_DBNC_TM) {    // If fully debounced, turn on
        TURN12VON;                              // Enable fan and solenoid voltage
        if (batterymode == ON)                  // If in battery mode, turn off the relay to allow charging
          RELAYOFF;
        else                                    // Otherwise, turn it on to enable reverse diode for AC supply
          RELAYON;
        analogWrite(FAN, FAN_FULL);             // High Fan to purge
        fan_speed = FAN_FULL;
        set_led(LEDYELLOW,0);
        ADC100K;                                // Reset the thermistor range to high range
        thermistor_state = HIGHSTATE;
        main_timer = millis();
        main_state = PURGE;
        Serial.println("Starting purge state..");
      }     
      break;

    case HIBERNATE:                             // This is essentially an OFF state, waits for OFF button or Wifi control
      if (GETSWITCH == LOW)
        switch_dbnc = millis();
      else if (millis() - switch_dbnc >= SW_DBNC_TM) {
        main_timer = switch_dbnc = millis();
        main_state = OFFSTATE;
        Serial.println("Exiting Hibernate state..");
      }     
      break;

    case PURGE:                                 // Runs fan at full speed to purge chamber of gas
      if (GETSWITCH == LOW)
        switch_dbnc = millis();
      else if (millis() - switch_dbnc >= SW_DBNC_TM)
          main_state = POWER_OFF;
      
      if (millis() - main_timer >= PURGE_TM ) { // After purge time, go wait, if necessary, before starting
        analogWrite(FAN, FAN_NORMAL);            
        fan_speed = FAN_NORMAL;
        main_timer = switch_dbnc = millis();
        main_state = WAITSTART;
        Serial.println("Entering wait start..");
      }
      break;

    case WAITSTART:                             // Comes here to wait for TO_IGNITOR time and thermistor to be in HIGHSTATE (cool)
      if (GETSWITCH == LOW)
        switch_dbnc = millis();
      else if (millis() - switch_dbnc >= SW_DBNC_TM)
          main_state = POWER_OFF;
     
      if (millis() - main_timer >= TO_IGNITOR && thermistor_state == HIGHSTATE) {   // If all conditions met, proceed to ignition
        main_timer = switch_dbnc = millis();
        IGNITORON;
        main_state = IGNITE;
        Serial.println("Ignitor turned on..");
      }
      break;

    case IGNITE:                                // Start the ignition process
      if (GETSWITCH == LOW)
        switch_dbnc = millis();
      else if (millis() - switch_dbnc >= SW_DBNC_TM)
          main_state = POWER_OFF;
      
      if (millis() - main_timer >= PRE_GAS ) {  // Wait to turn on gas (for Independence). For Liberty, PRE_GAS = 0, no wait. 
        main_timer = ignitor_timer = millis();
        SOLENOIDON;
        main_state = GASON;
        thermistor_pre = thermistor;            // Save current value to check against for ignition
        Serial.println("Gas turned on..");
      }
      break;

    case GASON:                                 // Gas turned on, check for ignition or failure to ignite
      if (GETSWITCH == LOW)
        switch_dbnc = millis();
      else if (millis() - switch_dbnc >= SW_DBNC_TM)
          main_state = POWER_OFF;
               
      if (thermistor_pre - thermistor > IGN_DETECT ) {     // If voltage is dropping, then we have ignition
        main_timer = millis();
        IGNITOROFF;                           // Ignitor should have turned off by now, but turn it off just in case
        analogWrite(FAN, FAN_FULL);           // Set fan to full speed
        fan_speed = FAN_FULL;
        main_state = WARMUP;
        Serial.print(thermistor);
        Serial.println(" Gas ignited, proceeding to warmup");   
      }

      if (millis() - ignitor_timer >= IGNITOR_WT )    // Check for maximum time for the ignitor
        IGNITOROFF;
        
      if (millis() - main_timer >= NO_IGNITE) {       // If we time out here, that means we didn't get ignition, so error out
        previous_state = GASON;
        main_state = FAIL;
      }
      break;

    case WARMUP:                                // This is the main warm up state, just check for operating temperatures, or timeout
      if (GETSWITCH == LOW)
        switch_dbnc = millis();
      else if (millis() - switch_dbnc >= SW_DBNC_TM){
        next_state = POWER_OFF;
        main_state = COOLDOWN;
        set_led(YELBLNK,5); 
        Serial.println("Cooling down before power off.."); 
      }

      if (thermistor_state == LOWSTATE) {                       // Should be in low ADC state to check for RUN mode.
        if (thermistor <= TH_GREEN && thermistor_block == OFF) {
          main_timer = millis();
          main_state = RUN;
          set_led(LEDGREEN,0);
          Serial.print(thermistor);
          Serial.println(" Proceeding to run state");
        }
        
        // Added LED indicators 

        if (thermistor >= 2.5 && thermistor < 3.3 && led_mode != WARM1) {
          led_mode = WARM1;
          set_led(YELBLNK,1);
        }
        if (thermistor >= 2.0 && thermistor < 2.5 && led_mode != WARM2) {
          led_mode = WARM2;
          set_led(YELBLNK,2);
        }
        if (thermistor >= 1.5 && thermistor < 2.0 && led_mode != WARM3) {
          led_mode = WARM3;
          set_led(YELBLNK,3);
        }
        if (thermistor >= 1.35 && thermistor < 1.5 && led_mode != WARM4) {
          led_mode = WARM4;
          set_led(YELBLNK,4);
        }
      }

      if (millis() - main_timer >= NORUN) {                     // Error out if we timed out and never made it to Run temperatures...
        previous_state = WARMUP;
        main_state = FAIL;
      }
      break;

    case RUN:                                           // We are now in normal operating mode, just check for over and under temps
      if (GETSWITCH == LOW)
        switch_dbnc = millis();
      else if (millis() - switch_dbnc >= SW_DBNC_TM) {
        next_state = POWER_OFF;
        main_state = COOLDOWN;
        set_led(YELBLNK, 5);
        Serial.println("Cooldown before power off..");
      }

      if (thermistor < TH_OVER ) {
        previous_state = RUN_OVER;
        main_state = FAIL;
      }
      if (thermistor >= TH_UNDER) {
        previous_state = RUN_UNDER;
        main_state = FAIL;
      }

      // Added LED indicators of operating temperature

      if (thermistor >= 1.2 && thermistor < 1.35 && led_mode != RUN1) {
        led_mode = RUN1;
        set_led(LEDGREEN,0);
      }
      if (thermistor >= 1.0 && thermistor < 1.2 && led_mode != RUN2) {
        led_mode = RUN2;
        set_led(GREENBLNK,1);
      }
      if (thermistor >= 0.8 && thermistor < 1.0 && led_mode != RUN3) {
        led_mode = RUN3;
        set_led(GREENBLNK,2);
      }
      if (thermistor >= 0.6 && thermistor < 0.8 && led_mode != RUN4) {
        led_mode = RUN4;
        set_led(GREENBLNK,3);
      }
      if (thermistor >= 0.4 && thermistor < 0.6 && led_mode != RUN5) {
        led_mode = RUN5;
        set_led(GREENBLNK,4);
      }
      if (thermistor >= 0.2 && thermistor < 0.4 && led_mode != RUN6) {
        led_mode = RUN6;
        set_led(GREENBLNK,5);
      }
      if (thermistor >= 1.35 && thermistor < 1.7 && led_mode != RUN7) {
        led_mode = RUN7;
        set_led(GREENBLNK,10);
      }
      break;

    case FAIL:                              // Comes here on error, turn off gas and set the error LEDs and error codes
      SOLENOIDOFF;
      IGNITOROFF;

      if (thermistor_state == HIGHSTATE) {  // Don't shut off fan until thermistor is in high state
        analogWrite(FAN, 0);
        fan_speed = 0;
      }
      else {
        analogWrite(FAN, FAN_NORMAL);
        fan_speed = FAN_NORMAL;
      }
      if (previous_state == GASON) {
        set_led(REDBLNK, 1);
        error_code = NO_IGN;
      }
      else if (previous_state == WARMUP) {
        set_led(REDBLNK, 2);
        error_code = NO_TEMP;
      }
      else if (previous_state == RUN_UNDER) {
        set_led(REDBLNK, 3);
        error_code = UNDER_TEMP;
      }
      else if (previous_state == RUN_OVER) {
        set_led(REDBLNK, 4);
        error_code = OVER_TEMP;
      }
      else if (previous_state == FANONLY) {        // Can only be from low battery, only for Independence
        set_led(REDBLNK, 5);
        error_code = LOW_BAT;
      }
      else {
        set_led(REDBLNK, 6);
        error_code = UNKNOWN_ERROR;
      }
      Serial.print("Something failed, error code = ");
      Serial.println(error_codes[error_code]);
      main_state = DONOTHING;
      break;

    case DONOTHING:                             // Just sit here, blink LEDs and keep fan on till cooled down
      if (GETSWITCH == LOW)
        switch_dbnc = millis();
      else if (millis() - switch_dbnc >= SW_DBNC_TM) {
        next_state = POWER_OFF;
        main_state = COOLDOWN;
        set_led(YELBLNK, 5);
        error_code = NO_ERROR;
        Serial.println("Cooldown before power off..");
      }
      if (thermistor_state == HIGHSTATE) {
        analogWrite(FAN, 0);
        fan_speed = 0;
        TURN12VOFF;
      }
      else {
        analogWrite(FAN, FAN_NORMAL);
        fan_speed = FAN_NORMAL;
      }
      break;

    case COOLDOWN:                                            // This is cooldown state before powering off or hibernating           
      analogWrite(FAN, FAN_FAST);
      fan_speed = FAN_FAST;
      SOLENOIDOFF;
      if (thermistor_state == HIGHSTATE) {
        main_state = next_state;
        set_led(LEDOFF,0);          // Either going to OFF or HIBERNATE
      }
      break;

    case FANONLY:                   // Comes here to keep fan on but no gas. Keeps insects in the net till they die
      if (GETSWITCH == LOW)
        switch_dbnc = millis();
      else if (millis() - switch_dbnc >= SW_DBNC_TM)  {
        next_state = POWER_OFF;
        main_state = COOLDOWN;
        set_led(YELBLNK, 5);
        Serial.println("Cooldown before power off..");
      }
      SOLENOIDOFF;      // Keep forcing gas off

      if (battery < LOW_BATTERY && batterymode) {            // If in battery mode, check for low battery
        previous_state = FANONLY;
        main_state = FAIL;
      }
      break;        

    case POWER_OFF:                 // Set everything into OFF state before powering off
      SOLENOIDOFF;
      IGNITOROFF;
      ADC100K;
      TURN12VOFF;
      set_led(LEDOFF,0);
      error_code = led_mode = 0;
      analogWrite(FAN, 0);
      fan_speed = 0;
      main_state = OFFSTATE;
      Serial.println("Powering off");
      break;

    case PRE_HIB:                   // Prepare for hibernation. This state is for battery powered devices to prepare for real hibernation
      SOLENOIDOFF;
      IGNITOROFF;
      ADC100K;
      TURN12VOFF;
      set_led(LEDOFF,0);
      error_code = led_mode = 0;
      analogWrite(FAN, 0);
      fan_speed = 0;
      main_state = HIBERNATE;
      Serial.println("Going to Hibernate state"); 
      break;

    default:
      break;
  }
  // End of main switch
    
}     // End of main loop


// Service routines follow

void service_terminal() {         // Service console commands for debugging, etc. 
  int param;
    if (terminal_flag == OFF) {    // Print one time help, always changing, not usually up to date....
      terminal_flag = ON;
      Serial.println("");
      Serial.println("Terminal Help menu:");
      Serial.println("0 = This help menu");
      Serial.println("1 = Turn on autoprint");
      Serial.println("2 = Turn off autoprint");
      Serial.println("");
    }

    if (Serial.available() > 0) {
      param = Serial.parseInt(); 
      if (Serial.read() == '\n') {    // Set up for only one parameter
        if (param == 0) {
          terminal_flag = OFF;       // Turn this off so Help menu will display again
        }
        if (param == 1) {
          auto_print = ON;
          Serial.println("Autoprint on");
        }
        if (param == 2) {
          auto_print = OFF;
          Serial.println("Autoprint off");
        }
        if (param == 3) {
          Serial.print("Days = ");
          Serial.print(days);
          Serial.print(" Hours = ");
          Serial.print(hours);
          Serial.print(" Mins = ");
          Serial.print(mins);
          Serial.print(" Secs = ");
          Serial.println((millis()-tracking_timer)/1000);
        }
        if (param == 4) {
          SOLENOIDON;
          Serial.println("Solenoid ON!");
        }
        if (param == 5) {
          SOLENOIDOFF;
          Serial.println("Solenoid OFF!");
        }
        if (param == 6) {
          TURN12VON;
          Serial.println("12V turned ON!");
        }
        if (param == 7) {
          set_led(LEDGREEN, 0);       // Solid Green
        }
        if (param == 8) {
          set_led(LEDRED, 0);       // Solid RED
        }
        if (param == 9) {
          set_led(LEDYELLOW, 0);       // Solid Yel
        }
        if (param == 10) {
          set_led(GREENBLNK, 2);       // Green Blink
        }
        if (param == 11) {
          set_led(REDBLNK, 2);       // Red Blink
        }
        if (param == 12) {
          set_led(GREENYEL, 0);       // Alt Blink
        }
        if (param > 100) {
          analogWrite(D6, param);
          Serial.println("Fan set!");
        }
      }
    }

  
  if (auto_print && millis() - temp_timer >= 1000) {    // This will print every sec when autoprint enabled
    Serial.print("Thermistor = ");
    Serial.print(thermistor,4);
    
    Serial.print("v Main St = ");
    Serial.print(main_state);
    
    Serial.print(" Ther St = ");
    Serial.print(thermistor_state);

    Serial.print(" Solenoid = ");
    Serial.print(GETSOLENOID);
    
    Serial.print(" Battery = ");
    Serial.print(battery,3);

    Serial.print("v TEG = ");
    Serial.print(teg,3);

    Serial.print("v Temperature = ");
    Serial.print(temperature,1);

    Serial.print(" Humidity = ");
    Serial.println(humidity,1);
    
    temp_timer = millis();
  }
}



void track_runtime() {
  switch(tracking_state) {
    case 0: 
      if (GETSOLENOID == HIGH ) {                     // If solenoid is on, track run time
        tracking_state = 1;
        days = EEPROM.read(2);
        hours = EEPROM.read(3);
        mins = EEPROM.read(4);
        tracking_timer = eeprom_save_timer = millis();
      }
      break;

    case 1:
      if (GETSOLENOID == HIGH) {                    // As long as solenoid is on, track and advance timers
        if (millis() - tracking_timer >= 60000) {   // Check for advance every minute
          tracking_timer = millis();
          if (++mins == 60) {
            mins = 0;
            if (++hours == 24) {
              hours = 0;
              days++;
            }
          }
        }
        if (millis() - eeprom_save_timer >= 86400000 )  { // If on for more than 1 day, save it anyway
          EEPROM.write(2, days);
          EEPROM.write(3, hours);
          EEPROM.write(4, mins);
          EEPROM.commit();  
          eeprom_save_timer = millis();
        }
      }
      else {
        EEPROM.write(2, days);
        EEPROM.write(3, hours);
        EEPROM.write(4, mins);
        EEPROM.commit();
        tracking_state = 0;
      }
      break;
  }
}

void service_temperature() {   // Read the temperature and humidity every second
  if (millis()-temperature_timer >= 1000) {
    temperature_timer = millis();
    temperature = mySensor.readTempF();
    humidity = mySensor.readFloatHumidity();
  }
}

void set_led(int value, int blinks) {     // Called to set the LEDs and blink states. Look up LED definitions for 'value'
  led_state = value;
  led_blinks = blinks;
  led_blink_state = BLNK_START;
}

void service_charger() {      // Manages the 'charger', if TEGs are working...

  if (GETRELAY)
    charger_state = OFF;
  else
    charger_state = ON;
  switch (charger_state) {
    case ON:
      if (battery >= 6.15) {
        RELAYON;                // Disable the charger
        Serial.println("Charger turned off!");
      }
      break;

    case OFF:
      if (battery <= 5.4) {
        RELAYOFF;         // Enable the charger
        Serial.println("Charger turned on!");
      }
  }  
}


void service_led() {          // This service routine handles all the LEDs. Set up via set_led() call
  switch (led_state) {

    case LEDOFF:
      LEDSOFF;
      break;

    case LEDYELLOW:
      YELLEDON;
      break;

    case LEDRED:
      REDLEDON;
      break;

    case LEDGREEN:
      GREENLEDON;
      break;

    case REDBLNK:
      switch (led_blink_state) {
        
        case BLNK_START:
          REDLEDON;
          led_timer = millis();
          led_blink_state = BLNK_ON;
          led_blink_cnt = 0;
          break;

        case BLNK_ON:
          if (millis() - led_timer >= LED_BLNK_ON) {
            REDLEDOFF;
            led_timer = millis();
            led_blink_state = BLNK_OFF;
          }
          break;

        case BLNK_OFF:
          if (millis() - led_timer >= LED_BLNK_OFF) {
            if (++led_blink_cnt >= led_blinks) {
              led_blink_state = BLNK_PAUSE;
            }
            else {
              led_blink_state = BLNK_ON;
              REDLEDON;
            }
            led_timer = millis();
          }
          break;

        case BLNK_PAUSE:
          if (millis() - led_timer >= LED_BLNK_WT) {
            led_blink_state = BLNK_START;
          }
          break;
          
        default:
          break;
      }
      break;

    case YELBLNK:                      // Mostly on, blinks OFF number of times specified
      switch (led_blink_state) {
        
        case BLNK_START:
          YELLEDOFF;
          led_timer = millis();
          led_blink_state = BLNK_OFF;
          led_blink_cnt = 0;
          break;

        case BLNK_OFF:
          if (millis() - led_timer >= LED_BLNK_ON) {
            YELLEDON;
            led_timer = millis();
            led_blink_state = BLNK_ON;
          }
          break;

        case BLNK_ON:
          if (millis() - led_timer >= LED_BLNK_OFF) {
            if (++led_blink_cnt >= led_blinks) {
              led_blink_state = BLNK_PAUSE;
            }
            else {
              led_blink_state = BLNK_OFF;
              YELLEDOFF;
            }
            led_timer = millis();
          }
          break;

        case BLNK_PAUSE:
          if (millis() - led_timer >= LED_BLNK_WT) {
            led_blink_state = BLNK_START;
          }
          break;
      }
      break;

    case GREENBLNK:                      // Blinks OFF number of times specified
      switch (led_blink_state) {
        
        case BLNK_START:
          GREENLEDOFF;
          led_timer = millis();
          led_blink_state = BLNK_OFF;
          led_blink_cnt = 0;
          break;

        case BLNK_OFF:
          if (millis() - led_timer >= LED_BLNK_ON) {
            GREENLEDON;
            led_timer = millis();
            led_blink_state = BLNK_ON;
          }
          break;

        case BLNK_ON:
          if (millis() - led_timer >= LED_BLNK_OFF) {
            if (++led_blink_cnt >= led_blinks) {
              led_blink_state = BLNK_PAUSE;
            }
            else {
              led_blink_state = BLNK_OFF;
              GREENLEDOFF;
          }
            led_timer = millis();
          }
          break;

        case BLNK_PAUSE:
          if (millis() - led_timer >= LED_BLNK_WT) {
            led_blink_state = BLNK_START;
          }
          break;

        default:
          break;
      }
      break;

    case GREENYEL:                      // Alternates between green and yellow
      switch (led_blink_state) {
        
        case BLNK_START:
          GREENLEDON;
          led_timer = millis();
          led_blink_state = BLNK_OFF;
          led_blink_cnt = 0;
          break;

        case BLNK_OFF:
          if (millis() - led_timer >= LED_BLNK_ALT) {
            YELLEDON;
            led_timer = millis();
            led_blink_state = BLNK_ON;
          }
          break;

        case BLNK_ON:
          if (millis() - led_timer >= LED_BLNK_ALT) {
            GREENLEDON;            
            led_timer = millis();
            led_blink_state = BLNK_OFF;
          }
          break;

        default:
          break;
      }
      break;

    default:
      break;
  }
}

void service_thermistor() {           // Services the thermistor ranging. Blocks when transiting range to prevent suprious readings

// Define the transition values
#define THERMISTOR_HIGH   3.6         // More than this value, switch to high range
#define THERMISTOR_LOW    1.0         // Less than this value, switch to low range
  
  switch (thermistor_state) {

    case LOWSTATE:
      if (thermistor >= THERMISTOR_HIGH )
        thermistor_cnt++;
      else
        thermistor_cnt = 0;
      if (thermistor_cnt >= 100) {      // Need at least 100 reads below threshold in order to change
        thermistor_state = TRANSHI;
        thermistor_timer = millis();
        thermistor_cnt = 0;
        thermistor_block = ON;
        ADC100K;
        Serial.print(thermistor);
        Serial.println(" Switching to High state");
      }
      break;

    case TRANSHI:
      if (millis() - thermistor_timer >= 100) {
        thermistor_block = OFF;
       thermistor_state = HIGHSTATE;
      }
      break;

    case TRANSLOW:
      if (millis() - thermistor_timer >= 100) {
       thermistor_block = OFF;
       thermistor_state = LOWSTATE;
      }
      break;

    case HIGHSTATE:
      if (thermistor <= THERMISTOR_LOW )
        thermistor_cnt++;
      else
        thermistor_cnt = 0;
      if (thermistor_cnt >= 100) {
        thermistor_state = TRANSLOW;
        thermistor_cnt = 0;
        thermistor_timer = millis();
        thermistor_block = ON;
        ADC10K;
        Serial.print(thermistor);
        Serial.println(" Switching to Low state");
      }
      break;

    default:
      break;
    
  }
}

void service_adc() {        // Cycle through each ADC channel, one at a time, in non-blocking way...

  switch(adc_state) {
    case 0:                                                 // This is the thermistor
      if (!adc.isBusy()) {
        adc0 = thermistor = adc.getResult_V();
        adc.setCompareChannels(ADS1115_COMP_1_GND);
        adc.startSingleMeasurement();
        adc_state = 1;
      }
      break;

    case 1:                                                 // This is battery voltage
      if (!adc.isBusy()) {
        adc1 = adc.getResult_V();
        battery = adc1*3.0;
        adc.setCompareChannels(ADS1115_COMP_2_GND);   
        adc.startSingleMeasurement();
        adc_state = 2;
      }
      break;

    case 2:                                                 // This is TEG voltage
      if (!adc.isBusy()) {
        adc2 = adc.getResult_V();
        teg = adc2*3.0;
        adc.setCompareChannels(ADS1115_COMP_0_GND); 
        adc.startSingleMeasurement();
        adc_state = 0;
      }
      break;
      
    default:
      break;

  }
}


void service_mqtt() {         // Sends MQTT messages periodically, if enabled
  int t;
   if (millis() - mqtt_timer >= mqtt_poll_time && client.connected() && mqtt_state == ENABLED) {
    mqtt_timer = millis();
      
    client.publish("sensor/" CLIENT "/mstate", String(main_state).c_str());
    client.publish("sensor/" CLIENT "/tstate", String(thermistor_state).c_str());
    client.publish("sensor/" CLIENT "/therm", String(thermistor,3).c_str());
    client.publish("sensor/" CLIENT "/battery", String(battery,3).c_str());
    client.publish("sensor/" CLIENT "/teg", String(teg,3).c_str());
    client.publish("sensor/" CLIENT "/fan", String(fan_speed).c_str());
    client.publish("sensor/" CLIENT "/ignitor", String(GETIGNITOR).c_str());
    client.publish("sensor/" CLIENT "/solenoid", String(GETSOLENOID).c_str());
    client.publish("sensor/" CLIENT "/charger", String(charger_state).c_str());
    client.publish("sensor/" CLIENT "/batterymode", String(batterymode).c_str());
    client.publish("sensor/" CLIENT "/temperature", String(temperature).c_str());
    client.publish("sensor/" CLIENT "/humidity", String(humidity).c_str());
    client.publish("sensor/" CLIENT "/error", error_codes[error_code]);
    client.publish("sensor/" CLIENT "/flag1", String(flag1).c_str());
    client.publish("sensor/" CLIENT "/flag2", String(flag2).c_str());
    Serial.println("MQTT Published!");    
  }
  if (millis() - connect_timer >= CONNECT_TM && !client.connected() && mqtt_state == ENABLED) {       // Check for mqtt broker periodically and reconnect as necessary
    connect_timer = millis();
    Serial.println("Reconnecting to MQTT broker..");
    if (client.connect(CLIENTID, mqttUser, mqttPassword )) {
      Serial.println("MQTT connected");
    }
    else {
      Serial.print("MQTT Connect Failed with state ");
      Serial.println(client.state());
    }    
  }
}


//===============================================================
// The following routines handle the web interface
//===============================================================
void handleRoot() {

  String webmessage;

  Serial.println("You called root page");       // This home page auto refreshes every 60 seconds
  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"60; url = ";
  webmessage += URL;
  webmessage += "\"><body><center><br>Experimental Controller ";
  webmessage += VERSION;
  webmessage += "<br><br><a href=\"normal\">Normal Mode</a>";
  webmessage += "<br><a href=\"fan\">Fan Mode</a>";
  webmessage += "<br><a href=\"hibernate\">Hibernate Mode</a>";
  webmessage += "<br>Options: <a href=\"optiona\"> A </a>&ensp;<a href=\"optionb\"> B </a>&ensp;<a href=\"optionc\"> C </a>&ensp;<a href=\"optiond\"> D </a><br>";
#ifdef OTAMENU
  webmessage += "<br><a href=\"upgrade\">Upgrade Firmware</a>";
#endif
  webmessage += "<br><a href=\"mqtt\">Toggle MQTT State</a>: Now ";
  if (mqtt_state == ENABLED) {
    webmessage += String(mqtt_poll_time/1000);
    webmessage += " sec <a href=\"decmqtt\">Decrease</a>";
  }
  else
    webmessage += "Disabled";
  webmessage += "<br><a href=\"togglebat\">Toggle Battery Mode</a>: Now ";
  if (batterymode)
    webmessage += "Enabled";
  else
    webmessage += "Disabled";

  webmessage += "<br>Run Time (D:H:M) = ";
  if (days < 10)
    webmessage += "0";
  webmessage += String(days);
  webmessage += ":";
  if (hours < 10)
    webmessage += "0";
  webmessage += String(hours);
  webmessage += ":";
  if (mins < 10)
    webmessage += "0";
  webmessage += String(mins);  
  webmessage += "&ensp;<a href=\"resrun\">Reset</a>";

  webmessage += "<BR><BR>Flag 1 = ";
  webmessage += String(flag1);
  webmessage += "<BR>Flag 2 = ";
  webmessage += String(flag2);
  webmessage += "</body></html>";
  webmessage += "<BR>Main St = ";
  webmessage += String(main_states[main_state]);
  webmessage += "<BR>Ignitor St = ";
  if (GETIGNITOR)
    webmessage += "On";
  else
    webmessage += "Off";
  webmessage += "<BR>Solenoid St = ";
  if (GETSOLENOID)
    webmessage += "On";
  else
    webmessage += "Off";
  webmessage += "<BR>Battery = ";
  webmessage += String(battery,3);
  webmessage += "V<BR>TEG = ";
  webmessage += String(teg,3);
  webmessage += "V<BR>Charger = ";
  if (GETRELAY)
    webmessage += "Off";
  else
    webmessage += "On";
  webmessage += "<BR>Thermistor = ";
  if (thermistor_state == HIGHSTATE)
    webmessage += "Hi - ";
  else
    webmessage += "Lo - ";
  webmessage += String(thermistor,3);
  webmessage += "V<BR>Fan PWM Speed = ";
  webmessage += String(fan_speed);
  webmessage += " <BR>Ambient Temp = ";
  webmessage += String(temperature,0);
  webmessage += "F<BR>Humidity = ";
  webmessage += String(humidity,0);
  webmessage += "%<BR>Error Code = ";
  webmessage += error_codes[error_code];
  server.send(200, "text/html", webmessage);
}
 
void normal() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Normal command sent</body></html>";
  Serial.println("Normal page");
  server.send(200, "text/html", webmessage);    

  if (main_state == FANONLY || main_state == HIBERNATE || main_state == DONOTHING) {
    main_state = POWER_OFF;
    error_code = NO_ERROR;
    Serial.println("Coming out of fan or hibernate mode and restarting");
  }
}

 void fan() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Fan command sent</body></html>";
  Serial.println("Fan page");
  server.send(200, "text/html", webmessage);  

  SOLENOIDOFF;                      // Turn off gas
  IGNITOROFF;                       // Also make sure ignitor is off
  TURN12VON;
  analogWrite(FAN, FAN_FULL);
  fan_speed = FAN_FULL;
  main_state = FANONLY;
  set_led(GREENYEL,0);
  Serial.println("Going into Fan only mode");
}

void hibernate() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Hibernate command sent</body></html>";
  Serial.println("Hibernate page");
  server.send(200, "text/html", webmessage);  

  SOLENOIDOFF;
  next_state = PRE_HIB;
  main_state = COOLDOWN;
  set_led(YELBLNK, 5);
  Serial.println("Going to Cooldown mode and then Hibernate");
}

void optiona() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Option A command sent</body></html>";
  Serial.println("Option A page");
  server.send(200, "text/html", webmessage);  
  analogWrite(FAN, FAN_FULL);
  fan_speed = FAN_FULL;
  Serial.println("Fan set to 100%");
}
void optionb() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Option B command sent</body></html>";
  Serial.println("Option B page");
  server.send(200, "text/html", webmessage);  
  analogWrite(FAN, FAN_90);
  fan_speed = FAN_90;
  Serial.println("Fan set to 90%");
}
void optionc() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Option C command sent</body></html>";
  Serial.println("Option C page");
  server.send(200, "text/html", webmessage);  
  analogWrite(FAN, FAN_80);
  fan_speed = FAN_80;
  Serial.println("Fan set to 80%");
}
void optiond() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Option D command sent</body></html>";
  Serial.println("Option D page");
  server.send(200, "text/html", webmessage);  
}
void decmqtt() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Decremented MQTT time</body></html>";
  server.send(200, "text/html", webmessage);  
  if (mqtt_poll_time > 10000)
    mqtt_poll_time = mqtt_poll_time - 10000;
  else
    mqtt_poll_time = 5000;            // Every 5 sec is min
  Serial.print("MQTT Poll time now ");
  Serial.print(mqtt_poll_time / 1000);
  Serial.println(" secs");
}

void togglebat() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Battery mode toggled</body></html>";
  server.send(200, "text/html", webmessage);  
  if (batterymode)  {
    batterymode = OFF;
    RELAYON;
    charger_state = OFF;
  }
  else {
    batterymode = ON;
  }
  EEPROM.write(1, batterymode);
  EEPROM.commit();
  tracking_state = 0;
  Serial.println("Battery mode toggled");
}

void resrun() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>Run counter reset</body></html>";
  server.send(200, "text/html", webmessage);  
  days = hours = mins = 0;
  EEPROM.write(2, days);
  EEPROM.write(3, hours);
  EEPROM.write(4, mins);
  EEPROM.commit();
  tracking_state = 0;
  Serial.println("Run time counters reset!");
}

void mqtt() { 
  String webmessage;

  webmessage = "<!DOCTYPE html><html><meta http-equiv = \"refresh\" content = \"1; url = ";
  webmessage += URL;
  webmessage += "\"></head><body>MQTT State toggled to ";
  Serial.println("MQTT toggled page");
  if (mqtt_state == ENABLED) {
    mqtt_state = DISABLED;
    webmessage += "DISABLED </body></html>";
  }
  else {
    mqtt_state = ENABLED; 
    mqtt_poll_time = MQTT_POLL;
    webmessage += String(MQTT_POLL/1000);
    webmessage += " sec and ENABLED </body></html>";
  }    
  server.send(200, "text/html", webmessage);  
}

Re: Mark II
October 30, 2020 08:20PM
I would just add that I didn't implement a deep sleep mode on the controller. This means that if you're in battery mode, even if you're in hibernate state or have the on/off switch off, the Wemos D1 is still consuming about 70-80ma. So, if you want to turn off the unit for a while and don't want to drain your battery, you need to disconnect the battery, not just turn it off at the switch. I didn't care since I only run in AC mode, but for those that want to implement a deep sleep mode, you will have to modify the board. Basically, you have the disconnect the D0 pin of the Wemos, which is currently connected to an INT pin of the port expander, and connect it to the RST pin. I should have made that a jumper-able option, especially since I am not even using the D0 pin right now, but I only investigated the deep sleep mode AFTER the board already went to fab. Basically, in order to wake up the Wemos periodically via a timer, the D0 pin has to be connected to the RST pin. If you do that, then you can write code that wakes up every couple of seconds and checks the on/off switch. If it is on, it then executes normal start up code. If it is still off, then it goes off to sleep for another couple of seconds. This will reduce power consumption significantly and it should be able to stay on battery for a long time in this mode.
Re: Mark II
July 13, 2021 02:03PM
sevans01 wrote:

I worked though enough of the issues to get it to run pretty reliably on my unit except for a couple that could be my unit (or my board construction) but 1 showstopper for now - I run cordless and my TEGs don't generate enough power to keep it running. I think I was about 250mA over budget

This is an interesting issue and one I was investigating recently. The single esp8266 board should consume less power than the dual board design, but I haven't actually measured it. But I am in the process of enabling that howling unit to be a 24/7 unit far away from the house so I was testing it's ability to run full cordless. Here's what I found so far:

When the unit is warming up, obviously the battery is supplying the bulk of the power and I can measure the current flowing FROM the battery.

As the TEGs heat up, that current reduces to the point where it is now negative, indicating that the TEGs are powering the unit AND supplying a little bit of current to the battery. The TEG voltage also indicates higher than the battery voltage, as expected.

HOWEVER, if I unplug the battery at this point, the TEG voltage immediately drops and the fan speed slows down a bit. Apparently, without the battery, the TEG doesn't seem to be able to supply enough power to the board to keep it at full voltage. The unit continues to run, but with the reduced fan speed, the combustion temperature gets lower, so it may be a vicious cycle. It's strange that with the battery, it can actually power the board AND provide a little current back to the battery, but without the battery, it can't even keep the voltage up.

The unit is running out in the wild now but I will be retrieving it today to add an external wifi antenna for extended range, and also do some swap tests with my other unit to see if the howling follows the housing. I will also do some actual current measurements so you have a basis to compare with your unit. 250ma overbudget just seems too high. Did you take note of the battery and TEG voltages when the unit was running at temperature? And what temperature was the unit running at?
Swap Test and some TEG readings..
July 13, 2021 11:10PM
So I swapped the entire combustion unit from the howling unit to the good unit, and it did EXACTLY the same thing, so the housing had nothing to do with it. This has something to do with the carburetor or airflow within the combustion unit.

It's a weird problem. It starts howling shortly after initial combustion and I can only stop the howling by opening the cover or covering up the exhaust plume. By performing various opening and closing maneuvers as it's warming up, I can eventually get it to not howl when I close the cover. At that point, it runs properly and the temperature gets to where it's supposed to be and it will run 24/7 with no problem. But the next start up.....

I'm not sure what else to try. It's a spare/cordless unit and I don't use it much but since I am able to get it running, I don't know how much more time I want to spend on it. But there's a part of me that wants to figure it out...

Anyway, once I was able to get this unit up to normal operating temperature, I was able to take some power consumption readings. When the unit has ignited and the fan is running at full speed, it consumes about 770 ma, which is all supplied by the battery. However, once the unit reaches operating temperature, the TEGS generate enough power to trickle 40ma back into the battery. So it should be capable of running 24/7 without changing the battery, though I cannot account for why it won't power it fully without the battery. @sevans01, you can check these readings against your unit.
Re: Independence Model version...
July 14, 2021 12:01AM
Oh wow - I think the 250 mA was the difference in start up current draw - I was having a lot of ignition problems and was trying to see if it was a power problem (often, but not always the ignitor stops as soon as the gas solenoid clicks on)

I didn't keep records but I did find a pic of the power supply from one of the tests along with what the web interface showed, it was not fully up to temp yet
Power supply indicated: 5.06v 90mA draw
Web interface: Battery 4.999v TEG 5.031v Thermistor 0.845v
I believe that would be 2 green blinks and my unit seemed to settle in at 4 green blinks

Running on battery, within a day the battery and TEG voltages were in the 3v range and fan would be very slow

This already makes me feel more optimistic - with the 250 mA in my head I was "how am I going to make that up, I would have to get rid of the fan"

So far, my project list to figure out are:
Fit within the TEG current window
Solve why the ignitor stops when the solenoid kicks in
Resolved issues:
Battery and TEG voltages triple and Thermistor volatage doubles once the ignitor starts - seems fixed since changing ADS1115 range to 2048
No ignition when ignitor and solenoid did work right - fixed by creating a new fan mode for ignition with a lower speed at 200
Re: Independence Model version...
July 14, 2021 12:40AM
sevans01 wrote:


Power supply indicated: 5.06v 90mA draw
Web interface: Battery 4.999v TEG 5.031v Thermistor 0.845v
I believe that would be 2 green blinks and my unit seemed to settle in at 4 green blinks


Your voltages seem too low. You should be powering it from four batteries or 6V. At 5V, the LDO regulator is barely able to do its job. And at that thermistor temperature, the TEGs should be reading higher. Here is a current web page of my cordless unit right now:



Notice my battery and TEG voltages. You are pretty much running on battery all the time.

As to your problems, make sure you are using the specified low dropout regulator and a 6V power supply capable of at least 1A. When the voltage drops below 5V strange things can happen, which could explain why turning on the solenoid or ignitor results in strange behavior.
Re: Independence Model version...
July 14, 2021 02:53AM
I went ahead and put the experimental board back in (really need to get an external box for the controller). I bumped the power supply up to 6v and it can supply a few amps, but still had some glitchy starts (ignitor stopping at or a couple clicks after the solenoid starts, a couple times the fan also stopped and the esp8266 went offline. I just keep resetting the power until I get a successful start.

The unit howled again when it ignited and the fan was at low 200 speed, but went away when it kicked back up to full speed

Once the fan was at full speed after ignition the current draw was about 740 mA

The temp seems to have leveled off and I'm at about 77-80 mA current draw

Attached is a screen shot of the current state web interface, it definitely looks like my TEGs are just generating less power than yours

Re: Independence Model version...
July 14, 2021 02:23PM
sevans01 wrote:

The unit howled again when it ignited and the fan was at low 200 speed, but went away when it kicked back up to full speed

It sure looks like you have a similar problem to me. I suspect it will gradually get worse and I will eventually have to figure out what is causing this.

Once the fan was at full speed after ignition the current draw was about 740 mA

That sounds about right.

Attached is a screen shot of the current state web interface, it definitely looks like my TEGs are just generating less power than yours

Actually, I think they are ok. If you have a power supply at about 6V, it is going to supply more than the TEGs, hence the positive current. If you actually have some nimh batteries, they will be around 5.5V or so, and at that voltage, the TEGs should be able to power the unit AND trickle charge the batteries. You need to turn battery mode on for that to happen though.

..still had some glitchy starts (ignitor stopping at or a couple clicks after the solenoid starts, a couple times the fan also stopped and the esp8266 went offline. I just keep resetting the power until I get a successful start.

Something is definitely off on your circuit board. I have never experienced any of those issues before and the board has been rock solid. The first suspect is always the power rails into the esp8266. If you have a scope, check the power rails when the ignitor and solenoid engage. I also notice you don't have the temp sensor. You may want to disable the driver for that as I'm not sure how the missing sensor will affect the I2C bus for the other devices.
External WiFi antenna for esp8266
July 15, 2021 06:25PM
So, since I've verified that my cordless Independence can run indefinitely via the TEGs (after it warms up and I can get the howling to stop), I decided to see what it can catch 200' away from the house, near some wetlands. I still wanted wifi access to it, so I had to add an external antenna. I've done this on several other projects.

On the Wemos D1 esp8266s that I buy, there is a PCB trace antenna but you can cut into it in order to add a wired antenna. See pic below:



You basically cut the trace as it comes out from the esp8266 module and splice in the coax antenna cable. I use these antennas:



They are less than $9 for two and you basically drill a 1/4" hole on the side of the unit and fasten the antenna mount with the nut. You cut off the IPEX connector and solder the coax to the esp8266.

The external antenna works great. The unit is over 200' from my house and I can still access the web interface. Now all I need is a remote camera to check the mosquitoes!
Re: External WiFi antenna for esp8266
July 16, 2021 01:40PM
The external antenna works great.

Super idea. How much signal strength improvement are you getting compared to an unmodified Mini D1?
Re: External WiFi antenna for esp8266
July 16, 2021 02:10PM
I don't know how accurate the RSSI readings from the WiFi library are, but with side by side units on my deck, I was seeing about 58 dBm on the external antenna, and about 65 dBm on the standard trace antenna. Placed 200' out in the woods, the external antenna was reading about 71 dBm. The external antenna are supposed to provide 8 dBi of gain.
Re: Independence Model version...
October 10, 2021 09:04PM
I picked up another Independence that has the same controller but is in better shape that my original.
Posting some photos of the board for reference


Re: Independence Model version...
October 11, 2021 03:10PM
Wow, 2 years difference between my boards and this (2008 vs 2010) and it's quite a bit different in layout. Main difference appears to be the use of the more efficient MT2700 device for the boost converter. Much smaller inductor and capacitors. Also, soldered wires for the solenoid and ignitor. You should trace out the schematic and post it here.. :)
134-061 Rev B
October 11, 2021 06:09PM
That's quite a difference between this and the 134-031 board shown earlier in this topic. It makes the -031 board look positively retro. And this was all 11 years ago in 2010! I guess that makes sense, seeing as how we are all interested in keeping the old machines going. I wonder what a modern machine tear-down al la new iPhones, etc., would reveal.

The soldered wires suggest reliability problems with the Molex connectors (or "terminal" penny pinching). Hooray for using a transistor to connect the charging to the battery instead of that relay-diode mess. Might the battery be different, as well? And a programming port on the PIC, wow!
Re: 134-061 Rev B
October 11, 2021 10:07PM
dev wrote:

Hooray for using a transistor to connect the charging to the battery instead of that relay-diode mess. Might the battery be different, as well?

My Independence used 4 C size alkaline cells, so charging wasn't originally supported. But that board was also used on the Liberty Plus, which used a 4 NiMh pack, and had the external charging circuitry populated. I think the relay was for the Liberty Plus too. If this newer Independence also uses alkaline cells, then I'm not sure what Q4 and Q5 are for and when they might activate them...
Re: Independence Model version...
October 11, 2021 10:22PM
These do use alkaline cells, but I wonder if they also used this board for a model with rechargeable cells
Re: Independence Model version...
October 11, 2021 10:29PM
sevans01 wrote:

These do use alkaline cells, but I wonder if they also used this board for a model with rechargeable cells

The Liberty Plus used rechargeable cells, but it also had a power adapter that plugged into the unit to charge the battery in-situ. As such, the Liberty Plus board had additional components populated on the 134-031 board to handle the charging. Your 134-061 board doesn't appear to have those components but maybe the newer unit batteries were charged externally....
Re: Independence Model version...
October 20, 2021 03:06AM
I did some more trial and error trying to get the v1.3 board to work within my TEGs power budget -
- my newer Independence powers it completely starting when the thermistor gets to 0.95v
- my original Independence is drawing about 150mA at the same point and is drawing about 90mA when fully up to temperature

I gave a shot at reducing the power consumption of the gas solenoid by only using the 9.73v until ignition was detected then using 5-6v from the pre regulated Bat/Teg rail for hold in

It is just hacked together to test right now:
- I am manually disconnecting the 9.73 after ignition
- The 6v to the solenoid is not switched, but is through a diode to keep from backfeeding 9.73v
- I am seeing about 90-100 mA reduction and just get to 0 mA draw at 0.814v on the thermistor

I want to run a longer test this weekend to see if it holds steady
Re: Independence Model version...
October 20, 2021 03:54PM
sevans01 wrote:

- my newer Independence powers it completely starting when the thermistor gets to 0.95v
- my original Independence is drawing about 150mA at the same point and is drawing about 90mA when fully up to temperature


That sure seems to indicate that your original Independence's TEGs are not producing as much as they should be. You can take them apart and make sure that there is enough thermal grease mating them to the combustion chamber and heatsink. The greater temperature difference between the sides, the more power they produce and the thermal grease can improve the thermal difference. You are close with 90 ma difference, and if you can make it up by making sure the thermal conductivity is correct, that would be better than the hack with the solenoid.

BTW, did you figure out why the board was resetting during ignition?
Re: Independence Model version...
October 20, 2021 09:00PM
It seemed to be transients from the spark ignitor pulses overloading the ADS. Adding a ferrite to the ignitor wires from the controller helped
Prior to the ferrite I had backed the ADS gain down to 2048 which helped but really narrowed down the thermistor measurement windows.
So I changed it to 2048 gain for ignition then once the ignitor is turned off it sets the gain back to 6144
Re: Independence Model version...
October 21, 2021 03:48AM
Why do you suspect that transients to the ADC is causing resets of the esp8266? And why do you think changing the gain made a difference?

I'm thinking that you might be having problems with the ignitor board since neither of my systems have ever had any reset issues during ignition. There might be coupling from the HV side to the LV side that is feeding back onto the main board.
Re: Independence Model version...
October 21, 2021 06:57PM
Sorry, misread your question - I was thinking of the board as the overall controller, not specifically the esp8266

I don't really know what was causing the ESP resetting, but hasn't been an issue lately I don't think changing the ADS gain was it though

Changing the gain 100% help resolve the ADS switching to reporting the Bat, TEG, and Thermistor voltages at 2-3x their actual values

I had changed the I2C pullup to 3v3, but I don't really think that changed anything as the ESP GPIOs are supposed to be 5v tolerant
Re: Independence Model version...
October 21, 2021 10:20PM
sevans01 wrote:

Changing the gain 100% help resolve the ADS switching to reporting the Bat, TEG, and Thermistor voltages at 2-3x their actual values

I think you are getting transient voltages from the ignitor board or solenoid that is causing the ADC to misread values and also the esp8266 to reset. If you have a oscilloscope, watch the power lines of the controller board when the ignitor is enabled and make sure they are steady. You can also check to see that there are no sparks visible anywhere on the inside of the Independence. I've seen sparks jump from the ignitor board to the outside of the combustion chamber and heatsink.

I had changed the I2C pullup to 3v3, but I don't really think that changed anything as the ESP GPIOs are supposed to be 5v tolerant

The I2C pullups are connected to 5V because both the port expander and ADC are both running at 5V, not 3.3V. Pulling them up to 5V will give better noise margin than 3.3V, and as you noted, the esp8266 is tolerant of that 5V.
Sorry, only registered users may post in this forum.

Click here to login