From a35f9ffc10319349eaece999c74671ea594b69ee Mon Sep 17 00:00:00 2001 From: bwduncan Date: Fri, 3 Jan 2020 14:38:19 +0000 Subject: [PATCH 01/78] Update emonhub.service - Set the log dir mode with mkdir. - Use Type=exec, it's more resilient to some failures (i.e. absent binary). - Use a better name - Use the default dependencies which seemed to be duplicated here anyway. Remove nonexistent syslog.target - Remove PIDFile. It's unnecessary and systemd doesn't use it except for units of Type=forking - Use a slower RestartSec (the default of 100ms is probably inappropriate) --- service/emonhub.service | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/service/emonhub.service b/service/emonhub.service index 216f391b..d622763e 100644 --- a/service/emonhub.service +++ b/service/emonhub.service @@ -1,21 +1,19 @@ [Unit] -Description=emonHub service description -DefaultDependencies=no -Before=shutdown.target -Conflicts=shutdown.target -Requires=local-fs.target -After=sysinit.target syslog.target local-fs.target +Description=emonHub data multiplexer +# The config file lives in /home/pi/data (symlinked from /etc/emonhub) +# The log file lives in the tmpfs at /var/log +Requires=var-log.mount home-pi-data.mount +After=var-log.mount home-pi-data.mount network.target [Service] User=emonhub -PIDFile=/var/run/emonhub.pid ExecStart=/usr/local/bin/emonhub/emonhub.py --config-file=/etc/emonhub/emonhub.conf --logfile=/var/log/emonhub/emonhub.log PermissionsStartOnly=true -ExecStartPre=/bin/mkdir -p /var/log/emonhub/ +ExecStartPre=/bin/mkdir -p -m 0775 /var/log/emonhub/ ExecStartPre=/bin/chgrp -R emonhub /var/log/emonhub/ -ExecStartPre=/bin/chmod 775 /var/log/emonhub/ -Type=simple +Type=exec Restart=always +RestartSec=5 [Install] WantedBy=multi-user.target From 51a3c1879c16b43caf9084d005bb62baf91b6907 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:23:09 +0000 Subject: [PATCH 02/78] Make logging lazy. Calls to logging functions should use %s/%d/etc and pass parameters instead of interpolating first. The logging module can avoid doing that work if the log message would be filtered anyway. --- src/emonhub.py | 28 ++++---- src/emonhub_buffer.py | 4 +- src/emonhub_interfacer.py | 67 ++++++++++--------- src/emonhub_setup.py | 12 ++-- src/interfacers/EmonHubBMWInterfacer.py | 14 ++-- .../EmonHubEmoncmsHTTPInterfacer.py | 24 +++---- src/interfacers/EmonHubGraphiteInterfacer.py | 12 ++-- src/interfacers/EmonHubJeeInterfacer.py | 30 ++++----- src/interfacers/EmonHubMqttInterfacer.py | 24 +++---- src/interfacers/EmonHubPacketGenInterfacer.py | 20 +++--- src/interfacers/EmonHubSMASolarInterfacer.py | 24 ++++--- src/interfacers/EmonHubSerialInterfacer.py | 4 +- src/interfacers/EmonHubSocketInterfacer.py | 18 ++--- src/interfacers/EmonHubTemplateInterfacer.py | 6 +- src/interfacers/EmonHubTx3eInterfacer.py | 4 +- src/interfacers/EmonHubVEDirectInterfacer.py | 8 +-- src/interfacers/EmonModbusTcpInterfacer.py | 37 +++++----- .../tmp/EmonHubSmilicsInterfacer.py | 6 +- 18 files changed, 175 insertions(+), 167 deletions(-) diff --git a/src/emonhub.py b/src/emonhub.py index d7112bd9..1f474f97 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -68,7 +68,7 @@ def __init__(self, setup): # Initialize logging self._log = logging.getLogger("EmonHub") self._set_logging_level('INFO', False) - self._log.info("EmonHub %s" % self.__version__) + self._log.info("EmonHub %s", self.__version__) self._log.info("Opening hub...") # Initialize Interfacers @@ -115,7 +115,7 @@ def run(self): # Post to each subscriber interface for sub_interfacer in self._interfacers.values(): - # For each subsciber channel + # For each subscriber channel for sub_channel in sub_interfacer._settings['subchannels']: # If channel names match if sub_channel == pub_channel: @@ -128,14 +128,14 @@ def run(self): # ->avoid modification of iterable within loop for name in kill_list: - self._log.warning(name + " thread is dead.") + self._log.warning("%s thread is dead.", name) # The following should trigger a restart ... unless the # interfacer is also removed from the settings table. del self._interfacers[name] # Trigger restart by calling update settings - self._log.warning("Attempting to restart thread " + name + " (thread has been restarted " + str(restart_count[name]) + " times...") + self._log.warning("Attempting to restart thread %s (thread has been restarted %d times...)", name, restart_count[name]) restart_count[name] += 1 self._update_settings(self._setup.settings) @@ -184,14 +184,14 @@ def _update_settings(self, settings): settings['interfacers'][name]['runtimesettings'] except Exception as e: # If interfacer's settings are incomplete, continue without updating - self._log.error("Unable to update '" + name + "' configuration: " + str(e)) + self._log.error("Unable to update '%s' configuration: %s", name, e) continue else: # check init_settings against the file copy, if they are the same move on to the next if self._interfacers[name].init_settings == settings['interfacers'][name]['init_settings']: continue # Delete interfacers if setting changed or name is unlisted or Type is missing - self._log.info("Deleting interfacer '%s' ", name) + self._log.info("Deleting interfacer '%s'", name) self._interfacers[name].stop = True del self._interfacers[name] @@ -201,19 +201,19 @@ def _update_settings(self, settings): try: if 'Type' not in I: continue - self._log.info("Creating " + I['Type'] + " '%s' ", name) + self._log.info("Creating %s '%s'", I['Type'], name) # This gets the class from the 'Type' string - interfacer = getattr(ehi, I['Type'])(name,**I['init_settings']) + interfacer = getattr(ehi, I['Type'])(name, **I['init_settings']) interfacer.set(**I['runtimesettings']) interfacer.init_settings = I['init_settings'] interfacer.start() except ehi.EmonHubInterfacerInitError as e: # If interfacer can't be created, log error and skip to next - self._log.error("Failed to create '" + name + "' interfacer: " + str(e)) + self._log.error("Failed to create '%s' interfacer: %s", name, e) continue except Exception as e: # If interfacer can't be created, log error and skip to next - self._log.error("Unable to create '" + name + "' interfacer: " + str(e)) + self._log.error("Unable to create '%s' interfacer: %s", name, e) continue else: self._interfacers[name] = interfacer @@ -240,17 +240,17 @@ def _set_logging_level(self, level='WARNING', log=True): try: loglevel = getattr(logging, level) except AttributeError: - self._log.error('Logging level %s invalid' % level) + self._log.error('Logging level %s invalid', level) return False except Exception as e: - self._log.error('Logging level %s ' % str(e)) + self._log.error('Logging level %s', e) return False # Change level if different from current level if loglevel != self._log.getEffectiveLevel(): self._log.setLevel(level) if log: - self._log.info('Logging level set to %s' % level) + self._log.info('Logging level set to %s', level) if __name__ == "__main__": @@ -275,7 +275,7 @@ def _set_logging_level(self, level='WARNING', log=True): # Display version number and exit if args.version: - print('emonHub %s' % EmonHub.__version__) + print('emonHub', EmonHub.__version__) sys.exit() # Logging configuration diff --git a/src/emonhub_buffer.py b/src/emonhub_buffer.py index c1d28dfc..22c62f63 100644 --- a/src/emonhub_buffer.py +++ b/src/emonhub_buffer.py @@ -66,8 +66,8 @@ def discardOldestItems(self): def discardOldestItemsIfFull(self): if self.isFull(): self._log.warning( - "In-memory buffer (%s) reached limit of %d items, deleting oldest" - % (self._bufferName, self._maximumEntriesInBuffer)) + "In-memory buffer (%s) reached limit of %d items, deleting oldest", + self._bufferName, self._maximumEntriesInBuffer) self.discardOldestItems() def storeItem(self, data): diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index ee4bc8cc..67c2f43b 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -101,7 +101,7 @@ def run(self): rxc = self._process_rx(rxc) if rxc: for channel in self._settings["pubchannels"]: - self._log.debug(str(rxc.uri) + " Sent to channel(start)' : " + str(channel)) + self._log.debug("%d Sent to channel(start)' : %d", rxc.uri, channel) # Initialise channel if needed if channel not in self._pub_channels: @@ -110,7 +110,7 @@ def run(self): # Add cargo item to channel self._pub_channels[channel].append(rxc) - self._log.debug(str(rxc.uri) + " Sent to channel(end)' : " + str(channel)) + self._log.debug("%d Sent to channel(end)' : %d", rxc.uri, channel) # Subscriber channels for channel in self._settings["subchannels"]: @@ -144,10 +144,10 @@ def add(self, cargo): if cargo.rssi: f.append(cargo.rssi) - # self._log.debug(str(cargo.uri) + " adding frame to buffer => "+ str(f)) + # self._log.debug("%d adding frame to buffer => %s", rxc.uri, str) except: - self._log.warning("Failed to create emonCMS frame " + str(f)) + self._log.warning("Failed to create emonCMS frame %s", f) # self._log.debug(str(carg.ref) + " added to buffer =>" # + " time: " + str(carg.timestamp) @@ -203,7 +203,7 @@ def flush(self): # Buffer management # If data buffer not empty, send a set of values if self.buffer.hasItems(): - self._log.debug("Buffer size: " + str(self.buffer.size())) + self._log.debug("Buffer size: %d", self.buffer.size()) max_items = int(self._settings['batchsize']) if max_items > self._item_limit: @@ -253,7 +253,7 @@ def _send_post(self, post_url, post_body=None): reply = requests.get(post_url) reply.raise_for_status() # Raise an exception if status code isn't 200 except requests.exceptions.RequestException as ex: - self._log.warning(self.name + " couldn't send to server: " + str(ex)) + self._log.warning("%s couldn't send to server: %s", self.name, ex) return reply.text def _process_rx(self, cargo): @@ -271,7 +271,7 @@ def _process_rx(self, cargo): """ # Log data - self._log.debug(str(cargo.uri) + " NEW FRAME : " + str(cargo.rawdata)) + self._log.debug("%d NEW FRAME : %s", cargo.uri, cargo.rawdata) rxc = cargo decoded = [] @@ -280,20 +280,23 @@ def _process_rx(self, cargo): # Discard if data is non-existent if len(rxc.realdata) < 1: - self._log.warning(str(cargo.uri) + " Discarded RX frame 'string too short' : " + str(rxc.realdata)) + self._log.warning("%d Discarded RX frame 'string too short' : %s", + cargo.uri, rxc.realdata) return False # Discard if anything non-numerical found try: [float(val) for val in rxc.realdata] except Exception: - self._log.warning(str(cargo.uri) + " Discarded RX frame 'non-numerical content' : " + str(rxc.realdata)) + self._log.warning("%d Discarded RX frame 'non-numerical content' : %s", + cargo.uri, rxc.realdata) return False # Discard if first value is not a valid node id # n = float(rxc.realdata[0]) # if n % 1 != 0 or n < 0 or n > 31: - # self._log.warning(str(cargo.uri) + " Discarded RX frame 'node id outside scope' : " + str(rxc.realdata)) + # self._log.warning("%d Discarded RX frame 'node id outside scope' : %s", + # cargo.uri, rxc.realdata) # return False # Data whitening uses for ensuring rfm sync @@ -314,8 +317,8 @@ def _process_rx(self, cargo): datasizes.append(ehc.check_datacode(str(code))) # Discard the frame & return 'False' if it doesn't match the summed datasizes if len(rxc.realdata) != sum(datasizes): - self._log.warning(str(rxc.uri) + " RX data length: " + str(len(rxc.realdata)) + - " is not valid for datacodes " + str(datacodes)) + self._log.warning("%d RX data length: %d is not valid for datacodes %s", + rxc.uri, len(rxc.realdata), datacodes) return False else: # Determine the expected number of values to be decoded @@ -342,8 +345,8 @@ def _process_rx(self, cargo): decoded.append(val) # Discard frame if total size is not an exact multiple of the specified datacode size. elif len(rxc.realdata) % ehc.check_datacode(datacode) != 0: - self._log.warning(str(rxc.uri) + " RX data length: " + str(len(rxc.realdata)) + - " is not valid for datacode " + str(datacode)) + self._log.warning("%d RX data length: %d is not valid for datacode %s", + rxc.uri, len(rxc.realdata), datacode) return False else: # Determine the number of values in the frame of the specified code & size @@ -361,8 +364,8 @@ def _process_rx(self, cargo): size = int(ehc.check_datacode(dc)) try: value = ehc.decode(dc, [int(v) for v in rxc.realdata[bytepos:bytepos+size]]) - except: - self._log.warning(str(rxc.uri) + " Unable to decode as values incorrect for datacode(s)") + except Exception: + self._log.warning("%d Unable to decode as values incorrect for datacode(s)", rxc.uri) return False bytepos += size decoded.append(value) @@ -373,7 +376,7 @@ def _process_rx(self, cargo): # === Removed check for scales length so that failure mode is more gracious === # Discard the frame & return 'False' if it doesn't match the number of scales # if len(decoded) != len(scales): - # self._log.warning(str(rxc.uri) + " Scales " + str(scales) + " for RX data : " + str(rxc.realdata) + " not suitable " ) + # self._log.warning("%d Scales %s for RX data : %s not suitable", rxc.uri, scales, rxc.realdata) # return False # else: # Determine the expected number of values to be decoded @@ -422,13 +425,13 @@ def _process_rx(self, cargo): if not rxc: return False - self._log.debug(str(rxc.uri) + " Timestamp : " + str(rxc.timestamp)) - self._log.debug(str(rxc.uri) + " From Node : " + str(rxc.nodeid)) + self._log.debug("%d Timestamp : %f", rxc.uri, rxc.timestamp) + self._log.debug("%d From Node : %d", rxc.uri, rxc.nodeid) if rxc.target: - self._log.debug(str(rxc.uri) + " To Target : " + str(rxc.target)) - self._log.debug(str(rxc.uri) + " Values : " + str(rxc.realdata)) + self._log.debug("%d To Target : %d", rxc.uri, rxc.target) + self._log.debug("%d Values : %s", rxc.uri, rxc.realdata) if rxc.rssi: - self._log.debug(str(rxc.uri) + " RSSI : " + str(rxc.rssi)) + self._log.debug("%d RSSI : %d", rxc.uri, rxc.rssi) return rxc @@ -468,8 +471,8 @@ def _process_tx(self, cargo): scales = ehc.nodelist[dest]['tx']['scales'] # Discard the frame & return 'False' if it doesn't match the number of scales if len(txc.realdata) != len(scales): - self._log.warning(str(txc.uri) + " Scales " + str(scales) + " for RX data : " + str(txc.realdata) + - " not suitable ") + self._log.warning("%d Scales %s for RX data : %s not suitable ", + txc.uri, scales, txc.realdata) return False else: # Determine the expected number of values to be decoded @@ -502,7 +505,7 @@ def _process_tx(self, cargo): val = int(val) scaled.append(val) - # self._log.info("Scaled: " + json.dumps(scaled)) + # self._log.info("Scaled: %s", json.dumps(scaled)) # check if node is listed and has individual datacodes for each value if dest in ehc.nodelist and 'tx' in ehc.nodelist[dest] and 'datacodes' in ehc.nodelist[dest]['tx']: @@ -516,8 +519,8 @@ def _process_tx(self, cargo): datasizes.append(ehc.check_datacode(str(code))) # Discard the frame & return 'False' if it doesn't match the summed datasizes if len(scaled) != len(datasizes): - self._log.warning(str(txc.uri) + " TX datacodes: " + str(datacodes) + - " are not valid for values " + str(scaled)) + self._log.warning("%d TX datacodes: %s are not valid for values %s", + txc.uri, datacodes, scaled) return False else: # Determine the expected number of values to be decoded @@ -550,8 +553,8 @@ def _process_tx(self, cargo): encoded.append(val) # Discard frame if total size is not an exact multiple of the specified datacode size. # elif len(data) * ehc.check_datacode(datacode) != 0: - # self._log.warning(str(uri) + " TX data length: " + str(len(data)) + - # " is not valid for datacode " + str(datacode)) + # self._log.warning("%d TX data length: %d is not valid for datacode %s", + # txc.uri, len(data), datacode) # return False else: # Determine the number of values in the frame of the specified code & size @@ -568,7 +571,7 @@ def _process_tx(self, cargo): for b in ehc.encode(dc, int(scaled[i])): encoded.append(b) - # self._log.info("Encoded: "+json.dumps(encoded)) + # self._log.info("Encoded: %s", json.dumps(encoded)) txc.encoded.update({self.getName():encoded}) return txc @@ -614,10 +617,10 @@ def set(self, **kwargs): elif key == 'subchannels': pass else: - self._log.warning("In interfacer set '%s' is not a valid setting for %s: %s" % (str(setting), self.name, key)) + self._log.warning("In interfacer set '%s' is not a valid setting for %s: %s", setting, self.name, key) continue self._settings[key] = setting - self._log.debug("Setting " + self.name + " " + key + ": " + str(setting)) + self._log.debug("Setting %s %s: %s", self.name, key, setting) """class EmonHubInterfacerInitError diff --git a/src/emonhub_setup.py b/src/emonhub_setup.py index d956dd4e..7f616b7e 100644 --- a/src/emonhub_setup.py +++ b/src/emonhub_setup.py @@ -103,7 +103,7 @@ def __init__(self, filename): raise EmonHubSetupInitError(e) except SyntaxError as e: raise EmonHubSetupInitError( - 'Error parsing config file \"%s\": ' % filename + str(e)) + 'Error parsing config file "%s": ' % filename + str(e)) except KeyError as e: raise EmonHubSetupInitError( 'Configuration file error - section: ' + str(e)) @@ -134,18 +134,18 @@ def check_settings(self): self.settings = json.loads(f.read()) except IOError as e: - self._log.warning('Could not get settings: ' + str(e) + self.retry_msg) + self._log.warning('Could not get settings: %s %s', e, self.retry_msg) self._settings_update_timestamp = now + self._retry_time_interval return except SyntaxError as e: self._log.warning('Could not get settings: ' + - 'Error parsing config file: ' + str(e) + self.retry_msg) + 'Error parsing config file: %s %s', e, self.retry_msg) self._settings_update_timestamp = now + self._retry_time_interval return except Exception: import traceback - self._log.warning("Couldn't get settings, Exception: " + - traceback.format_exc() + self.retry_msg) + self._log.warning("Couldn't get settings, Exception: %s %s", + traceback.format_exc(), self.retry_msg) self._settings_update_timestamp = now + self._retry_time_interval return @@ -155,7 +155,7 @@ def check_settings(self): self.settings['hub'] self.settings['interfacers'] except KeyError as e: - self._log.warning("Configuration file missing section: " + str(e)) + self._log.warning("Configuration file missing section: %s", e) else: return True diff --git a/src/interfacers/EmonHubBMWInterfacer.py b/src/interfacers/EmonHubBMWInterfacer.py index 519ff618..f4285753 100644 --- a/src/interfacers/EmonHubBMWInterfacer.py +++ b/src/interfacers/EmonHubBMWInterfacer.py @@ -86,7 +86,7 @@ def obtainCredentials(self): for word in parts[1:]: values = word.split("=") d[values[0]] = values[1] - #self._log.debug("word=" + word) + #self._log.debug("word=%s", word) access_token = parts[0].split("#") for word in access_token[1:]: @@ -101,7 +101,7 @@ def obtainCredentials(self): self.saveCredentials() else: - self._log.error("locationHeader=" + location) + self._log.error("locationHeader=%s", location) self._log.error("Location URL is different from expected") else: @@ -122,7 +122,7 @@ def saveCredentials(self): with open(self._TempCredentialFile, "w") as credentials_file: json.dump(credentials, credentials_file, indent=4) - self._log.info("Cached credentials to " + self._TempCredentialFile) + self._log.info("Cached credentials to %s", self._TempCredentialFile) def close(self): """Close""" @@ -165,7 +165,7 @@ def call(self, path, post_data=None): headers = {"Authorization": "Bearer " + self._AccessToken, "User-Agent": self.USER_AGENT} - #self._log.debug("headers=" + str(headers)) + #self._log.debug("headers=%s", headers) if post_data is None: r = requests.get(self.ROOT_URL + path, headers=headers) @@ -210,7 +210,7 @@ def read(self): # u'hasSunRoof': False, u'steering': u'RIGHT', u'licensePlate': u'AA11ABC', # u'dcOnly': True, u'driveTrain': u'BEV_REX', u'doorCount': 4, u'maxFuel': u'8.5', u'hasRex': True}] - self._log.debug("modelName='" + myvehicle["modelName"] + "' VIN='" + myvehicle["vin"] + "'") + self._log.debug("modelName='%s' VIN='%s'", myvehicle["modelName"], myvehicle["vin"]) vin = myvehicle["vin"] @@ -218,7 +218,7 @@ def read(self): #Report efficiency of driving (not currently used by this program) #efficiency = self.call('/api/vehicle/efficiency/v1/' + vin) - #self._log.debug("efficiency=" + str(efficiency)) + #self._log.debug("efficiency=%s", efficiency) attributesMap = dynamic["attributesMap"] @@ -241,7 +241,7 @@ def read(self): else: values.append(0) - self._log.debug("chargingSystemStatus=" + self._chargingSystemStatus) + self._log.debug("chargingSystemStatus=%s", self._chargingSystemStatus) #u'unitOfElectricConsumption = u'mls/kWh' #u'unitOfCombustionConsumption = u'mpg' diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index 9a14e6c0..1dcae3a6 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -59,7 +59,7 @@ def _process_post(self, databuffer): post_body = "data=" + data_string + "&sentat=" + str(sentat) # logged before apikey added for security - self._log.info("sending: " + post_url + "E-M-O-N-C-M-S-A-P-I-K-E-Y&" + post_body) + self._log.info("sending: %sE-M-O-N-C-M-S-A-P-I-K-E-Y&%s", post_url, post_body) # Add apikey to post_url post_url = post_url + self._settings['apikey'] @@ -70,10 +70,10 @@ def _process_post(self, databuffer): reply = self._send_post(post_url, {'data': data_string, 'sentat': str(sentat)}) if reply == 'ok': - self._log.debug("acknowledged receipt with '" + reply + "' from " + self._settings['url']) + self._log.debug("acknowledged receipt with '%s' from %s", reply, self._settings['url') return True else: - self._log.warning("send failure: wanted 'ok' but got '" + reply + "'") + self._log.warning("send failure: wanted 'ok' but got '%s'", reply) return False def sendstatus(self): @@ -109,29 +109,29 @@ def set(self, **kwargs): continue elif key == 'apikey': if setting.lower().startswith('xxxx'): # FIXME compare whole string to 'x'*32? - self._log.warning("Setting " + self.name + " apikey: obscured") + self._log.warning("Setting %s apikey: obscured", self.name) elif len(setting) == 32: - self._log.info("Setting " + self.name + " apikey: set") + self._log.info("Setting %s apikey: set", self.name) elif setting == "": - self._log.info("Setting " + self.name + " apikey: null") + self._log.info("Setting %s apikey: null", self.name) else: - self._log.warning("Setting " + self.name + " apikey: invalid format") + self._log.warning("Setting %s apikey: invalid format", self.name) continue self._settings[key] = setting # Next line will log apikey if uncommented (privacy ?) - #self._log.debug(self.name + " apikey: " + str(setting)) + #self._log.debug("%s apikey: %s", self.name, setting) continue elif key == 'url' and setting.startswith("http"): - self._log.info("Setting " + self.name + " url: " + setting) + self._log.info("Setting %s url: %s", self.name, setting) self._settings[key] = setting continue elif key == 'senddata': - self._log.info("Setting " + self.name + " senddata: " + setting) + self._log.info("Setting %s senddata: %s", self.name, setting) self._settings[key] = setting continue elif key == 'sendstatus': - self._log.info("Setting " + self.name + " sendstatus: " + setting) + self._log.info("Setting %s sendstatus: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (setting, self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) diff --git a/src/interfacers/EmonHubGraphiteInterfacer.py b/src/interfacers/EmonHubGraphiteInterfacer.py index 8a85bbd2..c882bfe1 100644 --- a/src/interfacers/EmonHubGraphiteInterfacer.py +++ b/src/interfacers/EmonHubGraphiteInterfacer.py @@ -91,9 +91,9 @@ def _send_metrics(self, metrics=[]): host = str(self._settings['graphite_host']).strip('[\'\']') port = int(str(self._settings['graphite_port']).strip('[\'\']')) - self._log.debug("Graphite target: {}:{}".format(host, port)) + self._log.debug("Graphite target: %s:%s", host, port) message = '\n'.join(metrics) + '\n' - self._log.debug("Sending metrics:\n" + message) + self._log.debug("Sending metrics: %s", message) try: sock = socket.socket() @@ -125,17 +125,17 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'graphite_host': - self._log.info("Setting " + self.name + " graphite_host: " + setting) + self._log.info("Setting %s graphite_host: %s", self.name, setting) self._settings[key] = setting continue elif key == 'graphite_port': - self._log.info("Setting " + self.name + " graphite_port: " + setting) + self._log.info("Setting %s graphite_port: %s", self.name, setting) self._settings[key] = setting continue elif key == 'prefix': - self._log.info("Setting " + self.name + " prefix: " + setting) + self._log.info("Setting %s prefix: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (setting, self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) """ diff --git a/src/interfacers/EmonHubJeeInterfacer.py b/src/interfacers/EmonHubJeeInterfacer.py index b3911dc7..f664845f 100644 --- a/src/interfacers/EmonHubJeeInterfacer.py +++ b/src/interfacers/EmonHubJeeInterfacer.py @@ -35,12 +35,12 @@ def __init__(self, name, com_port='/dev/ttyAMA0', com_baud=38400): # Split the returned "info" string into firmware version & current settings self.info[0] = info.strip().split(' ')[0] self.info[1] = info.replace(str(self.info[0]), "") - self._log.info(self.name + " device firmware version: " + self.info[0]) - self._log.info(self.name + " device current settings: " + str(self.info[1])) + self._log.info("%s device firmware version: %s", self.name, self.info[0]) + self._log.info("%s device current settings: %s", self.name, self.info[1]) else: # since "v" command only v11> recommend firmware update ? - #self._log.info(self.name + " device firmware is pre-version RFM12demo.11") - self._log.info(self.name + " device firmware version & configuration: not available") + #self._log.info("%s device firmware is pre-version RFM12demo.11", self.name) + self._log.info("%s device firmware version & configuration: not available", self.name) else: self._log.warning("Device communication error - check settings") self._rx_buf = "" @@ -97,25 +97,25 @@ def read(self): return if f[0] == '\x01': - #self._log.debug("Ignoring frame consisting of SOH character" + str(f)) + #self._log.debug("Ignoring frame consisting of SOH character %s", f) return if f[0] == '?': - self._log.debug("Discarding RX frame 'unreliable content'" + str(f)) + self._log.debug("Discarding RX frame 'unreliable content': %s", f) return False # Discard information messages if '>' in f: if '->' in f: - self._log.debug("confirmed sent packet size: " + str(f)) + self._log.debug("confirmed sent packet size: %s", f) return - self._log.debug("acknowledged command: " + str(f)) + self._log.debug("acknowledged command: %s", f) return # Record current device settings if all(i in f for i in (" i", " g", " @ ", " MHz")): self.info[1] = f - self._log.debug("device settings updated: " + str(self.info[1])) + self._log.debug("device settings updated: %s", self.info[1]) return # Save raw packet to new cargo object @@ -134,7 +134,7 @@ def read(self): try: c.rssi = int(r) except ValueError: - self._log.warning("Packet discarded as the RSSI format is invalid: " + str(f)) + self._log.warning("Packet discarded as the RSSI format is invalid: %s", f) return f = f[:-1] @@ -191,10 +191,10 @@ def set(self, **kwargs): command = '2p' else: - self._log.warning("In interfacer set '%s' is not a valid setting for %s: %s" % (str(setting), self.name, key)) + self._log.warning("In interfacer set '%s' is not a valid setting for %s: %s", setting, self.name, key) continue self._settings[key] = setting - self._log.info("Setting " + self.name + " %s: %s" % (key, setting) + " (" + command + ")") + self._log.info("Setting %s %s: %s (%s)", self.name, key, setting, command) self._ser.write(command.encode()) # Wait a sec between two settings time.sleep(1) @@ -226,7 +226,7 @@ def _process_post(self, databuffer): """ for frame in databuffer: - self._log.debug("node = " + str(frame[1]) + " node_data = " + json.dumps(frame)) + self._log.debug("node = %s node_data = %s", frame[1], json.dumps(frame)) self.send(frame) return True @@ -242,11 +242,11 @@ def send(self, cargo): payload = "" for value in data: if not 0 < int(value) < 255: - self._log.warning(self.name + " discarding Tx packet: values out of scope") + self._log.warning("%s discarding Tx packet: values out of scope", self.name) return payload += str(int(value)) + "," payload += cmd - self._log.debug(str(f.uri) + " sent TX packet: " + payload) + self._log.debug("%d sent TX packet: %s", f.uri, payload) self._ser.write(payload.encode()) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index 0a9bc966..f0b75b90 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -119,7 +119,7 @@ def _process_post(self, databuffer): topic = self._settings["nodevar_format_basetopic"] + nodename + "/" + inputname payload = str(value) - self._log.debug("Publishing: " + topic + " " + payload) + self._log.debug("Publishing: %s %s", topic, payload) result = self._mqttc.publish(topic, payload=payload, qos=2, retain=False) if result[0] == 4: @@ -131,7 +131,7 @@ def _process_post(self, databuffer): topic = self._settings["nodevar_format_basetopic"] + nodename + "/rssi" payload = str(frame['rssi']) - self._log.debug("Publishing: " + topic + " " + payload) + self._log.debug("Publishing: %s %s", topic, payload) result = self._mqttc.publish(topic, payload=payload, qos=2, retain=False) if result[0] == 4: @@ -149,7 +149,7 @@ def _process_post(self, databuffer): if 'rssi' in frame: payload = payload + "," + str(frame['rssi']) - self._log.info("Publishing: " + topic + " " + payload) + self._log.info("Publishing: %s %s", topic, payload) result = self._mqttc.publish(topic, payload=payload, qos=2, retain=False) if result[0] == 4: @@ -190,12 +190,12 @@ def on_connect(self, client, userdata, flags, rc): if rc: self._log.warning(connack_string[rc]) else: - self._log.info("connection status: " + connack_string[rc]) + self._log.info("connection status: %s", connack_string[rc]) self._connected = True # Subscribe to MQTT topics self._mqttc.subscribe(str(self._settings["node_format_basetopic"]) + "tx/#") - self._log.debug("CONACK => Return code: " + str(rc)) + self._log.debug("CONACK => Return code: %d", rc) def on_disconnect(self, client, userdata, rc): if rc != 0: @@ -213,7 +213,7 @@ def on_message(self, client, userdata, msg): payload = msg.payload realdata = payload.split(",") - self._log.debug("Nodeid: " + str(nodeid) + " values: " + msg.payload) + self._log.debug("Nodeid: %s values: %s", nodeid, msg.payload) rxc = Cargo.new_cargo(realdata=realdata) rxc.nodeid = nodeid @@ -230,7 +230,7 @@ def on_message(self, client, userdata, msg): # Add cargo item to channel self._pub_channels[channel].append(rxc) - self._log.debug(str(rxc.uri) + " Sent to channel' : " + str(channel)) + self._log.debug("%d Sent to channel' : %s", rxc.uri, channel) def set(self, **kwargs): """ @@ -250,20 +250,20 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'node_format_enable': - self._log.info("Setting " + self.name + " node_format_enable: " + setting) + self._log.info("Setting %s node_format_enable: %s", self.name, setting) self._settings[key] = setting continue elif key == 'node_format_basetopic': - self._log.info("Setting " + self.name + " node_format_basetopic: " + setting) + self._log.info("Setting %s node_format_basetopic: %s", self.name, setting) self._settings[key] = setting continue elif key == 'nodevar_format_enable': - self._log.info("Setting " + self.name + " nodevar_format_enable: " + setting) + self._log.info("Setting %s nodevar_format_enable: %s", self.name, setting) self._settings[key] = setting continue elif key == 'nodevar_format_basetopic': - self._log.info("Setting " + self.name + " nodevar_format_basetopic: " + setting) + self._log.info("Setting %s nodevar_format_basetopic: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (setting, self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) diff --git a/src/interfacers/EmonHubPacketGenInterfacer.py b/src/interfacers/EmonHubPacketGenInterfacer.py index 31a35225..36b6310a 100644 --- a/src/interfacers/EmonHubPacketGenInterfacer.py +++ b/src/interfacers/EmonHubPacketGenInterfacer.py @@ -38,12 +38,12 @@ def read(self): "/emoncms/packetgen/getpacket.json?apikey=" # logged without apikey added for security - self._log.info("requesting packet: " + req + "E-M-O-N-C-M-S-A-P-I-K-E-Y") + self._log.info("requesting packet: %sE-M-O-N-C-M-S-A-P-I-K-E-Y", req) try: packet = requests.get(req + self._settings['apikey']).json() except (ValueError, requests.exceptions.RequestException) as ex: - self._log.warning("no packet returned: " + str(ex)) + self._log.warning("no packet returned: %s", ex) return raw = "" @@ -104,7 +104,7 @@ def action(self): if self._control_interval != i: self._control_interval = i - self._log.info("request interval set to: " + str(i) + " seconds") + self._log.info("request interval set to: %d seconds", i) self._interval_timestamp = t @@ -123,24 +123,24 @@ def set(self, **kwargs): continue elif key == 'apikey': if setting.lower().startswith('xxxx'): # FIXME compare whole string to 'x'*32? - self._log.warning("Setting " + self.name + " apikey: obscured") + self._log.warning("Setting %s apikey: obscured", self.name) elif len(setting) == 32: - self._log.info("Setting " + self.name + " apikey: set") + self._log.info("Setting %s apikey: set", self.name) elif setting == "": - self._log.info("Setting " + self.name + " apikey: null") + self._log.info("Setting %s apikey: null", self.name) else: - self._log.warning("Setting " + self.name + " apikey: invalid format") + self._log.warning("Setting %s apikey: invalid format", self.name) continue self._settings[key] = setting # Next line will log apikey if uncommented (privacy ?) - #self._log.debug(self.name + " apikey: " + str(setting)) + #self._log.debug("%s apikey: %s", self.name, setting) continue elif key == 'url' and setting.startswith("http"): - self._log.info("Setting " + self.name + " url: " + setting) + self._log.info("Setting %s url: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) # include kwargs from parent super().set(**kwargs) diff --git a/src/interfacers/EmonHubSMASolarInterfacer.py b/src/interfacers/EmonHubSMASolarInterfacer.py index 3087e075..7a7f7943 100644 --- a/src/interfacers/EmonHubSMASolarInterfacer.py +++ b/src/interfacers/EmonHubSMASolarInterfacer.py @@ -49,13 +49,13 @@ def __init__(self, name, inverteraddress='', inverterpincode='0000', timeinverva self._reset_duration_timer() self._reset_time_to_disconnect_timer() - self._log.info("Reading from SMASolar every " + str(self._time_inverval) + " seconds") + self._log.info("Reading from SMASolar every %d seconds", self._time_inverval) def _login_inverter(self): """Log into the SMA solar inverter""" - self._log.info("Log into the SMA solar inverter " + str(self._inverteraddress)) + self._log.info("Log into the SMA solar inverter %s", self._inverteraddress) self._btSocket = self._open_bluetooth(self._inverteraddress, self._port) @@ -85,10 +85,10 @@ def _login_inverter(self): dictInverterData = SMASolar_library.getInverterDetails(self._btSocket, self._packet_send_counter, self.mylocalBTAddress, self.MySerialNumber) self._increment_packet_send_counter() - #Returns dictionary like + #Returns dictionary like # FIXME this is dumped from python2, get an updated example dictionary from python3. #{'inverterName': u'SN2120051742\x00\x00', 'serialNumber': 2120051742L, 'ClassName': 'SolarInverter', 'TypeName': 'SB 3000HF-30', 'susyid': 131L, 'Type': 9073L, 'Class': 8001L} - self._log.debug(str(dictInverterData)) + self._log.debug("%s", dictInverterData) #Clear rogue characters from name dictInverterData["inverterName"] = re.sub(r'[^a-zA-Z0-9]', '', dictInverterData["inverterName"]) @@ -100,8 +100,12 @@ def _login_inverter(self): self._Inverters[nodeName]["NodeId"] = self._nodeid - self._log.info("Connected to SMA inverter named [" + self._Inverters[nodeName]["inverterName"] + "] with serial number [" + str(self._Inverters[nodeName]["serialNumber"]) - + "] using NodeId=" + str(self._Inverters[nodeName]["NodeId"]) + ". Model " + self._Inverters[nodeName]["TypeName"]) + self._log.info("Connected to SMA inverter named [%s] with serial number [%d] using NodeId=%d. Model %s", + self._Inverters[nodeName]["inverterName"], + self._Inverters[nodeName]["serialNumber"], + self._Inverters[nodeName]["NodeId"], + self._Inverters[nodeName]["TypeName"], + ) def close(self): """Close bluetooth port""" @@ -117,7 +121,7 @@ def _open_bluetooth(self, inverteraddress, port): """ try: - self._log.info("Opening bluetooth address " + str(inverteraddress)) + self._log.info("Opening bluetooth address %s", inverteraddress) btSocket = bluetooth.BluetoothSocket(bluetooth.RFCOMM) # Connect btSocket.connect((inverteraddress, port)) @@ -126,7 +130,7 @@ def _open_bluetooth(self, inverteraddress, port): except bluetooth.btcommon.BluetoothError as err: self._log.error(err) - self._log.error('Bluetooth error while connecting to %s' % inverteraddress) + self._log.error('Bluetooth error while connecting to %s', inverteraddress) else: return btSocket @@ -228,7 +232,7 @@ def read(self): #Get first inverter in dictionary # FIXME dicts aren't sorted, there is no "first" inverter = self._Inverters[list(self._Inverters.keys())[0]] - self._log.debug("Reading from inverter " + inverter["inverterName"]) + self._log.debug("Reading from inverter %s", inverter["inverterName"]) #Loop through dictionary and take readings, building "output" dictionary as we go output = {} @@ -248,7 +252,7 @@ def read(self): if data is not None: output.update(SMASolar_library.extract_data(data)) if self._packettrace: - self._log.debug("Packet reply for " + reading + ". Packet from {0:04x}/{1:08x}".format(data.getTwoByte(14), data.getFourByteLong(16))) + self._log.debug("Packet reply for %s. Packet from {0:04x}/{1:08x}".format(data.getTwoByte(14), data.getFourByteLong(16)), reading) self._log.debug(data.debugViewPacket()) #self._log.debug("Building cargo") diff --git a/src/interfacers/EmonHubSerialInterfacer.py b/src/interfacers/EmonHubSerialInterfacer.py index 68ea155b..f5b431f0 100644 --- a/src/interfacers/EmonHubSerialInterfacer.py +++ b/src/interfacers/EmonHubSerialInterfacer.py @@ -43,12 +43,12 @@ def _open_serial_port(self, com_port, com_baud): """ #if not int(com_baud) in [75, 110, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]: - # self._log.debug("Invalid 'com_baud': " + str(com_baud) + " | Default of 9600 used") + # self._log.debug("Invalid 'com_baud': %d | Default of 9600 used", com_baud) # com_baud = 9600 try: s = serial.Serial(com_port, com_baud, timeout=0) - self._log.debug("Opening serial port: " + str(com_port) + " @ " + str(com_baud) + " bits/s") + self._log.debug("Opening serial port: %s @ %d bits/s", com_port, com_baud) except serial.SerialException as e: self._log.error(e) s = False diff --git a/src/interfacers/EmonHubSocketInterfacer.py b/src/interfacers/EmonHubSocketInterfacer.py index b9c1bf0b..62c0e4b2 100644 --- a/src/interfacers/EmonHubSocketInterfacer.py +++ b/src/interfacers/EmonHubSocketInterfacer.py @@ -38,7 +38,7 @@ def _open_socket(self, port_nb): """ - self._log.debug('Opening socket on port %s', port_nb) + self._log.debug('Opening socket on port %d', port_nb) try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -98,7 +98,7 @@ def read(self): if len(self._settings['apikey']) == 32 and self._settings['apikey'].lower() != "x" * 32: # Discard if apikey is not in received frame if self._settings['apikey'] not in f: - self._log.warning(str(c.uri) + " discarded frame: apikey not matched") + self._log.warning("%d discarded frame: apikey not matched", c.uri) return # Otherwise remove apikey from frame f = [v for v in f if self._settings['apikey'] not in v] @@ -139,24 +139,24 @@ def set(self, **kwargs): continue elif key == 'apikey': if setting.lower().startswith('xxxx'): # FIXME compare whole string to 'x'*32? - self._log.warning("Setting " + self.name + " apikey: obscured") + self._log.warning("Setting %s apikey: obscured", self.name) elif len(setting) == 32: - self._log.info("Setting " + self.name + " apikey: set") + self._log.info("Setting %s apikey: set", self.name) elif setting == "": - self._log.info("Setting " + self.name + " apikey: null") + self._log.info("Setting %s apikey: null", self.name) else: - self._log.warning("Setting " + self.name + " apikey: invalid format") + self._log.warning("Setting %s apikey: invalid format", self.name) continue self._settings[key] = setting # Next line will log apikey if uncommented (privacy ?) - #self._log.debug(self.name + " apikey: " + str(setting)) + #self._log.debug("%s apikey: %s", self.name, setting) continue elif key == 'url' and setting.startswith("http"): - self._log.info("Setting " + self.name + " url: " + setting) + self._log.info("Setting %s url: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) # include kwargs from parent super().set(**kwargs) diff --git a/src/interfacers/EmonHubTemplateInterfacer.py b/src/interfacers/EmonHubTemplateInterfacer.py index 6b0de755..62f8adb2 100644 --- a/src/interfacers/EmonHubTemplateInterfacer.py +++ b/src/interfacers/EmonHubTemplateInterfacer.py @@ -103,7 +103,7 @@ def _process_post(self, databuffer): for frame in databuffer: # Here we might typically publish or post the data # via MQTT, HTTP a socket or other output - self._log.debug("node = " + frame['node'] + " node_data = " + json.dumps(frame['data'])) + self._log.debug("node = %s node_data = %s", frame['node'], json.dumps(frame['data'])) # We could check for successful data receipt here # and return false to retry next time @@ -121,11 +121,11 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'read_interval': - self._log.info("Setting " + self.name + " read_interval: " + str(setting)) + self._log.info("Setting %s read_interval: %s", self.name, setting) self._settings[key] = float(setting) continue else: - self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) # include kwargs from parent super().set(**kwargs) diff --git a/src/interfacers/EmonHubTx3eInterfacer.py b/src/interfacers/EmonHubTx3eInterfacer.py index e5d64add..c855061b 100644 --- a/src/interfacers/EmonHubTx3eInterfacer.py +++ b/src/interfacers/EmonHubTx3eInterfacer.py @@ -72,12 +72,12 @@ def read(self): try: value = float(parts[1]) except Exception: - self._log.debug("input value is not numeric: " + parts[1]) + self._log.debug("input value is not numeric: %s", parts[1]) names.append(parts[0]) values.append(value) else: - self._log.debug("invalid input name: " + parts[0]) + self._log.debug("invalid input name: %s", parts[0]) if self._settings["nodename"] != "": c.nodename = self._settings["nodename"] diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index e989bf3d..e6f19be3 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -43,7 +43,7 @@ def __init__(self, name, com_port='', com_baud=9600, toextract='', poll_interval # Parser requirements self._extract = toextract - #print "init system with to extract %s"%self._extract + #print("init system with to extract", self._extract) def input(self, byte): @@ -101,12 +101,12 @@ def _open_serial_port(self, com_port, com_baud): """ #if not int(com_baud) in [75, 110, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]: - # self._log.debug("Invalid 'com_baud': " + str(com_baud) + " | Default of 9600 used") + # self._log.debug("Invalid 'com_baud': %d | Default of 9600 used", com_baud) # com_baud = 9600 try: s = serial.Serial(com_port, com_baud, timeout=10) - self._log.debug("Opening serial port: " + str(com_port) + " @ "+ str(com_baud) + " bits/s") + self._log.debug("Opening serial port: %s @ %d bits/s", com_port, com_baud) except serial.SerialException as e: self._log.error(e) s = False @@ -162,7 +162,7 @@ def read(self): # Read serial RX now = time.time() if now - self.last_read <= self.poll_interval: - #self._log.debug(" Waiting for %s seconds " % (str(now - self.last_read))) + #self._log.debug("Waiting for %d seconds ", now - self.last_read) # Wait to read based on poll_interval return diff --git a/src/interfacers/EmonModbusTcpInterfacer.py b/src/interfacers/EmonModbusTcpInterfacer.py index b165c0fd..818c444f 100644 --- a/src/interfacers/EmonModbusTcpInterfacer.py +++ b/src/interfacers/EmonModbusTcpInterfacer.py @@ -28,23 +28,24 @@ def __init__(self, name, modbus_IP='192.168.1.10', modbus_port=0): # Initialization super().__init__(name) + self._modcon = False if not pymodbus_found: self._log.error("PYMODBUS NOT PRESENT BUT NEEDED !!") # open connection if pymodbus_found: self._log.info("pymodbus installed") - self._log.debug("EmonModbusTcpInterfacer args: " + str(modbus_IP) + " - " + str(modbus_port)) + self._log.debug("EmonModbusTcpInterfacer args: %s - %d", modbus_IP, modbus_port) self._con = self._open_modTCP(modbus_IP, modbus_port) if self._modcon: - self._log.info("Modbustcp client Connected") + self._log.info("Modbustcp client Connected") else: - self._log.info("Connection to ModbusTCP client failed. Will try again") + self._log.info("Connection to ModbusTCP client failed. Will try again") def set(self, **kwargs): for key in kwargs: setting = kwargs[key] self._settings[key] = setting - self._log.debug("Setting " + self.name + " %s: %s" % (key, setting)) + self._log.debug("Setting %s %s: %s", self.name, key, setting) def close(self): # Close TCP connection @@ -58,13 +59,13 @@ def _open_modTCP(self, modbus_IP, modbus_port): try: c = ModbusClient(modbus_IP, modbus_port) if c.connect(): - self._log.info("Opening modbusTCP connection: " + str(modbus_port) + " @ " + str(modbus_IP)) + self._log.info("Opening modbusTCP connection: %d @ %s", modbus_port, modbus_IP) self._modcon = True else: self._log.debug("Connection failed") self._modcon = False except Exception as e: - self._log.error("modbusTCP connection failed" + str(e)) + self._log.error("modbusTCP connection failed %s", e) #raise EmonHubInterfacerInitError('Could not open connection to host %s' %modbus_IP) else: return c @@ -81,7 +82,7 @@ def read(self): if not self._modcon: self._con.close() - self._log.info("Not connected, retrying connect" + str(self.init_settings)) + self._log.info("Not connected, retrying connect %s", self.init_settings) self._con = self._open_modTCP(self.init_settings["modbus_IP"], self.init_settings["modbus_port"]) if self._modcon: @@ -145,7 +146,7 @@ def read(self): else: expectedSize = len(rNames) * valid_datacodes[datacode] * 2 - self._log.debug("expected bytes number after encoding: " + str(expectedSize)) + self._log.debug("expected bytes number after encoding: %s", expectedSize) # at this stage, we don't have any invalid datacode(s) # so we can loop and read registers @@ -158,21 +159,21 @@ def read(self): if datacodes is not None: datacode = datacodes[idx] - self._log.debug("datacode " + datacode) + self._log.debug("datacode %s", datacode) qty = valid_datacodes[datacode] - self._log.debug("reading register # :" + str(register) + ", qty #: " + str(qty) + ", unit #: " + str(unitId)) + self._log.debug("reading register #: %s, qty #: %s, unit #: %s", register, qty, unitId) try: self.rVal = self._con.read_holding_registers(register - 1, qty, unit=unitId) assert self.rVal.function_code < 0x80 except Exception as e: - self._log.error("Connection failed on read of register: " + str(register) + " : " + str(e)) + self._log.error("Connection failed on read of register: %s : %s", register, e) self._modcon = False #missing datas will lead to an incorrect encoding #we have to drop the payload return else: - #self._log.debug("register value:" + str(self.rVal.registers) + " type= " + str(type(self.rVal.registers))) + #self._log.debug("register value: %s type= %s", self.rVal.registers, type(self.rVal.registers)) #f = f + self.rVal.registers decoder = BinaryPayloadDecoder.fromRegisters(self.rVal.registers, byteorder=Endian.Big, wordorder=Endian.Big) if datacode == 'h': @@ -198,16 +199,16 @@ def read(self): t = ehc.encode(datacode, rValD) f = f + list(t) - self._log.debug("Encoded value: " + str(t)) - self._log.debug("value: " + str(rValD)) + self._log.debug("Encoded value: %s", t) + self._log.debug("value: %s", rValD) #test if payload length is OK if len(f) == expectedSize: - self._log.debug("payload size OK (" + str(len(f)) + ")") - self._log.debug("reporting data: " + str(f)) + self._log.debug("payload size OK (%d)", len(f)) + self._log.debug("reporting data: %s", f) c.nodeid = node c.realdata = f - self._log.debug("Return from read data: " + str(c.realdata)) + self._log.debug("Return from read data: %s", c.realdata) return c else: - self._log.error("incorrect payload size :" + str(len(f)) + " expecting " + str(expectedSize)) + self._log.error("incorrect payload size: %d expecting %d", len(f), expectedSize) diff --git a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py index 97f5f379..bb022043 100644 --- a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py +++ b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py @@ -65,7 +65,7 @@ def run(self): rxc = self._process_rx(rxc) if rxc: for channel in self._settings["pubchannels"]: - self._log.debug(str(rxc.uri) + " Sent to channel(start)' : " + str(channel)) + self._log.debug("%d Sent to channel(start)' : %s", rxc.uri, channel) # Initialize channel if needed if channel not in self._pub_channels: @@ -74,7 +74,7 @@ def run(self): # Add cargo item to channel self._pub_channels[channel].append(rxc) - self._log.debug(str(rxc.uri) + " Sent to channel(end)' : " + str(channel)) + self._log.debug("%d Sent to channel(end)' : %s", rxc.uri, channel) # Don't loop too fast time.sleep(0.1) @@ -98,7 +98,7 @@ def _process_rx(self, smilics_dict): c.nodeid = smilics_dict['mac'][0] if c.nodeid not in ehc.nodelist.keys(): - self._log.debug(str(c.nodeid) + " Not in config") + self._log.debug("%d Not in config", c.nodeid) return None node_config = ehc.nodelist[str(c.nodeid)] From 579a4a48dbb29a5eeb26bdaab83d0523093dfe6e Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:27:15 +0000 Subject: [PATCH 03/78] More cleanups - Avoid import * - Use setdefault for dict initialisation. - Remove/simplify various bits. --- src/emonhub.py | 11 +--- src/emonhub_coder.py | 1 + src/emonhub_interfacer.py | 11 ++-- src/interfacers/EmonHubGraphiteInterfacer.py | 4 +- src/interfacers/EmonHubMqttInterfacer.py | 50 ++++--------------- src/interfacers/EmonHubSocketInterfacer.py | 6 +-- .../tmp/EmonHubSmilicsInterfacer.py | 10 ++-- 7 files changed, 24 insertions(+), 69 deletions(-) diff --git a/src/emonhub.py b/src/emonhub.py index 1f474f97..c381a752 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -23,7 +23,6 @@ import emonhub_setup as ehs import emonhub_coder as ehc import emonhub_interfacer as ehi -from interfacers import * # this namespace and path namespace = sys.modules[__name__] @@ -119,12 +118,8 @@ def run(self): for sub_channel in sub_interfacer._settings['subchannels']: # If channel names match if sub_channel == pub_channel: - # init if empty - if sub_channel not in sub_interfacer._sub_channels: - sub_interfacer._sub_channels[sub_channel] = [] - # APPEND cargo item - sub_interfacer._sub_channels[sub_channel].append(cargo) + sub_interfacer._sub_channels.setdefault(sub_channel, []).append(cargo) # ->avoid modification of iterable within loop for name in kill_list: @@ -152,7 +147,6 @@ def close(self): I.join() self._log.info("Exit completed") - logging.shutdown() def _sigint_handler(self, signal, frame): """Catch SIGINT (Ctrl+C).""" @@ -170,8 +164,6 @@ def _update_settings(self, settings): else: self._set_logging_level() - # Create a place to hold buffer contents whilst a deletion & rebuild occurs - self.temp_buffer = {} # Interfacers for name in self._interfacers: @@ -251,6 +243,7 @@ def _set_logging_level(self, level='WARNING', log=True): self._log.setLevel(level) if log: self._log.info('Logging level set to %s', level) + return True if __name__ == "__main__": diff --git a/src/emonhub_coder.py b/src/emonhub_coder.py index 9836e54f..470a7932 100644 --- a/src/emonhub_coder.py +++ b/src/emonhub_coder.py @@ -1,6 +1,7 @@ import struct # Initialize nodes data +# FIXME this shouldn't live here nodelist = {} diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 67c2f43b..9fca9371 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -25,12 +25,12 @@ """ -def log_exceptions_from_class_method(f): +def log_exceptions_from_class_method(func): def wrapper(*args): self = args[0] try: - return f(*args) - except: + return func(*args) + except Exception: self._log.warning("Exception caught in " + self.name + " thread. " + traceback.format_exc()) return wrapper @@ -533,10 +533,7 @@ def _process_tx(self, cargo): datacode = ehc.nodelist[dest]['tx']['datacode'] else: # when node not listed or has no datacode(s) use the interfacers default if specified - if 'datacode' in self._settings: - datacode = self._settings['datacode'] - else: - datacode = "h" + datacode = self._settings.get('datacode', 'h') # Ensure only int 0 is passed not str 0 if datacode == '0': diff --git a/src/interfacers/EmonHubGraphiteInterfacer.py b/src/interfacers/EmonHubGraphiteInterfacer.py index c882bfe1..6077ef2b 100644 --- a/src/interfacers/EmonHubGraphiteInterfacer.py +++ b/src/interfacers/EmonHubGraphiteInterfacer.py @@ -89,8 +89,8 @@ def _send_metrics(self, metrics=[]): """ - host = str(self._settings['graphite_host']).strip('[\'\']') - port = int(str(self._settings['graphite_port']).strip('[\'\']')) + host = self._settings['graphite_host'].strip("[']") + port = int(self._settings['graphite_port'].strip("[']")) self._log.debug("Graphite target: %s:%s", host, port) message = '\n'.join(metrics) + '\n' self._log.debug("Sending metrics: %s", message) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index f0b75b90..ecb664c2 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -53,16 +53,11 @@ def add(self, cargo): format: {"emontx":{"power1":100,"power2":200,"power3":300}} """ - - nodename = str(cargo.nodeid) - if cargo.nodename: - nodename = cargo.nodename - - f = {} - f['nodeid'] = cargo.nodeid - f['node'] = nodename - f['names'] = cargo.names - f['data'] = cargo.realdata + f = {'nodeid': cargo.nodeid, + 'node': cargo.nodename or str(cargo.nodeid), + 'names': cargo.names, + 'data': cargo.realdata, + } if cargo.rssi: f['rssi'] = cargo.rssi @@ -78,9 +73,7 @@ def add(self, cargo): # This is a bit of a hack, the final approach is currently being considered # as part of ongoing discussion on future direction of emonhub - databuffer = [] - databuffer.append(f) - self._process_post(databuffer) + self._process_post([f]) # To re-enable buffering comment the above three lines and uncomment the following # note that at preset _process_post will not handle buffered data correctly and @@ -159,10 +152,6 @@ def _process_post(self, databuffer): return True def action(self): - """ - - :return: - """ self._mqttc.loop(0) # pause output if 'pause' set to 'all' or 'out' @@ -179,7 +168,6 @@ def action(self): self.flush() def on_connect(self, client, userdata, flags, rc): - connack_string = {0: 'Connection successful', 1: 'Connection refused - incorrect protocol version', 2: 'Connection refused - invalid client identifier', @@ -215,34 +203,18 @@ def on_message(self, client, userdata, msg): realdata = payload.split(",") self._log.debug("Nodeid: %s values: %s", nodeid, msg.payload) - rxc = Cargo.new_cargo(realdata=realdata) - rxc.nodeid = nodeid + rxc = Cargo.new_cargo(realdata=realdata, nodeid=nodeid) - if rxc: - # rxc = self._process_tx(rxc) - if rxc: - for channel in self._settings["pubchannels"]: + for channel in self._settings["pubchannels"]: + # Add cargo item to channel + self._pub_channels.setdefault(channel, []).append(rxc) - # Initialize channel if needed - if channel not in self._pub_channels: - self._pub_channels[channel] = [] - - # Add cargo item to channel - self._pub_channels[channel].append(rxc) - - self._log.debug("%d Sent to channel' : %s", rxc.uri, channel) + self._log.debug("%d Sent to channel' : %s", rxc.uri, channel) def set(self, **kwargs): - """ - - :param kwargs: - :return: - """ - super().set(**kwargs) for key, setting in self._mqtt_settings.items(): - #valid = False if key not in kwargs: setting = self._mqtt_settings[key] else: diff --git a/src/interfacers/EmonHubSocketInterfacer.py b/src/interfacers/EmonHubSocketInterfacer.py index 62c0e4b2..852c3c33 100644 --- a/src/interfacers/EmonHubSocketInterfacer.py +++ b/src/interfacers/EmonHubSocketInterfacer.py @@ -105,7 +105,6 @@ def read(self): c.rawdata = ' '.join(f) # Extract timestamp value if one is expected or use 0 - timestamp = 0.0 if self._settings['timestamped']: c.timestamp = f[0] f = f[1:] @@ -114,13 +113,10 @@ def read(self): f = f[1:] # Extract the Target id if one is expected if self._settings['targeted']: - #setting = str(setting).capitalize() c.target = int(f[0]) f = f[1:] # Extract list of data values - c.realdata = f#[1:] - # Create a Payload object - #f = new_cargo(data, node, timestamp, dest) + c.realdata = f return c diff --git a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py index bb022043..13a5f528 100644 --- a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py +++ b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py @@ -67,12 +67,8 @@ def run(self): for channel in self._settings["pubchannels"]: self._log.debug("%d Sent to channel(start)' : %s", rxc.uri, channel) - # Initialize channel if needed - if channel not in self._pub_channels: - self._pub_channels[channel] = [] - # Add cargo item to channel - self._pub_channels[channel].append(rxc) + self._pub_channels.setdefault(channel, []).append(rxc) self._log.debug("%d Sent to channel(end)' : %s", rxc.uri, channel) @@ -120,13 +116,13 @@ def _process_rx(self, smilics_dict): c.timestamp = time.mktime(datetime.datetime.now().timetuple()) return c - except: + except Exception: return None def set(self, **kwargs): """ Override default settings with settings entered in the config file """ - for key, setting in self._settings.iteritems(): + for key, setting in self._settings.tems(): if key in kwargs.keys(): # replace default self._settings[key] = kwargs[key] From 724e5aeb8964ca1c5337f4e7a49d7b87210e57fe Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:29:29 +0000 Subject: [PATCH 04/78] Simplify code with list comprehension and zip. --- src/emonhub_interfacer.py | 3 +-- src/interfacers/EmonHubTemplateInterfacer.py | 28 +++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 9fca9371..08e15164 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -303,8 +303,7 @@ def _process_rx(self, cargo): if node in ehc.nodelist and 'rx' in ehc.nodelist[node] and 'whitening' in ehc.nodelist[node]['rx']: whitening = ehc.nodelist[node]['rx']['whitening'] if whitening is True or whitening == "1": - for i in range(len(rxc.realdata)): - rxc.realdata[i] = rxc.realdata[i] ^ 0x55 + rxc.realdata = [x ^ 0x55 for x in rxc.realdata] # check if node is listed and has individual datacodes for each value if node in ehc.nodelist and 'rx' in ehc.nodelist[node] and 'datacodes' in ehc.nodelist[node]['rx']: diff --git a/src/interfacers/EmonHubTemplateInterfacer.py b/src/interfacers/EmonHubTemplateInterfacer.py index 62f8adb2..0c1ab024 100644 --- a/src/interfacers/EmonHubTemplateInterfacer.py +++ b/src/interfacers/EmonHubTemplateInterfacer.py @@ -1,4 +1,7 @@ -import time, json, Cargo +import time +import json +from itertools import zip_longest +import Cargo from emonhub_interfacer import EmonHubInterfacer """class EmonHubTemplateInterfacer @@ -78,17 +81,18 @@ def add(self, cargo): if cargo.nodename: nodename = cargo.nodename - f = {} - f['node'] = nodename - f['data'] = {} - - # FIXME replace with zip - for i in range(len(cargo.realdata)): - name = str(i + 1) - if i < len(cargo.names): - name = cargo.names[i] - value = cargo.realdata[i] - f['data'][name] = value + f = {'node': nodename, + 'data': {} + } + + # FIXME zip_longest mimics the previous behaviour of this code which + # filled the gaps with a numeric string. However it's surely an error + # to provide more data than the schema expects, so it should either + # be an explicit error or silently dropped. + # If that's the case all this code can be simplified to: + # f['data'] = {name: value for name, value in zip(cargo.names, cargo.realdata)} + for i, (name, value) in enumerate(zip_longest(cargo.names, cargo.realdata, fill_value=None), start=1): + f['data'][name or str(i)] = value if cargo.rssi: f['data']['rssi'] = cargo.rssi From 45bfe44cdd3bac2489b4a664b1f8fc0e69cdf252 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:30:13 +0000 Subject: [PATCH 05/78] Simplify EmonHubFileSetup. This class is only ever a ConfigObj wrapper, so remove the json code. --- src/emonhub_setup.py | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/emonhub_setup.py b/src/emonhub_setup.py index 7f616b7e..05467538 100644 --- a/src/emonhub_setup.py +++ b/src/emonhub_setup.py @@ -9,7 +9,6 @@ import time import logging -import json from configobj import ConfigObj """class EmonHubSetup @@ -73,28 +72,17 @@ def __init__(self, filename): # Initialization super().__init__() - self._fileformat = "ConfigObj" # or "ConfigObj" - self._filename = filename # Initialize update timestamp self._settings_update_timestamp = 0 self._retry_time_interval = 5 - # create a timeout message if time out is set (>0) - if self._retry_time_interval > 0: - self.retry_msg = " Retry in " + str(self._retry_time_interval) + " seconds" - else: - self.retry_msg = "" + self.retry_msg = " Retry in " + str(self._retry_time_interval) + " seconds" # Initialize attribute settings as a ConfigObj instance try: - - if self._fileformat == "ConfigObj": - self.settings = ConfigObj(filename, file_error=True) - else: - with open(filename) as f: - self.settings = json.loads(f.read()) + self.settings = ConfigObj(filename, file_error=True) # Check the settings file sections self.settings['hub'] @@ -127,12 +115,7 @@ def check_settings(self): # Get settings from file try: - if self._fileformat == "ConfigObj": - self.settings.reload() - else: - with open(self._filename) as f: - self.settings = json.loads(f.read()) - + self.settings.reload() except IOError as e: self._log.warning('Could not get settings: %s %s', e, self.retry_msg) self._settings_update_timestamp = now + self._retry_time_interval From a360874b4118fa7b2705dc53a86ed5360b6585de Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:32:25 +0000 Subject: [PATCH 06/78] Add log_backup_count and log_max_bytes settings. This adds config options to control the log file rotation. --- src/emonhub.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/emonhub.py b/src/emonhub.py index c381a752..649d3cac 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -164,6 +164,16 @@ def _update_settings(self, settings): else: self._set_logging_level() + if 'log_backup_count' in settings['hub']: + for handler in self._log.handlers: + if isinstance(handler, logging.handlers.RotatingFileHandler): + handler.backupCount = settings['hub']['log_backup_count'] + self._log.info("Logging backup count set to %d", handler.backupCount) + if 'log_max_bytes' in settings['hub']: + for handler in self._log.handlers: + if isinstance(handler, logging.handlers.RotatingFileHandler): + handler.maxBytes = settings['hub']['log_max_bytes'] + self._log.info("Logging max file size set to %d bytes", handler.maxBytes) # Interfacers for name in self._interfacers: @@ -279,8 +289,10 @@ def _set_logging_level(self, level='WARNING', log=True): # this ensures it is writable # Close the file for now and get its path args.logfile.close() + # These maxBytes and backupCount defaults can be overridden in the config file. loghandler = logging.handlers.RotatingFileHandler(args.logfile.name, - 'a', 5000 * 1024, 1) + maxBytes=5000 * 1024, + backupCount=1) else: # Otherwise, if no path was specified, everything goes to sys.stderr loghandler = logging.StreamHandler() From 5269897a40410e353340fc831db2bfe9570b8e72 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:41:07 +0000 Subject: [PATCH 07/78] Typo. --- src/interfacers/EmonHubEmoncmsHTTPInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index 1dcae3a6..0972d8bc 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -70,7 +70,7 @@ def _process_post(self, databuffer): reply = self._send_post(post_url, {'data': data_string, 'sentat': str(sentat)}) if reply == 'ok': - self._log.debug("acknowledged receipt with '%s' from %s", reply, self._settings['url') + self._log.debug("acknowledged receipt with '%s' from %s", reply, self._settings['url']) return True else: self._log.warning("send failure: wanted 'ok' but got '%s'", reply) From ddbaa09c7757674a0b94f73cb730a2b402408e74 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:50:16 +0000 Subject: [PATCH 08/78] Revert removal of import * This import mechanism is fragile and needs to be converted into a true module. Revert this for now. --- src/emonhub.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/emonhub.py b/src/emonhub.py index 649d3cac..9a2b71fb 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -23,6 +23,7 @@ import emonhub_setup as ehs import emonhub_coder as ehc import emonhub_interfacer as ehi +from interfacers import * # this namespace and path namespace = sys.modules[__name__] From ab0beaa4629e14a80f0c9334d3944ee1c49e2ec7 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:55:11 +0000 Subject: [PATCH 09/78] Fix log format types. --- src/emonhub_interfacer.py | 4 ++-- src/interfacers/EmonHubSerialInterfacer.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 08e15164..3cc5d2db 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -101,7 +101,7 @@ def run(self): rxc = self._process_rx(rxc) if rxc: for channel in self._settings["pubchannels"]: - self._log.debug("%d Sent to channel(start)' : %d", rxc.uri, channel) + self._log.debug("%d Sent to channel(start)' : %s", rxc.uri, channel) # Initialise channel if needed if channel not in self._pub_channels: @@ -110,7 +110,7 @@ def run(self): # Add cargo item to channel self._pub_channels[channel].append(rxc) - self._log.debug("%d Sent to channel(end)' : %d", rxc.uri, channel) + self._log.debug("%d Sent to channel(end)' : %s", rxc.uri, channel) # Subscriber channels for channel in self._settings["subchannels"]: diff --git a/src/interfacers/EmonHubSerialInterfacer.py b/src/interfacers/EmonHubSerialInterfacer.py index f5b431f0..8b07bcd9 100644 --- a/src/interfacers/EmonHubSerialInterfacer.py +++ b/src/interfacers/EmonHubSerialInterfacer.py @@ -48,7 +48,7 @@ def _open_serial_port(self, com_port, com_baud): try: s = serial.Serial(com_port, com_baud, timeout=0) - self._log.debug("Opening serial port: %s @ %d bits/s", com_port, com_baud) + self._log.debug("Opening serial port: %s @ %s bits/s", com_port, com_baud) except serial.SerialException as e: self._log.error(e) s = False From ada3181910d2519a32d9b3e531bb04b2e6da66e7 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:59:09 +0000 Subject: [PATCH 10/78] Also catch SIGTERM. This means that emonhub shuts down cleanly when stopped by, e.g., systemd. --- src/emonhub.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/emonhub.py b/src/emonhub.py index 9a2b71fb..3e854289 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -85,8 +85,9 @@ def run(self): """ - # Set signal handler to catch SIGINT and shutdown gracefully - signal.signal(signal.SIGINT, self._sigint_handler) + # Set signal handler to catch SIGINT or SIGTERM and shutdown gracefully + signal.signal(signal.SIGINT, self._signal_handler) + signal.signal(signal.SIGTERM, self._signal_handler) # Initialise thread restart counters restart_count = defaultdict(int) @@ -149,10 +150,10 @@ def close(self): self._log.info("Exit completed") - def _sigint_handler(self, signal, frame): - """Catch SIGINT (Ctrl+C).""" + def _signal_handler(self, signal, frame): + """Catch fatal signals like SIGINT (Ctrl+C) and SIGTERM (service stop).""" - self._log.debug("SIGINT received.") + self._log.debug("Signal %d received.", signal) # hub should exit at the end of current iteration. self._exit = True From 4137587c130445e6d6d2b97c4946db4cb447563e Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Sun, 8 Dec 2019 23:58:15 +0000 Subject: [PATCH 11/78] Fix typo bug. --- src/interfacers/EmonModbusTcpInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonModbusTcpInterfacer.py b/src/interfacers/EmonModbusTcpInterfacer.py index abbdc48d..9b1cf096 100644 --- a/src/interfacers/EmonModbusTcpInterfacer.py +++ b/src/interfacers/EmonModbusTcpInterfacer.py @@ -121,7 +121,7 @@ def read(self): elif 'datacodes' in ehc.nodelist[node]['rx']: datacodes = ehc.nodelist[node]['rx']['datacodes'] else: - _self._log.error("please provide a datacode or a list of datacodes") + self._log.error("please provide a datacode or a list of datacodes") return # check if number of registers and number of names are the same From ba2835d93f188680021a6e4e934903b2b2279927 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Thu, 17 Oct 2019 23:31:43 +0100 Subject: [PATCH 12/78] Whitespace, brackets, spelling, remove dead code. --- src/emonhub.py | 50 ++-- src/emonhub_buffer.py | 11 +- src/emonhub_coder.py | 1 - src/emonhub_interfacer.py | 112 ++++----- src/emonhub_setup.py | 56 ++--- src/interfacers/EmonHubBMWInterfacer.py | 147 +++++------ .../EmonHubEmoncmsHTTPInterfacer.py | 43 ++-- src/interfacers/EmonHubGraphiteInterfacer.py | 69 +++-- src/interfacers/EmonHubJeeInterfacer.py | 81 +++--- src/interfacers/EmonHubMqttInterfacer.py | 157 ++++++------ src/interfacers/EmonHubPacketGenInterfacer.py | 16 +- src/interfacers/EmonHubSMASolarInterfacer.py | 118 ++++----- src/interfacers/EmonHubSerialInterfacer.py | 14 +- src/interfacers/EmonHubSocketInterfacer.py | 26 +- src/interfacers/EmonHubTemplateInterfacer.py | 83 +++--- src/interfacers/EmonHubTx3eInterfacer.py | 40 +-- src/interfacers/EmonHubVEDirectInterfacer.py | 55 ++-- src/interfacers/EmonModbusTcpInterfacer.py | 88 +++---- src/interfacers/__init__.py | 32 +-- .../tmp/EmonFroniusModbusTcpInterfacer.py | 28 +-- .../tmp/EmonHubSmilicsInterfacer.py | 10 +- src/smalibrary/SMABluetoothPacket.py | 17 +- src/smalibrary/SMANET2PlusPacket.py | 43 ++-- src/smalibrary/SMASolar_library.py | 236 ++++++++---------- 24 files changed, 722 insertions(+), 811 deletions(-) diff --git a/src/emonhub.py b/src/emonhub.py index e6695ac3..f42ef9ff 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -16,7 +16,8 @@ import signal import argparse import pprint -import glob, os +import glob +import os import emonhub_setup as ehs import emonhub_coder as ehc @@ -28,11 +29,11 @@ path = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) # scan interfacers directory and import all interfacers -for f in glob.glob(path+"/interfacers/*.py"): - name = f.replace(".py","").replace(path+"/interfacers/","") - if name!="__init__": - # print "Loading: "+name - setattr(ehi,name,getattr(getattr(namespace,name),name)) +for f in glob.glob(path + "/interfacers/*.py"): + name = f.replace(".py", "").replace(path + "/interfacers/", "") + if name != "__init__": + # print "Loading: " + name + setattr(ehi, name, getattr(getattr(namespace, name), name)) """class EmonHub @@ -86,9 +87,9 @@ def run(self): signal.signal(signal.SIGINT, self._sigint_handler) # Initialise thread restart counters - restart_count={} + restart_count = {} for I in self._interfacers.itervalues(): - restart_count[I.name]=0 + restart_count[I.name] = 0 # Until asked to stop while not self._exit: @@ -99,7 +100,7 @@ def run(self): self._update_settings(self._setup.settings) # For all Interfacers - kill_list=[] + kill_list = [] for I in self._interfacers.itervalues(): # Check threads are still running if not I.isAlive(): @@ -107,23 +108,23 @@ def run(self): # Read each interfacers pub channels for pub_channel in I._settings['pubchannels']: - + if pub_channel in I._pub_channels: - if len(I._pub_channels[pub_channel])>0: - + if len(I._pub_channels[pub_channel]) > 0: + # POP cargo item (one at a time) cargo = I._pub_channels[pub_channel].pop(0) - + # Post to each subscriber interface for sub_interfacer in self._interfacers.itervalues(): # For each subsciber channel for sub_channel in sub_interfacer._settings['subchannels']: # If channel names match - if sub_channel==pub_channel: + if sub_channel == pub_channel: # init if empty if not sub_channel in sub_interfacer._sub_channels: sub_interfacer._sub_channels[sub_channel] = [] - + # APPEND cargo item sub_interfacer._sub_channels[sub_channel].append(cargo) @@ -133,13 +134,13 @@ def run(self): # The following should trigger a restart ... unless the # interfacer is also removed from the settings table. - del(self._interfacers[name]) + del self._interfacers[name] # Trigger restart by calling update settings - self._log.warning("Attempting to restart thread "+name+" (thread has been restarted "+str(restart_count[name])+" times...") - restart_count[name]+=1 + self._log.warning("Attempting to restart thread " + name + " (thread has been restarted " + str(restart_count[name]) + " times...") + restart_count[name] += 1 self._update_settings(self._setup.settings) - + # Sleep until next iteration time.sleep(0.2) @@ -171,7 +172,6 @@ def _update_settings(self, settings): else: self._set_logging_level() - # Create a place to hold buffer contents whilst a deletion & rebuild occurs self.temp_buffer = {} @@ -197,7 +197,7 @@ def _update_settings(self, settings): # Delete interfacers if setting changed or name is unlisted or Type is missing self._log.info("Deleting interfacer '%s' ", name) self._interfacers[name].stop = True - del(self._interfacers[name]) + del self._interfacers[name] for name, I in settings['interfacers'].iteritems(): # If interfacer does not exist, create it @@ -264,7 +264,7 @@ def _set_logging_level(self, level='WARNING', log=True): # Configuration file parser.add_argument("--config-file", action="store", - help='Configuration file', default=sys.path[0]+'/../conf/emonhub.conf') + help='Configuration file', default=sys.path[0] + '/../conf/emonhub.conf') # Log file parser.add_argument('--logfile', action='store', type=argparse.FileType('a'), help='Log file (default: log to Standard error stream STDERR)') @@ -294,10 +294,10 @@ def _set_logging_level(self, level='WARNING', log=True): # Close the file for now and get its path args.logfile.close() loghandler = logging.handlers.RotatingFileHandler(args.logfile.name, - 'a', 5000 * 1024, 1) + 'a', 5000 * 1024, 1) # Format log strings loghandler.setFormatter(logging.Formatter( - '%(asctime)s %(levelname)-8s %(threadName)-10s %(message)s')) + '%(asctime)s %(levelname)-8s %(threadName)-10s %(message)s')) logger.addHandler(loghandler) @@ -312,7 +312,7 @@ def _set_logging_level(self, level='WARNING', log=True): if setup.settings['hub']['use_syslog'] == 'yes': syslogger = logging.handlers.SysLogHandler(address='/dev/log') syslogger.setFormatter(logging.Formatter( - 'emonHub[%(process)d]: %(levelname)-8s %(threadName)-10s %(message)s')) + 'emonHub[%(process)d]: %(levelname)-8s %(threadName)-10s %(message)s')) logger.addHandler(syslogger) # If in "Show settings" mode, print settings and exit diff --git a/src/emonhub_buffer.py b/src/emonhub_buffer.py index 468d1764..b9c617cb 100644 --- a/src/emonhub_buffer.py +++ b/src/emonhub_buffer.py @@ -1,7 +1,7 @@ """ This code is released under the GNU Affero General Public License. - + OpenEnergyMonitor project: http://openenergymonitor.org @@ -23,7 +23,7 @@ def storeItem(self, data): def retrieveItems(self, number): raise NotImplementedError - def retrieveItem(self): + def retrieveItem(self): raise NotImplementedError def discardLastRetrievedItem(self): @@ -32,7 +32,7 @@ def discardLastRetrievedItem(self): def discardLastRetrievedItems(self, number): raise NotImplementedError - def hasItems(self): + def hasItems(self): raise NotImplementedError """ @@ -42,7 +42,7 @@ def hasItems(self): class InMemoryBuffer(AbstractBuffer): - + def __init__(self, bufferName, buffer_size): self._bufferName = str(bufferName) self._buffer_type = "memory" @@ -97,7 +97,7 @@ def size(self): """ -The getBuffer function returns the buffer class corresponding to a +The getBuffer function returns the buffer class corresponding to a buffering method passed as argument. """ bufferMethodMap = { @@ -112,4 +112,3 @@ def getBuffer(method): """ return bufferMethodMap[method] - diff --git a/src/emonhub_coder.py b/src/emonhub_coder.py index 6e3cf2d2..e54e6492 100644 --- a/src/emonhub_coder.py +++ b/src/emonhub_coder.py @@ -5,7 +5,6 @@ def check_datacode(datacode): - # Data types & sizes (number of bytes) datacodes = {'b': '1', 'h': '2', 'i': '4', 'l': '4', 'q': '8', 'f': '4', 'd': '8', 'B': '1', 'H': '2', 'I': '4', 'L': '4', 'Q': '8', 'c': '1', '?': '1'} diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index ae089dda..99e0bcb3 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -22,7 +22,6 @@ import paho.mqtt.client as mqtt import emonhub_coder as ehc - import emonhub_buffer as ehb """class EmonHubInterfacer @@ -33,20 +32,18 @@ their data source. """ + def log_exceptions_from_class_method(f): def wrapper(*args): - self=args[0] + self = args[0] try: return f(*args) except: - self._log.warning("Exception caught in "+self.name+" thread. "+traceback.format_exc()) - return + self._log.warning("Exception caught in " + self.name + " thread. " + traceback.format_exc()) return wrapper class EmonHubInterfacer(threading.Thread): - def __init__(self, name): - # Initialize logger self._log = logging.getLogger("EmonHub") @@ -55,16 +52,24 @@ def __init__(self, name): self.setName(name) # Initialise settings - self._defaults = {'pause': 'off', 'interval': 0, 'datacode': '0', - 'scale':'1', 'timestamped': False, 'targeted': False, 'nodeoffset' : '0','pubchannels':[],'subchannels':[], 'batchsize': '1'} - + self._defaults = {'pause': 'off', + 'interval': 0, + 'datacode': '0', + 'scale': '1', + 'timestamped': False, + 'targeted': False, + 'nodeoffset': '0', + 'pubchannels': [], + 'subchannels': [], + 'batchsize': '1'} + self.init_settings = {} self._settings = {} # Initialize message queue self._sub_channels = {} self._pub_channels = {} - + # This line will stop the default values printing to logfile at start-up # unless they have been overwritten by emonhub.conf entries # comment out if diagnosing a startup value issue @@ -72,7 +77,7 @@ def __init__(self, name): # Initialize interval timer's "started at" timestamp self._interval_timestamp = 0 - + buffer_type = "memory" buffer_size = 1000 @@ -83,7 +88,7 @@ def __init__(self, name): # number of items posted is the lower of this item limit, buffer_size, or the # batchsize, as set in reporter settings or by the default value. self._item_limit = buffer_size - + # create a stop self.stop = False @@ -105,28 +110,28 @@ def run(self): if rxc: for channel in self._settings["pubchannels"]: self._log.debug(str(rxc.uri) + " Sent to channel(start)' : " + str(channel)) - + # Initialize channel if needed if not channel in self._pub_channels: self._pub_channels[channel] = [] - + # Add cargo item to channel self._pub_channels[channel].append(rxc) - + self._log.debug(str(rxc.uri) + " Sent to channel(end)' : " + str(channel)) # Subscriber channels for channel in self._settings["subchannels"]: if channel in self._sub_channels: - for i in range(0,len(self._sub_channels[channel])): + for i in range(0, len(self._sub_channels[channel])): frame = self._sub_channels[channel].pop(0) self.add(frame) - - # Don't loop to fast + + # Don't loop too fast time.sleep(0.1) # Action reporter tasks self.action() - + def add(self, cargo): """Append data to buffer. @@ -143,12 +148,12 @@ def add(self, cargo): f.append(i) if cargo.rssi: f.append(cargo.rssi) - + # self._log.debug(str(cargo.uri) + " adding frame to buffer => "+ str(f)) - + except: self._log.warning("Failed to create emonCMS frame " + str(f)) - + # self._log.debug(str(carg.ref) + " added to buffer =>" # + " time: " + str(carg.timestamp) # + ", node: " + str(carg.node) @@ -157,9 +162,9 @@ def add(self, cargo): # databuffer is of format: # [[timestamp, nodeid, datavalues][timestamp, nodeid, datavalues]] # [[1399980731, 10, 150, 3450 ...]] - - # datauffer format can be overwritten by interfacer - + + # databuffer format can be overwritten by interfacer + self.buffer.storeItem(f) def read(self): @@ -199,12 +204,12 @@ def action(self): def flush(self): """Send oldest data in buffer, if any.""" - + # Buffer management # If data buffer not empty, send a set of values if self.buffer.hasItems(): - self._log.debug("Buffer size: "+str(self.buffer.size())) - + self._log.debug("Buffer size: " + str(self.buffer.size())) + max_items = int(self._settings['batchsize']) if max_items > self._item_limit: max_items = self._item_limit @@ -221,8 +226,8 @@ def flush(self): else: # slow down retry rate in the case where the last attempt failed # stops continuous retry attempts filling up the log - self._interval_timestamp = time.time() - + self._interval_timestamp = time.time() + def _process_post(self, data): """ @@ -268,7 +273,7 @@ def _send_post(self, post_url, post_body=None): reply = response.read() finally: return reply - + def _process_rx(self, cargo): """Process a frame of data @@ -282,7 +287,7 @@ def _process_rx(self, cargo): Return data as a list: [NodeID, val1, val2] """ - + # Log data self._log.debug(str(cargo.uri) + " NEW FRAME : " + str(cargo.rawdata)) @@ -312,7 +317,7 @@ def _process_rx(self, cargo): # Data whitening uses for ensuring rfm sync if node in ehc.nodelist and 'rx' in ehc.nodelist[node] and 'whitening' in ehc.nodelist[node]['rx']: whitening = ehc.nodelist[node]['rx']['whitening'] - if whitening==True or whitening=="1": + if whitening == True or whitening == "1": for i in range(0, len(rxc.realdata), 1): rxc.realdata[i] = rxc.realdata[i] ^ 0x55 @@ -341,7 +346,7 @@ def _process_rx(self, cargo): if node in ehc.nodelist and 'rx' in ehc.nodelist[node] and 'datacode' in ehc.nodelist[node]['rx']: datacode = ehc.nodelist[node]['rx']['datacode'] else: - # when node not listed or has no datacode(s) use the interfacers default if specified + # when node not listed or has no datacode(s) use the interfacers default if specified datacode = self._settings['datacode'] # Ensure only int 0 is passed not str 0 if datacode == '0': @@ -380,7 +385,7 @@ def _process_rx(self, cargo): return False bytepos += size decoded.append(value) - + # check if node is listed and has individual scales for each value if node in ehc.nodelist and 'rx' in ehc.nodelist[node] and 'scales' in ehc.nodelist[node]['rx']: scales = ehc.nodelist[node]['rx']['scales'] @@ -393,7 +398,7 @@ def _process_rx(self, cargo): # Determine the expected number of values to be decoded # Set decoder to "Per value" scaling using scale 'False' as flag # scale = False - if len(scales)>1: + if len(scales) > 1: scale = False else: scale = "1" @@ -409,7 +414,7 @@ def _process_rx(self, cargo): for i in range(0, len(decoded), 1): x = scale if not scale: - if i waittime) + # Not charging + waittime = self._time_inverval + return int(duration_of_delay) > waittime def call(self, path, post_data=None): """ @@ -171,43 +161,41 @@ def call(self, path, post_data=None): If a dictionary 'post_data' is specified, the request will be a POST, otherwise a GET. """ - # - if (time.time() > self._TokenExpiry): + if time.time() > self._TokenExpiry: self.obtainCredentials() headers = {"Authorization": "Bearer " + self._AccessToken, - "User-Agent":self.USER_AGENT} + "User-Agent": self.USER_AGENT} - #self._log.debug("headers="+str(headers)) + #self._log.debug("headers=" + str(headers)) if post_data is None: - r = requests.get(self.ROOT_URL + path, headers=headers) + r = requests.get(self.ROOT_URL + path, headers=headers) else: - r = requests.post(self.ROOT_URL + path, headers=headers, data=post_data) + r = requests.post(self.ROOT_URL + path, headers=headers, data=post_data) #Raise exception if problem with request r.raise_for_status() return r.json() - - #Override base _process_rx code from emonhub_interfacer + # Override base _process_rx code from emonhub_interfacer def _process_rx(self, rxc): if not rxc: return False return rxc - #Override base read code from emonhub_interfacer + # Override base read code from emonhub_interfacer def read(self): """Read data from BMW API""" #Wait until we are ready to read from inverter - if (self._is_it_time() == False and self._first_time_loop == False): + if self._is_it_time() == False and self._first_time_loop == False: return self._reset_duration_timer() - self._first_time_loop=False + self._first_time_loop = False try: #self._log.debug("Entering read section") @@ -215,7 +203,7 @@ def read(self): #https://www.bmw-connecteddrive.co.uk/api/me/vehicles/v2?all=true vehicles = self.call('/api/me/vehicles/v2?all=true') - myvehicle=vehicles[0] + myvehicle = vehicles[0] #Returns a dictionary object containing... #[{u'supportedChargingModes': [u'AC_LOW', u'DC'], u'hasNavi': True, @@ -224,39 +212,38 @@ def read(self): # u'hasSunRoof': False, u'steering': u'RIGHT', u'licensePlate': u'AA11ABC', # u'dcOnly': True, u'driveTrain': u'BEV_REX', u'doorCount': 4, u'maxFuel': u'8.5', u'hasRex': True}] - self._log.debug("modelName='" + myvehicle["modelName"] + "' VIN='" + myvehicle["vin"]+"'") + self._log.debug("modelName='" + myvehicle["modelName"] + "' VIN='" + myvehicle["vin"] + "'") - vin=myvehicle["vin"] + vin = myvehicle["vin"] - dynamic = self.call('/api/vehicle/dynamic/v1/'+vin+"?offset=0") + dynamic = self.call('/api/vehicle/dynamic/v1/' + vin + "?offset=0") - #Report efficieny of driving (not currently used by this program) - #efficiency = self.call('/api/vehicle/efficiency/v1/'+vin) - #self._log.debug("efficiency="+str(efficiency)) + #Report efficiency of driving (not currently used by this program) + #efficiency = self.call('/api/vehicle/efficiency/v1/' + vin) + #self._log.debug("efficiency=" + str(efficiency)) attributesMap = dynamic["attributesMap"] - names= [] + names = [] values = [] - for key in ['battery_size_max','beMaxRangeElectricKm','beMaxRangeElectricMile','beRemainingRangeElectricKm','beRemainingRangeElectricMile','beRemainingRangeFuelKm','beRemainingRangeFuelMile','chargingLevelHv','fuelPercent','kombi_current_remaining_range_fuel','mileage','remaining_fuel','soc_hv_percent']: + for key in ['battery_size_max', 'beMaxRangeElectricKm', 'beMaxRangeElectricMile', 'beRemainingRangeElectricKm', 'beRemainingRangeElectricMile', 'beRemainingRangeFuelKm', 'beRemainingRangeFuelMile', 'chargingLevelHv', 'fuelPercent', 'kombi_current_remaining_range_fuel', 'mileage', 'remaining_fuel', 'soc_hv_percent']: if key in attributesMap: - names.append( key ) - values.append( float(attributesMap[key]) ) + names.append(key) + values.append(float(attributesMap[key])) - #Store if the car is charging or not - self._chargingSystemStatus=attributesMap["chargingSystemStatus"] + # Store if the car is charging or not + self._chargingSystemStatus = attributesMap["chargingSystemStatus"] - #CHARGINGACTIVE + # CHARGINGACTIVE names.append("ChargingActive") - if (self._chargingSystemStatus=="CHARGINGACTIVE"): + if self._chargingSystemStatus == "CHARGINGACTIVE": values.append(1) else: values.append(0) - - self._log.debug("chargingSystemStatus="+self._chargingSystemStatus) + self._log.debug("chargingSystemStatus=" + self._chargingSystemStatus) #u'unitOfElectricConsumption = u'mls/kWh' #u'unitOfCombustionConsumption = u'mpg' @@ -273,7 +260,7 @@ def read(self): # events which are in the past #Ensure we use the datetime from the API call as this only changes every few hours #when the car is NOT charging - c.timestamp = float(attributesMap["updateTime_converted_timestamp"])/1000 + c.timestamp = float(attributesMap["updateTime_converted_timestamp"]) / 1000 c.nodeid = self._NodeId c.nodename = self._NodeName @@ -284,4 +271,4 @@ def read(self): except Exception as err2: exc_type, exc_value, exc_traceback = sys.exc_info() self._log.error(err2) - self._log.error(repr(traceback.format_exception(exc_type, exc_value,exc_traceback))) + self._log.error(repr(traceback.format_exception(exc_type, exc_value, exc_traceback))) diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index 66299725..5089eeea 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -9,15 +9,15 @@ class EmonHubEmoncmsHTTPInterfacer(EmonHubInterfacer): def __init__(self, name): # Initialization super(EmonHubEmoncmsHTTPInterfacer, self).__init__(name) - + # add or alter any default settings for this reporter # defaults previously defined in inherited emonhub_interfacer # here we are just changing the batchsize from 1 to 100 # and the interval from 0 to 30 - self._defaults.update({'batchsize': 100,'interval': 30}) + self._defaults.update({'batchsize': 100, 'interval': 30}) # This line will stop the default values printing to logfile at start-up self._settings.update(self._defaults) - + # interfacer specific settings self._cms_settings = { 'apikey': "", @@ -25,28 +25,27 @@ def __init__(self, name): 'senddata': 1, 'sendstatus': 0 } - + # set an absolute upper limit for number of items to process per post self._item_limit = 250 - + # maximum buffer size self.buffer._maximumEntriesInBuffer = 100000 - + def _process_post(self, databuffer): """Send data to server.""" # databuffer is of format: # [[timestamp, nodeid, datavalues][timestamp, nodeid, datavalues]] # [[1399980731, 10, 150, 250 ...]] - + if not 'apikey' in self._settings.keys() or str.__len__(str(self._settings['apikey'])) != 32 \ or str.lower(str(self._settings['apikey'])) == 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx': # Return true to clear buffer if the apikey is not set return True - - + data_string = json.dumps(databuffer, separators=(',', ':')) - + # Prepare URL string of the form # http://domain.tld/emoncms/input/bulk.json?apikey=12345 # &data=[[0,10,82,23],[5,10,82,23],[10,10,82,23]] @@ -56,8 +55,8 @@ def _process_post(self, databuffer): sentat = int(time.time()) # Construct post_url (without apikey) - post_url = self._settings['url']+'/input/bulk'+'.json?apikey=' - post_body = "data="+data_string+"&sentat="+str(sentat) + post_url = self._settings['url'] + '/input/bulk.json?apikey=' + post_body = "data=" + data_string + "&sentat=" + str(sentat) # logged before apikey added for security self._log.info("sending: " + post_url + "E-M-O-N-C-M-S-A-P-I-K-E-Y&" + post_body) @@ -74,24 +73,23 @@ def _process_post(self, databuffer): self._log.debug("acknowledged receipt with '" + reply + "' from " + self._settings['url']) return True else: - self._log.warning("send failure: wanted 'ok' but got '" +reply+ "'") + self._log.warning("send failure: wanted 'ok' but got '" + reply + "'") return False - - + def sendstatus(self): if not 'apikey' in self._settings.keys() or str.__len__(str(self._settings['apikey'])) != 32 \ or str.lower(str(self._settings['apikey'])) == 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx': return - + # MYIP url - post_url = self._settings['url']+'/myip/set.json?apikey=' + post_url = self._settings['url'] + '/myip/set.json?apikey=' # Print info log self._log.info("sending: " + post_url + "E-M-O-N-C-M-S-A-P-I-K-E-Y") # add apikey post_url = post_url + self._settings['apikey'] # send request - reply = self._send_post(post_url,None) - + reply = self._send_post(post_url, None) + def set(self, **kwargs): """ @@ -99,7 +97,7 @@ def set(self, **kwargs): :return: """ - super (EmonHubEmoncmsHTTPInterfacer, self).set(**kwargs) + super(EmonHubEmoncmsHTTPInterfacer, self).set(**kwargs) for key, setting in self._cms_settings.iteritems(): #valid = False @@ -112,13 +110,10 @@ def set(self, **kwargs): elif key == 'apikey': if str.lower(setting[:4]) == 'xxxx': self._log.warning("Setting " + self.name + " apikey: obscured") - pass - elif str.__len__(setting) == 32 : + elif str.__len__(setting) == 32: self._log.info("Setting " + self.name + " apikey: set") - pass elif setting == "": self._log.info("Setting " + self.name + " apikey: null") - pass else: self._log.warning("Setting " + self.name + " apikey: invalid format") continue diff --git a/src/interfacers/EmonHubGraphiteInterfacer.py b/src/interfacers/EmonHubGraphiteInterfacer.py index 855ea954..0c63f579 100644 --- a/src/interfacers/EmonHubGraphiteInterfacer.py +++ b/src/interfacers/EmonHubGraphiteInterfacer.py @@ -10,10 +10,10 @@ def __init__(self, name): # Initialization super(EmonHubGraphiteInterfacer, self).__init__(name) - self._defaults.update({'batchsize': 100,'interval': 30}) + self._defaults.update({'batchsize': 100, 'interval': 30}) self._settings.update(self._defaults) - # interfacer specific settings + # interfacer specific settings self._graphite_settings = { 'graphite_host': 'localhost', 'graphite_port': '2003', @@ -22,59 +22,58 @@ def __init__(self, name): self.lastsent = time.time() self.lastsentstatus = time.time() - + # set an absolute upper limit for number of items to process per post self._item_limit = 250 def add(self, cargo): """Append data to buffer. - + format: {"emontx":{"power1":100,"power2":200,"power3":300}} - + """ - + nodename = str(cargo.nodeid) - if cargo.nodename: nodename = cargo.nodename - + if cargo.nodename: + nodename = cargo.nodename + f = {} f['node'] = nodename f['data'] = {} - - for i in range(0,len(cargo.realdata)): - name = str(i+1) - if i recommend firmware update ? - #self._log.info( self.name + " device firmware is pre-version RFM12demo.11") - self._log.info( self.name + " device firmware version & configuration: not available") + #self._log.info(self.name + " device firmware is pre-version RFM12demo.11") + self._log.info(self.name + " device firmware version & configuration: not available") else: self._log.warning("Device communication error - check settings") - self._rx_buf="" + self._rx_buf = "" self._ser.flushInput() # Initialize settings @@ -59,16 +58,12 @@ def __init__(self, name, com_port='/dev/ttyAMA0', com_baud=0): self._settings.update(self._defaults) # Jee specific settings to be picked up as changes not defaults to initialise "Jee" device - self._jee_settings = ({'baseid': '15', 'frequency': '433', 'group': '210', 'quiet': 'True', 'calibration': '230V'}) - self._jee_prefix = ({'baseid': 'i', 'frequency': '', 'group': 'g', 'quiet': 'q', 'calibration': 'p'}) + self._jee_settings = {'baseid': '15', 'frequency': '433', 'group': '210', 'quiet': 'True', 'calibration': '230V'} + self._jee_prefix = {'baseid': 'i', 'frequency': '', 'group': 'g', 'quiet': 'q', 'calibration': 'p'} # Pre-load Jee settings only if info string available for checks if all(i in self.info[1] for i in (" i", " g", " @ ", " MHz")): self._settings.update(self._jee_settings) - - - - def add(self, cargo): """Append data to buffer. @@ -77,13 +72,10 @@ def add(self, cargo): """ - #just send it - txc = self._process_tx(cargo) - self.send(txc) - + #just send it + txc = self._process_tx(cargo) + self.send(txc) - - def read(self): """Read data from serial port and process if complete line received. @@ -136,11 +128,11 @@ def read(self): f = f.split(' ') # Strip leading 'OK' from frame if needed - if f[0]=='OK': + if f[0] == 'OK': f = f[1:] - + # Extract RSSI value if it's available - if str(f[-1])[0]=='(' and str(f[-1])[-1]==')': + if str(f[-1])[0] == '(' and str(f[-1])[-1] == ')': r = f[-1][1:-1] try: c.rssi = int(r) @@ -169,7 +161,7 @@ def set(self, **kwargs): **kwargs (dict): settings to be modified. Available settings are 'baseid', 'frequency', 'group'. Example: {'baseid': '15', 'frequency': '4', 'group': '210'} - + """ for key, setting in self._jee_settings.iteritems(): @@ -181,26 +173,26 @@ def set(self, **kwargs): # convert bools to ints if str.capitalize(str(setting)) in ['True', 'False']: setting = int(setting == "True") - # confirmation string always contains baseid, group anf freq + # confirmation string always contains baseid, group and freq if " i" and " g" and " @ " and " MHz" in self.info[1]: # If setting confirmed as already set, continue without changing if (self._jee_prefix[key] + str(setting)) in self.info[1].split(): continue elif key in self._settings and self._settings[key] == setting: continue - if key == 'baseid' and int(setting) >=1 and int(setting) <=26: + if key == 'baseid' and int(setting) >= 1 and int(setting) <= 26: command = str(setting) + 'i' - elif key == 'frequency' and setting in ['433','868','915']: + elif key == 'frequency' and setting in ['433', '868', '915']: command = setting[:1] + 'b' - elif key == 'group'and int(setting) >=0 and int(setting) <=250: + elif key == 'group' and int(setting) >= 0 and int(setting) <= 250: command = str(setting) + 'g' - elif key == 'quiet' and int(setting) >=0 and int(setting) <2: + elif key == 'quiet' and int(setting) >= 0 and int(setting) < 2: command = str(setting) + 'q' elif key == 'calibration' and setting == '230V': command = '1p' elif key == 'calibration' and setting == '110V': command = '2p' - + else: self._log.warning("In interfacer set '%s' is not a valid setting for %s: %s" % (str(setting), self.name, key)) continue @@ -214,10 +206,10 @@ def set(self, **kwargs): super(EmonHubJeeInterfacer, self).set(**kwargs) def action(self): - """Actions that need to be done on a regular basis. - + """Actions that need to be done on a regular basis. + This should be called in main loop by instantiater. - + """ t = time.time() @@ -225,30 +217,24 @@ def action(self): # Broadcast time to synchronize emonGLCD interval = int(self._settings['interval']) if interval: # A value of 0 means don't do anything - if (t - self._interval_timestamp) > interval: + if t - self._interval_timestamp > interval: self._interval_timestamp = t now = datetime.datetime.now() self._log.debug(self.name + " broadcasting time: %02d:%02d" % (now.hour, now.minute)) self._ser.write("00,%02d,%02d,00,s" % (now.hour, now.minute)) - - def _process_post(self, databuffer): """Send data to server/broker or other output """ - for i in range(0,len(databuffer)): + for i in range(0, len(databuffer)): frame = databuffer[i] - self._log.debug("node = " + str(frame[1]) + " node_data = "+json.dumps(frame)) + self._log.debug("node = " + str(frame[1]) + " node_data = " + json.dumps(frame)) self.send(frame) - - return True - - def send (self, cargo): - + def send(self, cargo): f = cargo cmd = "s" @@ -260,14 +246,11 @@ def send (self, cargo): payload = "" for value in data: if int(value) < 0 or int(value) > 255: - self._log.warning(self.name + " discarding Tx packet: values out of scope" ) + self._log.warning(self.name + " discarding Tx packet: values out of scope") return - payload += str(int(value))+"," + payload += str(int(value)) + "," payload += cmd self._log.debug(str(f.uri) + " sent TX packet: " + payload) self._ser.write(payload) - - - diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index 558dc33d..9f7642b2 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -12,35 +12,35 @@ def __init__(self, name, mqtt_user=" ", mqtt_passwd=" ", mqtt_host="127.0.0.1", """Initialize interfacer """ - + # Initialization super(EmonHubMqttInterfacer, self).__init__(name) # set the default setting values for this interfacer self._defaults.update({'datacode': '0'}) self._settings.update(self._defaults) - + # Add any MQTT specific settings self._mqtt_settings = { # emonhub/rx/10/values format - default emoncms nodes module 'node_format_enable': 1, 'node_format_basetopic': 'emonhub/', - + # nodes/emontx/power1 format 'nodevar_format_enable': 0, 'nodevar_format_basetopic': "nodes/" } self._settings.update(self._mqtt_settings) - + self.init_settings.update({ - 'mqtt_host':mqtt_host, - 'mqtt_port':mqtt_port, - 'mqtt_user':mqtt_user, - 'mqtt_passwd':mqtt_passwd + 'mqtt_host': mqtt_host, + 'mqtt_port': mqtt_port, + 'mqtt_user': mqtt_user, + 'mqtt_passwd': mqtt_passwd }) - self._connected = False - + self._connected = False + self._mqttc = mqtt.Client() self._mqttc.on_connect = self.on_connect self._mqttc.on_disconnect = self.on_disconnect @@ -49,45 +49,46 @@ def __init__(self, name, mqtt_user=" ", mqtt_passwd=" ", mqtt_host="127.0.0.1", def add(self, cargo): """Append data to buffer. - + format: {"emontx":{"power1":100,"power2":200,"power3":300}} - + """ - + nodename = str(cargo.nodeid) - if cargo.nodename: nodename = cargo.nodename - + if cargo.nodename: + nodename = cargo.nodename + f = {} f['nodeid'] = cargo.nodeid f['node'] = nodename f['names'] = cargo.names f['data'] = cargo.realdata - + if cargo.rssi: f['rssi'] = cargo.rssi - + # This basic QoS level 1 MQTT interfacer does not require buffering # therefore we call _process_post here directly with an array # containing only the one frame. - + # _process_post will never be called from the emonhub_interfacer # run > action > flush > _process_post chain as the buffer will # always be empty. - + # This is a bit of a hack, the final approach is currently being considered - # as part of ongoing discussion on futue direction of emonhub - + # as part of ongoing discussion on future direction of emonhub + databuffer = [] databuffer.append(f) self._process_post(databuffer) - + # To re-enable buffering comment the above three lines and uncomment the following # note that at preset _process_post will not handle buffered data correctly and # no time is transmitted to the subscribing clients - + # self.buffer.storeItem(f) - - + + def _process_post(self, databuffer): if not self._connected: self._log.info("Connecting to MQTT Server") @@ -97,65 +98,63 @@ def _process_post(self, databuffer): except: self._log.info("Could not connect...") time.sleep(1.0) - + else: frame = databuffer[0] nodename = frame['node'] nodeid = frame['nodeid'] - + # ---------------------------------------------------------- # General MQTT format: emonhub/rx/emonpi/power1 ... 100 # ---------------------------------------------------------- - if int(self._settings["nodevar_format_enable"])==1: - - for i in range(0,len(frame['data'])): - inputname = str(i+1) - if i Return code: "+str(rc)) - + self._mqttc.subscribe(str(self._settings["node_format_basetopic"]) + "tx/#") + + self._log.debug("CONACK => Return code: " + str(rc)) + def on_disconnect(self, client, userdata, rc): if rc != 0: self._log.info("Unexpected disconnection") self._connected = False - + def on_subscribe(self, mqttc, obj, mid, granted_qos): self._log.info("on_subscribe") - + def on_message(self, client, userdata, msg): topic_parts = msg.topic.split("/") - + if topic_parts[0] == self._settings["node_format_basetopic"][:-1]: if topic_parts[1] == "tx": if topic_parts[3] == "values": nodeid = int(topic_parts[2]) - + payload = msg.payload realdata = payload.split(",") - self._log.debug("Nodeid: "+str(nodeid)+" values: "+msg.payload) + self._log.debug("Nodeid: " + str(nodeid) + " values: " + msg.payload) rxc = Cargo.new_cargo(realdata=realdata) rxc.nodeid = nodeid @@ -224,24 +223,24 @@ def on_message(self, client, userdata, msg): # rxc = self._process_tx(rxc) if rxc: for channel in self._settings["pubchannels"]: - + # Initialize channel if needed if not channel in self._pub_channels: self._pub_channels[channel] = [] - + # Add cargo item to channel self._pub_channels[channel].append(rxc) - + self._log.debug(str(rxc.uri) + " Sent to channel' : " + str(channel)) - + def set(self, **kwargs): """ :param kwargs: :return: """ - - super (EmonHubMqttInterfacer, self).set(**kwargs) + + super(EmonHubMqttInterfacer, self).set(**kwargs) for key, setting in self._mqtt_settings.iteritems(): #valid = False diff --git a/src/interfacers/EmonHubPacketGenInterfacer.py b/src/interfacers/EmonHubPacketGenInterfacer.py index 9f45f8bd..797864b5 100644 --- a/src/interfacers/EmonHubPacketGenInterfacer.py +++ b/src/interfacers/EmonHubPacketGenInterfacer.py @@ -64,7 +64,7 @@ def read(self): # Extract the Target id if one is expected if self._settings['targeted']: - #setting = str.capitalize(str(setting)) + #setting = str(setting).capitalize() c.target = int(values[0]) values = values[1:] datacodes = datacodes[1:] @@ -97,10 +97,10 @@ def action(self): return try: - z = urllib2.urlopen(self._settings['url'] + - "/emoncms/packetgen/getinterval.json?apikey=" - + self._settings['apikey']).read() - i = int(z[1:-1]) + z = urllib2.urlopen(self._settings['url'] + + "/emoncms/packetgen/getinterval.json?apikey=" + + self._settings['apikey']).read() + i = int(z[1:-1]) except: self._log.info("request interval not returned") return @@ -129,13 +129,10 @@ def set(self, **kwargs): elif key == 'apikey': if str.lower(setting[:4]) == 'xxxx': self._log.warning("Setting " + self.name + " apikey: obscured") - pass - elif str.__len__(setting) == 32 : + elif str.__len__(setting) == 32: self._log.info("Setting " + self.name + " apikey: set") - pass elif setting == "": self._log.info("Setting " + self.name + " apikey: null") - pass else: self._log.warning("Setting " + self.name + " apikey: invalid format") continue @@ -152,4 +149,3 @@ def set(self, **kwargs): # include kwargs from parent super(EmonHubPacketGenInterfacer, self).set(**kwargs) - diff --git a/src/interfacers/EmonHubSMASolarInterfacer.py b/src/interfacers/EmonHubSMASolarInterfacer.py index 347dd212..549e9109 100644 --- a/src/interfacers/EmonHubSMASolarInterfacer.py +++ b/src/interfacers/EmonHubSMASolarInterfacer.py @@ -9,7 +9,7 @@ bluetooth_found = True except ImportError: bluetooth_found = False - + import time import sys import traceback @@ -32,26 +32,26 @@ def __init__(self, name, inverteraddress='', inverterpincode='0000', timeinverva super(EmonHubSMASolarInterfacer, self).__init__(name) self._btSocket = None - self._inverteraddress=inverteraddress - self._inverterpincode=inverterpincode - self._port=1 - self._nodeid=int(nodeid) + self._inverteraddress = inverteraddress + self._inverterpincode = inverterpincode + self._port = 1 + self._nodeid = int(nodeid) - if packettrace==0: - self._packettrace=False + if packettrace == 0: + self._packettrace = False else: - self._packettrace=True + self._packettrace = True - self.MySerialNumber = bytearray([0x08, 0x00, 0xaa, 0xbb, 0xcc, 0xdd]); + self.MySerialNumber = bytearray([0x08, 0x00, 0xaa, 0xbb, 0xcc, 0xdd]) self._reset_packet_send_counter() self._Inverters = None #Duration in seconds - self._time_inverval = int(timeinverval) + self._time_inverval = int(timeinverval) self._InverterPasswordArray = SMASolar_library.encodeInverterPassword(self._inverterpincode) self._reset_duration_timer() - self._reset_time_to_disconnect_timer(); + self._reset_time_to_disconnect_timer() self._log.info("Reading from SMASolar every " + str(self._time_inverval) + " seconds") @@ -63,7 +63,7 @@ def _login_inverter(self): self._btSocket = self._open_bluetooth(self._inverteraddress, self._port) - #If bluetooth didnt work, exit here + #If bluetooth didn't work, exit here if self._btSocket is None: return @@ -81,9 +81,9 @@ def _login_inverter(self): SMASolar_library.logon(self._btSocket, self.mylocalBTAddress, self.MySerialNumber, self._packet_send_counter, self._InverterPasswordArray) self._increment_packet_send_counter() - self._reset_time_to_disconnect_timer(); + self._reset_time_to_disconnect_timer() - self._Inverters={} + self._Inverters = {} #TODO: We need to see what packets look like when we get multiple inverters talking to us dictInverterData = SMASolar_library.getInverterDetails(self._btSocket, self._packet_send_counter, self.mylocalBTAddress, self.MySerialNumber) @@ -95,17 +95,17 @@ def _login_inverter(self): self._log.debug(str(dictInverterData)) #Clear rogue characters from name - dictInverterData["inverterName"] = re.sub(r'[^a-zA-Z0-9]','', dictInverterData["inverterName"]) + dictInverterData["inverterName"] = re.sub(r'[^a-zA-Z0-9]', '', dictInverterData["inverterName"]) - nodeName=dictInverterData["inverterName"] + nodeName = dictInverterData["inverterName"] #Build list of inverters we communicate with self._Inverters[nodeName] = dictInverterData - self._Inverters[nodeName]["NodeId"]=self._nodeid + self._Inverters[nodeName]["NodeId"] = self._nodeid - self._log.info("Connected to SMA inverter named [" + self._Inverters[nodeName]["inverterName"] + "] with serial number ["+str(self._Inverters[nodeName]["serialNumber"]) - +"] using NodeId="+str(self._Inverters[nodeName]["NodeId"])+ ". Model "+ self._Inverters[nodeName]["TypeName"]) + self._log.info("Connected to SMA inverter named [" + self._Inverters[nodeName]["inverterName"] + "] with serial number [" + str(self._Inverters[nodeName]["serialNumber"]) + + "] using NodeId=" + str(self._Inverters[nodeName]["NodeId"]) + ". Model " + self._Inverters[nodeName]["TypeName"]) def close(self): """Close bluetooth port""" @@ -164,14 +164,14 @@ def _is_it_time(self): """ duration_of_delay = time.time() - self._last_time_reading - return (int(duration_of_delay) > self._time_inverval) + return int(duration_of_delay) > self._time_inverval def _is_it_time_to_disconnect(self): """Checks to see if 8 minutes has passed, if so, force a disconnect Return true or false """ duration_of_delay = time.time() - self._last_time_auto_disconnect - return (int(duration_of_delay) > 480) + return int(duration_of_delay) > 480 def _reset_time_to_disconnect_timer(self): """Reset timer to current date/time""" @@ -187,10 +187,11 @@ def _process_rx(self, rxc): #Override base read code from emonhub_interfacer def read(self): """Read data from inverter and process""" - if not bluetooth_found: return False + if not bluetooth_found: + return False #Wait until we are ready to read from inverter - if (self._is_it_time() == False): + if self._is_it_time() == False: return self._reset_duration_timer() @@ -206,61 +207,62 @@ def read(self): if self._btSocket is None: return - readingsToMake= {} + readingsToMake = {} - readingsToMake["EnergyProduction"]=[0x54000200, 0x00260100, 0x002622FF] + readingsToMake["EnergyProduction"] = [0x54000200, 0x00260100, 0x002622FF] #This causes problems with some inverters - #readingsToMake["SpotDCPower"]=[0x53800200, 0x00251E00, 0x00251EFF] - - readingsToMake["SpotACPower"]=[0x51000200, 0x00464000, 0x004642FF] - readingsToMake["SpotACTotalPower"]=[0x51000200, 0x00263F00, 0x00263FFF] - readingsToMake["SpotDCVoltage"]=[0x53800200, 0x00451F00, 0x004521FF] - readingsToMake["SpotACVoltage"]=[0x51000200, 0x00464800, 0x004655FF] - readingsToMake["SpotGridFrequency"]=[0x51000200, 0x00465700, 0x004657FF] - readingsToMake["OperationTime"]=[0x54000200, 0x00462E00, 0x00462FFF] - readingsToMake["InverterTemperature"]=[0x52000200, 0x00237700, 0x002377FF] + #readingsToMake["SpotDCPower"] = [0x53800200, 0x00251E00, 0x00251EFF] + + readingsToMake["SpotACPower"] = [0x51000200, 0x00464000, 0x004642FF] + readingsToMake["SpotACTotalPower"] = [0x51000200, 0x00263F00, 0x00263FFF] + readingsToMake["SpotDCVoltage"] = [0x53800200, 0x00451F00, 0x004521FF] + readingsToMake["SpotACVoltage"] = [0x51000200, 0x00464800, 0x004655FF] + readingsToMake["SpotGridFrequency"] = [0x51000200, 0x00465700, 0x004657FF] + readingsToMake["OperationTime"] = [0x54000200, 0x00462E00, 0x00462FFF] + readingsToMake["InverterTemperature"] = [0x52000200, 0x00237700, 0x002377FF] #Not very useful for reporting - #readingsToMake["MaxACPower"]=[0x51000200, 0x00411E00, 0x004120FF] - #readingsToMake["MaxACPower2"]=[0x51000200, 0x00832A00, 0x00832AFF] - #readingsToMake["GridRelayStatus"]=[0x51800200, 0x00416400, 0x004164FF] + #readingsToMake["MaxACPower"] = [0x51000200, 0x00411E00, 0x004120FF] + #readingsToMake["MaxACPower2"] = [0x51000200, 0x00832A00, 0x00832AFF] + #readingsToMake["GridRelayStatus"] = [0x51800200, 0x00416400, 0x004164FF] #Only useful on off grid battery systems - #readingsToMake["ChargeStatus"]=[0x51000200, 0x00295A00, 0x00295AFF] - #readingsToMake["BatteryInfo"]=[0x51000200, 0x00491E00, 0x00495DFF] + #readingsToMake["ChargeStatus"] = [0x51000200, 0x00295A00, 0x00295AFF] + #readingsToMake["BatteryInfo"] = [0x51000200, 0x00491E00, 0x00495DFF] #Get first inverter in dictionary - inverter=self._Inverters[ self._Inverters.keys()[0] ] - self._log.debug("Reading from inverter "+inverter["inverterName"] ) + inverter = self._Inverters[self._Inverters.keys()[0]] + self._log.debug("Reading from inverter " + inverter["inverterName"]) #Loop through dictionary and take readings, building "output" dictionary as we go output = {} for key in readingsToMake: - data=SMASolar_library.request_data(self._btSocket, - self._packet_send_counter, - self.mylocalBTAddress, - self.MySerialNumber, - readingsToMake[key][0], - readingsToMake[key][1], - readingsToMake[key][2], - inverter["susyid"],inverter["serialNumber"]) + data = SMASolar_library.request_data(self._btSocket, + self._packet_send_counter, + self.mylocalBTAddress, + self.MySerialNumber, + readingsToMake[key][0], + readingsToMake[key][1], + readingsToMake[key][2], + inverter["susyid"], + inverter["serialNumber"]) self._increment_packet_send_counter() if data is not None: output.update(SMASolar_library.extract_data(data)) - if (self._packettrace): - self._log.debug("Packet reply for "+key + ". Packet from {0:04x}/{1:08x}".format(data.getTwoByte(14),data.getFourByteLong(16)) ) + if self._packettrace: + self._log.debug("Packet reply for " + key + ". Packet from {0:04x}/{1:08x}".format(data.getTwoByte(14), data.getFourByteLong(16))) self._log.debug(data.debugViewPacket()) - #Sort the output to keep the keys in a consistant order - names= [] + #Sort the output to keep the keys in a consistent order + names = [] values = [] for key in sorted(output): - names.append( output[key].Label ) - values.append( output[key].Value ) + names.append(output[key].Label) + values.append(output[key].Value) #self._log.debug("Building cargo") c = Cargo.new_cargo() @@ -273,11 +275,11 @@ def read(self): c.nodename = inverter["inverterName"] #TODO: We should be able to populate the rssi number from the bluetooth signal strength - c.rssi=0 + c.rssi = 0 #Inverter appears to kill our connection every 10 minutes, so disconnect after 8 minutes #to avoid errors in log files - if (self._is_it_time_to_disconnect() == True): + if self._is_it_time_to_disconnect() == True: self._log.info("Disconnecting Bluetooth after timer expired") SMASolar_library.logoff(self._btSocket, self._packet_send_counter, self.mylocalBTAddress, self.MySerialNumber) self._reset_time_to_disconnect_timer() @@ -295,5 +297,5 @@ def read(self): except Exception as err2: exc_type, exc_value, exc_traceback = sys.exc_info() self._log.error(err2) - self._log.error(repr(traceback.format_exception(exc_type, exc_value,exc_traceback))) + self._log.error(repr(traceback.format_exception(exc_type, exc_value, exc_traceback))) self._btSocket = None diff --git a/src/interfacers/EmonHubSerialInterfacer.py b/src/interfacers/EmonHubSerialInterfacer.py index 055580f6..a80e3262 100644 --- a/src/interfacers/EmonHubSerialInterfacer.py +++ b/src/interfacers/EmonHubSerialInterfacer.py @@ -24,13 +24,13 @@ def __init__(self, name, com_port='', com_baud=9600): # Open serial port self._ser = self._open_serial_port(com_port, com_baud) - + # Initialize RX buffer self._rx_buf = '' def close(self): """Close serial port""" - + # Close serial port if self._ser is not None: self._log.debug("Closing serial port") @@ -60,14 +60,15 @@ def read(self): """Read data from serial port and process if complete line received. Return data as a list: [NodeID, val1, val2] - + """ - - if not self._ser: return False + + if not self._ser: + return False # Read serial RX self._rx_buf = self._rx_buf + self._ser.readline() - + # If line incomplete, exit if '\r\n' not in self._rx_buf: return @@ -91,4 +92,3 @@ def read(self): c.realdata = f[1:] return c - diff --git a/src/interfacers/EmonHubSocketInterfacer.py b/src/interfacers/EmonHubSocketInterfacer.py index e7bb561d..d06dd3dc 100644 --- a/src/interfacers/EmonHubSocketInterfacer.py +++ b/src/interfacers/EmonHubSocketInterfacer.py @@ -22,7 +22,7 @@ def __init__(self, name, port_nb=50011): super(EmonHubSocketInterfacer, self).__init__(name) # add an apikey setting - self._skt_settings = {'apikey':""} + self._skt_settings = {'apikey': ""} self._settings.update(self._skt_settings) # Open socket @@ -61,7 +61,7 @@ def read(self): """Read data from socket and process if complete line received. Return data as a list: [NodeID, val1, val2] - + """ # Check if data received @@ -73,10 +73,10 @@ def read(self): # Accept connection conn, addr = self._socket.accept() - + # Read data self._sock_rx_buf = self._sock_rx_buf + conn.recv(1024) - + # Close connection conn.close() @@ -101,23 +101,20 @@ def read(self): self._log.warning(str(c.uri) +" discarded frame: apikey not matched") return # Otherwise remove apikey from frame - f = [ v for v in f if self._settings['apikey'] not in v ] + f = [v for v in f if self._settings['apikey'] not in v] c.rawdata = ' '.join(f) - else: - pass - # Extract timestamp value if one is expected or use 0 timestamp = 0.0 if self._settings['timestamped']: - c.timestamp=f[0] + c.timestamp = f[0] f = f[1:] # Extract source's node id c.nodeid = int(f[0]) + int(self._settings['nodeoffset']) - f=f[1:] + f = f[1:] # Extract the Target id if one is expected if self._settings['targeted']: - #setting = str.capitalize(str(setting)) + #setting = str(setting).capitalize() c.target = int(f[0]) f = f[1:] # Extract list of data values @@ -127,7 +124,6 @@ def read(self): return c - def set(self, **kwargs): """ @@ -144,13 +140,10 @@ def set(self, **kwargs): elif key == 'apikey': if str.lower(setting[:4]) == 'xxxx': self._log.warning("Setting " + self.name + " apikey: obscured") - pass - elif str.__len__(setting) == 32 : + elif str.__len__(setting) == 32: self._log.info("Setting " + self.name + " apikey: set") - pass elif setting == "": self._log.info("Setting " + self.name + " apikey: null") - pass else: self._log.warning("Setting " + self.name + " apikey: invalid format") continue @@ -167,4 +160,3 @@ def set(self, **kwargs): # include kwargs from parent super(EmonHubSocketInterfacer, self).set(**kwargs) - diff --git a/src/interfacers/EmonHubTemplateInterfacer.py b/src/interfacers/EmonHubTemplateInterfacer.py index 3cb09aff..d397ab00 100644 --- a/src/interfacers/EmonHubTemplateInterfacer.py +++ b/src/interfacers/EmonHubTemplateInterfacer.py @@ -11,7 +11,7 @@ class EmonHubTemplateInterfacer(EmonHubInterfacer): def __init__(self, name, port_nb=50011): """Initialize Interfacer - + """ # Initialization @@ -22,20 +22,20 @@ def __init__(self, name, port_nb=50011): # here we are just changing the batchsize from 1 to 100 # and the interval from 0 to 30 # self._defaults.update({'batchsize': 100,'interval': 30}) - + # This line will stop the default values printing to logfile at start-up self._settings.update(self._defaults) # Interfacer specific settings # (settings not included in the inherited EmonHubInterfacer) # The set method below is called from emonhub.py on - # initialisation and settings change and copies the + # initialisation and settings change and copies the # interfacer specific settings over to _settings - + # read_interval is just an example setting here - # and can be removed and replaced with applicable settings - self._template_settings = {'read_interval':10.0} - + # and can be removed and replaced with applicable settings + self._template_settings = {'read_interval': 10.0} + # set an absolute upper limit for number of items to process per post self._item_limit = 250 @@ -43,82 +43,76 @@ def read(self): """Read data and process Return data as a list: [NodeID, val1, val2] - + """ # create a new cargo object, set data values c = Cargo.new_cargo() - + # Example cargo data # An interfacer would typically at this point # read from a socket or serial port and decode # the read data before setting the cargo object # variables c.nodeid = "test" - c.names = ["power1","power2","power3"] - c.realdata = [100,200,300] - + c.names = ["power1", "power2", "power3"] + c.realdata = [100, 200, 300] + # usually the serial port or socket will provide - # a delay as the interfacer waits at this point + # a delay as the interfacer waits at this point # to read a line of data but for testing here # we slow it down. - + time.sleep(self._settings['read_interval']) - + return c def add(self, cargo): """Append data to buffer. - + format: {"emontx":{"power1":100,"power2":200,"power3":300}} - + """ - + nodename = str(cargo.nodeid) - if cargo.nodename: nodename = cargo.nodename - + if cargo.nodename: + nodename = cargo.nodename + f = {} f['node'] = nodename f['data'] = {} - - for i in range(0,len(cargo.realdata)): - name = str(i+1) - if i self.poll_interval: - #self._log.debug(" Waiting for %s seconds "%(str(now - self.last_read))) + #self._log.debug(" Waiting for %s seconds " % (str(now - self.last_read))) # Wait to read based on poll_interval return @@ -180,13 +178,13 @@ def read(self): # Update last read time self.last_read = now # If line incomplete, exit - if self._rx_buf == None: + if self._rx_buf is None: return #Sample data looks like {'FW': '0307', 'SOC': '1000', 'Relay': 'OFF', 'PID': '0x203', 'H10': '6', 'BMV': '700', 'TTG': '-1', 'H12': '0', 'H18': '0', 'I': '0', 'H11': '0', 'Alarm': 'OFF', 'CE': '0', 'H17': '9', 'P': '0', 'AR': '0', 'V': '26719', 'H8': '29011', 'H9': '0', 'H2': '0', 'H3': '0', 'H1': '-1633', 'H6': '-5775', 'H7': '17453', 'H4': '0', 'H5': '0'} # Create a Payload object - c = Cargo.new_cargo(rawdata = self._rx_buf) + c = Cargo.new_cargo(rawdata=self._rx_buf) f = self.parse_package(self._rx_buf) f = f.split() @@ -198,7 +196,6 @@ def read(self): c.nodeid = int(self._settings['nodeoffset']) c.realdata = f[1:] else: - self._log.error("nodeoffset needed in emonhub configuratio, make sure it exits ans is integer ") - pass + self._log.error("nodeoffset needed in emonhub configuration, make sure it exists and is integer") return c diff --git a/src/interfacers/EmonModbusTcpInterfacer.py b/src/interfacers/EmonModbusTcpInterfacer.py index 9b1cf096..8c3ed6f9 100644 --- a/src/interfacers/EmonModbusTcpInterfacer.py +++ b/src/interfacers/EmonModbusTcpInterfacer.py @@ -33,46 +33,42 @@ def __init__(self, name, modbus_IP='192.168.1.10', modbus_port=0): # open connection if pymodbus_found: self._log.info("pymodbus installed") - self._log.debug("EmonModbusTcpInterfacer args: " + str(modbus_IP) + " - " + str(modbus_port) ) - self._con = self._open_modTCP(modbus_IP,modbus_port) - if self._modcon : + self._log.debug("EmonModbusTcpInterfacer args: " + str(modbus_IP) + " - " + str(modbus_port)) + self._con = self._open_modTCP(modbus_IP, modbus_port) + if self._modcon: self._log.info("Modbustcp client Connected") else: self._log.info("Connection to ModbusTCP client failed. Will try again") def set(self, **kwargs): - for key in kwargs.keys(): setting = kwargs[key] self._settings[key] = setting - self._log.debug("Setting " + self.name + " %s: %s" % (key, setting) ) + self._log.debug("Setting " + self.name + " %s: %s" % (key, setting)) def close(self): - # Close TCP connection if self._con is not None: self._log.debug("Closing tcp port") self._con.close() - def _open_modTCP(self,modbus_IP,modbus_port): + def _open_modTCP(self, modbus_IP, modbus_port): """ Open connection to modbus device """ try: - c = ModbusClient(modbus_IP,modbus_port) + c = ModbusClient(modbus_IP, modbus_port) if c.connect(): - self._log.info("Opening modbusTCP connection: " + str(modbus_port) + " @ " +str(modbus_IP)) + self._log.info("Opening modbusTCP connection: " + str(modbus_port) + " @ " + str(modbus_IP)) self._modcon = True else: self._log.debug("Connection failed") self._modcon = False except Exception as e: self._log.error("modbusTCP connection failed" + str(e)) - #raise EmonHubInterfacerInitError('Could not open connection to host %s' %modbus_IP) - pass + #raise EmonHubInterfacerInitError('Could not open connection to host %s' %modbus_IP) else: return c - def read(self): """ Read registers from client""" if pymodbus_found: @@ -82,35 +78,33 @@ def read(self): # valid datacodes list and number of registers associated # in modbus protocol, one register is 16 bits or 2 bytes valid_datacodes = ({'h': 1, 'H': 1, 'i': 2, 'l': 2, 'I': 2, 'L': 2, 'f': 2, 'q': 4, 'Q': 4, 'd': 4}) - - if not self._modcon : + + if not self._modcon: self._con.close() self._log.info("Not connected, retrying connect" + str(self.init_settings)) - self._con = self._open_modTCP(self.init_settings["modbus_IP"],self.init_settings["modbus_port"]) + self._con = self._open_modTCP(self.init_settings["modbus_IP"], self.init_settings["modbus_port"]) - if self._modcon : - + if self._modcon: # fetch nodeid if 'nodeId' in self._settings: node = str(self._settings["nodeId"]) else: self._log.error("please provide a nodeId") return - + # stores registers if 'register' in self._settings: registers = self._settings["register"] else: self._log.error("please provide a register number or a list of registers") return - + # fetch unitids if present if "nUnit" in self._settings: UnitIds = self._settings["nUnit"] else: UnitIds = None - - + # stores names # fetch datacode or datacodes if node in ehc.nodelist and 'rx' in ehc.nodelist[node]: @@ -123,9 +117,9 @@ def read(self): else: self._log.error("please provide a datacode or a list of datacodes") return - + # check if number of registers and number of names are the same - if len(rNames) != len (registers): + if len(rNames) != len(registers): self._log.error("You have to define an equal number of registers and of names") return # check if number of names and number of datacodes are the same @@ -133,9 +127,9 @@ def read(self): if len(datacodes) != len(rNames): self._log.error("You are using datacodes. You have to define an equal number of datacodes and of names") return - - # calculate expected size in bytes and search for invalid datacode(s) - expectedSize=0 + + # calculate expected size in bytes and search for invalid datacode(s) + expectedSize = 0 if datacodes is not None: for code in datacodes: if code not in valid_datacodes: @@ -144,7 +138,7 @@ def read(self): self._log.debug("-" * 46) return else: - expectedSize+=valid_datacodes[code]*2 + expectedSize += valid_datacodes[code] * 2 else: if datacode not in valid_datacodes: self._log.debug("-" * 46) @@ -152,10 +146,10 @@ def read(self): self._log.debug("-" * 46) return else: - expectedSize=len(rNames)*valid_datacodes[datacode]*2 - - self._log.debug("expected bytes number after encoding: "+str(expectedSize)) - + expectedSize = len(rNames) * valid_datacodes[datacode] * 2 + + self._log.debug("expected bytes number after encoding: " + str(expectedSize)) + # at this stage, we don't have any invalid datacode(s) # so we can loop and read registers for idx, rName in enumerate(rNames): @@ -166,27 +160,27 @@ def read(self): unitId = 1 if datacodes is not None: datacode = datacodes[idx] - + self._log.debug("datacode " + datacode) qty = valid_datacodes[datacode] self._log.debug("reading register # :" + str(register) + ", qty #: " + str(qty) + ", unit #: " + str(unitId)) - + try: - self.rVal = self._con.read_holding_registers(register-1,qty,unit=unitId) - assert(self.rVal.function_code < 0x80) + self.rVal = self._con.read_holding_registers(register - 1, qty, unit=unitId) + assert self.rVal.function_code < 0x80 except Exception as e: - self._log.error("Connection failed on read of register: " +str(register) + " : " + str(e)) + self._log.error("Connection failed on read of register: " + str(register) + " : " + str(e)) self._modcon = False #missing datas will lead to an incorrect encoding #we have to drop the payload return else: - #self._log.debug("register value:" + str(self.rVal.registers)+" type= " + str(type(self.rVal.registers))) + #self._log.debug("register value:" + str(self.rVal.registers) + " type= " + str(type(self.rVal.registers))) #f = f + self.rVal.registers decoder = BinaryPayloadDecoder.fromRegisters(self.rVal.registers, byteorder=Endian.Big, wordorder=Endian.Big) - if datacode == 'h': + if datacode == 'h': rValD = decoder.decode_16bit_int() - elif datacode == 'H': + elif datacode == 'H': rValD = decoder.decode_16bit_uint() elif datacode == 'i': rValD = decoder.decode_32bit_int() @@ -197,28 +191,26 @@ def read(self): elif datacode == 'L': rValD = decoder.decode_32bit_uint() elif datacode == 'f': - rValD = decoder.decode_32bit_float()*10 + rValD = decoder.decode_32bit_float() * 10 elif datacode == 'q': rValD = decoder.decode_64bit_int() elif datacode == 'Q': rValD = decoder.decode_64bit_uint() elif datacode == 'd': - rValD = decoder.decode_64bit_float()*10 - - t = ehc.encode(datacode,rValD) + rValD = decoder.decode_64bit_float() * 10 + + t = ehc.encode(datacode, rValD) f = f + list(t) self._log.debug("Encoded value: " + str(t)) self._log.debug("value: " + str(rValD)) - + #test if payload length is OK if len(f) == expectedSize: - self._log.debug("payload size OK (" + str(len(f)) +")") + self._log.debug("payload size OK (" + str(len(f)) + ")") self._log.debug("reporting data: " + str(f)) c.nodeid = node - c.realdata = f + c.realdata = f self._log.debug("Return from read data: " + str(c.realdata)) return c else: self._log.error("incorrect payload size :" + str(len(f)) + " expecting " + str(expectedSize)) - return - \ No newline at end of file diff --git a/src/interfacers/__init__.py b/src/interfacers/__init__.py index 77d0ed06..1e6ad7eb 100644 --- a/src/interfacers/__init__.py +++ b/src/interfacers/__init__.py @@ -1,18 +1,18 @@ __all__ = [ - "EmonHubSocketInterfacer", - "EmonHubSerialInterfacer", - "EmonHubJeeInterfacer", - "EmonHubPacketGenInterfacer", - "EmonHubEmoncmsHTTPInterfacer", - "EmonHubMqttInterfacer", - - "EmonHubTx3eInterfacer", - "EmonHubVEDirectInterfacer", - # "EmonHubSmilicsInterfacer", - "EmonHubSMASolarInterfacer", - "EmonHubGraphiteInterfacer", - "EmonHubBMWInterfacer", - "EmonModbusTcpInterfacer", - "EmonHubTemplateInterfacer" - #"EmonFroniusModbusTcpInterfacer" + "EmonHubSocketInterfacer", + "EmonHubSerialInterfacer", + "EmonHubJeeInterfacer", + "EmonHubPacketGenInterfacer", + "EmonHubEmoncmsHTTPInterfacer", + "EmonHubMqttInterfacer", + + "EmonHubTx3eInterfacer", + "EmonHubVEDirectInterfacer", + # "EmonHubSmilicsInterfacer", + "EmonHubSMASolarInterfacer", + "EmonHubGraphiteInterfacer", + "EmonHubBMWInterfacer", + "EmonModbusTcpInterfacer", + "EmonHubTemplateInterfacer" + #"EmonFroniusModbusTcpInterfacer" ] diff --git a/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py b/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py index b651de1e..9d96c95d 100644 --- a/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py +++ b/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py @@ -16,27 +16,27 @@ def __init__(self, name, modbus_IP='192.168.1.10', modbus_port=502): com_port (string): path to COM port """ -# Initialization + # Initialization super(EmonFroniusModbusTcpInterfacer, self).__init__(name) - # Connection opened by parent class INIT - # Retrieve fronius specific inverter info if connection successfull if connection successfull - self._log.debug("Fronius args: " + str(modbus_IP) + " - " + str(modbus_port) ) + # Connection opened by parent class INIT + # Retrieve Fronius specific inverter info if connection successful + self._log.debug("Fronius args: " + str(modbus_IP) + " - " + str(modbus_port)) self._log.debug("EmonFroniusModbusTcpInterfacer: Init") - if self._modcon : + if self._modcon: # Display device firmware version and current settings - self.info =["",""] + self.info =["", ""] #self._log.info("Modtcp Connected") - r2= self._con.read_holding_registers(40005-1,4,unit=1) - r3= self._con.read_holding_registers(40021-1,4,unit=1) + r2 = self._con.read_holding_registers(40005 - 1, 4, unit=1) + r3 = self._con.read_holding_registers(40021 - 1, 4, unit=1) invBrand = BinaryPayloadDecoder.fromRegisters(r2.registers, endian=Endian.Big) invModel = BinaryPayloadDecoder.fromRegisters(r3.registers, endian=Endian.Big) - self._log.info( self.name + " Inverter: " + invBrand.decode_string(8) + " " + invModel.decode_string(8)) - swDM= self._con.read_holding_registers(40037-1,8,unit=1) - swInv= self._con.read_holding_registers(40045-1,8,unit=1) + self._log.info(self.name + " Inverter: " + invBrand.decode_string(8) + " " + invModel.decode_string(8)) + swDM = self._con.read_holding_registers(40037 - 1, 8, unit=1) + swInv = self._con.read_holding_registers(40045 - 1, 8, unit=1) swDMdecode = BinaryPayloadDecoder.fromRegisters(swDM.registers, endian=Endian.Big) swInvdecode = BinaryPayloadDecoder.fromRegisters(swInv.registers, endian=Endian.Big) - self._log.info( self.name + " SW Versions: Datamanager " + swDMdecode.decode_string(16) + "- Inverter " + swInvdecode.decode_string(16)) - r1 = self._con.read_holding_registers(40070-1,1,unit=1) + self._log.info(self.name + " SW Versions: Datamanager " + swDMdecode.decode_string(16) + "- Inverter " + swInvdecode.decode_string(16)) + r1 = self._con.read_holding_registers(40070 - 1, 1, unit=1) ssModel = BinaryPayloadDecoder.fromRegisters(r1.registers, endian=Endian.Big) - self._log.info( self.name + " SunSpec Model: " + str(ssModel.decode_16bit_uint()) ) + self._log.info(self.name + " SunSpec Model: " + str(ssModel.decode_16bit_uint())) diff --git a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py index abacec2b..ae5355ce 100644 --- a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py +++ b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py @@ -66,23 +66,23 @@ def run(self): if rxc: for channel in self._settings["pubchannels"]: self._log.debug(str(rxc.uri) + " Sent to channel(start)' : " + str(channel)) - + # Initialize channel if needed if not channel in self._pub_channels: self._pub_channels[channel] = [] - + # Add cargo item to channel self._pub_channels[channel].append(rxc) - + self._log.debug(str(rxc.uri) + " Sent to channel(end)' : " + str(channel)) - # Don't loop to fast + # Don't loop too fast time.sleep(0.1) self.close() def _process_rx(self, smilics_dict): - """ Converts the data recieved on the webserver to an instance of + """ Converts the data received on the webserver to an instance of the Cargo class Args: diff --git a/src/smalibrary/SMABluetoothPacket.py b/src/smalibrary/SMABluetoothPacket.py index a1378878..ceb2514c 100644 --- a/src/smalibrary/SMABluetoothPacket.py +++ b/src/smalibrary/SMABluetoothPacket.py @@ -29,7 +29,8 @@ def getLevel2Payload(self): def pushRawByteArray(self, barray): # Raw byte array - for bte in barray: self.pushRawByte(bte) + for bte in barray: + self.pushRawByte(bte) def pushRawByte(self, value): # Accept a byte of ESCAPED data (ie. raw byte from Bluetooth) @@ -37,7 +38,8 @@ def pushRawByte(self, value): self.RawByteArray.append(value) def pushUnescapedByteArray(self, barray): - for bte in barray: self.pushUnescapedByte(bte) + for bte in barray: + self.pushUnescapedByte(bte) def pushUnescapedByte(self, value): # Store the raw byte @@ -66,13 +68,13 @@ def pushEscapedByte(self, value): previousUnescapedByte = 0 if len(self.RawByteArray) > 0: - previousUnescapedByte = self.RawByteArray[ len(self.RawByteArray) - 1 ]; + previousUnescapedByte = self.RawByteArray[len(self.RawByteArray) - 1] # Store the raw byte as it was received into RawByteArray self.RawByteArray.append(value) # did we receive the escape char in previous byte? - if (len(self.RawByteArray) > 0 and previousUnescapedByte == 0x7d): + if len(self.RawByteArray) > 0 and previousUnescapedByte == 0x7d: self.UnescapedArray[len(self.UnescapedArray) - 1] = value ^ 0x20 else: # Unescaped array is same as raw array @@ -101,7 +103,8 @@ def getByte(self, indexfromstartofdatapayload): return self.UnescapedArray[indexfromstartofdatapayload] def pushEscapedByteArray(self, barray): - for bte in barray: self.pushEscapedByte(bte) + for bte in barray: + self.pushEscapedByte(bte) def TotalUnescapedPacketLength(self): return len(self.UnescapedArray) + self.headerlength @@ -116,7 +119,7 @@ def ValidateHeaderChecksum(self): # Thanks to # http://groups.google.com/group/sma-bluetooth/browse_thread/thread/50fe13a7c39bdce0/2caea56cdfb3a68a#2caea56cdfb3a68a # for this checksum information !! - return (self.header[0] ^ self.header[1] ^ self.header[2] ^ self.header[3]) == 0 + return (self.header[0] ^ self.header[1] ^ self.header[2] ^ self.header[3]) == 0 def __init__(self, length1, length2, checksum=0, cmd1=0, cmd2=0, SourceAddress=bytearray(), DestinationAddress=bytearray([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])): self.headerlength = 18 @@ -134,5 +137,5 @@ def __init__(self, length1, length2, checksum=0, cmd1=0, cmd2=0, SourceAddress=b self.UnescapedArray = bytearray() self.setCommandCode(cmd1, cmd2) - if (checksum > 0) and (self.ValidateHeaderChecksum() == False): + if checksum > 0 and self.ValidateHeaderChecksum() == False: raise Exception("Invalid header checksum!") diff --git a/src/smalibrary/SMANET2PlusPacket.py b/src/smalibrary/SMANET2PlusPacket.py index ed4a7b3a..f9f60f7f 100644 --- a/src/smalibrary/SMANET2PlusPacket.py +++ b/src/smalibrary/SMANET2PlusPacket.py @@ -10,7 +10,7 @@ class SMANET2PlusPacket: """Holds a second type of SMA protocol packet""" - def __init__(self, ctrl1=0, ctrl2=0, packetcount=0, InverterCodeArray=bytearray(), a=0, b=0, c=0,SusyID=0xFFFF,DestinationAddress=0xFFFFFFFF ): + def __init__(self, ctrl1=0, ctrl2=0, packetcount=0, InverterCodeArray=bytearray(), a=0, b=0, c=0, SusyID=0xFFFF, DestinationAddress=0xFFFFFFFF): self.packet = bytearray() self.FCSChecksum = 0xffff @@ -60,10 +60,10 @@ def __init__(self, ctrl1=0, ctrl2=0, packetcount=0, InverterCodeArray=bytearray( 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 ]) - if (ctrl1 != 0 or ctrl2 != 0): + if ctrl1 != 0 or ctrl2 != 0: self.pushLong(0x656003FF) - self.pushByte(ctrl1); - self.pushByte(ctrl2); + self.pushByte(ctrl1) + self.pushByte(ctrl2) #Who are we sending this packet to? #SusyID (FFFFF = any SusyID) @@ -71,11 +71,11 @@ def __init__(self, ctrl1=0, ctrl2=0, packetcount=0, InverterCodeArray=bytearray( #Serial (FFFFFFFF is any) self.pushLong(DestinationAddress) - self.pushByte(a); - self.pushByte(b); - self.pushByteArray(InverterCodeArray); - self.pushByte(0x00); - self.pushByte(c); + self.pushByte(a) + self.pushByte(b) + self.pushByteArray(InverterCodeArray) + self.pushByte(0x00) + self.pushByte(c) self.pushLong(0x00000000) self.pushShort(packetcount | 0x8000) @@ -85,14 +85,14 @@ def getByte(self, offset): def getTwoByte(self, offset): value = self.packet[offset] * math.pow(256, 0) value += self.packet[offset + 1] * math.pow(256, 1) - return long(value); + return long(value) def getFourByteLong(self, offset): value = self.packet[offset] * math.pow(256, 0) value += self.packet[offset + 1] * math.pow(256, 1) value += self.packet[offset + 2] * math.pow(256, 2) value += self.packet[offset + 3] * math.pow(256, 3) - return long(value); + return long(value) def getEightByte(self, offset): return self.packet[offset] * math.pow(256, 0) \ @@ -150,7 +150,8 @@ def calculateFCS(self): myfcs ^= 0xffff def pushByteArray(self, barray): - for bte in barray: self.pushByte(bte) + for bte in barray: + self.pushByte(bte) def pushByte(self, value): self.FCSChecksum = (self.FCSChecksum >> 8) ^ self.fcstab[(self.FCSChecksum ^ value) & 0xff] @@ -161,7 +162,7 @@ def pushShort(self, value): self.pushByte((value >> 0) & 0xFF) self.pushByte((value >> 8) & 0xFF) - def pushLongs(self, value1,value2,value3): + def pushLongs(self, value1, value2, value3): self.pushLong(value1) self.pushLong(value2) self.pushLong(value3) @@ -177,13 +178,13 @@ def getBytesForSending(self): outputpacket = bytearray() realLength = 0 - # //Header byte + # Header byte outputpacket.append(0x7e) realLength += 1 - # //Copy packet to output escaping values along the way + # Copy packet to output escaping values along the way for value in self.packet: - if (value == 0x7d) or (value == 0x7e) or (value == 0x11) or (value == 0x12) or (value == 0x13): + if value == 0x7d or value == 0x7e or value == 0x11 or value == 0x12 or value == 0x13: outputpacket.append(0x7d) # byte to indicate escape character outputpacket.append(value ^ 0x20) realLength += 1 @@ -203,9 +204,9 @@ def getBytesForSending(self): outputpacket.append(0x7e) realLength += 1 - # print "Packet length {0} vs {1}".format(realLength,self.totalCalculatedPacketLength()) + # print "Packet length {0} vs {1}".format(realLength, self.totalCalculatedPacketLength()) - if (self.totalCalculatedPacketLength() != realLength): + if self.totalCalculatedPacketLength() != realLength: raise Exception("Packet length is incorrect {0} vs {1}".format(realLength, self.totalCalculatedPacketLength())) return outputpacket @@ -213,7 +214,7 @@ def getBytesForSending(self): def debugViewPacket(self): str_list = [] - pos = 0; + pos = 0 #str_list.append("L2 ARRAY LENGTH = {0}".format(len(self.packet))) str_list.append("L2 {0:04x} START = {1:02x}".format(pos, 0x7e)) @@ -226,7 +227,7 @@ def debugViewPacket(self): pos += 1 str_list.append("L2 {0:04x} ?= {1:02x}".format(pos, self.packet[pos])) pos += 1 - str_list.append("L2 {0:04x} susyid= {1:02x} {2:02x}".format(pos, self.packet[pos + 0],self.packet[pos + 1])) + str_list.append("L2 {0:04x} susyid= {1:02x} {2:02x}".format(pos, self.packet[pos + 0], self.packet[pos + 1])) pos += 2 str_list.append("L2 {0:04x} Add1= {1:02x} {2:02x} {3:02x} {4:02x}".format(pos, self.packet[pos + 0], self.packet[pos + 1], @@ -263,7 +264,7 @@ def debugViewPacket(self): s = "" for j in range(pos, len(self.packet)): - if (j % 16 == 0 or j == pos): + if j % 16 == 0 or j == pos: s += "\n %08x: " % j s += "%02x " % self.packet[j] diff --git a/src/smalibrary/SMASolar_library.py b/src/smalibrary/SMASolar_library.py index 53482546..3d513715 100644 --- a/src/smalibrary/SMASolar_library.py +++ b/src/smalibrary/SMASolar_library.py @@ -15,7 +15,7 @@ # https://github.com/Rincewind76/SMAInverter/blob/master/76_SMAInverter.pm # https://sbfspot.codeplex.com/ (credit back to myself!!) -def Read_Level1_Packet_From_BT_Stream(btSocket,mylocalBTAddress): +def Read_Level1_Packet_From_BT_Stream(btSocket, mylocalBTAddress): while True: #print "Waiting for SMA level 1 packet from Bluetooth stream" start = btSocket.recv(1) @@ -52,7 +52,7 @@ def read_SMA_BT_Packet(btSocket, waitPacketNumber=0, waitForPacket=False, myloca #else: # print "Waiting for reply to any packet" - bluetoothbuffer = Read_Level1_Packet_From_BT_Stream(btSocket,mylocalBTAddress) + bluetoothbuffer = Read_Level1_Packet_From_BT_Stream(btSocket, mylocalBTAddress) v = namedtuple("SMAPacket", ["levelone", "leveltwo"], verbose=False) v.levelone = bluetoothbuffer @@ -72,8 +72,8 @@ def read_SMA_BT_Packet(btSocket, waitPacketNumber=0, waitForPacket=False, myloca # print "Level 2 packet length (according to packet): %d" % level2Packet.totalCalculatedPacketLength() # Loop until we have the entire packet rebuilt (may take several level 1 packets) - while (bluetoothbuffer.CommandCode() != 0x0001) and (bluetoothbuffer.lastByte() != 0x7e): - bluetoothbuffer = Read_Level1_Packet_From_BT_Stream(btSocket,mylocalBTAddress) + while bluetoothbuffer.CommandCode() != 0x0001 and bluetoothbuffer.lastByte() != 0x7e: + bluetoothbuffer = Read_Level1_Packet_From_BT_Stream(btSocket, mylocalBTAddress) level2Packet.pushByteArray(bluetoothbuffer.getLevel2Payload()) v.levelone = bluetoothbuffer @@ -108,7 +108,7 @@ def encodeInverterPassword(InverterPassword): a = bytearray(InverterPassword) - for i in range( 12- len(a)): + for i in range(12 - len(a)): a.append(0) for i in range(len(a)): @@ -120,25 +120,24 @@ def encodeInverterPassword(InverterPassword): return a def getInverterDetails(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber): - - DeviceClass={} - DeviceClass['8000']='AllDevices' - DeviceClass['8001']='SolarInverter' - DeviceClass['8002']='WindTurbineInverter' - DeviceClass['8007']='BatteryInverter' - DeviceClass['8033']='Consumer' - DeviceClass['8064']='SensorSystem' - DeviceClass['8065']='ElectricityMeter' - DeviceClass['8128']='CommunicationProduct' - - DeviceType={} - DeviceType['9073']='SB 3000HF-30' - DeviceType['9074']='SB 3000TL-21' - DeviceType['9075']='SB 4000TL-21' - DeviceType['9076']='SB 5000TL-21' - DeviceType['9119']='Sunny HomeManager' - - data=request_data(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber, 0x58000200, 0x00821E00, 0x008220FF) + DeviceClass = {} + DeviceClass['8000'] = 'AllDevices' + DeviceClass['8001'] = 'SolarInverter' + DeviceClass['8002'] = 'WindTurbineInverter' + DeviceClass['8007'] = 'BatteryInverter' + DeviceClass['8033'] = 'Consumer' + DeviceClass['8064'] = 'SensorSystem' + DeviceClass['8065'] = 'ElectricityMeter' + DeviceClass['8128'] = 'CommunicationProduct' + + DeviceType = {} + DeviceType['9073'] = 'SB 3000HF-30' + DeviceType['9074'] = 'SB 3000TL-21' + DeviceType['9075'] = 'SB 4000TL-21' + DeviceType['9076'] = 'SB 5000TL-21' + DeviceType['9119'] = 'Sunny HomeManager' + + data = request_data(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber, 0x58000200, 0x00821E00, 0x008220FF) reply = {} @@ -148,66 +147,65 @@ def getInverterDetails(btSocket, packet_send_counter, mylocalBTAddress, MySerial # idate = bluetoothbuffer.leveltwo.getFourByteLong(40 + 4) # t = time.localtime(long(idate)) - offset=40 + offset = 40 valuetype = data.getTwoByte(offset + 1) if valuetype == 0x821e: reply["inverterName"] = data.getArray()[48:62].decode("utf-8") - offset+=40 + offset += 40 valuetype = data.getTwoByte(offset + 1) #INV_CLASS if valuetype == 0x821F: - idx=8 + idx = 8 while idx < 40: attribute = data.getFourByteLong(offset + idx) & 0x00FFFFFF status = data.getByte(offset + idx + 3) - if (attribute == 0xFFFFFE): + if attribute == 0xFFFFFE: break - if (status == 1): - reply["Class"]=attribute & 0x0000FFFF + if status == 1: + reply["Class"] = attribute & 0x0000FFFF if str(attribute & 0x0000FFFF) in DeviceClass: - reply["ClassName"]=DeviceClass[str(attribute & 0x0000FFFF)] + reply["ClassName"] = DeviceClass[str(attribute & 0x0000FFFF)] else: - reply["ClassName"]="Unknown" + reply["ClassName"] = "Unknown" break - idx+=4 + idx += 4 - offset+=40 + offset += 40 #INV_TYPE valuetype = data.getTwoByte(offset + 1) if valuetype == 0x8220: - idx=8 + idx = 8 while idx < 40: attribute = data.getFourByteLong(offset + idx) & 0x00FFFFFF status = data.getByte(offset + idx + 3) - if (attribute == 0xFFFFFE): + if attribute == 0xFFFFFE: break - if (status == 1): - reply["Type"]=attribute & 0x0000FFFF + if status == 1: + reply["Type"] = attribute & 0x0000FFFF if str(attribute & 0x0000FFFF) in DeviceType: - reply["TypeName"]=DeviceType[str(attribute & 0x0000FFFF)] + reply["TypeName"] = DeviceType[str(attribute & 0x0000FFFF)] else: - reply["TypeName"]="Unknown" + reply["TypeName"] = "Unknown" break - idx+=4 - - offset+=40 + idx += 4 + offset += 40 return reply -def logon(btSocket,mylocalBTAddress,MySerialNumber,packet_send_counter, InverterPasswordArray): +def logon(btSocket, mylocalBTAddress, MySerialNumber, packet_send_counter, InverterPasswordArray): # Logon to inverter - pluspacket1 = SMANET2PlusPacket(0x0e, 0xa0, packet_send_counter, MySerialNumber, 0x00, 0x01, 0x01) + pluspacket1 = SMANET2PlusPacket(0x0e, 0xa0, packet_send_counter, MySerialNumber, 0x00, 0x01, 0x01) pluspacket1.pushLong(0xFFFD040C) #0x07 = User logon, 0x0a = installer logon pluspacket1.pushLong(0x00000007) @@ -231,39 +229,37 @@ def logon(btSocket,mylocalBTAddress,MySerialNumber,packet_send_counter, Inverter if bluetoothbuffer.leveltwo.errorCode() > 0: raise Exception("Error code returned from inverter - during logon - wrong password?") - - -def initaliseSMAConnection(btSocket,mylocalBTAddress,MySerialNumber,packet_send_counter): +def initaliseSMAConnection(btSocket, mylocalBTAddress, MySerialNumber, packet_send_counter): # Wait for 1st message from inverter to arrive (should be an 0002 command) - bluetoothbuffer = read_SMA_BT_Packet(btSocket,mylocalBTAddress) - checkPacketReply(bluetoothbuffer,0x0002); + bluetoothbuffer = read_SMA_BT_Packet(btSocket, mylocalBTAddress) + checkPacketReply(bluetoothbuffer, 0x0002) - netid = bluetoothbuffer.levelone.getByte(4); + netid = bluetoothbuffer.levelone.getByte(4) #print "netid=%02x" % netid - inverterAddress = bluetoothbuffer.levelone.SourceAddress; + inverterAddress = bluetoothbuffer.levelone.SourceAddress # Reply to 0x0002 cmd with our data - send = SMABluetoothPacket(0x1F, 0x00, 0x00, 0x02, 0x00, mylocalBTAddress, inverterAddress); - send.pushUnescapedByteArray( bytearray([0x00, 0x04, 0x70, 0x00, + send = SMABluetoothPacket(0x1F, 0x00, 0x00, 0x02, 0x00, mylocalBTAddress, inverterAddress) + send.pushUnescapedByteArray(bytearray([0x00, 0x04, 0x70, 0x00, netid, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00]) ) + 0x01, 0x00, 0x00, 0x00])) send.finish() - send.sendPacket(btSocket); + send.sendPacket(btSocket) # Receive 0x000a cmd - bluetoothbuffer = read_SMA_BT_Packet(btSocket,mylocalBTAddress); - checkPacketReply(bluetoothbuffer,0x000a); + bluetoothbuffer = read_SMA_BT_Packet(btSocket, mylocalBTAddress) + checkPacketReply(bluetoothbuffer, 0x000a) - # Receive 0x000c cmd (sometimes this doesnt turn up!) - bluetoothbuffer = read_SMA_BT_Packet(btSocket,mylocalBTAddress); + # Receive 0x000c cmd (sometimes this doesn't turn up!) + bluetoothbuffer = read_SMA_BT_Packet(btSocket, mylocalBTAddress) if bluetoothbuffer.levelone.CommandCode() != 0x0005 and bluetoothbuffer.levelone.CommandCode() != 0x000c: - raise Exception("Expected different command 0x0005 or 0x000c"); + raise Exception("Expected different command 0x0005 or 0x000c") - # Receive 0x0005 if we didnt get it above + # Receive 0x0005 if we didn't get it above if bluetoothbuffer.levelone.CommandCode() != 0x0005: - bluetoothbuffer = read_SMA_BT_Packet(btSocket,mylocalBTAddress) - checkPacketReply(bluetoothbuffer,0x0005) + bluetoothbuffer = read_SMA_BT_Packet(btSocket, mylocalBTAddress) + checkPacketReply(bluetoothbuffer, 0x0005) # Now the fun begins... @@ -276,8 +272,8 @@ def initaliseSMAConnection(btSocket,mylocalBTAddress,MySerialNumber,packet_send_ send.finish() send.sendPacket(btSocket) - bluetoothbuffer = read_SMA_BT_Packet(btSocket, packet_send_counter, True,mylocalBTAddress) - checkPacketReply(bluetoothbuffer,0x0001) + bluetoothbuffer = read_SMA_BT_Packet(btSocket, packet_send_counter, True, mylocalBTAddress) + checkPacketReply(bluetoothbuffer, 0x0001) if bluetoothbuffer.leveltwo.errorCode() > 0: raise Exception("Error code returned from inverter") @@ -295,9 +291,9 @@ def initaliseSMAConnection(btSocket,mylocalBTAddress,MySerialNumber,packet_send_ #send.sendPacket(btSocket) #packet_send_counter += 1 -def checkPacketReply(bluetoothbuffer,commandcode): +def checkPacketReply(bluetoothbuffer, commandcode): if bluetoothbuffer.levelone.CommandCode() != commandcode: - raise Exception("Expected command 0x{0:04x} received 0x{1:04x}".format(commandcode,bluetoothbuffer.levelone.CommandCode())) + raise Exception("Expected command 0x{0:04x} received 0x{1:04x}".format(commandcode, bluetoothbuffer.levelone.CommandCode())) def logoff(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber): p1 = SMABluetoothPacket(0x01, 0x01, 0x00, 0x01, 0x00, mylocalBTAddress) @@ -307,10 +303,8 @@ def logoff(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber): p1.pushRawByteArray(data.getBytesForSending()) p1.finish() p1.sendPacket(btSocket) - return def request_data(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber, cmd, first, last, susyid=0xFFFF, destinationAddress=0xFFFFFFFF): - send9 = SMABluetoothPacket(0x01, 0x01, 0x00, 0x01, 0x00, mylocalBTAddress) pluspacket9 = SMANET2PlusPacket(0x09, 0xA0, packet_send_counter, MySerialNumber, 0x00, 0x00, 0x00, susyid, destinationAddress) pluspacket9.pushLong(cmd) @@ -319,17 +313,17 @@ def request_data(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber send9.pushRawByteArray(pluspacket9.getBytesForSending()) send9.finish() send9.sendPacket(btSocket) - bluetoothbuffer = read_SMA_BT_Packet(btSocket, packet_send_counter, True,mylocalBTAddress) + bluetoothbuffer = read_SMA_BT_Packet(btSocket, packet_send_counter, True, mylocalBTAddress) if bluetoothbuffer.leveltwo.errorCode() > 0: return None - leveltwo=bluetoothbuffer.leveltwo + leveltwo = bluetoothbuffer.leveltwo - if (susyid!=0xFFFF and leveltwo.getDestinationSusyid() != susyid): + if susyid != 0xFFFF and leveltwo.getDestinationSusyid() != susyid: raise Exception("request_data reply susy id mismatch") - if (destinationAddress!=0xFFFFFFFF and leveltwo.getDestinationSerial() != destinationAddress): + if destinationAddress != 0xFFFFFFFF and leveltwo.getDestinationSerial() != destinationAddress: raise Exception("request_data reply destination address mismatch") if leveltwo.errorCode() != 0: @@ -338,14 +332,13 @@ def request_data(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber #If no errors return packet return leveltwo - def extract_data(level2Packet): #Return a dictionary outputlist = {} spotvaluelist = {} - SpotValue=namedtuple("spotvalue", ["Description", "Scale", "RecSize"]) + SpotValue = namedtuple("spotvalue", ["Description", "Scale", "RecSize"]) spotvaluelist[0x263f] = SpotValue("ACTotalPower", 1, 28) spotvaluelist[0x411e] = SpotValue("Ph1ACMax", 1, 28) spotvaluelist[0x411f] = SpotValue("Ph2ACMax", 1, 28) @@ -353,86 +346,79 @@ def extract_data(level2Packet): spotvaluelist[0x4640] = SpotValue("Ph1Power", 1, 28) spotvaluelist[0x4641] = SpotValue("Ph2Power", 1, 28) spotvaluelist[0x4642] = SpotValue("Ph3Power", 1, 28) - spotvaluelist[0x4648] = SpotValue("Ph1ACVolt",100, 28) - spotvaluelist[0x4649] = SpotValue("Ph2ACVolt",100, 28) - spotvaluelist[0x464a] = SpotValue("Ph3ACVolt",100, 28) - spotvaluelist[0x4650] = SpotValue("Ph1ACCurrent",1000, 28) - spotvaluelist[0x4651] = SpotValue("Ph2ACCurrent",1000, 28) - spotvaluelist[0x4652] = SpotValue("Ph3ACCurrent",1000, 28) - spotvaluelist[0x4657] = SpotValue("ACGridFreq",100, 28) - - spotvaluelist[0x2601] = SpotValue("TotalYield",1, 16) #8 byte word - spotvaluelist[0x2622] = SpotValue("DayYield",1, 16) #8 byte word - spotvaluelist[0x462f] = SpotValue("FeedInTime",3600, 16)#8 byte word - spotvaluelist[0x462e] = SpotValue("OperatingTime",3600, 16)#8 byte word + spotvaluelist[0x4648] = SpotValue("Ph1ACVolt", 100, 28) + spotvaluelist[0x4649] = SpotValue("Ph2ACVolt", 100, 28) + spotvaluelist[0x464a] = SpotValue("Ph3ACVolt", 100, 28) + spotvaluelist[0x4650] = SpotValue("Ph1ACCurrent", 1000, 28) + spotvaluelist[0x4651] = SpotValue("Ph2ACCurrent", 1000, 28) + spotvaluelist[0x4652] = SpotValue("Ph3ACCurrent", 1000, 28) + spotvaluelist[0x4657] = SpotValue("ACGridFreq", 100, 28) - spotvaluelist[0x251e] = SpotValue("DCPower1",1, 28) - spotvaluelist[0x451f] = SpotValue("DCVoltage1",100, 28) - spotvaluelist[0x4521] = SpotValue("DCCurrent1",1000, 28) + spotvaluelist[0x2601] = SpotValue("TotalYield", 1, 16) #8 byte word + spotvaluelist[0x2622] = SpotValue("DayYield", 1, 16) #8 byte word + spotvaluelist[0x462f] = SpotValue("FeedInTime", 3600, 16) #8 byte word + spotvaluelist[0x462e] = SpotValue("OperatingTime", 3600, 16) #8 byte word - spotvaluelist[0x2377] = SpotValue("InvTemperature",100, 28) - #spotvaluelist[0x821e] = SpotValue("Inverter Name",0, 28) - spotvaluelist[0x295A] = SpotValue("ChargeStatus",1, 28) + spotvaluelist[0x251e] = SpotValue("DCPower1", 1, 28) + spotvaluelist[0x451f] = SpotValue("DCVoltage1", 100, 28) + spotvaluelist[0x4521] = SpotValue("DCCurrent1", 1000, 28) + spotvaluelist[0x2377] = SpotValue("InvTemperature", 100, 28) + #spotvaluelist[0x821e] = SpotValue("Inverter Name", 0, 28) + spotvaluelist[0x295A] = SpotValue("ChargeStatus", 1, 28) - SpotValueOutput=namedtuple("SpotValueOutput", ["Label", "Value"]) + SpotValueOutput = namedtuple("SpotValueOutput", ["Label", "Value"]) #Start here offset = 40 - if (level2Packet.totalPayloadLength()==0): + if level2Packet.totalPayloadLength() == 0: return outputlist - while (offset < level2Packet.totalPayloadLength()): - recordSize=28 - value=0 + while offset < level2Packet.totalPayloadLength(): + recordSize = 28 + value = 0 classtype = level2Packet.getByte(offset) #classtype should always be =1 - readingtype = level2Packet.getTwoByte(offset+1) - dataType = level2Packet.getByte(offset+3) - datetime = level2Packet.getFourByteLong(offset+4) + readingtype = level2Packet.getTwoByte(offset + 1) + dataType = level2Packet.getByte(offset + 3) + datetime = level2Packet.getFourByteLong(offset + 4) - if (readingtype==0): - break; + if readingtype == 0: + break - if (dataType != 0x10) and (dataType != 0x08): + if dataType != 0x10 and dataType != 0x08: # Not TEXT or STATUS, so it should be DWORD - value = level2Packet.getTwoByte(offset+8) - - #Check for NULLs - if (value == 0x8000) or (value == 0xFFFF): - value = 0; + value = level2Packet.getTwoByte(offset + 8) + # Check for NULLs + if value == 0x8000 or value == 0xFFFF: + value = 0 if readingtype in spotvaluelist: v = spotvaluelist[readingtype] #Check if its an 4 byte value (QWORD) - if (v.RecSize==16): - value = level2Packet.getEightByte(offset+8) - if (value == 0x80000000) or (value == 0xFFFFFFFF): - value = 0; - - - - + if v.RecSize == 16: + value = level2Packet.getEightByte(offset + 8) + if value == 0x80000000 or value == 0xFFFFFFFF: + value = 0 # Special case for DC voltage input (aka SPOT_UDC1 / SPOT_UDC2) - #if (readingtype==0x451f): - # if (classtype==1): + #if readingtype == 0x451f: + # if classtype == 1: # outputlist["DCVoltage1"] = SpotValueOutput("DCVoltage1".format(readingtype), toVolt(value)) - # if (classtype==2): + # if classtype == 2: # outputlist["DCVoltage2"] = SpotValueOutput("DCVoltage2".format(readingtype), toVolt(value)) #else: # outputlist[v.Description] = SpotValueOutput(v.Description, float(value) / float(v.Scale)) - outputlist[v.Description] = SpotValueOutput(v.Description, round( float(value) / float(v.Scale) ,4) ) - offset+=v.RecSize - + outputlist[v.Description] = SpotValueOutput(v.Description, round(float(value) / float(v.Scale), 4)) + offset += v.RecSize else: #Output to caller in raw format for debugging outputlist[readingtype] = SpotValueOutput("DebugX{0:04x}".format(readingtype), value) #Guess offset/default - offset+=28 + offset += 28 return outputlist From 9e3661b289450e25b3d69124ed46cf0d03f88a62 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 21 Oct 2019 21:58:08 +0100 Subject: [PATCH 13/78] Tidy up imports. - Standard library imports should go first. - Remove unused or duplicate imports. --- src/emonhub_interfacer.py | 24 +++++++------------ src/interfacers/EmonHubBMWInterfacer.py | 5 ++-- src/interfacers/EmonHubSMASolarInterfacer.py | 1 - src/interfacers/EmonHubTx3eInterfacer.py | 4 +--- .../tmp/EmonFroniusModbusTcpInterfacer.py | 3 --- src/smalibrary/SMANET2PlusPacket.py | 7 ++++-- src/smalibrary/SMASolar_library.py | 7 ++++-- 7 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 99e0bcb3..3997e0ac 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -7,20 +7,15 @@ """ -import serial import time -import datetime import logging -import socket -import select import threading -import urllib2 -import json -import uuid +try: + import urllib +except ImportError: + import urllib2 as urllib # FIXME Python2 compatibility import traceback -import paho.mqtt.client as mqtt - import emonhub_coder as ehc import emonhub_buffer as ehb @@ -254,19 +249,18 @@ def _send_post(self, post_url, post_body=None): """ reply = "" - request = urllib2.Request(post_url, post_body) + request = urllib.Request(post_url, post_body) try: - response = urllib2.urlopen(request, timeout=60) - except urllib2.HTTPError as e: + response = urllib.urlopen(request, timeout=60) + except urllib.HTTPError as e: self._log.warning(self.name + " couldn't send to server, HTTPError: " + str(e.code)) - except urllib2.URLError as e: + except urllib.URLError as e: self._log.warning(self.name + " couldn't send to server, URLError: " + str(e.reason)) - except httplib.HTTPException: + except urllib.HTTPException: self._log.warning(self.name + " couldn't send to server, HTTPException") except Exception: - import traceback self._log.warning(self.name + " couldn't send to server, Exception: " + traceback.format_exc()) else: diff --git a/src/interfacers/EmonHubBMWInterfacer.py b/src/interfacers/EmonHubBMWInterfacer.py index dc2c01d0..0a034942 100644 --- a/src/interfacers/EmonHubBMWInterfacer.py +++ b/src/interfacers/EmonHubBMWInterfacer.py @@ -10,11 +10,10 @@ import time import sys import traceback -import Cargo import json -import requests import os.path -from datetime import datetime +import requests +import Cargo from emonhub_interfacer import EmonHubInterfacer """class EmonHubBMWInterfacer diff --git a/src/interfacers/EmonHubSMASolarInterfacer.py b/src/interfacers/EmonHubSMASolarInterfacer.py index 549e9109..9368417b 100644 --- a/src/interfacers/EmonHubSMASolarInterfacer.py +++ b/src/interfacers/EmonHubSMASolarInterfacer.py @@ -16,7 +16,6 @@ import re import Cargo -from time import sleep from emonhub_interfacer import EmonHubInterfacer from smalibrary import SMASolar_library diff --git a/src/interfacers/EmonHubTx3eInterfacer.py b/src/interfacers/EmonHubTx3eInterfacer.py index 1627e291..ae44d2fc 100644 --- a/src/interfacers/EmonHubTx3eInterfacer.py +++ b/src/interfacers/EmonHubTx3eInterfacer.py @@ -1,7 +1,5 @@ -import serial -import time -import Cargo import re +import Cargo import EmonHubSerialInterfacer as ehi """class EmonHubTx3eInterfacer diff --git a/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py b/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py index 9d96c95d..68905182 100644 --- a/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py +++ b/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py @@ -1,6 +1,3 @@ -import time -import datetime -import logging from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder import EmonModbusTcpInterfacer as EmonModbusTcpInterfacer diff --git a/src/smalibrary/SMANET2PlusPacket.py b/src/smalibrary/SMANET2PlusPacket.py index f9f60f7f..a03593bf 100644 --- a/src/smalibrary/SMANET2PlusPacket.py +++ b/src/smalibrary/SMANET2PlusPacket.py @@ -3,7 +3,10 @@ import array import math -import string +try: + from __builtin__ import long +except ImportError: + long = int # FIXME Python2 compatibility __author__ = 'Stuart Pittaway' @@ -274,4 +277,4 @@ def debugViewPacket(self): str_list.append("L2 Checksu= {0:02x} {1:02x}".format(myfcs & 0x00ff, (myfcs >> 8) & 0x00ff)) str_list.append("L2 END = {0:02x}".format(0x7e)) - return string.join(str_list, '\n') + return '\n'.join(str_list) diff --git a/src/smalibrary/SMASolar_library.py b/src/smalibrary/SMASolar_library.py index 3d513715..e04829ab 100644 --- a/src/smalibrary/SMASolar_library.py +++ b/src/smalibrary/SMASolar_library.py @@ -3,10 +3,13 @@ from collections import namedtuple import time -from __builtin__ import long +try: + from __builtin__ import long # FIXME long is defined by default on Python2, but is equivalent to int on Python3 +except ImportError: + long = int +from datetime import datetime from SMABluetoothPacket import SMABluetoothPacket from SMANET2PlusPacket import SMANET2PlusPacket -from datetime import datetime __author__ = 'Stuart Pittaway' From 9153ab2c073681cd336f17802d2802520fa32d92 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 21 Oct 2019 21:58:52 +0100 Subject: [PATCH 14/78] Fix missing super() --- src/emonhub_interfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 3997e0ac..0ea28cee 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -43,7 +43,7 @@ def __init__(self, name): self._log = logging.getLogger("EmonHub") # Initialise thread - threading.Thread.__init__(self) + super(threading.Thread, self).__init__(self) self.setName(name) # Initialise settings From 0e636c6b43dc05b69da869731614548e59f5452a Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 21 Oct 2019 22:00:05 +0100 Subject: [PATCH 15/78] Fix undefined variable bug. --- src/smalibrary/SMANET2PlusPacket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smalibrary/SMANET2PlusPacket.py b/src/smalibrary/SMANET2PlusPacket.py index a03593bf..22d67069 100644 --- a/src/smalibrary/SMANET2PlusPacket.py +++ b/src/smalibrary/SMANET2PlusPacket.py @@ -147,7 +147,7 @@ def errorCode(self): def calculateFCS(self): myfcs = 0xffff - for bte in packet: + for bte in self.packet: myfcs = (myfcs >> 8) ^ self.fcstab[(myfcs ^ bte) & 0xff] myfcs ^= 0xffff From dd8d1f7d8a1094505cf0711c817f7466460b5c72 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 21 Oct 2019 22:13:16 +0100 Subject: [PATCH 16/78] Tidy up logic. - expr == False -> not expr - expr == True -> expr (or expr is True, if the type isn't clear). - not x in y => x not in y - not x == "1" => x != "1" - omit range default - 'if not x or not y: pass else: z' => 'if x and y: z' - str.__len__ => len - x > 1 and x < 10 => 1 < x < 10 - not x > y => x <= y - if x: if y: if z: _ => if x and y and x: _ - BUGFIX: "a" and "b" in f => all(i in f for i in ["a","b"]) - BUGFIX: .lower => .lower() --- src/emonhub.py | 50 +++++++++---------- src/emonhub_interfacer.py | 24 +++++---- src/interfacers/EmonHubBMWInterfacer.py | 2 +- .../EmonHubEmoncmsHTTPInterfacer.py | 14 +++--- src/interfacers/EmonHubGraphiteInterfacer.py | 8 +-- src/interfacers/EmonHubJeeInterfacer.py | 21 ++++---- src/interfacers/EmonHubMqttInterfacer.py | 41 ++++++++------- src/interfacers/EmonHubPacketGenInterfacer.py | 8 +-- src/interfacers/EmonHubSMASolarInterfacer.py | 4 +- src/interfacers/EmonHubSocketInterfacer.py | 12 ++--- src/interfacers/EmonHubTemplateInterfacer.py | 7 ++- src/interfacers/EmonHubVEDirectInterfacer.py | 4 +- src/interfacers/EmonModbusTcpInterfacer.py | 5 +- .../tmp/EmonHubSmilicsInterfacer.py | 2 +- src/smalibrary/SMABluetoothPacket.py | 4 +- src/smalibrary/SMASolar_library.py | 2 +- 16 files changed, 100 insertions(+), 108 deletions(-) diff --git a/src/emonhub.py b/src/emonhub.py index f42ef9ff..6c1ac112 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -109,24 +109,22 @@ def run(self): # Read each interfacers pub channels for pub_channel in I._settings['pubchannels']: - if pub_channel in I._pub_channels: - if len(I._pub_channels[pub_channel]) > 0: - - # POP cargo item (one at a time) - cargo = I._pub_channels[pub_channel].pop(0) - - # Post to each subscriber interface - for sub_interfacer in self._interfacers.itervalues(): - # For each subsciber channel - for sub_channel in sub_interfacer._settings['subchannels']: - # If channel names match - if sub_channel == pub_channel: - # init if empty - if not sub_channel in sub_interfacer._sub_channels: - sub_interfacer._sub_channels[sub_channel] = [] - - # APPEND cargo item - sub_interfacer._sub_channels[sub_channel].append(cargo) + if pub_channel in I._pub_channels and len(I._pub_channels[pub_channel]) > 0: + # POP cargo item (one at a time) + cargo = I._pub_channels[pub_channel].pop(0) + + # Post to each subscriber interface + for sub_interfacer in self._interfacers.values(): + # For each subsciber channel + for sub_channel in sub_interfacer._settings['subchannels']: + # If channel names match + if sub_channel == pub_channel: + # init if empty + if sub_channel not in sub_interfacer._sub_channels: + sub_interfacer._sub_channels[sub_channel] = [] + + # APPEND cargo item + sub_interfacer._sub_channels[sub_channel].append(cargo) # ->avoid modification of iterable within loop for name in kill_list: @@ -179,9 +177,7 @@ def _update_settings(self, settings): for name in self._interfacers.keys(): # Delete interfacers if not listed or have no 'Type' in the settings without further checks # (This also provides an ability to delete & rebuild by commenting 'Type' in conf) - if not name in settings['interfacers'] or not 'Type' in settings['interfacers'][name]: - pass - else: + if name in settings['interfacers'] and 'Type' in settings['interfacers'][name]: try: # test for 'init_settings' and 'runtime_setting' sections settings['interfacers'][name]['init_settings'] @@ -203,7 +199,7 @@ def _update_settings(self, settings): # If interfacer does not exist, create it if name not in self._interfacers: try: - if not 'Type' in I: + if 'Type' not in I: continue self._log.info("Creating " + I['Type'] + " '%s' ", name) # This gets the class from the 'Type' string @@ -284,17 +280,17 @@ def _set_logging_level(self, level='WARNING', log=True): # Logging configuration logger = logging.getLogger("EmonHub") - if args.logfile is None: - # If no path was specified, everything goes to sys.stderr - loghandler = logging.StreamHandler() - else: - # Otherwise, rotating logging over two 5 MB files + if args.logfile: + # If path was given, rotating logging over two 5 MB files # If logfile is supplied, argparse opens the file in append mode, # this ensures it is writable # Close the file for now and get its path args.logfile.close() loghandler = logging.handlers.RotatingFileHandler(args.logfile.name, 'a', 5000 * 1024, 1) + else: + # Otherwise, if no path was specified, everything goes to sys.stderr + loghandler = logging.StreamHandler() # Format log strings loghandler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)-8s %(threadName)-10s %(message)s')) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 0ea28cee..c6d1957f 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -107,7 +107,7 @@ def run(self): self._log.debug(str(rxc.uri) + " Sent to channel(start)' : " + str(channel)) # Initialize channel if needed - if not channel in self._pub_channels: + if channel not in self._pub_channels: self._pub_channels[channel] = [] # Add cargo item to channel @@ -118,7 +118,9 @@ def run(self): # Subscriber channels for channel in self._settings["subchannels"]: if channel in self._sub_channels: - for i in range(0, len(self._sub_channels[channel])): + # FIXME should be: while self._sub_channels[channel] + for _ in range(len(self._sub_channels[channel])): + # FIXME pop(0) has O(n) complexity. Can we use pop's default of last? frame = self._sub_channels[channel].pop(0) self.add(frame) @@ -311,13 +313,12 @@ def _process_rx(self, cargo): # Data whitening uses for ensuring rfm sync if node in ehc.nodelist and 'rx' in ehc.nodelist[node] and 'whitening' in ehc.nodelist[node]['rx']: whitening = ehc.nodelist[node]['rx']['whitening'] - if whitening == True or whitening == "1": - for i in range(0, len(rxc.realdata), 1): + if whitening is True or whitening == "1": + for i in range(len(rxc.realdata)): rxc.realdata[i] = rxc.realdata[i] ^ 0x55 # check if node is listed and has individual datacodes for each value if node in ehc.nodelist and 'rx' in ehc.nodelist[node] and 'datacodes' in ehc.nodelist[node]['rx']: - # fetch the string of datacodes datacodes = ehc.nodelist[node]['rx']['datacodes'] @@ -364,8 +365,8 @@ def _process_rx(self, cargo): # Decode the string of data one value at a time into "decoded" if not decoded: - bytepos = int(0) - for i in range(0, count, 1): + bytepos = 0 + for i in range(count): # Use single datacode unless datacode = False then use datacodes dc = str(datacode) if not datacode: @@ -404,8 +405,9 @@ def _process_rx(self, cargo): # when node not listed or has no scale(s) use the interfacers default if specified scale = self._settings['scale'] - if not scale == "1": - for i in range(0, len(decoded), 1): + if scale != "1": + # FIXME replace with zip + for i in range(len(decoded)): x = scale if not scale: if i < len(scales): @@ -502,7 +504,7 @@ def _process_tx(self, cargo): if scale == "1": scaled = txc.realdata else: - for i in range(0, len(txc.realdata), 1): + for i in range(len(txc.realdata)): x = scale if not scale: x = scales[i] @@ -571,7 +573,7 @@ def _process_tx(self, cargo): if not encoded: encoded.append(dest) - for i in range(0, count, 1): + for i in range(count): # Use single datacode unless datacode = False then use datacodes dc = str(datacode) if not datacode: diff --git a/src/interfacers/EmonHubBMWInterfacer.py b/src/interfacers/EmonHubBMWInterfacer.py index 0a034942..3062b735 100644 --- a/src/interfacers/EmonHubBMWInterfacer.py +++ b/src/interfacers/EmonHubBMWInterfacer.py @@ -189,7 +189,7 @@ def read(self): """Read data from BMW API""" #Wait until we are ready to read from inverter - if self._is_it_time() == False and self._first_time_loop == False: + if not self._is_it_time() and not self._first_time_loop: return self._reset_duration_timer() diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index 5089eeea..a5ea36b7 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -39,8 +39,8 @@ def _process_post(self, databuffer): # [[timestamp, nodeid, datavalues][timestamp, nodeid, datavalues]] # [[1399980731, 10, 150, 250 ...]] - if not 'apikey' in self._settings.keys() or str.__len__(str(self._settings['apikey'])) != 32 \ - or str.lower(str(self._settings['apikey'])) == 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx': + if 'apikey' not in self._settings.keys() or len(str(self._settings['apikey'])) != 32 \ + or str(self._settings['apikey']).lower() == 'x' * 32: # Return true to clear buffer if the apikey is not set return True @@ -77,8 +77,8 @@ def _process_post(self, databuffer): return False def sendstatus(self): - if not 'apikey' in self._settings.keys() or str.__len__(str(self._settings['apikey'])) != 32 \ - or str.lower(str(self._settings['apikey'])) == 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx': + if 'apikey' not in self._settings.keys() or len(str(self._settings['apikey'])) != 32 \ + or str(self._settings['apikey']).lower() == 'x' * 32: return # MYIP url @@ -101,16 +101,16 @@ def set(self, **kwargs): for key, setting in self._cms_settings.iteritems(): #valid = False - if not key in kwargs.keys(): + if key not in kwargs.keys(): setting = self._cms_settings[key] else: setting = kwargs[key] if key in self._settings and self._settings[key] == setting: continue elif key == 'apikey': - if str.lower(setting[:4]) == 'xxxx': + if setting[:4].lower() == 'xxxx': # FIXME compare whole string to 'x'*32? self._log.warning("Setting " + self.name + " apikey: obscured") - elif str.__len__(setting) == 32: + elif len(setting) == 32: self._log.info("Setting " + self.name + " apikey: set") elif setting == "": self._log.info("Setting " + self.name + " apikey: null") diff --git a/src/interfacers/EmonHubGraphiteInterfacer.py b/src/interfacers/EmonHubGraphiteInterfacer.py index 0c63f579..9ece01be 100644 --- a/src/interfacers/EmonHubGraphiteInterfacer.py +++ b/src/interfacers/EmonHubGraphiteInterfacer.py @@ -41,7 +41,8 @@ def add(self, cargo): f['node'] = nodename f['data'] = {} - for i in range(0, len(cargo.realdata)): + # FIXME replace with zip + for i in range(len(cargo.realdata)): name = str(i + 1) if i < len(cargo.names): name = cargo.names[i] @@ -58,8 +59,7 @@ def _process_post(self, databuffer): timestamp = int(time.time()) metrics = [] - for c in range(0, len(databuffer)): - frame = databuffer[c] + for frame in databuffer: nodename = frame['node'] for inputname, value in frame['data'].iteritems(): @@ -118,7 +118,7 @@ def set(self, **kwargs): super (EmonHubGraphiteInterfacer, self).set(**kwargs) for key, setting in self._graphite_settings.iteritems(): #valid = False - if not key in kwargs.keys(): + if key not in kwargs.keys(): setting = self._graphite_settings[key] else: setting = kwargs[key] diff --git a/src/interfacers/EmonHubJeeInterfacer.py b/src/interfacers/EmonHubJeeInterfacer.py index 1ec0b2ec..2af397fc 100644 --- a/src/interfacers/EmonHubJeeInterfacer.py +++ b/src/interfacers/EmonHubJeeInterfacer.py @@ -116,7 +116,7 @@ def read(self): return # Record current device settings - if " i" and " g" and " @ " and " MHz" in f: + if all(i in f for i in (" i", " g", " @ ", " MHz")): self.info[1] = f self._log.debug("device settings updated: " + str(self.info[1])) return @@ -132,7 +132,7 @@ def read(self): f = f[1:] # Extract RSSI value if it's available - if str(f[-1])[0] == '(' and str(f[-1])[-1] == ')': + if f[-1].startswith('(') and f[-1].endswith(')'): r = f[-1][1:-1] try: c.rssi = int(r) @@ -171,22 +171,22 @@ def set(self, **kwargs): else: setting = self._jee_settings[key] # convert bools to ints - if str.capitalize(str(setting)) in ['True', 'False']: + if str(setting).capitalize() in ['True', 'False']: setting = int(setting == "True") # confirmation string always contains baseid, group and freq - if " i" and " g" and " @ " and " MHz" in self.info[1]: + if all(i in self.info[1] for i in (" i", " g", " @ ", " MHz")): # If setting confirmed as already set, continue without changing - if (self._jee_prefix[key] + str(setting)) in self.info[1].split(): + if self._jee_prefix[key] + str(setting) in self.info[1].split(): continue elif key in self._settings and self._settings[key] == setting: continue - if key == 'baseid' and int(setting) >= 1 and int(setting) <= 26: + if key == 'baseid' and 1 <= int(setting) <= 26: command = str(setting) + 'i' elif key == 'frequency' and setting in ['433', '868', '915']: command = setting[:1] + 'b' - elif key == 'group' and int(setting) >= 0 and int(setting) <= 250: + elif key == 'group' and 0 <= int(setting) <= 250: command = str(setting) + 'g' - elif key == 'quiet' and int(setting) >= 0 and int(setting) < 2: + elif key == 'quiet' and 0 <= int(setting) < 2: command = str(setting) + 'q' elif key == 'calibration' and setting == '230V': command = '1p' @@ -228,8 +228,7 @@ def _process_post(self, databuffer): """ - for i in range(0, len(databuffer)): - frame = databuffer[i] + for frame in databuffer: self._log.debug("node = " + str(frame[1]) + " node_data = " + json.dumps(frame)) self.send(frame) return True @@ -245,7 +244,7 @@ def send(self, cargo): payload = "" for value in data: - if int(value) < 0 or int(value) > 255: + if not 0 < int(value) < 255: self._log.warning(self.name + " discarding Tx packet: values out of scope") return payload += str(int(value)) + "," diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index 9f7642b2..c21662b9 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -108,7 +108,8 @@ def _process_post(self, databuffer): # General MQTT format: emonhub/rx/emonpi/power1 ... 100 # ---------------------------------------------------------- if int(self._settings["nodevar_format_enable"]) == 1: - for i in range(0, len(frame['data'])): + # FIXME replace with zip + for i in range(len(frame['data'])): inputname = str(i + 1) if i < len(frame['names']): inputname = frame['names'][i] @@ -207,31 +208,29 @@ def on_subscribe(self, mqttc, obj, mid, granted_qos): def on_message(self, client, userdata, msg): topic_parts = msg.topic.split("/") - if topic_parts[0] == self._settings["node_format_basetopic"][:-1]: - if topic_parts[1] == "tx": - if topic_parts[3] == "values": - nodeid = int(topic_parts[2]) + if topic_parts[0] == self._settings["node_format_basetopic"][:-1] and topic_parts[1] == "tx" and topic_parts[3] == "values": + nodeid = int(topic_parts[2]) - payload = msg.payload - realdata = payload.split(",") - self._log.debug("Nodeid: " + str(nodeid) + " values: " + msg.payload) + payload = msg.payload + realdata = payload.split(",") + self._log.debug("Nodeid: " + str(nodeid) + " values: " + msg.payload) - rxc = Cargo.new_cargo(realdata=realdata) - rxc.nodeid = nodeid + rxc = Cargo.new_cargo(realdata=realdata) + rxc.nodeid = nodeid - if rxc: - # rxc = self._process_tx(rxc) - if rxc: - for channel in self._settings["pubchannels"]: + if rxc: + # rxc = self._process_tx(rxc) + if rxc: + for channel in self._settings["pubchannels"]: - # Initialize channel if needed - if not channel in self._pub_channels: - self._pub_channels[channel] = [] + # Initialize channel if needed + if channel not in self._pub_channels: + self._pub_channels[channel] = [] - # Add cargo item to channel - self._pub_channels[channel].append(rxc) + # Add cargo item to channel + self._pub_channels[channel].append(rxc) - self._log.debug(str(rxc.uri) + " Sent to channel' : " + str(channel)) + self._log.debug(str(rxc.uri) + " Sent to channel' : " + str(channel)) def set(self, **kwargs): """ @@ -244,7 +243,7 @@ def set(self, **kwargs): for key, setting in self._mqtt_settings.iteritems(): #valid = False - if not key in kwargs.keys(): + if key not in kwargs.keys(): setting = self._mqtt_settings[key] else: setting = kwargs[key] diff --git a/src/interfacers/EmonHubPacketGenInterfacer.py b/src/interfacers/EmonHubPacketGenInterfacer.py index 797864b5..e7b0df7b 100644 --- a/src/interfacers/EmonHubPacketGenInterfacer.py +++ b/src/interfacers/EmonHubPacketGenInterfacer.py @@ -28,7 +28,7 @@ def read(self): """ t = time.time() - if not (t - self._control_timestamp) > self._control_interval: + if t - self._control_timestamp <= self._control_interval: return req = self._settings['url'] + \ @@ -93,7 +93,7 @@ def action(self): # Keep in touch with PacketGen and update refresh time interval = int(self._settings['interval']) if interval: # A value of 0 means don't do anything - if not (t - self._interval_timestamp) > interval: + if t - self._interval_timestamp < interval: return try: @@ -127,9 +127,9 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'apikey': - if str.lower(setting[:4]) == 'xxxx': + if setting[:4].lower() == 'xxxx': # FIXME compare whole string to 'x'*32? self._log.warning("Setting " + self.name + " apikey: obscured") - elif str.__len__(setting) == 32: + elif len(setting) == 32: self._log.info("Setting " + self.name + " apikey: set") elif setting == "": self._log.info("Setting " + self.name + " apikey: null") diff --git a/src/interfacers/EmonHubSMASolarInterfacer.py b/src/interfacers/EmonHubSMASolarInterfacer.py index 9368417b..7bc8bcc0 100644 --- a/src/interfacers/EmonHubSMASolarInterfacer.py +++ b/src/interfacers/EmonHubSMASolarInterfacer.py @@ -190,7 +190,7 @@ def read(self): return False #Wait until we are ready to read from inverter - if self._is_it_time() == False: + if not self._is_it_time(): return self._reset_duration_timer() @@ -278,7 +278,7 @@ def read(self): #Inverter appears to kill our connection every 10 minutes, so disconnect after 8 minutes #to avoid errors in log files - if self._is_it_time_to_disconnect() == True: + if self._is_it_time_to_disconnect(): self._log.info("Disconnecting Bluetooth after timer expired") SMASolar_library.logoff(self._btSocket, self._packet_send_counter, self.mylocalBTAddress, self.MySerialNumber) self._reset_time_to_disconnect_timer() diff --git a/src/interfacers/EmonHubSocketInterfacer.py b/src/interfacers/EmonHubSocketInterfacer.py index d06dd3dc..0530b2b6 100644 --- a/src/interfacers/EmonHubSocketInterfacer.py +++ b/src/interfacers/EmonHubSocketInterfacer.py @@ -81,7 +81,7 @@ def read(self): conn.close() # If there is at least one complete frame in the buffer - if not '\r\n' in self._sock_rx_buf: + if '\r\n' not in self._sock_rx_buf: return # Process and return first frame in buffer: @@ -95,10 +95,10 @@ def read(self): # If apikey is specified, 32chars and not all x's if 'apikey' in self._settings: - if len(self._settings['apikey']) == 32 and self._settings['apikey'].lower != "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": + if len(self._settings['apikey']) == 32 and self._settings['apikey'].lower() != "x" * 32: # Discard if apikey is not in received frame - if not self._settings['apikey'] in f: - self._log.warning(str(c.uri) +" discarded frame: apikey not matched") + if self._settings['apikey'] not in f: + self._log.warning(str(c.uri) + " discarded frame: apikey not matched") return # Otherwise remove apikey from frame f = [v for v in f if self._settings['apikey'] not in v] @@ -138,9 +138,9 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'apikey': - if str.lower(setting[:4]) == 'xxxx': + if setting[:4].lower() == 'xxxx': # FIXME compare whole string to 'x'*32? self._log.warning("Setting " + self.name + " apikey: obscured") - elif str.__len__(setting) == 32: + elif len(setting) == 32: self._log.info("Setting " + self.name + " apikey: set") elif setting == "": self._log.info("Setting " + self.name + " apikey: null") diff --git a/src/interfacers/EmonHubTemplateInterfacer.py b/src/interfacers/EmonHubTemplateInterfacer.py index d397ab00..9df4bca8 100644 --- a/src/interfacers/EmonHubTemplateInterfacer.py +++ b/src/interfacers/EmonHubTemplateInterfacer.py @@ -82,7 +82,8 @@ def add(self, cargo): f['node'] = nodename f['data'] = {} - for i in range(0, len(cargo.realdata)): + # FIXME replace with zip + for i in range(len(cargo.realdata)): name = str(i + 1) if i < len(cargo.names): name = cargo.names[i] @@ -99,9 +100,7 @@ def _process_post(self, databuffer): """ - for i in range(0, len(databuffer)): - frame = databuffer[i] - + for frame in databuffer: # Here we might typically publish or post the data # via MQTT, HTTP a socket or other output self._log.debug("node = " + frame['node'] + " node_data = " + json.dumps(frame['data'])) diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index 99d0033e..4f484084 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -168,7 +168,7 @@ def read(self): # Read serial RX now = time.time() - if not (now - self.last_read) > self.poll_interval: + if now - self.last_read <= self.poll_interval: #self._log.debug(" Waiting for %s seconds " % (str(now - self.last_read))) # Wait to read based on poll_interval return @@ -178,7 +178,7 @@ def read(self): # Update last read time self.last_read = now # If line incomplete, exit - if self._rx_buf is None: + if self._rx_buf == '': return #Sample data looks like {'FW': '0307', 'SOC': '1000', 'Relay': 'OFF', 'PID': '0x203', 'H10': '6', 'BMV': '700', 'TTG': '-1', 'H12': '0', 'H18': '0', 'I': '0', 'H11': '0', 'Alarm': 'OFF', 'CE': '0', 'H17': '9', 'P': '0', 'AR': '0', 'V': '26719', 'H8': '29011', 'H9': '0', 'H2': '0', 'H3': '0', 'H1': '-1633', 'H6': '-5775', 'H7': '17453', 'H4': '0', 'H5': '0'} diff --git a/src/interfacers/EmonModbusTcpInterfacer.py b/src/interfacers/EmonModbusTcpInterfacer.py index 8c3ed6f9..a999e8b3 100644 --- a/src/interfacers/EmonModbusTcpInterfacer.py +++ b/src/interfacers/EmonModbusTcpInterfacer.py @@ -100,10 +100,7 @@ def read(self): return # fetch unitids if present - if "nUnit" in self._settings: - UnitIds = self._settings["nUnit"] - else: - UnitIds = None + UnitIds = self._settings.get("nUnit", None) # stores names # fetch datacode or datacodes diff --git a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py index ae5355ce..0af7559a 100644 --- a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py +++ b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py @@ -68,7 +68,7 @@ def run(self): self._log.debug(str(rxc.uri) + " Sent to channel(start)' : " + str(channel)) # Initialize channel if needed - if not channel in self._pub_channels: + if channel not in self._pub_channels: self._pub_channels[channel] = [] # Add cargo item to channel diff --git a/src/smalibrary/SMABluetoothPacket.py b/src/smalibrary/SMABluetoothPacket.py index ceb2514c..ff4f3ef5 100644 --- a/src/smalibrary/SMABluetoothPacket.py +++ b/src/smalibrary/SMABluetoothPacket.py @@ -61,7 +61,7 @@ def finish(self): self.setChecksum() # Just in case! - if self.ValidateHeaderChecksum() == False: + if not self.ValidateHeaderChecksum(): raise Exception("Invalid header checksum when finishing!") def pushEscapedByte(self, value): @@ -137,5 +137,5 @@ def __init__(self, length1, length2, checksum=0, cmd1=0, cmd2=0, SourceAddress=b self.UnescapedArray = bytearray() self.setCommandCode(cmd1, cmd2) - if checksum > 0 and self.ValidateHeaderChecksum() == False: + if checksum > 0 and not self.ValidateHeaderChecksum(): raise Exception("Invalid header checksum!") diff --git a/src/smalibrary/SMASolar_library.py b/src/smalibrary/SMASolar_library.py index e04829ab..7133d8f8 100644 --- a/src/smalibrary/SMASolar_library.py +++ b/src/smalibrary/SMASolar_library.py @@ -67,7 +67,7 @@ def read_SMA_BT_Packet(btSocket, waitPacketNumber=0, waitForPacket=False, myloca # Write the payload into a level2 class structure level2Packet.pushByteArray(bluetoothbuffer.getLevel2Payload()) - if waitForPacket == True and level2Packet.getPacketCounter() != waitPacketNumber: + if waitForPacket and level2Packet.getPacketCounter() != waitPacketNumber: #print("Received packet number {0:02x} expected {1:02x}".format(level2Packet.getPacketCounter(),waitPacketNumber)) raise Exception("Wrong Level 2 packet returned!") From e0033560aad1bd6fc5cc2ac2ddd247a6f300e9a6 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Sun, 8 Dec 2019 17:35:20 +0000 Subject: [PATCH 17/78] Simplify code. - defaultdict to avoid initialising every entry up front - Use struct.calcsize instead of copying the values. - if x == 0: y = False: else y = True => y = bool(x) - list[len(list)-1] => list[-1] - math.pow(256, 1) => 0x08 etc. - x = bytearray(); x.append(y); x.append(z) => x = bytearray([y, z]) - value = x; return value => return x --- src/emonhub.py | 5 ++- src/emonhub_coder.py | 15 +++------ src/interfacers/EmonHubSMASolarInterfacer.py | 7 ++--- src/smalibrary/SMABluetoothPacket.py | 28 +++++++---------- src/smalibrary/SMANET2PlusPacket.py | 33 ++++++++++---------- 5 files changed, 35 insertions(+), 53 deletions(-) diff --git a/src/emonhub.py b/src/emonhub.py index 6c1ac112..cef077e3 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -18,6 +18,7 @@ import pprint import glob import os +from collections import defaultdict import emonhub_setup as ehs import emonhub_coder as ehc @@ -87,9 +88,7 @@ def run(self): signal.signal(signal.SIGINT, self._sigint_handler) # Initialise thread restart counters - restart_count = {} - for I in self._interfacers.itervalues(): - restart_count[I.name] = 0 + restart_count = defaultdict(int) # Until asked to stop while not self._exit: diff --git a/src/emonhub_coder.py b/src/emonhub_coder.py index e54e6492..9836e54f 100644 --- a/src/emonhub_coder.py +++ b/src/emonhub_coder.py @@ -5,15 +5,9 @@ def check_datacode(datacode): - # Data types & sizes (number of bytes) - datacodes = {'b': '1', 'h': '2', 'i': '4', 'l': '4', 'q': '8', 'f': '4', 'd': '8', - 'B': '1', 'H': '2', 'I': '4', 'L': '4', 'Q': '8', 'c': '1', '?': '1'} - - # if datacode is valid return the data size in bytes - if datacode in datacodes: - return int(datacodes[datacode]) - # if not valid return False - else: + try: + return struct.calcsize(datacode) + except struct.error: return False @@ -42,5 +36,4 @@ def encode(datacode, value): #value = 60 #datacode = "b" - result = struct.unpack(e + b*s, struct.pack(e + datacode, value)) - return result + return struct.unpack(e + b*s, struct.pack(e + datacode, value)) diff --git a/src/interfacers/EmonHubSMASolarInterfacer.py b/src/interfacers/EmonHubSMASolarInterfacer.py index 7bc8bcc0..82949a38 100644 --- a/src/interfacers/EmonHubSMASolarInterfacer.py +++ b/src/interfacers/EmonHubSMASolarInterfacer.py @@ -36,15 +36,12 @@ def __init__(self, name, inverteraddress='', inverterpincode='0000', timeinverva self._port = 1 self._nodeid = int(nodeid) - if packettrace == 0: - self._packettrace = False - else: - self._packettrace = True + self._packettrace = bool(packettrace) self.MySerialNumber = bytearray([0x08, 0x00, 0xaa, 0xbb, 0xcc, 0xdd]) self._reset_packet_send_counter() - self._Inverters = None + self._Inverters = {} #Duration in seconds self._time_inverval = int(timeinverval) self._InverterPasswordArray = SMASolar_library.encodeInverterPassword(self._inverterpincode) diff --git a/src/smalibrary/SMABluetoothPacket.py b/src/smalibrary/SMABluetoothPacket.py index ff4f3ef5..967854ed 100644 --- a/src/smalibrary/SMABluetoothPacket.py +++ b/src/smalibrary/SMABluetoothPacket.py @@ -8,10 +8,10 @@ def __str__(self): return "I am an instance of SMABluetoothPacket" def getLevel2Checksum(self): - return self.UnescapedArray[len(self.UnescapedArray) - 2] * 256 + self.UnescapedArray[len(self.UnescapedArray) - 3] + return (self.UnescapedArray[-2] << 8) + self.UnescapedArray[-3] def lastByte(self): - return self.UnescapedArray[len(self.UnescapedArray) - 1] + return self.UnescapedArray[-1] def getLevel2Payload(self): skipendbytes = 0 @@ -23,9 +23,9 @@ def getLevel2Payload(self): if self.lastByte() == 0x7e: skipendbytes = 3 + # FIXME This comment says to skip the first 3 bytes, but the code skips the *last* 3 bytes # Skip the first 3 bytes, they are the command code 0x0001 and 0x7E start byte - l = len(self.UnescapedArray) - skipendbytes - return self.UnescapedArray[startbyte:l] + return self.UnescapedArray[startbyte:-skipendbytes] def pushRawByteArray(self, barray): # Raw byte array @@ -68,14 +68,14 @@ def pushEscapedByte(self, value): previousUnescapedByte = 0 if len(self.RawByteArray) > 0: - previousUnescapedByte = self.RawByteArray[len(self.RawByteArray) - 1] + previousUnescapedByte = self.RawByteArray[-1] # Store the raw byte as it was received into RawByteArray self.RawByteArray.append(value) # did we receive the escape char in previous byte? if len(self.RawByteArray) > 0 and previousUnescapedByte == 0x7d: - self.UnescapedArray[len(self.UnescapedArray) - 1] = value ^ 0x20 + self.UnescapedArray[-1] = value ^ 0x20 else: # Unescaped array is same as raw array self.UnescapedArray.append(value) @@ -84,17 +84,15 @@ def sendPacket(self, btSocket): l = btSocket.send(str(self.header) + str(self.SourceAddress) + str(self.DestinationAddress) + str(self.cmdcode) + str(self.RawByteArray)) def containsLevel2Packet(self): - if len(self.UnescapedArray) < 5: - return False - - return (self.UnescapedArray[0] == 0x7e and + return (len(self.UnescapedArray) >= 5 and + self.UnescapedArray[0] == 0x7e and self.UnescapedArray[1] == 0xff and self.UnescapedArray[2] == 0x03 and self.UnescapedArray[3] == 0x60 and self.UnescapedArray[4] == 0x65) def CommandCode(self): - return self.cmdcode[0] + (self.cmdcode[1] * 256) + return self.cmdcode[0] + (self.cmdcode[1] << 8) def setCommandCode(self, byteone, bytetwo): self.cmdcode = bytearray([byteone, bytetwo]) @@ -110,7 +108,7 @@ def TotalUnescapedPacketLength(self): return len(self.UnescapedArray) + self.headerlength def TotalRawPacketLength(self): - return self.header[1] + (self.header[2] * 256) + return self.header[1] + (self.header[2] << 8) def TotalPayloadLength(self): return self.TotalRawPacketLength() - self.headerlength @@ -126,11 +124,7 @@ def __init__(self, length1, length2, checksum=0, cmd1=0, cmd2=0, SourceAddress=b self.SourceAddress = SourceAddress self.DestinationAddress = DestinationAddress - self.header = bytearray() - self.header.append(0x7e) - self.header.append(length1) - self.header.append(length2) - self.header.append(checksum) + self.header = bytearray([0x7e, length1, length2, checksum]) # Create our array to hold the payload bytes self.RawByteArray = bytearray() diff --git a/src/smalibrary/SMANET2PlusPacket.py b/src/smalibrary/SMANET2PlusPacket.py index 22d67069..b2070756 100644 --- a/src/smalibrary/SMANET2PlusPacket.py +++ b/src/smalibrary/SMANET2PlusPacket.py @@ -2,7 +2,6 @@ # See LICENCE and README file for details import array -import math try: from __builtin__ import long except ImportError: @@ -86,26 +85,26 @@ def getByte(self, offset): return self.packet[offset] def getTwoByte(self, offset): - value = self.packet[offset] * math.pow(256, 0) - value += self.packet[offset + 1] * math.pow(256, 1) + value = self.packet[offset] + value += self.packet[offset + 1] << 8 return long(value) def getFourByteLong(self, offset): - value = self.packet[offset] * math.pow(256, 0) - value += self.packet[offset + 1] * math.pow(256, 1) - value += self.packet[offset + 2] * math.pow(256, 2) - value += self.packet[offset + 3] * math.pow(256, 3) + value = self.packet[offset] + value += self.packet[offset + 1] << 0x08 + value += self.packet[offset + 2] << 0x10 + value += self.packet[offset + 3] << 0x18 return long(value) def getEightByte(self, offset): - return self.packet[offset] * math.pow(256, 0) \ - + self.packet[offset + 1] * math.pow(256, 1) \ - + self.packet[offset + 2] * math.pow(256, 2) \ - + self.packet[offset + 3] * math.pow(256, 3) \ - + self.packet[offset + 4] * math.pow(256, 4) \ - + self.packet[offset + 5] * math.pow(256, 5) \ - + self.packet[offset + 6] * math.pow(256, 6) \ - + self.packet[offset + 7] * math.pow(256, 7) + return self.packet[offset] \ + + (self.packet[offset + 1] << 0x08) \ + + (self.packet[offset + 2] << 0x10) \ + + (self.packet[offset + 3] << 0x18) \ + + (self.packet[offset + 4] << 0x20) \ + + (self.packet[offset + 5] << 0x28) \ + + (self.packet[offset + 6] << 0x30) \ + + (self.packet[offset + 7] << 0x38) def getArray(self): return self.packet @@ -139,8 +138,7 @@ def getFragment(self): return self.packet[24] def getTwoByteuShort(self, offset): - value = self.packet[offset] * math.pow(256, 0) + self.packet[offset + 1] * math.pow(256, 1) - return value + return self.packet[offset] + (self.packet[offset + 1] << 8) def errorCode(self): return self.getTwoByteuShort(22) @@ -151,6 +149,7 @@ def calculateFCS(self): myfcs = (myfcs >> 8) ^ self.fcstab[(myfcs ^ bte) & 0xff] myfcs ^= 0xffff + return myfcs def pushByteArray(self, barray): for bte in barray: From 41364ecb7b048719e87bfb7f4d0c8bafb8c1823c Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Sun, 8 Dec 2019 17:42:40 +0000 Subject: [PATCH 18/78] Caller wants a list so avoid string concat loop. --- src/interfacers/EmonHubVEDirectInterfacer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index 4f484084..c5a4cdc8 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -125,7 +125,7 @@ def parse_package(self, data): Convert package from vedirect dictionary format to emonhub expected format """ - clean_data = "%s"%self._settings['nodeoffset'] + clean_data = [str(self._settings['nodeoffset'])] for key in self._extract: if key in data: # Emonhub doesn't like strings so we convert them to ints @@ -140,7 +140,7 @@ def parse_package(self, data): else: data[key] = 1 - clean_data = clean_data + " " + str(data[key]) + clean_data.append(str(data[key])) return clean_data def _read_serial(self): @@ -186,7 +186,6 @@ def read(self): # Create a Payload object c = Cargo.new_cargo(rawdata=self._rx_buf) f = self.parse_package(self._rx_buf) - f = f.split() # Reset buffer self._rx_buf = '' From 7d680f2391a253296d4cba8441d1067f1f982ab4 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Sun, 8 Dec 2019 23:43:16 +0000 Subject: [PATCH 19/78] Misc cleanups. - Create dicts at compile time. - Use startswith as it's less error prone. - Add some FIXMEs. - Make dict at compile time. - Use enumerate. - Lift sum out of if blocks. --- src/Cargo.py | 17 +-- src/emonhub.py | 1 + src/emonhub_interfacer.py | 17 +-- src/emonhub_setup.py | 2 +- src/interfacers/EmonHubBMWInterfacer.py | 5 +- .../EmonHubEmoncmsHTTPInterfacer.py | 2 +- src/interfacers/EmonHubJeeInterfacer.py | 13 +- src/interfacers/EmonHubMqttInterfacer.py | 4 +- src/interfacers/EmonHubPacketGenInterfacer.py | 9 +- src/interfacers/EmonHubSMASolarInterfacer.py | 83 +++++------ src/interfacers/EmonHubSerialInterfacer.py | 1 - src/interfacers/EmonHubSocketInterfacer.py | 4 +- src/interfacers/EmonHubVEDirectInterfacer.py | 15 +- src/interfacers/EmonModbusTcpInterfacer.py | 2 +- .../tmp/EmonFroniusModbusTcpInterfacer.py | 2 +- src/smalibrary/SMANET2PlusPacket.py | 4 +- src/smalibrary/SMASolar_library.py | 139 +++++++++--------- 17 files changed, 142 insertions(+), 178 deletions(-) diff --git a/src/Cargo.py b/src/Cargo.py index 991a8620..e4ef6811 100644 --- a/src/Cargo.py +++ b/src/Cargo.py @@ -2,13 +2,6 @@ class EmonHubCargo(object): uri = 0 - timestamp = 0.0 - target = 0 - nodeid = 0 - nodename = False - names = [] - realdata = [] - rssi = 0 # The class "constructor" - It's actually an initializer def __init__(self, timestamp, target, nodeid, nodename, names, realdata, rssi, rawdata): @@ -31,12 +24,4 @@ def __init__(self, timestamp, target, nodeid, nodename, names, realdata, rssi, r # self.realdatacodes = [] def new_cargo(rawdata="", nodename=False, names=[], realdata=[], nodeid=0, timestamp=0.0, target=0, rssi=0.0): - """ - - :rtype : object - """ - - if not timestamp: - timestamp = time.time() - cargo = EmonHubCargo(timestamp, target, nodeid, nodename, names, realdata, rssi, rawdata) - return cargo + return EmonHubCargo(timestamp or time.time(), target, nodeid, nodename, names, realdata, rssi, rawdata) diff --git a/src/emonhub.py b/src/emonhub.py index cef077e3..ae951964 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -35,6 +35,7 @@ if name != "__init__": # print "Loading: " + name setattr(ehi, name, getattr(getattr(namespace, name), name)) +del name """class EmonHub diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index c6d1957f..b115c7de 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -39,7 +39,7 @@ def wrapper(*args): class EmonHubInterfacer(threading.Thread): def __init__(self, name): - # Initialize logger + # Initialise logger self._log = logging.getLogger("EmonHub") # Initialise thread @@ -61,7 +61,7 @@ def __init__(self, name): self.init_settings = {} self._settings = {} - # Initialize message queue + # Initialise message queue self._sub_channels = {} self._pub_channels = {} @@ -106,7 +106,7 @@ def run(self): for channel in self._settings["pubchannels"]: self._log.debug(str(rxc.uri) + " Sent to channel(start)' : " + str(channel)) - # Initialize channel if needed + # Initialise channel if needed if channel not in self._pub_channels: self._pub_channels[channel] = [] @@ -141,6 +141,7 @@ def add(self, cargo): try: f.append(cargo.timestamp) f.append(cargo.nodeid) + # FIXME replace with f.extend(cargo.realdata) for i in cargo.realdata: f.append(i) if cargo.rssi: @@ -218,12 +219,10 @@ def flush(self): if self._process_post(databuffer): # In case of success, delete sample set from buffer self.buffer.discardLastRetrievedItems(retrievedlength) - # log the time of last succesful post - self._interval_timestamp = time.time() - else: - # slow down retry rate in the case where the last attempt failed - # stops continuous retry attempts filling up the log - self._interval_timestamp = time.time() + # log the time of last successful post + # slow down retry rate in the case where the last attempt failed + # stops continuous retry attempts filling up the log + self._interval_timestamp = time.time() def _process_post(self, data): diff --git a/src/emonhub_setup.py b/src/emonhub_setup.py index 2b4b8a11..189ec07e 100644 --- a/src/emonhub_setup.py +++ b/src/emonhub_setup.py @@ -9,8 +9,8 @@ import time import logging -from configobj import ConfigObj import json +from configobj import ConfigObj """class EmonHubSetup diff --git a/src/interfacers/EmonHubBMWInterfacer.py b/src/interfacers/EmonHubBMWInterfacer.py index 3062b735..b966e086 100644 --- a/src/interfacers/EmonHubBMWInterfacer.py +++ b/src/interfacers/EmonHubBMWInterfacer.py @@ -46,7 +46,7 @@ def __init__(self, name, bmwapiusername='', bmwapipassword='', tempcredentialfil self._TempCredentialFile = tempcredentialfile self._first_time_loop = True - if os.path.exists(self._TempCredentialFile): + if os.path.exists(self._TempCredentialFile): # FIXME race condition with open(self._TempCredentialFile, "r") as cf: credentials = json.load(cf) @@ -91,7 +91,7 @@ def obtainCredentials(self): access_token = parts[0].split("#") for word in access_token[1:]: values = word.split("=") - d[values[0]] = values[1] + d[values[0]] = values[1] # FIXME dict comprehension # We should now have a dictionary object with three entries # token_type, access_token, expires_in @@ -131,7 +131,6 @@ def close(self): def _reset_duration_timer(self): """Reset timer to current date/time""" self._last_time_reading = time.time() - return def _is_it_time(self): """Checks to see if the duration has expired diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index a5ea36b7..745ea269 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -121,7 +121,7 @@ def set(self, **kwargs): # Next line will log apikey if uncommented (privacy ?) #self._log.debug(self.name + " apikey: " + str(setting)) continue - elif key == 'url' and setting[:4] == "http": + elif key == 'url' and setting.startswith("http"): self._log.info("Setting " + self.name + " url: " + setting) self._settings[key] = setting continue diff --git a/src/interfacers/EmonHubJeeInterfacer.py b/src/interfacers/EmonHubJeeInterfacer.py index 2af397fc..fb04d4ab 100644 --- a/src/interfacers/EmonHubJeeInterfacer.py +++ b/src/interfacers/EmonHubJeeInterfacer.py @@ -12,7 +12,7 @@ class EmonHubJeeInterfacer(ehi.EmonHubSerialInterfacer): - def __init__(self, name, com_port='/dev/ttyAMA0', com_baud=0): + def __init__(self, name, com_port='/dev/ttyAMA0', com_baud=38400): """Initialize Interfacer com_port (string): path to COM port @@ -20,16 +20,13 @@ def __init__(self, name, com_port='/dev/ttyAMA0', com_baud=0): """ # Initialization - if com_baud != 0: - super(EmonHubJeeInterfacer, self).__init__(name, com_port, com_baud) - else: - super(EmonHubJeeInterfacer, self).__init__(name, com_port, 38400) + super().__init__(name, com_port, 38400) # Display device firmware version and current settings self.info = ["", ""] if self._ser is not None: self._ser.write("v") - time.sleep(2) + time.sleep(2) # FIXME sleep in initialiser smells self._rx_buf = self._rx_buf + self._ser.readline() if '\r\n' in self._rx_buf: self._rx_buf = "" @@ -137,7 +134,7 @@ def read(self): try: c.rssi = int(r) except ValueError: - self._log.warning("Packet discarded as the RSSI format is invalid: "+ str(f)) + self._log.warning("Packet discarded as the RSSI format is invalid: " + str(f)) return f = f[:-1] @@ -172,7 +169,7 @@ def set(self, **kwargs): setting = self._jee_settings[key] # convert bools to ints if str(setting).capitalize() in ['True', 'False']: - setting = int(setting == "True") + setting = int(setting.capitalize() == "True") # confirmation string always contains baseid, group and freq if all(i in self.info[1] for i in (" i", " g", " @ ", " MHz")): # If setting confirmed as already set, continue without changing diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index c21662b9..70d24099 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -95,9 +95,9 @@ def _process_post(self, databuffer): try: self._mqttc.username_pw_set(self.init_settings['mqtt_user'], self.init_settings['mqtt_passwd']) self._mqttc.connect(self.init_settings['mqtt_host'], self.init_settings['mqtt_port'], 60) - except: + except Exception: self._log.info("Could not connect...") - time.sleep(1.0) + time.sleep(1.0) # FIXME why sleep? we're just about to return True else: frame = databuffer[0] diff --git a/src/interfacers/EmonHubPacketGenInterfacer.py b/src/interfacers/EmonHubPacketGenInterfacer.py index e7b0df7b..7cbe9bc9 100644 --- a/src/interfacers/EmonHubPacketGenInterfacer.py +++ b/src/interfacers/EmonHubPacketGenInterfacer.py @@ -1,3 +1,6 @@ +import time +import requests +from Cargo import new_cargo from emonhub_interfacer import EmonHubInterfacer """class EmonHubPacketGenInterfacer @@ -111,8 +114,6 @@ def action(self): self._interval_timestamp = t - return - def set(self, **kwargs): """ @@ -127,7 +128,7 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'apikey': - if setting[:4].lower() == 'xxxx': # FIXME compare whole string to 'x'*32? + if setting.lower().startswith('xxxx'): # FIXME compare whole string to 'x'*32? self._log.warning("Setting " + self.name + " apikey: obscured") elif len(setting) == 32: self._log.info("Setting " + self.name + " apikey: set") @@ -140,7 +141,7 @@ def set(self, **kwargs): # Next line will log apikey if uncommented (privacy ?) #self._log.debug(self.name + " apikey: " + str(setting)) continue - elif key == 'url' and setting[:4] == "http": + elif key == 'url' and setting.startswith("http"): self._log.info("Setting " + self.name + " url: " + setting) self._settings[key] = setting continue diff --git a/src/interfacers/EmonHubSMASolarInterfacer.py b/src/interfacers/EmonHubSMASolarInterfacer.py index 82949a38..9132a6ff 100644 --- a/src/interfacers/EmonHubSMASolarInterfacer.py +++ b/src/interfacers/EmonHubSMASolarInterfacer.py @@ -141,7 +141,7 @@ def _increment_packet_send_counter(self): #Prevent roll over if self._packet_send_counter >= 0x0FFF: - self._packet_send_counter = 0 + self._packet_send_counter = 0 # FIXME is this the same as "_reset_packet_send_counter" or not? #Appears that comms hangs on certain numbers #simple reset for now to resolve @@ -203,46 +203,44 @@ def read(self): if self._btSocket is None: return - readingsToMake = {} - - readingsToMake["EnergyProduction"] = [0x54000200, 0x00260100, 0x002622FF] - - #This causes problems with some inverters - #readingsToMake["SpotDCPower"] = [0x53800200, 0x00251E00, 0x00251EFF] - - readingsToMake["SpotACPower"] = [0x51000200, 0x00464000, 0x004642FF] - readingsToMake["SpotACTotalPower"] = [0x51000200, 0x00263F00, 0x00263FFF] - readingsToMake["SpotDCVoltage"] = [0x53800200, 0x00451F00, 0x004521FF] - readingsToMake["SpotACVoltage"] = [0x51000200, 0x00464800, 0x004655FF] - readingsToMake["SpotGridFrequency"] = [0x51000200, 0x00465700, 0x004657FF] - readingsToMake["OperationTime"] = [0x54000200, 0x00462E00, 0x00462FFF] - readingsToMake["InverterTemperature"] = [0x52000200, 0x00237700, 0x002377FF] - #Not very useful for reporting - #readingsToMake["MaxACPower"] = [0x51000200, 0x00411E00, 0x004120FF] - #readingsToMake["MaxACPower2"] = [0x51000200, 0x00832A00, 0x00832AFF] - #readingsToMake["GridRelayStatus"] = [0x51800200, 0x00416400, 0x004164FF] - - #Only useful on off grid battery systems - #readingsToMake["ChargeStatus"] = [0x51000200, 0x00295A00, 0x00295AFF] - #readingsToMake["BatteryInfo"] = [0x51000200, 0x00491E00, 0x00495DFF] - - - #Get first inverter in dictionary - inverter = self._Inverters[self._Inverters.keys()[0]] + readingsToMake = { + "EnergyProduction": [0x54000200, 0x00260100, 0x002622FF], + + #This causes problems with some inverters + #readingsToMake["SpotDCPower"] = [0x53800200, 0x00251E00, 0x00251EFF] + + "SpotACPower": [0x51000200, 0x00464000, 0x004642FF], + "SpotACTotalPower": [0x51000200, 0x00263F00, 0x00263FFF], + "SpotDCVoltage": [0x53800200, 0x00451F00, 0x004521FF], + "SpotACVoltage": [0x51000200, 0x00464800, 0x004655FF], + "SpotGridFrequency": [0x51000200, 0x00465700, 0x004657FF], + "OperationTime": [0x54000200, 0x00462E00, 0x00462FFF], + "InverterTemperature": [0x52000200, 0x00237700, 0x002377FF], + #Not very useful for reporting + #"MaxACPower": [0x51000200, 0x00411E00, 0x004120FF], + #"MaxACPower2": [0x51000200, 0x00832A00, 0x00832AFF], + #"GridRelayStatus": [0x51800200, 0x00416400, 0x004164FF], + + #Only useful on off grid battery systems + #"ChargeStatus": [0x51000200, 0x00295A00, 0x00295AFF], + #"BatteryInfo": [0x51000200, 0x00491E00, 0x00495DFF], + } + + #Get first inverter in dictionary # FIXME dicts aren't sorted, there is no "first" + inverter = self._Inverters[list(self._Inverters.keys())[0]] self._log.debug("Reading from inverter " + inverter["inverterName"]) - #Loop through dictionary and take readings, building "output" dictionary as we go output = {} - for key in readingsToMake: - - data = SMASolar_library.request_data(self._btSocket, + for reading, command in readingsToMake.items(): + data = SMASolar_library.request_data( + self._btSocket, self._packet_send_counter, self.mylocalBTAddress, self.MySerialNumber, - readingsToMake[key][0], - readingsToMake[key][1], - readingsToMake[key][2], + command[0], + command[1], + command[2], inverter["susyid"], inverter["serialNumber"]) @@ -250,21 +248,18 @@ def read(self): if data is not None: output.update(SMASolar_library.extract_data(data)) if self._packettrace: - self._log.debug("Packet reply for " + key + ". Packet from {0:04x}/{1:08x}".format(data.getTwoByte(14), data.getFourByteLong(16))) + self._log.debug("Packet reply for " + reading + ". Packet from {0:04x}/{1:08x}".format(data.getTwoByte(14), data.getFourByteLong(16))) self._log.debug(data.debugViewPacket()) - #Sort the output to keep the keys in a consistent order - names = [] - values = [] - for key in sorted(output): - names.append(output[key].Label) - values.append(output[key].Value) - #self._log.debug("Building cargo") c = Cargo.new_cargo() c.rawdata = None - c.realdata = values - c.names = names + #Sort the output to keep the keys in a consistent order + c.names = [] + c.realdata = [] + for key in sorted(output): + c.names.append(output[key].Label) + c.realdata.append(output[key].Value) #TODO: We need to revisit this once we know how multiple inverters communicate with us c.nodeid = inverter["NodeId"] diff --git a/src/interfacers/EmonHubSerialInterfacer.py b/src/interfacers/EmonHubSerialInterfacer.py index a80e3262..fa6395d6 100644 --- a/src/interfacers/EmonHubSerialInterfacer.py +++ b/src/interfacers/EmonHubSerialInterfacer.py @@ -1,5 +1,4 @@ import serial -import time from emonhub_interfacer import EmonHubInterfacer import Cargo diff --git a/src/interfacers/EmonHubSocketInterfacer.py b/src/interfacers/EmonHubSocketInterfacer.py index 0530b2b6..8f159978 100644 --- a/src/interfacers/EmonHubSocketInterfacer.py +++ b/src/interfacers/EmonHubSocketInterfacer.py @@ -138,7 +138,7 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'apikey': - if setting[:4].lower() == 'xxxx': # FIXME compare whole string to 'x'*32? + if setting.lower().startswith('xxxx'): # FIXME compare whole string to 'x'*32? self._log.warning("Setting " + self.name + " apikey: obscured") elif len(setting) == 32: self._log.info("Setting " + self.name + " apikey: set") @@ -151,7 +151,7 @@ def set(self, **kwargs): # Next line will log apikey if uncommented (privacy ?) #self._log.debug(self.name + " apikey: " + str(setting)) continue - elif key == 'url' and setting[:4] == "http": + elif key == 'url' and setting.startswith("http"): self._log.info("Setting " + self.name + " url: " + setting) self._settings[key] = setting continue diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index c5a4cdc8..1fa3f1b6 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -50,16 +50,14 @@ def input(self, byte): """ Parse serial byte code from VE.Direct """ + # FIXME This whole setup is just to parse 'key\tvalue\r\n' lines with 'Checksum' keys over serial + self.bytes_sum += ord(byte) if self.state == self.WAIT_HEADER: - self.bytes_sum += ord(byte) if byte == self.header1: self.state = self.WAIT_HEADER elif byte == self.header2: self.state = self.IN_KEY - - return elif self.state == self.IN_KEY: - self.bytes_sum += ord(byte) if byte == self.delimiter: if self.key == 'Checksum': self.state = self.IN_CHECKSUM @@ -67,9 +65,7 @@ def input(self, byte): self.state = self.IN_VALUE else: self.key += byte - return elif self.state == self.IN_VALUE: - self.bytes_sum += ord(byte) if byte == self.header1: self.state = self.WAIT_HEADER self.dict[self.key] = self.value @@ -77,18 +73,15 @@ def input(self, byte): self.value = '' else: self.value += byte - return elif self.state == self.IN_CHECKSUM: - self.bytes_sum += ord(byte) self.key = '' self.value = '' self.state = self.WAIT_HEADER if self.bytes_sum % 256 == 0: self.bytes_sum = 0 return self.dict - else: - self.bytes_sum = 0 - + # FIXME if the checksum is wrong should we not throw away the dict? + self.bytes_sum = 0 else: raise AssertionError() diff --git a/src/interfacers/EmonModbusTcpInterfacer.py b/src/interfacers/EmonModbusTcpInterfacer.py index a999e8b3..102dbe2b 100644 --- a/src/interfacers/EmonModbusTcpInterfacer.py +++ b/src/interfacers/EmonModbusTcpInterfacer.py @@ -50,7 +50,7 @@ def close(self): # Close TCP connection if self._con is not None: self._log.debug("Closing tcp port") - self._con.close() + self._con.close() def _open_modTCP(self, modbus_IP, modbus_port): """ Open connection to modbus device """ diff --git a/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py b/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py index 68905182..f81e4546 100644 --- a/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py +++ b/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py @@ -22,7 +22,7 @@ def __init__(self, name, modbus_IP='192.168.1.10', modbus_port=502): self._log.debug("EmonFroniusModbusTcpInterfacer: Init") if self._modcon: # Display device firmware version and current settings - self.info =["", ""] + self.info = ["", ""] #self._log.info("Modtcp Connected") r2 = self._con.read_holding_registers(40005 - 1, 4, unit=1) r3 = self._con.read_holding_registers(40021 - 1, 4, unit=1) diff --git a/src/smalibrary/SMANET2PlusPacket.py b/src/smalibrary/SMANET2PlusPacket.py index b2070756..e3adec87 100644 --- a/src/smalibrary/SMANET2PlusPacket.py +++ b/src/smalibrary/SMANET2PlusPacket.py @@ -265,11 +265,11 @@ def debugViewPacket(self): pos += 4 s = "" - for j in range(pos, len(self.packet)): + for j, b in enumerate(self.packet[pos:]): if j % 16 == 0 or j == pos: s += "\n %08x: " % j - s += "%02x " % self.packet[j] + s += "%02x " % b str_list.append("L2 Payload= %s" % s) myfcs = self.FCSChecksum ^ 0xffff diff --git a/src/smalibrary/SMASolar_library.py b/src/smalibrary/SMASolar_library.py index 7133d8f8..9079bfc5 100644 --- a/src/smalibrary/SMASolar_library.py +++ b/src/smalibrary/SMASolar_library.py @@ -57,8 +57,8 @@ def read_SMA_BT_Packet(btSocket, waitPacketNumber=0, waitForPacket=False, myloca bluetoothbuffer = Read_Level1_Packet_From_BT_Stream(btSocket, mylocalBTAddress) - v = namedtuple("SMAPacket", ["levelone", "leveltwo"], verbose=False) - v.levelone = bluetoothbuffer + v = namedtuple("SMAPacket", ["levelone", "leveltwo"]) + v.levelone = bluetoothbuffer # FIXME that's not how namedtuples work! if bluetoothbuffer.containsLevel2Packet(): # Instance to hold level 2 packet @@ -97,15 +97,12 @@ def read_SMA_BT_Packet(btSocket, waitPacketNumber=0, waitForPacket=False, myloca # """Convert a byte string to it's hex string representation e.g. for output.""" # return ''.join(["%02X " % x for x in byteStr]) -def BTAddressToByteArray(hexStr, sep): - """Convert a hex string containing seperators to a bytearray object""" - b = bytearray() - for i in hexStr.split(sep): - b.append(int(i, 16)) - return b +def BTAddressToByteArray(hexStr, sep=':'): + """Convert a hex string containing separators to a bytearray object""" + return bytearray([int(i, 16) for i in hexStr.split(sep)]) def encodeInverterPassword(InverterPassword): - # """Encodes InverterPassword (digit number) into array for passing to SMA protocol""" + """Encodes InverterPassword (digit number) into array for passing to SMA protocol""" if len(InverterPassword) > 12: raise Exception("Password can only be up to 12 digits in length") @@ -123,29 +120,31 @@ def encodeInverterPassword(InverterPassword): return a def getInverterDetails(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber): - DeviceClass = {} - DeviceClass['8000'] = 'AllDevices' - DeviceClass['8001'] = 'SolarInverter' - DeviceClass['8002'] = 'WindTurbineInverter' - DeviceClass['8007'] = 'BatteryInverter' - DeviceClass['8033'] = 'Consumer' - DeviceClass['8064'] = 'SensorSystem' - DeviceClass['8065'] = 'ElectricityMeter' - DeviceClass['8128'] = 'CommunicationProduct' - - DeviceType = {} - DeviceType['9073'] = 'SB 3000HF-30' - DeviceType['9074'] = 'SB 3000TL-21' - DeviceType['9075'] = 'SB 4000TL-21' - DeviceType['9076'] = 'SB 5000TL-21' - DeviceType['9119'] = 'Sunny HomeManager' + DeviceClass = { + '8000': 'AllDevices', + '8001': 'SolarInverter', + '8002': 'WindTurbineInverter', + '8007': 'BatteryInverter', + '8033': 'Consumer', + '8064': 'SensorSystem', + '8065': 'ElectricityMeter', + '8128': 'CommunicationProduct', + } + + DeviceType = { + '9073': 'SB 3000HF-30', + '9074': 'SB 3000TL-21', + '9075': 'SB 4000TL-21', + '9076': 'SB 5000TL-21', + '9119': 'Sunny HomeManager', + } data = request_data(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber, 0x58000200, 0x00821E00, 0x008220FF) - reply = {} - - reply["susyid"] = data.getDestinationSusyid() - reply["serialNumber"] = data.getDestinationSerial() + reply = { + "susyid": data.getDestinationSusyid(), + "serialNumber": data.getDestinationSerial(), + } # idate = bluetoothbuffer.leveltwo.getFourByteLong(40 + 4) # t = time.localtime(long(idate)) @@ -244,9 +243,9 @@ def initaliseSMAConnection(btSocket, mylocalBTAddress, MySerialNumber, packet_se # Reply to 0x0002 cmd with our data send = SMABluetoothPacket(0x1F, 0x00, 0x00, 0x02, 0x00, mylocalBTAddress, inverterAddress) send.pushUnescapedByteArray(bytearray([0x00, 0x04, 0x70, 0x00, - netid, - 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00])) + netid, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00])) send.finish() send.sendPacket(btSocket) @@ -319,7 +318,7 @@ def request_data(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber bluetoothbuffer = read_SMA_BT_Packet(btSocket, packet_send_counter, True, mylocalBTAddress) if bluetoothbuffer.leveltwo.errorCode() > 0: - return None + return None # FIXME raise an Exception like all the other error paths leveltwo = bluetoothbuffer.leveltwo @@ -335,51 +334,47 @@ def request_data(btSocket, packet_send_counter, mylocalBTAddress, MySerialNumber #If no errors return packet return leveltwo +SpotValue = namedtuple("spotvalue", ["Description", "Scale", "RecSize"]) +spotvalues = { + 0x263f: SpotValue("ACTotalPower", 1, 28), + 0x411e: SpotValue("Ph1ACMax", 1, 28), + 0x411f: SpotValue("Ph2ACMax", 1, 28), + 0x4120: SpotValue("Ph3ACMax", 1, 28), + 0x4640: SpotValue("Ph1Power", 1, 28), + 0x4641: SpotValue("Ph2Power", 1, 28), + 0x4642: SpotValue("Ph3Power", 1, 28), + 0x4648: SpotValue("Ph1ACVolt", 100, 28), + 0x4649: SpotValue("Ph2ACVolt", 100, 28), + 0x464a: SpotValue("Ph3ACVolt", 100, 28), + 0x4650: SpotValue("Ph1ACCurrent", 1000, 28), + 0x4651: SpotValue("Ph2ACCurrent", 1000, 28), + 0x4652: SpotValue("Ph3ACCurrent", 1000, 28), + 0x4657: SpotValue("ACGridFreq", 100, 28), + + 0x2601: SpotValue("TotalYield", 1, 16), # 8 byte word + 0x2622: SpotValue("DayYield", 1, 16), # 8 byte word + 0x462f: SpotValue("FeedInTime", 3600, 16), # 8 byte word + 0x462e: SpotValue("OperatingTime", 3600, 16), # 8 byte word + + 0x251e: SpotValue("DCPower1", 1, 28), + 0x451f: SpotValue("DCVoltage1", 100, 28), + 0x4521: SpotValue("DCCurrent1", 1000, 28), + + 0x2377: SpotValue("InvTemperature", 100, 28), + 0x821e: SpotValue("Inverter Name", 0, 28), + 0x295A: SpotValue("ChargeStatus", 1, 28), +} + +SpotValueOutput = namedtuple("SpotValueOutput", ["Label", "Value"]) + def extract_data(level2Packet): #Return a dictionary outputlist = {} - spotvaluelist = {} - - SpotValue = namedtuple("spotvalue", ["Description", "Scale", "RecSize"]) - spotvaluelist[0x263f] = SpotValue("ACTotalPower", 1, 28) - spotvaluelist[0x411e] = SpotValue("Ph1ACMax", 1, 28) - spotvaluelist[0x411f] = SpotValue("Ph2ACMax", 1, 28) - spotvaluelist[0x4120] = SpotValue("Ph3ACMax", 1, 28) - spotvaluelist[0x4640] = SpotValue("Ph1Power", 1, 28) - spotvaluelist[0x4641] = SpotValue("Ph2Power", 1, 28) - spotvaluelist[0x4642] = SpotValue("Ph3Power", 1, 28) - spotvaluelist[0x4648] = SpotValue("Ph1ACVolt", 100, 28) - spotvaluelist[0x4649] = SpotValue("Ph2ACVolt", 100, 28) - spotvaluelist[0x464a] = SpotValue("Ph3ACVolt", 100, 28) - spotvaluelist[0x4650] = SpotValue("Ph1ACCurrent", 1000, 28) - spotvaluelist[0x4651] = SpotValue("Ph2ACCurrent", 1000, 28) - spotvaluelist[0x4652] = SpotValue("Ph3ACCurrent", 1000, 28) - spotvaluelist[0x4657] = SpotValue("ACGridFreq", 100, 28) - - spotvaluelist[0x2601] = SpotValue("TotalYield", 1, 16) #8 byte word - spotvaluelist[0x2622] = SpotValue("DayYield", 1, 16) #8 byte word - spotvaluelist[0x462f] = SpotValue("FeedInTime", 3600, 16) #8 byte word - spotvaluelist[0x462e] = SpotValue("OperatingTime", 3600, 16) #8 byte word - - spotvaluelist[0x251e] = SpotValue("DCPower1", 1, 28) - spotvaluelist[0x451f] = SpotValue("DCVoltage1", 100, 28) - spotvaluelist[0x4521] = SpotValue("DCCurrent1", 1000, 28) - - spotvaluelist[0x2377] = SpotValue("InvTemperature", 100, 28) - #spotvaluelist[0x821e] = SpotValue("Inverter Name", 0, 28) - spotvaluelist[0x295A] = SpotValue("ChargeStatus", 1, 28) - - SpotValueOutput = namedtuple("SpotValueOutput", ["Label", "Value"]) - #Start here offset = 40 - if level2Packet.totalPayloadLength() == 0: - return outputlist - while offset < level2Packet.totalPayloadLength(): - recordSize = 28 value = 0 classtype = level2Packet.getByte(offset) #classtype should always be =1 @@ -398,8 +393,8 @@ def extract_data(level2Packet): if value == 0x8000 or value == 0xFFFF: value = 0 - if readingtype in spotvaluelist: - v = spotvaluelist[readingtype] + if readingtype in spotvalues: + v = spotvalues[readingtype] #Check if its an 4 byte value (QWORD) if v.RecSize == 16: From 7f0253abb5f353a1891ab0a286a6247fabae28ce Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Sun, 8 Dec 2019 23:47:12 +0000 Subject: [PATCH 20/78] Use requests instead of urllib2. It's much easier to use and it didn't change from python2 to python3. I can't easily test PacketGen, though, but it seems to be unused. --- src/emonhub_interfacer.py | 31 ++++++------------- .../EmonHubEmoncmsHTTPInterfacer.py | 2 +- src/interfacers/EmonHubPacketGenInterfacer.py | 18 ++++------- 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index b115c7de..68398fba 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -10,11 +10,8 @@ import time import logging import threading -try: - import urllib -except ImportError: - import urllib2 as urllib # FIXME Python2 compatibility import traceback +import requests import emonhub_coder as ehc import emonhub_buffer as ehb @@ -249,25 +246,15 @@ def _send_post(self, post_url, post_body=None): """ - reply = "" - request = urllib.Request(post_url, post_body) try: - response = urllib.urlopen(request, timeout=60) - except urllib.HTTPError as e: - self._log.warning(self.name + " couldn't send to server, HTTPError: " + - str(e.code)) - except urllib.URLError as e: - self._log.warning(self.name + " couldn't send to server, URLError: " + - str(e.reason)) - except urllib.HTTPException: - self._log.warning(self.name + " couldn't send to server, HTTPException") - except Exception: - self._log.warning(self.name + " couldn't send to server, Exception: " + - traceback.format_exc()) - else: - reply = response.read() - finally: - return reply + if post_body: + reply = requests.post(post_url, post_body) + else: + reply = requests.get(post_url) + reply.raise_for_status() # Raise an exception if status code isn't 200 + except requests.exceptions.RequestException as ex: + self._log.warning(self.name + " couldn't send to server: " + str(ex)) + return reply.text def _process_rx(self, cargo): """Process a frame of data diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index 745ea269..500c247c 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -88,7 +88,7 @@ def sendstatus(self): # add apikey post_url = post_url + self._settings['apikey'] # send request - reply = self._send_post(post_url, None) + self._send_post(post_url) def set(self, **kwargs): """ diff --git a/src/interfacers/EmonHubPacketGenInterfacer.py b/src/interfacers/EmonHubPacketGenInterfacer.py index 7cbe9bc9..b9ce63c8 100644 --- a/src/interfacers/EmonHubPacketGenInterfacer.py +++ b/src/interfacers/EmonHubPacketGenInterfacer.py @@ -37,22 +37,16 @@ def read(self): req = self._settings['url'] + \ "/emoncms/packetgen/getpacket.json?apikey=" - try: - packet = urllib2.urlopen(req + self._settings['apikey']).read() - except: - return - # logged without apikey added for security self._log.info("requesting packet: " + req + "E-M-O-N-C-M-S-A-P-I-K-E-Y") try: - packet = json.loads(packet) - except ValueError: - self._log.warning("no packet returned") + packet = requests.get(req + self._settings['apikey']).json() + except (ValueError, requests.exceptions.RequestException) as ex: + self._log.warning("no packet returned: " + str(ex)) return raw = "" - target = 0 values = [] datacodes = [] @@ -100,9 +94,9 @@ def action(self): return try: - z = urllib2.urlopen(self._settings['url'] + - "/emoncms/packetgen/getinterval.json?apikey=" - + self._settings['apikey']).read() + z = requests.get(self._settings['url'] + + "/emoncms/packetgen/getinterval.json?apikey=" + + self._settings['apikey']).text i = int(z[1:-1]) except: self._log.info("request interval not returned") From 5fe771b555e95fab979996212bf2b59228121d3c Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Sun, 8 Dec 2019 23:58:57 +0000 Subject: [PATCH 21/78] Return result instead of discarding. --- src/smalibrary/SMABluetoothPacket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smalibrary/SMABluetoothPacket.py b/src/smalibrary/SMABluetoothPacket.py index 967854ed..5688f4ec 100644 --- a/src/smalibrary/SMABluetoothPacket.py +++ b/src/smalibrary/SMABluetoothPacket.py @@ -81,7 +81,7 @@ def pushEscapedByte(self, value): self.UnescapedArray.append(value) def sendPacket(self, btSocket): - l = btSocket.send(str(self.header) + str(self.SourceAddress) + str(self.DestinationAddress) + str(self.cmdcode) + str(self.RawByteArray)) + return btSocket.send(str(self.header) + str(self.SourceAddress) + str(self.DestinationAddress) + str(self.cmdcode) + str(self.RawByteArray)) def containsLevel2Packet(self): return (len(self.UnescapedArray) >= 5 and From 03fb179ec0c19c445d7cb1e519b73f7f6af653fc Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Sun, 8 Dec 2019 23:55:31 +0000 Subject: [PATCH 22/78] Python3. --- README.md | 4 ++-- install.sh | 4 ++-- src/Cargo.py | 2 +- src/emonhub.py | 12 ++++++------ src/emonhub_buffer.py | 2 +- src/emonhub_interfacer.py | 4 ++-- src/emonhub_setup.py | 4 ++-- src/interfacers/EmonHubBMWInterfacer.py | 4 ++-- src/interfacers/EmonHubEmoncmsHTTPInterfacer.py | 14 +++++++------- src/interfacers/EmonHubGraphiteInterfacer.py | 16 ++++++++-------- src/interfacers/EmonHubJeeInterfacer.py | 8 ++++---- src/interfacers/EmonHubMqttInterfacer.py | 8 ++++---- src/interfacers/EmonHubPacketGenInterfacer.py | 8 ++++---- src/interfacers/EmonHubSMASolarInterfacer.py | 4 ++-- src/interfacers/EmonHubSerialInterfacer.py | 2 +- src/interfacers/EmonHubSocketInterfacer.py | 8 ++++---- src/interfacers/EmonHubTemplateInterfacer.py | 8 ++++---- src/interfacers/EmonHubTx3eInterfacer.py | 8 ++++---- src/interfacers/EmonHubVEDirectInterfacer.py | 2 +- src/interfacers/EmonModbusTcpInterfacer.py | 4 ++-- .../tmp/EmonFroniusModbusTcpInterfacer.py | 4 ++-- src/interfacers/tmp/EmonHubSmilicsInterfacer.py | 2 +- src/smalibrary/SMANET2PlusPacket.py | 8 ++------ src/smalibrary/SMASolar_library.py | 10 +++------- 24 files changed, 71 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 48a8bb14..759b0a5d 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ Update apt information: sudo apt-get update - sudo apt-get install -y mosquitto python-pip python-serial python-configobj python-requests - sudo pip install paho-mqtt + sudo apt-get install -y mosquitto python3-pip python3-serial python3-configobj python3-requests + sudo pip3 install paho-mqtt It is recommended to turn off mosquitto persistence diff --git a/install.sh b/install.sh index 6d50f939..866f710a 100755 --- a/install.sh +++ b/install.sh @@ -16,8 +16,8 @@ if [ "$emonSD_pi_env" = "" ]; then echo fi -sudo apt-get install -y python-serial python-configobj -sudo pip install paho-mqtt requests +sudo apt-get install -y python3-serial python3-configobj python3-pip +sudo pip3 install paho-mqtt requests if [ "$emonSD_pi_env" = "1" ]; then # RaspberryPi Serial configuration diff --git a/src/Cargo.py b/src/Cargo.py index e4ef6811..7d0295aa 100644 --- a/src/Cargo.py +++ b/src/Cargo.py @@ -1,6 +1,6 @@ import time -class EmonHubCargo(object): +class EmonHubCargo: uri = 0 # The class "constructor" - It's actually an initializer diff --git a/src/emonhub.py b/src/emonhub.py index ae951964..c9dd1ddb 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ @@ -47,7 +47,7 @@ """ -class EmonHub(object): +class EmonHub: __version__ = "emonHub emon-pi variant v2.1.2" @@ -101,7 +101,7 @@ def run(self): # For all Interfacers kill_list = [] - for I in self._interfacers.itervalues(): + for I in self._interfacers.values(): # Check threads are still running if not I.isAlive(): kill_list.append(I.name) # <-avoid modification of iterable within loop @@ -147,7 +147,7 @@ def close(self): self._log.info("Exiting hub...") - for I in self._interfacers.itervalues(): + for I in self._interfacers.values(): I.stop = True I.join() @@ -174,7 +174,7 @@ def _update_settings(self, settings): self.temp_buffer = {} # Interfacers - for name in self._interfacers.keys(): + for name in self._interfacers: # Delete interfacers if not listed or have no 'Type' in the settings without further checks # (This also provides an ability to delete & rebuild by commenting 'Type' in conf) if name in settings['interfacers'] and 'Type' in settings['interfacers'][name]: @@ -195,7 +195,7 @@ def _update_settings(self, settings): self._interfacers[name].stop = True del self._interfacers[name] - for name, I in settings['interfacers'].iteritems(): + for name, I in settings['interfacers'].items(): # If interfacer does not exist, create it if name not in self._interfacers: try: diff --git a/src/emonhub_buffer.py b/src/emonhub_buffer.py index b9c617cb..c1d28dfc 100644 --- a/src/emonhub_buffer.py +++ b/src/emonhub_buffer.py @@ -15,7 +15,7 @@ """ -class AbstractBuffer(): +class AbstractBuffer: def storeItem(self, data): raise NotImplementedError diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 68398fba..85c66549 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -40,7 +40,7 @@ def __init__(self, name): self._log = logging.getLogger("EmonHub") # Initialise thread - super(threading.Thread, self).__init__(self) + super().__init__() self.setName(name) # Initialise settings @@ -588,7 +588,7 @@ def set(self, **kwargs): """ #def setall(self, **kwargs): - for key, setting in self._defaults.iteritems(): + for key, setting in self._defaults.items(): if key in kwargs.keys(): setting = kwargs[key] else: diff --git a/src/emonhub_setup.py b/src/emonhub_setup.py index 189ec07e..d956dd4e 100644 --- a/src/emonhub_setup.py +++ b/src/emonhub_setup.py @@ -42,7 +42,7 @@ """ -class EmonHubSetup(object): +class EmonHubSetup: def __init__(self): # Initialize logger self._log = logging.getLogger("EmonHub") @@ -71,7 +71,7 @@ def check_settings(self): class EmonHubFileSetup(EmonHubSetup): def __init__(self, filename): # Initialization - super(EmonHubFileSetup, self).__init__() + super().__init__() self._fileformat = "ConfigObj" # or "ConfigObj" diff --git a/src/interfacers/EmonHubBMWInterfacer.py b/src/interfacers/EmonHubBMWInterfacer.py index b966e086..519ff618 100644 --- a/src/interfacers/EmonHubBMWInterfacer.py +++ b/src/interfacers/EmonHubBMWInterfacer.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # EmonHubBMWInterfacer released for use by OpenEnergyMonitor project # GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 # See LICENCE and README file for details @@ -32,7 +32,7 @@ def __init__(self, name, bmwapiusername='', bmwapipassword='', tempcredentialfil """Initialize interfacer""" # Initialization - super(EmonHubBMWInterfacer, self).__init__(name) + super().__init__(name) self._NodeId = int(nodeid) self._Username = bmwapiusername diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index 500c247c..c5544896 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -8,7 +8,7 @@ class EmonHubEmoncmsHTTPInterfacer(EmonHubInterfacer): def __init__(self, name): # Initialization - super(EmonHubEmoncmsHTTPInterfacer, self).__init__(name) + super().__init__(name) # add or alter any default settings for this reporter # defaults previously defined in inherited emonhub_interfacer @@ -39,7 +39,7 @@ def _process_post(self, databuffer): # [[timestamp, nodeid, datavalues][timestamp, nodeid, datavalues]] # [[1399980731, 10, 150, 250 ...]] - if 'apikey' not in self._settings.keys() or len(str(self._settings['apikey'])) != 32 \ + if 'apikey' not in self._settings or len(str(self._settings['apikey'])) != 32 \ or str(self._settings['apikey']).lower() == 'x' * 32: # Return true to clear buffer if the apikey is not set return True @@ -77,7 +77,7 @@ def _process_post(self, databuffer): return False def sendstatus(self): - if 'apikey' not in self._settings.keys() or len(str(self._settings['apikey'])) != 32 \ + if 'apikey' not in self._settings or len(str(self._settings['apikey'])) != 32 \ or str(self._settings['apikey']).lower() == 'x' * 32: return @@ -97,18 +97,18 @@ def set(self, **kwargs): :return: """ - super(EmonHubEmoncmsHTTPInterfacer, self).set(**kwargs) + super().set(**kwargs) - for key, setting in self._cms_settings.iteritems(): + for key, setting in self._cms_settings.items(): #valid = False - if key not in kwargs.keys(): + if key not in kwargs: setting = self._cms_settings[key] else: setting = kwargs[key] if key in self._settings and self._settings[key] == setting: continue elif key == 'apikey': - if setting[:4].lower() == 'xxxx': # FIXME compare whole string to 'x'*32? + if setting.lower().startswith('xxxx'): # FIXME compare whole string to 'x'*32? self._log.warning("Setting " + self.name + " apikey: obscured") elif len(setting) == 32: self._log.info("Setting " + self.name + " apikey: set") diff --git a/src/interfacers/EmonHubGraphiteInterfacer.py b/src/interfacers/EmonHubGraphiteInterfacer.py index 9ece01be..8a85bbd2 100644 --- a/src/interfacers/EmonHubGraphiteInterfacer.py +++ b/src/interfacers/EmonHubGraphiteInterfacer.py @@ -8,7 +8,7 @@ class EmonHubGraphiteInterfacer(EmonHubInterfacer): def __init__(self, name): # Initialization - super(EmonHubGraphiteInterfacer, self).__init__(name) + super().__init__(name) self._defaults.update({'batchsize': 100, 'interval': 30}) self._settings.update(self._defaults) @@ -62,7 +62,7 @@ def _process_post(self, databuffer): for frame in databuffer: nodename = frame['node'] - for inputname, value in frame['data'].iteritems(): + for inputname, value in frame['data'].items(): # path path = self._settings['prefix'] + '.' + nodename + "." + inputname # payload @@ -107,18 +107,18 @@ def _send_metrics(self, metrics=[]): return True def set(self, **kwargs): - super(EmonHubGraphiteInterfacer, self).set(**kwargs) - for key, setting in self._graphite_settings.iteritems(): - if key in kwargs.keys(): + super().set(**kwargs) + for key, setting in self._graphite_settings.items(): + if key in kwargs: # replace default self._settings[key] = kwargs[key] """ def set(self, **kwargs): - super (EmonHubGraphiteInterfacer, self).set(**kwargs) - for key, setting in self._graphite_settings.iteritems(): + super ().set(**kwargs) + for key, setting in self._graphite_settings.items(): #valid = False - if key not in kwargs.keys(): + if key not in kwargs: setting = self._graphite_settings[key] else: setting = kwargs[key] diff --git a/src/interfacers/EmonHubJeeInterfacer.py b/src/interfacers/EmonHubJeeInterfacer.py index fb04d4ab..82f6a65e 100644 --- a/src/interfacers/EmonHubJeeInterfacer.py +++ b/src/interfacers/EmonHubJeeInterfacer.py @@ -2,7 +2,7 @@ import json import datetime import Cargo -import EmonHubSerialInterfacer as ehi +from . import EmonHubSerialInterfacer as ehi """class EmonHubJeeInterfacer @@ -161,9 +161,9 @@ def set(self, **kwargs): """ - for key, setting in self._jee_settings.iteritems(): + for key, setting in self._jee_settings.items(): # Decide which setting value to use - if key in kwargs.keys(): + if key in kwargs: setting = kwargs[key] else: setting = self._jee_settings[key] @@ -200,7 +200,7 @@ def set(self, **kwargs): time.sleep(1) # include kwargs from parent - super(EmonHubJeeInterfacer, self).set(**kwargs) + super().set(**kwargs) def action(self): """Actions that need to be done on a regular basis. diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index 70d24099..03a516a4 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -14,7 +14,7 @@ def __init__(self, name, mqtt_user=" ", mqtt_passwd=" ", mqtt_host="127.0.0.1", """ # Initialization - super(EmonHubMqttInterfacer, self).__init__(name) + super().__init__(name) # set the default setting values for this interfacer self._defaults.update({'datacode': '0'}) @@ -239,11 +239,11 @@ def set(self, **kwargs): :return: """ - super(EmonHubMqttInterfacer, self).set(**kwargs) + super().set(**kwargs) - for key, setting in self._mqtt_settings.iteritems(): + for key, setting in self._mqtt_settings.items(): #valid = False - if key not in kwargs.keys(): + if key not in kwargs: setting = self._mqtt_settings[key] else: setting = kwargs[key] diff --git a/src/interfacers/EmonHubPacketGenInterfacer.py b/src/interfacers/EmonHubPacketGenInterfacer.py index b9ce63c8..31a35225 100644 --- a/src/interfacers/EmonHubPacketGenInterfacer.py +++ b/src/interfacers/EmonHubPacketGenInterfacer.py @@ -17,7 +17,7 @@ def __init__(self, name): """ # Initialization - super(EmonHubPacketGenInterfacer, self).__init__(name) + super().__init__(name) self._control_timestamp = 0 self._control_interval = 5 @@ -113,9 +113,9 @@ def set(self, **kwargs): """ - for key, setting in self._pg_settings.iteritems(): + for key, setting in self._pg_settings.items(): # Decide which setting value to use - if key in kwargs.keys(): + if key in kwargs: setting = kwargs[key] else: setting = self._pg_settings[key] @@ -143,4 +143,4 @@ def set(self, **kwargs): self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) # include kwargs from parent - super(EmonHubPacketGenInterfacer, self).set(**kwargs) + super().set(**kwargs) diff --git a/src/interfacers/EmonHubSMASolarInterfacer.py b/src/interfacers/EmonHubSMASolarInterfacer.py index 9132a6ff..3087e075 100644 --- a/src/interfacers/EmonHubSMASolarInterfacer.py +++ b/src/interfacers/EmonHubSMASolarInterfacer.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # EmonHubSMASolarInterfacer released for use by OpenEnergyMonitor project # GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 # See LICENCE and README file for details @@ -28,7 +28,7 @@ def __init__(self, name, inverteraddress='', inverterpincode='0000', timeinverva """Initialize interfacer""" # Initialization - super(EmonHubSMASolarInterfacer, self).__init__(name) + super().__init__(name) self._btSocket = None self._inverteraddress = inverteraddress diff --git a/src/interfacers/EmonHubSerialInterfacer.py b/src/interfacers/EmonHubSerialInterfacer.py index fa6395d6..d471355b 100644 --- a/src/interfacers/EmonHubSerialInterfacer.py +++ b/src/interfacers/EmonHubSerialInterfacer.py @@ -19,7 +19,7 @@ def __init__(self, name, com_port='', com_baud=9600): """ # Initialization - super(EmonHubSerialInterfacer, self).__init__(name) + super().__init__(name) # Open serial port self._ser = self._open_serial_port(com_port, com_baud) diff --git a/src/interfacers/EmonHubSocketInterfacer.py b/src/interfacers/EmonHubSocketInterfacer.py index 8f159978..b9c1bf0b 100644 --- a/src/interfacers/EmonHubSocketInterfacer.py +++ b/src/interfacers/EmonHubSocketInterfacer.py @@ -19,7 +19,7 @@ def __init__(self, name, port_nb=50011): """ # Initialization - super(EmonHubSocketInterfacer, self).__init__(name) + super().__init__(name) # add an apikey setting self._skt_settings = {'apikey': ""} @@ -129,9 +129,9 @@ def set(self, **kwargs): """ - for key, setting in self._skt_settings.iteritems(): + for key, setting in self._skt_settings.items(): # Decide which setting value to use - if key in kwargs.keys(): + if key in kwargs: setting = kwargs[key] else: setting = self._skt_settings[key] @@ -159,4 +159,4 @@ def set(self, **kwargs): self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) # include kwargs from parent - super(EmonHubSocketInterfacer, self).set(**kwargs) + super().set(**kwargs) diff --git a/src/interfacers/EmonHubTemplateInterfacer.py b/src/interfacers/EmonHubTemplateInterfacer.py index 9df4bca8..6b0de755 100644 --- a/src/interfacers/EmonHubTemplateInterfacer.py +++ b/src/interfacers/EmonHubTemplateInterfacer.py @@ -15,7 +15,7 @@ def __init__(self, name, port_nb=50011): """ # Initialization - super(EmonHubTemplateInterfacer, self).__init__(name) + super().__init__(name) # add or alter any default settings for this interfacer # defaults previously defined in inherited emonhub_interfacer @@ -112,9 +112,9 @@ def _process_post(self, databuffer): return True def set(self, **kwargs): - for key, setting in self._template_settings.iteritems(): + for key, setting in self._template_settings.items(): # Decide which setting value to use - if key in kwargs.keys(): + if key in kwargs: setting = kwargs[key] else: setting = self._template_settings[key] @@ -128,4 +128,4 @@ def set(self, **kwargs): self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) # include kwargs from parent - super(EmonHubTemplateInterfacer, self).set(**kwargs) + super().set(**kwargs) diff --git a/src/interfacers/EmonHubTx3eInterfacer.py b/src/interfacers/EmonHubTx3eInterfacer.py index ae44d2fc..e5d64add 100644 --- a/src/interfacers/EmonHubTx3eInterfacer.py +++ b/src/interfacers/EmonHubTx3eInterfacer.py @@ -1,6 +1,6 @@ import re import Cargo -import EmonHubSerialInterfacer as ehi +from . import EmonHubSerialInterfacer as ehi """class EmonHubTx3eInterfacer @@ -22,7 +22,7 @@ def __init__(self, name, com_port='', com_baud=9600): """ # Initialization - super(EmonHubTx3eInterfacer, self).__init__(name, com_port, com_baud) + super().__init__(name, com_port, com_baud) self._settings.update({ 'nodename': "" @@ -94,8 +94,8 @@ def read(self): return c def set(self, **kwargs): - for key, setting in self._settings.iteritems(): - if key in kwargs.keys(): + for key, setting in self._settings.items(): + if key in kwargs: # replace default # self._log.debug(kwargs[key]) self._settings[key] = kwargs[key] diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index 1fa3f1b6..17b2ce19 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -21,7 +21,7 @@ def __init__(self, name, com_port='', com_baud=9600, toextract='', poll_interval """ # Initialization - super(EmonHubVEDirectInterfacer, self).__init__(name) + super().__init__(name) # Open serial port self._ser = self._open_serial_port(com_port, com_baud) diff --git a/src/interfacers/EmonModbusTcpInterfacer.py b/src/interfacers/EmonModbusTcpInterfacer.py index 102dbe2b..b165c0fd 100644 --- a/src/interfacers/EmonModbusTcpInterfacer.py +++ b/src/interfacers/EmonModbusTcpInterfacer.py @@ -26,7 +26,7 @@ def __init__(self, name, modbus_IP='192.168.1.10', modbus_port=0): """ # Initialization - super(EmonModbusTcpInterfacer, self).__init__(name) + super().__init__(name) if not pymodbus_found: self._log.error("PYMODBUS NOT PRESENT BUT NEEDED !!") @@ -41,7 +41,7 @@ def __init__(self, name, modbus_IP='192.168.1.10', modbus_port=0): self._log.info("Connection to ModbusTCP client failed. Will try again") def set(self, **kwargs): - for key in kwargs.keys(): + for key in kwargs: setting = kwargs[key] self._settings[key] = setting self._log.debug("Setting " + self.name + " %s: %s" % (key, setting)) diff --git a/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py b/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py index f81e4546..59eed556 100644 --- a/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py +++ b/src/interfacers/tmp/EmonFroniusModbusTcpInterfacer.py @@ -1,6 +1,6 @@ from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder -import EmonModbusTcpInterfacer as EmonModbusTcpInterfacer +from . import EmonModbusTcpInterfacer as EmonModbusTcpInterfacer """class EmonModbusTcpInterfacer Monitors Solar Inverter using modbus tcp @@ -14,7 +14,7 @@ def __init__(self, name, modbus_IP='192.168.1.10', modbus_port=502): """ # Initialization - super(EmonFroniusModbusTcpInterfacer, self).__init__(name) + super().__init__(name) # Connection opened by parent class INIT # Retrieve Fronius specific inverter info if connection successful diff --git a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py index 0af7559a..97f5f379 100644 --- a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py +++ b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py @@ -34,7 +34,7 @@ def __init__(self, name, port): name (str): Configuration name. port (int): The port the webserver should listen on. """ - super(EmonHubSmilicsInterfacer, self).__init__(name) + super().__init__(name) self._settings = { 'subchannels': ['ch1'], diff --git a/src/smalibrary/SMANET2PlusPacket.py b/src/smalibrary/SMANET2PlusPacket.py index e3adec87..2c6dac00 100644 --- a/src/smalibrary/SMANET2PlusPacket.py +++ b/src/smalibrary/SMANET2PlusPacket.py @@ -2,10 +2,6 @@ # See LICENCE and README file for details import array -try: - from __builtin__ import long -except ImportError: - long = int # FIXME Python2 compatibility __author__ = 'Stuart Pittaway' @@ -87,14 +83,14 @@ def getByte(self, offset): def getTwoByte(self, offset): value = self.packet[offset] value += self.packet[offset + 1] << 8 - return long(value) + return value def getFourByteLong(self, offset): value = self.packet[offset] value += self.packet[offset + 1] << 0x08 value += self.packet[offset + 2] << 0x10 value += self.packet[offset + 3] << 0x18 - return long(value) + return value def getEightByte(self, offset): return self.packet[offset] \ diff --git a/src/smalibrary/SMASolar_library.py b/src/smalibrary/SMASolar_library.py index 9079bfc5..aa940ba1 100644 --- a/src/smalibrary/SMASolar_library.py +++ b/src/smalibrary/SMASolar_library.py @@ -3,13 +3,9 @@ from collections import namedtuple import time -try: - from __builtin__ import long # FIXME long is defined by default on Python2, but is equivalent to int on Python3 -except ImportError: - long = int from datetime import datetime -from SMABluetoothPacket import SMABluetoothPacket -from SMANET2PlusPacket import SMANET2PlusPacket +from smalibrary.SMABluetoothPacket import SMABluetoothPacket +from smalibrary.SMANET2PlusPacket import SMANET2PlusPacket __author__ = 'Stuart Pittaway' @@ -214,7 +210,7 @@ def logon(btSocket, mylocalBTAddress, MySerialNumber, packet_send_counter, Inver #Timeout = 900sec ? pluspacket1.pushLong(0x00000384) - pluspacket1.pushLong(long(time.mktime(datetime.today().timetuple()))) + pluspacket1.pushLong(time.mktime(datetime.today().timetuple())) pluspacket1.pushLong(0x00000000) pluspacket1.pushByteArray(InverterPasswordArray) From f9359e00aaf29521431d22b27d9f966e4435f283 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 9 Dec 2019 00:48:03 +0000 Subject: [PATCH 23/78] Encode strings for serial. --- src/interfacers/EmonHubJeeInterfacer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/interfacers/EmonHubJeeInterfacer.py b/src/interfacers/EmonHubJeeInterfacer.py index 82f6a65e..d9d6d963 100644 --- a/src/interfacers/EmonHubJeeInterfacer.py +++ b/src/interfacers/EmonHubJeeInterfacer.py @@ -25,12 +25,12 @@ def __init__(self, name, com_port='/dev/ttyAMA0', com_baud=38400): # Display device firmware version and current settings self.info = ["", ""] if self._ser is not None: - self._ser.write("v") + self._ser.write(b"v") time.sleep(2) # FIXME sleep in initialiser smells - self._rx_buf = self._rx_buf + self._ser.readline() + self._rx_buf = self._rx_buf + self._ser.readline().decode() if '\r\n' in self._rx_buf: self._rx_buf = "" - info = self._rx_buf + self._ser.readline()[:-2] + info = self._rx_buf + self._ser.readline().decode()[:-2] if info != "": # Split the returned "info" string into firmware version & current settings self.info[0] = info.strip().split(' ')[0] @@ -81,7 +81,7 @@ def read(self): """ # Read serial RX - self._rx_buf = self._rx_buf + self._ser.readline() + self._rx_buf = self._rx_buf + self._ser.readline().decode() # If line incomplete, exit if '\r\n' not in self._rx_buf: @@ -195,7 +195,7 @@ def set(self, **kwargs): continue self._settings[key] = setting self._log.info("Setting " + self.name + " %s: %s" % (key, setting) + " (" + command + ")") - self._ser.write(command) + self._ser.write(command.encode()) # Wait a sec between two settings time.sleep(1) @@ -218,7 +218,7 @@ def action(self): self._interval_timestamp = t now = datetime.datetime.now() self._log.debug(self.name + " broadcasting time: %02d:%02d" % (now.hour, now.minute)) - self._ser.write("00,%02d,%02d,00,s" % (now.hour, now.minute)) + self._ser.write(b"00,%02d,%02d,00,s" % (now.hour, now.minute)) def _process_post(self, databuffer): """Send data to server/broker or other output @@ -249,4 +249,4 @@ def send(self, cargo): payload += cmd self._log.debug(str(f.uri) + " sent TX packet: " + payload) - self._ser.write(payload) + self._ser.write(payload.encode()) From 844890fb8627eeabca411197184066f2a72ac583 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 9 Dec 2019 00:58:26 +0000 Subject: [PATCH 24/78] Fix MQTT interfacer bug. We were passing a string where an integer port number was expected. Add a cast. --- src/interfacers/EmonHubMqttInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index 03a516a4..0a9bc966 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -94,7 +94,7 @@ def _process_post(self, databuffer): self._log.info("Connecting to MQTT Server") try: self._mqttc.username_pw_set(self.init_settings['mqtt_user'], self.init_settings['mqtt_passwd']) - self._mqttc.connect(self.init_settings['mqtt_host'], self.init_settings['mqtt_port'], 60) + self._mqttc.connect(self.init_settings['mqtt_host'], int(self.init_settings['mqtt_port']), 60) except Exception: self._log.info("Could not connect...") time.sleep(1.0) # FIXME why sleep? we're just about to return True From 87b1eccf3ff0b26f48613230528d942f9340e919 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 9 Dec 2019 01:06:11 +0000 Subject: [PATCH 25/78] Send post as dict, instead of creating URL string. --- src/interfacers/EmonHubEmoncmsHTTPInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index c5544896..9a14e6c0 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -68,7 +68,7 @@ def _process_post(self, databuffer): # body, this should be moved from the url to the body as soon as this is widely # adopted - reply = self._send_post(post_url, post_body) + reply = self._send_post(post_url, {'data': data_string, 'sentat': str(sentat)}) if reply == 'ok': self._log.debug("acknowledged receipt with '" + reply + "' from " + self._settings['url']) return True From b441fd3fb945d22454620a0167801234d711b429 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 9 Dec 2019 01:13:49 +0000 Subject: [PATCH 26/78] More decode very likely needed. --- src/interfacers/EmonHubSerialInterfacer.py | 4 ++-- src/interfacers/EmonHubVEDirectInterfacer.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/interfacers/EmonHubSerialInterfacer.py b/src/interfacers/EmonHubSerialInterfacer.py index d471355b..68ea155b 100644 --- a/src/interfacers/EmonHubSerialInterfacer.py +++ b/src/interfacers/EmonHubSerialInterfacer.py @@ -48,7 +48,7 @@ def _open_serial_port(self, com_port, com_baud): try: s = serial.Serial(com_port, com_baud, timeout=0) - self._log.debug("Opening serial port: " + str(com_port) + " @ "+ str(com_baud) + " bits/s") + self._log.debug("Opening serial port: " + str(com_port) + " @ " + str(com_baud) + " bits/s") except serial.SerialException as e: self._log.error(e) s = False @@ -66,7 +66,7 @@ def read(self): return False # Read serial RX - self._rx_buf = self._rx_buf + self._ser.readline() + self._rx_buf = self._rx_buf + self._ser.readline().decode() # If line incomplete, exit if '\r\n' not in self._rx_buf: diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index 17b2ce19..e989bf3d 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -140,7 +140,7 @@ def _read_serial(self): self._log.debug(" Starting Serial read") try: while self._rx_buf == '': - byte = self._ser.read(1) + byte = self._ser.read(1).decode() packet = self.input(byte) if packet is not None: self._rx_buf = packet From 5171fdd576c8924cb457c46abd683bad297b1397 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 9 Dec 2019 23:31:23 +0000 Subject: [PATCH 27/78] Fix wrong baud parameter in Jee init. --- src/interfacers/EmonHubJeeInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubJeeInterfacer.py b/src/interfacers/EmonHubJeeInterfacer.py index d9d6d963..b3911dc7 100644 --- a/src/interfacers/EmonHubJeeInterfacer.py +++ b/src/interfacers/EmonHubJeeInterfacer.py @@ -20,7 +20,7 @@ def __init__(self, name, com_port='/dev/ttyAMA0', com_baud=38400): """ # Initialization - super().__init__(name, com_port, 38400) + super().__init__(name, com_port, com_baud) # Display device firmware version and current settings self.info = ["", ""] From 8199e319671d0f44392211cbfeadd7469032cd11 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 9 Dec 2019 23:33:23 +0000 Subject: [PATCH 28/78] Bump to v3. --- src/emonhub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emonhub.py b/src/emonhub.py index c9dd1ddb..52243c42 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -49,7 +49,7 @@ class EmonHub: - __version__ = "emonHub emon-pi variant v2.1.2" + __version__ = "emonHub emon-pi variant v3" def __init__(self, setup): """Setup an OpenEnergyMonitor emonHub. From d5dfb0f363844a318928b23680f5f660f1ff0e1f Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Tue, 10 Dec 2019 13:46:58 +0000 Subject: [PATCH 29/78] Fix float divison error. This code shouldn't really be relying on division, but change it to return an integer anyway. --- src/emonhub_interfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 85c66549..ee4bc8cc 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -347,7 +347,7 @@ def _process_rx(self, cargo): return False else: # Determine the number of values in the frame of the specified code & size - count = len(rxc.realdata) / ehc.check_datacode(datacode) + count = len(rxc.realdata) // ehc.check_datacode(datacode) # Decode the string of data one value at a time into "decoded" if not decoded: From e4387ec5646a248af720420203927de10b2b7c22 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Tue, 10 Dec 2019 13:48:31 +0000 Subject: [PATCH 30/78] Use a beta version number. --- src/emonhub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emonhub.py b/src/emonhub.py index 52243c42..d7112bd9 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -49,7 +49,7 @@ class EmonHub: - __version__ = "emonHub emon-pi variant v3" + __version__ = "emonHub emon-pi variant v3-beta" def __init__(self, setup): """Setup an OpenEnergyMonitor emonHub. From a164904e85e04ea5b5d42908998f1067fd7f4e16 Mon Sep 17 00:00:00 2001 From: bwduncan Date: Fri, 3 Jan 2020 14:38:19 +0000 Subject: [PATCH 31/78] Update emonhub.service - Set the log dir mode with mkdir. - Use Type=exec, it's more resilient to some failures (i.e. absent binary). - Use a better name - Use the default dependencies which seemed to be duplicated here anyway. Remove nonexistent syslog.target - Remove PIDFile. It's unnecessary and systemd doesn't use it except for units of Type=forking - Use a slower RestartSec (the default of 100ms is probably inappropriate) --- service/emonhub.service | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/service/emonhub.service b/service/emonhub.service index 216f391b..d622763e 100644 --- a/service/emonhub.service +++ b/service/emonhub.service @@ -1,21 +1,19 @@ [Unit] -Description=emonHub service description -DefaultDependencies=no -Before=shutdown.target -Conflicts=shutdown.target -Requires=local-fs.target -After=sysinit.target syslog.target local-fs.target +Description=emonHub data multiplexer +# The config file lives in /home/pi/data (symlinked from /etc/emonhub) +# The log file lives in the tmpfs at /var/log +Requires=var-log.mount home-pi-data.mount +After=var-log.mount home-pi-data.mount network.target [Service] User=emonhub -PIDFile=/var/run/emonhub.pid ExecStart=/usr/local/bin/emonhub/emonhub.py --config-file=/etc/emonhub/emonhub.conf --logfile=/var/log/emonhub/emonhub.log PermissionsStartOnly=true -ExecStartPre=/bin/mkdir -p /var/log/emonhub/ +ExecStartPre=/bin/mkdir -p -m 0775 /var/log/emonhub/ ExecStartPre=/bin/chgrp -R emonhub /var/log/emonhub/ -ExecStartPre=/bin/chmod 775 /var/log/emonhub/ -Type=simple +Type=exec Restart=always +RestartSec=5 [Install] WantedBy=multi-user.target From b0082b72d77df7ac5d5f9816292f62076203bc0e Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:23:09 +0000 Subject: [PATCH 32/78] Make logging lazy. Calls to logging functions should use %s/%d/etc and pass parameters instead of interpolating first. The logging module can avoid doing that work if the log message would be filtered anyway. --- src/emonhub.py | 28 ++++---- src/emonhub_buffer.py | 4 +- src/emonhub_interfacer.py | 67 ++++++++++--------- src/emonhub_setup.py | 12 ++-- src/interfacers/EmonHubBMWInterfacer.py | 14 ++-- .../EmonHubEmoncmsHTTPInterfacer.py | 24 +++---- src/interfacers/EmonHubGraphiteInterfacer.py | 12 ++-- src/interfacers/EmonHubJeeInterfacer.py | 30 ++++----- src/interfacers/EmonHubMqttInterfacer.py | 24 +++---- src/interfacers/EmonHubPacketGenInterfacer.py | 20 +++--- src/interfacers/EmonHubSMASolarInterfacer.py | 24 ++++--- src/interfacers/EmonHubSerialInterfacer.py | 4 +- src/interfacers/EmonHubSocketInterfacer.py | 18 ++--- src/interfacers/EmonHubTemplateInterfacer.py | 6 +- src/interfacers/EmonHubTx3eInterfacer.py | 4 +- src/interfacers/EmonHubVEDirectInterfacer.py | 8 +-- src/interfacers/EmonModbusTcpInterfacer.py | 37 +++++----- .../tmp/EmonHubSmilicsInterfacer.py | 6 +- 18 files changed, 175 insertions(+), 167 deletions(-) diff --git a/src/emonhub.py b/src/emonhub.py index d7112bd9..1f474f97 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -68,7 +68,7 @@ def __init__(self, setup): # Initialize logging self._log = logging.getLogger("EmonHub") self._set_logging_level('INFO', False) - self._log.info("EmonHub %s" % self.__version__) + self._log.info("EmonHub %s", self.__version__) self._log.info("Opening hub...") # Initialize Interfacers @@ -115,7 +115,7 @@ def run(self): # Post to each subscriber interface for sub_interfacer in self._interfacers.values(): - # For each subsciber channel + # For each subscriber channel for sub_channel in sub_interfacer._settings['subchannels']: # If channel names match if sub_channel == pub_channel: @@ -128,14 +128,14 @@ def run(self): # ->avoid modification of iterable within loop for name in kill_list: - self._log.warning(name + " thread is dead.") + self._log.warning("%s thread is dead.", name) # The following should trigger a restart ... unless the # interfacer is also removed from the settings table. del self._interfacers[name] # Trigger restart by calling update settings - self._log.warning("Attempting to restart thread " + name + " (thread has been restarted " + str(restart_count[name]) + " times...") + self._log.warning("Attempting to restart thread %s (thread has been restarted %d times...)", name, restart_count[name]) restart_count[name] += 1 self._update_settings(self._setup.settings) @@ -184,14 +184,14 @@ def _update_settings(self, settings): settings['interfacers'][name]['runtimesettings'] except Exception as e: # If interfacer's settings are incomplete, continue without updating - self._log.error("Unable to update '" + name + "' configuration: " + str(e)) + self._log.error("Unable to update '%s' configuration: %s", name, e) continue else: # check init_settings against the file copy, if they are the same move on to the next if self._interfacers[name].init_settings == settings['interfacers'][name]['init_settings']: continue # Delete interfacers if setting changed or name is unlisted or Type is missing - self._log.info("Deleting interfacer '%s' ", name) + self._log.info("Deleting interfacer '%s'", name) self._interfacers[name].stop = True del self._interfacers[name] @@ -201,19 +201,19 @@ def _update_settings(self, settings): try: if 'Type' not in I: continue - self._log.info("Creating " + I['Type'] + " '%s' ", name) + self._log.info("Creating %s '%s'", I['Type'], name) # This gets the class from the 'Type' string - interfacer = getattr(ehi, I['Type'])(name,**I['init_settings']) + interfacer = getattr(ehi, I['Type'])(name, **I['init_settings']) interfacer.set(**I['runtimesettings']) interfacer.init_settings = I['init_settings'] interfacer.start() except ehi.EmonHubInterfacerInitError as e: # If interfacer can't be created, log error and skip to next - self._log.error("Failed to create '" + name + "' interfacer: " + str(e)) + self._log.error("Failed to create '%s' interfacer: %s", name, e) continue except Exception as e: # If interfacer can't be created, log error and skip to next - self._log.error("Unable to create '" + name + "' interfacer: " + str(e)) + self._log.error("Unable to create '%s' interfacer: %s", name, e) continue else: self._interfacers[name] = interfacer @@ -240,17 +240,17 @@ def _set_logging_level(self, level='WARNING', log=True): try: loglevel = getattr(logging, level) except AttributeError: - self._log.error('Logging level %s invalid' % level) + self._log.error('Logging level %s invalid', level) return False except Exception as e: - self._log.error('Logging level %s ' % str(e)) + self._log.error('Logging level %s', e) return False # Change level if different from current level if loglevel != self._log.getEffectiveLevel(): self._log.setLevel(level) if log: - self._log.info('Logging level set to %s' % level) + self._log.info('Logging level set to %s', level) if __name__ == "__main__": @@ -275,7 +275,7 @@ def _set_logging_level(self, level='WARNING', log=True): # Display version number and exit if args.version: - print('emonHub %s' % EmonHub.__version__) + print('emonHub', EmonHub.__version__) sys.exit() # Logging configuration diff --git a/src/emonhub_buffer.py b/src/emonhub_buffer.py index c1d28dfc..22c62f63 100644 --- a/src/emonhub_buffer.py +++ b/src/emonhub_buffer.py @@ -66,8 +66,8 @@ def discardOldestItems(self): def discardOldestItemsIfFull(self): if self.isFull(): self._log.warning( - "In-memory buffer (%s) reached limit of %d items, deleting oldest" - % (self._bufferName, self._maximumEntriesInBuffer)) + "In-memory buffer (%s) reached limit of %d items, deleting oldest", + self._bufferName, self._maximumEntriesInBuffer) self.discardOldestItems() def storeItem(self, data): diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index ee4bc8cc..67c2f43b 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -101,7 +101,7 @@ def run(self): rxc = self._process_rx(rxc) if rxc: for channel in self._settings["pubchannels"]: - self._log.debug(str(rxc.uri) + " Sent to channel(start)' : " + str(channel)) + self._log.debug("%d Sent to channel(start)' : %d", rxc.uri, channel) # Initialise channel if needed if channel not in self._pub_channels: @@ -110,7 +110,7 @@ def run(self): # Add cargo item to channel self._pub_channels[channel].append(rxc) - self._log.debug(str(rxc.uri) + " Sent to channel(end)' : " + str(channel)) + self._log.debug("%d Sent to channel(end)' : %d", rxc.uri, channel) # Subscriber channels for channel in self._settings["subchannels"]: @@ -144,10 +144,10 @@ def add(self, cargo): if cargo.rssi: f.append(cargo.rssi) - # self._log.debug(str(cargo.uri) + " adding frame to buffer => "+ str(f)) + # self._log.debug("%d adding frame to buffer => %s", rxc.uri, str) except: - self._log.warning("Failed to create emonCMS frame " + str(f)) + self._log.warning("Failed to create emonCMS frame %s", f) # self._log.debug(str(carg.ref) + " added to buffer =>" # + " time: " + str(carg.timestamp) @@ -203,7 +203,7 @@ def flush(self): # Buffer management # If data buffer not empty, send a set of values if self.buffer.hasItems(): - self._log.debug("Buffer size: " + str(self.buffer.size())) + self._log.debug("Buffer size: %d", self.buffer.size()) max_items = int(self._settings['batchsize']) if max_items > self._item_limit: @@ -253,7 +253,7 @@ def _send_post(self, post_url, post_body=None): reply = requests.get(post_url) reply.raise_for_status() # Raise an exception if status code isn't 200 except requests.exceptions.RequestException as ex: - self._log.warning(self.name + " couldn't send to server: " + str(ex)) + self._log.warning("%s couldn't send to server: %s", self.name, ex) return reply.text def _process_rx(self, cargo): @@ -271,7 +271,7 @@ def _process_rx(self, cargo): """ # Log data - self._log.debug(str(cargo.uri) + " NEW FRAME : " + str(cargo.rawdata)) + self._log.debug("%d NEW FRAME : %s", cargo.uri, cargo.rawdata) rxc = cargo decoded = [] @@ -280,20 +280,23 @@ def _process_rx(self, cargo): # Discard if data is non-existent if len(rxc.realdata) < 1: - self._log.warning(str(cargo.uri) + " Discarded RX frame 'string too short' : " + str(rxc.realdata)) + self._log.warning("%d Discarded RX frame 'string too short' : %s", + cargo.uri, rxc.realdata) return False # Discard if anything non-numerical found try: [float(val) for val in rxc.realdata] except Exception: - self._log.warning(str(cargo.uri) + " Discarded RX frame 'non-numerical content' : " + str(rxc.realdata)) + self._log.warning("%d Discarded RX frame 'non-numerical content' : %s", + cargo.uri, rxc.realdata) return False # Discard if first value is not a valid node id # n = float(rxc.realdata[0]) # if n % 1 != 0 or n < 0 or n > 31: - # self._log.warning(str(cargo.uri) + " Discarded RX frame 'node id outside scope' : " + str(rxc.realdata)) + # self._log.warning("%d Discarded RX frame 'node id outside scope' : %s", + # cargo.uri, rxc.realdata) # return False # Data whitening uses for ensuring rfm sync @@ -314,8 +317,8 @@ def _process_rx(self, cargo): datasizes.append(ehc.check_datacode(str(code))) # Discard the frame & return 'False' if it doesn't match the summed datasizes if len(rxc.realdata) != sum(datasizes): - self._log.warning(str(rxc.uri) + " RX data length: " + str(len(rxc.realdata)) + - " is not valid for datacodes " + str(datacodes)) + self._log.warning("%d RX data length: %d is not valid for datacodes %s", + rxc.uri, len(rxc.realdata), datacodes) return False else: # Determine the expected number of values to be decoded @@ -342,8 +345,8 @@ def _process_rx(self, cargo): decoded.append(val) # Discard frame if total size is not an exact multiple of the specified datacode size. elif len(rxc.realdata) % ehc.check_datacode(datacode) != 0: - self._log.warning(str(rxc.uri) + " RX data length: " + str(len(rxc.realdata)) + - " is not valid for datacode " + str(datacode)) + self._log.warning("%d RX data length: %d is not valid for datacode %s", + rxc.uri, len(rxc.realdata), datacode) return False else: # Determine the number of values in the frame of the specified code & size @@ -361,8 +364,8 @@ def _process_rx(self, cargo): size = int(ehc.check_datacode(dc)) try: value = ehc.decode(dc, [int(v) for v in rxc.realdata[bytepos:bytepos+size]]) - except: - self._log.warning(str(rxc.uri) + " Unable to decode as values incorrect for datacode(s)") + except Exception: + self._log.warning("%d Unable to decode as values incorrect for datacode(s)", rxc.uri) return False bytepos += size decoded.append(value) @@ -373,7 +376,7 @@ def _process_rx(self, cargo): # === Removed check for scales length so that failure mode is more gracious === # Discard the frame & return 'False' if it doesn't match the number of scales # if len(decoded) != len(scales): - # self._log.warning(str(rxc.uri) + " Scales " + str(scales) + " for RX data : " + str(rxc.realdata) + " not suitable " ) + # self._log.warning("%d Scales %s for RX data : %s not suitable", rxc.uri, scales, rxc.realdata) # return False # else: # Determine the expected number of values to be decoded @@ -422,13 +425,13 @@ def _process_rx(self, cargo): if not rxc: return False - self._log.debug(str(rxc.uri) + " Timestamp : " + str(rxc.timestamp)) - self._log.debug(str(rxc.uri) + " From Node : " + str(rxc.nodeid)) + self._log.debug("%d Timestamp : %f", rxc.uri, rxc.timestamp) + self._log.debug("%d From Node : %d", rxc.uri, rxc.nodeid) if rxc.target: - self._log.debug(str(rxc.uri) + " To Target : " + str(rxc.target)) - self._log.debug(str(rxc.uri) + " Values : " + str(rxc.realdata)) + self._log.debug("%d To Target : %d", rxc.uri, rxc.target) + self._log.debug("%d Values : %s", rxc.uri, rxc.realdata) if rxc.rssi: - self._log.debug(str(rxc.uri) + " RSSI : " + str(rxc.rssi)) + self._log.debug("%d RSSI : %d", rxc.uri, rxc.rssi) return rxc @@ -468,8 +471,8 @@ def _process_tx(self, cargo): scales = ehc.nodelist[dest]['tx']['scales'] # Discard the frame & return 'False' if it doesn't match the number of scales if len(txc.realdata) != len(scales): - self._log.warning(str(txc.uri) + " Scales " + str(scales) + " for RX data : " + str(txc.realdata) + - " not suitable ") + self._log.warning("%d Scales %s for RX data : %s not suitable ", + txc.uri, scales, txc.realdata) return False else: # Determine the expected number of values to be decoded @@ -502,7 +505,7 @@ def _process_tx(self, cargo): val = int(val) scaled.append(val) - # self._log.info("Scaled: " + json.dumps(scaled)) + # self._log.info("Scaled: %s", json.dumps(scaled)) # check if node is listed and has individual datacodes for each value if dest in ehc.nodelist and 'tx' in ehc.nodelist[dest] and 'datacodes' in ehc.nodelist[dest]['tx']: @@ -516,8 +519,8 @@ def _process_tx(self, cargo): datasizes.append(ehc.check_datacode(str(code))) # Discard the frame & return 'False' if it doesn't match the summed datasizes if len(scaled) != len(datasizes): - self._log.warning(str(txc.uri) + " TX datacodes: " + str(datacodes) + - " are not valid for values " + str(scaled)) + self._log.warning("%d TX datacodes: %s are not valid for values %s", + txc.uri, datacodes, scaled) return False else: # Determine the expected number of values to be decoded @@ -550,8 +553,8 @@ def _process_tx(self, cargo): encoded.append(val) # Discard frame if total size is not an exact multiple of the specified datacode size. # elif len(data) * ehc.check_datacode(datacode) != 0: - # self._log.warning(str(uri) + " TX data length: " + str(len(data)) + - # " is not valid for datacode " + str(datacode)) + # self._log.warning("%d TX data length: %d is not valid for datacode %s", + # txc.uri, len(data), datacode) # return False else: # Determine the number of values in the frame of the specified code & size @@ -568,7 +571,7 @@ def _process_tx(self, cargo): for b in ehc.encode(dc, int(scaled[i])): encoded.append(b) - # self._log.info("Encoded: "+json.dumps(encoded)) + # self._log.info("Encoded: %s", json.dumps(encoded)) txc.encoded.update({self.getName():encoded}) return txc @@ -614,10 +617,10 @@ def set(self, **kwargs): elif key == 'subchannels': pass else: - self._log.warning("In interfacer set '%s' is not a valid setting for %s: %s" % (str(setting), self.name, key)) + self._log.warning("In interfacer set '%s' is not a valid setting for %s: %s", setting, self.name, key) continue self._settings[key] = setting - self._log.debug("Setting " + self.name + " " + key + ": " + str(setting)) + self._log.debug("Setting %s %s: %s", self.name, key, setting) """class EmonHubInterfacerInitError diff --git a/src/emonhub_setup.py b/src/emonhub_setup.py index d956dd4e..7f616b7e 100644 --- a/src/emonhub_setup.py +++ b/src/emonhub_setup.py @@ -103,7 +103,7 @@ def __init__(self, filename): raise EmonHubSetupInitError(e) except SyntaxError as e: raise EmonHubSetupInitError( - 'Error parsing config file \"%s\": ' % filename + str(e)) + 'Error parsing config file "%s": ' % filename + str(e)) except KeyError as e: raise EmonHubSetupInitError( 'Configuration file error - section: ' + str(e)) @@ -134,18 +134,18 @@ def check_settings(self): self.settings = json.loads(f.read()) except IOError as e: - self._log.warning('Could not get settings: ' + str(e) + self.retry_msg) + self._log.warning('Could not get settings: %s %s', e, self.retry_msg) self._settings_update_timestamp = now + self._retry_time_interval return except SyntaxError as e: self._log.warning('Could not get settings: ' + - 'Error parsing config file: ' + str(e) + self.retry_msg) + 'Error parsing config file: %s %s', e, self.retry_msg) self._settings_update_timestamp = now + self._retry_time_interval return except Exception: import traceback - self._log.warning("Couldn't get settings, Exception: " + - traceback.format_exc() + self.retry_msg) + self._log.warning("Couldn't get settings, Exception: %s %s", + traceback.format_exc(), self.retry_msg) self._settings_update_timestamp = now + self._retry_time_interval return @@ -155,7 +155,7 @@ def check_settings(self): self.settings['hub'] self.settings['interfacers'] except KeyError as e: - self._log.warning("Configuration file missing section: " + str(e)) + self._log.warning("Configuration file missing section: %s", e) else: return True diff --git a/src/interfacers/EmonHubBMWInterfacer.py b/src/interfacers/EmonHubBMWInterfacer.py index 519ff618..f4285753 100644 --- a/src/interfacers/EmonHubBMWInterfacer.py +++ b/src/interfacers/EmonHubBMWInterfacer.py @@ -86,7 +86,7 @@ def obtainCredentials(self): for word in parts[1:]: values = word.split("=") d[values[0]] = values[1] - #self._log.debug("word=" + word) + #self._log.debug("word=%s", word) access_token = parts[0].split("#") for word in access_token[1:]: @@ -101,7 +101,7 @@ def obtainCredentials(self): self.saveCredentials() else: - self._log.error("locationHeader=" + location) + self._log.error("locationHeader=%s", location) self._log.error("Location URL is different from expected") else: @@ -122,7 +122,7 @@ def saveCredentials(self): with open(self._TempCredentialFile, "w") as credentials_file: json.dump(credentials, credentials_file, indent=4) - self._log.info("Cached credentials to " + self._TempCredentialFile) + self._log.info("Cached credentials to %s", self._TempCredentialFile) def close(self): """Close""" @@ -165,7 +165,7 @@ def call(self, path, post_data=None): headers = {"Authorization": "Bearer " + self._AccessToken, "User-Agent": self.USER_AGENT} - #self._log.debug("headers=" + str(headers)) + #self._log.debug("headers=%s", headers) if post_data is None: r = requests.get(self.ROOT_URL + path, headers=headers) @@ -210,7 +210,7 @@ def read(self): # u'hasSunRoof': False, u'steering': u'RIGHT', u'licensePlate': u'AA11ABC', # u'dcOnly': True, u'driveTrain': u'BEV_REX', u'doorCount': 4, u'maxFuel': u'8.5', u'hasRex': True}] - self._log.debug("modelName='" + myvehicle["modelName"] + "' VIN='" + myvehicle["vin"] + "'") + self._log.debug("modelName='%s' VIN='%s'", myvehicle["modelName"], myvehicle["vin"]) vin = myvehicle["vin"] @@ -218,7 +218,7 @@ def read(self): #Report efficiency of driving (not currently used by this program) #efficiency = self.call('/api/vehicle/efficiency/v1/' + vin) - #self._log.debug("efficiency=" + str(efficiency)) + #self._log.debug("efficiency=%s", efficiency) attributesMap = dynamic["attributesMap"] @@ -241,7 +241,7 @@ def read(self): else: values.append(0) - self._log.debug("chargingSystemStatus=" + self._chargingSystemStatus) + self._log.debug("chargingSystemStatus=%s", self._chargingSystemStatus) #u'unitOfElectricConsumption = u'mls/kWh' #u'unitOfCombustionConsumption = u'mpg' diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index 9a14e6c0..1dcae3a6 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -59,7 +59,7 @@ def _process_post(self, databuffer): post_body = "data=" + data_string + "&sentat=" + str(sentat) # logged before apikey added for security - self._log.info("sending: " + post_url + "E-M-O-N-C-M-S-A-P-I-K-E-Y&" + post_body) + self._log.info("sending: %sE-M-O-N-C-M-S-A-P-I-K-E-Y&%s", post_url, post_body) # Add apikey to post_url post_url = post_url + self._settings['apikey'] @@ -70,10 +70,10 @@ def _process_post(self, databuffer): reply = self._send_post(post_url, {'data': data_string, 'sentat': str(sentat)}) if reply == 'ok': - self._log.debug("acknowledged receipt with '" + reply + "' from " + self._settings['url']) + self._log.debug("acknowledged receipt with '%s' from %s", reply, self._settings['url') return True else: - self._log.warning("send failure: wanted 'ok' but got '" + reply + "'") + self._log.warning("send failure: wanted 'ok' but got '%s'", reply) return False def sendstatus(self): @@ -109,29 +109,29 @@ def set(self, **kwargs): continue elif key == 'apikey': if setting.lower().startswith('xxxx'): # FIXME compare whole string to 'x'*32? - self._log.warning("Setting " + self.name + " apikey: obscured") + self._log.warning("Setting %s apikey: obscured", self.name) elif len(setting) == 32: - self._log.info("Setting " + self.name + " apikey: set") + self._log.info("Setting %s apikey: set", self.name) elif setting == "": - self._log.info("Setting " + self.name + " apikey: null") + self._log.info("Setting %s apikey: null", self.name) else: - self._log.warning("Setting " + self.name + " apikey: invalid format") + self._log.warning("Setting %s apikey: invalid format", self.name) continue self._settings[key] = setting # Next line will log apikey if uncommented (privacy ?) - #self._log.debug(self.name + " apikey: " + str(setting)) + #self._log.debug("%s apikey: %s", self.name, setting) continue elif key == 'url' and setting.startswith("http"): - self._log.info("Setting " + self.name + " url: " + setting) + self._log.info("Setting %s url: %s", self.name, setting) self._settings[key] = setting continue elif key == 'senddata': - self._log.info("Setting " + self.name + " senddata: " + setting) + self._log.info("Setting %s senddata: %s", self.name, setting) self._settings[key] = setting continue elif key == 'sendstatus': - self._log.info("Setting " + self.name + " sendstatus: " + setting) + self._log.info("Setting %s sendstatus: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (setting, self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) diff --git a/src/interfacers/EmonHubGraphiteInterfacer.py b/src/interfacers/EmonHubGraphiteInterfacer.py index 8a85bbd2..c882bfe1 100644 --- a/src/interfacers/EmonHubGraphiteInterfacer.py +++ b/src/interfacers/EmonHubGraphiteInterfacer.py @@ -91,9 +91,9 @@ def _send_metrics(self, metrics=[]): host = str(self._settings['graphite_host']).strip('[\'\']') port = int(str(self._settings['graphite_port']).strip('[\'\']')) - self._log.debug("Graphite target: {}:{}".format(host, port)) + self._log.debug("Graphite target: %s:%s", host, port) message = '\n'.join(metrics) + '\n' - self._log.debug("Sending metrics:\n" + message) + self._log.debug("Sending metrics: %s", message) try: sock = socket.socket() @@ -125,17 +125,17 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'graphite_host': - self._log.info("Setting " + self.name + " graphite_host: " + setting) + self._log.info("Setting %s graphite_host: %s", self.name, setting) self._settings[key] = setting continue elif key == 'graphite_port': - self._log.info("Setting " + self.name + " graphite_port: " + setting) + self._log.info("Setting %s graphite_port: %s", self.name, setting) self._settings[key] = setting continue elif key == 'prefix': - self._log.info("Setting " + self.name + " prefix: " + setting) + self._log.info("Setting %s prefix: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (setting, self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) """ diff --git a/src/interfacers/EmonHubJeeInterfacer.py b/src/interfacers/EmonHubJeeInterfacer.py index b3911dc7..f664845f 100644 --- a/src/interfacers/EmonHubJeeInterfacer.py +++ b/src/interfacers/EmonHubJeeInterfacer.py @@ -35,12 +35,12 @@ def __init__(self, name, com_port='/dev/ttyAMA0', com_baud=38400): # Split the returned "info" string into firmware version & current settings self.info[0] = info.strip().split(' ')[0] self.info[1] = info.replace(str(self.info[0]), "") - self._log.info(self.name + " device firmware version: " + self.info[0]) - self._log.info(self.name + " device current settings: " + str(self.info[1])) + self._log.info("%s device firmware version: %s", self.name, self.info[0]) + self._log.info("%s device current settings: %s", self.name, self.info[1]) else: # since "v" command only v11> recommend firmware update ? - #self._log.info(self.name + " device firmware is pre-version RFM12demo.11") - self._log.info(self.name + " device firmware version & configuration: not available") + #self._log.info("%s device firmware is pre-version RFM12demo.11", self.name) + self._log.info("%s device firmware version & configuration: not available", self.name) else: self._log.warning("Device communication error - check settings") self._rx_buf = "" @@ -97,25 +97,25 @@ def read(self): return if f[0] == '\x01': - #self._log.debug("Ignoring frame consisting of SOH character" + str(f)) + #self._log.debug("Ignoring frame consisting of SOH character %s", f) return if f[0] == '?': - self._log.debug("Discarding RX frame 'unreliable content'" + str(f)) + self._log.debug("Discarding RX frame 'unreliable content': %s", f) return False # Discard information messages if '>' in f: if '->' in f: - self._log.debug("confirmed sent packet size: " + str(f)) + self._log.debug("confirmed sent packet size: %s", f) return - self._log.debug("acknowledged command: " + str(f)) + self._log.debug("acknowledged command: %s", f) return # Record current device settings if all(i in f for i in (" i", " g", " @ ", " MHz")): self.info[1] = f - self._log.debug("device settings updated: " + str(self.info[1])) + self._log.debug("device settings updated: %s", self.info[1]) return # Save raw packet to new cargo object @@ -134,7 +134,7 @@ def read(self): try: c.rssi = int(r) except ValueError: - self._log.warning("Packet discarded as the RSSI format is invalid: " + str(f)) + self._log.warning("Packet discarded as the RSSI format is invalid: %s", f) return f = f[:-1] @@ -191,10 +191,10 @@ def set(self, **kwargs): command = '2p' else: - self._log.warning("In interfacer set '%s' is not a valid setting for %s: %s" % (str(setting), self.name, key)) + self._log.warning("In interfacer set '%s' is not a valid setting for %s: %s", setting, self.name, key) continue self._settings[key] = setting - self._log.info("Setting " + self.name + " %s: %s" % (key, setting) + " (" + command + ")") + self._log.info("Setting %s %s: %s (%s)", self.name, key, setting, command) self._ser.write(command.encode()) # Wait a sec between two settings time.sleep(1) @@ -226,7 +226,7 @@ def _process_post(self, databuffer): """ for frame in databuffer: - self._log.debug("node = " + str(frame[1]) + " node_data = " + json.dumps(frame)) + self._log.debug("node = %s node_data = %s", frame[1], json.dumps(frame)) self.send(frame) return True @@ -242,11 +242,11 @@ def send(self, cargo): payload = "" for value in data: if not 0 < int(value) < 255: - self._log.warning(self.name + " discarding Tx packet: values out of scope") + self._log.warning("%s discarding Tx packet: values out of scope", self.name) return payload += str(int(value)) + "," payload += cmd - self._log.debug(str(f.uri) + " sent TX packet: " + payload) + self._log.debug("%d sent TX packet: %s", f.uri, payload) self._ser.write(payload.encode()) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index 0a9bc966..f0b75b90 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -119,7 +119,7 @@ def _process_post(self, databuffer): topic = self._settings["nodevar_format_basetopic"] + nodename + "/" + inputname payload = str(value) - self._log.debug("Publishing: " + topic + " " + payload) + self._log.debug("Publishing: %s %s", topic, payload) result = self._mqttc.publish(topic, payload=payload, qos=2, retain=False) if result[0] == 4: @@ -131,7 +131,7 @@ def _process_post(self, databuffer): topic = self._settings["nodevar_format_basetopic"] + nodename + "/rssi" payload = str(frame['rssi']) - self._log.debug("Publishing: " + topic + " " + payload) + self._log.debug("Publishing: %s %s", topic, payload) result = self._mqttc.publish(topic, payload=payload, qos=2, retain=False) if result[0] == 4: @@ -149,7 +149,7 @@ def _process_post(self, databuffer): if 'rssi' in frame: payload = payload + "," + str(frame['rssi']) - self._log.info("Publishing: " + topic + " " + payload) + self._log.info("Publishing: %s %s", topic, payload) result = self._mqttc.publish(topic, payload=payload, qos=2, retain=False) if result[0] == 4: @@ -190,12 +190,12 @@ def on_connect(self, client, userdata, flags, rc): if rc: self._log.warning(connack_string[rc]) else: - self._log.info("connection status: " + connack_string[rc]) + self._log.info("connection status: %s", connack_string[rc]) self._connected = True # Subscribe to MQTT topics self._mqttc.subscribe(str(self._settings["node_format_basetopic"]) + "tx/#") - self._log.debug("CONACK => Return code: " + str(rc)) + self._log.debug("CONACK => Return code: %d", rc) def on_disconnect(self, client, userdata, rc): if rc != 0: @@ -213,7 +213,7 @@ def on_message(self, client, userdata, msg): payload = msg.payload realdata = payload.split(",") - self._log.debug("Nodeid: " + str(nodeid) + " values: " + msg.payload) + self._log.debug("Nodeid: %s values: %s", nodeid, msg.payload) rxc = Cargo.new_cargo(realdata=realdata) rxc.nodeid = nodeid @@ -230,7 +230,7 @@ def on_message(self, client, userdata, msg): # Add cargo item to channel self._pub_channels[channel].append(rxc) - self._log.debug(str(rxc.uri) + " Sent to channel' : " + str(channel)) + self._log.debug("%d Sent to channel' : %s", rxc.uri, channel) def set(self, **kwargs): """ @@ -250,20 +250,20 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'node_format_enable': - self._log.info("Setting " + self.name + " node_format_enable: " + setting) + self._log.info("Setting %s node_format_enable: %s", self.name, setting) self._settings[key] = setting continue elif key == 'node_format_basetopic': - self._log.info("Setting " + self.name + " node_format_basetopic: " + setting) + self._log.info("Setting %s node_format_basetopic: %s", self.name, setting) self._settings[key] = setting continue elif key == 'nodevar_format_enable': - self._log.info("Setting " + self.name + " nodevar_format_enable: " + setting) + self._log.info("Setting %s nodevar_format_enable: %s", self.name, setting) self._settings[key] = setting continue elif key == 'nodevar_format_basetopic': - self._log.info("Setting " + self.name + " nodevar_format_basetopic: " + setting) + self._log.info("Setting %s nodevar_format_basetopic: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (setting, self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) diff --git a/src/interfacers/EmonHubPacketGenInterfacer.py b/src/interfacers/EmonHubPacketGenInterfacer.py index 31a35225..36b6310a 100644 --- a/src/interfacers/EmonHubPacketGenInterfacer.py +++ b/src/interfacers/EmonHubPacketGenInterfacer.py @@ -38,12 +38,12 @@ def read(self): "/emoncms/packetgen/getpacket.json?apikey=" # logged without apikey added for security - self._log.info("requesting packet: " + req + "E-M-O-N-C-M-S-A-P-I-K-E-Y") + self._log.info("requesting packet: %sE-M-O-N-C-M-S-A-P-I-K-E-Y", req) try: packet = requests.get(req + self._settings['apikey']).json() except (ValueError, requests.exceptions.RequestException) as ex: - self._log.warning("no packet returned: " + str(ex)) + self._log.warning("no packet returned: %s", ex) return raw = "" @@ -104,7 +104,7 @@ def action(self): if self._control_interval != i: self._control_interval = i - self._log.info("request interval set to: " + str(i) + " seconds") + self._log.info("request interval set to: %d seconds", i) self._interval_timestamp = t @@ -123,24 +123,24 @@ def set(self, **kwargs): continue elif key == 'apikey': if setting.lower().startswith('xxxx'): # FIXME compare whole string to 'x'*32? - self._log.warning("Setting " + self.name + " apikey: obscured") + self._log.warning("Setting %s apikey: obscured", self.name) elif len(setting) == 32: - self._log.info("Setting " + self.name + " apikey: set") + self._log.info("Setting %s apikey: set", self.name) elif setting == "": - self._log.info("Setting " + self.name + " apikey: null") + self._log.info("Setting %s apikey: null", self.name) else: - self._log.warning("Setting " + self.name + " apikey: invalid format") + self._log.warning("Setting %s apikey: invalid format", self.name) continue self._settings[key] = setting # Next line will log apikey if uncommented (privacy ?) - #self._log.debug(self.name + " apikey: " + str(setting)) + #self._log.debug("%s apikey: %s", self.name, setting) continue elif key == 'url' and setting.startswith("http"): - self._log.info("Setting " + self.name + " url: " + setting) + self._log.info("Setting %s url: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) # include kwargs from parent super().set(**kwargs) diff --git a/src/interfacers/EmonHubSMASolarInterfacer.py b/src/interfacers/EmonHubSMASolarInterfacer.py index 3087e075..7a7f7943 100644 --- a/src/interfacers/EmonHubSMASolarInterfacer.py +++ b/src/interfacers/EmonHubSMASolarInterfacer.py @@ -49,13 +49,13 @@ def __init__(self, name, inverteraddress='', inverterpincode='0000', timeinverva self._reset_duration_timer() self._reset_time_to_disconnect_timer() - self._log.info("Reading from SMASolar every " + str(self._time_inverval) + " seconds") + self._log.info("Reading from SMASolar every %d seconds", self._time_inverval) def _login_inverter(self): """Log into the SMA solar inverter""" - self._log.info("Log into the SMA solar inverter " + str(self._inverteraddress)) + self._log.info("Log into the SMA solar inverter %s", self._inverteraddress) self._btSocket = self._open_bluetooth(self._inverteraddress, self._port) @@ -85,10 +85,10 @@ def _login_inverter(self): dictInverterData = SMASolar_library.getInverterDetails(self._btSocket, self._packet_send_counter, self.mylocalBTAddress, self.MySerialNumber) self._increment_packet_send_counter() - #Returns dictionary like + #Returns dictionary like # FIXME this is dumped from python2, get an updated example dictionary from python3. #{'inverterName': u'SN2120051742\x00\x00', 'serialNumber': 2120051742L, 'ClassName': 'SolarInverter', 'TypeName': 'SB 3000HF-30', 'susyid': 131L, 'Type': 9073L, 'Class': 8001L} - self._log.debug(str(dictInverterData)) + self._log.debug("%s", dictInverterData) #Clear rogue characters from name dictInverterData["inverterName"] = re.sub(r'[^a-zA-Z0-9]', '', dictInverterData["inverterName"]) @@ -100,8 +100,12 @@ def _login_inverter(self): self._Inverters[nodeName]["NodeId"] = self._nodeid - self._log.info("Connected to SMA inverter named [" + self._Inverters[nodeName]["inverterName"] + "] with serial number [" + str(self._Inverters[nodeName]["serialNumber"]) - + "] using NodeId=" + str(self._Inverters[nodeName]["NodeId"]) + ". Model " + self._Inverters[nodeName]["TypeName"]) + self._log.info("Connected to SMA inverter named [%s] with serial number [%d] using NodeId=%d. Model %s", + self._Inverters[nodeName]["inverterName"], + self._Inverters[nodeName]["serialNumber"], + self._Inverters[nodeName]["NodeId"], + self._Inverters[nodeName]["TypeName"], + ) def close(self): """Close bluetooth port""" @@ -117,7 +121,7 @@ def _open_bluetooth(self, inverteraddress, port): """ try: - self._log.info("Opening bluetooth address " + str(inverteraddress)) + self._log.info("Opening bluetooth address %s", inverteraddress) btSocket = bluetooth.BluetoothSocket(bluetooth.RFCOMM) # Connect btSocket.connect((inverteraddress, port)) @@ -126,7 +130,7 @@ def _open_bluetooth(self, inverteraddress, port): except bluetooth.btcommon.BluetoothError as err: self._log.error(err) - self._log.error('Bluetooth error while connecting to %s' % inverteraddress) + self._log.error('Bluetooth error while connecting to %s', inverteraddress) else: return btSocket @@ -228,7 +232,7 @@ def read(self): #Get first inverter in dictionary # FIXME dicts aren't sorted, there is no "first" inverter = self._Inverters[list(self._Inverters.keys())[0]] - self._log.debug("Reading from inverter " + inverter["inverterName"]) + self._log.debug("Reading from inverter %s", inverter["inverterName"]) #Loop through dictionary and take readings, building "output" dictionary as we go output = {} @@ -248,7 +252,7 @@ def read(self): if data is not None: output.update(SMASolar_library.extract_data(data)) if self._packettrace: - self._log.debug("Packet reply for " + reading + ". Packet from {0:04x}/{1:08x}".format(data.getTwoByte(14), data.getFourByteLong(16))) + self._log.debug("Packet reply for %s. Packet from {0:04x}/{1:08x}".format(data.getTwoByte(14), data.getFourByteLong(16)), reading) self._log.debug(data.debugViewPacket()) #self._log.debug("Building cargo") diff --git a/src/interfacers/EmonHubSerialInterfacer.py b/src/interfacers/EmonHubSerialInterfacer.py index 68ea155b..f5b431f0 100644 --- a/src/interfacers/EmonHubSerialInterfacer.py +++ b/src/interfacers/EmonHubSerialInterfacer.py @@ -43,12 +43,12 @@ def _open_serial_port(self, com_port, com_baud): """ #if not int(com_baud) in [75, 110, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]: - # self._log.debug("Invalid 'com_baud': " + str(com_baud) + " | Default of 9600 used") + # self._log.debug("Invalid 'com_baud': %d | Default of 9600 used", com_baud) # com_baud = 9600 try: s = serial.Serial(com_port, com_baud, timeout=0) - self._log.debug("Opening serial port: " + str(com_port) + " @ " + str(com_baud) + " bits/s") + self._log.debug("Opening serial port: %s @ %d bits/s", com_port, com_baud) except serial.SerialException as e: self._log.error(e) s = False diff --git a/src/interfacers/EmonHubSocketInterfacer.py b/src/interfacers/EmonHubSocketInterfacer.py index b9c1bf0b..62c0e4b2 100644 --- a/src/interfacers/EmonHubSocketInterfacer.py +++ b/src/interfacers/EmonHubSocketInterfacer.py @@ -38,7 +38,7 @@ def _open_socket(self, port_nb): """ - self._log.debug('Opening socket on port %s', port_nb) + self._log.debug('Opening socket on port %d', port_nb) try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -98,7 +98,7 @@ def read(self): if len(self._settings['apikey']) == 32 and self._settings['apikey'].lower() != "x" * 32: # Discard if apikey is not in received frame if self._settings['apikey'] not in f: - self._log.warning(str(c.uri) + " discarded frame: apikey not matched") + self._log.warning("%d discarded frame: apikey not matched", c.uri) return # Otherwise remove apikey from frame f = [v for v in f if self._settings['apikey'] not in v] @@ -139,24 +139,24 @@ def set(self, **kwargs): continue elif key == 'apikey': if setting.lower().startswith('xxxx'): # FIXME compare whole string to 'x'*32? - self._log.warning("Setting " + self.name + " apikey: obscured") + self._log.warning("Setting %s apikey: obscured", self.name) elif len(setting) == 32: - self._log.info("Setting " + self.name + " apikey: set") + self._log.info("Setting %s apikey: set", self.name) elif setting == "": - self._log.info("Setting " + self.name + " apikey: null") + self._log.info("Setting %s apikey: null", self.name) else: - self._log.warning("Setting " + self.name + " apikey: invalid format") + self._log.warning("Setting %s apikey: invalid format", self.name) continue self._settings[key] = setting # Next line will log apikey if uncommented (privacy ?) - #self._log.debug(self.name + " apikey: " + str(setting)) + #self._log.debug("%s apikey: %s", self.name, setting) continue elif key == 'url' and setting.startswith("http"): - self._log.info("Setting " + self.name + " url: " + setting) + self._log.info("Setting %s url: %s", self.name, setting) self._settings[key] = setting continue else: - self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) # include kwargs from parent super().set(**kwargs) diff --git a/src/interfacers/EmonHubTemplateInterfacer.py b/src/interfacers/EmonHubTemplateInterfacer.py index 6b0de755..62f8adb2 100644 --- a/src/interfacers/EmonHubTemplateInterfacer.py +++ b/src/interfacers/EmonHubTemplateInterfacer.py @@ -103,7 +103,7 @@ def _process_post(self, databuffer): for frame in databuffer: # Here we might typically publish or post the data # via MQTT, HTTP a socket or other output - self._log.debug("node = " + frame['node'] + " node_data = " + json.dumps(frame['data'])) + self._log.debug("node = %s node_data = %s", frame['node'], json.dumps(frame['data'])) # We could check for successful data receipt here # and return false to retry next time @@ -121,11 +121,11 @@ def set(self, **kwargs): if key in self._settings and self._settings[key] == setting: continue elif key == 'read_interval': - self._log.info("Setting " + self.name + " read_interval: " + str(setting)) + self._log.info("Setting %s read_interval: %s", self.name, setting) self._settings[key] = float(setting) continue else: - self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) # include kwargs from parent super().set(**kwargs) diff --git a/src/interfacers/EmonHubTx3eInterfacer.py b/src/interfacers/EmonHubTx3eInterfacer.py index e5d64add..c855061b 100644 --- a/src/interfacers/EmonHubTx3eInterfacer.py +++ b/src/interfacers/EmonHubTx3eInterfacer.py @@ -72,12 +72,12 @@ def read(self): try: value = float(parts[1]) except Exception: - self._log.debug("input value is not numeric: " + parts[1]) + self._log.debug("input value is not numeric: %s", parts[1]) names.append(parts[0]) values.append(value) else: - self._log.debug("invalid input name: " + parts[0]) + self._log.debug("invalid input name: %s", parts[0]) if self._settings["nodename"] != "": c.nodename = self._settings["nodename"] diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index e989bf3d..e6f19be3 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -43,7 +43,7 @@ def __init__(self, name, com_port='', com_baud=9600, toextract='', poll_interval # Parser requirements self._extract = toextract - #print "init system with to extract %s"%self._extract + #print("init system with to extract", self._extract) def input(self, byte): @@ -101,12 +101,12 @@ def _open_serial_port(self, com_port, com_baud): """ #if not int(com_baud) in [75, 110, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]: - # self._log.debug("Invalid 'com_baud': " + str(com_baud) + " | Default of 9600 used") + # self._log.debug("Invalid 'com_baud': %d | Default of 9600 used", com_baud) # com_baud = 9600 try: s = serial.Serial(com_port, com_baud, timeout=10) - self._log.debug("Opening serial port: " + str(com_port) + " @ "+ str(com_baud) + " bits/s") + self._log.debug("Opening serial port: %s @ %d bits/s", com_port, com_baud) except serial.SerialException as e: self._log.error(e) s = False @@ -162,7 +162,7 @@ def read(self): # Read serial RX now = time.time() if now - self.last_read <= self.poll_interval: - #self._log.debug(" Waiting for %s seconds " % (str(now - self.last_read))) + #self._log.debug("Waiting for %d seconds ", now - self.last_read) # Wait to read based on poll_interval return diff --git a/src/interfacers/EmonModbusTcpInterfacer.py b/src/interfacers/EmonModbusTcpInterfacer.py index b165c0fd..818c444f 100644 --- a/src/interfacers/EmonModbusTcpInterfacer.py +++ b/src/interfacers/EmonModbusTcpInterfacer.py @@ -28,23 +28,24 @@ def __init__(self, name, modbus_IP='192.168.1.10', modbus_port=0): # Initialization super().__init__(name) + self._modcon = False if not pymodbus_found: self._log.error("PYMODBUS NOT PRESENT BUT NEEDED !!") # open connection if pymodbus_found: self._log.info("pymodbus installed") - self._log.debug("EmonModbusTcpInterfacer args: " + str(modbus_IP) + " - " + str(modbus_port)) + self._log.debug("EmonModbusTcpInterfacer args: %s - %d", modbus_IP, modbus_port) self._con = self._open_modTCP(modbus_IP, modbus_port) if self._modcon: - self._log.info("Modbustcp client Connected") + self._log.info("Modbustcp client Connected") else: - self._log.info("Connection to ModbusTCP client failed. Will try again") + self._log.info("Connection to ModbusTCP client failed. Will try again") def set(self, **kwargs): for key in kwargs: setting = kwargs[key] self._settings[key] = setting - self._log.debug("Setting " + self.name + " %s: %s" % (key, setting)) + self._log.debug("Setting %s %s: %s", self.name, key, setting) def close(self): # Close TCP connection @@ -58,13 +59,13 @@ def _open_modTCP(self, modbus_IP, modbus_port): try: c = ModbusClient(modbus_IP, modbus_port) if c.connect(): - self._log.info("Opening modbusTCP connection: " + str(modbus_port) + " @ " + str(modbus_IP)) + self._log.info("Opening modbusTCP connection: %d @ %s", modbus_port, modbus_IP) self._modcon = True else: self._log.debug("Connection failed") self._modcon = False except Exception as e: - self._log.error("modbusTCP connection failed" + str(e)) + self._log.error("modbusTCP connection failed %s", e) #raise EmonHubInterfacerInitError('Could not open connection to host %s' %modbus_IP) else: return c @@ -81,7 +82,7 @@ def read(self): if not self._modcon: self._con.close() - self._log.info("Not connected, retrying connect" + str(self.init_settings)) + self._log.info("Not connected, retrying connect %s", self.init_settings) self._con = self._open_modTCP(self.init_settings["modbus_IP"], self.init_settings["modbus_port"]) if self._modcon: @@ -145,7 +146,7 @@ def read(self): else: expectedSize = len(rNames) * valid_datacodes[datacode] * 2 - self._log.debug("expected bytes number after encoding: " + str(expectedSize)) + self._log.debug("expected bytes number after encoding: %s", expectedSize) # at this stage, we don't have any invalid datacode(s) # so we can loop and read registers @@ -158,21 +159,21 @@ def read(self): if datacodes is not None: datacode = datacodes[idx] - self._log.debug("datacode " + datacode) + self._log.debug("datacode %s", datacode) qty = valid_datacodes[datacode] - self._log.debug("reading register # :" + str(register) + ", qty #: " + str(qty) + ", unit #: " + str(unitId)) + self._log.debug("reading register #: %s, qty #: %s, unit #: %s", register, qty, unitId) try: self.rVal = self._con.read_holding_registers(register - 1, qty, unit=unitId) assert self.rVal.function_code < 0x80 except Exception as e: - self._log.error("Connection failed on read of register: " + str(register) + " : " + str(e)) + self._log.error("Connection failed on read of register: %s : %s", register, e) self._modcon = False #missing datas will lead to an incorrect encoding #we have to drop the payload return else: - #self._log.debug("register value:" + str(self.rVal.registers) + " type= " + str(type(self.rVal.registers))) + #self._log.debug("register value: %s type= %s", self.rVal.registers, type(self.rVal.registers)) #f = f + self.rVal.registers decoder = BinaryPayloadDecoder.fromRegisters(self.rVal.registers, byteorder=Endian.Big, wordorder=Endian.Big) if datacode == 'h': @@ -198,16 +199,16 @@ def read(self): t = ehc.encode(datacode, rValD) f = f + list(t) - self._log.debug("Encoded value: " + str(t)) - self._log.debug("value: " + str(rValD)) + self._log.debug("Encoded value: %s", t) + self._log.debug("value: %s", rValD) #test if payload length is OK if len(f) == expectedSize: - self._log.debug("payload size OK (" + str(len(f)) + ")") - self._log.debug("reporting data: " + str(f)) + self._log.debug("payload size OK (%d)", len(f)) + self._log.debug("reporting data: %s", f) c.nodeid = node c.realdata = f - self._log.debug("Return from read data: " + str(c.realdata)) + self._log.debug("Return from read data: %s", c.realdata) return c else: - self._log.error("incorrect payload size :" + str(len(f)) + " expecting " + str(expectedSize)) + self._log.error("incorrect payload size: %d expecting %d", len(f), expectedSize) diff --git a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py index 97f5f379..bb022043 100644 --- a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py +++ b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py @@ -65,7 +65,7 @@ def run(self): rxc = self._process_rx(rxc) if rxc: for channel in self._settings["pubchannels"]: - self._log.debug(str(rxc.uri) + " Sent to channel(start)' : " + str(channel)) + self._log.debug("%d Sent to channel(start)' : %s", rxc.uri, channel) # Initialize channel if needed if channel not in self._pub_channels: @@ -74,7 +74,7 @@ def run(self): # Add cargo item to channel self._pub_channels[channel].append(rxc) - self._log.debug(str(rxc.uri) + " Sent to channel(end)' : " + str(channel)) + self._log.debug("%d Sent to channel(end)' : %s", rxc.uri, channel) # Don't loop too fast time.sleep(0.1) @@ -98,7 +98,7 @@ def _process_rx(self, smilics_dict): c.nodeid = smilics_dict['mac'][0] if c.nodeid not in ehc.nodelist.keys(): - self._log.debug(str(c.nodeid) + " Not in config") + self._log.debug("%d Not in config", c.nodeid) return None node_config = ehc.nodelist[str(c.nodeid)] From 9b5f5d7b4a652929227130015e7151f739d92e34 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:27:15 +0000 Subject: [PATCH 33/78] More cleanups - Avoid import * - Use setdefault for dict initialisation. - Remove/simplify various bits. --- src/emonhub.py | 11 +--- src/emonhub_coder.py | 1 + src/emonhub_interfacer.py | 11 ++-- src/interfacers/EmonHubGraphiteInterfacer.py | 4 +- src/interfacers/EmonHubMqttInterfacer.py | 50 ++++--------------- src/interfacers/EmonHubSocketInterfacer.py | 6 +-- .../tmp/EmonHubSmilicsInterfacer.py | 10 ++-- 7 files changed, 24 insertions(+), 69 deletions(-) diff --git a/src/emonhub.py b/src/emonhub.py index 1f474f97..c381a752 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -23,7 +23,6 @@ import emonhub_setup as ehs import emonhub_coder as ehc import emonhub_interfacer as ehi -from interfacers import * # this namespace and path namespace = sys.modules[__name__] @@ -119,12 +118,8 @@ def run(self): for sub_channel in sub_interfacer._settings['subchannels']: # If channel names match if sub_channel == pub_channel: - # init if empty - if sub_channel not in sub_interfacer._sub_channels: - sub_interfacer._sub_channels[sub_channel] = [] - # APPEND cargo item - sub_interfacer._sub_channels[sub_channel].append(cargo) + sub_interfacer._sub_channels.setdefault(sub_channel, []).append(cargo) # ->avoid modification of iterable within loop for name in kill_list: @@ -152,7 +147,6 @@ def close(self): I.join() self._log.info("Exit completed") - logging.shutdown() def _sigint_handler(self, signal, frame): """Catch SIGINT (Ctrl+C).""" @@ -170,8 +164,6 @@ def _update_settings(self, settings): else: self._set_logging_level() - # Create a place to hold buffer contents whilst a deletion & rebuild occurs - self.temp_buffer = {} # Interfacers for name in self._interfacers: @@ -251,6 +243,7 @@ def _set_logging_level(self, level='WARNING', log=True): self._log.setLevel(level) if log: self._log.info('Logging level set to %s', level) + return True if __name__ == "__main__": diff --git a/src/emonhub_coder.py b/src/emonhub_coder.py index 9836e54f..470a7932 100644 --- a/src/emonhub_coder.py +++ b/src/emonhub_coder.py @@ -1,6 +1,7 @@ import struct # Initialize nodes data +# FIXME this shouldn't live here nodelist = {} diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 67c2f43b..9fca9371 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -25,12 +25,12 @@ """ -def log_exceptions_from_class_method(f): +def log_exceptions_from_class_method(func): def wrapper(*args): self = args[0] try: - return f(*args) - except: + return func(*args) + except Exception: self._log.warning("Exception caught in " + self.name + " thread. " + traceback.format_exc()) return wrapper @@ -533,10 +533,7 @@ def _process_tx(self, cargo): datacode = ehc.nodelist[dest]['tx']['datacode'] else: # when node not listed or has no datacode(s) use the interfacers default if specified - if 'datacode' in self._settings: - datacode = self._settings['datacode'] - else: - datacode = "h" + datacode = self._settings.get('datacode', 'h') # Ensure only int 0 is passed not str 0 if datacode == '0': diff --git a/src/interfacers/EmonHubGraphiteInterfacer.py b/src/interfacers/EmonHubGraphiteInterfacer.py index c882bfe1..6077ef2b 100644 --- a/src/interfacers/EmonHubGraphiteInterfacer.py +++ b/src/interfacers/EmonHubGraphiteInterfacer.py @@ -89,8 +89,8 @@ def _send_metrics(self, metrics=[]): """ - host = str(self._settings['graphite_host']).strip('[\'\']') - port = int(str(self._settings['graphite_port']).strip('[\'\']')) + host = self._settings['graphite_host'].strip("[']") + port = int(self._settings['graphite_port'].strip("[']")) self._log.debug("Graphite target: %s:%s", host, port) message = '\n'.join(metrics) + '\n' self._log.debug("Sending metrics: %s", message) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index f0b75b90..ecb664c2 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -53,16 +53,11 @@ def add(self, cargo): format: {"emontx":{"power1":100,"power2":200,"power3":300}} """ - - nodename = str(cargo.nodeid) - if cargo.nodename: - nodename = cargo.nodename - - f = {} - f['nodeid'] = cargo.nodeid - f['node'] = nodename - f['names'] = cargo.names - f['data'] = cargo.realdata + f = {'nodeid': cargo.nodeid, + 'node': cargo.nodename or str(cargo.nodeid), + 'names': cargo.names, + 'data': cargo.realdata, + } if cargo.rssi: f['rssi'] = cargo.rssi @@ -78,9 +73,7 @@ def add(self, cargo): # This is a bit of a hack, the final approach is currently being considered # as part of ongoing discussion on future direction of emonhub - databuffer = [] - databuffer.append(f) - self._process_post(databuffer) + self._process_post([f]) # To re-enable buffering comment the above three lines and uncomment the following # note that at preset _process_post will not handle buffered data correctly and @@ -159,10 +152,6 @@ def _process_post(self, databuffer): return True def action(self): - """ - - :return: - """ self._mqttc.loop(0) # pause output if 'pause' set to 'all' or 'out' @@ -179,7 +168,6 @@ def action(self): self.flush() def on_connect(self, client, userdata, flags, rc): - connack_string = {0: 'Connection successful', 1: 'Connection refused - incorrect protocol version', 2: 'Connection refused - invalid client identifier', @@ -215,34 +203,18 @@ def on_message(self, client, userdata, msg): realdata = payload.split(",") self._log.debug("Nodeid: %s values: %s", nodeid, msg.payload) - rxc = Cargo.new_cargo(realdata=realdata) - rxc.nodeid = nodeid + rxc = Cargo.new_cargo(realdata=realdata, nodeid=nodeid) - if rxc: - # rxc = self._process_tx(rxc) - if rxc: - for channel in self._settings["pubchannels"]: + for channel in self._settings["pubchannels"]: + # Add cargo item to channel + self._pub_channels.setdefault(channel, []).append(rxc) - # Initialize channel if needed - if channel not in self._pub_channels: - self._pub_channels[channel] = [] - - # Add cargo item to channel - self._pub_channels[channel].append(rxc) - - self._log.debug("%d Sent to channel' : %s", rxc.uri, channel) + self._log.debug("%d Sent to channel' : %s", rxc.uri, channel) def set(self, **kwargs): - """ - - :param kwargs: - :return: - """ - super().set(**kwargs) for key, setting in self._mqtt_settings.items(): - #valid = False if key not in kwargs: setting = self._mqtt_settings[key] else: diff --git a/src/interfacers/EmonHubSocketInterfacer.py b/src/interfacers/EmonHubSocketInterfacer.py index 62c0e4b2..852c3c33 100644 --- a/src/interfacers/EmonHubSocketInterfacer.py +++ b/src/interfacers/EmonHubSocketInterfacer.py @@ -105,7 +105,6 @@ def read(self): c.rawdata = ' '.join(f) # Extract timestamp value if one is expected or use 0 - timestamp = 0.0 if self._settings['timestamped']: c.timestamp = f[0] f = f[1:] @@ -114,13 +113,10 @@ def read(self): f = f[1:] # Extract the Target id if one is expected if self._settings['targeted']: - #setting = str(setting).capitalize() c.target = int(f[0]) f = f[1:] # Extract list of data values - c.realdata = f#[1:] - # Create a Payload object - #f = new_cargo(data, node, timestamp, dest) + c.realdata = f return c diff --git a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py index bb022043..13a5f528 100644 --- a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py +++ b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py @@ -67,12 +67,8 @@ def run(self): for channel in self._settings["pubchannels"]: self._log.debug("%d Sent to channel(start)' : %s", rxc.uri, channel) - # Initialize channel if needed - if channel not in self._pub_channels: - self._pub_channels[channel] = [] - # Add cargo item to channel - self._pub_channels[channel].append(rxc) + self._pub_channels.setdefault(channel, []).append(rxc) self._log.debug("%d Sent to channel(end)' : %s", rxc.uri, channel) @@ -120,13 +116,13 @@ def _process_rx(self, smilics_dict): c.timestamp = time.mktime(datetime.datetime.now().timetuple()) return c - except: + except Exception: return None def set(self, **kwargs): """ Override default settings with settings entered in the config file """ - for key, setting in self._settings.iteritems(): + for key, setting in self._settings.tems(): if key in kwargs.keys(): # replace default self._settings[key] = kwargs[key] From c717867a7cf66e292c9e4343a83437a93291508c Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:29:29 +0000 Subject: [PATCH 34/78] Simplify code with list comprehension and zip. --- src/emonhub_interfacer.py | 3 +-- src/interfacers/EmonHubTemplateInterfacer.py | 28 +++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 9fca9371..08e15164 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -303,8 +303,7 @@ def _process_rx(self, cargo): if node in ehc.nodelist and 'rx' in ehc.nodelist[node] and 'whitening' in ehc.nodelist[node]['rx']: whitening = ehc.nodelist[node]['rx']['whitening'] if whitening is True or whitening == "1": - for i in range(len(rxc.realdata)): - rxc.realdata[i] = rxc.realdata[i] ^ 0x55 + rxc.realdata = [x ^ 0x55 for x in rxc.realdata] # check if node is listed and has individual datacodes for each value if node in ehc.nodelist and 'rx' in ehc.nodelist[node] and 'datacodes' in ehc.nodelist[node]['rx']: diff --git a/src/interfacers/EmonHubTemplateInterfacer.py b/src/interfacers/EmonHubTemplateInterfacer.py index 62f8adb2..0c1ab024 100644 --- a/src/interfacers/EmonHubTemplateInterfacer.py +++ b/src/interfacers/EmonHubTemplateInterfacer.py @@ -1,4 +1,7 @@ -import time, json, Cargo +import time +import json +from itertools import zip_longest +import Cargo from emonhub_interfacer import EmonHubInterfacer """class EmonHubTemplateInterfacer @@ -78,17 +81,18 @@ def add(self, cargo): if cargo.nodename: nodename = cargo.nodename - f = {} - f['node'] = nodename - f['data'] = {} - - # FIXME replace with zip - for i in range(len(cargo.realdata)): - name = str(i + 1) - if i < len(cargo.names): - name = cargo.names[i] - value = cargo.realdata[i] - f['data'][name] = value + f = {'node': nodename, + 'data': {} + } + + # FIXME zip_longest mimics the previous behaviour of this code which + # filled the gaps with a numeric string. However it's surely an error + # to provide more data than the schema expects, so it should either + # be an explicit error or silently dropped. + # If that's the case all this code can be simplified to: + # f['data'] = {name: value for name, value in zip(cargo.names, cargo.realdata)} + for i, (name, value) in enumerate(zip_longest(cargo.names, cargo.realdata, fill_value=None), start=1): + f['data'][name or str(i)] = value if cargo.rssi: f['data']['rssi'] = cargo.rssi From a594bf368f9e4c5b17a2795863d490f21ded9ef9 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:30:13 +0000 Subject: [PATCH 35/78] Simplify EmonHubFileSetup. This class is only ever a ConfigObj wrapper, so remove the json code. --- src/emonhub_setup.py | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/emonhub_setup.py b/src/emonhub_setup.py index 7f616b7e..05467538 100644 --- a/src/emonhub_setup.py +++ b/src/emonhub_setup.py @@ -9,7 +9,6 @@ import time import logging -import json from configobj import ConfigObj """class EmonHubSetup @@ -73,28 +72,17 @@ def __init__(self, filename): # Initialization super().__init__() - self._fileformat = "ConfigObj" # or "ConfigObj" - self._filename = filename # Initialize update timestamp self._settings_update_timestamp = 0 self._retry_time_interval = 5 - # create a timeout message if time out is set (>0) - if self._retry_time_interval > 0: - self.retry_msg = " Retry in " + str(self._retry_time_interval) + " seconds" - else: - self.retry_msg = "" + self.retry_msg = " Retry in " + str(self._retry_time_interval) + " seconds" # Initialize attribute settings as a ConfigObj instance try: - - if self._fileformat == "ConfigObj": - self.settings = ConfigObj(filename, file_error=True) - else: - with open(filename) as f: - self.settings = json.loads(f.read()) + self.settings = ConfigObj(filename, file_error=True) # Check the settings file sections self.settings['hub'] @@ -127,12 +115,7 @@ def check_settings(self): # Get settings from file try: - if self._fileformat == "ConfigObj": - self.settings.reload() - else: - with open(self._filename) as f: - self.settings = json.loads(f.read()) - + self.settings.reload() except IOError as e: self._log.warning('Could not get settings: %s %s', e, self.retry_msg) self._settings_update_timestamp = now + self._retry_time_interval From 4ba3b0ffc38ab5c46fea39a201d601b6dbda56c5 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:32:25 +0000 Subject: [PATCH 36/78] Add log_backup_count and log_max_bytes settings. This adds config options to control the log file rotation. --- src/emonhub.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/emonhub.py b/src/emonhub.py index c381a752..649d3cac 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -164,6 +164,16 @@ def _update_settings(self, settings): else: self._set_logging_level() + if 'log_backup_count' in settings['hub']: + for handler in self._log.handlers: + if isinstance(handler, logging.handlers.RotatingFileHandler): + handler.backupCount = settings['hub']['log_backup_count'] + self._log.info("Logging backup count set to %d", handler.backupCount) + if 'log_max_bytes' in settings['hub']: + for handler in self._log.handlers: + if isinstance(handler, logging.handlers.RotatingFileHandler): + handler.maxBytes = settings['hub']['log_max_bytes'] + self._log.info("Logging max file size set to %d bytes", handler.maxBytes) # Interfacers for name in self._interfacers: @@ -279,8 +289,10 @@ def _set_logging_level(self, level='WARNING', log=True): # this ensures it is writable # Close the file for now and get its path args.logfile.close() + # These maxBytes and backupCount defaults can be overridden in the config file. loghandler = logging.handlers.RotatingFileHandler(args.logfile.name, - 'a', 5000 * 1024, 1) + maxBytes=5000 * 1024, + backupCount=1) else: # Otherwise, if no path was specified, everything goes to sys.stderr loghandler = logging.StreamHandler() From 55401f2fb7a91116c964fe2361b13612804d6475 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:41:07 +0000 Subject: [PATCH 37/78] Typo. --- src/interfacers/EmonHubEmoncmsHTTPInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py index 1dcae3a6..0972d8bc 100644 --- a/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py +++ b/src/interfacers/EmonHubEmoncmsHTTPInterfacer.py @@ -70,7 +70,7 @@ def _process_post(self, databuffer): reply = self._send_post(post_url, {'data': data_string, 'sentat': str(sentat)}) if reply == 'ok': - self._log.debug("acknowledged receipt with '%s' from %s", reply, self._settings['url') + self._log.debug("acknowledged receipt with '%s' from %s", reply, self._settings['url']) return True else: self._log.warning("send failure: wanted 'ok' but got '%s'", reply) From 1192a115e46bfed03d013edc8232cb7add20a269 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:50:16 +0000 Subject: [PATCH 38/78] Revert removal of import * This import mechanism is fragile and needs to be converted into a true module. Revert this for now. --- src/emonhub.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/emonhub.py b/src/emonhub.py index 649d3cac..9a2b71fb 100755 --- a/src/emonhub.py +++ b/src/emonhub.py @@ -23,6 +23,7 @@ import emonhub_setup as ehs import emonhub_coder as ehc import emonhub_interfacer as ehi +from interfacers import * # this namespace and path namespace = sys.modules[__name__] From 039e3500989707466c26dbd2d5821d497eca7045 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Fri, 3 Jan 2020 16:55:11 +0000 Subject: [PATCH 39/78] Fix log format types. --- src/emonhub_interfacer.py | 4 ++-- src/interfacers/EmonHubSerialInterfacer.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 08e15164..3cc5d2db 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -101,7 +101,7 @@ def run(self): rxc = self._process_rx(rxc) if rxc: for channel in self._settings["pubchannels"]: - self._log.debug("%d Sent to channel(start)' : %d", rxc.uri, channel) + self._log.debug("%d Sent to channel(start)' : %s", rxc.uri, channel) # Initialise channel if needed if channel not in self._pub_channels: @@ -110,7 +110,7 @@ def run(self): # Add cargo item to channel self._pub_channels[channel].append(rxc) - self._log.debug("%d Sent to channel(end)' : %d", rxc.uri, channel) + self._log.debug("%d Sent to channel(end)' : %s", rxc.uri, channel) # Subscriber channels for channel in self._settings["subchannels"]: diff --git a/src/interfacers/EmonHubSerialInterfacer.py b/src/interfacers/EmonHubSerialInterfacer.py index f5b431f0..8b07bcd9 100644 --- a/src/interfacers/EmonHubSerialInterfacer.py +++ b/src/interfacers/EmonHubSerialInterfacer.py @@ -48,7 +48,7 @@ def _open_serial_port(self, com_port, com_baud): try: s = serial.Serial(com_port, com_baud, timeout=0) - self._log.debug("Opening serial port: %s @ %d bits/s", com_port, com_baud) + self._log.debug("Opening serial port: %s @ %s bits/s", com_port, com_baud) except serial.SerialException as e: self._log.error(e) s = False From a77e7350b45f3710ae54b5437b7a71f0e0601a09 Mon Sep 17 00:00:00 2001 From: TrystanLea Date: Mon, 11 May 2020 11:46:24 +0100 Subject: [PATCH 40/78] interfacer for SDS011 Nova PM sensor --- src/interfacers/EmonHubSDS011Interfacer.py | 123 +++++++++++++++++++++ src/interfacers/__init__.py | 3 +- 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/interfacers/EmonHubSDS011Interfacer.py diff --git a/src/interfacers/EmonHubSDS011Interfacer.py b/src/interfacers/EmonHubSDS011Interfacer.py new file mode 100644 index 00000000..0d31b8da --- /dev/null +++ b/src/interfacers/EmonHubSDS011Interfacer.py @@ -0,0 +1,123 @@ +# -*- coding: UTF-8 -*- + +import time, Cargo, serial, struct +from emonhub_interfacer import EmonHubInterfacer + +"""class EmonHubTemplateInterfacer + +Template interfacer for use in development + +""" + +class EmonHubSDS011Interfacer(EmonHubInterfacer): + + def __init__(self, name, serial_port='/dev/ttyUSB0'): + """Initialize Interfacer + + """ + # Initialization + super(EmonHubSDS011Interfacer, self).__init__(name) + + self._settings.update(self._defaults) + + self._template_settings = {'nodename':'SDS011','readinterval':10.0} + + # Open serial port + self._ser = self._open_serial_port(serial_port, 9600) + + self.byte, self.lastbyte = "\x00", "\x00" + self.pm_25_sum = 0 + self.pm_10_sum = 0 + self.count = 0 + self.lasttime = time.time() + + def close(self): + """Close serial port""" + + # Close serial port + if self._ser is not None: + self._log.debug("Closing serial port") + self._ser.close() + + def _open_serial_port(self, serial_port, baudrate): + """Open serial port""" + + try: + s = serial.Serial(serial_port, baudrate, stopbits=1, parity="N", timeout=2) + s.flushInput() + self._log.debug("Opening serial port: " + str(serial_port) + " @ "+ str(baudrate) + " bits/s") + except serial.SerialException as e: + self._log.error(e) + s = False + return s + + def read(self): + """Read data and process + + """ + if not self._ser: return False + + self.lastbyte = self.byte + self.byte = self._ser.read(size=1) + + # Valid packet header + if self.lastbyte == "\xAA" and self.byte == "\xC0": + sentence = self._ser.read(size=8) # Read 8 more bytes + readings = struct.unpack('=self._settings['readinterval']: + self.lasttime = time.time() + if self.count>0: + pm_25 = round(self.pm_25_sum/self.count,3) + pm_10 = round(self.pm_10_sum/self.count,3) + self.pm_25_sum = 0 + self.pm_10_sum = 0 + self.count = 0 + self._log.debug("PM 2.5:"+str(pm_25)+"μg/m^3 PM 10:"+str(pm_10)+"μg/m^3") + + # create a new cargo object, set data values + c = Cargo.new_cargo() + c.nodeid = self._settings['nodename'] + c.names = ["pm_25","pm_10"] + c.realdata = [pm_25,pm_10] + return c + + # nothing to return + return False + + def set(self, **kwargs): + """ + + """ + + for key, setting in self._template_settings.iteritems(): + # Decide which setting value to use + if key in kwargs.keys(): + setting = kwargs[key] + else: + setting = self._template_settings[key] + if key in self._settings and self._settings[key] == setting: + continue + elif key == 'readinterval': + self._log.info("Setting " + self.name + " readinterval: " + str(setting)) + self._settings[key] = float(setting) + continue + elif key == 'nodename': + self._log.info("Setting " + self.name + " nodename: " + str(setting)) + self._settings[key] = setting + continue + else: + self._log.warning("'%s' is not valid for %s: %s" % (str(setting), self.name, key)) + + # include kwargs from parent + super(EmonHubSDS011Interfacer, self).set(**kwargs) + diff --git a/src/interfacers/__init__.py b/src/interfacers/__init__.py index 77d0ed06..ee3c265b 100644 --- a/src/interfacers/__init__.py +++ b/src/interfacers/__init__.py @@ -13,6 +13,7 @@ "EmonHubGraphiteInterfacer", "EmonHubBMWInterfacer", "EmonModbusTcpInterfacer", - "EmonHubTemplateInterfacer" + "EmonHubTemplateInterfacer", + "EmonHubSDS011Interfacer" #"EmonFroniusModbusTcpInterfacer" ] From 5756fc688683e8f340dfb30ef97318f34d3046bb Mon Sep 17 00:00:00 2001 From: borpin Date: Sat, 6 Jun 2020 17:07:54 +0100 Subject: [PATCH 41/78] Add option to pass data as single JSON String Add option and pocessing to pass data message as single JSON payload --- src/interfacers/EmonHubMqttInterfacer.py | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index 0a9bc966..b49ab2af 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -1,5 +1,31 @@ """class EmonHubMqttGenInterfacer +Example emonhub configuration +[[MQTT]] + + Type = EmonHubMqttInterfacer + [[[init_settings]]] + mqtt_host = 127.0.0.1 + mqtt_port = 1883 + mqtt_user = emonpi + mqtt_passwd = emonpimqtt2016 + + [[[runtimesettings]]] + subchannels = ToEmonCMS, + + # emonhub/rx/10/values format + # Use with emoncms Nodes module + node_format_enable = 1 + node_format_basetopic = emonhub/ + + # emon/emontx/power1 format - use with Emoncms MQTT input + # http://github.com/emoncms/emoncms/blob/master/docs/RaspberryPi/MQTT.md + nodevar_format_enable = 1 + nodevar_format_basetopic = emon/ + + node_JSON_enable = 1 + node_JSON_basetopic = emon/JSON/ + """ import time import paho.mqtt.client as mqtt @@ -29,6 +55,10 @@ def __init__(self, name, mqtt_user=" ", mqtt_passwd=" ", mqtt_host="127.0.0.1", # nodes/emontx/power1 format 'nodevar_format_enable': 0, 'nodevar_format_basetopic': "nodes/" + + # JSON format + 'node_JSON_enable': 0, + 'node_JSON_basetopic': "emon/" } self._settings.update(self._mqtt_settings) @@ -156,6 +186,25 @@ def _process_post(self, databuffer): self._log.info("Publishing error? returned 4") return False + # ---------------------------------------------------------- + # Emoncms JSON format: / {"key":Value, ... "time":} + # ---------------------------------------------------------- + if int(self._settings["node_JSON_enable"]) == 1: + topic = self._settings["node_JSON_basetopic"] + nodename + payload = dict(zip(frame['names'],frame['data'])) + payload['time'] = frame['timestamp'] + if 'rssi' in frame: + payload['rssi'] = frame['rssi'] + + payloadJSON = json.dumps(payload) + + self._log.debug("Publishing: " + topic + " " + payloadJSON) + result = self._mqttc.publish(topic, payload=payloadJSON, qos=2, retain=False) + + if result[0] == 4: + self._log.info("Publishing error? returned 4") + return False + return True def action(self): @@ -265,5 +314,13 @@ def set(self, **kwargs): self._log.info("Setting " + self.name + " nodevar_format_basetopic: " + setting) self._settings[key] = setting continue + elif key == 'node_JSON_enable': + self._log.info("Setting " + self.name + " node_JSON_enable: " + setting) + self._settings[key] = setting + continue + elif key == 'node_JSON_basetopic': + self._log.info("Setting " + self.name + " node_JSON_basetopic: " + setting) + self._settings[key] = setting + continue else: self._log.warning("'%s' is not valid for %s: %s" % (setting, self.name, key)) From 68dee792cc92fbf1edb8168067b2a4e4a689acd5 Mon Sep 17 00:00:00 2001 From: borpin Date: Sat, 6 Jun 2020 17:45:54 +0100 Subject: [PATCH 42/78] MInor edit plus fromatting --- src/interfacers/EmonHubMqttInterfacer.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index b49ab2af..da821c77 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -31,6 +31,7 @@ import paho.mqtt.client as mqtt from emonhub_interfacer import EmonHubInterfacer import Cargo +import json class EmonHubMqttInterfacer(EmonHubInterfacer): @@ -54,7 +55,7 @@ def __init__(self, name, mqtt_user=" ", mqtt_passwd=" ", mqtt_host="127.0.0.1", # nodes/emontx/power1 format 'nodevar_format_enable': 0, - 'nodevar_format_basetopic': "nodes/" + 'nodevar_format_basetopic': "nodes/", # JSON format 'node_JSON_enable': 0, @@ -80,7 +81,7 @@ def __init__(self, name, mqtt_user=" ", mqtt_passwd=" ", mqtt_host="127.0.0.1", def add(self, cargo): """Append data to buffer. - format: {"emontx":{"power1":100,"power2":200,"power3":300}} + format: {"emontx":{"power1":100,"power2":200,"power3":300}} """ @@ -93,6 +94,7 @@ def add(self, cargo): f['node'] = nodename f['names'] = cargo.names f['data'] = cargo.realdata + f['timestamp'] = cargo.timestamp if cargo.rssi: f['rssi'] = cargo.rssi @@ -229,12 +231,12 @@ def action(self): def on_connect(self, client, userdata, flags, rc): - connack_string = {0: 'Connection successful', - 1: 'Connection refused - incorrect protocol version', - 2: 'Connection refused - invalid client identifier', - 3: 'Connection refused - server unavailable', - 4: 'Connection refused - bad username or password', - 5: 'Connection refused - not authorised'} + connack_string = { 0: 'Connection successful', + 1: 'Connection refused - incorrect protocol version', + 2: 'Connection refused - invalid client identifier', + 3: 'Connection refused - server unavailable', + 4: 'Connection refused - bad username or password', + 5: 'Connection refused - not authorised'} if rc: self._log.warning(connack_string[rc]) From c7128b007d3cebcd116f1ee1f47a2f6b9ec989c2 Mon Sep 17 00:00:00 2001 From: borpin Date: Tue, 9 Jun 2020 20:28:09 +0100 Subject: [PATCH 43/78] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9b370748..f2a222d0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ README.md *.swp *.idea *~ +.vscode/settings.json From 525c3a76748a0f09034020c9b855dea11bb87b92 Mon Sep 17 00:00:00 2001 From: borpin Date: Tue, 9 Jun 2020 20:28:40 +0100 Subject: [PATCH 44/78] Update install.sh sudo should not be used for pip3 commands --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 866f710a..a2c2bde6 100755 --- a/install.sh +++ b/install.sh @@ -17,7 +17,7 @@ if [ "$emonSD_pi_env" = "" ]; then fi sudo apt-get install -y python3-serial python3-configobj python3-pip -sudo pip3 install paho-mqtt requests +pip3 install paho-mqtt requests if [ "$emonSD_pi_env" = "1" ]; then # RaspberryPi Serial configuration From 547eb72dc29a14b2f89b348d9dfeed1b60cd8d64 Mon Sep 17 00:00:00 2001 From: borpin Date: Tue, 9 Jun 2020 20:28:57 +0100 Subject: [PATCH 45/78] Update documentation --- README.md | 103 ++++++----- configuration.md | 453 +++++++++++++++++++++++++++++------------------ 2 files changed, 326 insertions(+), 230 deletions(-) diff --git a/README.md b/README.md index 759b0a5d..6c990711 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,14 @@ -## emonHub (emon-pi variant) +# emonHub -Emonhub is used in the OpenEnergyMonitor system to read data received over serial from either the EmonPi board or the RFM12/69Pi adapter board then forward the data to emonCMS in a decoded ready-to-use form - based on the configuration in [emonhub.conf](https://github.com/openenergymonitor/emonhub/blob/emon-pi/configuration.md) +emonHub is used in the OpenEnergyMonitor system to read data received over serial from either the EmonPi board or the RFM12/69Pi adapter board then forward the data to emonCMS in a decoded ready-to-use form - based on the configuration in [emonhub.conf](https://github.com/openenergymonitor/emonhub/configuration.md) More generally: Emonhub consists of a series of interfacers that can read/subscribe or send/publish data to and from a multitude of services. EmonHub supports decoding data from: -### Default Interfacers - -- `EmonHubJeeInterfacer`: Decode data received from RFM69Pi & emonPi in [JeeLabs data packet structure](http://jeelabs.org/2010/12/07/binary-packet-decoding/) e.g. emonTx, emonTH, JeeNode RFM12 demo etc. -- `EmonHubMqttInterfacer`: Publish decoded data to MQTT - -### Other Interfacers - -*See interfacer specific readmes in [/conf/interfacer_examples](conf/interfacer_examples)* - -- Direct Serial: space seperated value format -- Direct Serial (emontx3e): current emonTx V3 CSV key:value format (added by @owenduffy) -- Smilics energy monitors (added by @K0den) -- Victron Products e.g BMV 700 battery monitor (added by @jlark) -- ModBus e.g. FRONIUS Solar inverter (added by @cjthuys) -- Graphite timeseries DB (added by @hmm01i) -- SMASolar (added by @stuartpittaway) -- BMW EV API e.g state of charge, charging state etc. (added by @stuartpittaway) - -*** - Emonhub is included on the [emonsD pre-built SD card](https://github.com/openenergymonitor/emonpi/wiki/emonSD-pre-built-SD-card-Download-&-Change-Log) used by both the EmonPi and Emonbase. The documentation below covers installing the emon-pi variant of emonhub on linux for self build setups. -### 'Emon-Pi' variant +## Features -This variant of emonhub is based on [@pb66 Paul Burnell's](https://github.com/pb66) experimental branch adding: +This vesrion of emonhub is based on [@pb66 Paul Burnell's](https://github.com/pb66) original adding: - Internal pub/sub message bus based on pydispatcher - Publish to MQTT @@ -37,58 +17,75 @@ This variant of emonhub is based on [@pb66 Paul Burnell's](https://github.com/pb - Rx and tx modes for node decoding/encoding provides improved control support. - json based config file option so that emonhub.conf can be loaded by emoncms -### [emonhub.conf configuration](https://github.com/openenergymonitor/emonhub/blob/emon-pi/configuration.md) +## Interfacers -### Installing Emonhub +### Default Interfacers -Emonhub requires the following dependencies: +- `EmonHubJeeInterfacer`: Decode data received from RFM69Pi & emonPi in [JeeLabs data packet structure](http://jeelabs.org/2010/12/07/binary-packet-decoding/) e.g. emonTx, emonTH, JeeNode RFM12 demo etc. +- `EmonHubMqttInterfacer`: Publish decoded data to MQTT in a format compatible with emonCMS. -Mosquitto: (see http://mosquitto.org/2013/01/mosquitto-debian-repository) +### Other Interfacers - wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key - sudo apt-key add mosquitto-repo.gpg.key - cd /etc/apt/sources.list.d/ +*See interfacer specific readmes in [/conf/interfacer_examples](conf/interfacer_examples)* -Depending on which version of Debian you're using: +- Direct Serial: space seperated value format +- Direct Serial (EmonHubTx3eInterfacer): current emonTx V3 CSV key:value format (added by @owenduffy) +- Smilics energy monitors (added by @K0den) +- Victron Products e.g BMV 700 battery monitor (added by @jlark) +- ModBus e.g. FRONIUS Solar inverter (added by @cjthuys) +- Graphite timeseries DB (added by @hmm01i) +- SMASolar (added by @stuartpittaway) +- BMW EV API e.g state of charge, charging state etc. (added by @stuartpittaway) - sudo wget http://repo.mosquitto.org/debian/mosquitto-wheezy.list +*** +## Installing Emonhub -or: +### emonScripts - sudo wget http://repo.mosquitto.org/debian/mosquitto-jessie.list +It can be installed by making suitable modifications to the emonScripts script. -Update apt information: +### Manual Install - sudo apt-get update +Emonhub requires Mosquitto - sudo apt-get install -y mosquitto python3-pip python3-serial python3-configobj python3-requests - sudo pip3 install paho-mqtt +```bash +sudo apt-get update +sudo apt-get install -y mosquitto +``` It is recommended to turn off mosquitto persistence - sudo nano /etc/mosquitto/mosquitto.conf +```bash +sudo nano /etc/mosquitto/mosquitto.conf +``` Set - persistence false - -Install the emon-pi variant of emonhub: +```text +persistence false +``` - git clone https://github.com/openenergymonitor/emonhub.git - cd emonhub - sudo ./install.sh +Install emonhub: -The emonhub configuration guide can be found here: - -[emonhub.conf configuration](https://github.com/openenergymonitor/emonhub/blob/emon-pi/configuration.md) +```bash +git clone https://github.com/openenergymonitor/emonhub.git +cd emonhub +git checkout stable +sudo ./install.sh +``` To view the emonhub log via terminal on the emonpi or emonbase: - journalctl -f -u emonhub -n 1000 +```bash +journalctl -f -u emonhub +``` +## Configuration + +The emonhub configuration guide can be found here: -### EmonHub Emoncms config module +[emonhub.conf configuration](https://github.com/openenergymonitor/emonhub/configuration.md) -If you're using Emoncms on the same Raspberry Pi as emonhub, you may find the emoncms config module useful which provides in-browser access to `emonhub.conf` and `emonhub.log`: +## EmonHub Emoncms config module -https://github.com/emoncms/config +The emonhub config module is now included on the standard emoncms build. If you're using Emoncms on the same Raspberry Pi as emonhub, the emoncms config module provides in-browser access to `emonhub.conf` and `emonhub.log` diff --git a/configuration.md b/configuration.md index ed7626f7..82f92769 100644 --- a/configuration.md +++ b/configuration.md @@ -8,18 +8,29 @@ Emonhub.conf has 3 sections: Hub is a section for emonhub global settings such as the loglevel. - ## 2. `interfacers` -Interfacers holds the configuration for the different interfacers that emonhub supports such as the EmonHubJeeInterfacer for reading and writing to the RFM69Pi adapter board or emonPi board via serial, or the EmonHubMqttInterfacer which can be used to publish the data received from EmonHubJeeInterfacer to MQTT topics. For more interfacer examples see [conf/interfacer_examples](https://github.com/openenergymonitor/emonhub/blob/emon-pi/conf/interfacer_examples) +Interfacers holds the configuration for the different interfacers that emonhub supports such as the EmonHubJeeInterfacer for reading and writing to the RFM69Pi adapter board or emonPi board via serial, or the EmonHubMqttInterfacer which can be used to publish the data received from EmonHubJeeInterfacer to MQTT topics. For more interfacer examples see [conf/interfacer_examples](https://github.com/openenergymonitor/emonhub/conf/interfacer_examples) + +### Channels + +Each interfacer can listen on a `subchannel` or publish on a `pubchannel`. Some interfacers can do both. + +For Example: +Ther Serial Interfacer listens on the serial port then publishes that data for onward transmission - it has a `pubchannel` defined. + +The MQTT interfacer listens for data which it then sends out via MQTT, it therefore defines a `subchannel` that it will listen on for data to send via MQTT. + +For data to be passed, the name of the 2 channels must match. + +Each interfacer can have multiple channels defined and multiple interfacers can listen to the same channel. e.g. data published by the Serial Interfacer can be listened (subscribed) for by the MQTT and the HTTP interfacer. ## 3. `nodes` Nodes holds the decoder configuration for rfm12/69 node data which are sent as binary structures. - -``` +```text ####################################################################### ####################### emonhub.conf ######################### ####################################################################### @@ -31,39 +42,48 @@ Nodes holds the decoder configuration for rfm12/69 node data which are sent as b [hub] ### loglevel must be one of DEBUG, INFO, WARNING, ERROR, and CRITICAL loglevel = DEBUG - + ####################################################################### ####################### Interfacers ####################### ####################################################################### [interfacers] - + ####################################################################### ####################### Nodes ####################### ####################################################################### [nodes] ``` -**View full latest [default emonHub.conf](https://github.com/openenergymonitor/emonhub/blob/emon-pi/conf/emonpi.default.emonhub.conf)** +**View full latest [default emonHub.conf](https://github.com/openenergymonitor/emonhub/conf/emonpi.default.emonhub.conf)** *** -# 1. 'hub' Configuration +## 1. 'hub' Configuration Hub is a section for emonhub global settings such as the loglevel. The hub configuration should be self explanatory. Emonhub log can be viewed in the `Setup > EmonHub` section of local Emoncms on an emonPi / emonBase. Default settings are: -``` +```text ### loglevel must be one of DEBUG, INFO, WARNING, ERROR, and CRITICAL loglevel = DEBUG ### Uncomment this to also send to syslog # use_syslog = yes ``` + *** -# 2. 'interfacers' Configuration -Interfacers holds the configuration for the different interfacers that emonhub supports such as the EmonHubJeeInterfacer for reading and writing to the RFM69Pi adapter board or emonPi board via serial, or the EmonHubMqttInterfacer which can be used to publish the data received from EmonHubJeeInterfacer to MQTT topics: +## 2. 'interfacers' Configuration + +Interfacers holds the configuration for the different interfacers that emonhub supports such as the EmonHubJeeInterfacer for reading and writing to the RFM69Pi adapter board or emonPi board via serial, or the EmonHubMqttInterfacer which can be used to publish the data received from EmonHubJeeInterfacer to MQTT topics. + +Each interfacer has a unique name between the first set of double brackets `[[xxx]]` +The `Type` corresponds to the interfacer file name as found in `src/interfacers/` directory. + +`[[[init_settings]]]` are settings used by the interfacer on setup. These are usually defined in the header of the interfacer file. + +`[[[runtime_settings]]]` are other settings for the interfacer. The first setting in this group must be either `pubchannels` or `subchannels` and must end with a comma. There must be a blank line. The remaining options are optional and if not specified will fall back to the interfacer defaults. ### a.) [[RFM2Pi]] @@ -73,7 +93,7 @@ The frequency and network group must match the hardware and other nodes on the n The `calibration` config is used to set the calibration of the emonPi when using USA AC-AC adapters 110V. Set `calibration = 110V` when using USA AC-AC adapter. -``` +```text [[RFM2Pi]] Type = EmonHubJeeInterfacer [[[init_settings]]] @@ -86,15 +106,13 @@ The `calibration` config is used to set the calibration of the emonPi when using group = 210 frequency = 433 baseid = 5 # emonPi / emonBase nodeID - quiet = true # Report incomplete RF packets (no implemented on emonPi) + quiet = true # Report incomplete RF packets (not implemented on emonPi) calibration = 230V # (UK/EU: 230V, US: 110V) # interval = 0 # Interval to transmit time to emonGLCD (seconds) ``` - ### b.) [[MQTT]] - Emonhub supports publishing to MQTT topics through the EmonHubMqttInterfacer, defined in the interfacers section of emonhub.conf. There are two formats that can be used for publishing node data to MQTT: @@ -103,23 +121,38 @@ There are two formats that can be used for publishing node data to MQTT: (default base topic is `emonhub`) +```text topic: basetopic/rx/10/values payload: 100,200,300 +``` -The 'node only format' is used with the emoncms [Nodes Module](https://github.com/emoncms/nodes) (now deprecated on Emoncms V9) and the emonPiLCD python service. +The 'node only format' is used with the emoncms [Nodes Module](https://github.com/emoncms/nodes) (now deprecated on Emoncms V9+) and the emonPiLCD python service. #### **2. Node variable format** (default base topic is `emon`) +```text topic: basetopic/emontx/power1 payload: 100 +``` -The 'Node variable format' is the current default format on Emoncms V9. It's a more generic MQTT publishing format that can more easily be used by applications such as NodeRED and OpenHab. This format can also be used with the emoncms `phpmqtt_input.php` script in conjunction with the emoncms inputs module. See [User Guide > Technical MQTT](https://guide.openenergymonitor.org/technical/mqtt/). +The 'Node variable format' is the current default format from Emoncms V9. It's a more generic MQTT publishing format that can more easily be used by applications such as NodeRED and OpenHab. This format can also be used with the emoncms `phpmqtt_input.php` script in conjunction with the emoncms inputs module. See [User Guide > Technical MQTT](https://guide.openenergymonitor.org/technical/mqtt/). -Default `[MQTT]` config: +#### **3. JSON format*** +(default base topic is `emon`) + +```text + topic: basetopic/ + payload: {"key1":value1, "key2":value2, .... "time":, "rssi":} ``` + +This forat exports the data as a single JSOn string with key:value pairs. The timestamp is automatically added and used for the input time to emoncms. The RSSI is added if available (RF in use). + +**Default `[MQTT]` config:** + +```text [[MQTT]] Type = EmonHubMqttInterfacer @@ -130,29 +163,31 @@ Default `[MQTT]` config: mqtt_passwd = emonpimqtt2016 [[[runtimesettings]]] - pubchannels = ToRFM12, + # pubchannels = ToRFM12, subchannels = ToEmonCMS, # emonhub/rx/10/values format # Use with emoncms Nodes module - node_format_enable = 1 + node_format_enable = 0 node_format_basetopic = emonhub/ # emon/emontx/power1 format - use with Emoncms MQTT input # http://github.com/emoncms/emoncms/blob/master/docs/RaspberryPi/MQTT.md nodevar_format_enable = 1 nodevar_format_basetopic = emon/ -``` - -To enable the node variable format set `nodevar_format_enable = 1`. To disable the node only format set `node_format_enable = 0`. + # Single JSON payload published - use with Emoncms MQTT + node_JSON_enable = 0 + node_JSON_basetopic = emon/ +``` +To enable one of the formats set the `enable` flag to `1`. More than one format can be used simultaneously. ### c.) [[emoncmsorg]] -The EmonHubEmoncmsHTTPInterfacer configuration that is used for sending data to emoncms.org can also be found in the interfacers section of emonhub.conf. If you wish to use emoncms.org the only change to make here is to replace the blank apikey with your write apikey from emoncms.org found on the user account page. See [Setup Guide > Setup > Remote logging](https://guide.openenergymonitor.org/setup/remote). +The EmonHubEmoncmsHTTPInterfacer configuration that is used for sending data to emoncms.org (or any instance of emoncms). If you wish to use emoncms.org the only change to make here is to replace the blank apikey with your write apikey from emoncms.org found on the user account page. See [Setup Guide > Setup > Remote logging](https://guide.openenergymonitor.org/setup/remote). -``` +```text [[emoncmsorg]] Type = EmonHubEmoncmsHTTPInterfacer [[[init_settings]]] @@ -165,14 +200,13 @@ The EmonHubEmoncmsHTTPInterfacer configuration that is used for sending data to sendstatus = 1 ``` +`sendstatus` - It is possible to the EmonHubEmoncmsHTTPInterfacer to send a 'ping' to the destination emoncms that can be picked up by the myip module which will then list the source IP address. This can be useful for remote login to a home emonpi if port forwarding is enabled on your router. -**sendstatus** It's possible to the EmonHubEmoncmsHTTPInterfacer to send a 'ping' to the destination emoncms that can be picked up by the myip module which will then list the source IP address. This can be useful for remote login to a home emonpi if port forwarding is enabled on your router. +`senddata` - If you only want to send the ping request, and no data, to emoncms.org set this to 0 -**senddata** If you only want to send the ping request, and no data, to emoncms.org set this to 0 +You can create more than one of these sections to send data to multiple emoncms instances. For example, if you wanted to send to an emoncms running at emoncms.example.com (or on a local LAN) you would add the following underneath the `emoncmsorg` section described above: -You can create more than one of these sections to send data to multiple emoncms instances. For example, if you wanted to send to an emoncms running at emoncms.example.com you would add the following underneath the `emoncmsorg` section described above: - -``` +```text [[emoncmsexample]] Type = EmonHubEmoncmsHTTPInterfacer [[[init_settings]]] @@ -187,42 +221,71 @@ You can create more than one of these sections to send data to multiple emoncms This time, the API key will be the API key from your account at emoncms.example.com. -### d.) Socket Interfacer +### d.) [[Serial]] (EmonTX V3) + +There are 2 different serial interfacers. The TX3e interfacer is for connecting an RPi directly to the serial output (UART) of the emonTX. + +The default nodeID is `0` and the `nodeoffset` optional parameter will increase that nodeID by its value. + +```text + [[SerialTx]] + Type = EmonHubTx3eInterfacer + [[[init_settings]]] + com_port= /dev/ttyAMA0 + com_baud = 115200 + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + + nodeoffset = 1 +``` + +The latest version of the emonTX CM Firmware, sends the data to the serial interface as a `key:value` pair. There is no need for a `node` decoder to be defined so a nodeID with no decoder should be specified. + +### e.) Socket Interfacer The EmonHub socket interfacer is particularly useful for inputing data from a range of sources. e.g a script monitoring server status where you wish to post the result to both a local instance of emoncms and a remote instance of emoncms alongside other data from other sources such as rfm node data. As an example, the following python script will post a single line of data values on node 98 to an emonhub instance running locally and listening on port 8080: - import socket, time - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(('localhost', 8080)) - s.sendall('98 3.8 1.6 5.2 80.3\r\n') - +```python +import socket, time +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect(('localhost', 8080)) +s.sendall('98 3.8 1.6 5.2 80.3\r\n') +``` + The following emonhub.conf interfacer definition will listen on the choosen socket and forward the data on the ToEmonCMS channel: +```text [[mysocketlistener]] Type = EmonHubSocketInterfacer [[[init_settings]]] - port_nb = 8080 + port_nb = 8080 [[[runtimesettings]]] - pubchannels = ToEmonCMS, - -#### **1. Timestamped data** + pubchannels = ToEmonCMS, +``` + +#### **Timestamped data** To set a timestamp for the posted data add the timestamped property to the emonhub.conf runtimesettings section: - [[[runtimesettings]]] - pubchannels = ToEmonCMS, - timestamped = True - +```text + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + timestamped = True +``` + The python client example needs to include the timestamp e.g: - s.sendall(str(time.time())+' 98 3.8 1.6 5.2 80.3\r\n') +```python +s.sendall(str(time.time())+' 98 3.8 1.6 5.2 80.3\r\n') +``` -### e.) Tesla Power Wall Interfacer +### f.) Tesla Power Wall Interfacer This interfacer fetches the state of charge of a Tesla Power Wall on the local network. Enter your PowerWall IP-address or hostname in the URL section of the following emonhub.conf configuration: +```text [[PowerWall]] Type = EmonHubTeslaPowerWallInterfacer [[[init_settings]]] @@ -231,14 +294,17 @@ This interfacer fetches the state of charge of a Tesla Power Wall on the local n name = powerwall url = http://POWERWALL-IP/api/system_status/soe readinterval = 10 - +``` + *** -# 3. 'nodes' Configuration +## 3. 'nodes' Configuration -The 2nd part of the emonhub.conf configuration concerns decoding of RFM12 and RFM69 nodes. Here's an example of what this section looks like from the default emonpi emonhub.conf. The rest of this readme explains what each line means and how to write your own node decoders or adapt existing decoders for new requirements. +The 2nd part of the emonhub.conf configuration concerns decoding of RFM12 and RFM69 nodes. The data in encoded before transmission and the received data must therefore be 'decoded' i.e. converted from a raw datacode to recgonisable values. -``` +Here's an example of what this section looks like from the default emonpi emonhub.conf. The rest of this readme explains what each line means and how to write your own node decoders or adapt existing decoders for new requirements. + +```text ####################################################################### ####################### Nodes ####################### @@ -267,15 +333,17 @@ The 2nd part of the emonhub.conf configuration concerns decoding of RFM12 and RF ### NodeID -`[[10]]` - +```text +[[10]] +``` A numeric NodeID. This identifies the node to emonHub. Every node within your system MUST have a unique ID. There may be only one definition for each NodeId. The NodeID is programmed into the node firmware, either in the sketch and/or by switches. ### nodename - `nodename =` - +```text +nodename = +``` A text string, for your benefit in identifying each node. *This field is optional.* @@ -283,46 +351,54 @@ MQTT: The nodename can be used with the MQTT interfacer to send topics of the fo ### firmware - `firmware =` +```text +firmware = +``` A text string specifying the sketch running on the node. (At present, this is for information only. At some future time, it might be used to auto-configure emonHub and/or the sketch.) *This field is optional.* ### hardware - `hardware =` +```text +hardware = +``` Indicates the host environment for human reference. **This field is optional.** ### rx - `[[[rx]]]` +```text +[[[rx]]] +``` -This must be "rx" and specifies that the next section is for the config of the sensor values received from a node. Its also possible to define a "tx" section for variables to be sent to the node such as control state's. +This must be "rx" and specifies that the next section is for the config of the sensor values received from a node. Its also possible to define a "tx" section for variables to be sent to the node such as control state's. ### tx -`[[[tx]]]` +```text +[[[tx]]] +``` It's possible to transmitt data to other nodes via RFM e.g. the following config -``` - [[[tx]]] - names=nodeid,hour,minute,second,utilityW,solarW,utilityKwh,solarKwh - datacodes =b,b,b,h,h,H,H - units = h,min,sec,W,W,kwh,kwh +```text + [[[tx]]] + names=nodeid,hour,minute,second,utilityW,solarW,utilityKwh,solarKwh + datacodes =b,b,b,h,h,H,H + units = h,min,sec,W,W,kwh,kwh ``` -The following data published to MQTT +The following data published to MQTT `emonhub/tx/20/values/14,38,34,700,138,2700,829` -Will result in the follwing data being transmitted via RF in JeeLib packet formatt: +Will result in the follwing data being transmitted via RF in JeeLib packet formatt: `14,38,34,700,138,2700,829` -To decode the RFM data use the following struct in the receiver node, emonGLCD in this example: +To decode the RFM data use the following struct in the receiver node, emonGLCD in this example: -``` +```python typedef struct { byte nodeId ; byte hour, min, sec ; @@ -340,22 +416,23 @@ Earlier OpenEnergyMonitor nodes always sent a series of integers and so no decod Its possible to decode any radio packet that is packed as a binary structure with Emonhub. For example if we look at the relevant part of the node decoder for the emontx v3 we can see 11 integers (h) and one unsigned long at the end (L) -``` - [[8]] - [[[rx]]] - datacodes = h,h,h,h,h,h,h,h,h,h,h,L +```text +[[8]] + [[[rx]]] + datacodes = h,h,h,h,h,h,h,h,h,h,h,L ``` The node decoder could be left like this if we only wanted to decode the packet structure correctly. Alternatively, if the packet structure is a series of integers its possible to write: -``` - [[1]] - [[[rx]]] - datacode = h +```text +[[1]] + [[[rx]]] + datacode = h ``` Notice that the name is datacode rather than datacode**s** with an s. There are 13 different datatypes that can be decoded: +```text b: byte, 1 byte h: short integer, 2 bytes i: integer, 4 bytes @@ -369,6 +446,7 @@ Notice that the name is datacode rather than datacode**s** with an s. There are L: unsigned long, 4 bytes Q: unsigned long long, 8 bytes c: char, 1 byte +``` `datacode = 0` is a valid datacode. It is best remembered by thinking of it as either "0 = False" (no decoding) or "Zero decoding required"; in code it is a logical test as to whether to continue, or bypass, decoding the value(s). @@ -378,11 +456,11 @@ Notice that the name is datacode rather than datacode**s** with an s. There are It's possible to specify sensor value names to help with identification. The emoncms nodes module can also load these names for its node list. Another possibility not yet implemented, is to use these names to publish sensor values to MQTT topics of the form nodes/emontx/power1. -``` - [[8]] - [[[rx]]] - names = power1, power2, power3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L +```text +[[8]] + [[[rx]]] + names = power1, power2, power3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L ``` ### Scales @@ -391,23 +469,23 @@ In order to keep radio packet length small, a sensor value measured as a float o The scales to be applied can be specified for each sensor value as in this example for the emontx: -``` - [[8]] - [[[rx]]] - names = power1, power2, power3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 +```text +[[8]] + [[[rx]]] + names = power1, power2, power3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 ``` In this example, the RMS Voltage is multiplied by 0.01, and temperature values by 0.1. Which means the RMS voltage was multiplied by 100 and the temperature value(s) by 10, on the emontx. or a single scale can be applied (note scale instead of scale**s** with an s) -``` - [[8]] - [[[rx]]] - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scale = 1 +```text +[[8]] + [[[rx]]] + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scale = 1 ``` The default scale value is 1, so where no scaling is needed, this line can be left out of the configuration. @@ -418,13 +496,13 @@ The latest version of the emon-pi variant of emonhub does not require the number A comma-separated list of engineering units to describe the data. Common units are W, kW, V, A, C, %. These are only to help with identification. The are currently used in the emoncms nodes module UI. -``` - [[8]] - [[[rx]]] - names = power1, power2, power3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 - units =W,W,W,W,V,C,C,C,C,C,C,p +```text +[[8]] + [[[rx]]] + names = power1, power2, power3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 + units =W,W,W,W,V,C,C,C,C,C,C,p ``` ## Standard node decoders @@ -433,79 +511,93 @@ The following lists the standard node decoders for recent versions of the EmonPi If you upload firmware to any of these nodes, and wish to have the data decoded with names, units, and scaled correctly, these are the decoders for the standard firmware. The node decoders are also included at the top of each firmware file for reference. -### EmonPi:[Firmware location](https://github.com/openenergymonitor/emonpi/) +### EmonPi + +[Firmware location](https://github.com/openenergymonitor/emonpi/) Copied here for reference: +```text +[[5]] + nodename = emonPi + firmware = emonPi_RFM69CW_RF12Demo_DiscreteSampling.ino + hardware = emonpi + [[[rx]]] + names = power1,power2,power1_plus_power2,Vrms,T1,T2,T3,T4,T5,T6,pulseCount + datacodes = h, h, h, h, h, h, h, h, h, h, L + scales = 1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 + units = W,W,W,V,C,C,C,C,C,C,p ``` - [[5]] - nodename = emonPi - firmware = emonPi_RFM69CW_RF12Demo_DiscreteSampling.ino - hardware = emonpi - [[[rx]]] - names = power1,power2,power1_plus_power2,Vrms,T1,T2,T3,T4,T5,T6,pulseCount - datacodes = h, h, h, h, h, h, h, h, h, h, L - scales = 1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 - units = W,W,W,V,C,C,C,C,C,C,p -``` -### EmonTx v3: [Firmware location](https://github.com/openenergymonitor/emonTx3) +### EmonTx v3.4 DS Firmware V2.3+ + +[Firmware location](https://github.com/openenergymonitor/emonTx3) Node ID when DIP switch1 is off = 8, node ID when DIP switch1 is on is 7 Copied here for reference: -``` - [[8]] - nodename = emonTx_3 - firmware =V2_3_emonTxV3_4_DiscreteSampling - hardware = emonTx_(NodeID_DIP_Switch1:OFF) - [[[rx]]] - names = power1, power2, power3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 - units =W,W,W,W,V,C,C,C,C,C,C,p +```text +[[8]] + nodename = emonTx_3 + firmware =V2_3_emonTxV3_4_DiscreteSampling + hardware = emonTx_(NodeID_DIP_Switch1:OFF) + [[[rx]]] + names = power1, power2, power3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units =W,W,W,W,V,C,C,C,C,C,C,p ``` -### EmonTx v3, emonTxV3_4_DiscreteSampling.ino, v1.6+ [Firmware Location](https://github.com/openenergymonitor/emonTxFirmware) +### EmonTx v3.4 DS Firmware 1.6+ + +EmonTx v3 (emonTxV3_4_DiscreteSampling.ino, v1.6+) [Firmware Location](https://github.com/openenergymonitor/emonTxFirmware) Can be on either nodeid 10 or 9 - [[10]] - nodename = emonTx_1 - firmware =V1_6_emonTxV3_4_DiscreteSampling - hardware = emonTx_(NodeID_DIP_Switch1:OFF) - [[[rx]]] - names = power1, power2, power3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacode = h - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 #Firmware V1.6 - units =W,W,W,W,V,C,C,C,C,C,C,p - -### EmonTx v3, emonTxV3_4_DiscreteSampling.ino, v1.6.1 @@ -513,32 +605,39 @@ emonTH V1 [Firmware location](https://github.com/openenergymonitor/emonTH) Standard nodeid's: 19, 20, 21 & 22 depending on DIP switch positions: - [[19]] - nodename = emonTH_1 - firmware = emonTH_DHT22_DS18B20_RFM69CW - hardware = emonTH_(Node_ID_Switch_DIP1:OFF_DIP2:OFF) - [[[rx]]] - names = temperature, external temperature, humidity, battery - datacode = h - scales = 0.1,0.1,0.1,0.1 - units = C,C,%,V - -### EmonTx Shield [Firmware location]https://github.com/openenergymonitor/emontx-shield) - - [[6]] - nodename = emonTxShield - firmware =emonTxShield - hardware = emonTxShield - [[[rx]]] - names = power1, power2, power3, power4, Vrms - datacode = h - scales = 1,1,1,1,0.01 - units =W,W,W,W,V +```text +[[19]] + nodename = emonTH_1 + firmware = emonTH_DHT22_DS18B20_RFM69CW + hardware = emonTH_(Node_ID_Switch_DIP1:OFF_DIP2:OFF) + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V +``` + +### EmonTx Shield + +EmonTX Shield [Firmware location](https://github.com/openenergymonitor/emontx-shield) + +```text +[[6]] + nodename = emonTxShield + firmware =emonTxShield + hardware = emonTxShield + [[[rx]]] + names = power1, power2, power3, power4, Vrms + datacode = h + scales = 1,1,1,1,0.01 + units =W,W,W,W,V +``` *** -# 4. Troubleshooting -### Node data inactive or, node data does not appear for a configured node +## 4. Troubleshooting + +## Node data inactive or, node data does not appear for a configured node Try replacing the datacodes = h,h,h,h,... line with **datacode = h** (note: datacode without an s). This will decode most of the radio packet content for the standard OpenEnergyMonitor emontx,emonth and emonpi firmwares, including historic versions. From 10a01ac315e0a9e4890196b474229bfd0e6d29a8 Mon Sep 17 00:00:00 2001 From: borpin Date: Tue, 9 Jun 2020 21:07:59 +0100 Subject: [PATCH 46/78] Update emonhub.conf Update with JSON format setting Update indenting. --- conf/emonhub.conf | 234 ++++++++++++++++--------------- conf/emonpi.default.emonhub.conf | 186 ++++++++++++------------ 2 files changed, 214 insertions(+), 206 deletions(-) diff --git a/conf/emonhub.conf b/conf/emonhub.conf index b2b46857..fa2cc57d 100644 --- a/conf/emonhub.conf +++ b/conf/emonhub.conf @@ -26,55 +26,59 @@ loglevel = DEBUG #(default:WARNING) [interfacers] ### This interfacer manages the RFM12Pi/RFM69Pi/emonPi module [[RFM2Pi]] - Type = EmonHubJeeInterfacer - [[[init_settings]]] - com_port = /dev/ttyAMA0 - com_baud = 38400 # 9600 for old RFM12Pi modules - [[[runtimesettings]]] - pubchannels = ToEmonCMS, - subchannels = ToRFM12, - - group = 210 - frequency = 433 - baseid = 5 # emonPi / emonBase nodeID - quiet = true # Report incomplete RF packets (no implemented on emonPi) - calibration = 230V # (UK/EU: 230V, US: 110V) - # interval = 0 # Interval to transmit time to emonGLCD (seconds) + Type = EmonHubJeeInterfacer + [[[init_settings]]] + com_port = /dev/ttyAMA0 + com_baud = 38400 # 9600 for old RFM12Pi modules + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + subchannels = ToRFM12, + + group = 210 + frequency = 433 + baseid = 5 # emonPi / emonBase nodeID + quiet = true # Report incomplete RF packets (no implemented on emonPi) + calibration = 230V # (UK/EU: 230V, US: 110V) + # interval = 0 # Interval to transmit time to emonGLCD (seconds) [[MQTT]] - Type = EmonHubMqttInterfacer - [[[init_settings]]] - mqtt_host = 127.0.0.1 - mqtt_port = 1883 - mqtt_user = '' - mqtt_passwd = '' + Type = EmonHubMqttInterfacer + [[[init_settings]]] + mqtt_host = 127.0.0.1 + mqtt_port = 1883 + mqtt_user = '' + mqtt_passwd = '' - [[[runtimesettings]]] - pubchannels = ToRFM12, - subchannels = ToEmonCMS, + [[[runtimesettings]]] + pubchannels = ToRFM12, + subchannels = ToEmonCMS, - # emonhub/rx/10/values format - # Use with emoncms Nodes module - node_format_enable = 1 - node_format_basetopic = emonhub/ + # emonhub/rx/10/values format + # Use with emoncms Nodes module + node_format_enable = 1 + node_format_basetopic = emonhub/ - # emon/emontx/power1 format - use with Emoncms MQTT input - # http://github.com/emoncms/emoncms/blob/master/docs/RaspberryPi/MQTT.md - nodevar_format_enable = 1 - nodevar_format_basetopic = emon/ + # emon/emontx/power1 format - use with Emoncms MQTT input + # http://github.com/emoncms/emoncms/blob/master/docs/RaspberryPi/MQTT.md + nodevar_format_enable = 1 + nodevar_format_basetopic = emon/ + + # Single JSON payload published - use with Emoncms MQTT + node_JSON_enable = 0 + node_JSON_basetopic = emon/ [[emoncmsorg]] - Type = EmonHubEmoncmsHTTPInterfacer - [[[init_settings]]] - [[[runtimesettings]]] - pubchannels = ToRFM12, - subchannels = ToEmonCMS, - url = https://emoncms.org - apikey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - senddata = 0 # Enable sending data to Emoncms.org - sendstatus = 1 # Enable sending WAN IP to Emoncms.org MyIP > https://emoncms.org/myip/list - interval = 30 # Bulk send interval to Emoncms.org in seconds + Type = EmonHubEmoncmsHTTPInterfacer + [[[init_settings]]] + [[[runtimesettings]]] + pubchannels = ToRFM12, + subchannels = ToEmonCMS, + url = https://emoncms.org + apikey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + senddata = 0 # Enable sending data to Emoncms.org + sendstatus = 1 # Enable sending WAN IP to Emoncms.org MyIP > https://emoncms.org/myip/list + interval = 30 # Bulk send interval to Emoncms.org in seconds ####################################################################### ####################### Nodes ####################### @@ -85,20 +89,20 @@ loglevel = DEBUG #(default:WARNING) ## See config user guide: https://github.com/openenergymonitor/emonhub/blob/emon-pi/configuration.md [[5]] - nodename = emonpi - [[[rx]]] - names = power1,power2,power1pluspower2,vrms,t1,t2,t3,t4,t5,t6,pulsecount - datacodes = h, h, h, h, h, h, h, h, h, h, L - scales = 1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 - units = W,W,W,V,C,C,C,C,C,C,p + nodename = emonpi + [[[rx]]] + names = power1,power2,power1pluspower2,vrms,t1,t2,t3,t4,t5,t6,pulsecount + datacodes = h, h, h, h, h, h, h, h, h, h, L + scales = 1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 + units = W,W,W,V,C,C,C,C,C,C,p [[6]] - nodename = emontxshield - [[[rx]]] - names = power1, power2, power3, power4, vrms - datacode = h - scales = 1,1,1,1,0.01 - units =W,W,W,W,V + nodename = emontxshield + [[[rx]]] + names = power1, power2, power3, power4, vrms + datacode = h + scales = 1,1,1,1,0.01 + units =W,W,W,W,V [[7]] nodename = emontx4 @@ -109,12 +113,12 @@ loglevel = DEBUG #(default:WARNING) units =W,W,W,W,V,C,C,C,C,C,C,p [[8]] - nodename = emontx3 - [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 - units =W,W,W,W,V,C,C,C,C,C,C,p + nodename = emontx3 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units =W,W,W,W,V,C,C,C,C,C,C,p [[9]] nodename = emontx2 @@ -125,44 +129,44 @@ loglevel = DEBUG #(default:WARNING) units =W,W,W,W,V,C,C,C,C,C,C,p [[10]] - nodename = emontx1 - [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacode = h - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 - units =W,W,W,W,V,C,C,C,C,C,C,p + nodename = emontx1 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacode = h + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units =W,W,W,W,V,C,C,C,C,C,C,p [[11]] - nodename = 3phase - [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units =W,W,W,W,V,C,C,C,C,C,C,p - - [[12]] - nodename = 3phase2 - [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units =W,W,W,W,V,C,C,C,C,C,C,p + nodename = 3phase + [[[rx]]] + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units =W,W,W,W,V,C,C,C,C,C,C,p + +[[12]] + nodename = 3phase2 + [[[rx]]] + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units =W,W,W,W,V,C,C,C,C,C,C,p [[13]] - nodename = 3phase3 - [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units =W,W,W,W,V,C,C,C,C,C,C,p + nodename = 3phase3 + [[[rx]]] + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units =W,W,W,W,V,C,C,C,C,C,C,p [[14]] - nodename = 3phase4 - [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units =W,W,W,W,V,C,C,C,C,C,C,p + nodename = 3phase4 + [[[rx]]] + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units =W,W,W,W,V,C,C,C,C,C,C,p [[19]] nodename = emonth1 @@ -197,33 +201,33 @@ loglevel = DEBUG #(default:WARNING) units = C,C,%,V [[23]] - nodename = emonth5 - [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + nodename = emonth5 + [[[rx]]] + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p [[24]] - nodename = emonth6 - [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + nodename = emonth6 + [[[rx]]] + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p [[25]] - nodename = emonth7 - [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + nodename = emonth7 + [[[rx]]] + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p [[26]] - nodename = emonth8 - [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + nodename = emonth8 + [[[rx]]] + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p diff --git a/conf/emonpi.default.emonhub.conf b/conf/emonpi.default.emonhub.conf index 6d6a055c..91733a66 100644 --- a/conf/emonpi.default.emonhub.conf +++ b/conf/emonpi.default.emonhub.conf @@ -58,6 +58,10 @@ loglevel = DEBUG nodevar_format_enable = 1 nodevar_format_basetopic = emon/ + # Single JSON payload published - use with Emoncms MQTT + node_JSON_enable = 0 + node_JSON_basetopic = emon/ + [[emoncmsorg]] Type = EmonHubEmoncmsHTTPInterfacer [[[init_settings]]] @@ -89,153 +93,153 @@ loglevel = DEBUG [[6]] nodename = emontxshield [[[rx]]] - names = power1, power2, power3, power4, vrms - datacode = h - scales = 1,1,1,1,0.01 - units = W,W,W,W,V + names = power1, power2, power3, power4, vrms + datacode = h + scales = 1,1,1,1,0.01 + units = W,W,W,W,V [[7]] - nodename = emontx4 - [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + nodename = emontx4 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[8]] nodename = emontx3 [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[9]] - nodename = emontx2 - [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + nodename = emontx2 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[10]] nodename = emontx1 [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[11]] nodename = 3phase [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p - + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + [[12]] nodename = 3phase2 [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[13]] nodename = 3phase3 [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[14]] nodename = 3phase4 [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[15]] nodename = emontx3cm15 [[[rx]]] - names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse - datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L - scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 - units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p - whitening = 1 + names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse + datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L + scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 + units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p + whitening = 1 [[16]] nodename = emontx3cm16 [[[rx]]] - names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse - datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L - scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 - units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p - whitening = 1 + names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse + datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L + scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 + units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p + whitening = 1 [[19]] - nodename = emonth1 - [[[rx]]] - names = temperature, external temperature, humidity, battery - datacode = h - scales = 0.1,0.1,0.1,0.1 - units = C,C,%,V + nodename = emonth1 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V [[20]] - nodename = emonth2 - [[[rx]]] - names = temperature, external temperature, humidity, battery - datacode = h - scales = 0.1,0.1,0.1,0.1 - units = C,C,%,V + nodename = emonth2 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V [[21]] - nodename = emonth3 - [[[rx]]] - names = temperature, external temperature, humidity, battery - datacode = h - scales = 0.1,0.1,0.1,0.1 - units = C,C,%,V + nodename = emonth3 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V [[22]] - nodename = emonth4 - [[[rx]]] - names = temperature, external temperature, humidity, battery - datacode = h - scales = 0.1,0.1,0.1,0.1 - units = C,C,%,V + nodename = emonth4 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V [[23]] nodename = emonth5 [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p [[24]] nodename = emonth6 [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p [[25]] nodename = emonth7 [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p [[26]] nodename = emonth8 [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p From 963013b7510f6bcff93829306986370089258b50 Mon Sep 17 00:00:00 2001 From: borpin Date: Tue, 9 Jun 2020 21:25:25 +0100 Subject: [PATCH 47/78] Minor formatting and baud speed note --- configuration.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/configuration.md b/configuration.md index 82f92769..e2864c8e 100644 --- a/configuration.md +++ b/configuration.md @@ -139,18 +139,31 @@ The 'node only format' is used with the emoncms [Nodes Module](https://github.co The 'Node variable format' is the current default format from Emoncms V9. It's a more generic MQTT publishing format that can more easily be used by applications such as NodeRED and OpenHab. This format can also be used with the emoncms `phpmqtt_input.php` script in conjunction with the emoncms inputs module. See [User Guide > Technical MQTT](https://guide.openenergymonitor.org/technical/mqtt/). -#### **3. JSON format*** +#### **3. JSON format** -(default base topic is `emon`) +##### Defaults + +```python +'node_format_enable': 1, +'node_format_basetopic': 'emonhub/', +'nodevar_format_enable': 0, +'nodevar_format_basetopic': "nodes/", +'node_JSON_enable': 0, +'node_JSON_basetopic': "emon/" +``` + +Emoncms default base topic that it listens for is `emon/`. ```text - topic: basetopic/ - payload: {"key1":value1, "key2":value2, .... "time":, "rssi":} +topic: basetopic/ +payload: {"key1":value1, "key2":value2, .... "time":, "rssi":} ``` This forat exports the data as a single JSOn string with key:value pairs. The timestamp is automatically added and used for the input time to emoncms. The RSSI is added if available (RF in use). -**Default `[MQTT]` config:** +### Default `[MQTT]` config + +Note - the trailing `/` is required on the topic definition. ```text [[MQTT]] @@ -223,7 +236,7 @@ This time, the API key will be the API key from your account at emoncms.example. ### d.) [[Serial]] (EmonTX V3) -There are 2 different serial interfacers. The TX3e interfacer is for connecting an RPi directly to the serial output (UART) of the emonTX. +There are 2 different serial interfacers. The TX3e interfacer is for connecting an RPi directly to the serial output (UART) of the emonTX. COM port speed on newer devices is 115200. The default nodeID is `0` and the `nodeoffset` optional parameter will increase that nodeID by its value. From a66b87321d159188e6d2dd28684530f9c4607ce4 Mon Sep 17 00:00:00 2001 From: borpin Date: Thu, 11 Jun 2020 16:53:51 +0100 Subject: [PATCH 48/78] Initial Pulse Counter Interfacer --- .gitignore | 1 + .../EmonHubPulseCounterInterfacer.py | 96 +++++++++++++++++++ src/interfacers/__init__.py | 1 + 3 files changed, 98 insertions(+) create mode 100644 src/interfacers/EmonHubPulseCounterInterfacer.py diff --git a/.gitignore b/.gitignore index 9b370748..f2a222d0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ README.md *.swp *.idea *~ +.vscode/settings.json diff --git a/src/interfacers/EmonHubPulseCounterInterfacer.py b/src/interfacers/EmonHubPulseCounterInterfacer.py new file mode 100644 index 00000000..edabdc08 --- /dev/null +++ b/src/interfacers/EmonHubPulseCounterInterfacer.py @@ -0,0 +1,96 @@ +rom emonhub_interfacer import EmonHubInterfacer +from collections import defaultdict +import time +import atexit +import RPi.GPIO as GPIO + +import Cargo + +"""class EmonhubPulseCounterInterfacer + +Monitors GPIO pins for pulses + +""" + +class EmonHubPulseCounterInterfacer(EmonHubInterfacer): + + def __init__(self, name, pulse_pins=None, bouncetime=1): + """Initialize interfacer + + """ + + # Initialization + super().__init__(name) + + self.gpio_pin = int(pulse_pins) + + self._pulse_settings = { + 'pulse_pins': pulse_pins, + 'bouncetime' : bouncetime, + } + + self._settings.update(self._pulse_settings) + self.pulse_count = defaultdict(int) + + self.pulse_received = 0 + + self.init_gpio() + + def init_gpio(self): + """Register GPIO callbacks + + """ + + atexit.register(GPIO.cleanup) + GPIO.setmode(GPIO.BOARD) + self._log.debug('Pulse pin set to: %d', self.gpio_pin) + GPIO.setup(self.gpio_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + GPIO.add_event_detect(self.gpio_pin, GPIO.FALLING, callback=self.process_pulse, bouncetime=int(self._settings['bouncetime'])) + + def process_pulse(self, channel): + self.pulse_count[channel] += 1 + self._log.debug('Pulse Channel %d pulse: %d', channel, self.pulse_count[channel]) + self.pulse_received += 1 + + def read(self): + + if self.pulse_received == 0: + return False + + self.pulse_received = 0 + t = time.time() + f = '{t} {nodeid}'.format(t=t, nodeid=self._settings['nodeoffset']) + f += ' {}'.format(self.pulse_count[self.gpio_pin]) + + # Create a Payload object + c = Cargo.new_cargo(rawdata=f) + + f = f.split() + + if int(self._settings['nodeoffset']): + c.nodeid = int(self._settings['nodeoffset']) + c.realdata = f + else: + c.nodeid = int(f[0]) + c.realdata = f[1:] + + return c + + + def set(self, **kwargs): + super().set(**kwargs) + + for key, setting in self._pulse_settings.items(): + + if key not in kwargs: + self._log.error("ERROR 1 %s", key) + setting = self._pulse_settings[key] + else: + self._log.error("ERROR 2") + setting = kwargs[key] + + if key in self._settings and self._settings[key] == setting: + self._log.error("ERROR 3 %s", key) + continue + else: + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) diff --git a/src/interfacers/__init__.py b/src/interfacers/__init__.py index b0077565..17a4cda7 100644 --- a/src/interfacers/__init__.py +++ b/src/interfacers/__init__.py @@ -1,4 +1,5 @@ __all__ = [ + "EmonHubPulseCounterInterfacer", "EmonHubSocketInterfacer", "EmonHubSerialInterfacer", "EmonHubJeeInterfacer", From 28a49ea7357f14a26f05c8f7166b241c42ee0524 Mon Sep 17 00:00:00 2001 From: borpin Date: Thu, 11 Jun 2020 17:08:46 +0100 Subject: [PATCH 49/78] Update settings --- .../EmonHubPulseCounterInterfacer.py | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/interfacers/EmonHubPulseCounterInterfacer.py b/src/interfacers/EmonHubPulseCounterInterfacer.py index edabdc08..146ee51f 100644 --- a/src/interfacers/EmonHubPulseCounterInterfacer.py +++ b/src/interfacers/EmonHubPulseCounterInterfacer.py @@ -1,4 +1,4 @@ -rom emonhub_interfacer import EmonHubInterfacer +from emonhub_interfacer import EmonHubInterfacer from collections import defaultdict import time import atexit @@ -22,14 +22,17 @@ def __init__(self, name, pulse_pins=None, bouncetime=1): # Initialization super().__init__(name) - self.gpio_pin = int(pulse_pins) - - self._pulse_settings = { + self._settings.update( { 'pulse_pins': pulse_pins, 'bouncetime' : bouncetime, - } + }) + + self.gpio_pin = int(pulse_pins) + + self._pulse_settings = {} + +# self._settings.update(self._pulse_settings) - self._settings.update(self._pulse_settings) self.pulse_count = defaultdict(int) self.pulse_received = 0 @@ -58,22 +61,16 @@ def read(self): return False self.pulse_received = 0 - t = time.time() - f = '{t} {nodeid}'.format(t=t, nodeid=self._settings['nodeoffset']) - f += ' {}'.format(self.pulse_count[self.gpio_pin]) # Create a Payload object - c = Cargo.new_cargo(rawdata=f) - - f = f.split() + c = Cargo.new_cargo() + c.names = ["Pulse"] + c.realdata = [self.pulse_count[self.gpio_pin]] if int(self._settings['nodeoffset']): c.nodeid = int(self._settings['nodeoffset']) - c.realdata = f else: - c.nodeid = int(f[0]) - c.realdata = f[1:] - + c.nodeid = 0 return c @@ -83,14 +80,14 @@ def set(self, **kwargs): for key, setting in self._pulse_settings.items(): if key not in kwargs: - self._log.error("ERROR 1 %s", key) +# self._log.error("ERROR 1 %s", key) setting = self._pulse_settings[key] else: - self._log.error("ERROR 2") +# self._log.error("ERROR 2") setting = kwargs[key] if key in self._settings and self._settings[key] == setting: - self._log.error("ERROR 3 %s", key) +# self._log.error("ERROR 3 %s", key) continue else: self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) From 9f64b97bd21cef3c14bf6c40f98e34882148496a Mon Sep 17 00:00:00 2001 From: borpin Date: Thu, 11 Jun 2020 17:20:09 +0100 Subject: [PATCH 50/78] Make timestamp optional use timestamped setting --- src/interfacers/EmonHubMqttInterfacer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index da821c77..3e1e5d0e 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -23,6 +23,8 @@ nodevar_format_enable = 1 nodevar_format_basetopic = emon/ + # JSON format data that can have a timestamp + timestamped = True node_JSON_enable = 1 node_JSON_basetopic = emon/JSON/ From c91846361f50917df48e4a1732545e927e6651df Mon Sep 17 00:00:00 2001 From: borpin Date: Thu, 11 Jun 2020 17:34:37 +0100 Subject: [PATCH 51/78] Add cliet ID to MQTT connection --- src/interfacers/EmonHubMqttInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index 3e1e5d0e..8d23254a 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -74,7 +74,7 @@ def __init__(self, name, mqtt_user=" ", mqtt_passwd=" ", mqtt_host="127.0.0.1", self._connected = False - self._mqttc = mqtt.Client() + self._mqttc = mqtt.Client(client_id="emonhub-"+name) self._mqttc.on_connect = self.on_connect self._mqttc.on_disconnect = self.on_disconnect self._mqttc.on_message = self.on_message From b587610deb57f132248808b2c5eac66060b4958e Mon Sep 17 00:00:00 2001 From: borpin Date: Thu, 11 Jun 2020 18:00:47 +0100 Subject: [PATCH 52/78] Update header --- .../EmonHubPulseCounterInterfacer.py | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/interfacers/EmonHubPulseCounterInterfacer.py b/src/interfacers/EmonHubPulseCounterInterfacer.py index 146ee51f..bd2faf8b 100644 --- a/src/interfacers/EmonHubPulseCounterInterfacer.py +++ b/src/interfacers/EmonHubPulseCounterInterfacer.py @@ -8,8 +8,25 @@ """class EmonhubPulseCounterInterfacer +Authors @borpin & @bwduncan +Version: 1 +Date: 11 June 2020 + Monitors GPIO pins for pulses +Example emonhub configuration +[[pulse2]] + Type = EmonHubPulseCounterInterfacer + [[[init_settings]]] + # pin number must be specified. Create a second + # interfacer for more than one pulse sensor + pulse_pin = 15 + # bouncetime default to 1. + # bouncetime = 2 + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + + nodeoffset = 3 """ class EmonHubPulseCounterInterfacer(EmonHubInterfacer): @@ -31,8 +48,6 @@ def __init__(self, name, pulse_pins=None, bouncetime=1): self._pulse_settings = {} -# self._settings.update(self._pulse_settings) - self.pulse_count = defaultdict(int) self.pulse_received = 0 @@ -46,13 +61,13 @@ def init_gpio(self): atexit.register(GPIO.cleanup) GPIO.setmode(GPIO.BOARD) - self._log.debug('Pulse pin set to: %d', self.gpio_pin) + self._log.info('%s : Pulse pin set to: %d', self.name, self.gpio_pin) GPIO.setup(self.gpio_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(self.gpio_pin, GPIO.FALLING, callback=self.process_pulse, bouncetime=int(self._settings['bouncetime'])) def process_pulse(self, channel): self.pulse_count[channel] += 1 - self._log.debug('Pulse Channel %d pulse: %d', channel, self.pulse_count[channel]) + self._log.debug('%s : Pulse Channel %d pulse: %d', self.name, channel, self.pulse_count[channel]) self.pulse_received += 1 def read(self): @@ -63,8 +78,8 @@ def read(self): self.pulse_received = 0 # Create a Payload object - c = Cargo.new_cargo() - c.names = ["Pulse"] + c = Cargo.new_cargo(nodename=self.name) + c.names = ["PulseCount"] c.realdata = [self.pulse_count[self.gpio_pin]] if int(self._settings['nodeoffset']): @@ -80,14 +95,11 @@ def set(self, **kwargs): for key, setting in self._pulse_settings.items(): if key not in kwargs: -# self._log.error("ERROR 1 %s", key) setting = self._pulse_settings[key] else: -# self._log.error("ERROR 2") setting = kwargs[key] if key in self._settings and self._settings[key] == setting: -# self._log.error("ERROR 3 %s", key) continue else: self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) From f2c7da1f97b851326b9871f59869360f13f149f0 Mon Sep 17 00:00:00 2001 From: borpin Date: Thu, 11 Jun 2020 20:21:02 +0100 Subject: [PATCH 53/78] Updates from comments --- src/interfacers/EmonHubPulseCounterInterfacer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/interfacers/EmonHubPulseCounterInterfacer.py b/src/interfacers/EmonHubPulseCounterInterfacer.py index bd2faf8b..578d7923 100644 --- a/src/interfacers/EmonHubPulseCounterInterfacer.py +++ b/src/interfacers/EmonHubPulseCounterInterfacer.py @@ -31,7 +31,7 @@ class EmonHubPulseCounterInterfacer(EmonHubInterfacer): - def __init__(self, name, pulse_pins=None, bouncetime=1): + def __init__(self, name, pulse_pin=None, bouncetime=1): """Initialize interfacer """ @@ -40,17 +40,17 @@ def __init__(self, name, pulse_pins=None, bouncetime=1): super().__init__(name) self._settings.update( { - 'pulse_pins': pulse_pins, + 'pulse_pin': pulse_pin, 'bouncetime' : bouncetime, }) - self.gpio_pin = int(pulse_pins) + self.gpio_pin = int(pulse_pin) self._pulse_settings = {} self.pulse_count = defaultdict(int) - self.pulse_received = 0 + self.pulse_received = False self.init_gpio() @@ -68,14 +68,14 @@ def init_gpio(self): def process_pulse(self, channel): self.pulse_count[channel] += 1 self._log.debug('%s : Pulse Channel %d pulse: %d', self.name, channel, self.pulse_count[channel]) - self.pulse_received += 1 + self.pulse_received = True def read(self): - if self.pulse_received == 0: + if not self.pulse_received: return False - self.pulse_received = 0 + self.pulse_received = False # Create a Payload object c = Cargo.new_cargo(nodename=self.name) From 4d19bfaffafa33013191e30c60df7a8c616412c5 Mon Sep 17 00:00:00 2001 From: borpin Date: Thu, 11 Jun 2020 20:27:24 +0100 Subject: [PATCH 54/78] Use setting object for pin --- src/interfacers/EmonHubPulseCounterInterfacer.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/interfacers/EmonHubPulseCounterInterfacer.py b/src/interfacers/EmonHubPulseCounterInterfacer.py index 578d7923..83cabb5c 100644 --- a/src/interfacers/EmonHubPulseCounterInterfacer.py +++ b/src/interfacers/EmonHubPulseCounterInterfacer.py @@ -40,12 +40,10 @@ def __init__(self, name, pulse_pin=None, bouncetime=1): super().__init__(name) self._settings.update( { - 'pulse_pin': pulse_pin, + 'pulse_pin': int(pulse_pin), 'bouncetime' : bouncetime, }) - self.gpio_pin = int(pulse_pin) - self._pulse_settings = {} self.pulse_count = defaultdict(int) @@ -61,9 +59,9 @@ def init_gpio(self): atexit.register(GPIO.cleanup) GPIO.setmode(GPIO.BOARD) - self._log.info('%s : Pulse pin set to: %d', self.name, self.gpio_pin) - GPIO.setup(self.gpio_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) - GPIO.add_event_detect(self.gpio_pin, GPIO.FALLING, callback=self.process_pulse, bouncetime=int(self._settings['bouncetime'])) + self._log.info('%s : Pulse pin set to: %d', self.name, self._settings['pulse_pin']) + GPIO.setup(self._settings['pulse_pin'], GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + GPIO.add_event_detect(self._settings['pulse_pin'], GPIO.FALLING, callback=self.process_pulse, bouncetime=int(self._settings['bouncetime'])) def process_pulse(self, channel): self.pulse_count[channel] += 1 @@ -80,7 +78,7 @@ def read(self): # Create a Payload object c = Cargo.new_cargo(nodename=self.name) c.names = ["PulseCount"] - c.realdata = [self.pulse_count[self.gpio_pin]] + c.realdata = [self.pulse_count[self._settings['pulse_pin']]] if int(self._settings['nodeoffset']): c.nodeid = int(self._settings['nodeoffset']) From 5fc4d7b16bd37923dcbaf3a87f892fef139c3e2d Mon Sep 17 00:00:00 2001 From: borpin Date: Thu, 11 Jun 2020 20:29:52 +0100 Subject: [PATCH 55/78] Update header information --- src/interfacers/EmonHubPulseCounterInterfacer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/interfacers/EmonHubPulseCounterInterfacer.py b/src/interfacers/EmonHubPulseCounterInterfacer.py index 83cabb5c..419e4262 100644 --- a/src/interfacers/EmonHubPulseCounterInterfacer.py +++ b/src/interfacers/EmonHubPulseCounterInterfacer.py @@ -26,6 +26,8 @@ [[[runtimesettings]]] pubchannels = ToEmonCMS, + # Default NodeID is 0. Use nodeoffset to set NodeID + # No decoder required as key:value pair returned nodeoffset = 3 """ From 2df88f061a837045868f1ea5b95f7aeda1746486 Mon Sep 17 00:00:00 2001 From: borpin Date: Thu, 11 Jun 2020 22:47:56 +0100 Subject: [PATCH 56/78] Removed clientid --- src/interfacers/EmonHubMqttInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubMqttInterfacer.py b/src/interfacers/EmonHubMqttInterfacer.py index 8d23254a..3e1e5d0e 100644 --- a/src/interfacers/EmonHubMqttInterfacer.py +++ b/src/interfacers/EmonHubMqttInterfacer.py @@ -74,7 +74,7 @@ def __init__(self, name, mqtt_user=" ", mqtt_passwd=" ", mqtt_host="127.0.0.1", self._connected = False - self._mqttc = mqtt.Client(client_id="emonhub-"+name) + self._mqttc = mqtt.Client() self._mqttc.on_connect = self.on_connect self._mqttc.on_disconnect = self.on_disconnect self._mqttc.on_message = self.on_message From b398b06f5dd910e38e1d460c6fc8242d653c8c10 Mon Sep 17 00:00:00 2001 From: borpin Date: Fri, 12 Jun 2020 12:39:30 +0100 Subject: [PATCH 57/78] Conditional import of GPIO library --- src/interfacers/EmonHubPulseCounterInterfacer.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/interfacers/EmonHubPulseCounterInterfacer.py b/src/interfacers/EmonHubPulseCounterInterfacer.py index 419e4262..52ef9147 100644 --- a/src/interfacers/EmonHubPulseCounterInterfacer.py +++ b/src/interfacers/EmonHubPulseCounterInterfacer.py @@ -2,10 +2,15 @@ from collections import defaultdict import time import atexit -import RPi.GPIO as GPIO import Cargo +try: + import RPi.GPIO as GPIO + RPi_found = True +except: + RPi_found = False + """class EmonhubPulseCounterInterfacer Authors @borpin & @bwduncan @@ -52,7 +57,10 @@ def __init__(self, name, pulse_pin=None, bouncetime=1): self.pulse_received = False - self.init_gpio() + if RPi_found: + self.init_gpio() + else: + self._log.error("Pulse counter not initialised. Please install the RPi GPIO Python3 module") def init_gpio(self): """Register GPIO callbacks From 91c45ae387acb74ae1ca59edc8ca2f3d379275a5 Mon Sep 17 00:00:00 2001 From: borpin Date: Fri, 12 Jun 2020 12:42:33 +0100 Subject: [PATCH 58/78] Add install of GPIO library and add user --- install.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/install.sh b/install.sh index 866f710a..ec5bd00e 100755 --- a/install.sh +++ b/install.sh @@ -20,6 +20,11 @@ sudo apt-get install -y python3-serial python3-configobj python3-pip sudo pip3 install paho-mqtt requests if [ "$emonSD_pi_env" = "1" ]; then + # Only install the GPIO library if on a Pi. Used by Pulse interfacer + sudo apt install python3-gpiozero + # Need to add the emonhub user to the GPIO group + sudo adduser emonhub gpio + # RaspberryPi Serial configuration # disable Pi3 Bluetooth and restore UART0/ttyAMA0 over GPIOs 14 & 15; # Review should this be: dtoverlay=pi3-miniuart-bt? From 709565fa51c1fb5702450a52433fdb605f0d6704 Mon Sep 17 00:00:00 2001 From: borpin Date: Fri, 12 Jun 2020 12:48:47 +0100 Subject: [PATCH 59/78] Change install of GPIO to pip3 --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index ec5bd00e..a88967c5 100755 --- a/install.sh +++ b/install.sh @@ -21,7 +21,7 @@ sudo pip3 install paho-mqtt requests if [ "$emonSD_pi_env" = "1" ]; then # Only install the GPIO library if on a Pi. Used by Pulse interfacer - sudo apt install python3-gpiozero + pip3 install RPi.GPIO # Need to add the emonhub user to the GPIO group sudo adduser emonhub gpio From edb2c3a44550430e6b2d55d24fa724edf4aa40b7 Mon Sep 17 00:00:00 2001 From: borpin Date: Fri, 12 Jun 2020 16:53:33 +0100 Subject: [PATCH 60/78] remove indentation improvements --- conf/emonpi.default.emonhub.conf | 182 +++++++++++++++---------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/conf/emonpi.default.emonhub.conf b/conf/emonpi.default.emonhub.conf index 91733a66..0e0b6d76 100644 --- a/conf/emonpi.default.emonhub.conf +++ b/conf/emonpi.default.emonhub.conf @@ -93,153 +93,153 @@ loglevel = DEBUG [[6]] nodename = emontxshield [[[rx]]] - names = power1, power2, power3, power4, vrms - datacode = h - scales = 1,1,1,1,0.01 - units = W,W,W,W,V + names = power1, power2, power3, power4, vrms + datacode = h + scales = 1,1,1,1,0.01 + units = W,W,W,W,V [[7]] - nodename = emontx4 - [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + nodename = emontx4 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[8]] nodename = emontx3 [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[9]] - nodename = emontx2 - [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + nodename = emontx2 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[10]] nodename = emontx1 [[[rx]]] - names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[11]] nodename = 3phase [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p - + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + [[12]] nodename = 3phase2 [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[13]] nodename = 3phase3 [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[14]] nodename = 3phase4 [[[rx]]] - names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse - datacodes = h,h,h,h,h,h,h,h,h,h,h,L - scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 - units = W,W,W,W,V,C,C,C,C,C,C,p + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p [[15]] nodename = emontx3cm15 [[[rx]]] - names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse - datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L - scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 - units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p - whitening = 1 + names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse + datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L + scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 + units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p + whitening = 1 [[16]] nodename = emontx3cm16 [[[rx]]] - names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse - datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L - scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 - units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p - whitening = 1 + names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse + datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L + scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 + units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p + whitening = 1 [[19]] - nodename = emonth1 - [[[rx]]] - names = temperature, external temperature, humidity, battery - datacode = h - scales = 0.1,0.1,0.1,0.1 - units = C,C,%,V + nodename = emonth1 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V [[20]] - nodename = emonth2 - [[[rx]]] - names = temperature, external temperature, humidity, battery - datacode = h - scales = 0.1,0.1,0.1,0.1 - units = C,C,%,V + nodename = emonth2 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V [[21]] - nodename = emonth3 - [[[rx]]] - names = temperature, external temperature, humidity, battery - datacode = h - scales = 0.1,0.1,0.1,0.1 - units = C,C,%,V + nodename = emonth3 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V [[22]] - nodename = emonth4 - [[[rx]]] - names = temperature, external temperature, humidity, battery - datacode = h - scales = 0.1,0.1,0.1,0.1 - units = C,C,%,V + nodename = emonth4 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V [[23]] nodename = emonth5 [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p [[24]] nodename = emonth6 [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p [[25]] nodename = emonth7 [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p [[26]] nodename = emonth8 [[[rx]]] - names = temperature, external temperature, humidity, battery, pulsecount - datacodes = h,h,h,h,L - scales = 0.1,0.1,0.1,0.1,1 - units = C,C,%,V,p + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p \ No newline at end of file From 9bb20e481f67db4a2f8a25bd55cc61e2227e18c7 Mon Sep 17 00:00:00 2001 From: borpin Date: Fri, 12 Jun 2020 21:53:12 +0100 Subject: [PATCH 61/78] Add an example emonhub.conf --- conf/example.emonhub.conf | 268 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 conf/example.emonhub.conf diff --git a/conf/example.emonhub.conf b/conf/example.emonhub.conf new file mode 100644 index 00000000..570dce26 --- /dev/null +++ b/conf/example.emonhub.conf @@ -0,0 +1,268 @@ +####################################################################### +####################### emonhub.conf ######################### +####################################################################### +### emonHub configuration file, for info see documentation: +### https://github.com/openenergymonitor/emonhub/blob/emon-pi/configuration.md +####################################################################### +####################### emonHub settings ####################### +####################################################################### + +[hub] +### loglevel must be one of DEBUG, INFO, WARNING, ERROR, and CRITICAL +loglevel = DEBUG +### Uncomment this to also send to syslog +# use_syslog = yes +####################################################################### +####################### Interfacers ####################### +####################################################################### + +[interfacers] +### This interfacer manages the RFM12Pi/RFM69Pi/emonPi module +[[RFM2Pi]] + Type = EmonHubJeeInterfacer + [[[init_settings]]] + com_port = /dev/ttyAMA0 + com_baud = 38400 # 9600 for old RFM12Pi + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + subchannels = ToRFM12, + + group = 210 + frequency = 433 + baseid = 5 # emonPi / emonBase nodeID + calibration = 230V # (UK/EU: 230V, US: 110V) + quiet = true # Disable quite mode (default enabled) to enable RF packet debugging, show packets which fail crc + # interval = 300 # Interval to transmit time to emonGLCD (seconds) + +[[MQTT]] + Type = EmonHubMqttInterfacer + [[[init_settings]]] + mqtt_host = 127.0.0.1 + mqtt_port = 1883 + mqtt_user = emonpi + mqtt_passwd = emonpimqtt2016 + + [[[runtimesettings]]] + subchannels = ToEmonCMS, + timestamped = True # Otional - Include timestamp with data + + # emonhub/rx/10/values format + # Use with emoncms Nodes module + node_format_enable = 0 + node_format_basetopic = emonhub/ + + # emon/emontx/power1 format - use with Emoncms MQTT input + # http://github.com/emoncms/emoncms/blob/master/docs/RaspberryPi/MQTT.md + nodevar_format_enable = 0 + nodevar_format_basetopic = emon/ + + # Single JSON payload published - use with Emoncms MQTT + node_JSON_enable = 1 + node_JSON_basetopic = emon/ + +[[emoncmsorg]] + Type = EmonHubEmoncmsHTTPInterfacer + [[[init_settings]]] + [[[runtimesettings]]] + subchannels = ToEmonCMS, + url = https://emoncms.org # Use Loacl IP if on LAN + apikey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + senddata = 1 # Enable sending data to Emoncms.org + sendstatus = 1 # Enable sending WAN IP to Emoncms.org MyIP > https://emoncms.org/myip/list + sendinterval= 30 # Bulk send interval to Emoncms.org in seconds + +# [[SerialTx]] +# Type = EmonHubTx3eInterfacer +# [[[init_settings]]] +# com_port= /dev/ttyAMA0 +# com_baud = 115200 +# [[[runtimesettings]]] +# pubchannels = ToEmonCMS, +# nodeoffset = 1 # Default NodeID is 0. Nodeoffset will be used as the NodeID +# nodename = Serial_PiZ # Optional - set NodeName + +# [[Socket]] +# Type = EmonHubSocketInterfacer +# [[[init_settings]]] +# port_nb = 50012 +# [[[runtimesettings]]] +# pubchannels = ToEmonCMS, +# timestamped = True # Otional - Include timestamp with data + +# [[pulse]] +# Type = EmonHubPulseCounterInterfacer +# [[[init_settings]]] +# pulse_pin = 15 +# [[[runtimesettings]]] +# pubchannels = ToEmonCMS, +# nodeoffset = 3 + +####################################################################### +####################### Nodes ####################### +####################################################################### + +[nodes] + +## See config user guide: https://github.com/openenergymonitor/emonhub/blob/emon-pi/conf/emonhub.conf + +[[5]] + nodename = emonpi + [[[rx]]] + names = power1,power2,power1pluspower2,vrms,t1,t2,t3,t4,t5,t6,pulsecount + datacodes = h, h, h, h, h, h, h, h, h, h, L + scales = 1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 + units = W,W,W,V,C,C,C,C,C,C,p + +[[6]] + nodename = emontxshield + [[[rx]]] + names = power1, power2, power3, power4, vrms + datacode = h + scales = 1,1,1,1,0.01 + units = W,W,W,W,V + +[[7]] + nodename = emontx4 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + +[[8]] + nodename = emontx3 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + +[[9]] + nodename = emontx2 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.1,0.1, 0.1,0.1,0.1,0.1,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + +[[10]] + nodename = emontx1 + [[[rx]]] + names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + +[[11]] + nodename = 3phase + [[[rx]]] + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + +[[12]] + nodename = 3phase2 + [[[rx]]] + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + +[[13]] + nodename = 3phase3 + [[[rx]]] + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + +[[14]] + nodename = 3phase4 + [[[rx]]] + names = powerL1, powerL2, powerL3, power4, Vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse + datacodes = h,h,h,h,h,h,h,h,h,h,h,L + scales = 1,1,1,1,0.01,0.01,0.01,0.01,0.01,0.01,0.01,1 + units = W,W,W,W,V,C,C,C,C,C,C,p + +[[15]] + nodename = emontx3cm15 + [[[rx]]] + names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse + datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L + scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 + units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p + whitening = 1 + +[[16]] + nodename = emontx3cm16 + [[[rx]]] + names = MSG, Vrms, P1, P2, P3, P4, E1, E2, E3, E4, T1, T2, T3, pulse + datacodes = L,h,h,h,h,h,L,L,L,L,h,h,h,L + scales = 1,0.01,1,1,1,1,1,1,1,1,0.01,0.01,0.01,1 + units = n,V,W,W,W,W,Wh,Wh,Wh,Wh,C,C,C,p + whitening = 1 + +[[19]] + nodename = emonth1 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V + +[[20]] + nodename = emonth2 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V + +[[21]] + nodename = emonth3 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V + +[[22]] + nodename = emonth4 + [[[rx]]] + names = temperature, external temperature, humidity, battery + datacode = h + scales = 0.1,0.1,0.1,0.1 + units = C,C,%,V + +[[23]] + nodename = emonth5 + [[[rx]]] + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p + +[[24]] + nodename = emonth6 + [[[rx]]] + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p + +[[25]] + nodename = emonth7 + [[[rx]]] + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p + +[[26]] + nodename = emonth8 + [[[rx]]] + names = temperature, external temperature, humidity, battery, pulsecount + datacodes = h,h,h,h,L + scales = 0.1,0.1,0.1,0.1,1 + units = C,C,%,V,p From 89dbc2857eff3d7000fe836898c1408b45c73c13 Mon Sep 17 00:00:00 2001 From: borpin Date: Tue, 30 Jun 2020 21:32:26 +0100 Subject: [PATCH 62/78] Modify processing to rate limit reporting --- .../EmonHubPulseCounterInterfacer.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/interfacers/EmonHubPulseCounterInterfacer.py b/src/interfacers/EmonHubPulseCounterInterfacer.py index 52ef9147..b5da6752 100644 --- a/src/interfacers/EmonHubPulseCounterInterfacer.py +++ b/src/interfacers/EmonHubPulseCounterInterfacer.py @@ -23,7 +23,7 @@ [[pulse2]] Type = EmonHubPulseCounterInterfacer [[[init_settings]]] - # pin number must be specified. Create a second + # pin number must be specified. Create a second # interfacer for more than one pulse sensor pulse_pin = 15 # bouncetime default to 1. @@ -53,9 +53,11 @@ def __init__(self, name, pulse_pin=None, bouncetime=1): self._pulse_settings = {} - self.pulse_count = defaultdict(int) + self.pulse_count = 0 - self.pulse_received = False + self.last_pulse = 0 + + self.last_time = (time.time()//10)*10 if RPi_found: self.init_gpio() @@ -74,21 +76,25 @@ def init_gpio(self): GPIO.add_event_detect(self._settings['pulse_pin'], GPIO.FALLING, callback=self.process_pulse, bouncetime=int(self._settings['bouncetime'])) def process_pulse(self, channel): - self.pulse_count[channel] += 1 - self._log.debug('%s : Pulse Channel %d pulse: %d', self.name, channel, self.pulse_count[channel]) - self.pulse_received = True + self.pulse_count += 1 + self._log.debug('%s : pulse received - count: %d', self.name, self.pulse_count) def read(self): - if not self.pulse_received: + time_now = time.time() + + if self.last_pulse == self.pulse_count: + return False + elif self.last_time + 2 > time_now: return False - self.pulse_received = False + self._log.debug('Data to Post: last_time: %d time_now: %d', self.last_time, time_now) + self.last_pulse = self.pulse_count + self.last_time = int(time_now) - # Create a Payload object - c = Cargo.new_cargo(nodename=self.name) - c.names = ["PulseCount"] - c.realdata = [self.pulse_count[self._settings['pulse_pin']]] + c = Cargo.new_cargo(nodename=self.name, timestamp=time_now) + c.names = ["Pulse"] + c.realdata = [self.last_pulse] if int(self._settings['nodeoffset']): c.nodeid = int(self._settings['nodeoffset']) From 008d547b4377bc4c3d8e3287da8f4278c92f9e32 Mon Sep 17 00:00:00 2001 From: borpin Date: Tue, 30 Jun 2020 21:43:58 +0100 Subject: [PATCH 63/78] Add rate_limit as parameter --- .../EmonHubPulseCounterInterfacer.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/interfacers/EmonHubPulseCounterInterfacer.py b/src/interfacers/EmonHubPulseCounterInterfacer.py index b5da6752..3f3430c6 100644 --- a/src/interfacers/EmonHubPulseCounterInterfacer.py +++ b/src/interfacers/EmonHubPulseCounterInterfacer.py @@ -28,6 +28,12 @@ pulse_pin = 15 # bouncetime default to 1. # bouncetime = 2 + # Rate_limit is the rate at which the interfacer will pass data + # to emonhub for sending on. Too short and pulses will be missed. + # rate_limit is minimum number of seconds between data output. + # pulses are accumulated in this period. + # rate_limit default to 2. + # rate_limit = 2 [[[runtimesettings]]] pubchannels = ToEmonCMS, @@ -38,7 +44,7 @@ class EmonHubPulseCounterInterfacer(EmonHubInterfacer): - def __init__(self, name, pulse_pin=None, bouncetime=1): + def __init__(self, name, pulse_pin=None, bouncetime=1, rate_limit=2): """Initialize interfacer """ @@ -47,8 +53,9 @@ def __init__(self, name, pulse_pin=None, bouncetime=1): super().__init__(name) self._settings.update( { - 'pulse_pin': int(pulse_pin), - 'bouncetime' : bouncetime, + 'pulse_pin' : int(pulse_pin), + 'bouncetime' : int(bouncetime), + 'rate_limit' : int(rate_limit) }) self._pulse_settings = {} @@ -73,7 +80,7 @@ def init_gpio(self): GPIO.setmode(GPIO.BOARD) self._log.info('%s : Pulse pin set to: %d', self.name, self._settings['pulse_pin']) GPIO.setup(self._settings['pulse_pin'], GPIO.IN, pull_up_down=GPIO.PUD_DOWN) - GPIO.add_event_detect(self._settings['pulse_pin'], GPIO.FALLING, callback=self.process_pulse, bouncetime=int(self._settings['bouncetime'])) + GPIO.add_event_detect(self._settings['pulse_pin'], GPIO.FALLING, callback=self.process_pulse, bouncetime=self._settings['bouncetime']) def process_pulse(self, channel): self.pulse_count += 1 @@ -85,7 +92,7 @@ def read(self): if self.last_pulse == self.pulse_count: return False - elif self.last_time + 2 > time_now: + elif self.last_time + self._settings['rate_limit'] > time_now: return False self._log.debug('Data to Post: last_time: %d time_now: %d', self.last_time, time_now) From e1df15c066be58893aae6ff473016ec5b89f482e Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Tue, 14 Jul 2020 00:35:36 +0100 Subject: [PATCH 64/78] Properly rewrite VEDirect interfacer for Python3. The main problem was trying to decode the checksum byte (0-255) as utf-8 (0-127 plus continuation bytes), so we have to store everything as bytes until we are ready to decode it. I also took the opportunity to rewrite a few things in a simpler / more pythonic style. This might raise different exceptions when things go wrong, but it is shorter and it should be simpler to understand. I tried to add some helpful comments! --- src/interfacers/EmonHubVEDirectInterfacer.py | 148 ++++++++----------- 1 file changed, 58 insertions(+), 90 deletions(-) diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index e989bf3d..4f50a757 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -3,13 +3,13 @@ import Cargo from emonhub_interfacer import EmonHubInterfacer -"""class EmonhubSerialInterfacer +class EmonHubVEDirectInterfacer(EmonHubInterfacer): + """class EmonhubSerialInterfacer -Monitors the serial port for data + Monitors the serial port for data -""" + """ -class EmonHubVEDirectInterfacer(EmonHubInterfacer): WAIT_HEADER, IN_KEY, IN_VALUE, IN_CHECKSUM = range(4) @@ -26,31 +26,28 @@ def __init__(self, name, com_port='', com_baud=9600, toextract='', poll_interval # Open serial port self._ser = self._open_serial_port(com_port, com_baud) - # Initialize RX buffer - self._rx_buf = '' - - # VE Direct requirements - self.header1 = '\r' - self.header2 = '\n' - self.delimiter = '\t' - self.key = '' - self.value = '' + # VE Direct state machine requirements + self.header1 = b'\r' + self.header2 = b'\n' + self.delimiter = b'\t' + self.key = bytearray() + self.value = bytearray() self.bytes_sum = 0 self.state = self.WAIT_HEADER self.dict = {} - self.poll_interval = int(poll_interval) - self.last_read = time.time() # Parser requirements self._extract = toextract - #print "init system with to extract %s"%self._extract + self.poll_interval = float(poll_interval) + + # Polling timer + self.last_read = 0.0 def input(self, byte): """ Parse serial byte code from VE.Direct """ - # FIXME This whole setup is just to parse 'key\tvalue\r\n' lines with 'Checksum' keys over serial self.bytes_sum += ord(byte) if self.state == self.WAIT_HEADER: if byte == self.header1: @@ -59,7 +56,7 @@ def input(self, byte): self.state = self.IN_KEY elif self.state == self.IN_KEY: if byte == self.delimiter: - if self.key == 'Checksum': + if self.key.decode() == 'Checksum': self.state = self.IN_CHECKSUM else: self.state = self.IN_VALUE @@ -68,22 +65,24 @@ def input(self, byte): elif self.state == self.IN_VALUE: if byte == self.header1: self.state = self.WAIT_HEADER - self.dict[self.key] = self.value - self.key = '' - self.value = '' + self.dict[self.key.decode()] = self.value.decode() + self.key = bytearray() + self.value = bytearray() else: self.value += byte elif self.state == self.IN_CHECKSUM: - self.key = '' - self.value = '' self.state = self.WAIT_HEADER - if self.bytes_sum % 256 == 0: + self.key = bytearray() + self.value = bytearray() + try: + if self.bytes_sum % 256 == 0: + return self.dict + self._log.error("Invalid checksum, discarding data") + finally: + self.dict = {} self.bytes_sum = 0 - return self.dict - # FIXME if the checksum is wrong should we not throw away the dict? - self.bytes_sum = 0 else: - raise AssertionError() + raise RuntimeError("Impossible state") def close(self): """Close serial port""" @@ -97,97 +96,66 @@ def _open_serial_port(self, com_port, com_baud): """Open serial port com_port (string): path to COM port + com_baud (int): baud rate """ - #if not int(com_baud) in [75, 110, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]: - # self._log.debug("Invalid 'com_baud': " + str(com_baud) + " | Default of 9600 used") - # com_baud = 9600 - try: - s = serial.Serial(com_port, com_baud, timeout=10) - self._log.debug("Opening serial port: " + str(com_port) + " @ "+ str(com_baud) + " bits/s") - except serial.SerialException as e: - self._log.error(e) - s = False - # raise EmonHubInterfacerInitError('Could not open COM port %s' % com_port) - return s + self._log.debug("Opening serial port: %s @ %d bits/s", com_port, com_baud) + return serial.Serial(com_port, com_baud, timeout=10) + except serial.SerialException: + self._log.exception() def parse_package(self, data): """ Convert package from vedirect dictionary format to emonhub expected format """ - clean_data = [str(self._settings['nodeoffset'])] + clean_data = [] for key in self._extract: - if key in data: - # Emonhub doesn't like strings so we convert them to ints - tempval = 0 - try: - tempval = float(data[key]) - except Exception as e: - tempval = data[key] - if not isinstance(tempval, float): - if data[key] == "OFF": - data[key] = 0 - else: - data[key] = 1 - - clean_data.append(str(data[key])) + # Emonhub doesn't like strings so we convert them to floats + try: + clean_data.append(float(data[key])) + except KeyError: + self._log.warning("Requested key %s missing from received data", key) + continue + except ValueError: + # VEDirect values which aren't numerical are either "OFF" or... something else. + clean_data.append(0.0 if data[key] == 'OFF' else 1.0) return clean_data def _read_serial(self): - self._log.debug(" Starting Serial read") + self._log.debug("Starting Serial read from %s", self._ser) try: - while self._rx_buf == '': - byte = self._ser.read(1).decode() - packet = self.input(byte) + while True: + # Read one byte at a time from the serial port and pass it into + # the input FSM until we have a complete packet, then return it + packet = self.input(self._ser.read()) if packet is not None: - self._rx_buf = packet - - except Exception as e: - self._log.error(e) - self._rx_buf = "" + return packet + except Exception: # FIXME Too general Exception. Maybe SerialException? + self._log.exception() def read(self): """Read data from serial port and process if complete line received. - - Return data as a list: [NodeID, val1, val2] - """ - if not self._ser: - return False - - # Read serial RX now = time.time() if now - self.last_read <= self.poll_interval: - #self._log.debug(" Waiting for %s seconds " % (str(now - self.last_read))) # Wait to read based on poll_interval return - # Read from serial - self._read_serial() - # Update last read time + rx_buf = self._read_serial() self.last_read = now - # If line incomplete, exit - if self._rx_buf == '': + # If _read_serial raised an exception, exit + if rx_buf is None: return #Sample data looks like {'FW': '0307', 'SOC': '1000', 'Relay': 'OFF', 'PID': '0x203', 'H10': '6', 'BMV': '700', 'TTG': '-1', 'H12': '0', 'H18': '0', 'I': '0', 'H11': '0', 'Alarm': 'OFF', 'CE': '0', 'H17': '9', 'P': '0', 'AR': '0', 'V': '26719', 'H8': '29011', 'H9': '0', 'H2': '0', 'H3': '0', 'H1': '-1633', 'H6': '-5775', 'H7': '17453', 'H4': '0', 'H5': '0'} - # Create a Payload object - c = Cargo.new_cargo(rawdata=self._rx_buf) - f = self.parse_package(self._rx_buf) - - # Reset buffer - self._rx_buf = '' - - if f: - if int(self._settings['nodeoffset']): - c.nodeid = int(self._settings['nodeoffset']) - c.realdata = f[1:] - else: - self._log.error("nodeoffset needed in emonhub configuration, make sure it exists and is integer") + if 'nodeoffset' not in self._settings: + self._log.error("nodeoffset needed in emonhub configuration, make sure it exists and is integer") - return c + return Cargo.new_cargo(rawdata=rx_buf, + realdata=self.parse_package(rx_buf), + nodeid=int(self._settings['nodeoffset'])) From 7f8a7a85bee96277afed161ce0d42a4cb7aa29e8 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Tue, 14 Jul 2020 21:21:30 +0100 Subject: [PATCH 65/78] Fix logging in VEDirect. --- src/interfacers/EmonHubVEDirectInterfacer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index 4f50a757..3fd8038c 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -101,10 +101,10 @@ def _open_serial_port(self, com_port, com_baud): """ try: - self._log.debug("Opening serial port: %s @ %d bits/s", com_port, com_baud) + self._log.debug("Opening serial port: %s @ %s bits/s", com_port, com_baud) return serial.Serial(com_port, com_baud, timeout=10) except serial.SerialException: - self._log.exception() + self._log.exception("Open error") def parse_package(self, data): """ @@ -134,7 +134,7 @@ def _read_serial(self): if packet is not None: return packet except Exception: # FIXME Too general Exception. Maybe SerialException? - self._log.exception() + self._log.exception("Read error") def read(self): """Read data from serial port and process if complete line received. From fe443b6031e827d9cf314cb31216b1da35f34867 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Sat, 18 Jul 2020 00:49:24 +0100 Subject: [PATCH 66/78] Bail out if data fails checksum. Exit the read_serial loop if the data are bad. This will return to the interfacer loop and try again in "pool_interval" time, instead of waiting forever for valid data. --- src/interfacers/EmonHubVEDirectInterfacer.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index 3fd8038c..280e3f92 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -47,6 +47,9 @@ def __init__(self, name, com_port='', com_baud=9600, toextract='', poll_interval def input(self, byte): """ Parse serial byte code from VE.Direct + + Return None if more data is needed, return an empty dict if the + checksum was wrong, return a dict of valid data otherwise. """ self.bytes_sum += ord(byte) if self.state == self.WAIT_HEADER: @@ -78,6 +81,7 @@ def input(self, byte): if self.bytes_sum % 256 == 0: return self.dict self._log.error("Invalid checksum, discarding data") + return {} finally: self.dict = {} self.bytes_sum = 0 @@ -147,8 +151,8 @@ def read(self): rx_buf = self._read_serial() self.last_read = now - # If _read_serial raised an exception, exit - if rx_buf is None: + # If _read_serial raised an exception or returned empty dict, exit + if not rx_buf: return #Sample data looks like {'FW': '0307', 'SOC': '1000', 'Relay': 'OFF', 'PID': '0x203', 'H10': '6', 'BMV': '700', 'TTG': '-1', 'H12': '0', 'H18': '0', 'I': '0', 'H11': '0', 'Alarm': 'OFF', 'CE': '0', 'H17': '9', 'P': '0', 'AR': '0', 'V': '26719', 'H8': '29011', 'H9': '0', 'H2': '0', 'H3': '0', 'H1': '-1633', 'H6': '-5775', 'H7': '17453', 'H4': '0', 'H5': '0'} From 98e05e8c0db94ff878a88257b9a5d47f850603a9 Mon Sep 17 00:00:00 2001 From: Anthony Coleman Date: Sun, 9 Aug 2020 09:38:08 -0500 Subject: [PATCH 67/78] ModBus interfacer for Renogy charge controllers POC of ModBus (over USB <> RS232 cable) for Renogy charge controllers --- .../EmonHubModbusRenogyInterfacer.py | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/interfacers/EmonHubModbusRenogyInterfacer.py diff --git a/src/interfacers/EmonHubModbusRenogyInterfacer.py b/src/interfacers/EmonHubModbusRenogyInterfacer.py new file mode 100644 index 00000000..089cc053 --- /dev/null +++ b/src/interfacers/EmonHubModbusRenogyInterfacer.py @@ -0,0 +1,147 @@ +import time +import datetime +import Cargo + +try: + from pymodbus.client.sync import ModbusSerialClient as ModbusClient + pymodbus_found = True +except ImportError: + pymodbus_found = False + +from emonhub_interfacer import EmonHubInterfacer + +"""class EmonModbusTcpInterfacer +Monitors Renogy Rover via USB RS232 Cable over modbus +""" + +class EmonHubModbusRenogyInterfacer(EmonHubInterfacer): + + def __init__(self, name, com_port='/dev/ttyUSB0', com_baud=9600, toextract='' , poll_interval=30): + """Initialize Interfacer + com_port (string): path to COM port + """ + + # Initialization + super(EmonHubModbusRenogyInterfacer, self).__init__(name) + self.poll_interval = int(poll_interval) + self.last_read = time.time() + + if not pymodbus_found: + self._log.error("PYMODBUS NOT PRESENT BUT NEEDED !!") + # open connection + if pymodbus_found: + self._log.info("pymodbus installed") + self._log.debug("EmonHubModbusRenogyInterfacer args: " + com_port + " - " + str(com_baud) ) + + self._con = self._open_modbus(com_port,com_baud) + if self._con : + self._log.info("Modbus client Connected!") + else: + self._log.info("Connection to Modbus client failed. Will try again later") + + def close(self): + + # Close TCP connection + if self._con is not None: + self._log.debug("Closing USB/Serial port") + self._con.close() + + def _open_modbus(self,com_port,com_baud): + """ Open connection to modbus device """ + BATTERY_TYPE = { + 1: 'open', + 2: 'sealed', + 3: 'gel', + 4: 'lithium', + 5: 'self-customized' + } + + try: + self._log.info("Starting Modbus client . . . ") + c = ModbusClient(method = 'rtu', port = com_port, baudrate = com_baud, stopbits = 1, bytesize = 8, parity = 'N') + if c.connect(): + Model = c.read_holding_registers(12, 8, unit=1) + self._log.info("Connected to Renogy Model: " + str(Model.registers[0])) + BatteryType = c.read_holding_registers(57348, 1, unit=1).registers[0] + BatteryCapacity = c.read_holding_registers(57346, 1, unit=1).registers[0] + self._log.info("Battery Type: " + BATTERY_TYPE[BatteryType] + " " + str(BatteryCapacity) + "ah") + self._modcon = True + else: + self._log.debug("Connection failed") + self._modcon = False + except Exception as e: + self._log.error("modbus connection failed" + str(e)) + #raise EmonHubInterfacerInitError('Could not open connection to host %s' %modbus_IP) + pass + else: + return c + + def read(self): + + now = time.time() + if not (now - self.last_read) > self.poll_interval: + # Wait to read based on poll_interval + return + + self.last_read = now + + # CHARGING_STATE = { + # 0: 'deactivated', + # 1: 'activated', + # 2: 'mppt', + # 3: 'equalizing', + # 4: 'boost', + # 5: 'floating', + # 6: 'current limiting' + # } + + """ Read registers from client""" + if pymodbus_found: + time.sleep(float(self._settings["interval"])) + f = [] + c = Cargo.new_cargo(rawdata="") + + if not self._modcon : + self._con.close() + self._log.info("Not connected, retrying connect" + str(self.init_settings)) + self._con = self._open_modbus(self.init_settings["modbus_IP"],self.init_settings["modbus_port"]) + + if self._modcon : + + # read battery registers + BatteryPercent = self._con.read_holding_registers(256, 1, unit=1).registers[0] + #Charging_Stage = CHARGING_STATE[self._con.read_holding_registers(288, 1, unit=1).registers[0]] + Charging_Stage = self._con.read_holding_registers(288, 1, unit=1).registers[0] + self._log.debug("Battery Percent " + str(BatteryPercent) + "%") + self._log.debug("Charging Stage " + str(Charging_Stage)) + + Temp_raw = self._con.read_holding_registers(259, 2, unit=1) + temp_value = Temp_raw.registers[0] & 0x0ff + sign = Temp_raw.registers[0] >> 7 + BatteryTemp_C = -(temp_value - 128) if sign == 1 else temp_value + BatteryTemp_F = (BatteryTemp_C * 9/5) + 32 + self._log.debug("BatteryTemp_C " + str(BatteryTemp_C)) + self._log.debug("BatteryTemp_F " + str(BatteryTemp_F)) + + # read Solar registers + SolarVoltage = self._con.read_holding_registers(263, 1, unit=1).registers[0] + SolarCurrent = self._con.read_holding_registers(264, 1, unit=1).registers[0] + SolarPower = self._con.read_holding_registers(265, 1, unit=1).registers[0] + self._log.debug("SolarVoltage " + str(SolarVoltage) + "v") + self._log.debug("SolarCurrent " + str(SolarCurrent) + "a") + self._log.debug("SolarPower " + str(SolarPower) + "w") + + # Create a Payload object + c = Cargo.new_cargo() + + if int(self._settings['nodeoffset']): + c.nodeid = int(self._settings['nodeoffset']) + c.realdata = [BatteryPercent, Charging_Stage, BatteryTemp_F, SolarVoltage, SolarCurrent, SolarPower] + else: + self._log.error("nodeoffset needed in emonhub configuration, make sure it exists and is a integer ") + pass + + self._log.debug("Return from read data: " + str(c.realdata)) + return c + + From 723507c2f9c90ef64ef016ba57f95bd881df1227 Mon Sep 17 00:00:00 2001 From: Anthony Coleman Date: Thu, 13 Aug 2020 07:52:54 -0500 Subject: [PATCH 68/78] Added Example Config --- .../Renogy/Renogy.emonhub.conf | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 conf/interfacer_examples/Renogy/Renogy.emonhub.conf diff --git a/conf/interfacer_examples/Renogy/Renogy.emonhub.conf b/conf/interfacer_examples/Renogy/Renogy.emonhub.conf new file mode 100644 index 00000000..06e90206 --- /dev/null +++ b/conf/interfacer_examples/Renogy/Renogy.emonhub.conf @@ -0,0 +1,23 @@ +#Configuration is for Renogy Rover on USB0 +[[Renogy]] + Type = EmonHubModbusRenogyInterfacer + [[[init_settings]]] + com_port = /dev/ttyUSB0 + com_baud = 9600 + toextract = BatteryPercent,Charging_Stage,BatteryTemp_F,SolarVoltage,SolarCurrent,SolarPower + poll_interval = 30 # More fields can be found in datalist.py + [[[runtimesettings]]] + nodeoffset = 28 #make sure this matches with nodename below + pubchannels = ToEmonCMS, + subchannels = ToRenogy, + basetopic = emonhub/ + +[nodes] + + [[28]] + nodename = Renogy + [[[rx]]] + names = BatteryPercent,Charging_Stage,BatteryTemp_F,SolarVoltage,SolarCurrent,SolarPower + datacode = 0 + scales = 1, 1, 1, 0.1,0.01,1,1 + units = %, s, s, V, A,W From de8d6576ecbf3e84e6d9c30c1b67c02cc596aed0 Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Thu, 20 Aug 2020 21:19:18 +0100 Subject: [PATCH 69/78] Set timeouts on requests transactions. Requests will wait forever for a response once the server has opened the connection. This might cause hard to detect bugs. Set a ridiculously long timeout. From the docs: timeout is not a time limit on the entire response download; rather, an exception is raised if the server has not issued a response for timeout seconds (more precisely, if no bytes have been received on the underlying socket for timeout seconds). If no timeout is specified explicitly, requests do not time out. --- src/emonhub_interfacer.py | 4 ++-- src/interfacers/EmonHubBMWInterfacer.py | 4 ++-- src/interfacers/EmonHubPacketGenInterfacer.py | 4 ++-- src/interfacers/EmonHubTeslaPowerWallInterfacer.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 7446519e..5d598e62 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -248,9 +248,9 @@ def _send_post(self, post_url, post_body=None): try: if post_body: - reply = requests.post(post_url, post_body) + reply = requests.post(post_url, post_body, timeout=60) else: - reply = requests.get(post_url) + reply = requests.get(post_url, timeout=60) reply.raise_for_status() # Raise an exception if status code isn't 200 return reply.text except requests.exceptions.RequestException as ex: diff --git a/src/interfacers/EmonHubBMWInterfacer.py b/src/interfacers/EmonHubBMWInterfacer.py index 519ff618..531f1461 100644 --- a/src/interfacers/EmonHubBMWInterfacer.py +++ b/src/interfacers/EmonHubBMWInterfacer.py @@ -168,9 +168,9 @@ def call(self, path, post_data=None): #self._log.debug("headers=" + str(headers)) if post_data is None: - r = requests.get(self.ROOT_URL + path, headers=headers) + r = requests.get(self.ROOT_URL + path, headers=headers, timeout=60) else: - r = requests.post(self.ROOT_URL + path, headers=headers, data=post_data) + r = requests.post(self.ROOT_URL + path, headers=headers, data=post_data, timeout=60) #Raise exception if problem with request r.raise_for_status() diff --git a/src/interfacers/EmonHubPacketGenInterfacer.py b/src/interfacers/EmonHubPacketGenInterfacer.py index 31a35225..7c0b1eab 100644 --- a/src/interfacers/EmonHubPacketGenInterfacer.py +++ b/src/interfacers/EmonHubPacketGenInterfacer.py @@ -41,7 +41,7 @@ def read(self): self._log.info("requesting packet: " + req + "E-M-O-N-C-M-S-A-P-I-K-E-Y") try: - packet = requests.get(req + self._settings['apikey']).json() + packet = requests.get(req + self._settings['apikey'], timeout=60).json() except (ValueError, requests.exceptions.RequestException) as ex: self._log.warning("no packet returned: " + str(ex)) return @@ -96,7 +96,7 @@ def action(self): try: z = requests.get(self._settings['url'] + "/emoncms/packetgen/getinterval.json?apikey=" - + self._settings['apikey']).text + + self._settings['apikey'], timeout=60).text i = int(z[1:-1]) except: self._log.info("request interval not returned") diff --git a/src/interfacers/EmonHubTeslaPowerWallInterfacer.py b/src/interfacers/EmonHubTeslaPowerWallInterfacer.py index 9512858e..82bad49c 100644 --- a/src/interfacers/EmonHubTeslaPowerWallInterfacer.py +++ b/src/interfacers/EmonHubTeslaPowerWallInterfacer.py @@ -35,7 +35,7 @@ def read(self): if self._settings['url']: # HTTP Request try: - reply = requests.get(self._settings['url'], timeout=int(self._settings['readinterval']), verify=False) + reply = requests.get(self._settings['url'], timeout=int(self._settings['readinterval']), verify=False, timeout=60) reply.raise_for_status() # Raise an exception if status code isn't 200 except requests.exceptions.RequestException as ex: self._log.warning("%s couldn't send to server: %s", self.name, ex) From 1ed5959d907d808805fcfa23f1ab6f31f306d95a Mon Sep 17 00:00:00 2001 From: Bruce Duncan Date: Mon, 21 Sep 2020 10:54:51 +0100 Subject: [PATCH 70/78] Fix zero-length slice bug introduced by 035d2666454 --- src/smalibrary/SMABluetoothPacket.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/smalibrary/SMABluetoothPacket.py b/src/smalibrary/SMABluetoothPacket.py index 5cb3b68a..794a1997 100644 --- a/src/smalibrary/SMABluetoothPacket.py +++ b/src/smalibrary/SMABluetoothPacket.py @@ -14,18 +14,18 @@ def lastByte(self): return self.UnescapedArray[-1] def getLevel2Payload(self): - skipendbytes = 0 + skipendbytes = None startbyte = 0 if self.UnescapedArray[0] == 0x7e: startbyte = 1 if self.lastByte() == 0x7e: - skipendbytes = 3 + skipendbytes = -3 # FIXME This comment says to skip the first 3 bytes, but the code skips the *last* 3 bytes # Skip the first 3 bytes, they are the command code 0x0001 and 0x7E start byte - return self.UnescapedArray[startbyte:-skipendbytes] + return self.UnescapedArray[startbyte:skipendbytes] def pushRawByteArray(self, barray): # Raw byte array From acefe46fc00b343275cbe7d9019f292c21327715 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Mon, 21 Sep 2020 13:09:16 +0100 Subject: [PATCH 71/78] Update install.sh Add in bluetooth Python dependencies for SMALibrary --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 8afbd74e..89d33a9f 100755 --- a/install.sh +++ b/install.sh @@ -18,8 +18,8 @@ if [ "$emonSD_pi_env" = "" ]; then sudo apt update fi -sudo apt-get install -y python3-serial python3-configobj python3-pip python3-pymodbus -sudo pip3 install paho-mqtt requests +sudo apt-get install -y python3-serial python3-configobj python3-pip python3-pymodbus bluetooth libbluetooth-dev +sudo pip3 install paho-mqtt requests pybluez if [ "$emonSD_pi_env" = "1" ]; then # RaspberryPi Serial configuration From 0b6ad1ab3a919038f2ccafaa96496719f6ddb49a Mon Sep 17 00:00:00 2001 From: TrystanLea Date: Mon, 21 Sep 2020 13:36:44 +0100 Subject: [PATCH 72/78] fix tesla power wall timeout --- src/interfacers/EmonHubTeslaPowerWallInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubTeslaPowerWallInterfacer.py b/src/interfacers/EmonHubTeslaPowerWallInterfacer.py index 82bad49c..9512858e 100644 --- a/src/interfacers/EmonHubTeslaPowerWallInterfacer.py +++ b/src/interfacers/EmonHubTeslaPowerWallInterfacer.py @@ -35,7 +35,7 @@ def read(self): if self._settings['url']: # HTTP Request try: - reply = requests.get(self._settings['url'], timeout=int(self._settings['readinterval']), verify=False, timeout=60) + reply = requests.get(self._settings['url'], timeout=int(self._settings['readinterval']), verify=False) reply.raise_for_status() # Raise an exception if status code isn't 200 except requests.exceptions.RequestException as ex: self._log.warning("%s couldn't send to server: %s", self.name, ex) From 1e09d5a7b77aa820ba1dea8f2c7031916b277dd3 Mon Sep 17 00:00:00 2001 From: Trystan Lea Date: Tue, 22 Sep 2020 10:23:32 +0100 Subject: [PATCH 73/78] Update EmonHubSmilicsInterfacer.py --- src/interfacers/tmp/EmonHubSmilicsInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py index 13a5f528..198417ad 100644 --- a/src/interfacers/tmp/EmonHubSmilicsInterfacer.py +++ b/src/interfacers/tmp/EmonHubSmilicsInterfacer.py @@ -122,7 +122,7 @@ def _process_rx(self, smilics_dict): def set(self, **kwargs): """ Override default settings with settings entered in the config file """ - for key, setting in self._settings.tems(): + for key, setting in self._settings.items(): if key in kwargs.keys(): # replace default self._settings[key] = kwargs[key] From ca13484ac28eeb46da168e48f05004e01d2c6290 Mon Sep 17 00:00:00 2001 From: Trystan Lea Date: Tue, 22 Sep 2020 13:27:23 +0100 Subject: [PATCH 74/78] Fix SDS011 interfacer compatibility for python3 --- src/emonhub_interfacer.py | 2 +- src/interfacers/EmonHubSDS011Interfacer.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/emonhub_interfacer.py b/src/emonhub_interfacer.py index 9dd2eb76..8b672c91 100644 --- a/src/emonhub_interfacer.py +++ b/src/emonhub_interfacer.py @@ -426,7 +426,7 @@ def _process_rx(self, cargo): if not rxc: return False self._log.debug("%d Timestamp : %f", rxc.uri, rxc.timestamp) - self._log.debug("%d From Node : %d", rxc.uri, rxc.nodeid) + self._log.debug("%d From Node : %s", rxc.uri, str(rxc.nodeid)) if rxc.target: self._log.debug("%d To Target : %d", rxc.uri, rxc.target) self._log.debug("%d Values : %s", rxc.uri, rxc.realdata) diff --git a/src/interfacers/EmonHubSDS011Interfacer.py b/src/interfacers/EmonHubSDS011Interfacer.py index 0d31b8da..b13fd03a 100644 --- a/src/interfacers/EmonHubSDS011Interfacer.py +++ b/src/interfacers/EmonHubSDS011Interfacer.py @@ -59,14 +59,16 @@ def read(self): self.lastbyte = self.byte self.byte = self._ser.read(size=1) - + # Valid packet header - if self.lastbyte == "\xAA" and self.byte == "\xC0": + if self.lastbyte == b"\xaa" and self.byte == b"\xc0": + sentence = self._ser.read(size=8) # Read 8 more bytes readings = struct.unpack(' Date: Tue, 22 Sep 2020 13:29:39 +0100 Subject: [PATCH 75/78] configuration note --- configuration.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/configuration.md b/configuration.md index ed7626f7..aa70d59b 100644 --- a/configuration.md +++ b/configuration.md @@ -231,7 +231,20 @@ This interfacer fetches the state of charge of a Tesla Power Wall on the local n name = powerwall url = http://POWERWALL-IP/api/system_status/soe readinterval = 10 - + +### f.) SDS011 Air Quality Sensor + +Read data from the SDS011 sensor. Note that this implementation keeps the SDS011 sensor on all the time and may reduce the lifespan of the sensor significantly. Further work required! + + [[SDS011]] + Type = EmonHubSDS011Interfacer + [[[init_settings]]] + serial_port = /dev/ttyUSB0 + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + nodename = SDS011 + readinterval = 10 + *** # 3. 'nodes' Configuration From a74e8717698176ee1351a435fab1edfbc23a5bad Mon Sep 17 00:00:00 2001 From: Trystan Lea Date: Tue, 22 Sep 2020 14:05:04 +0100 Subject: [PATCH 76/78] include in init --- src/interfacers/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interfacers/__init__.py b/src/interfacers/__init__.py index 97d3d1c8..5c0b3e78 100644 --- a/src/interfacers/__init__.py +++ b/src/interfacers/__init__.py @@ -16,6 +16,7 @@ "EmonModbusTcpInterfacer", "EmonHubTeslaPowerWallInterfacer", "EmonHubSDS011Interfacer", + "EmonHubModbusRenogyInterfacer", "EmonHubTemplateInterfacer" #"EmonFroniusModbusTcpInterfacer" ] From b22d168e30e9438ec56dbcac75b34d8eff0c064c Mon Sep 17 00:00:00 2001 From: Trystan Lea Date: Tue, 22 Sep 2020 14:20:08 +0100 Subject: [PATCH 77/78] only read from serial if available --- src/interfacers/EmonHubVEDirectInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubVEDirectInterfacer.py b/src/interfacers/EmonHubVEDirectInterfacer.py index 596c9074..f3d49d40 100644 --- a/src/interfacers/EmonHubVEDirectInterfacer.py +++ b/src/interfacers/EmonHubVEDirectInterfacer.py @@ -129,7 +129,7 @@ def parse_package(self, data): def _read_serial(self): self._log.debug("Starting Serial read from %s", self._ser) try: - while True: + while self._ser: # Read one byte at a time from the serial port and pass it into # the input FSM until we have a complete packet, then return it packet = self.input(self._ser.read()) From b012343e813a1234aea586458a9dc648e776de20 Mon Sep 17 00:00:00 2001 From: TrystanLea Date: Mon, 12 Oct 2020 15:45:02 +0100 Subject: [PATCH 78/78] update version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 399088bf..04b10b4f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.1.6 +2.1.7