#!/usr/bin/python
#
# arch-tag: simulated interface to node power controller
# Time-stamp: <2004-12-17 14:40:14 mike>
#
import threading
import time
from NPCData import adc_offset, dio_offset

__version__ = '1.0'

class Board(object):
    """Simulate the I/O board"""
    def __init__(self):
        self.dio = [255, 255, 255, 0]
        self.adc = [0] * 32
        self.errs = 0
    def _getrec(self):
        return self.dio + self.adc
    record = property(_getrec, doc='Get latest sample record')


class Device(object):
    """Class to simulate the XML-RPC interface of the remote device"""
    def __init__(self, nboards=4):
        self.boards = []
        for i in range(4):
            self.boards.append(Board())
            
    def _getrec(self):
        return reduce(lambda a,b: a+b, [b.record for b in self.boards])
    
    def iopSet(self, board, port, mask):
        b = self.boards[board]
        b.dio[port] |= mask
        return b.dio[port]

    def iopClear(self, board, port, mask):
        b = self.boards[board]
        b.dio[port] &= ~mask
        return b.dio[port]

    def setADerror(self, board, chan):
        mask = 1L << chan
        self.boards[board].errs |= mask

    def clearADerror(self, board, chan):
        mask = 1L << chan
        self.boards[board].errs &= ~mask

    def getADerrors(self):
        return [b.errs for b in self.boards]

    def setADmax(self, *args):
        pass

    def setADswitch(self, *args):
        pass
    
    record = property(_getrec, doc='Get latest sample record')
    
class NPC(object):
    def __init__(self, host):
        self.boards = []
        tstamp = time.time()
        secs = int(tstamp)
        usecs = int((tstamp - secs)*1e6)
        self.record = [secs, usecs, 4]
        self.dev = Device(4)
        self._fmt = '<' + ('4B32h' * 4)
        self.record = self.dev.record
        self.interval = 1
        self.running = 0
        self.monitor = None
        self.host = host
        self.listeners = []
        self.logger = None
        
    def __getstate__(self):
        d = {}
        d.update(self.__dict__)
        d['logger'] = None
        return d

    def __setstate__(self, d):
        self.__dict__.update(d)
        
    def _action(self):
        record = None
        tstamp = time.time()
        secs = int(tstamp)
        usecs = int((tstamp - secs)*1e6)
        record = [secs, usecs, 4] + self.dev.record
        if record:
            self._lock.acquire()
            self.record = record
            self._lock.release()
        
    def __call__(self):
        """Thread function.  Executes the _action method on a periodic
        schedule using the specified interval.
        """
        listeners = self.listeners
        action = self._action
        inc = self.interval
        self.running = 1
        t0 = time.time()
        while self.running:
            t = time.time()
            action()
            for l in listeners:
                l.recvSample(self.record)
            t0 += inc
            time.sleep(max(t0-t, 0))

    def register(self, obj):
        """Add an object to the listeners list.  Each listener must implement
        a method named recvSample.  On each poll, this method will be called
        with the sample record as an argument.
        """
        try:
            if callable(getattr(obj, 'recvSample')):
                self.listeners.append(obj)
        except AttributeError:
            pass

    def set_logger(self, logger=None):
        self.logger = logger
        
    def getSample(self):
        """Return the most recent sample record"""
        if not self.running:
            return self.record
        self._lock.acquire()
        rval = self.record
        self._lock.release()
        return rval

    def data_format(self):
        """Return the struct.unpack format string for the data
        portion of the sample record.
        """
        return self._fmt
    
    def start_poll(self, interval=1):
        """Start a thread to poll the NPC at the specified interval
        (in seconds).  An optional handler function may be specified.
        The handler function is called after each sample and is passed
        a single argument, a binary string containing the data record.
        """
        self.interval = interval
        self._lock = threading.Lock()
        self.monitor = threading.Thread(target=self)
        self.monitor.setDaemon(1)
        self.monitor.start()

    def stop_poll(self):
        self.running = 0
        self.monitor.join(timeout=self.interval*2)
        self._lock = None
        self.monitor = None
        
    def is_running(self):
        return self.running

    def sample_time(self):
        return self.record[0:2]

    def __getattr__(self, name):
        """Dispatch remote function calls to the Device object"""
        return getattr(self.dev, name)
    
