diff --git a/README.md b/README.md
index 4b35b96..4888ec7 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,24 @@
# AutoSTAR_remote
-This is a GUI to remote control (using ASCOM) the Meade AutoSTAR #497 handheld.
+This is a GUI to remote control (using ASCOM or serial interface) the Meade AutoSTAR #497 handheld.

Press [SHIFT] when clicking on "ENTER", "MODE" or "GO TO" to generate a long key press.
+You can change the serial port parameters when connecting with UART. Default parameters for the MEADE AutoSTAR #497 are:
+- Speed: 9600 baud
+- 8 data bits
+- 1 stop bit
+- no parity
+- no flow control
+
+When connecting with the serial port you have the option to set the time and date of the AutoSTAR to the computer clock. This feature is not fully tested. Especially the daylight saving may be wrong. Please check the AutoSTAR settings if you see strange errors when doing GOTO to an object.
+
The compiled binary just needs to be unpacked. No installation and no Python is needed. ASCOM driver https://bitbucket.org/cjdskunkworks/meadeautostar497 must be installed and off course you need to connect your #497 AutoSTAR with your computer.
+
+For running the Python source code you need the following packages:
+- PyQt5
+- pyserial
+- win32com when using ASCOM on Windows
+
+The Python source code runs also on Raspberry Pi (Astroberry).
diff --git a/make_UI.bat b/make_UI.bat
index fca046c..1db08f8 100644
--- a/make_UI.bat
+++ b/make_UI.bat
@@ -1 +1,2 @@
call pyuic5 -x src\AutoSTAR_remote_ui.ui -o src\AutoSTAR_remote_ui.py
+call pyuic5 -x src\UART_ui.ui -o src\UART_ui.py
diff --git a/src/ASCOM.py b/src/ASCOM.py
new file mode 100644
index 0000000..686c8d4
--- /dev/null
+++ b/src/ASCOM.py
@@ -0,0 +1,100 @@
+"""
+ASCOM interface for AutoSTAR_remote
+"""
+
+from PyQt5 import QtWidgets
+import win32com.client
+
+
+class ASCOM:
+
+ def __init__(self, showDebugMessages=False):
+ self.Telescope = None
+ self.Name = ""
+ #
+ self.showDebugMessages = showDebugMessages
+
+ def dbgMsg(self, msg):
+ if self.showDebugMessages:
+ print(f'DEBUG: {msg}')
+
+ def open(self):
+ self.close()
+ try:
+ Chooser = win32com.client.Dispatch("ASCOM.Utilities.Chooser")
+ except win32com.client.pywintypes.com_error:
+ QtWidgets.QMessageBox.critical(None, "Can not call ASCOM!",
+ f"Is ASCOM installed?")
+ else:
+ Chooser.DeviceType = 'Telescope'
+ self.Name = Chooser.Choose(None)
+ if self.Name != "":
+ self.Telescope = win32com.client.Dispatch(self.Name)
+ self.Telescope.Connected = True
+ if not self.Telescope.Connected:
+ QtWidgets.QMessageBox.critical(None, "Can not connect to telescope!",
+ f"Please check connection to\n{self.Name}.\nMaybe it is already in use.")
+ self.Telescope = None
+
+ def get_Parameter(self):
+ # has no parameter
+ return dict()
+
+ def is_open(self):
+ if self.Telescope is not None:
+ if self.Telescope.Connected:
+ return True
+ return False
+
+ def close(self):
+ if self.is_open():
+ self.Telescope.Connected = False
+ self.Telescope = None
+ self.Name = ""
+
+ def sendCommandBlind(self, cmd):
+ if self.is_open():
+ self.dbgMsg(f'sendCommandBlind: {cmd}')
+ try:
+ ret = self.Telescope.CommandBlind(cmd, False)
+ except win32com.client.pywintypes.com_error as e:
+ print(f'ERROR in sendCommandBlind: {e}')
+ return None
+ else:
+ return ret
+ return None
+
+ # The :ED# command sends the LCD contents, coded with the char table of the SED1233 LCD controller.
+ # For any reason the COM interface or the win32com transforms this into unicode. Unfortunately the
+ # special characters of the SED1233 controller get mapped to the wrong unicode. Here we fix this
+ # with a translation table:
+ CharacterTranslationTable = {
+ 0x0d: ord('\n'),
+ # 0x2020: ord(' '),
+ 0xDF: ord('°'),
+ 0x7E: 0x2192, # ord('>'),
+ 0x7F: 0x2190, # ord('<'),
+ 0x18: 0x2191, # ord('^'),
+ 0x19: 0x2193, # ord('v'),
+ # bar graph symbols
+ 0x5F: 0x2582,
+ 0x81: 0x2583,
+ 0x201A: 0x2584, # raw: 0x82
+ 0x0192: 0x2585, # raw: 0x83
+ 0x201E: 0x2586, # raw: 0x84
+ 0x2026: 0x2587, # raw: 0x85
+ 0x2020: 0x2588, # raw: 0x86
+ }
+
+ def get_LCD(self):
+ if self.is_open():
+ try:
+ Response = self.Telescope.CommandString("ED", False)
+ except win32com.client.pywintypes.com_error as e:
+ # Sometimes the handbox needs long time for calculations and does not
+ # send the LCD contents before the ASCOM driver trows a timeout exception.
+ # Here we catch these timeout exceptions.
+ print(f'ERROR in get_LCD: {e}')
+ self.dbgMsg(f'get_LCD response: ED --> {Response}')
+ return Response[1:].translate(self.CharacterTranslationTable)
+ return None
diff --git a/src/AutoSTAR_remote.py b/src/AutoSTAR_remote.py
index 5a6a52f..f58ce7c 100644
--- a/src/AutoSTAR_remote.py
+++ b/src/AutoSTAR_remote.py
@@ -5,15 +5,23 @@
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
-import win32com.client
+
+try:
+ import ASCOM
+except ImportError:
+ has_ASCOM = False
+else:
+ has_ASCOM = True
+
+import UART
import AutoSTAR_remote_ui
-version = "V1.0.1"
+version = "V1.1.0"
-theme_selection = "Dark" # "Dark", "Light"
-LCD_polling_time = 1000 # milliseconds
-LCD_earlyUpdate_time = 200 # milliseconds
+theme_selection = "Dark" # "Dark", "Light"
+LCD_polling_time = 1000 # milliseconds
+LCD_earlyUpdate_time = 50 # milliseconds
"""
By watching the RS232 communication of the AutoStart Suit telescope control I found the following commands:
@@ -49,13 +57,27 @@
+ ? :EK63#
"""
+"""
+How the ASCOM driver sets time and date:
+
+21:06:26.686 UTCDate Set - 11.22.22 20:06:26
+21:06:26.686 SendString Transmitting #:GG#
+21:06:26.704 SendString Received -01
+21:06:26.708 SendChars Transmitting #:SL21:06:26# <-- 21:06
+21:06:26.744 SendChars Received 1
+21:06:26.744 SendChars Transmitting #:SC11.22.22# <-- 22. Nov 2022
+21:06:26.962 SendChars Received 1
+"""
+
+
class MainWin(QtWidgets.QMainWindow):
"""
AutoSTAR_remote main window.
"""
- def __init__(self):
+ def __init__(self, showDebugMessages=False):
super(MainWin, self).__init__()
+ self.showDebugMessages = showDebugMessages
self.ui = AutoSTAR_remote_ui.Ui_MainWindow()
self.ui.setupUi(self)
self.setWindowTitle(f'AutoSTAR_remote {version}')
@@ -63,9 +85,12 @@ def __init__(self):
font.setStyleHint(QtGui.QFont.TypeWriter)
font.setPointSizeF(10)
self.ui.plainTextEdit_LCD.setFont(font)
- # states
- self.Telescope = None
- self.TelescopeName = ""
+ self.ui.actionconnect_ASCOM.setEnabled(has_ASCOM)
+ # communication interface
+ self.Interface = None
+ # persistent settings
+ self.Settings = QtCore.QSettings()
+ self.dbgMsg(f'QSettings file: {self.Settings.fileName()}')
# LCD polling timer
self.PollingTimer = QtCore.QTimer()
self.PollingTimer.setSingleShot(True)
@@ -105,70 +130,78 @@ def __init__(self):
self.ui.pushButton_FocOut.pressed.connect(lambda: self.sendCommandBlind("F+"))
self.ui.pushButton_FocOut.released.connect(lambda: self.sendCommandBlind("FQ"))
+ def dbgMsg(self, msg):
+ if self.showDebugMessages:
+ print(f'DEBUG: {msg}')
@QtCore.pyqtSlot()
def closeEvent(self, event):
self.PollingTimer.stop()
- if self.Telescope is not None:
- if self.Telescope.Connected:
- self.Telescope.Connected = False
+ if self.Interface is not None:
+ self.Interface.close()
# proceed with close
event.accept()
+ def update_GuiOpenInterface(self):
+ """ update GUI elements after opening interface
+ """
+ self.ui.statusbar.showMessage(self.Interface.Name)
+ self.ui.actionconnect_ASCOM.setEnabled(False)
+ self.ui.actionconnect_UART.setEnabled(False)
+ self.ui.actiondisconnect.setEnabled(True)
+ self.ui.centralwidget.setEnabled(True)
+ if self.ui.actionpoll.isChecked():
+ if not self.PollingTimer.isActive():
+ self.PollingTimer.setInterval(LCD_earlyUpdate_time)
+ self.PollingTimer.start()
+ self.ui.actionupdate_now.setEnabled(True)
+
+ @QtCore.pyqtSlot()
+ def on_actionconnect_ASCOM_triggered(self):
+ self.Interface = ASCOM.ASCOM(showDebugMessages=self.showDebugMessages)
+ self.Interface.open()
+ if self.Interface.is_open():
+ self.update_GuiOpenInterface()
+ else:
+ self.Interface.close()
+ self.Interface = None
+
@QtCore.pyqtSlot()
- def on_actionconnect_triggered(self):
- try:
- Chooser = win32com.client.Dispatch("ASCOM.Utilities.Chooser")
- except win32com.client.pywintypes.com_error:
- QtWidgets.QMessageBox.critical(None, "Can not call ASCOM!",
- f"Is ASCOM installed?")
- return
- Chooser.DeviceType = 'Telescope'
- self.TelescopeName = Chooser.Choose(None)
- self.ui.statusbar.showMessage(self.TelescopeName)
- self.Telescope = win32com.client.Dispatch(self.TelescopeName)
- self.Telescope.Connected = True
- if not self.Telescope.Connected:
- QtWidgets.QMessageBox.critical(None, "Can not connect to telescope!",
- f"Please check connection to\n{self.TelescopeName}.\nMaybe it is already in use.")
+ def on_actionconnect_UART_triggered(self):
+ Parameter = {k: self.Settings.value(k) for k in self.Settings.allKeys()}
+ self.Interface = UART.UART(Parameter=Parameter, showDebugMessages=self.showDebugMessages)
+ self.Interface.open()
+ if self.Interface.is_open():
+ Parameter = self.Interface.get_Parameter()
+ for k, v in Parameter.items():
+ self.Settings.setValue(k, v)
+ self.Settings.sync()
+ self.update_GuiOpenInterface()
else:
- self.ui.actionconnect.setEnabled(False)
- self.ui.actiondisconnect.setEnabled(True)
- self.ui.centralwidget.setEnabled(True)
- if self.ui.actionpoll.isChecked():
- if not self.PollingTimer.isActive():
- self.PollingTimer.setInterval(LCD_earlyUpdate_time)
- self.PollingTimer.start()
- self.ui.actionupdate_now.setEnabled(True)
+ self.Interface.close()
+ self.Interface = None
@QtCore.pyqtSlot()
def on_actiondisconnect_triggered(self):
self.PollingTimer.stop()
- if self.Telescope is not None:
- if self.Telescope.Connected:
- self.Telescope.Connected = False
- self.ui.actionconnect.setEnabled(True)
- self.ui.actiondisconnect.setEnabled(False)
- self.ui.centralwidget.setEnabled(False)
- self.ui.actionupdate_now.setEnabled(False)
-
- def sendAction(self, param):
- if self.Telescope is not None:
- if self.Telescope.Connected:
- return self.Telescope.Action("handbox", param)
- return None
+ if self.Interface is not None:
+ Parameter = self.Interface.get_Parameter()
+ for k, v in Parameter.items():
+ self.Settings.setValue(k, v)
+ self.Settings.sync()
+ self.Interface.close()
+ self.Interface = None
+ self.ui.actionconnect_ASCOM.setEnabled(has_ASCOM)
+ self.ui.actionconnect_UART.setEnabled(True)
+ self.ui.actiondisconnect.setEnabled(False)
+ self.ui.centralwidget.setEnabled(False)
+ self.ui.actionupdate_now.setEnabled(False)
def sendCommandBlind(self, cmd):
- if self.Telescope is not None:
- if self.Telescope.Connected:
- try:
- ret = self.Telescope.CommandBlind(cmd, False)
- except win32com.client.pywintypes.com_error as e:
- print(f'sendCommandBlind: {e}')
- return None
- else:
- return ret
- return None
+ if self.Interface is not None:
+ return self.Interface.sendCommandBlind(cmd)
+ else:
+ return None
def buttonAction(self, cmd, long_cmd=None):
"""
@@ -186,46 +219,18 @@ def buttonAction(self, cmd, long_cmd=None):
self.PollingTimer.setInterval(LCD_earlyUpdate_time)
self.PollingTimer.start()
- # The :ED# command sends the LCD contents, coded with the char table of the SED1233 LCD controller.
- # For any reason the COM interface or the win32com transforms this into unicode. Unfortunately the
- # special characters of the SED1233 controller get mapped to the wrong unicode. Here we fix this
- # with a translation table:
- CharacterTranslationTable = {
- 0x0d: ord('\n'),
- #0x2020: ord(' '),
- 0xDF: ord('°'),
- 0x7E: 0x2192, #ord('>'),
- 0x7F: 0x2190, #ord('<'),
- 0x18: 0x2191, #ord('^'),
- 0x19: 0x2193, #ord('v'),
- # bar graph symbols
- 0x5F: 0x2582,
- 0x81: 0x2583,
- 0x201A: 0x2584, # raw: 0x82
- 0x0192: 0x2585, # raw: 0x83
- 0x201E: 0x2586, # raw: 0x84
- 0x2026: 0x2587, # raw: 0x85
- 0x2020: 0x2588, # raw: 0x86
- }
-
def updateLCD(self):
- try:
- LcdText = self.Telescope.CommandString("ED", False)
- except win32com.client.pywintypes.com_error as e:
- # Sometimes the handbox needs long time for calculations and does not
- # send the LCD contents bfore the ASCOM driver trows a timeout exception.
- # Here we catch these timeout exceptions.
- print(f'updateLCD: {e}')
- LcdText = None
+ LcdText = None
+ if self.Interface is not None:
+ LcdText = self.Interface.get_LCD()
if LcdText is not None:
- LcdText = LcdText.translate(self.CharacterTranslationTable)
- Unknown = ord(LcdText[0])
- Line1 = LcdText[1:17]
- Line2 = LcdText[17:]
+ Line1 = LcdText[0:16]
+ Line2 = LcdText[16:]
self.ui.plainTextEdit_LCD.setPlainText(f'{Line1}\n{Line2}')
- #print(f'{Unknown}: >{Line1}< >{Line2}<')
- #print(", ".join([f'{ord(c):02X}' for c in LcdText]))
- #print(bytes(LcdText, 'utf-8'))
+ # print(", ".join([f'{ord(c):02X}' for c in LcdText]))
+ # print(bytes(LcdText, 'utf-8'))
+ else:
+ self.dbgMsg('No response from get_LCD.')
if self.ui.actionpoll.isChecked():
if not self.PollingTimer.isActive():
self.PollingTimer.setInterval(LCD_polling_time)
@@ -245,15 +250,20 @@ def on_actionpoll_toggled(self, isChecked):
self.PollingTimer.stop()
-## Start Qt event loop unless running in interactive mode.
+# Start Qt event loop unless running in interactive mode.
def main():
+ import argparse
+ parser = argparse.ArgumentParser(description="remote control for MEADE AutoSTAR #497")
+ parser.add_argument("-d", "--debug", action="store_true",
+ help="enable debug messages")
+ args = parser.parse_args()
# build application
App = QtWidgets.QApplication(sys.argv)
App.setOrganizationName("GeierSoft")
App.setOrganizationDomain("Astro")
App.setApplicationName("AutoSTAR_remote")
#
- # stolen from https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets
+ # copied from https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets
if theme_selection == 'Dark':
App.setStyle("Fusion")
#
@@ -284,13 +294,12 @@ def main():
else:
pass
#
- MainWindow = MainWin()
- #MainWindow.resize(1400, 900)
+ MainWindow = MainWin(showDebugMessages=args.debug)
+ # MainWindow.resize(1400, 900)
MainWindow.show()
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
- #QtGui.QApplication.instance().exec_()
sys.exit(App.exec_())
if __name__ == '__main__':
- main()
\ No newline at end of file
+ main()
diff --git a/src/AutoSTAR_remote_ui.py b/src/AutoSTAR_remote_ui.py
index 15d4e4f..c0969a1 100644
--- a/src/AutoSTAR_remote_ui.py
+++ b/src/AutoSTAR_remote_ui.py
@@ -11,7 +11,7 @@
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
- MainWindow.resize(315, 475)
+ MainWindow.resize(315, 503)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setEnabled(False)
self.centralwidget.setObjectName("centralwidget")
@@ -111,9 +111,9 @@ def setupUi(self, MainWindow):
MainWindow.setStatusBar(self.statusbar)
self.actionselect = QtWidgets.QAction(MainWindow)
self.actionselect.setObjectName("actionselect")
- self.actionconnect = QtWidgets.QAction(MainWindow)
- self.actionconnect.setEnabled(True)
- self.actionconnect.setObjectName("actionconnect")
+ self.actionconnect_ASCOM = QtWidgets.QAction(MainWindow)
+ self.actionconnect_ASCOM.setEnabled(True)
+ self.actionconnect_ASCOM.setObjectName("actionconnect_ASCOM")
self.actiondisconnect = QtWidgets.QAction(MainWindow)
self.actiondisconnect.setEnabled(False)
self.actiondisconnect.setObjectName("actiondisconnect")
@@ -124,7 +124,10 @@ def setupUi(self, MainWindow):
self.actionupdate_now = QtWidgets.QAction(MainWindow)
self.actionupdate_now.setEnabled(False)
self.actionupdate_now.setObjectName("actionupdate_now")
- self.menuTelescope.addAction(self.actionconnect)
+ self.actionconnect_UART = QtWidgets.QAction(MainWindow)
+ self.actionconnect_UART.setObjectName("actionconnect_UART")
+ self.menuTelescope.addAction(self.actionconnect_ASCOM)
+ self.menuTelescope.addAction(self.actionconnect_UART)
self.menuTelescope.addAction(self.actiondisconnect)
self.menuDisplay.addAction(self.actionpoll)
self.menuDisplay.addAction(self.actionupdate_now)
@@ -186,10 +189,11 @@ def retranslateUi(self, MainWindow):
self.menuTelescope.setTitle(_translate("MainWindow", "Telescope"))
self.menuDisplay.setTitle(_translate("MainWindow", "LCD"))
self.actionselect.setText(_translate("MainWindow", "select"))
- self.actionconnect.setText(_translate("MainWindow", "connect"))
+ self.actionconnect_ASCOM.setText(_translate("MainWindow", "connect ASCOM"))
self.actiondisconnect.setText(_translate("MainWindow", "disconnect"))
self.actionpoll.setText(_translate("MainWindow", "poll"))
self.actionupdate_now.setText(_translate("MainWindow", "update now"))
+ self.actionconnect_UART.setText(_translate("MainWindow", "connect UART"))
if __name__ == "__main__":
diff --git a/src/AutoSTAR_remote_ui.ui b/src/AutoSTAR_remote_ui.ui
index 2138b18..4863f99 100644
--- a/src/AutoSTAR_remote_ui.ui
+++ b/src/AutoSTAR_remote_ui.ui
@@ -7,7 +7,7 @@
0
0
315
- 475
+ 503
@@ -282,7 +282,8 @@
Telescope
-
+
+
-
+
true
- connect
+ connect ASCOM
@@ -336,6 +337,11 @@
update now
+
+
+ connect UART
+
+
diff --git a/src/UART.py b/src/UART.py
new file mode 100644
index 0000000..9575e82
--- /dev/null
+++ b/src/UART.py
@@ -0,0 +1,239 @@
+"""
+UART interface for AutoSTAR_remote
+"""
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+import sys
+import os
+import datetime
+import serial
+import serial.tools.list_ports
+
+import UART_ui
+
+
+class UART(QtWidgets.QDialog):
+
+ def __init__(self, Parameter={}, showDebugMessages=False):
+ super().__init__()
+ self.ui = UART_ui.Ui_Dialog()
+ self.ui.setupUi(self)
+ # populate GUI
+ self.find_Ports()
+ self.ui.comboBox_ComPort.addItems([p["desc"] for p in self.KnownPorts])
+ self.ui.comboBox_Speed.addItems([f'{s}' for s in [
+ 50, 75, 110, 134, 150, 200, 300, 600, 1200,
+ 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200
+ ]])
+ self.ui.comboBox_DataBits.addItems([f'{b}' for b in [5, 6, 7, 8]])
+ self.ui.comboBox_StopBits.addItems(["1", "1.5", "2"])
+ self.ui.comboBox_Parity.addItems(["none", "even", "odd", "mark", "space"])
+ self.ui.checkBox_RtsCts.setChecked(False)
+ self.ui.checkBox_DsrDtr.setChecked(False)
+ self.ui.checkBox_XonXoff.setChecked(False)
+ self.ui.checkBox_SetTimeDate.setChecked(True)
+ # set defaults
+ self.set_Parameter(Parameter)
+ # no port opened
+ self.PortHandle = None
+ self.Name = ""
+ #
+ self.showDebugMessages = showDebugMessages
+
+ def dbgMsg(self, msg):
+ if self.showDebugMessages:
+ print(f'DEBUG: {msg}')
+
+ def get_Parameter(self):
+ Parameter = {
+ "port": self.KnownPorts[self.ui.comboBox_ComPort.currentIndex()]["port"],
+ "baudrate": self.ui.comboBox_Speed.currentText(),
+ "bytesize": self.ui.comboBox_DataBits.currentText(),
+ "parity": self.ui.comboBox_Parity.currentText(),
+ "stopbits": self.ui.comboBox_StopBits.currentText(),
+ "xonxoff": str(self.ui.checkBox_XonXoff.isChecked()),
+ "rtscts": str(self.ui.checkBox_RtsCts.isChecked()),
+ "dsrdtr": str(self.ui.checkBox_DsrDtr.isChecked()),
+ "setTimeDate": str(self.ui.checkBox_SetTimeDate.isChecked()),
+ }
+ return Parameter
+
+ def set_Parameter(self, Parameter):
+ for i in range(self.ui.comboBox_ComPort.count()):
+ if self.KnownPorts[i]["port"] == Parameter.get("port", ""):
+ self.ui.comboBox_ComPort.setCurrentIndex(i)
+ break
+ def set_Current(cb, text):
+ for j in range(cb.count()):
+ if cb.itemText(j) == text:
+ cb.setCurrentIndex(j)
+ break
+ set_Current(self.ui.comboBox_Speed, Parameter.get("baudrate", "9600"))
+ set_Current(self.ui.comboBox_DataBits, Parameter.get("bytesize", "8"))
+ set_Current(self.ui.comboBox_Parity, Parameter.get("parity", "none"))
+ set_Current(self.ui.comboBox_StopBits, Parameter.get("stopbits", "1"))
+ self.ui.checkBox_XonXoff.setChecked(Parameter.get("xonxoff", "") == "True")
+ self.ui.checkBox_RtsCts.setChecked(Parameter.get("rtscts", "") == "True")
+ self.ui.checkBox_DsrDtr.setChecked(Parameter.get("dsrdtr", "") == "True")
+ self.ui.checkBox_SetTimeDate.setChecked(Parameter.get("setDateTime", "True") == "True")
+
+ def find_Ports(self, include_links=False):
+ iterator = sorted(serial.tools.list_ports.comports(include_links=include_links))
+ self.KnownPorts = []
+ for port, desc, hwid in iterator:
+ self.KnownPorts.append({"port": port, "desc": desc, "hwid": hwid})
+
+ def open(self, timeout=2.0, write_timeout=None, inter_byte_timeout=None, exclusive=False):
+ ret = self.exec_()
+ if (ret == self.Accepted) and (len(self.KnownPorts) > 0):
+ self.Name = self.ui.comboBox_ComPort.currentText()
+ port = self.KnownPorts[self.ui.comboBox_ComPort.currentIndex()]["port"]
+ baudrate = int(self.ui.comboBox_Speed.currentText())
+ bytesize = {
+ "5": serial.FIVEBITS, "6": serial.SIXBITS, "7": serial.SEVENBITS, "8": serial.EIGHTBITS
+ }[self.ui.comboBox_DataBits.currentText()]
+ parity = {
+ "none": serial.PARITY_NONE, "even": serial.PARITY_EVEN, "odd": serial.PARITY_ODD,
+ "mark": serial.PARITY_MARK, "space": serial.PARITY_SPACE
+ }[self.ui.comboBox_Parity.currentText()]
+ stopbits = {
+ "1": serial.STOPBITS_ONE, "1.5": serial.STOPBITS_ONE_POINT_FIVE, "2": serial.STOPBITS_TWO
+ }[self.ui.comboBox_StopBits.currentText()]
+ xonxoff = self.ui.checkBox_XonXoff.isChecked()
+ rtscts = self.ui.checkBox_RtsCts.isChecked()
+ dsrdtr = self.ui.checkBox_DsrDtr.isChecked()
+ if os.name == "nt":
+ # windows supports exclusive access only!
+ exclusive = True
+ try:
+ self.PortHandle = serial.Serial(
+ port=port, baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits,
+ timeout=timeout, xonxoff=xonxoff, rtscts=rtscts, dsrdtr=dsrdtr, write_timeout=write_timeout,
+ inter_byte_timeout=inter_byte_timeout, exclusive=exclusive
+ )
+ except serial.SerialException as e:
+ QtWidgets.QMessageBox.critical(None, "Can not open UART port!", f"{e}")
+ self.PortHandle = None
+ self.initializeCommunication()
+
+ def initializeCommunication(self):
+ if self.is_open():
+ # clear the buffer
+ self.PortHandle.write('#'.encode("ascii"))
+ # Attempting manual bypass of prompts
+ for i in range(10):
+ self.sendCommandBlind("EK9")
+ self.get_LCD()
+ # set date and time
+ if self.ui.checkBox_SetTimeDate.isChecked():
+ # TODO: make this aware of the daylight saving setting of the controller!
+ now = datetime.datetime.now()
+ time = now.strftime("%H:%M:%S")
+ cmd = f'#:SL{time}#'.encode("ascii")
+ self.PortHandle.write(cmd)
+ Response = self.PortHandle.read(size=1)
+ self.dbgMsg(f'{cmd} --> {Response}')
+ # MM/DD/YY
+ date = now.strftime("%m.%d.%y")
+ cmd = f'#:SC{date}#'.encode("ascii")
+ self.PortHandle.write(cmd)
+ Response = self.PortHandle.read(size=66)
+ self.dbgMsg(f'{cmd} --> {Response}')
+
+ def is_open(self):
+ if self.PortHandle is not None:
+ return self.PortHandle.is_open
+ else:
+ return False
+
+ def close(self):
+ if self.PortHandle is not None:
+ self.PortHandle.close()
+ self.PortHandle = None
+ self.Name = ""
+
+ def sendCommandBlind(self, cmd):
+ if self.is_open():
+ MeadeCmd = f'#:{cmd}#'.encode("ascii")
+ self.PortHandle.write(MeadeCmd)
+ self.PortHandle.flush()
+ self.dbgMsg(f'sendCommandBlind: {MeadeCmd}')
+
+ # The :ED# command sends the LCD contents, coded with the char table of the SED1233 LCD controller.
+ # For any reason the COM interface or the win32com transforms this into unicode. Unfortunately the
+ # special characters of the SED1233 controller get mapped to the wrong unicode. Here we fix this
+ # with a translation table:
+ CharacterTranslationTable = {
+ 0x0d: ord('\n'),
+ # 0x2020: ord(' '),
+ 0xDF: ord('°'),
+ 0x7E: 0x2192, # ord('>'),
+ 0x7F: 0x2190, # ord('<'),
+ 0x18: 0x2191, # ord('^'),
+ 0x19: 0x2193, # ord('v'),
+ # bar graph symbols
+ 0x5F: 0x2582,
+ 0x81: 0x2583,
+ 0x82: 0x2584, # raw: 0x82
+ 0x83: 0x2585, # raw: 0x83
+ 0x84: 0x2586, # raw: 0x84
+ 0x85: 0x2587, # raw: 0x85
+ 0x86: 0x2588, # raw: 0x86
+ }
+
+ def get_LCD(self):
+ if self.is_open():
+ MeadeCmd = b'#:ED#'
+ self.PortHandle.write(MeadeCmd)
+ self.PortHandle.flush()
+ Response = self.PortHandle.read_until(b"#")
+ self.dbgMsg(f'get_LCD response: {MeadeCmd} --> {Response}')
+ Response = Response[1:].rstrip(b"#")
+ Response = Response.decode("latin-1")
+ return Response.translate(self.CharacterTranslationTable)
+ return None
+
+
+
+if __name__ == '__main__':
+ # for debugging only
+ theme_selection = "Dark" # "Dark", "Light"
+ App = QtWidgets.QApplication(sys.argv)
+ App.setOrganizationName("GeierSoft")
+ App.setOrganizationDomain("Astro")
+ App.setApplicationName("AutoSTAR_remote")
+ # copied from https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets
+ if theme_selection == 'Dark':
+ App.setStyle("Fusion")
+ #
+ # # Now use a palette to switch to dark colors:
+ dark_palette = QtGui.QPalette()
+ dark_palette.setColor(QtGui.QPalette.Window, QtGui.QColor(53, 53, 53))
+ dark_palette.setColor(QtGui.QPalette.WindowText, QtCore.Qt.white)
+ dark_palette.setColor(QtGui.QPalette.Base, QtGui.QColor(35, 35, 35))
+ dark_palette.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(53, 53, 53))
+ dark_palette.setColor(QtGui.QPalette.ToolTipBase, QtGui.QColor(25, 25, 25))
+ dark_palette.setColor(QtGui.QPalette.ToolTipText, QtCore.Qt.white)
+ dark_palette.setColor(QtGui.QPalette.Text, QtCore.Qt.white)
+ dark_palette.setColor(QtGui.QPalette.Button, QtGui.QColor(53, 53, 53))
+ dark_palette.setColor(QtGui.QPalette.ButtonText, QtCore.Qt.white)
+ dark_palette.setColor(QtGui.QPalette.BrightText, QtCore.Qt.red)
+ dark_palette.setColor(QtGui.QPalette.Link, QtGui.QColor(42, 130, 218))
+ dark_palette.setColor(QtGui.QPalette.Highlight, QtGui.QColor(42, 130, 218))
+ dark_palette.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(35, 35, 35))
+ dark_palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Button, QtGui.QColor(53, 53, 53))
+ dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, QtCore.Qt.darkGray)
+ dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, QtCore.Qt.darkGray)
+ dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtCore.Qt.darkGray)
+ dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Light, QtGui.QColor(53, 53, 53))
+ App.setPalette(dark_palette)
+ elif theme_selection == 'Light':
+ App.setStyle("")
+ pass
+ else:
+ pass
+ #
+ UartDialog = UART()
+ UartDialog.show()
+ if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
+ sys.exit(App.exec_())
diff --git a/src/UART_ui.py b/src/UART_ui.py
new file mode 100644
index 0000000..db6bef2
--- /dev/null
+++ b/src/UART_ui.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'src\UART_ui.ui'
+#
+# Created by: PyQt5 UI code generator 5.9.2
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(390, 139)
+ self.gridLayout = QtWidgets.QGridLayout(Dialog)
+ self.gridLayout.setObjectName("gridLayout")
+ self.label = QtWidgets.QLabel(Dialog)
+ self.label.setObjectName("label")
+ self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
+ self.label_5 = QtWidgets.QLabel(Dialog)
+ self.label_5.setObjectName("label_5")
+ self.gridLayout.addWidget(self.label_5, 0, 4, 1, 1)
+ self.comboBox_Speed = QtWidgets.QComboBox(Dialog)
+ self.comboBox_Speed.setObjectName("comboBox_Speed")
+ self.gridLayout.addWidget(self.comboBox_Speed, 0, 5, 1, 1)
+ self.label_2 = QtWidgets.QLabel(Dialog)
+ self.label_2.setObjectName("label_2")
+ self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
+ self.comboBox_DataBits = QtWidgets.QComboBox(Dialog)
+ self.comboBox_DataBits.setObjectName("comboBox_DataBits")
+ self.gridLayout.addWidget(self.comboBox_DataBits, 1, 1, 1, 1)
+ self.label_3 = QtWidgets.QLabel(Dialog)
+ self.label_3.setObjectName("label_3")
+ self.gridLayout.addWidget(self.label_3, 1, 2, 1, 1)
+ self.comboBox_StopBits = QtWidgets.QComboBox(Dialog)
+ self.comboBox_StopBits.setObjectName("comboBox_StopBits")
+ self.gridLayout.addWidget(self.comboBox_StopBits, 1, 3, 1, 1)
+ self.label_4 = QtWidgets.QLabel(Dialog)
+ self.label_4.setObjectName("label_4")
+ self.gridLayout.addWidget(self.label_4, 1, 4, 1, 1)
+ self.comboBox_Parity = QtWidgets.QComboBox(Dialog)
+ self.comboBox_Parity.setObjectName("comboBox_Parity")
+ self.gridLayout.addWidget(self.comboBox_Parity, 1, 5, 1, 1)
+ self.label_6 = QtWidgets.QLabel(Dialog)
+ self.label_6.setObjectName("label_6")
+ self.gridLayout.addWidget(self.label_6, 2, 0, 1, 1)
+ self.checkBox_RtsCts = QtWidgets.QCheckBox(Dialog)
+ self.checkBox_RtsCts.setObjectName("checkBox_RtsCts")
+ self.gridLayout.addWidget(self.checkBox_RtsCts, 2, 1, 1, 1)
+ self.checkBox_DsrDtr = QtWidgets.QCheckBox(Dialog)
+ self.checkBox_DsrDtr.setObjectName("checkBox_DsrDtr")
+ self.gridLayout.addWidget(self.checkBox_DsrDtr, 2, 3, 1, 1)
+ self.checkBox_XonXoff = QtWidgets.QCheckBox(Dialog)
+ self.checkBox_XonXoff.setObjectName("checkBox_XonXoff")
+ self.gridLayout.addWidget(self.checkBox_XonXoff, 2, 5, 1, 1)
+ self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.gridLayout.addWidget(self.buttonBox, 4, 0, 1, 6)
+ self.comboBox_ComPort = QtWidgets.QComboBox(Dialog)
+ self.comboBox_ComPort.setObjectName("comboBox_ComPort")
+ self.gridLayout.addWidget(self.comboBox_ComPort, 0, 1, 1, 3)
+ self.label_7 = QtWidgets.QLabel(Dialog)
+ self.label_7.setObjectName("label_7")
+ self.gridLayout.addWidget(self.label_7, 3, 0, 1, 1)
+ self.checkBox_SetTimeDate = QtWidgets.QCheckBox(Dialog)
+ self.checkBox_SetTimeDate.setObjectName("checkBox_SetTimeDate")
+ self.gridLayout.addWidget(self.checkBox_SetTimeDate, 3, 1, 1, 3)
+
+ self.retranslateUi(Dialog)
+ self.buttonBox.accepted.connect(Dialog.accept)
+ self.buttonBox.rejected.connect(Dialog.reject)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.label.setText(_translate("Dialog", "COM port"))
+ self.label_5.setText(_translate("Dialog", "Speed"))
+ self.label_2.setText(_translate("Dialog", "Data bits"))
+ self.label_3.setText(_translate("Dialog", "Stop bits"))
+ self.label_4.setText(_translate("Dialog", "Parity"))
+ self.label_6.setText(_translate("Dialog", "Flow control"))
+ self.checkBox_RtsCts.setText(_translate("Dialog", "RTS/CTS"))
+ self.checkBox_DsrDtr.setText(_translate("Dialog", "DSR/DTR"))
+ self.checkBox_XonXoff.setText(_translate("Dialog", "XON/XOFF"))
+ self.label_7.setText(_translate("Dialog", "Telescope"))
+ self.checkBox_SetTimeDate.setText(_translate("Dialog", "Set current date and time"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ Dialog = QtWidgets.QDialog()
+ ui = Ui_Dialog()
+ ui.setupUi(Dialog)
+ Dialog.show()
+ sys.exit(app.exec_())
+
diff --git a/src/UART_ui.ui b/src/UART_ui.ui
new file mode 100644
index 0000000..f5e894c
--- /dev/null
+++ b/src/UART_ui.ui
@@ -0,0 +1,156 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 390
+ 139
+
+
+
+ Dialog
+
+
+ -
+
+
+ COM port
+
+
+
+ -
+
+
+ Speed
+
+
+
+ -
+
+
+ -
+
+
+ Data bits
+
+
+
+ -
+
+
+ -
+
+
+ Stop bits
+
+
+
+ -
+
+
+ -
+
+
+ Parity
+
+
+
+ -
+
+
+ -
+
+
+ Flow control
+
+
+
+ -
+
+
+ RTS/CTS
+
+
+
+ -
+
+
+ DSR/DTR
+
+
+
+ -
+
+
+ XON/XOFF
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+ -
+
+
+ -
+
+
+ Telescope
+
+
+
+ -
+
+
+ Set current date and time
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+