hculap/emodul

GitHub: hculap/emodul

一个用于Tech Sterowniki eModul.pl地暖控制器的Python CLI工具,支持通过MCP服务器与AI代理集成进行远程控制和管理。

Stars: 0 | Forks: 0

# emodul [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/8333e41e78122905.svg)](https://github.com/hculap/emodul/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/emodul.svg)](https://pypi.org/project/emodul/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![Claude Skill](https://img.shields.io/badge/Claude-Skill%20ready-D97757.svg)](SKILL.md) [![GitHub stars](https://img.shields.io/github/stars/hculap/emodul?style=social)](https://github.com/hculap/emodul/stargazers) 📖 **[着陆页](https://hculap.github.io/emodul/)** · 📜 **[更新日志](CHANGELOG.md)** · 🔌 **[PyPI](https://pypi.org/project/emodul/)** · 🤖 **[AGENT.md](AGENT.md)** 面向 **Tech Sterowniki eModul.pl** 云服务的非官方 Python CLI 工具 (波兰地暖控制器型号:L-4X WIFI、L-8、L-9、L-12 等)。 通过对 Angular SPA 包进行逆向工程开发,并针对社区 [`tech-controllers`](https://github.com/mariusz-ostoja-swierczynski/tech-controllers) Home Assistant 集成中发现的错误进行了加固处理, 并**旨在开箱即用地由 AI 代理驱动**,通过内置的 `SKILL.md` 文件实现。 ``` emodul status → live zone table with action (heating/idle) emodul settings audit → flags bad/non-default parameters across all controllers emodul zones set-temp Salon 21.5 → blocks until controller acknowledges (no race on re-read) emodul watch install-service → launchd/systemd background poller → SQLite event log ``` ## 为什么选择它 - **从终端控制您的地暖系统。** 设置温度、附加计划、审核配置、获取历史数据 — 无需点击,无需网页界面。 - **交给 AI 代理处理。** 每个命令都支持 `--json` 参数以输出稳定、机器可读的结果。代理无需了解 HTTP、JWT 或 PIN 处理 — 只需知道以 slug 命名的命令即可。 - **访问网页 SPA 隐藏的功能。** 服务菜单(PIN 5162)参数、原始统计信息、告警历史、多控制器交叉漂移检测、长期过渡日志。 - **重启后自动恢复。** 后台监视器安装为 launchd plist(macOS)或 systemd 用户单元(Linux)。崩溃后自动重启。通过操作系统密钥链在令牌过期时自动重新验证。 ## 三种使用方式 🤖 | 路径 | 适用于哪些代理 | 它能提供什么 | |---|---|---| | **A: MCP 服务器** | Claude Desktop / Cursor 聊天 / Continue / Cline / Zed / JetBrains AI / OpenCode / Gemini CLI | 仅需 `pipx install emodul` 并在客户端配置中添加一个 JSON 条目。聊天客户端可将 `get_status`、`set_zone_temperature` 等作为原生工具调用。 | | **B: AGENT.md 提示** | Claude Code / Codex CLI / Cursor 代理 / Aider | 粘贴单个 URL;CLI 代理会执行 `pipx install emodul && emodul skill install && emodul auth login --browser`。SKILL.md 会被自动发现。 | | **C: 复制粘贴后备方案** | claude.ai 网页版 / ChatGPT 网页版 / Cowork(沙箱环境) | 沙箱代理打印命令;用户在自己的终端中运行。 | 有关每种运行时的完整配置,请参见 [AGENT.md](AGENT.md)。 ### 路径 A 30 秒上手 — Claude Desktop ``` pipx install emodul emodul auth login --browser # one-time login (opens a local form) emodul install claude-desktop # drops MCP-flavored skill + writes the # mcpServers.emodul entry into your # claude_desktop_config.json (with backup) ``` 退出并重新打开 Claude Desktop(macOS 上按 ⌘+Q)→ 询问 *"客厅的温度是多少?"*,Claude 会原生调用 MCP 工具。 同时为 Claude Code 和 Claude Desktop 配置: ``` emodul install --all ``` 手动变体(如果您更愿意自己编辑 JSON): ``` { "mcpServers": { "emodul": { "command": "emodul", "args": ["mcp"] } } } ``` ### 路径 B 30 秒上手 — Claude Code(或任何 CLI 代理) 将此 URL 粘贴到您的代理中: ``` https://raw.githubusercontent.com/hculap/emodul/main/AGENT.md ``` 代理会处理一切:安装、技能注册、浏览器认证、默认模块选择。 ## 安装 ### 从 PyPI 安装(推荐) ``` pipx install emodul # isolated install, recommended # 或 pip install emodul # plain pip (use a venv on PEP-668 systems) ``` 安装后,将 emodul 连接到您的 AI 客户端: ``` emodul install claude-code # CLI-flavored skill for Claude Code / Codex CLI emodul install claude-desktop # MCP-flavored skill + mcpServers config for Claude Desktop emodul install --all # both at once (only what's detected) emodul install --dry-run # preview without writing emodul uninstall claude-desktop # reverse it ``` 每次安装都会对其修改的配置文件创建带时间戳的 `.bak-…` 备份;保留最近 5 个备份。如果现有 `mcpServers.emodul` 条目的参数不同,可使用 `--force` 覆盖。 验证: ``` emodul --version ``` ### 从源码安装(用于开发) ``` git clone https://github.com/hculap/emodul.git cd emodul python3 -m venv .venv .venv/bin/pip install -e . .venv/bin/emodul --version ``` 在 macOS 上,系统 Python 受 PEP-668 外部管理;使用 `.venv` 可以保持环境清洁。`pipx` 会自动处理此问题。如果您希望在 PATH 中直接使用 `emodul` 命令,请使用 `source .venv/bin/activate` 激活虚拟环境。 ## 首次设置 ### 浏览器流程(推荐 — 最适合由 AI 代理驱动时使用) ``` emodul auth login --browser ``` 会打开一个本地登录页面(`http://127.0.0.1:<随机端口>/`),表单采用 Apple 风格(支持深色模式)。您在**浏览器**中输入您的 eModul.pl 凭据 — CLI 会捕获 JWT 并存储它。运行此命令的代理永远看不到您的密码。 该流程会自动选择:当 stdin 不是 TTY 时(代理上下文)使用 `--browser`,在交互模式下使用 `--terminal`。可通过显式指定 `--terminal` / `--browser` 覆盖。 ### 终端流程(交互式) ``` emodul auth login --terminal --email you@example.com ``` 在标准输入中提示输入密码。 无论哪种方式,JWT 最终存储在 `~/.config/emodul/config.json`(权限 600)中,您的密码存储在操作系统密钥链中(macOS 上是 Keychain,Linux 上是 GNOME Keyring / KWallet,Windows 上是 Credential Locker)。未来遇到任何 401 错误时,CLI 都会静默重新验证。可使用 `--no-keychain` 选择退出。使用 `emodul auth forget-password` 移除密码。 …或者粘贴您已有的 JWT(例如,从 DevTools → Application → Local Storage → `token` 在 emodul.pl 上获取): ``` emodul auth import-token "eyJhbGciOi..." --user-id 123456789 # (稍后运行 `auth login` 以同步密钥链并启用自动刷新) ``` 选择一个默认控制器,这样 `-m` 参数就变成可选的了: ``` emodul modules list emodul modules select # name substring works (use a value from `modules list`) ``` 缓存波兰语翻译字典(16,368 个条目 — 用于解析图块和菜单中的 `txtId` 引用): ``` emodul i18n refresh ``` ## 日常命令 ### 状态与区域 ``` emodul status # rich table of all zones in default module emodul status --json # same data, machine-readable emodul zones list # current state per zone emodul zones list -a # cross-module, with "Module" column emodul zones show Salon # full data + raw JSON emodul zones audit # behavioural analysis (mean/min/max/stdev/gap) emodul zones audit --period week # uses /stats/linear emodul zones set-temp Salon 21.5 # constantTemp; blocks ~5-30s until settled emodul zones boost Salon 23 90 # 23 °C for 90 min, then revert emodul zones on Salon emodul zones off Salon emodul zones rename Salon "Living" emodul zones schedule Salon --mode global --index 0 # attach globalSchedule ``` **区域选择器**接受数字 `zone_id` 或唯一的、不区分大小写的名称子字符串。 **`--wait` / `--no-wait`**:默认情况下,所有对区域的写入操作都会阻塞,直到控制器清除其 `duringChange:"t"` 标志(否则 API 会在约 30 秒内报告旧值 — 这是 Home Assistant 集成的第 184 号问题)。使用 `--no-wait` 可禁用等待,实现“发后即忘”。 ### 设置(命名参数,无原始 IDO) 跨越 MU/MI/MS 共有 25 个命名 slug(没有 MP — 该 PIN 未知)。 ``` emodul settings list # inventory: name / label / category emodul settings show # dashboard table with audit verdicts emodul settings show --category safety # filter emodul settings show --include-locked # show items the server reports as access=false emodul settings get emergency-mode emodul settings set emergency-mode 30 emodul settings set diagnostic-file off --all-modules # apply to every controller emodul settings audit # bad/warn items + cross-module drift detection ``` Slug 分类:**安全**(紧急模式、防冻、执行器保护、温度最大/最小值)、**执行器**(迟滞、sigma 范围、天气控制、最佳启动、传感器校准)、**计划**(供暖、制冷、预设)、**诊断**(诊断文件、显示全部)。 ### 菜单(当需要访问原始 IDO 时) ``` emodul menu show MU # user menu (no PIN) emodul menu show MI # fitter menu (no PIN) emodul menu unlock MS 0 5162 # one-time PIN unlock — saved to config emodul menu show MS # subsequent reads auto-include PIN emodul menu set MI 3145755 30 # raw ido write emodul menu forget-pins MS # wipe saved PINs ``` 类型别名:`user`/`MU`, `fitters`/`MI`, `service`/`MS`, `manufacturer`/`MP`。**MP PIN 不是 5162** — 它是 Tech / 安装人员持有的单独代码。正常使用不需要此 PIN。 ### 计划 ``` emodul schedules list # all 5 globalSchedules: day mask, intervals, used-by emodul schedules show 0 # detail (by index) emodul schedules show "Salon i Łazienka" # detail (by name substring) ``` 每个 TECH 控制器恰好有 5 个全局计划槽位。CLI 可解码星期掩码(`Pn Wt Śr Cz Pt — —`)、时间段(`06:00-21:00 → 21.5 °C`)和待机温度。`Używają` 列列出了当前引用每个计划的区域。 ### 统计信息 ``` emodul stats available # what series exist emodul stats linear --period day # today's temp curves emodul stats linear --period week emodul stats linear --month 4 --year 2026 emodul stats column consumptions --period month --month 4 --year 2026 emodul stats csv consumptions --period month --month 4 --year 2026 --out apr.csv # 多月批处理: emodul stats dump --since 2025-10 --until 2026-05 # YYYY-MM emodul stats dump --since 6m # 6 months ago → now emodul stats dump --since 1y # 1 year ago → now emodul stats dump --since 12m --kind csv --state consumptions --out year.csv ``` **接受的周期**:`day`、`week` 和显式的 `--month X --year Y`。`year`/`total` 被服务器拒绝(在 L-4X WIFI 上返回 422 错误)。对于更长时间范围,可使用 `stats dump`,它会迭代月份并合并结果到一个有效负载中。默认情况下会自动丢弃空月份(`--keep-empty` 可覆盖此行为)。 ### 告警 ``` emodul alarms history # last 30 days, all types emodul alarms history --from 2026-04-01 --to 2026-05-18 --type warning emodul alarms ack 123 # acknowledge popup ``` ### 图块、国际化、底层命令 ``` emodul tiles list --translate # decode txtId via i18n cache emodul i18n refresh # fetch fresh 757 KB PL dictionary emodul i18n lookup 873 # txtId 873 → "Wersja modułu" emodul poll # one-shot delta poll emodul poll --since 1779120000 # only changes since epoch # 当您需要尚未封装的端点时的应急方案: emodul raw GET '/api/v1/users/{user_id}/modules' emodul raw POST '/api/v1/users/{user_id}/modules/{udid}/zones' \ --body '{"zone":{"id":9002,"zoneState":"zoneOn"}}' ``` `{user_id}` 和 `{udid}` 占位符会从您的配置中自动替换。 ## 监视器(后台进程) 一个长时间运行的轮询器,将继电器/区域状态变化记录到 SQLite 数据库中。仅在变化时插入 — 即使一年“无事发生”,数据库也保持很小。 ``` emodul watch run # foreground, Ctrl-C to stop emodul watch run --once # single poll then exit emodul watch run --interval 30 # custom poll seconds emodul watch install-service --interval 60 # auto-start on boot emodul watch status # recent events + service health emodul watch uninstall-service # stop + remove ``` **macOS** → 写入 `~/Library/LaunchAgents/com.emodul.watcher.plist`,使用 `launchctl load` 加载它,设置 `KeepAlive` + `ThrottleInterval=60`。 日志:`tail -f /tmp/emodul-watcher.{out,err}.log`。 **Linux** → 写入 `~/.config/systemd/user/emodul-watcher.service`,执行 `systemctl --user enable --now`。⚠️ 首次运行需执行:`sudo loginctl enable-linger $USER` 以在注销后保持进程存活。 日志:`journalctl --user -u emodul-watcher -f`。 ### 记录内容 数据库位于 `~/.local/state/emodul/state.db`: | 表 | 捕获内容 | 插入时机 | |---|---|---| | `tile_events` | 泵、无电压触点的开/关状态 | 仅在状态变化时 | | `zone_events` | 设定温度、当前温度、模式、每个区域的继电器状态 | 当设定温度/模式/继电器状态任一变化时 | | `run_log` | 启动、错误、API 失败 | 每次事件发生时 | 查询示例: ``` # Salon 过去 7 天的加热间隔: sqlite3 -header -column ~/.local/state/emodul/state.db \ "SELECT datetime(ts,'unixepoch','localtime') AS time, name, relay FROM zone_events WHERE name='Salon' AND ts > strftime('%s','now','-7 days') ORDER BY ts" # 本月泵循环计数: sqlite3 ~/.local/state/emodul/state.db \ "SELECT COUNT(*) FROM tile_events WHERE tile_id=8002 AND state=1 AND ts > strftime('%s','now','start of month')" ``` ## 面向 AI 代理 该 CLI 被设计为 LLM 代理的一个简洁工具接口。约定如下: 1. **每个命令都支持 `--json`**,以输出稳定的结构化数据。默认文本输出对人类友好(丰富的表格、颜色),但 `--json` 是规范格式。 2. **模块选择器 `-m`** 接受完整的 32 字符 udid、唯一前缀(例如 `abc12345`)或用户为其控制器命名的唯一名称子字符串。 3. **基于 slug 的设置**(`emodul settings list` 列出全部 25 个)代替原始 IDO。代理无需知道“紧急模式”位于 `MI:3145755:percent`。 4. **`--all-modules`** 用于在合理的情况下进行跨控制器扇出操作(`settings set`、`settings audit`、`zones list -a`)。 5. **`--no-wait`** 用于快速的“发后即忘”操作,当代理不关心确认落实时使用。 6. **`emodul raw [--body JSON]`** 是当代理需要访问未记录端点时的逃生通道。`{user_id}` 和 `{udid}` 会被自动替换。 一个典型的代理提示: ## 架构 ``` emodul/ api.py httpx wrapper; every endpoint as a method + wait_until_settled / is_*_settled helpers auth.py keychain-backed refresher (called by ApiClient on 401) config.py ~/.config/emodul/config.json (chmod 600) format.py °C ↔ tenths conversion, table rendering, JSON output i18n.py 16K-entry PL dictionary cache settings_map.py 25 named parameters → (menu_type, ido, kind, recommend, bad) storage.py SQLite schema for the watcher cli.py click root group, Ctx with module-name resolver commands/ auth.py login / import-token / whoami / logout / forget-password modules.py list / select / show / sync / rename zones.py list / show / set-temp / boost / on / off schedule / rename / schedule-set / audit menu.py show / unlock / set / forget-pins settings.py list / show / get / set / audit schedules.py list / show stats.py available / linear / column / csv / dump alarms.py history / ack misc.py tiles / i18n / poll / raw / status watch.py run / status / install-service / uninstall-service ``` ### 端点映射(概要) - 基础地址:`https://emodul.pl`(`.pl` 和 `.eu` 域名共享同一后端) - 认证:`Authorization: Bearer ` — **无 Cookie,必须包含 "Bearer " 前缀** - `POST /api/v1/authentication` → `{token, user_id}` - `GET /api/v1/users/{uid}/modules` 和 `…/modules/{udid}`(全量数据:区域 + 图块 + 计划) - `POST /…/zones` 用于 `constantTemp` / `timeLimit` / `zoneOn|zoneOff` - `POST /…/zones/{zoneId}/global_schedule` 关联一个 globalSchedule(请求体包含完整的计划定义 + `setInZones`) - `GET /…/menu/{MU|MI|MS|MP}[/{id}:{pin},…]` 遍历菜单树,并可内联注入 PIN - `POST /…/menu/{type}/ido/{id}` 写入任何参数 - `GET /api/v1/modules/{udid}/statistics/…` — **此处没有 `/users/` 前缀** - `GET /…/alarm_history/from/{date}/to/{date}/type/{all|alarm|warning|notification}` - `GET /api/v1/i18n/{lang}`(757 KB 的波兰语字典,16,368 个条目) - `GET /…/update/data/parents/{JSON}/alarm_ids/{JSON}[/last_update/{ts}]` — 是的,JSON 数组嵌入在 URL 路径中 ### 线规约定 - 所有温度均为 **整数的十分之一摄氏度**(`215` = 21.5 °C)。CLI 接受/显示摄氏度;转换在 `format.py` 中完成。 - 线格式代码:`7` = 十分之一摄氏度;`8` = 百分比;`10` = 开/关布尔值;`106` = 数值(子格式相关)。 - `humidity == 0` 表示 **“无传感器”**,而不是 0% 相对湿度 — CLI 返回 `None`。 - 任何写入操作后,服务器会在约 5-30 秒内保持 `duringChange:"t"` 标志,并在该窗口期内返回旧值。CLI 默认会轮询直到标志清除。 ## 安全 - JWT 没有 `exp` 声明 — 将其视为长期有效的 API 密钥。 - 配置文件 `~/.config/emodul/config.json` 的权限为 `chmod 600`。 - 密码(如果使用了 `auth login`)仅存储在操作系统密钥链中。在 macOS 上可通过 `security find-generic-password -s emodul -a ` 验证。 - **不要提交** `~/.config/emodul/` 或 `benchamr/probes/` 目录下的任何内容(它们可能包含 JWT)。 - CLI 默认不会将请求或响应记录到磁盘。监视器只持久化状态变化,永不存储令牌。 ## 注意事项与已知限制 - **MP(制造商)菜单的 PIN 未知。** PIN `5162` 仅适用于 MS 菜单。MP 需要不同的代码,Tech 未公开;正常使用无需此 PIN。泵防冻、最大地板温度安全设置、PID 与迟滞算法选择器等功能均位于 MP 菜单之后,此处不可见。 - **eModul 上没有 WebSocket / SSE 推送通道** — 已通过探测确认。所有“实时”更新均来自 HTTP 轮询。监视器执行此功能。 - **没有 `/refresh` 端点**,也没有长期有效的 API 令牌。CLI 通过密钥链支持的 401 重新认证来解决此问题。 - **统计信息**:`--period year` 和 `--period total` 被服务器拒绝(`422 Invalid range`)。对于当前数据,请使用 `--period day|week`;对于任意范围,请使用 `stats dump --since YYYY-MM`。 - **某些菜单项报告 `access=false`** — 这是服务器端限制。`settings show` 默认隐藏它们;使用 `--include-locked` 可选择显示。 ## 致谢 本项目的端点映射和若干加固模式得益于两个社区项目: - [`mariusz-ostoja-swierczynski/tech-controllers`](https://github.com/mariusz-ostoja-swierczynski/tech-controllers) — Home Assistant 集成,特别是 `tech.py`(HTTP 封装)、`const.py`(图块类型分类法)以及 `switch.py` / `select.py` / `number.py` 中记录 `duringChange:"t"` 竞态条件的注释。 - [`kamil-bednarek/homebridge-tech-emodul`](https://github.com/kamil-bednarek/homebridge-tech-emodul) — TypeScript Homebridge 插件,范围窄但代码清晰;确认了基本认证和区域 POST 的格式。 Tech Sterowniki 没有为 eModul 本身发布官方 SDK 或 schema,尽管他们的 [`techsterowniki/sinum-mcp`](https://github.com/techsterowniki/sinum-mcp) 仓库为其兄弟产品 Sinum 捆绑了 OpenAPI schema,这些 schema 确认了其代码库中使用的线规约定(×10 温度,单位代码 0-6)。
标签:AI代理驱动, API安全, API集成, Claude集成, Continue集成, Cursor集成, eModul.pl, Home Assistant集成, JSON输出, MCP服务器, Python编程, SQLite数据库, Tech Sterowniki, 事件日志, 云资产清单, 内核监控, 历史数据, 可观测性, 地板加热系统, 家用自动化, 抓包工具, 文档结构分析, 智能家居, 智能控制, 暖通空调, 服务管理, 波兰控制器, 温度控制, 物联网, 自动化控制, 设置审计, 逆向工具, 逆向工程, 逆向工程工具, 非官方工具