Skip to content

Commit

Permalink
- adds Santoker Q/R Series over BT
Browse files Browse the repository at this point in the history
- adds Kaleido Legacy setup
- turns serial port popup on machine setup editable
- fixes IO Command support for Phidget REL1101 (Issue #1141)
  • Loading branch information
MAKOMO committed Apr 23, 2023
1 parent c9d6e92 commit 9ca439b
Show file tree
Hide file tree
Showing 43 changed files with 53,386 additions and 52,941 deletions.
11 changes: 11 additions & 0 deletions src/artisanlib/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
from artisanlib.time import ArtisanTime
from artisanlib.filters import LiveMedian
from artisanlib.dialogs import ArtisanMessageBox
from artisanlib.types import SerialSettings

# import artisan.plus module
from plus.util import roastLink
Expand Down Expand Up @@ -11221,7 +11222,17 @@ def OnMonitor(self):
from artisanlib.santoker import SantokerNetwork
self.aw.santoker = SantokerNetwork()
self.aw.santoker.setLogging(self.device_logging)
santoker_serial:Optional[SerialSettings] = None
if self.aw.santokerSerial:
santoker_serial = {
'port': self.aw.ser.comport,
'baudrate': self.aw.ser.baudrate,
'bytesize': self.aw.ser.bytesize,
'stopbits': self.aw.ser.stopbits,
'parity': self.aw.ser.parity,
'timeout': self.aw.ser.timeout}
self.aw.santoker.start(self.aw.santokerHost, self.aw.santokerPort,
santoker_serial,
connected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', 'Santoker connected'),True,None),
disconnected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', 'Santoker disconnected'),True,None),
charge_handler=lambda : (self.markChargeSignal.emit() if (self.timeindex[0] == -1) else None),
Expand Down
2 changes: 1 addition & 1 deletion src/artisanlib/comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3801,7 +3801,7 @@ def phidgetBinaryOUTattach(self, channel:'Phidget', serial:Optional[str]=None) -
ports = 8
# try to attach up to 16 IO channels of the first Phidget REL1101 module
if ser is None:
ser,_ = self.aw.qmc.phidgetManager.getFirstMatchingPhidget('PhidgetDigitalOutput',DeviceID.PHIDID_REL1100,
ser,_ = self.aw.qmc.phidgetManager.getFirstMatchingPhidget('PhidgetDigitalOutput',DeviceID.PHIDID_REL1101,
remote=self.aw.qmc.phidgetRemoteFlag,remoteOnly=self.aw.qmc.phidgetRemoteOnlyFlag,serial=s,hubport=p)
ports = 16
if ser is not None:
Expand Down
52 changes: 45 additions & 7 deletions src/artisanlib/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ def __init__(self, parent, aw, activeTab = 0) -> None:

self.helpdialog = None

self.org_phidgetRemoteFlag = self.aw.qmc.phidgetRemoteFlag
self.org_yoctoRemoteFlag = self.aw.qmc.yoctoRemoteFlag
self.org_santokerSerial = self.aw.santokerSerial

################ TAB 1 WIDGETS
#ETcurve
self.ETcurve = QCheckBox(QApplication.translate('CheckBox', 'ET'))
Expand Down Expand Up @@ -940,21 +944,26 @@ def __init__(self, parent, aw, activeTab = 0) -> None:
self.phidgetBoxRemoteFlag = QCheckBox()
self.phidgetBoxRemoteFlag.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.phidgetBoxRemoteFlag.setChecked(self.aw.qmc.phidgetRemoteFlag)
self.phidgetBoxRemoteFlag.stateChanged.connect(self.phidgetRemoteStateChanged)
phidgetServerIdLabel = QLabel(QApplication.translate('Label','Host'))
self.phidgetServerId = QLineEdit(self.aw.qmc.phidgetServerID)
self.phidgetServerId.textChanged.connect(self.phidgetHostChanged)
self.phidgetServerId.setMinimumWidth(200)
self.phidgetServerId.setEnabled(self.aw.qmc.phidgetRemoteFlag)
phidgetPasswordLabel = QLabel(QApplication.translate('Label','Password'))
self.phidgetPassword = QLineEdit(self.aw.qmc.phidgetPassword)
self.phidgetPassword.setEchoMode(QLineEdit.EchoMode.PasswordEchoOnEdit)
self.phidgetPassword.setEnabled(self.aw.qmc.phidgetServerID != '')
self.phidgetPassword.setMinimumWidth(100)
self.phidgetPassword.setEnabled(self.aw.qmc.phidgetRemoteFlag)
phidgetPortLabel = QLabel(QApplication.translate('Label','Port'))
self.phidgetPort = QLineEdit(str(self.aw.qmc.phidgetPort))
self.phidgetPort.setMaximumWidth(70)
self.phidgetPort.setEnabled(self.aw.qmc.phidgetRemoteFlag)
self.phidgetBoxRemoteOnlyFlag = QCheckBox(QApplication.translate('Label','Remote Only'))
self.phidgetBoxRemoteOnlyFlag.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.phidgetBoxRemoteOnlyFlag.setChecked(self.aw.qmc.phidgetRemoteOnlyFlag)
self.phidgetBoxRemoteOnlyFlag.setEnabled(self.aw.qmc.phidgetRemoteFlag)
phidgetServerBox = QHBoxLayout()
phidgetServerBox.addWidget(phidgetServerIdLabel)
phidgetServerBox.addWidget(self.phidgetServerId)
Expand Down Expand Up @@ -998,8 +1007,10 @@ def __init__(self, parent, aw, activeTab = 0) -> None:
self.yoctoBoxRemoteFlag = QCheckBox()
self.yoctoBoxRemoteFlag.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.yoctoBoxRemoteFlag.setChecked(self.aw.qmc.yoctoRemoteFlag)
self.yoctoBoxRemoteFlag.stateChanged.connect(self.yoctoBoxRemoteFlagStateChanged)
yoctoServerIdLabel = QLabel(QApplication.translate('Label','VirtualHub'))
self.yoctoServerId = QLineEdit(self.aw.qmc.yoctoServerID)
self.yoctoServerId.setEnabled(self.aw.qmc.yoctoRemoteFlag)
YoctoEmissivityLabel = QLabel(QApplication.translate('Label','Emissivity'))
self.yoctoEmissivitySpinBox = MyQDoubleSpinBox()
self.yoctoEmissivitySpinBox.setAlignment(Qt.AlignmentFlag.AlignRight)
Expand Down Expand Up @@ -1115,20 +1126,25 @@ def __init__(self, parent, aw, activeTab = 0) -> None:
ambientVBox.addLayout(ambientHBox)
ambientVBox.addStretch()
ambientVBox.setContentsMargins(0,0,0,0)

santokerHostLabel = QLabel(QApplication.translate('Label','Host'))
self.santokerHost = QLineEdit(self.aw.santokerHost)
self.santokerHost.setAlignment(Qt.AlignmentFlag.AlignRight)
self.santokerHost.setFixedWidth(150)
self.santokerHost.setEnabled(not self.aw.santokerSerial)
santokerPortLabel = QLabel(QApplication.translate('Label','Port'))
self.santokerPort = QLineEdit(str(self.aw.santokerPort))
self.santokerPort.setAlignment(Qt.AlignmentFlag.AlignRight)
self.santokerPort.setFixedWidth(150)
self.santokerPort.setEnabled(not self.aw.santokerSerial)
self.santokerSerialFlag = QCheckBox()
self.santokerSerialFlag.setChecked(not self.aw.santokerSerial)
self.santokerSerialFlag.stateChanged.connect(self.santokerSerialStateChanged)
santokerNetworkGrid = QGridLayout()
santokerNetworkGrid.addWidget(santokerHostLabel,0,0)
santokerNetworkGrid.addWidget(self.santokerHost,0,1)
santokerNetworkGrid.addWidget(santokerPortLabel,1,0)
santokerNetworkGrid.addWidget(self.santokerPort,1,1)
santokerNetworkGrid.addWidget(self.santokerSerialFlag,0,0)
santokerNetworkGrid.addWidget(santokerHostLabel,0,1)
santokerNetworkGrid.addWidget(self.santokerHost,0,2)
santokerNetworkGrid.addWidget(santokerPortLabel,1,1)
santokerNetworkGrid.addWidget(self.santokerPort,1,2)
santokerNetworkGrid.setSpacing(20)
santokerNetworkGroupBox = QGroupBox(QApplication.translate('GroupBox','Network'))
santokerNetworkGroupBox.setLayout(santokerNetworkGrid)
Expand Down Expand Up @@ -1332,6 +1348,24 @@ def __init__(self, parent, aw, activeTab = 0) -> None:
self.restoreGeometry(settings.value('DeviceAssignmentGeometry'))
self.TabWidget.setCurrentIndex(activeTab)

@pyqtSlot(int)
def yoctoBoxRemoteFlagStateChanged(self, _:int) -> None:
self.aw.qmc.yoctoRemoteFlag = not self.aw.qmc.yoctoRemoteFlag
self.yoctoServerId.setEnabled(self.aw.qmc.yoctoRemoteFlag)

@pyqtSlot(int)
def phidgetRemoteStateChanged(self, _:int) -> None:
self.aw.qmc.phidgetRemoteFlag = not self.aw.qmc.phidgetRemoteFlag
self.phidgetServerId.setEnabled(self.aw.qmc.phidgetRemoteFlag)
self.phidgetPassword.setEnabled(self.aw.qmc.phidgetRemoteFlag)
self.phidgetPort.setEnabled(self.aw.qmc.phidgetRemoteFlag)
self.phidgetBoxRemoteOnlyFlag.setEnabled(self.aw.qmc.phidgetRemoteFlag)

@pyqtSlot(int)
def santokerSerialStateChanged(self, _:int) -> None:
self.aw.santokerSerial = not self.aw.santokerSerial
self.santokerHost.setEnabled(not self.aw.santokerSerial)
self.santokerPort.setEnabled(not self.aw.santokerSerial)

@pyqtSlot(str)
def phidgetHostChanged(self,s):
Expand All @@ -1342,7 +1376,7 @@ def changeOutprogramFlag(self,_):
self.aw.ser.externaloutprogramFlag = not self.aw.ser.externaloutprogramFlag

@pyqtSlot(int)
def asyncFlagStateChanged1048(self,x):
def asyncFlagStateChanged1048(self, x:int):
try:
sender = self.sender()
assert isinstance(sender, QCheckBox)
Expand Down Expand Up @@ -2132,6 +2166,9 @@ def close(self):
def cancelEvent(self):
self.aw.DeviceAssignmentDlg_activeTab = self.TabWidget.currentIndex()
self.close()
self.aw.qmc.phidgetRemoteFlag = self.org_phidgetRemoteFlag
self.aw.qmc.yoctoRemoteFlag = self.org_yoctoRemoteFlag
self.aw.santokerSerial = self.org_santokerSerial
self.reject()

@pyqtSlot()
Expand Down Expand Up @@ -3200,6 +3237,7 @@ def okEvent(self): # pyright: ignore # Code is too complex to analyze; reduce co
self.aw.santokerPort = int(self.santokerPort.text())
except Exception: # pylint: disable=broad-except
pass

for i in range(8):
self.aw.qmc.phidget1018_async[i] = self.asyncCheckBoxes[i].isChecked()
self.aw.qmc.phidget1018_ratio[i] = self.ratioCheckBoxes[i].isChecked()
Expand Down Expand Up @@ -3227,7 +3265,7 @@ def okEvent(self): # pyright: ignore # Code is too complex to analyze; reduce co
self.aw.sendmessage(message)
#open serial conf Dialog
#if device is not None or not external-program (don't need serial settings config)
if self.aw.qmc.device not in self.aw.qmc.nonSerialDevices:
if self.aw.qmc.device not in self.aw.qmc.nonSerialDevices or (self.aw.qmc.device == 134 and self.aw.santokerSerial):
self.aw.setcommport()
self.close()
self.accept()
Expand Down
142 changes: 130 additions & 12 deletions src/artisanlib/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,29 @@
# Marko Luther, 2023

import platform
import logging

try:
#pylint: disable = E, W, R, C
from PyQt6.QtCore import Qt, QSettings, pyqtSlot # @UnusedImport @Reimport @UnresolvedImport
from PyQt6.QtWidgets import (QApplication, QDialog, QMessageBox, QDialogButtonBox, QTextEdit, # @UnusedImport @Reimport @UnresolvedImport
from PyQt6.QtCore import Qt, QEvent, QSettings, pyqtSlot # @UnusedImport @Reimport @UnresolvedImport
from PyQt6.QtWidgets import (QApplication, QWidget, QDialog, QMessageBox, QDialogButtonBox, QTextEdit, # @UnusedImport @Reimport @UnresolvedImport
QHBoxLayout, QVBoxLayout, QLabel, QLineEdit) # @UnusedImport @Reimport @UnresolvedImport
from PyQt6.QtGui import QKeySequence, QAction # @UnusedImport @Reimport @UnresolvedImport
except Exception: # pylint: disable=broad-except
#pylint: disable = E, W, R, C
from PyQt5.QtCore import Qt, QSettings, pyqtSlot # type: ignore # @UnusedImport @Reimport @UnresolvedImport
from PyQt5.QtWidgets import (QApplication, QAction, QDialog, QMessageBox, QDialogButtonBox, QTextEdit, # type: ignore # @UnusedImport @Reimport @UnresolvedImport
from PyQt5.QtCore import Qt, QEvent, QSettings, pyqtSlot # type: ignore # @UnusedImport @Reimport @UnresolvedImport
from PyQt5.QtWidgets import (QApplication, QWidget, QAction, QDialog, QMessageBox, QDialogButtonBox, QTextEdit, # type: ignore # @UnusedImport @Reimport @UnresolvedImport
QHBoxLayout, QVBoxLayout, QLabel, QLineEdit) # type: ignore # @UnusedImport @Reimport @UnresolvedImport
from PyQt5.QtGui import QKeySequence # type: ignore # @UnusedImport @Reimport @UnresolvedImport

from artisanlib.widgets import MyQComboBox

from typing import Optional
from typing import Optional, List, Tuple, TYPE_CHECKING
from typing_extensions import Final # Python <=3.7
if TYPE_CHECKING:
from artisanlib.main import ApplicationWindow # pylint: disable=unused-import

_log: Final[logging.Logger] = logging.getLogger(__name__)

class ArtisanDialog(QDialog): # pyright: ignore # Argument to class must be a base class (reportGeneralTypeIssues)

Expand Down Expand Up @@ -124,7 +129,7 @@ class ArtisanMessageBox(QMessageBox): # pyright: ignore # Argument to class must

__slots__ = ['timeout', 'currentTime']

def __init__(self, parent = None, title=None, text=None, timeout=0, modal=True) -> None:
def __init__(self, parent:Optional[QWidget] = None, title=None, text=None, timeout=0, modal=True) -> None:
super().__init__(parent)
self.setWindowTitle(title)
self.setText(text)
Expand All @@ -147,7 +152,7 @@ def timerEvent(self,_):
self.done(0)

class HelpDlg(ArtisanDialog):
def __init__(self, parent, aw, title = '', content = '') -> None:
def __init__(self, parent:QWidget, aw:'ApplicationWindow', title = '', content = '') -> None:
super().__init__(parent, aw)
self.setWindowTitle(title)
self.setModal(False)
Expand Down Expand Up @@ -187,7 +192,7 @@ class ArtisanInputDialog(ArtisanDialog):

__slots__ = ['url', 'inputLine']

def __init__(self, parent, aw, title='',label='') -> None:
def __init__(self, parent:QWidget, aw:'ApplicationWindow', title='',label='') -> None:
super().__init__(parent, aw)

self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
Expand Down Expand Up @@ -227,24 +232,24 @@ def dropEvent(self, event):
if urls and len(urls)>0:
self.inputLine.setText(urls[0].toString())


class ArtisanComboBoxDialog(ArtisanDialog):

__slots__ = ['idx', 'comboBox']
__slots__ = [ 'idx', 'comboBox' ]

def __init__(self, parent, aw, title='',label='',choices=None,default=-1) -> None:
def __init__(self, parent:QWidget, aw:'ApplicationWindow', title:str = '', label:str='', choices:Optional[List[str]] = None, default:int = -1) -> None:
super().__init__(parent, aw)

self.idx:Optional[int] = None

self.setWindowTitle(title)
self.setModal(True)
label = QLabel(label)
self.comboBox = MyQComboBox()
if choices is not None:
self.comboBox.addItems(choices)
self.comboBox.setCurrentIndex(default)
layout = QVBoxLayout()
layout.addWidget(label)
layout.addWidget(QLabel(label))
layout.addWidget(self.comboBox)
layout.addWidget(self.dialogbuttons)
self.setLayout(layout)
Expand All @@ -258,3 +263,116 @@ def __init__(self, parent, aw, title='',label='',choices=None,default=-1) -> Non
def accept(self):
self.idx = self.comboBox.currentIndex()
QDialog.accept(self)


class PortComboBox(MyQComboBox): # pyright: ignore # Argument to class must be a base class (reportGeneralTypeIssues)

__slots__ = ['selection', 'select_device_name', 'ports','edited'] # save some memory by using slots

# the given select_device_name is preferred (if a corresponding port is found) over the given selection port name
def __init__(self, parent:Optional[QWidget] = None, selection:Optional[str] = None, select_device_name:Optional[str] = None) -> None:
super().__init__(parent)
self.installEventFilter(self)
self.selection:Optional[str] = selection # just the port name (first element of one of the triples in self.ports)
self.select_device_name:Optional[str] = select_device_name # device name (second element of one of the triples in self.ports)

self.setEditable(True)

# a list of triples as returned by serial.tools.list_ports
self.ports:List[Tuple[str, Optional[str], str]] = [] # list of tuples (port, desc, hwid)
self.updateMenu()
self.edited:Optional[str] = None
if self.selection is not None:
self.setCurrentText(self.selection)
self.editTextChanged.connect(self.textEdited)

# we prefer the device name if available over the selection port
try:
if self.select_device_name is not None:
pos = [p[1] for p in self.ports].index(self.select_device_name)
self.setCurrentIndex(pos)
except Exception: # pylint: disable=broad-except
pass

@pyqtSlot(str)
def textEdited(self, txt:str) -> None:
self.edited = txt

def getSelection(self) -> Optional[str]:
return self.edited or self.selection

def setSelection(self, i:int) -> None:
if i >= 0:
try:
self.selection = self.ports[i][0]
self.edited = None # reset the user text editing
except Exception: # pylint: disable=broad-except
pass

def eventFilter(self, obj, event):
# the next prevents correct setSelection on Windows
# if event.type() == QEvent.Type.FocusIn:
# self.setSelection(self.currentIndex())
if event.type() == QEvent.Type.MouseButtonPress:
self.updateMenu()
return super().eventFilter(obj, event)

def updateMenu(self) -> None:
self.blockSignals(True)
try:
import serial.tools.list_ports
# on older versions of pyserial list_ports.comports() returned a list of tuples (port, desc, hwid), current versions return a list of ListPortInfo objects
comports = [(cp.device, cp.product, 'n/a') for cp in serial.tools.list_ports.comports()]
if platform.system() == 'Darwin':
self.ports = [p for p in comports if (p[0] not in ['/dev/cu.Bluetooth-PDA-Sync',
'/dev/cu.Bluetooth-Modem','/dev/tty.Bluetooth-PDA-Sync','/dev/tty.Bluetooth-Modem','/dev/cu.Bluetooth-Incoming-Port','/dev/tty.Bluetooth-Incoming-Port'])]
else:
self.ports = list(comports)
if self.selection is not None and self.selection not in [p[0] for p in self.ports]:
self.ports.append((self.selection,'',''))
self.ports = sorted(self.ports,key=lambda p: p[0])
self.clear()
self.addItems([(p[1] if (p[1] is not None and p[1]!='n/a') else p[0]) for p in self.ports])
try:
if self.selection is not None:
pos = [p[0] for p in self.ports].index(self.selection)
self.setCurrentIndex(pos)
except Exception: # pylint: disable=broad-except
pass
except Exception as e: # pylint: disable=broad-except
_log.exception(e)
self.blockSignals(False)


class ArtisanPortsDialog(ArtisanDialog):

__slots__ = [ 'idx', 'comboBox' ]

def __init__(self, parent:QWidget, aw:'ApplicationWindow', title:Optional[str] = None,
label:Optional[str] = None,
selection:Optional[str] = None,
select_device_name:Optional[str] = None) -> None:
super().__init__(parent, aw)
self.idx:Optional[int] = None
self.comboBox = PortComboBox(parent, selection, select_device_name)

self.setWindowTitle(QApplication.translate('Message', 'Port Configuration') if title is None else title)
self.setModal(True)
layout = QVBoxLayout()
layout.addWidget(QLabel(QApplication.translate('Message', 'Comm Port') if label is None else label))
layout.addWidget(self.comboBox)
layout.addWidget(self.dialogbuttons)
self.setLayout(layout)
self.setFixedHeight(self.sizeHint().height())
# connect the ArtisanDialog standard OK/Cancel buttons
self.dialogbuttons.rejected.connect(self.reject)
self.dialogbuttons.accepted.connect(self.accept)
self.dialogbuttons.button(QDialogButtonBox.StandardButton.Ok).setFocus()

def getSelection(self) -> Optional[str]:
return self.comboBox.getSelection()

@pyqtSlot()
def accept(self):
self.idx = self.comboBox.currentIndex()
QDialog.accept(self)
Loading

0 comments on commit 9ca439b

Please sign in to comment.