Skip to content

Commit

Permalink
Adjusted tests to use AsyncModbusConnector, fixed writing method
Browse files Browse the repository at this point in the history
  • Loading branch information
imbeacon committed Dec 2, 2024
1 parent 0e5464f commit ca9d6ea
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 54 deletions.
68 changes: 39 additions & 29 deletions tests/integration/connectors/modbus/test_modbus_connector.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import asyncio
import logging
import unittest
from os import path
from time import sleep
from unittest.mock import Mock, patch

from pymodbus.pdu import ExceptionResponse
from simplejson import load

from thingsboard_gateway.connectors.modbus.entities.bytes_uplink_converter_config import BytesUplinkConverterConfig
from thingsboard_gateway.gateway.tb_client import TBClient
from thingsboard_gateway.tb_utility.tb_logger import TbLogger

Expand Down Expand Up @@ -49,7 +52,8 @@ def _create_connector(self, config_file_name, test_patch):
with open(self.CONFIG_PATH + config_file_name, 'r', encoding="UTF-8") as file:
self.config = load(file)
self.config['master']['slaves'][0]['uplink_converter'] = BytesModbusUplinkConverter(
{**self.config['master']['slaves'][0], 'deviceName': 'Test'}, logger=self.tb_logger)
BytesUplinkConverterConfig(**{**self.config['master']['slaves'][0], 'deviceName': 'Test'}),
logger=self.tb_logger)
self.config['master']['slaves'][0]['downlink_converter'] = BytesModbusDownlinkConverter(
{**self.config['master']['slaves'][0], 'deviceName': 'Test'}, logger=self.tb_logger)
self.connector = AsyncModbusConnector(self.gateway, self.config, "modbus")
Expand All @@ -76,80 +80,87 @@ def test_read_input_registers(self):
self._create_connector('modbus_attributes.json')

modbus_client_results = []
attrs = self.connector._AsyncModbusConnector__config['master']['slaves'][0]['attributes']
attrs = self.connector._AsyncModbusConnector__config['master']['slaves'][0]['attributes'] # noqa
for item in attrs:
modbus_client_results.append(
self.client.read_input_registers(item['address'], item['objectsCount'], slave=1).registers)

modbus_connector_results = []
for item in attrs:
modbus_connector_results.append(self.connector._AsyncModbusConnector__function_to_device(
self.connector._AsyncModbusConnector__slaves[0],
{'address': item['address'], 'objectsCount': item['objectsCount'],
'functionCode': 4}).registers)
modbus_connector_results.append(asyncio.run(self.__get_available_functions()[4](address=item['address'],
count=item['objectsCount'],
unit_id=self.connector._AsyncModbusConnector__slaves[0].unit_id)).registers) # noqa

for ir, ir1 in zip(modbus_client_results, modbus_connector_results):
self.assertEqual(ir, ir1)

def test_read_holding_registers(self):
self._create_connector('modbus_attributes.json')
modbus_client_results = []
attrs = self.connector._AsyncModbusConnector__config['master']['slaves'][0]['attributes']
attrs = self.connector._AsyncModbusConnector__config['master']['slaves'][0]['attributes'] # noqa
for item in attrs:
modbus_client_results.append(
self.client.read_holding_registers(item['address'], item['objectsCount'], slave=1).registers)

modbus_connector_results = []
for item in attrs:
modbus_connector_results.append(self.connector._AsyncModbusConnector__function_to_device(
self.connector._AsyncModbusConnector__slaves[0],
{'address': item['address'], 'objectsCount': item['objectsCount'],
'functionCode': 3}).registers)
modbus_connector_results.append(asyncio.run(self.__get_available_functions()[3](address=item['address'],
count=item['objectsCount'],
unit_id=self.connector._AsyncModbusConnector__slaves[0].unit_id)).registers) # noqa

for hr, hr1 in zip(modbus_client_results, modbus_connector_results):
self.assertEqual(hr, hr1)

@unittest.skip("Unstable, needs to be fixed")
def test_read_discrete_inputs(self):
self._create_connector('modbus_attributes.json')
modbus_client_results = []
attrs = self.connector._AsyncModbusConnector__config['master']['slaves'][0]['attributes']
attrs = self.connector._AsyncModbusConnector__config['master']['slaves'][0]['attributes'] # noqa
for item in attrs:
modbus_client_results.append(
self.client.read_discrete_inputs(item['address'], item['objectsCount'], slave=1).bits)
result = self.client.read_discrete_inputs(item['address'], item['objectsCount'], slave=1)
if not isinstance(result, ExceptionResponse):
modbus_client_results.append(result.bits)
else:
modbus_client_results.append(str(result))

modbus_connector_results = []
for item in attrs:
modbus_connector_results.append(self.connector._AsyncModbusConnector__function_to_device(
self.connector._AsyncModbusConnector__slaves[0],
{'address': item['address'], 'objectsCount': item['objectsCount'],
'functionCode': 2}).bits)
result = asyncio.run(self.__get_available_functions()[2](address=item['address'],
count=item['objectsCount'],
unit_id=self.connector._AsyncModbusConnector__slaves[0].unit_id)) # noqa
if not isinstance(result, ExceptionResponse):
modbus_connector_results.append(result.bits) # noqa
else:
modbus_connector_results.append(str(result))

for rd, rd1 in zip(modbus_client_results, modbus_connector_results):
self.assertEqual(rd, rd1)

def test_read_coils_inputs(self):
self._create_connector('modbus_attributes.json')
modbus_client_results = []
attrs = self.connector._AsyncModbusConnector__config['master']['slaves'][0]['attributes']
attrs = self.connector._AsyncModbusConnector__config['master']['slaves'][0]['attributes'] # noqa
for item in attrs:
rc = self.client.read_coils(item['address'], item['objectsCount'], slave=1)
if rc and hasattr(rc, 'bits'):
modbus_client_results.append(rc.bits)

modbus_connector_results = []
for item in attrs:
rc = self.connector._AsyncModbusConnector__function_to_device(
self.connector._AsyncModbusConnector__slaves[0],
{'address': item['address'], 'objectsCount': item['objectsCount'],
'functionCode': 1})
rc = asyncio.run(self.__get_available_functions()[1](address=item['address'],
count=item['objectsCount'],
unit_id=self.connector._AsyncModbusConnector__slaves[0].unit_id)) # noqa
if rc and hasattr(rc, 'bits'):
modbus_connector_results.append(rc.bits)

for rc, rc1 in zip(modbus_client_results, modbus_connector_results):
self.assertEqual(rc, rc1)


def __get_available_functions(self):
return self.connector._AsyncModbusConnector__slaves[0].available_functions # noqa



class ModbusConnectorRpcTest(ModbusConnectorTestsBase):
client = None

Expand All @@ -162,7 +173,6 @@ def setUpClass(cls) -> None:
def tearDownClass(cls) -> None:
cls.client.close()

@unittest.skip("Unstable, needs to be fixed")
def test_write_type_rpc(self):
self._create_connector('modbus_rpc.json')

Expand Down Expand Up @@ -223,8 +233,8 @@ def test_attribute_updates(self):

for attribute_updates in attribute_updates_list:
first_value = self.client.read_input_registers(attribute_updates['address'],
attribute_updates['objectsCount'],
slave=1).registers
attribute_updates['objectsCount'],
slave=1).registers
test_attribute_update = {
'device': 'MASTER Temp Sensor',
'data': {
Expand All @@ -235,8 +245,8 @@ def test_attribute_updates(self):
sleep(1)

last_value = self.client.read_input_registers(attribute_updates['address'],
attribute_updates['objectsCount'],
slave=1).registers
attribute_updates['objectsCount'],
slave=1).registers
self.assertNotEqual(first_value, last_value)

def test_deny_unknown_attribute_update(self):
Expand Down
4 changes: 2 additions & 2 deletions thingsboard_gateway/connectors/modbus/entities/master.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ async def write_register(self, address, value, unit_id):

@with_lock_for_serial
async def write_coils(self, address, values, unit_id):
return await self.__client.write_coils(address=address, value=values, slave=unit_id)
return await self.__client.write_coils(address=address, values=values, slave=unit_id)

@with_lock_for_serial
async def write_registers(self, address, values, unit_id):
return await self.__client.write_registers(address=address, value=values, slave=unit_id)
return await self.__client.write_registers(address=address, values=values, slave=unit_id)

def get_available_functions(self):
return {
Expand Down
52 changes: 29 additions & 23 deletions thingsboard_gateway/connectors/modbus/slave.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import asyncio
from threading import Thread
from time import sleep, monotonic
from typing import Tuple
from typing import Tuple, Dict, Union

from pymodbus.constants import Defaults

from thingsboard_gateway.connectors.modbus.bytes_modbus_downlink_converter import BytesModbusDownlinkConverter
from thingsboard_gateway.connectors.modbus.bytes_modbus_uplink_converter import BytesModbusUplinkConverter
from thingsboard_gateway.connectors.modbus.entities.bytes_uplink_converter_config import BytesUplinkConverterConfig
from thingsboard_gateway.connectors.modbus.modbus_converter import ModbusConverter
from thingsboard_gateway.gateway.constants import UPLINK_PREFIX, CONVERTER_PARAMETER, DOWNLINK_PREFIX, \
REPORT_STRATEGY_PARAMETER
from thingsboard_gateway.gateway.statistics.statistics_service import StatisticsService
Expand Down Expand Up @@ -114,29 +115,28 @@ def get_name(self):
return self.device_name

def __load_downlink_converter(self, config):
try:
if config.get(DOWNLINK_PREFIX + CONVERTER_PARAMETER) is not None:
converter = TBModuleLoader.import_module(self.connector.connector_type,
config[DOWNLINK_PREFIX + CONVERTER_PARAMETER])({}, self._log)
else:
converter = BytesModbusDownlinkConverter({}, self._log)

return converter
except Exception as e:
self._log.exception('Failed to load downlink converter for % slave: %s', self.name, e)
return self.__load_converter(config, DOWNLINK_PREFIX, {})

def __load_uplink_converter(self, config):
return self.__load_converter(config, UPLINK_PREFIX, self.uplink_converter_config)

def __load_converter(self, config, converter_type, converter_config: Union[Dict, BytesUplinkConverterConfig]={}):
try:
if config.get(UPLINK_PREFIX + CONVERTER_PARAMETER) is not None:
if isinstance(config.get(converter_type + CONVERTER_PARAMETER), str):
converter = TBModuleLoader.import_module(self.connector.connector_type,
config[UPLINK_PREFIX + CONVERTER_PARAMETER])(
self.uplink_converter_config, self._log)
config[converter_type + CONVERTER_PARAMETER])(
converter_config, self._log)
elif isinstance(config.get(converter_type + CONVERTER_PARAMETER), ModbusConverter):
converter = config[converter_type + CONVERTER_PARAMETER]
else:
converter = BytesModbusUplinkConverter(self.uplink_converter_config, self._log)
if converter_type == DOWNLINK_PREFIX:
converter = BytesModbusDownlinkConverter(converter_config, self._log)
else:
converter = BytesModbusUplinkConverter(converter_config, self._log)

return converter
except Exception as e:
self._log.exception('Failed to load uplink converter for % slave: %s', self.name, e)
self._log.exception('Failed to load %s converter for % slave: %s', converter_type, self.name, e)

async def connect(self) -> Tuple[bool, bool]:
cur_time = monotonic() * 1000
Expand Down Expand Up @@ -217,12 +217,18 @@ async def write(self, function_code, address, value):
async def __write(self, function_code, address, value):
result = None

if function_code in (5, 6):
result = await self.available_functions[function_code](address=address, value=value, unit_id=self.unit_id)
elif function_code in (15, 16):
result = await self.available_functions[function_code](address=address, values=value, unit_id=self.unit_id)
else:
self._log.error("Unknown Modbus function with code: %s", function_code)
try:
if function_code in (5, 6):
result = await self.available_functions[function_code](address=address, value=value, unit_id=self.unit_id)
elif function_code in (15, 16):
result = await self.available_functions[function_code](address=address, values=value, unit_id=self.unit_id)
else:
self._log.error("Unknown Modbus function with code: %s", function_code)
except Exception as e:
self._log.error("Failed to write with function code %s: %s", function_code, e)
future = asyncio.Future()
future.set_result(False)
return future

self._log.debug("Write with result: %s", str(result))
return result
Expand Down

0 comments on commit ca9d6ea

Please sign in to comment.