# Class that transmits data to Jace.

# This is kinda unavoidably hard-coded in some ways, depends completely on
# what points have been created on the other end...

# 21-Oct-11: 
# Added str() to text strings, have changed Mate class to report ints and floats 
# for my data-collection side of things...

import httplib, base64
import xml.parsers.expat

class Jace ():
    lastFX = {}
    lastCC = {}
    lastDC = {}

    fxModeCmd = ''
    fxAuxCmd = ''

    # Headers include authentication.  Created on class startup, stored
    # for future use.
    headers = ''
    server = ''
    conn = ''       # This will be the HTTP connection to the Jace

    def __init__ (self, server, user, passwd):
        self.server = server

        authString = 'Basic ' + base64.b64encode (user + ':' + passwd)
        self.headers = {'Authorization': authString, 'Content-type': 'text/xml'}

        self.conn = httplib.HTTPConnection (server)


    # For now, just updating whenever told to.  Any changes to values will
    # be propagated.  May want to modify this at some point so only changes
    # exceeding a particular delta are sent...

    # Passing in the data structure created by the Mate class.

    def update (self, data):
        
        # Pull out each device dictionary and process...
        for i in range(10):
            if data[i]['dev'] == 'FX':
                self.processFX (data[i])

            if data[i]['dev'] == 'CC':
                self.processCC (data[i])

            if data[i]['dev'] == 'DC':
                self.processDC (data[i])

        # Check FX mode setting for any updates.
        self.getCommands()


    def processFX (self, newFX):
        # Using a batch input method of the obix server to send multiple
        # updates at a time.
        if newFX == self.lastFX:
            return

        uriA = '  <uri is="obix:Invoke" val="http://' + self.server + '/obix/config/SolarSystem/FX/'
        uriB = '/set">\n    <real name="in" val="'
        uriC = '" />\n  </uri>\n'

        text = '<list is="obix:BatchIn">\n'

        names = ['invAmps', 'chgAmps', 'buyAmps', 'acInVolts', 'acOutVolts',
                'battVolts', 'fxMode', 'acMode']

        for name in names:
            if name not in self.lastFX or newFX[name] != self.lastFX[name]:
                # Update Jace with new value
                # Have to cast to str() to get old functionality - new Mate class uses int/float.
                text = text + uriA + name + uriB + str(newFX[name]) + uriC

        newList = newFX['errModes']
        if 'errModes' not in self.lastFX:
            oldList = [-1, -1, -1, -1, -1, -1, -1, -1]
        else:
            oldList = self.lastFX['errModes']

        names = ['errBackfeed', 'errShortedOutput', 'errHighBattery',
                'errPhaseLoss', 'errLowBattery', 'errOvertemp', 'errStackError',
                'errLowACOutput']
        for i in range(8):
            if newList[i] != oldList[i]:
                if newList[i] == 1:
                    value = 'true'
                else:
                    value = 'false'

                text = text + uriA + names[i] + uriB + value + uriC

        newList = newFX['fxMisc']
        if 'fxMisc' not in self.lastFX:
            oldList = [-1, -1]
        else:
            oldList = self.lastFX['fxMisc']

        # Only checking one item in this list...
        if newList[0] != oldList[0]:
            if newList[0] == 1:
                value = 'true'
            else:
                value = 'false'

            text = text + uriA + 'auxOutOn' + uriB + value + uriC

        newList = newFX['warnModes']
        if 'warnModes' not in self.lastFX:
            oldList = [-1, -1, -1, -1, -1, -1, -1, -1]
        else:
            oldList = self.lastFX['warnModes']

        names = ['warnFanFail', 'warnCommError', 'warnTempSensorFail',
                'warnBuyAmpsHigh', 'warnInVACLow', 'warnInVACHigh', 
                'warnACInFreqLow', 'warnACInFreqHigh']
        for i in range(8):
            if newList[i] != oldList[i]:
                if newList[i] == 1:
                    value = 'true'
                else:
                    value = 'false'

                text = text + uriA + names[i] + uriB + value + uriC

        # Close text block, then send to Jace!  And hope for the best...
        text = text + '</list>'

        self.conn.request ('POST', '/obix/batch/', text, self.headers)
        resp = self.conn.getresponse()

        if resp.status != 200:
            #print 'obix error!'
            pass
        else:
            # OK/200 just means the URL was okay.  Need to check XML for an
            # error message that it didn't properly update.  But then again, 
            # may not matter if it updates next time around...?

            #print 'successfully updated FX data'
            respdata = resp.read()

        self.lastFX = newFX.copy()


    def processCC (self, newCC):

        if newCC == self.lastCC:
            return

        uriA = '  <uri is="obix:Invoke" val="http://' + self.server + '/obix/config/SolarSystem/CC/'
        uriB = '/set">\n    <real name="in" val="'
        uriC = '" />\n  </uri>\n'

        text = '<list is="obix:BatchIn">\n'

        names = ['chgAmps', 'pvAmps', 'pvVolts', 'dailykWh', 'battVolts', 
                'dailyAH', 'auxMode', 'chgMode']

        for name in names:
            if name not in self.lastCC or newCC[name] != self.lastCC[name]:
                text = text + uriA + name + uriB + str(newCC[name]) + uriC

        newList = newCC['errMode']
        if 'errMode' not in self.lastCC:
            oldList = [-1, -1, -1]
        else:
            oldList = self.lastCC['errMode']

        names = ['errHighVOC', 'errTooHot', 'errShortedBattSensor']
        for i in range(3):
            if newList[i] != oldList[i]:
                if newList[i] == 1:
                    value = 'true'
                else:
                    value = 'false'

                text = text + uriA + names[i] + uriB + value + uriC

        # Close text block and send...
        text = text + '</list>'

        self.conn.request ('POST', '/obix/batch/', text, self.headers)
        resp = self.conn.getresponse()

        if resp.status != 200:
            #print 'obix error!'
            pass
        else:
            #print 'successfully updated CC data'
            respdata = resp.read()

        self.lastCC = newCC.copy()


    def processDC (self, newDC):

        if newDC == self.lastDC:
            return

        uriA = '  <uri is="obix:Invoke" val="http://' + self.server + '/obix/config/SolarSystem/DC/'
        uriB = '/set">\n    <real name="in" val="'
        uriC = '" />\n  </uri>\n'

        text = '<list is="obix:BatchIn">\n'

        names = ['battVolts', 'soc', 'battTemp', 'aAmps', 'bAmps', 'cAmps',
                'aAccumAH', 'aAccumKWH', 'bAccumAH', 'bAccumKWH', 'cAccumAH',
                'cAccumKWH', 'daysSinceFull', 'todayMinSOC', 'todayNetInAH', 
                'todayNetOutAH', 'todayNetInKWH', 'todayNetOutKWH', 
                'battNetInOut']

        # The 'edi' values aren't present in the Mate output until they have
        # been read at least once.  So, here have to check that the value is
        # present in new data before sending.
        for name in names:
            if name in newDC:
                if name not in self.lastDC or newDC[name] != self.lastDC[name]:
                    text = text + uriA + name + uriB + str(newDC[name]) + uriC

        names = ['relayMode', 'relayState', 'chgParmsMet', 'aEnable', 
                'bEnable', 'cEnable']

        for name in names:
            if name not in self.lastDC or newDC[name] != self.lastDC[name]:
                if newDC[name] == 1:
                    value = 'true'
                else:
                    value = 'false'

                text = text + uriA + name + uriB + value + uriC

        # Close block and write...
        text = text + '</list>'

        self.conn.request ('POST', '/obix/batch/', text, self.headers)
        resp = self.conn.getresponse()

        if resp.status != 200:
            #print 'obix error!'
            pass
        else:
            #print 'successfully updated DC data'
            respdata = resp.read()

        self.lastDC = newDC.copy()


    def getCommands (self):
        # Two commands are available.  FX mode control, and FX auxiliary
        # output control.

        text = '<list is="obix:BatchIn">\n  <uri is="obix:Read" val="http://' + self.server + '/obix/config/SolarSystem/FX/fxUseGridCmd/out" />\n  <uri is="obix:Read" val="http://' + self.server + '/obix/config/SolarSystem/FX/FXAuxCommand/out" />\n</list>'

        self.conn.request ('POST', '/obix/batch', text, self.headers)
        resp = self.conn.getresponse()

        if resp.status != 200:
            #print 'obix error!'
            pass
        else:
            #print 'successfully grabbed FX commands'
            respdata = resp.read()

            p = xml.parsers.expat.ParserCreate()
            p.StartElementHandler = self.start_element
            p.EndElementHandler = self.end_element
            p.CharacterDataHandler = self.char_data

            p.Parse (respdata, 1)

    def start_element (self, name, attrs):
        # Read values show up here.  In my case, I have one enum and one bool.
        if name == 'enum':
            self.fxModeCmd = attrs['val']

        if name == 'bool':
            self.fxAuxCmd = attrs['val']

    def end_element (self, name):
        pass

    def char_data (self, data):
        pass


