# $Id: owm.py 752 2014-01-01 16:15:47Z mwall $
# Copyright 2013 Matthew Wall

#==============================================================================
# OpenWeatherMap.org
#==============================================================================
# Upload data to OpenWeatherMap
#  http://openweathermap.org
#
# Thanks to Antonio Burriel for the dewpoint, longitude, and radiation fixes.
#
# Installation:
# 1) put this file in bin/user
# 2) add the following configuration stanza to weewx.conf
# 3) restart weewx
#
# [StdRESTful]
#     ...
#     [[OpenWeatherMap]]
#         username = OWM_USERNAME
#         password = OWM_PASSWORD
#         station_name = STATION_NAME
#         driver = user.owm.OpenWeatherMap

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

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

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.

    The OpenWeatherMap api does not include timestamp, so we can only upload
    the latest observation.
    """

    _VERSION = 0.2
    _SERVER_URL = 'http://openweathermap.org/data/post'
    _data_map = {
        'wind_dir':   ('windDir',     '%.0f', 1.0, 0.0), # degrees
        'wind_speed': ('windSpeed',   '%.1f', 0.2777777777, 0.0), # m/s
        'wind_gust':  ('windGust',    '%.1f', 0.2777777777, 0.0), # m/s
        'temp':       ('outTemp',     '%.1f', 1.0, 0.0), # C
        'humidity':   ('outHumidity', '%.0f', 1.0, 0.0), # percent
        'pressure':   ('barometer',   '%.3f', 1.0, 0.0), # mbar?
        'rain_1h':    ('hourRain',    '%.2f', 10.0, 0.0), # mm
        'rain_24h':   ('rain24',      '%.2f', 10.0, 0.0), # mm
        'rain_today': ('dayRain',     '%.2f', 10.0, 0.0), # mm
        'snow':       ('snow',        '%.2f', 10.0, 0.0), # mm
        'lum':        ('radiation',   '%.2f', 1.0, 0.0), # W/m^2
        'dewpoint':   ('dewpoint',    '%.1f', 1.0, 273.15), # K
        'uv':         ('UV',          '%.2f', 1.0, 0.0),
        }
    
    def __init__(self, site, **kwargs):
        """Initialize for posting data.

        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 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 = tobool(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, 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['long'] = 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] + self._data_map[key][3]
                values[key] = self._data_map[key][1] % v

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