169 lines
8.3 KiB
Markdown
169 lines
8.3 KiB
Markdown
# AutoFilm-ESP32 — Current State
|
||
|
||
Snapshot of the pre-refactor PlatformIO Arduino tree (`9186d84`).
|
||
Integration going forward is **`develop`**; this refactor lands via PR from `refactor/esp-idf-modular-ui` into `develop`.
|
||
This document describes the tree **as it existed at that snapshot**, before the ESP-IDF modular refactor. Recipe *values* (times, temperatures, rotation counts) are treated as frozen; this document does not propose changing them.
|
||
|
||
## What the product is
|
||
|
||
AutoFilm-ESP32 is a Kindermann-style rotary film processor controller. The operator picks a process (C41, E6, B&W, ECN-2, Custom, B&WREV), walks the chemical steps, starts a timed agitation cycle, and is prompted to pour / drain between steps. Temperature is *measured* only; closed-loop bath heat is an external sous-vide.
|
||
|
||
## Toolchain and layout
|
||
|
||
| Item | Today |
|
||
| --- | --- |
|
||
| Build | PlatformIO `platformio.ini`, env `esp32dev` |
|
||
| Framework | Arduino on Espressif32 |
|
||
| Target | Classic ESP32-WROOM (`board = esp32dev`) |
|
||
| Layout | `src/*.cpp`, `include/*.h`, Arduino-style `setup()` / `loop()` |
|
||
| Entry | `src/AutoFilmESP32.cpp` |
|
||
|
||
Arduino libraries (all treated as future debt):
|
||
|
||
- `AccelStepper` — STEP/DIR stepper
|
||
- `LiquidCrystal_I2C` — 20×4 character LCD
|
||
- `Keypad` — matrix scan
|
||
- `OneWire` + `DallasTemperature` — DS18B20
|
||
|
||
There is **no** ESP-IDF `CMakeLists.txt`, no `sdkconfig`, no component split, no CI matrix.
|
||
|
||
## Hardware as wired in this tree
|
||
|
||
From `include/config.h` and `src/config.cpp`:
|
||
|
||
| Function | GPIO / bus |
|
||
| --- | --- |
|
||
| Stepper STEP (pulse) | 12 |
|
||
| Stepper DIR | 14 |
|
||
| Stepper EN (active LOW) | 27 |
|
||
| DS18B20 data | 13 |
|
||
| Beeper | 25 |
|
||
| LCD I²C | SDA 21, SCL 22, address `0x27`, 20×4 |
|
||
| Keypad rows | 19, 18, 5, 17, 16 |
|
||
| Keypad cols | 15, 2, 0, 4 |
|
||
|
||
Motor constants: `STEPS_PER_REV = 4800`, `RPM = 60`, acceleration `9600`. Enable is driven HIGH at boot (disabled).
|
||
|
||
Keypad map (5×4):
|
||
|
||
```
|
||
F E # *
|
||
1 2 3 U
|
||
4 5 6 D
|
||
7 8 9 X ← 4 across, 4 down = Esc ('X')
|
||
L 0 R E
|
||
```
|
||
|
||
Keys `1`–`6` select programmes. `U`/`D` browse steps. `L`/`R` nudge step time ±5 s. `E` confirm. `X` escape.
|
||
|
||
## Module map
|
||
|
||
```
|
||
AutoFilmESP32.cpp setup/loop, LCD splash, watchdog off
|
||
config.cpp / .h globals, pin map, library objects
|
||
devSequence.cpp/.h six compiled-in recipes
|
||
menu.cpp / .h blocking menus + startProcessing()
|
||
display.cpp / .h starting menu, headings, mm:ss helper
|
||
temperature.cpp/.h DS18B20 read + LCD write
|
||
motor.cpp / .h FreeRTOS agitation task
|
||
sound.cpp / .h blocking tone() tunes
|
||
watchdog.h disables TWDT and idle-task WDT
|
||
```
|
||
|
||
There is no HAL. UI, recipes, and actuators share globals (`devPgm`, `run`, `lcd`, `stepper`, `keypad`, task handles).
|
||
|
||
## Recipe data
|
||
|
||
`struct devSequence` (`include/devSequence.h`):
|
||
|
||
- `processName[7]`
|
||
- `cycles` — number of steps
|
||
- `processTime[20]` — seconds
|
||
- `processCycleName[20][10]`
|
||
- `processCycle[2][20]` — `[0]` CW revolutions, `[1]` CCW revolutions
|
||
- `processTemp[3][20]` — `[0]` min, `[1]` preferred, `[2]` max °C
|
||
|
||
`NUM_DEV_SEQUENCES = 6`. Lookup is linear `strcmp` in `findSequenceByName()`.
|
||
|
||
### Compiled defaults (do not change values)
|
||
|
||
**C41** — 7 steps, ~38 °C developer:
|
||
|
||
| Step | Time s | Name | CW | CCW | Min | Pref | Max |
|
||
| --- | ---: | --- | ---: | ---: | ---: | ---: | ---: |
|
||
| 0 | 180 | Prewarm | 1 | 1 | 37.8 | 38 | 38.2 |
|
||
| 1 | 195 | Developer | 5.5 | 5 | 37.8 | 38 | 38.2 |
|
||
| 2 | 45 | Bleach | 5.5 | 5 | 32 | 38 | 38.2 |
|
||
| 3 | 180 | Fix | 5.5 | 5 | 32 | 38 | 38.2 |
|
||
| 4 | 60 | Rinse 1 | 3.5 | 3 | 32 | 38 | 38.2 |
|
||
| 5 | 60 | Rinse 2 | 3.5 | 3 | 32 | 38 | 38.2 |
|
||
| 6 | 30 | Fin Rinse | 1 | 1 | 32 | 38 | 38.2 |
|
||
|
||
**E6** — 12 steps (Preheat … Fin Rinse). FirstDev 360 s at 38 °C; ColorDev 360 s; Fin Rinse 30 s at 19–21 °C.
|
||
|
||
**ECN-2** — 9 steps. RemJet is a 0 s / 0 rotation placeholder (manual). Developer 210 s at 40.8–41.2 °C.
|
||
|
||
**B&W** — 7 steps. Developer 510 s at 19–21 °C.
|
||
|
||
**Custom** — 4×10 s stub (Developer / Stop / Fix / Rinse).
|
||
|
||
**B&WREV** — 12 steps. FirstDev 720 s; SecondDev 360 s; wash steps cooler (min 15.5 °C).
|
||
|
||
Left/right time edits mutate the in-RAM struct. They are **not** written to flash. Process names longer than 6 characters would not fit `processName[7]`.
|
||
|
||
## Runtime behaviour
|
||
|
||
1. `setup()`: Serial 115200, **watchdogs disabled**, I²C + LCD init, custom thermometer glyph, motor disabled, splash `AUTOFILM` + `V0.1.1 20240701` for 1 s.
|
||
2. `loop()` calls `startingMenu()` and never returns to a real idle loop while a programme is selected.
|
||
3. `startingMenu()` draws 1–6 and **busy-waits** until `devPgm != ""`.
|
||
4. `startDev()` walks `cycles`. If `run == 1` it chains `startProcessing()` for the next step (auto-advance after a completed step). Otherwise it shows Step / Time / Temp and waits for U/D/E/X/L/R.
|
||
5. `startProcessing()`:
|
||
- Shows step name, duration, preferred temp, `Ent:start Esc:quit`.
|
||
- Esc (`X`) here aborts **before** motion and returns 0.
|
||
- On Enter, records `processStartTime` / `processTimeMillis`, heap-allocates `MotorTaskParams`, and `xTaskCreatePinnedToCore(runMotorTask, …, core 0)`.
|
||
- Then **blocks** in `while (millis() < end) { update remaining; delay(410); readTemperature(); }`.
|
||
- Deletes the motor task, drives EN HIGH, plays a **blocking 10-beep alarm** (~7.5 s), frees params.
|
||
|
||
`runMotorTask` enables the driver and loops forever: CW `cwRotations` revs, then CCW `ccwRotations` revs, using `stepper.run()` busy-wait on each move. `processEndTime` is passed in and **never read**. The task does not check a stop flag. It only ends when the UI task `vTaskDelete`s it.
|
||
|
||
Temperature: `sensors.requestTemperatures()` + `getTempCByIndex(0) + 0.4 °C` offset, written to LCD row 3. Called from the UI wait loops. `updateTempDisplay(void *)` exists as a one-shot task that also talks to the LCD and then deletes itself; it is unused from `startProcessing()`.
|
||
|
||
Sound: `tone()` + `delay()` on the caller thread. `playTune()` is unused by the main flow.
|
||
|
||
## Concurrency as it stands
|
||
|
||
| Thread | Role | Problem |
|
||
| --- | --- | --- |
|
||
| Arduino `loopTask` | All UI, all input, all timing, temp, sound | Blocks for seconds to minutes |
|
||
| `MotorTask` (core 0) | Agitation | No cooperative stop; killed from outside |
|
||
| Idle 0/1 | — | Removed from TWDT |
|
||
|
||
LCD, `stepper`, and `sensors` are used without mutexes. That is safe only because the UI thread does not command the stepper while the motor task runs — except `vTaskDelete` can interrupt `stepper.run()` mid-pulse.
|
||
|
||
## Defects and hazards relevant to the refactor
|
||
|
||
1. **No stop while a step is running.** Esc is honoured only on the pre-start prompt. The run loop does not read the keypad. This violates the product rule that the machine must always stop on Esc.
|
||
2. **`vTaskDelete` of a live motor task** can leave STEP/DIR in an undefined state; EN is dropped only *after* delete.
|
||
3. **Watchdog deliberately off** (`include/watchdog.h`). A hung `stepper.run()` or OneWire slot will wedge the chip with no recovery.
|
||
4. **Busy-wait input** (`while (true)` + `keypad.getKey()`). `millis() % 1000 == 0` is not a 1 Hz tick; it is a one-millisecond race and can skip or repeat temp reads.
|
||
5. **`secondsToMinutesSeconds()` `malloc`s 6 bytes and never frees.** Called from the run loop ~twice per second.
|
||
6. **`startDev()` shadows the global** with `int run = 1;` on Enter, so the auto-advance path is inconsistent with the `run` flag used after `startProcessing()` returns.
|
||
7. **OneWire conversion blocks the UI** (typically 750 ms at 12-bit). Combined with `delay(410)` the remaining-time display stutters.
|
||
8. **Alarm after a step blocks everything** for several seconds; Esc cannot interrupt it.
|
||
9. **No input abstraction.** Touch cannot be added without rewriting `menu.cpp`.
|
||
10. **Board pins are compile-time literals.** The Guition S3 panel reuses many of these GPIOs for QSPI / GT911; the current map cannot be copied across.
|
||
|
||
## What is *not* in the tree
|
||
|
||
- ESP-IDF project or dual-firmware CI
|
||
- Touch / colour LCD support
|
||
- Pause / resume of a live step
|
||
- Persistent profiles (README already lists this as future)
|
||
- Pumps, valves, reservoirs, drain actuators
|
||
- Closed-loop heater control
|
||
- Unit tests beyond PlatformIO’s empty `test/README`
|
||
|
||
## README vs code
|
||
|
||
`README.MD` matches the hardware story (ESP32, NEMA-17, DS18B20, 2004 I²C, Kindermann tank) and the pour-start-drain loop. It still talks about opening the project in Arduino IDE / PlatformIO and does not mention FreeRTOS, the S3 panel, or stop-during-run.
|