From 271b0763948a528dd9803db1d5ac1c6cdc1b0a9c Mon Sep 17 00:00:00 2001 From: pgdalmeida Date: Wed, 5 Aug 2026 09:13:16 +0200 Subject: [PATCH] added temperature and humidity sensor (SHT41) --- CMakeLists.txt | 2 + config/config.example.ini | 11 +++ docs/configuration.md | 22 ++++- docs/known-issues.md | 6 +- docs/mqtt-api.md | 18 ++++ docs/roadmap.md | 3 - docs/test-roadmap.md | 26 +++--- include/fgc/Config.h | 12 +++ include/fgc/IControlChannel.h | 9 ++ include/fgc/IEnvSensor.h | 37 ++++++++ include/fgc/MqttControlChannel.h | 2 + include/fgc/mock/MockEnvSensor.h | 43 +++++++++ include/fgc/mock/NullControlChannel.h | 5 ++ include/fgc/sensors/I2cBus.h | 37 ++++++++ include/fgc/sensors/Sht41EnvSensor.h | 34 +++++++ include/fgc/ui/UiSnapshot.h | 10 ++- src/core/Application.cpp | 55 +++++++++++- src/core/Config.cpp | 6 ++ src/mqtt/MqttControlChannel.cpp | 9 ++ src/sensors/I2cBus.cpp | 51 +++++++++++ src/sensors/Sht41EnvSensor.cpp | 123 ++++++++++++++++++++++++++ src/ui/TuiUi.cpp | 6 +- src/ui/UiSnapshot.cpp | 11 +-- tests/test_config.cpp | 23 +++++ tests/test_scheduler.cpp | 1 + tests/test_uisnapshot.cpp | 8 +- 26 files changed, 535 insertions(+), 35 deletions(-) create mode 100644 include/fgc/IEnvSensor.h create mode 100644 include/fgc/mock/MockEnvSensor.h create mode 100644 include/fgc/sensors/I2cBus.h create mode 100644 include/fgc/sensors/Sht41EnvSensor.h create mode 100644 src/sensors/I2cBus.cpp create mode 100644 src/sensors/Sht41EnvSensor.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1384b1d..e813ad9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,8 @@ set(FGC_SOURCES src/camera/ImagePipeline.cpp src/serial/SerialMotorController.cpp src/serial/MtiImuSource.cpp + src/sensors/I2cBus.cpp + src/sensors/Sht41EnvSensor.cpp ) if(WITH_MQTT) list(APPEND FGC_SOURCES src/mqtt/MqttControlChannel.cpp) diff --git a/config/config.example.ini b/config/config.example.ini index b38dad8..4b115ed 100644 --- a/config/config.example.ini +++ b/config/config.example.ini @@ -72,6 +72,15 @@ baud = 115200 device = baud = 115200 +[Env] +; Ambient temperature/humidity sensor (Adafruit SHT41), wired to the +; LattePanda's OWN native I2C bus - NOT the Arduino/motor serial link. Bus +; number varies by board; confirm with `i2cdetect -y N` (SHT41 shows up at +; 0x44). Enable with [Features] enable_env = true. +i2c_device = /dev/i2c-1 +i2c_addr = 68 +period_ms = 2000 + [Motor] ; Degrees<->encoder-counts calibration for each axis. The firmware speaks only ; in absolute encoder counts; these map them to the heading/elevation degrees @@ -137,6 +146,8 @@ enable_imu = false mock_camera = false mock_serial = false mock_imu = false +enable_env = false +mock_env = false [Test] ; Hardware self-test command (`test`). `profile` is the default profile used when diff --git a/docs/configuration.md b/docs/configuration.md index 08311d3..7b092ed 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -55,8 +55,13 @@ Parsed and validated by `ConfigLoader` ([src/core/Config.cpp](../src/core/Config | `Features` | `mock_camera` | bool | `false` | Use the simulated camera | | `Features` | `mock_serial` | bool | `false` | Use the simulated motor controller | | `Features` | `mock_imu` | bool | `false` | Use the simulated IMU instead of the MTi | +| `Features` | `enable_env` | bool | `false` | Use the ambient temp/humidity sensor (SHT41) | +| `Features` | `mock_env` | bool | `false` | Use the simulated env sensor instead of the SHT41 | | `IMU` | `device` | string | — | MTi serial device (see `[IMU]` note); required when `enable_imu` | | `IMU` | `baud` | int | `115200` | MTi serial baud rate | +| `Env` | `i2c_device` | string | `/dev/i2c-1` | SHT41 I2C bus device (see `[Env]` note); required when `enable_env` | +| `Env` | `i2c_addr` | int | `68` (`0x44`) | SHT41 I2C slave address | +| `Env` | `period_ms` | int | `2000` | Sample interval | | `Logging` | `level` | enum | `info` | Linear log level (`--log-level` overrides) | | `Logging` | `trace` | csv | — | Wire-trace categories, off by default (`--trace` overrides) | | `UI` | `enable_tui` | bool | `false` | Full-screen terminal dashboard (`--tui`/`--no-tui` override; needs `WITH_TUI=ON`) | @@ -140,6 +145,18 @@ The config is read **once at startup** and cached, so if you change the XKF prof stays stale until you **refresh** it: press `r` (or type `refresh`). That re-queries the device (briefly pausing the stream) and also requests a fresh firmware dump for the gimbal `g` view. +### `[Env]` — SHT41 ambient temperature/humidity sensor + +Enable with `[Features] enable_env = true` and point `[Env] i2c_device` at the LattePanda's **own +native I2C bus** — this is a separate physical link from the motor Leonardo's serial connection, not +the Arduino's D2 pin (the original DHT11 plan). Bus number varies by board; confirm with +`i2cdetect -y N` (the SHT41 answers at `0x44`/`68`). The backend (`Sht41EnvSensor`) polls at +`period_ms`, discarding any sample that fails the SHT4x CRC-8 check. Set `[Features] mock_env = true` +to use a synthetic reading on dev machines (no hardware) — this is also the checked-in +`config.example.ini` default. The sensor sits behind the generic `IEnvSensor` interface +([include/fgc/IEnvSensor.h](../include/fgc/IEnvSensor.h)), so swapping in a different ambient sensor +later means adding a new backend, not touching `Application`, the UI, or MQTT. + ### Secrets `mqtt_user` / `mqtt_pw` are read from the environment variables **`FGC_MQTT_USER` / `FGC_MQTT_PW`** first, @@ -220,8 +237,9 @@ operation is unchanged and remains the default — the same binary runs under sy logs on stdout. Dashboard panels: **Gimbal** (per-axis state, heading, encoder counts, flag badges, target), -**Sensors** (DHT11 still *pending*; the **Xsens MTi** shows live roll/pitch/yaw + temp once -`enable_imu`), **Camera** (count, capture state, rate, last capture), **Connectivity** (MQTT state, +**Sensors** (the **Xsens MTi** shows live roll/pitch/yaw + temp once `enable_imu`; the ambient +**SHT41** shows live temp/humidity once `enable_env` — see [known-issues.md](known-issues.md) for +its hardware-bring-up status), **Camera** (count, capture state, rate, last capture), **Connectivity** (MQTT state, broker, tower, control mode, target heading). Adding a panel later is a struct in `UiSnapshot.h` plus one node in [src/ui/TuiUi.cpp](../src/ui/TuiUi.cpp). diff --git a/docs/known-issues.md b/docs/known-issues.md index 8b56bb5..602619a 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -94,4 +94,8 @@ persistence for LPM + usbfs, and verify a full sweep captures white-balanced fra yet (not homed / no `gimbal dump`), nudge requests one and does nothing that press — it never falls back to the configured degree clamps (which, if `*_min_deg`/`*_max_deg` were unset, produced absurd ±100000° steps). -- DHT11 temperature/humidity is still a Sensors-panel placeholder (the IMU half is integrated). +- Ambient temperature/humidity (SHT41, `IEnvSensor`/`Sht41EnvSensor`/`MockEnvSensor`) is + integrated in software — config-gated (`[Features] enable_env`/`mock_env`, `[Env]`), Sensors + panel + MQTT `Env` topic wired — but defaults to `mock_env = true` in the checked-in example + config. Bus/address bring-up and validation against the physical sensor on the LattePanda's I2C + bus is still pending; flip `mock_env = false` on the deployed `config.ini` once confirmed. diff --git a/docs/mqtt-api.md b/docs/mqtt-api.md index 0b89a2e..5818d87 100644 --- a/docs/mqtt-api.md +++ b/docs/mqtt-api.md @@ -52,6 +52,7 @@ When a ControlCode message arrives, the program echoes the current code back on |-------|------|---------|--------------| | `GGS/FWT//StatusCode` | At startup (`"0"`); whenever a ControlCode message is received (echoes the code) | integer as string | 1 / retained | | `GGS/FWT//CamEvent` | After each image is saved | JSON object (below) | 1 / retained | +| `GGS/FWT//Env` | Once per fresh ambient sensor sample (see `[Env] period_ms`) | JSON object (below) | 1 / retained | ### CamEvent payload @@ -72,6 +73,22 @@ Built by `MqttControlChannel::publishCamEvent` from a `CamEvent`: > The `time` value is the same Unix-ms timestamp used as the image filename, so a consumer can locate the file > for a given event: `/