It’s rare to get firsts these days :).

Some of the stick we put in the ground 8 years ago are starting to look like real trees. They have barely yielded any fruit so far, and so we hope that they’ll decide to produce a real crop one day. It’s been interesting to compare how well everything we bought from various nurseries performed. The best nursery by far has unfortunately closed. Another one I won’t name has given us only bad performers I’m tempted to just pull out of the ground after years of tending to. Worst failure rate, worst growth rate, worst everything, bleh.
plum
pear
This isn’t the most involved project but I might as well document it. I’ve been trying to automate some of the more boring tasks of running the evaporator, I’ve got some nice stainless steel float valves to regulate the sap going in now for example. One of the things that kept requiring constant attention is the air intake to adjust the strength of the fire. I’d have to sit with a foot on it to be able to regulate it almost constantly, to make sure the fire wasn’t burning too hard or too weak. And so naturally I thought I could do something with a Pi.
This proved quite successful even with very loose wiring and fastening just to see how it would work.
All of a sudden I barely need to pay attention to the fire’s strength, with a few refinements I won’t have to at all.
The circuit is quite simple:
Wifi barely reaches the sugarhouse so I made sure this could run independent of connectivity. Which involves coding threads on a Pi Pico, which is supported but not as one would expect.
import machine
import time
import network
import socket
from max6675 import MAX6675
import _thread
html = """{\"evaporator_temperature\":<TEMPERATURE>}"""
# LED
led = machine.Pin( "LED", machine.Pin.OUT )
# temperature
sck = machine.Pin( 2, machine.Pin.OUT )
cs = machine.Pin( 3, machine.Pin.OUT )
so = machine.Pin( 4, machine.Pin.IN )
sensor = MAX6675( sck, cs , so )
temperature_min = 25
temperature_max = 30
temperature = -1337.0
# servo
servo = machine.PWM( machine.Pin(0) )
servo.freq( 50 )
servo_min = 1000
servo_max = 8000
servo_at = 0
def temp_to_servo( temp ):
if (temperature_max - temperature_min)==0:
# right in the middle
return int( (servo_max-servo_min)/2 )
result = (temp - temperature_min) * (servo_max - servo_min) / (temperature_max - temperature_min) + servo_min
if result>servo_max:
result = servo_max
if result<servo_min:
result = servo_min
return int( result )
def blink_number( number ):
number = str( int(number) )
for char in number:
for i in range(int(char)):
led.value( 1 )
time.sleep( 0.2 )
led.value( 0 )
time.sleep( 0.2 )
time.sleep( 0.3 )
led.value( 1 )
time.sleep( 0.1 )
led.value( 0 )
time.sleep( 0.3 )
keep_going = False
def servo_thread():
global temperature, servo_at, servo
while keep_going:
time.sleep( 5 )
print( "# measuring average temperature over 1 seconds..." )
temperature_total = 0.0
for i in range(10):
temperature_total += sensor.read()
time.sleep( 0.1 )
temperature = temperature_total / 10
blink_number( temperature )
print( "# " + str(temperature) )
new_servo_position = temp_to_servo( temperature )
print( "# new_servo_position: " + str(new_servo_position) )
step = 1
if new_servo_position<servo_at:
step = -1
for i in range(servo_at, new_servo_position, step):
time.sleep( 0.001 )
servo_at = i
servo.duty_u16( i )
print( "# servo tread finishing" )
# main thread
servo.duty_u16( servo_min )
for i in range(servo_min, servo_max):
time.sleep( 0.001 )
servo_at = i
servo.duty_u16( i )
try:
ssid = "<wifi_ssid>"
password = "<wifi_password>"
wlan = network.WLAN( network.STA_IF )
wlan.active( True )
wlan.connect( ssid, password )
# wait for connect or fail
max_wait = 20
while max_wait>0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print( "> waiting for connection..." )
time.sleep( 1 )
# handle connection error
if wlan.status()!=3:
print( "> network connection failed, will launch servo thread in 1 minute" )
time.sleep( 60 )
print( "> launching" )
keep_going = True
_thread.start_new_thread(servo_thread, ())
while True:
time.sleep( 1 )
else:
print( "> connected" )
status = wlan.ifconfig()
print( "ip = " + status[0] )
# open socket
addr = socket.getaddrinfo( "0.0.0.0", 80)[0][-1]
s = socket.socket()
s.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
#s.settimeout(1)
s.bind( addr )
s.listen( 1 )
print( "> web server listening on", addr )
# listen for connections
while True:
print( ">" )
try:
cl, addr = s.accept()
print( "client connected from", addr)
request = cl.recv( 1024 )
request = request.decode( "utf-8" ).strip()
print( request )
if request.startswith( "GET / " ):
print( "get data" )
response = html.replace( "<TEMPERATURE>", str(temperature) )
cl.send( "HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n" )
cl.send( response )
elif request.startswith( "GET /min_temperature " ):
print( "get min_temperature" )
cl.send( "HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n" )
cl.send( str(temperature_min) )
elif request.startswith( "GET /max_temperature " ):
print( "get max_temperature" )
cl.send( "HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n" )
cl.send( str(temperature_max) )
elif request.startswith( "PUT /min_temperature " ):
print( "put min_temperature" )
new_min_temperature = int(request.split( "\r\n\r\n" )[1])
print( new_min_temperature )
temperature_min = new_min_temperature
cl.send( "HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n" )
cl.send( "\"ok\"" )
elif request.startswith( "PUT /max_temperature " ):
print( "put max_temperature" )
new_max_temperature = int(request.split( "\r\n\r\n" )[1])
print( new_max_temperature )
temperature_max = new_max_temperature
cl.send( "HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n" )
cl.send( "\"ok\"" )
elif request.startswith( "PUT /start " ):
print( "put start" )
cl.send( "HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n" )
if keep_going:
print( " already started" )
cl.send( "\"already started\"" )
else:
keep_going = True
_thread.start_new_thread(servo_thread, ())
cl.send( "\"started\"" )
for i in range(5000, 6000):
time.sleep( 0.001 )
servo_at = i
servo.duty_u16( i )
elif request.startswith( "PUT /stop " ):
print( "put stop" )
#cl.send( "HTTP/1.0 501 OK\r\nContent-type: application/json\r\n\r\n" )
#cl.send( "\"not implemented\"" )
# crash on stop, thread support is that bad
cl.send( "HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n" )
if keep_going:
keep_going = False
cl.send( "\"stopped\"" )
else:
print( " already stopped" )
cl.send( "\"already stopped\"" )
cl.close()
except OSError as e:
cl.close()
print( "> connection closed" )
keep_going = False
time.sleep( 2 )
except KeyboardInterrupt:
print( "> ctrl+c, wrapping up..." )
keep_going = False
time.sleep( 10 )
except Exception as e:
print( e )
print( "> unexpected exception, wrapping up..." )
keep_going = False
time.sleep( 10 )
import time
class MAX6675:
MEASUREMENT_PERIOD_MS = 220
def __init__(self, sck, cs, so):
"""
Creates new object for controlling MAX6675
:param sck: SCK (clock) pin, must be configured as Pin.OUT
:param cs: CS (select) pin, must be configured as Pin.OUT
:param so: SO (data) pin, must be configured as Pin.IN
"""
# Thermocouple
self._sck = sck
self._sck.low()
self._cs = cs
self._cs.high()
self._so = so
self._so.low()
self._last_measurement_start = 0
self._last_read_temp = 0
self._error = 0
def _cycle_sck(self):
self._sck.high()
time.sleep_us(1)
self._sck.low()
time.sleep_us(1)
def refresh(self):
"""
Start a new measurement.
"""
self._cs.low()
time.sleep_us(10)
self._cs.high()
self._last_measurement_start = time.ticks_ms()
def ready(self):
"""
Signals if measurement is finished.
:return: True if measurement is ready for reading.
"""
return time.ticks_ms() - self._last_measurement_start > MAX6675.MEASUREMENT_PERIOD_MS
def error(self):
"""
Returns error bit of last reading. If this bit is set (=1), there's problem with the
thermocouple - it can be damaged or loosely connected
:return: Error bit value
"""
return self._error
def read(self):
"""
Reads last measurement and starts a new one. If new measurement is not ready yet, returns last value.
Note: The last measurement can be quite old (e.g. since last call to `read`).
To refresh measurement, call `refresh` and wait for `ready` to become True before reading.
:return: Measured temperature
"""
# Check if new reading is available
if self.ready():
# Bring CS pin low to start protocol for reading result of
# the conversion process. Forcing the pin down outputs
# first (dummy) sign bit 15.
self._cs.low()
time.sleep_us(10)
# Read temperature bits 14-3 from MAX6675.
value = 0
for i in range(12):
# SCK should resemble clock signal and new SO value
# is presented at falling edge
self._cycle_sck()
value += self._so.value() << (11 - i)
# Read the TC Input pin to check if the input is open
self._cycle_sck()
self._error = self._so.value()
# Read the last two bits to complete protocol
for i in range(2):
self._cycle_sck()
# Finish protocol and start new measurement
self._cs.high()
self._last_measurement_start = time.ticks_ms()
self._last_read_temp = value * 0.25
return self._last_read_temp
The operations is smoother, the kids are older. Sugaring season is liminal time between Winter and Spring sitting by the evaporator for hours on end. Often prompting discussions we wouldn’t otherwise have. I love this time of the year very much.

Now this Winter has been more more “normal” for Vermont, and sugaring season started the week after Town Meeting day which is tradition I hear, but not anything we’re used to. Sap hit hard these past few days and we had to start boiling it off fast to make room for more.
Did you know that turkeys literally start showing up around Thanksgiving? Didn’t see them all Summer, and now they’re here everyday, looking tasty. Something makes them bolder and get quite close. The fact that culture and tradition were shaped by environmental cycles was completely lost on me as a city dweller. Maybe it didn’t help that I lived in places where that culture had been imported not in accordance with the environment, I’ve never seen wild turkeys in Utah.

With plants more established, and no clear effect, I decided to relax counter measures a bit this year and see if maybe the orchard, fruit trees, and garden could do with less fencing. The sentence was immediate, we didn’t get as many berries, trees got damaged, and hard work was voided. It is now clear that while various measures aren’t 100% effective, they definitely help. With this confirmed, they’ll get implemented with more vehemence next year.
It’s a lot less work and it looks better without fences. What a shame.
I started early in the season with the usual piers, beams, leveling, subfloor combination. Now I get to frame which is immensely more satisfying. Tearing the shingles was heartbreaking, I will never do shingles again. This poor early choice is likely why our house isn’t fully sided yet. I estimate it’s about 20 times the amount of work as doing boards. And I really don’t have time to side 20 houses. They’re also impossible to remove or replace. Bleh all around.
I’ve involved Robin more in the construction, he’s eager to learn for his own house. Music to my ears. We built a small covered thingy to hold all the things that might blow up. I didn’t like having them closer to buildings. This project had him use all the tools and techniques on a small scale, but he’s helped me on the subfloors since.
The house is reaching its final footprint. I still haven’t modeled the deck which didn’t really need much forethought, but I’d like to have a complete model of the house. It’s very useful on top of being fun.
This year we’re looking at a kitchen alcove/bay window.
And a “basement” with a small quirky room on top. We could really use a space to store a bunch of stuff in the absence of an actual basement.
It’s not much but for us it’s huge, we finally have enough water and ways to move it that we can give the gardens a good soak. For the past 9 years we’ve been reliant on the weather, which usually does its job in Vermont. Although when it didn’t, we’d be reactive and move a lot of water by hand only to keep plants alive. Now we’re able to soak several hundred square feet of soil whenever they could use it, and it’s less manual labor. Double win!
We want to build a rain capture system to diversify strategies, but that’s dependent on other projects. We’ll get there.
It’s scary how many things I started counting in decades. Also scary, how much “damage” 2 well motivated humans and their machines can do over a decade. This isn’t the best picture to show everything we’ve created but it’s a nice before and after. For the fun story, it’s not actually taken from our land but I didn’t know where the boundaries were exactly at the time. And we acquired the parcel since :).

We’ve definitely entered a new phase in our adventure, it started around year 7 when things started taking shape and being nice. We’ve been more retrospective as we entered this new phase and started reaching anniversaries like this. It’s hard to discern fully the impact the decision had on our life trajectory, evidently though it’s been very far reaching. For those wondering, there hasn’t been a single moment of regret, far from it. We don’t need to practice gratitude, it’s here anytime we look out the window. Living in a nice environment is one thing, enjoying the fruits of years of shaping it gets to a level of contentment that I believe is buried deep in all of us.
The goal for us in trying the “back to the land” experience, was to avoid later regrets. It would likely fail, we would compromise heavily on the ideal scenario, we had even started doing so before finally finding our land, but we would get it out of our systems and be able look at each other in midlife knowing we tried. Turns out, it’s been a home run. There’s a lot more to say about why it worked and how it’s shaped us, but I’m afraid it might be too philosophical and maybe even boasting at times, see I’m quite proud of what we’ve done.