ChiBio Next is a from-scratch rewrite of the Chi.Bio control software, built behind a hexagonal hardware boundary so the same controllers run on a BeagleBone Black with real I²C peripherals or on a laptop against a Python physics simulator.
The upstream Chi.Bio device is brilliant; the controller code is a single ~100 KB Flask file with hardware I/O, control loops, and view logic interleaved. ChiBio Next preserves the device behaviour but reorganises the software so each layer can be reasoned about, tested, and replaced on its own.
BioreactorUnitPort protocol — never on a concrete
I²C class. Swap in a simulator, a recorded trace, or a different
instrument without touching the algorithms.
SimulatedBioreactorFleet models
logistic culture growth (Beer–Lambert transmittance) and
Newton’s-law thermal response, so the whole stack — Flask app,
UI, control loops — runs on any laptop.
sysData[M][…] global dict is gone. Each
unit owns a UnitExperimentState dataclass; routes
are thin and never call I²C directly.
Each layer depends only on the one below it. The ports layer is the contract — everything above it is logic, everything below it is I/O.
The same Flask app boots in both modes. bootstrap.build_fleet()
picks the adapter based on a single environment variable.
Eight simulated units come up populated with the physics model. Start a turbidostat, watch the OD trace evolve in real time, hit the Playwright UI tests.
# install git clone https://github.com/fuzue/chibio-next cd chibio-next pip install -e . flask # run python app_new.py # → http://localhost:5000
On the real Chi.Bio device, the bootstrap probes each of M0–M7 over the TCA9548A multiplexer and skips any unit that doesn’t respond, so partially-populated devices work.
# require Adafruit_BBIO, smbus2, Adafruit_I2C BEAGLEBONE_I2C=1 python app_new.py # or, equivalently: CHIBIO_MODE=hardware python app_new.py
The simulator path is usable today; the hardware path has been wired but not yet end-to-end validated on a live device. Treat anything here as a work-in-progress until this section says otherwise.
runExperiment()