Welcome to James Brown's blagoblag! This contains various thoughts and opinions, mostly wrong, going back a couple of decades. All of the opinions are my own, and probably not my employer's. Feel free to visit the about page for more useless interesting facts about me.

trying out aerospace, a macos window manager

Back when I used Linux on the desktop, I used to use this great window manager called "awesome". It's probably the thing I've missed the most since switching to macOS, especially when on small screens like the 14" display of this MacBook I'm typing on. Awesome was what is known as a "tiling window manager". This means that rather than having a bunch of windows overlapping on some number of virtual desktops, windows are arranged into nonoverlapping segments called "tiles". Tiling window managers have been around for a long time; Windows 1.01 was a tiling window manager and competed with the overlapping-window desktop metaphor of the original Macintosh OS2. Tiling window managers require a lot of persnicketey window management to be useful (because otherwise you just end up with a bunch of unusably-tiny rectangles vanishing into the distance), but if you're willing to put up with it they can give you a super-fast way to organize and manage your applications. Tiling window managers often lean heavily onto the concept of "virtual desktops", allowing you to quickly switch between different sets of windows.

Macintosh windowing isn't bad. The original concept3 of overlapping windows whose order can be micromanaged is a great way to scale from one to several windows. Mac OS X 10.5 Leopard added a virtual desktop feature called "Spaces" that isn't terrible (especially once you add the "Displays have separate Spaces" option), but the animations are insufferably long and keyboard accessibility is pretty limited.

Anyhow, it turns out that nowadays, there are a few options for tiling window managers on macOS. The top few seem to be

There's also a few very new ones:

  • yashiki looks cool (and is actually very close to the design of awesome), but it appears to be heavily "vibe-coded" and I wasn't able to get it to work.
  • rift also looks cool, but crashed Dock.app when I tested it

I played with them all, but for the last week or so I've been using AeroSpace exclusively.

Desktop under AeroSpace
My desktop right now under AeroSpace, showing several windows in an accordion on the left and two windows tiled on the right

Setting Up

Installation (presuming you're using Homebrew) is very straightforward:

brew install --cask nikitabobko/tap/aerospace

You'll have to go to System Settings → Privacy & Security → Accessibility and add both /Applications/AeroSpace.app. At this point they can control your whole computer, so, uh, here's hoping they aren't malware.

AeroSpace is configured via a TOML file at ~/.aerospace.toml.

config-version = 2

# You can use it to add commands that run after AeroSpace startup.
# Available commands : https://nikitabobko.github.io/AeroSpace/commands
after-startup-command = []

start-at-login = true

# Normalizations. See: https://nikitabobko.github.io/AeroSpace/guide#normalization
enable-normalization-flatten-containers = true
enable-normalization-opposite-orientation-for-nested-containers = true

# See: https://nikitabobko.github.io/AeroSpace/guide#layouts
# The 'accordion-padding' specifies the size of accordion padding
# You can set 0 to disable the padding feature
accordion-padding = 30

# Possible values: tiles|accordion
default-root-container-layout = 'tiles'

# Possible values: horizontal|vertical|auto
# 'auto' means: wide monitor (anything wider than high) gets horizontal orientation,
#               tall monitor (anything higher than wide) gets vertical orientation
default-root-container-orientation = 'auto'

# Mouse follows focus when focused monitor changes
# Drop it from your config, if you don't like this behavior
# See https://nikitabobko.github.io/AeroSpace/guide#on-focus-changed-callbacks
# See https://nikitabobko.github.io/AeroSpace/commands#move-mouse
# Fallback value (if you omit the key): on-focused-monitor-changed = []
on-focused-monitor-changed = ['move-mouse monitor-lazy-center']

# Also see: https://nikitabobko.github.io/AeroSpace/goodies#disable-hide-app
automatically-unhide-macos-hidden-apps = false

# List of workspaces that should stay alive even when they contain no windows,
# even when they are invisible.
# This config version is only available since 'config-version = 2'
# Fallback value (if you omit the key): persistent-workspaces = []
persistent-workspaces = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "Z"]

# A callback that runs every time binding mode changes
# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
# See: https://nikitabobko.github.io/AeroSpace/commands#mode
on-mode-changed = []

# Possible values: (qwerty|dvorak|colemak)
# See https://nikitabobko.github.io/AeroSpace/guide#key-mapping
[key-mapping]
    preset = 'qwerty'

# Gaps between windows (inner-*) and between monitor edges (outer-*).
# Possible values:
# - Constant:     gaps.outer.top = 8
# - Per monitor:  gaps.outer.top = [{ monitor.main = 16 }, { monitor."some-pattern" = 32 }, 24]
#                 In this example, 24 is a default value when there is no match.
#                 Monitor pattern is the same as for 'workspace-to-monitor-force-assignment'.
#                 See:
#                 https://nikitabobko.github.io/AeroSpace/guide#assign-workspaces-to-monitors
[gaps]
    inner.horizontal = 0
    inner.vertical =   0
    outer.left =       0
    outer.bottom =     0
    outer.top =        0
    outer.right =      0

# 'main' binding mode declaration
# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
# 'main' binding mode must be always presented
# Fallback value (if you omit the key): mode.main.binding = {}
[mode.main.binding]

    # All possible keys:
    # - Letters.        a, b, c, ..., z
    # - Numbers.        0, 1, 2, ..., 9
    # - Keypad numbers. keypad0, keypad1, keypad2, ..., keypad9
    # - F-keys.         f1, f2, ..., f20
    # - Special keys.   minus, equal, period, comma, slash, backslash, quote, semicolon,
    #                   backtick, leftSquareBracket, rightSquareBracket, space, enter, esc,
    #                   backspace, tab, pageUp, pageDown, home, end, forwardDelete,
    #                   sectionSign (ISO keyboards only, european keyboards only)
    # - Keypad special. keypadClear, keypadDecimalMark, keypadDivide, keypadEnter, keypadEqual,
    #                   keypadMinus, keypadMultiply, keypadPlus
    # - Arrows.         left, down, up, right

    # All possible modifiers: cmd, alt, ctrl, shift

    # All possible commands: https://nikitabobko.github.io/AeroSpace/commands

    # See: https://nikitabobko.github.io/AeroSpace/commands#layout
    alt-slash = 'layout tiles horizontal vertical'
    alt-comma = 'layout accordion horizontal vertical'

    # See: https://nikitabobko.github.io/AeroSpace/commands#focus
    alt-h = 'focus --boundaries-action wrap-around-the-workspace left'
    alt-j = 'focus --boundaries-action wrap-around-the-workspace down'
    alt-k = 'focus --boundaries-action wrap-around-the-workspace up'
    alt-l = 'focus --boundaries-action wrap-around-the-workspace right'

    alt-q = 'focus-monitor left'
    alt-e = 'focus-monitor right'

    # See: https://nikitabobko.github.io/AeroSpace/commands#move
    alt-shift-h = 'move left'
    alt-shift-j = 'move down'
    alt-shift-k = 'move up'
    alt-shift-l = 'move right'

    # See: https://nikitabobko.github.io/AeroSpace/commands#resize
    alt-minus = 'resize smart -50'
    alt-equal = 'resize smart +50'

    # See: https://nikitabobko.github.io/AeroSpace/commands#workspace
    ctrl-1 = 'workspace 1'
    ctrl-2 = 'workspace 2'
    ctrl-3 = 'workspace 3'
    ctrl-4 = 'workspace 4'
    ctrl-5 = 'workspace 5'
    ctrl-6 = 'workspace 6'
    ctrl-7 = 'workspace 7'
    ctrl-8 = 'workspace 8'
    ctrl-9 = 'workspace 9'
    ctrl-0 = 'workspace Z'

    ctrl-left = 'workspace prev'
    ctrl-semicolon = 'workspace prev'
    ctrl-right = 'workspace next'
    ctrl-quote = 'workspace next'

    ctrl-shift-semicolon = 'focus-monitor prev'
    ctrl-shift-quote = 'focus-monitor next'

    # See: https://nikitabobko.github.io/AeroSpace/commands#move-node-to-workspace
    ctrl-shift-1 = 'move-node-to-workspace 1'
    ctrl-shift-2 = 'move-node-to-workspace 2'
    ctrl-shift-3 = 'move-node-to-workspace 3'
    ctrl-shift-4 = 'move-node-to-workspace 4'
    ctrl-shift-5 = 'move-node-to-workspace 5'
    ctrl-shift-6 = 'move-node-to-workspace 6'
    ctrl-shift-7 = 'move-node-to-workspace 7'
    ctrl-shift-8 = 'move-node-to-workspace 8'
    ctrl-shift-9 = 'move-node-to-workspace 9'
    ctrl-shift-0 = 'move-node-to-workspace Z'

    alt-tab = 'workspace-back-and-forth'
    alt-shift-tab = 'move-workspace-to-monitor --wrap-around next'

    alt-space = 'fullscreen'

    alt-shift-semicolon = 'mode service'

    ctrl-shift-enter = "exec-and-forget kitty -1 --detach -d $HOME"

# 'service' binding mode declaration.
# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
[mode.service.binding]
    alt-shift-semicolon = 'mode main'

    esc = ['reload-config', 'exec-and-forget osascript -e "display notification \"aerospace config reloaded\" with title \"aerospace\""', 'mode main']
    r = ['flatten-workspace-tree', 'mode main'] # reset layout
    f = ['layout floating tiling', 'mode main'] # Toggle between floating and tiling layout

    alt-shift-q = 'move-workspace-to-monitor left'
    alt-shift-e = 'move-workspace-to-monitor right'

    alt-shift-h = ['join-with left', 'mode main']
    alt-shift-j = ['join-with down', 'mode main']
    alt-shift-k = ['join-with up', 'mode main']
    alt-shift-l = ['join-with right', 'mode main']

[[on-window-detected]]
    if.app-id = "com.markmcguill.strongbox"
    run = ['layout floating']

[[on-window-detected]]
    if.app-id = "com.1password.1password"
    run = ['layout floating']

[[on-window-detected]]
    if.app-id = "com.iconfactory.Tot"
    run = ['layout floating']

[[on-window-detected]]
    if.app-id = "com.apple.mail"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "com.apple.MobileSMS"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "com.gather.GatherV2"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "com.tinyspeck.slackmacgap"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "net.shinyfrog.bear"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "com.mimestream.Mimestream"
    run = ["move-node-to-workspace Z"]

Add-Ons

There are a few useful addons I'd recommend; the first is SwipeAeroSpace, which lets you remap 3- or 4-finger swipes to switch virtual desktops in AeroSpace. If you already use BTT or equivalent, you don't need this.

Install it with

brew install --cask mediosz/tap/swipeaerospace

Then add it to Accessibility in System Settings (just like you did for AeroSpace) and launch the app.

The next thing I've been working on is an Alfred workflow to control Aerospace. You can download a prototype of it at 📁 aerospace.workflow. It supports the new alfred triggers asp to bring up the aerospace command menu, and aw to quickly switch workspaces.

Alfred workspace
Alfred showing the 'asp' action from this workflow

It's still super-janky, so I haven't published it to the workflow gallery.

A lot of people online use tools like SwiftBar and SketchyBar with these alternate WMs, but I haven't seen any reason to do so yet.

Anyhow, that's where I have it today. Maybe I'll blog some more if this sticks for a few months.

  1. Circa 1985

  2. Macintosh System Software

  3. Faithfully borrowed from Xerox, of course

  4. Technically, Niri/Paneru are "sliding" window managers, which represent the desktop as an infinitely-long one-dimensional space that your viewport slides across

what makes programming great?

Most of the time, when I'm actually doing it, I love my job. There's a reason that computer programming (or "software engineering" if you're highfalutin) attracts so many people, and it's not just the unsustainably high salaries at overvalued tech companies or the promise of free gogurt in a corporate cafeteria. I want to take this post to try and make a case for what makes it so great.

The first aspect I wanted to discuss, which I think is pretty well covered, is that programming is fundamentally a creative act. Even in the worst slop-house where you're writing boring Java or Go code that converts one form of ProtoBuf to another, you are making the decisions on how to do that, you are structuring the code, and you get to enjoy the satisfaction of building something yourself. Creating and building something is one of the most essential human joys there is, and is essential to human satisfaction1. Most of the time, there's more than one way to do it, and even for the simplest program, the design space is enormous enough that no two people will come up with the same approach. Exploring this space and deciding how to tackle a problem is beautiful.

The next aspect that I want to cover is that computers are close to perfect, something that's true in so few other fields. Computers have ridiculously low error rates2, and are perfectly deterministic unless you go out of your way to make them do something pseudorandom. If you write a program correctly, it'll respond the same way every time. Now, of course, you may still have bugs based on different inputs, or different states of the machine; I'm not trying to say that we all live in a strongly-typed pure-functional utopia. But compare this to other creative endeavors — no two pieces of wood that you cut will be the same every time; even if every ingredient looks the same, the dish is always going to be different. But when you program computers, you have this wonderful opportunity to hone a single project without worrying about variance or materials degradation.

Finally, and most important to me, everything in computing is knowable. Computer science is a small, shallow field that is deliberate about building reliable layers of abstractions. There was a blog post going around a few days ago whose conclusion is that, basically, slopcoding only makes things slightly worse because nobody actually understands the whole stack already. I couldnt't disagree with this more vociferously. Yes, obviously nobody knows everything! But somebody knows each thing (because these are all systems built by humans), and the beautiful thing about working on computers is that you can know any thing! I've had times in my career when I've had to work on kernel interrupt scheduling code; I do have a pretty good idea how the memory model of ARM processors works; I have built processors by drawing MOSFETs in Magic. And, yes, I don't know everything listed off in that blog post, but I know how to learn them if and when I need to. Because my job isn't just to produce widgets of output for some corporate masters; it's to grow and improve as a person and as at my career, so knowing how to learn new things and improve myself is perhaps the most key skill.

Obviously, this is a post about LLMs, which are pseudorandom lie factories which require their users to do you do the least-creative mind-numbing activities on the planet3 and encourage humans to adopt a kind of learned helplessness — to convince us all that the unexamined job is worth working. Maybe this is the future of my industry, the inevitable enshittification of creative work into a satire-of-a-satire, where former knowledge workers pull levers on an expensive slot machine until something that they don't understand comes out that meets a Business Need and makes some executive 0.003% richer, but I damn well hope it's not a future I'll ever participate in.

  1. people keep telling me that there are huge classes of people, called "managers", who get more satisfaction from ordering someone else to do something than from doing it themselves. This seems like a defect if true, but based on the general level of dissatisfaction and alcoholism in all the managers I know, I kind of think it isn't true.

  2. although obviously high enough that you can become a prominent computer person by working around them

  3. "prompt engineering", a.k.a. trying to randomly guess a series of english words whose highest-probability autocompletion will be the output you want

Some Good Stuff in 2025

I'm trying to get into a habit of reflecting on some good stuff from the past year at the end of the year; I did it last year and the year before, at least. It continues to be hard to get into the mood to think about good things while the world's burning, jack-booted gestapo are shooting fellow citizens, and my entire industry has been brain-rotted by the great "AI" scam, but, you know, gotta try to look on the bright side.

read more

⤭ Row Level Security: Defense in Depth

I wrote a blog post over on my employer's blog about how to use row-level security patterns in postgres and clickhouse and I think it's pretty neat. Every company I've worked at has done multitenancy inside of SQL databases, and the approach to prevent cross user access has basically boiled down to git gud; they've also all had at least one incident where some endpoint forgot to check permissions and you could access other users' data (sometimes just by incrementing an auto-incrementing ID in a URL). This is the first attempt I've seen to comprehensively fix that, so I wrote it up. Enjoy!

COVID-19 Update

It's been five years since the pandemic hit, and three years since the last time COVID hit our family, but it's hit for real now. My five year perfect-streak has been ended1.

My daughter came home from day-care sick on Monday and I started feeling bit crummy late Thursday afternoon. Lo and behold, not only am I positive for COVID, but so is everyone else in the family. The kids are mostly okay — the baby had a fever on Tuesday but has been fine since then, and my son has no symptoms — but my wife and I both have fevers and aches and fatigue and coughs and the whole nine yards. Let me tell you, having two healthy and rambunctious children home from school while the adults have COVID is the absolute worst.

Anyhow, I got a Paxlovid prescription. So far (< 12 hours in) it's not doing much for the symptoms, but everything tastes like rusty nails now. Yay.

Stay safe out there, nerds.

  1. Note: While I have not, to the best of my knowledge, had COVID since testing became available, I actually think there's a good chance that my wife and I both had COVID already — in late January 2020. Eva came back from a prison trip and we both came down with a virulent and miserable flu. This is before COVID was officially in the US, but it still seems likely to me.

Cooking is better than computers

I don't have time for a lot of hobbies these days, but one of my favorites is preparing food. Whether that's cooking four courses out of Julia Child1, grilling some meat, or just tossing a salad together, there's something deeply satisfying at turning ingredients into a meal. In some ways, it's not unlike programming; anyone can do a passable job using inferior tools and following other peoples' recipes, but there's almost infinite space to tinker, invest in better tools, and learn to make your own recipes. Also, as long as you follow some simple rules, the worst outcome you're likely to have is something that doesn't taste great.

More than that, though, it's something that's just fundamentally human. Despite what some of my Soylent-chugging former executive leaders might thing, making and eating food is an activity that connects us all. The forces of capitalism are trying as hard as they can to give us a world where everybody eats soulless chain-restaurant slop delivered by underpaid Gig Economy workers, but you can still make something all your own; you can still find joy in something that has no product-market fit, no CSAT, and nothing to optimize2.

Some good things

I got a new knife recently (a 200mm Senzo bunka knife). Does it make the food taste better than if I'd cut it with my 15-year-old chef's knife? No, but it makes the act of prepping food a bit more fun. It's wildly too fancy for my skill level, but ¯\(ツ)/¯.

I also replaced my 15-year-old All-Clad stainless steel frying pan with a new 11" Demeyere frying pan3. The old one's rivets were starting to come apart, so I got one without rivets!

The original Moosewood Cookbook continuous to be joyous; any recipe you cook is sure to include some novel ingredients and techniques.

Some good fresh spices can make all the difference to a simple dish — some tellicherry peppercorns and Calabrian chile powder (and oil and salt, of course) is really all you need for some quick sautéed or grilled vegetables.

The Temescal farmer's market is a great place to blow $50 on berries that your 5-year-old will eat all of on the walk home. And lots of other good produce, too. Oh! And the Garden Variety Cheese Hollyhock is so good.

Some bad things

Fuck Samsung appliances. Our house came with two of them: a range and a fridge. They're both stupid.

The range (which doesn't have a model number on it, but looks like the NX60A6511SS/AA) has capacitive touch buttons on top of it. Which are triggered by steam. Which means that if I boil something on the stovetop, it will turn on random settings on the oven. It's infuriating to be making a sauce and suddenly have the oven turn on at 700°F for no reason. Also, if they're damp, then don't respond to finger touches, which means that you then can't turn the oven off without getting a towel and drying the buttons. Such a fucking stupid design.

The fridge is a four-door model with a "Flex Zone", which means you can convert half of the freezer from a freezer into more fridge space. I have no idea why you'd want this (it leaves you with an absolutely minuscule freezer), but it's mapped to a single touch of a capacitive button, which means once in a while someone will brush by it and not notice and then half of your freezer will melt. There's a "control lock" feature, but it also completely disables the ice maker4 and water dispenser, so it's not really usable.

Outside the realm of Samsung appliance crap? It's become impossible to find recipes or blog posts about cooking on the Internet that haven't become LLM-slop-filled-nonsense. Thankfully, there are hundreds of years of writing about food that predate Truthiness as a Service.

Auf Wiedersehen and good night

  1. not really in the cards with a six-month-old and a five-year-old

  2. unless you want to for fun, I guess? I'm not the optimization police.

  3. no, I did not pay $190 for it, I'm not crazy

  4. which is very bad: if you let the ice maker go for more than about a week without using all the ice, it'll freeze over solid, the plastic parts will crack, and the whole thing will have to be replaced. This has happened to us twice so far!

More words about enphase

Last week, I wrote bout my experience getting a solar power system installed; since then, I've been spending some weekend and evening time working on improving the little corner of technical nonsense that I have any control over!

Background

First, let's do a quick recap of how my system works:

Solar system layout

I have 20 panels, each of which generates up to 460W of DC power and immediately converts it from DC to AC using an Enphase IQ8X microinverter1. This gives us a peak of 9.2kW possible power split across two phases.

This power comes off the roof to an Enphase IQ Combiner 5 PV combiner. I'm not really sure what the combiner does! I assume it somehow aligns and balances the different AC signals coming off the roof into two 60Hz AC phases, but I have no idea how! Maybe it's just an expensive subpanel with a busbar and some breakers, who knows.

Anyhow, on the other part of the wall we have three Enphase 5P Batteries, storing 5 kWh each (as DC, obviously, since you can't store AC). Each of them has six built-in IQ8D microinverters capable of putting out about 600W, so each battery can provide about 3.6 kW of instantaneous power.

The solar and the batteries both land on an Enphase IQ System Controller 3M, which merges all that power together and directs charge/discharge of the batteries. Unlike older Enphase systems, all of the communication is over powerline Ethernet instead of Zigbee.

The System Controller connects to the main panel in two ways: first, there's a 100A backfeed circuit to power the house with up to 19.2 kW of power; second, the System Controller is connected to the meter with an IQ Meter Collar , which allows the system to operate safely during an outage to provide whole-home backup without energizing PG&E's grid.

Management, Officially

So, how do you see what the system is doing and control it? It's all done through an Enphase app called Enlighten, available both as a webapp and a mobile app. The mobile app is mostly just a wrapper around the webapp; there's almost no native functionality. Both of these tools are incredibly slow. I just benchmarked launching the iOS app on my phone2 and it takes 13-21 seconds to start up. Once it starts up, it's not responsive to taps at all and many features take forever to load. It also doesn't support widgets or App Intents or anything else someone might want on a modern Apple platform. The webapp usually loads faster, but has a "do not sell my personal information" banner that can't be dismissed and the layout is broken at lots of screen sizes (including the one in this screenshot).

I don't really need my data hosted in some cloud service anyway. Isn't there a way to get this information directly off the Enphase system gateway (which is known as Envoy)?

Yes, Duh

This would be a short story if there weren't.

It turns out that Enphase provides a documented open API to get data off of the Envoy! The documentation is pretty hard to find, but it's still around at https://enphase.com/download/iq-gateway-access-using-local-apis-or-local-ui-token-based-authentication-tech-brief.

You need to obtain a JWT from some dinky web service (which expires after a year and can't be renewed without an interactive session, alas), but then you can get all kinds of nice JSON data. Sometimes it's oddly slow (like, 15-20 seconds), and there's no authz, so that same token that can read the data can also reconfigure the system, so you definitely don't want to expose the Envoy directly to the Internet.

So, I wrote a small Rust application named envoyproxy3 that polls the Envoy API every minute and serves the most recent result and some other metrics as JSON and Prometheus. I'm running mine in my little k8s cluster here, using the excellent Tailscale Kubernetes operator to expose it to my Tailnet so I can reach it from anywhere.

But why?

The immediate goal here was to have a pleasant iOS experience for seeing system status, so I've also built a SwiftUI app, tentatively codenamed Sunny Days:

It's pretty ugly, but it takes 14MiB of RAM and launches in approximately 300ms. Not too bad for something cracked up on a weekend by someone with absolutely no Swift experience.

I'm not sure what to do with it. It'd never make it onto the App Store — a third-party unsupported client for a proprietary solar system which requires running an open-source server somewhere? Maybe it'll just live on my and my wife's devices through TestFlight forever...

I also, of course, have a Prometheus instance ingesting these metrics, connected to Grafana.

Grafana

Anything else?

Yeah, the other thing is my car. It's connected to an Emporia EVSE on a fat 60A circuit. However, most of the time I'd prefer to charge it slowly with excess solar. This is... not supported4.

There are really two options:

  1. Pull out the Emporia EVSE and put in Enphase's, which can talk to the CTs in the Enphase boxes and natively do excess-solar charging. This would require permit and an electrician and a new EVSE and would probably come to around $1000
  2. Add the Emporia Vue CTs to the solar subpanel and main panel. This would require two sets (around $300) and should be done by a licensed electrician, so still pretty steep

I didn't want to do that, so instead I wrote a small Python program that reads the Envoy data every few minutes and uses it to adjust the Emporia charge rate up-or-down. It's not as nice, because it's not instantaneous so sometimes it'll draw amps from the batteries or the grid for minute, but that's not a big deal to me, and the cost was $0. It's called emporia-enphase-control and is... pretty rough around the edges5. It does work, though! It's running in Kubernetes, right alongside envoyproxy.

That's it, right?

For now. We're still adapting to life with solar; it's a surprising number of changes. For example, we usually ran the dishwasher and the dryer at night (when the grid is the cheapest), but now that power is totally free while the sun's up (the batteries are quite finite, especially when the A/C is running overnight on hot weeks like this one) it makes no sense to run these expensive appliances6 at night.

So far, we have only drawn a negligible amount of power from the grid this week7.

  1. This is to be contrasted with the more-traditional "string inverter" deployment, where the solar panels are all connected in series and send DC off the roof to a single large inverter at ground-level

  2. an iPhone 16 Pro with the fastest single-core processor performance in the world, connected to WiFi 6 and a 10Gbps Internet connection

  3. this is not an ambiguous name at all

  4. It really should be! There's a protocol called the Open Charge Point Protocol that should standardize these interconnects, but none of my devices support it. I guess I should've gotten a Wallbox...

  5. I'm sure the distinction between watts and volt-amps in a mixed DC/AC system is not important, right?

  6. A single dryer load consumes something like 4kWh of energy --- gotta get myself one of those fancy heat-pump combo units that only consumes 1.4 kWh for a wash and a dry cycle

  7. ~300Wh day, more than offset by the several kWh we've sold back. I wish I knew why the system ends up drawing ~14W from the grid, all the time...