Skip to content

Commit 08f87f5

Browse files
committed
Make all the rest also async
1 parent 635d72f commit 08f87f5

6 files changed

Lines changed: 184 additions & 106 deletions

File tree

custom_components/powertag_gateway/binary_sensor.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ async def async_setup_entry(
3636
presentation_url = data[CONF_INTERNAL_URL]
3737
client = data[CONF_CLIENT]
3838
gateway_device = await gateway_device_info(client, presentation_url)
39+
gateway_serial = await client.serial_number()
3940

4041
entities.extend([gateway_entity for gateway_entity
41-
in [GatewayStatus(client, gateway_device), GatewayHealth(client, gateway_device)]
42+
in [GatewayStatus(client, gateway_device, gateway_serial), GatewayHealth(client, gateway_device, gateway_serial)]
4243
if gateway_entity.supports_gateway(client.type_of_gateway)
4344
])
4445

@@ -49,8 +50,8 @@ class PowerTagWirelessCommunicationValid(WirelessDeviceEntity, BinarySensorEntit
4950
_attr_entity_category = EntityCategory.DIAGNOSTIC
5051
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY
5152

52-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
53-
super().__init__(client, modbus_index, tag_device, "wireless communication valid", unique_id_version)
53+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
54+
super().__init__(client, modbus_index, tag_device, "wireless communication valid", unique_id_version, serial_number)
5455

5556
async def async_update(self):
5657
self._attr_is_on = await self._client.tag_wireless_communication_valid(self._modbus_index)
@@ -71,8 +72,8 @@ class PowerTagRadioCommunicationValid(WirelessDeviceEntity, BinarySensorEntity):
7172
_attr_entity_category = EntityCategory.DIAGNOSTIC
7273
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY
7374

74-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
75-
super().__init__(client, modbus_index, tag_device, "radio communication valid", unique_id_version)
75+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
76+
super().__init__(client, modbus_index, tag_device, "radio communication valid", unique_id_version, serial_number)
7677

7778
async def async_update(self):
7879
self._attr_is_on = await self._client.tag_radio_communication_valid(self._modbus_index)
@@ -92,8 +93,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
9293
class PowerTagAlarm(WirelessDeviceEntity, BinarySensorEntity):
9394
_attr_device_class = BinarySensorDeviceClass.PROBLEM
9495

95-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
96-
super().__init__(client, modbus_index, tag_device, "alarm info", unique_id_version)
96+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
97+
super().__init__(client, modbus_index, tag_device, "alarm info", unique_id_version, serial_number)
9798

9899
async def async_update(self):
99100
alarm = await self._client.tag_get_alarm(self._modbus_index)
@@ -126,8 +127,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
126127
class AmbientTagAlarm(WirelessDeviceEntity, BinarySensorEntity):
127128
_attr_device_class = BinarySensorDeviceClass.PROBLEM
128129

129-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
130-
super().__init__(client, modbus_index, tag_device, "battery", unique_id_version)
130+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
131+
super().__init__(client, modbus_index, tag_device, "battery", unique_id_version, serial_number)
131132
self.__product_range = self._client.tag_product_range(self._modbus_index)
132133

133134
async def async_update(self):
@@ -147,8 +148,8 @@ class GatewayStatus(GatewayEntity, BinarySensorEntity):
147148
_attr_entity_category = EntityCategory.DIAGNOSTIC
148149
_attr_device_class = BinarySensorDeviceClass.PROBLEM
149150

150-
def __init__(self, client: SchneiderModbus, tag_device: DeviceInfo):
151-
super().__init__(client, tag_device, "status")
151+
def __init__(self, client: SchneiderModbus, tag_device: DeviceInfo, serial_number: str):
152+
super().__init__(client, tag_device, "status", serial_number)
152153
self._attr_extra_state_attributes = {}
153154

154155
async def async_update(self):
@@ -180,8 +181,8 @@ class GatewayHealth(GatewayEntity, BinarySensorEntity):
180181
_attr_entity_category = EntityCategory.DIAGNOSTIC
181182
_attr_device_class = BinarySensorDeviceClass.PROBLEM
182183

183-
def __init__(self, client: SchneiderModbus, tag_device: DeviceInfo):
184-
super().__init__(client, tag_device, "health")
184+
def __init__(self, client: SchneiderModbus, tag_device: DeviceInfo, serial_number: str):
185+
super().__init__(client, tag_device, "health", serial_number)
185186
self._attr_extra_state_attributes = {}
186187

187188
async def async_update(self):

custom_components/powertag_gateway/button.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ async def async_setup_entry(
3636

3737

3838
class PowerTagResetPeakDemand(WirelessDeviceEntity, ButtonEntity):
39-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
40-
super().__init__(client, modbus_index, tag_device, "reset peak demand", unique_id_version)
39+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
40+
super().__init__(client, modbus_index, tag_device, "reset peak demand", unique_id_version, serial_number)
4141

4242
async def async_press(self) -> None:
4343
await self.async_reset()
@@ -55,8 +55,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
5555

5656

5757
class PowerTagResetActiveEnergyDelivered(WirelessDeviceEntity, ButtonEntity):
58-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
59-
super().__init__(client, modbus_index, tag_device, "reset active energy delivered", unique_id_version)
58+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
59+
super().__init__(client, modbus_index, tag_device, "reset active energy delivered", unique_id_version, serial_number)
6060

6161
async def async_press(self) -> None:
6262
await self.async_reset()
@@ -76,8 +76,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
7676

7777

7878
class PowerTagResetActiveEnergyReceived(WirelessDeviceEntity, ButtonEntity):
79-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
80-
super().__init__(client, modbus_index, tag_device, "reset active energy received", unique_id_version)
79+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
80+
super().__init__(client, modbus_index, tag_device, "reset active energy received", unique_id_version, serial_number)
8181

8282
async def async_press(self) -> None:
8383
await self.async_reset()
@@ -97,8 +97,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
9797

9898

9999
class PowerTagResetReactiveEnergyDelivered(WirelessDeviceEntity, ButtonEntity):
100-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
101-
super().__init__(client, modbus_index, tag_device, "reset reactive energy delivered", unique_id_version)
100+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
101+
super().__init__(client, modbus_index, tag_device, "reset reactive energy delivered", unique_id_version, serial_number)
102102

103103
async def async_press(self) -> None:
104104
await self.async_reset()
@@ -118,8 +118,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
118118

119119

120120
class PowerTagResetReactiveEnergyReceived(WirelessDeviceEntity, ButtonEntity):
121-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
122-
super().__init__(client, modbus_index, tag_device, "reset reactive energy received", unique_id_version)
121+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
122+
super().__init__(client, modbus_index, tag_device, "reset reactive energy received", unique_id_version, serial_number)
123123

124124
async def async_press(self) -> None:
125125
await self.async_reset()
@@ -139,8 +139,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
139139

140140

141141
class PowerTagResetApparentEnergy(WirelessDeviceEntity, ButtonEntity):
142-
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
143-
super().__init__(client, modbus_index, tag_device, "reset apparent energy", unique_id_version)
142+
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion, serial_number: str):
143+
super().__init__(client, modbus_index, tag_device, "reset apparent energy", unique_id_version, serial_number)
144144

145145
async def async_press(self) -> None:
146146
await self.async_reset()

custom_components/powertag_gateway/config_flow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ async def async_step_discover(self, user_input=None) -> FlowResult:
130130
name = f"{device.friendly_name}: {device.model_name} ({device.host})"
131131
if name == user_input[CONF_DEVICE]:
132132
self.name = device.friendly_name
133-
self.serial_number = device.serial_number
133+
self.serial_number = await device.serial_number
134134
self.host = device.host
135135
self.port = device.port
136136
self.type_of_gateway = device.type_of_gateway.value
@@ -226,7 +226,7 @@ async def async_step_connect(self, user_input=None) -> FlowResult:
226226

227227
logging.info("Retrieving serial number...")
228228
if not self.serial_number:
229-
self.serial_number = self.client.serial_number()
229+
self.serial_number = await self.client.serial_number()
230230

231231
logging.info("Retrieving model name...")
232232
if not self.model_name:

custom_components/powertag_gateway/entity_base.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
async def gateway_device_info(
3232
client: SchneiderModbus, presentation_url: str
3333
) -> DeviceInfo:
34-
serial = client.serial_number()
34+
serial = await client.serial_number()
3535
name = await client.name()
3636
hw_version = await client.hardware_version()
3737
firmware_version = await client.firmware_version()
@@ -57,7 +57,7 @@ async def tag_device_info(
5757
gateway_identification: tuple[str, str],
5858
) -> DeviceInfo:
5959
is_unreachable = await client.tag_radio_lqi_gateway(modbus_index) is None
60-
serial_number = client.tag_serial_number(modbus_index)
60+
serial_number = await client.tag_serial_number(modbus_index)
6161

6262
kwargs = {
6363
"configuration_url": presentation_url,
@@ -134,14 +134,13 @@ def phase_sequence_to_line_voltages(
134134

135135
class GatewayEntity(Entity):
136136
def __init__(
137-
self, client: SchneiderModbus, tag_device: DeviceInfo, sensor_name: str
137+
self, client: SchneiderModbus, tag_device: DeviceInfo, sensor_name: str, serial_number: str
138138
):
139139
self._client = client
140140
self._attr_device_info = tag_device
141141
self._attr_name = f"{tag_device['name']} {sensor_name}"
142142

143-
serial = self._client.serial_number()
144-
self._attr_unique_id = f"{TAG_DOMAIN}{serial}{sensor_name}"
143+
self._attr_unique_id = f"{TAG_DOMAIN}{serial_number}{sensor_name}"
145144

146145
@staticmethod
147146
def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
@@ -156,19 +155,18 @@ def __init__(
156155
tag_device: DeviceInfo,
157156
entity_name: str,
158157
unique_id_version: UniqueIdVersion,
158+
serial_number: str
159159
):
160160
self._client = client
161161
self._modbus_index = modbus_index
162162

163163
self._attr_device_info = tag_device
164164
self._attr_name = f"{tag_device['name']} {entity_name}"
165165

166-
serial = self._client.tag_serial_number(self._modbus_index)
167-
168166
if unique_id_version == UniqueIdVersion.V2:
169-
self._attr_unique_id = f"{TAG_DOMAIN}{serial}{entity_name}{self._modbus_index}"
167+
self._attr_unique_id = f"{TAG_DOMAIN}{serial_number}{entity_name}{self._modbus_index}"
170168
else:
171-
self._attr_unique_id = f"{TAG_DOMAIN}{serial}{entity_name}"
169+
self._attr_unique_id = f"{TAG_DOMAIN}{serial_number}{entity_name}"
172170

173171
@staticmethod
174172
def supports_feature_set(feature_class: FeatureClass) -> bool:
@@ -224,6 +222,9 @@ def collect_entities(
224222
args.append(None)
225223
elif typey == UniqueIdVersion:
226224
args.append(device_unique_id_version)
225+
elif typey == str:
226+
assert param[0] == "serial_number"
227+
args.append(tag_device["serial_number"])
227228
else:
228229
raise AssertionError("Dev fucked up, please create a GitHub issue. :(")
229230
if enumerate_param:

custom_components/powertag_gateway/schneider_modbus.py

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import math
55
from datetime import datetime
66

7-
from pymodbus.client import ModbusTcpClient # type: ignore
7+
from pymodbus.client import ModbusTcpClient, AsyncModbusTcpClient # type: ignore
88
from pymodbus.constants import DeviceInformation # type: ignore
99
from pymodbus.pdu import ExceptionResponse # type: ignore
1010
from pymodbus.client.mixin import ModbusClientMixin # type: ignore
@@ -192,8 +192,7 @@ class TypeOfGateway(enum.Enum):
192192
class SchneiderModbus:
193193
def __init__(self, host, type_of_gateway: TypeOfGateway, port=502, timeout=5):
194194
_LOGGER.info(f"Connecting Modbus TCP to {host}:{port}")
195-
self.client = ModbusTcpClient(host=host, port=port, timeout=timeout)
196-
self.client.connect()
195+
self.client = AsyncModbusTcpClient(host=host, port=port, timeout=timeout)
197196
self.type_of_gateway = type_of_gateway
198197
self.synthetic_slave_id = None
199198

@@ -231,15 +230,15 @@ async def hardware_version(self) -> str | None:
231230
else:
232231
return await self.__read_string(0x0050, 6, GATEWAY_SLAVE_ID)
233232

234-
def serial_number(self) -> str | None:
233+
async def serial_number(self) -> str | None:
235234
"""[S/N]: PP YY WW [D [nnnn]]
236235
• PP: Plant
237236
• YY: Year in decimal notation [05...99]
238237
• WW: Week in decimal notation [1...53]
239238
• D: Day of the week in decimal notation [1...7]
240239
• nnnn: Sequence of numbers [0001...10.00-0–1]
241240
"""
242-
return self.__sync_read_string(0x0064, 6, GATEWAY_SLAVE_ID)
241+
return await self.__read_string(0x0064, 6, GATEWAY_SLAVE_ID)
243242

244243
async def firmware_version(self) -> str | None:
245244
"""valid for firmware version 001.008.007 and later."""
@@ -716,9 +715,9 @@ async def tag_hardware_revision(self, tag_index: int) -> str | None:
716715
"""Hardware revision"""
717716
return await self.__read_string(0x796A, 6, tag_index)
718717

719-
def tag_serial_number(self, tag_index: int) -> str | None:
718+
async def tag_serial_number(self, tag_index: int) -> str | None:
720719
"""Serial number"""
721-
return self.__sync_read_string(0x7970, 10, tag_index)
720+
return await self.__read_string(0x7970, 10, tag_index)
722721

723722
async def tag_product_range(self, tag_index: int) -> str | None:
724723
"""Product range"""
@@ -915,35 +914,50 @@ def round_to_significant_digits(number: float, significant_digits: int):
915914
def __write(self, address: int, registers: list[int], slave_id: int):
916915
self.client.write_registers(address, registers, device_id=slave_id)
917916

918-
def __read(self, address: int, count: int, slave_id: int):
919-
response = self.client.read_holding_registers(
920-
address=address, count=count, device_id=slave_id
921-
)
922-
if response.isError():
923-
raise ConnectionError(str(response))
924-
return response.registers
925-
926-
async def __async_read(self, address: int, count: int, slave_id: int) -> list[int]:
927-
loop = asyncio.get_event_loop()
917+
async def __async_read(
918+
self, address: int, count: int, slave_id: int
919+
) -> list[int] | None:
928920
try:
929-
return await asyncio.wait_for(
930-
loop.run_in_executor(None, self.__read, address, count, slave_id),
921+
if not self.client.connected:
922+
await self.client.connect()
923+
924+
result = await asyncio.wait_for(
925+
self.client.read_holding_registers(
926+
address=address, count=count, device_id=slave_id
927+
),
931928
timeout=5.0,
932929
)
930+
if result.isError():
931+
_LOGGER.debug(
932+
f"Modbus error reading {address} from slave ID {slave_id}"
933+
)
934+
return None
935+
return result.registers
936+
933937
except asyncio.TimeoutError:
934-
_LOGGER.debug(f"Timeout when fetching address {address} from slave ID {slave_id}")
938+
_LOGGER.debug(
939+
f"Timeout when fetching address {address} from slave ID {slave_id}"
940+
)
941+
return None
935942

936943
async def __async_write(
937944
self, address: int, registers: list[int], slave_id: int
938945
) -> None:
939-
loop = asyncio.get_event_loop()
940946
try:
941-
await asyncio.wait_for(
942-
loop.run_in_executor(None, self.__write, address, registers, slave_id),
947+
if not self.client.connected:
948+
await self.client.connect()
949+
950+
result = await asyncio.wait_for(
951+
self.client.write_registers(address, registers, device_id=slave_id),
943952
timeout=5.0,
944953
)
954+
if result.isError():
955+
_LOGGER.debug(f"Modbus error writing {address} to slave ID {slave_id}")
956+
return None
945957
except asyncio.TimeoutError:
946-
_LOGGER.debug(f"Timeout when writing to address {address} from slave ID {slave_id}")
958+
_LOGGER.debug(
959+
f"Timeout when writing to address {address} to slave ID {slave_id}"
960+
)
947961

948962
async def __identify(self, _: int):
949963
# data = self.client.read_device_information(read_code=DeviceInformation.REGULAR, device_id=0xFF)
@@ -959,16 +973,7 @@ async def __identify(self, _: int):
959973

960974
async def __read_string(self, address: int, count: int, slave_id: int) -> str | None:
961975
registers = await self.__async_read(address, count, slave_id)
962-
return self.client.convert_from_registers(
963-
registers, ModbusClientMixin.DATATYPE.STRING
964-
)
965-
966-
def __sync_read_string(self, address: int, count: int, slave_id: int) -> str | None:
967-
registers = self.__read(address, count, slave_id)
968-
return self.client.convert_from_registers(
969-
registers, ModbusClientMixin.DATATYPE.STRING
970-
)
971-
976+
return self.client.convert_from_registers(registers, ModbusClientMixin.DATATYPE.STRING)
972977

973978
async def __write_string(self, address: int, slave_id: int, string: str):
974979
registers = self.client.convert_to_registers(
@@ -1001,14 +1006,14 @@ async def __write_int_16(self, address: int, slave_id: int, value: int):
10011006
await self.__async_write(address, registers, slave_id)
10021007

10031008
async def __read_int_32(self, address: int, slave_id: int) -> int | None:
1004-
registers = self.__read(address, 2, slave_id)
1009+
registers = await self.__async_read(address, 2, slave_id)
10051010
result = self.client.convert_from_registers(
10061011
registers, ModbusClientMixin.DATATYPE.UINT32
10071012
)
10081013
return result if result != 0x8000_0000 else None
10091014

10101015
async def __read_int_64(self, address: int, slave_id: int) -> int | None:
1011-
registers = self.__read(address, 4, slave_id)
1016+
registers = await self.__async_read(address, 4, slave_id)
10121017
result = self.client.convert_from_registers(
10131018
registers, ModbusClientMixin.DATATYPE.UINT64
10141019
)

0 commit comments

Comments
 (0)