Skip to content

Commit 052f60d

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 052f60d

3 files changed

Lines changed: 267 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: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
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(
38+
address=address, count=count, device_id=self.device_address
39+
)
40+
if result.isError():
41+
msg = f"Failed to read input register. device_address={self.device_address} address={address} count={count}"
42+
raise ModbusException(msg)
43+
return result.registers
44+
45+
def _write_register(self, address: int, value: int) -> None:
46+
"""Write to register.
47+
48+
Internal helper to write to single register.
49+
50+
Args:
51+
address: Register to write to.
52+
value: Value to write the given register with.
53+
54+
Returns:
55+
True if register was written to.
56+
"""
57+
result = self.client.write_register(
58+
address=address, value=value, device_id=self.device_address
59+
)
60+
61+
if result.isError():
62+
msg = "Failed to write to single register."
63+
f"device_address={self.device_address} address={address} count={value}"
64+
raise ModbusException(msg)
65+
66+
def _unpack(self, regs: list[int], count: int) -> int:
67+
"""Unpack registers.
68+
69+
Internal helper to unpuck register list.
70+
"""
71+
if count == self.INT16_REG_COUNT:
72+
return regs[0]
73+
74+
if count == self.INT32_REG_COUNT:
75+
return (regs[1] << 16) + regs[0]
76+
msg = f"Failed to unpack register, not speciefied. device_address={self.device_address} regs={regs}"
77+
raise ModbusException(msg)
78+
79+
def v(self) -> float:
80+
"""Return V value."""
81+
regs = self._read_input_registers(0, self.INT32_REG_COUNT)
82+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
83+
84+
def a(self) -> float:
85+
"""Return A value."""
86+
regs = self._read_input_registers(2, self.INT32_REG_COUNT)
87+
return self._unpack(regs, self.INT32_REG_COUNT) / 1000
88+
89+
def w(self) -> float:
90+
"""Return W value."""
91+
regs = self._read_input_registers(4, self.INT32_REG_COUNT)
92+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
93+
94+
def w_dmd(self) -> float:
95+
"""Return W Dmd value."""
96+
regs = self._read_input_registers(10, self.INT32_REG_COUNT)
97+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
98+
99+
def w_dmd_peak(self) -> float:
100+
"""Return W dmd peak value."""
101+
regs = self._read_input_registers(12, self.INT32_REG_COUNT)
102+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
103+
104+
def hz(self) -> float:
105+
"""Return Hz value."""
106+
regs = self._read_input_registers(15, self.INT16_REG_COUNT)
107+
return self._unpack(regs, self.INT16_REG_COUNT) / 10
108+
109+
def kwh_tot(self) -> float:
110+
"""Return total kWh value."""
111+
regs = self._read_input_registers(16, self.INT32_REG_COUNT)
112+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
113+
114+
def kwh_partial(self) -> float:
115+
"""Return partial kwh value."""
116+
regs = self._read_input_registers(20, self.INT32_REG_COUNT)
117+
return self._unpack(regs, self.INT32_REG_COUNT) / 10
118+
119+
def hour_counter(self) -> float:
120+
"""Return hour count."""
121+
regs = self._read_input_registers(44, self.INT32_REG_COUNT)
122+
return self._unpack(regs, self.INT32_REG_COUNT) / 100
123+
124+
def lifetime_counter(self) -> float:
125+
"""Return partial hour count."""
126+
regs = self._read_input_registers(54, self.INT32_REG_COUNT)
127+
return self._unpack(regs, self.INT32_REG_COUNT) / 100
128+
129+
def hour_counter_partial(self) -> float:
130+
"""Return partial hour count."""
131+
regs = self._read_input_registers(54, self.INT32_REG_COUNT)
132+
return self._unpack(regs, self.INT32_REG_COUNT) / 100
133+
134+
def a_dmd(self) -> float:
135+
"""Return demand amps."""
136+
regs = self._read_input_registers(58, self.INT32_REG_COUNT)
137+
return self._unpack(regs, self.INT32_REG_COUNT) / 1000
138+
139+
def a_dmd_peak(self) -> float:
140+
"""Return demand amps."""
141+
regs = self._read_input_registers(60, self.INT32_REG_COUNT)
142+
return self._unpack(regs, self.INT32_REG_COUNT) / 1000
143+
144+
def password(self) -> int:
145+
"""Return password."""
146+
regs = self._read_input_registers(4096, self.INT16_REG_COUNT)
147+
return self._unpack(regs, self.INT16_REG_COUNT)
148+
149+
def dmd_integr_time(self) -> int:
150+
"""Return Integration time for DMD Power calculation unit. Default = 0."""
151+
regs = self._read_input_registers(4112, self.INT32_REG_COUNT)
152+
return self._unpack(regs, self.INT32_REG_COUNT)
153+
154+
def alarm_state(self) -> int:
155+
"""Return alarm state."""
156+
regs = self._read_input_registers(4116, self.INT16_REG_COUNT)
157+
return self._unpack(regs, self.INT16_REG_COUNT)
158+
159+
def alarm_mode(self) -> int:
160+
"""Read alarm linked state, default = 1."""
161+
regs = self._read_input_registers(4117, self.INT16_REG_COUNT)
162+
return self._unpack(regs, self.INT16_REG_COUNT)
163+
164+
def alarm_delay(self) -> int:
165+
"""Return alarm delay, 0 - 3600 s. Default = 0."""
166+
regs = self._read_input_registers(4122, self.INT16_REG_COUNT)
167+
return self._unpack(regs, self.INT16_REG_COUNT)
168+
169+
def device_id(self) -> int:
170+
"""Return device id."""
171+
regs = self._read_input_registers(8192, self.INT16_REG_COUNT)
172+
return self._unpack(regs, self.INT16_REG_COUNT)
173+
174+
def baud_rate(self) -> int:
175+
"""Return baud rate."""
176+
regs = self._read_input_registers(8193, self.INT16_REG_COUNT)
177+
return self._unpack(regs, self.INT16_REG_COUNT)
178+
179+
def parity(self) -> int:
180+
"""Return parity."""
181+
regs = self._read_input_registers(8194, self.INT16_REG_COUNT)
182+
return self._unpack(regs, self.INT16_REG_COUNT)
183+
184+
def stop_bit(self) -> int:
185+
"""Return stop bits."""
186+
regs = self._read_input_registers(8195, self.INT16_REG_COUNT)
187+
return self._unpack(regs, self.INT16_REG_COUNT)
188+
189+
def reply_delay(self) -> int:
190+
"""Return reply_delay."""
191+
regs = self._read_input_registers(8196, self.INT16_REG_COUNT)
192+
return self._unpack(regs, self.INT16_REG_COUNT)
193+
194+
def set_password(self, *, password: int) -> None:
195+
"""Set password."""
196+
self._write_register(4096, password)
197+
198+
def set_dmd_integr_time(self, *, dmd_time: int) -> None:
199+
"""Set IIntegration time for DMD power calculation, default = 3, self.INT16_REG_COUNT5 min."""
200+
self._write_register(4112, dmd_time)
201+
202+
def set_alarm_state(self, *, alarm: bool) -> None:
203+
"""Set alarm state."""
204+
self._write_register(4116, alarm)
205+
206+
def set_alarm_mode(self, *, alarm_mode: int) -> None:
207+
"""Set alarm linked state, default = 1."""
208+
self._write_register(4117, alarm_mode)
209+
210+
def set_alarm_delay(self, *, alarm_delay: int) -> None:
211+
"""Set alarm delay 0 - 3600 s, Default = 0."""
212+
self._write_register(4122, alarm_delay)
213+
214+
def set_device_id(self, *, device_id: int) -> None:
215+
"""Set device id."""
216+
self._write_register(8192, device_id)
217+
218+
def set_baud_rate(self, *, baud_rate: int) -> None:
219+
"""Set baud rate."""
220+
self._write_register(8193, baud_rate)
221+
222+
def set_parity(self, *, parity: int) -> None:
223+
"""Set parity."""
224+
self._write_register(8194, parity)
225+
226+
def set_stop_bit(self, *, stop_bit: int) -> None:
227+
"""Set stop bits."""
228+
self._write_register(8195, stop_bit)
229+
230+
def set_reply_delay(self, *, reply_delay: int) -> None:
231+
"""Set reply_delay."""
232+
self._write_register(8196, reply_delay)
233+
234+
def reset_tot_energy_and_run_hour_counter(self) -> None:
235+
"""Reset total energy + total run hour counters (excluding lifetime)."""
236+
self._write_register(16387, self.INT16_REG_COUNT)
237+
238+
def reset_partial_energy_and_partial_hour_counter(self) -> None:
239+
"""Reset partial energy + partial run hour counters."""
240+
self._write_register(16388, self.INT16_REG_COUNT)
241+
242+
def reset_dmd_and_dmd_max(self) -> None:
243+
"""Reset DMD and DMD max values."""
244+
self._write_register(16389, self.INT16_REG_COUNT)
245+
246+
def reset_to_factory_settings(self) -> None:
247+
"""Factory Restore (Default settings)."""
248+
self._write_register(16416, 2570)
249+
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)