1
0
mirror of https://github.com/thingsboard/thingsboard-gateway synced 2025-10-26 22:31:42 +08:00

Added Modbus as a slave (#640)

* Fixed stopping SQLite storage

* Changed the way of data reading and writing in SQLite Storage

* Changed stopping way for SQLite Storage

* Added Modbus Master Connector

* Created Modbus Server Connector

* Refactored Modbus connector

* Added backward compability adapter for modbus connector

* Fixed adapter

* Added license

* Added ASCII Framer support

* Added Modbus as a slave

* Added sub configuration to modbus slave

* Fixed modbus config file

* Fixed rpc and attributeUpdates for modbus slave

* Changed modbus config file

* Changed modbus config file

* Changed modbus config file

* Changed modbus config file
This commit is contained in:
Vitalii
2021-12-03 12:13:00 +02:00
committed by GitHub
parent e9f5950f43
commit 95fcc14b48
3 changed files with 178 additions and 13 deletions

View File

@@ -1,16 +1,17 @@
{
"server": {
"type": "tcp",
"host": "127.0.0.1",
"port": 5020,
"timeout": 35,
"method": "socket",
"byteOrder": "BIG",
"retries": true,
"retryOnEmpty": true,
"retryOnInvalid": true,
"devices": [
"master": {
"slaves": [
{
"host": "127.0.0.1",
"port": 5021,
"type": "tcp",
"method": "socket",
"timeout": 35,
"byteOrder": "BIG",
"retries": true,
"retryOnEmpty": true,
"retryOnInvalid": true,
"pollPeriod": 5000,
"unitId": 1,
"deviceName": "Temp Sensor",
"attributesPollPeriod": 5000,
@@ -171,5 +172,77 @@
]
}
]
},
"slave": {
"type": "tcp",
"host": "127.0.0.1",
"port": 5026,
"method": "socket",
"deviceName": "Gateway",
"deviceType": "default",
"pollPeriod": 5000,
"sendDataToThingsBoard": false,
"byteOrder": "BIG",
"unitId": 0,
"values": {
"holding_registers": [
{
"attributes": [
{
"address": 1,
"type": "string",
"tag": "sm",
"objectsCount": 1,
"value": "ON"
}
],
"timeseries": [
{
"address": 2,
"type": "int",
"tag": "smm",
"objectsCount": 1,
"value": "12334"
}
],
"attributeUpdates": [
{
"tag": "shared_attribute_write",
"type": "32int",
"functionCode": 6,
"objectsCount": 2,
"address": 29,
"value": 1243
}
],
"rpc": [
{
"tag": "setValue",
"type": "bits",
"functionCode": 5,
"objectsCount": 1,
"address": 31,
"value": 22
}
]
}
],
"coils_initializer": [
{
"attributes": [
{
"address": 5,
"type": "string",
"tag": "sm",
"objectsCount": 1,
"value": "12"
}
],
"timeseries": [],
"attributeUpdates": [],
"rpc": []
}
]
}
}
}

View File

@@ -26,7 +26,7 @@ class BackwardCompatibilityAdapter:
@staticmethod
def __save_json_config_file(config):
with open('config/modbus_new.json', 'w') as file:
file.writelines(dumps(config, sort_keys=True, indent=' ', separators=(',', ': ')))
file.writelines(dumps(config, sort_keys=False, indent=' ', separators=(',', ': ')))
def convert(self):
if not self.__config.get('server'):
@@ -48,7 +48,7 @@ class BackwardCompatibilityAdapter:
slaves.append(slave)
result_dict = {'master': {'slaves': slaves}}
result_dict = {'master': {'slaves': slaves}, 'slave': self.__config.get('slave')}
self.__save_json_config_file(result_dict)
return result_dict

View File

@@ -36,11 +36,17 @@ from pymodbus.bit_read_message import ReadBitsResponseBase
from pymodbus.client.sync import ModbusTcpClient, ModbusUdpClient, ModbusSerialClient
from pymodbus.client.sync import ModbusRtuFramer, ModbusSocketFramer, ModbusAsciiFramer
from pymodbus.exceptions import ConnectionException
from pymodbus.server.asynchronous import StartTcpServer, StartUdpServer, StartSerialServer, StopServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.version import version
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.datastore import ModbusSparseDataBlock
from thingsboard_gateway.connectors.connector import Connector, log
from thingsboard_gateway.connectors.modbus.constants import *
from thingsboard_gateway.connectors.modbus.slave import Slave
from thingsboard_gateway.connectors.modbus.backward_compability_adapter import BackwardCompatibilityAdapter
from thingsboard_gateway.connectors.modbus.bytes_modbus_downlink_converter import BytesModbusDownlinkConverter
CONVERTED_DATA_SECTIONS = [ATTRIBUTES_PARAMETER, TELEMETRY_PARAMETER]
FRAMER_TYPE = {
@@ -48,6 +54,27 @@ FRAMER_TYPE = {
'socket': ModbusSocketFramer,
'ascii': ModbusAsciiFramer
}
SLAVE_TYPE = {
'tcp': StartTcpServer,
'udp': StartUdpServer,
'serial': StartSerialServer
}
FUNCTION_TYPE = {
'coils_initializer': 'ci',
'holding_registers': 'hr',
'input_registers': 'ir',
'discrete_inputs': 'di'
}
FUNCTION_CODE_WRITE = {
'holding_registers': (6, 16),
'coils_initializer': (5, 15)
}
FUNCTION_CODE_READ = {
'holding_registers': 3,
'coils_initializer': 1,
'input_registers': 4,
'discrete_inputs': 2
}
class ModbusConnector(Connector, Thread):
@@ -59,13 +86,23 @@ class ModbusConnector(Connector, Thread):
super().__init__()
self.__gateway = gateway
self._connector_type = connector_type
self.__backward_compatibility_adapter = BackwardCompatibilityAdapter(config)
self.__config = self.__backward_compatibility_adapter.convert()
self.setName(self.__config.get("name", 'Modbus Default ' + ''.join(choice(ascii_lowercase) for _ in range(5))))
self.__connected = False
self.__stopped = False
self.daemon = True
if self.__config.get('slave'):
self.__slave_thread = Thread(target=self.__configure_and_run_slave, args=(self.__config['slave'],),
daemon=True, name='Gateway as a slave')
self.__slave_thread.start()
if config['slave'].get('sendDataToThingsBoard', False):
self.__modify_main_config()
self.__slaves = []
self.__load_slaves()
@@ -89,6 +126,60 @@ class ModbusConnector(Connector, Thread):
sleep(.2)
@staticmethod
def __configure_and_run_slave(config):
identity = None
if config.get('identity'):
identity = ModbusDeviceIdentification()
identity.VendorName = config['identity'].get('vendorName', '')
identity.ProductCode = config['identity'].get('productCode', '')
identity.VendorUrl = config['identity'].get('vendorUrl', '')
identity.ProductName = config['identity'].get('productName', '')
identity.ModelName = config['identity'].get('ModelName', '')
identity.MajorMinorRevision = version.short()
blocks = {}
for (key, value) in config.get('values').items():
values = {}
converter = BytesModbusDownlinkConverter({})
for item in value:
for section in ('attributes', 'timeseries', 'attributeUpdates', 'rpc'):
for val in item[section]:
function_code = FUNCTION_CODE_WRITE[key][0] if val['objectsCount'] <= 1 else \
FUNCTION_CODE_WRITE[key][1]
converted_value = converter.convert(
{**val,
'device': config.get('deviceName', 'Gateway'), 'functionCode': function_code,
'byteOrder': config['byteOrder']},
{'data': {'params': val['value']}})
values[val['address']] = converted_value
blocks[FUNCTION_TYPE[key]] = ModbusSparseDataBlock(values)
context = ModbusServerContext(slaves=ModbusSlaveContext(**blocks), single=True)
SLAVE_TYPE[config['type']](context, identity=identity,
address=(config.get('host'), config.get('port')) if (
config['type'] == 'tcp' or 'udp') else None,
port=config.get('port') if config['type'] == 'serial' else None,
framer=FRAMER_TYPE[config['method']])
def __modify_main_config(self):
config = self.__config['slave']
values = config.pop('values')
device = config
for (register, reg_values) in values.items():
for value in reg_values:
for section in ('attributes', 'timeseries', 'attributeUpdates', 'rpc'):
if not device.get(section):
device[section] = []
for item in value.get(section, []):
device[section].append({**item, 'functionCode': FUNCTION_CODE_READ[register]})
self.__config['master']['slaves'].append(device)
def __load_slaves(self):
self.__slaves = [
Slave(**{**device, 'connector': self, 'gateway': self.__gateway, 'callback': ModbusConnector.callback}) for
@@ -145,6 +236,7 @@ class ModbusConnector(Connector, Thread):
def close(self):
self.__stopped = True
self.__stop_connections_to_masters()
StopServer()
log.info('%s has been stopped.', self.get_name())
def get_name(self):