#!/usr/bin/python
#
# arch-tag: NPC loads module
# Time-stamp: <2005-06-21 16:17:47 mike>
#
from NPCData import adc_offset, dio_offset
from io import IOP, ADC
from elementtree import ElementTree
import xmlenc as xml

__version__ = '1.6'

def nxor(a, b):
    """NOT XOR function """
    return ~(a ^ b) & 1

class Load(object):
    """Class to represent the state of a load on the Node Power
    Controller.
    """
    __state = ['current', 'current_limit', 'switch', 'deadface',
               'breaker', 'error', 'name', 'bus', 'port']
    def __init__(self, desc, dsrc):
        self.__dict__.update(desc)
        self.__dict__.setdefault('activestate', 0)
        self.adc = ADC(dsrc=dsrc, **self.adc)
        self.error = 'n/a'
        self._data = []
        self.__dict__.setdefault('cal', [0., 10000.])
        if self.current_limit == 0:
            self.current_limit = self._to_mA(self.adc.get_limit())
        else:
            self.adc.set_limit(self._to_V(self.current_limit))
        self.power_sw = IOP(dsrc=dsrc, **self.power_sw)
        self.adc.link_sw(self.power_sw)
        try:
            self.isolation_sw = IOP(dsrc=dsrc, **self.isolation_sw)
            self.circuit_breaker = IOP(dsrc=dsrc, **self.circuit_breaker)
        except AttributeError:
            # isolation_sw and circuit_breaker are optional so ignore the
            # exception if they weren't specified
            pass

    def __setstate__(self, d):
        self.__dict__.update(d)
        if self.current_limit == 0:
            self.current_limit = self._to_mA(self.adc.get_limit())
        else:
            self.adc.set_limit(self._to_V(self.current_limit))
            
    def _getdf(self):
        try:
            df = self.isolation_sw.read(self._data)
        except AttributeError:
            df = 1
        return df
    deadface = property(_getdf, doc='State of the deadface switch')
    
    def _getcb(self):
        try:
            cb = self.circuit_breaker.read(self._data) ^ 1
        except AttributeError:
            cb = 1
        return cb
    breaker = property(_getcb, doc='State of the circuit breaker')
    
    def _getsw(self):
        return nxor(self.power_sw.read(self._data), self.activestate)
    switch = property(_getsw, doc='State of the power switch')
    
    def _getcur(self):
        return self._to_mA(self.adc.read(self._data))
    current = property(_getcur, doc='Current drawn by the load')
    
    def _to_mA(self, v):
        """Convert A/D voltage to a current value in mA"""
        return int(self.cal[0] + v*self.cal[1])

    def _to_V(self, mA):
        """Convert a current value in mA to A/D volts"""
        return (float(mA) - self.cal[0])/self.cal[1]
    
    def on(self):
        """Switch load on by setting the isolation switch and
        clearing the power switch."""
        try:
            self.isolation_sw.set()
        except AttributeError:
            pass
        self.adc.clear_error()
        self.error = ''
        if self.activestate:
            self.power_sw.set()
        else:
            self.power_sw.clear()
        
    def off(self):
        """Switch load off by setting the power switch"""
        self.adc.clear_error()
        self.error = ''
        if self.activestate:
            self.power_sw.clear()
        else:
            self.power_sw.set()
        self.unisolate()

    def isolate(self, reason=None):
        """Isolate a load"""
        self.off()
        try:
            self.isolation_sw.clear()
        except AttributeError:
            pass
        if reason:
            self.error = reason

    def unisolate(self):
        """Un-isolate a load"""
        try:
            self.isolation_sw.set()
        except AttributeError:
            pass
        
    def status(self, record):
        self._data = record
        # Check for over-current errors
        if self.adc.error():
            self.error = 'over-current'
        d = {}
        for name in self.__state:
            d[name] = getattr(self, name)
        return d

    def update(self, d):
        if d.has_key('current_limit'):
            self.current_limit = d['current_limit']
            self.adc.set_limit(self._to_V(self.current_limit))
        if d.has_key('state'):
            if d['state'] == 0:
                 self.off()
            elif d['state'] == 1:
                self.on()
            elif d['state'] == -1:
                self.isolate(reason='client request')
        if d.has_key('name'):
            self.name = d['name']
            
    def as_xml(self):
        """Return the XML description of the load as an ElementTree object"""
        root = ElementTree.Element('load')
        root.set('version', __version__)
        root.set('id', self.name)
        root.set('bus', self.bus)
        root.set('port', str(self.port))
        root.set('activestate', str(self.activestate))
        root.append(xml.sensor('current', self.adc, units='mA',
                               cal=self.cal,
                               name=self.name + '/current'))
        for name in ('power_sw', 'isolation_sw', 'circuit_breaker'):
            try:
                root.append(xml.switch(self.__dict__[name], name))
            except KeyError:
                pass
        return root
