Skip to content

Commit 59f0396

Browse files
authored
Reactivate simulator validate. (#2643)
1 parent e342eb5 commit 59f0396

File tree

4 files changed

+106
-26
lines changed

4 files changed

+106
-26
lines changed

pymodbus/datastore/context.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def decode(self, fx):
3131
"""
3232
return self._fx_mapper[fx]
3333

34-
async def async_getValues(self, fc_as_hex: int, address: int, count: int = 1) -> Sequence[int | bool]:
34+
async def async_getValues(self, fc_as_hex: int, address: int, count: int = 1) -> Sequence[int | bool] | int:
3535
"""Get `count` values from datastore.
3636
3737
:param fc_as_hex: The function we are working with
@@ -41,14 +41,14 @@ async def async_getValues(self, fc_as_hex: int, address: int, count: int = 1) ->
4141
"""
4242
return self.getValues(fc_as_hex, address, count)
4343

44-
async def async_setValues(self, fc_as_hex: int, address: int, values: Sequence[int | bool]) -> None:
44+
async def async_setValues(self, fc_as_hex: int, address: int, values: Sequence[int | bool]) -> None | int:
4545
"""Set the datastore with the supplied values.
4646
4747
:param fc_as_hex: The function we are working with
4848
:param address: The starting address
4949
:param values: The new values to be set
5050
"""
51-
self.setValues(fc_as_hex, address, values)
51+
return self.setValues(fc_as_hex, address, values)
5252

5353
def getValues(self, fc_as_hex: int, address: int, count: int = 1) -> Sequence[int | bool]:
5454
"""Get `count` values from datastore.
@@ -61,13 +61,15 @@ def getValues(self, fc_as_hex: int, address: int, count: int = 1) -> Sequence[in
6161
Log.error("getValues({},{},{}) not implemented!", fc_as_hex, address, count)
6262
return []
6363

64-
def setValues(self, fc_as_hex: int, address: int, values: Sequence[int | bool]) -> None:
64+
def setValues(self, fc_as_hex: int, address: int, values: Sequence[int | bool]) -> None | int:
6565
"""Set the datastore with the supplied values.
6666
6767
:param fc_as_hex: The function we are working with
6868
:param address: The starting address
6969
:param values: The new values to be set
7070
"""
71+
Log.error("setValues({},{},{}) not implemented!", fc_as_hex, address, values)
72+
return 1
7173

7274

7375
# ---------------------------------------------------------------------------#

pymodbus/datastore/simulator.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -534,11 +534,58 @@ def get_text_register(self, register):
534534
_write_func_code = (5, 6, 15, 16, 22, 23)
535535
_bits_func_code = (1, 2, 5, 15)
536536

537+
def loop_validate(self, address, end_address, fx_write):
538+
"""Validate entry in loop.
539+
540+
:meta private:
541+
"""
542+
i = address
543+
while i < end_address:
544+
reg = self.registers[i]
545+
if (fx_write and not reg.access) or reg.type == CellType.INVALID:
546+
return False
547+
if not self.type_exception:
548+
i += 1
549+
continue
550+
if reg.type == CellType.NEXT:
551+
return False
552+
if reg.type in (CellType.BITS, CellType.UINT16):
553+
i += 1
554+
elif reg.type in (CellType.UINT32, CellType.FLOAT32):
555+
if i + 1 >= end_address:
556+
return False
557+
i += 2
558+
else:
559+
i += 1
560+
while i < end_address:
561+
if self.registers[i].type == CellType.NEXT:
562+
i += 1
563+
return True
564+
565+
def validate(self, func_code, address, count=1):
566+
"""Check to see if the request is in range.
567+
568+
:meta private:
569+
"""
570+
if func_code in self._bits_func_code:
571+
# Bit count, correct to register count
572+
count = int((count + WORD_SIZE - 1) / WORD_SIZE)
573+
address = int(address / 16)
574+
575+
real_address = self.fc_offset[func_code] + address
576+
if real_address < 0 or real_address > self.register_count:
577+
return False
578+
579+
fx_write = func_code in self._write_func_code
580+
return self.loop_validate(real_address, real_address + count, fx_write)
581+
537582
def getValues(self, func_code, address, count=1):
538583
"""Return the requested values of the datastore.
539584
540585
:meta private:
541586
"""
587+
if not self.validate(func_code, address, count):
588+
return 2
542589
result = []
543590
if func_code not in self._bits_func_code:
544591
real_address = self.fc_offset[func_code] + address
@@ -577,15 +624,20 @@ def setValues(self, func_code, address, values):
577624
if func_code not in self._bits_func_code:
578625
real_address = self.fc_offset[func_code] + address
579626
for value in values:
627+
if not self.validate(func_code, address):
628+
return 2
580629
self.registers[real_address].value = value
581630
self.registers[real_address].count_write += 1
582631
real_address += 1
583-
return
632+
address += 1
633+
return None
584634

585635
# bit access
586636
real_address = self.fc_offset[func_code] + int(address / 16)
587637
bit_index = address % 16
588638
for value in values:
639+
if not self.validate(func_code, address):
640+
return 2
589641
bit_mask = 2**bit_index
590642
if bool(value):
591643
self.registers[real_address].value |= bit_mask
@@ -596,7 +648,8 @@ def setValues(self, func_code, address, values):
596648
if bit_index == 16:
597649
bit_index = 0
598650
real_address += 1
599-
return
651+
address += 1
652+
return None
600653

601654
# --------------------------------------------
602655
# Internal action methods

pymodbus/pdu/bit_message.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pymodbus.constants import ModbusStatus
77
from pymodbus.datastore import ModbusDeviceContext
88

9-
from .pdu import ModbusPDU, pack_bitstring, unpack_bitstring
9+
from .pdu import ExceptionResponse, ModbusPDU, pack_bitstring, unpack_bitstring
1010

1111

1212
class ReadCoilsRequest(ModbusPDU):
@@ -38,6 +38,8 @@ async def update_datastore(self, context: ModbusDeviceContext) -> ModbusPDU:
3838
values = await context.async_getValues(
3939
self.function_code, self.address, self.count
4040
)
41+
if isinstance(values, int):
42+
return ExceptionResponse(self.function_code, values)
4143
response_class = (ReadCoilsResponse if self.function_code == 1 else ReadDiscreteInputsResponse)
4244
return response_class(dev_id=self.dev_id, transaction_id=self.transaction_id, bits=cast(list[bool], values))
4345

@@ -93,9 +95,13 @@ class WriteSingleCoilRequest(WriteSingleCoilResponse):
9395

9496
async def update_datastore(self, context: ModbusDeviceContext) -> ModbusPDU:
9597
"""Run a request against a datastore."""
96-
await context.async_setValues(self.function_code, self.address, self.bits)
97-
values = cast(list[bool], await context.async_getValues(self.function_code, self.address, 1))
98-
return WriteSingleCoilResponse(address=self.address, bits=values, dev_id=self.dev_id, transaction_id=self.transaction_id)
98+
if (rc := await context.async_setValues(self.function_code, self.address, self.bits)):
99+
return ExceptionResponse(self.function_code, rc)
100+
values = await context.async_getValues(self.function_code, self.address, 1)
101+
if isinstance(values, int):
102+
return ExceptionResponse(self.function_code, values)
103+
104+
return WriteSingleCoilResponse(address=self.address, bits=cast(list[bool], values), dev_id=self.dev_id, transaction_id=self.transaction_id)
99105

100106
def get_response_pdu_size(self) -> int:
101107
"""Get response pdu size.
@@ -127,9 +133,12 @@ def decode(self, data: bytes) -> None:
127133
async def update_datastore(self, context: ModbusDeviceContext) -> ModbusPDU:
128134
"""Run a request against a datastore."""
129135
count = len(self.bits)
130-
await context.async_setValues(
136+
rc = await context.async_setValues(
131137
self.function_code, self.address, self.bits
132138
)
139+
if rc:
140+
return ExceptionResponse(self.function_code, rc)
141+
133142
return WriteMultipleCoilsResponse(address=self.address, count=count, dev_id=self.dev_id, transaction_id=self.transaction_id)
134143

135144
def get_response_pdu_size(self) -> int:

pymodbus/pdu/register_message.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ def get_response_pdu_size(self) -> int:
3434

3535
async def update_datastore(self, context: ModbusDeviceContext) -> ModbusPDU:
3636
"""Run a read holding request against a datastore."""
37-
values = cast(list[int], await context.async_getValues(
37+
values = await context.async_getValues(
3838
self.function_code, self.address, self.count
39-
))
39+
)
40+
if isinstance(values, int):
41+
return ExceptionResponse(self.function_code, values)
4042
response_class = (ReadHoldingRegistersResponse if self.function_code == 3 else ReadInputRegistersResponse)
41-
return response_class(registers=values, dev_id=self.dev_id, transaction_id=self.transaction_id)
43+
return response_class(registers=cast(list[int], values), dev_id=self.dev_id, transaction_id=self.transaction_id)
4244

4345

4446
class ReadHoldingRegistersResponse(ModbusPDU):
@@ -137,13 +139,17 @@ async def update_datastore(self, context: ModbusDeviceContext) -> ModbusPDU:
137139
return ExceptionResponse(self.function_code, ExceptionResponse.ILLEGAL_VALUE)
138140
if not 1 <= self.write_count <= 0x079:
139141
return ExceptionResponse(self.function_code, ExceptionResponse.ILLEGAL_VALUE)
140-
await context.async_setValues(
142+
rc = await context.async_setValues(
141143
self.function_code, self.write_address, self.write_registers
142144
)
143-
registers = cast(list[int], await context.async_getValues(
145+
if rc:
146+
return ExceptionResponse(self.function_code, rc)
147+
registers = await context.async_getValues(
144148
self.function_code, self.read_address, self.read_count
145-
))
146-
return ReadWriteMultipleRegistersResponse(registers=registers, dev_id=self.dev_id, transaction_id=self.transaction_id)
149+
)
150+
if isinstance(registers, int):
151+
return ExceptionResponse(self.function_code, registers)
152+
return ReadWriteMultipleRegistersResponse(registers=cast(list[int], registers), dev_id=self.dev_id, transaction_id=self.transaction_id)
147153

148154
def get_response_pdu_size(self) -> int:
149155
"""Get response pdu size.
@@ -182,11 +188,15 @@ async def update_datastore(self, context: ModbusDeviceContext) -> ModbusPDU:
182188
"""Run a write single register request against a datastore."""
183189
if not 0 <= self.registers[0] <= 0xFFFF:
184190
return ExceptionResponse(self.function_code, ExceptionResponse.ILLEGAL_VALUE)
185-
await context.async_setValues(
191+
rc = await context.async_setValues(
186192
self.function_code, self.address, self.registers
187193
)
188-
values = cast(list[int], await context.async_getValues(self.function_code, self.address, 1))
189-
return WriteSingleRegisterResponse(address=self.address, registers=values)
194+
if rc:
195+
return ExceptionResponse(self.function_code, rc)
196+
values = await context.async_getValues(self.function_code, self.address, 1)
197+
if isinstance(values, int):
198+
return ExceptionResponse(self.function_code, values)
199+
return WriteSingleRegisterResponse(address=self.address, registers=cast(list[int], values))
190200

191201
def get_response_pdu_size(self) -> int:
192202
"""Get response pdu size.
@@ -221,9 +231,11 @@ async def update_datastore(self, context: ModbusDeviceContext) -> ModbusPDU:
221231
"""Run a write single register request against a datastore."""
222232
if not 1 <= self.count <= 0x07B:
223233
return ExceptionResponse(self.function_code, ExceptionResponse.ILLEGAL_VALUE)
224-
await context.async_setValues(
234+
rc = await context.async_setValues(
225235
self.function_code, self.address, self.registers
226236
)
237+
if rc:
238+
return ExceptionResponse(self.function_code, rc)
227239
return WriteMultipleRegistersResponse(address=self.address, count=self.count, dev_id=self.dev_id, transaction_id=self.transaction_id)
228240

229241
def get_response_pdu_size(self) -> int:
@@ -275,11 +287,15 @@ async def update_datastore(self, context: ModbusDeviceContext) -> ModbusPDU:
275287
return ExceptionResponse(self.function_code, ExceptionResponse.ILLEGAL_VALUE)
276288
if not 0x0000 <= self.or_mask <= 0xFFFF:
277289
return ExceptionResponse(self.function_code, ExceptionResponse.ILLEGAL_VALUE)
278-
values = (await context.async_getValues(self.function_code, self.address, 1))[0]
279-
values = (values & self.and_mask) | (self.or_mask & ~self.and_mask)
280-
await context.async_setValues(
281-
self.function_code, self.address, [values]
290+
values = await context.async_getValues(self.function_code, self.address, 1)
291+
if isinstance(values, int):
292+
return ExceptionResponse(self.function_code, values)
293+
values = (values[0] & self.and_mask) | (self.or_mask & ~self.and_mask)
294+
rc = await context.async_setValues(
295+
self.function_code, self.address, cast(list[int], [values])
282296
)
297+
if rc:
298+
return ExceptionResponse(self.function_code, rc)
283299
return MaskWriteRegisterResponse(address=self.address, and_mask=self.and_mask, or_mask=self.or_mask, dev_id=self.dev_id, transaction_id=self.transaction_id)
284300

285301

0 commit comments

Comments
 (0)