# $Id: wbug.py 732 2013-12-12 07:50:46Z 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 hashlib
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

WBUG_DRIVER_VERSION = 0.1
WBUG_SERVER_URL = 'http://data.backyard2.weatherbug.com/data/livedata.aspx'

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.
    """

    _formats = {'barometer'   : '%.3f',
                'outTemp'     : '%.1f',
                'outHumidity' : '%.0f',
                'windSpeed'   : '%.1f',
                'windDir'     : '%.0f',
                'windGust'    : '%.1f',
                'dewpoint'    : '%.1f',
                'hourRain'    : '%.2f',
                'dayRain'     : '%.2f',
                'radiation'   : '%.2f',
                'UV'          : '%.2f',
                'rainRate'    : '%.2f'}

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

        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 WeatherBug server
        [Required]
        """
        self.site = site
        self.server_url = kwargs.get('server_url', WBUG_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._lastpost = None
        # FIXME: restful needs this, but weatherbug does not
        self.station = kwargs.get('station', None)

    # 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 is %s' % url)

        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 an URL for posting to WeatherBug.
        
        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 units WeatherBug wants
        if record['usUnits'] != weewx.US:
            converter = weewx.units.StdUnitConverters[weewx.US]
            record = converter.convertDict(record)

        # assemble an array of values in the order WeatherBug wants
        values = { 'action':'live' }
        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)
        values['tempf'] = self._format(record, 'outTemp')
        values['humidity'] = self._format(record, 'outHumidity')
        values['winddir'] = self._format(record, 'windDir')
        values['windspeedmph'] = self._format(record, 'windSpeed')
        values['windgustmph'] = self._format(record, 'windGust')
        values['baromin'] = self._format(record, 'barometer')
        values['rainin'] = self._format(record, 'rain')
        values['dailyRainin'] = self._format(record, 'dayRain')
#        values['monthlyrainin'] = None
#        values['tempfhi'] = None
#        values['tempflo'] = None
#        values['Yearlyrainin'] = None
        values['dewptf'] = self._format(record, 'dewpoint')
        values['softwaretype'] = 'weewx_%s' % weewx.__version__
        return self.server_url + '?' + urllib.urlencode(values)

    def _format(self, record, label):
        if record.has_key(label) and record[label] is not None:
            if WeatherBug._formats.has_key(label):
                return WeatherBug._formats[label] % record[label]
            return str(record[label])
        return ''
