On Tuesday, 2 April 2019 06:31:54 UTC-3, Hartmut Schweidler wrote:
>
> I use your files weatherCloud and Weather365 for Python3
> ? module are not loaded / callable
>

Hartmut,

I fixed the errors. Attached new versions of wcloud.py and weather365.py.

Luc 
# $Id: wcloud.py 1391 2015-12-28 14:51:29Z mwall $
# Copyright 2014 Matthew Wall

"""
This is a weewx extension that uploads data to WeatherCloud.

http://weather.weathercloud.com

Based on weathercloud API documentation v0.5 as of 15oct2014.

The preferred upload frequency (post_interval) is one record every 10 minutes.

These are the possible states for sensor values:
- sensor exists and returns valid value
- sensor exists and returns invalid value that passes StdQC
- sensor exists and returns None (e.g., windDir when windSpeed is zero)
- sensor exists but is not working
- sensor does not exist

Regarding None/NULL values, the folks at weathercloud say the following:

"In order to fix this issue, WeeWX should send our error code (-32768) instead
of omitting that variable in the frame. This way we know that the device is
able to measure the variable but that it's not currently working."

Minimal Configuration:

[StdRESTful]
    [[WeatherCloud]]
        id = WEATHERCLOUD_ID
        key = WEATHERCLOUD_KEY
"""
import re
import sys
import syslog
import time

# Python 2/3 compatiblity
try:
    import Queue as queue                    # python 2
    from urllib import urlencode            # python 2
    from urllib2 import Request                # python 2
except ImportError:
    import queue                            # python 3
    from urllib.parse import urlencode        # python 3
    from urllib.request import Request        # python 3

import weewx
import weewx.restx
import weewx.units
import weewx.wxformulas
from weeutil.weeutil import to_bool, accumulateLeaves

VERSION = "0.12"

if weewx.__version__ < "3":
    raise weewx.UnsupportedFeature("weewx 3 is required, found %s" %
                                   weewx.__version__)

def logmsg(level, msg):
    syslog.syslog(level, 'restx: WeatherCloud: %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)

# weewx uses a status of 1 to indicate failure, wcloud uses 0
def _invert(x):
    if x is None:
        return None
    if x == 0:
        return 1
    return 0

# utility to convert to METRICWX windspeed
def _convert_windspeed(v, from_unit_system):
    if from_unit_system is None:
        return None
    if from_unit_system != weewx.METRICWX:
        (from_unit, _) = weewx.units.getStandardUnitType(
            from_unit_system, 'windSpeed')
        from_t = (v, from_unit, 'group_speed')
        v = weewx.units.convert(from_t, 'meter_per_second')[0]
    return v

# FIXME: this formula is suspect
def _calc_thw(heatindex_C, windspeed_mps):
    if heatindex_C is None or windspeed_mps is None:
        return None
    windspeed_mph = 2.25 * windspeed_mps
    heatindex_F = 32 + heatindex_C * 9 / 5
    thw_F = heatindex_F - (1.072 * windspeed_mph)
    thw_C = (thw_F - 32) * 5 / 9
    return thw_C

def _get_windavg(dbm, ts, interval=600):
    sts = ts - interval
    val = dbm.getSql("SELECT AVG(windSpeed) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (sts, ts))
    return val[0] if val is not None else None

# weathercloud wants "10-min maximum gust of wind".  some hardware reports
# a wind gust, others do not, so try to deal with both.
def _get_windhi(dbm, ts, interval=600):
    sts = ts - interval
    val = dbm.getSql("""SELECT
 MAX(CASE WHEN windSpeed >= windGust THEN windSpeed ELSE windGust END)
 FROM %s
 WHERE dateTime>? AND dateTime<=?""" % dbm.table_name, (sts, ts))
    return val[0] if val is not None else None

def _get_winddiravg(dbm, ts, interval=600):
    sts = ts - interval
    val = dbm.getSql("SELECT AVG(windDir) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" %
                     dbm.table_name, (sts, ts))
    return val[0] if val is not None else None

class WeatherCloud(weewx.restx.StdRESTbase):
    def __init__(self, engine, config_dict):
        """This service recognizes standard restful options plus the following:

        id: WeatherCloud identifier

        key: WeatherCloud key
        """
        super(WeatherCloud, self).__init__(engine, config_dict)
        loginf("service version is %s" % VERSION)
        try:
            site_dict = config_dict['StdRESTful']['WeatherCloud']
            site_dict = accumulateLeaves(site_dict, max_level=1)
            site_dict['id']
            site_dict['key']
        except KeyError as e:
            logerr("Data will not be posted: Missing option %s" % e)
            return
        site_dict['manager_dict'] = weewx.manager.get_manager_dict(
            config_dict['DataBindings'], config_dict['Databases'], 'wx_binding')

        self.archive_queue = queue.Queue()
        self.archive_thread = WeatherCloudThread(self.archive_queue, **site_dict)
        self.archive_thread.start()
        self.bind(weewx.NEW_ARCHIVE_RECORD, self.new_archive_record)
        loginf("Data will be uploaded for id=%s" % site_dict['id'])

    def new_archive_record(self, event):
        self.archive_queue.put(event.record)

class WeatherCloudThread(weewx.restx.RESTThread):

    _SERVER_URL = 'http://api.weathercloud.net/v01/set'

    # this data map supports the default database schema
    # FIXME: design a config option to override this map
    #             wcloud_name   weewx_name      format  multiplier
    _DATA_MAP = {'temp':       ('outTemp',      '%.0f', 10.0), # C * 10
                 'hum':        ('outHumidity',  '%.0f', 1.0),  # percent
                 'wdir':       ('windDir',      '%.0f', 1.0),  # degree
                 'wspd':       ('windSpeed',    '%.0f', 10.0), # m/s * 10
                 'bar':        ('barometer',    '%.0f', 10.0), # hPa * 10
                 'rain':       ('dayRain',      '%.0f', 10.0), # mm * 10
                 'rainrate':   ('rainRate',     '%.0f', 10.0), # mm/hr * 10
                 'tempin':     ('inTemp',       '%.0f', 10.0), # C * 10
                 'humin':      ('inHumidity',   '%.0f', 1.0),  # percent
                 'uvi':        ('UV',           '%.0f', 10.0), # index * 10
                 'solarrad':   ('radiation',    '%.0f', 10.0), # W/m^2 * 10
                 'et':         ('EV',           '%.0f', 10.0), # mm * 10
                 'chill':      ('windchill',    '%.0f', 10.0), # C * 10
                 'heat':       ('heatindex',    '%.0f', 10.0), # C * 10
                 'dew':        ('dewpoint',     '%.0f', 10.0), # C * 10
                 'battery':    ('consBatteryVoltage', '%.0f', 100.0), # V * 100
                 'temp01':     ('extraTemp1',   '%.0f', 10.0), # C * 10
                 'temp02':     ('extraTemp2',   '%.0f', 10.0), # C * 10
                 'temp03':     ('extraTemp3',   '%.0f', 10.0), # C * 10
                 'temp04':     ('leafTemp1',    '%.0f', 10.0), # C * 10
                 'temp05':     ('leafTemp2',    '%.0f', 10.0), # C * 10
                 'temp06':     ('soilTemp1',    '%.0f', 10.0), # C * 10
                 'temp07':     ('soilTemp2',    '%.0f', 10.0), # C * 10
                 'temp08':     ('soilTemp3',    '%.0f', 10.0), # C * 10
                 'temp09':     ('soilTemp4',    '%.0f', 10.0), # C * 10
                 'temp10':     ('heatingTemp4', '%.0f', 10.0), # C * 10
                 'leafwet01':  ('leafWet1',     '%.0f', 1.0),  # [0,15]
                 'leafwet02':  ('leafWet2',     '%.0f', 1.0),  # [0,15]
                 'hum01':      ('extraHumid1',  '%.0f', 1.0),  # percent
                 'hum02':      ('extraHumid2',  '%.0f', 1.0),  # percent
                 'soilmoist01': ('soilMoist1',  '%.0f', 1.0),  # Cb [0,200]
                 'soilmoist02': ('soilMoist2',  '%.0f', 1.0),  # Cb [0,200]
                 'soilmoist03': ('soilMoist3',  '%.0f', 1.0),  # Cb [0,200]
                 'soilmoist04': ('soilMoist4',  '%.0f', 1.0),  # Cb [0,200]

                 # these are calculated by this extension
#                 'thw':        ('thw',          '%.0f', 10.0), # C * 10
                 'wspdhi':     ('windhi',       '%.0f', 10.0), # m/s * 10
                 'wspdavg':    ('windavg',      '%.0f', 10.0), # m/s * 10
                 'wdiravg':    ('winddiravg',   '%.0f', 1.0),  # degree
                 'heatin':     ('inheatindex',  '%.0f', 10.0), # C * 10
                 'dewin':      ('indewpoint',   '%.0f', 10.0), # C * 10
                 'battery01':  ('bat01',        '%.0f', 1.0),  # 0 or 1
                 'battery02':  ('bat02',        '%.0f', 1.0),  # 0 or 1
                 'battery03':  ('bat03',        '%.0f', 1.0),  # 0 or 1
                 'battery04':  ('bat04',        '%.0f', 1.0),  # 0 or 1
                 'battery05':  ('bat05',        '%.0f', 1.0),  # 0 or 1

                 # these are in the wcloud api but are not yet implemented
#                 'tempagroXX':   ('??',       '%.0f', 10.0), # C * 10
#                 'wspdXX':       ('??',       '%.0f', 10.0), # m/s * 10
#                 'wspdavgXX':    ('??',       '%.0f', 10.0), # m/s * 10
#                 'wspdhiXX':     ('??',       '%.0f', 10.0), # m/s * 10
#                 'wdirXX':       ('??',       '%.0f', 1.0), # degree
#                 'wdiravgXX':    ('??',       '%.0f', 1.0), # degree
#                 'bartrend':     ('??',       '%.0f', 1.0), # -60,-20,0,20,60
#                 'forecast':     ('??',       '%.0f', 1.0),
#                 'forecasticon': ('??',       '%.0f', 1.0),
                 }

    try:
        max_integer = sys.maxint    # python 2
    except AttributeError:
        max_integer = sys.maxsize    # python 3

    def __init__(self, queue, id, key, manager_dict,
                 server_url=_SERVER_URL, skip_upload=False,
                 post_interval=600, max_backlog=max_integer, stale=None,
                 log_success=True, log_failure=True,
                 timeout=60, max_tries=3, retry_wait=5):
        super(WeatherCloudThread, self).__init__(queue,
                                               protocol_name='WeatherCloud',
                                               manager_dict=manager_dict,
                                               post_interval=post_interval,
                                               max_backlog=max_backlog,
                                               stale=stale,
                                               log_success=log_success,
                                               log_failure=log_failure,
                                               max_tries=max_tries,
                                               timeout=timeout,
                                               retry_wait=retry_wait)
        self.id = id
        self.key = key
        self.server_url = server_url
        self.skip_upload = to_bool(skip_upload)

    def process_record(self, record, dbm):
        r = self.get_record(record, dbm)
        url = self.get_url(r)
        if self.skip_upload:
            loginf("skipping upload")
            return
        req = Request(url)
        req.add_header("User-Agent", "weewx/%s" % weewx.__version__)
        self.post_with_retries(req)

    # calculate derived quantities and other values needed by wcloud
    def get_record(self, record, dbm):
        rec = super(WeatherCloudThread, self).get_record(record, dbm)

        # put everything into units required by weathercloud
        rec = weewx.units.to_METRICWX(rec)

        # calculate additional quantities
        rec['windavg'] = _get_windavg(dbm, record['dateTime'])
        rec['windhi'] = _get_windhi(dbm, record['dateTime'])
        rec['winddiravg'] = _get_winddiravg(dbm, record['dateTime'])

        # ensure wind direction is in [0,359]
        if rec['windDir'] is not None:
            if int(rec['windDir']) > 359:
                rec['windDir'] -= 360
        if rec['winddiravg'] is not None:
            if int(rec['winddiravg']) > 359:
                rec['winddiravg'] -= 360

        # these observations are non-standard, so do unit conversions directly
        rec['windavg'] = _convert_windspeed(rec['windavg'], record['usUnits'])
        rec['windhi'] = _convert_windspeed(rec['windhi'], record['usUnits'])

        if 'inTemp' in rec and 'inHumidity' in rec:
            rec['inheatindex'] = weewx.wxformulas.heatindexC(
                rec['inTemp'], rec['inHumidity'])
            rec['indewpoint'] = weewx.wxformulas.dewpointC(
                rec['inTemp'], rec['inHumidity'])
#        if 'heatindex' in rec and 'windSpeed' in rec:
#            rec['thw'] = _calc_thw(rec['heatindex'], rec['windSpeed'])
        if 'txBatteryStatus' in record:
            rec['bat01'] = _invert(record['txBatteryStatus'])
        if 'windBatteryStatus' in record:
            rec['bat02'] = _invert(record['windBatteryStatus'])
        if 'rainBatteryStatus' in record:
            rec['bat03'] = _invert(record['rainBatteryStatus'])
        if 'outTempBatteryStatus' in record:
            rec['bat04'] = _invert(record['outTempBatteryStatus'])
        if 'inTempBatteryStatus' in record:
            rec['bat05'] = _invert(record['inTempBatteryStatus'])
        return rec

    def get_url(self, record):
        # put data into expected structure and format
        values = {}
        values['ver'] = str(weewx.__version__)
        values['type'] = 251 # identifier assigned to weewx by weathercloud
        values['wid'] = self.id
        values['key'] = self.key
        time_tt = time.gmtime(record['dateTime'])
        values['time'] = time.strftime("%H%M", time_tt) # assumes leading zeros
        values['date'] = time.strftime("%Y%m%d", time_tt)
        for key in self._DATA_MAP:
            rkey = self._DATA_MAP[key][0]
            if rkey in record and record[rkey] is not None:
                v = record[rkey] * self._DATA_MAP[key][2]
                values[key] = self._DATA_MAP[key][1] % v
        url = self.server_url + '?' + urlencode(values)
        if weewx.debug >= 2:
            logdbg('url: %s' % re.sub(r"key=[^\&]*", "key=XXX", url))
        return url
# $Id: weather365.py 0957 14-04-2017 1.3.1$
# Copyright 2017 Frank Bandle 
# based on Scripts written by M.Wall
# Thanks Luc Heijst for testing and correction
"""
Upload data to weather365.net
  https://channel1.weather365.net/stations/

[StdRESTful]
    [[Weather365]]
        stationid = INSERT_STATIONID_HERE
        password  = INSERT_PASSWORD_HERE
"""
import re
import sys
import syslog
import time

# Python 2/3 compatiblity
try:
    import Queue as queue                   # python 2
    from urllib import urlencode            # python 2
    from urllib2 import Request             # python 2
except ImportError:
    import queue                            # python 3
    from urllib.parse import urlencode      # python 3
    from urllib.request import Request      # python 3

import weewx
import weewx.restx
import weewx.units
from weeutil.weeutil import to_bool, accumulateLeaves

VERSION = "1.4.2"

if weewx.__version__ < "3":
    raise weewx.UnsupportedFeature("weewx 3 is required, found %s" %
                                   weewx.__version__)

def logmsg(level, msg):
    syslog.syslog(level, 'restx: Weather365: %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)

class Weather365(weewx.restx.StdRESTbase):
    def __init__(self, engine, config_dict):
        """This service recognizes standard restful options plus the following:

        stationid = INSERT_STATIONID_HERE
        password  = INSERT_PASSWORD_HERE

        latitude: Station latitude in decimal degrees
        Default is station latitude

        longitude: Station longitude in decimal degrees
        Default is station longitude        
        """
        super(Weather365, self).__init__(engine, config_dict)        
        loginf("service version is %s" % VERSION)
        try:
            site_dict = config_dict['StdRESTful']['Weather365']
            site_dict = accumulateLeaves(site_dict, max_level=1)
            site_dict['stationid']     
            site_dict['password']   
        except KeyError as e:
            logerr("Data will not be posted: Missing option %s" % e)
            return
        site_dict.setdefault('latitude', engine.stn_info.latitude_f)
        site_dict.setdefault('longitude', engine.stn_info.longitude_f)
        site_dict.setdefault('altitude', engine.stn_info.altitude_vt[0])            
        site_dict['manager_dict'] = weewx.manager.get_manager_dict(
            config_dict['DataBindings'], config_dict['Databases'], 'wx_binding')

        self.archive_queue = queue.Queue()
        self.archive_thread = Weather365Thread(self.archive_queue, **site_dict)
        self.archive_thread.start()
        self.bind(weewx.NEW_ARCHIVE_RECORD, self.new_archive_record)
        loginf("Data will be uploaded for station id %s" % site_dict['stationid'])

    def new_archive_record(self, event):
        self.archive_queue.put(event.record)

class Weather365Thread(weewx.restx.RESTThread):

    _SERVER_URL = 'https://channel1.weather365.net/stations/index.php'
    _DATA_MAP = {'winddir':    ('windDir',      '%.0f', 1.0), # degrees
                 'windspeed':  ('windSpeed',    '%.1f', 0.2777777777), # m/s
                 'windgust':   ('windGust',     '%.1f', 0.2777777777), # m/s
                 't2m':        ('outTemp',      '%.1f', 1.0), # C
                 'relhum':     ('outHumidity',  '%.0f', 1.0), # percent
                 'press':      ('barometer',    '%.3f', 1.0), # hpa?
                 'rainh':      ('hourRain',     '%.2f', 10.0), # mm
                 'raind':      ('dayRain',      '%.2f', 10.0), # mm
                 'rainrate':   ('rainRate',     '%.2f', 1.0), # mm/hr 
                 'uvi':        ('UV',           '%.1f', 1.0), # index * 1
                 'radi':       ('radiation',    '%.1f', 1.0), # W/m^2 * 1
                 'et':         ('ET',           '%.5f', 10.0), # mm * 1
                 'wchill':     ('windchill',    '%.1f', 1.0), # C * 1
                 'heat':       ('heatindex',    '%.1f', 1.0), # C * 1
                 'dew2m':      ('dewpoint',     '%.1f', 1.0), # C * 1
                 'rxsignal':   ('rxCheckPercent', '%.0f', 1.0), # percent / dB
                 'cloudbase':  ('cloudbase',     '%.1f', 1.0), # m 
                 'windrun':    ('windrun',       '%.1f', 1.0), # m
                 'humidex':    ('humidex',       '%.1f', 1.0), # 
                 'appTemp':    ('appTemp',       '%.1f', 1.0), # C
                 'soiltemp':      ('soilTemp1',   '%.1f', 1.0), # C
                 'soiltemp2':     ('soilTemp2',   '%.1f', 1.0), # C
                 'soiltemp3':     ('soilTemp3',   '%.1f', 1.0), # C
                 'soiltemp4':     ('soilTemp4',   '%.1f', 1.0), # C
                 'soilmoisture':  ('soilMoist1',  '%.1f', 1.0), # %
                 'soilmoisture2': ('soilMoist2',  '%.1f', 1.0), # %
                 'soilmoisture3': ('soilMoist3',  '%.1f', 1.0), # %
                 'soilmoisture4': ('soilMoist4',  '%.1f', 1.0), # %
                 'leafwetness':   ('leafWet1',    '%.1f', 1.0), # %
                 'leafwetness2':  ('leafWet2',    '%.1f', 1.0), # %
                 'temp2':         ('extraTemp1',  '%.1f', 1.0), # C
                 'temp3':         ('extraTemp2',  '%.1f', 1.0), # C
                 'temp4':         ('extraTemp3',  '%.1f', 1.0), # C
                 'humidity2':     ('extraHumid1', '%.0f', 1.0), # %
                 'humidity3':     ('extraHumid2', '%.0f', 1.0), # %                 
                 'txbattery':     ('txBatteryStatus', '%.0f', 1.0), # %  
                 }

    try:
        max_integer = sys.maxint    # python 2
    except AttributeError:
        max_integer = sys.maxsize    # python 3

    def __init__(self, queue, 
                 stationid, password, latitude, longitude, altitude,
                 manager_dict,
                 server_url=_SERVER_URL, skip_upload=False,
                 post_interval=None, max_backlog=max_integer, stale=None,
                 log_success=True, log_failure=True,
                 timeout=60, max_tries=3, retry_wait=5):
        super(Weather365Thread, self).__init__(queue,
                                           protocol_name='Weather365',
                                           manager_dict=manager_dict,
                                           post_interval=post_interval,
                                           max_backlog=max_backlog,
                                           stale=stale,
                                           log_success=log_success,
                                           log_failure=log_failure,
                                           max_tries=max_tries,
                                           timeout=timeout,
                                           retry_wait=retry_wait)
        self.latitude = float(latitude)
        self.longitude = float(longitude)
        self.altitude = float(altitude)                                      
        self.stationid = stationid
        self.server_url = server_url
        self.skip_upload = to_bool(skip_upload)

    def process_record(self, record, dbm):
        r = self.get_record(record, dbm)
        data = self.get_data(r)
        url_data = urlencode(data).encode('utf-8')
        if self.skip_upload:
            loginf("skipping upload")
            return
        req = Request(self.server_url, url_data)
        loginf("Data uploaded to %s is: (%s)" % (self.server_url, url_data))
        req.get_method = lambda: 'POST'
        req.add_header("User-Agent", "weewx/%s" % weewx.__version__)
        self.post_with_retries(req)

    def check_response(self, response):
        txt = response.read()
        if txt.find('invalid-login-data') >= 0:
            raise weewx.restx.BadLogin(txt)
        elif not txt.startswith('INSERT'):
            raise weewx.restx.FailedPost("Server returned '%s'" % txt)

    def get_data(self, in_record):
        # put everything into the right units
        record = weewx.units.to_METRIC(in_record)

        # put data into expected scaling, structure, and format
        values = {}      
        values['lat']  = str(self.latitude)
        values['long'] = str(self.longitude)
        values['alt']  = str(self.altitude) # meter
        values['stationid'] = self.stationid
        values['prec_time'] = 60
        values['datum'] = time.strftime('%Y%m%d%H%M',
                                        time.localtime(record['dateTime']))
        values['utcstamp'] = int(record['dateTime'])
        for key in self._DATA_MAP:
            rkey = self._DATA_MAP[key][0]
            if rkey in record and record[rkey] is not None:
                v = record[rkey] * self._DATA_MAP[key][2]
                values[key] = self._DATA_MAP[key][1] % v

        return values

Reply via email to