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.
connected
reactor_id: Optional[int]
port
baudrate
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