2026-04-29 21:51:15 -07:00
2026-04-30 18:53:52 -07:00
2026-04-30 04:47:41 +00:00
2026-04-27 19:28:47 -07:00
2026-04-27 19:49:53 -07:00

Cultivar Firmware

Arduino library and sketch for the Grove5 Cultivar appliance ENV-1 sensor board.

Grove5 — Open source software, honest hardware.


What it does

Runs on the STM32U585 MCU (via Arduino UNO Q) or any Arduino-compatible board. Reads the ENV-1-V0.1 sensor board — BME690 environmental sensor and optional SCD43 CO2 sensor — and outputs structured JSON payloads over serial every 5 minutes.

The daemon (cultivar-daemon) consumes these payloads on the Linux side.

Hardware

ENV-1-V0.1 — dual sensor board designed for the Cultivar appliance.

Sensor Measures I2C Address
BME690 Temp, humidity, pressure, gas resistance (10-point sweep) 0x76
SCD43 Real CO2, temp, humidity 0x62 (optional — DNP for colonization)

I2C pins (ESP32S3 dev board — adjust for production):

  • SDA → GPIO42
  • SCL → GPIO41

Chamber identity is self-declared: if SCD43 is not detected at init, the board identifies as colonization. If SCD43 is present, it identifies as fruiting. No jumpers, no config — the populated BOM is the configuration.

Library: cultivar_env

Wraps the Bosch BME690 SensorAPI directly. No Bosch BSEC2 blob — raw sensor access only.

Installation

  1. Download Bosch BME690 SensorAPI
  2. Copy bme69x.h, bme69x.c, bme69x_defs.h into cultivar_env/src/
  3. Install Sensirion I2C SCD4x via Arduino Library Manager
  4. Install cultivar_env via Sketch → Include Library → Add .ZIP Library

Dependencies

Quick start

#include <Wire.h>
#include <cultivar_env.h>

CultivarEnv env;
char jsonBuf[768];
const char* CHAMBER_ID;

void setup() {
    Serial.begin(115200);
    env.begin(Wire, 42, 41);
    CHAMBER_ID = env.scd4x_ok ? "fruiting" : "colonization";
}

void loop() {
    CultivarEnvPayload payload;
    env.collect(payload);

    env.toJSON(payload, jsonBuf, sizeof(jsonBuf));
    Serial.println(jsonBuf);  // → daemon via serial

    delay(300000); // 5 min
}

Gas sweep — 10-point mycology profile

# Temp°C Dur ms Zone Notes
0 200 150 1 — Baseline Humidity/CO2 proxy
1 220 150 1 — Baseline Light alcohols
2 250 150 1 — Baseline Ethanol
3 280 180 2 — Biological Acetic acid / bacterial marker
4 300 180 2 — Biological Terpene zone low
5 320 180 2 — Biological Geosmin / 1-octen-3-ol
6 340 200 2 — Biological Aldehyde zone
7 360 200 3 — Heavy Ammonia / nitrogen
8 380 200 3 — Heavy Sulfur compounds
9 400 200 3 — Heavy Heavy VOC fingerprint

Gas resistance values are raw — humidity correction, baseline normalization, and compound inference are handled by cultivar-daemon on the Linux side.

JSON payload format

One payload per collection cycle, emitted as a single newline-terminated JSON line:

{
  "chamber_id": "fruiting",
  "ts": 300000,
  "ambient": {"temp_c": 23.45, "humidity_pct": 82.10, "pressure_hpa": 929.88, "valid": true},
  "co2": {"ppm": 1850, "temp_c": 24.10, "humidity_pct": 83.20, "valid": true},
  "health": {"scd43_present": true, "temp_delta": 0.65, "humidity_delta": 1.10, "temp_ok": true, "humidity_ok": true},
  "baseline": {"established": false, "samples_collected": 3},
  "sweep": [
    {"heater_temp_c": 200, "heater_dur_ms": 150, "zone": 1, "resistance": 85000000, "heater_stable": true, "valid": true},
    {"heater_temp_c": 220, "heater_dur_ms": 150, "zone": 1, "resistance": 72000000, "heater_stable": true, "valid": true},
    {"heater_temp_c": 250, "heater_dur_ms": 150, "zone": 1, "resistance": 61000000, "heater_stable": true, "valid": true},
    {"heater_temp_c": 280, "heater_dur_ms": 180, "zone": 2, "resistance": 420000,   "heater_stable": true, "valid": true},
    {"heater_temp_c": 300, "heater_dur_ms": 180, "zone": 2, "resistance": 280000,   "heater_stable": true, "valid": true},
    {"heater_temp_c": 320, "heater_dur_ms": 180, "zone": 2, "resistance": 195000,   "heater_stable": true, "valid": true},
    {"heater_temp_c": 340, "heater_dur_ms": 200, "zone": 2, "resistance": 148000,   "heater_stable": true, "valid": true},
    {"heater_temp_c": 360, "heater_dur_ms": 200, "zone": 3, "resistance": 102400000,"heater_stable": true, "valid": true},
    {"heater_temp_c": 380, "heater_dur_ms": 200, "zone": 3, "resistance": 102400000,"heater_stable": true, "valid": true},
    {"heater_temp_c": 400, "heater_dur_ms": 200, "zone": 3, "resistance": 102400000,"heater_stable": true, "valid": true}
  ]
}

Collection timing

  • Production: 5-minute interval — allows full thermal dissipation between heater cycles
  • Development: 30-second interval — heater self-heating will elevate ambient temp readings
  • Ambient reading (heater off, 3s settle) runs before each gas sweep

Board setup (Arduino IDE)

Setting Value
Board ESP32S3 Dev Module
USB CDC On Boot Enabled
Flash Size 4MB
PSRAM OPI PSRAM
Upload Speed 921600

For Arduino UNO Q production deployment: board is STM32U585, Serial1 is used for daemon communication (reserved by Arduino Router — use Serial for debug only).

Notes on BME690 gas sensor

  • First power-on: allow 15-30 min for gas resistance to stabilize
  • Full sensor burn-in: ~7 days of cumulative operation per Bosch documentation
  • 102400000 Ω is the clean-air ceiling / saturated sentinel value — valid and expected in clean environments
  • Resistance values are raw — do not compare absolute values across different units or sessions without normalization

License

MIT — Grove5
Bosch BME690 SensorAPI: BSD-3-Clause — Bosch Sensortec GmbH

S
Description
MCU firmware for Cultivar's sensors, lights, fans, and other electronics.
Readme MIT
405 KiB
0 Stars 1 Watchers 0 Forks
Languages
C++ 99.7%
Shell 0.3%