TČ Samsung přes MIM-B19N: endpoint 172.16.1.17, plný poll, registry doc
- V096: endpoint home-01 TČ z placeholderu 192.168.1.103 na reálný Waveshare RS485 TO POE ETH (B) 172.16.1.17:502; telemetry_heat_pump.room_temp_c. - R__048: fn_telemetry_heat_pump_sample rozšířena (water_inlet, room_temp, defrost, alarm_code) — drop/comment bez parametrů dle konvence. - poll_heat_pump: místo TODO stubu (zapisoval dummy 45/55 °C!) skutečné čtení MIM bloku 50-75 + defrost reg 2; gate na comm_status ready (jinak skip); operating_mode off/heat/cool/auto/dhw/error; power_w NULL (MIM příkon nemá). - docs/04-modules/modbus-registers-mim-b19n.md (mapa, 9600 8E1, DIP adresa, troubleshooting E6xx) + heat-pump.md odkaz. Živý stav: TCP :502 OK, Modbus bez odpovědi (čeká na protokol převodníku / paritu EVEN / polaritu A-B — checklist v docu). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -418,6 +418,43 @@ async def poll_ev_chargers(site_id: int, db: asyncpg.Connection) -> None:
|
||||
asyncio.create_task(_on_ev_departure(site_id, str(code)))
|
||||
|
||||
|
||||
# Samsung MIM-B19N(T) — Modbus RTU slave za RS485→TCP (9600 8E1!).
|
||||
# Adresace: vnitřní jednotka IU má blok base = 50 + IU*50; zde IU 0 → 50..99.
|
||||
# Plný popis: docs/04-modules/modbus-registers-mim-b19n.md
|
||||
MIM_IU_BASE = 50 # blok vnitřní jednotky 0
|
||||
MIM_OFF_COMM_STATUS = 0 # b0 exist, b1 type OK, b2 ready, b3 comm error
|
||||
MIM_OFF_UNIT_TYPE = 1 # lower byte: 110=HE, 115-117=EHS, 120=HT
|
||||
MIM_OFF_ONOFF = 2
|
||||
MIM_OFF_MODE = 3 # 0 auto, 1 cool, 4 heat
|
||||
MIM_OFF_ROOM_TEMP = 9 # °C×10 signed
|
||||
MIM_OFF_ERROR_CODE = 13 # 0 = OK, 100-999 kód
|
||||
MIM_OFF_WATER_IN = 15 # °C×10 signed
|
||||
MIM_OFF_WATER_OUT = 16 # °C×10 signed
|
||||
MIM_OFF_DHW_ONOFF = 22
|
||||
MIM_OFF_DHW_TEMP = 25 # °C×10 (zásobník TUV)
|
||||
MIM_REG_DEFROST = 2 # modulový registr: 0=off, jinak defrost
|
||||
MIM_MODE_NAMES = {0: "auto", 1: "cool", 2: "dry", 3: "fan", 4: "heat"}
|
||||
|
||||
|
||||
def _mim_temp_c(raw: int) -> float | None:
|
||||
"""°C×10, signed 16bit; MIM drží 0 dokud jednotka hodnotu nedodá."""
|
||||
v = raw - 65536 if raw > 32767 else raw
|
||||
return round(v / 10.0, 1)
|
||||
|
||||
|
||||
def mim_operating_mode(on: int, mode: int, dhw_on: int, comm_ready: bool, error: int) -> str:
|
||||
if not comm_ready:
|
||||
return "offline"
|
||||
if error:
|
||||
return "error"
|
||||
parts = []
|
||||
if int(on) == 1:
|
||||
parts.append(MIM_MODE_NAMES.get(int(mode), f"mode{mode}"))
|
||||
if int(dhw_on) == 1:
|
||||
parts.append("dhw")
|
||||
return "+".join(parts) if parts else "off"
|
||||
|
||||
|
||||
async def poll_heat_pump(site_id: int, db: asyncpg.Connection) -> None:
|
||||
rows = await db.fetch(
|
||||
"""
|
||||
@@ -430,18 +467,54 @@ async def poll_heat_pump(site_id: int, db: asyncpg.Connection) -> None:
|
||||
measured_at = datetime.now(timezone.utc)
|
||||
for row in rows:
|
||||
code = row["code"]
|
||||
logger.info("TODO: heat pump Modbus registry pending (heat_pump=%s)", code)
|
||||
host = row["host"]
|
||||
port = int(row["port"] or 502)
|
||||
unit_id = int(row["unit_id"] if row["unit_id"] is not None else 1)
|
||||
try:
|
||||
client = await get_modbus_client(host, port)
|
||||
async with client.batch(unit_id) as mb:
|
||||
iu = await mb.read_holding_registers(MIM_IU_BASE, 26)
|
||||
defrost_raw = await mb.read_register(MIM_REG_DEFROST)
|
||||
except Exception as e:
|
||||
logger.warning("heat_pump %s: Modbus poll failed (%s)", code, e)
|
||||
continue
|
||||
|
||||
comm = int(iu[MIM_OFF_COMM_STATUS])
|
||||
comm_ready = (comm & 0b111) == 0b111
|
||||
error_code = int(iu[MIM_OFF_ERROR_CODE])
|
||||
mode_txt = mim_operating_mode(
|
||||
iu[MIM_OFF_ONOFF], iu[MIM_OFF_MODE], iu[MIM_OFF_DHW_ONOFF],
|
||||
comm_ready, error_code,
|
||||
)
|
||||
if not comm_ready:
|
||||
# MIM odpovídá, ale jednotka není ztrackovaná (b0-b2) — telemetrii
|
||||
# nezapisovat (samé nuly), jen log; trvalý stav = špatná adresa IU
|
||||
# nebo SEG5 "Use of central control" vypnuté.
|
||||
logger.warning(
|
||||
"heat_pump %s: jednotka není ready (comm_status=%s) — vzorek přeskočen",
|
||||
code, bin(comm),
|
||||
)
|
||||
continue
|
||||
|
||||
await db.execute(
|
||||
"select ems.fn_telemetry_heat_pump_sample($1::int, $2::int, $3::timestamptz, $4::int, $5::float8, $6::float8, $7::float8, $8::text)",
|
||||
"select ems.fn_telemetry_heat_pump_sample("
|
||||
"$1::int, $2::int, $3::timestamptz, $4::int, $5::float8, $6::float8,"
|
||||
" $7::float8, $8::text, $9::float8, $10::float8, $11::boolean, $12::int)",
|
||||
site_id,
|
||||
row["id"],
|
||||
measured_at,
|
||||
0,
|
||||
10.0,
|
||||
45.0,
|
||||
55.0,
|
||||
"standby",
|
||||
None, # příkon: MIM neměří — doplní elektroměr (Shelly/Chint)
|
||||
None, # venkovní teplota: v MIM mapě není
|
||||
_mim_temp_c(iu[MIM_OFF_WATER_OUT]),
|
||||
_mim_temp_c(iu[MIM_OFF_DHW_TEMP]),
|
||||
mode_txt,
|
||||
_mim_temp_c(iu[MIM_OFF_WATER_IN]),
|
||||
_mim_temp_c(iu[MIM_OFF_ROOM_TEMP]),
|
||||
bool(defrost_raw),
|
||||
error_code,
|
||||
)
|
||||
if error_code:
|
||||
logger.warning("heat_pump %s: error code %s", code, error_code)
|
||||
|
||||
|
||||
async def poll_loxone_sensors(site_id: int, db: asyncpg.Connection) -> None:
|
||||
|
||||
Reference in New Issue
Block a user