Files
AutoFilm-ESP32/docs/CURRENT_STATE.md
gronod d6a33996ce Document develop as the integration branch for PRs
Work is cut from develop and reviewed into develop, not main.
2026-09-16 10:50:49 +00:00

8.3 KiB
Raw Permalink Blame History

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 vTaskDeletes 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() mallocs 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.