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 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. 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)

        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])