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:
@@ -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": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"port": "5000",
|
||||
"SSL": true,
|
||||
"SSL": false,
|
||||
"security": {
|
||||
"cert": "~/ssl/cert.pem",
|
||||
"key": "~/ssl/key.pem"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user