# $Id: wbug.py 734 2013-12-13 15:38:27Z mwall $
# Copyright 2013 Matthew Wall

#==============================================================================
# WeatherBug
#==============================================================================
# Upload data to WeatherBug
#
# To enable this module, put this file in bin/user, add the following to
# weewx.conf, then restart weewx.
#
# [StdRESTful]
#     ...
#     [[WeatherBug]]
#         publisher_id = WEATHERBUG_ID
#         station_number = WEATHERBUG_STATION_NUMBER
#         password = WEATHERBUG_PASSWORD
#         driver = user.wbug.WeatherBug

import httplib
import socket
import syslog
import time
import urllib
import urllib2

from weewx.restful import REST, SkippedPost, FailedPost
import weewx.units
import weeutil.weeutil

def logmsg(level, msg):
    syslog.syslog(level, 'wbug: %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 WeatherBug(REST):
    """Upload using the WeatherBug protocol.
    """

    _VERSION = 0.2
    _SERVER_URL = 'http://data.backyard2.weatherbug.com/data/livedata.aspx'
    _data_map = {'tempf':         ('outTemp',     '%.1f'), # F
                 'humidity':      ('outHumidity', '%.0f'), # percent
                 'winddir':       ('windDir',     '%.0f'), # degree
                 'windspeedmph':  ('windSpeed',   '%.1f'), # mph
                 'windgustmph':   ('windGust',    '%.1f'), # mph
                 'baromin':       ('barometer',   '%.3f'), # inHg
                 'rainin':        ('rain',        '%.2f'), # in
                 'dailyRainin':   ('dayRain',     '%.2f'), # in
                 'monthlyrainin': ('monthRain',   '%.2f'), # in
                 'tempfhi':       ('outTempMax',  '%.1f'), # F
                 'tempflo':       ('outTempMin',  '%.1f'), # F
                 'Yearlyrainin':  ('yearRain',    '%.2f'), # in
                 'dewptf':        ('dewpoint',    '%.1f')} # F

    def __init__(self, site, **kwargs):
        """Initialize for posting data.

        site: The upload site ("WeatherBug")

        publisher_id: WeatherBug publisher identifier
        [Required]

        station_number: WeatherBug station number
        [Required]

        password: WeatherBug password
        [Required]

        interval: The interval in seconds between posts.
        [Optional.  Default is 300]

        max_tries: Maximum number of tries before giving up
        [Optional.  Default is 3]

        latitude: Station latitude
        [Required]

        longitude: Station longitude
        [Required]
        
        server_url: Base URL for the server
        [Required]
        """
        self.site = site
        self.server_url = kwargs.get('server_url', self._SERVER_URL)
        self.latitude = float(kwargs['latitude'])
        self.longitude = float(kwargs['longitude'])
        self.publisher_id = kwargs['publisher_id']
        self.station_number = kwargs['station_number']
        self.password = kwargs['password']
        self.interval = int(kwargs.get('interval', 300))
        self.max_tries = int(kwargs.get('max_tries', 3))
        self.skip_upload= kwargs.get('skip_upload', False)
        self._lastpost = None
        # FIXME: restful needs this
        self.station = kwargs.get('station', self.station_number)

    # FIXME: specify desired fields to base class so we do not have to do
    # a second lookup
    def extractRecordFrom(self, archive, time_ts):
        """The base class does not include rainRate, so we have to get it."""
        record = REST.extractRecordFrom(self, archive, time_ts)
        sqlrec = archive.getSql('select rainRate from archive where dateTime=?', (time_ts,))
        datadict = dict(zip(['rainRate'], sqlrec))
        if datadict.has_key('rainRate'):
            record['rainRate'] = datadict['rainRate']
        return record

    def postData(self, archive, time_ts):
        """Post data to the WeatherBug server.

        archive: An instance of weewx.archive.Archive
        
        time_ts: The record desired as a unix epoch time."""
        
        last_ts = archive.lastGoodStamp()

        if time_ts != last_ts:
            msg = "%s: Record %s is not last record" % \
                (self.site, weeutil.weeutil.timestamp_to_string(time_ts))
            logdbg(msg)
            raise SkippedPost, msg
        
        if self._lastpost and time_ts - self._lastpost < self.interval:
            msg = "%s: Wait interval (%d) has not passed." % \
                (self.site, self.interval)
            raise SkippedPost, msg

        url = self.getURL(archive, time_ts)
        logdbg('url: %s' % url)
        if self.skip_upload:
            raise SkippedPost, 'skip_upload=True'

        for count in range(self.max_tries):
            try:
                response = urllib2.urlopen(url)
            except (urllib2.URLError, socket.error, httplib.BadStatusLine), e:
                logerr("Failed upload attempt %d to %s" % (count+1, self.site))
                logerr("   ****  Reason: %s" % (e,))
                logdbg("   ****  URL: %s" % (url))
            else:
                for line in response:
                    if not line.startswith('Successfully Received'):
                        msg = "%s returned '%s'" % (self.site, line)
                        logerr(msg)
                        raise FailedPost, msg
                self._lastpost = time_ts
                return
        else:
            msg = "Failed upload to %s after %d tries" % (
                self.site, self.max_tries)
            logerr(msg)
            raise IOError, msg

    def getURL(self, archive, time_ts):
        """Return the URL for posting data.
        
        archive: An instance of weewx.archive.Archive
        
        time_ts: The record desired as a unix epoch time.
        """
        record = self.extractRecordFrom(archive, time_ts)

        # put everything into the right units and scaling
        if record['usUnits'] != weewx.US:
            converter = weewx.units.StdUnitConverters[weewx.US]
            record = converter.convertDict(record)

        # put data into expected structure and format
        values = { 'action':'live' }
        values['softwaretype'] = 'weewx_%s' % weewx.__version__
        values['ID'] = self.publisher_id
        values['Num'] = self.station_number
        values['Key'] = self.password
        time_tt = time.gmtime(record['dateTime'])
        values['dateutc'] = time.strftime("%Y-%m-%d %H:%M:%S", time_tt)
        for key in self._data_map:
            rkey = self._data_map[key][0]
            if record.has_key(rkey) and record[rkey] is not None:
                values[key] = self._data_map[key][1] % record[rkey]
        return self.server_url + '?' + urllib.urlencode(values)
