Blame view

src/device_controller/channels/server_udp_or_tcp.py 6.06 KB
ac0e5c70   Etienne Pallier   nouveau devices_c...
1
2
3
#!/usr/bin/env python3

# cf https://docs.python.org/3/library/socketserver.html#socketserver-tcpserver-example
fe86e95f   Etienne Pallier   bugfix exception ...
4
# https://www.linuxtopia.org/online_books/programming_books/python_programming/python_ch36s06.html
ac0e5c70   Etienne Pallier   nouveau devices_c...
5
6
7
8
9
10
11

"""Socket Server implementation

To be used as a minimalist telescope simulator to which a socket client (SocketClient) can connect
"""

# Standard library imports
ce74dbbb   Etienne Pallier   LOGGER unique : p...
12
import os
ac0e5c70   Etienne Pallier   nouveau devices_c...
13
import socketserver
3e61fc36   Etienne Pallier   Grosse refactoris...
14
import sys
ac0e5c70   Etienne Pallier   nouveau devices_c...
15
16
17
18
19
20

# Third party imports
# None

# Local application imports
# None
ce74dbbb   Etienne Pallier   LOGGER unique : p...
21
#from .client_channel import printd
3e61fc36   Etienne Pallier   Grosse refactoris...
22
23
24
25
'''
sys.path.append('..')
from device_controller.abstract_component.device_simulator import getc, getp
'''
ac0e5c70   Etienne Pallier   nouveau devices_c...
26

ce74dbbb   Etienne Pallier   LOGGER unique : p...
27
28
29
30
31

def printd(*args, **kwargs):
    if os.environ.get('PYROS_DEBUG', '0')=='1': print(*args, **kwargs)


3e61fc36   Etienne Pallier   Grosse refactoris...
32
33
# Very BASIC implementation
def make_answer_for_request_CMD_TO_UPPER(request_bytes):
827af78a   Etienne Pallier   Nouvelle base de ...
34
    #raise NotImplementedError
1cf4a67f   Etienne Pallier   Affichage console...
35
    printd("(SIM serv) Request received is", request_bytes)
ac0e5c70   Etienne Pallier   nouveau devices_c...
36

827af78a   Etienne Pallier   Nouvelle base de ...
37
38
39
    # Convert to string
    request = request_bytes.decode("utf-8")
    #if len(request) < STAMP_LENGTH+2+1: raise UnknownCommandException(request)
ac0e5c70   Etienne Pallier   nouveau devices_c...
40

827af78a   Etienne Pallier   Nouvelle base de ...
41
42
    # Remove TERMINATOR
    #request = request[0:-1]
ac0e5c70   Etienne Pallier   nouveau devices_c...
43

827af78a   Etienne Pallier   Nouvelle base de ...
44
45
46
47
    # Remove leading stamp
    #stamp = request[0:STAMP_LENGTH]
    #command = request[STAMP_LENGTH:]
    command = request
1cf4a67f   Etienne Pallier   Affichage console...
48
    printd("(SIM serv) Command received is", repr(command))
ac0e5c70   Etienne Pallier   nouveau devices_c...
49

827af78a   Etienne Pallier   Nouvelle base de ...
50
51
52
53
54
    answer = 'ANSWER TO '+command.upper()
    #if command == ':GR#': answer = "15:01:49"
    #elif command == ':GD#': answer = "+12:29"
    #elif command == ':SG+00#': answer = "1"
    #elif command == ':GG#': answer = "+00"
ac0e5c70   Etienne Pallier   nouveau devices_c...
55

827af78a   Etienne Pallier   Nouvelle base de ...
56
57
    #full_answer_in_bytes = bytes(stamp + answer + '#' + TERMINATOR, "utf-8")
    full_answer_in_bytes = bytes(answer, "utf-8")
1cf4a67f   Etienne Pallier   Affichage console...
58
59
    #printd("request str upper is", str(request).upper())
    printd("(SIM serv) Answer sent is", full_answer_in_bytes)
827af78a   Etienne Pallier   Nouvelle base de ...
60
    return full_answer_in_bytes
ac0e5c70   Etienne Pallier   nouveau devices_c...
61

ac0e5c70   Etienne Pallier   nouveau devices_c...
62

3e61fc36   Etienne Pallier   Grosse refactoris...
63
64
65
# Default BASIC function "make_answer_for_request" to be overriden by subclass
# Select the function we want to use by default
make_answer_for_request = make_answer_for_request_CMD_TO_UPPER
ac0e5c70   Etienne Pallier   nouveau devices_c...
66

ac0e5c70   Etienne Pallier   nouveau devices_c...
67

827af78a   Etienne Pallier   Nouvelle base de ...
68
69
def get_SocketServer_UDP_TCP(myhost:str="localhost", myport:int=11110, PROTOCOL:str="TCP", func_make_answer_for_request=make_answer_for_request):

ac0e5c70   Etienne Pallier   nouveau devices_c...
70
71
72
    socketserver_type = socketserver.UDPServer if PROTOCOL=="UDP" else socketserver.TCPServer
    socketserver_handler_type = socketserver.DatagramRequestHandler if PROTOCOL=="UDP" else socketserver.StreamRequestHandler

827af78a   Etienne Pallier   Nouvelle base de ...
73
    
ac0e5c70   Etienne Pallier   nouveau devices_c...
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
    """
    A classic version of request handler:
    """
    class _MyUDPorTCPHandler_classic(socketserver.BaseRequestHandler):
        # The request handler class for our server.
        # It is instantiated once per connection to the server, and must
        # override the handle() method to implement communication to the client
    
        def handle(self):
    
            # Get received data
            # - TCP
            if PROTOCOL == "TCP": 
                # For TCP, self.request is the TCP socket connected to the client
                data_rcv = self.request.recv(1024).strip()
            # - UDP
            else:
                # For UDP, self.request consists of a pair of data and client socket
                data_rcv = self.request[0].strip()
                socket_client = self.request[1]
1cf4a67f   Etienne Pallier   Affichage console...
94
95
            printd("(SIM serv) {} wrote:".format(self.client_address[0]))
            printd("(SIM serv)", data_rcv)
ac0e5c70   Etienne Pallier   nouveau devices_c...
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    
            # Send back the same data, but upper-cased
            # - TCP
            if PROTOCOL == "TCP": 
                self.request.sendall(data_rcv.upper())
            # - UDP
            # Since there is no connection, the client address must be given explicitly when sending data back via sendto().
            else:
                socket_client.sendto(data_rcv.upper(), self.client_address)

    """
    An alternative request handler class that makes use of streams 
    (file-like objects that simplify communication by providing the standard file interface):
    """
    #class MyTCPHandler(socketserver.StreamRequestHandler):
    #class MyUDPHandler(socketserver.DatagramRequestHandler):
    class _MyUDPorTCPHandler(socketserver_handler_type):

        
        def get_useful_data(self, data_received):
            return data_received
        
827af78a   Etienne Pallier   Nouvelle base de ...
118
        '''
ac0e5c70   Etienne Pallier   nouveau devices_c...
119
        def make_answer_for_request(self, request_bytes:bytes):
827af78a   Etienne Pallier   Nouvelle base de ...
120
121
122
            ##return self.make_answer_for_request_Gemini(request_bytes)
            raise NotImplementedError
        '''
102bd63f   Etienne Pallier   1er debut de solu...
123
124
125



827af78a   Etienne Pallier   Nouvelle base de ...
126
        # @override
ac0e5c70   Etienne Pallier   nouveau devices_c...
127
128
129
130
131
        def handle(self):
    
            # 1) RECEIVE REQUEST from client
            # self.rfile is a file-like object created by the handler;
            # we can now use e.g. readline() instead of raw recv() calls
fe86e95f   Etienne Pallier   bugfix exception ...
132
            #data_received = self.request.recv(1024)
ac0e5c70   Etienne Pallier   nouveau devices_c...
133
134
            data_received = self.rfile.readline().strip() # data is "bytes" type
            data_useful = self.get_useful_data(data_received)
1cf4a67f   Etienne Pallier   Affichage console...
135
136
137
            #printd("data type is", type(data))
            #printd("\nFrom {}, received: {}".format(self.client_address[0], data_useful))
            printd(f"\n(SIM server_udp_or_tcp) From {self.client_address[0]}, received: {data_useful}\n")
ac0e5c70   Etienne Pallier   nouveau devices_c...
138
139
140
    
            # 2) SEND REPLY to client
            # Likewise, self.wfile is a file-like object used to write back to the client
827af78a   Etienne Pallier   Nouvelle base de ...
141
142
            #self.wfile.write(self.make_answer_for_request(data_useful))
            self.wfile.write(func_make_answer_for_request(data_useful))
ac0e5c70   Etienne Pallier   nouveau devices_c...
143
144
145
            # inutile, ca plante car data est deja en bytes:
            #self.wfile.write(data.upper().encode())
    
827af78a   Etienne Pallier   Nouvelle base de ...
146
    
ac0e5c70   Etienne Pallier   nouveau devices_c...
147
148
149
150
151
    # Return a "classic" handler:
    #return socketserver_type((HOST, PORT), _MyUDPorTCPHandler_classic)

    # or a more "modern" handler:
    return socketserver_type((myhost, myport), _MyUDPorTCPHandler)
fe86e95f   Etienne Pallier   bugfix exception ...
152
153


ac0e5c70   Etienne Pallier   nouveau devices_c...
154
155
156
157
158
159
160

if __name__ == "__main__":
    #with socketserver_type((HOST, PORT), MyUDPorTCPHandler_classic) as myserver:
    '''
    with socketserver_type((HOST, PORT), MyUDPorTCPHandler) as myserver:
        myserver.serve_forever()
    '''
827af78a   Etienne Pallier   Nouvelle base de ...
161
    #with get_SocketServer_UDP_TCP("localhost", 11110, "UDP", make_answer_for_request) as myserver:
ac0e5c70   Etienne Pallier   nouveau devices_c...
162
    with get_SocketServer_UDP_TCP("localhost", 11110, "UDP") as myserver:
fe86e95f   Etienne Pallier   bugfix exception ...
163
        # Handle requests in an infinite loop. Runs until the loop is broken with an exception
ac0e5c70   Etienne Pallier   nouveau devices_c...
164
        myserver.serve_forever()