diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 3ea4c51..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,30 +0,0 @@ -# Repository Guidelines - -## Project Structure & Module Organization -- Energy simulator lives at `main.py`; MQTT examples under `data_transmission_example.py`. -- Home Assistant integration is in `custom_components/JackeryHome/` with `__init__.py`, `sensor.py`, `config_flow.py`, translations, and docs. -- Branding assets sit in `brands/`; release helpers and docs (e.g., `prepare_release.sh`, `README.md`, `energy_flow_card_config.yaml`) are at the repo root. -- Tests currently consist of targeted scripts such as `test_mqtt.py`; add new suites beside related modules. - -## Build, Test, and Development Commands -- `uv sync` — install the Python toolchain defined in `pyproject.toml` (uses uv for fast, reproducible envs). -- `uv run main.py` — run the MQTT simulator against the broker configured inside the script. -- `python test_mqtt.py` — quick publishing/subscription sanity check for MQTT topics. -- `./prepare_release.sh` — bump integration metadata and prep a tagged release; review the script before running. - -## Coding Style & Naming Conventions -- Python source follows 4-space indentation, snake_case identifiers, and descriptive constants (e.g., `MQTT_BROKER`). -- Keep Home Assistant entity IDs lowercase with underscores (`sensor.solar_power`). -- Maintain docstrings or top-of-file comments for modules that expose user-facing behavior; prefer concise inline comments for non-obvious logic. -- JSON/YAML assets should stay UTF-8, two-space indented, with trailing commas avoided. - -## Testing Guidelines -- Favor lightweight integration tests that exercise MQTT flows end-to-end (publish via simulator, assert consumption by HA sensors). -- Mirror Home Assistant’s naming pattern: `test_.py` with `async` helpers where applicable. -- Run tests locally before opening a PR; when adding new sensors, include topic fixtures and expected payload assertions. - -## Commit & Pull Request Guidelines -- Commits typically use an imperative summary (e.g., "Add inverter sensor mapping") followed by focused changes. -- Reference relevant issues in the body (`Fixes #42`) and keep commits scoped so they are reviewable. -- Pull requests should describe motivation, outline testing performed (`uv run main.py`, HA log screenshots), and mention any config migrations. -- Include UI screenshots/GIFs when altering Lovelace card guidance or other user-facing docs. diff --git a/README.md b/README.md index 13cef35..ef6dcc2 100644 --- a/README.md +++ b/README.md @@ -1,272 +1,145 @@ -# JackeryHome - Home Assistant 能源监控集成 +## JackeryHome – Home Assistant Energy Monitoring Integration [![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/hacs/integration) [![GitHub Release](https://img.shields.io/github/release/suyulin/jackery_home.svg)](https://github.com/suyulin/jackery_home/releases) [![License](https://img.shields.io/github/license/suyulin/jackery_home.svg)](LICENSE) -这是一个 Home Assistant 自定义集成,通过 MQTT 监控太阳能、电网、电池和家庭能源数据。 +JackeryHome is a **custom Home Assistant integration** that uses **MQTT** to monitor solar, grid, battery, EPS and home energy data from a Jackery energy system. -## 功能 +The integration is implemented in `custom_components/JackeryHome/sensor.py` and is built around a shared **coordinator** (`JackeryDataCoordinator`) that efficiently manages subscriptions and data requests for all sensors. -- 模拟太阳能发电、电网供电、家庭用电和电池充放电数据 -- 通过 MQTT 自动发现功能将传感器添加到 Home Assistant -- **提供 Home Assistant 自定义集成,用于接收和显示 MQTT 数据** -- 提供 Energy Flow Card Plus 卡片配置示例 -## 项目结构 +### Features -本项目包含两个主要部分: +- **Custom Home Assistant integration** (no YAML entities required) +- **MQTT-based data flow** with a shared `JackeryDataCoordinator` +- Periodic `data_get` requests every **5 seconds** for all sensors +- Real-time **power sensors** (W) and cumulative **energy sensors** (kWh) +- **Battery SoC** in percent with proper scaling +- Ready-to-use example configuration for **Energy Flow Card Plus** -1. **MQTT 模拟器** (`main.py`) - 模拟发送能源监控数据到 MQTT broker -2. **Home Assistant 自定义集成** (`custom_components/JackeryHome/`) - 接收 MQTT 数据并创建传感器实体 -### 集成架构 +### Prerequisites -集成采用**协调器模式**(Coordinator Pattern): -- 所有传感器共享一个 `JackeryDataCoordinator` 实例 -- 统一管理 MQTT 订阅和数据请求 -- 每 5 秒发送一次包含所有传感器 `meter_sn` 的数据请求 -- 自动解析响应并分发给对应的传感器实体 +Before the JackeryHome integration can receive any data, **two things must be in place**: -## 传感器列表 - -本项目会创建以下传感器: - -### 功率传感器(实时监测) - -- `sensor.jackeryhome_solar_power`: 太阳能发电功率(W) -- `sensor.jackeryhome_home_power`: 家庭用电功率(W) -- `sensor.jackeryhome_grid_import`: 从电网购买功率(W) -- `sensor.jackeryhome_grid_export`: 向电网出售功率(W) -- `sensor.jackeryhome_battery_charge`: 电池充电功率(W) -- `sensor.jackeryhome_battery_discharge`: 电池放电功率(W) -- `sensor.jackeryhome_battery_state_of_charge`: 电池电量百分比(%) - -### 能源传感器(用于能源仪表板) - -- `sensor.jackeryhome_solar_energy`: 太阳能发电总量(kWh) -- `sensor.jackeryhome_home_energy`: 家庭用电总量(kWh) -- `sensor.jackeryhome_grid_import_energy`: 电网购买总量(kWh) -- `sensor.jackeryhome_grid_export_energy`: 电网出售总量(kWh) -- `sensor.jackeryhome_battery_charge_energy`: 电池充电总量(kWh) -- `sensor.jackeryhome_battery_discharge_energy`: 电池放电总量(kWh) - -## 安装 - -### 方式一:通过 HACS 安装(推荐) - -1. **添加自定义存储库** - - 打开 HACS - - 点击右上角三个点 → "自定义存储库" - - 添加仓库 URL:`https://github.com/suyulin/jackery_home` - - 类别选择:`Integration` - - 点击"添加" - -2. **安装集成** - - 在 HACS 中搜索 "JackeryHome" - - 点击"安装" - - 重启 Home Assistant - -3. **配置集成** - - 进入 **设置** → **设备与服务** → **添加集成** - - 搜索 "JackeryHome" - - 输入 MQTT 主题前缀(默认:`homeassistant/sensor`) - - 点击提交完成配置 - -### 方式二:手动安装 - -1. 下载最新的 [Release](https://github.com/suyulin/jackery_home/releases) -2. 将 `custom_components/JackeryHome` 文件夹复制到你的 Home Assistant 配置目录的 `custom_components/` 文件夹中 -3. 重启 Home Assistant -4. 按照上述"配置集成"步骤进行配置 - -## 快速开始 - -### 使用 MQTT 模拟器 - -1. **安装依赖并运行模拟器** - ```bash - # 使用 uv(推荐) - uv sync - uv run main.py +1. **MQTT broker/server is configured and reachable** - # 或使用 pip - pip install paho-mqtt - python main.py - ``` - -2. **配置 MQTT Broker** + - A running MQTT broker (e.g. Mosquitto, EMQX, etc.) is required. + - Home Assistant's built‑in **MQTT integration** must be configured to connect to this broker. + - The broker address, port, username/password (if any) should match what your device and simulator are using. + - In your MQTT configuration, **replace the IP with the address of your own MQTT server**. + ![mqtt_config](./img/mqtt_config.png) + ![mqtt_config](./img/mqtt_config_2.png) +2. **Device is configured from the JackeryHome app** - 编辑 `main.py` 中的地址: - ```python - MQTT_BROKER = "192.168.0.101" # 修改为你的 MQTT Broker 地址 - ``` + - Use the vendor/JackeryHome mobile app to add the device/gateway and complete its initial setup. + - Make sure the device has network access and is configured so that it can connect to your MQTT/cloud backend. + - In the Jackery Home app, long-press the app logo to open the configuration screen. + - In the Jackery Home app configuration, **replace the IP with the address of your own MQTT server**. + ![jackery_home_config](./img/app_config_mqtt.png) -3. **在 Home Assistant 中查看传感器** +--- + +### Installation + +#### Option A: Install via HACS (recommended) + +1. **Add custom repository** - 传感器会自动通过 MQTT Discovery 添加 - -### 配置和使用 - -1. **确保已安装并配置集成**(参考上面的安装步骤) - -2. **运行模拟器** - ```bash - # 使用 uv(推荐) - uv run main.py + - Open HACS in Home Assistant + - Click the three dots in the top-right → **Custom repositories** + - Add repository URL: `https://github.com/suyulin/jackery_home` + - Category: `Integration` + - Click **Add** +2. **Install the integration** - # 或使用 python - python main.py - ``` + - In HACS, search for **"JackeryHome"** + - Click **Install** + - Restart Home Assistant +3. **Configure the integration** + + - Go to **Settings → Devices & Services → Add Integration** + - Search for **"JackeryHome"** + - Enter an MQTT topic prefix if needed (default: `homeassistant/sensor`) + - Submit to finish configuration + ![config](./img/jackery_home_add.png) + ![config](./img/jackery_home_config.png) +> **Requirement**: The built-in **MQTT integration** must be configured and connected to your MQTT broker **before** JackeryHome will work. -3. **查看传感器数据** - - 进入 **开发者工具** → **状态** - - 搜索 "jackeryhome" 或传感器名称(如 "Solar Power"、"Home Power" 等) - - 实体 ID 格式:`sensor.jackeryhome_{sensor_id}` +### Example: Energy Flow Card Plus -## Energy Flow Card Plus 配置 +You can use these sensors with the [Energy Flow Card Plus](https://github.com/flixlix/energy-flow-card-plus) Lovelace card. -### 安装卡片 +#### Install the card -1. **通过 HACS 安装(推荐):** - - 打开 HACS - - 点击"前端"(Frontend) - - 搜索 "Energy Flow Card Plus" - - 点击安装 - - 重启 Home Assistant +- Via HACS (recommended): + + - HACS → **Frontend** → search for **"Energy Flow Card Plus"** → install → restart HA. +- Manual: + + - Download from the GitHub repository. + - Place files under `www/community/energy-flow-card-plus/`. + - Add a Lovelace resource pointing to `/hacsfiles/energy-flow-card-plus/energy-flow-card-plus.js` (type: JavaScript module). -2. **手动安装:** - - 从 [GitHub](https://github.com/flixlix/energy-flow-card-plus) 下载最新版本 - - 将文件放到 `www/community/energy-flow-card-plus/` 目录 - - 在 Home Assistant 中添加资源: - - 设置 -> 仪表板 -> 右上角三点 -> 资源 - - URL: `/hacsfiles/energy-flow-card-plus/energy-flow-card-plus.js` - - 类型: JavaScript 模块 - -### 添加卡片到仪表板 - -1. 进入仪表板编辑模式 -2. 点击"添加卡片" -3. 选择"手动"(Manual) -4. 复制 `energy_flow_card_config.yaml` 中的配置 -5. 保存 - -### 基础配置示例 +#### Basic configuration example ```yaml type: custom:energy-flow-card-plus entities: solar: - entity: sensor.jackeryhome_solar_power - name: 太阳能 + entity: sensor.solar_power + name: Solar icon: mdi:solar-power grid: entity: - consumption: sensor.jackeryhome_grid_import # 从电网购买 - production: sensor.jackeryhome_grid_export # 向电网出售 - name: 电网 + consumption: sensor.grid_import_power # buying from grid + production: sensor.grid_export_power # selling to grid + name: Grid icon: mdi:transmission-tower battery: entity: - consumption: sensor.jackeryhome_battery_charge # 充电 - production: sensor.jackeryhome_battery_discharge # 放电 - state_of_charge: sensor.jackeryhome_battery_state_of_charge - name: 电池 + consumption: sensor.battery_charge_power # charging + production: sensor.battery_discharge_power # discharging + state_of_charge: sensor.battery_soc + name: Battery icon: mdi:battery home: - entity: sensor.jackeryhome_home_power - name: 家庭用电 + entity: sensor.home_power + name: Home icon: mdi:home-lightning-bolt display_zero_lines: mode: show transparency: 50 - grey_color: - - 189 - - 189 - - 189 + grey_color: [189, 189, 189] w_decimals: 0 kw_decimals: 2 color_icons: true animation_speed: 10 energy_date_selection: false ``` +![demo](img/demo.png) -**注意**:实体 ID 格式为 `sensor.jackeryhome_{sensor_id}`,其中 `{sensor_id}` 对应传感器 ID(如 `solar_power`、`grid_import` 等)。 +### Notes & Requirements -更多配置选项请查看 `energy_flow_card_config.yaml` 文件。 +- The MQTT broker must be running before you start the simulator or expect data in Home Assistant. +- The integration sends a single `data_get` request every 5 seconds for **all sensors**, reducing MQTT traffic. +- The device serial number (`device_sn`) is automatically obtained from LWT messages; no manual configuration is required. +- When the MQTT broker is unavailable, the coordinator logs a warning and retries automatically. + -## 项目文件说明 +--- -### 核心文件 -- `main.py`: MQTT 传感器模拟器主程序 -- `custom_components/JackeryHome/`: Home Assistant 自定义集成 - - `__init__.py`: 集成入口 - - `manifest.json`: 集成元数据 - - `sensor.py`: 传感器平台实现(包含协调器模式和所有传感器逻辑) - - `config_flow.py`: UI 配置流程 - - `strings.json`: 本地化字符串 - - `translations/zh-Hans.json`: 中文翻译 - - `README.md`: 集成技术文档(包含架构设计、MQTT 协议格式等) +### Links -### 文档和工具 -- `INTEGRATION_GUIDE.md`: 详细的集成使用指南 -- `energy_flow_card_config.yaml`: Energy Flow Card Plus 配置示例 -- `install.sh`: Linux/macOS 自动安装脚本 -- `install.ps1`: Windows PowerShell 自动安装脚本 -- `README.md`: 项目主文档(本文件) +- **Energy Flow Card Plus** – `https://github.com/flixlix/energy-flow-card-plus` +- **Home Assistant MQTT Discovery** – `https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery` +- **Home Assistant Developer Docs** – `https://developers.home-assistant.io/` +- **Paho MQTT Python Client** – `https://github.com/eclipse/paho.mqtt.python` -## 数据流向逻辑 +--- -1. **太阳能发电**:随机生成 200-3000W -2. **家庭用电**:随机生成 500-3500W -3. **电网功率**: - - grid_import(从电网购买):当家庭用电 > 太阳能发电时的差值 - - grid_export(向电网出售):当太阳能发电 > 家庭用电时的差值 -4. **电池功率**: - - battery_charge(充电):0-1000W - - battery_discharge(放电):0-1000W -5. **电池电量**:根据充放电动态变化(20%-100%) - -## 注意事项 - -- 确保 Home Assistant 已配置好 MQTT 集成 -- MQTT Broker 需要在运行此脚本之前启动 -- 集成会每 5 秒主动请求一次数据(所有传感器共享同一个请求) -- 数据为模拟值,用于演示目的 -- 集成会自动从 LWT 消息获取设备序列号,无需手动配置 - -## 文档 - -- [**HACS 发布指南**](HACS_PUBLISHING_GUIDE.md) - 如何发布到 HACS -- [自定义集成 README](custom_components/JackeryHome/README.md) - 集成技术文档 - -## 开发者 - -### 发布新版本 - -使用提供的发布脚本: - -```bash -./prepare_release.sh -``` - -或手动发布: - -1. 更新 `custom_components/JackeryHome/manifest.json` 中的版本号 -2. 提交更改并推送到 GitHub -3. 创建新的 Git tag(如 `v1.0.1`) -4. 在 GitHub 创建 Release - -详细说明请查看 [HACS 发布指南](HACS_PUBLISHING_GUIDE.md) - -## 相关链接 - -- [Energy Flow Card Plus GitHub](https://github.com/flixlix/energy-flow-card-plus) -- [Home Assistant MQTT Discovery](https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery) -- [Home Assistant 开发文档](https://developers.home-assistant.io/) -- [Paho MQTT Python Client](https://github.com/eclipse/paho.mqtt.python) - -## 许可证 +### License MIT License diff --git a/custom_components/JackeryHome/strings.json b/custom_components/JackeryHome/strings.json index d6dc86b..4ff5736 100644 --- a/custom_components/JackeryHome/strings.json +++ b/custom_components/JackeryHome/strings.json @@ -19,4 +19,4 @@ "single_instance_allowed": "只允许一个此集成的实例" } } -} +} \ No newline at end of file diff --git a/data_transmission_example.py b/data_transmission_example.py deleted file mode 100644 index 36bbd57..0000000 --- a/data_transmission_example.py +++ /dev/null @@ -1,272 +0,0 @@ -#!/usr/bin/env python3 -""" -数据传输示例 -演示如何使用修改后的 Energy Monitor 系统进行数据传输 -""" - -import json -import time -import random -import paho.mqtt.client as mqtt -battery_soc_point = "21548033" - ## 能量累计 -solar_energy_point = "16961537" -home_energy_point = "16980993" -grid_import_energy_point = "16969729" -grid_export_energy_point = "16970753" -battery_charge_energy_point = "16964609" -battery_discharge_energy_point = "16965633" - ## 实时功率 -solar_power_point = "1026001" -home_power_point = "21171201" -grid_import_power_point = "16930817" -grid_export_power_point = "16930817" -battery_charge_power_point = "16931841" -battery_discharge_power_point = "16931841" -class DataTransmissionExample: - - - def __init__(self, broker="192.168.1.100", port=1883): - self.broker = broker - self.port = port - self.client = None - self.running = False - self.device_status = "offline" - self.device_sn = "" - self.battery_soc = 0 - self.solar_energy = 0 - self.solar_energy = 0 - self.home_energy = 0 - self.grid_import_energy = 0 - self.grid_export_energy = 0 - self.battery_charge_energy = 0 - self.battery_discharge_energy = 0 - - self.energy_data = { - "solar_energy": 0, - "home_energy": 0, - "grid_import_energy": 0, - "grid_export_energy": 0, - "battery_charge_energy": 0, - "battery_discharge_energy": 0, - } - - ## 构造发送数据 - def construct_send_data(self): - data = { - "cmd": "data_get", - "gw_sn": self.device_sn, - "timestamp": str(int(time.time() * 1000)), - ## 随机数是字符串 - "token": str(random.randint(1000, 9999)), - "info": { - "dev_list": [ - { - "dev_sn": "ems_" + self.device_sn, - "meter_list": [ - battery_soc_point, - solar_energy_point, - home_energy_point, - grid_import_energy_point, - grid_export_energy_point, - battery_charge_energy_point, - battery_discharge_energy_point, - solar_power_point, - home_power_point, - grid_import_power_point, - grid_export_power_point, - battery_charge_power_point, - battery_discharge_power_point, - ] - } - ] - } - } - return data - ## 解析数据 - def parse_data(self, payload): - # payload 已经是字典类型,不需要再次解析 - if isinstance(payload, str): - data = json.loads(payload) - else: - data = payload - info = data["info"] - dev_list = info["dev_list"] - for dev in dev_list: - dev_sn = dev["dev_sn"] - meter_list = dev["meter_list"] - for meter in meter_list: - meter_sn = meter[0] - # 先转换为 float,然后判断是否可以转换为 int - meter_value_float = float(meter[1]) - # 如果小数部分为 0,则转换为 int,否则保留 float - meter_value = int(meter_value_float) if meter_value_float == int(meter_value_float) else meter_value_float - print(f"📨 收到设备数据: {dev_sn} {meter_sn} {meter_value}") - if meter_sn == battery_soc_point: - self.battery_soc = meter_value - print(f"📨 收到电池电量: {self.battery_soc}") - if meter_sn == solar_energy_point: - self.solar_energy = meter_value - print(f"📨 收到太阳能能量: {self.solar_energy}") - if meter_sn == home_energy_point: - self.home_energy = meter_value - print(f"📨 收到家庭能量: {self.home_energy}") - if meter_sn == grid_import_energy_point: - self.grid_import_energy = meter_value - print(f"📨 收到电网购买能量: {self.grid_import_energy}") - if meter_sn == grid_export_energy_point: - self.grid_export_energy = meter_value - print(f"📨 收到电网出售能量: {self.grid_export_energy}") - if meter_sn == battery_charge_energy_point: - self.battery_charge_energy = meter_value - print(f"📨 收到电池充电能量: {self.battery_charge_energy}") - if meter_sn == battery_discharge_energy_point: - self.battery_discharge_energy = meter_value - print(f"📨 收到电池放电能量: {self.battery_discharge_energy}") - if meter_sn == solar_power_point: - self.solar_power = meter_value - print(f"📨 收到太阳能功率: {self.solar_power}") - if meter_sn == home_power_point: - self.home_power = meter_value - print(f"📨 收到家庭功率: {self.home_power}") - ## 电网功率 负值为购买,正值为出售 - if meter_sn == grid_import_power_point: - self.grid_import_power = meter_value - if meter_value < 0: - self.grid_import_power = -meter_value - print(f"📨 收到电网购买功率: {self.grid_import_power}") - else: - self.grid_export_power = meter_value - print(f"📨 收到电网出售功率: {self.grid_export_power}") - ## 电池充放电功率 负值为充电,正值为放电 - if meter_sn == battery_charge_power_point: - self.battery_charge_power = meter_value - if meter_value < 0: - self.battery_charge_power = -meter_value - print(f"📨 收到电池充电功率: {self.battery_charge_power}") - else: - self.battery_discharge_power = meter_value - print(f"📨 收到电池放电功率: {self.battery_discharge_power}") - - def setup_mqtt(self): - """设置 MQTT 客户端""" - self.client = mqtt.Client(client_id="energy_device", callback_api_version=mqtt.CallbackAPIVersion.VERSION2) - self.client.on_connect = self.on_connect - self.client.on_message = self.on_message - - def on_connect(self, client, userdata, flags, rc, properties): - """MQTT 连接回调""" - if rc == 0: - print("✅ 连接到 MQTT 代理成功") - # 订阅数据获取请求主题 - client.subscribe("v1/iot_gw/gw/data") - client.subscribe("v1/iot_gw/gw_lwt") - print("✅ 订阅 v1/iot_gw/gw_data 主题成功") - print("✅ 订阅 v1/iot_gw/gw_lwt 主题成功") - else: - print(f"❌ 连接 MQTT 代理失败,错误码: {rc}") - - def on_message(self, client, userdata, msg): - """MQTT 消息接收回调""" - if msg.topic == "v1/iot_gw/gw/data": - print(f"📨 收到数据请求: {msg.payload.decode()}") - # 解析JSON - data = json.loads(msg.payload) - self.parse_data(data) - if msg.topic == "v1/iot_gw/gw_lwt": - print(f"📨 收到设备状态: {msg.payload.decode()}") - # 解析JSON - data = json.loads(msg.payload) - self.device_sn = data["gw_sn"] - info = data["info"] - print(f"📨 收到设备状态: {self.device_sn} {info}") - # 更新设备状态 - self.device_status = info - print(f"📨 设备状态: {self.device_status}") - - - def send_device_data(self): - data = self.construct_send_data() - - # 转换为 JSON 格式 - json_data = json.dumps(data, ensure_ascii=False, indent=2) - - # 发布到 device/data 主题 - result = self.client.publish("v1/iot_gw/cloud/data", json_data) - - if result.rc == mqtt.MQTT_ERR_SUCCESS: - print("📤 发送设备数据:") - print(f" 主题: v1/iot_gw/cloud/data") - print(f" 数据: {json_data}") - print() - else: - print(f"❌ 发送数据失败,错误码: {result.rc}") - - def start_simulation(self, duration=60): - """启动数据模拟""" - print("🚀 启动数据传输模拟") - print(f"📡 MQTT 代理: {self.broker}:{self.port}") - print(f"⏱️ 运行时长: {duration} 秒") - print("=" * 50) - - try: - # 连接 MQTT 代理 - self.client.connect(self.broker, self.port, 60) - self.client.loop_start() - self.running = True - - # 等待连接建立 - time.sleep(2) - - # 发送初始数据 - print("📤 发送初始数据...") - if self.device_sn != "": - self.send_device_data() - else: - print("❌ 设备SN为空,无法发送数据") - # return - - # 运行指定时间 - start_time = time.time() - while self.running and (time.time() - start_time) < duration: - time.sleep(1) - - # 每5秒发送一次数据(模拟设备主动发送) - if int(time.time() - start_time) % 5 == 0: - print("📤 设备主动发送数据...") - if self.device_sn != "": - self.send_device_data() - else: - print("❌ 设备SN为空,无法发送数据") - # return - - except KeyboardInterrupt: - print("\n⏹️ 用户中断模拟") - except Exception as e: - print(f"❌ 模拟出错: {e}") - finally: - self.stop_simulation() - - def stop_simulation(self): - """停止模拟""" - self.running = False - if self.client: - self.client.loop_stop() - self.client.disconnect() - print("✅ 模拟已停止") - -def main(): - """主函数""" - print("🏠 Energy Monitor 数据传输示例") - print("=" * 50) - print() - - # 创建示例实例 - example = DataTransmissionExample() - example.setup_mqtt() - - # 启动模拟(运行60秒) - example.start_simulation(duration=1000) - -if __name__ == "__main__": - main() diff --git a/delete.py b/delete.py deleted file mode 100644 index d90df4b..0000000 --- a/delete.py +++ /dev/null @@ -1,54 +0,0 @@ -import json -import paho.mqtt.client as mqtt - -MQTT_BROKER = "192.168.1.100" -MQTT_PORT = 1883 -MQTT_USERNAME = "" -MQTT_PASSWORD = "" -MQTT_CLIENT_ID = "ha_delete_discovery" - -SENSOR_IDS = [ - "solar_power", - "home_power", - "grid_import", - "grid_export", - "battery_charge", - "battery_discharge", - "battery_soc", - "battery_power", - "grid_power" -] - -# ==== 修改后的回调 ==== -def on_connect(client, userdata, flags, reason_code, properties=None): - if reason_code == 0: - print("✅ 已连接到 MQTT Broker") - else: - print(f"❌ 连接失败,原因码: {reason_code}") - -def on_publish(client, userdata, mid, reason_code, properties=None): - print(f"🧹 已发送删除命令 (mid={mid})") - -def delete_discovery_configs(): - client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, MQTT_CLIENT_ID) - if MQTT_USERNAME: - client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) - - client.on_connect = on_connect - client.on_publish = on_publish - - print("🚀 正在连接 MQTT Broker ...") - client.connect(MQTT_BROKER, MQTT_PORT, 60) - client.loop_start() - - for sensor_id in SENSOR_IDS: - topic = f"homeassistant/sensor/{sensor_id}/config" - client.publish(topic, None, retain=True) - print(f"🗑️ 已发布空配置以删除实体:{sensor_id}") - - client.loop_stop() - client.disconnect() - print("✅ 所有 Discovery 配置已删除") - -if __name__ == "__main__": - delete_discovery_configs() diff --git a/img/app_config_mqtt.png b/img/app_config_mqtt.png new file mode 100644 index 0000000..d0745fb Binary files /dev/null and b/img/app_config_mqtt.png differ diff --git a/img/demo.png b/img/demo.png new file mode 100644 index 0000000..07e0959 Binary files /dev/null and b/img/demo.png differ diff --git a/img/jackery_home_add.png b/img/jackery_home_add.png new file mode 100644 index 0000000..e661cf0 Binary files /dev/null and b/img/jackery_home_add.png differ diff --git a/img/jackery_home_config.png b/img/jackery_home_config.png new file mode 100644 index 0000000..f08ca85 Binary files /dev/null and b/img/jackery_home_config.png differ diff --git a/img/mqtt_config.png b/img/mqtt_config.png new file mode 100644 index 0000000..2e37eaf Binary files /dev/null and b/img/mqtt_config.png differ diff --git a/img/mqtt_config_2.png b/img/mqtt_config_2.png new file mode 100644 index 0000000..bc3b123 Binary files /dev/null and b/img/mqtt_config_2.png differ diff --git a/main.py b/main.py deleted file mode 100644 index 273a8c9..0000000 --- a/main.py +++ /dev/null @@ -1,191 +0,0 @@ -import json -import time -import random -import paho.mqtt.client as mqtt - -MQTT_BROKER = "192.168.1.100" -MQTT_PORT = 1883 -MQTT_CLIENT_ID = "jackery_home_simulator" - -# 模式控制变量 -# 0: 自发自用模式 (Self-consumption) -# 1: 电池优先模式 (Battery Priority) -current_mode = 0 - -# 模式名称映射 -MODE_NAMES = { - 0: "自发自用模式", - 1: "电池优先模式" -} - -client = mqtt.Client(client_id=MQTT_CLIENT_ID, callback_api_version=mqtt.CallbackAPIVersion.VERSION2) - -# MQTT 连接回调 -def on_connect(client, userdata, flags, rc, properties): - print("Connected to MQTT broker with result code " + str(rc)) - publish_discovery_configs() - # 订阅模式控制命令主题 - client.subscribe("homeassistant/select/mode_control/set") - print("Subscribed to mode control command topic") - -# MQTT 消息接收回调 -def on_message(client, userdata, msg): - global current_mode - if msg.topic == "homeassistant/select/mode_control/set": - try: - payload = msg.payload.decode() - # 支持数字或模式名称 - if payload.isdigit(): - new_mode = int(payload) - elif payload == "自发自用模式": - new_mode = 0 - elif payload == "电池优先模式": - new_mode = 1 - else: - print(f"Invalid mode payload: {payload}") - return - - if new_mode in [0, 1]: - current_mode = new_mode - # 发布新的状态(使用模式名称) - client.publish("homeassistant/select/mode_control/state", MODE_NAMES[current_mode]) - print(f"模式已切换到: {MODE_NAMES[current_mode]} ({current_mode})") - else: - print(f"Invalid mode value: {new_mode}") - except ValueError: - print(f"Invalid mode payload: {msg.payload.decode()}") - -client.on_connect = on_connect -client.on_message = on_message - - -def publish_discovery_configs(): - """发布 Home Assistant MQTT Discovery 配置""" - sensors = { - "solar_power": { - "name": "Solar Power", - "unit": "W", - "icon": "mdi:solar-power", - "device_class": "power", - }, - "home_power": { - "name": "Home Power", - "unit": "W", - "icon": "mdi:home-lightning-bolt", - "device_class": "power", - }, - # 电网 - 分为购买和出售 - "grid_import": { - "name": "Grid Import", - "unit": "W", - "icon": "mdi:transmission-tower-import", - "device_class": "power", - }, - "grid_export": { - "name": "Grid Export", - "unit": "W", - "icon": "mdi:transmission-tower-export", - "device_class": "power", - }, - # 电池 - 分为充电和放电 - "battery_charge": { - "name": "Battery Charge", - "unit": "W", - "icon": "mdi:battery-charging", - "device_class": "power", - }, - "battery_discharge": { - "name": "Battery Discharge", - "unit": "W", - "icon": "mdi:battery-minus", - "device_class": "power", - }, - "battery_soc": { - "name": "Battery State of Charge", - "unit": "%", - "icon": "mdi:battery-70", - "device_class": "battery", - }, - } - - for sensor_id, props in sensors.items(): - topic = f"homeassistant/sensor/{sensor_id}/config" - payload = { - "name": props["name"], - "state_topic": f"homeassistant/sensor/{sensor_id}/state", - "unit_of_measurement": props["unit"], - "device_class": props["device_class"], - "icon": props["icon"], - "unique_id": sensor_id, - } - client.publish(topic, json.dumps(payload), retain=True) - print(f"Published discovery config for {sensor_id}") - - # 发布模式控制的 discovery 配置 - mode_topic = "homeassistant/select/mode_control/config" - mode_payload = { - "name": "运行模式", - "state_topic": "homeassistant/select/mode_control/state", - "command_topic": "homeassistant/select/mode_control/set", - "options": ["自发自用模式", "电池优先模式"], - "icon": "mdi:cog-outline", - "unique_id": "mode_control", - } - client.publish(mode_topic, json.dumps(mode_payload), retain=True) - print("Published discovery config for mode_control") - - # 发布初始模式状态(使用模式名称) - client.publish("homeassistant/select/mode_control/state", MODE_NAMES[current_mode]) - print(f"Published initial mode state: {MODE_NAMES[current_mode]} ({current_mode})") - - -def publish_sensor_data(): - """定期发布模拟功率数据""" - battery_soc = random.uniform(20, 100) # 初始电池电量 - - while True: - solar = random.uniform(200, 3000) # 太阳能发电 - home = random.uniform(500, 3500) # 家庭负载 - grid = home - solar # 电网供电(可能为负) - battery = random.uniform(-1000, 1000) # 电池充/放电 - - # 将电网功率分离为购买(import)和出售(export) - grid_import = max(0, grid) # 从电网购买(正值) - grid_export = max(0, -grid) # 向电网出售(转为正值) - - # 将电池功率分离为充电和放电 - battery_charge = max(0, -battery) # 充电(转为正值) - battery_discharge = max(0, battery) # 放电(正值) - - # 根据电池充放电模拟电量变化 - if battery < 0: # 充电 - battery_soc = min(100, battery_soc + 0.5) - elif battery > 0: # 放电 - battery_soc = max(0, battery_soc - 0.3) - - data = { - "solar_power": round(solar, 2), - "home_power": round(home, 2), - "grid_import": round(grid_import, 2), - "grid_export": round(grid_export, 2), - "battery_charge": round(battery_charge, 2), - "battery_discharge": round(battery_discharge, 2), - "battery_soc": round(battery_soc, 1), - } - - for key, value in data.items(): - topic = f"homeassistant/sensor/{key}/state" - client.publish(topic, value) - - # 发布当前模式状态 - client.publish("homeassistant/select/mode_control/state", MODE_NAMES[current_mode]) - - print("Published:", data, f"| 运行模式: {MODE_NAMES[current_mode]}") - - time.sleep(5) - - -if __name__ == "__main__": - client.connect(MQTT_BROKER, MQTT_PORT, 60) - client.loop_start() - publish_sensor_data()