Skip to content

Commit cbfc1f2

Browse files
hiroaki0923claude
andcommitted
Refactor and add disclaimer to README
Changes: - Clean up code comments and improve readability - Use datetime.timezone instead of zoneinfo for JST handling - Add disclaimer section warning about AISEG2 device load - Emphasize self-responsibility and appropriate interval settings - Note unofficial status of the tool 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 42055ae commit cbfc1f2

2 files changed

Lines changed: 65 additions & 57 deletions

File tree

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,17 @@ MQTT Discoveryにより、以下のセンサーが自動的に作成されます
157157
### データが取得できない場合
158158

159159
- AISEG2のファームウェアバージョンによってはWebインターフェースが異なる可能性があります
160-
- `LOG_LEVEL=DEBUG`に設定して詳細なログを確認してください
160+
161+
## 免責事項
162+
163+
**重要:** 本ツールはAISEG2デバイスのWebインターフェースに定期的にアクセスしてデータを取得します。これにより、AISEG2デバイスに負荷がかかる可能性があります。
164+
165+
- 本ツールの使用は自己責任でお願いします
166+
- AISEG2デバイスへの過度なアクセスは機器の動作に影響を与える可能性があります
167+
- 適切な実行間隔(`INTERVAL_SECONDS`)を設定し、デバイスへの負荷を最小限に抑えてください
168+
- 本ツールの使用によって生じたいかなる損害についても、作者は責任を負いません
169+
- AISEG2は株式会社パナソニックの製品であり、本ツールは非公式のものです
161170

162171
## ライセンス
163172

164-
MIT License
173+
Apache-2.0 License

aiseg2_publish.py

Lines changed: 54 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,22 @@
22
# -*- coding: utf-8 -*-
33
"""
44
AISEG2 → MQTT Discovery (per-circuit daily kWh)
5-
- ブログ記載コードと等価のスクレイピング手順
6-
* 各リクエストごとに HTTPDigestAuth を付与
7-
* 総量: /page/graph/52111, 53111, 54111, 51111 の <span id="val_kwh"> 先頭
8-
* 回路一覧: /page/setting/installation/734 の window.onload JSON(arrayCircuitNameList, strBtnType=="1")
9-
* 回路kWh: /page/graph/584?data=base64({"circuitid":"NN"}) の <span id="val_kwh"> 先頭
5+
- スクレイプ: ブログ検証コードと同等(HTTPDigestAuth、/52111/53111/54111/51111、/584?data=base64({"circuitid":"NN"}))
106
- MQTT:
11-
* Discovery/config retain=True
12-
* stateretain=True(初回/再起動時に確実に値付与
13-
* 送信は publish → wait_for_publish() でACK待ち(sleep不要)
14-
- エンティティID: object_id で回路番号固定(sensor.c{ID}
15-
- 単発実行想定(定期実行は HA Automation / cron 推奨)
7+
* Discovery/config: retain=True
8+
* state: retain=True(HA再起動後も直ちに当日値を復元
9+
* publishはACK待ち(wait_for_publish)、sleep不使用
10+
- エンティティID: object_id を "aiseg2mqtt_..." として固定(例: sensor.aiseg2mqtt_c12
11+
- リセット表現: state_class=total + last_reset=当日0:00(JST)
1612
"""
1713

1814
import os
1915
import sys
2016
import json
2117
import base64
2218
import re
23-
import datetime as dt
19+
from datetime import datetime, timezone, timedelta
2420
from typing import Dict, List, Any
25-
from zoneinfo import ZoneInfo
2621

2722
import requests
2823
from requests.auth import HTTPDigestAuth
@@ -34,23 +29,30 @@
3429
load_dotenv(dotenv_path=os.getenv("AISEG2_ENV_FILE", ".env"))
3530

3631
# ----- Settings -----
37-
AISEG_HOST = os.getenv("AISEG_HOST", "192.168.0.216")
38-
AISEG_USER = os.getenv("AISEG_USER", "aiseg")
39-
AISEG_PASS = os.getenv("AISEG_PASS", "")
32+
AISEG_HOST = os.getenv("AISEG_HOST", "192.168.0.216")
33+
AISEG_USER = os.getenv("AISEG_USER", "aiseg")
34+
AISEG_PASS = os.getenv("AISEG_PASS", "")
4035

41-
MQTT_HOST = os.getenv("MQTT_HOST", "127.0.0.1")
42-
MQTT_PORT = int(os.getenv("MQTT_PORT", "1883"))
43-
MQTT_USER = os.getenv("MQTT_USER", "")
44-
MQTT_PASS = os.getenv("MQTT_PASS", "")
45-
MQTT_PREFIX = os.getenv("MQTT_PREFIX", "homeassistant")
36+
MQTT_HOST = os.getenv("MQTT_HOST", "127.0.0.1")
37+
MQTT_PORT = int(os.getenv("MQTT_PORT", "1883"))
38+
MQTT_USER = os.getenv("MQTT_USER", "")
39+
MQTT_PASS = os.getenv("MQTT_PASS", "")
40+
MQTT_PREFIX = os.getenv("MQTT_PREFIX", "homeassistant")
4641

4742
DEVICE_ID = os.getenv("DEVICE_ID", "aiseg2-scrape")
4843
DEVICE_NAME = os.getenv("DEVICE_NAME", "AISEG2 (Scraped)")
4944
MANUFACTURER = os.getenv("MANUFACTURER", "Panasonic")
5045
MODEL = os.getenv("MODEL", "AISEG2")
5146

5247
AISEG_TIMEOUT = int(os.getenv("AISEG_TIMEOUT", "10"))
53-
SCAN_TERM = os.getenv("SCAN_TERM", "day") # day固定想定
48+
SCAN_TERM = os.getenv("SCAN_TERM", "day") # day 固定想定
49+
50+
# ----- 時刻(JSTの当日0:00をlast_resetに使う) -----
51+
JST = timezone(timedelta(hours=9))
52+
def today_reset_iso() -> str:
53+
now = datetime.now(JST)
54+
reset = now.replace(hour=0, minute=0, second=0, microsecond=0)
55+
return reset.isoformat()
5456

5557
# ----- HTTP / Parse -----
5658
def _auth() -> HTTPDigestAuth:
@@ -114,15 +116,15 @@ def fetch_circuit_kwh(circuit_id: str) -> float:
114116

115117
# ----- MQTT -----
116118
def mqtt_client() -> mqtt.Client:
117-
mc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="", protocol=mqtt.MQTTv311)
119+
mc = mqtt.Client(protocol=mqtt.MQTTv311)
118120
if MQTT_USER:
119121
mc.username_pw_set(MQTT_USER, MQTT_PASS)
120122
mc.max_inflight_messages_set(100)
121123
mc.connect(MQTT_HOST, MQTT_PORT, 10)
122-
mc.loop_start() # 送受信ループ開始(ACK処理のため必須
124+
mc.loop_start() # ACK処理のため必須
123125
return mc
124126

125-
def publish_and_wait(mc: mqtt.Client, topic: str, payload: str, qos: int = 1, retain: bool = True) -> None:
127+
def publish_and_wait(mc: mqtt.Client, topic: str, payload: str | None, qos: int = 1, retain: bool = True) -> None:
126128
"""PublishしてACKを待つ(sleep不要)"""
127129
info = mc.publish(topic, payload, qos=qos, retain=retain)
128130
info.wait_for_publish()
@@ -143,60 +145,60 @@ def energy_state_topic(uid: str, key: str) -> str:
143145
def circuit_state_topic(uid: str, cid: str) -> str:
144146
return f"{MQTT_PREFIX}/aiseg2/{uid}/c{cid}/kwh/state"
145147

146-
def publish_discovery_energy(mc: mqtt.Client, uid: str, key: str, name: str, object_id: str, avty_t: str, attr_t: str) -> str:
148+
def publish_discovery_energy(mc: mqtt.Client, uid: str, key: str, name: str,
149+
object_id: str, avty_t: str, attr_t: str, last_reset_iso: str) -> str:
150+
"""
151+
合計系(当日使用/買電/売電/発電)。entity_id は object_id に依存。
152+
"""
147153
uniq = f"{uid}_{key}"
148154
cfg_t = f"{MQTT_PREFIX}/sensor/{uniq}/config"
149155
st_t = energy_state_topic(uid, key)
150-
# 日本時間の今日の0時を計算
151-
jst = ZoneInfo("Asia/Tokyo")
152-
now_jst = dt.datetime.now(jst)
153-
today_midnight_jst = now_jst.replace(hour=0, minute=0, second=0, microsecond=0)
154-
155156
payload = {
156157
"name": name,
157-
"object_id": f"aiseg2mqtt_{object_id}", # ← prefix追加
158+
"object_id": f"aiseg2mqtt_{object_id}", # sensor.aiseg2mqtt_total_today 等
158159
"uniq_id": uniq,
159160
"dev": {"ids":[uid], "name": DEVICE_NAME, "mf": MANUFACTURER, "mdl": MODEL, "sw": "web-scrape"},
160-
"stat_t": st_t, "avty_t": avty_t,
161+
"stat_t": st_t,
162+
"avty_t": avty_t,
161163
"dev_cla": "energy",
162-
"stat_cla": "total",
164+
"stat_cla": "total", # デイリーリセットの累積値
165+
"last_reset": last_reset_iso,
163166
"unit_of_meas": "kWh",
164167
"val_tpl": "{{ value_json.kwh }}",
165168
"json_attr_t": attr_t,
166-
"last_reset": today_midnight_jst.isoformat(),
167169
}
168170
publish_and_wait(mc, cfg_t, json.dumps(payload, ensure_ascii=False))
169171
return st_t
170172

171-
def publish_discovery_circuit(mc: mqtt.Client, uid: str, cid: str, cname: str, avty_t: str) -> str:
173+
def publish_discovery_circuit(mc: mqtt.Client, uid: str, cid: str, cname: str,
174+
avty_t: str, last_reset_iso: str) -> str:
175+
"""
176+
回路別(当日kWh)。entity_id は sensor.aiseg2mqtt_c{cid} に固定。
177+
"""
172178
uniq = f"{uid}_c{cid}_kwh"
173179
cfg_t = f"{MQTT_PREFIX}/sensor/{uniq}/config"
174180
st_t = circuit_state_topic(uid, cid)
175-
# 日本時間の今日の0時を計算
176-
jst = ZoneInfo("Asia/Tokyo")
177-
now_jst = dt.datetime.now(jst)
178-
today_midnight_jst = now_jst.replace(hour=0, minute=0, second=0, microsecond=0)
179-
180181
payload = {
181182
"name": cname,
182-
"object_id": f"aiseg2mqtt_c{cid}", # ← prefix追加
183+
"object_id": f"aiseg2mqtt_c{cid}",
183184
"uniq_id": uniq,
184185
"dev": {"ids":[uid], "name": DEVICE_NAME, "mf": MANUFACTURER, "mdl": MODEL, "sw": "web-scrape"},
185-
"stat_t": st_t, "avty_t": avty_t,
186+
"stat_t": st_t,
187+
"avty_t": avty_t,
186188
"dev_cla": "energy",
187189
"stat_cla": "total",
190+
"last_reset": last_reset_iso,
188191
"unit_of_meas": "kWh",
189192
"val_tpl": "{{ value_json.kwh }}",
190-
"json_attr_t": st_t,
191-
"last_reset": today_midnight_jst.isoformat(),
193+
"json_attr_t": st_t, # state payloadを属性にも流用
192194
}
193195
publish_and_wait(mc, cfg_t, json.dumps(payload, ensure_ascii=False))
194196
return st_t
195197

196-
197198
# ----- Main -----
198199
def main() -> None:
199200
uid = DEVICE_ID
201+
last_reset_iso = today_reset_iso()
200202

201203
# 1) データ取得
202204
circuits = fetch_circuit_catalog()
@@ -206,15 +208,14 @@ def main() -> None:
206208
# 2) MQTT接続
207209
mc = mqtt_client()
208210

209-
# 3) Availability / Meta(retain)
211+
# 3) Availability / Meta(retain=True
210212
avty_t = availability_topic(uid)
211213
publish_and_wait(mc, avty_t, "online")
212-
213214
meta_t = meta_topic(uid)
214-
meta = {"term": SCAN_TERM, "circuit_count": len(per), "ts": dt.datetime.now().isoformat()}
215+
meta = {"term": SCAN_TERM, "circuit_count": len(per), "ts": datetime.now(JST).isoformat()}
215216
publish_and_wait(mc, meta_t, json.dumps(meta, ensure_ascii=False))
216217

217-
# 4) Discovery(config retained
218+
# 4) Discovery(config 全件・retain=True
218219
total_map = [
219220
("total_use_kwh","Total Energy Today","total_today"),
220221
("buy_kwh", "Purchased Energy Today","buy_today"),
@@ -223,15 +224,13 @@ def main() -> None:
223224
]
224225
total_state_topics: Dict[str, str] = {}
225226
for key, disp, obj in total_map:
226-
st = publish_discovery_energy(mc, uid, key, disp, obj, avty_t, meta_t)
227+
st = publish_discovery_energy(mc, uid, key, disp, obj, avty_t, meta_t, last_reset_iso)
227228
total_state_topics[key] = st
228229

229-
circuit_state_topics: List[Dict[str, str]] = []
230230
for c in per:
231-
st = publish_discovery_circuit(mc, uid, c["id"], c["name"], avty_t)
232-
circuit_state_topics.append({"topic": st, "id": c["id"]})
231+
publish_discovery_circuit(mc, uid, c["id"], c["name"], avty_t, last_reset_iso)
233232

234-
# 5) state(retained, ACK待ち
233+
# 5) state(全件・retain=True
235234
for key, st in total_state_topics.items():
236235
publish_and_wait(mc, st, json.dumps({"kwh": totals.get(key, 0.0)}))
237236

@@ -247,7 +246,7 @@ def main() -> None:
247246
try:
248247
main()
249248
except Exception as e:
250-
# 失敗時は availability=offline を残す(ACK待ちで確実に送る)
249+
# 失敗時は availability=offline をretainで残す
251250
try:
252251
mc = mqtt_client()
253252
publish_and_wait(mc, availability_topic(DEVICE_ID), "offline")

0 commit comments

Comments
 (0)