Commit 5acf74617adab2056a39b8fdeab968a04573ccb9
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.
Showing
6 changed files
with
84 additions
and
44 deletions
Show diff stats
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) | ... | ... |