pyduino.slave
1#Meant to be used in the raspberries 2from crypt import methods 3from urllib import response 4from flask import Flask, request, jsonify 5from serial import Serial 6import serial.tools.list_ports as list_ports 7from time import sleep 8from pyduino.utils import bcolors 9import logging 10import re 11import os 12import socket 13from typing import Any, Optional, Dict 14 15TIMEOUT = 60 16HEADER_DELAY = 5 17 18class ReactorServer(Flask): 19 """ 20 Slave of HTTP Server to Serial handler. 21 """ 22 23 def __init__(self, serial_port: Optional[str] = None, baudrate: int = 9600, *args: Any, **kwargs: Any): 24 """ 25 Initializes the ReactorServer. 26 27 Args: 28 serial_port (str, optional): The serial port to connect to. If not specified, it searches for available devices. 29 baudrate (int, optional): The baud rate for the serial connection. 30 *args: Variable length arguments to pass to the Flask constructor. 31 **kwargs: Keyword arguments to pass to the Flask constructor. 32 """ 33 logging.debug("Creating server.") 34 super().__init__(*args, **kwargs) 35 self.connected = False 36 self.reactor_id: Optional[int] = None 37 self.port = serial_port 38 self.baudrate = baudrate 39 40 self.serial_connect() 41 42 # Routes 43 @self.route("/") 44 def root(): 45 """ 46 Root route handler. 47 """ 48 return "REACTOR SERVER", 200 49 50 @self.route("/connect") 51 def http_connect(): 52 """ 53 HTTP route for connecting to the reactor. 54 """ 55 self.connect() 56 return "OK", 200 57 58 @self.route("/reset") 59 def http_reset(): 60 """ 61 HTTP route for resetting the connection. 62 """ 63 self.reset() 64 return "OK", 200 65 66 @self.route("/reboot") 67 def reboot(): 68 """ 69 HTTP route for rebooting the server. 70 """ 71 os.system("sudo reboot") 72 return "OK", 200 73 74 @self.route("/send", methods=['POST']) 75 def http_send(): 76 """ 77 HTTP route for sending commands to the reactor. 78 79 Returns: 80 str: The response received from the reactor. 81 """ 82 content = request.json 83 logging.info(f"Received request: {content['command']}") 84 if content['await_response']: 85 response = self.send(content["command"]) 86 else: 87 response = self._send(content["command"]) 88 return jsonify({"response": response}), 200 89 90 @self.route("/ping") 91 def ping(): 92 """ 93 HTTP route for pinging the reactor. 94 95 Returns: 96 dict: A JSON object containing reactor information (id, serial_port, hostname). 97 """ 98 digit_regex = r"(\d+)(?!.*\d)" 99 hostname = socket.gethostname() 100 digits = int(re.findall(digit_regex, hostname)[0]) 101 self.reactor_id = digits 102 return jsonify({"id": self.reactor_id, "serial_port": self.port, "hostname": os.uname().nodename}) 103 104 def __delete__(self, _): 105 self.serial.__del__() 106 107 108 def serial_connect(self): 109 if self.port is None: 110 logging.debug("No serial port specified. Searching for available devices.") 111 self.available_ports = list_ports.comports() 112 self.available_ports = list(filter(lambda x: (x.vid,x.pid) in {(1027,24577),(9025,16),(6790,29987)},self.available_ports)) 113 self.port = self.available_ports[0].device 114 self.serial = Serial(self.port, baudrate=self.baudrate, timeout=TIMEOUT) 115 logging.info(f"Connected to serial port {self.serial}.") 116 117 def connect(self): 118 """ 119 Begins the connection to the reactor. 120 121 Args: 122 delay (float, optional): Delay in seconds before sending the initial command. 123 """ 124 sleep(HEADER_DELAY) 125 self.serial.flush() 126 self.serial.reset_input_buffer() 127 self._send("quiet_connect") 128 self.connected = True 129 130 def reset(self): 131 """ 132 Resets the connection to the reactor. 133 """ 134 self.serial.flush() 135 self.serial.close() 136 self.serial.open() 137 self.connected = True 138 139 def close(self): 140 """ 141 Interrupts the connection with the reactor. 142 """ 143 if self.serial.is_open: 144 self.serial.reset_input_buffer() 145 self.send("fim") 146 self.serial.close() 147 148 def send(self, msg: str) -> str: 149 """ 150 Sends a command to the reactor and receives the response. 151 152 Args: 153 msg (str): The command to send to the reactor. 154 155 Returns: 156 str: The response received from the reactor. 157 """ 158 if not self.connected: 159 self.connect() 160 self.serial.reset_input_buffer() 161 self._send(msg) 162 return self._recv() 163 164 def _send(self, msg: str): 165 """ 166 Sends a command to the reactor. 167 168 Args: 169 msg (str): The command to send to the reactor. 170 """ 171 self.serial.write(msg.encode('ascii') + b'\n\r') 172 173 def _recv(self) -> str: 174 """ 175 Reads from the serial port until it finds an End-of-Text ASCII token. 176 177 Returns: 178 str: The response received from the reactor. 179 """ 180 response = self.serial.read_until(b'\x04') \ 181 .decode('ascii') \ 182 .strip("\n") \ 183 .strip("\r") \ 184 .strip("\x04") 185 return response 186 187 188 def __repr__(self): 189 return f"{bcolors.OKCYAN}<Reactor at {self.port}>{bcolors.ENDC}" 190 191 def set(self, data: Optional[Dict[str, Any]] = None, **kwargs: Any): 192 """ 193 Sets the value of variables based on a dictionary. 194 195 Args: 196 data (dict, optional): Dictionary containing variable-value pairs. 197 **kwargs: Additional variable-value pairs. 198 199 Examples: 200 >>> reator.set({"440": 50, "brilho": 100}) 201 """ 202 data = {**(data or {}), **kwargs} 203 args = ",".join(f'{k},{v}' for k, v in data.items()) 204 cmd = f"set({args})" 205 self._send(cmd) 206 207 def get(self, key: Optional[str] = None) -> Any: 208 """ 209 Returns the value of a variable or variables. 210 211 Args: 212 key (str, optional): The key of the variable to retrieve. If not specified, returns all variables. 213 214 Returns: 215 Any: The value of the variable(s). 216 """ 217 if key is None: 218 return self._get_all() 219 if isinstance(key, str): 220 key = [key] 221 return 222 223 def _get_all(self) -> Any: 224 """ 225 Returns the values of all variables. 226 227 Returns: 228 Any: The values of all variables. 229 """ 230 resp = self.send("get") 231 # Parse the response and return the values of all variables. 232 return resp 233 234if __name__=="__main__": 235 rs = ReactorServer(import_name="Pyduino Slave Server") 236 rs.run(port=5000,host="0.0.0.0")
TIMEOUT =
60
HEADER_DELAY =
5
class
ReactorServer(flask.app.Flask):
19class ReactorServer(Flask): 20 """ 21 Slave of HTTP Server to Serial handler. 22 """ 23 24 def __init__(self, serial_port: Optional[str] = None, baudrate: int = 9600, *args: Any, **kwargs: Any): 25 """ 26 Initializes the ReactorServer. 27 28 Args: 29 serial_port (str, optional): The serial port to connect to. If not specified, it searches for available devices. 30 baudrate (int, optional): The baud rate for the serial connection. 31 *args: Variable length arguments to pass to the Flask constructor. 32 **kwargs: Keyword arguments to pass to the Flask constructor. 33 """ 34 logging.debug("Creating server.") 35 super().__init__(*args, **kwargs) 36 self.connected = False 37 self.reactor_id: Optional[int] = None 38 self.port = serial_port 39 self.baudrate = baudrate 40 41 self.serial_connect() 42 43 # Routes 44 @self.route("/") 45 def root(): 46 """ 47 Root route handler. 48 """ 49 return "REACTOR SERVER", 200 50 51 @self.route("/connect") 52 def http_connect(): 53 """ 54 HTTP route for connecting to the reactor. 55 """ 56 self.connect() 57 return "OK", 200 58 59 @self.route("/reset") 60 def http_reset(): 61 """ 62 HTTP route for resetting the connection. 63 """ 64 self.reset() 65 return "OK", 200 66 67 @self.route("/reboot") 68 def reboot(): 69 """ 70 HTTP route for rebooting the server. 71 """ 72 os.system("sudo reboot") 73 return "OK", 200 74 75 @self.route("/send", methods=['POST']) 76 def http_send(): 77 """ 78 HTTP route for sending commands to the reactor. 79 80 Returns: 81 str: The response received from the reactor. 82 """ 83 content = request.json 84 logging.info(f"Received request: {content['command']}") 85 if content['await_response']: 86 response = self.send(content["command"]) 87 else: 88 response = self._send(content["command"]) 89 return jsonify({"response": response}), 200 90 91 @self.route("/ping") 92 def ping(): 93 """ 94 HTTP route for pinging the reactor. 95 96 Returns: 97 dict: A JSON object containing reactor information (id, serial_port, hostname). 98 """ 99 digit_regex = r"(\d+)(?!.*\d)" 100 hostname = socket.gethostname() 101 digits = int(re.findall(digit_regex, hostname)[0]) 102 self.reactor_id = digits 103 return jsonify({"id": self.reactor_id, "serial_port": self.port, "hostname": os.uname().nodename}) 104 105 def __delete__(self, _): 106 self.serial.__del__() 107 108 109 def serial_connect(self): 110 if self.port is None: 111 logging.debug("No serial port specified. Searching for available devices.") 112 self.available_ports = list_ports.comports() 113 self.available_ports = list(filter(lambda x: (x.vid,x.pid) in {(1027,24577),(9025,16),(6790,29987)},self.available_ports)) 114 self.port = self.available_ports[0].device 115 self.serial = Serial(self.port, baudrate=self.baudrate, timeout=TIMEOUT) 116 logging.info(f"Connected to serial port {self.serial}.") 117 118 def connect(self): 119 """ 120 Begins the connection to the reactor. 121 122 Args: 123 delay (float, optional): Delay in seconds before sending the initial command. 124 """ 125 sleep(HEADER_DELAY) 126 self.serial.flush() 127 self.serial.reset_input_buffer() 128 self._send("quiet_connect") 129 self.connected = True 130 131 def reset(self): 132 """ 133 Resets the connection to the reactor. 134 """ 135 self.serial.flush() 136 self.serial.close() 137 self.serial.open() 138 self.connected = True 139 140 def close(self): 141 """ 142 Interrupts the connection with the reactor. 143 """ 144 if self.serial.is_open: 145 self.serial.reset_input_buffer() 146 self.send("fim") 147 self.serial.close() 148 149 def send(self, msg: str) -> str: 150 """ 151 Sends a command to the reactor and receives the response. 152 153 Args: 154 msg (str): The command to send to the reactor. 155 156 Returns: 157 str: The response received from the reactor. 158 """ 159 if not self.connected: 160 self.connect() 161 self.serial.reset_input_buffer() 162 self._send(msg) 163 return self._recv() 164 165 def _send(self, msg: str): 166 """ 167 Sends a command to the reactor. 168 169 Args: 170 msg (str): The command to send to the reactor. 171 """ 172 self.serial.write(msg.encode('ascii') + b'\n\r') 173 174 def _recv(self) -> str: 175 """ 176 Reads from the serial port until it finds an End-of-Text ASCII token. 177 178 Returns: 179 str: The response received from the reactor. 180 """ 181 response = self.serial.read_until(b'\x04') \ 182 .decode('ascii') \ 183 .strip("\n") \ 184 .strip("\r") \ 185 .strip("\x04") 186 return response 187 188 189 def __repr__(self): 190 return f"{bcolors.OKCYAN}<Reactor at {self.port}>{bcolors.ENDC}" 191 192 def set(self, data: Optional[Dict[str, Any]] = None, **kwargs: Any): 193 """ 194 Sets the value of variables based on a dictionary. 195 196 Args: 197 data (dict, optional): Dictionary containing variable-value pairs. 198 **kwargs: Additional variable-value pairs. 199 200 Examples: 201 >>> reator.set({"440": 50, "brilho": 100}) 202 """ 203 data = {**(data or {}), **kwargs} 204 args = ",".join(f'{k},{v}' for k, v in data.items()) 205 cmd = f"set({args})" 206 self._send(cmd) 207 208 def get(self, key: Optional[str] = None) -> Any: 209 """ 210 Returns the value of a variable or variables. 211 212 Args: 213 key (str, optional): The key of the variable to retrieve. If not specified, returns all variables. 214 215 Returns: 216 Any: The value of the variable(s). 217 """ 218 if key is None: 219 return self._get_all() 220 if isinstance(key, str): 221 key = [key] 222 return 223 224 def _get_all(self) -> Any: 225 """ 226 Returns the values of all variables. 227 228 Returns: 229 Any: The values of all variables. 230 """ 231 resp = self.send("get") 232 # Parse the response and return the values of all variables. 233 return resp
Slave of HTTP Server to Serial handler.
ReactorServer( serial_port: Optional[str] = None, baudrate: int = 9600, *args: Any, **kwargs: Any)
24 def __init__(self, serial_port: Optional[str] = None, baudrate: int = 9600, *args: Any, **kwargs: Any): 25 """ 26 Initializes the ReactorServer. 27 28 Args: 29 serial_port (str, optional): The serial port to connect to. If not specified, it searches for available devices. 30 baudrate (int, optional): The baud rate for the serial connection. 31 *args: Variable length arguments to pass to the Flask constructor. 32 **kwargs: Keyword arguments to pass to the Flask constructor. 33 """ 34 logging.debug("Creating server.") 35 super().__init__(*args, **kwargs) 36 self.connected = False 37 self.reactor_id: Optional[int] = None 38 self.port = serial_port 39 self.baudrate = baudrate 40 41 self.serial_connect() 42 43 # Routes 44 @self.route("/") 45 def root(): 46 """ 47 Root route handler. 48 """ 49 return "REACTOR SERVER", 200 50 51 @self.route("/connect") 52 def http_connect(): 53 """ 54 HTTP route for connecting to the reactor. 55 """ 56 self.connect() 57 return "OK", 200 58 59 @self.route("/reset") 60 def http_reset(): 61 """ 62 HTTP route for resetting the connection. 63 """ 64 self.reset() 65 return "OK", 200 66 67 @self.route("/reboot") 68 def reboot(): 69 """ 70 HTTP route for rebooting the server. 71 """ 72 os.system("sudo reboot") 73 return "OK", 200 74 75 @self.route("/send", methods=['POST']) 76 def http_send(): 77 """ 78 HTTP route for sending commands to the reactor. 79 80 Returns: 81 str: The response received from the reactor. 82 """ 83 content = request.json 84 logging.info(f"Received request: {content['command']}") 85 if content['await_response']: 86 response = self.send(content["command"]) 87 else: 88 response = self._send(content["command"]) 89 return jsonify({"response": response}), 200 90 91 @self.route("/ping") 92 def ping(): 93 """ 94 HTTP route for pinging the reactor. 95 96 Returns: 97 dict: A JSON object containing reactor information (id, serial_port, hostname). 98 """ 99 digit_regex = r"(\d+)(?!.*\d)" 100 hostname = socket.gethostname() 101 digits = int(re.findall(digit_regex, hostname)[0]) 102 self.reactor_id = digits 103 return jsonify({"id": self.reactor_id, "serial_port": self.port, "hostname": os.uname().nodename})
Initializes the ReactorServer.
Arguments:
- serial_port (str, optional): The serial port to connect to. If not specified, it searches for available devices.
- baudrate (int, optional): The baud rate for the serial connection.
- *args: Variable length arguments to pass to the Flask constructor.
- **kwargs: Keyword arguments to pass to the Flask constructor.
def
serial_connect(self):
109 def serial_connect(self): 110 if self.port is None: 111 logging.debug("No serial port specified. Searching for available devices.") 112 self.available_ports = list_ports.comports() 113 self.available_ports = list(filter(lambda x: (x.vid,x.pid) in {(1027,24577),(9025,16),(6790,29987)},self.available_ports)) 114 self.port = self.available_ports[0].device 115 self.serial = Serial(self.port, baudrate=self.baudrate, timeout=TIMEOUT) 116 logging.info(f"Connected to serial port {self.serial}.")
def
connect(self):
118 def connect(self): 119 """ 120 Begins the connection to the reactor. 121 122 Args: 123 delay (float, optional): Delay in seconds before sending the initial command. 124 """ 125 sleep(HEADER_DELAY) 126 self.serial.flush() 127 self.serial.reset_input_buffer() 128 self._send("quiet_connect") 129 self.connected = True
Begins the connection to the reactor.
Arguments:
- delay (float, optional): Delay in seconds before sending the initial command.
def
reset(self):
131 def reset(self): 132 """ 133 Resets the connection to the reactor. 134 """ 135 self.serial.flush() 136 self.serial.close() 137 self.serial.open() 138 self.connected = True
Resets the connection to the reactor.
def
close(self):
140 def close(self): 141 """ 142 Interrupts the connection with the reactor. 143 """ 144 if self.serial.is_open: 145 self.serial.reset_input_buffer() 146 self.send("fim") 147 self.serial.close()
Interrupts the connection with the reactor.
def
send(self, msg: str) -> str:
149 def send(self, msg: str) -> str: 150 """ 151 Sends a command to the reactor and receives the response. 152 153 Args: 154 msg (str): The command to send to the reactor. 155 156 Returns: 157 str: The response received from the reactor. 158 """ 159 if not self.connected: 160 self.connect() 161 self.serial.reset_input_buffer() 162 self._send(msg) 163 return self._recv()
Sends a command to the reactor and receives the response.
Arguments:
- msg (str): The command to send to the reactor.
Returns:
str: The response received from the reactor.
def
set(self, data: Optional[Dict[str, Any]] = None, **kwargs: Any):
192 def set(self, data: Optional[Dict[str, Any]] = None, **kwargs: Any): 193 """ 194 Sets the value of variables based on a dictionary. 195 196 Args: 197 data (dict, optional): Dictionary containing variable-value pairs. 198 **kwargs: Additional variable-value pairs. 199 200 Examples: 201 >>> reator.set({"440": 50, "brilho": 100}) 202 """ 203 data = {**(data or {}), **kwargs} 204 args = ",".join(f'{k},{v}' for k, v in data.items()) 205 cmd = f"set({args})" 206 self._send(cmd)
Sets the value of variables based on a dictionary.
Arguments:
- data (dict, optional): Dictionary containing variable-value pairs.
- **kwargs: Additional variable-value pairs.
Examples:
>>> reator.set({"440": 50, "brilho": 100})
def
get(self, key: Optional[str] = None) -> Any:
208 def get(self, key: Optional[str] = None) -> Any: 209 """ 210 Returns the value of a variable or variables. 211 212 Args: 213 key (str, optional): The key of the variable to retrieve. If not specified, returns all variables. 214 215 Returns: 216 Any: The value of the variable(s). 217 """ 218 if key is None: 219 return self._get_all() 220 if isinstance(key, str): 221 key = [key] 222 return
Returns the value of a variable or variables.
Arguments:
- key (str, optional): The key of the variable to retrieve. If not specified, returns all variables.
Returns:
Any: The value of the variable(s).
Inherited Members
- flask.app.Flask
- default_config
- request_class
- response_class
- session_interface
- cli
- get_send_file_max_age
- send_static_file
- open_resource
- open_instance_resource
- create_jinja_environment
- create_url_adapter
- raise_routing_exception
- update_template_context
- make_shell_context
- run
- test_client
- test_cli_runner
- handle_http_exception
- handle_user_exception
- handle_exception
- log_exception
- dispatch_request
- full_dispatch_request
- finalize_request
- make_default_options_response
- ensure_sync
- async_to_sync
- url_for
- make_response
- preprocess_request
- process_response
- do_teardown_request
- do_teardown_appcontext
- app_context
- request_context
- test_request_context
- wsgi_app
- flask.sansio.app.App
- aborter_class
- jinja_environment
- app_ctx_globals_class
- config_class
- testing
- secret_key
- permanent_session_lifetime
- json_provider_class
- jinja_options
- url_rule_class
- url_map_class
- test_client_class
- test_cli_runner_class
- instance_path
- config
- aborter
- json
- url_build_error_handlers
- teardown_appcontext_funcs
- shell_context_processors
- blueprints
- extensions
- url_map
- subdomain_matching
- name
- logger
- jinja_env
- make_config
- make_aborter
- auto_find_instance_path
- create_global_jinja_loader
- select_jinja_autoescape
- debug
- register_blueprint
- iter_blueprints
- add_url_rule
- template_filter
- add_template_filter
- template_test
- add_template_test
- template_global
- add_template_global
- teardown_appcontext
- shell_context_processor
- trap_http_exception
- should_ignore_error
- redirect
- inject_url_defaults
- handle_url_build_error
- flask.sansio.scaffold.Scaffold
- import_name
- static_folder
- static_url_path
- template_folder
- root_path
- view_functions
- error_handler_spec
- before_request_funcs
- after_request_funcs
- teardown_request_funcs
- template_context_processors
- url_value_preprocessors
- url_default_functions
- has_static_folder
- jinja_loader
- post
- put
- delete
- patch
- route
- endpoint
- before_request
- after_request
- teardown_request
- context_processor
- url_value_preprocessor
- url_defaults
- errorhandler
- register_error_handler