Skip to content

Commit c25c054

Browse files
committed
Seem to be running and updating values
1 parent 69c9653 commit c25c054

4 files changed

Lines changed: 217 additions & 23 deletions

File tree

__init__.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,20 @@ def setup(hass: HomeAssistant, base_config: ConfigType) -> bool:
4949

5050
host = config[CONF_HOST]
5151

52-
ph_device = device.Device(host)
52+
device_client = device.Device(host)
5353
try:
54-
if not ph_device.run(once=True):
54+
if not device_client.run(once=True):
5555
_LOGGER.error("Device found but no measuremetn was received")
5656
return False
5757
except TimeoutError:
5858
_LOGGER.error("Could no connect ot device")
5959
return False
6060

61-
hass.data[DOMAIN] = DeviceData(hass, ph_device)
61+
hass.data[DOMAIN] = DeviceData(hass, device_client)
6262
hass.data[DOMAIN].start()
6363

6464
discovery.load_platform(hass, Platform.SENSOR, DOMAIN, {}, config)
65-
# discovery.load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config)
65+
discovery.load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config)
6666
return True
6767

6868

@@ -74,12 +74,13 @@ class DeviceData(threading.Thread):
7474
for every new data, could work for the pH and ORP data but for the
7575
switches a more direct feedback is wanted."""
7676

77-
def __init__(self, hass, client: device.Device) -> None:
77+
def __init__(self, hass, device_client: device.Device) -> None:
7878
super().__init__()
7979
self.name = "Ph803wThread"
8080
self.hass = hass
81-
self.client = client
82-
self.unit = self.client.host
81+
self.device_client = device_client
82+
self.device_client.register_callback(self.dispatcher_new_data)
83+
self.host = self.device_client.host
8384
self._shutdown = False
8485
self._fails = 0
8586

@@ -94,7 +95,7 @@ def shutdown(event):
9495
"""Shutdown the thread."""
9596
_LOGGER.debug("Signaled to shutdown")
9697
self._shutdown = True
97-
self.client.abort()
98+
self.device_client.abort()
9899
self.join()
99100

100101
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown)
@@ -124,14 +125,19 @@ def shutdown(event):
124125
return
125126

126127
try:
127-
self.client.run(once=False)
128+
self.device_client.run(once=False)
128129
except device.DeviceError:
129130
_LOGGER.exception("Failed to read data, attempting to recover")
130-
self.client.close()
131+
self.device_client.close()
131132
self._fails += 1
132133
sleep_time = self._fails * ERROR_INTERVAL.total_seconds()
133134
_LOGGER.debug(
134135
"Sleeping for fail #%s, in %s seconds", self._fails, sleep_time
135136
)
136-
self.client.reset_socket()
137+
self.device_client.reset_socket()
137138
time.sleep(sleep_time)
139+
140+
@callback
141+
def dispatcher_new_data(self):
142+
"""Noyifying HASS that new data is ready to read."""
143+
dispatcher_send(self.hass, UPDATE_TOPIC)

binary_sensor.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""Platform for sensor integration."""
2+
from __future__ import annotations
3+
import logging
4+
5+
from homeassistant.components.binary_sensor import (
6+
ENTITY_ID_FORMAT,
7+
BinarySensorDeviceClass,
8+
BinarySensorEntity,
9+
)
10+
from homeassistant.core import HomeAssistant, callback
11+
from homeassistant.helpers.dispatcher import async_dispatcher_connect
12+
from homeassistant.helpers.entity_platform import AddEntitiesCallback
13+
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
14+
from homeassistant.util import slugify
15+
16+
from . import UPDATE_TOPIC
17+
from .const import DOMAIN
18+
19+
_LOGGER = logging.getLogger(__name__)
20+
21+
22+
class BinaryDeviceSensorConfig:
23+
"""PH-803W Device Sensor configuration."""
24+
25+
def __init__(
26+
self,
27+
friendly_name,
28+
field,
29+
icon="mdi:gauge",
30+
unit_of_measurement=None,
31+
device_class=None,
32+
):
33+
"""Initialize configuration."""
34+
self.device_class = device_class
35+
self.friendly_name = friendly_name
36+
self.field = field
37+
self.icon = icon
38+
self.unit_of_measurement = unit_of_measurement
39+
40+
41+
SENSORS = [
42+
BinaryDeviceSensorConfig(
43+
"PH-803W In water",
44+
"in_water",
45+
"mdi:water-check",
46+
"",
47+
BinarySensorDeviceClass.CONNECTIVITY,
48+
),
49+
BinaryDeviceSensorConfig(
50+
"PH-803W pH switch on",
51+
"ph_on",
52+
"mdi:water-plus",
53+
"",
54+
BinarySensorDeviceClass.RUNNING,
55+
),
56+
BinaryDeviceSensorConfig(
57+
"PH-803W ORP switch on",
58+
"orp_on",
59+
"mdi:water-plus",
60+
"",
61+
BinarySensorDeviceClass.RUNNING,
62+
),
63+
]
64+
65+
66+
def setup_platform(
67+
hass: HomeAssistant,
68+
config: ConfigType,
69+
add_entities: AddEntitiesCallback,
70+
discovery_info: DiscoveryInfoType | None = None,
71+
) -> None:
72+
"""Set up the PH-803W sensor."""
73+
if discovery_info is None:
74+
return
75+
76+
sensors = []
77+
device_data = hass.data[DOMAIN]
78+
for sconfig in SENSORS:
79+
sensors.append(DeviceSensor(device_data, sconfig))
80+
81+
add_entities(sensors)
82+
83+
84+
class DeviceSensor(BinarySensorEntity):
85+
"""Implementing the Waterfurnace sensor."""
86+
87+
def __init__(self, device_data, config):
88+
"""Initialize the sensor."""
89+
self.device_data = device_data
90+
self._name = config.friendly_name
91+
self._attr = config.field
92+
self._state = None
93+
if self.device_data.device_client.get_latest_measurement() is not None:
94+
self._state = getattr(
95+
self.device_data.device_client.get_latest_measurement(),
96+
self._attr,
97+
None,
98+
)
99+
self._icon = config.icon
100+
self._unit_of_measurement = config.unit_of_measurement
101+
self._attr_device_class = config.device_class
102+
103+
# This ensures that the sensors are isolated per waterfurnace unit
104+
self.entity_id = ENTITY_ID_FORMAT.format(
105+
f"wf_{slugify(self.device_data.host)}_{slugify(self._attr)}"
106+
)
107+
108+
@property
109+
def name(self):
110+
"""Return the name of the sensor."""
111+
return self._name
112+
113+
@property
114+
def device_info(self):
115+
"""Return information to link this entity with the correct device."""
116+
return {
117+
"identifiers": {(DOMAIN, self.device_data.device_client.passcode)},
118+
"name": self.device_data.device_client.get_unique_name(),
119+
}
120+
121+
@property
122+
def unique_id(self):
123+
"""Return the sensor unique id."""
124+
return self.device_data.device_client.passcode + self._attr
125+
126+
@property
127+
def is_on(self):
128+
"""Return the state of the sensor."""
129+
return self._state
130+
131+
@property
132+
def icon(self):
133+
"""Return icon."""
134+
return self._icon
135+
136+
@property
137+
def native_unit_of_measurement(self):
138+
"""Return the units of measurement."""
139+
return self._unit_of_measurement
140+
141+
@property
142+
def should_poll(self):
143+
"""Return the polling state."""
144+
return False
145+
146+
async def async_added_to_hass(self):
147+
"""Register callbacks."""
148+
self.async_on_remove(
149+
async_dispatcher_connect(
150+
self.hass, UPDATE_TOPIC, self.async_update_callback
151+
)
152+
)
153+
154+
@callback
155+
def async_update_callback(self):
156+
"""Update state."""
157+
if self.device_data.device_client.get_latest_measurement() is not None:
158+
self._state = getattr(
159+
self.device_data.device_client.get_latest_measurement(),
160+
self._attr,
161+
None,
162+
)
163+
self.async_write_ha_state()

lib/device.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(self, host):
2727
self._loop = True
2828
self._empty_counter = 0
2929
self._pong_thread = None
30+
self._callbacks = []
3031

3132
def reset_socket(self):
3233
try:
@@ -52,6 +53,12 @@ def run(self, once: bool = True) -> bool:
5253
self._run(once)
5354
return not self._loop
5455

56+
def register_callback(self, callback_function):
57+
self._callbacks.append(callback_function)
58+
59+
def get_unique_name(self) -> str:
60+
return "PH-803W_%s" % self.passcode
61+
5562
def _connect(self) -> bool:
5663
self._loop = True
5764
self._socket.connect((self.host, PH803W_DEFAULT_TCP_PORT))
@@ -176,6 +183,8 @@ def _handle_data_response(self, data):
176183
self._latest_measurement = meas
177184
if len(self._measurements) > 100:
178185
self._measurements.pop(0)
186+
for callback in self._callbacks:
187+
callback()
179188
else:
180189
pass
181190
_LOGGER.debug(meas)

sensor.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ def __init__(
4242

4343

4444
SENSORS = [
45-
DeviceSensorConfig("pH Sensor", "ph", "mdi:water-percent", ""),
45+
DeviceSensorConfig("PH-803W pH", "ph", "mdi:water-percent", ""),
4646
DeviceSensorConfig(
47-
"ORP Sensor",
47+
"PH-803W ORP",
4848
"orp",
4949
"mdi:water-opacity",
5050
ELECTRIC_POTENTIAL_MILLIVOLT,
@@ -64,40 +64,55 @@ def setup_platform(
6464
return
6565

6666
sensors = []
67-
client = hass.data[DOMAIN]
67+
device_data = hass.data[DOMAIN]
6868
for sconfig in SENSORS:
69-
sensors.append(DeviceSensor(client, sconfig))
69+
sensors.append(DeviceSensor(device_data, sconfig))
7070

7171
add_entities(sensors)
7272

7373

7474
class DeviceSensor(SensorEntity):
7575
"""Implementing the Waterfurnace sensor."""
7676

77-
def __init__(self, client, config):
77+
def __init__(self, device_data, config):
7878
"""Initialize the sensor."""
79-
self.client = client
79+
self.device_data = device_data
8080
self._name = config.friendly_name
8181
self._attr = config.field
8282
self._state = None
83-
if self.client.client.get_latest_measurement() is not None:
83+
if self.device_data.device_client.get_latest_measurement() is not None:
8484
self._state = getattr(
85-
self.client.client.get_latest_measurement(), self._attr, None
85+
self.device_data.device_client.get_latest_measurement(),
86+
self._attr,
87+
None,
8688
)
8789
self._icon = config.icon
8890
self._unit_of_measurement = config.unit_of_measurement
8991
self._attr_device_class = config.device_class
9092

9193
# This ensures that the sensors are isolated per waterfurnace unit
9294
self.entity_id = ENTITY_ID_FORMAT.format(
93-
f"wf_{slugify(self.client.unit)}_{slugify(self._attr)}"
95+
f"wf_{slugify(self.device_data.host)}_{slugify(self._attr)}"
9496
)
9597

9698
@property
9799
def name(self):
98100
"""Return the name of the sensor."""
99101
return self._name
100102

103+
@property
104+
def device_info(self):
105+
"""Return information to link this entity with the correct device."""
106+
return {
107+
"identifiers": {(DOMAIN, self.device_data.device_client.passcode)},
108+
"name": self.device_data.device_client.get_unique_name(),
109+
}
110+
111+
@property
112+
def unique_id(self):
113+
"""Return the sensor unique id."""
114+
return self.device_data.device_client.passcode + self._attr
115+
101116
@property
102117
def native_value(self):
103118
"""Return the state of the sensor."""
@@ -129,9 +144,10 @@ async def async_added_to_hass(self):
129144
@callback
130145
def async_update_callback(self):
131146
"""Update state."""
132-
_LOGGER.info("Read sensor data: %s" % self.client.get_latest_measurement())
133-
if self.client.client.get_latest_measurement() is not None:
147+
if self.device_data.device_client.get_latest_measurement() is not None:
134148
self._state = getattr(
135-
self.client.client.get_latest_measurement(), self._attr, None
149+
self.device_data.device_client.get_latest_measurement(),
150+
self._attr,
151+
None,
136152
)
137153
self.async_write_ha_state()

0 commit comments

Comments
 (0)