Six silent failures found by installing this on a live system
Migrated the reference site off the YAML packages and onto the add-on. Every bug below presented identically: the add-on starts, logs "started", serves its UI, and cannot do its job. - run.sh needs #!/usr/bin/with-contenv sh. s6-overlay sanitises the environment for services, so a plain shebang means SUPERVISOR_TOKEN is absent and every Core API call is 401 - while homeassistant_api: true makes permissions look granted. Startup now prints the token length and probes the API. - Supervisor keys the image by config.yaml `version`, so rebuilding without a bump reuses the old image. Two fixes appeared not to work because of it. - Alpine is musl and has no aiohttp wheel on PyPI; deps now come from apk so nothing compiles on a client's Pi. - Alpine ships paho-mqtt 1.x, which has no CallbackAPIVersion. That raised at construction and took the control loop down with it - so MQTT setup is now wrapped too. Observability must never be able to stop the controller. - MQTT discovery is published from on_connect: paho silently drops QoS-0 publishes issued before the CONNACK, so the previous code announced nothing while logging "MQTT connected". - Repeated failures now log once a minute. Six warnings a second rolled the log buffer and destroyed the startup diagnostics needed to find the 401. - auto_start could never fire, because the store's defaults always supplied auto: False for the fallback to find. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016NckgXecasQb2eSsPYNSW6
This commit is contained in:
@@ -17,21 +17,34 @@ except ImportError: # pragma: no cover - container always has it
|
||||
|
||||
_LOG = logging.getLogger("goodwe.mqtt")
|
||||
|
||||
# ⚠️ The DEVICE name is half of every entity_id. Home Assistant composes
|
||||
# entity_id from device name + entity name, so "GoodWe RS485 Controller" plus
|
||||
# "GoodWe battery power" yields
|
||||
# sensor.goodwe_rs485_controller_goodwe_battery_power. object_id in the
|
||||
# discovery payload did NOT override it (tested on HA 2026.8). So the device is
|
||||
# named "GoodWe" and the entities are named without repeating it - that is what
|
||||
# makes the ids short, predictable, and identical on every install.
|
||||
DEVICE = {
|
||||
"identifiers": ["goodwe_rs485_controller"],
|
||||
"name": "GoodWe RS485 Controller",
|
||||
"name": "GoodWe",
|
||||
"manufacturer": "GoodWe (via RS485 meter emulation)",
|
||||
"model": "ES/BP series",
|
||||
}
|
||||
|
||||
# (key, name, unit, device_class, state_class, icon)
|
||||
# (key, object_id, name, unit, device_class, state_class, icon)
|
||||
#
|
||||
# ⚠️ object_id is what pins the entity_id. Without it Home Assistant derives the
|
||||
# id from the DEVICE name plus the entity name and produces
|
||||
# `sensor.goodwe_rs485_controller_goodwe_battery_power` - unpredictable, ugly,
|
||||
# and different if anyone renames the device. Dashboards and documentation need
|
||||
# these ids to be stable across every install, so they are declared, not derived.
|
||||
SENSORS = [
|
||||
("setpoint", "GoodWe setpoint", "W", "power", "measurement", None),
|
||||
("grid", "GoodWe grid power", "W", "power", "measurement", None),
|
||||
("battery", "GoodWe battery power", "W", "power", "measurement", None),
|
||||
("soc", "GoodWe battery SoC", "%", "battery", "measurement", None),
|
||||
("phase", "GoodWe maintenance phase", None, None, None, "mdi:battery-sync"),
|
||||
("status", "GoodWe controller status", None, None, None, "mdi:heart-pulse"),
|
||||
("setpoint", "goodwe_setpoint", "Setpoint", "W", "power", "measurement", None),
|
||||
("grid", "goodwe_grid_power", "Grid power", "W", "power", "measurement", None),
|
||||
("battery", "goodwe_battery_power", "Battery power", "W", "power", "measurement", None),
|
||||
("soc", "goodwe_battery_soc", "Battery SoC", "%", "battery", "measurement", None),
|
||||
("phase", "goodwe_maintenance_phase", "Maintenance phase", None, None, None, "mdi:battery-sync"),
|
||||
("status", "goodwe_controller_status", "Controller status", None, None, None, "mdi:heart-pulse"),
|
||||
]
|
||||
|
||||
BASE = "goodwe_ctl"
|
||||
@@ -45,24 +58,37 @@ class MqttPublisher:
|
||||
if not self.enabled:
|
||||
_LOG.info("MQTT not configured - status entities will not be published")
|
||||
return
|
||||
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
|
||||
client_id="goodwe_rs485_controller")
|
||||
# paho-mqtt 2.x requires a callback API version; 1.x has no such
|
||||
# argument and Alpine ships 1.x. Support both rather than pinning, so
|
||||
# the container can use the distro package instead of compiling.
|
||||
try:
|
||||
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
|
||||
client_id="goodwe_rs485_controller")
|
||||
except AttributeError:
|
||||
self.client = mqtt.Client(client_id="goodwe_rs485_controller")
|
||||
if username:
|
||||
self.client.username_pw_set(username, password or "")
|
||||
self.client.will_set(AVAILABILITY, "offline", retain=True)
|
||||
# ⚠️ Announce from on_connect, never straight after connect(). paho
|
||||
# processes the CONNACK on its network thread, so a publish issued
|
||||
# immediately after connect() is made while still disconnected - and
|
||||
# paho DROPS QoS-0 publishes when disconnected, silently. The result is
|
||||
# an add-on that logs "MQTT connected" and creates no entities at all.
|
||||
# As a bonus, this also re-announces after every reconnect.
|
||||
self.client.on_connect = lambda *_args, **_kw: self._announce()
|
||||
try:
|
||||
self.client.connect(host, int(port), keepalive=60)
|
||||
self.client.loop_start()
|
||||
self._announce()
|
||||
_LOG.info("MQTT connected to %s:%s", host, port)
|
||||
except OSError as err:
|
||||
_LOG.warning("MQTT connect failed (%s) - continuing without it", err)
|
||||
self.enabled = False
|
||||
|
||||
def _announce(self) -> None:
|
||||
for key, name, unit, dev_class, state_class, icon in SENSORS:
|
||||
for key, object_id, name, unit, dev_class, state_class, icon in SENSORS:
|
||||
cfg = {
|
||||
"name": name,
|
||||
"object_id": object_id,
|
||||
"unique_id": f"{BASE}_{key}",
|
||||
"state_topic": f"{BASE}/{key}",
|
||||
"availability_topic": AVAILABILITY,
|
||||
|
||||
Reference in New Issue
Block a user