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 } .\n Maybe 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