While the solution worked, it didn't work well enough. The Nest thermostat, when perceiving that the yurt needed heat, would turn the pellet stove onto low. If, after twenty minutes or so, the yurt hadn't sufficiently heated, the Nest thermostat would advance the pellet stove to medium. It repeated that cycle to eventually advance to high. The meant that when the yurt was cold, it would take more than forty minutes to provide "room temperature". Once it did achieve room temperature, the thermostat would just turn the pellet stove off.
Rather than slowly advancing through the levels, I needed the stove to start on high and then back down through the levels on shutdown. I call this a "lingering shutdown". My original solution was based on a logic table that did not have to deal with past state; the Nest thermostat handled the progression through states. To implement the lingering shutdown, I'd instead need a finite state machine that took into account previous state and have an external timing trigger. That lead to a ridiculous logic table and I abandoned that path.
I eventually dropped the exclusively electromechanical aspect of this solution and wrote software running on a Raspberry Pi to do the interface. Relays were still used, but only for electrical isolation of the components. This software was problematic, too, but in a different way. The UX wasn't very good: to change the timing of the lingering shutdown levels required opening an ssh session and manually setting configuration values (yes Socorro people, complex configuration bit my ass here, too). It was just never critical enough to justify the effort of writing a UI the whole family could use.
That changed when Things Gateway with the Things Framework, was released. I could now create a program that could mediate between the Nest and the thermostat and, without additional effort, have a Web accessible user interface. Further, it enables a rule system that can adjust the behavior of the stove based on any device the Things Gateway can use: temperature sensors, wind sensors, etc. In fact, it is likely that I can just write the Nest thermostat entirely out of the system. One less cloud dependent service is a win.
The previous iteration of my software had already figured out how to get real world information in and out of the Raspberry Pi using the GPIO pins. I configured one GPIO pin as input and three GPIO pins as output.
GPIO pin 17 is in charge of actually turning the pellet stove on and off. This is done with another relay. When the software wants to turn the pellet stove on, it triggers GPIO17 which energizes the relay, which then shorts the red/white thermostat wires connected to the pellet stove.
Setting the level of the pellet stove is more complicated. Before my modifications, it was controlled manually via a SPDT center off switch on the back of the stove. It has three conductor connections, A, B, and C. It has three switch positions AB, BC, all off. To simulate this switch in the computer, I need three states, implemented as two binary bits. 00 is all off. 01 is AB, 10 is BC. The fourth state offered by 2 binary bits is unused. Moving into the physical world, this means using two GPIO pins to activate two more relays. The relays handle switching the 24VAC power from the original SPDT switch.
I wanted the Pellet Stove to appear as a thing within the Things Gateway. That means deriving a new class, PelletStove, from the WoTThing class. Within that, I define a number of properties:
- the on/off state of the thermostat
- the state of the stove itself (off, high, medium, low)
- the run level of the stove software (off, heating, lingering medium, lingering low)
- the number of minutes in a lingering shutdown for medium level
- the number of minutes in a lingering shutdown for low level
thermostat_state = WoTThing.wot_property( name='thermostat_state', description='the on/off state of the thermostat', initial_value=False, value_source_fn=get_thermostat_state, )(see this code in situ in the pellet_stove.py file in the pywot demo directory)
The code defines the value_source_fn as get_thermostat_state, an asynchronous method that will poll the status of the thermostat (via GPIO pin 18) and control the action of the rest of the system. When the thermostat goes from the off to on condition, this method will turn the pellet stove on high. When the thermostat goes from on to off, the method will trigger another asynchronous task that will slowly back down the pellet stove through the lingering shutdown procedure.
async def get_thermostat_state(self): previous_thermostat_state = self.thermostat_state self.thermostat_state = self._controller.get_thermostat_state() self.logging_count += 1 if self.logging_count % 300 == 0: logging.debug('still monitoring thermostat') self.logging_count = 0 if previous_thermostat_state != self.thermostat_state: if self.thermostat_state: logging.info('start heating') await self.set_stove_mode_to_heating() else: logging.info('start lingering shutdown') self.lingering_shutdown_task = asyncio.get_event_loop().create_task( self.set_stove_mode_to_lingering() )(see this code in situ in the pellet_stove.py file in the pywot demo directory)
The next property just reflects the current status of the stove: on/off and low/medium/high:
stove_state = WoTThing.wot_property( name='stove_state', description='the stove intensity level', initial_value='off', # off, low, medium, high )(see this code in situ in the pellet_stove.py file in the pywot demo directory)
The third property is the operating state of the software. Similar to stove_state, this property reflects the state of the stove as well as what's going on in the software. Right now, that is just indicated by heating and lingering shutdown levels. In the future, it will also show that manual overrides are in effect.
stove_automation_mode = WoTThing.wot_property( name='stove_automation_mode', description='the current operating mode of the stove', initial_value='off', # off, heating, lingering_in_medium, lingering_in_low, overridden )(see this code in situ in the pellet_stove.py file in the pywot demo directory)
During the process of lingering shutdown, the stove will progress through running at medium for a set number of minutes, to running at low for a set number of minutes, until finally turning off. The length of time that it takes for the medium and low states is something that can be set through the user interface. Because they can be set, that also means that Things Gateway Rules can also set them. In the future, I imagine connecting these with my Virtual Weather Station. This will facilitate adjusting the length of the lingering shutdown based on outdoor temperatures and wind speed.
medium_linger_minutes = WoTThing.wot_property( name='medium_linger_minutes', description='how long should the medium level last during lingering shutdown', initial_value=5.0, value_forwarder=set_medium_linger ) low_linger_minutes = WoTThing.wot_property( name='low_linger_minutes', description='how long should the low level last during lingering shutdown', initial_value=5.0, value_forwarder=set_low_linger )(see this code in situ in the pellet_stove.py file in the pywot demo directory)
My Things Gateway doesn't run in the yurt, it lives in my office in the old original farm house. Fortunately, ten years ago, I trenched in gigabit Ethernet between all my buildings. So, while standing in the yurt, I opened the Things Gateway in Firefox running on my Android tablet. I added the Pellet Stove thing, and it all worked correctly.
There appears to be a minor bug here. Only the "low_linger_minutes" and "medium_linger_minutes" are settable by the user. However, the Things Gateway is allowing all the other fields to be settable, too - even though setting them doesn't actually do anything. I've not yet figured out if this is a bug in my code or in the Things-URL adapter.
I monitored the logging of my pellet stove controller closely and saw this one morning:
2018-06-06 06:25:29,938 pellet_stove.py:100 INFO start heating 2018-06-06 06:53:39,008 pellet_stove.py:103 INFO start lingering shutdown 2018-06-06 06:53:39,014 pellet_stove.py:162 DEBUG stove set to medium for 120 seconds 2018-06-06 06:55:39,020 pellet_stove.py:171 DEBUG stove set to low for 300 seconds 2018-06-06 07:00:00,706 pellet_stove.py:100 INFO start heating 2018-06-06 07:00:00,707 pellet_stove.py:147 DEBUG canceling lingering shutdown to turn stove back to high 2018-06-06 07:14:14,256 pellet_stove.py:162 DEBUG stove set to medium for 120 seconds 2018-06-06 07:16:14,262 pellet_stove.py:171 DEBUG stove set to low for 300 seconds 2018-06-06 07:21:14,268 pellet_stove.py:178 INFO stove turned off
The stove started heating at 6:25am. It got the yurt up to temperature by 6:53am, so it started the lingering shutdown. At 7:00am, the Nest thermostat didn't know that the pellet stove was still running. From its perspective, it had turned the stove off at 6:53am. It acted according its own program to raise the temperature of the yurt from the nighttime level to the daytime level. My software was running the stove at low at that moment. It canceled the lingering shutdown and restored the stove to running on high. This is perfect behavior.
I'm almost disappointed that it is summer. In the next few days, I'll be shutting down the stove for the season and probably won't start it up again until late September. I look forward to next Fall when it get to really put this software through its paces.