Skip to content

Commit 7c9dfd9

Browse files
committed
Changed aproach
1 parent d93fdc1 commit 7c9dfd9

6 files changed

Lines changed: 310 additions & 378 deletions

File tree

__init__.py

Lines changed: 193 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,208 @@
1-
"""The Detailed Hello World Push integration."""
2-
from __future__ import annotations
1+
"""Support for PH-803W."""
2+
from datetime import timedelta
3+
import logging
4+
import threading
5+
import time
36

4-
from homeassistant.config_entries import ConfigEntry
5-
from homeassistant.core import HomeAssistant
6-
from homeassistant.helpers.typing import ConfigType
7+
import voluptuous as vol
78

8-
from . import hub
9+
from .lib import device
910
from .const import DOMAIN
1011

11-
VERSION = "0.0.1"
12+
from homeassistant.components import persistent_notification
13+
from homeassistant.const import (
14+
CONF_HOST,
15+
EVENT_HOMEASSISTANT_STOP,
16+
Platform,
17+
)
18+
from homeassistant.core import HomeAssistant, callback
19+
from homeassistant.helpers import config_validation as cv, discovery
20+
from homeassistant.helpers.dispatcher import dispatcher_send
21+
from homeassistant.helpers.typing import ConfigType
1222

13-
# List of platforms to support. There should be a matching .py file for each,
14-
# eg <cover.py> and <sensor.py>
15-
PLATFORMS: list[str] = ["sensor"]
23+
_LOGGER = logging.getLogger(__name__)
1624

25+
UPDATE_TOPIC = f"{DOMAIN}_update"
26+
SCAN_INTERVAL = timedelta(seconds=10)
27+
ERROR_INTERVAL = timedelta(seconds=300)
28+
MAX_FAILS = 10
29+
NOTIFICATION_ID = "ph803w_device_notification"
30+
NOTIFICATION_TITLE = "PH-803W Device status"
1731

18-
# async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
19-
# """Set up a skeleton component."""
20-
# hass.data.setdefault(DOMAIN, {})[entry.entry_id] = hub.Hub(hass, None)
21-
# hass.config_entries.async_setup_platforms(entry, PLATFORMS)
2232

23-
# return True
33+
CONFIG_SCHEMA = vol.Schema(
34+
{
35+
DOMAIN: vol.Schema(
36+
{
37+
vol.Required(CONF_HOST): cv.string,
38+
}
39+
)
40+
},
41+
extra=vol.ALLOW_EXTRA,
42+
)
43+
2444

45+
def setup(hass: HomeAssistant, base_config: ConfigType) -> bool:
46+
"""Set up waterfurnace platform."""
2547

26-
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
27-
"""Set up Hello World from a config entry."""
28-
# Store an instance of the "connecting" class that does the work of speaking
29-
# with your actual devices.
30-
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = hub.Hub(hass, entry.data["host"])
48+
config = base_config[DOMAIN]
3149

32-
# This creates each HA object for each platform your device requires.
33-
# It's done by calling the `async_setup_entry` function in each platform module.
34-
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
50+
host = config[CONF_HOST]
51+
52+
ph_device = device.Device(host)
53+
try:
54+
if not ph_device.run(once=True):
55+
_LOGGER.error("Device found but no measuremetn was received")
56+
return False
57+
except TimeoutError:
58+
_LOGGER.error("Could no connect ot device")
59+
return False
60+
61+
hass.data[DOMAIN] = DeviceData(hass, ph_device)
62+
hass.data[DOMAIN].start()
63+
64+
discovery.load_platform(hass, Platform.SENSOR, DOMAIN, {}, config)
65+
discovery.load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config)
3566
return True
3667

3768

38-
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
39-
"""Unload a config entry."""
40-
# This is called when an entry/configured device is to be removed. The class
41-
# needs to unload itself, and remove callbacks. See the classes for further
42-
# details
43-
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
44-
if unload_ok:
45-
hass.data[DOMAIN].pop(entry.entry_id)
69+
class DeviceData(threading.Thread):
70+
"""PH-803W Data Collector.
71+
72+
This is implemented as a dedicated thread polling the device as the
73+
device requires ping/pong every 4s. The alternative is to reconnect
74+
for every new data, could work for the pH and ORP data but for the
75+
switches a more direct feedback is wanted."""
76+
77+
def __init__(self, hass, client: device.Device) -> None:
78+
super().__init__()
79+
self.hass = hass
80+
self.client = client
81+
self.unit = self.client.host
82+
self._shutdown = False
83+
self._fails = 0
84+
85+
# def _reconnect(self):
86+
# """Reconnect on a failure."""
87+
88+
# self._fails += 1
89+
# if self._fails > MAX_FAILS:
90+
# _LOGGER.error("Failed to reconnect. Thread stopped")
91+
# persistent_notification.create(
92+
# self.hass,
93+
# "Error:<br/>Connection to PH-803W device failed "
94+
# "the maximum number of times. Thread has stopped",
95+
# title=NOTIFICATION_TITLE,
96+
# notification_id=NOTIFICATION_ID,
97+
# )
98+
99+
# self._shutdown = True
100+
# return
101+
102+
# # sleep first before the reconnect attempt
103+
# _LOGGER.debug("Sleeping for fail # %s", self._fails)
104+
# time.sleep(self._fails * ERROR_INTERVAL.total_seconds())
105+
106+
# try:
107+
# self.client.run(once=False)
108+
# except:
109+
# _LOGGER.exception("Failed to reconnect attempt %s", self._fails)
110+
# else:
111+
# _LOGGER.debug("Reconnected to device")
112+
# self._fails = 0
113+
114+
def run(self):
115+
"""Thread run loop."""
116+
117+
@callback
118+
def register():
119+
"""Connect to hass for shutdown."""
120+
121+
def shutdown(event):
122+
"""Shutdown the thread."""
123+
_LOGGER.debug("Signaled to shutdown")
124+
self._shutdown = True
125+
self.client.abort()
126+
self.join()
127+
128+
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown)
129+
130+
self.hass.add_job(register)
131+
132+
# This does a tight loop in sending ping/pong to the
133+
# device. That's a blocking call, which returns pretty
134+
# quickly (0.5 second). It's important that we do this
135+
# frequently though, because if we don't call the websocket at
136+
# least every 4 seconds the device side closes the
137+
# connection.
138+
while True:
139+
if self._shutdown:
140+
_LOGGER.debug("Graceful shutdown")
141+
return
142+
143+
if self._fails > MAX_FAILS:
144+
_LOGGER.error("Failed to reconnect. Thread stopped")
145+
persistent_notification.create(
146+
self.hass,
147+
"Error:<br/>Connection to PH-803W device failed "
148+
"the maximum number of times. Thread has stopped",
149+
title=NOTIFICATION_TITLE,
150+
notification_id=NOTIFICATION_ID,
151+
)
152+
return
153+
154+
try:
155+
self.client.run(once=False)
156+
except device.DeviceError:
157+
_LOGGER.exception("Failed to read data, attempting to recover")
158+
self.client.close()
159+
self._fails += 1
160+
sleep_time = self._fails * ERROR_INTERVAL.total_seconds()
161+
_LOGGER.debug(
162+
"Sleeping for fail #%s, in %s seconds", self._fails, sleep_time
163+
)
164+
self.client.reset_socket()
165+
time.sleep(sleep_time)
166+
167+
# while True:
168+
# if self._shutdown:
169+
# _LOGGER.debug("Graceful shutdown")
170+
# return
171+
172+
# try:
173+
# self.data = self.client.run(once=False)
174+
175+
# except WFException:
176+
# # WFExceptions are things the WF library understands
177+
# # that pretty much can all be solved by logging in and
178+
# # back out again.
179+
# _LOGGER.exception("Failed to read data, attempting to recover")
180+
# self._reconnect()
181+
182+
# else:
183+
# dispatcher_send(self.hass, UPDATE_TOPIC)
184+
# time.sleep(SCAN_INTERVAL.total_seconds())
185+
186+
187+
# async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
188+
# """Set up Hello World from a config entry."""
189+
# # Store an instance of the "connecting" class that does the work of speaking
190+
# # with your actual devices.
191+
# hass.data.setdefault(DOMAIN, {})[entry.entry_id] = hub.Hub(hass, entry.data["host"])
192+
193+
# # This creates each HA object for each platform your device requires.
194+
# # It's done by calling the `async_setup_entry` function in each platform module.
195+
# hass.config_entries.async_setup_platforms(entry, PLATFORMS)
196+
# return True
197+
198+
199+
# async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
200+
# """Unload a config entry."""
201+
# # This is called when an entry/configured device is to be removed. The class
202+
# # needs to unload itself, and remove callbacks. See the classes for further
203+
# # details
204+
# unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
205+
# if unload_ok:
206+
# hass.data[DOMAIN].pop(entry.entry_id)
46207

47-
return unload_ok
208+
# return unload_ok

config_flow.py

Lines changed: 0 additions & 119 deletions
This file was deleted.

0 commit comments

Comments
 (0)