#!/usr/bin/env python

SoftwareInfo = \
    { 'Function' : 'TEMPLATE for controller programs',
      'Design'   : 'CreativePeach c 2013',
      'Version'  : '1.0',
    }  

from cpphlib import *
import getopt
import sys
import termios, tty
        
class MYCONTROLLER:

    def __init__(self, CAEIF, IOIF, verbose, debug):
        """Initializing class of the control program"""
        
        # pass option flags to the class
        self.verbose = verbose
        self.debug = debug
             
        ## if want to write a debug log, then should create a PPDEBUG instance for MYCONTROLLER
        #self.DBG = PPDEBUG("dbg.log", True)
        #if debug:
        #    self.DBG.writetolog("MyController class instance is initializing")
                                 
        # Or can write messages to the console
        if self.verbose:
            print "MyController class instance is initializing"
            
        # pass interfaces to MYCONTROLLER
        self.CAEIF = CAEIF
        self.IOIF = IOIF
        
        # create a PPIOCARDGADGETS instance for MYCONTROLLER
        self.IOCG = PPIOCARDGADGETS(self.CAEIF, self.CAEIF)
        
        # create a PPBITMANIPULATOR instance for MYCONTROLLER
        self.BM = PPBITMANIPULATOR()
        
        # initialize address storage variables of the cards used for controlling
        # check hardwares to make sure if they are in the system
        # the hardware check passes card addresses to self.DGOAddr and self.DGIAddr variables,
        # or gives an error message, and then need to exit.
        self.DGOAddr = -1
        self.DGIAddr = -1
        if not(self.hardwarecheck()):
            # it is needed to send message to the log, or to the console, and then exit
            sys.exit(1)
        
        # if you still want to use any kind of handling class from cpphlib,
        # then make their instances for the MYCONTROLLER
        self.DIOH = PPDIGINOUTHANDLER(self.CAEIF, self.IOIF)
        self.BLINKTIMER = PPPASSIVETIMER(0.1, True)
            
        # if you use the PPDIGINOUTHANDLER, then register the IO channels, for the easiest usage.
        self.DIOH.registeroutchannel("BLINK LED", self.DGOAddr, 0, 7) # block 0 LED 7
        self.DIOH.registerinchannel("EXIT BUTTON", self.DGIAddr, 1, 7) # block 1 button 7
        # register input aliases if you want
        self.DIOH.registerinoneventaliases(["PUSH"])
        
        # Finally run the core of control program
        self.run()    

 
    def hardwarecheck(self):
        """Identifying used cards, and their addresses based on discover dict"""
        
        ## cycle runs through whole address range, and identifies the cards
        #for addr in range(16):
        #    if self.IOCG.DiscoverDict.get(addr) == "16 channel DIGITAL INPUT":
        #        self.DGIAddr = addr
        #    if self.IOCG.DiscoverDict.get(addr) == "16 channel DIGITAL OUTPUT":
        #        self.DGOAddr = addr
                
        # or you can look for cards by type
        ICards = self.IOCG.searchcards("16 channel DIGITAL INPUT")
        if ICards:
           self.DGIAddr = ICards[0] # warning! More input cards can be contained by the system. 
        OCards = self.IOCG.searchcards("16 channel DIGITAL OUTPUT")
        if OCards:
           self.DGOAddr = OCards[0] # warning! More output cards can be contained by the system.
               
        # if all the cards are found, it returns with True
        if self.DGOAddr == -1 or self.DGIAddr == -1:            
            return False
        else:
            return True           


    def getkey(self):
        """Non blocking keyboard reader for console input or mixed input applications"""
        file = sys.stdin.fileno()
        mode = termios.tcgetattr(file)
        try:
            ch = None
            inFile = None
            tty.setraw(file, termios.TCSANOW)
            inFile, unused1, unused2 = select.select([sys.stdin],[],[],0.001)
            if inFile:
                ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(file, termios.TCSANOW, mode)
        return ch
    

    def run(self):
        """The core of controller class"""
        # turn off every polling on the cards, and the driver
        # if one or another was turned on, which we do not need now
        for addr in range(16):
            if self.IOCG.DiscoverDict.get(addr) != "EMPTY":
                if self.IOCG.CardIOTypes.get(self.IOCG.DiscoverDict.get(addr)) == "INP":
                    self.CAEIF.commandstatemachine("POLLOFF:%X\r\n"%addr,">",500)
        
        # turn on polling on the used input card, and the driver.
        self.CAEIF.commandstatemachine("POLLON:%X\r\n"%self.DGIAddr,">",500)
        self.CAEIF.commandstatemachine("ENABLEPOLL\r\n",">",500)    
        
        # initialize the local variables
        Blink = False
        ToNextInputHandle = None
        
        # the controller working loop (neverending cycle)
        while(1):
            
            # test input changes on every cycle
            # in this program there is only one event handler, so DIOH.checkinchannelchages must be master
            ToNextInputHandle = self.DIOH.chekinchannelschanges("MASTER")
              
            # if you want to exit from the working loop, you can do it pushing EXIT button on input card
            if self.DIOH.inputevent("EXIT BUTTON", "PUSH"):
                break
            
            ## or you can exit from the console pushed q key's help too, 
            ## if you uncomment next 3 lines
            #ch = self.getkey()
            #if ch == "q":
            #    break
            
            # in this case the DIOH.chekinchannelschanges is set to MASTER, so it grabs all input change data
            # from PPDRIVER IO interface's queue, what it gives toward after processing, so it gives a chance
            # to use it in another handler. Here is an other own eventhandler is implemented for the input card
            # block 0's datachanges' reading out in whole.
            if ToNextInputHandle:
                # it is only interesting, if the data is coming from the input card's BLOCK0, because 
                # that is what we'd like to put to the output of the output card's BLOCK1.
                if "a%Xb0"%self.DGIAddr in ToNextInputHandle:
                    # isolate Data coming in from input from the whole input string, and we invert it 
                    # because physical outputs have negative logic, 
                    # and we send the output to the desired block directly
                    self.IOIF.send("a%Xb1d%s\r\n"%(self.DGOAddr, self.BM.inverse(ToNextInputHandle[5:7])))
            
            # timer event handler
            if self.BLINKTIMER.isexpired():
                Blink = not(Blink)
                if Blink:
                    self.DIOH.switch("BLINK LED", "ON")     
                else:
                    self.DIOH.switch("BLINK LED", "OFF")
                self.BLINKTIMER.reset()
                
            # send the output value changes in every cycle
            self.DIOH.sendoutputs()
          
        # if lost the working loop, then we must turn off all outputs
        self.DIOH.switchoffall(True)          
        
        # and turn off polling on the used input card, and driver
        self.CAEIF.commandstatemachine("DISABLEPOLL\r\n",">",500)
        self.CAEIF.commandstatemachine("POLLOFF:%X\r\n"%self.DGIAddr,">",500) 
        

def usage():
    sys.stdout.write("""\r\nTemplate controller %s %s \r\n\

USAGE: ./template.py [options]  

    options:
    --------
    -h  : print this help and exit
    -d  : write events to debug log
    -v  : verbose mode    
    
    More info, if you want to write them. For example:
    
    Hardware reqirements: two cards (PP-DI16-A and PP-DO16-A)
    
    Template controller blinks  BLOCK0 LED7 on digital output card,
    and read digital input card all BUTTONS of BLOCK0, forward states to
    digital output card BLOCK1. If you want to stop controller program
    you have to push BLOCK1 BUTTON7.
              
"""%(SoftwareInfo.get('Version'), SoftwareInfo.get('Design')))


if __name__ == '__main__': 
    """Main program"""
    Options, Arguments = getopt.getopt(sys.argv[1:], "dvh")
    verbose = False
    debug = False
    
    # parsing of option flags
    for option in Options:            
        if option[0] == "-v":    
            verbose = True
        if option[0] == "-h":
            # if you defined -h, then it displays the user manual, and quits.
            usage()
            sys.exit(0)
        if option[0] == "-d":    
            debug = True
    
    # create a PPDRIVER class instance
    PPDriver = PPDRIVER();
    
    # have to define which port is listened by the CAE interface, and wich machine is the ppds runs on. 
    PPDriver.registerCAEinterface("localhost", 8000) 
    
    # have to define which port is listened by the IO interface, and wich machine is the ppds runs on.
    PPDriver.registerIOinterface("localhost", 12000)
    
    # have to define which port is listened by the MON interface, and wich machine is the ppds runs on 
    # but
    # it is useful to leave free for external terminal, or checking program
    #PPDriver.registerMONinterface("localhost", 12002)
    
    # connecting to registered interfaces
    PPDriver.connectinterfaces()
    
    # get interfaces (MONIF value is None if did not registrate it)
    CAEIF, IOIF, MONIF = PPDriver.getinterfaces()
    
    # create instance of controller class and pass connected interfaces, and option flags to this class 
    MyController = MYCONTROLLER(CAEIF, IOIF, verbose, debug)
    
    # disconnect from ppds's all interfaces after finish the controlling
    PPDriver.disconnectinterfaces()
    
    # exit from the program
    sys.exit(0)
    
