OpenEPaperLink / Home_Assistant_Integration

Home assistant Integration for the OpenEPaperLink project
Apache License 2.0
132 stars 33 forks source link

Displays not updating, no errors in HA #109

Closed DrBlokmeister closed 5 months ago

DrBlokmeister commented 5 months ago

Describe the bug Occasionally I suffer from displays not updating, even though the update service calls do not produce an error in Home Assistant. The camera of the device displays the updated image, but this does not show up when navigating to the Epaperlink AP. I also don't see anything in the AP log that might indicate an update is pending or received.

The AP does seem to indicate that the tags are visible. The last seen and expected checkin indicators update. It seems so me that the AP is simply unaware that a new image should be sent to the tag.

Home Assistant also seems to lose track of the tags. All values from Home Assistant stop updating.

Note: most of the time, the system works just fine. Updating the tags via Home Assistant is received by the AP, and within a minute, the tag updates.

To Reproduce Steps to reproduce the behavior: It is an intermittent issue, making it hard to reproduce. This happens to me every day/other day. Sometimes rebooting the AP helps. I also find that sometimes moving the tag close to another AP can solve the issue. Reloading the HA integration also seems to work. Upon reloading, I see a lot of open_epaper_link errors that the platform does not generate unique IDs. Afterwards the sensors in HA update and show recent values. When I then call services to update the displays, these are received by the AP and get sent to the tags. However, both steps do not always solve the issue immediately. I have yet to find a clear route to solve this issue.

It seems to me like the connection is lost between the AP and HA, without the integration noticing.

Please let me know what kind of additional logging I might need.

jterrace commented 5 months ago

I'm seeing the same thing. It seems there are a bunch of entities without unique IDs. When the integration tries to query them, it fails and then it eventually crashes with a maximum recursion reached.

2024-01-25 22:32:57.544 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [281472352984512] id was not found yet, please wait for the display to check in at least once open_epaper_link.0000021f3e473414
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 238, in handle_call_service
    response = await hass.services.async_call(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2149, in async_call
    response_data = await coro
                    ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2186, in _execute_service
    return await target(service_call)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/open_epaper_link/__init__.py", line 38, in drawcustomservice
    imgbuff = await hass.async_add_executor_job(customimage,entity_id, service, hass)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/open_epaper_link/imagegen.py", line 112, in customimage
    raise HomeAssistantError("id was not found yet, please wait for the display to check in at least once " + entity_id)
homeassistant.exceptions.HomeAssistantError: id was not found yet, please wait for the display to check in at least once open_epaper_link.0000021f3e473414

2024-01-25 23:10:10.480 ERROR (Thread-4 (establish_connection)) [websocket] error from callback <bound method Hub.on_close of <custom_components.open_epaper_link.hub.Hub object at 0xffff6d9c5a10>>: maximum recursion depth exceeded while calling a Python object

It seems like it's trying to query these phantom entities. It's retrying over and over and eventually reaches recursion limit in python.

DrBlokmeister commented 5 months ago

Good catch. I parsed the github repository through GPT4 together with my errors and I think I came up with a solution. The unique_ids are NOT unique. They are just set to a static string depending on the sensor type, independent of the device itself...

This is the updated sensor.py file. Should be copy-pastable:

from __future__ import annotations
from .const import DOMAIN
import logging
import datetime
_LOGGER: Final = logging.getLogger(__name__)

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorStateClass,
)
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

async def async_setup_entry(hass, config_entry, async_add_entities):
    hub = hass.data[DOMAIN][config_entry.entry_id]
    new_devices = []
    new_devices.append(IPSensor(hub))
    new_devices.append(SystimeSensor(hub))
    new_devices.append(HeapSensor(hub))
    new_devices.append(RecordcountSensor(hub))
    new_devices.append(DBsizeSensor(hub))
    new_devices.append(LitefsfreeSensor(hub))
    new_devices.append(APWifiRssiSensor(hub))
    new_devices.append(APStateSensor(hub))
    new_devices.append(APRunStateSensor(hub))
    new_devices.append(APWifiStatusSensor(hub))
    new_devices.append(APWifiSssidSensor(hub))
    for esls in hub.esls:
        new_devices.append(LastSeenSensor(esls,hub))
        new_devices.append(NextUpdateSensor(esls,hub))
        new_devices.append(NextCheckinSensor(esls,hub))
        new_devices.append(PendingSensor(esls,hub))
        new_devices.append(WakeupReasonSensor(esls,hub))
        new_devices.append(CapabilitiesSensor(esls,hub))
        if (hub.data[esls]["lqi"] != 100 or hub.data[esls]["rssi"] != 100) and hub.data[esls]["hwtype"] != 224 and hub.data[esls]["hwtype"] != 240:
            new_devices.append(TempSensor(esls,hub))
            new_devices.append(RssiSensor(esls,hub))
            new_devices.append(BatteryVoltageSensor(esls,hub))
            new_devices.append(BatteryPercentageSensor(esls,hub))
            new_devices.append(LqiSensor(esls,hub))
    async_add_entities(new_devices)

class IPSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_ip_{hub.data['ap']['ip']}"
        self._attr_name = "AP IP"
        self._hub = hub
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")},
            "configuration_url": "http://" + self._hub.data["ap"]["ip"],
            "name": "OpenEpaperLink AP",
            "model": "esp32",
            "manufacturer": "OpenEpaperLink",
        }

    def update(self) -> None:
        self._attr_native_value = self._hub.data["ap"]["ip"]

class APWifiRssiSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_wifirssi_{hub.data['ap']['ip']}"
        self._attr_name = "AP Wifi RSSI"
        self._hub = hub
        self._attr_native_unit_of_measurement = "dB"
        self._attr_device_class = SensorDeviceClass.SIGNAL_STRENGTH
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        self._attr_native_value = self._hub.data["ap"]["rssi"]

class APStateSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_state_{hub.data['ap']['ip']}"
        self._attr_name = "AP State"
        self._hub = hub
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        lut = {0: "offline",1: "online",2: "flashing",3: "wait for reset",4: "requires power cycle",5: "failed",6: "coming online"}
        self._attr_native_value = lut[self._hub.data["ap"]["apstate"]]

class APRunStateSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_runstate_{hub.data['ap']['ip']}"
        self._attr_name = "AP Run State"
        self._hub = hub
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        lut = {0: "stopped",1: "pause",2: "running",3: "init"}
        self._attr_native_value = lut[self._hub.data["ap"]["runstate"]]

class APTempSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_temp_{hub.data['ap']['ip']}"
        self._attr_name = "AP Temp"
        self._hub = hub
        self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
        self._attr_device_class = SensorDeviceClass.TEMPERATURE
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        temp = self._hub.data["ap"]["temp"]
        if temp:
            self._attr_native_value = round(temp,1)

class APWifiStatusSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_wifistate_{hub.data['ap']['ip']}"
        self._attr_name = "AP Wifi State"
        self._hub = hub
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        lut = {3: "connected"}
        self._attr_native_value = lut[self._hub.data["ap"]["wifistatus"]]

class APWifiSssidSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_wifissid_{hub.data['ap']['ip']}"
        self._attr_name = "AP Wifi SSID"
        self._hub = hub
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        self._attr_native_value = self._hub.data["ap"]["wifissid"]

class SystimeSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_systime_{hub.data['ap']['ip']}"
        self._attr_name = "AP Systime"
        self._hub = hub
        self._attr_device_class = SensorDeviceClass.TIMESTAMP
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        self._attr_native_value = datetime.datetime.fromtimestamp(self._hub.data["ap"]["systime"], datetime.timezone.utc)

class HeapSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_heap_{hub.data['ap']['ip']}"
        self._attr_name = "AP free Heap"
        self._hub = hub
        self._attr_native_unit_of_measurement = "kB"
        self._attr_device_class = SensorDeviceClass.DATA_SIZE
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        self._attr_native_value = round(int(self._hub.data["ap"]["heap"]) / 1024,1)

class RecordcountSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_recordcount_{hub.data['ap']['ip']}"
        self._attr_name = "AP Recordcount"
        self._hub = hub
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        self._attr_native_value = self._hub.data["ap"]["recordcount"]

class DBsizeSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_dbsize_{hub.data['ap']['ip']}"
        self._attr_name = "AP DBSize"
        self._hub = hub
        self._attr_native_unit_of_measurement = "kB"
        self._attr_device_class = SensorDeviceClass.DATA_SIZE
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        self._attr_native_value = round(int(self._hub.data["ap"]["dbsize"]) / 1024,1)

class LitefsfreeSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_littlefsfree_{hub.data['ap']['ip']}"
        self._attr_name = "AP Free Space"
        self._hub = hub
        self._attr_native_unit_of_measurement = "kB"
        self._attr_device_class = SensorDeviceClass.DATA_SIZE
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, "ap")}
        }
    def update(self) -> None:
        self._attr_native_value = round(int(self._hub.data["ap"]["littlefsfree"]) / 1024,1)

class TempSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_temp"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Temperature"
        self._hub = hub
        self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
        self._attr_device_class = SensorDeviceClass.TEMPERATURE
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = self._hub.data[eslid]["temperature"]

class RssiSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_rssi"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Rssi"
        self._hub = hub
        self._attr_native_unit_of_measurement = "dB"
        self._attr_device_class = SensorDeviceClass.SIGNAL_STRENGTH
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = self._hub.data[eslid]["rssi"]

class BatteryVoltageSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_batteryvoltage"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Battery Voltage"
        self._hub = hub
        self._attr_native_unit_of_measurement = "V"
        self._attr_device_class = SensorDeviceClass.VOLTAGE
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = self._hub.data[eslid]["battery"] / 1000

class LqiSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_lqi"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Link Quality Index"
        self._hub = hub
        self._attr_native_unit_of_measurement = ""
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = self._hub.data[eslid]["lqi"]

class ContentModeSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_contentmode"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Content Mode"
        self._hub = hub
        self._attr_native_unit_of_measurement = ""
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = self._hub.data[eslid]["contentmode"]

class LastSeenSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_lastseen"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Last Seen"
        self._hub = hub
        self._attr_device_class = SensorDeviceClass.TIMESTAMP
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
            "name": self._hub.data[self._eslid]["tagname"],
            "sw_version": hex(self._hub.data[self._eslid]["ver"]),
            "serial_number": self._eslid,
            "model": self._hub.data[self._eslid]["hwstring"],
            "manufacturer": "OpenEpaperLink",
            "via_device": (DOMAIN, "ap")
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = datetime.datetime.fromtimestamp(self._hub.data[eslid]["lastseen"],datetime.timezone.utc)

class NextUpdateSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_nextupdate"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Next Update"
        self._hub = hub
        self._attr_device_class = SensorDeviceClass.TIMESTAMP
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = datetime.datetime.fromtimestamp(self._hub.data[eslid]["nextupdate"],datetime.timezone.utc)

class NextCheckinSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_nextcheckin"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Next Checkin"
        self._hub = hub
        self._attr_device_class = SensorDeviceClass.TIMESTAMP
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = datetime.datetime.fromtimestamp(self._hub.data[eslid]["nextcheckin"],datetime.timezone.utc)

class PendingSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_pending"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Pending Transfer"
        self._hub = hub
        self._attr_native_unit_of_measurement = ""
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = self._hub.data[eslid]["pending"]

class WakeupReasonSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_wakeupReason"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Wakeup Reason"
        self._hub = hub
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        lut = {0: "TIMED",1: "BOOT",2: "GPIO",3: "NFC",4: "BUTTON1",5: "BUTTON2",252: "FIRSTBOOT",253: "NETWORK_SCAN",254: "WDT_RESET"}
        wr = lut[self._hub.data[eslid]["wakeupReason"]]
        self._attr_native_value = wr

class CapabilitiesSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_capabilities"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Capabilities"
        self._hub = hub
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        self._attr_native_value = self._hub.data[eslid]["capabilities"]

class BatteryPercentageSensor(SensorEntity):
    def __init__(self, esls,hub):
        self._attr_unique_id = f"{esls}_battery"
        self._eslid = esls
        self._attr_name = hub.data[esls]["tagname"] + " Battery"
        self._hub = hub
        self._attr_native_unit_of_measurement = "%"
        self._attr_device_class = SensorDeviceClass.BATTERY
        self._attr_state_class = SensorStateClass.MEASUREMENT
    @property
    def device_info(self) -> DeviceInfo:
        return {
            "identifiers": {(DOMAIN, self._eslid)},
        }
    def update(self) -> None:
        eslid = self._eslid
        bperc = ((self._hub.data[eslid]["battery"] / 1000) - 2.20) * 250
        if bperc > 100:
            bperc = 100
        if bperc < 0:
            bperc = 0
        bperc = int(bperc)
        self._attr_native_value = bperc
jonasniesner commented 5 months ago

@DrBlokmeister can you test this solution? If it works, I will merge a PR with it.

DrBlokmeister commented 5 months ago

I'm running it right now. My scripts to update the displays are working fine. No errors in the logs. I'll report back in a few days to see if the disconnecting is solved.

That being said, I do think that an update like this is a good idea. Maybe it's good to check if the string structure will not cause conflicts. This is the structure that GPT4 suggested:

class IPSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_ip_{hub.data['ap']['ip']}"

class APWifiRssiSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_wifirssi_{hub.data['ap']['ip']}"

class APStateSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_state_{hub.data['ap']['ip']}"

class APRunStateSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_runstate_{hub.data['ap']['ip']}"

class APTempSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_temp_{hub.data['ap']['ip']}"

class APWifiStatusSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_wifistate_{hub.data['ap']['ip']}"

class APWifiSssidSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_wifissid_{hub.data['ap']['ip']}"

class SystimeSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_systime_{hub.data['ap']['ip']}"

class HeapSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_heap_{hub.data['ap']['ip']}"

class RecordcountSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_recordcount_{hub.data['ap']['ip']}"

class DBsizeSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_dbsize_{hub.data['ap']['ip']}"

class LitefsfreeSensor(SensorEntity):
    def __init__(self, hub):
        self._attr_unique_id = f"ap_littlefsfree_{hub.data['ap']['ip']}"

# For device-specific sensors, it seems you're already using a dynamic identifier (esls).
# Ensure that 'esls' itself provides uniqueness across devices. No change needed if 'esls' is unique.
DrBlokmeister commented 5 months ago

This morning I saw the following error in my log. I don't know what it means or what causes it. I have not seen this error before. The displays keep updating just fine.

2024-01-28 03:01:35.433 ERROR (Thread-2 (establish_connection)) [websocket] cannot decode: b'\x00\x00\x00\x00\x0cq\xd9?P\x02\x18\x00\x80\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00C3BC6341B","hash":"00000000000000000000000000000000","la\x00P\xba\xd0\xac\xa7\t\xe91706P\x087295\x00\x00\x81~\x028\x00\x00\x00\x00\x0cq\xd9?P\x02\x18\x00\x80\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00C3BC6341B","hash":"00000000000000000000000000000000","la\x00P\xba\xd0\xac\xa7\t\xe91706P\x087295\x00\x00\x81~\x028\x00\x00\x00\x00\x0cq\xd9?P\x02\x18\x00\x80\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00C3BC6341B","hash":"00000000000000000000000000000000","la\x00P\xba\xd0\xac\xa7\t\xe91706P\x087295\x00\x00\x81~\x028\x00\x00\x00\x00\x0cq\xd9?P\x02\x18\x00\x80\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00C3BC6341B","hash":"00000000000000000000000000000000","la\x00P\xba\xd0\xac\xa7\t\xe91706P\x087295\x00\x00\x81~\x028\x00\x00\x00\x00\x0cq\xd9?P\x02\x18\x00\x80\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00C3BC6341B","hash":"00000000000000000000000000000000","la\x00P\xba\xd0\xac\xa7\t\xe91706P\x087295\x00\x00\x81~\x028\x00\x00\x00\x00\x0cq\xd9?P\x02\x18\x00\x80\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00C3BC6341B","hash":"00000' - goodbye
2024-01-28 03:01:38.436 WARNING (Thread-2 (establish_connection)) [custom_components.open_epaper_link.hub] Websocket connection lost, trying to reconnect every 30 seconds
2024-01-28 03:01:38.513 INFO (Thread-2 (establish_connection)) [websocket] Websocket connected
DrBlokmeister commented 5 months ago

Okay, the connection still times out. I have not seen an update for 3 hours since 10:39 this morning. No relevant log entries around that time.

2024-01-28 10:30:02.259 INFO (MainThread) [homeassistant.components.script.update_study_energy_display] Update Study Energy Display: Executing step call service
2024-01-28 10:30:02.261 INFO (MainThread) [custom_components.open_epaper_link] Called entity_id: open_epaper_link.0000039223703418
2024-01-28 10:30:02.264 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.266 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.268 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.269 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.270 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.271 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.272 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.273 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.274 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.275 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.277 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.278 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:30:02.278 INFO (SyncWorker_28) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:38:35.994 INFO (MainThread) [homeassistant.components.automation.home_climate_heat_up_tapwater_in_the_morning] Initialized trigger [home|climate] Heat up tapwater in the morning
2024-01-28 10:41:47.266 WARNING (MainThread) [homeassistant.components.websocket_api.http.connection] [139957482681536] from IP_OBFUSCATED: Disconnected: Did not receive auth message within 10 seconds
2024-01-28 10:45:00.229 INFO (MainThread) [homeassistant.components.automation.misc_epaperlink_update_study_displays] [misc|epaperlink]Update study displays: Running automation actions
2024-01-28 10:45:00.230 INFO (MainThread) [homeassistant.components.automation.misc_epaperlink_update_study_displays] [misc|epaperlink]Update study displays: Executing step call service
2024-01-28 10:45:00.232 INFO (MainThread) [custom_components.open_epaper_link] Called entity_id: open_epaper_link.00000218736e3b1e
2024-01-28 10:45:00.253 INFO (MainThread) [homeassistant.components.automation.misc_epaperlink_update_study_displays] [misc|epaperlink]Update study displays: Executing step delay 0:00:02
2024-01-28 10:45:02.256 INFO (MainThread) [homeassistant.components.automation.misc_epaperlink_update_study_displays] [misc|epaperlink]Update study displays: Executing step call service
2024-01-28 10:45:02.257 INFO (MainThread) [homeassistant.components.script.update_study_energy_display] Update Study Energy Display: Running script sequence
2024-01-28 10:45:02.257 INFO (MainThread) [homeassistant.components.script.update_study_energy_display] Update Study Energy Display: Executing step call service
2024-01-28 10:45:02.258 INFO (MainThread) [custom_components.open_epaper_link] Called entity_id: open_epaper_link.0000039223703418
2024-01-28 10:45:02.259 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.260 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.260 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.261 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.261 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.262 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.263 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.263 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.264 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.265 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.265 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.266 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
2024-01-28 10:45:02.266 INFO (SyncWorker_59) [custom_components.open_epaper_link.imagegen] type: text
DrBlokmeister commented 5 months ago

It seems like the problem has not been solved. About every two days, the connection is still lost. A restart of the integration solves the problem though.

jterrace commented 5 months ago

Same here, I have to restart every couple days.

The logs show lots of errors like this:

2024-02-01 09:25:46.679 ERROR (Thread-4 (establish_connection)) [websocket] Connection to remote host was lost. - goodbye
2024-02-01 09:25:46.679 WARNING (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Websocket connection lost, trying to reconnect every 30 seconds
2024-02-01 09:32:37.691 ERROR (Thread-4 (establish_connection)) [websocket] Connection to remote host was lost. - goodbye
2024-02-01 09:32:37.691 WARNING (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Websocket connection lost, trying to reconnect every 30 seconds
2024-02-01 09:36:08.722 ERROR (Thread-4 (establish_connection)) [websocket] Connection to remote host was lost. - goodbye
2024-02-01 09:36:08.723 WARNING (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Websocket connection lost, trying to reconnect every 30 seconds
....
2024-02-02 01:19:44.251 ERROR (Thread-4 (establish_connection)) [websocket] Connection to remote host was lost. - goodbye
2024-02-02 01:19:44.252 WARNING (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Websocket connection lost, trying to reconnect every 30 seconds
2024-02-02 01:31:52.261 ERROR (Thread-4 (establish_connection)) [websocket] error from callback <bound method Hub.on_close of <custom_components.open_epaper_link.hub.Hub object at 0xffff81fc1a90>>: maximum recursion depth exceeded
2024-02-02 01:31:52.262 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.263 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.263 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.263 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.264 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.264 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.264 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.264 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.265 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.265 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.266 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.266 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
2024-02-02 01:31:52.267 ERROR (Thread-4 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect
jterrace commented 5 months ago

I don't think having on_close call establish_connection makes sense here. That's creating another WebSocketApp each time the connection is closed and winds up exceeding the maximum recursion stack depth of Python.

I think maybe the right solution is to specify the reconnect parameter to run_forever, which defaults to 0, which means no reconnects.

jterrace commented 5 months ago

I just loaded this fix on my server to test out:

https://github.com/jterrace/open_epaper_link_homeassistant/commit/64a83d725a3c9cd5d5450e366d332f30402b0864

Will report back.

DrBlokmeister commented 5 months ago

I also made this change. Let's see if that works. Thanks for the suggestion! :)

jterrace commented 5 months ago

So far so good, but I'll wait a couple days to be sure and then send the PR

jterrace commented 5 months ago

Unfortunately, I still got a crash this morning:

2024-02-04 04:00:03.351 WARNING (Thread-3 (establish_connection)) [custom_components.open_epaper_link.hub] Websocket connection lost to url=ws://192.168.56.164/ws (close_status_code={close_status_code}, close_msg={close_msg}), trying to reconnect every 30 seconds
2024-02-04 04:00:03.352 ERROR (Thread-3 (establish_connection)) [custom_components.open_epaper_link.hub] Integration crashed, this should never happen. It will not reconnect

I messed up the format string for the on_close variables so I can't see what the error is. But it's weird that run_forever is exiting even with reconnect param.

jterrace commented 5 months ago

I made a couple more changes on my branch and loaded it on my server:

https://github.com/jonasniesner/open_epaper_link_homeassistant/compare/main...jterrace:open_epaper_link_homeassistant:reconnect-fix

This should hopefully reconnect the websocket if it shuts down. I'll report back in another day.

jterrace commented 5 months ago

OK, got another disconnect. The close codes are None, which I think means a clean shutdown instead of an error which is weird?

2024-02-05 04:00:02.893 WARNING (Thread-4 (connection_thread)) [custom_components.open_epaper_link.hub] Websocket connection lost to url=ws://192.168.56.164/ws (close_status_code=None, close_msg=None), trying to reconnect every 30 seconds
2024-02-05 04:00:02.895 ERROR (Thread-4 (connection_thread)) [custom_components.open_epaper_link.hub] open_epaper_link WebSocketApp crashed, reconnecting in 30 seconds

Interestingly, it happened at exactly 4AM again, just like yesterday. There must be something triggering it daily?

But in any case, the reconnect seems to have worked well. Things are still connected and tags are updating. I'll send a PR.