#KAME extention by python
from kame import *

#sa pytestdriver.py

#Simpler version of 4-terminal resistance measurement with AC.
#Defines KAME driver class inside python.
#XPythonSecondaryDriver, the base abstract class for KAME driver which analyzes data from the other connected drivers.
class Test4Res(XPythonSecondaryDriver):
    def __init__(self, name, runtime, tr, meas):
        XPythonSecondaryDriver.__init__(self, name, runtime, tr, meas)  #super().__init__ cannot be used.

        #setups Qt UIs, using .ui generated by Qt designer.
        form = self.loadUIFile(':/python/formpytestdriver.ui') #From Qt resource. see pydrivers.qrc
        #adds nodes, which can be later accessed. ex. self["name"].
        self.insert(XDoubleNode("Wait", False))
        self["Wait"] = 400 #ms
        self.insert(XBoolNode("Control", False))
        self.insert(XUIntNode("DMMChannel", False))

        entry = XScalarEntry("Resistance", False, self, "%.7g")
        self.insert(entry)   #self does NOT belong to tr yet inside the constructor. Never use insert(tr,...).
        meas["ScalarEntries"].insert(tr, entry) #tr: transaction obj. during the creation.
        #Driver selecters
        self.insert(XDMMItemNode("DMM", False, tr, meas["Drivers"], True)) # choosing XDMM-based class from the driver list.
        self.insert(XDCSourceItemNode("DCSource", False, tr, meas["Drivers"], True)) # choosing XDCSource-based class from the driver list.
        #setups link btw this driver and a selected driver.
        self.connect(self["DMM"])
        self.connect(self["DCSource"])

        #stores UI connectors during the lifetime.
        self.conns = [
            #synchronizes nodes and UIs.
            XQDoubleSpinBoxConnector(self["Wait"], form.findChildWidget("Wait")),
            XQToggleButtonConnector(self["Control"], form.findChildWidget("Control")),
            XQSpinBoxUnsignedConnector(self["DMMChannel"], form.findChildWidget("DMMChannel")),
            XQComboBoxConnector(self["DMM"], form.findChildWidget("DMM"), tr),
            XQComboBoxConnector(self["DCSource"], form.findChildWidget("DCSource"), tr),
            XQLCDNumberConnector(self["Resistance"]["Value"], form.findChildWidget("lcdResistance")),
            ]

    #Pickups valid snapshots before going to analyze().
    # shot_self: Snapshot for self.
    # shot_emitter: Snapshot for the event emitting driver.
    # shot_others: Snapshot for the other connected dirvers.
    def checkDependency(self, shot_self, shot_emitter, shot_others, emitter):
#        pdb.set_trace()
        dmm = shot_self[self["DMM"]].get() #selected driver.
        dcsrc = shot_self[self["DCSource"]].get() #selected driver.
        if emitter == dmm:
            shot_dcsrc = shot_others
            shot_dmm = shot_emitter
            wait = float(shot_self[self["Wait"]]) * 1e-3 #[s]
            if (shot_dmm[dmm].timeAwared() - shot_dcsrc[dcsrc].time()).total_seconds() < wait:
                return False
            return True #Good, approved
        return False #skipping this record.

    #Analyzes data acquired by the connected drivers.
    #Never include I/O operations, because transaction might repreat many times.
    def analyze(self, tr, shot_emitter, shot_others, emitter):
#        pdb.set_trace()
        dmm = tr[self["DMM"]].get() #selected driver.
        dcsrc = tr[self["DCSource"]].get() #selected driver.
        shot_dcsrc = shot_others
        shot_dmm = shot_emitter
        
        dmmch = int(tr[self["DMMChannel"]])
        volt = shot_dmm[dmm].value(dmmch)
        curr = float(shot_dcsrc[dcsrc["Value"]])
 
        storage = tr[self].local() #dict for tr[self], storage linked to the transaction/snapshot.
        try:
            recent = storage["Recent"]
        except KeyError:
            storage[ "Recent"] = [] #for the first time
            recent = storage["Recent"]

        recent.append({'dmm_start':shot_emitter[dmm].timeAwared(),
            'dmm_fin':shot_emitter[dmm].time(),
            'curr':curr, 'volt':volt})
        if (recent[-1]['dmm_start'] - recent[0]['dmm_start']).total_seconds() > 30:
            del recent[0] #erase too old record.

        if not bool(tr[self["Control"]]):
            #this driver is NOT in charge of switching dc source polarity.
            raise KAMESkippedRecordError("Skip")

        #setups the current for visualize().
        #switching polarity.
        nextcurr = -curr
        storage["NextCurr"] = nextcurr

        if curr <= 0:
            raise KAMESkippedRecordError("Skip") #waits for positive current.

        #searching for the newest record with +curr.
        for idx in range(len(recent) - 1, 0, -1):
            if recent[idx]['curr'] == curr:
                volt = recent[idx]['volt']
                break

        #searching for the newest record with -curr.
        for idx in range(len(recent) - 1, 0, -1):
            if recent[idx]['curr'] == -curr:
                res = (volt - recent[idx]['volt']) / curr / 2
                self["Resistance"].value(tr, res)
                return #approved for recording.

        raise KAMESkippedRecordError("Skip") #no valid record.

    #may perform I/O ops or graph ops using the snapshot after analyze().
    def visualize(self, shot):
        if bool(shot[self["Control"]]):
            #this driver is in charge of switching dc source polarity.
            dcsrc = shot[self["DCSource"]].get() #selected driver.
            storage = shot[self].local() #dict for shot[self], storage linked to the transaction/snapshot.
            try:
                nextcurr = storage["NextCurr"]
            except KeyError:
                return
            curr = float(dcsrc["Value"])
            if curr != nextcurr:
                dcsrc["Value"] = nextcurr

#Declares that python-side driver to C++ driver list.
XPythonSecondaryDriver.exportClass("Test4Res", Test4Res, "Test python-based driver: 4-Terminal Resistance Measumrent")

#Advanced version of 4-terminal resistance measurement with AC, and with different current values.
#Defines KAME driver class inside python.
#XPythonSecondaryDriver, the base abstract class for KAME driver which analyzes data from the other connected drivers.
class Py4Res(XPythonSecondaryDriver):
    def __init__(self, name, runtime, tr, meas):
        XPythonSecondaryDriver.__init__(self, name, runtime, tr, meas)  #super().__init__ cannot be used.

        self.NumEntries = 3

        #setups Qt UIs, using .ui generated by Qt designer.
        form = self.loadUIFile(':/python/formpyfourres.ui') #From Qt resource. see pydrivers.qrc
        #adds nodes, which can be later accessed. ex. self["name"].
        self.insert(XDoubleNode("Wait", False))
        self["Wait"] = 400 #ms
        self.insert(XBoolNode("Control", False))
        self.insert(XUIntNode("DMMChannel", False))

        self.entries = []
        for i in range(self.NumEntries):
            self.entries.append(XScalarEntry("Resistance-{}".format(i+1), False, self, "%.7g"))
            self.insert(self.entries[-1])   #self does NOT belong to tr yet inside the constructor. Never use insert(tr,...).
            meas["ScalarEntries"].insert(tr, self.entries[-1]) #tr: transaction obj. during the creation.
            self.insert(XDoubleNode("Current-{}".format(i+1), False))
            self["Current-{}".format(i+1)] = 10.0**(-i)

#        pdb.set_trace()
        #Driver selecters
        self.insert(XDMMItemNode("DMM", False, tr, meas["Drivers"], True)) # choosing XDMM-based class from the driver list.
        self.insert(XDCSourceItemNode("DCSource", False, tr, meas["Drivers"], True)) # choosing XDCSource-based class from the driver list.
        #setups link btw this driver and a selected driver.
        self.connect(self["DMM"])
        self.connect(self["DCSource"])

        #stores UI connectors during the lifetime.
        self.conns = [
            #synchronizes nodes and UIs.
            XQDoubleSpinBoxConnector(self["Wait"], form.findChildWidget("Wait")),
            XQToggleButtonConnector(self["Control"], form.findChildWidget("Control")),
            XQSpinBoxUnsignedConnector(self["DMMChannel"], form.findChildWidget("DMMChannel")),
            XQComboBoxConnector(self["DMM"], form.findChildWidget("DMM"), tr),
            XQComboBoxConnector(self["DCSource"], form.findChildWidget("DCSource"), tr),
            ]
        for i in range(self.NumEntries):
            self.conns.append(XQLCDNumberConnector(self.entries[i]["Value"], form.findChildWidget("lcdResistance{}".format(i+1))))
            self.conns.append(XQLineEditConnector(self["Current-{}".format(i+1)], form.findChildWidget("edCurr{}".format(i+1))))

    #Pickups valid snapshots before going to analyze().
    # shot_self: Snapshot for self.
    # shot_emitter: Snapshot for the event emitting driver.
    # shot_others: Snapshot for the other connected dirvers.
    def checkDependency(self, shot_self, shot_emitter, shot_others, emitter):
        dmm = shot_self[self["DMM"]].get() #selected driver.
        dcsrc = shot_self[self["DCSource"]].get() #selected driver.
        if emitter == dmm:
            shot_dcsrc = shot_others
            shot_dmm = shot_emitter
            wait = float(shot_self[self["Wait"]]) * 1e-3 #[s]
            if (shot_dmm[dmm].timeAwared() - shot_dcsrc[dcsrc].time()).total_seconds() < wait:
                return False
            return True #Good, approved
        return False #skipping this record.

    #Analyzes data acquired by the connected drivers.
    #Never include I/O operations, because transaction might repreat many times.
    def analyze(self, tr, shot_emitter, shot_others, emitter):
        dmm = tr[self["DMM"]].get() #selected driver.
        dcsrc = tr[self["DCSource"]].get() #selected driver.
        shot_dcsrc = shot_others
        shot_dmm = shot_emitter
        
        dmmch = int(tr[self["DMMChannel"]])
        volt = shot_dmm[dmm].value(dmmch)
        curr = float(shot_dcsrc[dcsrc["Value"]])
 
        storage = tr[self].local() #dict for tr[self], storage linked to the transaction/snapshot.
        try:
            recent = storage["Recent"]
        except KeyError:
            storage[ "Recent"] = [] #for the first time
            recent = storage["Recent"]


        recent.append({'dmm_start':shot_emitter[dmm].timeAwared(),
            'dmm_fin':shot_emitter[dmm].time(),
            'curr':curr, 'volt':volt})
        if (recent[-1]['dmm_start'] - recent[0]['dmm_start']).total_seconds() > 30:
            del recent[0] #erase too old record.

        for i in range(self.NumEntries):
            if abs(float(tr[self["Current-{}".format(i+1)]]) * 1e-3 - abs(curr)) < 1e-8: #[A]
                break
        if i == self.NumEntries:
            raise KAMERecordError("No valid current setting.")

        #setups the current for visualize().
        if curr > 0:
            #switching polarity.
            nextcurr = -curr
        else:
            nextcurr = 0
            #finds valid setting.
            for j in range(self.NumEntries):
                k = (i + j + 1) % self.NumEntries
                nextcurr = float(tr[self["Current-{}".format(k + 1)]]) * 1e-3 #[A]
                if nextcurr > 0:
                    break
            #goes a round to the next current.
        storage["NextCurr"] = nextcurr

        if curr >= 0:
            raise KAMESkippedRecordError("Skip") #waits for negative current.

        curr = -curr
        #searching for the newest record with +curr.
        for idx in range(len(recent) - 1, 0, -1):
            if recent[idx]['curr'] == curr:
                volt = recent[idx]['volt']
                break

        #searching for the newest record with -curr.
        for idx in range(len(recent) - 1, 0, -1):
            if recent[idx]['curr'] == -curr:
                res = (volt - recent[idx]['volt']) / curr / 2

                self.entries[i].value(tr, res)
                return #approved for recording.

        raise KAMESkippedRecordError("Skip") #no valid record.

    #may perform I/O ops or graph ops using the snapshot after analyze().
    def visualize(self, shot):        
        dcsrc = shot[self["DCSource"]].get() #selected driver.
        storage = shot[self].local() #dict for shot[self], storage linked to the transaction/snapshot.
        #checks if the last member can take control ownership for the shared dc source.
        #this cannot be done in analyze(), because taking snapshot will conflict with the transaction.
        cousins = [d for d in Root()["Drivers"] if type(d) == type(self) and d["DCSource"].get() == dcsrc]
        if self != cousins[-1]:
            self["Control"].setUIEnabled(False)
            self["Control"] = False
        else:
            if not shot[self["Control"]].isUIEnabled():
                self["Control"].setUIEnabled(True)

            if bool(shot[self["Control"]]):
                #this driver is in charge of changing dc source.
                try:
                    nextcurr = storage["NextCurr"]
                except KeyError:
                    return
                curr = float(dcsrc["Value"])
                if curr != nextcurr:
                    dcsrc["Value"] = nextcurr

#Declares that python-side driver to C++ driver list.
XPythonSecondaryDriver.exportClass("Py4Res", Py4Res, "Python-based driver: 4-Terminal Resistance Measumrent")
