Skip to content

Commit 954361b

Browse files
committed
implemented direct UART interface
1 parent 2e91a25 commit 954361b

File tree

8 files changed

+618
-94
lines changed

8 files changed

+618
-94
lines changed

make_UI.bat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
call pyuic5 -x src\AutoSTAR_remote_ui.ui -o src\AutoSTAR_remote_ui.py
2+
call pyuic5 -x src\UART_ui.ui -o src\UART_ui.py

src/ASCOM.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
ASCOM interface for AutoSTAR_remote
3+
"""
4+
5+
from PyQt5 import QtWidgets
6+
import win32com.client
7+
8+
9+
class ASCOM:
10+
11+
def __init__(self):
12+
self.Telescope = None
13+
self.Name = ""
14+
15+
def open(self):
16+
self.close()
17+
try:
18+
Chooser = win32com.client.Dispatch("ASCOM.Utilities.Chooser")
19+
except win32com.client.pywintypes.com_error:
20+
QtWidgets.QMessageBox.critical(None, "Can not call ASCOM!",
21+
f"Is ASCOM installed?")
22+
else:
23+
Chooser.DeviceType = 'Telescope'
24+
self.Name = Chooser.Choose(None)
25+
self.Telescope = win32com.client.Dispatch(self.Name)
26+
self.Telescope.Connected = True
27+
if not self.Telescope.Connected:
28+
QtWidgets.QMessageBox.critical(None, "Can not connect to telescope!",
29+
f"Please check connection to\n{self.Name}.\nMaybe it is already in use.")
30+
self.Telescope = None
31+
32+
def get_Parameter(self):
33+
# has no parameter
34+
return dict()
35+
36+
def is_open(self):
37+
if self.Telescope is not None:
38+
if self.Telescope.Connected:
39+
return True
40+
return False
41+
42+
def close(self):
43+
if self.is_open():
44+
self.Telescope.Connected = False
45+
self.Telescope = None
46+
self.Name = ""
47+
48+
def sendCommandBlind(self, cmd):
49+
if self.is_open():
50+
try:
51+
ret = self.Telescope.CommandBlind(cmd, False)
52+
except win32com.client.pywintypes.com_error as e:
53+
print(f'sendCommandBlind: {e}')
54+
return None
55+
else:
56+
return ret
57+
return None
58+
59+
def sendCommandString(self, cmd):
60+
if self.is_open():
61+
try:
62+
return self.Telescope.CommandString(cmd, False)
63+
except win32com.client.pywintypes.com_error as e:
64+
# Sometimes the handbox needs long time for calculations and does not
65+
# send the LCD contents before the ASCOM driver trows a timeout exception.
66+
# Here we catch these timeout exceptions.
67+
print(f'updateLCD: {e}')
68+
return None

src/AutoSTAR_remote.py

Lines changed: 91 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@
55

66
from PyQt5 import QtCore, QtGui, QtWidgets
77
import sys
8-
import win32com.client
8+
9+
try:
10+
import ASCOM
11+
except ImportError:
12+
has_ASCOM = False
13+
else:
14+
has_ASCOM = True
15+
16+
import UART
917

1018
import AutoSTAR_remote_ui
1119

12-
version = "V1.0.1"
20+
version = "V1.1.0"
1321

14-
theme_selection = "Dark" # "Dark", "Light"
15-
LCD_polling_time = 1000 # milliseconds
16-
LCD_earlyUpdate_time = 200 # milliseconds
22+
theme_selection = "Dark" # "Dark", "Light"
23+
LCD_polling_time = 1000 # milliseconds
24+
LCD_earlyUpdate_time = 200 # milliseconds
1725

1826
"""
1927
By watching the RS232 communication of the AutoStart Suit telescope control I found the following commands:
@@ -49,6 +57,7 @@
4957
+ ? :EK63#
5058
"""
5159

60+
5261
class MainWin(QtWidgets.QMainWindow):
5362
"""
5463
AutoSTAR_remote main window.
@@ -63,9 +72,12 @@ def __init__(self):
6372
font.setStyleHint(QtGui.QFont.TypeWriter)
6473
font.setPointSizeF(10)
6574
self.ui.plainTextEdit_LCD.setFont(font)
66-
# states
67-
self.Telescope = None
68-
self.TelescopeName = ""
75+
self.ui.actionconnect_ASCOM.setEnabled(has_ASCOM)
76+
# communication interface
77+
self.Interface = None
78+
# persistent settings
79+
self.Settings = QtCore.QSettings()
80+
print(f'QSettings file: {self.Settings.fileName()}')
6981
# LCD polling timer
7082
self.PollingTimer = QtCore.QTimer()
7183
self.PollingTimer.setSingleShot(True)
@@ -105,70 +117,71 @@ def __init__(self):
105117
self.ui.pushButton_FocOut.pressed.connect(lambda: self.sendCommandBlind("F+"))
106118
self.ui.pushButton_FocOut.released.connect(lambda: self.sendCommandBlind("FQ"))
107119

108-
109120
@QtCore.pyqtSlot()
110121
def closeEvent(self, event):
111122
self.PollingTimer.stop()
112-
if self.Telescope is not None:
113-
if self.Telescope.Connected:
114-
self.Telescope.Connected = False
123+
if self.Interface is not None:
124+
self.Interface.close()
115125
# proceed with close
116126
event.accept()
117127

128+
def update_GuiOpenInterface(self):
129+
""" update GUI elements after opening interface
130+
"""
131+
self.ui.statusbar.showMessage(self.Interface.Name)
132+
self.ui.actionconnect_ASCOM.setEnabled(False)
133+
self.ui.actionconnect_UART.setEnabled(False)
134+
self.ui.actiondisconnect.setEnabled(True)
135+
self.ui.centralwidget.setEnabled(True)
136+
if self.ui.actionpoll.isChecked():
137+
if not self.PollingTimer.isActive():
138+
self.PollingTimer.setInterval(LCD_earlyUpdate_time)
139+
self.PollingTimer.start()
140+
self.ui.actionupdate_now.setEnabled(True)
141+
118142
@QtCore.pyqtSlot()
119-
def on_actionconnect_triggered(self):
120-
try:
121-
Chooser = win32com.client.Dispatch("ASCOM.Utilities.Chooser")
122-
except win32com.client.pywintypes.com_error:
123-
QtWidgets.QMessageBox.critical(None, "Can not call ASCOM!",
124-
f"Is ASCOM installed?")
125-
return
126-
Chooser.DeviceType = 'Telescope'
127-
self.TelescopeName = Chooser.Choose(None)
128-
self.ui.statusbar.showMessage(self.TelescopeName)
129-
self.Telescope = win32com.client.Dispatch(self.TelescopeName)
130-
self.Telescope.Connected = True
131-
if not self.Telescope.Connected:
132-
QtWidgets.QMessageBox.critical(None, "Can not connect to telescope!",
133-
f"Please check connection to\n{self.TelescopeName}.\nMaybe it is already in use.")
143+
def on_actionconnect_ASCOM_triggered(self):
144+
self.Interface = ASCOM.ASCOM()
145+
self.Interface.open()
146+
if self.Interface.is_open():
147+
self.update_GuiOpenInterface()
134148
else:
135-
self.ui.actionconnect.setEnabled(False)
136-
self.ui.actiondisconnect.setEnabled(True)
137-
self.ui.centralwidget.setEnabled(True)
138-
if self.ui.actionpoll.isChecked():
139-
if not self.PollingTimer.isActive():
140-
self.PollingTimer.setInterval(LCD_earlyUpdate_time)
141-
self.PollingTimer.start()
142-
self.ui.actionupdate_now.setEnabled(True)
149+
self.Interface.close()
150+
self.Interface = None
151+
152+
@QtCore.pyqtSlot()
153+
def on_actionconnect_UART_triggered(self):
154+
Parameter = {k: self.Settings.value(k) for k in self.Settings.allKeys()}
155+
self.Interface = UART.UART(Parameter=Parameter)
156+
self.Interface.open()
157+
if self.Interface.is_open():
158+
print("DBG: UART is open")
159+
self.update_GuiOpenInterface()
160+
else:
161+
self.Interface.close()
162+
self.Interface = None
143163

144164
@QtCore.pyqtSlot()
145165
def on_actiondisconnect_triggered(self):
146166
self.PollingTimer.stop()
147-
if self.Telescope is not None:
148-
if self.Telescope.Connected:
149-
self.Telescope.Connected = False
150-
self.ui.actionconnect.setEnabled(True)
151-
self.ui.actiondisconnect.setEnabled(False)
152-
self.ui.centralwidget.setEnabled(False)
153-
self.ui.actionupdate_now.setEnabled(False)
154-
155-
def sendAction(self, param):
156-
if self.Telescope is not None:
157-
if self.Telescope.Connected:
158-
return self.Telescope.Action("handbox", param)
159-
return None
167+
if self.Interface is not None:
168+
Parameter = self.Interface.get_Parameter()
169+
for k, v in Parameter.items():
170+
self.Settings.setValue(k, v)
171+
self.Settings.sync()
172+
self.Interface.close()
173+
self.Interface = None
174+
self.ui.actionconnect_ASCOM.setEnabled(has_ASCOM)
175+
self.ui.actionconnect_UART.setEnabled(True)
176+
self.ui.actiondisconnect.setEnabled(False)
177+
self.ui.centralwidget.setEnabled(False)
178+
self.ui.actionupdate_now.setEnabled(False)
160179

161180
def sendCommandBlind(self, cmd):
162-
if self.Telescope is not None:
163-
if self.Telescope.Connected:
164-
try:
165-
ret = self.Telescope.CommandBlind(cmd, False)
166-
except win32com.client.pywintypes.com_error as e:
167-
print(f'sendCommandBlind: {e}')
168-
return None
169-
else:
170-
return ret
171-
return None
181+
if self.Interface is not None:
182+
return self.Interface.sendCommandBlind(cmd)
183+
else:
184+
return None
172185

173186
def buttonAction(self, cmd, long_cmd=None):
174187
"""
@@ -192,40 +205,35 @@ def buttonAction(self, cmd, long_cmd=None):
192205
# with a translation table:
193206
CharacterTranslationTable = {
194207
0x0d: ord('\n'),
195-
#0x2020: ord(' '),
208+
# 0x2020: ord(' '),
196209
0xDF: ord('°'),
197-
0x7E: 0x2192, #ord('>'),
198-
0x7F: 0x2190, #ord('<'),
199-
0x18: 0x2191, #ord('^'),
200-
0x19: 0x2193, #ord('v'),
210+
0x7E: 0x2192, # ord('>'),
211+
0x7F: 0x2190, # ord('<'),
212+
0x18: 0x2191, # ord('^'),
213+
0x19: 0x2193, # ord('v'),
201214
# bar graph symbols
202215
0x5F: 0x2582,
203216
0x81: 0x2583,
204-
0x201A: 0x2584, # raw: 0x82
205-
0x0192: 0x2585, # raw: 0x83
206-
0x201E: 0x2586, # raw: 0x84
207-
0x2026: 0x2587, # raw: 0x85
208-
0x2020: 0x2588, # raw: 0x86
217+
0x201A: 0x2584, # raw: 0x82
218+
0x0192: 0x2585, # raw: 0x83
219+
0x201E: 0x2586, # raw: 0x84
220+
0x2026: 0x2587, # raw: 0x85
221+
0x2020: 0x2588, # raw: 0x86
209222
}
210223

211224
def updateLCD(self):
212-
try:
213-
LcdText = self.Telescope.CommandString("ED", False)
214-
except win32com.client.pywintypes.com_error as e:
215-
# Sometimes the handbox needs long time for calculations and does not
216-
# send the LCD contents bfore the ASCOM driver trows a timeout exception.
217-
# Here we catch these timeout exceptions.
218-
print(f'updateLCD: {e}')
219-
LcdText = None
225+
LcdText = None
226+
if self.Interface is not None:
227+
LcdText = self.Interface.sendCommandString("ED")
220228
if LcdText is not None:
221229
LcdText = LcdText.translate(self.CharacterTranslationTable)
222230
Unknown = ord(LcdText[0])
223231
Line1 = LcdText[1:17]
224232
Line2 = LcdText[17:]
225233
self.ui.plainTextEdit_LCD.setPlainText(f'{Line1}\n{Line2}')
226-
#print(f'{Unknown}: >{Line1}< >{Line2}<')
227-
#print(", ".join([f'{ord(c):02X}' for c in LcdText]))
228-
#print(bytes(LcdText, 'utf-8'))
234+
# print(f'{Unknown}: >{Line1}< >{Line2}<')
235+
# print(", ".join([f'{ord(c):02X}' for c in LcdText]))
236+
# print(bytes(LcdText, 'utf-8'))
229237
if self.ui.actionpoll.isChecked():
230238
if not self.PollingTimer.isActive():
231239
self.PollingTimer.setInterval(LCD_polling_time)
@@ -245,15 +253,15 @@ def on_actionpoll_toggled(self, isChecked):
245253
self.PollingTimer.stop()
246254

247255

248-
## Start Qt event loop unless running in interactive mode.
256+
# Start Qt event loop unless running in interactive mode.
249257
def main():
250258
# build application
251259
App = QtWidgets.QApplication(sys.argv)
252260
App.setOrganizationName("GeierSoft")
253261
App.setOrganizationDomain("Astro")
254262
App.setApplicationName("AutoSTAR_remote")
255263
#
256-
# stolen from https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets
264+
# copied from https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets
257265
if theme_selection == 'Dark':
258266
App.setStyle("Fusion")
259267
#
@@ -285,12 +293,11 @@ def main():
285293
pass
286294
#
287295
MainWindow = MainWin()
288-
#MainWindow.resize(1400, 900)
296+
# MainWindow.resize(1400, 900)
289297
MainWindow.show()
290298
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
291-
#QtGui.QApplication.instance().exec_()
292299
sys.exit(App.exec_())
293300

294301

295302
if __name__ == '__main__':
296-
main()
303+
main()

src/AutoSTAR_remote_ui.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
class Ui_MainWindow(object):
1212
def setupUi(self, MainWindow):
1313
MainWindow.setObjectName("MainWindow")
14-
MainWindow.resize(315, 475)
14+
MainWindow.resize(315, 503)
1515
self.centralwidget = QtWidgets.QWidget(MainWindow)
1616
self.centralwidget.setEnabled(False)
1717
self.centralwidget.setObjectName("centralwidget")
@@ -111,9 +111,9 @@ def setupUi(self, MainWindow):
111111
MainWindow.setStatusBar(self.statusbar)
112112
self.actionselect = QtWidgets.QAction(MainWindow)
113113
self.actionselect.setObjectName("actionselect")
114-
self.actionconnect = QtWidgets.QAction(MainWindow)
115-
self.actionconnect.setEnabled(True)
116-
self.actionconnect.setObjectName("actionconnect")
114+
self.actionconnect_ASCOM = QtWidgets.QAction(MainWindow)
115+
self.actionconnect_ASCOM.setEnabled(True)
116+
self.actionconnect_ASCOM.setObjectName("actionconnect_ASCOM")
117117
self.actiondisconnect = QtWidgets.QAction(MainWindow)
118118
self.actiondisconnect.setEnabled(False)
119119
self.actiondisconnect.setObjectName("actiondisconnect")
@@ -124,7 +124,10 @@ def setupUi(self, MainWindow):
124124
self.actionupdate_now = QtWidgets.QAction(MainWindow)
125125
self.actionupdate_now.setEnabled(False)
126126
self.actionupdate_now.setObjectName("actionupdate_now")
127-
self.menuTelescope.addAction(self.actionconnect)
127+
self.actionconnect_UART = QtWidgets.QAction(MainWindow)
128+
self.actionconnect_UART.setObjectName("actionconnect_UART")
129+
self.menuTelescope.addAction(self.actionconnect_ASCOM)
130+
self.menuTelescope.addAction(self.actionconnect_UART)
128131
self.menuTelescope.addAction(self.actiondisconnect)
129132
self.menuDisplay.addAction(self.actionpoll)
130133
self.menuDisplay.addAction(self.actionupdate_now)
@@ -186,10 +189,11 @@ def retranslateUi(self, MainWindow):
186189
self.menuTelescope.setTitle(_translate("MainWindow", "Telescope"))
187190
self.menuDisplay.setTitle(_translate("MainWindow", "LCD"))
188191
self.actionselect.setText(_translate("MainWindow", "select"))
189-
self.actionconnect.setText(_translate("MainWindow", "connect"))
192+
self.actionconnect_ASCOM.setText(_translate("MainWindow", "connect ASCOM"))
190193
self.actiondisconnect.setText(_translate("MainWindow", "disconnect"))
191194
self.actionpoll.setText(_translate("MainWindow", "poll"))
192195
self.actionupdate_now.setText(_translate("MainWindow", "update now"))
196+
self.actionconnect_UART.setText(_translate("MainWindow", "connect UART"))
193197

194198

195199
if __name__ == "__main__":

0 commit comments

Comments
 (0)