Skip to content

Commit 78c4853

Browse files
committed
initial release (V1.0)
1 parent c6511f0 commit 78c4853

File tree

7 files changed

+877
-1
lines changed

7 files changed

+877
-1
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ share/python-wheels/
2626
.installed.cfg
2727
*.egg
2828
MANIFEST
29+
.idea
2930

3031
# PyInstaller
3132
# Usually these files are written by a python script from a template
3233
# before PyInstaller builds the exe, so as to inject date/other infos into it.
3334
*.manifest
34-
*.spec
35+
#*.spec
3536

3637
# Installer logs
3738
pip-log.txt
@@ -127,3 +128,4 @@ dmypy.json
127128

128129
# Pyre type checker
129130
.pyre/
131+
environment.yml

AutoSTAR_remote.spec

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
3+
import os.path
4+
5+
block_cipher = None
6+
7+
8+
a = Analysis(['src\\AutoSTAR_remote.py'],
9+
pathex=[os.path.abspath(SPECPATH)],
10+
binaries=[],
11+
datas=[],
12+
hiddenimports=[],
13+
hookspath=[],
14+
runtime_hooks=[],
15+
excludes=[
16+
"readline", "email", "doctest",
17+
],
18+
win_no_prefer_redirects=False,
19+
win_private_assemblies=False,
20+
cipher=block_cipher,
21+
noarchive=False)
22+
pyz = PYZ(a.pure, a.zipped_data,
23+
cipher=block_cipher)
24+
exe = EXE(pyz,
25+
a.scripts,
26+
[],
27+
exclude_binaries=True,
28+
name='AutoSTAR_remote',
29+
debug=False,
30+
bootloader_ignore_signals=False,
31+
strip=False,
32+
upx=True,
33+
console=False )
34+
coll = COLLECT(exe,
35+
a.binaries,
36+
a.zipfiles,
37+
a.datas,
38+
strip=False,
39+
upx=True,
40+
upx_exclude=[],
41+
name='AutoSTAR_remote')

make_Exe.bat

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
del /S /Q dist\AutoSTAR_remote > NUL:
2+
rd /S /Q dist\AutoSTAR_remote
3+
4+
del /S /Q build > NUL:
5+
rd /S /Q build
6+
7+
call conda env export > environment.yml
8+
9+
pyinstaller AutoSTAR_remote.spec
10+
if %errorlevel% neq 0 exit /b %errorlevel%
11+

make_UI.bat

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

src/AutoSTAR_remote.py

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
"""
2+
This is a remote control program for the Meade #497 telescope control.
3+
It requires the Meade ASCOM driver.
4+
"""
5+
6+
from PyQt5 import QtCore, QtGui, QtWidgets
7+
import sys
8+
import win32com.client
9+
10+
import AutoSTAR_remote_ui
11+
12+
version = "V1.0"
13+
14+
theme_selection = "Dark" # "Dark", "Light"
15+
LCD_polling_time = 1000 # milliseconds
16+
17+
"""
18+
By watching the RS232 communication of the AutoStart Suit telescope control I found the following commands:
19+
- on a regular base the display is read with :ED#
20+
- more seldom but also regular the telescop position is read with :GR# and :GD#
21+
- the buttons send the following commands:
22+
+ ENTER :EK13#
23+
+ long ENTER :EK10#
24+
+ MODE :EK9#
25+
+ long MODE :EK11#
26+
+ GOTO :EK24#
27+
+ long GOTO :EK25#
28+
+ North :Mn# when button gets pressed, :Qn# when button gets released
29+
+ West :Me# when button gets pressed, :Qw# when button gets released (yes, West sends the slew east command!)
30+
+ East :Mw# when button gets pressed, :Qe# when button gets released (yes, West sends the slew west command!)
31+
+ South :Ms# when button gets pressed, :Qs# when button gets released
32+
+ 1 :EK49#
33+
+ 2 :EK50#
34+
+ 3 :EK51#
35+
+ 4 :EK52#
36+
+ 5 :EK53#
37+
+ 6 :EK54#
38+
+ 7 :EK55#
39+
+ 8 :EK56#
40+
+ 9 :EK57#
41+
+ 0 :EK48#
42+
+ Foc.In :F-# repeated as long as the button is pressed, :FQ# when button gets released
43+
+ Foc. Out :F+# repeated as long as the button is pressed, :FQ# when button gets released
44+
+ Scroll Up :EK94#
45+
+ Scroll Down :EK118#
46+
+ Back :EK87#
47+
+ Fwd :EK69#
48+
+ ? :EK63#
49+
"""
50+
51+
class MainWin(QtWidgets.QMainWindow):
52+
"""
53+
AutoSTAR_remote main window.
54+
"""
55+
56+
def __init__(self):
57+
super(MainWin, self).__init__()
58+
self.ui = AutoSTAR_remote_ui.Ui_MainWindow()
59+
self.ui.setupUi(self)
60+
self.setWindowTitle(f'AutoSTAR_remote {version}')
61+
font = QtGui.QFont("monospace")
62+
font.setStyleHint(QtGui.QFont.TypeWriter)
63+
font.setPointSizeF(10)
64+
self.ui.plainTextEdit_LCD.setFont(font)
65+
# states
66+
self.Telescope = None
67+
self.TelescopeName = ""
68+
# LCD polling timer
69+
self.PollingTimer = QtCore.QTimer()
70+
self.PollingTimer.setSingleShot(True)
71+
self.PollingTimer.setInterval(LCD_polling_time)
72+
self.PollingTimer.timeout.connect(self.updateLCD)
73+
# connect buttons
74+
self.ui.pushButton_Enter.clicked.connect(lambda: self.buttonAction("EK13", "EK10"))
75+
self.ui.pushButton_Mode.clicked.connect(lambda: self.buttonAction("EK9", "EK11"))
76+
self.ui.pushButton_Goto.clicked.connect(lambda: self.buttonAction("EK24", "EK25"))
77+
self.ui.pushButton_Num1.clicked.connect(lambda: self.buttonAction("EK49"))
78+
self.ui.pushButton_Num2.clicked.connect(lambda: self.buttonAction("EK50"))
79+
self.ui.pushButton_Num3.clicked.connect(lambda: self.buttonAction("EK51"))
80+
self.ui.pushButton_Num4.clicked.connect(lambda: self.buttonAction("EK52"))
81+
self.ui.pushButton_Num5.clicked.connect(lambda: self.buttonAction("EK53"))
82+
self.ui.pushButton_Num6.clicked.connect(lambda: self.buttonAction("EK54"))
83+
self.ui.pushButton_Num7.clicked.connect(lambda: self.buttonAction("EK55"))
84+
self.ui.pushButton_Num8.clicked.connect(lambda: self.buttonAction("EK56"))
85+
self.ui.pushButton_Num9.clicked.connect(lambda: self.buttonAction("EK57"))
86+
self.ui.pushButton_Num0.clicked.connect(lambda: self.buttonAction("EK48"))
87+
self.ui.pushButton_ScrollUp.clicked.connect(lambda: self.buttonAction("EK94"))
88+
self.ui.pushButton_ScrollDown.clicked.connect(lambda: self.buttonAction("EK118"))
89+
self.ui.pushButton_Back.clicked.connect(lambda: self.buttonAction("EK87"))
90+
self.ui.pushButton_Fwd.clicked.connect(lambda: self.buttonAction("EK69"))
91+
self.ui.pushButton_Help.clicked.connect(lambda: self.buttonAction("EK63"))
92+
# some buttons have actions when pressed and released
93+
self.ui.pushButton_North.pressed.connect(lambda: self.sendCommandBlind("Mn"))
94+
self.ui.pushButton_North.released.connect(lambda: self.sendCommandBlind("Qn"))
95+
self.ui.pushButton_West.pressed.connect(lambda: self.sendCommandBlind("Me"))
96+
self.ui.pushButton_West.released.connect(lambda: self.sendCommandBlind("Qw"))
97+
self.ui.pushButton_East.pressed.connect(lambda: self.sendCommandBlind("Mw"))
98+
self.ui.pushButton_East.released.connect(lambda: self.sendCommandBlind("Qe"))
99+
self.ui.pushButton_South.pressed.connect(lambda: self.sendCommandBlind("Ms"))
100+
self.ui.pushButton_South.released.connect(lambda: self.sendCommandBlind("Qs"))
101+
#
102+
self.ui.pushButton_FocIn.pressed.connect(lambda: self.sendCommandBlind("F-"))
103+
self.ui.pushButton_FocIn.released.connect(lambda: self.sendCommandBlind("FQ"))
104+
self.ui.pushButton_FocOut.pressed.connect(lambda: self.sendCommandBlind("F+"))
105+
self.ui.pushButton_FocOut.released.connect(lambda: self.sendCommandBlind("FQ"))
106+
107+
108+
@QtCore.pyqtSlot()
109+
def closeEvent(self, event):
110+
self.PollingTimer.stop()
111+
if self.Telescope is not None:
112+
if self.Telescope.Connected:
113+
self.Telescope.Connected = False
114+
# proceed with close
115+
event.accept()
116+
117+
@QtCore.pyqtSlot()
118+
def on_actionconnect_triggered(self):
119+
try:
120+
Chooser = win32com.client.Dispatch("ASCOM.Utilities.Chooser")
121+
except win32com.client.pywintypes.com_error:
122+
QtWidgets.QMessageBox.critical(None, "Can not call ASCOM!",
123+
f"Is ASCOM installed?")
124+
return
125+
Chooser.DeviceType = 'Telescope'
126+
self.TelescopeName = Chooser.Choose(None)
127+
self.ui.statusbar.showMessage(self.TelescopeName)
128+
self.Telescope = win32com.client.Dispatch(self.TelescopeName)
129+
self.Telescope.Connected = True
130+
if not self.Telescope.Connected:
131+
QtWidgets.QMessageBox.critical(None, "Can not connect to telescope!",
132+
f"Please check connection to\n{self.TelescopeName}.\nMaybe it is already in use.")
133+
else:
134+
self.ui.actionconnect.setEnabled(False)
135+
self.ui.actiondisconnect.setEnabled(True)
136+
self.ui.centralwidget.setEnabled(True)
137+
if self.ui.actionpoll.isChecked():
138+
self.PollingTimer.start()
139+
self.ui.actionupdate_now.setEnabled(True)
140+
141+
@QtCore.pyqtSlot()
142+
def on_actiondisconnect_triggered(self):
143+
self.PollingTimer.stop()
144+
if self.Telescope is not None:
145+
if self.Telescope.Connected:
146+
self.Telescope.Connected = False
147+
self.ui.actionconnect.setEnabled(True)
148+
self.ui.actiondisconnect.setEnabled(False)
149+
self.ui.centralwidget.setEnabled(False)
150+
self.ui.actionupdate_now.setEnabled(False)
151+
152+
def sendAction(self, param):
153+
if self.Telescope is not None:
154+
if self.Telescope.Connected:
155+
return self.Telescope.Action("handbox", param)
156+
return None
157+
158+
def sendCommandBlind(self, cmd):
159+
if self.Telescope is not None:
160+
if self.Telescope.Connected:
161+
return self.Telescope.CommandBlind(cmd, False)
162+
return None
163+
164+
def buttonAction(self, cmd, long_cmd=None):
165+
"""
166+
check if button was pressed with SHIFT and send commands cmd or cmd_long
167+
:param cmd: command for normal button click
168+
:param long_cmd: command for SHIFT+button click; if None -> use cmd
169+
"""
170+
modifiers = QtWidgets.QApplication.keyboardModifiers()
171+
if (modifiers == QtCore.Qt.ShiftModifier) and (long_cmd is not None):
172+
self.sendCommandBlind(long_cmd)
173+
else:
174+
self.sendCommandBlind(cmd)
175+
self.updateLCD()
176+
177+
# The :ED# command sends the LCD contents, coded withthe char table of the SED1233 LCD controller.
178+
# For any reason the COM interface or the win32com transforms this into unicode. Unfortunately the
179+
# special characters of the SED1233 controller get mapped to the wrong unicode. Here we fix this
180+
# with a translation table:
181+
CharacterTranslationTable = {
182+
0x0d: ord('\n'),
183+
#0x2020: ord(' '),
184+
0xDF: ord('°'),
185+
0x7E: 0x2192, #ord('>'),
186+
0x7F: 0x2190, #ord('<'),
187+
0x18: 0x2191, #ord('^'),
188+
0x19: 0x2193, #ord('v'),
189+
# bar graph symbols
190+
0x5F: 0x2582,
191+
0x81: 0x2583,
192+
0x201A: 0x2584, # raw: 0x82
193+
0x0192: 0x2585, # raw: 0x83
194+
0x201E: 0x2586, # raw: 0x84
195+
0x2026: 0x2587, # raw: 0x85
196+
0x2020: 0x2588, # raw: 0x86
197+
}
198+
199+
def updateLCD(self):
200+
#LcdText = self.sendAction("readdisplay")
201+
LcdText = self.Telescope.CommandString("ED", False)
202+
if LcdText is not None:
203+
LcdText = LcdText.translate(self.CharacterTranslationTable)
204+
Unknown = ord(LcdText[0])
205+
Line1 = LcdText[1:17]
206+
Line2 = LcdText[17:]
207+
self.ui.plainTextEdit_LCD.setPlainText(f'{Line1}\n{Line2}')
208+
#print(f'{Unknown}: >{Line1}< >{Line2}<')
209+
#print(", ".join([f'{ord(c):02X}' for c in LcdText]))
210+
#print(bytes(LcdText, 'utf-8'))
211+
if self.ui.actionpoll.isChecked():
212+
self.PollingTimer.start()
213+
214+
@QtCore.pyqtSlot()
215+
def on_actionupdate_now_triggered(self):
216+
self.updateLCD()
217+
218+
@QtCore.pyqtSlot("bool")
219+
def on_actionpoll_toggled(self, isChecked):
220+
if isChecked:
221+
# start polling timer
222+
self.PollingTimer.start()
223+
else:
224+
# stop polling timer
225+
self.PollingTimer.stop()
226+
227+
228+
## Start Qt event loop unless running in interactive mode.
229+
def main():
230+
# build application
231+
App = QtWidgets.QApplication(sys.argv)
232+
App.setOrganizationName("GeierSoft")
233+
App.setOrganizationDomain("Astro")
234+
App.setApplicationName("AutoSTAR_remote")
235+
#
236+
# stolen from https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets
237+
if theme_selection == 'Dark':
238+
App.setStyle("Fusion")
239+
#
240+
# # Now use a palette to switch to dark colors:
241+
dark_palette = QtGui.QPalette()
242+
dark_palette.setColor(QtGui.QPalette.Window, QtGui.QColor(53, 53, 53))
243+
dark_palette.setColor(QtGui.QPalette.WindowText, QtCore.Qt.white)
244+
dark_palette.setColor(QtGui.QPalette.Base, QtGui.QColor(35, 35, 35))
245+
dark_palette.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(53, 53, 53))
246+
dark_palette.setColor(QtGui.QPalette.ToolTipBase, QtGui.QColor(25, 25, 25))
247+
dark_palette.setColor(QtGui.QPalette.ToolTipText, QtCore.Qt.white)
248+
dark_palette.setColor(QtGui.QPalette.Text, QtCore.Qt.white)
249+
dark_palette.setColor(QtGui.QPalette.Button, QtGui.QColor(53, 53, 53))
250+
dark_palette.setColor(QtGui.QPalette.ButtonText, QtCore.Qt.white)
251+
dark_palette.setColor(QtGui.QPalette.BrightText, QtCore.Qt.red)
252+
dark_palette.setColor(QtGui.QPalette.Link, QtGui.QColor(42, 130, 218))
253+
dark_palette.setColor(QtGui.QPalette.Highlight, QtGui.QColor(42, 130, 218))
254+
dark_palette.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(35, 35, 35))
255+
dark_palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Button, QtGui.QColor(53, 53, 53))
256+
dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, QtCore.Qt.darkGray)
257+
dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, QtCore.Qt.darkGray)
258+
dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtCore.Qt.darkGray)
259+
dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Light, QtGui.QColor(53, 53, 53))
260+
App.setPalette(dark_palette)
261+
elif theme_selection == 'Light':
262+
App.setStyle("")
263+
pass
264+
else:
265+
pass
266+
#
267+
MainWindow = MainWin()
268+
#MainWindow.resize(1400, 900)
269+
MainWindow.show()
270+
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
271+
#QtGui.QApplication.instance().exec_()
272+
sys.exit(App.exec_())
273+
274+
275+
if __name__ == '__main__':
276+
main()

0 commit comments

Comments
 (0)