Skip to content

Commit b22bd25

Browse files
committed
feat(em511): Add the driver with some functions
also added some tests, should run! Signed-off-by: Bobo Bäck Engström <bobo@id8-engineering.io>
1 parent 951badb commit b22bd25

4 files changed

Lines changed: 160 additions & 5 deletions

File tree

src/em511/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"""Hello package."""
2+
from .em511 import Em511
3+
24

35

46
def hello() -> str:
57
"""Hello docstring."""
68
return "Hello from python-em511!"
9+

src/em511/em511.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
2+
"""Driver class for EM511"""
3+
class Em511:
4+
"""Em511 driver class"""
5+
6+
# ---------------------------------------------------------------------
7+
# Initialization & Connection
8+
# ---------------------------------------------------------------------
9+
10+
def __init__(self, device_address: int, client, auto_connect=True) -> None:
11+
"""Constructor"""
12+
self.device_address = device_address
13+
self.client = client
14+
self.connected = False
15+
if auto_connect:
16+
self.connect()
17+
18+
#---------------------------------------------------------------------
19+
20+
def connect(self) -> bool:
21+
"""Try to connect Modbus client."""
22+
if not self.client.connect():
23+
print("Could not open COM port", flush=True)
24+
self.connected = False
25+
return False
26+
self.connected = True
27+
return True
28+
29+
#---------------------------------------------------------------------
30+
31+
def _read_registers(self, address: int, count: int) -> list[int] | None:
32+
"""Internal helper to read Modbus registers safely."""
33+
if not self.connected and not self.connect():
34+
print("Connection failed", flush=True)
35+
return None
36+
37+
result = self.client.read_input_registers(address=address, count=count, device_id=self.device_address)
38+
if result.isError():
39+
print(f"Read error at {address}: {result}", flush=True)
40+
return None
41+
return result.registers
42+
43+
#---------------------------------------------------------------------
44+
45+
def _unpack(self, regs: list[int], reg_type: str) -> float | None:
46+
"""Internal helper to unpuck register list"""
47+
if reg_type == "int16":
48+
return regs[0]
49+
50+
elif reg_type == "int32":
51+
return (regs[1] << 16) + regs[0]
52+
else:
53+
return None
54+
55+
# ---------------------------------------------------------------------
56+
# Core Read Functions
57+
# ---------------------------------------------------------------------
58+
59+
def read_V(self) -> float | None:
60+
"""Return V value"""
61+
regs = self._read_registers(0, 2)
62+
if not regs:
63+
return None
64+
return self._unpack(regs, "int32") / 10
65+
66+
#---------------------------------------------------------------------
67+
68+
def read_A(self) -> float | None:
69+
"""Return A value"""
70+
regs = self._read_registers(2, 2)
71+
if not regs:
72+
return None
73+
return self._unpack(regs, "int32") / 1000
74+
75+
#---------------------------------------------------------------------
76+
77+
def read_W(self) -> float | None:
78+
"""Return W value"""
79+
regs = self._read_registers(4, 2)
80+
if not regs:
81+
return None
82+
return self._unpack(regs, "int32") / 10
83+
84+
#---------------------------------------------------------------------
85+
86+
def read_W_dmd(self) -> float | None:
87+
"""Return W Dmd value"""
88+
regs = self._read_registers(10, 2)
89+
if not regs:
90+
return None
91+
return self._unpack(regs, "int32") / 10
92+
93+
#---------------------------------------------------------------------
94+
95+
def read_W_dmd_peak(self) -> float | None:
96+
"""Return W dmd peak value"""
97+
regs = self._read_registers(12, 2)
98+
if not regs:
99+
return None
100+
return self._unpack(regs, "int32") / 10
101+
102+
#---------------------------------------------------------------------
103+
104+
def read_Hz(self) -> float | None:
105+
"""Return Hz value"""
106+
regs = self._read_registers(15, 1)
107+
if not regs:
108+
return None
109+
return self._unpack(regs, "int16") / 10
110+
111+
#---------------------------------------------------------------------
112+
113+
def read_kWh_tot(self) -> float | None:
114+
"""Return kWh value"""
115+
regs = self._read_registers(16, 2)
116+
if not regs:
117+
return None
118+
return self._unpack(regs, "int32") / 10
119+
120+
#---------------------------------------------------------------------

src/em511/my_functions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"""My functions file, not for the user"""
2+

src/em511/test_hello.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,37 @@
1-
# noqa: D100
2-
import em511
1+
"""Test file for driver"""
2+
from em511 import Em511
3+
from pymodbus.client import ModbusSerialClient
4+
from time import sleep
35

6+
"""Init client"""
7+
client = ModbusSerialClient(
8+
port="/dev/ttyUSB0",
9+
baudrate=9600,
10+
parity="E",
11+
stopbits=1,
12+
bytesize=8,
13+
timeout=1,
14+
)
15+
print("client params is set")
416

5-
def test_hello() -> None:
6-
"""Test hello function."""
7-
assert em511.hello() == "Hello from python-em511!" # noqa: S101
17+
"""Init constructor"""
18+
AC_meter = Em511(102, client)
19+
20+
def test():
21+
22+
if client.connect():
23+
print("Client is connected", flush=True)
24+
print("Testing Functions", flush=True)
25+
print("V: ",AC_meter.read_V())
26+
print("A: ",AC_meter.read_A())
27+
print("W: ",AC_meter.read_W())
28+
print("W dmd: ",AC_meter.read_W_dmd())
29+
print("W dmd peak: ",AC_meter.read_W_dmd_peak())
30+
print("Hz : ",AC_meter.read_Hz())
31+
print("kWh tot : ",AC_meter.read_kWh_tot())
32+
33+
else:
34+
print("Could not open COM port", flush=True)
35+
36+
if __name__ == "__main__":
37+
test()

0 commit comments

Comments
 (0)