1212import struct
1313from dataclasses import dataclass
1414from decimal import Decimal
15- from typing import Final , TypeVar
15+ from typing import ClassVar , Final , TypeVar
1616
1717from pymodbus .client import ModbusSerialClient
1818from pymodbus .exceptions import ModbusException
@@ -54,7 +54,7 @@ def register_properties(cls: T) -> T:
5454 a corresponding @property getter, and optionally a setter if `writable=True`.
5555
5656 The generated getter automatically calls `_read_register(register_name)`
57- and the setter calls `_write_register (address, value)` with range validation
57+ and the setter calls `_write_registers (address, value)` with range validation
5858 if enabled in the `RegisterSpec`.
5959
6060 Args:
@@ -97,7 +97,7 @@ def setter(self: "Dcm230", value: int, _name: str = name, _spec: "RegisterSpec"
9797 if _spec .range and not (_spec .min <= value <= _spec .max ):
9898 msg = f"Invalid value for '{ _name } ': { value } . Must be between { _spec .min } and { _spec .max } ."
9999 raise ValueError (msg )
100- self ._write_register (_spec .address , int (value ))
100+ self ._write_registers (_spec .address , int (value ))
101101
102102 prop = property (getter , setter ) if spec .writable else property (getter )
103103
@@ -121,11 +121,56 @@ class Dcm230:
121121
122122 SINGLE_REGISTER = 1
123123 MAX_REGS = 2
124+
124125 INPUT_REGISTER = 0x03
125126 HOLDING_REGISTER = 0x04
126127
128+ BACKLIT_OPTIONS : ClassVar [list [int ]] = [0 , 5 , 10 , 20 , 30 , 60 ]
129+ DCM230_REGISTER_BACKLIT_TIME = 0x003C
130+ DCM230_REGISTER_RESET_MAX_DMD_AND_PARTIAL_ENERGY = 0xF010
131+ RESET_MAX_DMD = 0x0000
132+ RESET_PARTIAL_ENERGY = 0x0003
133+
127134 _register_specs : Final [dict [str , RegisterSpec ]] = {
128- "V" : RegisterSpec (address = 0x0000 , count = 2 , decimals = 1 , reg_type = 0x03 ),
135+ "V" : RegisterSpec (address = 0x0000 , count = 2 , decimals = 1 , reg_type = INPUT_REGISTER ),
136+ "A" : RegisterSpec (address = 0x0006 , count = 2 , decimals = 1 , reg_type = INPUT_REGISTER ),
137+ "W" : RegisterSpec (address = 0x000C , count = 2 , decimals = 1 , reg_type = INPUT_REGISTER ),
138+ "kwh" : RegisterSpec (address = 0x0048 , count = 2 , decimals = 1 , reg_type = INPUT_REGISTER ),
139+ "W_dmd" : RegisterSpec (address = 0x0054 , count = 2 , decimals = 1 , reg_type = INPUT_REGISTER ),
140+ "W_dmd_peak" : RegisterSpec (address = 0x0056 , count = 2 , decimals = 1 , reg_type = INPUT_REGISTER ),
141+ "kwh_tot" : RegisterSpec (address = 0x0156 , count = 2 , decimals = 1 , reg_type = INPUT_REGISTER ),
142+ "kwh_partial" : RegisterSpec (address = 0x0180 , count = 2 , decimals = 1 , reg_type = INPUT_REGISTER ),
143+ "dmd_period" : RegisterSpec (
144+ address = 0x0002 ,
145+ count = 2 ,
146+ range = True ,
147+ min = 0 ,
148+ max = 60 ,
149+ writable = True ,
150+ reg_type = HOLDING_REGISTER ,
151+ return_type = int ,
152+ ),
153+ "network_info" : RegisterSpec (
154+ address = 0x0012 , count = 2 , range = True , min = 0 , max = 3 , writable = True , reg_type = HOLDING_REGISTER , return_type = int
155+ ),
156+ "device_id" : RegisterSpec (
157+ address = 0x0014 ,
158+ count = 2 ,
159+ range = True ,
160+ min = 1 ,
161+ max = 247 ,
162+ writable = True ,
163+ reg_type = HOLDING_REGISTER ,
164+ return_type = int ,
165+ ),
166+ "password" : RegisterSpec (address = 0x0018 , count = 2 , reg_type = HOLDING_REGISTER , return_type = int ),
167+ "baud_rate" : RegisterSpec (
168+ address = 0x001C , count = 2 , range = True , min = 0 , max = 5 , writable = True , reg_type = HOLDING_REGISTER , return_type = int
169+ ),
170+ "energy_measurement_tool" : RegisterSpec (
171+ address = 0xF920 , count = 2 , range = True , min = 0 , max = 3 , writable = True , reg_type = HOLDING_REGISTER , return_type = int
172+ ),
173+ "serial_number" : RegisterSpec (address = 0xFC00 , count = 2 , reg_type = HOLDING_REGISTER , return_type = int ),
129174 }
130175
131176 def __init__ (self , device_address : int , client : ModbusSerialClient ) -> None :
@@ -190,8 +235,8 @@ def _read_register(self, register_name: str) -> Decimal | int:
190235 return round (value , spec .decimals )
191236 return self ._unpack (regs , spec .address )
192237
193- def _write_register (self , address : int , value : int ) -> None :
194- """Write a single Modbus register .
238+ def _write_registers (self , address : int , value : int ) -> None :
239+ """Write to Modbus registers .
195240
196241 Args:
197242 address: Register address to write.
@@ -200,12 +245,9 @@ def _write_register(self, address: int, value: int) -> None:
200245 Raises:
201246 ModbusException: If the write operation fails.
202247 """
203- result = self .client .write_register (address = address , value = value , device_id = self .device_address )
248+ result = self .client .write_registers (address = address , values = [ value ] , device_id = self .device_address )
204249 if result .isError ():
205- msg = (
206- "Failed to write to single register. "
207- f"device_address={ self .device_address } address={ address } value={ value } "
208- )
250+ msg = f"Failed to write to registers. device_address={ self .device_address } address={ address } value={ value } "
209251 raise ModbusException (msg )
210252
211253 def _unpack (self , regs : list [int ], address : int ) -> int :
@@ -232,3 +274,60 @@ def _unpack(self, regs: list[int], address: int) -> int:
232274 raise ValueError (msg )
233275
234276 return struct .unpack (">f" , struct .pack (">HH" , regs [0 ], regs [1 ]))[0 ]
277+
278+ def reset_max_dmd (self ) -> None :
279+ """Reset max demand.
280+
281+ Raises:
282+ ModbusException: If failed to write to registers.
283+ """
284+ self ._write_registers (self .DCM230_REGISTER_RESET_MAX_DMD_AND_PARTIAL_ENERGY , self .RESET_MAX_DMD )
285+
286+ def reset_partial_energy (self ) -> None :
287+ """Reset partial energy.
288+
289+ Raises:
290+ ModbusException: If failed to write to registers.
291+ """
292+ self ._write_registers (self .DCM230_REGISTER_RESET_MAX_DMD_AND_PARTIAL_ENERGY , self .RESET_PARTIAL_ENERGY )
293+
294+ @property
295+ def backlit_time (self ) -> int :
296+ """Backlit time.
297+
298+ Options:
299+ 0, 5, 10, 20, 30, 60 minutes.
300+
301+ Returns:
302+ int: Current backlit time value.
303+
304+ Raises:
305+ ValueError: If not a specified value.
306+ ModbusException: If failed to read holding registers.
307+ """
308+ regs = self ._read_registers (self .DCM230_REGISTER_BACKLIT_TIME , self .MAX_REGS , self .HOLDING_REGISTER )
309+ value = round (self ._unpack (regs , self .DCM230_REGISTER_BACKLIT_TIME ))
310+
311+ if value not in self .BACKLIT_OPTIONS :
312+ msg = f"Invalid backlit option: { value } . Must be one of: { self .BACKLIT_OPTIONS } "
313+ raise ValueError (msg )
314+ return value
315+
316+ @backlit_time .setter
317+ def backlit_time (self , value : int ) -> None :
318+ """Backlite time.
319+
320+ Options:
321+ 0, 5, 10, 20, 30, 60 minutes.
322+
323+ Args:
324+ value (int): Set backlit time.
325+
326+ Raises:
327+ ModbusException: If failed to write to registers.
328+ ValueError: If not specified value.
329+ """
330+ if value not in self .BACKLIT_OPTIONS :
331+ msg = f"Invalid backlit option: { value } . Must be one of: { self .BACKLIT_OPTIONS } "
332+ raise ValueError (msg )
333+ self ._write_registers (self .DCM230_REGISTER_BACKLIT_TIME , value )
0 commit comments