← Beezy

Reviving the Terrashroom TS-01 after the cloud died

In June 2025 Terrashroom shut down the cloud its TS-01 mushroom chambers depended on. An estimated 1,040 units lost their app, their presets and their camera overnight. This is how one of them got local, better-than-stock firmware in a week, with a multimeter, a $10 USB-serial adapter, 13 years of tinkering with microcontrollers and IoT devices, and a lot of Claude Code sessions running in parallel.

The TS-01 main board's 1x7 programming header above the ESP32-WROOM-32E module, annotated with each pin's signal and the module pad where a needle held IO0 low
The programming header, annotated during the first session. There was no schematic, so every label came from a meter reading or a boot log.

The short version

The device, and why it died

The TS-01 is a countertop grow chamber. It has a water reservoir and an ultrasonic mister, two fans, a 5 V blower, a 24 V heater, a 24-LED ring light, a 64-LED base strip, a UV-C lamp for sterilising the empty chamber, and a camera for timelapses. Inside are two ESP32s: the main board running the vendor's Arduino and Blynk firmware, and a sealed camera board.

The stock firmware ran no climate control without the vendor's app. When the cloud went, the chamber became a box of fans with a light. There was no open teardown we could find. A closed-source community firmware, Hackshroom, existed, and later we found lbaker2/terrashroom-resurrection, whose GPIO map matched ours exactly. Both got credit where we used them.

I decided not to buy another cloud gadget. I'd reverse engineer this one, write everything down, and build firmware that answers to nobody's server, for my unit and for everyone else's.

Backup first, and rules the agent can't skip

Before any code, I wrote the hard rules into the project's CLAUDE.md, which every Claude session reads when it starts:

  1. Never write flash until a full backup has been read twice with matching hashes, and copied.
  2. Never burn eFuses.
  3. Gate every write on flash encryption and secure boot being off.
  4. The UV-C lamp is an eye hazard. It's off by default everywhere, and nothing turns it on without a person asking.
  5. Flash dumps can hold Wi-Fi passwords. They never get committed.

A sixth rule came later, the hard way: never connect or disconnect a serial adapter while the chamber is powered. Ground goes on first and comes off last.

On day one I buzzed out the header with a meter. The mounting screw was ground, pin 1 was 5 V, and listening on pin 5 gave the full stock boot log at 115200 baud. It named the firmware, the I²C pins and the cloud host, and relayed one line from a second ESP32: {"status":"ready","version":"camera/0.1.7.09"}.

Then came the backup the rules demand: two full 4 MB reads, 205 seconds each, with identical SHA-256, kept in two copies. Claude wrote the tool that does it in one esptool session, because every new esptool process glitched the reset line and dropped the chip out of download mode.

Claude as a team, not a chatbot

Most of the software was written by Claude Code, running as many sessions at once. Each had its own git worktree and branch, did one job, and opened one pull request that was squash-merged. The roles sorted themselves out:

One required CI check holds it together. It validates every firmware config, runs a safety checker (UV-C must boot off, no load may boot on, every update must switch the loads off first), compiles the public firmware against a size gate, and runs the host, site and iOS tests, a license lint and a secrets scan.

Where 13 years at the bench mattered

Claude was fast and thorough at decoding, writing and testing. These are the places where experience changed the outcome:

The honest lows

The main board after the dead ESP32 module was removed with hot air: 38 tinned edge pads and the centre ground pad, surrounded by burnt flux, with the heat-splayed programming header at the right
6 October: the dead module is off. The pads survived, the header's plastic didn't, and a tiny decoupling capacitor went missing in the rework.

What we found

The main board out of the chamber next to a ruler, with connectors along the top and bottom edges, two buck converters, and the empty module footprint
Measuring the board for the drop-in v2 replacement, which has since been ordered.

What we built

Firmware with interlocks

The controller is tuned from the stock values and runs on the chamber with no cloud. It handles pulsed misting, top-ups, an over-humidity purge, staged heating, species airflow, grow sessions and grow phases. Around it sit the interlocks. The heater cuts out on a stale sensor or above 88 °F, and never heats still air. The mister locks out on low water and fails closed at boot. UV-C is off at every boot, refused on low water and cut by a hard timer. Every update switches the heater, mister and UV-C off first.

A load check says when a fan, the blower, the mister or the heater isn't plugged in. Its first version called a running blower missing. The blower runs on 5 V and adds only about 6 mA to the 24 V rail being measured, and the camera booting and a light effect moved the rail by more than that. Now every load is measured between two baselines. On a noisy rail the check says "unsure" instead of "failed", and the blower can be confirmed but never called missing.

The AutoMyco app's dashboard on the demo chamber: a Blue Oyster grow on day 12, an All good health card, a camera tile and the temperature on target
The app's dashboard (demo chamber).
The app's Controls tab with Climate Control on, the atomizer running, and Fan 2 (intake) flagged as not detected because it drew no current
Load checks: Fan 2 unplugged on the demo chamber.

An iPhone app

A native app talks to the chamber over its local network. It has a dashboard built around "is my grow OK?", grow sessions, lights, history, the activity log and a camera view. It also updates the firmware: the first real update moved about 1 MB in 10.8 seconds. It's backed by close to 500 tests against a mock chamber, and it's in TestFlight beta.

Install without opening anything

Hold the rear button for about three seconds, and the stock firmware opens its own Bluetooth update window. The app or the website then sends AutoMyco block by block. The chamber checks the whole image before it switches over, and on first boot AutoMyco picks up the Wi-Fi the stock firmware had saved. The first run from stock passed, but 180 blocks failed their checksum and the link dropped once. iOS had been sending "apply this block" ahead of its queued writes. The fix, plus automatic reconnect and resume, went into all three installers.

The app's Install on a New Chamber screen at 63 percent, noting that stopping is safe because the chamber keeps its stock firmware
An early build of the Bluetooth installer, from before the project was called AutoMyco.

Website, cloud and fleet

Security and safety lessons

By the numbers

Orphaned units (estimate)~1,040
First boot log to release 1.1.07 days
Commits / merged pull requests~240 / 100+
Stock backup2 × 4 MB reads, identical SHA-256
First firmware update from the app1,073,024 bytes in 10.8 s
Public firmware size1,622,915 of 1,769,472 bytes (91.7 %)
iOS tests~490
Chips destroyed2

What's next

How to use Claude for a hardware revival

  1. Write the hard rules down first. Backup before write, no eFuses, hazards off by default, no secrets in git. The agent follows them every session, and that's what makes it safe to let it near real hardware.
  2. Keep the physical and the irreversible human. Needles, power, lamps, domains and the first flash belong to the person at the bench.
  3. Log everything, dated, in the repo. The bench log became the shared memory between dozens of sessions, and the source for this post.
  4. One session, one branch, one pull request. With a single required CI check, many sessions can work at once without trampling each other.
  5. Make safety a test, not a promise. Safety checker, size gate and secrets scan on every pull request.
  6. Run separate review and security sessions. They found the GET-drives-UV-C hole, the trust-on-first-use identity, the kill-switch DoS and the reusable camera key.
  7. Believe the bench over the model. When the numbers and the room disagree, measure again.
  8. Plan for being wrong, both of you. Inferred pins stayed labelled "inferred" until the bench confirmed them. The worst mistake of the week was mine, with a ground wire, and now it's a rule.

Own a TS-01? The firmware, installers and help are at automyco.com. Not affiliated with Terrashroom.