Skip to content

Commit 6ef6f0d

Browse files
Merge 11ff445 into b97de0d
2 parents b97de0d + 11ff445 commit 6ef6f0d

File tree

8 files changed

+299
-22
lines changed

8 files changed

+299
-22
lines changed

changelog.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
<!-- ## [Unreleased] -->
1616

1717
## Released
18+
## [2.3.1] - 2023-01-06
19+
### Added
20+
- Unittest to read multiple coils at any location if defined as list, verifies #35
21+
- Unittests to write a single coil or multiple coils at any location if defined as list, verifies fix #15 and #24
22+
23+
### Fixed
24+
- All configured register of a client can be accessed and modified individually, see #35
25+
- Resolved overlapping register positions in example JSON file
26+
- Register length of `EXAMPLE_IREG` in TCP and RTU examples corrected to 1 instead of 2
27+
1828
## [2.3.0] - 2023-01-03
1929
### Added
2030
- Custom callback functions can be registered on client (ModbusRTU or ModbusTCP) side with new parameters `on_set_cb` and `on_get_cb` available from [modbus.py](umodbus/modbus.py) functions `add_coil` and `add_hreg`. Functions `add_ist` and `add_ireg` support only `on_get_cb`, see #31
@@ -233,8 +243,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
233243
- PEP8 style issues on all files of [`lib/uModbus`](lib/uModbus)
234244

235245
<!-- Links -->
236-
[Unreleased]: https://github.com/brainelectronics/micropython-modbus/compare/2.3.0...develop
246+
[Unreleased]: https://github.com/brainelectronics/micropython-modbus/compare/2.3.1...develop
237247

248+
[2.3.1]: https://github.com/brainelectronics/micropython-modbus/tree/2.3.1
238249
[2.3.0]: https://github.com/brainelectronics/micropython-modbus/tree/2.3.0
239250
[2.2.0]: https://github.com/brainelectronics/micropython-modbus/tree/2.2.0
240251
[2.1.3]: https://github.com/brainelectronics/micropython-modbus/tree/2.1.3

examples/rtu_client_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
"IREGS": {
8989
"EXAMPLE_IREG": {
9090
"register": 10,
91-
"len": 2,
91+
"len": 1,
9292
"val": 60001
9393
}
9494
}

examples/rtu_host_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
"IREGS": {
9393
"EXAMPLE_IREG": {
9494
"register": 10,
95-
"len": 2,
95+
"len": 1,
9696
"val": 60001
9797
}
9898
}

examples/tcp_client_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def reset_data_registers_cb(reg_type, address, val):
157157
"IREGS": {
158158
"EXAMPLE_IREG": {
159159
"register": 10,
160-
"len": 2,
160+
"len": 1,
161161
"val": 60001
162162
}
163163
}

examples/tcp_host_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
"IREGS": {
102102
"EXAMPLE_IREG": {
103103
"register": 10,
104-
"len": 2,
104+
"len": 1,
105105
"val": 60001
106106
}
107107
}

registers/example.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"unit": ""
3434
},
3535
"ANOTHER_EXAMPLE_COIL": {
36-
"register": 126,
36+
"register": 127,
3737
"len": 3,
3838
"val": [0, 1, 0],
3939
"description": "Example COILS registers with length of 3, Coils (setter+getter) [0, 1]",
@@ -97,7 +97,7 @@
9797
"unit": ""
9898
},
9999
"ANOTHER_EXAMPLE_ISTS": {
100-
"register": 69,
100+
"register": 70,
101101
"len": 3,
102102
"val": [0, 1, 0],
103103
"description": "Example ISTS registers with length of 3, Ists (only getter) [0, 1]",

tests/test_tcp_example.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,39 @@ def test_read_coils_partially(self) -> None:
327327
self.assertTrue(all(isinstance(x, bool) for x in coil_status))
328328
self.assertEqual(coil_status, expectation_list_partial)
329329

330+
def test_read_coils_specific_of_multiple(self) -> None:
331+
"""Test reading specific coils of client defined as list"""
332+
# the offset based on the specified register
333+
# e.g. register = 150, offset = 3, qty = 5, the requested coils are
334+
# 153-158
335+
base_coil_offset = 3
336+
coil_qty = 5 # read only 5 coils of multiple defined ones
337+
338+
coil_address = (
339+
self._register_definitions['COILS']['MANY_COILS']['register'] +
340+
base_coil_offset
341+
)
342+
expectation_list_full = list(
343+
map(bool,
344+
self._register_definitions['COILS']['MANY_COILS']['val'])
345+
)
346+
expectation_list = expectation_list_full[
347+
base_coil_offset:base_coil_offset + coil_qty
348+
]
349+
350+
coil_status = self._host.read_coils(
351+
slave_addr=self._client_addr,
352+
starting_addr=coil_address,
353+
coil_qty=coil_qty)
354+
355+
self.test_logger.debug(
356+
'Status of COIL {} length {}: {}, expectation: {}'.
357+
format(coil_address, coil_qty, coil_status, expectation_list))
358+
self.assertIsInstance(coil_status, list)
359+
self.assertEqual(len(coil_status), coil_qty)
360+
self.assertTrue(all(isinstance(x, bool) for x in coil_status))
361+
self.assertEqual(coil_status, expectation_list)
362+
330363
def test_read_discrete_inputs_single(self) -> None:
331364
"""Test reading discrete inputs of client"""
332365
ist_address = \
@@ -748,6 +781,73 @@ def test_write_single_coil(self) -> None:
748781
self.assertTrue(all(isinstance(x, bool) for x in coil_status))
749782
self.assertEqual(coil_status, expectation_list)
750783

784+
# test setting a coil in a list of coils
785+
base_coil_offset = 3
786+
coil_qty = 1
787+
coil_address = (
788+
self._register_definitions['COILS']['MANY_COILS']['register'] +
789+
base_coil_offset
790+
)
791+
expectation_list_full = list(
792+
map(bool,
793+
self._register_definitions['COILS']['MANY_COILS']['val'])
794+
)
795+
expectation_list = expectation_list_full[
796+
base_coil_offset:base_coil_offset + coil_qty
797+
]
798+
799+
#
800+
# Check clean system (client register data is as initially defined)
801+
#
802+
# verify current state by reading coil states
803+
coil_status = self._host.read_coils(
804+
slave_addr=self._client_addr,
805+
starting_addr=coil_address,
806+
coil_qty=coil_qty)
807+
808+
self.test_logger.debug(
809+
'Initial status of COIL {}: {}, expectation: {}'.format(
810+
coil_address,
811+
coil_status,
812+
expectation_list))
813+
self.assertIsInstance(coil_status, list)
814+
self.assertEqual(len(coil_status), coil_qty)
815+
self.assertTrue(all(isinstance(x, bool) for x in coil_status))
816+
self.assertEqual(coil_status, expectation_list)
817+
818+
#
819+
# Test setting coil to True
820+
#
821+
# update coil state of client with a different than the current state
822+
new_coil_val = not expectation_list[0]
823+
expectation_list[0] = new_coil_val
824+
825+
operation_status = self._host.write_single_coil(
826+
slave_addr=self._client_addr,
827+
output_address=coil_address,
828+
output_value=new_coil_val)
829+
830+
self.test_logger.debug(
831+
'Result of setting COIL {} to {}: {}, expectation: {}'.format(
832+
coil_address, new_coil_val, operation_status, True))
833+
self.assertIsInstance(operation_status, bool)
834+
self.assertTrue(operation_status)
835+
836+
# verify setting of state by reading data back again
837+
coil_status = self._host.read_coils(
838+
slave_addr=self._client_addr,
839+
starting_addr=coil_address,
840+
coil_qty=coil_qty)
841+
842+
self.test_logger.debug('Status of COIL {}: {}, expectation: {}'.
843+
format(coil_address,
844+
coil_status,
845+
expectation_list))
846+
self.assertIsInstance(coil_status, list)
847+
self.assertEqual(len(coil_status), coil_qty)
848+
self.assertTrue(all(isinstance(x, bool) for x in coil_status))
849+
self.assertEqual(coil_status, expectation_list)
850+
751851
def test_write_single_register(self) -> None:
752852
"""Test updating single holding register of client"""
753853
hreg_address = \
@@ -934,6 +1034,83 @@ def test_write_multiple_coils(self) -> None:
9341034
# https://github.com/brainelectronics/micropython-modbus/issues/38
9351035
# self.assertEqual(coil_status, expectation_list)
9361036

1037+
def test_write_multiple_coils_specific_of_multiple(self) -> None:
1038+
"""Test updating specific coils of client defined as list"""
1039+
# test with more than 8 coils
1040+
coil_address = \
1041+
self._register_definitions['COILS']['MANY_COILS']['register']
1042+
coil_qty = \
1043+
self._register_definitions['COILS']['MANY_COILS']['len']
1044+
expectation_list = list(
1045+
map(bool, self._register_definitions['COILS']['MANY_COILS']['val'])
1046+
)
1047+
1048+
#
1049+
# Check clean system (client register data is as initially defined)
1050+
#
1051+
# verify current state by reading coil states
1052+
coil_status = self._host.read_coils(
1053+
slave_addr=self._client_addr,
1054+
starting_addr=coil_address,
1055+
coil_qty=coil_qty)
1056+
1057+
self.test_logger.debug(
1058+
'Initial status of COIL {} length {}: {}, expectation: {}'.format(
1059+
coil_address, coil_qty, coil_status, expectation_list))
1060+
self.assertIsInstance(coil_status, list)
1061+
self.assertEqual(len(coil_status), coil_qty)
1062+
self.assertTrue(all(isinstance(x, bool) for x in coil_status))
1063+
self.assertEqual(coil_status, expectation_list)
1064+
1065+
#
1066+
# Test setting coils to inverted initial states
1067+
#
1068+
# update coil states of client with a different than the current state
1069+
new_coil_vals_full = [not val for val in expectation_list]
1070+
1071+
# the offset based on the specified register
1072+
# e.g. register = 150, offset = 3, qty = 5, the requested coils are
1073+
# 153-158
1074+
base_coil_offset = 3
1075+
coil_qty = 5 # read only 5 coils of multiple defined ones
1076+
1077+
new_coil_vals = new_coil_vals_full[
1078+
base_coil_offset:(base_coil_offset + coil_qty)
1079+
]
1080+
expectation_list = (
1081+
expectation_list[:base_coil_offset] +
1082+
new_coil_vals +
1083+
expectation_list[base_coil_offset + coil_qty:]
1084+
)
1085+
1086+
operation_status = self._host.write_multiple_coils(
1087+
slave_addr=self._client_addr,
1088+
starting_address=coil_address,
1089+
output_values=new_coil_vals)
1090+
1091+
self.test_logger.debug(
1092+
'Result of setting COIL {} length {} to {}: {}, expectation: {}'.
1093+
format(
1094+
coil_address, coil_qty, new_coil_vals, operation_status, True))
1095+
self.assertIsInstance(operation_status, bool)
1096+
self.assertTrue(operation_status)
1097+
1098+
# verify setting of states by reading data back again
1099+
coil_status = self._host.read_coils(
1100+
slave_addr=self._client_addr,
1101+
starting_addr=coil_address,
1102+
coil_qty=coil_qty)
1103+
1104+
self.test_logger.debug(
1105+
'Status of COIL {} length {}: {}, expectation: {}'.format(
1106+
coil_address, coil_qty, coil_status, expectation_list))
1107+
self.assertIsInstance(coil_status, list)
1108+
self.assertEqual(len(coil_status), coil_qty)
1109+
self.assertTrue(all(isinstance(x, bool) for x in coil_status))
1110+
# Reading coil data bits is reversed, see #38
1111+
# https://github.com/brainelectronics/micropython-modbus/issues/38
1112+
# self.assertEqual(coil_status, expectation_list)
1113+
9371114
def test_write_multiple_registers(self) -> None:
9381115
"""Test updating multiple holding register of client"""
9391116
hreg_address = \

0 commit comments

Comments
 (0)