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

Merge branch 'master' of https://github.com/thingsboard/thingsboard-gateway into feature/grpc-api

This commit is contained in:
zbeacon
2022-01-04 12:48:51 +02:00
11 changed files with 113 additions and 72 deletions

View File

@@ -18,15 +18,13 @@
"key": "temperature",
"method": "notify",
"characteristicUUID": "226CAA55-6476-4566-7562-66734470666D",
"byteFrom": 2,
"byteTo": 6
"valueExpression": "[2]"
},
{
"key": "humidity",
"method": "notify",
"characteristicUUID": "226CAA55-6476-4566-7562-66734470666D",
"byteFrom": 9,
"byteTo": 13
"valueExpression": "[0]"
}
],
"attributes": [
@@ -34,8 +32,13 @@
"key": "name",
"method": "read",
"characteristicUUID": "00002A00-0000-1000-8000-00805F9B34FB",
"byteFrom": 0,
"byteTo": -1
"valueExpression": "[0:2]cm [2:]A"
},
{
"key": "values",
"method": "read",
"characteristicUUID": "00002A00-0000-1000-8000-00805F9B34FB",
"valueExpression": "All values: [:]"
}
],
"attributeUpdates": [

View File

@@ -1,7 +1,7 @@
{
"host": "127.0.0.1",
"port": "5000",
"SSL": true,
"SSL": false,
"security": {
"cert": "~/ssl/cert.pem",
"key": "~/ssl/key.pem"

View File

@@ -27,7 +27,6 @@ except ImportError:
print("BLE library not found - installing...")
TBUtility.install_package("bleak")
from thingsboard_gateway.connectors.ble.bytes_ble_uplink_converter import BytesBLEUplinkConverter
from thingsboard_gateway.connectors.connector import Connector, log
from thingsboard_gateway.connectors.ble.device import Device
@@ -74,8 +73,8 @@ class BLEConnector(Connector, Thread):
log.info(device)
def __configure_and_load_devices(self):
self.__devices = [Device({**device, 'callback': BLEConnector.callback}) for device in
self.__config.get('devices', [])]
self.__devices = [Device({**device, 'callback': BLEConnector.callback, 'connector_type': self._connector_type})
for device in self.__config.get('devices', [])]
def open(self):
self.__stopped = False
@@ -112,9 +111,10 @@ class BLEConnector(Connector, Thread):
device_config = BLEConnector.process_data.get()
data = device_config.pop('data')
config = device_config.pop('config')
converter = device_config.pop('converter')
try:
converter = BytesBLEUplinkConverter(device_config)
converter = converter(device_config)
converted_data = converter.convert(config, data)
self.statistics['MessagesReceived'] = self.statistics['MessagesReceived'] + 1
log.debug(converted_data)

View File

@@ -25,6 +25,7 @@
# limitations under the License.
from pprint import pformat
from re import findall
from thingsboard_gateway.connectors.ble.ble_uplink_converter import BLEUplinkConverter, log
@@ -46,22 +47,30 @@ class BytesBLEUplinkConverter(BLEUplinkConverter):
for section in ('telemetry', 'attributes'):
for item in data[section]:
byte_from = item['byteFrom']
byte_to = item['byteTo']
try:
byte_to = byte_to if byte_to != -1 else len(data)
converted_data = item['data'][byte_from:byte_to]
expression_arr = findall(r'\[[^\s][0-9:]*]', item['valueExpression'])
converted_data = item['valueExpression']
try:
converted_data = converted_data.replace(b"\x00", b'').decode('UTF-8')
except UnicodeDecodeError:
converted_data = str(converted_data)
for exp in expression_arr:
indexes = exp[1:-1].split(':')
data_to_replace = ''
if len(indexes) == 2:
from_index, to_index = indexes
concat_arr = item['data'][
int(from_index) if from_index != '' else None:int(
to_index) if to_index != '' else None]
for sub_item in concat_arr:
data_to_replace += str(sub_item)
else:
data_to_replace += str(item['data'][int(indexes[0])])
converted_data = converted_data.replace(exp, data_to_replace)
if item.get('key') is not None:
self.dict_result[section].append({item['key']: converted_data})
else:
log.error('Key for %s not found in config: %s', config['type'], config['section_config'])
log.error('Key for %s not found in config: %s', config['type'], config[section])
except Exception as e:
log.error('\nException caught when processing data for %s\n\n', pformat(config))
log.exception(e)

View File

@@ -32,11 +32,13 @@ import asyncio
from bleak import BleakClient
from thingsboard_gateway.connectors.connector import log
from thingsboard_gateway.tb_utility.tb_loader import TBModuleLoader
MAC_ADDRESS_FORMAT = {
'Darwin': '-',
'other': ':'
}
DEFAULT_CONVERTER_CLASS_NAME = 'BytesBLEUplinkConverter'
class Device(Thread):
@@ -48,6 +50,7 @@ class Device(Thread):
self.device_type = config.get('deviceType', 'default')
self.timeout = config.get('timeout', 10000) / 1000
self.show_map = config.get('showMap', False)
self.__connector_type = config['connector_type']
self.daemon = True
@@ -61,6 +64,7 @@ class Device(Thread):
self.poll_period = config.get('pollPeriod', 5000) / 1000
self.config = {
'extension': config.get('extension', DEFAULT_CONVERTER_CLASS_NAME),
'telemetry': config.get('telemetry', []),
'attributes': config.get('attributes', []),
'attributeUpdates': config.get('attributeUpdates', []),
@@ -71,6 +75,9 @@ class Device(Thread):
self.notifying_chars = []
self.__converter = None
self.__load_converter()
self.start()
@staticmethod
@@ -82,6 +89,17 @@ class Device(Thread):
return mac_address.upper()
def __load_converter(self):
converter_class_name = self.config['extension']
module = TBModuleLoader.import_module(self.__connector_type, converter_class_name)
if module:
log.debug('Converter %s for device %s - found!', converter_class_name, self.name)
self.__converter = module
else:
log.error("Cannot find converter for %s device", self.name)
self.stopped = True
async def timer(self):
await self.__process_self()
self.last_polled_time = time()
@@ -103,6 +121,7 @@ class Device(Thread):
data_for_converter = {
'deviceName': self.name,
'deviceType': self.device_type,
'converter': self.__converter,
'config': {
'attributes': self.config['attributes'],
'telemetry': self.config['telemetry']
@@ -133,6 +152,7 @@ class Device(Thread):
data_for_converter = {
'deviceName': self.name,
'deviceType': self.device_type,
'converter': self.__converter,
'config': {
'attributes': self.config['attributes'],
'telemetry': self.config['telemetry']

View File

@@ -24,7 +24,7 @@ class BytesModbusDownlinkConverter(ModbusConverter):
self.__config = config
def convert(self, config, data):
byte_order_str = config.get("byteOrder", "BIG")
byte_order_str = config.get("byteOrder", "LITTLE")
word_order_str = config.get("wordOrder", "LITTLE")
byte_order = Endian.Big if byte_order_str.upper() == "BIG" else Endian.Little
word_order = Endian.Big if word_order_str.upper() == "BIG" else Endian.Little

View File

@@ -46,9 +46,9 @@ class BytesModbusUplinkConverter(ModbusConverter):
if configuration.get("wordOrder"):
word_order = configuration["wordOrder"]
elif config.get("wordOrder"):
word_order = config.get("wordOrder", "BIG")
word_order = config.get("wordOrder", "LITTLE")
else:
word_order = "BIG"
word_order = "LITTLE"
endian_order = Endian.Little if byte_order.upper() == "LITTLE" else Endian.Big
word_endian_order = Endian.Little if word_order.upper() == "LITTLE" else Endian.Big
decoded_data = None

View File

@@ -27,8 +27,10 @@ except ImportError:
print("Modbus library not found - installing...")
TBUtility.install_package("pymodbus", ">=2.3.0")
TBUtility.install_package('pyserial')
TBUtility.install_package('twisted')
from pymodbus.constants import Defaults
from twisted.internet import reactor
from pymodbus.bit_write_message import WriteSingleCoilResponse, WriteMultipleCoilsResponse
from pymodbus.register_write_message import WriteMultipleRegistersResponse, WriteSingleRegisterResponse
from pymodbus.register_read_message import ReadRegistersResponseBase
@@ -236,7 +238,8 @@ class ModbusConnector(Connector, Thread):
def close(self):
self.__stopped = True
self.__stop_connections_to_masters()
StopServer()
if reactor.running:
StopServer()
log.info('%s has been stopped.', self.get_name())
def get_name(self):
@@ -312,7 +315,7 @@ class ModbusConnector(Connector, Thread):
if not device.config['master'].is_socket_open():
if device.config['connection_attempt'] >= connect_attempt_count and current_time - device.config[
'last_connection_attempt_time'] >= wait_after_failed_attempts_ms:
'last_connection_attempt_time'] >= wait_after_failed_attempts_ms:
device.config['connection_attempt'] = 0
while not device.config['master'].is_socket_open() \
@@ -456,7 +459,7 @@ class ModbusConnector(Connector, Thread):
elif isinstance(device.config[RPC_SECTION], list):
for rpc_command_config in device.config[RPC_SECTION]:
if rpc_command_config[TAG_PARAMETER] == server_rpc_request[DATA_PARAMETER][
RPC_METHOD_PARAMETER]:
RPC_METHOD_PARAMETER]:
self.__process_rpc_request(server_rpc_request, rpc_command_config)
break
else:
@@ -479,9 +482,12 @@ class ModbusConnector(Connector, Thread):
self.__connect_to_current_master(device)
if rpc_command_config.get(FUNCTION_CODE_PARAMETER) in (5, 6, 15, 16):
rpc_command_config[PAYLOAD_PARAMETER] = device.config[
DOWNLINK_PREFIX + CONVERTER_PARAMETER].convert(
rpc_command_config, content)
converted_data = device.config[DOWNLINK_PREFIX + CONVERTER_PARAMETER].convert(rpc_command_config,
content)
try:
rpc_command_config[PAYLOAD_PARAMETER] = converted_data[0]
except IndexError:
rpc_command_config[PAYLOAD_PARAMETER] = converted_data
try:
response = self.__function_to_device(device, rpc_command_config)

View File

@@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
from queue import Queue
from random import choice
from re import fullmatch
@@ -22,7 +21,7 @@ from time import time
import ssl
import os
from simplejson import JSONDecodeError, loads
from simplejson import JSONDecodeError
import requests
from requests.auth import HTTPBasicAuth as HTTPBasicAuthRequest
from requests.exceptions import RequestException
@@ -135,7 +134,7 @@ class RESTConnector(Connector, Thread):
self.load_handlers()
web.run_app(self._app, host=self.__config['host'], port=self.__config['port'], handle_signals=False,
ssl_context=ssl_context)
ssl_context=ssl_context, reuse_port=self.__config['port'], reuse_address=self.__config['host'])
def run(self):
self._connected = True
@@ -246,15 +245,7 @@ class RESTConnector(Connector, Thread):
request_timeout = request_dict["config"].get("timeout")
try:
if request_dict["config"].get("data") and \
(isinstance(request_dict["config"]["data"], str) and loads(request_dict["config"]["data"])):
data = {"json": loads(request_dict["config"]["data"])}
else:
data = {"data": request_dict["config"].get("data")}
except JSONDecodeError:
data = {"data": request_dict.get("data")}
data = {"data": request_dict["config"]["data"]}
params = {
"method": request_dict["config"].get("HTTPMethod", "GET"),
"url": url,

View File

@@ -139,6 +139,8 @@ class TBGatewayService:
"devices": self.__rpc_devices,
"update": self.__rpc_update,
"version": self.__rpc_version,
"device_renamed": self.__process_renamed_gateway_devices,
"device_deleted": self.__process_deleted_gateway_devices,
}
self.__remote_shell = None
if self.__config["thingsboard"].get("remoteShell"):
@@ -307,14 +309,10 @@ class TBGatewayService:
def __process_attribute_update(self, content):
self.__process_remote_logging_update(content.get("RemoteLoggingLevel"))
self.__process_remote_configuration(content.get("configuration"))
self.__process_deleted_gateway_devices(content.get("deletedGatewayDevices"))
self.__process_renamed_gateway_devices(content.get("renamedGatewayDevices"))
def __process_attributes_response(self, shared_attributes, client_attributes):
self.__process_remote_logging_update(shared_attributes.get('RemoteLoggingLevel'))
self.__process_remote_configuration(shared_attributes.get("configuration"))
self.__process_deleted_gateway_devices(shared_attributes.get("deletedGatewayDevices"))
self.__process_renamed_gateway_devices(shared_attributes.get("renamedGatewayDevices"))
def __process_remote_logging_update(self, remote_logging_level):
if remote_logging_level == 'NONE':
@@ -327,25 +325,31 @@ class TBGatewayService:
log.info('Remote logging has being updated. Current logging level is: %s ',
remote_logging_level)
def __process_deleted_gateway_devices(self, deleted_devices):
if deleted_devices:
log.debug("Received deleted gateway devices notification: %s", deleted_devices)
devices_list_changed = False
for device in deleted_devices:
if device in self.__connected_devices:
del self.__connected_devices[device]
log.debug("Device %s - was removed", device)
devices_list_changed = True
if devices_list_changed:
self.__save_persistent_devices()
self.__load_persistent_devices()
def __process_deleted_gateway_devices(self, deleted_device_name: str):
log.info("Received deleted gateway device notification: %s", deleted_device_name)
if deleted_device_name in list(self.__renamed_devices.values()):
first_device_name = TBUtility.get_dict_key_by_value(self.__renamed_devices, deleted_device_name)
del self.__renamed_devices[first_device_name]
deleted_device_name = first_device_name
log.debug("Current renamed_devices dict: %s", self.__renamed_devices)
if deleted_device_name in self.__connected_devices:
del self.__connected_devices[deleted_device_name]
log.debug("Device %s - was removed", deleted_device_name)
self.__save_persistent_devices()
self.__load_persistent_devices()
def __process_renamed_gateway_devices(self, renamed_devices):
if renamed_devices:
log.debug("Received renamed gateway devices notification: %s", renamed_devices)
self.__renamed_devices = renamed_devices
self.__save_persistent_devices()
self.__load_persistent_devices()
def __process_renamed_gateway_devices(self, renamed_device: dict):
log.info("Received renamed gateway device notification: %s", renamed_device)
old_device_name, new_device_name = renamed_device.popitem()
if old_device_name in list(self.__renamed_devices.values()):
device_name_key = TBUtility.get_dict_key_by_value(self.__renamed_devices, old_device_name)
else:
device_name_key = new_device_name
self.__renamed_devices[device_name_key] = new_device_name
self.__save_persistent_devices()
self.__load_persistent_devices()
log.debug("Current renamed_devices dict: %s", self.__renamed_devices)
def __process_remote_configuration(self, new_configuration):
if new_configuration is not None and self.__remote_configurator is not None:
@@ -991,12 +995,14 @@ class TBGatewayService:
log.debug("Old connected_devices file, new file will be created")
return
if self.available_connectors.get(devices[device_name][0]):
self.__connected_devices[device_name] = {
"connector": self.available_connectors[devices[device_name][0]],
"device_type": devices[device_name][1]}
self.__saved_devices[device_name] = {
device_data_to_save = {
"connector": self.available_connectors[devices[device_name][0]],
"device_type": devices[device_name][1]}
if len(devices[device_name] > 2) and device_name not in self.__renamed_devices:
new_device_name = devices[device_name][2]
self.__renamed_devices[device_name] = new_device_name
self.__connected_devices[device_name] = device_data_to_save
self.__saved_devices[device_name] = device_data_to_save
except Exception as e:
log.exception(e)
continue
@@ -1005,12 +1011,14 @@ class TBGatewayService:
self.__connected_devices = {} if self.__connected_devices is None else self.__connected_devices
def __save_persistent_devices(self):
data_to_save = {}
for device in self.__connected_devices:
if self.__connected_devices[device]["connector"] is not None:
data_to_save[device] = [self.__connected_devices[device]["connector"].get_name(), self.__connected_devices[device]["device_type"]]
if device in self.__renamed_devices:
data_to_save[device].append(self.__renamed_devices.get(device))
with open(self._config_dir + CONNECTED_DEVICES_FILENAME, 'w') as config_file:
try:
data_to_save = {}
for device in self.__connected_devices:
if self.__connected_devices[device]["connector"] is not None:
data_to_save[device] = (self.__connected_devices[device]["connector"].get_name(), self.__connected_devices[device]["device_type"])
config_file.write(dumps(data_to_save, indent=2, sort_keys=True))
except Exception as e:
log.exception(e)

View File

@@ -166,3 +166,7 @@ class TBUtility:
value = TBUtility.get_value(item, data['data'], 'params', expression_instead_none=True)
text = text.replace(tag, str(value))
return text
@staticmethod
def get_dict_key_by_value(dictionary: dict, value):
return list(dictionary.values())[list(dictionary.values()).index(value)]