Oh by the way, if you're here you should already know what Stellarium is for, but if you're don't know, it's for rendering the sky and stars in real time, and more. And so, it can communicate with some telescopes for visualising where they are pointed to, and to GOTO them to a sky's position.
Some helpful links:
Complete documentation about the LX200 protocol and commands set
Link to Stellarium's LX200 plugin source code
Link to a very interesting instructables.com page
First, as it is a serial protocol and we're not running our Python code on a device, we need a couple of virtual serial ports. It's easy to do on GNU/Linux with the Socat command (you may need to apt-get install it before). If you're on Windows, you still can use VSPE.
$ socat -dd pty,raw,echo=0,link=/tmp/A pty,raw,echo=0,link=/tmp/B 2025/05/07 13:39:59 socat[2267928] N PTY is /dev/pts/16 2025/05/07 13:39:59 socat[2267928] N PTY is /dev/pts/17 2025/05/07 13:39:59 socat[2267928] N starting data transfer loop with FDs [5,5] and [7,7]Thanks to the link options, there are reproducible endpoints symlinks pointing to the pts devices.
$ ls -l /tmp/A /tmp/B lrwxrwxrwx 1 clx clx 11 May 7 13:39 /tmp/A -> /dev/pts/16 lrwxrwxrwx 1 clx clx 11 May 7 13:39 /tmp/B -> /dev/pts/17So, in this example, we can configure /tmp/A on Stellarium and use /tmp/B in our code without the need to change that if in a later use the pts numbers have changed. If the command is terminated (with Ctrl+C for example), the bridge is destroyed, along with its two endpoints /tmp/A and /tmp/B.
![[screen capture]](pics/stellarium-communication-protocol/stellarium_configuration1.jpg)
First, you need to activate the telescope control plugin. Press F2, then click on Plugins tab, select "Telescope Control" on the list, and click on "Load at startup". Then restart Stellarium.
![[screen capture]](pics/stellarium-communication-protocol/stellarium_configuration2.png)
Once Stellarium restarted, click on the "configure button" of the previous screen, which is now activated. You can also do Ctrl+0 and click on "Configure telescopes". Then click on the "Add a new telescope" button (the one with a "+" symbol), and you get this dialog window. Here you can type one virtual serial port we made, and select device model "Meade LX200 (compatible)".
![[screen capture]](pics/stellarium-communication-protocol/stellarium_configuration3.png)
Usage: press Alt+1 to GOTO to the center of the current view, or Ctrl+1 to GOTO to the selected object coordinates. Ctrl+0 brings up the "Slew telescope to" dialog.
Very basic Python example code:
from serial import Serial class Lx200Responder: def __init__(self, serialDeviceName): self.ra = 0.0 # Right angle (azimut) self.dec = 0.0 # Declination serial = Serial(serialDeviceName) s = bytes() while True: c = serial.read(1) s+=c if c == b'#': if len(s) > 1 and s[0] == 0x3a: r = self.parse(s[1:-1].decode('latin1')) if r: serial.write(r.encode('latin1')) elif r == False: serial.write(0x15) s=bytes() def parse(self, s): if s == "GR": # Get Telescope RA return "%02d:%02d:%02d#" % v2hms(self.ra) # HH:MM:SS# if s == "GD": # Get Telescope Declination r = "%c%02dß%02d:%02d#" % v2sdms(self.dec) # sDD*MM'SS# return r if s.startswith("Sr"): # Set target object RA to HH:MM.T or HH:MM:SS depending on the current precision setting self.ra = hms2v(s[2:]) print("Sr:", s[2:], self.ra) return '1' # accept if s.startswith("Sd"): # Set target object declination to sDD*MM or sDD*MM:SS depending on the current precision setting self.dec = dms2v(s[2:]) print("Sd:", s[2:], self.dec) return '1' # accept if s == "Q": # Halt all current slewing (returns nothing) print("Halt move.") return if s == "MS": # Slew to Target Object print("Slew to", self.ra, self.dec) return '0' # says slew is possible if s == "CM": # Synchronizes the telescope's position with the currently selected database object's coordinates print("Software wants the sync...") return '1Sync OK#' print("Unknown command:", s) return False def main(): lx200Responder = Lx200Responder("/tmp/B") # Now some misc helper functions... def v2hms(v): h = int(v); v=60*(v-h) m = int(v); s=60*(v-m) return (h, m, round(s)) def hms2v(s): f = 1.0 value = 0.0 for v in s.split(':'): value+=float(v)*f f/=60.0 return value def v2sdms(v): if v<0: sign = '-'; v=-v else: sign = '+' d = int(v); v=60*(v-d) m = int(v); s=60*(v-m) return sign, d, m, s def dms2v(s): sign = 1 if s[0] == '-': s = s[1:] sign = -1 value = 0.0 f = 1 for v in s.replace('ß', ':').split(':'): value+=float(v)*f f/=60.0 return sign*value main()