# $Id: cosm.py 737 2013-12-14 14:27:30Z mwall $
# Copyright 2013 Matthew Wall

#==============================================================================
# COSM
#==============================================================================
# Upload data to COSM (aka Pachube, Xively)
# https://xively.com/
#
# Installation:
# 1) put this file in bin/user
# 2) add the following configuration stanza to weewx.conf
# 3) restart weewx
#
# [StdRESTful]
#     ...
#     [[COSM]]
#         token = TOKEN
#         feed = FEED_ID
#         station = station_name
#         driver = user.cosm.COSM

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

import weewx
import weewx.restful
from weeutil.weeutil import tobool

try:
    import cjson as json
    # XXX: maintain compatibility w/ json module
    setattr(json, 'dumps', json.encode)
    setattr(json, 'loads', json.decode)
except Exception, e:
    try:
        import simplejson as json
    except Exception, e:
        import json

def logmsg(level, msg):
    syslog.syslog(level, 'cosm: %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 COSM(weewx.restful.REST):
    """Upload to a cosm server."""

    _VERSION = 0.2
    _SERVER_URL = 'http://api.cosm.com/v2/feeds'

    # Types and formats of the data to be published:
    _formats = {'barometer'   : 'barometer %.3f',        # inHg
                'outTemp'     : 'temperature_out %.1f',  # F
                'outHumidity' : 'humidity_out %03.0f',   # %
#                'inTemp'      : 'temperature_in %.1f',  # F
#                'inHumidity'  : 'humidity_in %03.0f',   # %
                'windSpeed'   : 'windSpeed %.2f',        # mph
                'windDir'     : 'windDir %03.0f',        # compass degree
                'windGust'    : 'windGust %.2f',         # mph
                'dewpoint'    : 'dewpoint %.1f',         # F
                'rain24'      : 'rain24 %.2f',           # in
                'hourRain'    : 'hourRain %.2f',         # in
                'dayRain'     : 'dayRain %.2f',          # in
                'radiation'   : 'radiation %.2f',        # W/m^2
                'UV'          : 'UV %.2f'}               # number

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

        token: unique token
        [Required]

        feed: the cosm feed
        [Required]

        station: station identifier
        [Optional. Default is None]

        url: URL of the server
        [Optional. Default is the cosm 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.feed       = kwargs['feed']
        self.station    = kwargs.get('station', None)
        self.server_url = kwargs.get('url', self._SERVER_URL)
        self.token      = kwargs['token']
        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))

    def postData(self, archive, time_ts):
        """Post using the COSM 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')
            raise weewx.restful.SkippedPost, 'nothing to upload'
        logdbg('url: %s' % _url)
        logdbg('data: %s' % _data)
        if self.skip_upload:
            raise weewx.restful.SkippedPost, 'skip_upload=True'

        for _count in range(self.max_tries):
            # Now use an HTTP PUT to post the data.
            try:
                _request = urllib2.Request(_url)
                _request.add_header("User-Agent",
                                    "weewx/%s" % weewx.__version__)
                _request.add_header("X-PachubeApiKey", self.token)
                _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 upload attempt %d to %s: %s' %
                       (_count+1, self.site, e,))
            else:
                # No exception thrown, but must still check for errors.
                # if you call read() on the response twice, the second is empty
                txt = _response.read()
                logdbg("code: %s" % _response.getcode())
                logdbg("read: '%s'" % txt)
                if txt != '':
                    logerr('Failed upload to %s: %s' % (self.site, txt))
                    raise weewx.restful.FailedPost, txt
                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.site)
            raise IOError, 'Failed upload to %s after %d tries' % (
                self.site, self.max_tries)

    def getURL(self):
        return '%s/%s' % (self.server_url, self.feed)
        
    def getData(self, archive, time_ts):
        """Format a single archive record for posting to COSM as JSON data.
        
        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
        tstr = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(time_ts))
        streams = {}
        for k in COSM._formats:
            v = record[k]
            if v is not None:
                dskey = '%s_%s' % (station, k) if station is not None else k
                if not dskey in streams:
                    streams[dskey] = {'id':dskey, 'datapoints':[]}
                dp = {'at':tstr, 'value':v}
                streams[dskey]['datapoints'].append(dp)
        if len(streams.keys()) == 0:
            return None
        data = {
            'version':'1.0.0',
            'datastreams':[]
            }
        for k in streams.keys():
            data['datastreams'].append(streams[k])
        data = json.dumps(data)
        return data
