Skip to content

[Feature] Port the Sign firmware to ESP32-native FreeRTOS #48

Description

@RomanAlexandroff

Summary

Migrate the Smart Cluster Sign firmware from its current predominantly sequential Arduino architecture to an explicit FreeRTOS-based architecture while retaining the Arduino framework.

The ESP32 already runs FreeRTOS underneath the Arduino framework. The current firmware, however, relies primarily on the Arduino setup()/loop() programming model and does not explicitly organize the application's subsystems into independent FreeRTOS tasks.

The goal of this task is to take advantage of the concurrency and synchronisation primitives provided by FreeRTOS while preserving the existing Arduino-based development environment and functionality.

This task does not include migrating from Arduino to ESP-IDF.


Motivation

The firmware has grown into a relatively complex system consisting of multiple independent subsystems, including:

  • Wi-Fi connectivity
  • Internet connectivity monitoring
  • OTA update handling
  • GitHub-based firmware retrieval
  • Telegram functionality
  • e-paper display management
  • display update scheduling
  • power/sleep management
  • device state management
  • various timers and periodic operations

The current architecture handles much of this logic through sequential execution.

As the firmware continues to grow, this can make the control flow increasingly difficult to understand and maintain. Slow operations, particularly e-paper display updates and network operations, can also interfere with unrelated parts of the application.

FreeRTOS provides a natural way to express the actual concurrency already present in the system.


Target Architecture

The target architecture should remain based on the Arduino framework:

Arduino Framework
        │
        ▼
     FreeRTOS
        │
        ├── System / State Task
        ├── Network Task
        ├── Display Task
        ├── OTA Task
        └── Telegram Task

The exact task structure should be determined during implementation. The above is an architectural direction rather than a strict requirement that every subsystem must become its own task.

The objective is clear ownership and well-defined communication between subsystems, rather than simply creating as many tasks as possible.


Important Architectural Boundary

This migration should use Arduino + explicit FreeRTOS. It should not migrate the project to ESP-IDF.

The existing Arduino ecosystem should remain available, including where appropriate:

  • Arduino.h
  • Arduino Wi-Fi APIs
  • existing Arduino-compatible libraries
  • existing peripheral libraries
  • existing OTA infrastructure
  • existing filesystem functionality

The project should continue to be buildable using the existing Arduino/arduino-cli toolchain.

A future ESP-IDF migration may be considered as a separate project, but it is explicitly outside the scope of this task.


Proposed Tasks

The following task structure should be investigated.

Display Task

Responsible for e-paper display operations.

Potential responsibilities:

  • receiving display update requests
  • preparing display content
  • performing e-paper updates
  • handling the display's BUSY state
  • managing display-related timing

The display task should be able to block while waiting for the e-paper hardware without unnecessarily blocking unrelated system functionality.


Network Task

Responsible for network connectivity.

Potential responsibilities:

  • Wi-Fi connection
  • Wi-Fi reconnection
  • connection state monitoring
  • Internet availability
  • network-related events

The network subsystem should expose its state to other tasks through a well-defined interface rather than requiring every subsystem to independently inspect Wi-Fi state.


OTA Task

Responsible for OTA update operations.

Potential responsibilities:

  • periodically checking for updates
  • retrieving the OTA manifest
  • determining whether an update is available
  • downloading firmware
  • validating firmware integrity
  • initiating OTA installation
  • handling OTA failures
  • coordinating reboot after successful installation

OTA operations should not unnecessarily block display or other independent application functionality.


Telegram Task

If Telegram functionality remains part of the firmware, investigate separating its processing into its own task.

Potential responsibilities:

  • checking for incoming commands
  • processing commands
  • triggering system events
  • communicating requested actions to other tasks

Telegram functionality should communicate with other subsystems through explicit interfaces rather than directly manipulating their internal state.


System / State Task

A central system-level task may be introduced to coordinate high-level device state.

Potential responsibilities:

  • coordinating major system events
  • managing device operating states
  • coordinating sleep/wake transitions
  • coordinating OTA and reboot states
  • initiating display updates
  • handling interactions between subsystems

This should not become another giant centralised function containing all application logic.

Its purpose should be coordination rather than accumulation of business logic.


Inter-Task Communication

The migration should make use of appropriate FreeRTOS synchronization and communication mechanisms.

Investigate the use of:

  • Queues for passing data or requests between tasks.
  • Task notifications for lightweight task-to-task signaling.
  • Event groups for representing system-wide state and events.
  • Mutexes for protecting shared resources.
  • Semaphores where appropriate for synchronisation.
  • Software timers for periodic operations.

The appropriate primitive should be selected according to the communication requirement rather than using one mechanism everywhere.


Resource Ownership

The architecture should establish clear ownership of shared resources.

Particular attention should be paid to:

  • SPI
  • e-paper display
  • Wi-Fi
  • filesystem
  • network clients
  • shared configuration/state
  • serial output

Tasks should avoid directly manipulating resources owned by another task.

For example, instead of allowing arbitrary tasks to access the e-paper driver:

OTA Task ──────┐
               │
Telegram Task ─┼──> Display Driver
               │
System Task ───┘

prefer:

OTA Task ──────┐
               │
Telegram Task ─┼──> Display Queue ──> Display Task ──> E-Paper
               │
System Task ───┘

This provides a clear ownership model and reduces the need for excessive locking.


State Machines

Where appropriate, major subsystems should be represented as explicit state machines.

For example, the network subsystem could have states such as:

DISCONNECTED
      │
      ▼
 CONNECTING
      │
      ▼
 CONNECTED
      │
      ▼
 INTERNET_OK

Similarly, OTA could have states such as:

IDLE
 │
 ▼
CHECKING
 │
 ▼
DOWNLOADING
 │
 ▼
VERIFYING
 │
 ▼
INSTALLING
 │
 ▼
REBOOT

The exact states should be determined during implementation.

The purpose is to make complex asynchronous behaviour explicit rather than allowing it to emerge from deeply nested conditional logic.


E-Paper Display Considerations

The e-paper display is particularly important in this migration because display operations can take a significant amount of time.

The new architecture should ensure that:

  • a display update does not block unrelated tasks,
  • the display's BUSY state is handled by the Display Task,
  • other tasks can continue operating while the display task waits,
  • multiple simultaneous display requests are handled deterministically,
  • unnecessary concurrent access to the display hardware is prevented.

The existing e-paper performance issues should be considered during the redesign, but optimizing the display driver itself is not necessarily part of this issue unless required by the new architecture.


Scheduling and Timing

Review existing timing mechanisms and determine which should become FreeRTOS mechanisms.

Potential candidates include:

  • periodic OTA checks
  • Wi-Fi monitoring
  • Telegram polling
  • display update scheduling
  • inactivity timers
  • sleep scheduling

Avoid replacing every delay() mechanically with a FreeRTOS API.

The goal is to identify operations that genuinely benefit from independent scheduling and asynchronous execution.


Error Handling

The new architecture should define how errors propagate between tasks.

Examples:

  • Wi-Fi failure
  • Internet unavailable
  • OTA download failure
  • checksum mismatch
  • display timeout
  • filesystem error
  • Telegram/network failure

Errors should not require unrelated tasks to inspect arbitrary global variables.

Where appropriate, errors should be represented as explicit events or messages.


Power Management

Review the existing sleep and power-management implementation as part of the migration.

Determine how FreeRTOS tasks should behave when:

  • the device is preparing to sleep,
  • the device is asleep,
  • the device is waking,
  • a wake-up event occurs.

Tasks should be able to terminate, suspend, or otherwise enter an appropriate low-activity state when the device does not need them.

Power consumption should not regress as a result of introducing additional tasks.


Task Priorities and Stack Sizes

Task priorities and stack sizes should be chosen deliberately.

Do not simply assign high priority to every task.

The implementation should consider:

  • task execution frequency
  • latency requirements
  • blocking operations
  • CPU usage
  • memory consumption
  • stack requirements
  • interaction with ESP32 system tasks

Task stack usage should be monitored during development to avoid stack overflows and unnecessary RAM consumption.


Avoid Over-Engineering

The migration should not turn the firmware into a collection of unnecessarily tiny tasks.

Avoid creating separate tasks for every small operation, such as:

wifi_task
wifi_check_task
wifi_timeout_task
wifi_reconnect_task
wifi_monitor_task

unless there is a genuine architectural reason for doing so.

The goal is clear subsystem boundaries, not maximum task count.


Migration Strategy

The migration should be performed incrementally rather than rewriting the entire firmware in one step.

A possible progression:

  1. Identify the current major subsystems.
  2. Identify shared state and shared resources.
  3. Define ownership boundaries.
  4. Introduce the first FreeRTOS task.
  5. Move one subsystem into an explicit task.
  6. Introduce appropriate inter-task communication.
  7. Verify functionality.
  8. Migrate the next subsystem.
  9. Repeat until the desired architecture is reached.
  10. Remove obsolete sequential control flow.
  11. Perform a full regression test on real hardware.

The firmware should remain buildable and testable throughout the migration wherever practical.


Testing

The migrated firmware must be tested on real ESP32 hardware.

At minimum, verify:

  • boot
  • Wi-Fi connection
  • Internet connectivity
  • e-paper display updates
  • Telegram functionality
  • OTA update checks
  • OTA firmware download
  • SHA-256 verification
  • firmware installation
  • rollback behaviour
  • sleep/wake behaviour
  • power consumption
  • recovery from network failures
  • recovery from display failures
  • long-running stability

Particular attention should be paid to race conditions and timing-dependent failures that may not occur during short manual tests.


Performance and Stability

Compare the new architecture against the current implementation.

Investigate:

  • CPU utilization
  • free heap
  • task stack high-water marks
  • boot time
  • display responsiveness
  • Wi-Fi responsiveness
  • OTA behaviour
  • power consumption
  • long-term stability

The migration should not introduce unacceptable memory or CPU overhead.


Documentation

Document the resulting FreeRTOS architecture after implementation.

Documentation should explain:

  • which tasks exist
  • what each task owns
  • task priorities
  • major queues/events
  • shared resources
  • synchronization mechanisms
  • important state machines
  • task interactions

A high-level architecture diagram should be added to the project documentation.


Out of Scope

The following are explicitly outside the scope of this issue:

  • Migrating from Arduino to ESP-IDF.
  • Rewriting the firmware in a different programming language.
  • Replacing all Arduino libraries.
  • Replacing the existing OTA architecture.
  • Reimplementing the e-paper driver solely for performance reasons.
  • Creating tasks merely for the sake of increasing concurrency.

A future Arduino → ESP-IDF migration may be considered independently.


Acceptance Criteria

The migration is considered complete when:

  • The firmware explicitly uses FreeRTOS tasks for its major independent subsystems.
  • The Arduino framework remains the project's application framework.
  • Inter-task communication uses appropriate FreeRTOS primitives.
  • Shared resources have clearly defined ownership.
  • Major asynchronous subsystems have explicit state management where appropriate.
  • Slow e-paper operations no longer unnecessarily block unrelated application functionality.
  • Existing firmware functionality remains operational.
  • OTA functionality continues to work correctly.
  • Rollback functionality continues to work correctly.
  • Power consumption does not regress unacceptably.
  • No task stack overflows or race conditions are observed during testing.
  • The firmware demonstrates stable long-term operation on real hardware.
  • The resulting architecture is documented.

Expected Result

The final firmware architecture should evolve from:

Arduino
   │
   └── FreeRTOS
         │
         └── Arduino loop()
               └── most application logic

toward something closer to:

Arduino Framework
        │
        ▼
     FreeRTOS
        │
        ├── System / State Task
        │
        ├── Network Task
        │
        ├── Display Task
        │
        ├── OTA Task
        │
        └── Telegram Task

with explicit communication and synchronization between the subsystems.

The objective is not simply to "use FreeRTOS", but to make the firmware's concurrency, state, timing, and subsystem boundaries explicit, understandable, and maintainable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    advancedOnly for those who is highly familiar with the code baseenhancementNew feature or requesthelp wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions