# -*- coding: utf-8 -*- import sys, os import time import traceback import socket from threading import Thread, Event #from threading import RLock import select, queue try: from .mountastro import Mountastro except: from mountastro import Mountastro try: from .mountchannel import Mountchannel except: from mountchannel import Mountchannel try: from .mountaxis import Mountaxis except: from mountaxis import Mountaxis try: from .mounttools import Mounttools except: from mounttools import Mounttools path = os.path.join(os.path.dirname(__file__), '..') if path not in sys.path: sys.path.append(path) # --- celme imports modulename = 'celme' if modulename in dir(): del celme if modulename not in dir(): import celme # ##################################################################### # ##################################################################### # ##################################################################### # Class MountremoteServer # ##################################################################### # ##################################################################### # This class provides a server # for mount communication and a client software # ##################################################################### #lock = RLock() class MountremoteServer(Thread): """ For SERIAL or TCP connections """ def __init__(self, *args, **kwargs): Thread.__init__(self) self._themount = args[0] #print("*args={}".format(args)) #print("**kwargs={}".format(kwargs)) # --- Dico of optional parameters for all axis_types param_optionals = {} param_optionals["PROTOCOL"] = (str, "LX200") param_optionals["VERBOSE_LEVEL"] = (int, 1) # --- Dico of axis_types and their parameters remote_transport_protocols = {} remote_transport_protocols["SERIAL"] = {"MANDATORY" : {"PORT":[str,"//./COM1"]}, "OPTIONAL" : {"BAUD":[int,9600]} } remote_transport_protocols["TCP"] = {"MANDATORY" : {"PORT":[int,1111]}, "OPTIONAL" : {} } # --- Decode args and kwargs parameters mounttools = Mounttools() params = mounttools.decode_args_kwargs(1,remote_transport_protocols, param_optionals, *args, **kwargs) self._remote_transport_protocol = params["SELECTED_ARG"] self._remote_command_protocol = params["PROTOCOL"] self._verbose_level = params["VERBOSE_LEVEL"] if self._remote_transport_protocol=="SERIAL": self._port = params["PORT"] self._baud_rate = params["BAUD"] #msg = "port={} baud={}".format(self._port,self._baud_rate) #self.print(msg) elif self._remote_transport_protocol=="TCP": self._port = params["PORT"] #msg = "port={}".format(self._port) #self.print(msg) # --- Propagate the protocol to the mount for the remote_command_processing self._themount.remote_command_protocol(self._remote_command_protocol) # --- self._stopevent = Event( ) def run(self): """Code à exécuter pendant l'exécution du Thread""" msg = "The thread MountremoteServer is started" self.print(msg) # --- if self._remote_command_protocol=="LX200": delay_init_chan=0.1 end_of_command_to_send="#".encode('utf8') end_of_command_to_receive="#".encode('utf8') delay_put_read=0.06 elif self._remote_command_protocol=="MCS": delay_init_chan=0.1 end_of_command_to_send="#".encode('utf8') end_of_command_to_receive="#".encode('utf8') delay_put_read=0.06 if self._remote_command_protocol=="ASCOM": delay_init_chan=0.1 end_of_command_to_send="#".encode('utf8') end_of_command_to_receive="#".encode('utf8') delay_put_read=0.06 # --- if self._remote_transport_protocol=="SERIAL": self._channel = None while not self._stopevent.isSet(): # --- Open the serial server if needed if self._channel==None: try: params = dict(port=self._port, baud_rate=self._baud_rate) params["delay_init_chan"]=delay_init_chan params["end_of_command_to_send"]=end_of_command_to_send params["end_of_command_to_receive"]=end_of_command_to_receive params["delay_put_read"]=delay_put_read #print("MountremoteServer params={}".format(params)) self._channel = Mountchannel("SERIAL", **params) self._channel.verbose_chan = False except: msg = "{} instance problem. {}".format(self._remote_transport_protocol,self._channel.get_last_error_msg()) self.print(msg) time.sleep(0.1) break if self._channel.check_chan()[0] != Mountchannel.NO_ERROR: msg = "{} opening problem. {}".format(self._remote_transport_protocol,self._channel.get_last_error_msg()) self.print(msg) time.sleep(0.1) break msg = "{} server listening port {} for protocol {}".format(self._remote_transport_protocol, self._port, self._remote_command_protocol) self.print(msg) # --- read the commands from the client valid_commands = False try: err, commands = self._channel.read_chan() #print("=== Recu commands={} type={}".format(commands,type(commands))) if isinstance(commands, list) == True: # --- used by LX200 while '' in commands: commands.remove('') if len(commands)>0: #print("=== Recu List commands={}".format(commands)) valid_commands = True elif isinstance(commands, str) == True: # --- used by ASTROMECCA if len(commands)>0: cmd = [] cmd.append(commands) commands = cmd #print("=== Recu Str commands={}".format(commands)) valid_commands = True except: # --- connexion problem msg = "{} server problem, close. {}".format(self._remote_transport_protocol,sys.exc_info()[0]) self.print(msg) traceback.print_exc(file=sys.stdout) try: self._channel.close_chan() except: pass #self._channel = None time.sleep(1) # --- process the received message try: if err==self._channel.NO_ERROR and valid_commands==True: for command in commands: msg = self._themount.remote_command_processing(command) #message = command + " - SERIAL -> " + msg #self.print(message) # --- send the answer to client self._channel.put_chan(msg) except: traceback.print_exc(file=sys.stdout) time.sleep(0.01) # --- if self._channel != None: self._channel.close_chan() elif self._remote_transport_protocol=="TCP": self._channel = None # --- Open the serial server if needed try: self._channel = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._channel.setblocking(0) self._channel.bind((socket.gethostname(), self._port)) self._channel.listen(5) inputs = [self._channel] outputs = [] message_queues = {} except: # --- connexion problem try: self._channel.close_chan() except: pass self._channel = None msg = "Unexpected error: {}".format(sys.exc_info()) self.print(msg) return msg = "{} server listening port {} for protocol {}".format(self._remote_transport_protocol, self._port, self._remote_command_protocol) self.print(msg) msg = "{}".format(self._channel) self.print(msg) # --- read the commands from the client try: while inputs: if self._stopevent.isSet(): break time_out = 0.5 readable, writable, exceptional = select.select(inputs, outputs, inputs, time_out) #readable, writable, exceptional = select.select(inputs, outputs, inputs) # --- for s in readable: if s is self._channel: connection, client_address = s.accept() connection.setblocking(0) inputs.append(connection) message_queues[connection] = queue.Queue() else: data = s.recv(1024) if data: #print("data={}".format(data)) commands = str(data.decode("utf-8")) #print("commands={}".format(commands)) if end_of_command_to_receive!="": commands = commands.split(end_of_command_to_receive.decode("utf-8")) #print("commands={}".format(commands)) #print("=== Recu commands={} type={}".format(commands,type(commands))) if isinstance(commands, list) == True: # --- used by LX200 while '' in commands: commands.remove('') if len(commands)>0: #print("=== Recu List commands={}".format(commands)) valid_commands = True elif isinstance(commands, str) == True: # --- used by ASTROMECCA if len(commands)>0: cmd = [] cmd.append(commands) commands = cmd #print("=== Recu Str commands={}".format(commands)) valid_commands = True # --- process the received message try: if valid_commands==True: for command in commands: msg = self._themount.remote_command_processing(command) #message = command + " - TCP -> " + msg #self.print(message) # --- send the answer to client message_queues[s].put(msg.encode("utf-8")) if s not in outputs: outputs.append(s) except: traceback.print_exc(file=sys.stdout) else: #print("step 2") if s in outputs: outputs.remove(s) inputs.remove(s) s.close() del message_queues[s] # --- for s in writable: try: #print("step 3 s.fileno()={}".format(s.fileno())) if s.fileno()==-1: continue next_msg = message_queues[s].get_nowait() except queue.Empty: outputs.remove(s) else: s.send(next_msg) for s in exceptional: inputs.remove(s) if s in outputs: outputs.remove(s) s.close() del message_queues[s] # --- #time.sleep(0.7) except: traceback.print_exc(file=sys.stdout) # --- #print("STEP 200 self._channel={}".format(self._channel)) if self._channel != None: self._channel.close() msg = "The thread MountremoteServer termined properly" self.print(msg) def print(self, *args, **kwargs): """ print method """ if self._verbose_level>0: print (*args, **kwargs,end='\n') def stop(self): self._stopevent.set( ) def __del__(self): if self._channel != None: if self._remote_transport_protocol=="SERIAL": self._channel.close_chan() elif self._remote_transport_protocol=="TCP": self._channel.close() self.stop() # ##################################################################### # ##################################################################### # ##################################################################### # Class MountremoteClient # ##################################################################### # ##################################################################### # This class provides a client # for mount communication and a server software # ##################################################################### class MountremoteClient(): """ For SERIAL or TCP connections """ def __init__(self, *args, **kwargs): """ Initialisation du protocole Remote. Blabla. Example: >>> MountremoteClient("SERIAL",port="COM1") """ Thread.__init__(self) #self._themount = args[0] #print("*args={}".format(args)) #print("**kwargs={}".format(kwargs)) # --- Dico of optional parameters for all axis_types param_optionals = {} param_optionals["PROTOCOL"] = (str, "LX200") param_optionals["VERBOSE_LEVEL"] = (int, 1) param_optionals["WAIT_MS"] = (int, 200) # --- Dico of axis_types and their parameters remote_transport_protocols = {} remote_transport_protocols["SERIAL"] = {"MANDATORY" : {"PORT":[str,"//./COM1"]}, "OPTIONAL" : {"BAUD":[int,9600]} } #remote_transport_protocols["TCP"] = {"MANDATORY" : {"PORT":[int,1111]}, "OPTIONAL" : {} } remote_transport_protocols["TCP"] = {"MANDATORY" : {"HOSTNAME":[str,'localhost'], "PORT":[int,1111]}, "OPTIONAL" : {} } # --- Decode args and kwargs parameters mounttools = Mounttools() params = mounttools.decode_args_kwargs(0,remote_transport_protocols, param_optionals, *args, **kwargs) self._remote_transport_protocol = params["SELECTED_ARG"] self._remote_command_protocol = params["PROTOCOL"] self._verbose_level = params["VERBOSE_LEVEL"] self._wait_sec = params["WAIT_MS"]/1000.0 if self._remote_transport_protocol=="SERIAL": self._port = params["PORT"] self._baud_rate = params["BAUD"] #msg = "port={} baud={}".format(self._port,self._baud_rate) #self.print(msg) elif self._remote_transport_protocol=="TCP": self._hostname = params["HOSTNAME"] self._port = params["PORT"] #msg = "port={}".format(self._port) #self.print(msg) # --- self._channel = None def close_chan(self): if self._channel != None: self._channel.close_chan() msg = "The MountremoteClient is closed" self.print(msg) def open_chan(self): """Open a channel""" if self._channel != None: return params = dict() if self._remote_transport_protocol=="SERIAL": params = dict(port=self._port, baud_rate=self._baud_rate) elif self._remote_transport_protocol=="TCP": params = dict(port=self._port, hostname=self._hostname) if self._remote_command_protocol=="LX200": params["delay_init_chan"]=0.1 params["end_of_command_to_send"]="#".encode('utf8') params["end_of_command_to_receive"]="#".encode('utf8') params["delay_put_read"]=0.06 elif self._remote_command_protocol=="ASTROMECCA": params["delay_init_chan"]=0.1 params["end_of_command_to_send"]="".encode('utf8') params["end_of_command_to_receive"]="".encode('utf8') params["delay_put_read"]=0.06 #print("MountremoteClient params={}".format(params)) try: self._channel = Mountchannel(self._remote_transport_protocol, **params) self._channel.verbose_chan = True #print("#"*30) except: # --- connexion problem try: self._channel.close() except: pass self._channel = None msg = "{} opening problem. {}".format(self._remote_transport_protocol,sys.exc_info()) self.print(msg) return msg = "{} client can communicate on port {} for protocol {}".format(self._remote_transport_protocol, self._port, self._remote_command_protocol) self.print(msg) msg = "The MountremoteClient is opened" self.print(msg) def put_chan(self, msg): # -- open a channel if not ever done self.open_chan() # -- do not work if the channel does not exists if self._channel == None: return # -- send the message according the transport protocol try: # --- send the message to the client # .encode("utf-8") #self.print("=== envoi message={}".format(msg)) self._channel.put_chan(msg) except: # --- connection problem msg = "{} client problem. {}".format(self._remote_transport_protocol,sys.exc_info()) self.print(msg) traceback.print_exc(file=sys.stdout) def read_chan(self): # -- open a channel if not ever done self.open_chan() # -- do not work if the channel does not exists if self._channel == None: return # -- read the message according the transport protocol valid_responses = False try: #print("On va lire") err, responses = self._channel.read_chan() #print("read_chan responses={}".format(responses)) if isinstance(responses, list) == True: # --- used by LX200 while '' in responses: responses.remove('') if len(responses)>0: #self.print("=== Recu responses={}".format(responses)) valid_responses = True elif isinstance(responses, str) == True: # --- used by ASTROMECCA valid_responses = True except: # --- connection problem msg = "{} client problem. {}".format(self._remote_transport_protocol,sys.exc_info()) self.print(msg) traceback.print_exc(file=sys.stdout) if valid_responses == True: return responses def putread_chan(self, msg): self.put_chan(msg) time.sleep(self._wait_sec) res = self.read_chan() return res def print(self, *args, **kwargs): """ print method """ if self._verbose_level>0: print (*args, **kwargs,end='\n') def __del__(self): self.close_chan() # ##################################################################### # ##################################################################### # ##################################################################### # Main # ##################################################################### # ##################################################################### # ##################################################################### if __name__ == "__main__": example = 1 print("Example = {}".format(example)) if example == 1: """ Create a Mount in simulation Create a Server linked to the mount Create a Client linked to the server """ #--- Choice the transport and the langage protocol remote_transport = "TCP" ; # SERIAL TCP remote_protocol = "MCS" ; # LX200 ASTROMECCA ASCOM test_client = True ; # True -> port_serial_remote_client connect_real_mount = False ; # True -> port_serial_astromecca use_pad = False ; # True to use the GUI pad if 'remote_client' in globals(): del(remote_client) if 'remote_server' in globals(): del(remote_server) time.sleep(1) if 'mount_astromecca' in globals(): del(mount_astromecca) time.sleep(0.5) # --- configuration depending the computer hostname = socket.gethostname() # --- serial port configurations if remote_transport == "SERIAL": tools = Mounttools() available_serial_ports = tools.get_available_serial_ports() nport = len(available_serial_ports) # --- At minima the server port exists if nport<1: print("Not enough serial ports for the server") exit() else: port_serial_remote_server=available_serial_ports[0] # --- Other ports if test_client == True and connect_real_mount == False: # --- Case : only client test, no real mount port_serial_remote_client=available_serial_ports[1] if test_client == False and connect_real_mount == True: # --- Case : only real mount, no client test port_serial_astromecca=available_serial_ports[1] if test_client == True and connect_real_mount == True: # --- Case : real mount and client test port_serial_remote_client=available_serial_ports[1] port_serial_astromecca=available_serial_ports[2] # === Special computers if hostname == "titanium": print("Configuration = {}".format(hostname)) port_serial_remote_server=available_serial_ports[0] port_serial_remote_client=available_serial_ports[1] port_serial_astromecca=available_serial_ports[0] elif hostname == "rapido2": print("Configuration = {}".format(hostname)) port_serial_astromecca='/dev/ttyAMA0' port_serial_remote_server='/dev/ttyAMA1' port_serial_remote_client='/dev/ttyAMA0' else: print("No predefined configuration for {}".format(hostname)) # --- TCP port configurations if remote_transport == "TCP": host_tcp_remote_server = socket.gethostname() port_tcp_remote_server = 1111 # === Special computers if hostname == "titanium": print("Configuration = {}".format(hostname)) host_tcp_remote_server = '192.168.0.4' elif hostname == "rapido2": print("Configuration = {}".format(hostname)) else: print("No predefined configuration for {}".format(hostname)) # --- Summary of the configuration print("Transport protocol = {}".format(remote_transport)) print("Langage protocol = {}".format(remote_protocol)) if connect_real_mount == True: print("port_serial_mount = {}".format(port_serial_astromecca)) if remote_transport == "SERIAL": print("port_serial_remote_server = {}".format(port_serial_remote_server)) if test_client == True: print("port_serial_remote_client = {}".format(port_serial_remote_client)) else: print("host_tcp_remote_server = {}".format(host_tcp_remote_server)) print("port_tcp_remote_server = {}".format(port_tcp_remote_server)) home = celme.Home("GPS 2.0375 E 43.6443484725 136.9") site = celme.Site(home) # === ASTROMECCA connection mount_astromecca = Mountastro("HADEC", name="Guitalens Mount", manufacturer="Astro MECCA", model="TM350", serial_number="beta001", site=site, CONTROLLER_BASE_ID=1, CONTROLLER_POLAR_ID=2) if connect_real_mount == True: mount_astromecca.set_channel_params("SERIAL", port=port_serial_astromecca, baud_rate=115200, delay_init_chan=0.1, end_of_command_to_send="\r\n".encode('utf8'), end_of_command_to_receive="\r\n".encode('utf8'), delay_put_read=0.06) mount_astromecca.verbose_chan = False # --- shortcuts mount_astromecca_axisb = mount_astromecca.axis[Mountaxis.BASE] mount_astromecca_axisp = mount_astromecca.axis[Mountaxis.POLAR] # --- simulation or not mount_astromecca_axisb.real = False mount_astromecca_axisb.ratio_wheel_puley = 6.132857; # 6.27819 ; # 6.32721 ; # D=208.0 ; d=32.5 ; f=1.5 ; (D+f/2)/(d+f/2) mount_astromecca_axisb.inc_per_motor_rev = 1540 # DPR for -490000 to +490000 mount_astromecca_axisb.senseinc = 1 mount_astromecca_axisp.real = False mount_astromecca_axisp.ratio_wheel_puley = 6.75 ; # 6.75 ; # 6.7462935 ; # D=208.0 ; d=30.0 ; f=1.5 ; (D+f/2)/(d+f/2) mount_astromecca_axisp.inc_per_motor_rev = 1421 mount_astromecca_axisp.senseinc = -1 # --- Initial ha,dec for encoders #mount_astromecca_axisb.update_inc0(10750,-90,mount_astromecca_axisb.PIERSIDE_POS1) mount_astromecca.set_param("CONFIGURATION","Fork") if mount_astromecca.get_param("CONFIGURATION")=="German": # --- German mount mount_astromecca.set_param("LABEL_REGULAR","Tube West") ; # Tube west = PIERSIDE_POS1 mount_astromecca.set_param("LABEL_FLIPED","Tube East") mount_astromecca.set_param("CAN_REVERSE",True) mount_astromecca.set_param("LIME_REVERSE",+30) ; # Tube west = PIERSIDE_POS1 = [-180 : lim_side_east] mount_astromecca.set_param("LIMW_REVERSE",-30) ; # Tube east = PIERSIDE_POS2 = [lim_side_west : +180] mount_astromecca_axisb.update_inc0(0,-90,mount_astromecca_axisb.PIERSIDE_POS1) mount_astromecca_axisp.update_inc0(0,90,mount_astromecca_axisp.PIERSIDE_POS1) if mount_astromecca_axisb.real == True: mount_astromecca_axisb.update_inc0(62500,-90,mount_astromecca_axisb.PIERSIDE_POS1) if mount_astromecca_axisp.real == True: mount_astromecca_axisp.update_inc0(6500,90,mount_astromecca_axisp.PIERSIDE_POS1) mount_astromecca.park_ha = 270 mount_astromecca.park_dec = 90 mount_astromecca.park_side = mount_astromecca_axisb.PIERSIDE_POS1 elif mount_astromecca.get_param("CONFIGURATION")=="Fork": # --- Fork mount. Tube always "west" in "auto" mount_astromecca.set_param("LABEL_REGULAR","Regular") ; # Regular = PIERSIDE_POS1 mount_astromecca.set_param("LABEL_FLIPED","Fliped") mount_astromecca.set_param("CAN_REVERSE",False) mount_astromecca.set_param("LIME_REVERSE",+90) ; # Tube west = PIERSIDE_POS1 = [-180 : lim_side_east] mount_astromecca.set_param("LIMW_REVERSE",-90) ; # Tube east = PIERSIDE_POS2 = [lim_side_west : +180] mount_astromecca_axisb.update_inc0(0,0,mount_astromecca_axisb.PIERSIDE_POS1) mount_astromecca_axisp.update_inc0(0,90,mount_astromecca_axisp.PIERSIDE_POS1) if mount_astromecca_axisb.real == True: mount_astromecca_axisb.update_inc0(62500,0,mount_astromecca_axisb.PIERSIDE_POS1) if mount_astromecca_axisp.real == True: mount_astromecca_axisp.update_inc0(6500,90,mount_astromecca_axisp.PIERSIDE_POS1) else: mount_astromecca_axisp._incsimu = -239793.8 mount_astromecca.park_ha = 0 mount_astromecca.park_dec = 0 mount_astromecca.park_side = mount_astromecca_axisb.PIERSIDE_POS1 # --- first read of encoders (zero values the first time) incsimus = ["" for kaxis in range(Mountaxis.AXIS_MAX)] mount_astromecca.enc2cel(incsimus, save=mount_astromecca.SAVE_ALL) # --- second read of encoders (valid values) time.sleep(0.05) mount_astromecca.enc2cel(incsimus, save=mount_astromecca.SAVE_ALL) # --- Init the simulation values according the real ones mount_astromecca_axisb.synchro_real2simu() # --- Get the initial position res = mount_astromecca.hadec_coord() mount_astromecca.log.print("Initial position = {}".format(res)) # ======= ASTROMECCA parameters mount_astromecca.speedslew(5.0,5.0) mount_astromecca.disp() # ======= Transport protocol parameter if remote_protocol == "LX200" and remote_transport == "SERIAL": bauds = 9600 else: bauds = 115200 # ======= Server print("="*30) if remote_transport == "SERIAL": remote_port = port_serial_remote_server else: remote_port = port_tcp_remote_server msg = "{} server on port {} for protocol {}".format(remote_transport, remote_port, remote_protocol) print(msg) remote_server = MountremoteServer(mount_astromecca, remote_transport, port=remote_port, protocol=remote_protocol, verbose_level=1, baud=bauds) remote_server.start() time.sleep(1) # The server is non blocking # Now you have the hand to send commands to the server # To illustrate that, we send a message via a client if test_client == True: # ======= Client print("="*30) if remote_transport == "SERIAL": remote_port = port_serial_remote_client else: remote_port = port_tcp_remote_server msg = "{} client on port {} for protocol {}".format(remote_transport, remote_port, remote_protocol) print(msg) if remote_transport == "SERIAL": remote_client = MountremoteClient(remote_transport, port=remote_port, baud=bauds, protocol=remote_protocol, verbose_level=1) else: remote_client = MountremoteClient(remote_transport, hostname=host_tcp_remote_server, port=remote_port, protocol=remote_protocol, verbose_level=1) remote_client.open_chan() print("="*30) if remote_protocol == "LX200": command = ':GR' result = remote_client.putread_chan(command) print("Command = {}\nResult = {}".format(command,result)) print("="*30) command = '#:GD#' result = remote_client.putread_chan(command) print("Command = {}\nResult = {}".format(command,result)) elif remote_protocol == "MCS": command = "{'req': {'do':{'exec' : 'self.radec_coord()'}}}" result = remote_client.putread_chan(command) print("Command = {}\nResult = {}".format(command,result)) print("="*30) command = "{'req': {'get': 'radec'}}" result = remote_client.putread_chan(command) print("Command = {}\nResult = {}".format(command,result)) print("="*30) command = "{'req': {'get': ''}}" result = remote_client.putread_chan(command) print("Command = {}\nResult = {}".format(command,result)) elif remote_protocol == "ASCOM": command = 'RightAscension' result = remote_client.putread_chan(command) print("Command = {}\nResult = {}".format(command,result)) print("="*30) command = 'Declination' result = remote_client.putread_chan(command) print("Command = {}\nResult = {}".format(command,result)) print("="*30) if use_pad == True: try: print("Create the pad. Tk Event loop activated.") mount_astromecca.pad_create("pad_dev1") print("Pad deleted. Tk Event loop stopped") except (KeyboardInterrupt, SystemExit): pass except: raise # To stop the server: # remote_server.stop()