Esempio n. 1
0
def MakeRoundButton(parent, label, bType=0):
    btn = RoundButton(parent,
                      wx.ID_ANY,
                      label,
                      size=(RoundButtonSize, RoundButtonSize))
    btn.SetBackgroundColour(wx.WHITE)
    btn.SetForegroundColour(buttonColors[bType])
    btn.SetFontToFitLabel()
    return btn
Esempio n. 2
0
def CreateCaptureButtons( parent ):	
	snapshot = RoundButton( parent, label="SNAPSHOT", size=(90,90) )
	snapshot.SetBackgroundColour( wx.WHITE )
	snapshot.SetForegroundColour( snapshotEnableColour )
	snapshot.SetFontToFitLabel( wx.Font(wx.FontInfo(10).Bold()) )
	snapshot.SetToolTip( _('Record a Single Frame') )
	
	autoCapture = RoundButton( parent, label="AUTO\nCAPTURE", size=(90,90) )
	autoCapture.SetBackgroundColour( wx.WHITE )
	autoCapture.SetForegroundColour( autoCaptureEnableColour )
	autoCapture.SetFontToFitLabel( wx.Font(wx.FontInfo(10).Bold()) )
	autoCapture.SetToolTip( _('Capture Video for an Automatic Interval\nSet in "Config Auto Capture"') )
	
	capture = RoundButton( parent, label="CAPTURE", size=(90,90) )
	capture.SetBackgroundColour( wx.WHITE )
	capture.SetForegroundColour( captureEnableColour )
	capture.SetFontToFitLabel( wx.Font(wx.FontInfo(10).Bold()) )
	capture.SetToolTip( _('Capture Video\nwhile the Button is held down') )
		
	return snapshot, autoCapture, capture
Esempio n. 3
0
class MainWin(wx.Frame):
    def __init__(self, parent, id=wx.ID_ANY, title='', size=(200, 200)):
        wx.Frame.__init__(self, parent, id, title, size=size)

        dataDir = Utils.getHomeDir()
        configFileName = os.path.join(dataDir, 'CrossMgrImpinj.cfg')
        self.config = wx.Config(appName="CrossMgrImpinj",
                                vendorName="SmartCyclingSolutions",
                                localFilename=configFileName)

        ID_MENU_ADVANCECONFIG = wx.NewIdRef()
        ID_MENU_COPYLOGS = wx.NewIdRef()
        ID_MENU_AUTODETECT = wx.NewIdRef()
        self.menuBar = wx.MenuBar(wx.MB_DOCKABLE)
        if 'WXMAC' in wx.Platform:
            self.appleMenu = self.menuBar.OSXGetAppleMenu()
            self.appleMenu.SetTitle("CrossMgrImpinj")

            self.appleMenu.Insert(0, wx.ID_ABOUT, "&About")

            self.Bind(wx.EVT_MENU, self.OnAboutBox, id=wx.ID_ABOUT)

            self.editMenu = wx.Menu()
            self.editMenu.Append(
                wx.MenuItem(self.editMenu, ID_MENU_ADVANCECONFIG,
                            "A&dvanced Configuration"))
            self.editMenu.Append(
                wx.MenuItem(self.editMenu, ID_MENU_COPYLOGS,
                            "&Copy Logs to Clipboard"))
            self.editMenu.Append(
                wx.MenuItem(self.editMenu, ID_MENU_AUTODETECT,
                            "&Autodetect Reader"))

            self.Bind(wx.EVT_MENU, self.doAdvanced, id=ID_MENU_ADVANCECONFIG)
            self.Bind(wx.EVT_MENU, self.doCopyToClipboard, id=ID_MENU_COPYLOGS)
            self.Bind(wx.EVT_MENU, self.doAutoDetect, id=ID_MENU_AUTODETECT)
            self.menuBar.Append(self.editMenu, "&Edit")

        else:
            self.fileMenu = wx.Menu()
            self.fileMenu.Append(
                wx.MenuItem(self.fileMenu, ID_MENU_ADVANCECONFIG,
                            "A&dvanced Configuration"))
            self.fileMenu.Append(
                wx.MenuItem(self.fileMenu, ID_MENU_COPYLOGS,
                            "&Copy Logs to Clipboard"))
            self.fileMenu.Append(
                wx.MenuItem(self.fileMenu, ID_MENU_AUTODETECT,
                            "&Autodetect Reader"))
            self.fileMenu.Append(wx.ID_EXIT)
            self.Bind(wx.EVT_MENU, self.doAdvanced, id=ID_MENU_ADVANCECONFIG)
            self.Bind(wx.EVT_MENU, self.doCopyToClipboard, id=ID_MENU_COPYLOGS)
            self.Bind(wx.EVT_MENU, self.doAutoDetect, id=ID_MENU_AUTODETECT)
            self.Bind(wx.EVT_MENU, self.onCloseWindow, id=wx.ID_EXIT)
            self.menuBar.Append(self.fileMenu, "&File")
            self.helpMenu = wx.Menu()
            self.helpMenu.Insert(0, wx.ID_ABOUT, "&About")
            self.Bind(wx.EVT_MENU, self.OnAboutBox, id=wx.ID_ABOUT)
            self.menuBar.Append(self.helpMenu, "&Help")

        self.SetMenuBar(self.menuBar)
        self.SetBackgroundColour(wx.Colour(232, 232, 232))

        self.LightGreen = wx.Colour(153, 255, 153)
        self.LightRed = wx.Colour(255, 153, 153)

        font = self.GetFont()
        bigFont = wx.Font(int(font.GetPointSize() * 1.2), font.GetFamily(),
                          font.GetStyle(), wx.FONTWEIGHT_BOLD)
        titleFont = wx.Font(int(bigFont.GetPointSize() * 2.2),
                            bigFont.GetFamily(), bigFont.GetStyle(),
                            bigFont.GetWeight())

        self.vbs = wx.BoxSizer(wx.VERTICAL)

        bs = wx.BoxSizer(wx.HORIZONTAL)

        self.reset = RoundButton(self, label='Reset', size=(80, 80))
        self.reset.SetBackgroundColour(wx.WHITE)
        self.reset.SetForegroundColour(wx.Colour(0, 128, 128))
        self.reset.SetFontToFitLabel(
        )  # Use the button's default font, but change the font size to fit the label.
        self.reset.Bind(wx.EVT_BUTTON, self.doReset)
        self.reset.Refresh()
        bs.Add(self.reset, border=8, flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.Add(setFont(titleFont, wx.StaticText(self, label='CrossMgrImpinj')),
               border=8,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.AddStretchSpacer()
        bitmap = wx.Bitmap(clipboard_xpm)
        self.copyToClipboard = wx.BitmapButton(self, bitmap=bitmap)
        self.copyToClipboard.SetToolTip(
            wx.ToolTip('Copy Configuration and Logs to Clipboard...'))
        self.copyToClipboard.Bind(wx.EVT_BUTTON, self.doCopyToClipboard)
        bs.Add(self.copyToClipboard,
               border=32,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        self.tStart = datetime.datetime.now()
        bs.Add(setFont(
            bigFont,
            wx.StaticText(self,
                          label='Last Reset: %s' %
                          self.tStart.strftime('%H:%M:%S'))),
               border=10,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        self.runningTime = setFont(bigFont,
                                   wx.StaticText(self, label='00:00:00'))
        bs.Add(self.runningTime,
               border=20,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.Add(setFont(bigFont, wx.StaticText(self, label=' / ')),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.time = setFont(bigFont, wx.StaticText(self, label='00:00:00'))
        bs.Add(self.time, flag=wx.ALIGN_CENTER_VERTICAL)

        self.vbs.Add(bs, flag=wx.ALL | wx.EXPAND, border=4)

        fgs = wx.FlexGridSizer(rows=2, cols=2, vgap=4, hgap=4)
        fgs.AddGrowableRow(1)
        fgs.AddGrowableCol(0)
        fgs.AddGrowableCol(1)
        fgs.SetFlexibleDirection(wx.BOTH)

        self.vbs.Add(fgs, flag=wx.EXPAND, proportion=5)

        #------------------------------------------------------------------------------------------------
        # Impinj configuration.
        #
        gbs = wx.GridBagSizer(4, 4)
        fgs.Add(gbs, flag=wx.EXPAND | wx.ALL, border=4)

        iRow = 0
        hb = wx.BoxSizer(wx.HORIZONTAL)
        hb.Add(setFont(bigFont,
                       wx.StaticText(self, label='Impinj Configuration:')),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.autoDetectButton = wx.Button(self, label='Auto Detect')
        self.autoDetectButton.Bind(wx.EVT_BUTTON, self.doAutoDetect)
        hb.Add(self.autoDetectButton, flag=wx.LEFT, border=6)

        self.advancedButton = wx.Button(self, label='Advanced...')
        self.advancedButton.Bind(wx.EVT_BUTTON, self.doAdvanced)
        hb.Add(self.advancedButton, flag=wx.LEFT, border=6)

        gbs.Add(hb, pos=(iRow, 0), span=(1, 2), flag=wx.ALIGN_LEFT)

        iRow += 1

        gs = wx.GridSizer(rows=0, cols=4, vgap=0, hgap=2)
        self.antennaLabels = []
        self.antennas = []
        for i in range(4):
            self.antennaLabels.append(
                wx.StaticText(self,
                              label='{}'.format(i + 1),
                              style=wx.ALIGN_CENTER))
            gs.Add(self.antennaLabels[-1], flag=wx.ALIGN_CENTER | wx.EXPAND)
        for i in range(4):
            cb = wx.CheckBox(self, wx.ID_ANY, '')
            if i < 2:
                cb.SetValue(True)
            cb.Bind(wx.EVT_CHECKBOX, lambda x: self.getAntennaStr())
            gs.Add(cb, flag=wx.ALIGN_CENTER)
            self.antennas.append(cb)

        hb = wx.BoxSizer()
        hb.Add(gs)
        self.methodName = wx.StaticText(self)
        self.refreshMethodName()
        hb.Add(self.methodName, flag=wx.ALIGN_BOTTOM | wx.LEFT, border=8)

        gbs.Add(wx.StaticText(self, label='ANT Ports:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM)
        gbs.Add(hb, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL)

        iRow += 1

        self.useHostName = wx.RadioButton(self,
                                          label='Host Name:',
                                          style=wx.RB_GROUP)
        gbs.Add(self.useHostName,
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_CENTER_VERTICAL)
        hb = wx.BoxSizer(wx.HORIZONTAL)
        hb.Add(wx.StaticText(self, label=ImpinjHostNamePrefix),
               flag=wx.ALIGN_CENTER_VERTICAL)
        if 'WXMAC' in wx.Platform:
            self.impinjHostName = masked.TextCtrl(
                self,
                defaultValue='00-00-00',
                useFixedWidthFont=True,
                size=(80, -1),
            )
        else:
            self.impinjHostName = masked.TextCtrl(
                self,
                mask='NN-NN-NN',
                defaultValue='00-00-00',
                useFixedWidthFont=True,
                size=(80, -1),
            )
        hb.Add(self.impinjHostName)
        hb.Add(wx.StaticText(self, label=ImpinjHostNameSuffix),
               flag=wx.ALIGN_CENTER_VERTICAL)
        hb.Add(wx.StaticText(self,
                             label=' : ' + '{}'.format(ImpinjInboundPort)),
               flag=wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(hb, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_LEFT)

        iRow += 1
        self.useStaticAddress = wx.RadioButton(self, label='IP:')
        gbs.Add(self.useStaticAddress,
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_CENTER_VERTICAL)
        hb = wx.BoxSizer(wx.HORIZONTAL)
        self.impinjHost = IpAddrCtrl(self, style=wx.TE_PROCESS_TAB)
        hb.Add(self.impinjHost)
        hb.Add(wx.StaticText(self,
                             label=' : ' + '{}'.format(ImpinjInboundPort)),
               flag=wx.ALIGN_CENTER_VERTICAL)

        gbs.Add(hb, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_LEFT)

        self.useHostName.SetValue(True)
        self.useStaticAddress.SetValue(False)

        iRow += 1
        self.antennaReads = AntennaReads(self)
        gbs.Add(self.antennaReads,
                pos=(iRow, 0),
                span=(1, 3),
                flag=wx.ALIGN_LEFT | wx.EXPAND)

        iRow += 1
        self.antennaReadCount = wx.StaticText(
            self,
            label='ANT Reads: 1:0 0% | 2:0 0% | 3:0 0% | 4:0 0%               '
        )
        gbs.Add(self.antennaReadCount,
                pos=(iRow, 0),
                span=(1, 3),
                flag=wx.ALIGN_LEFT)

        iRow += 1
        gbs.Add(wx.StaticText(self, label='Backup File:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT)
        self.backupFile = wx.StaticText(
            self, label='                                                   ')
        gbs.Add(self.backupFile,
                pos=(iRow, 1),
                span=(1, 2),
                flag=wx.ALIGN_LEFT)

        #------------------------------------------------------------------------------------------------
        # CrossMgr configuration.
        #
        cmcs = wx.BoxSizer(wx.VERTICAL)
        fgs.Add(cmcs, flag=wx.EXPAND | wx.ALL, border=4)

        cmcs.Add(
            setFont(bigFont,
                    wx.StaticText(self, label='CrossMgr Configuration:')))
        hh = wx.BoxSizer(wx.HORIZONTAL)
        hh.Add(wx.StaticText(self, label='CrossMgr Address:'),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.crossMgrHost = IpAddrCtrl(self, style=wx.TE_PROCESS_TAB)
        hh.Add(self.crossMgrHost, flag=wx.ALIGN_LEFT)
        hh.Add(wx.StaticText(self, label=' : 53135'),
               flag=wx.ALIGN_CENTER_VERTICAL)
        cmcs.Add(hh, flag=wx.ALL, border=4)

        #------------------------------------------------------------------------------------------------
        # Add strays
        #
        cmcs.Add(wx.StaticLine(self, style=wx.LI_HORIZONTAL),
                 flag=wx.EXPAND | wx.TOP | wx.BOTTOM,
                 border=2)
        self.strayTagsLabel = wx.StaticText(self, label='Stray Tags:         ')
        cmcs.Add(self.strayTagsLabel, flag=wx.LEFT | wx.RIGHT, border=4)

        self.strays = wx.ListCtrl(self,
                                  style=wx.LC_REPORT | wx.BORDER_SUNKEN,
                                  size=(-1, 50))
        self.strays.InsertColumn(0, 'Tag', wx.LIST_AUTOSIZE_USEHEADER)
        self.strays.InsertColumn(1, 'Time', wx.LIST_AUTOSIZE_USEHEADER)

        cmcs.Add(self.strays,
                 1,
                 flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP,
                 border=4)

        #------------------------------------------------------------------------------------------------
        # Add messages
        #
        self.impinjMessagesText = wx.TextCtrl(self,
                                              style=wx.TE_READONLY
                                              | wx.TE_MULTILINE | wx.HSCROLL,
                                              size=(-1, 400))
        fgs.Add(self.impinjMessagesText, flag=wx.EXPAND, proportion=2)
        self.impinjMessages = MessageManager(self.impinjMessagesText)

        self.crossMgrMessagesText = wx.TextCtrl(self,
                                                style=wx.TE_READONLY
                                                | wx.TE_MULTILINE | wx.HSCROLL,
                                                size=(-1, 400))
        fgs.Add(self.crossMgrMessagesText, flag=wx.EXPAND, proportion=2)
        self.crossMgrMessages = MessageManager(self.crossMgrMessagesText)
        self.fgs = fgs

        #------------------------------------------------------------------------------------------------
        # Create a timer to update the messages.
        #
        self.timer = wx.Timer()
        self.timer.Bind(wx.EVT_TIMER, self.updateMessages)
        self.timer.Start(1000, False)

        self.Bind(wx.EVT_CLOSE, self.onCloseWindow)

        self.readOptions()

        self.SetSizer(self.vbs)
        self.start()

    def OnAboutBox(self, e):
        description = """CrossMgrImpinj is an Impinj interface to CrossMgr
	"""

        licence = """CrossMgrImpinjis free software; you can redistribute 
	it and/or modify it under the terms of the GNU General Public License as 
	published by the Free Software Foundation; either version 2 of the License, 
	or (at your option) any later version.

	CrossMgrImpinj is distributed in the hope that it will be useful, 
	but WITHOUT ANY WARRANTY; without even the implied warranty of 
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
	See the GNU General Public License for more details. You should have 
	received a copy of the GNU General Public License along with File Hunter; 
	if not, write to the Free Software Foundation, Inc., 59 Temple Place, 
	Suite 330, Boston, MA  02111-1307  USA"""

        info = wx.adv.AboutDialogInfo()

        crossMgrPng = Utils.getImageFolder() + '/CrossMgrImpinj.png'
        info.SetIcon(wx.Icon(crossMgrPng, wx.BITMAP_TYPE_PNG))
        info.SetName('CrossMgrImpinj')
        info.SetVersion(AppVerName.split(' ')[1])
        info.SetDescription(description)
        info.SetCopyright('(C) 2020 Edward Sitarski')
        info.SetWebSite('http://www.sites.google.com/site/crossmgrsoftware/')
        info.SetLicence(licence)

        wx.adv.AboutBox(info, self)

    def readerStatusCB(self, **kwargs):
        # As this is called from another thread, make sure all UI updates are done from CallAfter.
        connectedAntennas = set(kwargs.get('connectedAntennas', []))
        for i in range(4):
            wx.CallAfter(
                self.antennaLabels[i].SetBackgroundColour, self.LightGreen if
                (i + 1) in connectedAntennas else wx.NullColour)

    def refreshMethodName(self):
        if Impinj.ProcessingMethod == 0 and QuadReg.samplesTotal:
            s = u'{}: Inliers: {:.1%}'.format(
                MethodNames[Impinj.ProcessingMethod],
                float(QuadReg.inliersTotal) / float(QuadReg.samplesTotal))
        else:
            s = MethodNames[Impinj.ProcessingMethod]
        self.methodName.SetLabel(s)

    def refreshStrays(self, strays):
        if self.strays.GetItemCount() != len(strays):
            self.strayTagsLabel.SetLabel('Stray Tags: {}'.format(len(strays)))
        if not strays:
            if self.strays.GetItemCount():
                self.strays.DeleteAllItems()
            return

        if (self.strays.GetItemCount() == len(strays)
                and strays[0][0] == self.strays.GetItemText(0)
                and strays[-1][0]
                == self.strays.GetItemText(self.strays.GetItemCount() - 1)):
            return

        self.strays.DeleteAllItems()
        for tag, discovered in strays:
            i = self.strays.InsertItem(1000000, tag)
            self.strays.SetItem(i, 1, discovered.strftime('%H:%M:%S'))
        for c in range(self.strays.GetColumnCount()):
            self.strays.SetColumnWidth(c, wx.LIST_AUTOSIZE_USEHEADER)

    def strayHandler(self, strayQ):
        while 1:
            msg = strayQ.get()
            if msg[0] == 'strays':
                wx.CallAfter(self.refreshStrays, msg[1])
            elif msg[0] == 'shutdown':
                break

    def start(self):
        self.dataQ = Queue()
        self.strayQ = Queue()
        self.messageQ = Queue()
        self.shutdownQ = Queue(
        )  # Queue to tell the Impinj monitor to shut down.
        self.readerStatusCB()

        if self.useHostName.GetValue():
            self.impinjProcess = Process(
                name='ImpinjProcess',
                target=ImpinjServer,
                args=(
                    self.dataQ,
                    self.strayQ,
                    self.messageQ,
                    self.shutdownQ,
                    ImpinjHostNamePrefix + self.impinjHostName.GetValue() +
                    ImpinjHostNameSuffix,
                    ImpinjInboundPort,
                    self.getAntennaStr(),
                    self.readerStatusCB,
                ))
        else:
            self.impinjProcess = Process(name='ImpinjProcess',
                                         target=ImpinjServer,
                                         args=(
                                             self.dataQ,
                                             self.strayQ,
                                             self.messageQ,
                                             self.shutdownQ,
                                             self.impinjHost.GetAddress(),
                                             ImpinjInboundPort,
                                             self.getAntennaStr(),
                                             self.readerStatusCB,
                                         ))
        self.impinjProcess.daemon = True

        self.strayProcess = Process(name='StrayProcess',
                                    target=self.strayHandler,
                                    args=(self.strayQ, ))
        self.strayProcess.daemon = True

        self.crossMgrProcess = Process(name='CrossMgrProcess',
                                       target=CrossMgrServer,
                                       args=(self.dataQ, self.messageQ,
                                             self.shutdownQ,
                                             self.getCrossMgrHost(),
                                             CrossMgrPort))
        self.crossMgrProcess.daemon = True

        self.impinjProcess.start()
        self.strayProcess.start()
        self.crossMgrProcess.start()

    def shutdown(self):
        self.impinjProcess = None
        self.crossMgrProcess = None
        self.strayProcess = None
        self.messageQ = None
        self.dataQ = None
        self.strayQ = None
        self.shutdownQ = None

    def doReset(self, event, confirm=True):
        if confirm:
            dlg = wx.MessageDialog(self, 'Reset CrossMgrImpinj Adapter?',
                                   'Confirm Reset',
                                   wx.OK | wx.CANCEL | wx.ICON_WARNING)
            ret = dlg.ShowModal()
            dlg.Destroy()
            if ret != wx.ID_OK:
                return

        self.reset.Enable(
            False)  # Prevent multiple clicks while shutting down.
        self.writeOptions()
        self.refreshMethodName()

        self.gracefulShutdown()

        self.impinjMessages.clear()
        self.crossMgrMessages.clear()
        self.shutdown()

        self.reset.Enable(True)
        QuadReg.ResetStats()

        wx.CallAfter(self.start)

    def doAutoDetect(self, event):
        wx.BeginBusyCursor()
        self.gracefulShutdown()
        self.shutdown()
        impinjHost, crossMgrHost = AutoDetect(
            ImpinjInboundPort)[0], '127.0.0.1'
        wx.EndBusyCursor()

        if impinjHost and crossMgrHost:
            self.useStaticAddress.SetValue(True)
            self.useHostName.SetValue(False)

            self.impinjHost.SetValue(impinjHost)
            self.crossMgrHost.SetValue(crossMgrHost)
        else:
            dlg = wx.MessageDialog(
                self,
                'Auto Detect Failed.\nCheck that reader has power and is connected to the router.',
                'Auto Detect Failed', wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()

        self.doReset(event, False)

    def doAdvanced(self, event):
        dlg = AdvancedSetup(self)
        if dlg.ShowModal() == wx.ID_OK:
            if Utils.MessageOKCancel(self, 'Reset Reader Now?',
                                     'Reset Reader'):
                self.doReset(event, confirm=False)
        dlg.Destroy()

    def gracefulShutdown(self):
        # Shutdown the CrossMgr process by sending it a shutdown command.
        if self.shutdownQ:
            self.shutdownQ.put('shutdown')
            self.shutdownQ.put('shutdown')
            self.shutdownQ.put('shutdown')
        if self.dataQ:
            self.dataQ.put('shutdown')
            self.dataQ.put('shutdown')
        if self.strayQ:
            self.strayQ.put(('shutdown', ))

        if self.crossMgrProcess:
            self.crossMgrProcess.join()
        if self.impinjProcess:
            self.impinjProcess.join()
        if self.strayProcess:
            self.strayProcess.join()

        self.crossMgrProcess = None
        self.impinjProcess = None
        self.strayProcess = None

    def onCloseWindow(self, event):
        self.gracefulShutdown()
        wx.Exit()

    def doCopyToClipboard(self, event):
        cc = [
            AppVerName,
            'Configuration: CrossMgrImpinj',
            '    RunningTime:   {}'.format(self.runningTime.GetLabel()),
            '    Time:          {}'.format(self.time.GetLabel()),
            '    BackupFile:    {}'.format(self.backupFile.GetLabel()),
            '',
            'Configuration: Impinj:',
            '    Use Host Name: {}'.format(
                'True' if self.useHostName.GetValue() else 'False'),
            '    HostName:      {}'.format((ImpinjHostNamePrefix +
                                            self.impinjHostName.GetValue()) +
                                           ImpinjHostNameSuffix),
            '    ImpinjHost:    {}'.format(self.impinjHost.GetAddress()),
            '    ImpinjPort:    {}'.format(ImpinjInboundPort),
            '    ReportMethod:  {}'.format(
                MethodNames[Impinj.ProcessingMethod]),
            '    AntennaChoice: {}'.format(
                AntennaChoiceNames[Impinj.AntennaChoice]),
            '    RemoveOutliers:{}'.format(Impinj.RepeatSeconds),
            '',
            '    ConnectionTimeoutSeconds: {}'.format(
                Impinj.ConnectionTimeoutSeconds),
            '    KeepaliveSeconds:         {}'.format(Impinj.KeepaliveSeconds),
            '    RepeatSeconds:            {}'.format(Impinj.RepeatSeconds),
            '',
            'Configuration: CrossMgr',
            '    CrossMgrHost:  {}'.format(self.getCrossMgrHost()),
            '    CrossMgrPort:  {}'.format(CrossMgrPort),
        ]
        cc.append('\nLog: Impinj')
        log = self.impinjMessagesText.GetValue()
        cc.extend(['    ' + line for line in log.split('\n')])

        cc.append('\nLog: CrossMgr')
        log = self.crossMgrMessagesText.GetValue()
        cc.extend(['    ' + line for line in log.split('\n')])

        cc.append('\nLog: Application\n')
        try:
            with open(redirectFileName, 'r') as fp:
                for line in fp:
                    cc.append(line)
        except:
            pass

        if wx.TheClipboard.Open():
            do = wx.TextDataObject()
            do.SetText('\n'.join(cc))
            wx.TheClipboard.SetData(do)
            wx.TheClipboard.Close()
            dlg = wx.MessageDialog(
                self, 'Configuration and Logs copied to the Clipboard.',
                'Copy to Clipboard Succeeded', wx.OK | wx.ICON_INFORMATION)
            ret = dlg.ShowModal()
            dlg.Destroy()
        else:
            # oops... something went wrong!
            wx.MessageBox("Unable to open the clipboard", "Error")

    def getCrossMgrHost(self):
        return self.crossMgrHost.GetAddress()

    def getAntennaStr(self):
        selectedAntennas = [i for i in range(4) if self.antennas[i].GetValue()]
        # Ensure at least one antenna is selected.
        if not selectedAntennas:
            self.antennas[0].SetValue(True)
            selectedAntennas = [0]
        return ' '.join('{}'.format(a + 1) for a in selectedAntennas)

    def setAntennaStr(self, s):
        antennas = set(int(a) for a in s.split())
        for i in range(4):
            self.antennas[i].SetValue((i + 1) in antennas)

    def writeOptions(self):
        self.config.Write('CrossMgrHost', self.getCrossMgrHost())
        self.config.Write('UseHostName',
                          'True' if self.useHostName.GetValue() else 'False')
        self.config.Write(
            'ImpinjHostName', ImpinjHostNamePrefix +
            self.impinjHostName.GetValue() + ImpinjHostNameSuffix)
        self.config.Write('ImpinjAddr', self.impinjHost.GetAddress())
        self.config.Write('ImpinjPort', '{}'.format(ImpinjInboundPort))
        self.config.Write('Antennas', self.getAntennaStr())

        self.config.Write('ConnectionTimeoutSeconds',
                          '{}'.format(Impinj.ConnectionTimeoutSeconds))
        self.config.Write('KeepaliveSeconds',
                          '{}'.format(Impinj.KeepaliveSeconds))
        self.config.Write('RepeatSeconds', '{}'.format(Impinj.RepeatSeconds))
        self.config.Write('PlaySounds', '{}'.format(Utils.playBell))

        self.config.Write('ReceiverSensitivity',
                          '{}'.format(Impinj.ReceiverSensitivity or 0))
        self.config.Write('TransmitPower', '{}'.format(Impinj.TransmitPower
                                                       or 0))
        self.config.Write('TagPopulation', '{}'.format(Impinj.TagPopulation
                                                       or 0))
        self.config.Write('TagTransitTime', '{}'.format(Impinj.TagTransitTime
                                                        or 0))
        self.config.Write('ProcessingMethod',
                          '{}'.format(Impinj.ProcessingMethod))
        self.config.Write('AntennaChoice', '{}'.format(Impinj.AntennaChoice))
        self.config.Write(
            'RemoveOutliers',
            '{}'.format('True' if Impinj.RemoveOutliers else 'False'))

        self.config.Flush()

    def readOptions(self):
        self.crossMgrHost.SetValue(
            self.config.Read('CrossMgrHost', Utils.DEFAULT_HOST))
        useHostName = (self.config.Read('UseHostName',
                                        'True').upper()[:1] == 'T')
        self.useHostName.SetValue(useHostName)
        self.useStaticAddress.SetValue(not useHostName)
        self.impinjHostName.SetValue(
            self.config.Read(
                'ImpinjHostName',
                ImpinjHostNamePrefix + '00-00-00' + ImpinjHostNameSuffix)
            [len(ImpinjHostNamePrefix):-len(ImpinjHostNameSuffix)])
        self.impinjHost.SetValue(self.config.Read('ImpinjAddr', '0.0.0.0'))
        self.setAntennaStr(self.config.Read('Antennas', '1 2 3 4'))
        Utils.playBell = (self.config.Read('PlaySounds',
                                           'True').upper()[:1] == 'T')

        Impinj.ConnectionTimeoutSeconds = int(
            self.config.Read('ConnectionTimeoutSeconds',
                             '{}'.format(Impinj.ConnectionTimeoutSeconds)))
        Impinj.KeepaliveSeconds = int(
            self.config.Read('KeepaliveSeconds',
                             '{}'.format(Impinj.KeepaliveSeconds)))
        Impinj.RepeatSeconds = int(
            self.config.Read('RepeatSeconds',
                             '{}'.format(Impinj.RepeatSeconds)))

        Impinj.ReceiverSensitivity = int(
            self.config.Read('ReceiverSensitivity', '0')) or None
        Impinj.TransmitPower = int(self.config.Read('TransmitPower',
                                                    '0')) or None
        Impinj.TagPopulation = int(self.config.Read('TagPopulation',
                                                    '0')) or None
        Impinj.TagTransitTime = int(self.config.Read('TagTransitTime',
                                                     '0')) or None
        Impinj.ProcessingMethod = int(self.config.Read(
            'ProcessingMethod', '0'))  # Default to QuadraticRegression.
        Impinj.AntennaChoice = int(self.config.Read(
            'AntennaChoice', '1'))  # Default to MaxDB antenna.
        Impinj.RemoveOutliers = (self.config.Read('RemoveOutliers',
                                                  'True').upper()[:1] == 'T')

    def updateMessages(self, event):
        tNow = datetime.datetime.now()
        running = int((tNow - self.tStart).total_seconds())
        self.runningTime.SetLabel('{:02d}:{:02d}:{:02d}'.format(
            running // (60 * 60), (running // 60) % 60, running % 60))
        self.time.SetLabel(tNow.strftime('%H:%M:%S'))

        def formatAntennaReadCount(arc):
            if arc < 1000:
                return arc
            if arc < 1000000:
                return '{:.1f}k'.format(arc / 1000.0)
            return '{:.1f}m'.format(arc / 1000000.0)

        if not self.messageQ:
            return
        while 1:
            try:
                d = self.messageQ.get(False)
            except Empty:
                break
            if isinstance(d[-1], dict):
                antennaReadCount = d[-1]
                d = d[:-1]
            else:
                antennaReadCount = None

            message = ' '.join(six.text_type(x) for x in d[1:])
            if d[0] == 'Impinj':
                if 'state' in d:
                    self.impinjMessages.messageList.SetBackgroundColour(
                        self.LightGreen if d[2] else self.LightRed)
                else:
                    self.impinjMessages.write(message)
                    if antennaReadCount is not None:
                        total = max(
                            1,
                            sum(antennaReadCount[i] for i in range(1, 4 + 1)))
                        label = '{}: {} ({})'.format(
                            'ANT Used'
                            if Impinj.ProcessingMethod != FirstReadMethod else
                            'ANT Reads',
                            ' | '.join('{}:{} {:.1f}%'.format(
                                i, formatAntennaReadCount(antennaReadCount[i]),
                                antennaReadCount[i] * 100.0 / total)
                                       for i in range(1, 4 + 1)),
                            'Peak RSSI'
                            if Impinj.ProcessingMethod != FirstReadMethod else
                            'First Read',
                        )
                        self.antennaReadCount.SetLabel(label)
                        self.antennaReads.Set(
                            [antennaReadCount[i] for i in range(1, 4 + 1)])
                self.refreshMethodName()
            elif d[0] == 'Impinj2JChip':
                if 'state' in d:
                    self.crossMgrMessages.messageList.SetBackgroundColour(
                        self.LightGreen if d[2] else self.LightRed)
                else:
                    self.crossMgrMessages.write(message)
            elif d[0] == 'BackupFile':
                self.backupFile.SetLabel(d[1])
Esempio n. 4
0
class MainWin(wx.Frame):
    def __init__(self, parent, id=wx.ID_ANY, title='', size=(200, 200)):
        wx.Frame.__init__(self, parent, id, title, size=size)

        self.config = wx.Config(
            appName="CrossMgrImpinj",
            vendorName="SmartCyclingSolutions",
            #style=wx.Config.CONFIG_USE_LOCAL_FILE
        )

        self.SetBackgroundColour(wx.Colour(232, 232, 232))

        self.LightGreen = wx.Colour(153, 255, 153)
        self.LightRed = wx.Colour(255, 153, 153)

        font = self.GetFont()
        bigFont = wx.Font(int(font.GetPointSize() * 1.5), font.GetFamily(),
                          font.GetStyle(), wx.FONTWEIGHT_BOLD)
        titleFont = wx.Font(int(bigFont.GetPointSize() * 2.2),
                            bigFont.GetFamily(), bigFont.GetStyle(),
                            bigFont.GetWeight())

        self.vbs = wx.BoxSizer(wx.VERTICAL)

        bs = wx.BoxSizer(wx.HORIZONTAL)

        self.reset = RoundButton(self, label='Reset', size=(80, 80))
        self.reset.SetBackgroundColour(wx.WHITE)
        self.reset.SetForegroundColour(wx.Colour(0, 128, 128))
        self.reset.SetFontToFitLabel(
        )  # Use the button's default font, but change the font size to fit the label.
        self.reset.Bind(wx.EVT_BUTTON, self.doReset)
        self.reset.Refresh()
        bs.Add(self.reset, border=8, flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.Add(setFont(titleFont, wx.StaticText(self, label='CrossMgrImpinj')),
               border=8,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.AddStretchSpacer()
        bitmap = wx.Bitmap(clipboard_xpm)
        self.copyToClipboard = wx.BitmapButton(self, bitmap=bitmap)
        self.copyToClipboard.SetToolTip(
            wx.ToolTip('Copy Configuration and Logs to Clipboard...'))
        self.copyToClipboard.Bind(wx.EVT_BUTTON, self.doCopyToClipboard)
        bs.Add(self.copyToClipboard,
               border=32,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        self.tStart = datetime.datetime.now()
        bs.Add(setFont(
            bigFont,
            wx.StaticText(self,
                          label='Last Reset: %s' %
                          self.tStart.strftime('%H:%M:%S'))),
               border=10,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        self.runningTime = setFont(bigFont,
                                   wx.StaticText(self, label='00:00:00'))
        bs.Add(self.runningTime,
               border=20,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.Add(setFont(bigFont, wx.StaticText(self, label=' / ')),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.time = setFont(bigFont, wx.StaticText(self, label='00:00:00'))
        bs.Add(self.time, flag=wx.ALIGN_CENTER_VERTICAL)

        self.vbs.Add(bs, flag=wx.ALL | wx.EXPAND, border=4)

        fgs = wx.FlexGridSizer(rows=2, cols=2, vgap=4, hgap=4)
        fgs.AddGrowableRow(1)
        fgs.AddGrowableCol(0)
        fgs.AddGrowableCol(1)
        fgs.SetFlexibleDirection(wx.BOTH)

        self.vbs.Add(fgs, flag=wx.EXPAND, proportion=5)

        #------------------------------------------------------------------------------------------------
        # Impinj configuration.
        #
        gbs = wx.GridBagSizer(4, 4)
        fgs.Add(gbs, flag=wx.EXPAND | wx.ALL, border=4)

        iRow = 0
        hb = wx.BoxSizer(wx.HORIZONTAL)
        hb.Add(setFont(bigFont,
                       wx.StaticText(self, label='Impinj Configuration:')),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.autoDetectButton = wx.Button(self, label='Auto Detect')
        self.autoDetectButton.Bind(wx.EVT_BUTTON, self.doAutoDetect)
        hb.Add(self.autoDetectButton, flag=wx.LEFT, border=6)

        self.advancedButton = wx.Button(self, label='Advanced...')
        self.advancedButton.Bind(wx.EVT_BUTTON, self.doAdvanced)
        hb.Add(self.advancedButton, flag=wx.LEFT, border=6)

        gbs.Add(hb, pos=(iRow, 0), span=(1, 2), flag=wx.ALIGN_LEFT)

        iRow += 1

        gs = wx.GridSizer(rows=0, cols=4, vgap=0, hgap=2)
        self.antennaLabels = []
        self.antennas = []
        for i in xrange(4):
            self.antennaLabels.append(
                wx.StaticText(self,
                              label='{}'.format(i + 1),
                              style=wx.ALIGN_CENTER))
            gs.Add(self.antennaLabels[-1], flag=wx.ALIGN_CENTER | wx.EXPAND)
        for i in xrange(4):
            cb = wx.CheckBox(self, wx.ID_ANY, '')
            if i < 2:
                cb.SetValue(True)
            cb.Bind(wx.EVT_CHECKBOX, lambda x: self.getAntennaStr())
            gs.Add(cb, flag=wx.ALIGN_CENTER)
            self.antennas.append(cb)

        gbs.Add(wx.StaticText(self, label='ANT Ports:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM)
        gbs.Add(gs, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL)

        iRow += 1

        self.useHostName = wx.RadioButton(self,
                                          label='Host Name:',
                                          style=wx.RB_GROUP)
        gbs.Add(self.useHostName,
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_CENTER_VERTICAL)
        hb = wx.BoxSizer(wx.HORIZONTAL)
        hb.Add(wx.StaticText(self, label=ImpinjHostNamePrefix),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.impinjHostName = masked.TextCtrl(
            self,
            mask='NN-NN-NN',
            defaultValue='00-00-00',
            useFixedWidthFont=True,
            size=(80, -1),
        )
        hb.Add(self.impinjHostName)
        hb.Add(wx.StaticText(self, label=ImpinjHostNameSuffix),
               flag=wx.ALIGN_CENTER_VERTICAL)
        hb.Add(wx.StaticText(self,
                             label=' : ' + '{}'.format(ImpinjInboundPort)),
               flag=wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(hb, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_LEFT)

        iRow += 1
        self.useStaticAddress = wx.RadioButton(self, label='IP:')
        gbs.Add(self.useStaticAddress,
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_CENTER_VERTICAL)
        hb = wx.BoxSizer(wx.HORIZONTAL)
        self.impinjHost = IpAddrCtrl(self, style=wx.TE_PROCESS_TAB)
        hb.Add(self.impinjHost)
        hb.Add(wx.StaticText(self,
                             label=' : ' + '{}'.format(ImpinjInboundPort)),
               flag=wx.ALIGN_CENTER_VERTICAL)

        gbs.Add(hb, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_LEFT)

        self.useHostName.SetValue(True)
        self.useStaticAddress.SetValue(False)

        iRow += 1
        gbs.Add(wx.StaticText(self, label='ANT Reads:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT)
        self.antennaReadCount = wx.StaticText(
            self, label='1:0 0% | 2:0 0% | 3:0 0% | 4:0 0%               ')
        gbs.Add(self.antennaReadCount,
                pos=(iRow, 1),
                span=(1, 2),
                flag=wx.ALIGN_LEFT)

        iRow += 1
        gbs.Add(wx.StaticText(self, label='Backup File:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT)
        self.backupFile = wx.StaticText(
            self, label='                                                   ')
        gbs.Add(self.backupFile,
                pos=(iRow, 1),
                span=(1, 2),
                flag=wx.ALIGN_LEFT)

        #------------------------------------------------------------------------------------------------
        # CrossMgr configuration.
        #
        gbs = wx.GridBagSizer(4, 4)
        fgs.Add(gbs, flag=wx.EXPAND | wx.ALL, border=4)

        gbs.Add(setFont(bigFont,
                        wx.StaticText(self, label='CrossMgr Configuration:')),
                pos=(0, 0),
                span=(1, 2),
                flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(wx.StaticText(self, label='CrossMgr Address:'),
                pos=(1, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)

        hb = wx.BoxSizer(wx.HORIZONTAL)
        self.crossMgrHost = IpAddrCtrl(self, style=wx.TE_PROCESS_TAB)
        hb.Add(self.crossMgrHost, flag=wx.ALIGN_LEFT)
        hb.Add(wx.StaticText(self, label=' : 53135'),
               flag=wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(hb, pos=(1, 1), span=(1, 1), flag=wx.ALIGN_LEFT)

        #------------------------------------------------------------------------------------------------
        # Add messages
        #
        self.impinjMessagesText = wx.TextCtrl(self,
                                              style=wx.TE_READONLY
                                              | wx.TE_MULTILINE | wx.HSCROLL,
                                              size=(-1, 400))
        fgs.Add(self.impinjMessagesText, flag=wx.EXPAND, proportion=2)
        self.impinjMessages = MessageManager(self.impinjMessagesText)

        self.crossMgrMessagesText = wx.TextCtrl(self,
                                                style=wx.TE_READONLY
                                                | wx.TE_MULTILINE | wx.HSCROLL,
                                                size=(-1, 400))
        fgs.Add(self.crossMgrMessagesText, flag=wx.EXPAND, proportion=2)
        self.crossMgrMessages = MessageManager(self.crossMgrMessagesText)
        self.fgs = fgs

        #------------------------------------------------------------------------------------------------
        # Create a timer to update the messages.
        #
        self.timer = wx.Timer()
        self.timer.Bind(wx.EVT_TIMER, self.updateMessages)
        self.timer.Start(1000, False)

        self.Bind(wx.EVT_CLOSE, self.onCloseWindow)

        self.readOptions()

        self.SetSizer(self.vbs)
        self.start()

    def readerStatusCB(self, **kwargs):
        # As this is called from another thread, make sure all UI updates are done from CallAfter.
        connectedAntennas = set(kwargs.get('connectedAntennas', []))
        for i in xrange(4):
            wx.CallAfter(
                self.antennaLabels[i].SetBackgroundColour, self.LightGreen if
                (i + 1) in connectedAntennas else wx.NullColour)

    def start(self):
        self.dataQ = Queue()
        self.messageQ = Queue()
        self.shutdownQ = Queue(
        )  # Queue to tell the Impinj monitor to shut down.
        self.readerStatusCB()

        if self.useHostName.GetValue():
            self.impinjProcess = Process(
                name='ImpinjProcess',
                target=ImpinjServer,
                args=(
                    self.dataQ,
                    self.messageQ,
                    self.shutdownQ,
                    ImpinjHostNamePrefix + self.impinjHostName.GetValue() +
                    ImpinjHostNameSuffix,
                    ImpinjInboundPort,
                    self.getAntennaStr(),
                    self.readerStatusCB,
                ))
        else:
            self.impinjProcess = Process(name='ImpinjProcess',
                                         target=ImpinjServer,
                                         args=(
                                             self.dataQ,
                                             self.messageQ,
                                             self.shutdownQ,
                                             self.impinjHost.GetAddress(),
                                             ImpinjInboundPort,
                                             self.getAntennaStr(),
                                             self.readerStatusCB,
                                         ))
        self.impinjProcess.daemon = True

        self.crossMgrProcess = Process(name='CrossMgrProcess',
                                       target=CrossMgrServer,
                                       args=(self.dataQ, self.messageQ,
                                             self.shutdownQ,
                                             self.getCrossMgrHost(),
                                             CrossMgrPort))
        self.crossMgrProcess.daemon = True

        self.impinjProcess.start()
        self.crossMgrProcess.start()

    def shutdown(self):
        self.impinjProcess = None
        self.crossMgrProcess = None
        self.messageQ = None
        self.dataQ = None
        self.shutdownQ = None

    def doReset(self, event, confirm=True):
        if confirm:
            dlg = wx.MessageDialog(self, 'Reset CrossMgrImpinj Adapter?',
                                   'Confirm Reset',
                                   wx.OK | wx.CANCEL | wx.ICON_WARNING)
            ret = dlg.ShowModal()
            dlg.Destroy()
            if ret != wx.ID_OK:
                return

        self.reset.Enable(
            False)  # Prevent multiple clicks while shutting down.
        self.writeOptions()

        self.gracefulShutdown()

        self.impinjMessages.clear()
        self.crossMgrMessages.clear()
        self.shutdown()

        self.reset.Enable(True)

        wx.CallAfter(self.start)

    def doAutoDetect(self, event):
        wx.BeginBusyCursor()
        self.gracefulShutdown()
        self.shutdown()
        impinjHost, crossMgrHost = AutoDetect(
            ImpinjInboundPort)[0], '127.0.0.1'
        wx.EndBusyCursor()

        if impinjHost and crossMgrHost:
            self.useStaticAddress.SetValue(True)
            self.useHostName.SetValue(False)

            self.impinjHost.SetValue(impinjHost)
            self.crossMgrHost.SetValue(crossMgrHost)
        else:
            dlg = wx.MessageDialog(
                self,
                'Auto Detect Failed.\nCheck that reader has power and is connected to the router.',
                'Auto Detect Failed', wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()

        self.doReset(event, False)

    def doAdvanced(self, event):
        dlg = AdvancedSetup(self)
        dlg.ShowModal()
        dlg.Destroy()

    def gracefulShutdown(self):
        # Shutdown the CrossMgr process by sending it a shutdown command.
        if self.shutdownQ:
            self.shutdownQ.put('shutdown')
            self.shutdownQ.put('shutdown')
            self.shutdownQ.put('shutdown')
        if self.dataQ:
            self.dataQ.put('shutdown')
            self.dataQ.put('shutdown')

        if self.crossMgrProcess:
            self.crossMgrProcess.join()
        if self.impinjProcess:
            self.impinjProcess.join()

        self.crossMgrProcess = None
        self.impinjProcess = None

    def onCloseWindow(self, event):
        self.gracefulShutdown()
        wx.Exit()

    def doCopyToClipboard(self, event):
        cc = [
            'Configuration: CrossMgrImpinj',
            '    RunningTime:   {}'.format(self.runningTime.GetLabel()),
            '    Time:          {}'.format(self.time.GetLabel()),
            '    BackupFile:    {}'.format(self.backupFile.GetLabel()),
            '',
            'Configuration: Impinj:',
            '    Use Host Name: {}'.format(
                'True' if self.useHostName.GetValue() else 'False'),
            '    HostName:      {}'.format((ImpinjHostNamePrefix +
                                            self.impinjHostName.GetValue()) +
                                           ImpinjHostNameSuffix),
            '    ImpinjHost:    {}'.format(self.impinjHost.GetAddress()),
            '    ImpinjPort:    {}'.format(ImpinjInboundPort),
            ''
            '    ConnectionTimeoutSeconds: {}'.format(
                Impinj.ConnectionTimeoutSeconds),
            '    KeepaliveSeconds:         {}'.format(Impinj.KeepaliveSeconds),
            '    RepeatSeconds:            {}'.format(Impinj.RepeatSeconds),
            '',
            'Configuration: CrossMgr',
            '    CrossMgrHost:  {}'.format(self.getCrossMgrHost()),
            '    CrossMgrPort:  {}'.format(CrossMgrPort),
        ]
        cc.append('\nLog: Impinj')
        log = self.impinjMessagesText.GetValue()
        cc.extend(['    ' + line for line in log.split('\n')])

        cc.append('\nLog: CrossMgr')
        log = self.crossMgrMessagesText.GetValue()
        cc.extend(['    ' + line for line in log.split('\n')])

        cc.append('\nLog: Application\n')
        try:
            with open(redirectFileName, 'r') as fp:
                for line in fp:
                    cc.append(line)
        except:
            pass

        if wx.TheClipboard.Open():
            do = wx.TextDataObject()
            do.SetText('\n'.join(cc))
            wx.TheClipboard.SetData(do)
            wx.TheClipboard.Close()
            dlg = wx.MessageDialog(
                self, 'Configuration and Logs copied to the Clipboard.',
                'Copy to Clipboard Succeeded', wx.OK | wx.ICON_INFORMATION)
            ret = dlg.ShowModal()
            dlg.Destroy()
        else:
            # oops... something went wrong!
            wx.MessageBox("Unable to open the clipboard", "Error")

    def getCrossMgrHost(self):
        return self.crossMgrHost.GetAddress()

    def getAntennaStr(self):
        selectedAntennas = [
            i for i in xrange(4) if self.antennas[i].GetValue()
        ]
        # Ensure at least one antenna is selected.
        if not selectedAntennas:
            self.antennas[0].SetValue(True)
            selectedAntennas = [0]
        return ' '.join('{}'.format(a + 1) for a in selectedAntennas)

    def setAntennaStr(self, s):
        antennas = set(int(a) for a in s.split())
        for i in xrange(4):
            self.antennas[i].SetValue((i + 1) in antennas)

    def writeOptions(self):
        self.config.Write('CrossMgrHost', self.getCrossMgrHost())
        self.config.Write('UseHostName',
                          'True' if self.useHostName.GetValue() else 'False')
        self.config.Write(
            'ImpinjHostName', ImpinjHostNamePrefix +
            self.impinjHostName.GetValue() + ImpinjHostNameSuffix)
        self.config.Write('ImpinjAddr', self.impinjHost.GetAddress())
        self.config.Write('ImpinjPort', '{}'.format(ImpinjInboundPort))
        self.config.Write('Antennas', self.getAntennaStr())

        self.config.Write('ConnectionTimeoutSeconds',
                          '{}'.format(Impinj.ConnectionTimeoutSeconds))
        self.config.Write('KeepaliveSeconds',
                          '{}'.format(Impinj.KeepaliveSeconds))
        self.config.Write('RepeatSeconds', '{}'.format(Impinj.RepeatSeconds))
        self.config.Write('PlaySounds', '{}'.format(Utils.playBell))

        self.config.Write('ReceiverSensitivity',
                          '{}'.format(Impinj.ReceiverSensitivity or 0))
        self.config.Write('TransmitPower', '{}'.format(Impinj.TransmitPower
                                                       or 0))
        self.config.Write('TagPopulation', '{}'.format(Impinj.TagPopulation
                                                       or 0))
        self.config.Write('TagTransitTime', '{}'.format(Impinj.TagTransitTime
                                                        or 0))

        self.config.Flush()

    def readOptions(self):
        self.crossMgrHost.SetValue(
            self.config.Read('CrossMgrHost', Utils.DEFAULT_HOST))
        useHostName = (self.config.Read('UseHostName',
                                        'True').upper()[:1] == 'T')
        self.useHostName.SetValue(useHostName)
        self.useStaticAddress.SetValue(not useHostName)
        self.impinjHostName.SetValue(
            self.config.Read(
                'ImpinjHostName',
                ImpinjHostNamePrefix + '00-00-00' + ImpinjHostNameSuffix)
            [len(ImpinjHostNamePrefix):-len(ImpinjHostNameSuffix)])
        self.impinjHost.SetValue(self.config.Read('ImpinjAddr', '0.0.0.0'))
        self.setAntennaStr(self.config.Read('Antennas', '1 2 3 4'))
        Utils.playBell = (self.config.Read('PlaySounds',
                                           'True').upper()[:1] == 'T')

        Impinj.ConnectionTimeoutSeconds = int(
            self.config.Read('ConnectionTimeoutSeconds',
                             '{}'.format(Impinj.ConnectionTimeoutSeconds)))
        Impinj.KeepaliveSeconds = int(
            self.config.Read('KeepaliveSeconds',
                             '{}'.format(Impinj.KeepaliveSeconds)))
        Impinj.RepeatSeconds = int(
            self.config.Read('RepeatSeconds',
                             '{}'.format(Impinj.RepeatSeconds)))

        Impinj.ReceiverSensitivity = int(
            self.config.Read('ReceiverSensitivity', '0')) or None
        Impinj.TransmitPower = int(self.config.Read('TransmitPower',
                                                    '0')) or None
        Impinj.TagPopulation = int(self.config.Read('TagPopulation',
                                                    '0')) or None
        Impinj.TagTransitTime = int(self.config.Read('TagTransitTime',
                                                     '0')) or None

    def updateMessages(self, event):
        tNow = datetime.datetime.now()
        running = int((tNow - self.tStart).total_seconds())
        self.runningTime.SetLabel('{:02d}:{:02d}:{:02d}'.format(
            running // (60 * 60), (running // 60) % 60, running % 60))
        self.time.SetLabel(tNow.strftime('%H:%M:%S'))

        if not self.messageQ:
            return
        while 1:
            try:
                d = self.messageQ.get(False)
            except Empty:
                break
            if isinstance(d[-1], dict):
                antennaReadCount = d[-1]
                d = d[:-1]
            else:
                antennaReadCount = None

            message = ' '.join(unicode(x) for x in d[1:])
            if d[0] == 'Impinj':
                if 'state' in d:
                    self.impinjMessages.messageList.SetBackgroundColour(
                        self.LightGreen if d[2] else self.LightRed)
                else:
                    self.impinjMessages.write(message)
                    if antennaReadCount is not None:
                        total = max(
                            1,
                            sum(antennaReadCount[i] for i in xrange(1, 4 + 1)))
                        self.antennaReadCount.SetLabel(' | '.join(
                            '{}:{} {:.1f}%'.format(
                                i, antennaReadCount[i], antennaReadCount[i] *
                                100.0 / total) for i in xrange(1, 4 + 1)))
            elif d[0] == 'Impinj2JChip':
                if 'state' in d:
                    self.crossMgrMessages.messageList.SetBackgroundColour(
                        self.LightGreen if d[2] else self.LightRed)
                else:
                    self.crossMgrMessages.write(message)
            elif d[0] == 'BackupFile':
                self.backupFile.SetLabel(d[1])
Esempio n. 5
0
class Actions(wx.Panel):
    iResetStartClockOnFirstTag = 1
    iSkipFirstTagRead = 2

    def __init__(self, parent, id=wx.ID_ANY):
        wx.Panel.__init__(self, parent, id)

        self.SetBackgroundColour(wx.Colour(255, 255, 255))

        ps = wx.BoxSizer(wx.VERTICAL)
        self.splitter = wx.SplitterWindow(self, wx.VERTICAL)
        ps.Add(self.splitter, 1, flag=wx.EXPAND)
        self.SetSizer(ps)

        #---------------------------------------------------------------------------------------------

        self.leftPanel = wx.Panel(self.splitter)
        bs = wx.BoxSizer(wx.VERTICAL)
        self.leftPanel.SetSizer(bs)
        self.leftPanel.SetBackgroundColour(wx.Colour(255, 255, 255))
        self.leftPanel.Bind(wx.EVT_SIZE, self.setWrappedRaceInfo)

        buttonSize = 220
        self.button = RoundButton(self.leftPanel,
                                  size=(buttonSize, buttonSize))
        self.button.SetLabel(FinishText)
        self.button.SetFontToFitLabel()
        self.button.SetForegroundColour(wx.Colour(128, 128, 128))
        self.Bind(wx.EVT_BUTTON, self.onPress, self.button)

        self.clock = Clock(self, size=(190, 190), checkFunc=self.updateClock)
        self.clock.SetBackgroundColour(wx.WHITE)

        self.raceIntro = wx.StaticText(self.leftPanel, label=u'')
        self.raceIntro.SetFont(wx.Font(20, wx.DEFAULT, wx.NORMAL, wx.NORMAL))

        self.chipTimingOptions = wx.RadioBox(
            self.leftPanel,
            label=_("Chip Timing Options"),
            majorDimension=1,
            choices=Properties.RfidProperties.choices,
            style=wx.RA_SPECIFY_COLS)

        self.Bind(wx.EVT_RADIOBOX, self.onChipTimingOptions,
                  self.chipTimingOptions)

        self.settingsButton = wx.BitmapButton(
            self.leftPanel, bitmap=Utils.GetPngBitmap('settings-icon.png'))
        self.settingsButton.SetToolTip(wx.ToolTip(_('Properties Shortcut')))
        self.settingsButton.Bind(wx.EVT_BUTTON, self.onShowProperties)

        self.startRaceTimeCheckBox = wx.CheckBox(
            self.leftPanel, label=_('Start Race Automatically at Future Time'))

        hsSettings = wx.BoxSizer(wx.HORIZONTAL)
        hsSettings.Add(self.settingsButton, flag=wx.ALIGN_CENTER_VERTICAL)
        hsSettings.Add(self.startRaceTimeCheckBox,
                       flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL,
                       border=12)

        border = 8
        hs = wx.BoxSizer(wx.HORIZONTAL)
        hs.Add(self.button, border=border, flag=wx.LEFT | wx.TOP)
        hs.Add(self.raceIntro,
               1,
               border=border,
               flag=wx.LEFT | wx.TOP | wx.RIGHT | wx.EXPAND)
        bs.Add(hs, border=border, flag=wx.ALL)

        hsClock = wx.BoxSizer(wx.HORIZONTAL)
        hsClock.AddSpacer(26)
        hsClock.Add(self.clock)
        hsClock.Add(hsSettings, border=4, flag=wx.LEFT)
        bs.Add(hsClock, border=4, flag=wx.ALL)

        bs.Add(self.chipTimingOptions, border=border, flag=wx.ALL)

        #---------------------------------------------------------------------------------------------

        self.rightPanel = wx.Panel(self.splitter)
        self.rightPanel.SetBackgroundColour(wx.Colour(255, 255, 255))
        checklistTitle = wx.StaticText(self.rightPanel, label=_('Checklist:'))
        checklistTitle.SetFont(
            wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_NORMAL))
        self.checklist = Checklist.Checklist(self.rightPanel)

        hsSub = wx.BoxSizer(wx.VERTICAL)
        hsSub.Add(checklistTitle, 0, flag=wx.ALL, border=4)
        hsSub.Add(self.checklist,
                  1,
                  flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
                  border=4)
        self.rightPanel.SetSizer(hsSub)

        #---------------------------------------------------------------------------------------------

        self.splitter.SplitVertically(self.leftPanel, self.rightPanel)
        self.splitter.SetMinimumPaneSize(100)
        wx.CallAfter(self.refresh)
        wx.CallAfter(self.splitter.SetSashPosition, 650)
        wx.CallAfter(self.GetSizer().Layout)

    def setWrappedRaceInfo(self, event=None):
        wrapWidth = self.leftPanel.GetClientSize(
        )[0] - self.button.GetClientSize()[0] - 20
        dc = wx.WindowDC(self.raceIntro)
        dc.SetFont(self.raceIntro.GetFont())
        label = wordwrap(Model.race.getRaceIntro() if Model.race else u'',
                         wrapWidth, dc)
        self.raceIntro.SetLabel(label)
        self.leftPanel.GetSizer().Layout()
        if event:
            event.Skip()

    def updateChipTimingOptions(self):
        if not Model.race:
            return

        iSelection = self.chipTimingOptions.GetSelection()
        race = Model.race
        race.resetStartClockOnFirstTag = bool(
            iSelection == self.iResetStartClockOnFirstTag)
        race.skipFirstTagRead = bool(iSelection == self.iSkipFirstTagRead)

    def updateClock(self):
        mainWin = Utils.getMainWin()
        return not mainWin or mainWin.isShowingPage(self)

    def onShowProperties(self, event):
        if not Model.race:
            Utils.MessageOK(
                self,
                _("You must have a valid race.  Open or New a race first."),
                _("No Valid Race"),
                iconMask=wx.ICON_ERROR)
            return
        if not hasattr(self, 'propertiesDialog'):
            self.propertiesDialog = PropertiesDialog(self,
                                                     showFileFields=False,
                                                     updateProperties=True,
                                                     size=(600, 400))
        else:
            self.propertiesDialog.properties.refresh(forceUpdate=True)
        self.propertiesDialog.properties.setPage('raceOptionsProperties')
        if self.propertiesDialog.ShowModal() == wx.ID_OK:
            self.propertiesDialog.properties.doCommit()
            Utils.refresh()

    def onChipTimingOptions(self, event):
        if not Model.race:
            return
        self.updateChipTimingOptions()

    def onPress(self, event):
        if not Model.race:
            return
        with Model.LockRace() as race:
            running = race.isRunning()
        if running:
            self.onFinishRace(event)
            return

        self.updateChipTimingOptions()
        if getattr(Model.race, 'enableJChipIntegration', False):
            try:
                externalFields = race.excelLink.getFields()
                externalInfo = race.excelLink.read()
            except:
                externalFields = []
                externalInfo = {}
            if not externalInfo:
                Utils.MessageOK(
                    self, u'\n\n'.join([
                        _('Cannot Start. Excel Sheet read failure.'),
                        _('The Excel file is either unconfigured or unreadable.'
                          )
                    ]), _('Excel Sheet Read '), wx.ICON_ERROR)
                return
            try:
                i = next((i for i, field in enumerate(externalFields)
                          if field.startswith('Tag')))
            except StopIteration:
                Utils.MessageOK(
                    self, u'\n\n'.join([
                        _('Cannot Start.  Excel Sheet missing Tag column.'),
                        _('The Excel file must contain a Tag column to use RFID.'
                          )
                    ]), _('Excel Sheet missing Tag column'), wx.ICON_ERROR)
                return

        if self.startRaceTimeCheckBox.IsChecked():
            self.onStartRaceTime(event)
        else:
            self.onStartRace(event)

    def onStartRace(self, event):
        if Model.race and Utils.MessageOKCancel(self, _('Start Race Now?\n\n'),
                                                _('Start Race')):
            StartRaceNow()

    def onStartRaceTime(self, event):
        if Model.race is None:
            return
        dlg = StartRaceAtTime(self)
        dlg.ShowModal()
        dlg.Destroy()

    def onFinishRace(self, event):
        if Model.race is None or not Utils.MessageOKCancel(
                self, _('Finish Race Now?'), _('Finish Race')):
            return

        with Model.LockRace() as race:
            race.finishRaceNow()
            if race.numLaps is None:
                race.numLaps = race.getMaxLap()
            SetNoDataDNS()
            Model.resetCache()

        Utils.writeRace()
        self.refresh()
        mainWin = Utils.getMainWin()
        if mainWin:
            mainWin.refresh()

        OutputStreamer.writeRaceFinish()
        OutputStreamer.StopStreamer()
        try:
            ChipReader.chipReaderCur.StopListener()
        except:
            pass

        if getattr(Model.race, 'ftpUploadDuringRace', False):
            realTimeFtpPublish.publishEntry(True)

    def commit(self):
        self.checklist.commit()

    def refresh(self):
        self.clock.Start()
        self.button.Enable(False)
        self.startRaceTimeCheckBox.Enable(False)
        self.settingsButton.Enable(False)
        self.button.SetLabel(StartText)
        self.button.SetForegroundColour(wx.Colour(100, 100, 100))
        self.chipTimingOptions.SetSelection(0)
        self.chipTimingOptions.Enable(False)

        with Model.LockRace() as race:
            if race:
                self.settingsButton.Enable(True)

                # Adjust the chip recording options for TT.
                if getattr(race, 'isTimeTrial', False):
                    race.resetStartClockOnFirstTag = False
                    race.skipFirstTagRead = False

                if getattr(race, 'resetStartClockOnFirstTag', True):
                    self.chipTimingOptions.SetSelection(
                        self.iResetStartClockOnFirstTag)
                elif getattr(race, 'skipFirstTagRead', False):
                    self.chipTimingOptions.SetSelection(self.iSkipFirstTagRead)

                if race.startTime is None:
                    self.button.Enable(True)
                    self.button.SetLabel(StartText)
                    self.button.SetForegroundColour(wx.Colour(0, 128, 0))

                    self.startRaceTimeCheckBox.Enable(True)
                    self.startRaceTimeCheckBox.Show(True)

                    self.chipTimingOptions.Enable(
                        getattr(race, 'enableJChipIntegration', False))
                    self.chipTimingOptions.Show(
                        getattr(race, 'enableJChipIntegration', False))
                elif race.isRunning():
                    self.button.Enable(True)
                    self.button.SetLabel(FinishText)
                    self.button.SetForegroundColour(wx.Colour(128, 0, 0))

                    self.startRaceTimeCheckBox.Enable(False)
                    self.startRaceTimeCheckBox.Show(False)

                    self.chipTimingOptions.Enable(False)
                    self.chipTimingOptions.Show(False)

                # Adjust the time trial display options.
                if getattr(race, 'isTimeTrial', False):
                    self.chipTimingOptions.Enable(False)
                    self.chipTimingOptions.Show(False)

            self.GetSizer().Layout()

        self.setWrappedRaceInfo()
        self.checklist.refresh()

        mainWin = Utils.getMainWin()
        if mainWin is not None:
            mainWin.updateRaceClock()
Esempio n. 6
0
class MainWin(wx.Frame):
    def __init__(self, parent, id=wx.ID_ANY, title='', size=(200, 200)):
        wx.Frame.__init__(self, parent, id, title, size=size)

        self.SetBackgroundColour(wx.Colour(240, 240, 240))

        self.fname = None
        self.updated = False
        self.firstTime = True
        self.lastUpdateTime = None
        self.comments = []

        self.filehistory = wx.FileHistory(16)
        self.config = wx.Config(appName="StageRaceGC",
                                vendorName="*****@*****.**",
                                style=wx.CONFIG_USE_LOCAL_FILE)
        self.filehistory.Load(self.config)

        inputBox = wx.StaticBox(self, label=_('Input'))
        inputBoxSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
        self.fileBrowse = filebrowse.FileBrowseButtonWithHistory(
            self,
            labelText=_('Excel File'),
            buttonText=('Browse...'),
            startDirectory=os.path.expanduser('~'),
            fileMask=
            'Excel Spreadsheet (*.xlsx; *.xlsm; *.xls)|*.xlsx; *.xlsml; *.xls',
            size=(400, -1),
            history=lambda: [
                self.filehistory.GetHistoryFile(i)
                for i in xrange(self.filehistory.GetCount())
            ],
            changeCallback=self.doChangeCallback,
        )
        inputBoxSizer.Add(self.fileBrowse,
                          0,
                          flag=wx.EXPAND | wx.ALL,
                          border=4)

        horizontalControlSizer = wx.BoxSizer(wx.HORIZONTAL)

        self.updateButton = RoundButton(self, size=(96, 96))
        self.updateButton.SetLabel(_('Update'))
        self.updateButton.SetFontToFitLabel()
        self.updateButton.SetForegroundColour(wx.Colour(0, 100, 0))
        self.updateButton.Bind(wx.EVT_BUTTON, self.doUpdate)
        horizontalControlSizer.Add(self.updateButton, flag=wx.ALL, border=4)

        horizontalControlSizer.AddSpacer(48)

        vs = wx.BoxSizer(wx.VERTICAL)
        self.tutorialButton = wx.Button(self, label=_('Help/Tutorial...'))
        self.tutorialButton.Bind(wx.EVT_BUTTON, self.onTutorial)
        vs.Add(self.tutorialButton, flag=wx.ALL, border=4)
        branding = wx.adv.HyperlinkCtrl(
            self,
            id=wx.ID_ANY,
            label=u"Powered by CrossMgr",
            url=u"http://www.sites.google.com/site/crossmgrsoftware/")
        vs.Add(branding, flag=wx.ALL, border=4)
        horizontalControlSizer.Add(vs)

        self.openExcel = wx.Button(self, label=_('Open Excel File...'))
        self.openExcel.Bind(wx.EVT_BUTTON, self.onOpenExcel)

        horizontalControlSizer.AddSpacer(48)
        horizontalControlSizer.Add(self.openExcel,
                                   flag=wx.ALIGN_RIGHT | wx.ALL,
                                   border=4)

        inputBoxSizer.Add(horizontalControlSizer, flag=wx.EXPAND)

        self.stageList = ListMixCtrl(self, style=wx.LC_REPORT, size=(-1, 160))
        self.stageList.InsertColumn(0, "Sheet")
        self.stageList.InsertColumn(1, "Bibs", wx.LIST_FORMAT_RIGHT)
        self.stageList.InsertColumn(2, "Errors/Warnings")
        self.stageList.setResizeColumn(2)

        bookStyle = (flatnotebook.FNB_NO_X_BUTTON
                     | flatnotebook.FNB_FF2
                     | flatnotebook.FNB_NODRAG
                     | flatnotebook.FNB_DROPDOWN_TABS_LIST
                     | flatnotebook.FNB_NO_NAV_BUTTONS
                     | flatnotebook.FNB_BOTTOM)
        self.notebook = flatnotebook.FlatNotebook(self,
                                                  1000,
                                                  agwStyle=bookStyle)
        self.notebook.SetBackgroundColour(wx.WHITE)

        self.saveAsExcelButton = wx.Button(self, label=u'Save as Excel')
        self.saveAsExcelButton.Bind(wx.EVT_BUTTON, self.saveAsExcel)

        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(inputBoxSizer, flag=wx.EXPAND | wx.ALL, border=4)
        mainSizer.Add(self.stageList, flag=wx.EXPAND | wx.ALL, border=4)
        mainSizer.Add(self.notebook, 1, flag=wx.EXPAND | wx.ALL, border=4)
        mainSizer.Add(self.saveAsExcelButton, flag=wx.ALL, border=4)

        self.SetSizer(mainSizer)

    def onOpenExcel(self, event):
        filename = self.fileBrowse.GetValue()
        if not filename:
            return
        wait = wx.BusyCursor()
        Utils.LaunchApplication(filename)

    def onTutorial(self, event):
        if not Utils.MessageOKCancel(
                self, u"\n".join([
                    _("Launch the StageRaceGC Tutorial."),
                    _("This open a sample Excel input file created into your home folder."
                      ),
                    _("This data in this sheet is made-up, although it does include some current rider's names."
                      ),
                    u"",
                    _("It will also open the Tutorial page in your browser.  If you can't see your browser, make sure you bring to the front."
                      ),
                    u"",
                    _("Continue?"),
                ])):
            return
        try:
            fname_excel = MakeExampleExcel()
        except Exception as e:
            traceback.print_exc()
            Utils.MessageOK(
                self, u'{}\n\n{}\n\n{}'.format(
                    u'Problem creating Excel sheet.', e,
                    _('If the Excel file is open, please close it and try again'
                      )))
            return
        self.fileBrowse.SetValue(fname_excel)
        self.doUpdate(event)
        Utils.LaunchApplication(fname_excel)
        Utils.LaunchApplication(
            os.path.join(Utils.getHtmlDocFolder(), 'Tutorial.html'))

    def doChangeCallback(self, event):
        fname = event.GetString()
        if not fname:
            self.setUpdated(False)
            return
        if fname != self.fname:
            wx.CallAfter(self.doUpdate, fnameNew=fname)

    def setUpdated(self, updated=True):
        self.updated = updated
        for w in [self.stageList, self.saveAsExcelButton]:
            w.Enable(updated)
        if not updated:
            self.stageList.DeleteAllItems()
            self.notebook.DeleteAllPages()

    def updateStageList(self, registration=None, stages=None):
        self.stageList.DeleteAllItems()

        registration = (registration
                        or (Model.model and Model.model.registration) or None)
        if not registration:
            return
        stages = (stages or (Model.model and Model.model.stages) or [])

        def insert_stage_info(stage):
            idx = self.stageList.InsertItem(sys.maxint, stage.sheet_name)
            self.stageList.SetItem(idx, 1, unicode(len(stage)))
            if stage.errors:
                self.stageList.SetItem(
                    idx, 2, u'{}: {}'.format(
                        len(stage.errors),
                        u'  '.join(u'[{}]'.format(e) for e in stage.errors)))
            else:
                self.stageList.SetItem(
                    idx, 2,
                    u'                                                                '
                )

        insert_stage_info(registration)
        for stage in stages:
            insert_stage_info(stage)

        for col in xrange(3):
            self.stageList.SetColumnWidth(col, wx.LIST_AUTOSIZE)
        self.stageList.SetColumnWidth(1, 52)
        self.stageList.Refresh()

    def callbackUpdate(self, message):
        pass

    def doUpdate(self, event=None, fnameNew=None):
        try:
            self.fname = (fnameNew or event.GetString()
                          or self.fileBrowse.GetValue())
        except:
            self.fname = u''

        if not self.fname:
            Utils.MessageOK(
                self, _('Missing Excel file.  Please select an Excel file.'),
                _('Missing Excel File'))
            self.setUpdated(False)
            return

        if self.lastUpdateTime and (datetime.datetime.now() -
                                    self.lastUpdateTime).total_seconds() < 1.0:
            return

        try:
            with open(self.fname, 'rb') as f:
                pass
        except Exception as e:
            traceback.print_exc()
            Utils.MessageOK(
                self,
                u'{}:\n\n    {}\n\n{}'.format(_('Cannot Open Excel file'),
                                              self.fname, e),
                _('Cannot Open Excel File'))
            self.setUpdated(False)
            return

        self.filehistory.AddFileToHistory(self.fname)
        self.filehistory.Save(self.config)
        self.fileBrowse.SetValue(self.fname)

        wait = wx.BusyCursor()
        labelSave, backgroundColourSave = self.updateButton.GetLabel(
        ), self.updateButton.GetForegroundColour()

        try:
            Model.read(self.fname, callbackfunc=self.updateStageList)
        except Exception as e:
            traceback.print_exc()
            Utils.MessageOK(
                self, u'{}:\n\n    {}\n\n{}'.format(_('Excel File Error'),
                                                    self.fname, e),
                _('Excel File Error'))
            self.setUpdated(False)
            return

        Model.model.getGCs()
        self.setUpdated(True)

        self.updateStageList()
        StageRaceGCToGrid(self.notebook)

        self.lastUpdateTime = datetime.datetime.now()

    def getOutputExcelName(self):
        fname_base, fname_suffix = os.path.splitext(self.fname)
        fname_excel = '{}-{}{}'.format(fname_base, 'GC', '.xlsx')
        return fname_excel

    def saveAsExcel(self, event):
        fname_excel = self.getOutputExcelName()
        if os.path.isfile(fname_excel):
            if not Utils.MessageOKCancel(
                    self,
                    u'"{}"\n\n{}'.format(fname_excel,
                                         _('File exists.  Replace?')),
                    _('Output Excel File Exists'),
            ):
                return

        try:
            StageRaceGCToExcel(fname_excel, Model.model)
        except Exception as e:
            Utils.MessageOK(
                self,
                u'{}: "{}"\n\n{}\n\n"{}"'.format(
                    _("Write Failed"), e,
                    _("If you have this file open, close it and try again."),
                    fname_excel),
                _("Excel Write Failed."),
                iconMask=wx.ICON_ERROR,
            )
            return

        wait = wx.BusyCursor()
        Utils.LaunchApplication(fname_excel)
Esempio n. 7
0
class MainWin(wx.Frame):
    def __init__(self, parent, id=wx.ID_ANY, title='', size=(200, 200)):
        wx.Frame.__init__(self, parent, id, title, size=size)

        self.config = wx.Config(
            appName="CrossMgrAlien",
            vendorName="SmartCyclingSolutions",
            #style=wx.CONFIG_USE_LOCAL_FILE
        )

        self.SetBackgroundColour(wx.Colour(232, 232, 232))

        self.LightGreen = wx.Colour(153, 255, 153)
        self.LightRed = wx.Colour(255, 153, 153)

        font = self.GetFont()
        bigFont = wx.Font(font.GetPointSize() * 1.5, font.GetFamily(),
                          font.GetStyle(), wx.FONTWEIGHT_BOLD)
        titleFont = wx.Font(bigFont.GetPointSize() * 2.2, bigFont.GetFamily(),
                            bigFont.GetStyle(), bigFont.GetWeight())

        self.vbs = wx.BoxSizer(wx.VERTICAL)

        bs = wx.BoxSizer(wx.HORIZONTAL)

        self.reset = RoundButton(self, wx.ID_ANY, 'Reset', size=(80, 80))
        self.reset.SetBackgroundColour(wx.WHITE)
        self.reset.SetForegroundColour(wx.Colour(0, 128, 128))
        self.reset.SetFontToFitLabel(
        )  # Use the button's default font, but change the font size to fit the label.
        self.reset.Bind(wx.EVT_BUTTON, self.doReset)
        self.reset.Refresh()
        bs.Add(self.reset, border=8, flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.Add(setFont(titleFont,
                       wx.StaticText(self, wx.ID_ANY, 'CrossMgrAlien')),
               border=8,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.AddStretchSpacer()
        bitmap = wx.Bitmap(clipboard_xpm)
        self.copyToClipboard = wx.BitmapButton(self, wx.ID_ANY, bitmap)
        self.copyToClipboard.SetToolTip(
            wx.ToolTip('Copy Configuration and Logs to Clipboard...'))
        self.copyToClipboard.Bind(wx.EVT_BUTTON, self.doCopyToClipboard)
        bs.Add(self.copyToClipboard,
               border=32,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        self.tStart = datetime.datetime.now()
        bs.Add(setFont(
            bigFont,
            wx.StaticText(
                self, wx.ID_ANY,
                'Last Reset: {}'.format(self.tStart.strftime('%H:%M:%S')))),
               border=10,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        self.runningTime = setFont(bigFont,
                                   wx.StaticText(self, wx.ID_ANY, '00:00:00'))
        bs.Add(self.runningTime,
               border=20,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.Add(setFont(bigFont, wx.StaticText(self, wx.ID_ANY, ' / ')),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.time = setFont(bigFont, wx.StaticText(self, wx.ID_ANY,
                                                   '00:00:00'))
        bs.Add(self.time, flag=wx.ALIGN_CENTER_VERTICAL)

        self.vbs.Add(bs, flag=wx.ALL | wx.EXPAND, border=4)

        fgs = wx.FlexGridSizer(rows=0, cols=2, vgap=4, hgap=4)
        fgs.AddGrowableRow(1)
        fgs.AddGrowableCol(0)
        fgs.AddGrowableCol(1)
        fgs.SetFlexibleDirection(wx.BOTH)

        self.vbs.Add(fgs, flag=wx.EXPAND, proportion=5)

        #------------------------------------------------------------------------------------------------
        # Alien configuration.
        #
        gbs = wx.GridBagSizer(4, 4)
        fgs.Add(gbs, flag=wx.EXPAND | wx.ALL, border=4)

        iRow = 0
        hs = wx.BoxSizer(wx.HORIZONTAL)
        hs.Add(setFont(bigFont,
                       wx.StaticText(self, wx.ID_ANY, 'Alien Configuration:')),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.autoDetectButton = wx.Button(self, wx.ID_ANY, 'Auto Detect')
        self.autoDetectButton.Bind(wx.EVT_BUTTON, self.doAutoDetect)
        hs.Add(self.autoDetectButton,
               flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT,
               border=6)

        self.advancedButton = wx.Button(self, wx.ID_ANY, 'Advanced...')
        self.advancedButton.Bind(wx.EVT_BUTTON, self.doAdvanced)
        hs.Add(self.advancedButton, flag=wx.LEFT, border=6)

        gbs.Add(hs, pos=(iRow, 0), span=(1, 2), flag=wx.ALIGN_LEFT)
        iRow += 1

        gbs.Add(wx.StaticText(self, wx.ID_ANY, 'Antennas:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM)

        gs = wx.GridSizer(rows=0, cols=4, hgap=2, vgap=2)
        self.antennas = []
        for i in range(4):
            gs.Add(wx.StaticText(self, wx.ID_ANY, '{}'.format(i)),
                   flag=wx.ALIGN_CENTER)
        for i in range(4):
            cb = wx.CheckBox(self, wx.ID_ANY, '')
            if i < 2:
                cb.SetValue(True)
            cb.Bind(wx.EVT_CHECKBOX, lambda x: self.getAntennaStr())
            gs.Add(cb, flag=wx.ALIGN_CENTER)
            self.antennas.append(cb)

        gbs.Add(gs, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL)

        iRow += 1

        gbs.Add(wx.StaticText(self, wx.ID_ANY, 'Notify Address:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)

        hb = wx.BoxSizer(wx.HORIZONTAL)
        ips = Utils.GetAllIps()
        self.notifyHost = wx.Choice(self, choices=ips)
        hb.Add(self.notifyHost)
        hb.Add(wx.StaticText(self, label=' : {}'.format(NotifyPort)),
               flag=wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(hb, pos=(iRow, 1), span=(1, 1))

        iRow += 1
        self.listenForHeartbeat = wx.CheckBox(
            self,
            label='Listen for Alien Heartbeat on Port: {}'.format(
                HeartbeatPort),
            style=wx.ALIGN_LEFT)
        self.listenForHeartbeat.SetValue(True)
        gbs.Add(self.listenForHeartbeat, pos=(iRow, 0), span=(1, 2))

        iRow += 1
        gbs.Add(wx.StaticText(self, label='Alien Cmd Address:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        hb = wx.BoxSizer(wx.HORIZONTAL)
        self.cmdHost = IpAddrCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_TAB)

        hb.Add(self.cmdHost)
        hb.Add(wx.StaticText(self, label=' : '), flag=wx.ALIGN_CENTER_VERTICAL)
        self.cmdPort = intctrl.IntCtrl(self, size=(50, -1), min=0, max=999999)
        hb.Add(self.cmdPort, flag=wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(hb, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_LEFT)

        iRow += 1
        gbs.Add(wx.StaticText(self, label='Backup File:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT)
        self.backupFile = wx.StaticText(self, label='')
        gbs.Add(self.backupFile,
                pos=(iRow, 1),
                span=(1, 1),
                flag=wx.ALIGN_LEFT)

        #------------------------------------------------------------------------------------------------
        # CrossMgr configuration.
        #
        gbs = wx.GridBagSizer(4, 4)
        fgs.Add(gbs, flag=wx.EXPAND | wx.ALL, border=4)

        gbs.Add(setFont(bigFont,
                        wx.StaticText(self, label='CrossMgr Configuration:')),
                pos=(0, 0),
                span=(1, 2),
                flag=wx.ALIGN_LEFT)
        gbs.Add(wx.StaticText(self, label='CrossMgr Address:'),
                pos=(1, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)

        hb = wx.BoxSizer(wx.HORIZONTAL)
        self.crossMgrHost = IpAddrCtrl(self, style=wx.TE_PROCESS_TAB)
        hb.Add(self.crossMgrHost, flag=wx.ALIGN_LEFT)
        hb.Add(wx.StaticText(self, label=' : 53135'),
               flag=wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(hb, pos=(1, 1), span=(1, 1), flag=wx.ALIGN_LEFT)

        #------------------------------------------------------------------------------------------------
        # Add messages
        #
        self.alienMessagesText = wx.TextCtrl(self,
                                             style=wx.TE_READONLY
                                             | wx.TE_MULTILINE | wx.HSCROLL,
                                             size=(-1, 400))
        fgs.Add(self.alienMessagesText, flag=wx.EXPAND, proportion=2)
        self.alienMessages = MessageManager(self.alienMessagesText)

        self.crossMgrMessagesText = wx.TextCtrl(self,
                                                style=wx.TE_READONLY
                                                | wx.TE_MULTILINE | wx.HSCROLL,
                                                size=(-1, 400))
        fgs.Add(self.crossMgrMessagesText, flag=wx.EXPAND, proportion=2)
        self.crossMgrMessages = MessageManager(self.crossMgrMessagesText)
        self.fgs = fgs

        #------------------------------------------------------------------------------------------------
        # Create a timer to update the messages.
        #
        self.timer = wx.Timer()
        self.timer.Bind(wx.EVT_TIMER, self.updateMessages)
        self.timer.Start(1000, False)

        self.Bind(wx.EVT_CLOSE, self.onCloseWindow)

        self.readOptions()

        self.SetSizer(self.vbs)
        self.start()

    def start(self):
        self.dataQ = Queue()
        self.messageQ = Queue()
        self.shutdownQ = Queue(
        )  # Queue to tell the Alien monitor to shut down.

        self.alienProcess = Process(
            name='AlienProcess',
            target=AlienServer,
            args=(self.dataQ, self.messageQ, self.shutdownQ,
                  self.getNotifyHost(), NotifyPort, HeartbeatPort,
                  self.getAntennaStr(), self.listenForHeartbeat.GetValue(),
                  self.cmdHost.GetAddress(), self.cmdPort.GetValue()))
        self.alienProcess.daemon = True

        self.crossMgrProcess = Process(name='CrossMgrProcess',
                                       target=CrossMgrServer,
                                       args=(self.dataQ, self.messageQ,
                                             self.shutdownQ,
                                             self.getCrossMgrHost(),
                                             CrossMgrPort))
        self.crossMgrProcess.daemon = True

        self.alienProcess.start()
        self.crossMgrProcess.start()

    def doAdvanced(self, event):
        dlg = AdvancedSetup(self)
        dlg.ShowModal()
        dlg.Destroy()
        self.writeOptions()

    def shutdown(self):
        self.alienProcess = None
        self.crossMgrProcess = None
        self.messageQ = None
        self.dataQ = None
        self.shutdownQ = None

    def gracefulShutdown(self):
        # Shutdown the CrossMgr process by sending it a shutdown command.
        if self.shutdownQ:
            self.shutdownQ.put('shutdown')
            self.shutdownQ.put('shutdown')
            self.shutdownQ.put('shutdown')

        if self.dataQ:
            self.dataQ.put('shutdown')

        if self.crossMgrProcess:
            self.crossMgrProcess.join()
        if self.alienProcess:
            self.alienProcess.join()

        self.crossMgrProcess = None
        self.alienProcess = None

    def doReset(self, event, confirm=True):
        if confirm:
            dlg = wx.MessageDialog(self, 'Reset CrossMgrAlien Adapter?',
                                   'Confirm Reset',
                                   wx.OK | wx.CANCEL | wx.ICON_WARNING)
            ret = dlg.ShowModal()
            dlg.Destroy()
            if ret != wx.ID_OK:
                return

        self.reset.Enable(
            False)  # Prevent multiple clicks while shutting down.
        self.writeOptions()

        self.gracefulShutdown()

        self.alienMessages.clear()
        self.crossMgrMessages.clear()

        self.shutdown()
        self.reset.Enable(True)

        wx.CallAfter(self.start)

    def doAutoDetect(self, event):
        wx.BeginBusyCursor()
        self.gracefulShutdown()
        self.shutdown()
        alienHost, crossmgrHost = AutoDetect()
        wx.EndBusyCursor()

        if alienHost and crossmgrHost:
            self.setNotifyHost(
                crossmgrHost
            )  # Assumes CrossMgr is on the same computer as CrossMgrAlien.

            self.cmdHost.SetValue(alienHost)
            self.cmdPort.SetValue(str(DefaultAlienCmdPort))
            self.crossMgrHost.SetValue(crossmgrHost)

            self.listenForHeartbeat.SetValue(False)
        else:
            dlg = wx.MessageDialog(
                self,
                'Auto Detect Failed.\nCheck that reader has power and is connected to the router.',
                'Auto Detect Failed', wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()

        self.doReset(event, False)

    def onCloseWindow(self, event):
        wx.Exit()

    def doCopyToClipboard(self, event):
        cc = [
            'Configuration: CrossMgrAlien',
            '    NotifyHost:    {}'.format(self.getNotifyHost()),
            '    NotifyPort:    {}'.format(NotifyPort),
            '    RunningTime:   {}'.format(self.runningTime.GetLabel()),
            '    Time:          {}'.format(self.time.GetLabel()),
            '    BackupFile:    {}'.format(self.backupFile.GetLabel()),
            '',
            'Configuration: Alien:',
            '    ListenForAlienHeartbeat: {}'.format(
                'True' if self.listenForHeartbeat.GetValue() else 'False'),
            '    Antennas:      {}'.format(self.getAntennaStr()),
            '    HeartbeatPort: {}'.format(HeartbeatPort),
            '    AlienCmdHost:  {}'.format(self.cmdHost.GetAddress()),
            '    AlienCmdPort:  {}'.format(self.cmdPort.GetValue()),
            '',
            '    RepeatSeconds: {}'.format(Alien.RepeatSeconds),
            '',
            'Configuration: CrossMgr',
            '    CrossMgrHost:  {}'.format(self.getCrossMgrHost()),
            '    CrossMgrPort:  {}'.format(CrossMgrPort),
        ]
        cc.append('\nLog: Alien')
        log = self.alienMessagesText.GetValue()
        cc.extend(['    ' + line for line in log.split('\n')])

        cc.append('\nLog: CrossMgr')
        log = self.crossMgrMessagesText.GetValue()
        cc.extend(['    ' + line for line in log.split('\n')])

        cc.append('\nLog: Application\n')
        try:
            with open(redirectFileName, 'r') as fp:
                for line in fp:
                    cc.append(line)
        except:
            pass

        if wx.TheClipboard.Open():
            do = wx.TextDataObject()
            do.SetText('\n'.join(cc))
            wx.TheClipboard.SetData(do)
            wx.TheClipboard.Close()
            dlg = wx.MessageDialog(
                self, 'Configuration and Logs copied to the Clipboard.',
                'Copy to Clipboard Succeeded', wx.OK | wx.ICON_INFORMATION)
            ret = dlg.ShowModal()
            dlg.Destroy()
        else:
            # oops... something went wrong!
            wx.MessageBox("Unable to open the clipboard", "Error")

    def getNotifyHost(self):
        s = self.notifyHost.GetSelection()
        return self.notifyHost.GetString(s) if s != wx.NOT_FOUND else None

    def setNotifyHost(self, notifyHost):
        for i, s in enumerate(self.notifyHost.GetItems()):
            if s == notifyHost:
                self.notifyHost.SetSelection(i)
                return
        self.notifyHost.SetSelection(0)

    def getCrossMgrHost(self):
        return self.crossMgrHost.GetAddress()

    def getAntennaStr(self):
        s = []
        for i in range(4):
            if self.antennas[i].GetValue():
                s.append('%d' % i)
        if not s:
            # Ensure that at least one antenna is selected.
            self.antennas[0].SetValue(True)
            s.append('0')
        return ' '.join(s)

    def setAntennaStr(self, s):
        antennas = set(int(a) for a in s.split())
        for i in range(4):
            self.antennas[i].SetValue(i in antennas)

    def writeOptions(self):
        self.config.Write('CrossMgrHost', self.getCrossMgrHost())
        self.config.Write(
            'ListenForAlienHeartbeat',
            'True' if self.listenForHeartbeat.GetValue() else 'False')
        self.config.Write('AlienCmdAddr', self.cmdHost.GetAddress())
        self.config.Write('AlienCmdPort', str(self.cmdPort.GetValue()))
        self.config.Write('Antennas', self.getAntennaStr())
        self.config.Write('RepeatSeconds', '{}'.format(Alien.RepeatSeconds))
        self.config.Write('PlaySounds', '{}'.format(Utils.playBell))
        s = self.notifyHost.GetSelection()
        if s != wx.NOT_FOUND:
            self.config.Write('NotifyHost', self.notifyHost.GetString(s))
        self.config.Flush()

    def readOptions(self):
        self.crossMgrHost.SetValue(
            self.config.Read('CrossMgrHost', Utils.DEFAULT_HOST))
        self.listenForHeartbeat.SetValue(
            self.config.Read('ListenForAlienHeartbeat', 'True').upper()[:1] ==
            'T')
        Utils.playBell = (self.config.Read('PlaySounds',
                                           'True').upper()[:1] == 'T')
        self.cmdHost.SetValue(self.config.Read('AlienCmdAddr', '0.0.0.0'))
        self.cmdPort.SetValue(int(self.config.Read('AlienCmdPort', '0')))
        self.setAntennaStr(self.config.Read('Antennas', '0 1'))
        Alien.RepeatSeconds = int(
            self.config.Read('RepeatSeconds',
                             '{}'.format(Alien.RepeatSeconds)))
        notifyHost = self.config.Read('NotifyHost', Utils.DEFAULT_HOST)
        self.setNotifyHost(notifyHost)

    def updateMessages(self, event):
        tNow = datetime.datetime.now()
        running = int((tNow - self.tStart).total_seconds())
        self.runningTime.SetLabel('{:02d}:{:02d}:{:02d}'.format(
            running // (60 * 60), (running // 60) % 60, running % 60))
        self.time.SetLabel(tNow.strftime('%H:%M:%S'))

        if not self.messageQ:
            return

        while 1:
            try:
                d = self.messageQ.get(False)
            except Empty:
                break
            message = ' '.join(str(x) for x in d[1:])
            if d[0] == 'Alien':
                if 'state' in d:
                    self.alienMessages.messageList.SetBackgroundColour(
                        self.LightGreen if d[2] else self.LightRed)
                else:
                    self.alienMessages.write(message)
            elif d[0] == 'Alien2JChip':
                if 'state' in d:
                    self.crossMgrMessages.messageList.SetBackgroundColour(
                        self.LightGreen if d[2] else self.LightRed)
                else:
                    self.crossMgrMessages.write(message)
            elif d[0] == 'CmdHost':
                cmdHost, cmdPort = d[1].split(':')
                self.cmdHost.SetValue(cmdHost)
                self.cmdPort.SetValue(int(cmdPort))
            elif d[0] == 'BackupFile':
                self.backupFile.SetLabel(d[1])
Esempio n. 8
0
class MainWin(wx.Frame):
    def __init__(self, parent, id=wx.ID_ANY, title='', size=(200, 200)):
        wx.Frame.__init__(self, parent, id, title, size=size)

        self.SetBackgroundColour(wx.Colour(240, 240, 240))

        self.fname = None
        self.updated = False
        self.firstTime = True
        self.lastUpdateTime = None
        self.sources = []
        self.errors = []

        self.filehistory = wx.FileHistory(16)

        dataDir = Utils.getHomeDir()
        configFileName = os.path.join(dataDir, 'CallupSeedingMgr.cfg')
        self.config = wx.Config(appName="CallupSeedingMgr",
                                vendorName="SmartCyclingSolutions",
                                localFilename=configFileName)

        self.filehistory.Load(self.config)

        ID_MENU_UPDATE = wx.NewIdRef()
        ID_MENU_HELP = wx.NewIdRef()
        self.menuBar = wx.MenuBar(wx.MB_DOCKABLE)
        if 'WXMAC' in wx.Platform:
            self.appleMenu = self.menuBar.OSXGetAppleMenu()
            self.appleMenu.SetTitle("CallupSeedingMgr")

            self.appleMenu.Insert(0, wx.ID_ABOUT, "&About")

            self.Bind(wx.EVT_MENU, self.OnAboutBox, id=wx.ID_ABOUT)

            self.editMenu = wx.Menu()
            self.editMenu.Append(
                wx.MenuItem(self.editMenu, ID_MENU_UPDATE, "&Update"))

            self.Bind(wx.EVT_MENU, self.doUpdate, id=ID_MENU_UPDATE)
            self.menuBar.Append(self.editMenu, "&Edit")

            self.helpMenu = wx.Menu()
            self.helpMenu.Append(
                wx.MenuItem(self.helpMenu, ID_MENU_HELP, "&Help"))

            self.menuBar.Append(self.helpMenu, "&Help")
            self.Bind(wx.EVT_MENU, self.onTutorial, id=ID_MENU_HELP)

        else:
            self.fileMenu = wx.Menu()
            self.fileMenu.Append(
                wx.MenuItem(self.fileMenu, ID_MENU_UPDATE, "&Update"))
            self.fileMenu.Append(wx.ID_EXIT)
            self.Bind(wx.EVT_MENU, self.doUpdate, id=ID_MENU_UPDATE)
            self.Bind(wx.EVT_MENU, self.onClose, id=wx.ID_EXIT)
            self.menuBar.Append(self.fileMenu, "&File")
            self.helpMenu = wx.Menu()
            self.helpMenu.Insert(0, wx.ID_ABOUT, "&About")
            self.helpMenu.Insert(1, ID_MENU_HELP, "&Help")
            self.Bind(wx.EVT_MENU, self.OnAboutBox, id=wx.ID_ABOUT)
            self.Bind(wx.EVT_MENU, self.onTutorial, id=ID_MENU_HELP)
            self.menuBar.Append(self.helpMenu, "&Help")

        self.SetMenuBar(self.menuBar)

        inputBox = wx.StaticBox(self, label=_('Input'))
        inputBoxSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
        self.fileBrowse = filebrowse.FileBrowseButtonWithHistory(
            self,
            labelText=_('Excel File'),
            buttonText=('Browse...'),
            startDirectory=os.path.expanduser('~'),
            fileMask=
            'Excel Spreadsheet (*.xlsx; *.xlsm; *.xls)|*.xlsx; *.xlsml; *.xls',
            size=(400, -1),
            history=lambda: [
                self.filehistory.GetHistoryFile(i)
                for i in range(self.filehistory.GetCount())
            ],
            changeCallback=self.doChangeCallback,
        )
        inputBoxSizer.Add(self.fileBrowse,
                          0,
                          flag=wx.EXPAND | wx.ALL,
                          border=4)

        horizontalControlSizer = wx.BoxSizer(wx.HORIZONTAL)

        #-------------------------------------------------------------------------------------------
        verticalControlSizer = wx.BoxSizer(wx.VERTICAL)

        self.useUciIdCB = wx.CheckBox(self,
                                      label=_("Use UCI ID (assume no errors)"))
        self.useUciIdCB.SetValue(True)
        verticalControlSizer.Add(self.useUciIdCB, flag=wx.ALL, border=4)

        self.useLicenseCB = wx.CheckBox(
            self, label=_("Use License (assume no errors)"))
        self.useLicenseCB.SetValue(True)
        verticalControlSizer.Add(self.useLicenseCB, flag=wx.ALL, border=4)

        self.soundalikeCB = wx.CheckBox(
            self, label=_("Match misspelled names with Sound-Alike"))
        self.soundalikeCB.SetValue(True)
        verticalControlSizer.Add(self.soundalikeCB, flag=wx.ALL, border=4)

        self.callupSeedingRB = wx.RadioBox(
            self,
            style=wx.RA_SPECIFY_COLS,
            majorDimension=1,
            label=_("Sequence"),
            choices=[
                _("Callups: Highest ranked FIRST (Cyclo-cross, MTB)"),
                _("Seeding: Highest ranked LAST (Time Trials)"),
            ],
        )
        verticalControlSizer.Add(self.callupSeedingRB,
                                 flag=wx.EXPAND | wx.ALL,
                                 border=4)
        verticalControlSizer.Add(wx.StaticText(
            self,
            label=_('Riders with no criteria will be sequenced randomly.')),
                                 flag=wx.ALL,
                                 border=4)

        horizontalControlSizer.Add(verticalControlSizer, flag=wx.EXPAND)

        self.updateButton = RoundButton(self, size=(96, 96))
        self.updateButton.SetLabel(_('Update'))
        self.updateButton.SetFontToFitLabel()
        self.updateButton.SetForegroundColour(wx.Colour(0, 100, 0))
        self.updateButton.Bind(wx.EVT_BUTTON, self.doUpdate)
        horizontalControlSizer.Add(self.updateButton, flag=wx.ALL, border=4)

        horizontalControlSizer.AddSpacer(48)

        vs = wx.BoxSizer(wx.VERTICAL)
        self.tutorialButton = wx.Button(self, label=_('Help/Tutorial...'))
        self.tutorialButton.Bind(wx.EVT_BUTTON, self.onTutorial)
        vs.Add(self.tutorialButton, flag=wx.ALL, border=4)
        branding = wx.adv.HyperlinkCtrl(
            self,
            id=wx.ID_ANY,
            label=u"Powered by CrossMgr",
            url=u"http://www.sites.google.com/site/crossmgrsoftware/")
        vs.Add(branding, flag=wx.ALL, border=4)
        horizontalControlSizer.Add(vs)

        inputBoxSizer.Add(horizontalControlSizer, flag=wx.EXPAND)

        self.sourceList = wx.ListCtrl(self, style=wx.LC_REPORT, size=(-1, 100))
        inputBoxSizer.Add(self.sourceList, flag=wx.ALL | wx.EXPAND, border=4)
        self.sourceList.InsertColumn(0, "Sheet")
        self.sourceList.InsertColumn(1, "Data Columns and Derived Information")
        self.sourceList.InsertColumn(2, "Key Fields")
        self.sourceList.InsertColumn(3, "Rows", wx.LIST_FORMAT_RIGHT)
        self.sourceList.InsertColumn(4, "Errors/Warnings",
                                     wx.LIST_FORMAT_RIGHT)
        self.sourceList.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)

        instructions = [
            _('Drag-and-Drop the row numbers on the Left to change the sequence.'
              ),
            _('Click on Points or Position cells for details.'),
            _('Orange Cells: Multiple Matches.  Click on the cell to see what you need to fix in the spreadsheet.'
              ),
            _('Yellow Cells: Soundalike Matches.  Click on the cell to validate if the names are matched correctly.'
              ),
        ]

        self.grid = ReorderableGrid(self)
        self.grid.CreateGrid(0, 1)
        self.grid.SetColLabelValue(0, u'')
        self.grid.EnableDragRowSize(False)
        self.grid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.onGridCellClick)
        #self.grid.Bind( wx.EVT_MOTION, self.onMouseOver )

        outputBox = wx.StaticBox(self, label=_('Output'))
        outputBoxSizer = wx.StaticBoxSizer(outputBox, wx.VERTICAL)

        hs = wx.BoxSizer(wx.HORIZONTAL)
        self.excludeUnrankedCB = wx.CheckBox(
            self, label=_("Exclude riders with no ranking info"))
        hs.Add(self.excludeUnrankedCB,
               flag=wx.ALL | wx.ALIGN_CENTRE_VERTICAL,
               border=4)
        hs.AddSpacer(24)
        hs.Add(wx.StaticText(self, label=_("Output:")),
               flag=wx.ALL | wx.ALIGN_CENTRE_VERTICAL,
               border=4)
        self.topRiders = wx.Choice(self,
                                   choices=[
                                       _('All Riders'),
                                       _('Top 5'),
                                       _('Top 10'),
                                       _('Top 15'),
                                       _('Top 20'),
                                       _('Top 25')
                                   ])
        self.topRiders.SetSelection(0)
        hs.Add(self.topRiders, flag=wx.ALIGN_CENTRE_VERTICAL)

        self.saveAsExcel = wx.Button(self, label=_('Save as Excel...'))
        self.saveAsExcel.Bind(wx.EVT_BUTTON, self.doSaveAsExcel)
        hs.AddSpacer(48)
        hs.Add(self.saveAsExcel, flag=wx.ALL, border=4)

        outputBoxSizer.Add(hs)

        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(inputBoxSizer, flag=wx.EXPAND | wx.ALL, border=4)
        for i, instruction in enumerate(instructions):
            flag = wx.LEFT | wx.RIGHT
            if i == len(instructions) - 1:
                flag |= wx.BOTTOM
            mainSizer.Add(wx.StaticText(self, label=instruction),
                          flag=flag,
                          border=8)
        mainSizer.Add(self.grid, 1, flag=wx.EXPAND | wx.ALL, border=4)
        mainSizer.Add(outputBoxSizer, flag=wx.EXPAND | wx.ALL, border=4)

        self.SetSizer(mainSizer)

    def onClose(self, event):
        wx.Exit()

    def OnAboutBox(self, e):
        description = """CallupSeedingMgr is an Seeding Manager for CrossMgr
	"""

        licence = """CallupSeedingMgr free software; you can redistribute 
	it and/or modify it under the terms of the GNU General Public License as 
	published by the Free Software Foundation; either version 2 of the License, 
	or (at your option) any later version.

	CallupSeedingMgr is distributed in the hope that it will be useful, 
	but WITHOUT ANY WARRANTY; without even the implied warranty of 
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
	See the GNU General Public License for more details. You should have 
	received a copy of the GNU General Public License along with File Hunter; 
	if not, write to the Free Software Foundation, Inc., 59 Temple Place, 
	Suite 330, Boston, MA  02111-1307  USA"""

        info = wx.adv.AboutDialogInfo()

        crossMgrPng = Utils.getImageFolder() + '/CallupSeedingMgr.png'
        info.SetIcon(wx.Icon(crossMgrPng, wx.BITMAP_TYPE_PNG))
        info.SetName('CallupSeedingMgr')
        info.SetVersion(AppVerName.split(' ')[1])
        info.SetDescription(description)
        info.SetCopyright('(C) 2020 Edward Sitarski')
        info.SetWebSite('http://www.sites.google.com/site/crossmgrsoftware/')
        info.SetLicence(licence)

        wx.adv.AboutBox(info, self)

    def onTutorial(self, event):
        if not Utils.MessageOKCancel(
                self, u"\n".join([
                    _("Launch the CallupSeedingMgr Tutorial."),
                    _("This open a sample Excel input file created into your home folder."
                      ),
                    _("This data in this sheet is made-up, although it does include some current rider's names."
                      ),
                    u"",
                    _("It will also open the Tutorial page in your browser.  If you can't see your browser, make sure you bring to the front."
                      ),
                    u"",
                    _("Continue?"),
                ])):
            return
        try:
            fname_excel = MakeExampleExcel()
            self.fileBrowse.SetValue(fname_excel)
        except Exception as e:
            Utils.MessageOK(
                self, u'{}\n\n{}\n\n{}'.format(
                    u'Problem creating Excel sheet.', e,
                    _('If the Excel file is open, please close it and try again'
                      )))
        self.doUpdate(event)
        Utils.LaunchApplication([
            fname_excel,
            os.path.join(Utils.getHtmlDocFolder(), 'Tutorial.html')
        ])

    def onItemSelected(self, event):
        currentItem = event.GetIndex()
        errors = self.errors[(currentItem + len(self.errors) - 1) %
                             len(self.errors)]
        if not errors:
            return
        dialog = ErrorDialog(self, errors=errors)
        dialog.ShowModal()
        dialog.Destroy()
        event.Skip()

    def onMouseOver(self, event):
        '''
		Method to calculate where the mouse is pointing and
		then set the tooltip dynamically.
		'''

        # Use CalcUnscrolledPosition() to get the mouse position
        # within the
        # entire grid including what's offscreen
        x, y = self.grid_area.CalcUnscrolledPosition(event.GetX(),
                                                     event.GetY())

        coords = self.grid_area.XYToCell(x, y)
        # you only need these if you need the value in the cell
        row = coords[0]
        col = coords[1]
        iRecord = int(
            self.grid.GetCellValue(row,
                                   self.grid.GetNumberCols() - 1))
        iSource = self.grid.GetNumberCols() - col
        try:
            v = self.callup_results[iRecord][-iSource + 1]
        except IndexError:
            event.Skip()
            return

        try:
            status = v.get_status()
        except AttributeError:
            event.Skip()
            return

        if status == v.NoMatch:
            event.Skip()
            return

        message = u'{}\n\n{}'.format(
            v.get_message(),
            _('Make changes in the Spreadsheet (if necessary), then press "Update" to refresh the screen.'
              ),
        )
        event.GetEventObject().SetToolTipString(message)
        event.Skip()

    def onGridCellClick(self, event):
        row = event.GetRow()
        col = event.GetCol()
        iRecord = int(
            self.grid.GetCellValue(row,
                                   self.grid.GetNumberCols() - 1))
        iSource = self.grid.GetNumberCols() - col
        try:
            v = self.callup_results[iRecord][-iSource + 1]
        except IndexError:
            return

        try:
            status = v.get_status()
        except AttributeError:
            return

        if status == v.NoMatch:
            return

        message = u'{}\n\n{}'.format(
            v.get_message(),
            _('Make changes in the Spreadsheet (if necessary), then press "Update" to refresh the screen.'
              ),
        )
        Utils.MessageOK(
            self, message,
            _('Soundalike Match')
            if status == v.SuccessSoundalike else _('Multiple Matches')
            if status == v.MultiMatch else _('Match Success'))

    def getTopRiders(self):
        i = self.topRiders.GetSelection()
        return 5 * i if i > 0 else 999999

    def getIsCallup(self):
        return self.callupSeedingRB.GetSelection() == 0

    def getIsSoundalike(self):
        return self.soundalikeCB.GetValue()

    def getUseUciId(self):
        return self.useUciIdCB.GetValue()

    def getUseLicense(self):
        return self.useLicenseCB.GetValue()

    def getOutputExcelName(self):
        fname = os.path.abspath(self.fname)
        dirname, basename = os.path.dirname(fname), os.path.basename(fname)
        fname_base, fname_suffix = os.path.splitext(basename)
        dirchild = 'CallupsOutput' if self.getIsCallup() else 'SeedingOutput'
        try:
            os.makedirs(os.path.join(dirname, dirchild))
        except Exception as e:
            pass
        fname_excel = os.path.join(
            dirname, dirchild,
            '{}{}{}'.format(fname_base,
                            '_Callups' if self.getIsCallup() else '_Seeding',
                            '.xlsx'))
        return fname_excel

    def doChangeCallback(self, event):
        fname = event.GetString()
        if not fname:
            self.setUpdated(False)
            return
        if fname != self.fname:
            wx.CallAfter(self.doUpdate, fnameNew=fname)

    def setUpdated(self, updated=True):
        self.updated = updated
        for w in [self.sourceList, self.grid, self.saveAsExcel]:
            w.Enable(updated)
        if not updated:
            self.sourceList.DeleteAllItems()
            Utils.DeleteAllGridRows(self.grid)

    def updateSourceList(self, sources=None, errors=None):
        self.sourceList.DeleteAllItems()
        sources = (sources or self.sources)
        errors = (errors or self.errors)
        if not sources:
            return

        def insert_source_info(source, errors, add_value_field=True):
            idx = self.sourceList.InsertItem(999999, source.sheet_name)
            fields = source.get_ordered_fields()
            if add_value_field and source.get_cmp_policy_field():
                fields = [source.get_cmp_policy_field()] + list(fields)
            self.sourceList.SetItem(idx, 1,
                                    u', '.join(make_title(f) for f in fields))
            match_fields = source.get_match_fields(
                sources[-1]) if source != sources[-1] else []
            self.sourceList.SetItem(
                idx, 2, u', '.join(make_title(f) for f in match_fields))
            self.sourceList.SetItem(idx, 3, u'{}'.format(len(source.results)))
            self.sourceList.SetItem(idx, 4, u'{}'.format(len(errors)))

        insert_source_info(sources[-1], errors[-1], False)
        for i, source in enumerate(sources[:-1]):
            insert_source_info(source, errors[i])

        for col in range(3):
            self.sourceList.SetColumnWidth(col, wx.LIST_AUTOSIZE)
        self.sourceList.SetColumnWidth(3, 52)
        self.sourceList.Refresh()

    def callbackUpdate(self, message):
        pass

    def doUpdate(self, event=None, fnameNew=None):
        try:
            self.fname = (fnameNew or event.GetString()
                          or self.fileBrowse.GetValue())
        except:
            self.fname = u''

        if not self.fname:
            Utils.MessageOK(
                self, _('Missing Excel file.  Please select an Excel file.'),
                _('Missing Excel File'))
            self.setUpdated(False)
            return

        if self.lastUpdateTime and (datetime.datetime.now() -
                                    self.lastUpdateTime).total_seconds() < 1.0:
            return

        try:
            with open(self.fname, 'rb') as f:
                pass
        except Exception as e:
            Utils.MessageOK(
                self,
                u'{}:\n\n    {}\n\n{}'.format(_('Cannot Open Excel file'),
                                              self.fname, e),
                _('Cannot Open Excel File'))
            self.setUpdated(False)
            return

        self.filehistory.AddFileToHistory(self.fname)
        self.filehistory.Save(self.config)

        wait = wx.BusyCursor()
        labelSave, backgroundColourSave = self.updateButton.GetLabel(
        ), self.updateButton.GetForegroundColour()

        try:
            self.registration_headers, self.callup_headers, self.callup_results, self.sources, self.errors = GetCallups(
                self.fname,
                soundalike=self.getIsSoundalike(),
                useUciId=self.getUseUciId(),
                useLicense=self.getUseLicense(),
                callbackfunc=self.updateSourceList,
                callbackupdate=self.callbackUpdate,
            )
        except Exception as e:
            traceback.print_exc()
            Utils.MessageOK(
                self, u'{}:\n\n    {}\n\n{}'.format(_('Excel File Error'),
                                                    self.fname, e),
                _('Excel File Error'))
            self.setUpdated(False)
            return

        self.setUpdated(True)

        self.updateSourceList()

        CallupResultsToGrid(
            self.grid,
            self.registration_headers,
            self.callup_headers,
            self.callup_results,
            is_callup=(self.callupSeedingRB.GetSelection() == 0),
            top_riders=self.getTopRiders(),
            exclude_unranked=self.excludeUnrankedCB.GetValue(),
        )
        self.GetSizer().Layout()
        self.lastUpdateTime = datetime.datetime.now()

    def doSaveAsExcel(self, event):
        if self.grid.GetNumberRows() == 0:
            return

        fname_excel = self.getOutputExcelName()
        if os.path.isfile(fname_excel):
            if not Utils.MessageOKCancel(
                    self,
                    u'"{}"\n\n{}'.format(fname_excel,
                                         _('File exists.  Replace?')),
                    _('Output Excel File Exists'),
            ):
                return

        user_sequence = [
            int(self.grid.GetCellValue(row,
                                       self.grid.GetNumberCols() - 1))
            for row in range(self.grid.GetNumberRows())
        ]
        user_callup_results = [self.callup_results[i] for i in user_sequence]

        try:
            CallupResultsToExcel(
                fname_excel,
                self.registration_headers,
                self.callup_headers,
                user_callup_results,
                is_callup=self.getIsCallup(),
                top_riders=self.getTopRiders(),
                exclude_unranked=self.excludeUnrankedCB.GetValue(),
            )
        except Exception as e:
            traceback.print_exc()
            Utils.MessageOK(
                self,
                u'{}: "{}"\n\n{}\n\n"{}"'.format(
                    _("Write Failed"), e,
                    _("If you have this file open, close it and try again."),
                    fname_excel),
                _("Excel Write Failed."),
                iconMask=wx.ICON_ERROR,
            )
            return

        webbrowser.open(fname_excel, new=2, autoraise=True)
Esempio n. 9
0
class MainWin(wx.Frame):
    def __init__(self, parent, id=wx.ID_ANY, title='', size=(200, 200)):
        wx.Frame.__init__(self, parent, id, title, size=size)

        dataDir = Utils.getHomeDir()
        configFileName = os.path.join(dataDir, 'CrossMgrAlien.cfg')
        self.config = wx.Config(appName="CrossMgrAlien",
                                vendorName="SmartCyclingSolutions",
                                localFilename=configFileName)

        ID_MENU_ADVANCECONFIG = wx.NewIdRef()
        ID_MENU_COPYLOGS = wx.NewIdRef()
        ID_MENU_AUTODETECT = wx.NewIdRef()
        self.menuBar = wx.MenuBar(wx.MB_DOCKABLE)
        if 'WXMAC' in wx.Platform:
            self.appleMenu = self.menuBar.OSXGetAppleMenu()
            self.appleMenu.SetTitle("CrossMgrAlien")

            self.appleMenu.Insert(0, wx.ID_ABOUT, "&About")

            self.Bind(wx.EVT_MENU, self.OnAboutBox, id=wx.ID_ABOUT)

            self.editMenu = wx.Menu()
            self.editMenu.Append(
                wx.MenuItem(self.editMenu, ID_MENU_ADVANCECONFIG,
                            "A&dvanced Configuration"))
            self.editMenu.Append(
                wx.MenuItem(self.editMenu, ID_MENU_COPYLOGS,
                            "&Copy Logs to Clipboard"))
            self.editMenu.Append(
                wx.MenuItem(self.editMenu, ID_MENU_AUTODETECT,
                            "&Autodetect Reader"))

            self.Bind(wx.EVT_MENU, self.doAdvanced, id=ID_MENU_ADVANCECONFIG)
            self.Bind(wx.EVT_MENU, self.doCopyToClipboard, id=ID_MENU_COPYLOGS)
            self.Bind(wx.EVT_MENU, self.doAutoDetect, id=ID_MENU_AUTODETECT)
            self.menuBar.Append(self.editMenu, "&Edit")

        else:
            self.fileMenu = wx.Menu()
            self.fileMenu.Append(
                wx.MenuItem(self.fileMenu, ID_MENU_ADVANCECONFIG,
                            "A&dvanced Configuration"))
            self.fileMenu.Append(
                wx.MenuItem(self.fileMenu, ID_MENU_COPYLOGS,
                            "&Copy Logs to Clipboard"))
            self.fileMenu.Append(
                wx.MenuItem(self.fileMenu, ID_MENU_AUTODETECT,
                            "&Autodetect Reader"))
            self.fileMenu.Append(wx.ID_EXIT)
            self.Bind(wx.EVT_MENU, self.doAdvanced, id=ID_MENU_ADVANCECONFIG)
            self.Bind(wx.EVT_MENU, self.doCopyToClipboard, id=ID_MENU_COPYLOGS)
            self.Bind(wx.EVT_MENU, self.doAutoDetect, id=ID_MENU_AUTODETECT)
            self.Bind(wx.EVT_MENU, self.onCloseWindow, id=wx.ID_EXIT)
            self.menuBar.Append(self.fileMenu, "&File")
            self.helpMenu = wx.Menu()
            self.helpMenu.Insert(0, wx.ID_ABOUT, "&About")
            self.Bind(wx.EVT_MENU, self.OnAboutBox, id=wx.ID_ABOUT)
            self.menuBar.Append(self.helpMenu, "&Help")

        self.SetMenuBar(self.menuBar)
        self.SetBackgroundColour(wx.Colour(232, 232, 232))

        self.LightGreen = wx.Colour(153, 255, 153)
        self.LightRed = wx.Colour(255, 153, 153)

        font = self.GetFont()
        bigFont = wx.Font(font.GetPointSize() * 1.5, font.GetFamily(),
                          font.GetStyle(), wx.FONTWEIGHT_BOLD)
        titleFont = wx.Font(bigFont.GetPointSize() * 2.2, bigFont.GetFamily(),
                            bigFont.GetStyle(), bigFont.GetWeight())

        self.vbs = wx.BoxSizer(wx.VERTICAL)

        bs = wx.BoxSizer(wx.HORIZONTAL)

        self.reset = RoundButton(self, wx.ID_ANY, 'Reset', size=(80, 80))
        self.reset.SetBackgroundColour(wx.WHITE)
        self.reset.SetForegroundColour(wx.Colour(0, 128, 128))
        self.reset.SetFontToFitLabel(
        )  # Use the button's default font, but change the font size to fit the label.
        self.reset.Bind(wx.EVT_BUTTON, self.doReset)
        self.reset.Refresh()
        bs.Add(self.reset, border=8, flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.Add(setFont(titleFont,
                       wx.StaticText(self, wx.ID_ANY, 'CrossMgrAlien')),
               border=8,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.AddStretchSpacer()
        bitmap = wx.Bitmap(clipboard_xpm)
        self.copyToClipboard = wx.BitmapButton(self, wx.ID_ANY, bitmap)
        self.copyToClipboard.SetToolTip(
            wx.ToolTip('Copy Configuration and Logs to Clipboard...'))
        self.copyToClipboard.Bind(wx.EVT_BUTTON, self.doCopyToClipboard)
        bs.Add(self.copyToClipboard,
               border=32,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        self.tStart = datetime.datetime.now()
        bs.Add(setFont(
            bigFont,
            wx.StaticText(
                self, wx.ID_ANY,
                'Last Reset: {}'.format(self.tStart.strftime('%H:%M:%S')))),
               border=10,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        self.runningTime = setFont(bigFont,
                                   wx.StaticText(self, wx.ID_ANY, '00:00:00'))
        bs.Add(self.runningTime,
               border=20,
               flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
        bs.Add(setFont(bigFont, wx.StaticText(self, wx.ID_ANY, ' / ')),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.time = setFont(bigFont, wx.StaticText(self, wx.ID_ANY,
                                                   '00:00:00'))
        bs.Add(self.time, flag=wx.ALIGN_CENTER_VERTICAL)

        self.vbs.Add(bs, flag=wx.ALL | wx.EXPAND, border=4)

        fgs = wx.FlexGridSizer(rows=0, cols=2, vgap=4, hgap=4)
        fgs.AddGrowableRow(1)
        fgs.AddGrowableCol(0)
        fgs.AddGrowableCol(1)
        fgs.SetFlexibleDirection(wx.BOTH)

        self.vbs.Add(fgs, flag=wx.EXPAND, proportion=5)

        #------------------------------------------------------------------------------------------------
        # Alien configuration.
        #
        gbs = wx.GridBagSizer(4, 4)
        fgs.Add(gbs, flag=wx.EXPAND | wx.ALL, border=4)

        iRow = 0
        hs = wx.BoxSizer(wx.HORIZONTAL)
        hs.Add(setFont(bigFont,
                       wx.StaticText(self, wx.ID_ANY, 'Alien Configuration:')),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.autoDetectButton = wx.Button(self, wx.ID_ANY, 'Auto Detect')
        self.autoDetectButton.Bind(wx.EVT_BUTTON, self.doAutoDetect)
        hs.Add(self.autoDetectButton,
               flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT,
               border=6)

        self.advancedButton = wx.Button(self, wx.ID_ANY, 'Advanced...')
        self.advancedButton.Bind(wx.EVT_BUTTON, self.doAdvanced)
        hs.Add(self.advancedButton, flag=wx.LEFT, border=6)

        gbs.Add(hs, pos=(iRow, 0), span=(1, 2), flag=wx.ALIGN_LEFT)
        iRow += 1

        gbs.Add(wx.StaticText(self, wx.ID_ANY, 'Antennas:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM)

        gs = wx.GridSizer(rows=0, cols=4, hgap=2, vgap=2)
        self.antennas = []
        for i in range(4):
            gs.Add(wx.StaticText(self, wx.ID_ANY, '{}'.format(i)),
                   flag=wx.ALIGN_CENTER)
        for i in range(4):
            cb = wx.CheckBox(self, wx.ID_ANY, '')
            if i < 2:
                cb.SetValue(True)
            cb.Bind(wx.EVT_CHECKBOX, lambda x: self.getAntennaStr())
            gs.Add(cb, flag=wx.ALIGN_CENTER)
            self.antennas.append(cb)

        gbs.Add(gs, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL)

        iRow += 1

        gbs.Add(wx.StaticText(self, wx.ID_ANY, 'Notify Address:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)

        hb = wx.BoxSizer(wx.HORIZONTAL)
        ips = Utils.GetAllIps()
        self.notifyHost = wx.Choice(self, choices=ips)
        hb.Add(self.notifyHost)
        hb.Add(wx.StaticText(self, label=' : {}'.format(NotifyPort)),
               flag=wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(hb, pos=(iRow, 1), span=(1, 1))

        iRow += 1
        self.listenForHeartbeat = wx.CheckBox(
            self,
            label='Listen for Alien Heartbeat on Port: {}'.format(
                HeartbeatPort),
            style=wx.ALIGN_LEFT)
        self.listenForHeartbeat.SetValue(True)
        gbs.Add(self.listenForHeartbeat, pos=(iRow, 0), span=(1, 2))

        iRow += 1
        gbs.Add(wx.StaticText(self, label='Alien Cmd Address:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        hb = wx.BoxSizer(wx.HORIZONTAL)
        self.cmdHost = IpAddrCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_TAB)

        hb.Add(self.cmdHost)
        hb.Add(wx.StaticText(self, label=' : '), flag=wx.ALIGN_CENTER_VERTICAL)
        self.cmdPort = intctrl.IntCtrl(self, size=(50, -1), min=0, max=999999)
        hb.Add(self.cmdPort, flag=wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(hb, pos=(iRow, 1), span=(1, 1), flag=wx.ALIGN_LEFT)

        iRow += 1
        gbs.Add(wx.StaticText(self, label='Backup File:'),
                pos=(iRow, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT)
        self.backupFile = wx.StaticText(self, label='')
        gbs.Add(self.backupFile,
                pos=(iRow, 1),
                span=(1, 1),
                flag=wx.ALIGN_LEFT)

        #------------------------------------------------------------------------------------------------
        # CrossMgr configuration.
        #
        gbs = wx.GridBagSizer(4, 4)
        fgs.Add(gbs, flag=wx.EXPAND | wx.ALL, border=4)

        gbs.Add(setFont(bigFont,
                        wx.StaticText(self, label='CrossMgr Configuration:')),
                pos=(0, 0),
                span=(1, 2),
                flag=wx.ALIGN_LEFT)
        gbs.Add(wx.StaticText(self, label='CrossMgr Address:'),
                pos=(1, 0),
                span=(1, 1),
                flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)

        hb = wx.BoxSizer(wx.HORIZONTAL)
        self.crossMgrHost = IpAddrCtrl(self, style=wx.TE_PROCESS_TAB)
        hb.Add(self.crossMgrHost, flag=wx.ALIGN_LEFT)
        hb.Add(wx.StaticText(self, label=' : 53135'),
               flag=wx.ALIGN_CENTER_VERTICAL)
        gbs.Add(hb, pos=(1, 1), span=(1, 1), flag=wx.ALIGN_LEFT)

        #------------------------------------------------------------------------------------------------
        # Add messages
        #
        self.alienMessagesText = wx.TextCtrl(self,
                                             style=wx.TE_READONLY
                                             | wx.TE_MULTILINE | wx.HSCROLL,
                                             size=(-1, 400))
        fgs.Add(self.alienMessagesText, flag=wx.EXPAND, proportion=2)
        self.alienMessages = MessageManager(self.alienMessagesText)

        self.crossMgrMessagesText = wx.TextCtrl(self,
                                                style=wx.TE_READONLY
                                                | wx.TE_MULTILINE | wx.HSCROLL,
                                                size=(-1, 400))
        fgs.Add(self.crossMgrMessagesText, flag=wx.EXPAND, proportion=2)
        self.crossMgrMessages = MessageManager(self.crossMgrMessagesText)
        self.fgs = fgs

        #------------------------------------------------------------------------------------------------
        # Create a timer to update the messages.
        #
        self.timer = wx.Timer()
        self.timer.Bind(wx.EVT_TIMER, self.updateMessages)
        self.timer.Start(1000, False)

        self.Bind(wx.EVT_CLOSE, self.onCloseWindow)

        self.readOptions()

        self.SetSizer(self.vbs)
        self.start()

    def OnAboutBox(self, e):
        description = """CrossMgrAlien is an Impinj interface to CrossMgr
	"""

        licence = """CrossMgrAlien is free software; you can redistribute 
	it and/or modify it under the terms of the GNU General Public License as 
	published by the Free Software Foundation; either version 2 of the License, 
	or (at your option) any later version.

	CrossMgrImpinj is distributed in the hope that it will be useful, 
	but WITHOUT ANY WARRANTY; without even the implied warranty of 
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
	See the GNU General Public License for more details. You should have 
	received a copy of the GNU General Public License along with File Hunter; 
	if not, write to the Free Software Foundation, Inc., 59 Temple Place, 
	Suite 330, Boston, MA  02111-1307  USA"""

        info = wx.adv.AboutDialogInfo()

        crossMgrPng = Utils.getImageFolder() + '/CrossMgrAlien.png'
        info.SetIcon(wx.Icon(crossMgrPng, wx.BITMAP_TYPE_PNG))
        info.SetName('CrossMgrAlien')
        info.SetVersion(AppVerName.split(' ')[1])
        info.SetDescription(description)
        info.SetCopyright('(C) 2020 Edward Sitarski')
        info.SetWebSite('http://www.sites.google.com/site/crossmgrsoftware/')
        info.SetLicence(licence)

        wx.adv.AboutBox(info, self)

    def start(self):
        self.dataQ = Queue()
        self.messageQ = Queue()
        self.shutdownQ = Queue(
        )  # Queue to tell the Alien monitor to shut down.

        self.alienProcess = Process(
            name='AlienProcess',
            target=AlienServer,
            args=(self.dataQ, self.messageQ, self.shutdownQ,
                  self.getNotifyHost(), NotifyPort, HeartbeatPort,
                  self.getAntennaStr(), self.listenForHeartbeat.GetValue(),
                  self.cmdHost.GetAddress(), self.cmdPort.GetValue()))
        self.alienProcess.daemon = True

        self.crossMgrProcess = Process(name='CrossMgrProcess',
                                       target=CrossMgrServer,
                                       args=(self.dataQ, self.messageQ,
                                             self.shutdownQ,
                                             self.getCrossMgrHost(),
                                             CrossMgrPort))
        self.crossMgrProcess.daemon = True

        self.alienProcess.start()
        self.crossMgrProcess.start()

    def doAdvanced(self, event):
        dlg = AdvancedSetup(self)
        dlg.ShowModal()
        dlg.Destroy()
        self.writeOptions()

    def shutdown(self):
        self.alienProcess = None
        self.crossMgrProcess = None
        self.messageQ = None
        self.dataQ = None
        self.shutdownQ = None

    def gracefulShutdown(self):
        # Shutdown the CrossMgr process by sending it a shutdown command.
        if self.shutdownQ:
            self.shutdownQ.put('shutdown')
            self.shutdownQ.put('shutdown')
            self.shutdownQ.put('shutdown')

        if self.dataQ:
            self.dataQ.put('shutdown')

        if self.crossMgrProcess:
            self.crossMgrProcess.join()
        if self.alienProcess:
            self.alienProcess.join()

        self.crossMgrProcess = None
        self.alienProcess = None

    def doReset(self, event, confirm=True):
        if confirm:
            dlg = wx.MessageDialog(self, 'Reset CrossMgrAlien Adapter?',
                                   'Confirm Reset',
                                   wx.OK | wx.CANCEL | wx.ICON_WARNING)
            ret = dlg.ShowModal()
            dlg.Destroy()
            if ret != wx.ID_OK:
                return

        self.reset.Enable(
            False)  # Prevent multiple clicks while shutting down.
        self.writeOptions()

        self.gracefulShutdown()

        self.alienMessages.clear()
        self.crossMgrMessages.clear()

        self.shutdown()
        self.reset.Enable(True)

        wx.CallAfter(self.start)

    def doAutoDetect(self, event):
        wx.BeginBusyCursor()
        self.gracefulShutdown()
        self.shutdown()
        alienHost, crossmgrHost = AutoDetect()
        wx.EndBusyCursor()

        if alienHost and crossmgrHost:
            self.setNotifyHost(
                crossmgrHost
            )  # Assumes CrossMgr is on the same computer as CrossMgrAlien.

            self.cmdHost.SetValue(alienHost)
            self.cmdPort.SetValue(str(DefaultAlienCmdPort))
            self.crossMgrHost.SetValue(crossmgrHost)

            self.listenForHeartbeat.SetValue(False)
        else:
            dlg = wx.MessageDialog(
                self,
                'Auto Detect Failed.\nCheck that reader has power and is connected to the router.',
                'Auto Detect Failed', wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()

        self.doReset(event, False)

    def onCloseWindow(self, event):
        wx.Exit()

    def doCopyToClipboard(self, event):
        cc = [
            'Configuration: CrossMgrAlien',
            '    NotifyHost:    {}'.format(self.getNotifyHost()),
            '    NotifyPort:    {}'.format(NotifyPort),
            '    RunningTime:   {}'.format(self.runningTime.GetLabel()),
            '    Time:          {}'.format(self.time.GetLabel()),
            '    BackupFile:    {}'.format(self.backupFile.GetLabel()),
            '',
            'Configuration: Alien:',
            '    ListenForAlienHeartbeat: {}'.format(
                'True' if self.listenForHeartbeat.GetValue() else 'False'),
            '    Antennas:      {}'.format(self.getAntennaStr()),
            '    HeartbeatPort: {}'.format(HeartbeatPort),
            '    AlienCmdHost:  {}'.format(self.cmdHost.GetAddress()),
            '    AlienCmdPort:  {}'.format(self.cmdPort.GetValue()),
            '',
            '    RepeatSeconds: {}'.format(Alien.RepeatSeconds),
            '',
            'Configuration: CrossMgr',
            '    CrossMgrHost:  {}'.format(self.getCrossMgrHost()),
            '    CrossMgrPort:  {}'.format(CrossMgrPort),
        ]
        cc.append('\nLog: Alien')
        log = self.alienMessagesText.GetValue()
        cc.extend(['    ' + line for line in log.split('\n')])

        cc.append('\nLog: CrossMgr')
        log = self.crossMgrMessagesText.GetValue()
        cc.extend(['    ' + line for line in log.split('\n')])

        cc.append('\nLog: Application\n')
        try:
            with open(redirectFileName, 'r') as fp:
                for line in fp:
                    cc.append(line)
        except:
            pass

        if wx.TheClipboard.Open():
            do = wx.TextDataObject()
            do.SetText('\n'.join(cc))
            wx.TheClipboard.SetData(do)
            wx.TheClipboard.Close()
            dlg = wx.MessageDialog(
                self, 'Configuration and Logs copied to the Clipboard.',
                'Copy to Clipboard Succeeded', wx.OK | wx.ICON_INFORMATION)
            ret = dlg.ShowModal()
            dlg.Destroy()
        else:
            # oops... something went wrong!
            wx.MessageBox("Unable to open the clipboard", "Error")

    def getNotifyHost(self):
        s = self.notifyHost.GetSelection()
        return self.notifyHost.GetString(s) if s != wx.NOT_FOUND else None

    def setNotifyHost(self, notifyHost):
        for i, s in enumerate(self.notifyHost.GetItems()):
            if s == notifyHost:
                self.notifyHost.SetSelection(i)
                return
        self.notifyHost.SetSelection(0)

    def getCrossMgrHost(self):
        return self.crossMgrHost.GetAddress()

    def getAntennaStr(self):
        s = []
        for i in range(4):
            if self.antennas[i].GetValue():
                s.append('%d' % i)
        if not s:
            # Ensure that at least one antenna is selected.
            self.antennas[0].SetValue(True)
            s.append('0')
        return ' '.join(s)

    def setAntennaStr(self, s):
        antennas = set(int(a) for a in s.split())
        for i in range(4):
            self.antennas[i].SetValue(i in antennas)

    def writeOptions(self):
        self.config.Write('CrossMgrHost', self.getCrossMgrHost())
        self.config.Write(
            'ListenForAlienHeartbeat',
            'True' if self.listenForHeartbeat.GetValue() else 'False')
        self.config.Write('AlienCmdAddr', self.cmdHost.GetAddress())
        self.config.Write('AlienCmdPort', str(self.cmdPort.GetValue()))
        self.config.Write('Antennas', self.getAntennaStr())
        self.config.Write('RepeatSeconds', '{}'.format(Alien.RepeatSeconds))
        self.config.Write('PlaySounds', '{}'.format(Utils.playBell))
        s = self.notifyHost.GetSelection()
        if s != wx.NOT_FOUND:
            self.config.Write('NotifyHost', self.notifyHost.GetString(s))
        self.config.Flush()

    def readOptions(self):
        self.crossMgrHost.SetValue(
            self.config.Read('CrossMgrHost', Utils.DEFAULT_HOST))
        self.listenForHeartbeat.SetValue(
            self.config.Read('ListenForAlienHeartbeat', 'True').upper()[:1] ==
            'T')
        Utils.playBell = (self.config.Read('PlaySounds',
                                           'True').upper()[:1] == 'T')
        self.cmdHost.SetValue(self.config.Read('AlienCmdAddr', '0.0.0.0'))
        self.cmdPort.SetValue(int(self.config.Read('AlienCmdPort', '0')))
        self.setAntennaStr(self.config.Read('Antennas', '0 1'))
        Alien.RepeatSeconds = int(
            self.config.Read('RepeatSeconds',
                             '{}'.format(Alien.RepeatSeconds)))
        notifyHost = self.config.Read('NotifyHost', Utils.DEFAULT_HOST)
        self.setNotifyHost(notifyHost)

    def updateMessages(self, event):
        tNow = datetime.datetime.now()
        running = int((tNow - self.tStart).total_seconds())
        self.runningTime.SetLabel('{:02d}:{:02d}:{:02d}'.format(
            running // (60 * 60), (running // 60) % 60, running % 60))
        self.time.SetLabel(tNow.strftime('%H:%M:%S'))

        if not self.messageQ:
            return

        while 1:
            try:
                d = self.messageQ.get(False)
            except Empty:
                break
            message = ' '.join(str(x) for x in d[1:])
            if d[0] == 'Alien':
                if 'state' in d:
                    self.alienMessages.messageList.SetBackgroundColour(
                        self.LightGreen if d[2] else self.LightRed)
                else:
                    self.alienMessages.write(message)
            elif d[0] == 'Alien2JChip':
                if 'state' in d:
                    self.crossMgrMessages.messageList.SetBackgroundColour(
                        self.LightGreen if d[2] else self.LightRed)
                else:
                    self.crossMgrMessages.write(message)
            elif d[0] == 'CmdHost':
                cmdHost, cmdPort = d[1].split(':')
                self.cmdHost.SetValue(cmdHost)
                self.cmdPort.SetValue(int(cmdPort))
            elif d[0] == 'BackupFile':
                self.backupFile.SetLabel(d[1])