kk
Gabriel Morell





Big Deploy Button
posted Oct. 4, 2019, 10:53 p.m.
perma · all entries

A while back I purchased a dozen 100mm arcade buttons in the hope that I could make physical artifacts with them. At my previous employer I thought it would be fun to use one of them for something vaguely useful, thus the Big Deploy Button (BDB) was born.

The Design

The button has a massive stem with the MicroSwitch actuator at the end of it which means that it can't quite be a small build. The measured length of the stem is nearly 2 inches. This means I'd have to use between eight and sixteen layers of plywood and acrylic in order to enclose the stem, lighting and microcontroller. This also allowed for room (barely) to put in a key interlock device so not just anyone could deploy to prod.

The Build

Alternating layers of 2447 Acrylic and plywood were used to build the body. A few attempts were involved with the key interlock since I somewhat neglected the reality of geometry and cut out space for the faceplate but not the actual stem of the key interlock device. The layers were impregnated with holes in the structure and these holes were then filled with hot glue as the build progressed. The bottom layer above the base plate included a small gap where the USB cable could be passed through for power and initial flashing. Two sets of leds were installed into the structure, one facing down into the base plate which was acrylic and another facing up into the body of the device. The key interlock was friction fit into its faceplate and the whole assembly was lowered into the slot and glued into place. The microcontroller, of the venerable esp8266 family was carefully wired into the LEDs and key interlock and stuffed unceremoniously into place. This was done in two halves both containing approximately half the volume of the device and the hot glue that escaped during the initial build up provided just enough tack for the two halves to stick together firmly.

The Code

Micropython was used for its ability to quickly prototype simple logical components and was approximately below

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import machine, network
from machine import Timer, Pin
from neopixel import NeoPixel

# global DEPLOYING
DEPLOYING= False


UP_LEDS = 16
DN_LEDS = 8

# ws2812 pins upfacing/downfacing
p_WS_UP = Pin(13, Pin.OUT)
p_WS_DN = Pin(12, Pin.OUT)
np_UP = NeoPixel(p_WS_UP, UP_LEDS, bpp=4)
np_DN = NeoPixel(p_WS_DN, DN_LEDS, bpp=3)

# network
sta_if = network.WLAN(network.STA_IF)
ap_if = network.WLAN(network.AP_IF)
if not sta_if.isconnected():
    sta_if.active(True)
    sta_if.connect('blah', 'blah')
    ap_if.active(False)

    while not sta_if.isconnected():
        for i in range(UP_LEDS):
            np_UP[i] = [0x0,0x69,0x0,0x32]
        for i in range(DN_LEDS):
            np_DN[i] = [0x0,0x69,0x0]
        np_UP.write()
        np_DN.write()


# key nd button switches, wired between the pin and ground so we pull em right up
p_SW_KEY = Pin(14, Pin.IN, Pin.PULL_UP)
# also lmao 15 is reserved, 16 is unpullable
p_SW_BUT = Pin(5, Pin.IN, Pin.PULL_UP)

tim_pd = Timer(-1)
tim2_pd = Timer(-1)

def set_deploying_false(*timer):
    print("Cbfalse")
    globals()['DEPLOYING'] = False

def set_deploying_true(timer):
    print("cbtrue")
    globals()['DEPLOYING'] = True

def printstates(args):
    # print("BTN", p_SW_BUT.value())
    # print("KEY", p_SW_KEY.value())
    print("DEPLOYING", DEPLOYING)

def write_ws2812s(timer):
    DEPLOYING = globals()['DEPLOYING']
    print(DEPLOYING)
    print("ELIF", p_SW_KEY.value() == 0 and p_SW_BUT.value() == 0 or DEPLOYING)
    if p_SW_KEY.value() == 0 and p_SW_BUT.value() == 1 and not DEPLOYING: #key turned and button pulled high
        for i in range(UP_LEDS):
            np_UP[i] = [0x0,0x66,0xCA,0x0]
        for i in range(DN_LEDS):
            np_DN[i] = [0x0,0x66,0xCA]
    elif p_SW_KEY.value() == 0 and p_SW_BUT.value() == 0 or DEPLOYING: # key turned and button pulled low / "deployin"
        for i in range(UP_LEDS):
            np_UP[i] = [0x89, 0x89, 0x89, 0x0]
        for i in range(DN_LEDS):
            np_DN[i] = [0x89, 0x89, 0x89]
        if not DEPLOYING:
            print("")
            tim_pd.init(period=0, mode=Timer.ONE_SHOT, callback=set_deploying_true)
            # replace this with HTTP code that eventually hits set_deploying_false
            urequests.get("http://gmp.io")
            tim2_pd.init(period=10000, mode=Timer.ONE_SHOT, callback=set_deploying_false)

        else:
            pass
    else: # key unturned
        for i in range(UP_LEDS):
            np_UP[i] = [0x89, 0x0, 0x0, 0x0]
        for i in range(DN_LEDS):
            np_DN[i] = [0x89, 0x0, 0x0]
    np_UP.write()
    np_DN.write()

tim = Timer(-1)
tim.init(period=100, mode=Timer.PERIODIC, callback=write_ws2812s, )

Most of the code

The indicator lights were used to Signal to the user the various states the device was in.

  • A mint green emenated while the device was looking for and connecting to the wifi network
  • The default state once the device was connected to the wifi was a solid red
  • Once the key was turned it indicated in a deep blue
  • Once the button was turned the indicator glows white temporarily until the network request completed then turned back to the purple
Boxes.py MicroRack
posted Oct. 4, 2019, 10:44 p.m.
perma · all entries

While the official cases for single board computers are nice enough, I wanted something where I could apply airflow to a whole cluster worth of SBCs without thinking too hard.

I've used the boxes.py library before and decided to leverage it for this project. It provides primitives to create boxes and mating edges easily. I started with the paintcan generator and adjusted it to add holes for the SBC USB ports and the standard SBC mounting hole layout. The end result is below:

MicroRack A

MicroRack A

MicroRack B

MicroRack B

Most of the configurable parameters default to Raspi Style moutning and dimensions but there is no reason a pi-zero couldn't be accounted for by passing along the correct dimensions for the mounting holes.

Extensible Signage Architecture
posted Jan. 19, 2018, 9:44 p.m.
perma · all entries

Motivation

After laser cutting and building my information display #link I wanted to have a backing software architecture that met two requirements.

  • First: It should be capable enough to minimize the amount of processing on the receiving software implementation.
  • Secondly: Adding additional datasources should be as easy as possible.

Architecture

Signage Architecture

Signage Architecture

The train sign components are written as a series Crossbar components, each with a specific task. There are the data source components such as providers for time, weather and train data for the area. These publish their cleaned textual data to a data topic, which the signage service spools and publishes periodically to the signs over MQTT.

Configuration

Configuration is handled entirely in a Thingistry namespace that the service reads from. Below is one such example that implements a simple and complex example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
{
  "sources": {
    "us.thingcosm.data.services.rfid2sign": {
      "fields": [
        "__all__"
      ],
      "ttl": 90
    },
    "us.thingcosm.data.services.weather": {
      "fields": [
        "temp_f",
        "temp_c",
        "hum"
      ],
      "config": {
        "incr": {
          "wind_m": [
            2,
            2,
            0,
            1,
            1,
            1
          ],
          "hum": [
            1,
            1,
            0,
            2,
            3,
            3
          ],
          "temp_f": [
            1,
            1,
            1,
            2,
            3,
            3
          ],
          "temp_c": [
            1,
            1,
            1,
            2,
            3,
            3
          ]
        },
        "colormap": {
          "1": [
            40,
            20,
            40
          ],
          "0": [
            0,
            0,
            0
          ],
          "3": [
            20,
            50,
            20
          ],
          "2": [
            40,
            20,
            0
          ],
          "4": [
            20,
            20,
            50
          ]
        }
      },
      "ttl": 600
    }
  }
}

The first example accepts from all available keys within the namespace with a TTL of 90 seconds.

1
2
3
4
5
6
"us.thingcosm.data.services.rfid2sign": {
      "fields": [
        "__all__"
      ],
      "ttl": 90
    },

The second example is slightly more involved. It selects data to read from a specific set of keys.

1
2
3
4
5
6
"us.thingcosm.data.services.weather": {
      "fields": [
        "DATE",
        "TIME24"
      ],
}

The next section is a configuration entry and is involved with incrementing the indices of certain colors under certain indices. In the example below the 0th index is incremented by two, the 1st index by 1 as well, and will end up the same color. The second section is what the color values at the elevated indices end up being.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"us.thingcosm.data.services.weather": {
     "config": {
        "incr": {
          "DATE: [
            2,
            1,
            0
          ],
       },
       "colormap": {
          "0": [
            0,
            0,
            0
          ],
          "1": [
            40,
            20,
            40
          ],
         "2": [
            30,
            20,
            10
          ],

   }
}

Data Sources

Data providers are designed to be standalone publishers that either inject new information into the system or mutate existing information and publish it on a different topic.

In the example given below, the nightshift time provider publishes a dictionary containing a top level text_micro key and then the various topic subkeys and their values.

1
2
3
yield self.publish("us.thingcosm.data.services.nightshift", **{
            "text_micro": {"DATE":str_disp_date, "TIME12": str_disp_time_12, "TIME24": str_disp_time_24}
        })

Spooler

Showing the data as it comes in from the sources would be a bad viewing experience so a buffer must be kept.. As the subscriber receives data from its configured topics they are kept in a dictionary in memory. Each source is configured with a time-to-live and old values are purged periodically. There is an array that gets a value popped off with each tick and the values published to the configured topics. When the array is empty, the array is repopulated from the living dictionary with the new set of values and it the loop continues publishing data.

With our commitment to keeping the receiving implementation simple the publishing step involves transforming the output text into sprites to display. This is what the sprite service is in charge of dealing with. An extracted code block follows

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@inlinecallbacks
def publish(text):
    sprite_res = yield self.call("us.thingcosm.data.services.sprites.letter", string=st_text, map_incr=st_incr)
    sprite = sprite_res.get('sprites')

    # 1) A bit of preprocessing
    rows = {i:[] for i in range(0,7)}
    for letter in sprite:
        row_pos = 0
        for row in letter:
            # let's map this to the values we're going to be putting down while we're in here
            # fail to zero by default
            new_row = [st_color.get(str(j), st_color.get(0)) for j in row]
            rows[row_pos].extend(new_row)
            row_pos += 1

    # 2) append the sides and bottom framing (test patterns for now)
    left_color = st_color.get('sleft', [0,0,0])
    right_color = st_color.get('sright', [0,0,0])
    bottom_color = st_color.get('sbottom', [4,0,0])
    for index,row in rows.items():
        # print(row)
        row.insert(0, left_color)
        row.append(right_color)

    # 8x32 display and the sprites are 7x5
    rows[7] = [bottom_color]*32

    # 3) Build the snake
    snake = []
    dau = down_and_up()
    for i in range(0,32):
        for j in range(0,8):
            row = next(dau)
            snake.append(rows[row][i])


    # 4) ready for the world
    yield self.publish("us.thingcosm.data.signage.pub.%s" % self.topic_name, **{
        "snake":snake
    })

The sprite service is called and the text is converted to a sprite array.

  • The array gets modified in the first step to fit 8x32
  • In the second step the values are set in the non sprite ranges
  • In the third step we use the down_and_up generator to rearrange the two dimensional array into a single array that matches the ordering of the LEDs on the display board.
  • The fourth step is publishing the output to the correct topic.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def down_and_up():
    first = False
    status = 0
    going_up = True
    while True:
        yield status
        if status == 7:
            going_up = False
            # this will make sense one day
            if first:
                first=False
            else:
                status-=1
                first=True
        elif status == 0:
            going_up = True
            # just think 01234567 and 76543210 emitted forever from the machine
            if first:
                first=False
            else:
                status+=1
                first=True
        elif going_up:
            status +=1
        elif going_up == False:
            status -=1

The generator that returns the snake returns the correct index to be converted into the snake. With each iteration it snakes back and forth across the array retrieving values and putting them in the correct order.

Sprites

In order to keep the processing on the target hardware low we preprocess the data coming before sent. This means converting the text into RGB values on the signage service before sending it down the pipeline. The sprite services exposes an interface that converts strings to arrays, supporting mutiple fonts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
font = {"x": [
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [1, 0, 0, 0, 1],
        [0, 1, 0, 1, 0],
        [0, 0, 1, 0, 1],
        [0, 1, 0, 1, 0],
        [1, 0, 0, 0, 1],
    ],
    "y": [
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [1, 0, 0, 0, 1],
        [1, 0, 0, 0, 1],
        [0, 1, 1, 1, 1],
        [0, 0, 0, 0, 1],
        [0, 1, 1, 1, 0],
    ],
    "z": [
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1],
        [0, 0, 0, 1, 0],
        [0, 0, 1, 0, 0],
        [0, 1, 0, 0, 0],
        [1, 1, 1, 1, 1],
    ]
}

The code block above shows an available font and sprites for the letters X, Y, and Z

Receiver

The receiver code was written in micropython for the sake of development speed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# setup
import network
sta_if = network.WLAN(network.STA_IF)
ap_if = network.WLAN(network.AP_IF)
if not sta_if.isconnected():
    sta_if.active(True)
    sta_if.connect('lol', 'no')
    ap_if.active(False)

    while not sta_if.isconnected():
        pass


import neopixel, machine

np = neopixel.NeoPixel(machine.Pin(13), 8*32)
import time, ujson
from umqtt.simple import MQTTClient

# callbacks
def sub_cb(topic, msg):
    #print((topic, msg))
    decoded = msg.decode('utf-8')
    loaded = ujson.loads(decoded)
    kw = loaded.get('kwargs')
    snake = kw.get('snake')
    for i in range(0, 8*32):
        np[i] = snake[i]
    np.write()

# main function
def main(server="192.168.13.71"):
    c = MQTTClient("umqtt_client",server)
    c.set_callback(sub_cb)
    c.connect()
    c.subscribe(b"us.thingcosm.data.signage.pub.door.panel")

    while True:
        if True:
            c.wait_msg()
        else:
            c.check_msg()
            time.sleep(1)

    c.disconnect()

# wrap it all in a try:except    
if __name__ == "__main__":
    try:
        main()
    except Exception:
        import machine
        machine.reset()

The board connects to the wifi, connects to the MQTT transport running on the crossbar.io router and then subscribes to the MQTT topic and has it setup to trigger a callback. The callback simply takes the array values that were published and shoves them down the wire to the display components.

Train Sign
posted Nov. 27, 2017, 6 p.m.
perma · all entries

Train Sign

I had a bit of a problem with ordering random LEDs on Ali so when I saw 8x32 WS2812 LED arrays for a good price I jumped on them. A while after the displays arrived I was looking at the train signs on the MBTA platform and was inspired. I did some measurements and designed an enclosure in Inkscape. I wanted it to be visible from across the room. It features an acrylic diffusal layer infront of a grid of holes on the layer below. This allows the light to be contained to the segment below and then has it diffuse on the layer above. After some initial trouble with scaling to fit the array, I finished cutting all the parts at the local space.

Cut and Assembled

Cut and Assembled

Cut and Assembled 2

Cut and Assembled 2

Assembly

The box was assembled and the electronics put inside. A hole was cut on the other side to run a USB cable in to power the ESP8266 and Array.

Electronics Assembly

Electronics Assembly

A quick of the back napkin calculation notes that the display at full white will draw over fifteen amps, so I had to be careful to limit the style of the display.

Grid Testing

Grid Testing

Software

The first test involved figuring out the electrical order of the array. This was done by iterating over values and noting how the array was filled.

Abstract Train

Abstract Train

From there the implementation was incredibly abstract and was a series of lines representing trains and how far away from the stop they were. This was done by publishing to an MQTT topic and doing some transformation on the time and spreading it across the display. In the image above, there are four trains within twentry minutes of the station, the green ones ten+ minutes away, the yellow one less than ten and more than 3, and the red one imminent.

TrainSign Result Time

TrainSign Result Time

TrainSign Result Train

TrainSign Result Train

TrainSign Result Weather

TrainSign Result Weather

This implementation however, was not super friendly to people reading it so I created the Extensible Signage Architecture and gave it the data sources for time, weather and train distance.

In the next post I detail the architectural considerations in building the Extensible Signage Architecture.

A namespaced registry
posted June 23, 2017, 2:17 a.m.
perma · all entries

TBD

Send the envoy
posted June 23, 2017, 2:17 a.m.
perma · all entries

While implementing a stream parser for JSON responses for each and every wireless microcontroller project I build seemed like a fantastic use of time, I figured that I could minify responses coming from large APIs and only pull out the pertinent information that the microcontroller would need to use. Thus was born the Envoy. The envoy sits between a microcontroller and a datasource and through a series of user configurable rules will pull out the relevant data.

The initial purpose for this was polling the old hubway API that would return all the stations in the system and their statuses which was in the tens of kilobytes and would exhaust my heap. The initial version worked well and there was value in making it generic.

On top of being a minifier, the original unfiltered payload is cached in memory. This is so different devices are able to poll the same Envoy instance at the same time and have their own unique filtering rules against the original payload while lowering the call count to the original service. Users are able to configure a TTL on their filter rules to uniquely control when the microcontrollers get fresh data.

ThingHole Intro
posted June 23, 2017, 2:17 a.m.
perma · all entries

ThingHole is the center of the ThingCosm and the first tool that was written. It was written with multiple instances of any service running in parallel from the start., thinghole has its base services written in twisted with a Django front-end for management, configuration, and data visualization.

The twisted services are:

  • Horizon which ingests data,
  • Stream which deals with passing the data along to the front-end
  • Manifold which specifies triggers to pass the data onto other services.

[arch]

The horizon

The horizon is a service that collects arbitrary data and inserts it into the database as well as passing it along to the other services such that they don't need to access the database. For speed reasons, the horizon service connects to postgres and loads all the auth tokens into memory and continues to update the in-memory cache every thirty seconds. On data received the horizon inserts the data into postgres with [code] as well as passes the data along to the various stream and manifold workers.

The stream

The stream service receives data from the horizon services and forwards them to connected web socket clients for charting and tabulation. It makes sure that it passes on the values to the correct connected client. The data is also converted into the various formats for each charting and tabulation feature.

The manifold

The manifold service passes on data to other services based on user described arbitrary rules, either to a crossbar topic, another thinghole reciever, or arbitrary URLs with programatic injection of additional data and headers

A cosmic Introduction
posted June 23, 2017, 2:16 a.m.
perma · all entries

The ThingCosm is series of loosely connected tools for sending and receiving data to and from services. While similar solutions exist, I wanted a series of tools that were open source, standalone, and not beholden to any particular vendor. In the past I had been burned by proprietary tools disappearing or being bought out. My goal was to have the only person who could cause me trouble be either my hosting providers or myself.

From this motivation, I initially built three tools, The Hole an arbitrary data storage and graphing tool. The Envoy, an api minimizer for microcontrollers, and the Registry a place to store key value pairs within a namespace

Making it Ubiquitous
posted May 20, 2016, 12:41 a.m.
perma · all entries

Ubiquiti has a line of network enabled power outlets, With nice looking hardware (that won't burn down my house) that comes with a centralized controller and phone applications. This however, didn't quite fit into my vision of a single set of interconnected services that managed all other devices on the network within a single user interface. So I read a few blog posts about accessing the firmware and implemented a small twisted service that exposed a similar API to the Lambent Aether components I had written previously.

This improved on the stock interfaces in a few ways.

  • Outlet names are stored on the service and not the individual client applications.
  • Can manage multiple different types of outlets via a normalized interface.

There was a major fun caveat along the way however!!

The device expires cookies after a few weeks so we have to catch that the cookie is expired and then renew it as part of the control message.

1
2
3
4
5
try:
    requests.put("http://%s/sensors/%i" % (self.host, port), cookies=self.cookie, data={"output": target})
except requests.exceptions.TooManyRedirects:
    self.login() # our mfi cookie was expiring, re-login and then retry
    requests.put("http://%s/sensors/%i" % (self.host, port), cookies=self.cookie, data={"output": target})

I'll one day change that code to use treq and not block, but its fine for now.

Lowering the price w/ ESP8266
posted Sept. 27, 2015, 8 a.m.
perma · all entries

After the fourth install I realized I would run out of budget space fairly quickly if I kept on using a raspberry PI and its assorted accessories for each install. I found a bin that could be flashed to an esp8266 and recieve udp packets for color data on a ws2812 strand; Perfect.

It came with a small C binary with example code to write simple patterns over UDP which while I could call wholesale from twisted, would get hairy fairly quickly. After a quick read I put together an output plugin converted my internal array format into buffers to be sent to the esp8266.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class ESPDevice(BaseDevice):
    def __init__(self, addrs=[], port=7777):
        self.addr = addrs
        self.port = port
        self.socket = socket.socket(socket.AF_INET,  socket.SOCK_DGRAM) # UDP
    def write(self, values):
        chunked = chunks(values, 3)
        # convert list from RGB to GRB internally
        # yay code reuse
        filt = RGBtoGRBLambentOutputFilter()
        filtered = [filt.do_filter(i) for i in chunked]
        values = list(itertools.chain.from_iterable(filtered))
        structd = struct.pack('B'*len(values), *values)
        for a in self.addr:
            self.socket.sendto(structd, (a, self.port))

This output module is instantiated with a list of IP addresses that are injected into the service from the environment, and supports writing UDP packets to multiple devices. This is useful for tileable patterns that may need to be repeated or more realistically the area behind my speaker grilles.

At this time my fixed cost-per install has gone down from $50+LEDs+Power to $3+LEDs+Power. Which means I can expand rapidly.

Running Automated Deploys
posted Aug. 8, 2015, 6 p.m.
perma · all entries

While logging into each host controller box was fun. I soon realized that it would become unwieldy so I put together a few ansible scripts. These scripts download the latest software, configure the service and write configurations for the lights.

Discovering New Instances
posted July 28, 2015, 4 p.m.
perma · all entries

In order to simply deploy of new Lambent devices, I decided to bake in a zeroconf (avahi) announce so that future clients could find all available instances. This worked great for the prototype kivy application being built in parallel and multiple instances could be controlled from a single interface.

Notes on my implementation

Because I was uncertain of how the interfaces on the host machine were configured. I bound the zeroconf announce to every available interface except virtual and the loopback. This ensured I would have working announced on every interface, be it wifi or ethernet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def announce(self, discovery_name, port=8680):
    self.zeroconf = Zeroconf()

    self.zconfigs = []
    for i in netifaces.interfaces():
        if i.startswith("lo"):
            # remove loopback from announce
            continue
        if i.startswith("veth"):
            # remove docker interface from announce
            continue

        addrs = netifaces.ifaddresses(i)
        if addrs.keys() == [17]:
            continue

        for a in addrs[netifaces.AF_INET]:

            info_desc = {'path': '/progs_grp/', 'name': discovery_name, "port":port}
            config = ServiceInfo("_aether._tcp.local.",
                           "%s_%s_%s_lambent._aether._tcp.local." % (socket.gethostname(),i, port),
                           socket.inet_aton(a['addr']), port, 0, 0,
                           info_desc)
            try:
                self.zeroconf.register_service(config)
                self.zconfigs.append(config)
            except:
                print("Service %s failed to register" % config)
Filtering the output
posted June 20, 2015, 1 p.m.
perma · all entries

So now that I had a base twisted project, a working loop, and some rudimentary lighting programs, I decided to look into output filtering,

In my mind output filters can do any number of mathematical operation to the calculated output. Things like inverting the colors, reordering the output channels. Output filtering is done at the end as part of the write() function before the arrays are converted into bytes and shoved into the output device.

The entire output buffer is passed into a class and the do_filter() function is called. One of the simplest filters written is the RGB to GRB which I used for a ws2811 string that had the channels inverted:

1
2
3
4
5
6
class RGBtoGRBLambentOutputFilter(object):
    _desc = "RGB -> GRB"

    def do_filter(self, rgbvals):
        val = [rgbvals[1], rgbvals[0], rgbvals[2]]
        return val

Based on this I wrote a few useful filters including, inverted colors, pastels, saturated, RGB=>GRB conversion. I also considered output filters that only modify a few pixels in the array, such as a weather overlay filter, and time until next train filter.

Building the Aether
posted April 12, 2015, 4 p.m.
perma · all entries

I spent this past saturday learning twisted and going through the tutorial.

It took me some time to understand initially how twisted worked and that after reading the section on the reactor in the documentation, especially the phrase "You don't call Twisted, Twisted calls you." it finally made sense.

I made a prototype, simplified version of the lighting classes I had written prior and had been running manually via the shell. After confirming that it worked like I iterated and ported my lighting class to support being run via twisted.

The result was a simple lighting server that could be called over telnet, and could switch between the different lighting classes via telnet commands.

The next steps?

  • Web RPC
  • Frontend for Web
ESP-USB Assembly
posted March 1, 2015, 1:55 a.m.
perma · all entries

After designing the board and planning out what features I wanted on it, I put it together and sent it off to OSHPark for fabrication

Glamour shot of all the parts together

Glamour shot of all the parts together

I started putting together these boards 3 at a time. This was my first time doing SMT

Serious kind of parts

Serious kind of parts

So many small caps

So many small caps

Regulators and caps on

Regulators and caps on

At this point I'm getting the hang of SMT soldering.

So many small resistors

So many small resistors

Amber LEDs

Amber LEDs

Almost fully populated

Almost fully populated

Many burns later...

With the ESP-01

With the ESP-01

Regulator test

Regulator test

Fully powered on

Fully powered on

I realized that testing the regulator I had put in the power LED backwards. :-\

ESP-USB pre-fabrication
posted Feb. 4, 2015, 11 p.m.
perma · all entries
Pre-Fab Preview of ESP-USB

Pre-Fab Preview of ESP-USB

Soon
posted March 3, 2014, 5 p.m.
perma · all entries
Soon

Soon

Soon

Soon

Soon

Soon

Soon

Soon

Soon

Soon

Soon

Soon

Soon

Soon

Disco Light Attack
posted Aug. 19, 2013, 10 p.m.
perma · all entries

I finally got back to working on these lights. A few weeks ago I assembled everything and built a case out of a sparkfun box.

best project box ever

best project box ever

I coded a few methods into the bot and it's far less flaky than writing to an arduino's serial port like before. Check out sieze aka disco mode:

The code is here

Of RGB LEDs and raspberries
posted April 3, 2013, 11 p.m.
perma · all entries

I saw a 5M strip of RGB LEDS (SMD5050) on amazon that were "used" for 2/5 the original asking price, so I decided it was a pretty good idea to snag it.

Upon arrival I inspected that product and noted that the built-in connector with fragile pins was busted. It didn't really matter as

  • A the LEDs worked
  • B I had no actual intention of using the built-in connector.

Treading onward, I soldered a female header onto the end of the strip and attached it to my stripboard prototype platform. (see last post)

A floppy drive connector from an ATX PSU became actually useful because it plugged into the pin headers on the stripboard happily.

I compiled the fantastic pi-blaster library and powered the contraption up. After debugging a few connections I was able to change the light colors successfully but I may be dealing with some odd capacitance or such along the way that messes with the MOSFETs and PWM unpredictably. But it's a good start.

Strip Board to the rescue
posted March 23, 2013, 11 p.m.
perma · all entries

I needed an easy way to test RGB control, so I put together a strip board to prototype using N-channel MOSFETs and a lot of male pin headers and jumper wires.

Strip Board

Strip Board

Scheduling Mania
posted March 17, 2013, 11 p.m.
perma · all entries

Now I needed a way to control the lights depending on time of day. I setup celery with redis and used django-celery purely for an easy-mode gui for adding tasks.

As you can see below, the lights turn on in the evening and then fade to minimum as the night progresses.

Mono light Command

Mono light Command

I set up a listening socket on the IRC bot and pointed the tasks to write raw commands to the socket for the bot to echo.

Below it shows what it looks like from the irc channel. It's getting cozy in there now.

screenshot from putty showing scheduler

screenshot from putty showing scheduler

Around this time I wrote another bot plugin to query what was being played on the tv. Now we're getting places.

Living Room LED Control
posted March 4, 2013, midnight
perma · all entries

The living room has LEDs around the edges of the wall pointing down. I took a FET and a power supply and hooked them to the PC that was controlling the TV and added a few basic commands to control the lights:

  • Single - a single light value
  • Blink - Blinks on and off
  • Fade - Fades between on and off
  • Spaz - Jumps between steps randomly
screenshot from putty showing led control

screenshot from putty showing led control

Initial Bot
posted March 2, 2013, midnight
perma · all entries

I started this project by making a class that talks to a TV over serial and commands inputs.

I took the class from this and linked it to a supybot plugin and...

screenshot from putty showing tv control

screenshot from putty showing tv control