Commit 5acf74617adab2056a39b8fdeab968a04573ccb9

Authored by Olli-Pekka Kahilakoski
1 parent 9460b9cd
Exists in master

MOD: Allow selecting baud rate for serial port communication

Other changes:

- Disallow not selecting COM port in COM port selection dialog by
  defaulting to the first COM port in the list. Previously, it was
  possible to pass an empty string as the COM port by not making
  the selection.

- When the "Serial port" checkbox is pressed and the user presses
  "Cancel" in the dialog that is opened, uncheck the "Serial port" checkbox
  instead of keeping it checked.

- Be explicit about if the serial port is in use or not, instead of inferring
  it from the COM port variable being set or not.

- Pass the serial port parameters (COM port, baud rate) directly to the
  Navigation class, instead of passing them through NeuronavigationPanel.

- Minor improvements to style and naming of the variables.
invesalius/constants.py
... ... @@ -830,3 +830,7 @@ TREKKER_CONFIG = {'seed_max': 1, 'step_size': 0.1, 'min_fod': 0.1, 'probe_qualit
830 830 MARKER_FILE_MAGICK_STRING = "INVESALIUS3_MARKER_FILE_"
831 831 CURRENT_MARKER_FILE_VERSION = 0
832 832 WILDCARD_MARKER_FILES = _("Marker scanner coord files (*.mkss)|*.mkss")
  833 +
  834 +# Serial port
  835 +BAUD_RATES = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]
  836 +BAUD_RATE_DEFAULT_SELECTION = 4
... ...
invesalius/data/serial_port_connection.py
... ... @@ -28,7 +28,7 @@ from invesalius.pubsub import pub as Publisher
28 28 class SerialPortConnection(threading.Thread):
29 29 BINARY_PULSE = b'\x01'
30 30  
31   - def __init__(self, port, serial_port_queue, event, sleep_nav):
  31 + def __init__(self, com_port, baud_rate, serial_port_queue, event, sleep_nav):
32 32 """
33 33 Thread created to communicate using the serial port to interact with software during neuronavigation.
34 34 """
... ... @@ -37,28 +37,29 @@ class SerialPortConnection(threading.Thread):
37 37 self.connection = None
38 38 self.stylusplh = False
39 39  
40   - self.port = port
  40 + self.com_port = com_port
  41 + self.baud_rate = baud_rate
41 42 self.serial_port_queue = serial_port_queue
42 43 self.event = event
43 44 self.sleep_nav = sleep_nav
44 45  
45 46 def Connect(self):
46   - if self.port is None:
  47 + if self.com_port is None:
47 48 print("Serial port init error: COM port is unset.")
48 49 return
49 50 try:
50 51 import serial
51   - self.connection = serial.Serial(self.port, baudrate=115200, timeout=0)
52   - print("Connection to port {} opened.".format(self.port))
  52 + self.connection = serial.Serial(self.com_port, baudrate=self.baud_rate, timeout=0)
  53 + print("Connection to port {} opened.".format(self.com_port))
53 54  
54 55 Publisher.sendMessage('Serial port connection', state=True)
55 56 except:
56   - print("Serial port init error: Connecting to port {} failed.".format(self.port))
  57 + print("Serial port init error: Connecting to port {} failed.".format(self.com_port))
57 58  
58 59 def Disconnect(self):
59 60 if self.connection:
60 61 self.connection.close()
61   - print("Connection to port {} closed.".format(self.port))
  62 + print("Connection to port {} closed.".format(self.com_port))
62 63  
63 64 Publisher.sendMessage('Serial port connection', state=False)
64 65  
... ... @@ -74,12 +75,11 @@ class SerialPortConnection(threading.Thread):
74 75 trigger_on = False
75 76 try:
76 77 lines = self.connection.readlines()
  78 + if lines:
  79 + trigger_on = True
77 80 except:
78 81 print("Error: Serial port could not be read.")
79 82  
80   - if lines:
81   - trigger_on = True
82   -
83 83 if self.stylusplh:
84 84 trigger_on = True
85 85 self.stylusplh = False
... ...
invesalius/data/trackers.py
... ... @@ -266,7 +266,7 @@ def PlhSerialConnection(tracker_id):
266 266 import serial
267 267 from wx import ID_OK
268 268 trck_init = None
269   - dlg_port = dlg.SetCOMport()
  269 + dlg_port = dlg.SetCOMPort(select_baud_rate=False)
270 270 if dlg_port.ShowModal() == ID_OK:
271 271 com_port = dlg_port.GetValue()
272 272 try:
... ...
invesalius/gui/dialogs.py
... ... @@ -4322,7 +4322,8 @@ class SetNDIconfigs(wx.Dialog):
4322 4322 self._init_gui()
4323 4323  
4324 4324 def serial_ports(self):
4325   - """ Lists serial port names and pre-select the description containing NDI
  4325 + """
  4326 + Lists serial port names and pre-select the description containing NDI
4326 4327 """
4327 4328 import serial.tools.list_ports
4328 4329  
... ... @@ -4430,13 +4431,16 @@ class SetNDIconfigs(wx.Dialog):
4430 4431 return self.com_ports.GetString(self.com_ports.GetSelection()).encode(const.FS_ENCODE), fn_probe, fn_ref, fn_obj
4431 4432  
4432 4433  
4433   -class SetCOMport(wx.Dialog):
4434   - def __init__(self, title=_("Select COM port")):
4435   - wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), -1, title, style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP)
  4434 +class SetCOMPort(wx.Dialog):
  4435 + def __init__(self, select_baud_rate, title=_("Select COM port")):
  4436 + wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), -1, title, style=wx.DEFAULT_DIALOG_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.STAY_ON_TOP)
  4437 +
  4438 + self.select_baud_rate = select_baud_rate
4436 4439 self._init_gui()
4437 4440  
4438 4441 def serial_ports(self):
4439   - """ Lists serial port names
  4442 + """
  4443 + Lists serial port names
4440 4444 """
4441 4445 import serial.tools.list_ports
4442 4446 if sys.platform.startswith('win'):
... ... @@ -4446,12 +4450,26 @@ class SetCOMport(wx.Dialog):
4446 4450 return ports
4447 4451  
4448 4452 def _init_gui(self):
4449   - self.com_ports = wx.ComboBox(self, -1, style=wx.CB_DROPDOWN|wx.CB_READONLY)
  4453 + # COM port selection
4450 4454 ports = self.serial_ports()
4451   - self.com_ports.Append(ports)
  4455 + self.com_port_dropdown = wx.ComboBox(self, -1, choices=ports, style=wx.CB_DROPDOWN | wx.CB_READONLY)
  4456 + self.com_port_dropdown.SetSelection(0)
  4457 +
  4458 + com_port_text_and_dropdown = wx.BoxSizer(wx.VERTICAL)
  4459 + com_port_text_and_dropdown.Add(wx.StaticText(self, wx.ID_ANY, "COM port"), 0, wx.TOP | wx.RIGHT,5)
  4460 + com_port_text_and_dropdown.Add(self.com_port_dropdown, 0, wx.EXPAND)
4452 4461  
4453   - # self.goto_orientation.SetSelection(cb_init)
  4462 + # Baud rate selection
  4463 + if self.select_baud_rate:
  4464 + baud_rates_as_strings = [str(baud_rate) for baud_rate in const.BAUD_RATES]
  4465 + self.baud_rate_dropdown = wx.ComboBox(self, -1, choices=baud_rates_as_strings, style=wx.CB_DROPDOWN | wx.CB_READONLY)
  4466 + self.baud_rate_dropdown.SetSelection(const.BAUD_RATE_DEFAULT_SELECTION)
4454 4467  
  4468 + baud_rate_text_and_dropdown = wx.BoxSizer(wx.VERTICAL)
  4469 + baud_rate_text_and_dropdown.Add(wx.StaticText(self, wx.ID_ANY, "Baud rate"), 0, wx.TOP | wx.RIGHT,5)
  4470 + baud_rate_text_and_dropdown.Add(self.baud_rate_dropdown, 0, wx.EXPAND)
  4471 +
  4472 + # OK and Cancel buttons
4455 4473 btn_ok = wx.Button(self, wx.ID_OK)
4456 4474 btn_ok.SetHelpText("")
4457 4475 btn_ok.SetDefault()
... ... @@ -4464,10 +4482,16 @@ class SetCOMport(wx.Dialog):
4464 4482 btnsizer.AddButton(btn_cancel)
4465 4483 btnsizer.Realize()
4466 4484  
  4485 + # Set up the main sizer
4467 4486 main_sizer = wx.BoxSizer(wx.VERTICAL)
4468 4487  
4469 4488 main_sizer.Add((5, 5))
4470   - main_sizer.Add(self.com_ports, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 5)
  4489 + main_sizer.Add(com_port_text_and_dropdown, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
  4490 +
  4491 + if self.select_baud_rate:
  4492 + main_sizer.Add((5, 5))
  4493 + main_sizer.Add(baud_rate_text_and_dropdown, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
  4494 +
4471 4495 main_sizer.Add((5, 5))
4472 4496 main_sizer.Add(btnsizer, 0, wx.EXPAND)
4473 4497 main_sizer.Add((5, 5))
... ... @@ -4478,7 +4502,14 @@ class SetCOMport(wx.Dialog):
4478 4502 self.CenterOnParent()
4479 4503  
4480 4504 def GetValue(self):
4481   - return self.com_ports.GetString(self.com_ports.GetSelection())
  4505 + com_port = self.com_port_dropdown.GetString(self.com_port_dropdown.GetSelection())
  4506 +
  4507 + if self.select_baud_rate:
  4508 + baud_rate = self.baud_rate_dropdown.GetString(self.baud_rate_dropdown.GetSelection())
  4509 + else:
  4510 + baud_rate = None
  4511 +
  4512 + return com_port, baud_rate
4482 4513  
4483 4514  
4484 4515 class ManualWWWLDialog(wx.Dialog):
... ...
invesalius/gui/task_navigator.py
... ... @@ -222,8 +222,8 @@ class InnerFoldPanel(wx.Panel):
222 222 checkcamera.Bind(wx.EVT_CHECKBOX, self.OnVolumeCamera)
223 223 self.checkcamera = checkcamera
224 224  
225   - # Check box to create markers from serial port
226   - tooltip = wx.ToolTip(_("Enable serial port communication for creating markers"))
  225 + # Check box to use serial port to trigger pulse signal and create markers
  226 + tooltip = wx.ToolTip(_("Enable serial port communication to trigger pulse and create markers"))
227 227 checkbox_serial_port = wx.CheckBox(self, -1, _('Serial port'))
228 228 checkbox_serial_port.SetToolTip(tooltip)
229 229 checkbox_serial_port.SetValue(False)
... ... @@ -285,14 +285,17 @@ class InnerFoldPanel(wx.Panel):
285 285 self.checkobj.Enable(True)
286 286  
287 287 def OnEnableSerialPort(self, evt, ctrl):
288   - com_port = None
289 288 if ctrl.GetValue():
290 289 from wx import ID_OK
291   - dlg_port = dlg.SetCOMport()
292   - if dlg_port.ShowModal() == ID_OK:
293   - com_port = dlg_port.GetValue()
  290 + dlg_port = dlg.SetCOMPort(select_baud_rate=True)
  291 + if dlg_port.ShowModal() != ID_OK:
  292 + ctrl.SetValue(False)
  293 + return
294 294  
295   - Publisher.sendMessage('Update serial port', serial_port=com_port)
  295 + com_port, baud_rate = dlg_port.GetValue()
  296 + Publisher.sendMessage('Update serial port', serial_port_in_use=True, com_port=com_port, baud_rate=baud_rate)
  297 + else:
  298 + Publisher.sendMessage('Update serial port', serial_port_in_use=False)
296 299  
297 300 def OnShowObject(self, evt=None, flag=None, obj_name=None, polydata=None, use_default_object=True):
298 301 if not evt:
... ... @@ -490,7 +493,6 @@ class NeuronavigationPanel(wx.Panel):
490 493 Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials')
491 494 Publisher.subscribe(self.SetImageFiducial, 'Set image fiducial')
492 495 Publisher.subscribe(self.SetTrackerFiducial, 'Set tracker fiducial')
493   - Publisher.subscribe(self.UpdateSerialPort, 'Update serial port')
494 496 Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state')
495 497 Publisher.subscribe(self.UpdateImageCoordinates, 'Set cross focal point')
496 498 Publisher.subscribe(self.OnDisconnectTracker, 'Disconnect tracker')
... ... @@ -614,9 +616,6 @@ class NeuronavigationPanel(wx.Panel):
614 616 def UpdateTrackObjectState(self, evt=None, flag=None, obj_name=None, polydata=None, use_default_object=True):
615 617 self.navigation.track_obj = flag
616 618  
617   - def UpdateSerialPort(self, serial_port):
618   - self.navigation.serial_port = serial_port
619   -
620 619 def ResetICP(self):
621 620 self.icp.ResetICP()
622 621 self.checkbox_icp.Enable(False)
... ...
invesalius/navigation/navigation.py
... ... @@ -168,7 +168,9 @@ class Navigation():
168 168 self.sleep_nav = const.SLEEP_NAVIGATION
169 169  
170 170 # Serial port
171   - self.serial_port = None
  171 + self.serial_port_in_use = False
  172 + self.com_port = None
  173 + self.baud_rate = None
172 174 self.serial_port_connection = None
173 175  
174 176 # During navigation
... ... @@ -178,6 +180,7 @@ class Navigation():
178 180  
179 181 def __bind_events(self):
180 182 Publisher.subscribe(self.CoilAtTarget, 'Coil at target')
  183 + Publisher.subscribe(self.UpdateSerialPort, 'Update serial port')
181 184  
182 185 def CoilAtTarget(self, state):
183 186 self.coil_at_target = state
... ... @@ -186,8 +189,10 @@ class Navigation():
186 189 self.sleep_nav = sleep
187 190 self.serial_port_connection.sleep_nav = sleep
188 191  
189   - def SerialPortEnabled(self):
190   - return self.serial_port is not None
  192 + def UpdateSerialPort(self, serial_port_in_use, com_port=None, baud_rate=None):
  193 + self.serial_port_in_use = serial_port_in_use
  194 + self.com_port = com_port
  195 + self.baud_rate = baud_rate
191 196  
192 197 def SetReferenceMode(self, value):
193 198 self.ref_mode_id = value
... ... @@ -215,7 +220,7 @@ class Navigation():
215 220 return fre, fre <= const.FIDUCIAL_REGISTRATION_ERROR_THRESHOLD
216 221  
217 222 def PedalStateChanged(self, state):
218   - if state is True and self.coil_at_target and self.SerialPortEnabled():
  223 + if state is True and self.coil_at_target and self.serial_port_in_use:
219 224 self.serial_port_connection.SendPulse()
220 225  
221 226 def StartNavigation(self, tracker):
... ... @@ -227,7 +232,7 @@ class Navigation():
227 232 if self.event.is_set():
228 233 self.event.clear()
229 234  
230   - vis_components = [self.SerialPortEnabled(), self.view_tracts, self.peel_loaded]
  235 + vis_components = [self.serial_port_in_use, self.view_tracts, self.peel_loaded]
231 236 vis_queues = [self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue]
232 237  
233 238 Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components)
... ... @@ -276,12 +281,13 @@ class Navigation():
276 281  
277 282 if not errors:
278 283 #TODO: Test the serial port thread
279   - if self.SerialPortEnabled():
  284 + if self.serial_port_in_use:
280 285 self.serial_port_connection = spc.SerialPortConnection(
281   - self.serial_port,
282   - self.serial_port_queue,
283   - self.event,
284   - self.sleep_nav,
  286 + com_port=self.com_port,
  287 + baud_rate=self.baud_rate,
  288 + serial_port_queue=self.serial_port_queue,
  289 + event=self.event,
  290 + sleep_nav=self.sleep_nav,
285 291 )
286 292 self.serial_port_connection.Connect()
287 293 jobs_list.append(self.serial_port_connection)
... ... @@ -327,7 +333,7 @@ class Navigation():
327 333 if self.serial_port_connection is not None:
328 334 self.serial_port_connection.join()
329 335  
330   - if self.SerialPortEnabled():
  336 + if self.serial_port_in_use:
331 337 self.serial_port_queue.clear()
332 338 self.serial_port_queue.join()
333 339  
... ... @@ -338,5 +344,5 @@ class Navigation():
338 344 self.tracts_queue.clear()
339 345 self.tracts_queue.join()
340 346  
341   - vis_components = [self.SerialPortEnabled(), self.view_tracts, self.peel_loaded]
  347 + vis_components = [self.serial_port_in_use, self.view_tracts, self.peel_loaded]
342 348 Publisher.sendMessage("Navigation status", nav_status=False, vis_status=vis_components)
... ...