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

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

    [StdRESTful]
        ...
        [[SmartEnergyGroups]]
            token = TOKEN
            station = station_name
            driver = user.seg.SEG
"""

import httplib
import socket
import syslog
import urllib
import urllib2

import weewx
import weewx.restful

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

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

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

class SEG(weewx.restful.REST):
    """Upload to a smart energy groups server. 
    
    For details about smart energy groups, see http://smartenergygroups.com
    """

    # Types and formats of the data to be published:        weewx  seg default
    _formats = {'barometer'   : 'barometric_pressure %.3f', # inHg   mbar
                'outTemp'     : 'temperature %.1f',         # F      C
                'outHumidity' : 'relative_humidity %.0f',   # %      %
#                'inTemp'      : 'temperature_in %.1f',     # F      C
#                'inHumidity'  : 'humidity_in %03.0f',      # %      %
                'windSpeed'   : 'wind_speed %.2f',          # mph    m/s
                'windDir'     : 'wind_direction %03.0f',    # compass degree
                'windGust'    : 'wind_gust %.2f',           # mph    m/s
                'dewpoint'    : 'dewpoint %.1f',            # F      C
                'rain'        : 'rainfall %.2f',            # in     mm
                'dayRain'     : 'day_rainfall %.2f',        # in     mm
                'radiation'   : 'illuminance %.2f',         # W/m^2  W/m^2
                'UV'          : 'UV %.2f'}                  # number

    # unit conversions
    _units = {'barometer' : ['group_pressure','inHg','mbar'],
              'outTemp'   : ['group_temperature','degree_F','degree_C'],
              'windSpeed' : ['group_speed','mile_per_hour','meter_per_second'],
              'windGust'  : ['group_speed','mile_per_hour','meter_per_second'],
              'dewpoint'  : ['group_temperature','degree_F','degree_C'],
              'rain'      : ['group_rain','inch','mm'],
              'dayRain'   : ['group_rain','inch','mm'] }

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

        token: unique token
        [Required]
        
        station: station identifier - the seg node
        [Required]

        server_url: URL of the server
        [Optional. Default is the smart energy groups 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['station']
        self.server_url = kwargs.get('url', 'http://api.smartenergygroups.com/api_sites/stream')
        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 smart energy groups protocol.

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

        _url = self.getURL()
        _data = self.getData(archive, time_ts)
        if _data is None:
            logdbg('nothing to upload')
            return
        logdbg('url: %s' % _url)
        logdbg('data: %s' % _data)
        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__)
                _request.get_method = lambda: 'PUT'
                _response = urllib2.urlopen(_request, _data, 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):
        return self.server_url

    def getData(self, archive, time_ts):
        """Return the data for posting to smart energy groups.
        
        archive: An instance of weewx.archive.Archive
        
        time_ts: The record desired as a unix epoch time.
        """

        record = self.extractRecordFrom(archive, time_ts)
        elements = []
        for k in SEG._formats:
            v = record[k]
            if v is not None:
                if k in SEG._units:
                    v = self._convert(v, SEG._units[k][0],
                                      SEG._units[k][1], SEG._units[k][2])
                s = SEG._formats[k] % v
                elements.append('(%s)' % s)
        if len(elements) == 0:
            return None
        node = urllib.quote_plus(self.station)
        elements.insert(0, '(node %s %s ' % (node, time_ts))
        elements.append(')')
        elements.insert(0, 'data_post=(site %s ' % self.token)
        elements.append(')')
        data = ''.join(elements)
        logdbg('data: %s' % data)
        return data

    def _convert(self, value, group, from_units, to_units):
        vt = (value, from_units, group)
        return weewx.units.convert(vt, to_units)[0]
