Automation

My security camera tells me when the laundry is done

I wish Reolink did more with the speakers in its cameras. They’re already powered, on the network, and sitting in useful places. They could double as little smart speakers for announcements, but that use doesn’t seem to get much attention.

I’d already opened an issue about adding speaker playback to the library behind Home Assistant’s official Reolink integration. With AI coding tools like Codex around to help, I decided to take a crack at getting it working myself.

I wanted to know when the washer or dryer finished, even from across the house with the laundry-room door closed. There was already a Reolink camera with a speaker next to the stairs, near the bedrooms and a bit away from the machines. That seemed like a useful place for the announcements to come from.

First I needed Home Assistant to know when the appliances finished. Samsung had announced a $4.99/month plan for SmartThings API access, with free access being phased out starting in October. I wasn’t about to pay a stupid subscription for that. I used LocalThings instead: a free, open source integration that talks to the washer and dryer directly over the local network.

I still had to connect the appliances to SmartThings first to get LAN access working, and keep them registered there. LocalThings handles Home Assistant’s ongoing communication with them locally.

The official Reolink integration still lists two-way audio and text-to-speech as unavailable, so I used a custom integration.

I started with ha-reolink-talk, Matthew Holm’s integration based on joeblack2k’s Reolink Talk. It already provides camera media players for audio files and text-to-speech, including cameras behind an NVR. I forked it to fix a couple of problems with my setup.

The first was camera discovery. A separate request to the NVR failed because of its self-signed HTTPS certificate. The error was caught, and discovery quietly fell back to channel 0. I’m still wondering how the hell this worked in the upstream author’s setup without hitting that certificate error. Meanwhile, Home Assistant’s official Reolink integration was already connected and knew which channels existed. Reusing that information got the right cameras to show up.

The audio framing needed fixing too. The encoded audio blocks weren’t being packaged the way the camera expected. Correcting the block metadata, padding, and stream framing got speech playing through the camera. Those fixes are in my fork and an upstream pull request.

Once playback worked, the laundry part was small. Each appliance’s LocalThings progress sensor reaching finish triggers an announcement through the camera by the stairs. Piper supplies the speech: “The washer has finished its cycle,” or the dryer equivalent.

I tried a few versions of the sound, including repeated flute announcements and a Samsung completion melody. With the appliance and the camera playing the Samsung melody out of sync, it was a confusing cacophony. I went with one short tone followed by speech instead. The washer gets a higher tone and the dryer a lower one. Each tone and its spoken message are rendered together into one audio file, so the camera only has to start playback once.

The laundry automations are specific to this house, but the integration fixes are there for other Reolink owners to use. A camera speaker is a pretty convenient little announcement speaker when it’s already in the right place.

12:00 am / Home assistant , Reolink , Automation

My wobbly VHS pipeline

I wanted to recover some VHS tapes. Somehow this turned into tapping a signal inside a Sony VCR, putting capture cards in a computer from the junk pile at work, spreading the processing across three machines, and asking Codex to help redesign an amplifier PCB.

There is a reason I went this way. The usual way to digitize VHS is to connect a VCR’s video and audio outputs to a capture device and record them on a computer. The VCR reads the tape and turns its signal into a picture before the computer ever sees it.

For better results, you can look for a nicer VCR with a built-in time base corrector, or TBC, which helps correct playback timing errors that make the picture wobble. Unfortunately, the decks I was looking at were $$$. With ancient media having a moment again, it feels like everybody wants the same aging hardware. The expensive stuff is, well, expensive.

VHS-Decode offers a more hardcore route. You still need a working VCR, but you tap the RF signal inside it, near the tape-head amplifier, before the machine turns it into ordinary video. This is not the antenna connector on the back. Suitable capture hardware records that earlier signal, and the computer reconstructs the picture, including time base correction, in software.

[... 3,297 words]

10:34 am / Vhs , Preservation , Codex , Automation

codex-status-json

I made a small Rust CLI that prints Codex status as JSON.

The short version is that I wanted Codex conversations to know my account, model, and quota state without opening the interactive TUI and reading /status like a person.

This is mostly useful with Codex goals. I am on the Pro 20x plan, which is the $200/month tier, so sometimes I really do have quota to burn. If I can read the remaining quota first, I can make a goal with an actual target instead of guessing how ambitious I should be.

The missing command

Codex has /status in the interactive CLI. It shows useful stuff: account, model, context, writable roots, and rate limits.

That is fine when I am sitting in the TUI. It is less fine when I want another Codex conversation to ask the same question.

There is an upstream issue asking for this exact kind of thing: a non-interactive JSON/headless replacement for /status. The issue is still open as I write this. There is also codex doctor --json, which is useful, but that is a diagnostic report. It is not the same as “tell me my account/model/quota status right now.”

So I made codex-status-json.

The first version was worse

The first version did what you would expect from a tool born out of impatience: it opened Codex in a PTY, sent /status, waited for the panel to settle, captured the terminal output, stripped ANSI escapes, and parsed the text.

It worked. It was also clearly a bad long-term place to live.

Terminal scraping is annoying because every little UI change can break you. The parser has to care about box drawing, timing, update notices, trust prompts, terminal width, and whatever else the TUI happens to render that day.

Still, it was useful. It gave me JSON like this:

{
  "schema_version": 1,
  "account": {
    "email": "user@example.com",
    "plan": "Pro",
    "raw": "user@example.com (Pro)"
  },
  "model": {
    "name": "gpt-5.5",
    "details": "reasoning high",
    "raw": "gpt-5.5 (reasoning high)"
  },
  "buckets": [
    {
      "name": "default",
      "limits": [
        {
          "kind": "5h",
          "remaining_percent": 99,
          "resets": "14:44",
          "reset_at_unix": 1780350290
        },
        {
          "kind": "weekly",
          "remaining_percent": 70,
          "resets": "08:23 on 7 Jun",
          "reset_at_unix": 1780845815
        }
      ]
    }
  ]
}

That was enough to unblock the thing I wanted.

The less cursed version

A few days later, I switched the default backend to Codex’s app-server.

That is a much better source than scraping the TUI. The tool starts:

codex app-server --listen stdio://

Then it sends JSON-RPC calls for:

  • account/read
  • account/rateLimits/read
  • config/read

That gives it the same kind of account, model, and quota information without pretending a terminal status panel is an API.

Important caveat: Codex app-server is documented, but it is also described as experimental and may change. So this is still not a supported codex status --json. It is just a more sensible workaround than screen scraping.

The PTY backend is still there as a fallback:

codex-status-json --backend pty

But the default is now:

codex-status-json --backend app-server

This is a bridge

I do not expect this to be the forever answer.

Codex’s usage and billing surface is still moving. OpenAI has already introduced things like banked rate-limit resets, so the status shape is not just “two counters and call it done.” There are plans, windows, reset times, secondary buckets, and whatever else comes next.

So I can see why OpenAI might not want to commit to a stable status JSON contract yet. That is fine. I still wanted something local that worked now.

This is the same kind of bridge as a lot of these small tools: use the available pieces, make the annoying gap tolerable, and be happy if the official thing eventually replaces it.

The repo

The project is here:

https://github.com/nelsonjchen/codex-status-command

Install it locally with:

cargo install --path .

The README has the options and limitations. The tests include a captured /status fixture so the text parser can at least fail loudly when Codex changes its display.

I would still rather have an official codex status --json. Until then, this is small, local, and useful enough.

3:03 pm / Codex , Rust , CLI , JSON , Automation