Ben's Blog

plots ben April 13, 2025

Winter Warmth & Shitty Servos

I launched another big plot, it was a 7 day one, I swapped the servo motor for the occasion to reset the pen up/down cycles it would have, but that was a bad idea. I think I’ve learned it’s better to stick with a lucky servo that keeps performing. And so on day 5, the new servo failed. So I stopped things early, the plot still looks pretty good, you might recognize it from this post.

Bellow is the unfortunate artifact of a pen that doesn’t go off the paper. Another interesting thing I’ve learned from these large deployments, is that the slight misalignment I keep having from one plot to the next, but not enough to be a real bother, I think is due to the paper or the belts loosening over the days.

An so I bought a couple of very premium servos, it’s time to get hardware in line with the ambition. I’m hoping they’ll make a difference. They better.

aesthetics, all out geekery, electronics, plots, plotters ben April 10, 2025

Laser Portraits

I had a chance to run 2 Pewtybots at a public event where people could have their picture taken to be turned into line art to be rendered by lasers. I should have taken better pictures and videos, but I was too busy manning the station and talking to people. It’s unfortunate because it’s hard to convey the experience with words.

Ever since the first successful prototype, I kind of knew I wanted to do something for this event. And so I built 2 machines, refined the software, the math… well maybe that’ll be another post… I’m not sure I have it right just yet, I might. I have it right enough at least, let’s just say I refined the math. Finally, I spent time developing and practicing a pipeline where I can take someone’s picture and send it to either Pewtybot happens to be idle.

Esther helped man the station so we practiced at home with all her toys pretending to be the various personalities she’d encounter at such an event. From the overly curious bear to the llama in a hurry. And so the pipeline is as such: first we take your picture. A monitor is facing you to see what it is.

Then we turn that into lines to be drawn (or lasered in this case). This is supposed to be a first taste of eye candy as these algorithms are cool to see at work.

Then you go in a dark room, and see it all get zapped on the wall (I don’t have a picture of the lasered dog plush).

Because this was a first on many fronts, I was pretty anxious some things would go wrong (they did). I also didn’t know how to present it, or how people would react. So the first couple of “customers” helped me figure out how to guide them through the pipeline. And when the time comes to go in the dark room, I purposefully kind of dump people in there and vaguely tell them to wait for the wall to light up. I have the laser write their name and count down from 3 to 1, and then the laser moves much faster through their portrait.

Nicole quickly realized adding chairs in the dark room would invite people to take in the experience more. And I realized I was silly to tune my setting for single portraits, I almost exclusively had families and groups of friends in the same picture. The reactions were great, although I didn’t get any from inside the room, people coming out were full of questions and kids were smiling. As always with my silly projects, there’s also a smaller fraction of people with whom they resonate more deeply.

Overall it was a big success and pretty smooth for a first. I want to do more for sure. There’s something fireworksy about when the laser really starts moving and light shows up everywhere.

nature encounters ben April 10, 2025

You Have to Admire

This Beaver’s perfect execution of instinct. It saw an undammed stream, a tree standing near, and it knew just what to do. And the tree went right where the beaver wanted it.


I would have loved to see it at work.
miscellaneous ben April 05, 2025

Protected: Umbrella Flight Trials #2

This content is password-protected. To view it, please enter the password below.

maple syrup, self sustainability ben April 05, 2025

The Season is Done

Very smooth throughout. I’m not even sure how much we got, 10 gallons? More than enough for us and copious gifting. All we need to do is clean it all up now.

nature encounters ben March 30, 2025

Protected: Encrusted in Ice

This content is password-protected. To view it, please enter the password below.

plots, plotters ben March 30, 2025

Eink, Plots & Luxembourg

I’ve been revamping and refactoring the software stack for plotty… pewty… gondola plotty… etchy? bot. Everytime a new riff on drawing machines came up, I’d grab the last code I worked on and tailored it for the new endeavor, often adding generalizable improvements along the way, but never taking the time to refactor previous work.

I always try to be helpful when people reach out on this blog about something they need/want/would like, but I have limited time and I’ve learned to filter a bit and not let other people’s projects take too much of my time. Last December though, the folks from Code Club Luxembourg got in touch with a few questions, and I gave them the usual “helpful but not too much” filter. Except they went on to build 5 PlottyBots, a whole integration with Scratch, and now use it to teach coding. Music to my ears, and clearly they meant business. And so we hopped on a call to exchange ideas.

Clearly the software could use consistency and so I started thinking holistically with the laser code. Each machine has slightly different motor control, but it’s now evident which software pieces are consistent across implementations. Ideally, I’d like the same software stack no matter which machine you happen to be running on. So I refactored back to the gondola plotter, and finally the tabletop one. It’s still not finished but it’s definitely better and more consistent.

To test new software on the original PlottyBot, I ran it on my reMarkable tablet.

I’m not going to do a whole post dedicated to its merits, I’m allergic to promotional content, but I will say a quick few things on this unrelated post. I’ve been interested in Eink for the longest time but never found a compelling device I felt was more than a temporary gimmick. Even getting a pricy reMarkable was a bit of an experiment that could land in a closet gathering dust. But I did want to experiment with quieter and purpose targeted devices so I went on with it after more than a decade of seeing various Eink devices go by. I haven’t adopted it for everything I want to yet, it takes time to change long established work habits, but I will say that it absolutely NAILS writing. It nails it so hard it changed the way I problem solve to an earlier saner process. It might be a generational thing, but writing is key to information absorption and processing for me. The problem with paper is that I have pieces flying everywhere, and mistakes make for an unclean train of thought committed to paper which is frustrating to engage with further. With Eink writing, you get an infinite canvas, and the ability to massage thoughts into a perfect form, one ripe for implementation. I have found that I will pick this device with a sense of relief as it means I’m about to engage in uninterrupted deep thinking. I find many signs that it was carefully designed to be such a device and not your next fancy tech gizmo, and I absolutely love it for this. Hopping on a computer on the other hand does not yield a sense of relief, but rather stressed anticipation at the onslaught of mechanisms devised to get in the way of what I was trying to do. Of course, I can’t do as much on the reMarkable, but I’m curious more than ever to shift to it whatever I can.

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.

Antoine de Saint-Exupéry
miscellaneous ben March 29, 2025

Protected: Or not

This content is password-protected. To view it, please enter the password below.

miscellaneous ben March 22, 2025

Protected: Spring is here

This content is password-protected. To view it, please enter the password below.

3D modeling / printing, electronics, I.T., plotters ben March 22, 2025

PewtyBot 1.0 x2

I built another one, it’s always been useful to have 2 of my drawing machines.

electronics, homestead automation, I.T., maple syrup, self sustainability ben March 15, 2025

Evaporator Regulator

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

Web requests collection.

maple syrup, self sustainability ben March 15, 2025

We’re Boiling

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.

building, self sustainability ben March 11, 2025

Protected: Thermosiphons Everywhere

This content is password-protected. To view it, please enter the password below.

building, self sustainability ben March 11, 2025

Another Room Done

Tucked in a weird corner of a stair case.

It’s tiny with a tree house feel to it, we can barely stand in it.

We haven’t had an upstairs window facing this direction since the original tiny house we moved into, and now there is a an orchard to be admired.

It’s quirky as heck and we love it.

3D modeling / printing, electronics, I.T., plotters ben March 11, 2025

PewtyBot 1.0

I’ve used this project as a stepping stone and modified it some. I can’t say enough good things about it, it propelled development forward significantly and I found it to be expertly designed. Too bad the project it now defunct. I had to find assembly documentation on archive.org.

I’m not sure I’ll document it as well as I have the tabletop plotter or the gondola one. At least not yet, maybe that’ll be a Christmas project like the other 2. I did port the same software stack, it would be a shame not to given the years of feature development that went into it. Of course we should use these features with lasers. It might also change drastically, I want to investigate what rotating mirrors could do for speed. Currently some of the moving parts of the machine have enough mass that their inertia causes inaccuracies when moving at high speed.

It’s nice to have things tidied up, the development machine was a mess.

maple syrup, self sustainability ben March 04, 2025

Protected: It’s Cold

This content is password-protected. To view it, please enter the password below.

aesthetics, electronics, I.T., plotters ben February 03, 2025

Laser Pew Pew

I have yet to crack the math, I’ve been banging my head against the wall with various implementations but this is a lot worse than the Gondola’s double polar system. I’d like the laser to be able to project a cartesian system in any position relative to the surface it’s drawing on. Alas, I’ve only been able to get decent results in ideal positions.

self sustainability ben January 29, 2025

Burning Brush

electronics, I.T., plotters ben January 26, 2025

For Posterity

The first successful prototype of PewtyBot.

Philip from Germany got in touch to tell me about a cool project he had seen that involved photoluminescent paper. He thought maybe PlottyBot could so something with it, and maybe it could, but not fast enough I thought. I knew exactly what I wanted to do with it though, with the PlottyBot software stack, but a different machine. I love that people get in touch to show me cool things. I’ve been working on plotters for years now, and in some sense it felt like I had turned every stone. Out of nowhere Philip got in touch and steered me toward a whole new area of exploration. Of course one can buy glow-in-the-dark paper, of course I can shoot lasers at it, of course all the algorithms I’ve been working on these past years lend themselves to this new endeavor. Well, with some tweaking :).

It was a real struggle to get Trinamic drivers working on a Pi, but I wanted to step up my motor stepping game. Once I figured it out, holy shit do they work! There’s much else to talk about here, but this isn’t the point of this post, I just want to capture the miletone that is shooting lasers super fast at photoluminescent paper. I still haven’t wrapped my mind around what this all means.

plots ben January 22, 2025

Circles

This one is very abstract except when viewed from a good distance it comes together.

plots ben January 18, 2025

Runestone of Memory

I often contact artists to see if they’d like to do something public on the big plotter, and I’m often met with silence :). Sometimes though, I meet the rare characters who are completely fine doing something cool just because it’s cool, new, and takes their art in another dimension, oh and by the way there’s $0 to be made for either of us. I appreciate disinterested people, and Irish artist Patrick Boullier is such a person. He kindly provided a high resolution scan of his amazing piece: Runestone of Memory so I could try to turn that into penstrokes for the giant plotter. After several trials and help from Vectorizer.ai (top of the line for SVG tracing), I got somewhere we both felt was good, and so it ran on the big plotter for a week.

As is routine, the public reveal was preceded by several smaller scale trials.

This is an awesome piece on Norse mythology riddled with symbolism. I highly recommend reading the explanation of some of it (scroll down some).

old vinyls ben January 03, 2025

Umbrella Dance

Ok the quality is terrible, but it’s from 1901 and has the oddest dimensions.

building, self sustainability ben January 01, 2025

Quaint Booth

We finally have the quaintest place to eat and hang out in the kitchen. It has the intimacy of a restaurant booth, and we’re surrounded by windows showing nature.

It’s not completely finished, but it’s usable. We still need to come up with a table which embraces the peculiar shapes of this space.

Posts pagination

← Previous 1 … 4 5 6 … 52 Next →

This blog is solar powered

Interactive

Handwriting Capture
Mandalagaba
IPv6 link-local to MAC converter
IPv6 MAC to link-local converter
Markov Text Generation
Markov Word Generation
Markov Music Generation
Duplogrifier
Flood Fill Algorithms
Homestead Metrics
RGB Playground
Web Games

Categories

  • aesthetics111
    • plots54
    • specular holography6
  • Books3
  • I.T.202
    • 3D modeling / printing21
    • AI6
    • all out geekery36
    • electronics27
    • homestead automation6
    • maniacal paranoia25
    • plotters49
    • unix / linux29
    • video games4
    • web development29
    • web games3
  • Lego / Duplo67
  • life in the U.S.42
  • miscellaneous202
  • nature encounters114
  • old vinyls3
  • organs2
  • self sustainability560
    • agriculture105
    • apiculture38
    • apple20
    • building131
    • canning3
    • crochet6
    • foraging6
    • hunting10
    • maple syrup47
    • poultry39
    • preserving2
    • solar power28
    • water23
    • wood84
  • trip to a new life6
Theme by Bloompixel. Proudly Powered by WordPress