Welcome! Log In Create A New Profile

Advanced

Liberty MM Conversion Project

Posted by Independence 
Re: Liberty Schematic Rev 0.1
September 04, 2020 11:17PM
...when they are actually pulled down from 5V to ground by the PIC...

Thanks, will update soon.
Liberty MM Schematic and Board Candidate
September 10, 2020 08:03PM
So I've been working on an initial schematic for the replacement Liberty controller. I think I've got it to the point where I'm ready to lay out the board, so I'm putting it out here for critique and feedback. It's quite a bit simpler than the original board as I removed all the feedback checks, depending just on the thermistor temperature for information on what is going on. This is the same method that is used on all their later models, so I'm quite comfortable doing that. I'm also going with a PWM driven OEM hot-surface igniter for now, and will add an external spark igniter board at a later date, should it become necessary. The version 1 schematic is shown below:



It uses the Wemos D1 mini module as the controller, which in turns uses an esp8266 module as it's main component. This module will be mounted on the underside of the main board, so that it will face upwards in normal operation. That puts the wifi antenna facing upwards and unencumbered. Although the Wemos D1 module has a single ADC input, there is an internal 320K resistive divider that scales it up to 3.2v. These internal resistors kind of skews the thermistor readings so I decided to add an external ADC, which is the commonly used ADS1115 4 channel chip. Since I will have the 0.05 ohm current resistor for the igniter anyway, I decided to keep that in there and will use an extra ADC line to monitor it. Another ADC line is used to detect the two push buttons using different pull-down resistors. That leaves me with one digital GPIO and two ADC inputs spare for future use.

I kept most of the circuitry the same as I will be cannibalizing the OEM board for most of the components. I will also be laying out the major components in pretty much the same location as the original, except where it doesn't make sense. Here are the Kicad renderings of what the board will look like. I took shortcuts on some of the footprints so they may not have the correct shapes in the renderings, but the footprint and placement are accurate:




Based on the Defender schematic, this same circuit design should work for it, though I have no idea of the size of the board, type of connectors, etc. If someone could provide some of this information, I could potentially add different connectors types, mounting holes, etc that could make it usable on a Defender or Patriot as well.
Re: Liberty MM Schematic and Board Candidate
September 11, 2020 06:07PM
This looks pretty good.

o Using the switch assembly ground as a signal directly coupled to the AD mux input: Could that be susceptible to static discharge zapping that input, or interference? Perhaps you could achieve a similar effect with 3 resistors in series: Vcc - R1 - Run - R2 - Stop - R3 - gnd, and yet another perhaps 10k resistor from Run or Stop into the mux for ESD protection?

o The 0.05 Ohm current sense resistor will reflect the PWM ramp-up waveform. Can this be synchronized with the ADC reading? If not, you may want some sort of RC low pass filter to remove or reduce the PWM pulses from this signal.

o I vote for including additional headers (e.g., +3.3, gnd, scl, scd) to connect other i2c accessories, such as the htu-21d temperature/humidity sensor (although the htu-21d reverses scl and scd), including an expanded IO port array. Perhaps this could control the Independence, if populated.

o The ads1115 might be happier on 3.3v instead of 5v

o The ADC is reading the thermistor voltage from the module 3.3v output. I suggest the IOT Defender approach that calculates the thermistor resistance from the ratio of the voltages from a fixed resistance (e.g., 20k0) into a lightly loaded (e.g., the 330k A0) input with the bottom side of the thermistor grounded to measure the thermistor contribution and open drain to measure the 3.3v. This way, power supply variations are eliminated. The bypass cap would be very small or eliminated.

o After 2 years of this approach, I do not miss the additional precision of switching scales. The 20k0 scale is accurate enough to troubleshoot those nasty "abnormal combustion" issues I wrote about earlier. This would save two outputs and some logic to do the switching. Plus the nuisance of the calibrating the overlapping ranges. And no issues with different drive currents.

o Hey, it is really handy to have access to the console Tx and Rx for those rainy nights when the system keeps crashing. Another NodeMCU connected to those ports can transmit the serial output to a dry location for analysis. A header with ground, Tx, Rx, and maybe power might come in very handy.

o I thought D0 couldn't do PWM ??? It is perfect for driving an LED, though.

o The 511 ohm LED resistors may be too large for the 3.3 volt output, resulting in dim LEDs. What a waste of precious IO. Another reason to perhaps put in an expander.

o I suppose you will should support the existing Liberty error codes, which you can do perhaps with clever analysis of the temperature vs time. The fan error might be a challenge, since you are not using the fan output...

I really like the smaller form factor D1 mini, except for the cheaper USB chip, and no flash button, although I don't use the flash button anyway, only RTS for reset and OTA. I assume you are using a 4 mb flash unit, some are 1 mb. Either way, there is enough flash memory, but still... I suggest including telnet and ftp, or some other solution for OTA programming. Still it is fun to telnet into the unit. I modified ftp to allow a command (rhelp) to reboot the system.

My Defender and Patriot are catching mosquitos now, so I can't get dimensions. I agree it would a good idea to consider and perhaps accommodate those dimensions, but I can't do it right now.
Re: Liberty MM Schematic and Board Candidate
September 12, 2020 05:03AM
Well, thanks for taking the time to look it over. A second opinion is always useful.

>Could that be susceptible to static discharge zapping that input, or interference?

It IS a membrane button, so I think that provides some degree of protection, but having a path to ground all the time wouldn't hurt so I added an extra resistor to ground.

> ..some sort of RC low pass filter to remove or reduce the PWM pulses from this signal.

The goal of that current sensor is just to check that the igniter is still working, so I won't even check it until it has reached steady state. I wasn't planning on measuring the current during the PWM ramp up. For the record, the deadbug igniter controller has been working very well the last couple of days of starts. The code I posted reflects what I am using now. It's a two stage ramp and the current during the ramp ups never exceeds the final steady state value. Ignition is detected at about 45 seconds after the igniter is turned on, and turning it off at 60 secs has been working fine.

I also decided to add some circuitry to detect that the fan is running. It's just a simple Zener diode voltage drop to 3.3v into a digital input of the Wemos. Arduino has a pulsein() function that can measure the width of the pulse, so simple enough.

> o I vote for including additional headers (e.g., +3.3, gnd, scl, scd) to connect other i2c accessories,

I added an i2c header with both 3.3v and 5v power.

> o The ads1115 might be happier on 3.3v instead of 5v

It will work all the way to 5.5v and I wanted it to be able to measure up to 5v. Fortunately the Wemos is 5V tolerant and the i2c bus works fine in this mode.

> o The ADC is reading the thermistor voltage from the module 3.3v output. I suggest the IOT Defender
> approach that calculates the thermistor resistance from the ratio of the voltages from a fixed resist

I'll have to look at your IOT Defender blog to see exactly what you are proposing. I did it the 2 stage way, ala Independence, even though the Liberty doesn't need to measure as high temperatures. Since all the code is already written to automatically handle the two stage system and I have all the existing worksheets for temperature vs voltage, for me, it was an easier way to go.

> o Hey, it is really handy to have access to the console Tx and Rx for those rainy nights when the system keeps crashing.

I know you used the LUA environment for your NodeMCU project, but if you didn't know, the Arduino environment supports a full serial console environment via the USB port. It's trivial to include simple CLI commands and responses in your code and control and view your program via the Arduino IDE console. All my debugging is done this way, and sometimes you end up with quite a list of debugging commands and functions! Also, the whole point of using the Wemos 8266 controller is to do everything by wifi anyway.

> o I thought D0 couldn't do PWM ???

The Wemos D1 esp8266 module can do PWM on all the digital GPIO pins.

> o The 511 ohm LED resistors may be too large for the 3.3 volt output, resulting in dim LEDs.

The LEDs are pulled down from 5V by the Wemos output, so no problem there.

> What a waste of precious IO. Another reason to perhaps put in an expander.

That's why I like the Atmega 32u4 chip. Plenty of GPIO and ADC pins. But you gotta love the Wifi on these modules, so that's the compromise.

> o I suppose you will should support the existing Liberty error codes,

I don't even know what the Liberty error codes are, but before I added the Wifi Web interface to my Independence, I had developed a multitude of flashing LED signals. The Independence has a single combined RED/GREEN LED. So you could get green, red, and orange colors. I actually had an LED handler that could blink each color a number of times. So, while it was warming up, for example, the orange LED would be on. I would start to flash it an increasing number of times as it approached RUN mode. It was my way of telling the temperature. 1 flash was a certain temperature range, 2 flash another, etc. And then when it turned green, it would also flash green a number of times to indicated what temperature range it was currently operating in. And obviously, if it errored out, it would sit there and blink the red LED a number of times. I had a blink code for every possible error.

I left the code in even though all the temperatures and states are now available via the Web interface and MQTT, and since I will be reusing the code, I guess it will remain. Having 3 separate LEDs to play with expands the possibilities though!

> I suggest including telnet and ftp, or some other solution for OTA programming.

The Arduino platform has a lot of available libraries, including OTA programming. I've played with OTA programming but it's not a priority for me. My MMs are nearby and it's easy enough to go out there with a Macbook and plug into the USB and program it, which doesn't happen very often. I try to use the Web interface for everything and having it makes it easy to integrate into Smarthome. There's no point in doing it since it's already automated, but it would be trivial to add the Independence to my Smarthome and be able to say 'Alexa, turn on/off Mosquito Magnet'

> My Defender and Patriot are catching mosquitos now, so I can't get dimensions.

That's ok, this is going to be version 1 of the PCB, and we all know it never ends at 1.0...

So, having said that, here is version 1.1 of the schematic, based on your comments:


Here it is, first version of the Liberty controller...
September 19, 2020 09:05PM
Well, mosquito season is pretty much over where I live, so this project well may continue next spring, but I just received the fabricated PCB, so here is the first version of the Liberty controller. For comparison, here is how the original Liberty MM controller board looks like:



And here is how the new controller ended up, based on the V1.1 schematic in the previous post:



and the back side:



(continued on next post)
Re: Here it is, first version of the Liberty controller...
September 19, 2020 09:13PM
And here is how it looks installed in the Liberty:



As mentioned earlier, I pretty much kept most of the mechanical and electrical design, where possible. After I received the board, it occurred to me that the top of the Liberty case slopes down a bit so there might be a clearance issue with the Wemos board, but even with it raised up, it clears fine.

A quick check of the board seems to indicate I didn't screw anything up and everything should work as expected. I will start porting over my Independence code to this controller but probably won't get to test it in a real environment until next spring/summer.

Happy Fall/Winter... :)
Re: Here it is, first version of the Liberty controller...
September 22, 2020 02:36PM
Uh oh, spoke to soon. I thought I could multiplex the RX and TX pins as i/o but I was wrong and you can't use them as i/o if you want the console to work, so those pins have been freed up since I need the console to debug. The 2 stage ADC voltage divider has been simplified to use a single digital pin. I also thought the fan input was a signal with a frequency that reflected the RPM, but it's actually just an inverted version of the driving PWM signal. So that signal has been modified to be filtered into an input voltage for the built in ADC. These mods could be done via simple rework, so I didn't re-spin the board. So here is V1.2 of the schematic:



9/24/20 - Edited the schematic to just feed the pulled-up tach input into the ADC. Not sure what to do with it at this point...



Edited 2 time(s). Last edit at 09/24/2020 03:41PM by Independence.
No Tach Signal?
September 23, 2020 05:06AM
I have to say your board looks great!

...I also thought the fan input was a signal with a frequency that reflected the RPM, but it's actually just an inverted version of the driving PWM signal...

That's disappointing. I thought so, too. What is the point of such a signal? And how does a fan do inversion?

Perhaps a small tach signal is riding on the large PWM waveform, so is not being detected. I would expect the tach signal to be referenced to the 'ground' lead, which here is being driven. If the fan is 100% on, just grounded, can you see a signal? If so, driving the fan 'hot' side may be the simplest approach, but it requires new and different components, and messes up the layout. Depending on the tach signal level and PWM frequency, a differential measurement using two ADS1115 pins and some resistors might work. Fortunately, fan speed measurement is not critical for operation.

Anyway, I hope you can get this going before the mosquitos disappear.
Re: No Tach Signal?
September 23, 2020 03:32PM
Ahh.. as you surmised, driving the ground with PWM is resulting in what I'm seeing. The fan tach output is an open collector to ground, and when pulled up, with the fan fully driven, I do see a correct frequency. I don't think it's worth redesigning, so I think I am going to integrate the signal and only check it when the fan is set for full speed. Maybe, once a day, set the fan to full speed and check the voltage for half the pull up voltage. Then restore normal speed.

Speaking of fan speed, it appears the Liberty doesn't run at full speed, but at some level below it. I remember this because I saw the PWM signal when I tried to measure the tach. Unfortunately, I didn't measure all the duty cycles and now I don't have the OEM controller running anymore, so I don't know what to set the run speed of the fan. In looking at the Arduino Defender code, it's like between 205, 210 out of 255. I guess I'll start with those numbers first....
Re: Here it is, first version of the Liberty controller...
September 25, 2020 10:27PM
So, the weather has warmed up a bit here and I've had a chance to port my Independence code to the new esp8266 based Liberty controller. All the code ported over without any problems, which is a testament to the Arduino architecture. I now have the same operational functionality of my Independence MM on a Liberty MM, minus the battery operation mode. The only issue I had was with the thermistor readings, which I'll cover later in this post.

I've already posted pictures of the controller board mounted in the Liberty, so this post will cover mostly the functionality. To recap, the idea was to re-configure the Liberty so it had wireless network control, as well as full control of it's operation. The Independence was done with an Atmega 32U4 controller chip, with a Wemos D1 mini module added later to provide the WiFi access. For the Liberty, it was decided to use the Wemos D1 mini for all the functions. The basic software includes the following:
  • The Liberty now powers on in the ON state always. So, if you loose power, when it returns, the Liberty will go through its power on cycle. You can still turn it on and off using the buttons, or via Wifi, if you so desire.
  • To deal with short outages, the Liberty will not go through it's ignite cycle until the thermistor is above a certain value. It will go through the purge stage, but will not advance to the ignition or gas stage until the thermistor temperature has reached a certain level.
  • The igniter has a PWM ramped turn-on sequence stretched out over 20 seconds. The goal was to preserve the igniter by preventing the 'shock' of a hard current turn-on. Others have had success with this method, so lets see how it works out.
  • The current turn-on sequence is to purge for 25 seconds, and then, if the thermistor is within range, start the igniter ramp, along with turning on the gas. The igniter stays on for a total of 65 seconds, or until ignition is detected via a 0.1v change in thermistor voltage. If no ignition is detected within 4 mins, it shuts off the gas and errors out.
  • Once ignition is detected, it proceeds to the warm-up stage, where it will wait up to 30 mins to reach operating temperature. If it fails to do so in this time, it will error out.
  • Once it reaches operating temperature, it will monitor for over and under temperatures and error out appropriately.
  • During the entire start-up and run process, the LEDs provide visual indications of the state, in case WiFi access is not available. When the unit powers on, the Yellow LED comes on solid. It stays this way until ignition is detected, whereupon it changes to a single yellow blink. There are then up to 4 yellow blink sequences that indicates the range of temperate that the unit is currently in. When it reaches operating temperature, the Green LED comes on solid. The Green LED is then blinked to indicate the operating temperature range. I currently only have 1 blink range as I don't have a good feel for the operating temperatures of the Liberty yet.
  • If the unit errors out for any reason, it will sit there and blink the Red LED a number of times that indicates which error caused it to error out.
  • If the unit is put into Fan only mode via Wifi, it will alternate between the Green and Yellow LED.
  • The Liberty controller will join your Wifi WAP as a client and will then appear on your network as an accessible device. It has a simple Web interface that can be used via any browser, or can be used to receive http commands from other web controllers. It also has a MQTT interface with which to report it's operating parameters. It can be enabled or disabled as needed.

This is what the web interface looks like. It's pretty much the same as my Independence.



The top part of the interface is used to control the Liberty. It's pretty much self explanatory. Option A-D are placeholders to easily add test functions without changing the interface. Right now, they set different fan speeds as I am experiment to see if there is a difference. The lower portion of the web screen just provides a status of the Liberty. They are also reported via MQTT, if enabled. The web API is super simple, you just send the http command for action. For example, to set the unit into fan mode, you just send it the request http://liberty1/fan. Yes, there is no security and anyone that has access to your network can control your MM. You COULD use https and obfuscate your URL with a key only you know, but I don't really care...

Here's a startup plot from the MQTT data:



Not much to see compared to the Independence. I'm only sending it once every minute, so the startup is somewhat compressed. The orange line is the state, and you can see it go from purge (1), to ignite (4), to warmup (6), to run mode (7). Since the igniter stays on for barely a minute, often it doesn't even register. The green line is the thermistor, with it's two stage setting. It turns out it doesn't get that hot compared to the Independence.

Well, that's about it. It ran last night and it's running now, but I don't really have any more mosquitoes to catch, so the real test will come next year. I was going to cover some of the other things I found in this post, but I think that's enough for this post. I will make another post for those items.
Some Thermistor musings...
September 26, 2020 02:21AM
As part of the testing I did on the software for the Liberty, I seem to be seeing a difference in the thermistor readings. I have limited data on the Liberty and in fact, the only data I have of it are the couple of DVM thermocouple based readings I made earlier. The thermocouple was taped with thermal grease right next to the thermistor. Those readings seemed reasonable and the Liberty reached operating mode at an indicated thermocouple temperature of about 110C. Well, after bringing up the new Liberty controller, I am seeing some different results from what I expected.

Take a look at these readings:



The first two columns are the expected values from the OEM thermistor. The 3rd column is the expected measured voltage from the ADC based on the high (25-70C) and low (80C upwards) ADC settings, based on the thermistor resistance. The 4th column, however is what was measured by the ADC, based on the thermocouple temperature. It is significantly different and I'm suspecting that I don't have same 200K@25 B25/85=4623 thermistor as I do on the Independence.

The numbers are closer with the Digikey KC014G-ND thermistor:



What is unusual is that even with the Digikey thermistor, it is still reading low, i.e. thermistor voltage is telling me the calculated temperature is lower than what the thermocouple is telling me. And I would expect the thermocouple to read a bit lower than the thermistor, due to worse thermal contact. For now, I am just adjusting my temperature thresholds based on the measured data, but at some point, I may have to test the thermistor to see if I can account for the difference.

The other thing I noticed about the Liberty is that the thermal behavior of the cat chamber is different from the Independence. On the Liberty, when I shut off the gas after ignition, the temperature continues to rise for at least a minute or two before it starts to decline. On the Independence, it starts to drop almost immediately. I think the combustion and reaction inside the cat chambers are slightly different, with the Liberty taking longer to respond thermally.

The nights are getting too cool to bother to run the MMs and the days are not hot enough to get a summer type run, so I'm probably going to bag it till next year.
Re: No Tach Signal?
September 26, 2020 11:27PM
...The thermocouple was taped with thermal grease right next to the thermistor. ...

Was it attached to the metal bracket holding the thermistor, and away from the case? I remember that the Digikey thermistor is supposed to be within 1% or so. There is a (low) thermal impedance in that bracket, and that the end (plus your thermocouple) form a thermal shunt to the ambient air around the case. I don't know how much thermal mass and loss is coming from the thermocouple, it may be significant.

the 4th column ... is significantly different

(I was confused because the numbers in the 4th column are identical between the two charts in your post. Column 3, the 'expected' voltage, is different.)

There must be an explanation. Perhaps the 4th column voltages are off because the +3.3 driving voltage is changing as the system heats up. That's one reason the NodeMCU design measures resistor ratios rather than voltages, to calculate the thermistor resistance, eliminating that potential error source.

An interesting idea to work with voltages instead of °C. That calculation requires a table, and a log function, which was not in the lua math library... Had to roll my own. It wasn't that difficult. The table is based on the DK thermistor specs, so is I suppose not so accurate. But it is not too far off, either, compared to an IR temperature probe. The benefit was that a 5°C temperature rise for combustion detection works at any temperature.

...when I shut off the gas after ignition, the temperature continues to rise for at least a minute...

That's the tank gas valve, in the warm up phase, no? The gas does not really stop for a while after shutting the tank valve. The electric valve is much quicker. That said, I had an issue 2 years ago that when the unit was running steady, something would trigger a 10-15° temperature rise then a combustion failure. That rise would also frequently occur when shutting off the electric gas valve remotely. I called that "abnormal combustion." This was fixed by replacing the regulator. See the blog.

All in all, it looks like your system is excellent. It should be reliable if you add sufficient conformal coating. It needs a weatherproof laminated guide to the blinking lights! And a warning label that it will start when plugged in. I avoided all that by making the unit (switch and LED UI) behave exactly the same as the Defender. Still, it might be good to save the on/off state so that it persists during a power failure, just like the other adjustable settings.
Re: No Tach Signal?
September 27, 2020 09:15PM
> Was it attached to the metal bracket holding the thermistor, and away from the case?

Yes, it was taped right next to where the thermistor is notched into the bracket, with thermal grease. The thermocouple is a tiny device, less than a 1.5mm size bead, and shouldn't affect the overall temperature. See pic:



> (I was confused because the numbers in the 4th column are identical between the two charts in your post.

I did a poor job explaining it. Column 3 in both tables is the expected/calculated thermistor voltage by temperature, based on the type of thermistor and resistors used. Column 4 is the actual thermistor voltage measured by the controller, but mapped to the temperature indicated by the thermocouple. I don't think the differences can be accounted for by any minute changes in the 3.3v driving voltage. The only definitive way to find out is to measure the thermistor over a range of temperature, and I may do that in the down season.

> An interesting idea to work with voltages instead of °C.

It never really occurred to me to convert to temperature as I had always worked with the thermistor calculated voltages. All my data for the Independence was derived by using a potentiometer on it and finding the transitions points and corresponding thermistor voltages. The state machine works off voltage thresholds. If I find the real characteristics of the thermistor, I will just build a new temperature table and change the voltage thresholds points in the program to reflect it.

> The benefit was that a 5°C temperature rise for combustion detection works at any temperature.

I'll give you that one, but the voltage changes at the normal ignition temperatures range are pretty large so it's never been a problem.

> That's the tank gas valve, in the warm up phase, no? The gas does not really stop for a while after
> shutting the tank valve. The electric valve is much quicker.

I was actually shutting it off using the electric valve in both cases, and so expected to see a faster response. Not only does it not decrease in temperature, it actually continues to rise for almost 2 mins before it starts to decrease. I will play with and monitor this more next year.

> It needs a weatherproof laminated guide to the blinking lights! And a warning label that it will start when
> plugged in.

Um, I'm not making a product here.. :) I'm the only person using these devices. And the LEDs are only the visual backup. The same data is available on the web interface in a more verbose manner.

> Still, it might be good to save the on/off state so that it persists during a power failure,

Yes, I will be using the EEPROM on the unit to store the last state so that it would power back up in that state. That's the more logical way to do it... I'm also going to add an ambient temperature sensor as I like having that to correlate to the chamber temperature.

It's been running very nicely for the last 2 days. Since I don't really have any mosquitoes left, I basically have it run two short propane cycles at dusk and dawn, just as a test. My igniter ramp cycles must be good enough as it's been working flawlessly so far. The real test will be next year when I run it that way for several months.
Use at own risk!
September 29, 2020 07:30PM
After thinking about it for a bit, I've decided to post the PCB files and code for this project. This is a hobby and information site, and obviously anything that anyone takes from it is at their own risk. However, I've gotten a lot of useful information from things people have posted over the years, and if anything that I've done is useful to someone else, then it's worth posting here. It's being posted with all the usual disclaimers, including it all being experimental and untested, so use at your own risk!

This isn't a step by step post. Quite frankly, if you are able to take any the information here and actually make something out of it, then you probably have enough expertise to have done it all yourself anyway. The information is posted here mostly to use as an example as you navigate your own journey. So, no comments about how many design rules the PCB layout breaks, or how badly written the source code is!

So, with that said, here are the Gerber files for the controller PCB. It is based on the Version 1.2 of the schematic posted before. I actually haven't used this particular version as I am on Version 1.1, but it should be fine. In order to procure the board, you basically upload the zip file to jlcpcb.com, pay them about $25, and in just about 7 days, 5 nice little PCBs will arrive at your front door via DHL.

The source code follows. As mentioned, it's developed under the Arduino IDE. It's not fully documented, but fundamentally the main loop is just a state machine that handles the various states. All other functions are handled by service routines that are called every pass through the main loop. The SSID/password as well as the mqtt broker information is set in the code and is essentially hard coded. All the code takes less than
60K of space, so there is plenty of available space. The code posted is version 1.1 and includes support for a BME280 temp/hum sensor. It also uses the Flash memory to store the ON/OFF state of the machine so it powers up in the last state it was in. I also took the opportunity to add a Gas run time counter. It basically counts days, hours, minutes when the gas solenoid is turned on. Because there is no real EEPROM on the Wemos D1 mini, I had to limit the number of times the Flash was written to. So, it only updates the Flash each time the solenoid turns off, or once a day, whichever comes first. In my case, I use two cycles a day, so the Flash is only written twice a day during the season. For those who run it continuously, if you loose power, you will loose up to one day of usage.

So, here is the experimental code, mistakes, bugs and all:
/*
 * ESP8266 Wemos D1 mini controller for Experimentation

      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 for AC (N/A)

      Status LEDs based on Thermistor voltage:
        Warmup mode - Yellow LED
          Startup            - Solid Yellow
          Ignition Detected  - 1 Blink Yellow
          Various Voltages   - 2-4 blinks
        Run Mode - Green LED
          Run to 1.8v    - Solid Green
          2.0v to 1.8v   - 1 Blink
          1.8v to 1.1v   - 2 Blink
        Cooldown Mode
          5 blinks Yellow
        Fan Mode
          Alternate Green/Yellow

          Ver 1.0 - 9/26/2020 - First version
          Ver 1.1 - 9/29/2020 - Added temp/hum sensor, persistent ON/OFF state, and run time counter
 * 
 */

// Set the specific variables below first!!

//Define the client
#define CLIENT  "Liberty1"

// Client specific configurations below
#define VERSION "V1.1 Sep 29th 2020 " CLIENT
#define URL "/"
#define CLIENTID "esp8266" CLIENT

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

//mqtt information
const char* mqttServer = "<your.mqtt.broker.com>";
const int mqttPort = 1883;
const char* mqttUser = "";
const char* mqttPassword = "";

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

BME280 mySensor;

#define I2C_ADDRESS 0x48
ADS1115_WE adc(I2C_ADDRESS);

#define ON        1
#define OFF       0
#define DISABLED  0
#define ENABLED   1


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

// Define the Wemos pin mappings

#define SOLENOID  D8       // High turns on solenoid
#define IGNITOR   D0       // High turns on ignitor - not sure about cadence yet
#define GREEN_LED D5       // Green LED - Low to turn on
#define RED_LED   D4       // Red LED - Low to turn on
#define YEL_LED   D6       // Yel LED
#define ADLOW     D7       // Out high to set A/D lower mode - Set to input otherwise
#define FAN       D3       // PWM pin for the fan

// MQTT related definitions and variables

#define MQTT_POLL   60000      // 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;

//Membrane Switch definitions and variables
// Voltage thresholds
#define NULL_HIGH   5.0
#define NULL_LOW    3.24
#define OFF_HIGH    3.24
#define OFF_LOW     1.95
#define ON_HIGH     1.95
#define ON_LOW      0.2

// Debounce time in ms
#define SW_DBNC_TM  300

byte switch_latch, on_sw_state, off_sw_state;
unsigned long int on_switch_timer, off_switch_timer;

//Thermistor definitions and variables

// Voltage thresholds for Thermistor

#define YEL_1BLNK   2.418     // 40C
#define YEL_2BLNK   1.791     // 60C
#define YEL_3BLNK   1.239     // 80C
#define YEL_4BLNK   2.649     // 90C in low ADC range
#define TH_GREEN    2.30      // Run mode
#define GREEN_1BLNK 2.0       // Higher temps thresholds
#define GREEN_2BLNK 1.8       //
#define TH_UNDER    2.6       // Under temperature
#define TH_OVER     1.1       // Over temperature
#define IGN_DETECT  0.1       // 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;

// Define LED states for servicing. Set via set_led(STATE, #blinks)

#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
// 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. Stored in led_mode to keep track of current mode so as not to set more than once

#define WARM1     1           // Warmup state 1, solid yellow
#define WARM2     2           // Yellow 1 blinks
#define WARM3     3           // Yellow 2 blinks
#define WARM4     4           // Yellow 3 blinks
#define WARM5     5           // Yellow 4 blinks
#define RUN1      6           // Run mode, solid green
#define RUN2      7           // Green 1 blink
#define RUN3      8           // Green 2 blink

// Ignitor slow ramp up definitions. PWM range is 0-1023 for esp8266

#define TIMEOUT     65000     // Total timeout before shutting off igniter. Starts when IGN turns on
#define RAMPTHRESH  400       // PWM Threshold value for RAMP2
#define RAMP2HOLD   2000      // How many ms to hold on ramp2 wait stage
#define RAMP1WAIT   200       // Ramp 1 increment time
#define RAMP2WAIT   400       // Ramp 3 increment time
#define INCREMENT   20        // How many PWM increments per time period

#define PWMZERO     0         // PWM goes between 0 (OFF) and 255 (FULL ON)
#define PWMFULL     1023

// Ignitor State machine States

#define START     0             // Start here and wait for changes
#define RAMP1     1             // First stage of ramp
#define RAMPHOLD  2             // Hold stage of ramp
#define RAMP2     3             // Final stage of ramp
#define OFFHOLD   4             // Force igniter off, regardless of input

int ignitor_state, pwm;
unsigned long int ignitor_ramp_timer, ignitor_timeout;

// Some timer values in ms

#define PURGE_TM    25000     // Amount of time to purge
#define TO_IGNITOR  000       // Before ignitor
#define PRE_GAS     000       // Ignitor on before gas
#define NO_IGNITE   240000    // Maximum time to wait for ignition 4 mins
#define NORUN       1800000   // Max wait till run mode 30 mins
// Fan PWM definitions, need to validate normal running speed
#define FAN_NORMAL  1023
#define FAN_FAST    920       // Cooldown fan speed
#define FAN_90      920       // 90% fan speed
#define FAN_80      818       // 80% fan speed
#define FAN_65      665       // 65%
#define FAN_FULL    1023

// States for the main running routine. Not all 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, tempflag;
float temperature, humidity;
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){

  EEPROM.begin(8);
  
  pinMode(RED_LED, INPUT); digitalWrite(RED_LED, LOW);        // All LEDs off
  pinMode(GREEN_LED, INPUT); digitalWrite(GREEN_LED, LOW);
  pinMode(YEL_LED, INPUT); digitalWrite(YEL_LED, LOW);  
  analogWrite(FAN, 0); pinMode(FAN, OUTPUT);                  // Fan off
  analogWrite(IGNITOR, 0); pinMode(IGNITOR, OUTPUT);          // Ignitor off
  pinMode(SOLENOID, OUTPUT); digitalWrite(SOLENOID, LOW);     // Gas Solenoid off
  pinMode(ADLOW, INPUT);digitalWrite(ADLOW, HIGH);            // Disable low range of ADC

  Serial.begin(115200);
  WiFi.begin(ssid, password);     //Connect to your WiFi router
  Serial.println("");
  
  // Wait for connection
  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("/resrun", resrun);       // Reset run counter
  server.on("/optiona", optiona);     // Send options
  server.on("/optionb", optionb);     // Send options
  server.on("/optionc", optionc);     // Send options
  server.on("/optiond", optiond);     // Send options
  
  server.begin();                  //Start server
  Serial.println("HTTP server started");


  Wire.begin();         // Start i2c and initialize devices
  
// Setup address of temp sensor
  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);
  adc.setCompareChannels(ADS1115_COMP_0_GND);
  adc.startSingleMeasurement();                 // Kick off first measurement; it is read in arrears and 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 = on_switch_timer = off_switch_timer = thermistor_timer = temperature_timer = millis();
  main_state = adc_state = thermistor_state = 0;

  if (EEPROM.read(1) != OFF) {
    on_sw_state = ON;               // Force the unit to come on by spoofing the ON switch
  }
  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_switches();
  service_thermistor();
  service_led();
  service_mqtt();
  service_temperature(); 
  track_runtime(); 
  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 (off_sw_state == ON)
        off_sw_state = OFF;                     // Ignore off switch here
 
      if (on_sw_state == ON) {
        set_on();                              // Save the state
        on_sw_state = OFF;
        analogWrite(FAN, FAN_FULL);            // High Fan
        fan_speed = FAN_FULL;
        set_led(LEDYELLOW,0);
        pinMode(ADLOW, INPUT);               // Reset the thermistor range
        thermistor_state = HIGHSTATE;
        main_timer = millis();
        main_state = PURGE;
        Serial.println("Entering purge state..");
      }     
      break;

    case HIBERNATE:                             // This is essentially an OFF state, waits for ON button or Wifi control
      if (off_sw_state == ON)
        off_sw_state = OFF;                     // Ignore off switch here
 
      if (on_sw_state == ON) {                  // Don't reset switch state so it will continue to power on from OFFSTATE
        main_timer = millis();
        main_state = OFFSTATE;
        Serial.println("Exiting Hibernate state..");
      }     
      break;

    case PURGE:                                 // Runs fan at full speed to purge chamber of all gas
      if (on_sw_state == ON)
        on_sw_state = OFF;                     // Ignore on switch here
 
      if (off_sw_state == ON) {
        off_sw_state = OFF;
        main_state = POWER_OFF;
      }
      if (millis() - main_timer >= PURGE_TM ) { // After purge time, go wait, if necessary, before starting
        analogWrite(FAN, FAN_FULL);            
        fan_speed = FAN_FULL;
        main_timer = 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 (on_sw_state == ON)
        on_sw_state = OFF;                     // Ignore on switch here
        
      if (off_sw_state == ON) {
        off_sw_state = OFF;
        main_state = POWER_OFF;
      }      
      if (millis() - main_timer >= TO_IGNITOR && thermistor_state == HIGHSTATE) {   // If all conditions met, proceed to ignition
        analogWrite(FAN, FAN_NORMAL);            
        fan_speed = FAN_NORMAL;
        main_timer = ignitor_timer = millis();
        ignitor_state = START;
        main_state = IGNITE;
        Serial.println("Ignitor turned on..");
      }
      break;

    case IGNITE:                                // Start the ignition process
      if (on_sw_state == ON)
        on_sw_state = OFF;                     // Ignore on switch here

      if (off_sw_state == ON) {
        off_sw_state = OFF;
        main_state = POWER_OFF;
      }
      service_ignitor();                        // Only serviced here and in GASON. Starts ramping up the ignitor PWM values
      
      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();
        digitalWrite(SOLENOID, HIGH);
        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 (on_sw_state == ON)
        on_sw_state = OFF;                     // Ignore on switch here

      if (off_sw_state == ON) {
        off_sw_state = OFF;
        main_state = POWER_OFF;
      }
      
      service_ignitor();                        // Ignitor should turn off automatically around this time...
      
      if (thermistor_pre - thermistor > IGN_DETECT ) {     // If voltage is dropping, then we have ignition
        main_timer = millis();
        digitalWrite(IGNITOR, LOW);           // Ignitor should time out in service_ignitor(), but shut it off just in case
        ignitor_state = OFFHOLD;
        analogWrite(FAN, FAN_NORMAL);         // Set fan to normal speed
        fan_speed = FAN_NORMAL;
        main_state = WARMUP;
        set_led(YELBLNK,1);
        Serial.print(thermistor);
        Serial.println(" Gas ignited, proceeding to warmup");
   
      }

      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 (on_sw_state == ON)
        on_sw_state = OFF;                     // Ignore on switch here
      
      if (off_sw_state == ON) {
        off_sw_state = OFF;
        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");
        }

        if ( thermistor <= YEL_4BLNK && thermistor > TH_GREEN && led_mode != WARM5 ) {
          led_mode = WARM5;
          set_led(YELBLNK,4);
        }
      }
      else { 
        if ( thermistor <= YEL_1BLNK && thermistor > YEL_2BLNK && led_mode != WARM2 ) {
          led_mode = WARM2;
          set_led(YELBLNK,1);
        }      
        if ( thermistor <= YEL_2BLNK && thermistor > YEL_3BLNK && led_mode != WARM3 ) {
          led_mode = WARM3;
          set_led(YELBLNK,2);
        } 
         if ( thermistor <= YEL_3BLNK && thermistor > 1.1 && led_mode != WARM4 ) {
          led_mode = WARM4;
          set_led(YELBLNK,3);
        } 
      }

      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 (on_sw_state == ON)
        on_sw_state = OFF;                     // Ignore on switch here

      if (off_sw_state == ON) {
        off_sw_state = OFF;
        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;
      }

// Monitor the temps and set the Green LED appropriately

      if ( thermistor <= TH_GREEN && thermistor > GREEN_1BLNK && led_mode != RUN1 ) {
       led_mode = RUN1;
       set_led(LEDGREEN,0);
      } 
      if ( thermistor <= GREEN_1BLNK && thermistor > GREEN_2BLNK && led_mode != RUN2 ) {
        led_mode = RUN2;
        set_led(GREENBLNK,1);
      }
      if ( thermistor <= GREEN_2BLNK && thermistor > TH_OVER && led_mode != RUN3 ) {
       led_mode = RUN3;
       set_led(GREENBLNK,2);
      }
      break;

    case FAIL:                              // Comes here on error, turn off gas and set the error LEDs and error codes
      digitalWrite(SOLENOID, LOW);
      digitalWrite(IGNITOR, LOW);
      ignitor_state = OFFHOLD;
      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, 10);
        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 (on_sw_state == ON)
        on_sw_state = OFF;                     // Ignore on switch here

      if (off_sw_state == ON) {
        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;
      }
      else {
        analogWrite(FAN, FAN_NORMAL);
        fan_speed = FAN_NORMAL;
      }
      break;

    case COOLDOWN:                                            // This is cooldown state before powering off or hibernating
      if (on_sw_state || off_sw_state)
        on_sw_state = off_sw_state = OFF;                     // Ignore both switches here
            
      analogWrite(FAN, FAN_FAST);
      fan_speed = FAN_FAST;
      digitalWrite(SOLENOID, LOW);
      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 mosquitoes in the net till they die
      if (on_sw_state == ON) {      // Don't reset switch so it will power on normally from OFF state
        next_state = POWER_OFF;
        main_state = COOLDOWN;
        set_led(YELBLNK, 5);
        Serial.println("Cooldown before re-powering..");
      }
        
      if (off_sw_state == ON) {
        off_sw_state = OFF;
        next_state = POWER_OFF;
        main_state = COOLDOWN;
        set_led(YELBLNK, 5);
        Serial.println("Cooldown before power off..");
      }
      digitalWrite(SOLENOID, LOW);      // Keep forcing gas off
      break;        

    case POWER_OFF:                 // Set everything into OFF state before powering off
      digitalWrite(SOLENOID, LOW);
      digitalWrite(IGNITOR, LOW);
      ignitor_state = OFFHOLD;
      pinMode(ADLOW, INPUT);
      set_led(LEDOFF,0);
      error_code = led_mode = 0;
      analogWrite(FAN, 0);
      fan_speed = 0;
      main_state = OFFSTATE;
      set_off();
      Serial.println("Powering off");

      break;

    case PRE_HIB:                   // Prepare for hibernation. This state is for battery powered devices to prepare for real hibernation
      digitalWrite(SOLENOID, LOW);
      digitalWrite(IGNITOR, LOW);
      ignitor_state = OFFHOLD;
      pinMode(ADLOW, INPUT);
      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
      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("10-16 = Set error codes 0 to 6");
      Serial.println("20 = Set ADC High range");
      Serial.println("30 = Set ADC Low range");
      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 >= 10 && param <=16) {
          error_code = param-10;
          Serial.println(error_codes[error_code]);
        }
        if (param == 20) {
          pinMode(ADLOW, INPUT);
          Serial.println("High ADC Range");
        }
        if (param == 30) {
          pinMode(ADLOW, OUTPUT);
          Serial.println("Low ADC Range");
        }
      }
    }

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

    Serial.print(" Ignitor St = ");
    Serial.print(ignitor_state);

    Serial.print(" Ignitor = ");
    Serial.print(digitalRead(IGNITOR));

    Serial.print(" Solenoid = ");
    Serial.print(digitalRead(SOLENOID));
    
    Serial.print(" Fan Sp = ");
    Serial.print(fan_speed);

    Serial.print(" Temp = ");
    Serial.print(temperature,1);

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



void track_runtime() {
  switch(tracking_state) {
    case 0: 
      if (digitalRead(SOLENOID) == 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 (digitalRead(SOLENOID) == 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 set_on() {              // Set the EEPROM state to ON
  EEPROM.write(1, ON);
  EEPROM.commit();  
}

void set_off() {             // Set the EEPROM state to OFF
  EEPROM.write(1, OFF);
  EEPROM.commit();  
}
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 service_ignitor() {      // When serviced, starting with ignitor_state = START, will cycle through a ramp PWM cycle for the ignitor
  switch(ignitor_state) {
    
    case START:
      ignitor_state = RAMP1;
      pwm = PWMZERO;
      ignitor_ramp_timer = ignitor_timeout = millis(); 
      break;

    case RAMP1:
      if (millis() - ignitor_ramp_timer >= RAMP1WAIT) {     // Check for timed increment
        ignitor_ramp_timer = millis();
        pwm = pwm + INCREMENT;
        if (pwm >= RAMPTHRESH) {
          ignitor_state = RAMPHOLD;
        }
        analogWrite(IGNITOR, pwm);
      }
      break;

    case RAMPHOLD:                            // Hold here for a bit
      if (millis() - ignitor_ramp_timer >= RAMP2HOLD) {
        ignitor_state = RAMP2;                        // If hold time expired, continue rest of ramp
        ignitor_ramp_timer = millis();
      }

      break;

    case RAMP2:                               // Complete the ramp and hold till timeout
      if (millis() - ignitor_ramp_timer >= RAMP2WAIT) {
        ignitor_ramp_timer = millis();
        if (pwm + INCREMENT >= PWMFULL) {
          pwm = PWMFULL;
        }
        else {
          pwm = pwm + INCREMENT;
        }
        analogWrite(IGNITOR, pwm);
      }
      if (millis() - ignitor_timeout >= TIMEOUT) {    // If total ON timeout has expired, turn off and go to hold state
        ignitor_state = OFFHOLD;
        analogWrite(IGNITOR,PWMZERO);
      }
      break;

    case OFFHOLD:                             // Do nothing until state reset
      break;
        
    default:
      break;
  }
}

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_led() {          // This service routine handles all the LEDs. Set up via set_led() call
  switch (led_state) {

    case LEDOFF:
      pinMode(RED_LED, INPUT);
      pinMode(GREEN_LED, INPUT);
      pinMode(YEL_LED, INPUT);
      break;

    case LEDYELLOW:
      pinMode(RED_LED, INPUT);
      pinMode(GREEN_LED, INPUT);
      pinMode(YEL_LED, OUTPUT);
      break;

    case LEDRED:
      pinMode(RED_LED, OUTPUT);
      pinMode(GREEN_LED, INPUT);
      pinMode(YEL_LED, INPUT);
      break;

    case LEDGREEN:
      pinMode(RED_LED, INPUT);
      pinMode(GREEN_LED, OUTPUT);
      pinMode(YEL_LED, INPUT);
      break;

    case REDBLNK:
      switch (led_blink_state) {
        
        case BLNK_START:
          pinMode(RED_LED, OUTPUT);
          pinMode(GREEN_LED, INPUT);
          pinMode(YEL_LED, INPUT);
          led_timer = millis();
          led_blink_state = BLNK_ON;
          led_blink_cnt = 0;
          break;

        case BLNK_ON:
          if (millis() - led_timer >= LED_BLNK_ON) {
            pinMode(RED_LED, INPUT);
            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;
              pinMode(RED_LED, OUTPUT);
            }
            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:                      // Blinks OFF number of times specified
      switch (led_blink_state) {
        
        case BLNK_START:
          pinMode(YEL_LED, INPUT);
          pinMode(RED_LED, INPUT);
          pinMode(GREEN_LED, INPUT);
          led_timer = millis();
          led_blink_state = BLNK_OFF;
          led_blink_cnt = 0;
          break;

        case BLNK_OFF:
          if (millis() - led_timer >= LED_BLNK_ON) {
            pinMode(YEL_LED, OUTPUT);
            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;
              pinMode(YEL_LED, INPUT);
            }
            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:
          pinMode(RED_LED, INPUT);
          pinMode(GREEN_LED, INPUT);
          pinMode(YEL_LED, INPUT);
          led_timer = millis();
          led_blink_state = BLNK_OFF;
          led_blink_cnt = 0;
          break;

        case BLNK_OFF:
          if (millis() - led_timer >= LED_BLNK_ON) {
            pinMode(GREEN_LED, OUTPUT);
            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;
              pinMode(GREEN_LED, INPUT);
          }
            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:
          pinMode(GREEN_LED, OUTPUT);
          pinMode(YEL_LED, INPUT);
          led_timer = millis();
          led_blink_state = BLNK_OFF;
          led_blink_cnt = 0;
          break;

        case BLNK_OFF:
          if (millis() - led_timer >= LED_BLNK_ALT) {
            pinMode(GREEN_LED, INPUT);
            pinMode(YEL_LED, OUTPUT);
            led_timer = millis();
            led_blink_state = BLNK_ON;
          }
          break;

        case BLNK_ON:
          if (millis() - led_timer >= LED_BLNK_ALT) {
            pinMode(GREEN_LED, OUTPUT);
            pinMode(YEL_LED, INPUT);            
            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   2.8         // More than this value, switch to high range
#define THERMISTOR_LOW    1.1         // 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;
        pinMode(ADLOW, INPUT);
        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;
        pinMode(ADLOW, OUTPUT);
        Serial.print(thermistor);
        Serial.println(" Switching to Low state");
      }
      break;

    default:
      break;
    
  }
}

void service_switches() {       // Sets on/off_sw_state to ON if ADC range detected for debounced time. Set to OFF once you've acknowledged it.

  if (adc2 >= NULL_LOW) {
    on_switch_timer = off_switch_timer = millis();
    switch_latch = OFF;
  }
  else if (adc2 >= OFF_LOW && adc2 < OFF_HIGH)
    on_switch_timer = millis();
  else if (adc2 < ON_HIGH)
    off_switch_timer = millis();

  if (millis() - on_switch_timer >= SW_DBNC_TM && on_sw_state == OFF && switch_latch == OFF) {
    on_sw_state = switch_latch = ON;
  }
  if (millis() - off_switch_timer >= SW_DBNC_TM && off_sw_state == OFF && switch_latch == OFF) {
    off_sw_state = switch_latch = ON;
  }


/*-* Debug for now
  if (millis() - on_switch_timer >= 5000 && switch_latch == ON) {
    Serial.println("ON switch pressed for 5 secs!");
    on_switch_timer = millis();
  }
  if (on_sw_state == ON) {
    Serial.println("ON switch detected!");
    on_sw_state = OFF;
  }
  if (off_sw_state == ON) {
    Serial.println("OFF switch detected!");
    off_sw_state = OFF;
  }
*-*/

}

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.setVoltageRange_mV(ADS1115_RANGE_0256);         // Switch to more sensitive range for ignitor current resistor
        adc.startSingleMeasurement();
        adc_state = 1;
      }
      break;

    case 1:                                                 // This is ignitor current resistor
      if (!adc.isBusy()) {
        adc1 = adc.getResult_V();
        adc.setCompareChannels(ADS1115_COMP_2_GND);   
        adc.setVoltageRange_mV(ADS1115_RANGE_6144);         // Switch back to wide range for membrane switches and thermistor
        adc.startSingleMeasurement();
        adc_state = 2;
      }
      break;

    case 2:                                                 // This is membrane switches
      if (!adc.isBusy()) {
        adc2 = adc.getResult_V();
        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 "/fan", String(fan_speed).c_str());
    client.publish("sensor/" CLIENT "/ignitor", String(digitalRead(IGNITOR)).c_str());
    client.publish("sensor/" CLIENT "/solenoid", String(digitalRead(SOLENOID)).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>";
  webmessage += "<br><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>Run Time (D:H:M) = ";
  webmessage += String(days);
  webmessage += ":";
  webmessage += String(hours);
  webmessage += ":";
  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 (digitalRead(IGNITOR))
    webmessage += "On";
  else
    webmessage += "Off";
  webmessage += "<BR>Solenoid St = ";
  if (digitalRead(SOLENOID))
    webmessage += "On";
  else
    webmessage += "Off";
  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;
    on_sw_state = ON;
    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);  

  digitalWrite(SOLENOID, LOW);     // Turn off gas
  digitalWrite(IGNITOR, LOW);      // Also make sure ignitor is off
  ignitor_state = OFFHOLD;
  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);  

  digitalWrite(SOLENOID, LOW);
  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 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);  
}



Edited 3 time(s). Last edit at 09/30/2020 01:43AM by Independence.
Re: Use at own risk!
September 29, 2020 07:38PM
FYI, this is what the web screen from the posted V1.1 code looks like:



Because this version has only one controller and doesn't pass messages between two different controllers like my Independence, it is easier to update the web screen with useful information. In fact, now that I've enhanced this code, I may go back and re-spin my Independence board to just use the Wemos D1 as well. I think the run time counter will be useful next year to check on gas consumption.
Temperature rise with gas off...
September 30, 2020 07:20PM
Just to follow up on my comment that the Liberty temperature seems to continue to rise for a bit even though the gas is shut off at the solenoid, here are some data points:



The plot above represents what happens during the warmup phase, when the Liberty is put into fan mode, thus turning off the gas. You can't quite see the exact timings in the plot, but the solenoid was shut off at 11:48:26 and the temperature continued to rise (thermistor voltage keeps going down) until 11:50:56, pretty much for 2 1/2 minutes. This was happening at a temperature of about 40-50C.

Now, with the Liberty running at operating temperature. approximately 120C, this is what happens when the gas is shut off:



The gas was shut off at 12:42:19, but the temperature didn't start to drop until 12:43:09, a time of 50 seconds. Despite the much higher difference between chamber temperature and ambient, it still took a while to start dropping, almost like some reaction was continuing inside, without any gas.

On the Independence, the temperature starts to drop almost immediately when the gas is shut off. Granted, it does have the TEGs that draw heat away from it, so maybe that accounts for it. But when I was doing my initial investigation on the Liberty, I just thought it was strange that the temperature could continue to rise for such a long time after the gas was shut off.

** File under 'Too much time on my hands..' :) **
Re: Liberty MM Conversion Project
November 01, 2020 02:10PM
I just finished reading through all the details of the Liberty engineering effort. Holy cow!!! Probably as much real engineering as the original coding and electronics design at the OEM.

With the Defenders relegated to the history bin, and only an occasional one popping up on eBay, when my current stash of four Defenders starts to fade, switching to the latest model will be the next big leap.

The ignition issues are somewhat surprising- the nitride igniters have been working perfectly ever since the Dev suggested the slow PWM ramp up. There were over 600 good ignitions this past summer across four Mega Defenders, and no igniter failures. The design seems to be working- but we'll see how the do after a winter in storage (inside).

Hats off to the super detailed and well documented efforts!
Re: Liberty MM Conversion Project
November 01, 2020 08:49PM
> With the Defenders relegated to the history bin, and only an occasional one popping up on eBay, when
> my current stash of four Defenders starts to fade,switching to the latest model will be the next big
> leap.

When choosing units to re-engineer, I would have preferred a Patriot or Patriot Plus. The Defenders only do 1/2 acre, and the Liberty nets are hard to find now. I got all my units off Craigslist for free, so beggars can't be choosy! But Patriots seem to not have the same failure problems and never show up for free. The Independence/Liberty Plus/Executive have the advantage of battery operation, so that's nice if you need a remote solution, and still occasionally show up.

> The ignition issues are somewhat surprising- the nitride igniters have been working perfectly ever since the Dev
>suggested the slow PWM ramp up.

What ignition issues are you referring to? I implemented the same slow start algorithm and based on your results this year, hope that it resolves any igniter failure issues. I've basically shelved the spark ignition for now. My home furnaces use similar type of nitride igniters and have been igniting for 22 years, so I'm guessing with proper use, they should last a long time...

> Hats off to the super detailed and well documented efforts!

Thanks! Together with yours and Dev's efforts, I'm hoping this place becomes the de facto place for everything Mosquito Magnet...
First real test of converted Liberty
June 10, 2021 02:35PM
This is a follow-up from last year, when I really didn't get to fully test out the new board due to the end of the season:

The converted Liberty has been running for about 4 days now. It came out of storage and started up with no issues, running 10 hours a day in a 5 hour dusk and 5 hour dawn cycle. It's been catching mostly no-see-ums and just a couple of mosquitoes per cycle. The ignition cycle with the soft start ignitor has been working fine, with no failures to ignite, and hopefully it will last a long time, despite the two cycle a day usage.

Since I am just testing it, I have been running it side-by-side with my regular Independence. The Independence seems to catch more mosquitoes and the Liberty seems to catch more no-see-ums. Not sure why, but I will keep monitoring as the season heats up. I've been running MMs for quite a few years so they don't catch as many pests as they did when I first started, so this is mostly maintenance mode. I miss seeing a bag full of mosquitoes, but that's a good thing!

Other than adding in OTA firmware update code, the firmware is essentially the same as what was last posted. At this point, it does everything I need so I'll just be monitoring the two MMs and eventually decide which one I will run as my primary unit. The Run time counter should be useful and I'm interested to see the number when it runs out of gas, which should be about 50 days at my current duty cycle.
Re: First real test of converted Liberty
September 06, 2022 02:50PM
I'm pretty ashamed that it has taken me so long to get back on here.

I have a Liberty unit. It will not keep running properly despite all the things I've tried to keep it going.
I ended up getting a big dynatrap, which gobbled up pretty much everything and a few mosquitoes. Putting an octenol pod on the top drew mosquitoes to the trap, but saturated the yard with it and brought more mosquitoes than it could trap. Yeah, dumb, but this is how we learn.

When my Liberty was new, I could knock down the female population at the start of the season and keep reducing it. The result was great (really great) and I could get a 50% fill on the catch bag with each tank. So I am still very much interested in getting mine working again. Mine starts up properly, but then shuts down after running for a while. I thought it was overheating, but I don't have a scope anymore to check states on the PIC.

I've always enjoyed reading Pal's ("dev") work on his (non-Liberty) unit, adding more and more features and creating a seriously analytic controller for it. The data collected helps to focus in on fine-tuning the capability to maximize the catch. That being said, it never really got applied to the Liberty and I got sidetracked with life for some time.

Now, this igniter ramp-up (in lieu of your spark igniter for the moment). This is to maximize the lifespan of the igniter as I understand it? Your new controller seems to enable your MM to work better or should I say operate without intermittent failures. It seems to do this by eliminating some error-prone self-monitoring-checks built into the PIC, one of which may be shutting down my trap.

I'd like to give your new controller a shot to see if I can just get it to reliably stay running, and if so, then I'd like to start adding features to maximize the catch if possible.

Do you have a board you can sell me and are the notes in here enough to do the conversion?
Also, have you done any more development work on yours since last year?

Thanks!
Re: First real test of converted Liberty
September 06, 2022 04:38PM
liberty1 wrote:

Mine starts up properly, but then shuts down after running for a while. I thought it was overheating, but I don't have a scope anymore to check states on the PIC.

When it shuts down, there should be a blinking fault code. That should provide information on why it shut down.

Do you have a board you can sell me and are the notes in here enough to do the conversion?

I don't have any complete boards, but I do have extra PCBs that I can send you for postage costs, but you will have to gather components and build and program it yourself.


Also, have you done any more development work on yours since last year?

No, both my independence and liberty units had been running reliably and I have not had to do any more work on them. Also, I was away most of this summer, so did not even fire up the units this year.

If you are not able to build your own controller, I suggest you do a little bit of troubleshooting first on your unit. There are people who will help you on this forum. Just post what you are observing and you should get some advice. You said your liberty shuts down after running a while. That is usually from over or under temperature. Finding out which it is would be the first step. This site has the fault codes:

MM3000 fault codes
Fix that Liberty
September 10, 2022 02:36PM
... has taken me so long to get back on here ...

Same here. I disassembled my Liberty 2 years ago, removed the controller, and June 2021 made a list of steps for modifying the board to use an EPS32 NodeMCU instead of the PIC. The first step was "Lift Liberty U1 pin 28" to remove power from the PIC. This was not easy to do, those PICs are pretty tough! So much so that I stopped working, given other pressing matters plus a general lack of urgency as my 2 other traps were catching and the mosquitos more or less under control.

With the season coming to a close, I have a little time to assist you in getting your trap to run. Replacing the original controller might make sense, but you still might need some troubleshooting instruments (e.g., a cheap scope, voltmeter) to test a new controller, as well as see what is going on in the trap. If you can get the tools, it makes sense to measure the existing controller to see where it fails. Also pay attention to any error codes. It is very encouraging that the trap starts and runs for a while, this means the controller's sensors and output drivers are mostly working.

One thing that might be an issue is the nozzle being partially clogged. I suggest removing the nozzle and cleaning it using brake cleaner, etc. This maintenance step would be desirable in any event, working or not.

If you believe the controller is defective, first try removing and thoroughly cleaning it. You can wash it with alcohol then water. Use an old toothbrush to brush the surface. Remove and repair any corrosion. Be careful with the foil traces going to the thermistor, they are high impedance and cannot tolerate any leakage currents. If this fixes the controller, apply a light coating of a PCB sealer or acrylic spray coating (that can be used on electric circuits).

If the old controller is really failing for mysterious reasons, building and testing a whole new controller may be too daunting. Consider adding the ESP32 NodeMCU PIC replacement instead. It uses the existing parts and just replaces the PIC, and provides real time WiFi internet access to the unit and its operating conditions, plus you can revert to the PIC when you finally solve the problem.

Good luck with your project!
Sorry, only registered users may post in this forum.

Click here to login