Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The data source handles account sign-in and usage retrieval. Bridge sends only u
| Waveshare ePaper 3.97 | 800×480 e-paper with four grayscale levels | Temperature and humidity, usage trends (microSD card required) |
| ESP-Mosaico | 480×480 AMOLED | Touchscreen, AI button, auto-rotation, camera gestures (experimental) |
| Waveshare RLCD 4.2 | 400×300 monochrome reflective LCD | KEY / BOOT navigation, clock, usage trends (microSD card required) |
| MindReset Read Pico | 684×1216 e-paper with 16 grayscale levels | Touchscreen and three keys, clock, auto-rotation, usage trends (microSD card required) |

All devices support Codex / Claude usage display, retain the last usage data when disconnected, and support firmware updates over Bluetooth. See the [device guide](docs/devices.md) for a full feature comparison and control shortcuts.

Expand All @@ -43,7 +44,7 @@ All devices support Codex / Claude usage display, retain the last usage data whe

### 1. Flash the Firmware

Open the [web flasher](https://quotaframe.com/) in Chrome or Edge on your computer, select your device model, connect it via USB, and follow the instructions. No ESP-IDF installation is required.
Open the [web flasher](https://quotaframe.com/) in Chrome or Edge on your computer, select your device model, connect it via USB, and follow the instructions. No ESP-IDF installation is required. MindReset Read Pico is not in the web flasher; write its release image as described in the firmware guide.

For manual flashing or serial recovery, see the [firmware guide](firmware/README.md#烧录和串口日志).

Expand Down Expand Up @@ -78,7 +79,7 @@ See the [Bridge guide](bridge/README.md) for installation and connection trouble

## Acknowledgments

Thanks to Waveshare for supporting this project through sponsorship.
Thanks to [Waveshare](https://www.waveshare.com/) and [MindReset](https://dot.mindreset.tech/) for their support with development devices.

Usage data is provided by [CodexBar](https://github.com/steipete/CodexBar) and [Win-CodexBar](https://github.com/nesszer/Win-CodexBar). Thanks to [esp-desktop-buddy](https://github.com/espressif/esp-desktop-buddy), [LVGL](https://github.com/lvgl/lvgl), [Bleak](https://github.com/hbldh/bleak), [pystray](https://github.com/moses-palmer/pystray), [Pillow](https://github.com/python-pillow/Pillow), and [PyObjC](https://github.com/ronaldoussoren/pyobjc) for supporting the firmware and desktop application.

Expand Down
5 changes: 3 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ flowchart LR
| Waveshare ePaper 3.97 | 800×480 四灰阶墨水屏 | 温湿度、用量趋势(需 microSD 卡) |
| ESP-Mosaico | 480×480 AMOLED | 触屏、AI 键、自动旋转、摄像头手势(实验性) |
| Waveshare RLCD 4.2 | 400×300 黑白反射式 LCD | KEY / BOOT 翻页、时钟、用量趋势(需 microSD 卡) |
| MindReset Read Pico | 684×1216 十六灰阶墨水屏 | 触屏与三枚按键、时钟、自动旋转、用量趋势(需 microSD 卡) |

所有设备均支持 Codex / Claude 用量显示、断连保留最后用量与蓝牙固件更新。完整功能对照及快捷操作见[设备说明](docs/devices.md)。

Expand All @@ -43,7 +44,7 @@ flowchart LR

### 1. 烧录固件

使用桌面版 Chrome 或 Edge 打开 [Web 烧录器](https://quotaframe.com/),选择手中的设备型号、连接 USB,按页面提示烧录,无需安装 ESP-IDF。
使用桌面版 Chrome 或 Edge 打开 [Web 烧录器](https://quotaframe.com/),选择手中的设备型号、连接 USB,按页面提示烧录,无需安装 ESP-IDF。MindReset Read Pico 不在 Web 烧录器中,请按固件说明写入 Release 镜像。

需要手工烧录或串口恢复时,参阅[固件说明](firmware/README.md#烧录和串口日志)。

Expand Down Expand Up @@ -78,7 +79,7 @@ flowchart LR

## 致谢

感谢微雪电子对本项目的赞助支持。
感谢[微雪电子](https://www.waveshare.com/)与 [MindReset](https://dot.mindreset.tech/) 在开发设备上给予的支持。

用量数据依赖 [CodexBar](https://github.com/steipete/CodexBar) 与 [Win-CodexBar](https://github.com/nesszer/Win-CodexBar)。感谢 [esp-desktop-buddy](https://github.com/espressif/esp-desktop-buddy)、[LVGL](https://github.com/lvgl/lvgl)、[Bleak](https://github.com/hbldh/bleak)、[pystray](https://github.com/moses-palmer/pystray)、[Pillow](https://github.com/python-pillow/Pillow) 与 [PyObjC](https://github.com/ronaldoussoren/pyobjc) 提供固件及桌面端支持。

Expand Down
10 changes: 10 additions & 0 deletions THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,23 @@ For ESP Component Registry entries, `Registry license` is the API value for the
- SPDX: `OFL-1.1`
- Generated subsets: weight 700; printable ASCII at 24–56 px, digits and `%:-. ` at 104–184 px
- Copyright/notice: `firmware/targets/waveshare_epaper_397/main/fonts/Archivo-OFL.txt`
- Read Pico subsets: weight 700; printable ASCII at 16–56 px, digits and `%:-. ` at 58–236 px; notice: `firmware/targets/mindreset_read_pico/main/fonts/Archivo-OFL.txt`

## JetBrains Mono generated font subsets

- Source: https://github.com/google/fonts/tree/main/ofl/jetbrainsmono (`JetBrainsMono[wght].ttf`); the upstream commit used for generation was not recorded
- SPDX: `OFL-1.1`
- Generated subsets: weight 500; printable ASCII at 15–20 px
- Copyright/notice: `firmware/targets/waveshare_epaper_397/main/fonts/JetBrainsMono-OFL.txt`
- Read Pico subsets: printable ASCII at weight 500, 18 px, and weight 700, 16–30 px; notice: `firmware/targets/mindreset_read_pico/main/fonts/JetBrainsMono-OFL.txt`

## Read Pico board components

- Version: `28cde682a4468a581c278761922724f57d976418`
- Source: https://github.com/MindReset/read_pico_firmware/tree/28cde682a4468a581c278761922724f57d976418/components
- SPDX: `Apache-2.0` (`e0470_epaper_waveform`, `read_pico`, `read_pico_pmu`, `cst836u`, `sc7a20h`, `fca9555`, `sy7636a`, `pwm_audio`); `LGPL-3.0-or-later` (`epdiy`, a trimmed fork of https://github.com/vroland/epdiy v2.0.0)
- Copyright/notice: `LICENSE` in each directory under `firmware/targets/mindreset_read_pico/components/`
- Local integration: [UPSTREAM.md](firmware/targets/mindreset_read_pico/components/UPSTREAM.md)

## Waveshare ESP32-S3-ePaper-3.97 screen driver

Expand Down
2 changes: 1 addition & 1 deletion bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ macOS 提供控制台与菜单栏形态,Release workflow 生成带图标和原

滚动仅处理图标范围内的纵向事件,触控板惯性滚动不触发翻页,连续操作最多每 200 ms 翻一页。

菜单按已添加的设备显示固件更新项,包含新版提示、等待设备确认、传输百分比和重连状态。点击后下载并校验固件,在桌面确认设备、来源、版本和摘要,再通过蓝牙更新;设备仍需逐次物理确认。下载与桌面确认不占独占连接,期间继续同步用量。macOS OTA 软件流程已接入,实机升级、重连和数据恢复仍待验收。
菜单按已添加的设备显示固件更新项,包含新版提示、等待设备确认、传输百分比和重连状态。点击后下载并校验固件,在桌面确认设备、来源、版本和摘要,再通过蓝牙更新;设备仍需逐次物理确认。下载与桌面确认不占独占连接,期间继续同步用量。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the macOS OTA verification caveat

This deletion removes the only nearby distinction between the implemented macOS OTA software flow and hardware validation, while docs/verification.md still states that macOS OTA for every target remains pending. The remaining paragraph now presents transfer, confirmation, and reconnection as an unqualified supported workflow, so release readers can mistake unverified device behavior and recovery for completed validation; restore the caveat unless corresponding hardware acceptance evidence is added.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.


**「重新配对…」只弹说明**:macOS 不提供解绑 API,只能引导去蓝牙设置。

Expand Down
11 changes: 11 additions & 0 deletions bridge/src/quotaframe_bridge/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ def ota_transfer_bytes(self) -> int:
asset="assets/devices/waveshare_rlcd_42.jpg",
),
),
TargetDefinition(
id="mindreset_read_pico",
label="MindReset Read Pico",
firmware_project="firmware/targets/mindreset_read_pico",
idf_version="6.1",
ota_image="mr_read_pico.bin",
full_image="mr_read_pico_full.bin",
release_stem="mindreset-read-pico",
image_chip_id=9,
ota_partition_bytes=4 * 1024 * 1024,
),
Comment on lines +141 to +142

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add Read Pico to the browser flasher registry

This target leaves web_flash at its None default, so both scripts/export_web_devices.py and scripts/assemble_web_flasher.py explicitly filter Read Pico out of the website device list and firmware manifests. Users following the newly updated Quick Start therefore cannot select this supported model in the advertised web flasher; provide its WebFlashDefinition and asset, or qualify the installation documentation.

Useful? React with 👍 / 👎.

)

IDF_IMAGES: Mapping[str, str] = MappingProxyType(
Expand Down
12 changes: 11 additions & 1 deletion bridge/src/quotaframe_bridge/ui/macos/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def __init__(
self._service: MultiDeviceBridgeService | None = None
self._graph: TrayServiceGraph[DarwinPairer] | None = None
self._adoption_running = False
self._adoption_future: concurrent.futures.Future[None] | None = None
self._stopping = False
self._shutdown_lock = threading.Lock()
self._firmware_updates = 0
Expand Down Expand Up @@ -194,15 +195,24 @@ def _notify_firmware(self, title: str, body: str) -> None:

def _add_device(self) -> None:
loop, graph = self._loop, self._graph
if loop is None or graph is None:
if loop is None or graph is None or self._stopping:
return
if self._adoption_future is not None and not self._adoption_future.done():
return
future = asyncio.run_coroutine_threadsafe(self._run_adoption(graph), loop)
# Retain the task chain while CoreBluetooth is awaiting a native callback.
self._adoption_future = future

def report_failure(completed: concurrent.futures.Future[object]) -> None:
try:
completed.result()
except concurrent.futures.CancelledError:
pass
except Exception:
LOGGER.exception("manual device adoption failed")
finally:
if self._adoption_future is completed:
self._adoption_future = None

future.add_done_callback(report_failure)

Expand Down
39 changes: 39 additions & 0 deletions bridge/tests/test_macos_app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

import asyncio
import gc
import os
import unittest
import weakref
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock, patch

Expand Down Expand Up @@ -74,6 +76,43 @@ async def check(self, *, manual: bool = False) -> None:


class MenuBarUpdateWiringTests(unittest.IsolatedAsyncioTestCase):
async def test_adoption_survives_gc_and_releases_completed_future(self):
application, _, _ = self.make_application()
application._loop = asyncio.get_running_loop()
application._graph = object()
waiting = []

async def scan(*args, **kwargs):
response = asyncio.get_running_loop().create_future()
waiting.append(weakref.ref(response))
await response

with patch.object(app, "run_adoption", new=scan):
application._add_device()
await asyncio.sleep(0)
await asyncio.sleep(0)
gc.collect()
self.assertIsNotNone(waiting[0]())
application._add_device()
await asyncio.sleep(0)
await asyncio.sleep(0)
self.assertEqual(len(waiting), 1)
waiting[0]().set_result(())
await asyncio.sleep(0)
await asyncio.sleep(0)
self.assertFalse(application._adoption_running)
self.assertIsNone(application._adoption_future)
application._add_device()
await asyncio.sleep(0)
await asyncio.sleep(0)
with patch.object(app.LOGGER, "exception") as report:
application._adoption_future.cancel()
await asyncio.sleep(0)
await asyncio.sleep(0)
report.assert_not_called()
self.assertFalse(application._adoption_running)
self.assertIsNone(application._adoption_future)

def make_application(
self,
*,
Expand Down
9 changes: 9 additions & 0 deletions bridge/tests/test_target_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ def test_preserves_existing_maintained_target_contracts(self) -> None:
"ws_rlcd_42_full.bin",
"waveshare-esp32-s3-rlcd-42",
),
"mindreset_read_pico": (
"MindReset Read Pico",
"firmware/targets/mindreset_read_pico",
"6.1",
"mr_read_pico.bin",
"mr_read_pico_full.bin",
"mindreset-read-pico",
),
}
self.assertEqual(set(expected), set(TARGETS_BY_ID))
self.assertEqual(
Expand All @@ -69,6 +77,7 @@ def test_preserves_existing_maintained_target_contracts(self) -> None:
"waveshare_epaper_397": 9,
"esp_mosaico": 32,
"waveshare_rlcd_42": 9,
"mindreset_read_pico": 9,
},
)
for target_id, contract in expected.items():
Expand Down
3 changes: 3 additions & 0 deletions docs/devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ ePaper 3.97 与 ESP-Mosaico 的详细说明见 [ePaper 3.97 固件说明](../fir

RLCD 4.2 使用 400×300 黑白反射式 LCD,提供总览、Codex / Claude 详情、趋势、时钟和设置页;竖屏仅在总览和时钟间轮换。它支持 RTC、温湿度显示、电压估算电量,以及需 FAT microSD 卡的 48 点趋势。屏幕方向由设置选择,可通过 Web 烧录器安装。当前实机验证范围和完整操作见 [RLCD 4.2 说明](../firmware/targets/waveshare_rlcd_42/README.md)。

Read Pico 使用 684×1216 十六灰阶墨水屏,主页同屏显示 Codex 与 Claude 用量、告警状态和 24 小时趋势,另有时钟、设置、配对和 OTA 页面。它支持 PMU RTC、电量与充电状态、四向自动旋转、拿起时从自动时钟返回主页,以及需 FAT microSD 卡的 48 点趋势。首次安装写入 Release 中的 `mindreset-read-pico-full-` 镜像,方法见[固件说明](../firmware/README.md#烧录和串口日志);完整操作见 [Read Pico 说明](../firmware/targets/mindreset_read_pico/README.md)。

## 屏幕快捷操作

- **Read Pico**:KEY1 / KEY3 在主页与时钟间切换,KEY2 整屏刷新,长按 KEY2 约 0.8 秒进入或退出设置;设置中点按选项即保存,点 DONE 退出。OTA 待确认时点 CONFIRM / DENY,或按 KEY2 确认、长按拒绝。
- **RLCD 4.2**:KEY 上一页,BOOT 下一页,长按 KEY 进入或退出设置;设置中 KEY 选择项目、BOOT 修改选项。KEY 可关闭当前用量告警。OTA 待确认时 KEY 确认,长按 KEY 拒绝。
- **480×480 触屏设置(微雪 AMOLED 2.16 与 ESP-Mosaico)**:长按触屏约 0.8 秒进入,两块板均支持亮度与自动时钟设置。亮度即时预览;AUTO CLOCK 可选择 OFF、1、5、10、30 分钟。SAVE 保存并在重启后保留,CANCEL 撤销未保存调整。OFF 仅关闭自动进入时钟,托盘/菜单栏左键仍可主动切换;微雪另有 PWR 侧键,ESP-Mosaico 另有 AI 键。
- **ESP-Mosaico 实验性手势**:安装左槽 CameraBoard 后,在设置中打开 CAMERA 并保存。用量页向任意方向挥手或左右招手切到下一页;OK 或点赞稳定保持约半秒进入时钟,挥手或招手唤醒。点击顶栏摄像头标识切换预览。完整说明见[手势指南](validation/mosaico-gestures.md)。
Expand Down
2 changes: 2 additions & 0 deletions docs/release/FIRMWARE_RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ espressif-esp-mosaico-ota-v<version>.bin
espressif-esp-mosaico-full-v<version>.bin
waveshare-esp32-s3-rlcd-42-ota-v<version>.bin
waveshare-esp32-s3-rlcd-42-full-v<version>.bin
mindreset-read-pico-ota-v<version>.bin
mindreset-read-pico-full-v<version>.bin
manifest.json
SHA256SUMS.txt
LICENSE
Expand Down
2 changes: 2 additions & 0 deletions firmware/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Folder Push 本地快照原因见 [UPSTREAM.md](components/esp_desktop_buddy_fol
| ESP32-S3-ePaper-3.97 | 已维护 | 自绘帧缓冲、SSD1677 驱动、三向拨轮 |
| ESP-Mosaico | 已维护 | 乐鑫 BSP、LVGL、触控标签和手势、AI 键 |
| ESP32-S3-RLCD-4.2 | 已维护 | 自绘帧缓冲、ST7305 驱动、KEY / BOOT 按键 |
| MindReset Read Pico | 已维护 | 自绘帧缓冲、epdiy 驱动、触屏与三枚电容键 |

各目标的实机验收范围见 [verification.md](../docs/verification.md)。

Expand Down Expand Up @@ -65,6 +66,7 @@ eim run "idf.py -C firmware/targets/<target> merge-bin -o <full-image>" v6.1
| `waveshare_epaper_397` | `ws_epaper_397.bin` | `ws_epaper_397_full.bin` |
| `esp_mosaico` | `mosaico_usage_panel.bin` | `mosaico_usage_panel_full.bin` |
| `waveshare_rlcd_42` | `ws_rlcd_42.bin` | `ws_rlcd_42_full.bin` |
| `mindreset_read_pico` | `mr_read_pico.bin` | `mr_read_pico_full.bin` |

产物位于各目标的 `build/`。ESP-Mosaico 首次配置需启用 preview target,见[目标说明](targets/esp_mosaico/README.md);墨水屏刷新、输入和诊断见[ePaper 说明](targets/waveshare_epaper_397/README.md)。

Expand Down
2 changes: 1 addition & 1 deletion firmware/targets/esp_mosaico/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ OTA 元数据未被改写。新固件启动后,再次完成应用 CDC → ROM
看门狗复位 → 应用 CDC,全程无需按键。

两次启动均检测到 SC101IOT 并持续采集;分别读取 CDC 日志 30 秒、20 秒,
未见崩溃或串口断连。这是短时并行验证,长期稳定性和 Windows 行为仍待验证。
未见崩溃或串口断连。
应用 CDC 与 ROM 使用不同的 USB 标识,端口会变化;此项验证不代表单条
`idf.py flash` 命令已自动处理端口切换。

Expand Down
12 changes: 12 additions & 0 deletions firmware/targets/mindreset_read_pico/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.16)

set(EXTRA_COMPONENT_DIRS
"${CMAKE_CURRENT_LIST_DIR}/../../components/usage_json"
"${CMAKE_CURRENT_LIST_DIR}/../../components/usage_core"
"${CMAKE_CURRENT_LIST_DIR}/../../components/usage_protocol"
"${CMAKE_CURRENT_LIST_DIR}/../../components/usage_ble"
"${CMAKE_CURRENT_LIST_DIR}/../../components/usage_ota"
"${CMAKE_CURRENT_LIST_DIR}/../../components/esp_desktop_buddy_folder_push")

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mr_read_pico)
Loading
Loading