# $Id: owm.py 736 2013-12-13 16:22:54Z mwall $
# Copyright 2013 Matthew Wall

#==============================================================================
# OpenWeatherMap.org
#==============================================================================
# Upload data to OpenWeatherMap
#
# To enable this module, put this file in bin/user, add the following to
# weewx.conf, then restart weewx.
#
# [StdRESTful]
#     ...
#     [[OpenWeatherMap]]
#         username = OWM_USERNAME
#         password = OWM_PASSWORD
#         station_name = STATION_NAME
#         driver = user.owm.OpenWeatherMap

# FIXME: the openweathermap api includes no timestamp

import base64
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, 'owm: %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 OpenWeatherMap(REST):
    """Upload using the OpenWeatherMap protocol.
    """

    _VERSION = 0.1
    _SERVER_URL = 'http://openweathermap.org/data/post'
    _data_map = {'wind_dir':   ('windDir',     '%.0f', 1.0), # degrees
                 'wind_speed': ('windSpeed',   '%.1f', 0.2777777777), # m/s
                 'wind_gust':  ('windGust',    '%.1f', 0.2777777777), # m/s
                 'temp':       ('outTemp',     '%.1f', 1.0), # C
                 'humidity':   ('outHumidity', '%.0f', 1.0), # percent
                 'pressure':   ('barometer',   '%.3f', 1.0), # mbar?
                 'rain_1h':    ('hourRain',    '%.2f', 0.1), # mm
                 'rain_24h':   ('rain24',      '%.2f', 0.1), # mm
                 'rain_today': ('dayRain',     '%.2f', 0.1), # mm
                 'snow':       ('snow',        '%.2f', 0.1), # mm
                 'lum':        ('luminosity',  '%.2f', 1.0), # W/m^2
                 'radiation':  ('radiation',   '%.2f', 1.0), # ?
                 'dewpoint':   ('dewpoint',    '%.1f', 1.0), # C
                 'uv':         ('UV',          '%.2f', 1.0)}

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

        site: The upload site ("OpenWeatherMap")

        username: OpenWeatherMap username
        [Required]

        password: OpenWeatherMap password
        [Required]

        station_name: station name
        [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]

        altitude: Station altitude
        [Required]
        
        server_url: Base URL for the OpenWeatherMap 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.altitude = float(kwargs['altitude'])
        self.username = kwargs['username']
        self.password = kwargs['password']
        self.station_name = kwargs['station_name']
        self.interval = int(kwargs.get('interval', 300))
        self.max_tries = int(kwargs.get('max_tries', 3))
        self.timeout   = int(kwargs.get('timeout', 60))
        self.skip_upload= kwargs.get('skip_upload', False)
        self._lastpost = None
        # FIXME: restful needs this
        self.station = kwargs.get('station', self.station_name)

    def postData(self, archive, time_ts):
        """Post data to the 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

        data = self.getData(archive, time_ts)
        logdbg('data encoded: %s' % urllib.urlencode(data))
        if self.skip_upload:
            raise SkippedPost, 'skip_upload=True'

        for _count in range(self.max_tries):
            # Now use an HTTP POST to post the data.
            try:
                _request = urllib2.Request(self.server_url)
                _request.get_method = lambda: 'POST'
                _request.add_header("User-Agent",
                                    "weewx/%s" % weewx.__version__)
                b64s = base64.encodestring('%s:%s' % (self.username, self.password)).replace('\n', '')
                _request.add_header("Authorization", "Basic %s" % b64s)
                _response = urllib2.urlopen(_request, urllib.urlencode(data),
                                            self.timeout)
            except (urllib2.URLError, socket.error, httplib.BadStatusLine), e:
                logerr('Failed upload attempt %d to %s: %s' %
                       (_count+1, self.site, e,))
            else:
                txt = _response.read()
                code = _response.getcode()
                logdbg("code: %s" % code)
                logdbg("read: '%s'" % txt)
                if code != 200 :
                    logerr('Failed upload to %s: %s' % (self.site, txt))
                    raise weewx.restful.FailedPost, txt
                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 getData(self, archive, time_ts):
        """Format data for posting.
        
        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
        if record['usUnits'] != weewx.METRIC:
            converter = weewx.units.StdUnitConverters[weewx.METRIC]
            record = converter.convertDict(record)

        # put data into expected scaling, structure, and format
        values = {}
        values['name'] = self.station_name
        values['lat'] = str(self.latitude)
        values['lon'] = str(self.longitude)
        values['alt'] = str(self.altitude) # meter
        for key in self._data_map:
            rkey = self._data_map[key][0]
            if record.has_key(rkey) and record[rkey] is not None:
                v = record[rkey] * self._data_map[key][2]
                values[key] = self._data_map[key][1] % v

        logdbg('data: %s' % values)
        return values
