diff --git a/examples/led-matrix-painter/README.md b/examples/led-matrix-painter/README.md index 8f524a9..3ef18f7 100644 --- a/examples/led-matrix-painter/README.md +++ b/examples/led-matrix-painter/README.md @@ -1,26 +1,29 @@ # LED Matrix Painter -The **LED Matrix Painter** example provides a web-based tool for designing frames and animations for the Arduino UNO Q 8×13 LED matrix. You can create individual frames, organize them into animations, preview them in real-time on the board, and export them as C++ code. +The **LED Matrix Painter** example provides a web-based interface to draw, animate, and control the built-in LED Matrix of the Arduino UNO Q in real-time. It features a pixel editor with 8-bit brightness control, database storage for your designs, and a code generator to export your frames as ready-to-use C++ code. -![LED Matrix Painter](assets/docs_assets/led-matrix-painter.png) +![LED Matrix Painter Example](assets/docs_assets/thumbnail.png) ## Description -This example allows you to design LED matrix frames using an interactive web interface. Each frame can have custom brightness levels (0-7) for each LED pixel. You can: +This App allows you to design visuals for the 8x13 LED matrix directly from your browser. It uses the `web_ui` Brick to host a graphical editor where you can paint individual pixels, adjust their brightness, and apply transformations like flipping or rotating. Every change you make in the browser is immediately reflected on the physical board. -- **Design frames** with pixel-by-pixel control using an interactive grid -- **Preview in real-time** on the Arduino UNO Q LED matrix -- **Save and organize** multiple frames with a persistent database -- **Create animations** by sequencing multiple frames together -- **Export code** as C++ arrays ready to use in your Arduino sketches -- **Transform frames** with operations like invert, rotate, and flip +The application uses the `dbstorage_sqlstore` Brick to automatically save your work in a local database. You can create multiple frames, organize them into animations, and use the "Code panel" to see the generated C++ code in real-time. -The application uses the Router Bridge to communicate between the web interface (running on Linux) and the Arduino sketch (running on the microcontroller), enabling real-time updates to the physical LED matrix. +Key features include: +- **Real-time Control:** Drawing on the web grid updates the UNO Q matrix instantly. +- **Grayscale Control:** 8 brightness presets (0-7) for intuitive pixel control, with full 8-bit precision (0-255) supported at the hardware level. +- **Persistent Storage:** Frames are automatically saved to a database, allowing you to build complex animations over time. +- **Transformation Tools:** Invert, rotate, or flip designs with a single click. +- **Animation Mode:** Sequence frames to create animations and preview them on the board. +- **Code Export:** Generate `uint32_t` arrays compatible with the `Arduino_LED_Matrix` library for use in standalone sketches. ## Bricks Used -- `web_ui`: Provides the web server and HTTP API endpoints for the interactive frame designer interface. -- `dbstorage_sqlstore` (implicit): Stores frame data persistently in a SQLite database. +The LED Matrix Painter example uses the following Bricks: + +- `web_ui`: Brick to create the interactive grid editor and manage API endpoints. +- `dbstorage_sqlstore`: Brick to persist frames and animation sequences using a SQLite database. ## Hardware and Software Requirements @@ -35,127 +38,152 @@ The application uses the Router Bridge to communicate between the web interface ## How to Use the Example -1. Launch the App by clicking the **Play** button in the top-right corner. Wait until the App has launched. -2. **Design a frame:** - - Click on individual pixels in the 8×13 grid to toggle them on/off - - Use the brightness slider to adjust LED intensity (0-7) - - Click on pixels with different brightness values to paint with varying intensities -3. **Save your frame:** - - Click the **Save Frame** button to persist the current design - - Frames are automatically saved to a database and appear in the sidebar -4. **Create animations:** - - Save multiple frames - - Switch to **Animations** mode in the sidebar - - Create a new animation and add frames to it - - Use **Play** to preview the animation on the board -5. **Export code:** - - Select the frames or animations you want to export - - Click **Export** to generate C++ code - - Copy the generated arrays into your Arduino sketch - -## How it Works - -The LED Matrix Painter consists of three main components working together: - -- **Web Interface (Frontend)**: An interactive grid editor built with HTML/CSS/JavaScript that sends pixel data to the backend via HTTP API calls. - -- **Python Backend**: Handles frame storage in a SQLite database, manages frame transformations, and communicates with the Arduino via Router Bridge. - -- **Arduino Sketch**: Receives frame data over Router Bridge and displays it on the physical LED matrix using the `Arduino_LED_Matrix` library. - -High-level data flow: - -``` -Web Browser → HTTP API → Python Backend → Router Bridge → Arduino LED Matrix - ↓ - SQLite Database -``` - -The workflow: -1. User edits a frame in the web interface -2. Changes are sent to Python backend via HTTP POST -3. Backend validates and stores the frame in SQLite -4. Backend sends frame data to Arduino via Bridge -5. Arduino sketch renders the frame on the LED matrix - -## Understanding the Code - -### 🔧 Backend (`main.py`) - -The Python application manages the HTTP API, database operations, and communication with the Arduino. - -- **`designer = FrameDesigner()`**: Initializes the frame designer utility from `arduino.app_utils`, which provides transformation operations (invert, rotate, flip). - -- **`store.init_db()`**: Creates the SQLite database and tables for storing frames if they don't exist. +1. **Run the App** + Launch the example by clicking the **Run** button from Arduino App Lab. -- **`ui.expose_api('POST', '/persist_frame', persist_frame)`**: Exposes an HTTP endpoint that saves or updates frames in the database. +2. **Access the Editor** + Open the App in your browser at `:7000`. -- **`ui.expose_api('POST', '/load_frame', load_frame)`**: Loads a frame from the database by ID or retrieves the last edited frame. +3. **Draw Frames** + - **Paint:** Click any cell in the central grid to turn it on. + - **Adjust Brightness:** Click an active cell again (or hover/wait) to open the floating slider and set the brightness level (0-7). + - **Preview:** Observe the UNO Q; the matrix updates instantly as you draw. -- **`ui.expose_api('GET', '/list_frames', list_frames)`**: Returns all saved frames for display in the sidebar. +4. **Use the Design Tools** + - **Transform:** Use the **Tools** panel on the left to **Flip Vertically/Horizontally**, **Rotate 180°**, **Invert Matrix** (negative), or **Invert Draw** (brightness). + - **Clear:** Use the **Clear Frame** button above the grid to reset the canvas. -- **`ui.expose_api('POST', '/play_animation', play_animation)`**: Sends a sequence of frames to the Arduino to play as an animation. +5. **Manage Frames (Bottom Panel)** + - **Auto-save:** Your work is saved to the database automatically. + - **Create:** Click the **+** button to add a new empty frame. + - **Edit Details:** Assign a **Name** and **Duration** (in milliseconds) for each frame using the inputs above the frame list. + - **Reorder:** Drag and drop frame thumbnails to change their sequence. + - **Load/Delete:** Use the **Load** and **Del** buttons on each thumbnail to switch between frames or remove them. -- **`ui.expose_api('POST', '/export_frames', export_frames)`**: Generates C++ code arrays from selected frames for use in Arduino sketches. +6. **Create Animations** + - Switch the mode to **Animations** using the radio buttons in the bottom panel. + - Select multiple frames by clicking on their thumbnails (they will highlight). + - Click the **Play Animation** button below the grid to preview the sequence on the board. -- **`Bridge.call("draw", frame_bytes)`**: Sends frame data to the Arduino sketch to update the LED matrix display. +7. **Export Code** + - Toggle the **Code panel** switch in the top right header to view the C++ code for the current frame or animation in real-time. + - Click the **Export .h** button to download a header file containing your selected designs, ready to be included in an Arduino sketch. -- **`AppFrame` class**: Custom extension of `arduino.app_utils.Frame` that adds metadata like frame name, position, duration, and database persistence methods. - -### 💻 Frontend (`app.js` + `index.html`) - -The web interface provides an interactive pixel grid editor and frame management tools. - -- **Pixel Grid**: An 8×13 canvas that responds to mouse clicks and drag operations for painting pixels. - -- **Brightness Control**: A slider (0-7) that controls the current brush brightness level. - -- **Frame List**: Displays all saved frames in a sidebar with options to load, delete, or reorder them. - -- **Animations Mode**: Allows creating and managing animation sequences by dragging frames into animation containers. +## How it Works -- **Transform Tools**: Buttons for applying transformations (invert, rotate 180°, flip horizontal/vertical). +The LED Matrix Painter relies on a synchronized data flow between the browser, the Python backend, and the hardware. -- **Export Modal**: Generates and displays C++ code for selected frames or animations. +**High-level data flow:** -- **HTTP API calls**: Uses `fetch()` to communicate with the Python backend for all frame operations (save, load, delete, transform, export, play). +``` +Web Browser ──► HTTP API ──► Python Backend ──► Router Bridge ──► Arduino Sketch + │ │ + ▼ ▼ + SQLite Database LED Matrix Display +``` -### 🔧 Hardware (`sketch.ino`) +1. **Web Interface**: The `app.js` script captures clicks on the grid. It debounces these events and sends the pixel data to the backend via the `/persist_frame` endpoint. +2. **Python Backend**: + * **Data Model**: The `AppFrame` class normalizes the data, converting between frontend JSON, database records, and hardware byte arrays. + * **Persistence**: The `store.py` module uses `SQLStore` to save the frame data to a `frames` table in a SQLite database. + * **Bridge**: The `main.py` script sends the raw byte array to the board via `Bridge.call("draw", frame_bytes)`. +3. **Arduino Sketch**: The sketch receives the raw byte data and uses the `Arduino_LED_Matrix` library to render the grayscale image. -The Arduino code handles LED matrix control and Router Bridge communication. +## Understanding the Code -- **`matrix.begin()`**: Initializes the Arduino_LED_Matrix library for controlling the UNO Q LED matrix. +### 🔧 Backend (`main.py`, `store.py` & `app_frame.py`) + +The Python backend manages the application logic, database, and hardware communication. + +- **Data Model (`app_frame.py`)**: The `AppFrame` class is the core data structure that acts as a bridge between the different components. It extends the base `Frame` class to add application-specific metadata like `id`, `name`, `position`, and `duration`. It handles three distinct data contracts: + - **API Contract**: `to_json()` / `from_json()` formats data for the web frontend. + - **Database Contract**: `to_record()` / `from_record()` formats data for `SQLStore` storage. + - **Hardware Contract**: `to_board_bytes()` packs pixels into the specific byte format expected by the Arduino sketch. + +```python +class AppFrame(Frame): + def to_record(self) -> dict: + """Convert to a database record dict for storage.""" + return { + "id": self.id, + "name": self.name, + "rows": json.dumps(self.arr.tolist()), # Serialize pixels to JSON string + "brightness_levels": int(self.brightness_levels), + # ... + } +``` -- **`matrix.setGrayscaleBits(8)`**: Configures the matrix to accept 8-bit brightness values (0-255) for each pixel. +- **Initialization**: + - `designer = FrameDesigner()`: Initializes the frame designer utility from `arduino.app_utils`, which provides the logic for transformation operations (invert, rotate, flip). + - `store.init_db()`: Creates the SQLite database and tables for storing frames if they don't exist. + +- **API Endpoints**: The backend exposes several HTTP endpoints using `ui.expose_api` to handle frontend requests: + - `POST /persist_frame`: Saves or updates frames in the database and updates the board. + - `POST /load_frame`: Loads a specific frame by ID or retrieves the last edited frame. + - `GET /list_frames`: Returns all saved frames to populate the bottom panel. + - `POST /play_animation`: Sends a sequence of frames to the Arduino to play as an animation. + - `POST /transform_frame`: Applies geometric transformations to the pixel data. + - `POST /export_frames`: Generates the C++ header file content. + +- **Hardware Update**: The `apply_frame_to_board` function sends the visual data to the microcontroller via the Bridge. + +```python +# main.py +def apply_frame_to_board(frame: AppFrame): + """Send frame bytes to the Arduino board.""" + frame_bytes = frame.to_board_bytes() + Bridge.call("draw", frame_bytes) +``` -- **`Bridge.begin()`**: Initializes Router Bridge for receiving commands from the Python application. +- **Code Generation**: The `AppFrame` class generates the C++ code displayed in the UI. It formats the internal array data into `uint32_t` hex values. -- **`Bridge.provide("draw", draw)`**: Registers the `draw` function to be callable from Python, which accepts frame data and renders it on the matrix. +```python +# app_frame.py +def to_c_string(self) -> str: + c_type = "uint32_t" + parts = [f"const {c_type} {self.name}[] = {{"] + # Converts pixel brightness data to uint32_t hex format + parts.append("};") + return "\n".join(parts) +``` -- **`Bridge.provide("play_animation", play_animation)`**: Registers the animation playback function that accepts multiple frames and plays them sequentially. +### 🔧 Arduino Component (`sketch.ino`) -- **`matrix.draw(frame.data())`**: Renders a single frame on the LED matrix using raw byte data. +The sketch is designed to be a passive renderer, accepting commands from the Python backend. -- **`matrix.loadWrapper()` + `matrix.playSequence()`**: Loads an animation sequence and plays it on the LED matrix. +- **Grayscale Setup**: The matrix is initialized with 8-bit grayscale support to allow for varying brightness levels. -## Frame Data Format +```cpp +void setup() { + matrix.begin(); + // configure grayscale bits to 8 so the display can accept 0..255 brightness + matrix.setGrayscaleBits(8); + Bridge.begin(); + // ... +} +``` -Frames are stored as 8×13 arrays where each value represents LED brightness (0-255): +- **Drawing**: The `draw` provider accepts a vector of bytes and renders it directly. ```cpp -// Example frame in C++ format -const uint8_t frame[][12] = { - {255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255}, - {0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0}, - // ... 6 more rows -}; +void draw(std::vector frame) { + matrix.draw(frame.data()); +} ``` -For animations, frames are stored as `uint32_t` arrays compatible with the Arduino_LED_Matrix library: +### 🔧 Frontend (`app.js`) -```cpp -const uint32_t animation[][5] = { - {0x12345678, 0x9abcdef0, 0x12345678, 0x9abcdef0, 1000}, // Frame 1, 1000ms duration - {0xfedcba98, 0x76543210, 0xfedcba98, 0x76543210, 1000}, // Frame 2, 1000ms duration -}; +The JavaScript frontend handles the UI logic and data synchronization. + +- **Auto-Persist**: To provide a responsive experience, changes are saved automatically after a short delay (debounce), updating both the database and the board simultaneously. + +```javascript +// Unified persist: save to DB and update board together +function schedulePersist(){ + if (persistTimeout) clearTimeout(persistTimeout); + persistTimeout = setTimeout(()=> { + persistFrame(); + persistTimeout = null; + }, AUTO_PERSIST_DELAY_MS); +} ``` diff --git a/examples/led-matrix-painter/assets/docs_assets/thumbnail.png b/examples/led-matrix-painter/assets/docs_assets/thumbnail.png new file mode 100644 index 0000000..6d90cff Binary files /dev/null and b/examples/led-matrix-painter/assets/docs_assets/thumbnail.png differ