From 275e2451f0b29a46c6dda4e1dd9977b0fa716324 Mon Sep 17 00:00:00 2001 From: "Jimbo.Automates" Date: Sun, 25 Sep 2022 14:56:37 -0700 Subject: [PATCH 01/15] Add node info to runCmd for #23 (#25) Add the node address to runCmd error messages to make it clear which node caused the error. --- udi_interface/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/udi_interface/node.py b/udi_interface/node.py index b124d0d..ba9e5a5 100644 --- a/udi_interface/node.py +++ b/udi_interface/node.py @@ -164,13 +164,13 @@ def runCmd(self, command): fun = self.commands[command['cmd']] fun(self, command) else: - NLOGGER.error('command {} not defined'.format(command['cmd'])) + NLOGGER.error('node {} command {} not defined'.format(self.address,command['cmd'])) elif 'success' in command: if not command['success']: - NLOGGER.error('Command message failed: {}'.format(command)) + NLOGGER.error('Command message failed for node {}: {}'.format(self.address,command)) else: - NLOGGER.error('Invalid command message: {}'.format(command)) + NLOGGER.error('Invalid command message for node {}: {}'.format(self.address,command)) def start(self): From c95b0caebdc521b5bbeec8ec058f932baae06114 Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Wed, 13 Jul 2022 09:52:00 -0700 Subject: [PATCH 02/15] SSL certificate and connection status. Add support to use a client certificate for a secure mqtt connection. This assumes the certificates are in the node server's directory and are named with the node server's ID (uuid_slot). Add support for publishing the client connection status when we connect. This is to notify PG3 that we have connected to the mqtt broker. Publish a client disconnect status before we disconnect from the mqtt broker. --- udi_interface/interface.py | 46 +++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/udi_interface/interface.py b/udi_interface/interface.py index d39b213..177f3cd 100644 --- a/udi_interface/interface.py +++ b/udi_interface/interface.py @@ -7,7 +7,7 @@ import logging import markdown2 import os -from os.path import join, expanduser +from os.path import join, expanduser, exists import paho.mqtt.client as mqtt try: import queue @@ -196,20 +196,16 @@ def __init__(self, classes, envVar=None): self._threads['input'] = Thread( target=self._parseInput, name='Command') self._mqttc = mqtt.Client(self.id, True) - self._mqttc.username_pw_set(self.id, self.pg3init['token']) self._mqttc.on_connect = self._connect self._mqttc.on_message = self._message self._mqttc.on_subscribe = self._subscribe self._mqttc.on_disconnect = self._disconnect self._mqttc.on_publish = self._publish self._mqttc.on_log = self._log + self.using_mosquitto = True self.useSecure = True self._nodes = {} self.nodes_internal = {} - if self.pg3init['secure'] == 1: - self.sslContext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) - self.sslContext.check_hostname = False - self._mqttc.tls_set_context(self.sslContext) self.loop = None self.inQueue = queue.Queue() self.isyVersion = None @@ -269,6 +265,14 @@ def _connect(self, mqttc, userdata, flags, rc): results = [] LOGGER.info("MQTT Connected with result code " + str(rc) + " (Success)") + + # Publish connection status and set up will + if self.using_mosquitto: + connected = {"connected": [{}]} + self._mqttc.publish('udi/pg3/connections/ns/{}'.format(self.id), json.dumps(connected), retain=True) + failed = {"disconnected": [{}]} + self._mqttc.will_set('udi/pg3/connections/ns/{}'.format(self.id), json.dumps(failed), qos=0, retain=True) + results.append((self.topicInput, tuple( self._mqttc.subscribe(self.topicInput)))) for (topic, (result, mid)) in results: @@ -445,6 +449,34 @@ def _startMqtt(self): """ LOGGER.info('Connecting to MQTT... {}:{}'.format( self._server, self._port)) + + self.username = self.id + + # Load the client SSL certificate. What if this fails? + if self.pg3init['secure'] == 1: + self.sslContext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) + self.sslContext.check_hostname = False + cert = self.username + ".cert" + key = self.username + ".key" + + # only if certs exist! + if exists(cert) and exists(key): + LOGGER.info('Using SSL certs: {} {}'.format(cert, key)) + self.sslContext.load_cert_chain(cert, key) + self.username = self.id.replace(':', '') + self.using_mosquitto = True + else: + self.using_mosquitto = False + + self._mqttc.tls_set_context(self.sslContext) + + self._mqttc.username_pw_set(self.username, self.pg3init['token']) + + if self.using_mosquitto: + # Set up the will, do we need this here? + failed = {"disconnected": [{}]} + self._mqttc.will_set('udi/pg3/connections/ns/{}'.format(self.id), json.dumps(failed), qos=0, retain=True) + done = False while not done: try: @@ -512,6 +544,8 @@ def stop(self): LOGGER.info('Disconnecting from MQTT... {}:{}'.format( self._server, self._port)) self._mqttc.loop_stop() + disconnect = {"disconnected": [{}]} + self._mqttc.publish('udi/pg3/connections/ns/{}'.format(self.id), json.dumps(disconnect), retain=True) self._mqttc.disconnect() def send(self, message, type): From 9234ddf0a18405b1a47efdb3f007a821f25b93d6 Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Wed, 13 Jul 2022 10:00:02 -0700 Subject: [PATCH 03/15] Don't confuse users with useless warnings. When first initializing a node, it won't be in the database so ignore the warning that it's not found in the database. This seems to confuse users who see this and assume something is wrong. --- udi_interface/interface.py | 6 ++++-- udi_interface/node.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/udi_interface/interface.py b/udi_interface/interface.py index 177f3cd..ecdedc5 100644 --- a/udi_interface/interface.py +++ b/udi_interface/interface.py @@ -988,7 +988,7 @@ def getConfig(self): """ Returns a copy of the last config received. """ return self.config - def db_getNodeDrivers(self, addr = None): + def db_getNodeDrivers(self, addr = None, init = False): """ Returns a list of nodes or a list of drivers that were saved in the database. @@ -1020,7 +1020,9 @@ def db_getNodeDrivers(self, addr = None): for n in self._nodes: if self._nodes[n]['address'] == addr: return self._nodes[n]['drivers'] # this is an array - LOGGER.warning(f'{addr} not found in database.') + # ignore the warning if we're initialzing the node. + if not init: + LOGGER.warning(f'{addr} not found in database.') else: for n in self._nodes: nl.append(self._nodes[n]) diff --git a/udi_interface/node.py b/udi_interface/node.py index ba9e5a5..0a53e04 100644 --- a/udi_interface/node.py +++ b/udi_interface/node.py @@ -53,7 +53,7 @@ def _convertDrivers(self, drivers): def updateDrivers(self, drivers): self.drivers = deepcopy(drivers) - def _updateDrivers(self, poly, address): + def _updateDrivers(self, poly, address, init=True): db_drivers = poly.db_getNodeDrivers(address) try: for drv in db_drivers: From e72b743db9737ddc99452edb65455dc373099356 Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Tue, 27 Sep 2022 11:18:05 -0700 Subject: [PATCH 04/15] Version 3.0.48 --- udi_interface/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/udi_interface/__init__.py b/udi_interface/__init__.py index e3cd0ba..76e3dd0 100755 --- a/udi_interface/__init__.py +++ b/udi_interface/__init__.py @@ -7,7 +7,7 @@ from .custom import Custom from .isy import ISY -__version__ = '3.0.47' +__version__ = '3.0.48' __description__ = 'UDI Python Interface for Polyglot version 3' __url__ = 'https://github.com/UniversalDevicesInc/udi_python_interface' __author__ = 'Universal Devices Inc.' From fba8f44860fc305a1fd8893d9b9741c134d2c0ed Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Wed, 28 Sep 2022 15:15:19 -0700 Subject: [PATCH 05/15] Change address filter behavior for non-start events. Event subscriptions take an optional parameter specifying and address filter. The purpose of this filter is to only call the event handler if the address matches the address specified when the event is published. However, only the START event includes an address when published, all other events set the address to None. The result is that if you specify an address when subscribing to any event except START, your subscription will never trigger and never call the event handler because non-None will never equal None. This wasn't really the intended behavior. Events published with address set to None were ment to apply to any/all address. This change ignores the filter address in the subscription if the the event is published with address=None. Basically all event subscriptions will trigger the handler except for START now. --- udi_interface/interface.py | 40 +++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/udi_interface/interface.py b/udi_interface/interface.py index ecdedc5..cfd73c9 100644 --- a/udi_interface/interface.py +++ b/udi_interface/interface.py @@ -70,6 +70,17 @@ class pub(object): cfg_threads = [] topic_data = [] + ''' + when called, this adds a callback, address pair to a topic. The 'topics' + dictionary will look like: + topics = { + config: [(callback, address), (callback, address)], + start: [(callback, address), (callback, address)], + } + + When something subscribes, send any previous published events for the + topic to the subscriber. I.E. backlog events. + ''' @staticmethod def subscribe(topic, callback, address): if int(topic) >= len(pub.topic_list): @@ -80,26 +91,44 @@ def subscribe(topic, callback, address): else: pub.topics[pub.topic_list[topic]].append([callback, address]) - # QUESTION: Should this publish any existing info to the subscriber? + # Send backlog events if any for item in pub.topic_data: - if item[0] == topic and item[1] == address: + if item[0] == topic and (address == None or item[1] == address): Thread(target=callback, args=item[2:]).start() + ''' + when we publish an event, we first push the event on to the backlog + queue so that any later subscribers will get the event when they + subscribe. + ''' @staticmethod def publish(topic, address, *argv): pub.topic_data.append([topic, address, *argv]) + + # check if anyone has subscribed to this event if pub.topic_list[topic] in pub.topics: + # loop through all of the subscribers for item in pub.topics[pub.topic_list[topic]]: - if item[1] == address: + ''' + With the exception of the START event, all others are + published with address == None. + + For the START event we want to check the subscribers + address filter value (item[1]) and compare it with the + address in the event. For all other events we don't + really care. + ''' + if address == None or item[1] == address: Thread(target=item[0], args=[*argv]).start() @staticmethod def publish_nt(topic, address, *argv): pub.topic_data.append([topic, address, *argv]) + if pub.topic_list[topic] in pub.topics: for item in pub.topics[pub.topic_list[topic]]: - if item[1] == address: + if address == None or item[1] == address: t = Thread(target=item[0], args=[*argv]) pub.cfg_threads.append(t) t.start() @@ -107,9 +136,10 @@ def publish_nt(topic, address, *argv): @staticmethod def publish_wait(topic, address, *argv): pub.topic_data.append([topic, address, *argv]) + if pub.topic_list[topic] in pub.topics: for item in pub.topics[pub.topic_list[topic]]: - if item[1] == address: + if address == None or item[1] == address: Thread(target=pub.waitForFinish, args=[item[0], *argv]).start() From 7d329219ea848bbee3f39dcc04d15c590fc3014e Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Wed, 28 Sep 2022 15:32:43 -0700 Subject: [PATCH 06/15] No server.json is not an error. Warn if the server.json file isn't available. The only thing that really depends on this is the checkProfile() method and we'd like to deprecate that eventually. Warn if no version is specified when calling the interface.start() method. --- udi_interface/interface.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/udi_interface/interface.py b/udi_interface/interface.py index cfd73c9..134e2a9 100644 --- a/udi_interface/interface.py +++ b/udi_interface/interface.py @@ -529,13 +529,8 @@ def _startMqtt(self): def _get_server_data(self): """ _get_server_data: Loads the server.json and returns as a dict - :param check_profile: Calls the check_profile method if True - - If profile_version in json is null then profile will be loaded on - every restart. - """ - self.serverdata = {'version': 'unknown'} + self.serverdata = {'version': '0.0.0', 'profile_version': 'NotDefined'} # Read the SERVER info from the json. try: @@ -543,7 +538,13 @@ def _get_server_data(self): self.serverdata = json.load(data) data.close() except Exception as err: - LOGGER.error('get_server_data: failed to read file {0}: {1}'.format( + """ + Failure to load the server.json file is no longer an error. + The only things that may be used from the server.json file are + the version number (as a fallback if start isn't called with one) + and the profile_version for checkProfile(). + """ + LOGGER.warning('get_server_data: failed to read file {0}: {1}'.format( Interface.SERVER_JSON_FILE_NAME, err), exc_info=True) return @@ -960,6 +961,8 @@ def start(self, version=None): # Tell PG3 our version if version: self.serverdata['version'] = version + else: + LOGGER.warning('No node server version specified. Using deprecated server.json version') def ready(self): @@ -1271,14 +1274,13 @@ def checkProfile(self, force=False, build_profile=None): LOGGER.debug('check_profile: force={} build_profile={}'.format( force, build_profile)) - """ FIXME: this should be from self._ifaceData """ cdata = self._ifaceData.profile_version LOGGER.debug('check_profile: saved_version={}'.format(cdata)) LOGGER.debug('check_profile: profile_version={}'.format( self.serverdata['profile_version'])) if self.serverdata['profile_version'] == "NotDefined": - LOGGER.error( + LOGGER.warning( 'check_profile: Ignoring since nodeserver does not have profile_version') return False From e9e36417542e427b0b5c8b2665a17a31e05e00f4 Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Thu, 29 Sep 2022 10:17:15 -0700 Subject: [PATCH 07/15] Version 3.0.49 --- udi_interface/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/udi_interface/__init__.py b/udi_interface/__init__.py index 76e3dd0..c757d64 100755 --- a/udi_interface/__init__.py +++ b/udi_interface/__init__.py @@ -7,7 +7,7 @@ from .custom import Custom from .isy import ISY -__version__ = '3.0.48' +__version__ = '3.0.49' __description__ = 'UDI Python Interface for Polyglot version 3' __url__ = 'https://github.com/UniversalDevicesInc/udi_python_interface' __author__ = 'Universal Devices Inc.' From a4030ae428ea0ccd8d23de03e9f459f6ca9875af Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Wed, 26 Oct 2022 14:00:41 -0700 Subject: [PATCH 08/15] add details to conn_status --- API.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index 7f12285..4521c43 100644 --- a/API.md +++ b/API.md @@ -237,7 +237,8 @@ __addNode(node, conn_status = None)__ Adds a new node to Polyglot. You fist need node using your custom class, which you then pass to addNode. Return value is the node passed in. -If conn\_status is set to a driver string, this node and that driver specified will be used by PG3 to represent the connection status (0 = disconnected, 1 = connected, 2 = failed). By default, conn\_status is None. +If conn\_status is set to a driver string, this node and the driver specified will be used by PG3 to represent the connection status (0 = disconnected, 1 = connected, 2 = failed). By default, conn\_status is None. When the node server connects via MQTT, PG3 will send a value of 1 to the ISY/IoP for that node/driver. When PG3 stops the node server it will send a value of 0 to the ISY/IoP for that node/driver. If the MQTT connection drops for any other reason, PG3 will send a value of 2 to the ISY/IoP for that node/driver. + Notes: 1. Only Node class common information is stored in the database, not your From 9c7660b068d923bc07b36375f547dc96855103c9 Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Mon, 14 Nov 2022 10:05:50 -0800 Subject: [PATCH 09/15] Fix node rename operations When we call PG3 to rename a node it renames the node in it's database and on the ISY. However, it also needs to update the node class object in the interface. There are also some restrictions. We shouldn't rename the node in the database or ISY if the node class object hasn't been created yet. While we're at it make sure that addNode warns about renaming the node properly. Issue: #121 --- udi_interface/interface.py | 35 +++++++++++++++++++++++++++-------- udi_interface/node.py | 1 + 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/udi_interface/interface.py b/udi_interface/interface.py index 134e2a9..70303b9 100644 --- a/udi_interface/interface.py +++ b/udi_interface/interface.py @@ -414,7 +414,16 @@ def _message(self, mqttc, userdata, msg): elif key == 'setLogLevelList': LOGGER.info('setLogList response {}'.format(parsed_msg[key])) elif key == 'renamenode': + # [{'address': 'addr_0001', 'name': 'today', 'success': True}] LOGGER.info('renamenode response {}'.format(parsed_msg[key])) + for resp in parsed_msg[key]: + try: + if resp['success']: + addr = resp['address'] + self.nodes_internal[addr].name = resp['name'] + except Exception as error: + LOGGER.error('Failed to update internal nodelist: {} :: {}'.format(resp, error)) + else: LOGGER.error( 'Invalid command received in message from PG3: \'{}\' {}'.format(key, parsed_msg[key])) @@ -985,9 +994,14 @@ def addNode(self, node, conn_status=None): :param node: Dictionary of node settings. Keys: address, name, node_def_id, primary, and drivers are required. """ - if node.address in self._nodes and self._nodes[node.address]['name'] != node.name: - LOGGER.warning("addNode(): Cannot be used to change the node's name") - node.name = self._nodes[node.address]['name'] + if node.address in self.nodes_internal: + if self.nodes_internal[node.address].name != node.name: + LOGGER.warning("addNode(): Cannot be used to change the node's name from {} to {}".format(self.nodes_internal[node.address].name, node.name)) + node.name = self.nodes_internal[node.address].name + else: + if node.address in self._nodes and self._nodes[node.address]['name'] != node.name: + LOGGER.warning("addNode(): Cannot be used to change the node's name from {} to {}".format(self._nodes[node.address]['name'], node.name)) + node.name = self._nodes[node.address]['name'] LOGGER.info('Adding node {}({}) [{}]'.format(node.name, node.address, node.private)) message = { @@ -1124,17 +1138,22 @@ def getNode(self, address): def renameNode(self, address, newname): """ - Rename a node from the Node Server + Rename a node from the Node Server. """ - LOGGER.info('Renaming node {}'.format(address)) - message = { + if address in self.nodes_internal: + LOGGER.info('Renaming node {}'.format(address)) + message = { 'renamenode': [{ 'address': address, 'name': newname }] - } + } + + self.send(message, 'command') + self.nodes_internal[address].name = newname + else: + LOGGER.error('renameNode: Node {} doesn\'t exist'.format(address)) - self.send(message, 'command') def delNode(self, address): """ diff --git a/udi_interface/node.py b/udi_interface/node.py index 0a53e04..697eb04 100644 --- a/udi_interface/node.py +++ b/udi_interface/node.py @@ -144,6 +144,7 @@ def rename(self, newname): } self.poly.send(message, 'command') + self.name = newname def reportCmd(self, command, value=None, uom=None): message = { From 88842447a4d46220d311320824d18c34259a1d7c Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Mon, 14 Nov 2022 11:23:10 -0800 Subject: [PATCH 10/15] Add a note about modifying the node list while iterating it. --- API.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index 4521c43..c192630 100644 --- a/API.md +++ b/API.md @@ -257,7 +257,8 @@ __getNodes()__ Returns your list of nodes. The interface will attempt to wrap the list of nodes from Polyglot with your custom classes. But this can fail if your custom class needs additional parameters when creating the class object. Your node server should call addNode() to make sure the objects on -this list are your custom class objects. +this list are your custom class objects. Note that the behavior is undefined +if you try to modify the list while iterating it. __getNode(address)__ Returns a single node. From 1d89160c62be7b2a34d337dde1161773188dfe42 Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Thu, 17 Nov 2022 11:45:02 -0800 Subject: [PATCH 11/15] Add rename option to addNode addNode now takes an optional parameter; rename that can be either true or false. It defaults to false. If set to true, it will cause the node to be renamed on the ISY and in the PG3 database if using PG3 version 3.1.15 or later. Earlier versions of PG3 should ignore this parameter --- API.md | 4 +++- udi_interface/interface.py | 17 +++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/API.md b/API.md index c192630..f5ba759 100644 --- a/API.md +++ b/API.md @@ -233,12 +233,14 @@ It is best to use event handlers to access configuration data as then you are as __isConnected()__ which tells you if this NodeServer and Polyglot are connected via MQTT. -__addNode(node, conn_status = None)__ Adds a new node to Polyglot. You fist need to instantiate a +__addNode(node, conn_status = None, rename = False)__ Adds a new node to Polyglot. You fist need to instantiate a node using your custom class, which you then pass to addNode. Return value is the node passed in. If conn\_status is set to a driver string, this node and the driver specified will be used by PG3 to represent the connection status (0 = disconnected, 1 = connected, 2 = failed). By default, conn\_status is None. When the node server connects via MQTT, PG3 will send a value of 1 to the ISY/IoP for that node/driver. When PG3 stops the node server it will send a value of 0 to the ISY/IoP for that node/driver. If the MQTT connection drops for any other reason, PG3 will send a value of 2 to the ISY/IoP for that node/driver. +If rename is set to True, addNode will attempt to rename the node on the ISY and in the PG3 database to match the name specified in node. When rename is set to False, the name of the node on the ISY and in PG3's database record are unchanged. + Notes: 1. Only Node class common information is stored in the database, not your diff --git a/udi_interface/interface.py b/udi_interface/interface.py index 70303b9..2eab139 100644 --- a/udi_interface/interface.py +++ b/udi_interface/interface.py @@ -554,7 +554,7 @@ def _get_server_data(self): and the profile_version for checkProfile(). """ LOGGER.warning('get_server_data: failed to read file {0}: {1}'.format( - Interface.SERVER_JSON_FILE_NAME, err), exc_info=True) + Interface.SERVER_JSON_FILE_NAME, err), exc_info=False) return # Get the version info @@ -988,21 +988,12 @@ def isConnected(self): """ Tells you if this nodeserver and Polyglot are connected via MQTT """ return self.connected - def addNode(self, node, conn_status=None): + def addNode(self, node, conn_status=None, rename=False): """ Add a node to the NodeServer :param node: Dictionary of node settings. Keys: address, name, node_def_id, primary, and drivers are required. """ - if node.address in self.nodes_internal: - if self.nodes_internal[node.address].name != node.name: - LOGGER.warning("addNode(): Cannot be used to change the node's name from {} to {}".format(self.nodes_internal[node.address].name, node.name)) - node.name = self.nodes_internal[node.address].name - else: - if node.address in self._nodes and self._nodes[node.address]['name'] != node.name: - LOGGER.warning("addNode(): Cannot be used to change the node's name from {} to {}".format(self._nodes[node.address]['name'], node.name)) - node.name = self._nodes[node.address]['name'] - LOGGER.info('Adding node {}({}) [{}]'.format(node.name, node.address, node.private)) message = { 'addnode': [{ @@ -1012,7 +1003,8 @@ def addNode(self, node, conn_status=None): 'primaryNode': node.primary, 'drivers': node.drivers, 'hint': node.hint, - 'private': node.private + 'private': node.private, + 'rename': rename }] } self.send(message, 'command') @@ -1139,6 +1131,7 @@ def getNode(self, address): def renameNode(self, address, newname): """ Rename a node from the Node Server. + Can we do this if the node is not on the internal node list? """ if address in self.nodes_internal: LOGGER.info('Renaming node {}'.format(address)) From e2c55b75fc9af2e0b92b676fd37944322db725ba Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Thu, 17 Nov 2022 11:49:52 -0800 Subject: [PATCH 12/15] Version 3.0.50 --- udi_interface/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/udi_interface/__init__.py b/udi_interface/__init__.py index c757d64..ff8b2bd 100755 --- a/udi_interface/__init__.py +++ b/udi_interface/__init__.py @@ -7,7 +7,7 @@ from .custom import Custom from .isy import ISY -__version__ = '3.0.49' +__version__ = '3.0.50' __description__ = 'UDI Python Interface for Polyglot version 3' __url__ = 'https://github.com/UniversalDevicesInc/udi_python_interface' __author__ = 'Universal Devices Inc.' From 3aae0e2310203b3fe09c4e9f16123058d65c724a Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Mon, 21 Nov 2022 15:53:21 -0800 Subject: [PATCH 13/15] Fix the name of SSL certificates. --- udi_interface/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/udi_interface/interface.py b/udi_interface/interface.py index 2eab139..6a5fee8 100644 --- a/udi_interface/interface.py +++ b/udi_interface/interface.py @@ -489,7 +489,7 @@ def _startMqtt(self): LOGGER.info('Connecting to MQTT... {}:{}'.format( self._server, self._port)) - self.username = self.id + self.username = self.id.replace(':', '') # Load the client SSL certificate. What if this fails? if self.pg3init['secure'] == 1: @@ -502,9 +502,9 @@ def _startMqtt(self): if exists(cert) and exists(key): LOGGER.info('Using SSL certs: {} {}'.format(cert, key)) self.sslContext.load_cert_chain(cert, key) - self.username = self.id.replace(':', '') self.using_mosquitto = True else: + self.username = self.id self.using_mosquitto = False self._mqttc.tls_set_context(self.sslContext) From f604650f2f0dae6b969b221e72415bcd084aded3 Mon Sep 17 00:00:00 2001 From: Bob Paauwe Date: Mon, 21 Nov 2022 15:54:03 -0800 Subject: [PATCH 14/15] Version 3.0.51 --- udi_interface/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/udi_interface/__init__.py b/udi_interface/__init__.py index ff8b2bd..d5ea806 100755 --- a/udi_interface/__init__.py +++ b/udi_interface/__init__.py @@ -7,7 +7,7 @@ from .custom import Custom from .isy import ISY -__version__ = '3.0.50' +__version__ = '3.0.51' __description__ = 'UDI Python Interface for Polyglot version 3' __url__ = 'https://github.com/UniversalDevicesInc/udi_python_interface' __author__ = 'Universal Devices Inc.' From 9f59373a638d14a58db3c9d12a31a5585abd3f8f Mon Sep 17 00:00:00 2001 From: Dmitri Kostukov Date: Sat, 28 Jan 2023 14:22:41 -0500 Subject: [PATCH 15/15] Fix formatting and add uom comment --- API.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/API.md b/API.md index f5ba759..02da31e 100644 --- a/API.md +++ b/API.md @@ -237,7 +237,7 @@ __addNode(node, conn_status = None, rename = False)__ Adds a new node to Polyglo node using your custom class, which you then pass to addNode. Return value is the node passed in. -If conn\_status is set to a driver string, this node and the driver specified will be used by PG3 to represent the connection status (0 = disconnected, 1 = connected, 2 = failed). By default, conn\_status is None. When the node server connects via MQTT, PG3 will send a value of 1 to the ISY/IoP for that node/driver. When PG3 stops the node server it will send a value of 0 to the ISY/IoP for that node/driver. If the MQTT connection drops for any other reason, PG3 will send a value of 2 to the ISY/IoP for that node/driver. +If conn\_status is set to a driver string, this node and the driver specified will be used by PG3 to represent the connection status (0 = disconnected, 1 = connected, 2 = failed). By default, conn\_status is None. When the node server connects via MQTT, PG3 will send a value of 1 to the ISY/IoP for that node/driver. When PG3 stops the node server it will send a value of 0 to the ISY/IoP for that node/driver. If the MQTT connection drops for any other reason, PG3 will send a value of 2 to the ISY/IoP for that node/driver. Node server should use uom 25 to display status correctly. If rename is set to True, addNode will attempt to rename the node on the ISY and in the PG3 database to match the name specified in node. When rename is set to False, the name of the node on the ISY and in PG3's database record are unchanged. @@ -251,7 +251,6 @@ Notes: a list of nodes available in the onConfig handler. However, you should still call __addNode()__ for each node to replace the generic node object with your custom node class object. -``` __getConfig()__ Returns a copy of the last config received.