### Build Firmware using Make Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Compile the firmware using the provided makefiles. Ensure Python is installed for post-build steps. The output includes ELF and binary files, along with CRC32 checksum. ```bash # Linux cd Firmware make # Windows cd Firmware makeit.exe -j12 # Expected output excerpt # text data bss dec hex filename # 75608 4604 25341 105553 19c51 ./out/ATC_Paper.elf # tl_fireware_tools.py v0.1 # Firmware CRC32: 0xe62d501e # 'Build complete: out/../ATC_Paper.bin' ``` -------------------------------- ### Firmware RTC Set and Get Time (C) Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Functions to set the device's internal Unix timestamp and calendar fields, and to retrieve current time for display rendering. `handler_time()` must be called periodically. ```C #include "etime.h" // Set time from BLE command 0xDD payload void on_time_command_received(uint32_t unix_ts, uint16_t year, uint8_t month, uint8_t day, uint8_t week) { set_time(unix_ts, year, month, day, week); // After this call, get_time() returns up-to-date calendar info } // Read current time for display rendering void render_clock_example(void) { struct date_time t = get_time(); // t.tm_hour, t.tm_min, t.tm_sec — 24-hour time // t.tm_year, t.tm_month, t.tm_day — calendar date // t.tm_week — ISO weekday (1=Mon … 7=Sun) char buf[16]; sprintf(buf, "%02d:%02d", t.tm_hour, t.tm_min); // "14:35" } // Periodic timer helper — returns 1 if have elapsed on channel ch void app_loop(void) { handler_time(); // must be called once per second if (time_reached_period(Timer_CH_0, 60)) { // triggers once per minute } } ``` -------------------------------- ### EPaperBLE.discover() — Scan for Nearby Devices Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Static method that performs a BLE scan and returns all devices whose name starts with `S24_`. ```APIDOC ## EPaperBLE.discover() — Scan for Nearby Devices Static method that performs a BLE scan and returns all devices whose name starts with `S24_`. ```python import asyncio from python_tools.myesl import EPaperBLE async def scan(): devices = await EPaperBLE.discover() if not devices: print("No EPaper devices found") return for d in devices: print(f"Name: {d.name} - Address: {d.address}") # Name: S24_A1B2C3 - Address: AA:BB:CC:DD:EE:FF asyncio.run(scan()) # CLI equivalent: # python python_tools/myesl.py -scan ``` ``` -------------------------------- ### EPaperBLE.discover() - Scan for Devices Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Perform a BLE scan to find nearby devices. This static method returns devices whose names start with 'S24_'. Handles cases where no devices are found. ```python import asyncio from python_tools.myesl import EPaperBLE async def scan(): devices = await EPaperBLE.discover() if not devices: print("No EPaper devices found") return for d in devices: print(f"Name: {d.name} - Address: {d.address}") # Name: S24_A1B2C3 - Address: AA:BB:CC:DD:EE:FF asyncio.run(scan()) # CLI equivalent: # python python_tools/myesl.py -scan ``` -------------------------------- ### Get Current Time and Date Information Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/index.html Retrieves the current Unix timestamp, locale-specific time, and date components (year, month, day, week day), adjusting for a user-specified hour offset. ```javascript function getUnixTime() { const hourOffset = document.getElementById('hour-offset').value; const unixNow = Math.round(Date.now() / 1000)+(60*60*hourOffset) - new Date().getTimezoneOffset() * 60; const date = new Date((unixNow + new Date().getTimezoneOffset() * 60)*1000); const localeTimeString = date.toLocaleTimeString(); return {unixNow, localeTimeString, year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate(), week: date.getDay() || 7} } ``` -------------------------------- ### Initialize and Control LED Color (C) Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Initializes the GPIO pins for the RGB LED and provides functions to set the LED color. Note that the LEDs are active-low, meaning a `0` turns the color on. ```c #include "led.h" #include "main.h" // Initialize GPIO directions for all three LED pins void init_led(void) { gpio_set_func(LED_BLUE, AS_GPIO); gpio_set_output_en(LED_BLUE, 1); gpio_write(LED_BLUE, 1); gpio_set_func(LED_RED, AS_GPIO); gpio_set_output_en(LED_RED, 1); gpio_write(LED_RED, 1); gpio_set_func(LED_GREEN, AS_GPIO); gpio_set_output_en(LED_GREEN, 1); gpio_write(LED_GREEN, 1); } ``` ```c // Usage: call from cmd_parser when command 0x81 is received void example_led_usage(void) { set_led_color(1); // Red — LED_RED=0, others=1 set_led_color(2); // Green — LED_GREEN=0, others=1 set_led_color(3); // Blue — LED_BLUE=0, others=1 set_led_color(4); // Yellow — LED_RED=0, LED_GREEN=0, LED_BLUE=1 set_led_color(5); // Aqua — LED_BLUE=0, LED_GREEN=0, LED_RED=1 set_led_color(6); // Magenta— LED_BLUE=0, LED_RED=0, LED_GREEN=1 set_led_color(7); // White — all=0 set_led_color(0); // Off — all=1 } ``` -------------------------------- ### SerialController Initialization Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/uart_flasher.html Initializes the serial controller by requesting a port from the user and opening it with specified baud rate. Handles potential errors during the process. ```javascript class SerialController { async init(init_cb) { if ('serial' in navigator) { try { connect.disabled = true; this.port = await navigator.serial.requestPort(); await this.port.open({baudRate: ubaud.value, baudrate: ubaud.value, bufferSi ``` -------------------------------- ### Upload Image via BLE (Python) Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Loads a local image file, resizes it, applies dithering, and streams it to the device in BLE chunks. Requires device address and image path. Supports different dithering modes. ```Python import asyncio from python_tools.myesl import EPaperBLE, DitherMode async def upload(): epaper = EPaperBLE() await epaper.connect("AA:BB:CC:DD:EE:FF") try: # Upload with Floyd-Steinberg dithering (best for photos) await epaper.upload_picture("my_image.png", DitherMode.FLOYDSTEINBERG) # Upload with no dithering (best for high-contrast graphics) # await epaper.upload_picture("logo.bmp", DitherMode.NONE) # Upload with Atkinson dithering (good balance) # await epaper.upload_picture("chart.jpg", DitherMode.ATKINSON) print("Image uploaded and display refreshed") finally: await epaper.disconnect() asyncio.run(upload()) ``` ```Shell # CLI equivalent: # python python_tools/myesl.py -address AA:BB:CC:DD:EE:FF \ # -uploadpicture my_image.png -dither floydsteinberg ``` -------------------------------- ### Initialize Bluetooth Variables Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/index.html Initializes global variables for Bluetooth device, GATT server, and services. Resets the log. ```javascript let bleDevice; let gattServer; let epdService; let rxtxService; let epdCharacteristic; let rxtxCharacteristic; let reconnectTrys = 0; function resetVariables() { gattServer = null; epdService = null; epdCharacteristic = null; rxtxCharacteristic = null; rxtxService = null; document.getElementById("log").value = ''; } ``` -------------------------------- ### File Selection and Validation for Firmware Update Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/ATC_TLSR_Paper_OTA_writing.html Handles the file selection process for firmware updates. It reads the file, validates if it's a Telink firmware (.bin) based on a magic number, and prepares the firmware data. ```javascript window.onload = function () { document.querySelector("#file").addEventListener("change", function () { var reader = new FileReader(); reader.onload = function () { firmwareArray = bytesToHex(this.result); if (firmwareArray.substring(16, 24) != "4b4e4c54") { alert("Select file is no telink firmware .bin"); addLog("Select file is no telink firmware .bin"); firmwareArray = ""; return; } addLog("File was selected, size: " + firmwareArray.length / 2 + " bytes"); document.getElementById("cmdIMAGE").value = firmwareArray; } if (thi ``` -------------------------------- ### Page Load Initialization and Event Listeners Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/index.html Sets up interval timers for updating the time display, initializes the canvas to a white background, and configures event listeners for mouse interactions on the canvas editor. ```javascript document.body.onload = () => { setInterval(() => { const { localeTimeString, year, month, day, week } = getUnixTime(); document.getElementById('time-setter').innerText = `Set the time to:${year}-${month}-${day} ${localeTimeString} Week ${week}`; }, 1000); const canvas = document.getElementById('canvas'); const ctx = canvas.getContext("2d"); ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); let is_allow_drawing = false; let is_allow_move_editor = false; const image_mode = document.getElementById('canvas-mode'); const paint_size = document.getElementById('paint-size'); const paint_color = document.getElementById('paint-color'); const editor = document.getElementById('edit-font'); const font = document.getElementById('font'); document.getElementById('dithering').value = 'bwr_Atkinson'; image_mode.value = 'paint'; paint_color.value = 'black'; font.value = 'Bold'; editor.onmousemove = function (e) { editor.style.fontSize = `${paint_size.value * 10}px`; editor.style.color = paint_color.value; editor.style.fontFamily = font.value; editor.style.fontWeight = 'bold'; if (is_allow_move_editor) { const {x, y} = get_position(canvas, e.clientX, e.clientY); if (x < 0 || y < 0 || x > canvas.width || y > canvas.height) { return; } e } } } ``` -------------------------------- ### Activate Device for Flashing Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/uart_flasher.html Prepares the device for flashing by resetting DTR/RTS, performing a soft MCU reset, and then activating the CPU stop mode for a specified duration. Use before flashing operations. ```javascript async function Activate(tim) { let blk = sws_wr_addr(0x0602, new Uint8Array([0x05])); // CPU stop let s = 'Reset DTR/RTS (100 ms)'; swrite.innerHTML = s; log(s); await serialController.reset(100); // DTR & RTS console.log('Soft Reset MCU'); await SoftResetMSU(); s = 'Activate ('+(tim/1000.0)+' sec)...'; swrite.innerHTML = s; log(s); let t = new Date().getTime(); while(new Date().getTime() - t < tim) { await serialController.write_raw(blk); // CPU stop } await serialController.write_raw(sws_wr_addr(0x00b2, new Uint8Array([55]))); // Set SWS Speed await serialController.write_raw(blk); return await FlashWakeUp(); } ``` -------------------------------- ### Synchronize RTC via BLE (Python) Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Reads the host system clock, applies an optional UTC hour offset, and sends a packed binary timestamp to the device. Triggers a display refresh afterward. Requires device address. ```Python import asyncio from python_tools.myesl import EPaperBLE async def sync_time(): epaper = EPaperBLE() await epaper.connect("AA:BB:CC:DD:EE:FF") try: await epaper.set_datetime(offset=2) # UTC+2 (e.g. CEST) # Packet sent: 0xDD + pack(">IHBBB", unix_ts, year, month, day, weekday) # Followed by: 0xE2 (force EPD flush) print("Date/time synchronized") finally: await epaper.disconnect() asyncio.run(sync_time()) ``` ```Shell # CLI equivalent: # python python_tools/myesl.py -address AA:BB:CC:DD:EE:FF -setdatetime 2 ``` -------------------------------- ### Flash Firmware via WebSerial UART Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Connect a CH340 USB-UART adapter to the label's test pads and use the browser-based flasher. Ensure correct wiring and baud rate. Unlock flash write protection on the first use. ```text Hardware wiring (Hanshow Stellar L3N@): CH340 GND → Label GND CH340 3.3V → Label VCC CH340 TX → Label RX CH340 RX → Label TX CH340 RTS → Label RTS (or short to GND manually before flashing) Flasher URL: https://atc1441.github.io/ATC_TLSR_Paper_UART_Flasher.html 1. Select baud rate: 460800 2. Load file: Firmware/ATC_Paper.bin 3. Click "Unlock" (first time only — unlocks flash write protection) 4. Click "Write" and wait for completion 5. Display refreshes automatically on success ``` -------------------------------- ### File Change Event Listener Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/uart_flasher.html Handles the file selection for firmware upload. Reads the file as an ArrayBuffer, validates it as a Telink firmware .bin file, and stores it in `firmwareArray`. Enables the flash writer button if connected. ```javascript window.onload = function() { document.querySelector("#file").addEventListener("change", function() { var reader = new FileReader(); reader.onload = function() { firmwareArray = this.result; log("File was selected, size: " + firmwareArray.byteLength + " bytes"); if(firmwareArray.byteLength < 16 || (new Uint32Array(firmwareArray.slice(8,12)))[0] != 0x544C4E4B){ log("Select file is no telink firmware .bin"); alert("Select file is no telink firmware .bin"); firmwareArray = null; return; } if(connect.opened) { fwriter.disabled = false; swrite.innerHTML = ''; } } if (this.files[0] != null) reader.readAsArrayBuffer(this.files[0]); else log("No file selected"); }, false); } ``` -------------------------------- ### BLE Device and GATT Server Initialization Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/ATC_TLSR_Paper_OTA_writing.html Initializes global variables for BLE device, GATT server, services, and characteristics. Used for managing BLE connections and data transfer. ```javascript let bleDevice; let gattServer; let Theservice; let ServiceMain; let settingsCharacteristics; let writeCharacteristic; let busy = false; let imgArray; let imgArrayLen = 0; let uploadPart = 0; let startTime = 0; let reconnectTrys = 0; ``` -------------------------------- ### OpenOCD Command Sequences Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/Firmware/tc32_linux/share/openocd/scripts/target/readme.txt These command sequences demonstrate expected behavior for reset and flash operations. Ensure your target configuration supports these operations. ```tcl reset flash info ``` ```tcl reset flash erase_address ``` ```tcl reset init load ``` -------------------------------- ### EPaperBLE Class - Connect to Device Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Initialize the EPaperBLE class and connect to a device using its MAC address. This class wraps the Bleak library for high-level async operations. Ensure disconnection after use. ```python import asyncio from python_tools.myesl import EPaperBLE async def main(): epaper = EPaperBLE() # Scan for nearby S24_ devices devices = await EPaperBLE.discover() for d in devices: print(f"Found: {d.name} @ {d.address}") # Found: S24_A1B2C3 @ AA:BB:CC:DD:EE:FF # Connect by address success = await epaper.connect("AA:BB:CC:DD:EE:FF") # Connected to AA:BB:CC:DD:EE:FF try: pass # use methods below finally: await epaper.disconnect() asyncio.run(main()) ``` -------------------------------- ### Flash Write and Verify Commands Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/Firmware/tc32_linux/share/openocd/scripts/target/readme.txt Use these commands for writing binary images to flash and verifying their integrity. Ensure write-protect mechanisms are disabled. ```tcl flash write_image [file] ``` ```tcl verify_image [file] ``` -------------------------------- ### Sending Image Data via BLE Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/ATC_TLSR_Paper_OTA_writing.html Prepares and sends image data over BLE. It first sends initialization commands (0000, 020000) and then proceeds to send image parts. ```javascript function sendimg(cmdIMG) { imgArray = cmdIMG.replace(/(?:\r\n|\r|\n|,|0x| )/g, ''); imgArrayLen = imgArray.length; uploadPart = 0; console.log('Sending image ' + imgArrayLen); sendCommand(hexToBytes("0000")).then(() => { sendCommand(hexToBytes("020000")).then(() => { sendIMGpart(); }) }) .catch(handleError); } ``` -------------------------------- ### Connect to Bluetooth Device Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/ATC_TLSR_Paper_OTA_writing.html Initiates the connection process to a Bluetooth device. It requests device access and handles connection events. Requires user interaction to select a device. ```javascript function preConnect() { if (gattServer != null && gattServer.connected) { if (bleDevice != null && bleDevice.gatt.connected) bleDevice.gatt.disconnect(); } else { connectTrys = 0; navigator.bluetooth.requestDevice({ optionalServices: ['0000221f-0000-1000-8000-00805f9b34fb', '00001f10-0000-1000-8000-00805f9b34fb', '13187b10-eba9-a3ba-044e-83d3217d9a38'], acceptAllDevices: true }).then(device => { device.addEventListener('gattserverdisconnected', disconnect); bleDevice = device; connect(); }).catch(handleError); } } ``` -------------------------------- ### Writing Firmware Data Part by Part Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/ATC_TLSR_Paper_OTA_writing.html Sends a portion of firmware data to the device. This function is designed to be called repeatedly to transmit the entire firmware. ```javascript async function sendPart(address, data) { hex_address = decimalToHex(address, 8); var part_len = 480; while (data.length) { var cur_part_len = part_len; if (data.length < part_len) cur_part_len = data.length; var data_part = data.substring(0, cur_part_len); data = data.substring(cur_part_len); console.log("Sub Part: " + "03" + data_part); await sendCommand(hexToBytes("03" + data_part)); fwBytesTransmited += cur_part_len; setStatus('Current part: ' + fwBytesTransmited / 2 + " All: " + fwSizeComplete / 2 + " Time: " + (new Date().getTime() - startTime) / 1000.0 + "s"); } await sendCommand(hexToBytes("02" + hex_address)); console.log("Writing bank: " + hex_address); await delay(50); } ``` -------------------------------- ### Blink LED with EPaperBLE (Python) Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Connects to an E-Paper display via BLE and blinks the onboard LED a specified number of times. Ensure the device is discoverable and the address is correct. ```python async def alert_red(): epaper = EPaperBLE() await epaper.connect("AA:BB:CC:DD:EE:FF") try: await epaper.blink(1) # Red await asyncio.sleep(2) await epaper.blink(0) # Off finally: await epaper.disconnect() asyncio.run(alert_red()) ``` -------------------------------- ### Flash Wake Up Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/uart_flasher.html Wakes up the flash memory from a low-power state by sending the flash command 0xab. ```javascript async function FlashWakeUp() { // send flash cmd 0xab to wakeup flash return FlashByteCmd(0xab); } ``` -------------------------------- ### cmd_parser() Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt The central BLE write handler for the RxTx GATT characteristic. Decodes the first byte of each incoming packet and dispatches to the appropriate firmware subsystem. ```APIDOC ## cmd_parser() ### Description Decodes incoming BLE packets and dispatches commands to various firmware subsystems. The first byte of the packet determines the command. ### Parameters #### Request Body - **p** (*) - `rf_packet_att_data_t *req` - Pointer to the received BLE packet. - `req->dat[0]` (uint8_t) - Command byte. - `req->dat[1..n]` - Optional arguments for the command. ### Supported Commands - **0xE1** ``: Switch display scene (0–5). - **0x81** ``: Set LED color (0–7). - **0xDD** `<4-byte unix time> <2-byte year> `: Set RTC. - **0xDE**: Reset settings to defaults and save. - **0xDF**: Save current settings to flash. - **0xE0** ``: Force EPD model (1=BW213, 2=BWR213, 4=213ICE, 5=BWR296). - **0xFE** ``: Set advertising interval (val * 10 seconds). - **0xFA** ``: Set temperature offset (-12.5 to +12.5 °C). - **0xFC** ``: Set temperature alarm threshold (val / 10 °C). - **0x0F / 0x0C**: Switch advertising temperature unit (F / C). ### Request Example ``` // Example: switch to scene 5 // BLE write to RxTx char: bytes { 0xE1, 0x05 } // Example: set LED to green // BLE write to RxTx char: bytes { 0x81, 0x02 } ``` ``` -------------------------------- ### Discover RX/TX Characteristics Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/ATC_TLSR_Paper_OTA_writing.html Searches for and connects to the custom Main service and its write characteristic for RX/TX communication after the initial connection. ```javascript function connect_to_rxtx() { gattServer.getPrimaryServices().then(services => { for (var i = 0; i < services.length; i++) { console.log("Services: " + services[i].uuid); if (services[i].uuid == "00001f10-0000-1000-8000-00805f9b34fb") { gattServer.getPrimaryService('00001f10-0000-1000-8000-00805f9b34fb') .then(service => { addLog("Found custom Main service"); ServiceMain = service; return ServiceMain.getCharacteristic('00001f1f-0000-1000-8000-00805f9b34fb'); }).then(characteristic => { addLog("Found custom write characteristic"); settingsCharacteristics = characteristic; }).catch(); return; } } }).catch(); } ``` -------------------------------- ### set_time() / get_time() Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Manages the device's internal Real-Time Clock (RTC) by setting the time from external commands and retrieving it for display rendering. ```APIDOC ## `set_time()` / `get_time()` — Firmware RTC (C) Sets the device's internal Unix timestamp and calendar fields, which are then used by all display scenes for clock rendering. `handler_time()` must be called every second from the main loop. ### Functions - **`set_time(uint32_t unix_ts, uint16_t year, uint8_t month, uint8_t day, uint8_t week)`**: Sets the device's time. Called internally when a time synchronization command is received. - **`get_time()`**: Returns a `struct date_time` containing the current hour, minute, second, year, month, day, and weekday. - **`handler_time()`**: Must be called once per second in the main loop to update the RTC. ### Usage Example ```c #include "etime.h" void on_time_command_received(uint32_t unix_ts, uint16_t year, uint8_t month, uint8_t day, uint8_t week) { set_time(unix_ts, year, month, day, week); } void render_clock_example(void) { struct date_time t = get_time(); char buf[16]; sprintf(buf, "%02d:%02d", t.tm_hour, t.tm_min); // "14:35" } void app_loop(void) { handler_time(); // must be called once per second } ``` ``` -------------------------------- ### Low-level E-Paper Refresh with EPD_Display() Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Drives the EPD panel controller to refresh the display. Accepts a 1-bpp pixel buffer for black/white and an optional red-channel buffer for three-color panels. Use the `epd_state_handler()` to poll until the display is ready. ```c #include "epd.h" void display_example(void) { // epd_buffer and epd_temp are global 296×128/8 = 4736-byte arrays // Clear to white epd_clear(); // Draw something into epd_temp via OneBitDisplay (obdWriteStringCustom, etc.) // ... // Rotate/mirror the OBD virtual buffer into the EPD physical buffer FixBuffer(epd_temp, epd_buffer, 296, 128); // Trigger a full refresh (1) or partial refresh (0) EPD_Display(epd_buffer, NULL, epd_buffer_size, 1 /* full */); // For three-color BWR panels, supply a red-channel buffer: // EPD_Display(epd_buffer, red_buffer, epd_buffer_size, 1); // Poll until busy line de-asserts, then sleep the panel while (epd_state_handler() != 0) { /* wait */ } } ``` -------------------------------- ### EPaperBLE.set_mode() - Switch Display Scene Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Send a BLE command to switch the active display scene. Supports modes 0-5, each corresponding to a different display layout or function. Handles invalid mode input. ```python import asyncio from python_tools.myesl import EPaperBLE # Scene index reference: # 0 - Image mode (show uploaded bitmap) # 1 - Clock scene (time + temp + battery) # 2 - Clock + date scene # 3 - Translated scene 1 variant # 4 - Translated scene 2 variant # 5 - Full calendar/clock/temperature-graph scene (new) async def switch_scene(): epaper = EPaperBLE() await epaper.connect("AA:BB:CC:DD:EE:FF") try: await epaper.set_mode(5) # Activate the calendar+temp-graph scene print("Scene switched to mode 5") except ValueError as e: print(f"Invalid mode: {e}") finally: await epaper.disconnect() asyncio.run(switch_scene()) # CLI equivalent: # python python_tools/myesl.py -address AA:BB:CC:DD:EE:FF -setmode 5 ``` -------------------------------- ### Firmware Battery Measurement (C) Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Samples battery voltage using the internal ADC and converts it to a 0-100% level. Anchored at 2200 mV (0%) and a 100% upper bound. Provides both millivolts and percentage. ```C #include "battery.h" void battery_read_example(void) { uint16_t mv = get_battery_mv(); // e.g. 2950 uint8_t level = get_battery_level(mv); // e.g. 83 // level = (mv - 2200) / 9 — clamped to [0, 100] // mv < 2200 → level = 0 (critical) // mv = 2950 → level = 83% // mv ≥ 3100 → level = 100% } ``` -------------------------------- ### Adapter Speed Configuration Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/Firmware/tc32_linux/share/openocd/scripts/target/readme.txt The adapter_khz command sets the maximum JTAG speed. The last setting in the configuration file takes precedence. Settings in interface files are overridden by target files. ```tcl adapter_khz ``` -------------------------------- ### Complete Firmware Sending Process Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/ATC_TLSR_Paper_OTA_writing.html Orchestrates the entire firmware sending process, including erasing the firmware area, sending data in parts, and performing the final flash with CRC check. ```javascript async function sendFile(address, data) { startTime = new Date().getTime(); var part_len = 0x200; var addressOffset = 0; var inCRC = calculateCRC(data); addLog("File CRC = " + inCRC); fwSizeComplete = data.length; fwBytesTransmited = 0; await eraseFwArea(); while (data.length) { var cur_part_len = part_len; if (data.length < part_len) cur_part_len = data.length; var data_part = data.substring(0, cur_part_len); data = data.substring(cur_part_len); await sendPart(address + addressOffset, data_part); addressOffset += cur_part_len / 2; } await doFinalFlash(inCRC); } ``` -------------------------------- ### BLE RxTx Command Dispatcher (C) Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Handles incoming BLE write requests to the RxTx GATT characteristic. It decodes the command byte and dispatches to the appropriate firmware function. Ensure correct packet structure for commands. ```c #include "cmd_parser.h" #include "epd.h" #include "led.h" #include "flash.h" #include "etime.h" // Called automatically by the BLE stack on RxTx characteristic writes. // Packet structure: dat[0] = command byte, dat[1..n] = optional arguments. void cmd_parser(void *p) { rf_packet_att_data_t *req = (rf_packet_att_data_t *)p; uint8_t cmd = req->dat[0]; // 0xE1 — switch display scene (0–5) // 0x81 — set LED color (0–7) // 0xDD <4-byte unix time> <2-byte year> — set RTC // 0xDE — reset settings to defaults and save // 0xDF — save current settings to flash // 0xE0 — force EPD model (1=BW213, 2=BWR213, 4=213ICE, 5=BWR296) // 0xFE — set advertising interval (val * 10 seconds) // 0xFA — set temperature offset (-12.5 to +12.5 °C) // 0xFC — set temperature alarm threshold (val / 10 °C) // 0x0F / 0x0C — switch advertising temperature unit (F / C) // Example: switch to scene 5 // BLE write to RxTx char: bytes { 0xE1, 0x05 } // Example: set LED to green // BLE write to RxTx char: bytes { 0x81, 0x02 } } ``` -------------------------------- ### Enable Apple Find My / AirTag Emulation Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Set AIR_TAG_OPEN to 1 and replace PUB_KEY with a 28-byte compressed EC public key to broadcast Apple Offline Finding advertisements. The firmware automatically encodes the key into the advertisement data. ```c // In Firmware/src/ble.c — customize before compiling: // Set AIR_TAG_OPEN = 1 to enable Find My broadcasting RAM uint8_t AIR_TAG_OPEN = 1; // Replace PUB_KEY with your own 28-byte compressed EC public key. // Generate a key pair using macless-haystack or openhaystack: // https://github.com/dchristl/macless-haystack // https://github.com/malmeloo/openhaystack RAM uint8_t PUB_KEY[28] = { 0x49, 0x88, 0x00, 0x7a, 0x27, 0xac, 0x38, 0xb7, 0x16, 0x55, 0x3c, 0xc8, 0x57, 0x62, 0x93, 0xc3, 0x95, 0xef, 0x3f, 0x63, 0x70, 0xb2, 0xa3, 0x96, 0x6d, 0x4c, 0x1a, 0x7d }; // The firmware automatically encodes the key into the Apple Manufacturer // Specific Data advertisement: // AD Type 0xFF, Company 0x004C (Apple), Type 0x12 (Offline Finding) // Nearby Apple devices will encrypt their location and upload it to // iCloud; retrieve it with your private key via the Find My app or // macless-haystack server. ``` -------------------------------- ### Establish GATT Server Connection Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/ATC_TLSR_Paper_OTA_writing.html Establishes a GATT server connection to the Bluetooth device and discovers services and characteristics. This is a core function for communication. ```javascript function connect() { if (writeCharacteristic == null) { addLog("> Connecting to: " + bleDevice.name); bleDevice.gatt.connect().then(server => { console.log('> Found GATT server'); gattServer = server; return gattServer.getPrimaryService('0000221f-0000-1000-8000-00805f9b34fb'); }) .then(service => { console.log('> Found service'); Theservice = service; return Theservice.getCharacteristic('0000331f-0000-1000-8000-00805f9b34fb'); }) .then(characteristic => { console.log('> Found write characteristic'); addLog('> Found write characteristic'); document.getElementById("connectbutton").innerHTML = 'Disconnect'; writeCharacteristic = characteristic; return writeCharacteristic.startNotifications().then(() => { writeCharacteristic.addEventListener('characteristicvaluechanged', event => { var value = event.target.value; handleNotify(value); }); connect_to_rxtx(); }); }) .catch(handleError); } } ``` -------------------------------- ### WebBluetooth BLE UUIDs and Commands Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Defines BLE service and characteristic UUIDs for EPD and RxTx communication. Includes functions to send raw commands for clearing the screen, pushing buffers, and switching scenes. ```javascript const EPD_SERVICE_UUID = "13187b10-eba9-a3ba-044e-83d3217d9a38"; const RXTX_SERVICE_UUID = "00001f10-0000-1000-8000-00805f9b34fb"; const EPD_CHAR_UUID = "4b646063-6264-f3a7-8941-e65356ea82fe"; const RXTX_CHAR_UUID = "00001f1f-0000-1000-8000-00805f9b34fb"; // Send a raw command to the EPD characteristic async function sendCommand(cmd) { await epdCharacteristic.writeValueWithResponse(cmd); } // Clear screen to white: byte 0x00 fill=0xFF await sendCommand(new Uint8Array([0x00, 0xFF])); // Push buffer to display (partial refresh) await sendCommand(new Uint8Array([0x01, 0x00])); // Switch to scene 5 via RxTx char await rxtxCharacteristic.writeValueWithoutResponse(new Uint8Array([0xE1, 0x05])); ``` -------------------------------- ### Connect/Disconnect Event Listener Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/uart_flasher.html Handles the connection logic for the serial port. Toggles between initializing the serial controller and closing the connection based on the button's value. ```javascript connect.addEventListener('pointerdown', () => { if(connect.value == 'Close') { DeviceStop(); } else { serialController.init(); } }); ``` -------------------------------- ### Set Device Time Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/index.html Sets the device time using the RxTx service. Sends current Unix timestamp, year, month, day, and week. ```javascript async function setTime() { const { unixNow, localeTimeString, year, month, day, week } = getUnixTime(); addLog("The time is set to: " + localeTimeString + " : dd" + intToHex(unixNow, 4)); await rxTxSendCommand(hexToBytes('dd' + [intToHex(unixNow, 4), intToHex(year, 2), intToHex(month, 1), intToHex(day, 1), intToHex(week, 1)].join(''))); await rxTxSendCommand(hexToBytes('e2')) } ``` -------------------------------- ### EPaperBLE.upload_picture() Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Uploads a local image file to the e-paper display. The image is resized, dithered, converted to 1-bpp format, and streamed over BLE. ```APIDOC ## `EPaperBLE.upload_picture()` — Upload Image via BLE (Python) Loads a local image file, resizes it to 296×128, applies the selected dithering algorithm, packs it into 1-bpp format, and streams it to the device in 240-byte BLE chunks. ### Parameters - **image_path** (str): Path to the local image file. - **dither_mode** (DitherMode): The dithering algorithm to apply. Options include `DitherMode.FLOYDSTEINBERG`, `DitherMode.NONE`, `DitherMode.ATKINSON`. ### Request Example ```python import asyncio from python_tools.myesl import EPaperBLE, DitherMode async def upload(): epaper = EPaperBLE() await epaper.connect("AA:BB:CC:DD:EE:FF") try: await epaper.upload_picture("my_image.png", DitherMode.FLOYDSTEINBERG) finally: await epaper.disconnect() asyncio.run(upload()) ``` ### CLI Equivalent ```bash python python_tools/myesl.py -address AA:BB:CC:DD:EE:FF -uploadpicture my_image.png -dither floydsteinberg ``` ``` -------------------------------- ### EPaperBLE Python Class - BLE Device Connection Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt The EPaperBLE class in python_tools/myesl.py wraps the Bleak BLE library to provide high-level async methods for all device operations. Call connect() with the device's MAC address before using any other method. ```APIDOC ## EPaperBLE Python Class — BLE Device Connection The `EPaperBLE` class in `python_tools/myesl.py` wraps the Bleak BLE library to provide high-level async methods for all device operations. Call `connect()` with the device's MAC address before using any other method. ```python import asyncio from python_tools.myesl import EPaperBLE async def main(): epaper = EPaperBLE() # Scan for nearby S24_ devices devices = await EPaperBLE.discover() for d in devices: print(f"Found: {d.name} @ {d.address}") # Found: S24_A1B2C3 @ AA:BB:CC:DD:EE:FF # Connect by address success = await epaper.connect("AA:BB:CC:DD:EE:FF") # Connected to AA:BB:CC:DD:EE:FF try: pass # use methods below finally: await epaper.disconnect() asyncio.run(main()) ``` ``` -------------------------------- ### Manage Persistent Settings with save_settings_to_flash() Source: https://context7.com/fantasyfactory/stellar-l3n-etag/llms.txt Saves the current settings to flash memory at address `0x78100`. Settings are CRC-protected and validated on boot. Use `reset_settings_to_default()` to restore factory defaults before saving. ```c #include "flash.h" // Available settings fields (settings_struct): // temp_C_or_F — display temperature in Celsius (false) or Fahrenheit (true) // advertising_temp_C_or_F — advertise temperature in C or F // blinking_smiley — enable blinking smiley icon on display // comfort_smiley — show comfort indicator icon // show_batt_enabled — show battery percentage on display // advertising_interval — BLE advertisement interval (val × 10 s, 0 = main_delay) // measure_interval — sensor measurement interval in seconds // temp_offset — temperature correction offset // temp_alarm_point — temperature alarm threshold void settings_example(void) { // Modify a setting settings.advertising_interval = 3; // advertise every 30 seconds settings.temp_C_or_F = false; // display in Celsius // Persist to flash (erases sector then writes) save_settings_to_flash(); // Restore factory defaults and save reset_settings_to_default(); save_settings_to_flash(); } // BLE command 0xDF triggers save_settings_to_flash() // BLE command 0xDE triggers reset_settings_to_default() + save_settings_to_flash() ``` -------------------------------- ### Serial Controller Class for USB-COM Flashing Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/uart_flasher.html Manages the USB-COM serial port connection for flashing firmware. Includes methods for opening, closing, writing data, and resetting the device. Ensure Web Serial API is enabled in your browser. ```javascript class SerialController { async open(firmwareArray, init_cb) { try { this.port = await navigator.serial.requestPort(); await this.port.open({ baudRate: 115200 }); await this.port.setSignals({ dataTerminalReady: true, requestToSend: true }); // DTR, RTS on. await delay(100); await this.port.setSignals({ dataTerminalReady: false, requestToSend: false }); // DTR, RTS off. const info = await this.port.getInfo(); log('Connected to ' + info.usbVendorId + ':' + info.usbProductId); log('Port opened successfully.'); this.writer = this.port.writable.getWriter(); this.reader = this.port.readable.getReader(); // Set buffer size for reading, adjust as needed // Example: this.reader.read().then(({ done, value }) => { ... }); // The provided snippet seems to be missing the actual buffer size configuration for the reader. // Assuming a placeholder or a default behavior for now. // Example of setting up a reader loop (not in original snippet): // (async () => { // while (true) { // const { value, done } = await this.reader.read(); // if (done) { // this.reader.releaseLock(); // break; // } // // Process received data (value) // console.log('Received:', value); // } // })(); connect.value = 'Close'; log('DTR, RTS on.'); connect.opened = true; berase.disabled = false; bunlock.disabled = false; breset.disabled = false; if (firmwareArray != null) { fwriter.disabled = false; swrite.innerHTML = ''; } if (typeof init_cb == 'function') await init_cb(this.port); } catch (err) { log('There was an error opening the serial port: ' + err); berase.disabled = true; bunlock.disabled = true; breset.disabled = true; fwriter.disabled = true; } connect.disabled = false; } async write_raw(data) { return await this.writer.write(data); } async reset(t_ms) { console.log('DTR, RTS off.'); await this.port.setSignals({ dataTerminalReady: true, requestToSend: true }); await delay(t_ms); console.log('DTR, RTS on.'); return await this.port.setSignals({ dataTerminalReady: false, requestToSend: false }); } async close() { fwriter.disabled = true; swrite.innerHTML = ''; await this.writer.close(); await this.port.close(); connect.value = 'Open'; connect.opened = false; fwriter.disabled = true; bunlock.disabled = true; berase.disabled = true; breset.disabled = true; log('USB-COM closed.'); connect.disabled = false; } } var serialController = new SerialController(); ``` -------------------------------- ### Flash Write Enable Source: https://github.com/fantasyfactory/stellar-l3n-etag/blob/main/web_tools/uart_flasher.html Enables write operations to the flash memory by sending the flash command 0x06. ```javascript async function FlashWriteEnable() { // send flash cmd 0x06 write enable to flash return FlashByteCmd(0x06); } ```