Jim's Depository

this code is not yet written

This is mostly me talking to myself about how I use power4 for running the battery banks and generators in my shed.

Hardware

  • 48v battery bank with attached controllable DC propane generator and a provision for a manual AC gasoline generator.
  • 24v battery bank with a manual AC gasoline generator for charging.
  • DC/DC 48v -> 24v charger controlled by a relay.
  • 400w of solar panels on the 24v bank.
  • Each bank has an inverter which can be used to power the house, only one at a time.

Configuration

The power4/examples/shed.lua script is my actual configuration, so updating my configuration looks like this…

  • Check out power4 on my laptop
  • Have a chat with an AI about how I want to change my script, or just change it myself
  • Commit the change and push
  • Log in the shed.island.XXX.XXX
  • Check out the power4 repository
  • power4ctl stage examples/shed.lua
  • power4ctl policy accept
  • power4ctl show logs

Extending

If I have to make changes to the power controller's firmware or the power4ctl program, then it goes like this:

On the laptop:

$ # make the changes
$ make package && scp dist/power4-firmware.tar.gz shed.island.XXX.XXX:

On shed.island:

$ # Deploy the new firmware
$ rm -rf power4-firmware && tar -zxvf power4-firmware.tar.gz && ( cd power4-firmware && ./flash.sh && ./monitor.sh )
$
$ # If we need a new config or power4ctl then
$ cd ~/coding/power4
$ git pull
$ make deb && sudo dpkg -i power4ctl/power4ctl_1.0.0_arm64.deb && sudo systemctl restart power4ctl

Maintaining

  • shed.island.XXX.XXX, use the power4ctl program. It has some native commands, but power4ctl help will tell you all the pass through commands.
  • power4ctl daemon mode is snapshotting to /run/power4/*.json, one of those files is logs.json. See how things are going.
  • You can connect with picocom, but unless you want real time logs you can just use power4ctl and the pass through commands.

I polished my various SQLite wrappers into their own public Swift package: minimal-sqlite.

The repository is here:

https://github.com/jimstudt/minimal-sqlite

And the DocC documentation, because apparently I am susceptible to fancy modern trappings, is here:

https://jimstudt.github.io/minimal-sqlite/documentation/minimalsqlite/

This came out of work on the latest Femtoblogger rewrite. I had a tiny local sqlite.swift wrapper which was doing the job, but it was just barely doing the job. The database layer in Femtoblogger was getting harder to read than it needed to be, mostly because every query had too much low-level SQLite plumbing showing through.

minimal-sqlite is not an ORM. That is an important non-feature. You still write SQL. You still know what tables you have. You still get to make your own indexing mistakes in the traditional manner.

What it does provide is a small, safe, Swift Concurrency-friendly shell around SQLite:

  • an SQLDatabase actor which owns the connections
  • one writer connection and a pool of read-only connections
  • explicit readwrite and readonly transaction APIs
  • prepared statement binding and typed tuple row decoding
  • convenience execute, query, lookup, and queryDict calls
  • a RowID type for hidden SQLite row IDs
  • support for lastInsertRowID, changes, and totalChanges
  • a formatted text-date wrapper for legacy SQLite timestamp columns

The transaction model is the bit I care about most. A write transaction uses BEGIN IMMEDIATE; a read transaction uses BEGIN DEFERRED; read-only transactions do not expose an execute API. If SQLite reports BUSY or LOCKED, the transaction body can be retried, so the usual warning applies: do not put non-idempotent side effects in there unless you enjoy mysterious little duplicate behaviors.

Femtoblogger is now using the public 0.1.0 release, so I suppose that makes it real enough for now.

The package is intentionally small. No migrations framework. No query builder. No magic model layer. Just enough SQLite glue to make the code above it easier to read without hiding what SQLite is doing.

I have a Sungold SPH5048P All-in-one inverter. It has an RS-485 port and a USB port where you can get information, though it isn't documented how you might do that.

I wrote a program I call sungold which interfaces to the USB port on the Sungold, extracts the information, and reports it in a giant JSON file of mostly correctly interpreted data.

I tried to turn the inverter section on and off, and failed miserably. But I have succeeded in setting and clearing power saver mode, which is good enough for me. How power saving mode works is poorly documented, but without grid/generator power and no load, my inverter eventually shuts off, and when load appears, it eventually comes back on.

The device presents as a serial port where you do Modbus commands to pull blocks of register values. Most values cannot be written, but some can. It's hit or miss.

You can run in daemon mode where it will hang around and periodically write a new .json file, or you can run it as a one-shot command and get a JSON output.

This may work with many different brands of inverters: Many companies use the SRNE internals, which is what this targets. Good luck there.

This program just makes the JSON, I grind it up, and put it into a web page to monitor things. That ends up looking like this:

alt text

Attachments

sungold.jpg 61058 bytes

While working with those horrible battery management system BLE interfaces, I wanted to probe my various BLE devices and see what services and characteristics they have. I suspect there is a way to do it with the standard-ish bluetooth tools, but they also looked overcomplicated and apparently they also hate humans.

So I did what any civilized program would do, I "wrote" my own in a programming language I do not know. The AIs assured me that Rust's zbus interface was the friendliest API to work with. I've never written Rust, or used D-bus, but why let that stop me.

So I sat down with Claude and told him my wishes for a bluetooth low energy scanning program, and after a false start and a bunch of refinement, and then some locating of catastrophic bugs and maybe getting rid of them I now have the BLE scanner of my dreams.

I still don't know Rust, I kind of meant to learn it, but I haven't really read any of the code. I made it move some things around and structure things a little different, but Claude didn't need me for my coding skills.

Anyway, you too can have blescan. There is a Raspberry Pi Debian package in the release prebuilt, or you can build it yourself.

With no arguments it lists the devices it sees advertising. With a MAC as an argument it maps out the services and characteristics of the device.

$ blescan
Scanning for BLE devices... (5s)
Found 2 device(s):
  AA:BB:CC:DD:EE:FF - My Sensor (-72 dBm)
  11:22:33:44:55:66 - Unknown (-85 dBm)

$ blescan AA:BB:CC:DD:EE:FF
Connecting to AA:BB:CC:DD:EE:FF...
Waiting for GATT services...

GATT profile for AA:BB:CC:DD:EE:FF:

Service: 0x1800 - Generic Access
  Characteristic: 0x2A00 - Device Name [read]
    Value: "My Sensor"
  Characteristic: 0x2A01 - Appearance [read]
    Value: 00 00

Service: 0x180A - Device Information
  Characteristic: 0x2A29 - Manufacturer Name String [read]
    Value: "ACME Corp"
  Characteristic: 0x2A26 - Firmware Revision String [read]
    Value: "1.2.3"

Service: 0x180F - Battery Service
  Characteristic: 0x2A19 - Battery Level [read, notify]
    Value: 64

There's a man page if you want to know more.

I've got 8 Vatrer 12v 100Ah LiFePO4 batteries in two banks powering a remote installation. The absolute #1 selling point of these batteries is that their BMS (battery management system) will look to see if it is too cold to charge the batteries and run a little heater until the cells warm up enough to charge without destroying them.

The #2 selling point is that the BMS units report the battery state of charge, voltage, current, and such over BLE (Bluetooth Low Energy). That lets me better track their state when unattended for months and decide when to cut power off to less critical systems or attempt to start a backup generator.

After much jiggery-pokery, I have this integrated in the control systems, but those little BMS controllers are really nasty to work with.

Woes

  • No sane BLE implementation. They sort of implement a RS-485 interface with a write-only string characteristic and a notify-only string characteristic. Listen to the one and shout in the other.

  • No sane protocol. I'm pretty sure that is some ancient Modbus controller protocol wrapped in the Bluetooth.

  • No documentation of the protocol. I get it, I'd be ashamed to admit to that too.

  • GIANT DANGER: If a device connects to the BMS, in the BLE sense, then the battery will stop advertising itself, and it appears the battery has vanished. If there is a timeout, it is more than 12 hours. Lest you think,“well, I won't do that then"… there are a lot of sneaky paths for BlueZ and NimBLE that will leave connection stuck on without you knowing it. I found several.

  • You apparently can't reset the BMS. I mean, it's not like you can take the batteries out or disconnect the power.

Moral

If one of your batteries drops off Bluetooth, then reboot all the devices that were talking to it, and it may well come back.

When writing Cassandra's Deal, I had in the plan a "settings" page and a little gear icon to get to it, as one does. In the end, I ended up not having any settings. Sure, there were a lot of visual representation decisions which could have had settings. And there are levels of trivial move automation that could have a setting.

But I realized that an app without settings is liberating for a user. There is no implied obligation to go to the settings, understand which ones are applicable to you, and how they work. Freedom!

There are still visual customizations, but I let them be driven by the system preferences the user has already made. Dark mode and light mode are automatic. It's not in the current release, but the drawing of the card face to make the rank and suit more visible will be driven by the "text size" preference of the user.

Many apps will require settings or some data input from the user to make sense, but for simple apps, it’s worth asking yourself if the user discomfort of having settings is worth the gain.

I'm pretty good at Klondike-type solitaire, but I do not always win winnable hands. (No backtracking; all hands are winnable with unlimited backtracking.) After not feeling I was getting any better at winning winnable hands, I decided what I needed was an indicator to show when I had "stepped off the path", when I had converted a winnable spread of cards to an unwinnable one. Not immediately finding such a program, I spent a day with an AI agent and wished one into existence.

Lessons learned:

  • There are solitaire card states where stepping off the golden path is just baffling. You can make what looks like the most obviously benign move and render it unsolvable. More disturbing, there are places where you have a choice of two cards to move to a destination, and one makes it unsolvable with no clear indication how you might choose one or the other. I fear that a human-operable algorithm for perfect solvability is not achievable. But, hey, give it a shot! I have an app for that.
  • The AI wrote an entire polished solitaire app with real-time solvability analysis just from my instructions and feedback. I was ready for a 1.0 release without ever having seen any of the code.
  • Having a knowledgeable computer scientist is still useful; in talking to it about solvability, I sped up its algorithms by about a factor of 1000.
  • Having a programmer with in-depth knowledge of a platform and its capabilities is also an asset. The AI frequently chose ways of implementing which didn't take advantage of capabilities; for instance, when dealing a deck of cards, it would use the "Task" system to delay and then deal the next card. This gives a deal subject to timing jerkiness. Encouraging it to use the animation system with delays built into the animation gets a smooth, synchronized experience. But if you didn't have a programmer who had watched an unreasonable number of WWDC videos, that approach would not have been explored.
  • I used Codex for most of the work. I let Claude help when Codex was on its marketing break. Claude seems a bit slower, though most of that feeling might be that it is less chatty while working. They both seemed to get the job done. The low end Claude gets a lot less work done before going on break compared to ChatGPT. Codex works better with Xcode 26.5, but I imagine they both do better with the Xcode beta. I just can't use that because I'm releasing to the store. Still, it might be worthwhile to develop mostly with the Xcode beta, then switch to the released Xcode when it is time to publish.

In any event, you can go to the iOS or macOS app store and download Cassandra's Deal and try for yourself.

Image of solitaire game

Attachments

cassandrasdeal.png 535506 bytes

Let's say your desktop machine has been sitting in place for 7 years without moving. Apple refuses to let your machine know its location without Wi-Fi active. But you are using your glorious Ethernet connection and don't want the chaos of a secondary connection that will sometimes get used and throw everything into a tizzy.

It is possible to work around this. Your Wi-Fi radio has to be on and watching beacons, but it doesn't have to be connected to a network. Go through any network you know how to join and turn off "Auto join". Cycle your Wi-Fi off and back on. If you connect to a network, go set that one to not "Auto join" as well.

Once you have no networks to auto join, your Wi-Fi rainbow in the menu bar will be grayed, but your location services still work because the radio is looking at beacon messages from the nearby access points.

Maybe someday Apple will give us "On/Off/Location Only" for our Wi-Fi state, but until then, this works. You just have to manually ask to join your network when lightning strikes your Ethernet switch.

While testing SMTP message reception and DKIM validation I ran into an evil action from Postfix.

In my basically default Debian Postfix/Dovecot system, if you send a message to Postfix, it DKIM signs it, then sends it off to the destination. But if your message needed 8BITMIME and your destination doesn't support that, then Postfix quietly re-encodes your message and body to be quoted-printable.

Now your DKIM signature is invalid!

It sounds like you can configure Postfix to bounce that instead, but that's not what I got for a simple installation.

That was about a day and a half of me debugging my DKIM verification code because my body hash calculation kept not matching the one in the DKIM header.

Morals:

  • Just go ahead and support 8BITMIME and SMTPUTF8 if you can. No need to poke the bear.
  • If your body header checksum matches for some messages, but not others, its probably something upstream corrupting the message bodies after the signature.

Lost half a day today working to a strange Cloudflare DNS resolver anomaly.

When you make a UDP request, it may answer correctly, or it may indicate truncation by setting the TC (TrunCation) bit and not give you an answer. The answer easily fits, but it just decides, "nope, not this time". If you reissue the query over TCP you will get the result every time (in my testing). But UDP gives an erroneous TC non-result about 25% of the time on the name I was testing.

So, I guess be alert for that. If your DNS code can flip over to TCP you may never realize this is happening other than some of your queries are oddly much delayed compared to the others.

Just the simple host command can show the behavior, though you won't realize it happened because it falls back to TCP. If you are watching packets with tcpdump you will see it.

I don't see the behavior using Google's 8.8.8.8.

Anyway, here's a little tcpdump capture if you want to look at it. That's a bunch of requests for the same TXT record (the SPF for lunarware.com, which is ultimately DNS hosted at Cloudflare.)

I don't have a resolution unfortunately, I just banned 1.1.1.1 until I add TCP lookup to the affected code.

Attachments

more articles