On Tuesday, September 27, 2016 at 10:37:59 AM UTC-4, fraban wrote:
>
> Any hints what happens here ? 
>

the driver tried to figure out the archive interval by downloading data 
from weatherlink.  that failed.  there *should* have been messages in the 
log that explain why the download failed, but for some reason the posting 
you made does not have those messages.

please try the attached v0.11rc1

it should provide more detail about why the download fails

m 


-- 
You received this message because you are subscribed to the Google Groups 
"weewx-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.
#!/usr/bin/python
# $Id: wlink.py 1400 2016-01-21 19:15:04Z mwall $
# Copyright 2014 Matthew Wall
"""weewx driver for WeatherLink web sites
   download data from a weatherlink site for use in weewx

To use this driver:

1) copy this file to the weewx user directory

   cp wlink.py /home/weewx/bin/user

2) configure weewx.conf

[Station]
    ...
    station_type = WeatherLink
[WeatherLink]
    username = USERNAME
    password = PASSWORD
    driver = user.wlink
"""

# FIXME: there are unresolved timezone issues in this code.  if you grab data
# from a weatherlink site that is in a time zone other than the one in which
# you are running weewx, you will have problems.  the timestamps coming from
# weatherlink are local to the weather station.  the 'loop' data have timestamp
# in the timezone of the machine on which weewx is running.

# FIXME: the archive records from weatherlink often have unusable timestamps.
# the last few records will often have datestamp of 0xff and timestamp of 0xff.
# for now we just skip these, since there is no time information we can use.

# FIXME: the web page parsing is really, really crude.  it will break if there
# are any significant changes to the weatherlink web page structure.
# unfortunately the weatherlink web page is really primitive and gives us
# no structural hints as to the content.

from __future__ import with_statement
import httplib
import re
import socket
import struct
import syslog
import time
import urllib2
from HTMLParser import HTMLParser

import weewx
import weewx.drivers

DRIVER_NAME = "WeatherLink"
DRIVER_VERSION = "0.11rc1"

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

def logmsg(dst, msg):
    syslog.syslog(dst, 'wlink: %s' % msg)

def logdbg(msg):
    logmsg(syslog.LOG_DEBUG, msg)

def loginf(msg):
    logmsg(syslog.LOG_INFO, msg)

def logcrt(msg):
    logmsg(syslog.LOG_CRIT, msg)

def logerr(msg):
    logmsg(syslog.LOG_ERR, msg)

def logdev(msg):
#    print msg
    pass

def loader(config_dict, engine):
    return WeatherLink(**config_dict['WeatherLink'])

class WeatherLink(weewx.drivers.AbstractDevice):
    """weewx driver to download data from WeatherLink web sites
    
    Parse the web page for 'loop' data.  This happens every 60 seconds.

    There are two formats:
    A - pre-2015; uses div and span within td
    B - end of 2015; elimited divs and inline styles

    For archive data, make a request to the server.
    """

    def __init__(self, **stn_dict):
        loginf("version is %s" % DRIVER_VERSION)
        self.username = stn_dict['username']
        self.password = stn_dict['password']
        self.format = stn_dict.get('format', 'B')
        loginf("expecting HTML format '%s'" % self.format)
        self.poll_interval = float(stn_dict.get('poll_interval', 60))
        self.max_tries = int(stn_dict.get('max_tries', 5))
        self.retry_wait = int(stn_dict.get('retry_wait', 30))
        self.raw_data_cache = stn_dict.get('raw_data_cache', None)
        self._archive_interval = stn_dict.get('archive_interval', None)
        if self._archive_interval is not None:
            self._archive_interval = int(self._archive_interval)
            loginf("archive interval is %s seconds" % self._archive_interval)
        loginf("polling interval is %s" % self.poll_interval)

    @property
    def hardware_name(self):
        return "WeatherLink"

    @property
    def archive_interval(self):
        # weewx wants the archive interval in seconds
        if self._archive_interval is None:
            self._archive_interval = self.download_archive_interval() * 60
        return self._archive_interval

    def genLoopPackets(self):
        while True:
            data = self.get_data("http://www.weatherlink.com/user/%s/index.php";
                                 "?view=main&headers=0&type=2" % self.username)
            packet = {
                'dateTime': int(time.time() + 0.5),
                'usUnits': weewx.US, # type 2 is US imperial
                }
            if self.raw_data_cache is not None:
                with open(self.raw_data_cache, "w") as f:
                    f.write(data)
            packet.update(self.parse_page(data))
            yield packet
            time.sleep(self.poll_interval)

    def genArchiveRecords(self, since_ts):
        if since_ts is None:
            since_ts = 0
        ts = _epoch_to_timestamp(since_ts)
        data = self.get_data("http://weatherlink.com/webdl.php";
                             "?timestamp=%s&user=%s&pass=%s&action=data" %
                             (ts, self.username, self.password))
        if data is None:
            return
        logdbg("found %s archive records since %s" %
               ((len(data) / 52), since_ts))
        sidx = 0
        while sidx < len(data):
            record = self.unpack_archive_packet(data[sidx:sidx + 52])
            if record['dateTime']:
                yield record
            sidx += 52

    def get_data(self, url):
        for count in range(self.max_tries):
            try:
                response = urllib2.urlopen(url)
                data = response.read()
                return data
            except (urllib2.URLError, socket.error,
                    httplib.BadStatusLine, httplib.IncompleteRead), e:
                logerr('download failed attempt %d of %d: %s' %
                       (count + 1, self.max_tries, e))
                time.sleep(self.retry_wait)
        else:
            logerr('download failed after %d tries' % self.max_tries)
        return None

    def parse_page(self, text):
        """parse weather data from a weatherlink web page"""
        packet = {}
        if text is not None:
            parser = WLParserB()
            if self.format == 'A':
                parser = WLParserA()
            parser.feed(text)
            packet = parser.get_data()
        return packet

    def download_archive_interval(self):
        logdbg('downloading archive interval')
        data = self.get_data("http://weatherlink.com/webdl.php";
                             "?timestamp=0&user=%s&pass=%s&action=headers" %
                             (self.username, self.password))
        if data is None:
            raise weewx.WeeWxIOError("download of archive interval failed")
        for line in data.splitlines():
            if line.find('ArchiveInt=') >= 0:
                logdbg('found archive interval: %s' % line)
                return int(line[11:])
        raise weewx.WeeWxIOError("cannot determine archive interval from %s" %
                                 data)

    # adapted from the implementation in the vantage driver
    def unpack_archive_packet(self, raw_record_string):
        packet_type = ord(raw_record_string[42])
        if packet_type == 0xff:
            archive_format = rec_fmt_A
            data_types = rec_types_A
        elif packet_type == 0x00:
            archive_format = rec_fmt_B
            data_types = rec_types_B
        else:
            raise weewx.UnknownArchiveType("Unknown archive type: 0x%x" %
                                           (packet_type,))
        data_tuple = archive_format.unpack(raw_record_string)
        raw_record = dict(zip(data_types, data_tuple))
        packet = {
            'dateTime': _archive_datetime(raw_record['date_stamp'],
                                          raw_record['time_stamp']),
            'usUnits': weewx.US,
            }
        for t in raw_record:
            func = _archive_map.get(t)
            if func:
                packet[t] = func(raw_record[t])

        if packet['windSpeed'] is None or packet['windSpeed'] == 0:
            packet['windDir'] = None
        packet['interval'] = self.archive_interval() / 60
        return packet

# =============================================================================
#                      Decoding routines                                      
# =============================================================================

def _epoch_to_timestamp(epoch):
    """convert unix epoch to davis timestamp"""
    tt = time.localtime(epoch)
    ds = tt[2] + (tt[1] << 5) + ((tt[0] - 2000) << 9)
    ts = tt[3] * 100 + tt[4]
    x = (ds << 16) | ts
    return x

def _archive_datetime(datestamp, timestamp):
    """Returns the epoch time of the archive packet."""
    try:
        time_tuple = ((0xfe00 & datestamp) >> 9,    # year
                      (0x01e0 & datestamp) >> 5,    # month
                      (0x001f & datestamp),         # day
                      timestamp // 100,             # hour
                      timestamp % 100,              # minute
                      0,                            # second
                      0, 0, -1)                     # have OS guess DST
        ts = int(time.mktime(time_tuple))
    except (OverflowError, ValueError, TypeError):
        logerr("cannot make timestamp: ds=%s ts=%s" % (datestamp, timestamp))
        ts = None
    return ts

def _stime(v):
    h = v/100
    m = v%100
    # Return seconds since midnight
    return 3600*h + 60*m

def _big_val(v):
    return float(v) if v != 0x7fff else None

def _big_val10(v):
    return float(v)/10.0 if v != 0x7fff else None

def _big_val100(v):
    return float(v)/100.0 if v != 0xffff else None

def _val100(v):
    return float(v)/100.0

def _val1000(v):
    return float(v)/1000.0

def _val1000Zero(v):
    return float(v)/1000.0 if v != 0 else None

def _little_val(v):
    return float(v) if v != 0x00ff else None

def _little_val10(v):
    return float(v)/10.0 if v != 0x00ff else None

def _little_temp(v):
    return float(v-90) if v != 0x00ff else None

def _null(v):
    return v

def _null_float(v):
    return float(v)

def _null_int(v):
    return int(v)

def _windDir(v):
    return float(v) * 22.5 if v!= 0x00ff else None

# Rain bucket type "1", a 0.2 mm bucket
def _bucket_1(v):
    return float(v)*0.00787401575

def _bucket_1_None(v):
    return float(v)*0.00787401575 if v != 0xffff else None

# Rain bucket type "2", a 0.1 mm bucket
def _bucket_2(v):
    return float(v)*0.00393700787

def _bucket_2_None(v):
    return float(v)*0.00393700787 if v != 0xffff else None

_archive_map={'barometer'      : _val1000Zero,
              'inTemp'         : _big_val10,
              'outTemp'        : _big_val10,
              'highOutTemp'    : lambda v : float(v/10.0) if v != -32768 else None,
              'lowOutTemp'     : _big_val10,
              'inHumidity'     : _little_val,
              'outHumidity'    : _little_val,
              'windSpeed'      : _little_val,
              'windDir'        : _windDir,
              'windGust'       : _null_float,
              'windGustDir'    : _windDir,
              'rain'           : _val100,
              'rainRate'       : _val100,
              'ET'             : _val1000,
              'radiation'      : _big_val,
              'highRadiation'  : _big_val,
              'UV'             : _little_val10,
              'highUV'         : _little_val10,
              'extraTemp1'     : _little_temp,
              'extraTemp2'     : _little_temp,
              'extraTemp3'     : _little_temp,
              'soilTemp1'      : _little_temp,
              'soilTemp2'      : _little_temp,
              'soilTemp3'      : _little_temp,
              'soilTemp4'      : _little_temp,
              'leafTemp1'      : _little_temp,
              'leafTemp2'      : _little_temp,
              'extraHumid1'    : _little_val,
              'extraHumid2'    : _little_val,
              'soilMoist1'     : _little_val,
              'soilMoist2'     : _little_val,
              'soilMoist3'     : _little_val,
              'soilMoist4'     : _little_val,
              'leafWet1'       : _little_val,
              'leafWet2'       : _little_val,
              'leafWet3'       : _little_val,
              'leafWet4'       : _little_val,
              'forecastRule'   : _null,
              'readClosed'     : _null,
              'readOpened'     : _null}

rec_format_A =[('date_stamp',              'H'), ('time_stamp',    'H'), ('outTemp',    'h'),
               ('highOutTemp',             'h'), ('lowOutTemp',    'h'), ('rain',       'H'),
               ('rainRate',                'H'), ('barometer',     'H'), ('radiation',  'H'),
               ('number_of_wind_samples',  'H'), ('inTemp',        'h'), ('inHumidity', 'B'),
               ('outHumidity',             'B'), ('windSpeed',     'B'), ('windGust',   'B'),
               ('windGustDir',             'B'), ('windDir',       'B'), ('UV',         'B'),
               ('ET',                      'B'), ('invalid_data',  'B'), ('soilMoist1', 'B'),
               ('soilMoist2',              'B'), ('soilMoist3',    'B'), ('soilMoist4', 'B'),
               ('soilTemp1',               'B'), ('soilTemp2',     'B'), ('soilTemp3',  'B'),
               ('soilTemp4',               'B'), ('leafWet1',      'B'), ('leafWet2',   'B'),
               ('leafWet3',                'B'), ('leafWet4',      'B'), ('extraTemp1', 'B'),
               ('extraTemp2',              'B'), ('extraHumid1',   'B'), ('extraHumid2','B'),
               ('readClosed',              'H'), ('readOpened',    'H'), ('unused',     'B')]

rec_format_B = [('date_stamp',             'H'), ('time_stamp',    'H'), ('outTemp',    'h'),
                ('highOutTemp',            'h'), ('lowOutTemp',    'h'), ('rain',       'H'),
                ('rainRate',               'H'), ('barometer',     'H'), ('radiation',  'H'),
                ('number_of_wind_samples', 'H'), ('inTemp',        'h'), ('inHumidity', 'B'),
                ('outHumidity',            'B'), ('windSpeed',     'B'), ('windGust',   'B'),
                ('windGustDir',            'B'), ('windDir',       'B'), ('UV',         'B'),
                ('ET',                     'B'), ('highRadiation', 'H'), ('highUV',     'B'),
                ('forecastRule',           'B'), ('leafTemp1',     'B'), ('leafTemp2',  'B'),
                ('leafWet1',               'B'), ('leafWet2',      'B'), ('soilTemp1',  'B'),
                ('soilTemp2',              'B'), ('soilTemp3',     'B'), ('soilTemp4',  'B'),
                ('download_record_type',   'B'), ('extraHumid1',   'B'), ('extraHumid2','B'),
                ('extraTemp1',             'B'), ('extraTemp2',    'B'), ('extraTemp3', 'B'),
                ('soilMoist1',             'B'), ('soilMoist2',    'B'), ('soilMoist3', 'B'),
                ('soilMoist4',             'B')]

rec_types_A, fmt_A = zip(*rec_format_A)
rec_types_B, fmt_B = zip(*rec_format_B)
rec_fmt_A = struct.Struct('<' + ''.join(fmt_A))
rec_fmt_B = struct.Struct('<' + ''.join(fmt_B))

compass_points = {'N': 0, 'NNE': 22.5, 'NE': 45, 'ENE': 67.5, 'E': 90,
                  'ESE': 112.5, 'SE': 135, 'SSE': 157.5, 'S': 180,
                  'SSW': 202.5, 'SW': 225, 'WSW': 247.5, 'W': 270,
                  'WNW': 292.5, 'NW': 315, 'NNW': 337.5}

# crude parser to get data from the weatherlink web page.
# works with html up to at least late 2015
class WLParserA(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.packet = {}
        self.hierarchy = []
        self.last_value = None
        self.last_key = None

    def handle_starttag(self, tag, attrs):
        if tag not in ['meta', 'link', 'br']:
            self.hierarchy.append(tag)

    def handle_endtag(self, tag):
        self.hierarchy.pop()

    def handle_data(self, data):
        data = data.strip()
        if not 'span' in self.hierarchy:
            if len(data):
                self.last_key = data
            return
        if data in compass_points:
            self.packet['windDir'] = compass_points[data]
        elif self.last_value is not None:
            if data == self.last_value:
                try:
                    v = float(self.last_value)
                    self.packet['outTemperature'] = v
                except ValueError:
                    pass
            elif data == 'km/h' or data == 'Mph':
                self.packet['windSpeed'] = float(self.last_value)
            elif data == '%':
                self.packet['outHumidity'] = float(self.last_value)
            self.last_value = None
        elif re.search('\d+mm', data):
            m = re.search('(\d+)mm', data)
            self.packet['rain'] = float(m.group(1)) / 10 # weewx wants cm
        elif re.search('[\d.]+mb', data):
            m = re.search('([\d.]+)mb', data)
            self.packet['barometer'] = float(m.group(1))
        elif re.search('[\d.]+hPa', data):
            m = re.search('([\d.]+)hPa', data)
            self.packet['barometer'] = float(m.group(1))
        elif re.search('[\d.]+"', data):
            m = re.search('([\d.]+)"', data)
            if self.last_key == 'Rain':
                self.packet['rain'] = float(m.group(1))
            elif self.last_key == 'Barometer':
                self.packet['barometer'] = float(m.group(1))
            self.last_key = None
        else:
            self.last_value = data

    def get_data(self):
        return self.packet

# works with weatherlink html as of 2016
# pick off temperature, wind, humidity, rain, barometer
class WLParserB(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.packet = {}
        self.hierarchy = []
        self.last_key = 'Temperature'

    def handle_starttag(self, tag, attrs):
        if tag not in ['meta', 'link', 'br']:
            self.hierarchy.append(tag)

    def handle_endtag(self, tag):
        self.hierarchy.pop()

    def handle_data(self, data):
        if not 'body' in self.hierarchy:
            return
        logdev('%s' % self.hierarchy)
        data = data.strip()
        logdev('%s' % data)
        if len(data) == 0:
            return
        if data in ['Wind', 'Humidity', 'Rain', 'Barometer']:
            self.last_key = data
            logdev('found key %s' % self.last_key)
            return

        if self.last_key == 'Wind':
            if data == 'Calm':
                self.packet['windSpeed'] = 0
                self.packet['windDir'] = None
            elif re.search('[NSEW]+', data):
                m = re.search('([NSEW]+)', data)
                wdir = m.group(1)
                if wdir in compass_points:
                    self.packet['windDir'] = compass_points[wdir]
                logdev('windDir: %s' % self.packet.get('windDir'))
                # do not reset last_key since we still need a wind speed
            elif re.search('[\d.]+', data):
                m = re.search('([\d.]+)', data)
                self.packet['windSpeed'] = float(m.group(1))
                logdev('windSpeed: %s' % self.packet.get('windSpeed')) 
                self.last_key = None
        if self.last_key == 'Temperature':
            try:
                v = float(data)
                self.packet['outTemperature'] = v
                logdev('outTemperature: %s' % self.packet['outTemperature'])
                self.last_key = None
            except ValueError:
                pass
        if self.last_key == 'Humidity':
            self.packet['outHumidity'] = float(data)
            logdev('outHumidity: %s' % self.packet['outHumidity'])
            self.last_key = None
        if self.last_key == 'Rain':
            if re.search('[\d.]+mm', data):
                m = re.search('([\d.]+)mm', data)
                self.packet['rain'] = float(m.group(1)) / 10 # weewx wants cm
                logdev('rain: %s' % self.packet['rain'])
            elif re.search('[\d.]+"', data):
                m = re.search('([\d.]+)"', data)
                self.packet['rain'] = float(m.group(1))
                logdev('rain: %s' % self.packet['rain'])
            self.last_key = None
        if self.last_key == 'Barometer':
            if re.search('[\d.]+mb', data):
                m = re.search('([\d.]+)mb', data)
                self.packet['barometer'] = float(m.group(1))
                logdev('barometer: %s' % self.packet['barometer']) # FIXME
            elif re.search('[\d.]+hPa', data):
                m = re.search('([\d.]+)hPa', data)
                self.packet['barometer'] = float(m.group(1))
                logdev('barometer: %s' % self.packet['barometer']) # FIXME
            elif re.search('[\d.]+"', data):
                m = re.search('([\d.]+)"', data)
                self.packet['barometer'] = float(m.group(1))
                logdev('barometer: %s' % self.packet['barometer'])
            self.last_key = None

    def get_data(self):
        return self.packet

# To test this driver, do the following:
#   PYTHONPATH=/home/weewx/bin python /home/weewx/bin/user/wlink.py
if __name__ == "__main__":
    usage = """%prog [options]"""
    import optparse
    parser = optparse.OptionParser(usage=usage)
    parser.add_option('--username', dest='username', default='user',
                      help='weatherlink username')
    parser.add_option('--password', dest='password', default='pass',
                      help='weatherlink password')
    parser.add_option('--test-driver', dest='test_driver', action='store_true',
                      help='test the driver')
    parser.add_option('--test-parser', dest='test_parser', action='store_true',
                      help='test the parser')
    parser.add_option('--filename', dest='filename', default='testfile.xml',
                      help='name of file for test-parser')
    parser.add_option('--format', dest='format', default='B',
                      help='html format, can be A or B')
    (options, args) = parser.parse_args()
    syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
    if options.test_parser:
        data = []
        with open(options.filename) as f:
            for line in f:
                data.append(line)
        parser = WLParserB()
        if options.format == 'A':
            parser = WLParserA()
        parser.feed(''.join(data))
        print parser.get_data()
    elif options.test_driver:
        import weeutil.weeutil
        station = WeatherLink(username=options.username,
                              password=options.password)
        for p in station.genLoopPackets():
            print weeutil.weeutil.timestamp_to_string(p['dateTime']), p

Reply via email to