# $Id: emoncms.py 572 2013-04-03 14:44:45Z mwall $
# Copyright 2013 Matthew Wall
"""weewx module that uploads data to emoncms

   Put this file into the weewx user directory, then add this to weewx.conf:

    [StdRESTful]
        ...
        [[EmonCMS]]
            token = TOKEN
            station = name_of_station
            driver = user.emoncms.EmonCMS
"""

import httplib
import socket
import syslog
import urllib
import urllib2

import weewx
import weewx.restful

def logdbg(msg):
    syslog.syslog(syslog.LOG_DEBUG, 'emoncms: %s' % msg)

def loginf(msg):
    syslog.syslog(syslog.LOG_INFO, 'emoncms: %s' % msg)

def logerr(msg):
    syslog.syslog(syslog.LOG_ERR, 'emoncms: %s' % msg)

class EmonCMS(weewx.restful.REST):
    """Upload to an emoncms server. 
    
    For details about emoncms (part of open energy monitor), see
    http://emoncms.org
    """

    # Types and formats of the data to be published:
    _formats = {'barometer'   : 'barometer_inHg:%.3f',
                'outTemp'     : 'outTemp_F:%.1f',
                'outHumidity' : 'outHumidity:%03.0f',
#                'inTemp'      : 'inTemp_F:%.1f',
#                'inHumidity'  : 'inHumidity:%03.0f',
                'windSpeed'   : 'windSpeed_mph:%.2f',
                'windDir'     : 'windDir:%03.0f',
                'windGust'    : 'windGust_mph:%.2f',
                'dewpoint'    : 'dewpoint_F:%.1f',
                'rain'        : 'rain_in:%.2f',
                'dayRain'     : 'dayRain_in:%.2f',
                'radiation'   : 'radiation:%.2f',
                'UV'          : 'UV:%.2f'}

    def __init__(self, site, **kwargs):
        """Initialize for a given upload site.

        token: unique token
        [Required]
        
        station: station identifier
        [Optional. Default is None]

        server_url: URL of the server
        [Optional. Default is the emoncms.org site]
        
        max_tries: maximum number of tries before giving up
        [Optional. Default is 3]

        timeout: timeout in seconds
        [Optional. Default is 60]

        skip_upload: debugging option to display data but do not upload
        [Optional. Default is False]
        """
        
        self.site       = site
        self.station    = kwargs.get('station', None)
        self.server_url = kwargs.get('url', 'http://emoncms.org/input/post')
        self.token      = kwargs['token']
        self.max_tries  = int(kwargs.get('max_tries', 3))
        self.timeout    = int(kwargs.get('timeout', 60))
        self.skip_upload= kwargs.get('skip_upload', False)

    def postData(self, archive, time_ts):
        """Post using the EmonCMS protocol.

        archive: An instance of weewx.archive.Archive
        
        time_ts: The record desired as a unix epoch time.
        """

        _url = self.getURL(archive, time_ts)
        logdbg('url: %s' % _url)
        if self.skip_upload:
            return

        # Retry up to max_tries times:
        for _count in range(self.max_tries):
            # Now use an HTTP GET to post the data. Wrap in a try block
            # in case there's a network problem.
            try:
                _request = urllib2.Request(_url)
                _request.add_header("User-Agent",
                                    "weewx/%s" % weewx.__version__)
                _response = urllib2.urlopen(_request, None, self.timeout)
            except (urllib2.URLError, socket.error, httplib.BadStatusLine), e:
                # Unsuccessful. Log it and go around again for another try
                logerr('Failed attempt %d to upload to %s' %
                       (_count+1, self.server_url))
                logerr('   **** Reason: %s' % (e,))
            else:
                # No exception thrown, but must still check for errors.
                logdbg('response: %s' % '\n'.join(_response))
                for line in _response:
                    # FIXME: get proper failure responses
                    if line.startswith('ERROR') or line.startswith('INVALID'):
                        # No reason to retry. Log it and raise an exception.
                        logerr('%s returned %s. Aborting.' %
                               (self.server_url, line))
                        raise FailedPost, line
                return
        else:
            # This is executed only if the loop terminates normally, meaning
            # the upload failed max_tries times. Log it.
            logerr('Failed upload to %s' % self.server_url)
            raise IOError, 'Failed upload to %s after %d tries' % (
                self.server_url, self.max_tries)

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

        record = self.extractRecordFrom(archive, time_ts)
        station = urllib.quote_plus(self.station) \
            if self.station is not None else None
        data = []
        for k in EmonCMS._formats:
            v = record[k]
            if v is not None:
                s = EmonCMS._formats[k] % v
                if station is not None:
                    s = '%s_%s' % (station, s)
                data.append(s)
        _url = '%s?apikey=%s&time=%s&json={%s}' % (
            self.server_url, self.token, time_ts, ','.join(data))
        return _url
