Skip to content

Commit 82dfc6c

Browse files
committed
feat(em511): Add the driver with some functions
Working on format Signed-off-by: Bobo Bäck Engström <bobo@id8-engineering.io>
1 parent 9ee3c30 commit 82dfc6c

3 files changed

Lines changed: 300 additions & 8 deletions

File tree

src/em511/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Hello package."""
22

3+
from .em511 import Em511
34

4-
def hello() -> str:
5-
"""Hello docstring."""
6-
return "Hello from python-em511!"
5+
__all__ = ["Em511"]

src/em511/em511.py

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
"""Driver class for EM511."""
2+
3+
from pymodbus.client import ModbusSerialClient
4+
from pymodbus.exceptions import ModbusException
5+
6+
7+
class Em511:
8+
"""Em511 driver class."""
9+
10+
INT16_REG_COUNT = 1
11+
INT32_REG_COUNT = 2
12+
13+
def __init__(self, device_address: int, client: ModbusSerialClient) -> None:
14+
"""Constructor.
15+
16+
Constructor for the object
17+
18+
Args:
19+
device_address: Modbus address for the meter in use.
20+
client: Client for serial port.
21+
"""
22+
self.device_address = device_address
23+
self.client = client()
24+
25+
def _read_input_registers(self, address: int, count: int) -> list[int]:
26+
"""Read input registers.
27+
28+
Internal helper to read Modbus registers safely.
29+
30+
Args:
31+
address: Register adress to read from.
32+
count: Number of register to read from
33+
34+
Returns:
35+
value of registers.
36+
"""
37+
result = self.client.read_input_registers(address=address, count=count, device_id=self.device_address)
38+
if result.isError():
39+
msg = f"Failed to read input register. device_address={self.device_address} address={address} count={count}"
40+
raise ModbusException(msg)
41+
return result.registers
42+
43+
def _write_register(self, address: int, value: int) -> None:
44+
"""Write to register.
45+
46+
Internal helper to write to single register.
47+
48+
Args:
49+
address: Register to write to.
50+
value: Value to write the given register with.
51+
52+
Returns:
53+
True if register was written to.
54+
"""
55+
result = self.client.write_register(address=address, value=value, device_id=self.device_address)
56+
57+
if result.isError():
58+
msg = "Failed to write to single register."
59+
f"device_address={self.device_address} address={address} count={value}"
60+
raise ModbusException(msg)
61+
62+
def _unpack(self, regs: list[int], count: int) -> int:
63+
"""Unpack registers.
64+
65+
Internal helper to unpuck register list.
66+
"""
67+
if count == self.INT16_REG_COUNT:
68+
return regs[0]
69+
70+
if count == self.INT32_REG_COUNT:
71+
return (regs[1] << 16) + regs[0]
72+
msg = f"Failed to unpack register, not speciefied. device_address={self.device_address} regs={regs}"
73+
raise ModbusException(msg)
74+
75+
@property
76+
def v(self) -> float:
77+
"""Return Voltage.
78+
79+
Returns:
80+
Voltage
81+
"""
82+
regs = self._read_input_registers(0, self.INT32_REG_COUNT)
83+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
84+
85+
@property
86+
def a(self) -> float:
87+
"""Return A value."""
88+
regs = self._read_input_registers(2, self.INT32_REG_COUNT)
89+
return self._unpack(regs, self.INT32_REG_COUNT) / 1000
90+
91+
@property
92+
def w(self) -> float:
93+
"""Return W value."""
94+
regs = self._read_input_registers(4, self.INT32_REG_COUNT)
95+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
96+
97+
@property
98+
def w_dmd(self) -> float:
99+
"""Return W Dmd value."""
100+
regs = self._read_input_registers(10, self.INT32_REG_COUNT)
101+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
102+
103+
@property
104+
def w_dmd_peak(self) -> float:
105+
"""Return W dmd peak value."""
106+
regs = self._read_input_registers(12, self.INT32_REG_COUNT)
107+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
108+
109+
@property
110+
def hz(self) -> float:
111+
"""Return Hz value."""
112+
regs = self._read_input_registers(15, self.INT16_REG_COUNT)
113+
return self._unpack(regs, self.INT16_REG_COUNT) / 10
114+
115+
@property
116+
def kwh_tot(self) -> float:
117+
"""Return total kWh value."""
118+
regs = self._read_input_registers(16, self.INT32_REG_COUNT)
119+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
120+
121+
@property
122+
def kwh_partial(self) -> float:
123+
"""Return partial kwh value."""
124+
regs = self._read_input_registers(20, self.INT32_REG_COUNT)
125+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
126+
127+
@property
128+
def hour_counter(self) -> float:
129+
"""Return hour count."""
130+
regs = self._read_input_registers(44, self.INT32_REG_COUNT)
131+
return self._unpack(regs, self.INT32_REG_COUNT) / 100
132+
133+
@property
134+
def lifetime_counter(self) -> float:
135+
"""Return partial hour count."""
136+
regs = self._read_input_registers(54, self.INT32_REG_COUNT)
137+
return self._unpack(regs, self.INT32_REG_COUNT) / 100
138+
139+
@property
140+
def hour_counter_partial(self) -> float:
141+
"""Return partial hour count."""
142+
regs = self._read_input_registers(54, self.INT32_REG_COUNT)
143+
return self._unpack(regs, self.INT32_REG_COUNT) / 100
144+
145+
@property
146+
def a_dmd(self) -> float:
147+
"""Return demand amps."""
148+
regs = self._read_input_registers(58, self.INT32_REG_COUNT)
149+
return self._unpack(regs, self.INT32_REG_COUNT) / 1000
150+
151+
@property
152+
def a_dmd_peak(self) -> float:
153+
"""Return demand amps."""
154+
regs = self._read_input_registers(60, self.INT32_REG_COUNT)
155+
return self._unpack(regs, self.INT32_REG_COUNT) / 1000
156+
157+
@property
158+
def password(self) -> int:
159+
"""Return password."""
160+
regs = self._read_input_registers(4096, self.INT16_REG_COUNT)
161+
return self._unpack(regs, self.INT16_REG_COUNT)
162+
163+
@property
164+
def dmd_integr_time(self) -> int:
165+
"""Return Integration time for DMD Power calculation unit. Default = 0."""
166+
regs = self._read_input_registers(4112, self.INT32_REG_COUNT)
167+
return self._unpack(regs, self.INT32_REG_COUNT)
168+
169+
@property
170+
def alarm_state(self) -> int:
171+
"""Return alarm state."""
172+
regs = self._read_input_registers(4116, self.INT16_REG_COUNT)
173+
return self._unpack(regs, self.INT16_REG_COUNT)
174+
175+
@property
176+
def alarm_mode(self) -> int:
177+
"""Read alarm linked state, default = 1."""
178+
regs = self._read_input_registers(4117, self.INT16_REG_COUNT)
179+
return self._unpack(regs, self.INT16_REG_COUNT)
180+
181+
@property
182+
def alarm_delay(self) -> int:
183+
"""Return alarm delay, 0 - 3600 s. Default = 0."""
184+
regs = self._read_input_registers(4122, self.INT16_REG_COUNT)
185+
return self._unpack(regs, self.INT16_REG_COUNT)
186+
187+
@property
188+
def device_id(self) -> int:
189+
"""Return device id."""
190+
regs = self._read_input_registers(8192, self.INT16_REG_COUNT)
191+
return self._unpack(regs, self.INT16_REG_COUNT)
192+
193+
@property
194+
def baud_rate(self) -> int:
195+
"""Return baud rate."""
196+
regs = self._read_input_registers(8193, self.INT16_REG_COUNT)
197+
return self._unpack(regs, self.INT16_REG_COUNT)
198+
199+
@property
200+
def parity(self) -> int:
201+
"""Return parity."""
202+
regs = self._read_input_registers(8194, self.INT16_REG_COUNT)
203+
return self._unpack(regs, self.INT16_REG_COUNT)
204+
205+
@property
206+
def stop_bit(self) -> int:
207+
"""Return stop bits."""
208+
regs = self._read_input_registers(8195, self.INT16_REG_COUNT)
209+
return self._unpack(regs, self.INT16_REG_COUNT)
210+
211+
@property
212+
def reply_delay(self) -> int:
213+
"""Return reply_delay."""
214+
regs = self._read_input_registers(8196, self.INT16_REG_COUNT)
215+
return self._unpack(regs, self.INT16_REG_COUNT)
216+
217+
@password.setter
218+
def password(self, *, value: int) -> None:
219+
"""Set password."""
220+
self._write_register(4096, value)
221+
222+
@dmd_integr_time.setter
223+
def dmd_integr_time(self, *, value: int) -> None:
224+
"""Set IIntegration time for DMD power calculation, default = 3, self.INT16_REG_COUNT5 min."""
225+
self._write_register(4112, value)
226+
227+
@alarm_state.setter
228+
def alarm_state(self, *, value: bool) -> None:
229+
"""Set alarm state."""
230+
self._write_register(4116, value)
231+
232+
@alarm_mode.setter
233+
def alarm_mode(self, *, value: int) -> None:
234+
"""Set alarm linked state, default = 1."""
235+
self._write_register(4117, value)
236+
237+
@alarm_delay.setter
238+
def alarm_delay(self, *, value: int) -> None:
239+
"""Set alarm delay 0 - 3600 s, Default = 0."""
240+
self._write_register(4122, value)
241+
242+
@device_id.setter
243+
def device_id(self, *, value: int) -> None:
244+
"""Set device id."""
245+
self._write_register(8192, value)
246+
247+
@baud_rate.setter
248+
def baud_rate(self, *, value: int) -> None:
249+
"""Set baud rate."""
250+
self._write_register(8193, value)
251+
252+
@parity.setter
253+
def parity(self, *, value: int) -> None:
254+
"""Set parity."""
255+
self._write_register(8194, value)
256+
257+
@stop_bit.setter
258+
def stop_bit(self, *, value: int) -> None:
259+
"""Set stop bits."""
260+
self._write_register(8195, value)
261+
262+
@reply_delay.setter
263+
def reply_delay(self, *, value: int) -> None:
264+
"""Set reply_delay."""
265+
self._write_register(8196, value)
266+
267+
def reset_tot_energy_and_run_hour_counter(self) -> None:
268+
"""Reset total energy + total run hour counters (excluding lifetime)."""
269+
self._write_register(16387, self.INT16_REG_COUNT)
270+
271+
def reset_partial_energy_and_partial_hour_counter(self) -> None:
272+
"""Reset partial energy + partial run hour counters."""
273+
self._write_register(16388, self.INT16_REG_COUNT)
274+
275+
def reset_dmd_and_dmd_max(self) -> None:
276+
"""Reset DMD and DMD max values."""
277+
self._write_register(16389, self.INT16_REG_COUNT)
278+
279+
def reset_to_factory_settings(self) -> None:
280+
"""Factory Restore (Default settings)."""
281+
self._write_register(16416, 2570)
282+
self._write_register(16416, 49568)

src/em511/test_hello.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1-
# noqa: D100
2-
import em511
1+
"""Test file for driver."""
32

3+
from pymodbus.client import ModbusSerialClient
44

5-
def test_hello() -> None:
6-
"""Test hello function."""
7-
assert em511.hello() == "Hello from python-em511!" # noqa: S101
5+
from em511 import Em511
6+
7+
"""Init client."""
8+
client = ModbusSerialClient(
9+
port="/dev/ttyUSB0",
10+
baudrate=9600,
11+
parity="E",
12+
stopbits=1,
13+
bytesize=8,
14+
timeout=1,
15+
)
16+
17+
"""Init constructor"""
18+
AC_meter = Em511(102, client)

0 commit comments

Comments
 (0)