From 1b1c5b2ee70f5de421af8afbdef224b7e6cd95b5 Mon Sep 17 00:00:00 2001 From: gjr80 Date: Wed, 27 Nov 2019 07:31:26 +1000 Subject: [PATCH 01/13] Starting to fix merge errors --- bin/user/bloomsky.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index fe41857..37153b5 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -139,16 +139,6 @@ setup.py install: $ PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/bloomsky.py --run-driver --api-key=INSERT_API_KEY - -Support for Multiple Device IDs - -1. In the case of Bloomsky accounts that have multiple device IDs (as distinct -from multiple devices) a default install will result in data being obtained and -used from the first found device ID. Other device IDs are ignored. This driver -can use data from multiple device IDs by defining a sensor map in weewx.conf -under [Bloomsky]. Refer to the Bloomsky driver User's Guide -(https://github.com/gjr80/weewx-bloomsky/wiki/User's-Guide). - 7. If WeeWX is running stop then start WeeWX otherwise start WeeWX. Support for Multiple Device IDs From a9f2a708f5c28f750c19390b6c471d998131991a Mon Sep 17 00:00:00 2001 From: gjr80 Date: Wed, 27 Nov 2019 07:46:24 +1000 Subject: [PATCH 02/13] Fixed more merge errors --- bin/user/bloomsky.py | 116 +++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 70 deletions(-) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index 37153b5..cf2f61e 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -149,14 +149,13 @@ can use data from multiple device IDs by defining a sensor map in weewx.conf under [Bloomsky]. Refer to the Bloomsky driver User's Guide (https://github.com/gjr80/weewx-bloomsky/wiki/User's-Guide). - - """ # Python imports +import fnmatch import json +import logging import socket -import syslog import threading import time @@ -170,37 +169,13 @@ import weewx.drivers import weewx.wxformulas +# obtain a logger object +log = logging.getLogger(__name__) + DRIVER_NAME = 'Bloomsky' DRIVER_VERSION = "1.0.0" -def logmsg(level, msg): - syslog.syslog(level, 'bloomsky: %s: %s' % - (threading.currentThread().getName(), msg)) - - -def logdbg(msg): - logmsg(syslog.LOG_DEBUG, msg) - - -def logdbg2(msg): - if weewx.debug >= 2: - logmsg(syslog.LOG_DEBUG, msg) - - -def logdbg3(msg): - if weewx.debug >= 3: - logmsg(syslog.LOG_DEBUG, msg) - - -def loginf(msg): - logmsg(syslog.LOG_INFO, msg) - - -def logerr(msg): - logmsg(syslog.LOG_ERR, msg) - - def loader(config_dict, engine): return BloomskyDriver(**config_dict[DRIVER_NAME]) @@ -255,7 +230,6 @@ class BloomskyDriver(weewx.drivers.AbstractDevice): 'deviceType': 'DeviceType', 'barometer': 'Pressure', 'luminance': 'Luminance', - 'barometer': 'pressure', 'raining': 'Rain', 'night': 'Night', 'imageTimestamp': 'ImageTS'}, @@ -265,8 +239,7 @@ class BloomskyDriver(weewx.drivers.AbstractDevice): 'windSpeed': 'SustainedWindSpeed', 'windDir': 'WindDirection', 'windGust': 'WindGust', - 'rainDaily': 'RainDaily'} - """Driver for obtaining data from the Bloomsky API.""" + 'rainDaily': 'RainDaily'}} # Sane default map from Bloomsky API field names to weeWX db schema names # that will work in most cases (ie single Sky and/or Storm). Accounts with @@ -295,16 +268,16 @@ class BloomskyDriver(weewx.drivers.AbstractDevice): DEFAULT_DELTAS = {'rain': 'rainDaily'} def __init__(self, **stn_dict): - loginf('driver version is %s' % DRIVER_VERSION) + log.info('driver version is %s' % DRIVER_VERSION) self.sensor_map = dict(BloomskyDriver.DEFAULT_SENSOR_MAP) if 'sensor_map' in stn_dict: self.sensor_map.update(stn_dict['sensor_map']) - loginf('sensor map is %s' % self.sensor_map) + log.info('sensor map is %s' % self.sensor_map) # number of time to try and get a response from the BloomSky API # get the deltas self.deltas = stn_dict.get('deltas', BloomskyDriver.DEFAULT_DELTAS) - loginf('deltas is %s' % self.deltas) + log.info('deltas is %s' % self.deltas) # number of time to try and get a response from the Bloomsky API max_tries = int(stn_dict.get('max_tries', 3)) @@ -316,10 +289,10 @@ def __init__(self, **stn_dict): # API key issued obtained from dashboard.bloomsky.com api_key = stn_dict['api_key'] obfuscated = ''.join(('"....', api_key[-4:], '"')) - logdbg('poll interval is %d seconds, API key is %s' % (poll_interval, - obfuscated)) - logdbg('max tries is %d, retry wait time is %d seconds' % (max_tries, - retry_wait)) + log.debug('poll interval is %d seconds, API key is %s' % (poll_interval, + obfuscated)) + log.debug('max tries is %d, retry wait time is %d seconds' % (max_tries, + retry_wait)) self._counter_values = dict() self.last_rain = None # create an ApiClient object to interact with the BloomSky API @@ -386,7 +359,8 @@ def genLoopPackets(self): # create a loop packet packet = self.data_to_packet(raw_data) # log the packet but only if debug>=2 - logdbg2('Packet: %s' % packet) + if weewx.debug >= 2: + log.debug('Packet: %s' % packet) # if we did get a packet then yield it for processing if packet: yield packet @@ -501,8 +475,8 @@ def get_sensor_value(data, sensor_pattern): if (total is not None and self.last_rain is not None and total < self.last_rain): # yes we have, just log it - loginf("dailyRain decrement ignored:" - " new: %s old: %s" % (total, self.last_rain)) + log.info("dailyRain decrement ignored:" + " new: %s old: %s" % (total, self.last_rain)) # calculate the rainfall since the last packet packet['rain'] = weewx.wxformulas.calculate_rain(total, self.last_rain) @@ -721,33 +695,33 @@ def collect_data(self): # dicts data = ApiClient.extract_data(raw_data) # log the extracted data for debug purposes - logdbg3("Extracted data: %s" % data) + if weewx.debug >= 3: + log.debug("Extracted data: %s" % data) # put the data in the queue Collector.queue.put(data) # we are done so break out of the for loop break except (urllib.error.HTTPError, urllib.error.URLError) as e: # handle any errors caught - logerr("Failed attempt %s of %s to get data: %s" % - (tries + 1, self._max_tries, e)) - logdbg("Waiting %s seconds before retry" % - self._retry_wait) + log.error("Failed attempt %s of %s to get data: %s" % (tries + 1, + self._max_tries, + e)) + log.debug("Waiting %s seconds before retry" % self._retry_wait) time.sleep(self._retry_wait) except IndexError as e: # most likely we got back a blank response or maybe # something couldn't be found where it was expected - logerr("Invalid data received on attempt %s of %s: %s" % - (tries + 1, self._max_tries, e)) - logdbg("Waiting %s seconds before retry" % - self._retry_wait) + log.error("Invalid data received on attempt %s of %s: %s" % (tries + 1, + self._max_tries, + e)) + log.debug("Waiting %s seconds before retry" % self._retry_wait) time.sleep(self._retry_wait) else: # if we did not get any data after self._max_tries log it - logerr("Failed to get data after %d attempts" % - self._max_tries) + log.error("Failed to get data after %d attempts" % self._max_tries) # reset the last poll ts last_poll = now - logdbg('Next update in %s seconds' % self._poll_interval) + log.debug('Next update in %s seconds' % self._poll_interval) # sleep and see if its time to poll again time.sleep(1) @@ -947,7 +921,7 @@ def run(self): self.client.collect_data() except: # we have an exception so log what we can - loginf('Exception:') + log.info('Exception:') weeutil.weeutil.log_traceback(" **** ", syslog.LOG_INFO) class StationData(object): @@ -994,14 +968,14 @@ def get_request(url, params, headers): obfuscated = dict(headers) if 'Authorization' in obfuscated: obfuscated['Authorization'] = ''.join(("....", obfuscated['Authorization'][-4:])) - logdbg("url: %s data: %s hdr: %s" % (url, params, obfuscated)) + log.debug("url: %s data: %s hdr: %s" % (url, params, obfuscated)) data = urlencode(params) # obtain an obfuscated copy of the API key being used for use when # logging obf = dict(headers) if 'Authorization' in obf: obf['Authorization'] = ''.join(("....", obf['Authorization'][-4:])) - logdbg("url: %s data: %s hdr: %s" % (url, params, obf)) + log.debug("url: %s data: %s hdr: %s" % (url, params, obf)) # A urllib2.Request that includes a 'data' parameter value will be sent # as a POST request. the BloomSky API requires a GET request. So to # send as a GET request with data just append '?' and the 'data' to the @@ -1030,12 +1004,13 @@ def get_request(url, params, headers): resp = w.read().decode() w.close() except (urllib.error.URLError, socket.timeout) as e: - logerr("Failed to get BloomSky API data") - logerr(" **** %s" % e) + log.error("Failed to get BloomSky API data") + log.error(" **** %s" % e) # convert the response to a JSON object resp_json = json.loads(resp) # log response as required - logdbg3("JSON API response: %s" % json.dumps(resp_json)) + if weewx.debug >= 3: + log.debug("JSON API response: %s" % json.dumps(resp_json)) # return the JSON object return resp_json @@ -1090,13 +1065,13 @@ def main(): # get config_dict to use config_path, config_dict = weecfg.read_config(opts.config_path, args) - print "Using configuration file %s" % config_path + print( "Using configuration file %s" % config_path) stn_dict = config_dict.get('Bloomsky', {}) # do we have a specific API key to use if opts.api_key: stn_dict['api_key'] = opts.api_key - print "Using Bloomsky API key %s" % opts.api_key + print("Using Bloomsky API key %s" % opts.api_key) # display device IDs if opts.get_ids: @@ -1120,11 +1095,11 @@ def run_driver(api_key): driver = BloomskyDriver(**stn_dict) ids = driver.ids if len(ids) > 1: - print "Found Bloomsky device IDs: %s" % ', '.join(ids) + print("Found Bloomsky device IDs: %s" % ', '.join(ids)) elif len(ids) == 1: - print "Found Bloomsky device ID: %s" % ', '.join(ids) + print("Found Bloomsky device ID: %s" % ', '.join(ids)) else: - print "No Bloomsky device IDS found" + print("No Bloomsky device IDS found") driver.closePort() exit(0) @@ -1165,11 +1140,12 @@ def get_json_data(stn_dict): # get the JSON response raw_data = api_client.sd.get_data() # display the JSON response on screen - print json.dumps(raw_data, sort_keys=True, indent=2) + print(json.dumps(raw_data, sort_keys=True, indent=2)) else: - print "Bloomsky API key required." - print "Specify API key in configuration file under [Bloomsky] or use --api_key option." - print "Exiting." + print("Bloomsky API key required.") + print("Specify API key in configuration file under [Bloomsky] or use --api_key option.") + print("Exiting.") exit(1) main() + From 3f59b1372d3448d3d17db281df7cade0b508b454 Mon Sep 17 00:00:00 2001 From: gjr80 Date: Wed, 27 Nov 2019 10:37:09 +1000 Subject: [PATCH 03/13] Fixed more merge errors --- bin/user/bloomsky.py | 243 +++++++++++++++++++++---------------------- 1 file changed, 119 insertions(+), 124 deletions(-) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index cf2f61e..b8bce1b 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -160,12 +160,16 @@ import time # python 2/3 compatibility shims +import six +from six import iteritems from six.moves import queue from six.moves import urllib +from six.moves.urllib.error import HTTPError, URLError +from six.moves.urllib.parse import urlencode # WeeWX imports import weeutil - +import weeutil.logger import weewx.drivers import weewx.wxformulas @@ -326,7 +330,7 @@ def ids(self): try: # get any data from the collector queue raw_data = self.collector.queue.get(True, 10) - except Queue.Empty: + except queue.Empty: # there was nothing in the queue so continue pass # The raw data will be a list of dicts where each dict is the data for @@ -370,27 +374,48 @@ def genLoopPackets(self): def data_to_packet(self, data): """Convert BloomSky data to WeeWX packet format. - def calculate_rain(self, packet): - """Calculate rainfall values for rain fields.""" - for delta_field, src_field in self.deltas.iteritems(): - if src_field in packet: - packet[delta_field] = self._calc_rain(src_field, - packet[src_field], - self._counter_values.get(src_field)) - self._counter_values[src_field] = packet[src_field] + The Collector provides BloomSky API response in the form of a dict that + may contain nested dicts of data. Fist map the BloomSky data to a flat + WeeWX data packet then add a packet timestamp and unit system fields. + Finally calculate rain since last packet. - @staticmethod - def _calc_rain(src_field, new_total, old_total): - """Calculate rainfall given the current and previous totals.""" + Input: + data: BloomSky API response in dict format - delta = None - if new_total is not None and old_total is not None: - if new_total >= old_total: - delta = new_total - old_total - else: - delta = new_total - return delta + Returns: + A WeeWX loop packet + """ + + # map the BloomSky API data to a WeeWX loop packet + packet = self._map_fields(data, self.sensor_map) + # add dateTime and usUnits fields + packet['dateTime'] = int(time.time() + 0.5) + # we ask the BloomSky API data for international units which gives us + # data conforming to the METRICWX unit system + packet['usUnits'] = weewx.METRICWX + # BloomSky reports 2 rainfall fields, RainDaily and 24hRain; the + # rainfall since midnight and the rainfall in the last 24 hours + # respectively. Therefore we need to calculate the incremental rain + # since the last packet using the RainDaily field (which was translated + # to the WeeWX dailyRain field). We will see a decrement at midnight + # when the counter is reset, this may cause issues if it is raining at + # the time but there is little that can be done. + if 'rainDaily' in packet: + # get the rain so far today + total = packet['rainDaily'] + # have we seen a daily rain reset? + if (total is not None and self.last_rain is not None + and total < self.last_rain): + # yes we have, just log it + log.info("dailyRain decrement ignored:" + " new: %s old: %s" % (total, self.last_rain)) + # calculate the rainfall since the last packet + packet['rain'] = weewx.wxformulas.calculate_rain(total, + self.last_rain) + # adjust our last rain total + self.last_rain = total + return packet def map_to_fields(self, packet, raw_data): """Map Bloomsky raw data to weeWX packet fields. @@ -420,6 +445,28 @@ def map_to_fields(self, packet, raw_data): packet[s] = value return + def calculate_rain(self, packet): + """Calculate rainfall values for rain fields.""" + + for delta_field, src_field in iteritems(self.deltas): + if src_field in packet: + packet[delta_field] = self._calc_rain(src_field, + packet[src_field], + self._counter_values.get(src_field)) + self._counter_values[src_field] = packet[src_field] + + @staticmethod + def _calc_rain(src_field, new_total, old_total): + """Calculate rainfall given the current and previous totals.""" + + delta = None + if new_total is not None and old_total is not None: + if new_total >= old_total: + delta = new_total - old_total + else: + delta = new_total + return delta + @staticmethod def get_sensor_value(data, sensor_pattern): """Get a Bloomsky sensor value given a Bloomsky sensor mapping. @@ -449,40 +496,10 @@ def get_sensor_value(data, sensor_pattern): sensor_pattern: sensor map pattern being used Returns: - A WeeWX loop packet The data element referred to by the sensor map pattern. Will be None if no element found. """ - # map the BloomSky API data to a WeeWX loop packet - packet = self._map_fields(data, self.sensor_map) - # add dateTime and usUnits fields - packet['dateTime'] = int(time.time() + 0.5) - # we ask the BloomSky API data for international units which gives us - # data conforming to the METRICWX unit system - packet['usUnits'] = weewx.METRICWX - # BloomSky reports 2 rainfall fields, RainDaily and 24hRain; the - # rainfall since midnight and the rainfall in the last 24 hours - # respectively. Therefore we need to calculate the incremental rain - # since the last packet using the RainDaily field (which was translated - # to the WeeWX dailyRain field). We will see a decrement at midnight - # when the counter is reset, this may cause issues if it is raining at - # the time but there is little that can be done. - if 'rainDaily' in packet: - # get the rain so far today - total = packet['rainDaily'] - # have we seen a daily rain reset? - if (total is not None and self.last_rain is not None - and total < self.last_rain): - # yes we have, just log it - log.info("dailyRain decrement ignored:" - " new: %s old: %s" % (total, self.last_rain)) - # calculate the rainfall since the last packet - packet['rain'] = weewx.wxformulas.calculate_rain(total, - self.last_rain) - # adjust our last rain total - self.last_rain = total - return packet # initialise our response, assume nothing found to start with element = None # Need the device ID portion of the sensor pattern so we know when we @@ -503,7 +520,7 @@ def get_sensor_value(data, sensor_pattern): # Iterate over the top level items in the dict looking for a # matching field. If the match happens to be a dict then ignore # it as our sensor pattern does not support that. - for (k, v) in device.iteritems(): + for (k, v) in iteritems(device): if sensor_pattern == k and not hasattr(k, 'keys'): element = v return element @@ -511,24 +528,19 @@ def get_sensor_value(data, sensor_pattern): @staticmethod def _map_fields(data, map): """Map BloomSky API response fields to WeeWX loop packet fields. - def _find_in_device(data, sensor_pattern): - """Find a data element for a device ID given a partial sensor map. - Recursively search the data dict for a key that matches the sensor - pattern. + Iterate over all all sensor map entries and if a field exists that + matches the map entry then map the field to a packet dict. Some data + fields may be a nested dict so recursively call the _map_fields() + method to map any child data fields. Input: data: BloomSky API response in dict format map: The sensor map (dict) - Inputs: - data: device ID data to be searched - sensor_pattern: sensor map pattern being used Returns: A WeeWX loop packet consisting of only those fields in the sensor map that also exist in the source data dict. - The data element referred to by the sensor map pattern. Will be - None if no element found. """ # initialise our result @@ -546,6 +558,23 @@ def _find_in_device(data, sensor_pattern): if b_field in data: packet[w_field] = data[b_field] return packet + + @staticmethod + def _find_in_device(data, sensor_pattern): + """Find a data element for a device ID given a partial sensor map. + + Recursively search the data dict for a key that matches the sensor + pattern. + + Inputs: + data: device ID data to be searched + sensor_pattern: sensor map pattern being used + + Returns: + The data element referred to by the sensor map pattern. Will be + None if no element found. + """ + # start of assuming we have no match element = None # Split the sensor map at the first period '.' character. Look for a @@ -556,7 +585,7 @@ def _find_in_device(data, sensor_pattern): # do we have a 'dotted' sensor map or just a sensor/field name if len(parts) > 1: # it's a 'dotted' sensor map - for (k, v) in data.iteritems(): + for (k, v) in iteritems(data): # iterate over the key, value pairs of our data to see if we # have a match if BloomskyDriver._match(k, parts[0]): @@ -565,7 +594,7 @@ def _find_in_device(data, sensor_pattern): element = BloomskyDriver._find_in_device(v, parts[1]) else: # just a straight up field/sensor name - for (k, v) in data.iteritems(): + for (k, v) in iteritems(data): # iterate over the key, value pairs of our data to see if we # have the field we want if sensor_pattern == k: @@ -701,7 +730,7 @@ def collect_data(self): Collector.queue.put(data) # we are done so break out of the for loop break - except (urllib.error.HTTPError, urllib.error.URLError) as e: + except (HTTPError, URLError) as e: # handle any errors caught log.error("Failed attempt %s of %s to get data: %s" % (tries + 1, self._max_tries, @@ -725,6 +754,7 @@ def collect_data(self): # sleep and see if its time to poll again time.sleep(1) + @staticmethod def extract_data(data): """Extract the data of interest. @@ -743,31 +773,6 @@ def extract_data(data): contain nested dicts. """ - data_dict = dict() - for item in ApiClient.BASE_ITEMS: - if item in data[0]: - data_dict[item] = data[0][item] - if 'Data' in data[0]: - data_dict['Data'] = dict() - for item in ApiClient.DATA_ITEMS: - if item in data[0]['Data']: - data_dict['Data'][item] = data[0]['Data'][item] - if 'Point' in data[0]: - data_dict['Point'] = dict() - for item in ApiClient.POINT_ITEMS: - if item in data[0]['Point']: - data_dict['Point'][item] = data[0]['Point'][item] - if 'Storm' in data[0]: - data_dict['Storm'] = dict() - for item in ApiClient.STORM_ITEMS: - if item in data[0]['Storm']: - data_dict['Storm'][item] = data[0]['Storm'][item] - # perform any manipulation/translation of the API response data (eg if - # no Storm device UV will be 9999, WeeWX expects so UV field or data is - # no sensor so delete the UV entry) - data_dict.update(ApiClient.translate_data(data_dict, - ApiClient.TRANSLATIONS)) - return data_dict device_list = [] for id in data: device_dict = dict() @@ -922,7 +927,7 @@ def run(self): except: # we have an exception so log what we can log.info('Exception:') - weeutil.weeutil.log_traceback(" **** ", syslog.LOG_INFO) + weeutil.logger.log_traceback(log.info, " **** ") class StationData(object): """Class to obtain station data from the BloomSky API.""" @@ -964,11 +969,6 @@ def get_request(url, params, headers): """ # encode the GET parameters - data = urllib.parse.urlencode(params) - obfuscated = dict(headers) - if 'Authorization' in obfuscated: - obfuscated['Authorization'] = ''.join(("....", obfuscated['Authorization'][-4:])) - log.debug("url: %s data: %s hdr: %s" % (url, params, obfuscated)) data = urlencode(params) # obtain an obfuscated copy of the API key being used for use when # logging @@ -1003,7 +1003,7 @@ def get_request(url, params, headers): else: resp = w.read().decode() w.close() - except (urllib.error.URLError, socket.timeout) as e: + except (URLError, socket.timeout) as e: log.error("Failed to get BloomSky API data") log.error(" **** %s" % e) # convert the response to a JSON object @@ -1015,30 +1015,36 @@ def get_request(url, params, headers): return resp_json -# To test this driver, do one of the following depending on your WeeWX install -# type: -# PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/bloomsky.py -# To use this driver in standalone mode for testing or development, use one of -# the following commands (depending on your weeWX install): -# -# $ PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/bloomsky.py -# -# or -# -# $ PYTHONPATH=/usr/share/weewx python /usr/share/weewx/user/bloomsky.py -# -# The above commands will display details of available command line options. +""" +To test this driver, do one of the following depending on your WeeWX install +type: + PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/bloomsky.py + +To use this driver in standalone mode for testing or development, use one of +the following commands (depending on your weeWX install): + + $ PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/bloomsky.py + + or + + $ PYTHONPATH=/usr/share/weewx python /usr/share/weewx/user/bloomsky.py + + The above commands will display details of available command line options. +""" if __name__ == "__main__": usage = """%prog [options] [--help]""" def main(): import optparse - syslog.openlog('wee_bloomsky', syslog.LOG_PID | syslog.LOG_CONS) + import weecfg + import weeutil.logger + + weeutil.logger.setup('bloomsky', {}) + parser = optparse.OptionParser(usage=usage) parser.add_option('--version', dest='version', action='store_true', help='display BloomSky driver version number') - help='display Bloomsky driver version number') parser.add_option('--config', dest='config_path', metavar='CONFIG_FILE', help="Use configuration file CONFIG_FILE.") parser.add_option('--debug', dest='debug', action='store_true', @@ -1049,14 +1055,14 @@ def main(): help='BloomSky API key') parser.add_option('--get-json-data', dest='jdata', action='store_true', help='get BloomSky API json response') - help='get Bloomsky API json response') parser.add_option('--get-deviceids', dest='get_ids', action='store_true', help='get Bloomsky device IDs associated with an API key') (opts, args) = parser.parse_args() # if --debug raise our log level if opts.debug: - syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG)) + pass + # syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG)) # display driver version number if opts.version: @@ -1082,15 +1088,12 @@ def main(): run_driver(stn_dict) # get BloomSky API JSON response - # display Bloomsky API JSON response if opts.jdata: get_json_data(stn_dict) def get_ids(stn_dict): - """Display Bloomsky device IDs associated with an API key.""" + """Display BloomSky device IDs associated with an API key.""" - def run_driver(api_key): - """Run the BloomSky driver.""" # get a BloomskyDriver object driver = BloomskyDriver(**stn_dict) ids = driver.ids @@ -1119,17 +1122,9 @@ def run_driver(stn_dict): # we have a keyboard interrupt so shut down driver.closePort() - def get_json_data(api_key): - """Get the BloomSky API JSON format response and display on screen.""" def get_json_data(stn_dict): """Obtain Bloomsky API response and display in JSON format.""" - # get an ApiClient object - api_client = ApiClient(api_key=api_key) - # get the JSON response - raw_data = api_client.sd.get_data() - # display the JSON response on screen - print(json.dumps(raw_data, sort_keys=True, indent=2)) # extract the API key api_key = stn_dict.get('api_key') # if we have an API key then get the data otherwise exit after From f734c948c5fbbcd0c094823582a72978b9360012 Mon Sep 17 00:00:00 2001 From: gjr80 Date: Wed, 27 Nov 2019 12:01:23 +1000 Subject: [PATCH 04/13] Tidy up main(), now running under WeeWX 4.0b3 python3 --- bin/user/bloomsky.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index b8bce1b..4e35044 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -1016,12 +1016,8 @@ def get_request(url, params, headers): """ -To test this driver, do one of the following depending on your WeeWX install -type: - PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/bloomsky.py - To use this driver in standalone mode for testing or development, use one of -the following commands (depending on your weeWX install): +the following commands (depending on your WeeWX install): $ PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/bloomsky.py @@ -1039,6 +1035,7 @@ def main(): import optparse import weecfg import weeutil.logger + import weewx weeutil.logger.setup('bloomsky', {}) @@ -1056,13 +1053,12 @@ def main(): parser.add_option('--get-json-data', dest='jdata', action='store_true', help='get BloomSky API json response') parser.add_option('--get-deviceids', dest='get_ids', action='store_true', - help='get Bloomsky device IDs associated with an API key') + help='get BloomSky device IDs associated with an API key') (opts, args) = parser.parse_args() # if --debug raise our log level if opts.debug: - pass - # syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG)) + weewx.debug = 1 # display driver version number if opts.version: @@ -1071,13 +1067,13 @@ def main(): # get config_dict to use config_path, config_dict = weecfg.read_config(opts.config_path, args) - print( "Using configuration file %s" % config_path) + print("Using configuration file %s" % config_path) stn_dict = config_dict.get('Bloomsky', {}) # do we have a specific API key to use if opts.api_key: stn_dict['api_key'] = opts.api_key - print("Using Bloomsky API key %s" % opts.api_key) + print("Using BloomSky API key %s" % opts.api_key) # display device IDs if opts.get_ids: @@ -1091,6 +1087,10 @@ def main(): if opts.jdata: get_json_data(stn_dict) + # otherwise print our help + parser.print_help() + exit(0) + def get_ids(stn_dict): """Display BloomSky device IDs associated with an API key.""" @@ -1098,16 +1098,16 @@ def get_ids(stn_dict): driver = BloomskyDriver(**stn_dict) ids = driver.ids if len(ids) > 1: - print("Found Bloomsky device IDs: %s" % ', '.join(ids)) + print("Found BloomSky device IDs: %s" % ', '.join(ids)) elif len(ids) == 1: - print("Found Bloomsky device ID: %s" % ', '.join(ids)) + print("Found BloomSky device ID: %s" % ', '.join(ids)) else: - print("No Bloomsky device IDS found") + print("No BloomSky device IDS found") driver.closePort() exit(0) def run_driver(stn_dict): - """Run the Bloomsky driver.""" + """Run the BloomSky driver.""" import weeutil.weeutil @@ -1123,7 +1123,7 @@ def run_driver(stn_dict): driver.closePort() def get_json_data(stn_dict): - """Obtain Bloomsky API response and display in JSON format.""" + """Obtain BloomSky API response and display in JSON format.""" # extract the API key api_key = stn_dict.get('api_key') @@ -1137,7 +1137,7 @@ def get_json_data(stn_dict): # display the JSON response on screen print(json.dumps(raw_data, sort_keys=True, indent=2)) else: - print("Bloomsky API key required.") + print("BloomSky API key required.") print("Specify API key in configuration file under [Bloomsky] or use --api_key option.") print("Exiting.") exit(1) From c1eaff2f68dd38a081cf428b22d02a5062fc22f7 Mon Sep 17 00:00:00 2001 From: gjr80 Date: Wed, 27 Nov 2019 12:04:56 +1000 Subject: [PATCH 05/13] missing exit(0) --- bin/user/bloomsky.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index 4e35044..183dc6c 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -1136,6 +1136,7 @@ def get_json_data(stn_dict): raw_data = api_client.sd.get_data() # display the JSON response on screen print(json.dumps(raw_data, sort_keys=True, indent=2)) + exit(0) else: print("BloomSky API key required.") print("Specify API key in configuration file under [Bloomsky] or use --api_key option.") From f46d53311156d48b46b339f21e5f479634371ece Mon Sep 17 00:00:00 2001 From: gjr80 Date: Wed, 27 Nov 2019 22:17:26 +1000 Subject: [PATCH 06/13] Working under WeeWX 4.0 python3 Bumped version to 2.0.0 --- bin/user/bloomsky.py | 138 ++++++++++++++----------------------------- 1 file changed, 44 insertions(+), 94 deletions(-) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index 183dc6c..2083f8c 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python """ bloomsky.py @@ -18,9 +18,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. -Version: 1.0.1 Date: 23 June 2019 +Version: 2.0.0 Date: 27 November 2019 Revision History + 27 November 2019 v2.0.0 + - WeeWX v4 python2/3 compatible 23 June 2019 v1.0.1 - additional exception handling to handle a malformed API response 31 May 2019 v1.0.0 @@ -159,8 +161,7 @@ import threading import time -# python 2/3 compatibility shims -import six +# Python 2/3 compatibility shims from six import iteritems from six.moves import queue from six.moves import urllib @@ -177,7 +178,7 @@ log = logging.getLogger(__name__) DRIVER_NAME = 'Bloomsky' -DRIVER_VERSION = "1.0.0" +DRIVER_VERSION = "2.0.0" def loader(config_dict, engine): @@ -223,28 +224,6 @@ def prompt_for_settings(self): class BloomskyDriver(weewx.drivers.AbstractDevice): """BloomSky driver class.""" - # map from BloomSky API field names to WeeWX db schema names - DEFAULT_SENSOR_MAP = {'deviceID': 'DeviceID', - 'deviceName': 'DeviceName', - 'Data': {'outTemp': 'Temperature', - 'txBatteryStatus': 'Voltage', - 'UV': 'UVIndex', - 'outHumidity': 'Humidity', - 'imageURL': 'ImageURL', - 'deviceType': 'DeviceType', - 'barometer': 'Pressure', - 'luminance': 'Luminance', - 'raining': 'Rain', - 'night': 'Night', - 'imageTimestamp': 'ImageTS'}, - 'Point': {'inTemp': 'Temperature', - 'inHumidity': 'Humidity'}, - 'Storm': {'rainRate': 'RainRate', - 'windSpeed': 'SustainedWindSpeed', - 'windDir': 'WindDirection', - 'windGust': 'WindGust', - 'rainDaily': 'RainDaily'}} - # Sane default map from Bloomsky API field names to weeWX db schema names # that will work in most cases (ie single Sky and/or Storm). Accounts with # multiple DeviceIDs (ie more than one Sky or more than one Storm) will @@ -272,18 +251,20 @@ class BloomskyDriver(weewx.drivers.AbstractDevice): DEFAULT_DELTAS = {'rain': 'rainDaily'} def __init__(self, **stn_dict): + # log the driver version log.info('driver version is %s' % DRIVER_VERSION) + + # obtain the sensor map self.sensor_map = dict(BloomskyDriver.DEFAULT_SENSOR_MAP) if 'sensor_map' in stn_dict: self.sensor_map.update(stn_dict['sensor_map']) log.info('sensor map is %s' % self.sensor_map) - # number of time to try and get a response from the BloomSky API # get the deltas self.deltas = stn_dict.get('deltas', BloomskyDriver.DEFAULT_DELTAS) log.info('deltas is %s' % self.deltas) - # number of time to try and get a response from the Bloomsky API + # number of times to try and get a response from the Bloomsky API max_tries = int(stn_dict.get('max_tries', 3)) # wait time in seconds between retries retry_wait = int(stn_dict.get('retry_wait', 10)) @@ -321,7 +302,7 @@ def hardware_name(self): @property def ids(self): - """Return the Bloomsky device IDs.""" + """Return the BloomSky device IDs.""" raw_data = None # loop until we get some raw data @@ -349,7 +330,7 @@ def ids(self): return ids def genLoopPackets(self): - """Wait for BloomSky API from the ApiClient and yield a loop packets. + """Wait for data from the ApiClient and yield loop packets. Run a continuous loop checking the ApiClient queue for data. When data arrives map the raw data to a WeeWX loop packet and yield the packet. @@ -360,63 +341,26 @@ def genLoopPackets(self): try: # get any day from the collector queue raw_data = self.collector.queue.get(True, 10) - # create a loop packet - packet = self.data_to_packet(raw_data) - # log the packet but only if debug>=2 + # log the raw data bt only if debug>=2 if weewx.debug >= 2: - log.debug('Packet: %s' % packet) - # if we did get a packet then yield it for processing + log.debug('Raw data: %s' % raw_data) + # create a loop packet and initialise with dateTime and usUnits + packet = {'dateTime': int(time.time() + 0.5), + 'usUnits': weewx.METRICWX + } + self.map_to_fields(packet, raw_data) if packet: + # calculate the packet rain value + self.calculate_rain(packet) + # log the packet but only if debug>=2 + if weewx.debug >= 2: + log.debug('Packet: %s' % packet) + # yield the packet for processing yield packet except queue.Empty: # there was nothing in the queue so continue pass - def data_to_packet(self, data): - """Convert BloomSky data to WeeWX packet format. - - The Collector provides BloomSky API response in the form of a dict that - may contain nested dicts of data. Fist map the BloomSky data to a flat - WeeWX data packet then add a packet timestamp and unit system fields. - Finally calculate rain since last packet. - - Input: - data: BloomSky API response in dict format - - Returns: - A WeeWX loop packet - """ - - # map the BloomSky API data to a WeeWX loop packet - packet = self._map_fields(data, self.sensor_map) - # add dateTime and usUnits fields - packet['dateTime'] = int(time.time() + 0.5) - # we ask the BloomSky API data for international units which gives us - # data conforming to the METRICWX unit system - packet['usUnits'] = weewx.METRICWX - # BloomSky reports 2 rainfall fields, RainDaily and 24hRain; the - # rainfall since midnight and the rainfall in the last 24 hours - # respectively. Therefore we need to calculate the incremental rain - # since the last packet using the RainDaily field (which was translated - # to the WeeWX dailyRain field). We will see a decrement at midnight - # when the counter is reset, this may cause issues if it is raining at - # the time but there is little that can be done. - if 'rainDaily' in packet: - # get the rain so far today - total = packet['rainDaily'] - # have we seen a daily rain reset? - if (total is not None and self.last_rain is not None - and total < self.last_rain): - # yes we have, just log it - log.info("dailyRain decrement ignored:" - " new: %s old: %s" % (total, self.last_rain)) - # calculate the rainfall since the last packet - packet['rain'] = weewx.wxformulas.calculate_rain(total, - self.last_rain) - # adjust our last rain total - self.last_rain = total - return packet - def map_to_fields(self, packet, raw_data): """Map Bloomsky raw data to weeWX packet fields. @@ -424,6 +368,7 @@ def map_to_fields(self, packet, raw_data): may contain nested dicts of data. Fist map the BloomSky data to a flat WeeWX data packet then add a packet timestamp and unit system fields. Finally calculate rain since last packet. + Bloomsky API response is provided as a list of dicts with one dict per device ID. Iterate over each sensor map entry adding sensor data to the packet. @@ -510,7 +455,7 @@ def get_sensor_value(data, sensor_pattern): for device in data: # do we have a device ID specifier or just a field specifier if len(parts) > 1: - if BloomskyDriver._match(parts[0], device['DeviceID']): + if BloomskyDriver._match(device['DeviceID'], parts[0]): element = BloomskyDriver._find_in_device(device, parts[1]) if element: return element @@ -754,7 +699,6 @@ def collect_data(self): # sleep and see if its time to poll again time.sleep(1) - @staticmethod def extract_data(data): """Extract the data of interest. @@ -1034,7 +978,6 @@ def get_request(url, params, headers): def main(): import optparse import weecfg - import weeutil.logger import weewx weeutil.logger.setup('bloomsky', {}) @@ -1043,9 +986,9 @@ def main(): parser.add_option('--version', dest='version', action='store_true', help='display BloomSky driver version number') parser.add_option('--config', dest='config_path', metavar='CONFIG_FILE', - help="Use configuration file CONFIG_FILE.") - parser.add_option('--debug', dest='debug', action='store_true', - help='display diagnostic information while running') + help="use configuration file CONFIG_FILE.") + parser.add_option('--debug', dest='debug', metavar='DEBUG', + help='use WeeWX debug level DEBUG') parser.add_option('--run-driver', dest='run_driver', action='store_true', metavar='RUN_DRIVER', help='run the BloomSky driver') parser.add_option('--api-key', dest='api_key', metavar='API_KEY', @@ -1056,10 +999,6 @@ def main(): help='get BloomSky device IDs associated with an API key') (opts, args) = parser.parse_args() - # if --debug raise our log level - if opts.debug: - weewx.debug = 1 - # display driver version number if opts.version: print("%s driver version: %s" % (DRIVER_NAME, DRIVER_VERSION)) @@ -1068,6 +1007,20 @@ def main(): # get config_dict to use config_path, config_dict = weecfg.read_config(opts.config_path, args) print("Using configuration file %s" % config_path) + + # Now we can set up the user customized logging but first override the + # debug level if one was provide on the command line + if opts.debug: + weewx.debug = int(opts.debug) + config_dict['debug'] = weewx.debug + else: + # if no --debug force debug=0 + weewx.debug = 0 + weeutil.logger.setup('bloomsky', config_dict) + if opts.debug: + log.debug("Debug is %s", weewx.debug) + + # get a station config dict stn_dict = config_dict.get('Bloomsky', {}) # do we have a specific API key to use @@ -1109,8 +1062,6 @@ def get_ids(stn_dict): def run_driver(stn_dict): """Run the BloomSky driver.""" - import weeutil.weeutil - # wrap in a try..except so we can pickup a keyboard interrupt try: # get a BloomskyDriver object @@ -1144,4 +1095,3 @@ def get_json_data(stn_dict): exit(1) main() - From a265c451cc0065fdf0258510574c55a356bda913 Mon Sep 17 00:00:00 2001 From: gjr80 Date: Wed, 27 Nov 2019 22:24:20 +1000 Subject: [PATCH 07/13] Bumped version to 2.0.0 --- install.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/install.py b/install.py index 6f471d6..e54d2f2 100644 --- a/install.py +++ b/install.py @@ -10,9 +10,11 @@ Installer for Bloomsky Driver -Version: 1.0.0 Date: 31 May 2019 +Version: 2.0.0 Date: 27 November 2019 Revision History + 27 November 2019 v2.0.0 + - now WeeWX v4.0 python2/3 compatible 31 May 2019 v1.0.0 - bump version number only 29 May 2019 v0.1.1 @@ -30,8 +32,8 @@ from distutils.version import StrictVersion from setup import ExtensionInstaller -REQUIRED_VERSION = "3.7.0" -BLOOMSKY_VERSION = "1.0.0" +REQUIRED_VERSION = "4.0.0a" +BLOOMSKY_VERSION = "2.0.0" def loader(): @@ -48,7 +50,7 @@ def __init__(self): super(BloomskyInstaller, self).__init__( version=BLOOMSKY_VERSION, name='Bloomsky', - description='weeWX driver for Bloomsky Sky1/Sky2/Storm personal weather stations.', + description='WeeWX driver for Bloomsky Sky1/Sky2/Storm personal weather stations.', author="Gary Roderick", author_email="gjroderick<@>gmail.com", files=[('bin/user', ['bin/user/bloomsky.py'])], From 37562c4b541323e6e27930c735630d927649b280 Mon Sep 17 00:00:00 2001 From: gjr80 Date: Thu, 28 Nov 2019 08:04:41 +1000 Subject: [PATCH 08/13] Comment updates --- bin/user/bloomsky.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index 2083f8c..b3e2d94 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -30,6 +30,8 @@ 29 May 2019 v0.1.1 - added missing barometer, luminance and raining fields to default sensor map + - changed default poll interval to 60 seconds (as advertised) + - revised driver comments 25 June 2017 v0.1.0 - initial release From 38a443f28677449c6b3344f6413eef26329737cd Mon Sep 17 00:00:00 2001 From: gjr80 Date: Thu, 28 Nov 2019 08:05:27 +1000 Subject: [PATCH 09/13] Added noop adder for raining and comment updates --- install.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/install.py b/install.py index e54d2f2..03f3170 100644 --- a/install.py +++ b/install.py @@ -15,6 +15,7 @@ Revision History 27 November 2019 v2.0.0 - now WeeWX v4.0 python2/3 compatible + - added accumulator noop adder for field raining 31 May 2019 v1.0.0 - bump version number only 29 May 2019 v0.1.1 @@ -78,6 +79,9 @@ def __init__(self): }, 'imageTimestamp': { 'adder': 'noop' + }, + 'raining': { + 'adder': 'noop' } } } From 6420e77b7778a1578f6443bf8a940bdfb3efcd3e Mon Sep 17 00:00:00 2001 From: gjr80 Date: Mon, 27 Jul 2020 13:53:12 +1000 Subject: [PATCH 10/13] - rework logging setup to be WeeWX 3/4 compatible - rework optparse menu - restructure main() --- bin/user/bloomsky.py | 228 ++++++++++++++++++++++++++++--------------- 1 file changed, 151 insertions(+), 77 deletions(-) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index b3e2d94..266580e 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -172,13 +172,60 @@ # WeeWX imports import weeutil -import weeutil.logger +# import weeutil.logger import weewx.drivers import weewx.wxformulas # obtain a logger object log = logging.getLogger(__name__) +# import/setup logging, WeeWX v3 is syslog based but WeeWX v4 is logging based, +# try v4 logging and if it fails use v3 logging +try: + # WeeWX4 logging + import logging + from weeutil.logger import log_traceback + log = logging.getLogger("%s: %s" % ('gw1000', __name__)) + + def logdbg(msg): + log.debug(msg) + + def loginf(msg): + log.info(msg) + + def logerr(msg): + log.error(msg) + + # log_traceback() generates the same output but the signature and code is + # different between v3 and v4. We only need log_traceback at the log.info + # level so define a suitable wrapper function. + def log_traceback_info(prefix=''): + log_traceback(log.info, prefix=prefix) + +except ImportError: + # WeeWX legacy (v3) logging via syslog + import syslog + from weeutil.weeutil import log_traceback + + def logmsg(level, msg): + syslog.syslog(level, 'gw1000: %s' % msg) + + def logdbg(msg): + logmsg(syslog.LOG_DEBUG, msg) + + def loginf(msg): + logmsg(syslog.LOG_INFO, msg) + + def logerr(msg): + logmsg(syslog.LOG_ERR, msg) + + # log_traceback() generates the same output but the signature and code is + # different between v3 and v4. We only need log_traceback at the log.info + # level so define a suitable wrapper function. + def log_traceback_info(prefix=''): + log_traceback(prefix=prefix, loglevel=syslog.LOG_INFO) + + DRIVER_NAME = 'Bloomsky' DRIVER_VERSION = "2.0.0" @@ -963,88 +1010,30 @@ def get_request(url, params, headers): """ To use this driver in standalone mode for testing or development, use one of -the following commands (depending on your WeeWX install): +the following commands (depending on your WeeWX install). For setup.py installs +use: - $ PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/bloomsky.py + $ PYTHONPATH=/home/weewx/bin python -m user.bloomsky - or + or for package installs use: - $ PYTHONPATH=/usr/share/weewx python /usr/share/weewx/user/bloomsky.py + $ PYTHONPATH=/usr/share/weewx python -m user.bloomsky - The above commands will display details of available command line options. +The above commands will display details of available command line options. + +Note. Whilst the driver may be run independently of WeeWX the driver still +requires WeeWX and it's dependencies be installed. Consequently, if WeeWX 4.0.0 +or later is installed the driver must be run under the same Python version as +WeeWX uses. This means that on some systems 'python' in the above commands may +need to be changed to 'python2' or 'python3'. """ -if __name__ == "__main__": - usage = """%prog [options] [--help]""" - - def main(): - import optparse - import weecfg - import weewx - - weeutil.logger.setup('bloomsky', {}) - - parser = optparse.OptionParser(usage=usage) - parser.add_option('--version', dest='version', action='store_true', - help='display BloomSky driver version number') - parser.add_option('--config', dest='config_path', metavar='CONFIG_FILE', - help="use configuration file CONFIG_FILE.") - parser.add_option('--debug', dest='debug', metavar='DEBUG', - help='use WeeWX debug level DEBUG') - parser.add_option('--run-driver', dest='run_driver', action='store_true', - metavar='RUN_DRIVER', help='run the BloomSky driver') - parser.add_option('--api-key', dest='api_key', metavar='API_KEY', - help='BloomSky API key') - parser.add_option('--get-json-data', dest='jdata', action='store_true', - help='get BloomSky API json response') - parser.add_option('--get-deviceids', dest='get_ids', action='store_true', - help='get BloomSky device IDs associated with an API key') - (opts, args) = parser.parse_args() - - # display driver version number - if opts.version: - print("%s driver version: %s" % (DRIVER_NAME, DRIVER_VERSION)) - exit(0) - # get config_dict to use - config_path, config_dict = weecfg.read_config(opts.config_path, args) - print("Using configuration file %s" % config_path) +def main(): - # Now we can set up the user customized logging but first override the - # debug level if one was provide on the command line - if opts.debug: - weewx.debug = int(opts.debug) - config_dict['debug'] = weewx.debug - else: - # if no --debug force debug=0 - weewx.debug = 0 - weeutil.logger.setup('bloomsky', config_dict) - if opts.debug: - log.debug("Debug is %s", weewx.debug) - - # get a station config dict - stn_dict = config_dict.get('Bloomsky', {}) - - # do we have a specific API key to use - if opts.api_key: - stn_dict['api_key'] = opts.api_key - print("Using BloomSky API key %s" % opts.api_key) - - # display device IDs - if opts.get_ids: - get_ids(stn_dict) - - # run the driver - if opts.run_driver: - run_driver(stn_dict) - - # get BloomSky API JSON response - if opts.jdata: - get_json_data(stn_dict) - - # otherwise print our help - parser.print_help() - exit(0) + import optparse + import weecfg + import weewx def get_ids(stn_dict): """Display BloomSky device IDs associated with an API key.""" @@ -1061,8 +1050,8 @@ def get_ids(stn_dict): driver.closePort() exit(0) - def run_driver(stn_dict): - """Run the BloomSky driver.""" + def test_driver(stn_dict): + """Test the BloomSky driver.""" # wrap in a try..except so we can pickup a keyboard interrupt try: @@ -1096,4 +1085,89 @@ def get_json_data(stn_dict): print("Exiting.") exit(1) + usage = """Usage: python -m user.bloomsky --help + python -m user.bloomsky --version + python -m user.bloomsky --test-driver + [CONFIG_FILE|--config=CONFIG_FILE] + [--api-key=API_KEY] + [--debug=0|1|2|3] + python -m user.bloomsky --get-json-data + [CONFIG_FILE|--config=CONFIG_FILE] + [--api-key=API_KEY] + [--debug=0|1|2|3] + python -m user.bloomsky --get-deviceids + [CONFIG_FILE|--config=CONFIG_FILE] + [--api-key=API_KEY] + [--debug=0|1|2|3]""" + + parser = optparse.OptionParser(usage=usage) + parser.add_option('--version', dest='version', action='store_true', + help='display BloomSky driver version number') + parser.add_option('--config', dest='config_path', metavar='CONFIG_FILE', + help="use configuration file CONFIG_FILE.") + parser.add_option('--debug', dest='debug', metavar='DEBUG', + help='use WeeWX debug level DEBUG') + parser.add_option('--test-driver', dest='test_driver', action='store_true', + metavar='TEST_DRIVER', help='test the BloomSky driver') + parser.add_option('--api-key', dest='api_key', metavar='API_KEY', + help='BloomSky API key') + parser.add_option('--get-json-data', dest='jdata', action='store_true', + help='get BloomSky API json response') + parser.add_option('--get-deviceids', dest='get_ids', action='store_true', + help='get BloomSky device IDs associated with an API key') + (opts, args) = parser.parse_args() + + # get config_dict to use + config_path, config_dict = weecfg.read_config(opts.config_path, args) + print("Using configuration file %s" % config_path) + stn_dict = config_dict.get('Bloomsky', {}) + + # set weewx.debug as necessary + if opts.debug is not None: + _debug = weeutil.weeutil.to_int(opts.debug) + else: + _debug = weeutil.weeutil.to_int(config_dict.get('debug', 0)) + weewx.debug = _debug + + # Now we can set up the user customized logging but we need to handle both + # v3 and v4 logging. V4 logging is very easy but v3 logging requires us to + # set up syslog and raise our log level based on weewx.debug + try: + # assume v 4 logging + weeutil.logger.setup('weewx', config_dict) + except AttributeError: + # must be v3 logging, so first set the defaults for the system logger + syslog.openlog('weewx', syslog.LOG_PID | syslog.LOG_CONS) + # now raise the log level if required + if weewx.debug > 0: + syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG)) + + # display driver version number + if opts.version: + print("%s driver version: %s" % (DRIVER_NAME, DRIVER_VERSION)) + exit(0) + + # do we have a specific API key to use + if opts.api_key: + stn_dict['api_key'] = opts.api_key + print("Using BloomSky API key %s" % opts.api_key) + + # display device IDs + if opts.get_ids: + get_ids(stn_dict) + + # run the driver + if opts.test_driver: + test_driver(stn_dict) + + # get BloomSky API JSON response + if opts.jdata: + get_json_data(stn_dict) + + # otherwise print our help + parser.print_help() + exit(0) + + +if __name__ == "__main__": main() From 861a125207745d71fc1544ea709888b77ac8b6e4 Mon Sep 17 00:00:00 2001 From: gjr80 Date: Mon, 27 Jul 2020 15:10:21 +1000 Subject: [PATCH 11/13] v2.0.0b1 - updated comments/instructions - fixed issue in main() where code did not exit cleanly - better handle HTTP 505 errors --- bin/user/bloomsky.py | 176 +++++++++++++++++++++++++------------------ 1 file changed, 103 insertions(+), 73 deletions(-) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index 266580e..02212dc 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -4,7 +4,7 @@ A WeeWX driver for the BloomSky family of personal weather devices. -Copyright (C) 2017-19 Gary Roderick gjroderickgmail.com +Copyright (C) 2017-20 Gary Roderick gjroderickgmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -18,11 +18,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. -Version: 2.0.0 Date: 27 November 2019 +Version: 2.0.0b1 Date: 27 July 2020 Revision History - 27 November 2019 v2.0.0 + 27 July 2020 v2.0.0 - WeeWX v4 python2/3 compatible + - restructured main() + - restructured options menu when running directly 23 June 2019 v1.0.1 - additional exception handling to handle a malformed API response 31 May 2019 v1.0.0 @@ -138,12 +140,15 @@ 6. Before running WeeWX with the BloomSky driver you may wish to run the driver from the command line to ensure correct operation. To run the driver from the command line enter one of the the following commands depending on your WeeWX -installation type: +installation type, for setup.py: - setup.py install: - $ PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/bloomsky.py --run-driver --api-key=INSERT_API_KEY + $ PYTHONPATH=/home/weewx/bin python -m user.bloomsky --test-driver -7. If WeeWX is running stop then start WeeWX otherwise start WeeWX. + or for package installs use: + + $ PYTHONPATH=/usr/share/weewx python -m user.bloomsky --test-driver + +7. If WeeWX is running restart WeeWX otherwise start WeeWX. Support for Multiple Device IDs @@ -185,7 +190,7 @@ # WeeWX4 logging import logging from weeutil.logger import log_traceback - log = logging.getLogger("%s: %s" % ('gw1000', __name__)) + log = logging.getLogger("%s: %s" % ('bloomsky', __name__)) def logdbg(msg): log.debug(msg) @@ -227,7 +232,7 @@ def log_traceback_info(prefix=''): DRIVER_NAME = 'Bloomsky' -DRIVER_VERSION = "2.0.0" +DRIVER_VERSION = "2.0.0b1" def loader(config_dict, engine): @@ -353,29 +358,31 @@ def hardware_name(self): def ids(self): """Return the BloomSky device IDs.""" - raw_data = None + got_data = False # loop until we get some raw data - while raw_data is None: + while not got_data: # wrap in try..except so we can catch the empty queue error try: # get any data from the collector queue raw_data = self.collector.queue.get(True, 10) + got_data = True except queue.Empty: # there was nothing in the queue so continue pass # The raw data will be a list of dicts where each dict is the data for - # a particular device ID. Each dict should have a DeviceID field - # containing the device ID. + # a particular device ID or it could be None. If a dict each should + # have a DeviceID field containing the device ID. # initialise our response ids = [] - # iterate over each of the device dicts - for device in raw_data: - # append the device ID to our response, be prepared to catch the - # case where there is no DeviceID field - try: - ids.append(device['DeviceID']) - except KeyError: - pass + if raw_data is not None: + # iterate over each of the device dicts + for device in raw_data: + # append the device ID to our response, be prepared to catch the + # case where there is no DeviceID field + try: + ids.append(device['DeviceID']) + except KeyError: + pass return ids def genLoopPackets(self): @@ -496,27 +503,29 @@ def get_sensor_value(data, sensor_pattern): # initialise our response, assume nothing found to start with element = None - # Need the device ID portion of the sensor pattern so we know when we - # have a matching device ID. Split the sensor pattern into device ID - # portion and the rest. - parts = sensor_pattern.split('.', 1) - # iterate over each device ID dict in our data - for device in data: - # do we have a device ID specifier or just a field specifier - if len(parts) > 1: - if BloomskyDriver._match(device['DeviceID'], parts[0]): - element = BloomskyDriver._find_in_device(device, parts[1]) - if element: - return element - else: - # The sensor pattern is for a field only, that makes it simple, - # just look for the field at the top level of the dict. - # Iterate over the top level items in the dict looking for a - # matching field. If the match happens to be a dict then ignore - # it as our sensor pattern does not support that. - for (k, v) in iteritems(device): - if sensor_pattern == k and not hasattr(k, 'keys'): - element = v + # do we have any data + if data is not None: + # Need the device ID portion of the sensor pattern so we know when we + # have a matching device ID. Split the sensor pattern into device ID + # portion and the rest. + parts = sensor_pattern.split('.', 1) + # iterate over each device ID dict in our data + for device in data: + # do we have a device ID specifier or just a field specifier + if len(parts) > 1: + if BloomskyDriver._match(device['DeviceID'], parts[0]): + element = BloomskyDriver._find_in_device(device, parts[1]) + if element: + return element + else: + # The sensor pattern is for a field only, that makes it simple, + # just look for the field at the top level of the dict. + # Iterate over the top level items in the dict looking for a + # matching field. If the match happens to be a dict then ignore + # it as our sensor pattern does not support that. + for (k, v) in iteritems(device): + if sensor_pattern == k and not hasattr(k, 'keys'): + element = v return element @staticmethod @@ -713,13 +722,17 @@ def collect_data(self): try: # get the raw JSON API response raw_data = self.sd.get_data() - # extract the data we want from the JSON response, do - # any manipulation/translation and return as a list of - # dicts - data = ApiClient.extract_data(raw_data) - # log the extracted data for debug purposes - if weewx.debug >= 3: - log.debug("Extracted data: %s" % data) + # do we have any data + if raw_data is not None: + # extract the data we want from the JSON response, do + # any manipulation/translation and return as a list of + # dicts + data = ApiClient.extract_data(raw_data) + # log the extracted data for debug purposes + if weewx.debug >= 3: + log.debug("Extracted data: %s" % data) + else: + data = None # put the data in the queue Collector.queue.put(data) # we are done so break out of the for loop @@ -938,11 +951,19 @@ def get_data(self, units='intl'): params = {} if units == 'intl': params['unit'] = units - # make the request and get the returned data - resp_json = ApiClient.get_request(ApiClient.API_URL, - params, headers) - self._last_update = int(time.time()) - return resp_json + # wrap the request in a try..except in case there is an error + try: + # make the request + resp_json = ApiClient.get_request(ApiClient.API_URL, + params, headers) + # update the time of last update + self._last_update = int(time.time()) + except (URLError, socket.timeout): + # we couldn't get any data so return None + return None + else: + # return the response + return resp_json @staticmethod def get_request(url, params, headers): @@ -997,15 +1018,19 @@ def get_request(url, params, headers): resp = w.read().decode() w.close() except (URLError, socket.timeout) as e: + # log the error log.error("Failed to get BloomSky API data") log.error(" **** %s" % e) - # convert the response to a JSON object - resp_json = json.loads(resp) - # log response as required - if weewx.debug >= 3: - log.debug("JSON API response: %s" % json.dumps(resp_json)) - # return the JSON object - return resp_json + # and raise it + raise + else: + # convert the response to a JSON object + resp_json = json.loads(resp) + # log response as required + if weewx.debug >= 3: + log.debug("JSON API response: %s" % json.dumps(resp_json)) + # return the JSON object + return resp_json """ @@ -1076,8 +1101,13 @@ def get_json_data(stn_dict): api_client = ApiClient(api_key=api_key) # get the JSON response raw_data = api_client.sd.get_data() - # display the JSON response on screen - print(json.dumps(raw_data, sort_keys=True, indent=2)) + # do we have any raw data? + if raw_data is not None: + # yes, display the JSON response on screen + print(json.dumps(raw_data, sort_keys=True, indent=2)) + else: + # no, display an appropriate message + print("Unable to obtain JSON data") exit(0) else: print("BloomSky API key required.") @@ -1089,16 +1119,13 @@ def get_json_data(stn_dict): python -m user.bloomsky --version python -m user.bloomsky --test-driver [CONFIG_FILE|--config=CONFIG_FILE] - [--api-key=API_KEY] - [--debug=0|1|2|3] + [--api-key=API_KEY] [--debug=0|1|2|3] python -m user.bloomsky --get-json-data [CONFIG_FILE|--config=CONFIG_FILE] - [--api-key=API_KEY] - [--debug=0|1|2|3] - python -m user.bloomsky --get-deviceids + [--api-key=API_KEY] [--debug=0|1|2|3] + python -m user.bloomsky --get-device-ids [CONFIG_FILE|--config=CONFIG_FILE] - [--api-key=API_KEY] - [--debug=0|1|2|3]""" + [--api-key=API_KEY] [--debug=0|1|2|3]""" parser = optparse.OptionParser(usage=usage) parser.add_option('--version', dest='version', action='store_true', @@ -1113,7 +1140,7 @@ def get_json_data(stn_dict): help='BloomSky API key') parser.add_option('--get-json-data', dest='jdata', action='store_true', help='get BloomSky API json response') - parser.add_option('--get-deviceids', dest='get_ids', action='store_true', + parser.add_option('--get-device-ids', dest='get_ids', action='store_true', help='get BloomSky device IDs associated with an API key') (opts, args) = parser.parse_args() @@ -1150,23 +1177,26 @@ def get_json_data(stn_dict): # do we have a specific API key to use if opts.api_key: stn_dict['api_key'] = opts.api_key - print("Using BloomSky API key %s" % opts.api_key) + obfuscated = ''.join(('"....', opts.api_key[-4:], '"')) + print("Using BloomSky API key %s" % obfuscated) # display device IDs if opts.get_ids: get_ids(stn_dict) + exit(0) # run the driver if opts.test_driver: test_driver(stn_dict) + exit(0) # get BloomSky API JSON response if opts.jdata: get_json_data(stn_dict) + exit(0) # otherwise print our help parser.print_help() - exit(0) if __name__ == "__main__": From 928414fcd286cb1e13ebb24f554626aaa70ae0f0 Mon Sep 17 00:00:00 2001 From: gjr80 Date: Mon, 27 Jul 2020 15:15:04 +1000 Subject: [PATCH 12/13] v2.0.0b1 --- install.py | 8 ++++---- readme.md | 22 ++++++++++------------ readme.txt | 16 ++++++++-------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/install.py b/install.py index 03f3170..e3bb25e 100644 --- a/install.py +++ b/install.py @@ -10,10 +10,10 @@ Installer for Bloomsky Driver -Version: 2.0.0 Date: 27 November 2019 +Version: 2.0.0b1 Date: 27 July 2020 Revision History - 27 November 2019 v2.0.0 + 27 July 2020 v2.0.0 - now WeeWX v4.0 python2/3 compatible - added accumulator noop adder for field raining 31 May 2019 v1.0.0 @@ -33,8 +33,8 @@ from distutils.version import StrictVersion from setup import ExtensionInstaller -REQUIRED_VERSION = "4.0.0a" -BLOOMSKY_VERSION = "2.0.0" +REQUIRED_VERSION = "3.7.0" +BLOOMSKY_VERSION = "2.0.0b1" def loader(): diff --git a/readme.md b/readme.md index 3d01bc0..1df98e5 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ The BloomSky driver requires WeeWX v3.7.0 or greater. A BloomSky API key is also 1. Download the latest BloomSky driver extension from the [Bloomsky driver releases page](https://github.com/gjr80/weewx-bloomsky/releases) into a directory accessible from the WeeWX machine. - $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v1.0.0/bloomsky-1.0.0.tar.gz + $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0b1.tar.gz where *$DOWNLOAD_ROOT* is the path to the directory where the BloomSky driver extension is to be downloaded. @@ -38,15 +38,15 @@ The BloomSky driver requires WeeWX v3.7.0 or greater. A BloomSky API key is also 1. Install the BloomSky driver extension downloaded at step 1 using the *wee_extension* utility: - $ wee_extension --install=$DOWNLOAD_ROOT/bloomsky-1.0.0.tar.gz + $ wee_extension --install=$DOWNLOAD_ROOT/bloomsky-2.0.0b1.tar.gz This will result in output similar to the following: - Request to install '/var/tmp/bloomsky-1.0.0.tar.gz' - Extracting from tar archive /var/tmp/bloomsky-1.0.0.tar.gz + Request to install '/var/tmp/bloomsky-2.0.0b1.tar.gz' + Extracting from tar archive /var/tmp/bloomsky-2.0.0b1.tar.gz Saving installer file to /home/weewx/bin/user/installer/Bloomsky - Saved configuration dictionary. Backup copy at /home/weewx/weewx.conf.20190523124410 - Finished installing extension '/var/tmp/bloomsky-1.0.0.tar.gz' + Saved configuration dictionary. Backup copy at /home/weewx/weewx.conf.20200523124410 + Finished installing extension '/var/tmp/bloomsky-2.0.0b1.tar.gz' 1. Select and configure the driver: @@ -84,20 +84,19 @@ The BloomSky driver requires WeeWX v3.7.0 or greater. A BloomSky API key is also $ sudo systemctl start weewx -This will result in the driver collecting data from the bloomsky API and emitting loop packets. The WeeWX log may be monitored to confirm operation. The BloomSky driver installation can be further customized (eg sensor mapping, -polling interval etc) by referring to the BloomSky driver extension wiki or the comments at the start of the BloomSky driver file *$BIN_ROOT/user/bloomsky.py*. +This will result in the driver collecting data from the bloomsky API and emitting loop packets. The WeeWX log may be monitored to confirm operation. The BloomSky driver installation can be further customized (eg sensor mapping, polling interval etc) by referring to the BloomSky driver extension wiki or the comments at the start of the BloomSky driver file *$BIN_ROOT/user/bloomsky.py*. ### Manual installation ### 1. Download the latest BloomSky driver extension from the [Bloomsky driver releases page](https://github.com/gjr80/weewx-bloomsky/releases) into a directory accessible from the WeeWX machine. - $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v1.0.0/bloomsky-1.0.0.tar.gz + $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0b1.tar.gz where *$DOWNLOAD_ROOT* is the path to the directory where the BloomSky driver extension is to be downloaded. 1. Unpack the extension as follows: - $ tar xvfz bloomsky-1.0.0.tar.gz + $ tar xvfz bloomsky-2.0.0b1.tar.gz 1. Copy the *bloomsky.py* file from within the resulting folder as follows: @@ -163,5 +162,4 @@ polling interval etc) by referring to the BloomSky driver extension wiki or the $ sudo systemctl start weewx -This will result in the driver collecting data from the bloomsky API and emitting loop packets. The WeeWX log may be monitored to confirm operation. The BloomSky driver installation can be further customized (eg sensor mapping, -polling interval etc) by referring to the BloomSky driver extension wiki or the comments at the start of the BloomSky driver file *$BIN_ROOT/user/bloomsky.py*. +This will result in the driver collecting data from the bloomsky API and emitting loop packets. The WeeWX log may be monitored to confirm operation. The BloomSky driver installation can be further customized (eg sensor mapping, polling interval etc) by referring to the BloomSky driver extension wiki or the comments at the start of the BloomSky driver file *$BIN_ROOT/user/bloomsky.py*. diff --git a/readme.txt b/readme.txt index 9a8c2fd..30f0ce1 100644 --- a/readme.txt +++ b/readme.txt @@ -29,7 +29,7 @@ names are used below: releases page (https://github.com/gjr80/weewx-bloomsky/releases) into a directory accessible from the WeeWX machine. - $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v1.0.0/bloomsky-1.0.0.tar.gz + $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0b1.tar.gz where $DOWNLOAD_ROOT is the path to the directory where the BloomSky driver data extension is to be downloaded. @@ -49,15 +49,15 @@ a directory accessible from the WeeWX machine. 3. Install the BloomSky driver extension downloaded at step 1 using the *wee_extension* utility: - $ wee_extension --install=$DOWNLOAD_ROOT/bloomsky-1.0.0.tar.gz + $ wee_extension --install=$DOWNLOAD_ROOT/bloomsky-2.0.0b1.tar.gz This will result in output similar to the following: - Request to install '/var/tmp/bloomsky-1.0.0.tar.gz' - Extracting from tar archive /var/tmp/bloomsky-1.0.0.tar.gz + Request to install '/var/tmp/bloomsky-2.0.0b1.tar.gz' + Extracting from tar archive /var/tmp/bloomsky-2.0.0b1.tar.gz Saving installer file to /home/weewx/bin/user/installer/Bloomsky - Saved configuration dictionary. Backup copy at /home/weewx/weewx.conf.20190523124410 - Finished installing extension '/var/tmp/bloomsky-1.0.0.tar.gz' + Saved configuration dictionary. Backup copy at /home/weewx/weewx.conf.20200523124410 + Finished installing extension '/var/tmp/bloomsky-2.0.0b1.tar.gz' 4. Select and configure the driver: @@ -106,14 +106,14 @@ Manual installation releases page (https://github.com/gjr80/weewx-bloomsky/releases) into a directory accessible from the WeeWX machine. - $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v1.0.0/bloomsky-1.0.0.tar.gz + $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0b1.tar.gz where $DOWNLOAD_ROOT is the path to the directory where the BloomSky driver data extension is to be downloaded. 2. Unpack the extension as follows: - $ tar xvfz bloomsky-1.0.0.tar.gz + $ tar xvfz bloomsky-2.0.0b1.tar.gz 3. Copy the bloomsky.py file from within the resulting folder as follows: From f64270588a0f153b8c83c08de496b7d12850a4d7 Mon Sep 17 00:00:00 2001 From: gjr80 Date: Mon, 3 Aug 2020 10:54:56 +1000 Subject: [PATCH 13/13] prep for v2.0.0 release --- bin/user/bloomsky.py | 6 +++--- changelog | 5 +++++ install.py | 8 ++++---- readme.md | 16 ++++++++-------- readme.txt | 19 ++++++++++--------- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/bin/user/bloomsky.py b/bin/user/bloomsky.py index 02212dc..d967039 100644 --- a/bin/user/bloomsky.py +++ b/bin/user/bloomsky.py @@ -18,10 +18,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. -Version: 2.0.0b1 Date: 27 July 2020 +Version: 2.0.0 Date: 3 August 2020 Revision History - 27 July 2020 v2.0.0 + 3 August 2020 v2.0.0 - WeeWX v4 python2/3 compatible - restructured main() - restructured options menu when running directly @@ -232,7 +232,7 @@ def log_traceback_info(prefix=''): DRIVER_NAME = 'Bloomsky' -DRIVER_VERSION = "2.0.0b1" +DRIVER_VERSION = "2.0.0" def loader(config_dict, engine): diff --git a/changelog b/changelog index 9d76743..248dd8b 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,8 @@ +v2.0.0 +* WeeWX 3.7+/4.x python2/3 compatible +* restructured main() +* restructured options menu when driver is run directly +* accumulator 'noop' adder now added for field 'raining' on install v1.0.0 * now works under python 2.6+ or python 3.5+ (python 2.5 no longer supported - use v0.1.1) diff --git a/install.py b/install.py index e3bb25e..5edf43d 100644 --- a/install.py +++ b/install.py @@ -10,11 +10,11 @@ Installer for Bloomsky Driver -Version: 2.0.0b1 Date: 27 July 2020 +Version: 2.0.0 Date: 3 August 2020 Revision History - 27 July 2020 v2.0.0 - - now WeeWX v4.0 python2/3 compatible + 27 August 2020 v2.0.0 + - now WeeWX 3.7+/4.x python2/3 compatible - added accumulator noop adder for field raining 31 May 2019 v1.0.0 - bump version number only @@ -34,7 +34,7 @@ from setup import ExtensionInstaller REQUIRED_VERSION = "3.7.0" -BLOOMSKY_VERSION = "2.0.0b1" +BLOOMSKY_VERSION = "2.0.0" def loader(): diff --git a/readme.md b/readme.md index 1df98e5..081e4d5 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ The BloomSky driver is a WeeWX driver that supports the BloomSky Sky1, Sky2 and ## Pre-Requisites ## -The BloomSky driver requires WeeWX v3.7.0 or greater. A BloomSky API key is also required and can be obtained from [dashboard.bloomsky.com](dashboard.bloomsky.com). +The BloomSky driver requires WeeWX v3.7.0 or greater. Both Python 2 and Python 3 are supported when using WeeWX v4.0.0 or later. A BloomSky API key is also required and can be obtained from [dashboard.bloomsky.com](dashboard.bloomsky.com). ## Installation Instructions ## @@ -20,7 +20,7 @@ The BloomSky driver requires WeeWX v3.7.0 or greater. A BloomSky API key is also 1. Download the latest BloomSky driver extension from the [Bloomsky driver releases page](https://github.com/gjr80/weewx-bloomsky/releases) into a directory accessible from the WeeWX machine. - $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0b1.tar.gz + $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0.tar.gz where *$DOWNLOAD_ROOT* is the path to the directory where the BloomSky driver extension is to be downloaded. @@ -38,15 +38,15 @@ The BloomSky driver requires WeeWX v3.7.0 or greater. A BloomSky API key is also 1. Install the BloomSky driver extension downloaded at step 1 using the *wee_extension* utility: - $ wee_extension --install=$DOWNLOAD_ROOT/bloomsky-2.0.0b1.tar.gz + $ wee_extension --install=$DOWNLOAD_ROOT/bloomsky-2.0.0.tar.gz This will result in output similar to the following: - Request to install '/var/tmp/bloomsky-2.0.0b1.tar.gz' - Extracting from tar archive /var/tmp/bloomsky-2.0.0b1.tar.gz + Request to install '/var/tmp/bloomsky-2.0.0.tar.gz' + Extracting from tar archive /var/tmp/bloomsky-2.0.0.tar.gz Saving installer file to /home/weewx/bin/user/installer/Bloomsky Saved configuration dictionary. Backup copy at /home/weewx/weewx.conf.20200523124410 - Finished installing extension '/var/tmp/bloomsky-2.0.0b1.tar.gz' + Finished installing extension '/var/tmp/bloomsky-2.0.0.tar.gz' 1. Select and configure the driver: @@ -90,13 +90,13 @@ This will result in the driver collecting data from the bloomsky API and emittin 1. Download the latest BloomSky driver extension from the [Bloomsky driver releases page](https://github.com/gjr80/weewx-bloomsky/releases) into a directory accessible from the WeeWX machine. - $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0b1.tar.gz + $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0.tar.gz where *$DOWNLOAD_ROOT* is the path to the directory where the BloomSky driver extension is to be downloaded. 1. Unpack the extension as follows: - $ tar xvfz bloomsky-2.0.0b1.tar.gz + $ tar xvfz bloomsky-2.0.0.tar.gz 1. Copy the *bloomsky.py* file from within the resulting folder as follows: diff --git a/readme.txt b/readme.txt index 30f0ce1..632c6ba 100644 --- a/readme.txt +++ b/readme.txt @@ -4,8 +4,9 @@ observation data from the BloomSky devices. Pre-Requisites -The BloomSky driver requires WeeWX v3.7.0 or greater. A BloomSky API key is -also required and can be obtained from dashboard.bloomsky.com. +The BloomSky driver requires WeeWX v3.7.0 or greater. Both Python 2 and Python 3 +are supported when using WeeWX v4.0.0 or later. A BloomSky API key is also +required and can be obtained from dashboard.bloomsky.com. Installation Instructions @@ -29,7 +30,7 @@ names are used below: releases page (https://github.com/gjr80/weewx-bloomsky/releases) into a directory accessible from the WeeWX machine. - $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0b1.tar.gz + $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0.tar.gz where $DOWNLOAD_ROOT is the path to the directory where the BloomSky driver data extension is to be downloaded. @@ -49,15 +50,15 @@ a directory accessible from the WeeWX machine. 3. Install the BloomSky driver extension downloaded at step 1 using the *wee_extension* utility: - $ wee_extension --install=$DOWNLOAD_ROOT/bloomsky-2.0.0b1.tar.gz + $ wee_extension --install=$DOWNLOAD_ROOT/bloomsky-2.0.0.tar.gz This will result in output similar to the following: - Request to install '/var/tmp/bloomsky-2.0.0b1.tar.gz' - Extracting from tar archive /var/tmp/bloomsky-2.0.0b1.tar.gz + Request to install '/var/tmp/bloomsky-2.0.0.tar.gz' + Extracting from tar archive /var/tmp/bloomsky-2.0.0.tar.gz Saving installer file to /home/weewx/bin/user/installer/Bloomsky Saved configuration dictionary. Backup copy at /home/weewx/weewx.conf.20200523124410 - Finished installing extension '/var/tmp/bloomsky-2.0.0b1.tar.gz' + Finished installing extension '/var/tmp/bloomsky-2.0.0.tar.gz' 4. Select and configure the driver: @@ -106,14 +107,14 @@ Manual installation releases page (https://github.com/gjr80/weewx-bloomsky/releases) into a directory accessible from the WeeWX machine. - $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0b1.tar.gz + $ wget -P $DOWNLOAD_ROOT https://github.com/gjr80/weewx-bloomsky/releases/download/v2.0.0/bloomsky-2.0.0.tar.gz where $DOWNLOAD_ROOT is the path to the directory where the BloomSky driver data extension is to be downloaded. 2. Unpack the extension as follows: - $ tar xvfz bloomsky-2.0.0b1.tar.gz + $ tar xvfz bloomsky-2.0.0.tar.gz 3. Copy the bloomsky.py file from within the resulting folder as follows: