temphummon/PROJECT_MEMORY.md
temphummon dev d9bac7cf28 Document icon-probe results; keep iconfinder bitprobe as artifact
Probed remaining frame bytes for Bluetooth/power icons on a blank panel:
- found cross-wired glyph dots (byte0 bit7, byte4 bit7) and an I2C timeout
  (byte8 bit4), but no standalone icon bits
- waveshare reference does not drive these icons; likely multi-bit or
  unpopulated on the glass (cosmetic, deprioritized)
- restore temphummon.c as the active firmware build in CMakeLists
2026-09-24 17:44:02 +00:00

11 KiB
Raw Blame History

Project Memory — temphummon

Temperature and humidity monitor using Matter over Thread. Based on the ESP32-H2 Zero and Waveshare 1.9" Segment E-Paper display.


Quick Reference

Item Value
MCU ESP32-H2 (RISC-V, IEEE 802.15.4 / Thread)
IDF version v6.0.1
Chip target esp32h2
Build tool idf.py (via Nix dev shell)
Main source firmware/main/temphummon.c
sdkconfig firmware/sdkconfig
Datasheets datasheets/
CAD files cad/
BOM See README.md
Reference code E-Paper-Segment-Code.zip (Waveshare, in project root)

Wiring (ESP32-H2-Zero ↔ E-Paper Module)

Fixed and tested — display does NOT overheat with correct wiring.

ESP32-H2-Zero E-Paper Module Wire color
GPIO4 SDA 🟢 Green
GPIO5 SCL 🟡 Yellow
GPIO10 RST 🟠 Orange
GPIO11 BUSY 🔵 Blue
3V3 VCC 🔴 Red
GND GND ⚫ Black

Note: The module version has its own level shifters and LDO — safe to connect directly. The bare panel (without PCB) has different pinout.


Important: Display Interface = I²C, not SPI

The IST7134 driver IC on the 1.9" Segment E-Paper uses I²C communication, NOT SPI. The Waveshare reference code confirms this.

I²C Protocol Details

Parameter Value
Command address 0x3C (A0=0, 7-bit)
Data address 0x3D (A0=1, 7-bit)
Speed 100 kHz
Pull-ups Internal (enabled in firmware)

The A0 bit is encoded in the I²C address itself: 0x3C for commands, 0x3D for data. This is handled by using two separate i2c_master_dev_handle_t handles in the firmware.

Display Data Format

  • 15 bytes per frame
  • 91 segment outputs + 2 background + 1 VCOM (94 bits ≈ 12 bytes, padded to 15)
  • Digit patterns (0-9) from Waveshare reference code
  • Two SRAM banks used: first for image data, second for control

Frame Layout (EMPIRICALLY VERIFIED with bit-probe)

The digits are dot-matrix style (each bit = one dot / short stroke of a column), NOT classic one-bit-per-7-segment. Each digit glyph maps to a byte pair. The two lines interleave: top-tens, top-ones, bottom-tens, bottom-ones, bottom-tenths, top-tenths.

Frame byte(s) Display location Position label
f[0] bits 0-4 Top line, leading "1" (hundreds) —
f[1], f[2] Top line, tens digit pos 1
f[3], f[4] Top line, ones digit pos 2
f[5], f[6] Bottom line, tens digit pos 3
f[7], f[8] Bottom line, ones digit pos 4
f[9], f[10] Bottom line, tenths digit pos 5
f[11], f[12] Top line, tenths digit pos 6
f[13] °C / °F letter (0x05 = °C) —
f[14] Padding / unknown —

→ Top line renders as [1][tens][ones].[tenths] °C e.g. 188.8 °C → Bottom line renders as [tens][ones].[tenths] % e.g. 88.8 %

Decimal point & % (THE missing piece — now found! 🎯)

All three are bit 5 (0x20) of the SECOND byte of the pair, matching the manual: "decimal point and % are the 5th place in the 4th, 8th, and 10th positions."

Symbol Frame byte Bit Where it appears
Decimal point (top) f[4] bit 5 (0x20) after top-line ones → 188.8
Decimal point (bottom) f[8] bit 5 (0x20) after bottom-line ones → 88.8
% sign (humidity) f[10] bit 5 (0x20) after bottom-line tenths → 88.8%

Bluetooth / power icons (probed — NOT found, likely multi-bit or unpopulated)

A single-bit probe of the remaining frame bytes (0 bits5-7, 4/8/10 bits 4/6/7, 14 all) found no standalone Bluetooth or battery/power icon. Bits found:

  • byte 0 bit7 → a cross-wired glyph dot (top-ones region)
  • byte 4 bit7 → a cross-wired glyph dot (bottom-ones region)
  • byte 8 bit4 → caused an I²C hardware timeout
  • most other candidate bits → blank

The Waveshare reference code does NOT drive any Bluetooth/power icon, and the IST7134 datasheet doesn't name one either. Conclusion: if the icons exist on the glass they are multi-bit patterns (like °C = 0x05), not a simple single bit reachable by a bare frame write — not worth more bench time. Cosmetic only.

Verified glyph segment map (from probing)

For a digit byte-pair, the first byte = left column + horizontal bars, the second byte = right column (bits 0-3) + decimal/control (bit 5+). Example (pos 1, top tens): byte1 bit5 = top bar, bit6 = middle bar, bit7 = bottom bar; byte2 bits 0-3 = right-side dots/bars.

⚠️ Control bits (probed): second-byte bit 4 of some pairs and byte13 bit1 flip the whole panel BLACK and wedge the driver until a hard re-init (RST pulse). Avoid setting them, or re-init after. (bitprobe had to power-cycle the board.)

Initialization Sequence

1. Hardware reset (RST pin: high → low → high with delays)
2. POWER_ON command (0x2B)
3. Boost + TSON (0xA7, 0xE0)
4. Temperature compensation based on ambient temp

Display Refresh Sequence

1. Wake from sleep (0xAC)
2. Power on (0x2B)
3. Set RAM address (0x40)
4. Open first SRAM (0xA9), close (0xA8)
5. Write 15 bytes of image data
6. Write extra byte (0x00 or 0x03)
7. Open second SRAM (0xAB), close (0xAA)
8. Display on (0xAF)
9. Wait for BUSY pin
10. Display off (0xAE), HV off (0x28), sleep (0xAD)

Waveform LUTs

Function Bytes sent Use case
epd_lut_DU_WB() 82 80 00 C0 80 80 62 Partial update (white extinction + black out)
epd_lut_GC() 82 20 00 A0 80 40 63 Full global refresh
epd_lut_5S() 82 28 20 A8 A0 50 65 Boot waveform (better ghosting)

Nix Development Shell

The project uses Nix flakes to provision the ESP-IDF toolchain.

How to enter the dev shell

export PATH="/root/.nix-profile/bin:$PATH"
cd /src && nix develop --command bash

One-off commands (no interactive shell needed)

cd /src/firmware && nix develop /src --no-write-lock-file --command idf.py build

Nix details

  • Flake: /src/flake.nix
  • Lock: /src/flake.lock
  • Nix binary: /root/.nix-profile/bin/nix (also at /nix/store/irfrbndi76zhkvqsfhmsn4a99iafck29-nix-2.35.2/bin/nix)
  • Nix config: /etc/nix/nix.conf (has experimental-features = nix-command flakes)
  • Dev shell inputs: esp-idf, git, minicom, usbutils, gawk, coreutils, cmake, ninja

⚠️ Note: Custom nixpkgs-esp-dev fork

The flake uses a custom fork of nixpkgs-esp-dev:

nixpkgs-esp-dev.url = "github:dvdvgt/nixpkgs-esp-dev/update-v6.0.1";

This fork has few/no cached binary substitutes, so nix develop compiles the entire ESP-IDF toolchain from source. This takes a long time on first run but we're keeping it for now since mainline mirrexagon/nixpkgs-esp-dev doesn't support ESP-IDF v6.0.1 yet.


Build State

✅ Successful build

The firmware compiles and links successfully for ESP32-H2 (RISC-V target). Size: 0x2b1b0 bytes (83% free).

Firmware behavior

Current firmware (main/temphummon.c):

  • Initializes GPIOs (RST output, BUSY input) and I²C bus
  • Initializes display (power on, boost, temperature compensation)
  • Clears display (full refresh cycle)
  • Reads internal CPU temperature, renders on top line as 188.8°C with correct decimal point (f[4] bit5) and leading "1" (f[0])
  • Renders fixed humidity sample on bottom line as 88.8% (tens/ones/tenths
    • decimal f[8] bit5 + percent sign f[10] bit5), updates every 1s
  • Prints status via ESP_LOG

✅ Hardware bringup (confirmed working)

  • ESP32-H2 Zero detected as rev v1.2 at 96 MHz
  • Flashing via USB-Serial/JTAG works at 460800 baud
  • Display driver (IST7134 via I²C at 0x3C/0x3D) works:
    • I²C communication succeeds, BUSY handshake works
    • Display clears properly
    • Digits 0–9 show correctly in sequence
    • All-black font pattern renders
    • Deep sleep mode works without errors
  • No hardware issues — wiring, level shifting, LDO all correct

Flashing (requires serial connected)

cd /src/firmware && nix develop /src --no-write-lock-file --command idf.py -p <PORT> flash monitor

Or via the flake app:

nix run .#flash

Hardware Info

ESP32-H2 Zero (Waveshare)

  • SoC: ESP32-H2 (single-core RISC-V 32-bit, up to 96 MHz)
  • Radio: IEEE 802.15.4 (Thread, Zigbee) + Bluetooth 5 (LE)
  • Flash: 4 MB
  • PSRAM: Not present on this board
  • Schematic: datasheets/ESP32-H2-Zero-Schematic.pdf
  • Datasheet: datasheets/Esp32-h2_datasheet_en.pdf
  • TRM: datasheets/Esp32-h2_technical_reference_manual_en.pdf
  • Wiki: https://docs.waveshare.com/ESP32-H2-Zero

1.9" Segment E-Paper Module (Waveshare)

Module board features

  • NDC7002N dual N-channel MOSFETs for level shifting
  • RT9193-33 LDO (3.3V regulator)
  • AO3401 P-MOSFET power switch (on by default without external EN)
  • Two 6-pin male headers: SDA, SCL, RST, BUSY, VCC, GND
  • Supports 3.3V or 5V input

Other components

  • WS2812B RGB LED: datasheets/XL-0807RGBC-WS2812B.pdf (on-board, on GPIO8)

Next Steps (TODO)

  • Board bringup — flashed and tested the display driver on real hardware
  • Port Waveshare digit lookup table — all 10 digits (0–9) verified working on the display
  • Internal temperature sensor — reads ESP32-H2 internal temp sensor, shows on display line 1, updates every 1s
  • Find decimal point & % bits — probing session mapped them: f[4]/f[8] bit5 = decimals, f[10] bit5 = %
  • [~] Power/Bluetooth icons — single-bit probe found no standalone icon bits; likely multi-bit patterns or unpopulated (cosmetic, deprioritized)
  • Add humidity sensor — wire up an external I²C sensor (SHT40, BME280, etc.) and show on display line 2
  • Configure Matter/Thread — enable OpenThread / Matter components in sdkconfig
  • Implement Matter cluster — temperature and humidity measurement clusters
  • Implement low-power operation — deep sleep between measurements

Flake Nix Apps

The flake defines these convenience apps (nix run .#<name>):

App Description
build Build firmware (idf.py build)
flash Flash to auto-detected serial port
monitor Open serial monitor
menuconfig Open idf.py menuconfig
clean idf.py clean
full Build → Flash → Monitor pipeline
run-agent Run pi agent container
build-agent Build pi agent container image

Environment Notes

  • Container: Debian-based (from Containerfile), runs as root
  • Working directory: /src (mounted volume)
  • Nix is installed but NOT in default PATH — must use /root/.nix-profile/bin/nix
  • .envrc contains use flake (for direnv, not active in this environment)
  • direnv state: .direnv/ directory exists