Exemplo n.º 1
0
    def on_start(self):
        """ Start the monitor: com_monitor thread and the update
            timer
        """
        if self.com_monitor is not None or self.portname.text() == '':
            return
        
        self.data_q = Queue.Queue()
        self.error_q = Queue.Queue()
        self.com_monitor = ComMonitorThread(
            self.data_q,
            self.error_q,
            full_port_name(str(self.portname.text())),
            19200)
        self.com_monitor.start()
        
        com_error = get_item_from_queue(self.error_q)
        if com_error is not None:
            QMessageBox.critical(self, 'ComMonitorThread error',
                com_error)
            self.com_monitor = None

        self.monitor_active = True
        self.set_actions_enable_state()
        
        self.timer = QTimer()
        self.connect(self.timer, SIGNAL('timeout()'), self.on_timer)
        
        self.timer.start(0.2)
        
        self.status_text.setText('Monitor running')

        self.startStop.setText('Stop  Monitor')

        self.start_logging()
Exemplo n.º 2
0
class PlottingDataMonitor(QMainWindow):
    def __init__(self, parent=None):
        super(PlottingDataMonitor, self).__init__(parent)

        if _platform == "linux" or _platform == "linux2":
            # linux
            self.defaultSerialPort = "/dev/ttySy"
        elif _platform == "darwin":
            # OS X
            self.defaultSerialPort = "/dev/ttySy"
        elif _platform == "win32":
            # Windows...
            self.defaultSerialPort = "COM4"

        self.setFont(QFont('Arial Unicode MS', 10))

        self.batteries = [[],[]]
        for i in range(20):
            self.batteries[0].append(Battery()) # (ET) Batman
            self.batteries[1].append(Battery()) # (ET) Robin

        self.mppts = []
        for i in range(4):
            self.mppts.append(MPPT(i))

        self.batteryCurrent = []
        self.arrayCurrent = []
        self.totalVoltage = []

        self.monitor_active = False
        self.logging_active = False
        self.com_monitor = None
        self.com_data_q = None
        self.com_error_q = None
        self.livefeed = LiveDataFeed()
        self.timer = QTimer()

        self.curTime = time.time()
        self.startTime = time.time()
        self.timeSinceStart = self.curTime - self.startTime

        self.create_menu()
        self.create_main_frame()
        self.create_status_bar()

        self.portname.setText(self.defaultSerialPort)            
        self.set_actions_enable_state()

        self.setWindowTitle('University of Kentucky Solar Car Telemetry')

    def create_main_frame(self):
        portname_l, self.portname = self.make_data_box('COM Port:')
        
        portname_layout = QHBoxLayout()
        portname_layout.addWidget(portname_l)
        portname_layout.addWidget(self.portname, 0)

        change_port = QPushButton('Change Port')
        change_port.clicked.connect(self.on_select_port)
        portname_layout.addWidget(change_port)

        self.startStop = QPushButton('Start Monitor')
        self.startStop.clicked.connect(self.on_start_stop)
        portname_layout.addWidget(self.startStop)

        self.loggingToggle = QPushButton('Start Logging')
        self.loggingToggle.clicked.connect(self.toggleLogging)
        portname_layout.addWidget(self.loggingToggle)

        exit = QPushButton('Exit')
        exit.clicked.connect(self.close)
        portname_layout.addWidget(exit)

        portname_layout.addStretch(1)

        portname_groupbox = QGroupBox('Controls')
        portname_groupbox.setLayout(portname_layout)

# TODO: (Ethan) Replace Batman display with a real time log and a command line at the bottom to send commands to the car.

        batteryLayout1 = QGridLayout()
        for i in range(5):
            batteryLayout1.setColumnMinimumWidth(i,52)
        for i in range(0,8,2):
            batteryLayout1.setRowMinimumHeight(i,92)
            batteryLayout1.setRowMinimumHeight(i+1,15)
        counter = 0
        for i in range(4):
            for j in range(5):
                batteryLayout1.addWidget(self.batteries[0][counter],2*i,j)
                tempLabel = QLabel(str(counter))
                tempLabel.setAlignment(Qt.AlignHCenter)
                batteryLayout1.addWidget(tempLabel,2*i+1,j)
                counter += 1

# TODO: (Ethan) Replace Robin display with two of the battery displays for each battery box. Or possibly someting else. Suggestions wanted

        batteryLayout2 = QGridLayout()
        for i in range(5):
            batteryLayout2.setColumnMinimumWidth(i,52)
        for i in range(0,8,2):
            batteryLayout2.setRowMinimumHeight(i,92)
            batteryLayout2.setRowMinimumHeight(i+1,15)
        counter = 0
        for i in range(4):
            for j in range(5):
                batteryLayout2.addWidget(self.batteries[1][counter],2*i,j)
                tempLabel = QLabel(str(counter+20))
                tempLabel.setAlignment(Qt.AlignHCenter)
                batteryLayout2.addWidget(tempLabel,2*i+1,j)
                counter += 1

        batteryWidget1 = QGroupBox('Batman')
        batteryWidget1.setAlignment(Qt.AlignHCenter)
        batteryWidget1.setLayout(batteryLayout1)

        batteryWidget2 = QGroupBox('Robin')
        batteryWidget2.setAlignment(Qt.AlignHCenter)
        batteryWidget2.setLayout(batteryLayout2)

        batteryWidget = QWidget()
        batteryWidgetLayout = QHBoxLayout()
        batteryWidgetLayout.addWidget(batteryWidget1)
        batteryWidgetLayout.addWidget(batteryWidget2)
        batteryWidget.setLayout(batteryWidgetLayout)

        #Time
        timeWidget = QGroupBox('Time')
        timeLayout = QGridLayout()
        self.currentTime = QLabel(time.strftime("%H:%M:%S", time.localtime(self.curTime)))
        self.timeSince = QLabel(time.strftime("%H:%M:%S", time.gmtime(self.timeSinceStart)))
        timeLayout.addWidget(self.currentTime,0,1)
        timeLayout.addWidget(self.timeSince,1,1)
        timeLayout.addWidget(QLabel('Current time:'),0,0)
        timeLayout.addWidget(QLabel('Run time:'),1,0)
        timeWidget.setLayout(timeLayout)

        #Batteries
        batteryStatsWidget = QGroupBox('Batteries')
        batteryStatsLayout = QGridLayout()

        self.tBatteryCurrent = QLabel('0.00 A')
        # self.tBatteryAverage = QLabel('0.00 V')
        # self.tBatteryHigh = QLabel('0.00 V (#)')
        # self.tBatteryLow = QLabel('0.00 V (#)')
        batteryStatsLayout.addWidget(QLabel('Current:'),4,0)
        batteryStatsLayout.addWidget(self.tBatteryCurrent,4,1)
        # batteryStatsLayout.addWidget(QLabel('Average:'),1,4)
        # batteryStatsLayout.addWidget(self.tBatteryAverage,1,5)
        # batteryStatsLayout.addWidget(QLabel('High:'),2,4)
        # batteryStatsLayout.addWidget(self.tBatteryHigh,2,5)
        # batteryStatsLayout.addWidget(QLabel('Low:'),3,4)
        # batteryStatsLayout.addWidget(self.tBatteryLow,3,5)

        self.tBatmanAverage = QLabel('0.00 V')
        self.tBatmanHigh = QLabel('0.00 V (#)')
        self.tBatmanLow = QLabel('0.00 V (#)')
        batteryStatsLayout.addWidget(QLabel('Batman'),0,0,1,2, Qt.AlignHCenter)
        batteryStatsLayout.addWidget(QLabel('Average:'),1,0)
        batteryStatsLayout.addWidget(self.tBatmanAverage,1,1)
        batteryStatsLayout.addWidget(QLabel('High:'),2,0)
        batteryStatsLayout.addWidget(self.tBatmanHigh,2,1)
        batteryStatsLayout.addWidget(QLabel('Low:'),3,0)
        batteryStatsLayout.addWidget(self.tBatmanLow,3,1)

        self.tRobinAverage = QLabel('0.00 V')
        self.tRobinHigh = QLabel('0.00 V (#)')
        self.tRobinLow = QLabel('0.00 V (#)')
        batteryStatsLayout.addWidget(QLabel('Robin'),0,2,1,2, Qt.AlignHCenter)
        batteryStatsLayout.addWidget(QLabel('Average:'),1,2)
        batteryStatsLayout.addWidget(self.tRobinAverage,1,3)
        batteryStatsLayout.addWidget(QLabel('High:'),2,2)
        batteryStatsLayout.addWidget(self.tRobinHigh,2,3)
        batteryStatsLayout.addWidget(QLabel('Low:'),3,2)
        batteryStatsLayout.addWidget(self.tRobinLow,3,3)

        batteryStatsWidget.setLayout(batteryStatsLayout)

        #Motor Controller
        self.motorControllerWidget = MotorController('Motor Controller')

        #MPPTs
        mpptWidget = QGroupBox('MPPTs')
        mpptLayout = QGridLayout()
        mpptLayout.addWidget(QLabel('#'),0,0)
        mpptLayout.addWidget(QLabel('Out Current'),0,1,1,2)
        for i in range(4):
            mpptLayout.addWidget(QLabel(str(i)),i+1,0)
            mpptLayout.addWidget(self.mppts[i],i+1,1)
            mpptLayout.addWidget(QLabel('A'),i+1,2)
        self.MPPTTotal = QLabel('0.000')
        mpptLayout.addWidget(QLabel('Total'),5,0)
        mpptLayout.addWidget(self.MPPTTotal,5,1)
        mpptLayout.addWidget(QLabel('A'),5,2)
        mpptWidget.setLayout(mpptLayout)

        #Graphs
        self.speedGraph = QGroupBox('Speed (mph)')
        self.speedGraph.setFlat(True)
        self.speedGraph.setMinimumWidth(150)
        self.speedFig = plt.figure()
        self.speedCanvas = FigureCanvas(self.speedFig)
        self.speedLayout = QVBoxLayout()
        self.speedLayout.addWidget(self.speedCanvas)
        self.speedGraph.setLayout(self.speedLayout)

        self.mCurrentGraph = QGroupBox('Motor Current (A)')
        self.mCurrentGraph.setFlat(True)
        self.mCurrentGraph.setMinimumWidth(150)
        self.mCurrentFig = plt.figure()
        self.mCurrentCanvas = FigureCanvas(self.mCurrentFig)
        self.mCurrentLayout = QVBoxLayout()
        self.mCurrentLayout.addWidget(self.mCurrentCanvas)
        self.mCurrentGraph.setLayout(self.mCurrentLayout)

        self.aCurrentGraph = QGroupBox('Array Current (A)')
        self.aCurrentGraph.setFlat(True)
        self.aCurrentGraph.setMinimumWidth(150)
        self.aCurrentFig = plt.figure()
        self.aCurrentCanvas = FigureCanvas(self.aCurrentFig)
        self.aCurrentLayout = QVBoxLayout()
        self.aCurrentLayout.addWidget(self.aCurrentCanvas)
        self.aCurrentGraph.setLayout(self.aCurrentLayout)

        self.voltageGraph = QGroupBox('Total Battery Voltage (V)')
        self.voltageGraph.setFlat(True)
        self.voltageGraph.setMinimumWidth(150)
        self.voltageFig = plt.figure()
        self.voltageCanvas = FigureCanvas(self.voltageFig)
        self.voltageLayout = QVBoxLayout()
        self.voltageLayout.addWidget(self.voltageCanvas)
        self.voltageGraph.setLayout(self.voltageLayout)

        #Logo
        logo = QLabel()
        logo.setGeometry(0,0,100,100)
        logo.setPixmap(QPixmap("img/logo1.png")) 

        #Main Layout
        self.main_frame1 = QWidget()
        main_layout1 = QVBoxLayout()
        main_layout1.addWidget(batteryWidget)
        main_layout1.addWidget(portname_groupbox)
        main_layout1.addStretch(1)
        self.main_frame1.setLayout(main_layout1)

        self.main_frame2 = QWidget()
        main_layout2 = QVBoxLayout()
        main_layout2.addWidget(timeWidget)
        main_layout2.addWidget(self.motorControllerWidget)
        main_layout2.addWidget(mpptWidget)
        main_layout2.addWidget(batteryStatsWidget)
        main_layout2.addStretch(1)
        # main_layout2.addWidget(logo)
        self.main_frame2.setLayout(main_layout2)

        self.graphs_frame = QWidget()
        graphs_layout = QVBoxLayout()
        graphs_layout.addWidget(self.speedGraph)
        graphs_layout.addWidget(self.mCurrentGraph)
        graphs_layout.addWidget(self.aCurrentGraph)
        graphs_layout.addWidget(self.voltageGraph)
        self.graphs_frame.setLayout(graphs_layout)
        
        self.main_frame = QWidget()
        main_layout = QHBoxLayout()
        main_layout.addWidget(self.main_frame1)
        main_layout.addWidget(self.main_frame2)
        main_layout.addWidget(self.graphs_frame)
        self.main_frame.setLayout(main_layout)

        self.setCentralWidget(self.main_frame)
        self.set_actions_enable_state()

        self.secTimer = QTimer()
        self.secTimer.timeout.connect(self.writeLog)
        self.secTimer.timeout.connect(self.updateTime)
        self.secTimer.timeout.connect(self.updateGraphs)
        self.secTimer.start(1000)

    def updateGraphs(self):
        speedData = self.motorControllerWidget.getSpeeds()
        speeds = [item[0] for item in speedData]
        times = [dt.utcfromtimestamp(item[1]) for item in speedData]
        axSpeed = self.speedFig.add_axes([.1,0,1,1])
        axSpeed.hold(False)
        axSpeed.plot_date(times, speeds, '')
        self.speedCanvas.draw()

        mCurrentData = self.motorControllerWidget.getCurrents()
        mCurrents = [item[0] for item in mCurrentData]
        times = [dt.utcfromtimestamp(item[1]) for item in mCurrentData]
        axMCurrent = self.mCurrentFig.add_axes([.1,0,1,1])
        axMCurrent.hold(False)
        axMCurrent.plot_date(times, mCurrents, '')
        self.mCurrentCanvas.draw()

        aCurrentData = self.getArrayCurrents()
        aCurrents = [item[0] for item in aCurrentData]
        times = [dt.utcfromtimestamp(item[1]) for item in aCurrentData]
        axACurrent = self.aCurrentFig.add_axes([.1,0,1,1])
        axACurrent.hold(False)
        axACurrent.plot_date(times, aCurrents, '')
        self.aCurrentCanvas.draw()

        voltageData = self.totalVoltage
        voltages = [item[0] for item in voltageData]
        times = [dt.utcfromtimestamp(item[1]) for item in voltageData]
        axVoltages = self.voltageFig.add_axes([.1,0,1,1])
        axVoltages.hold(False)
        axVoltages.plot_date(times, voltages, '')
        self.voltageCanvas.draw()

    def updateTime(self):
        self.curTime = time.time()
        self.timeSinceStart = self.curTime - self.startTime
        self.currentTime.setText(time.strftime("%H:%M:%S", time.localtime(self.curTime)))
        self.timeSince.setText(time.strftime("%H:%M:%S", time.gmtime(self.timeSinceStart)))
        # print time.strftime("%d.%m.%Y.%H:%M:%S")

    def toggleLogging(self):
        if self.logging_active:
            self.logging_active = False
            self.stop_logging()
            self.loggingToggle.setText('Start Logging')
        else:
            self.start_logging()

    def on_start_stop(self):
        if self.monitor_active:
            self.on_stop()
        else:
            self.on_start()

    def make_data_box(self, name):
        label = QLabel(name)
        qle = QLineEdit()
        qle.setEnabled(False)
        qle.setFrame(True)
        return (label, qle)

    def create_menu(self):
        self.file_menu = self.menuBar().addMenu("&File")

        selectport_action = self.create_action("Select COM &Port...",
            shortcut="Ctrl+P", slot=self.on_select_port, tip="Select a COM port")
        self.start_action = self.create_action("&Start monitor",
            shortcut="Ctrl+M", slot=self.on_start, tip="Start the data monitor")
        self.stop_action = self.create_action("&Stop monitor",
            shortcut="Ctrl+T", slot=self.on_stop, tip="Stop the data monitor")
        exit_action = self.create_action("E&xit", slot=self.close, 
            shortcut="Ctrl+X", tip="Exit the application")

        self.start_action.setEnabled(False)
        self.stop_action.setEnabled(False)
        
        self.add_actions(self.file_menu,
            (   selectport_action, self.start_action, self.stop_action,
                None, exit_action))
            
        self.help_menu = self.menuBar().addMenu("&Help")
        about_action = self.create_action("&About", 
            shortcut='F1', slot=self.on_about, 
            tip='About the monitor')
        
        self.add_actions(self.help_menu, (about_action,))

    def create_status_bar(self):
        self.status_text = QLabel('Monitor idle')
        self.statusBar().addWidget(self.status_text, 1)

    def set_actions_enable_state(self):
        if self.portname.text() == '':
            start_enable = stop_enable = False
        else:
            start_enable = not self.monitor_active
            stop_enable = self.monitor_active
        
        self.start_action.setEnabled(start_enable)
        self.stop_action.setEnabled(stop_enable)

    def add_actions(self, target, actions):
        for action in actions:
            if action is None:
                target.addSeparator()
            else:
                target.addAction(action)

    def create_action(  self, text, slot=None, shortcut=None, 
                        icon=None, tip=None, checkable=False, 
                        signal="triggered()"):
        action = QAction(text, self)
        if icon is not None:
            action.setIcon(QIcon(":/%s.png" % icon))
        if shortcut is not None:
            action.setShortcut(shortcut)
        if tip is not None:
            action.setToolTip(tip)
            action.setStatusTip(tip)
        if slot is not None:
            self.connect(action, SIGNAL(signal), slot)
        if checkable:
            action.setCheckable(True)
        return action

    def on_select_port(self):
        ports = list(enumerate_serial_ports())
        if len(ports) == 0:
            QMessageBox.critical(self, 'No ports',
                'No serial ports found')
            return
        
        item, ok = QInputDialog.getItem(self, 'Select a port',
                    'Serial port:', ports, 0, False)
        
        if ok and not item.isEmpty():
            self.portname.setText(item)            
            self.set_actions_enable_state()

    def on_start(self):
        """ Start the monitor: com_monitor thread and the update
            timer
        """
        if self.com_monitor is not None or self.portname.text() == '':
            return
        
        self.data_q = Queue.Queue()
        self.error_q = Queue.Queue()
        self.com_monitor = ComMonitorThread(
            self.data_q,
            self.error_q,
            full_port_name(str(self.portname.text())),
            19200)
        self.com_monitor.start()
        
        com_error = get_item_from_queue(self.error_q)
        if com_error is not None:
            QMessageBox.critical(self, 'ComMonitorThread error',
                com_error)
            self.com_monitor = None

        self.monitor_active = True
        self.set_actions_enable_state()
        
        self.timer = QTimer()
        self.connect(self.timer, SIGNAL('timeout()'), self.on_timer)
        
        self.timer.start(0.2)
        
        self.status_text.setText('Monitor running')

        self.startStop.setText('Stop  Monitor')

        self.start_logging()

    def start_logging(self):
        if not os.path.exists('logs'):
            os.makedirs('logs')
        self.logging_active = True
        self.loggingToggle.setText('Stop  Logging')
        fileName = "logs/log" + time.strftime("%d-%m-%Y-%H-%M-%S") + ".csv"
        self.logFile = open(fileName, "a+")
        self.logFile.write("timestamp,speed,motor current,array current,battery current,")
        for i in range(4):
            self.logFile.write("mppt[%d]," %i)
        for i in range(20):
            self.logFile.write("batman[%d]," %i)
        for i in range(20):
            self.logFile.write("robin[%d]," %i)
        self.logFile.write("\n")

    def writeLog(self):
        if self.logging_active:
            self.logFile.write(time.strftime("%d-%m-%Y-%H:%M:%S,"))
            self.logFile.write(str(self.motorControllerWidget.getSpeed())+",")
            self.logFile.write(str(self.motorControllerWidget.getCurrent())+",")
            self.logFile.write(str(self.getArrayCurrent())+",")
            self.logFile.write(str(self.getBatteryCurrent())+",")
            for mppt in self.mppts:
                self.logFile.write(str(mppt.getOutCurrent())+",")
            for pack in self.batteries:
                for battery in pack:
                    self.logFile.write(str(battery.getVoltage())+",")
            self.logFile.write("\n")

    def stop_logging(self):
        self.logging_active = False
        self.loggingToggle.setText('Start Logging')
        self.logFile.close()

    def on_stop(self):
        """ Stop the monitor
        """
        if self.com_monitor is not None:
            self.com_monitor.join(0.01)
            self.com_monitor = None

        self.monitor_active = False
        self.timer.stop()
        self.set_actions_enable_state()
        
        self.status_text.setText('Monitor idle')

        self.startStop.setText('Start Monitor')

        self.stop_logging()

    def on_about(self):
        msg = __doc__
        QMessageBox.about(self, "About the demo", msg.strip())

    def on_timer(self):
        """ Executed periodically when the monitor update timer
            is fired.
        """
        self.read_serial_data()
        self.update_monitor()

    def read_serial_data(self):
        """ Called periodically by the update timer to read data
            from the serial port.
        """
        qdata = list(get_all_from_queue(self.data_q))
        if len(qdata) > 0:
            data = qdata[-1][0]
            self.livefeed.add_data(data)

    def update_monitor(self):
        """ Updates the state of the monitor window with new 
            data. The livefeed is used to find out whether new
            data was received since the last update. If not, 
            nothing is updated.
        """
        if self.livefeed.has_new_data:
            data = self.livefeed.read_data().strip("\r\n")

            # Regular Expressions
            # with data examples commented above

            # V[1][16] = 33421
            batteryVoltageRX = re.compile("^\s*V\[([0-1])\]\[([0-1][0-9])\]\s\=\s(\d+)\s*$")
            # T[1][15] = 26
            batteryTemperatureRX = re.compile("^\s*T\[([0-1])\]\[([0-1][0-9])\]\s\=\s(\d+)\s*$")
            # C = 0
            batteryCurrentRX = re.compile("^\s*C\s=\s(\d+)\s*$")
            # S = 0
            motorControllerVelocityRX = re.compile("^\s*S\s=\s(\d+)\s*$")
            # E = 0
            motorControllerEnergyRX = re.compile("^\s*E\s=\s(\d+)\s*$")
            # W = 0
            motorControllerBusVoltageRX = re.compile("^\s*W\s=\s(\d+)\s*$")
            # M[2] 22 91 140
            MPPTDataRX = re.compile("^\s*M\[([0-3])\]\s(\d+)\s(\d+)\s(\d+)\s*$")
            # #   BPS TIMEOUT number [1][08]= ??
            BPSbadRX = re.compile("^\s*#\s\s\sBPS\sTIMEOUT\snumber\s\[([0-1])\]\[([0-1][0-9])\]= \?\?\s*$")

            if batteryVoltageRX.match(data):
                info = batteryVoltageRX.search(data).groups()
                self.batteries[int(info[0])][int(info[1])].setVoltage(int(info[2]))
                self.updateBatteries()

            elif batteryTemperatureRX.match(data):
                info = batteryTemperatureRX.search(data).groups()
                self.batteries[int(info[0])][int(info[1])].setTemperature(int(info[2]))

            elif batteryCurrentRX.match(data):
                info = batteryCurrentRX.search(data).groups()
                self.batteryCurrent.append((float(info[0]), time.time()))

            elif motorControllerVelocityRX.match(data):
                info = motorControllerVelocityRX.search(data).groups()
                self.motorControllerWidget.setSpeed(int(info[0]))

            elif motorControllerEnergyRX.match(data):
                info = motorControllerEnergyRX.search(data).groups()
                self.motorControllerWidget.setEnergy(int(info[0]))

            elif motorControllerBusVoltageRX.match(data):
                info = motorControllerBusVoltageRX.search(data).groups()
                busVoltage = int(info[0])

            elif MPPTDataRX.match(data):
                info = MPPTDataRX.search(data).groups()
                self.mppts[int(info[0])].setInVoltage(int(info[1]))
                self.mppts[int(info[0])].setInCurrent(int(info[2]))
                self.mppts[int(info[0])].setOutVoltage(int(info[3]))
                self.updateMPPT()

            elif BPSbadRX.match(data):
                info = BPSbadRX.search(data).groups()
                box = int(info[0])
                cell = int(info[1])
                print "WARNING BPS TIMEOUT number [%d][%2d]" %(box, cell)
                self.batteries[box][cell].setBad()

            else:
                info =  "Could not match input '" + data + "'"
                print info

            # print data #debug

    def updateMPPT(self):
        total = 0.0
        for mppt in self.mppts:
            total += mppt.getOutCurrent()
        self.MPPTTotal.setText("%.3f" %total)
        self.arrayCurrent.append((total, time.time()))

    def getArrayCurrent(self):
        return self.arrayCurrent[-1][0] if self.arrayCurrent else 0

    def getArrayCurrents(self):
        return self.arrayCurrent

    def getBatteryCurrent(self):
        return self.batteryCurrent[-1][0] if self.batteryCurrent else 0

    def updateBatteries(self):
        #find highest, lowest, and average values
        highestBatteryValue = 0.0
        highestBatteryModule = 0
        lowestBatteryValue = 4.0
        lowestBatteryModule = 0
        averageBatteryValue = 0.0
        batterySum = 0.0
        for i in range(2):            
            for j in range(20):
                voltage = self.batteries[i][j].getVoltage()
                batterySum = batterySum + voltage
                if voltage > highestBatteryValue:
                    highestBatteryValue = voltage
                    highestBatteryModule = (20*i + j)
                if voltage < lowestBatteryValue:
                    lowestBatteryValue = voltage
                    lowestBatteryModule = (20*i + j)
        averageBatteryValue = batterySum / 40;

        highestBatmanModule, highestBatmanValue = max(enumerate(batman.getVoltage() for batman in self.batteries[0]), key=operator.itemgetter(1))
        lowestBatmanModule, lowestBatmanValue = min(enumerate(batman.getVoltage() for batman in self.batteries[0]), key=operator.itemgetter(1))
        averageBatmanValue = sum(batman.getVoltage() for batman in self.batteries[0]) / 20.0

        highestRobinModule, highestRobinValue = max(enumerate(robin.getVoltage() for robin in self.batteries[1]), key=operator.itemgetter(1))
        highestRobinModule += 20
        lowestRobinModule, lowestRobinValue = min(enumerate(robin.getVoltage() for robin in self.batteries[1]), key=operator.itemgetter(1))
        lowestRobinModule += 20
        averageRobinValue = sum(robin.getVoltage() for robin in self.batteries[1]) / 20.0

        self.tBatmanAverage.setText('%.2f V' %averageBatmanValue)
        self.tBatmanHigh.setText('%.2f V (%d)' %(highestBatmanValue, highestBatmanModule))
        self.tBatmanLow.setText('%.2f V (%d)' %(lowestBatmanValue, lowestBatmanModule))

        self.tRobinAverage.setText('%.2f V' %averageRobinValue)
        self.tRobinHigh.setText('%.2f V (%d)' %(highestRobinValue, highestRobinModule))
        self.tRobinLow.setText('%.2f V (%d)' %(lowestRobinValue, lowestRobinModule))

        self.tBatteryCurrent.setText('%.2f A' %(self.batteryCurrent[-1][0] if self.batteryCurrent else 0))
        # self.tBatteryAverage.setText('%.2f V' %averageBatteryValue)
        # self.tBatteryHigh.setText('%.2f V (#%d)' %(highestBatteryValue, highestBatteryModule))
        # self.tBatteryLow.setText('%.2f V (#%d)' %(lowestBatteryValue, lowestBatteryModule))

        total = averageBatmanValue*20 + averageRobinValue*20
        self.totalVoltage.append((total, time.time()))