From f22383409685d606ceca37528ae19652f005ac91 Mon Sep 17 00:00:00 2001 From: Guilherme Laurindo Schneck <60226932+Schneck2004@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:08:41 +0200 Subject: [PATCH 1/2] ftespnow (#1) * ftespnow: Initial commit * ftespnow: Added function docstrings * ftespnow-server: Added JSON support, and write to dictionary functionality Added the receive_to_json() function, that supports exporting received data to a JSON file, and the receive_to_dict() function, that returns the received data as a dictionary * ftespnow: Fixed typo when importing ".client" extension in __init__.py * ftespnow: Added examples and updated docstrings * ftespnow: Moved manifests.py to the correct folder * ftespnow: Moved __init__.py to the correct folder * ftespnow: Fix .ftespnow/manifest.py. * ftespnow: Fix .ftespnow-client/manifest.py and .ftespnow-server/manifest.py, code formatting, and fixed spelling mistakes. ... Signed-off-by: Guilherme Schneck * ftespnow: Fix .ftespnow-client/manifest.py and .ftespnow-server/manifest.py, code formatting, and fixed spelling mistakes. * ftespnow: Fix code formatting errors. * ftespnow: Fix code formatting errors. --------- Signed-off-by: Guilherme Schneck --- .gitignore | 1 + .../ftespnow/examples/client_side_example.py | 54 +++++ .../ftespnow/examples/server_side_example.py | 52 +++++ .../ftespnow-client/ftespnow/client.py | 217 ++++++++++++++++++ .../ftespnow/ftespnow-client/manifest.py | 7 + .../ftespnow-server/ftespnow/server.py | 217 ++++++++++++++++++ .../ftespnow/ftespnow-server/manifest.py | 7 + .../ftespnow/ftespnow/ftespnow/__init__.py | 9 + micropython/ftespnow/ftespnow/manifest.py | 6 + 9 files changed, 570 insertions(+) create mode 100644 micropython/ftespnow/examples/client_side_example.py create mode 100644 micropython/ftespnow/examples/server_side_example.py create mode 100644 micropython/ftespnow/ftespnow-client/ftespnow/client.py create mode 100644 micropython/ftespnow/ftespnow-client/manifest.py create mode 100644 micropython/ftespnow/ftespnow-server/ftespnow/server.py create mode 100644 micropython/ftespnow/ftespnow-server/manifest.py create mode 100644 micropython/ftespnow/ftespnow/ftespnow/__init__.py create mode 100644 micropython/ftespnow/ftespnow/manifest.py diff --git a/.gitignore b/.gitignore index 65a81ba62..26e7a8ed0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ MANIFEST __pycache__ +.vscode/ *.egg-info */dist/ *.org diff --git a/micropython/ftespnow/examples/client_side_example.py b/micropython/ftespnow/examples/client_side_example.py new file mode 100644 index 000000000..ab72b3a1d --- /dev/null +++ b/micropython/ftespnow/examples/client_side_example.py @@ -0,0 +1,54 @@ +import ftespnow + +# Initialize ESP-NOW client +esp = ftespnow.CLIENT() + +# Connect to ESP-NOW server +esp.connect("a4f00f772d15") # Change to actual server mac address + +# Send a message +message = "Hello" +sent = esp.send_message(message) +if sent: # Check if server received the data + print("Message received by server") +else: + print("Message not received by server") + +# Receive a message +received_data = esp.receive_message() +if received_data is None: # Check if any data was received + print("No message was received (timed out)") +else: + print(f"Here is the received data: {received_data}") + +# Send a .txt file +txt_sent = esp.send_txt("filepath/filename.txt") +if txt_sent: # Check if server received the data + print("File received by server") +else: + print("File not received by server") + +# Send a .json file +json_sent = esp.send_json("filepath/filename.json") +if json_sent: # Check if server received the data + print("File received by server") +else: + print("File not received by server") + +# Write received data to .txt file +txt_received = esp.receive_to_txt("filepath/filename.txt", mode='w') # Set mode to 'w' so file is truncated before writing +if txt_received: + print("File received successfully") +else: + print("No file received. Destination file was not created/modified") + +# Write received data to .json file +json_received = esp.receive_to_json("filepath/filename.json", mode='w') # Set mode to 'w' so file is truncated before writing +if json_received: + print("File received successfully") +else: + print("No file received. Destination file was not created/modified") + +# Write received data to a python dictionary +data_dict = esp.receive_to_dict() +print(data_dict) diff --git a/micropython/ftespnow/examples/server_side_example.py b/micropython/ftespnow/examples/server_side_example.py new file mode 100644 index 000000000..c07d9b9d3 --- /dev/null +++ b/micropython/ftespnow/examples/server_side_example.py @@ -0,0 +1,52 @@ +import ftespnow + +# Initialize ESP-NOW client +esp = ftespnow.SERVER() + +# Send a message +message = "Hello" +peer = "a4f00f772d15" # Mac address of the client that you want to send data to +sent = esp.send_message(peer, message) +if sent: # Check if client received the data + print("Message received by clientclient") +else: + print("Message not received by client") + +# Receive a message +received_data = esp.receive_message() +if received_data is None: # Check if any data was received + print("No message was received (timed out)") +else: + print(f"Here is the received data: {received_data}") + +# Send a .txt file +txt_sent = esp.send_txt(peer, "filepath/filename.txt") +if txt_sent: # Check if client received the data + print("File received by client") +else: + print("File not received by client") + +# Send a .json file +json_sent = esp.send_json(peer, "filepath/filename.json") +if json_sent: # Check if client received the data + print("File received by client") +else: + print("File not received by client") + +# Write received data to .txt file +txt_received = esp.receive_to_txt("filepath/filename.txt", mode='w') # Set mode to 'w' so file is truncated before writing +if txt_received: + print("File received successfully") +else: + print("No file received. Destination file was not created/modified") + +# Write received data to .json file +json_received = esp.receive_to_json("filepath/filename.json", mode='w') # Set mode to 'w' so file is truncated before writing +if json_received: # Check if any data was received + print("File received successfully") +else: + print("No file received. Destination file was not created/modified") + +# Write received data to a python dictionary +data_dict = esp.receive_to_dict() # Will return {} if no data was received +print(data_dict) diff --git a/micropython/ftespnow/ftespnow-client/ftespnow/client.py b/micropython/ftespnow/ftespnow-client/ftespnow/client.py new file mode 100644 index 000000000..811a8734e --- /dev/null +++ b/micropython/ftespnow/ftespnow-client/ftespnow/client.py @@ -0,0 +1,217 @@ +import espnow +import json + +class CLIENT: + def __init__(self, *, timeout: int=5) -> None: + self.esp = espnow.ESPNow() + self.timeout = timeout + + def configure(self, *, timeout: int=5) -> None: + self.timeout: int = timeout + + def connect(self, peer: str) -> None: + self.peer: str = peer + self.esp.active(True) + self.esp.add_peer(self.peer) + + def send_message(self, data: str) -> bool: + """ + Send a string + + Args: + + data (str): Data to be sent + + Returns: + + bool: Confirmation flag (`True` if data was received, `False` otherwise) + """ + + ack: bool = self.esp.send(self.peer, data) + return ack + + def receive_message(self, recv_timeout :int= 5) -> list | None: + """ + Receive a string + + Args: + + recv_timeout (int, optional): Reception timeout. Defaults to 5. + + Returns: + + list | None: `[, ]` | `None` if no message is received + """ + + received = self.esp.recv(recv_timeout) + if received[0] is None: + return + return received + + def send_txt(self, filename: str) -> bool: + """ + Parse and send a `.txt` file as a `string` + + Args: + + filename (str): Filepath of the desired file to be sent with file name and extension + + Returns: + + sent (bool): Confirmation flag (`True` if data was received, `False` otherwise) + """ + + with open(filename, 'r') as f: + data: str = str(f.readlines()) + sent: bool = self.send_message(data) + return sent + + def send_json(self, filename: str, *, indent: int=4) -> bool: + """ + Parse and send a `.json` file as a `string` + + Args: + + filename (str): Filepath of the desired file to be sent with file name and extension + + indent (int, optional): Desired indent of the resulting parsed `string` (for formatting purposes). Defaults to 4. + + Returns: + + sent (bool): Confirmation flag (`True` if data was received, `False` otherwise) + """ + + with open(filename, 'r') as f: + unparsed = json.load(f) + parsed: str = json.dumps(unparsed, indent=indent) + sent: bool = self.send_message(parsed) + return sent + + def receive_to_txt(self, target_file: str, mode: str='a') -> bool: + """ + Write received `string` into a `.txt` file. + + **Will not write or create file if no data is received** + + Args: + + target_file (str): Filepath of the destination file for the received data with file name and extension. + + mode (str, optional): Editing mode + + - `r` - Read only + + - `w` - Write only (truncates file before writing) + + - `x` - Create a new file and open it for writing (raises `FileExistsError` if file already exists) + + - `a` - Append to the end of the file (default) + + - `b` - Binary mode + + - `t` - Text mode + + - `+` - Update (read and write) + + Read `open()`_ for more information + + Returns: + + received (bool): Confirmation flag (`True` if data was received, `False` otherwise) + + .. _open(): https://docs.python.org/3/library/functions.html#open + """ + + if ".txt" not in target_file: + raise SyntaxError("File format must be .txt") + try: + received: bool = False + data: list | None = self.receive_message() + if data is None: + return received + data_list: list[str] = str(data[-1]).split("\n") + if data_list[-1] == "": + data_list = data_list[:-1] + with open(target_file, mode) as f: + f.writelines(data_list) + return not received + except SyntaxError: + raise + + def receive_to_json(self, target_file: str, mode: str='a') -> bool: + """ + Write received `string` into a `.json` file. + + **Will not write or create file if no data is received** + + Args: + + target_file (str): Filepath of the destination file for the received data with file name and extension. + + mode (str, optional): Editing mode + + - `r` - Read only + + - `w` - Write only (truncates file before writing) + + - `x` - Create a new file and open it for writing (raises `FileExistsError` if file already exists) + + - `a` - Append to the end of the file (default) + + - `b` - Binary mode + + - `t` - Text mode + + - `+` - Update (read and write) + + Read `open()`_ for more information + + Returns: + + received (bool): Confirmation flag (`True` if data was received, `False` otherwise) + + .. _open(): https://docs.python.org/3/library/functions.html#open + """ + + if ".json" not in target_file: + raise SyntaxError("File format must be .json") + try: + received: bool = False + data: list | None = self.receive_message() + if data is None: + return received + mac: str = str(data[0]) + message = json.loads(str(data[-1])) + unparsed: dict = { + "mac": mac, + "message": message + } + with open(target_file, mode) as f: + json.dump(unparsed, f) + return not received + except SyntaxError: + raise + + def receive_to_dict(self) -> dict: + """ + Unparses received `string` into a `dict` object + + Args: + + None: + + Returns: + + unparsed (dict): `dictionary` object containing unparsed equivalent of the received `.json` + """ + + data: list | None = self.receive_message() + if data is None: + return {} + mac: str = str(data[0]) + message = json.loads(str(data[-1])) + unparsed: dict = { + "mac": mac, + "message": message + } + return unparsed diff --git a/micropython/ftespnow/ftespnow-client/manifest.py b/micropython/ftespnow/ftespnow-client/manifest.py new file mode 100644 index 000000000..66b5882bc --- /dev/null +++ b/micropython/ftespnow/ftespnow-client/manifest.py @@ -0,0 +1,7 @@ +metadata( + description="Client-side commands", + version="0.1.0", +) + +require("ftespnow") +package("ftespnow") diff --git a/micropython/ftespnow/ftespnow-server/ftespnow/server.py b/micropython/ftespnow/ftespnow-server/ftespnow/server.py new file mode 100644 index 000000000..b0635631b --- /dev/null +++ b/micropython/ftespnow/ftespnow-server/ftespnow/server.py @@ -0,0 +1,217 @@ +import espnow +import json + +class SERVER: + def __init__(self, *, timeout: int=5) -> None: + self.esp = espnow.ESPNow() + self.timeout = timeout + + def configure(self, *, timeout: int=5) -> None: + self.timeout = timeout + + def send_message(self, peer: str, data: str) -> bool: + """ + Send a string + + Args: + + peer (str): client's mac address + + data (str): Data to be sent + + Returns: + + bool: Confirmation flag (`True` if data was received, `False` otherwise) + """ + + ack: bool = self.esp.send(peer, data) + return ack + + def receive_message(self, recv_timeout :int= 5) -> list | None: + """ + Receive a string + + Args: + + recv_timeout (int, optional): Reception timeout. Defaults to 5. + + Returns: + + list | None: `[, ]` | `None` if no message is received + """ + + received = self.esp.recv(recv_timeout) + if received[0] is None: + return + return received + + def send_txt(self, peer: str, filename: str) -> bool: + """ + Parse and send a `.txt` file as a `string` + + Args: + + peer (str): client's mac address + + filename (str): Filepath of the desired file to be sent with file name and extension + + Returns: + + sent (bool): Confirmation flag (`True` if data was received, `False` otherwise) + """ + + with open(filename, 'r') as f: + data: str = str(f.readlines()) + sent: bool = self.send_message(peer, data) + return sent + + def send_json(self, peer: str, filename: str, *, indent: int=4) -> bool: + """ + Parse and send a `.json` file as a `string` + + Args: + + peer (str): client's mac address + + filename (str): Filepath of the desired file to be sent with file name and extension + + indent (int, optional): Desired indent of the resulting parsed `string` (for formatting purposes). Defaults to 4. + + Returns: + + sent (bool): Confirmation flag (`True` if data was received, `False` otherwise) + """ + + with open(filename, 'r') as f: + unparsed = json.load(f) + parsed: str = json.dumps(unparsed, indent=indent) + sent: bool = self.send_message(peer, parsed) + return sent + + def receive_to_txt(self, target_file: str, mode: str='a') -> bool: + """ + Write received `string` into a `.txt` file. + + **Will not write or create file if no data is received** + + Args: + + target_file (str): Filepath of the destination file for the received data with file name and extension. + + mode (str, optional): Editing mode + + - `r` - Read only + + - `w` - Write only (truncates file before writing) + + - `x` - Create a new file and open it for writing (raises `FileExistsError` if file already exists) + + - `a` - Append to the end of the file (default) + + - `b` - Binary mode + + - `t` - Text mode + + - `+` - Update (read and write) + + Read `open()`_ for more information + + Returns: + + received (bool): Confirmation flag (`True` if data was received, `False` otherwise) + + .. _open(): https://docs.python.org/3/library/functions.html#open + """ + + if ".txt" not in target_file: + raise SyntaxError("File format must be .txt") + try: + data: list | None = self.receive_message() + if data is None: + return False + data_list: list[str] = str(data[-1]).split("\n") + if data_list[-1] == "": + data_list = data_list[:-1] + with open(target_file, mode) as f: + f.writelines(data_list) + return True + except SyntaxError: + raise + + def receive_to_json(self, target_file: str, mode: str='a') -> bool: + """ + Write received `string` into a `.json` file. + + **Will not write or create file if no data is received** + + Args: + + target_file (str): Filepath of the destination file for the received data with file name and extension. + + mode (str, optional): Editing mode + + - `r` - Read only + + - `w` - Write only (truncates file before writing) + + - `x` - Create a new file and open it for writing (raises `FileExistsError` if file already exists) + + - `a` - Append to the end of the file (default) + + - `b` - Binary mode + + - `t` - Text mode + + - `+` - Update (read and write) + + Read `open()`_ for more information + + Returns: + + received (bool): Confirmation flag (`True` if data was received, `False` otherwise) + + .. _open(): https://docs.python.org/3/library/functions.html#open + """ + + if ".json" not in target_file: + raise SyntaxError("File format must be .json") + try: + received: bool = False + data: list | None = self.receive_message() + if data is None: + return received + mac: str = str(data[0]) + message = json.loads(str(data[-1])) + unparsed: dict = { + "mac": mac, + "message": message + } + with open(target_file, mode) as f: + json.dump(unparsed, f) + return not received + except SyntaxError: + raise + + def receive_to_dict(self) -> dict: + """ + Unparses received `string` into a `dict` object + + Args: + + None: + + Returns: + + unparsed (dict): `dictionary` object containing unparsed equivalent of the received `.json` + """ + + data: list | None = self.receive_message() + if data is None: + return {} + mac: str = str(data[0]) + message = json.loads(str(data[-1])) + unparsed: dict = { + "mac": mac, + "message": message + } + return unparsed diff --git a/micropython/ftespnow/ftespnow-server/manifest.py b/micropython/ftespnow/ftespnow-server/manifest.py new file mode 100644 index 000000000..add02364e --- /dev/null +++ b/micropython/ftespnow/ftespnow-server/manifest.py @@ -0,0 +1,7 @@ +metadata( + description="Server-side commands", + version="0.1.0", +) + +require("ftespnow") +package("ftespnow") diff --git a/micropython/ftespnow/ftespnow/ftespnow/__init__.py b/micropython/ftespnow/ftespnow/ftespnow/__init__.py new file mode 100644 index 000000000..d12258d10 --- /dev/null +++ b/micropython/ftespnow/ftespnow/ftespnow/__init__.py @@ -0,0 +1,9 @@ +try: + from .client import * +except ImportError: + pass + +try: + from.server import * +except ImportError: + pass diff --git a/micropython/ftespnow/ftespnow/manifest.py b/micropython/ftespnow/ftespnow/manifest.py new file mode 100644 index 000000000..9b87abb7c --- /dev/null +++ b/micropython/ftespnow/ftespnow/manifest.py @@ -0,0 +1,6 @@ +metadata( + description="Extends the micropython espnow module with methods to support file transfers.", + version="0.1.0", +) + +package("ftespnow") From ab236a9466e2b68913f74bdb45cf2b670e9d8913 Mon Sep 17 00:00:00 2001 From: Guilherme Laurindo Schneck Date: Fri, 10 Apr 2026 22:38:31 +0200 Subject: [PATCH 2/2] ftespnow: Publish version 0.1.0 of the library. * ftespnow: Initial commit * ftespnow: Added function docstrings * ftespnow-server: Added JSON support, and write to dictionary functionality Added the receive_to_json() function, that supports exporting received data to a JSON file, and the receive_to_dict() function, that returns the received data as a dictionary * ftespnow: Fixed typo when importing ".client" extension in __init__.py * ftespnow: Added examples and updated docstrings * ftespnow: Moved manifests.py to the correct folder * ftespnow: Moved __init__.py to the correct folder * ftespnow: Fix .ftespnow/manifest.py. * ftespnow: Fix .ftespnow-client/manifest.py and .ftespnow-server/manifest.py, code formatting, and fixed spelling mistakes. ... Signed-off-by: Guilherme Schneck * ftespnow: Fix .ftespnow-client/manifest.py and .ftespnow-server/manifest.py, code formatting, and fixed spelling mistakes. * ftespnow: Fix code formatting errors. * ftespnow: Fix code formatting errors. --------- Signed-off-by: Guilherme Schneck --- .gitignore | 1 + .../ftespnow/examples/client_side_example.py | 58 +++++ .../ftespnow/examples/server_side_example.py | 56 +++++ .../ftespnow-client/ftespnow/client.py | 212 ++++++++++++++++++ .../ftespnow/ftespnow-client/manifest.py | 7 + .../ftespnow-server/ftespnow/server.py | 211 +++++++++++++++++ .../ftespnow/ftespnow-server/manifest.py | 7 + .../ftespnow/ftespnow/ftespnow/__init__.py | 9 + micropython/ftespnow/ftespnow/manifest.py | 6 + micropython/usb/usb-device-hid/manifest.py | 2 +- .../usb/usb-device-hid/usb/device/hid.py | 6 +- 11 files changed, 573 insertions(+), 2 deletions(-) create mode 100644 micropython/ftespnow/examples/client_side_example.py create mode 100644 micropython/ftespnow/examples/server_side_example.py create mode 100644 micropython/ftespnow/ftespnow-client/ftespnow/client.py create mode 100644 micropython/ftespnow/ftespnow-client/manifest.py create mode 100644 micropython/ftespnow/ftespnow-server/ftespnow/server.py create mode 100644 micropython/ftespnow/ftespnow-server/manifest.py create mode 100644 micropython/ftespnow/ftespnow/ftespnow/__init__.py create mode 100644 micropython/ftespnow/ftespnow/manifest.py diff --git a/.gitignore b/.gitignore index 65a81ba62..26e7a8ed0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ MANIFEST __pycache__ +.vscode/ *.egg-info */dist/ *.org diff --git a/micropython/ftespnow/examples/client_side_example.py b/micropython/ftespnow/examples/client_side_example.py new file mode 100644 index 000000000..90ccfada7 --- /dev/null +++ b/micropython/ftespnow/examples/client_side_example.py @@ -0,0 +1,58 @@ +import ftespnow + +# Initialize ESP-NOW client +esp = ftespnow.CLIENT() + +# Connect to ESP-NOW server +esp.connect("a4f00f772d15") # Change to actual server mac address + +# Send a message +message = "Hello" +sent = esp.send_message(message) +if sent: # Check if server received the data + print("Message received by server") +else: + print("Message not received by server") + +# Receive a message +received_data = esp.receive_message() +if received_data is None: # Check if any data was received + print("No message was received (timed out)") +else: + print(f"Here is the received data: {received_data}") + +# Send a .txt file +txt_sent = esp.send_txt("filepath/filename.txt") +if txt_sent: # Check if server received the data + print("File received by server") +else: + print("File not received by server") + +# Send a .json file +json_sent = esp.send_json("filepath/filename.json") +if json_sent: # Check if server received the data + print("File received by server") +else: + print("File not received by server") + +# Write received data to .txt file +txt_received = esp.receive_to_txt( + "filepath/filename.txt", mode="w" + ) # Set mode to 'w' so file is truncated before writing +if txt_received: + print("File received successfully") +else: + print("No file received. Destination file was not created/modified") + +# Write received data to .json file +json_received = esp.receive_to_json( + "filepath/filename.json", mode="w" + ) # Set mode to 'w' so file is truncated before writing +if json_received: + print("File received successfully") +else: + print("No file received. Destination file was not created/modified") + +# Write received data to a python dictionary +data_dict = esp.receive_to_dict() +print(data_dict) diff --git a/micropython/ftespnow/examples/server_side_example.py b/micropython/ftespnow/examples/server_side_example.py new file mode 100644 index 000000000..836030d4f --- /dev/null +++ b/micropython/ftespnow/examples/server_side_example.py @@ -0,0 +1,56 @@ +import ftespnow + +# Initialize ESP-NOW client +esp = ftespnow.SERVER() + +# Send a message +message = "Hello" +peer = "a4f00f772d15" # Mac address of the client that you want to send data to +sent = esp.send_message(peer, message) +if sent: # Check if client received the data + print("Message received by clientclient") +else: + print("Message not received by client") + +# Receive a message +received_data = esp.receive_message() +if received_data is None: # Check if any data was received + print("No message was received (timed out)") +else: + print(f"Here is the received data: {received_data}") + +# Send a .txt file +txt_sent = esp.send_txt(peer, "filepath/filename.txt") +if txt_sent: # Check if client received the data + print("File received by client") +else: + print("File not received by client") + +# Send a .json file +json_sent = esp.send_json(peer, "filepath/filename.json") +if json_sent: # Check if client received the data + print("File received by client") +else: + print("File not received by client") + +# Write received data to .txt file +txt_received = esp.receive_to_txt( + "filepath/filename.txt", mode="w" + ) # Set mode to 'w' so file is truncated before writing +if txt_received: + print("File received successfully") +else: + print("No file received. Destination file was not created/modified") + +# Write received data to .json file +json_received = esp.receive_to_json( + "filepath/filename.json", mode="w" + ) # Set mode to 'w' so file is truncated before writing +if json_received: # Check if any data was received + print("File received successfully") +else: + print("No file received. Destination file was not created/modified") + +# Write received data to a python dictionary +data_dict = esp.receive_to_dict() # Will return {} if no data was received +print(data_dict) diff --git a/micropython/ftespnow/ftespnow-client/ftespnow/client.py b/micropython/ftespnow/ftespnow-client/ftespnow/client.py new file mode 100644 index 000000000..4b931e658 --- /dev/null +++ b/micropython/ftespnow/ftespnow-client/ftespnow/client.py @@ -0,0 +1,212 @@ +import espnow +import json + + +class CLIENT: + def __init__(self, *, timeout: int = 5) -> None: + self.esp = espnow.ESPNow() + self.timeout = timeout + + def configure(self, *, timeout: int = 5) -> None: + self.timeout: int = timeout + + def connect(self, peer: str) -> None: + self.peer: str = peer + self.esp.active(True) + self.esp.add_peer(self.peer) + + def send_message(self, data: str) -> bool: + """ + Send a string + + Args: + + data (str): Data to be sent + + Returns: + + bool: Confirmation flag (`True` if data was received, `False` otherwise) + """ + + ack: bool = self.esp.send(self.peer, data) + return ack + + def receive_message(self, recv_timeout :int = 5) -> list | None: + """ + Receive a string + + Args: + + recv_timeout (int, optional): Reception timeout. Defaults to 5. + + Returns: + + list | None: `[, ]` | `None` if no message is received + """ + + received = self.esp.recv(recv_timeout) + if received[0] is None: + return + return received + + def send_txt(self, filename: str) -> bool: + """ + Parse and send a `.txt` file as a `string` + + Args: + + filename (str): Filepath of the desired file to be sent with file name and extension + + Returns: + + sent (bool): Confirmation flag (`True` if data was received, `False` otherwise) + """ + + with open(filename, "r") as f: + data: str = str(f.readlines()) + sent: bool = self.send_message(data) + return sent + + def send_json(self, filename: str, *, indent: int = 4) -> bool: + """ + Parse and send a `.json` file as a `string` + + Args: + + filename (str): Filepath of the desired file to be sent with file name and extension + + indent (int, optional): Desired indent of the resulting parsed `string` (for formatting purposes). Defaults to 4. + + Returns: + + sent (bool): Confirmation flag (`True` if data was received, `False` otherwise) + """ + + with open(filename, "r") as f: + unparsed = json.load(f) + parsed: str = json.dumps(unparsed, indent=indent) + sent: bool = self.send_message(parsed) + return sent + + def receive_to_txt(self, target_file: str, mode: str = "a") -> bool: + """ + Write received `string` into a `.txt` file. + + **Will not write or create file if no data is received** + + Args: + + target_file (str): Filepath of the destination file for the received data with file name and extension. + + mode (str, optional): Editing mode + + - `r` - Read only + + - `w` - Write only (truncates file before writing) + + - `x` - Create a new file and open it for writing (raises `FileExistsError` if file already exists) + + - `a` - Append to the end of the file (default) + + - `b` - Binary mode + + - `t` - Text mode + + - `+` - Update (read and write) + + Read `open()`_ for more information + + Returns: + + received (bool): Confirmation flag (`True` if data was received, `False` otherwise) + + .. _open(): https://docs.python.org/3/library/functions.html#open + """ + + if ".txt" not in target_file: + raise SyntaxError("File format must be .txt") + try: + received: bool = False + data: list | None = self.receive_message() + if data is None: + return received + data_list: list[str] = str(data[-1]).split("\n") + if data_list[-1] == "": + data_list = data_list[:-1] + with open(target_file, mode) as f: + f.writelines(data_list) + return not received + except SyntaxError: + raise + + def receive_to_json(self, target_file: str, mode: str = "a") -> bool: + """ + Write received `string` into a `.json` file. + + **Will not write or create file if no data is received** + + Args: + + target_file (str): Filepath of the destination file for the received data with file name and extension. + + mode (str, optional): Editing mode + + - `r` - Read only + + - `w` - Write only (truncates file before writing) + + - `x` - Create a new file and open it for writing (raises `FileExistsError` if file already exists) + + - `a` - Append to the end of the file (default) + + - `b` - Binary mode + + - `t` - Text mode + + - `+` - Update (read and write) + + Read `open()`_ for more information + + Returns: + + received (bool): Confirmation flag (`True` if data was received, `False` otherwise) + + .. _open(): https://docs.python.org/3/library/functions.html#open + """ + + if ".json" not in target_file: + raise SyntaxError("File format must be .json") + try: + received: bool = False + data: list | None = self.receive_message() + if data is None: + return received + mac: str = str(data[0]) + message = json.loads(str(data[-1])) + unparsed: dict = {"mac": mac, "message": message} + with open(target_file, mode) as f: + json.dump(unparsed, f) + return not received + except SyntaxError: + raise + + def receive_to_dict(self) -> dict: + """ + Unparses received `string` into a `dict` object + + Args: + + None: + + Returns: + + unparsed (dict): `dictionary` object containing unparsed equivalent of the received `.json` + """ + + data: list | None = self.receive_message() + if data is None: + return {} + mac: str = str(data[0]) + message = json.loads(str(data[-1])) + unparsed: dict = {"mac": mac, "message": message} + return unparsed diff --git a/micropython/ftespnow/ftespnow-client/manifest.py b/micropython/ftespnow/ftespnow-client/manifest.py new file mode 100644 index 000000000..66b5882bc --- /dev/null +++ b/micropython/ftespnow/ftespnow-client/manifest.py @@ -0,0 +1,7 @@ +metadata( + description="Client-side commands", + version="0.1.0", +) + +require("ftespnow") +package("ftespnow") diff --git a/micropython/ftespnow/ftespnow-server/ftespnow/server.py b/micropython/ftespnow/ftespnow-server/ftespnow/server.py new file mode 100644 index 000000000..9fdb61c3e --- /dev/null +++ b/micropython/ftespnow/ftespnow-server/ftespnow/server.py @@ -0,0 +1,211 @@ +import espnow +import json + +class SERVER: + def __init__(self, *, timeout: int = 5) -> None: + self.esp = espnow.ESPNow() + self.timeout = timeout + + def configure(self, *, timeout: int = 5) -> None: + self.timeout = timeout + + def send_message(self, peer: str, data: str) -> bool: + """ + Send a string + + Args: + + peer (str): client's mac address + + data (str): Data to be sent + + Returns: + + bool: Confirmation flag (`True` if data was received, `False` otherwise) + """ + + ack: bool = self.esp.send(peer, data) + return ack + + def receive_message(self, recv_timeout :int = 5) -> list | None: + """ + Receive a string + + Args: + + recv_timeout (int, optional): Reception timeout. Defaults to 5. + + Returns: + + list | None: `[, ]` | `None` if no message is received + """ + + received = self.esp.recv(recv_timeout) + if received[0] is None: + return + return received + + def send_txt(self, peer: str, filename: str) -> bool: + """ + Parse and send a `.txt` file as a `string` + + Args: + + peer (str): client's mac address + + filename (str): Filepath of the desired file to be sent with file name and extension + + Returns: + + sent (bool): Confirmation flag (`True` if data was received, `False` otherwise) + """ + + with open(filename, "r") as f: + data: str = str(f.readlines()) + sent: bool = self.send_message(peer, data) + return sent + + def send_json(self, peer: str, filename: str, *, indent: int = 4) -> bool: + """ + Parse and send a `.json` file as a `string` + + Args: + + peer (str): client's mac address + + filename (str): Filepath of the desired file to be sent with file name and extension + + indent (int, optional): Desired indent of the resulting parsed `string` (for formatting purposes). Defaults to 4. + + Returns: + + sent (bool): Confirmation flag (`True` if data was received, `False` otherwise) + """ + + with open(filename, "r") as f: + unparsed = json.load(f) + parsed: str = json.dumps(unparsed, indent=indent) + sent: bool = self.send_message(peer, parsed) + return sent + + def receive_to_txt(self, target_file: str, mode: str = "a") -> bool: + """ + Write received `string` into a `.txt` file. + + **Will not write or create file if no data is received** + + Args: + + target_file (str): Filepath of the destination file for the received data with file name and extension. + + mode (str, optional): Editing mode + + - `r` - Read only + + - `w` - Write only (truncates file before writing) + + - `x` - Create a new file and open it for writing (raises `FileExistsError` if file already exists) + + - `a` - Append to the end of the file (default) + + - `b` - Binary mode + + - `t` - Text mode + + - `+` - Update (read and write) + + Read `open()`_ for more information + + Returns: + + received (bool): Confirmation flag (`True` if data was received, `False` otherwise) + + .. _open(): https://docs.python.org/3/library/functions.html#open + """ + + if ".txt" not in target_file: + raise SyntaxError("File format must be .txt") + try: + data: list | None = self.receive_message() + if data is None: + return False + data_list: list[str] = str(data[-1]).split("\n") + if data_list[-1] == "": + data_list = data_list[:-1] + with open(target_file, mode) as f: + f.writelines(data_list) + return True + except SyntaxError: + raise + + def receive_to_json(self, target_file: str, mode: str = "a") -> bool: + """ + Write received `string` into a `.json` file. + + **Will not write or create file if no data is received** + + Args: + + target_file (str): Filepath of the destination file for the received data with file name and extension. + + mode (str, optional): Editing mode + + - `r` - Read only + + - `w` - Write only (truncates file before writing) + + - `x` - Create a new file and open it for writing (raises `FileExistsError` if file already exists) + + - `a` - Append to the end of the file (default) + + - `b` - Binary mode + + - `t` - Text mode + + - `+` - Update (read and write) + + Read `open()`_ for more information + + Returns: + + received (bool): Confirmation flag (`True` if data was received, `False` otherwise) + + .. _open(): https://docs.python.org/3/library/functions.html#open + """ + + if ".json" not in target_file: + raise SyntaxError("File format must be .json") + try: + received: bool = False + data: list | None = self.receive_message() + if data is None: + return received + mac: str = str(data[0]) + message = json.loads(str(data[-1])) + unparsed: dict = {"mac": mac, "message": message} + with open(target_file, mode) as f: + json.dump(unparsed, f) + return not received + except SyntaxError: + raise + + def receive_to_dict(self) -> dict: + """ + Unparses received `string` into a `dict` object + + Args: + + None: + + Returns: + + unparsed (dict): `dictionary` object containing unparsed equivalent of the received `.json` + """ + + data: list | None = self.receive_message() + if data is None: + return {} + mac: str = str(data[0]) + message = json.loads(str(data[-1])) + unparsed: dict = {"mac": mac, "message": message} + return unparsed diff --git a/micropython/ftespnow/ftespnow-server/manifest.py b/micropython/ftespnow/ftespnow-server/manifest.py new file mode 100644 index 000000000..add02364e --- /dev/null +++ b/micropython/ftespnow/ftespnow-server/manifest.py @@ -0,0 +1,7 @@ +metadata( + description="Server-side commands", + version="0.1.0", +) + +require("ftespnow") +package("ftespnow") diff --git a/micropython/ftespnow/ftespnow/ftespnow/__init__.py b/micropython/ftespnow/ftespnow/ftespnow/__init__.py new file mode 100644 index 000000000..a4eccc5a1 --- /dev/null +++ b/micropython/ftespnow/ftespnow/ftespnow/__init__.py @@ -0,0 +1,9 @@ +try: + from .client import * +except ImportError: + pass + +try: + from .server import * +except ImportError: + pass diff --git a/micropython/ftespnow/ftespnow/manifest.py b/micropython/ftespnow/ftespnow/manifest.py new file mode 100644 index 000000000..9b87abb7c --- /dev/null +++ b/micropython/ftespnow/ftespnow/manifest.py @@ -0,0 +1,6 @@ +metadata( + description="Extends the micropython espnow module with methods to support file transfers.", + version="0.1.0", +) + +package("ftespnow") diff --git a/micropython/usb/usb-device-hid/manifest.py b/micropython/usb/usb-device-hid/manifest.py index e844b6f01..88fa821c5 100644 --- a/micropython/usb/usb-device-hid/manifest.py +++ b/micropython/usb/usb-device-hid/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.2") +metadata(version="0.2.0") require("usb-device") package("usb") diff --git a/micropython/usb/usb-device-hid/usb/device/hid.py b/micropython/usb/usb-device-hid/usb/device/hid.py index 07664fb02..e4d1bc53f 100644 --- a/micropython/usb/usb-device-hid/usb/device/hid.py +++ b/micropython/usb/usb-device-hid/usb/device/hid.py @@ -64,6 +64,7 @@ def __init__( set_report_buf=None, protocol=_INTERFACE_PROTOCOL_NONE, interface_str=None, + interval_ms=8, ): # Construct a new HID interface. # @@ -85,12 +86,15 @@ def __init__( # - protocol can be set to a specific value as per HID v1.11 section 4.3 Protocols, p9. # # - interface_str is an optional string descriptor to associate with the HID USB interface. + # + # - interval_ms this is the polling rate the device will request the host use in milliseconds. super().__init__() self.report_descriptor = report_descriptor self.extra_descriptors = extra_descriptors self._set_report_buf = set_report_buf self.protocol = protocol self.interface_str = interface_str + self.interval_ms = interval_ms self._int_ep = None # set during enumeration @@ -150,7 +154,7 @@ def desc_cfg(self, desc, itf_num, ep_num, strs): # Add the typical single USB interrupt endpoint descriptor associated # with a HID interface. self._int_ep = ep_num | _EP_IN_FLAG - desc.endpoint(self._int_ep, "interrupt", 8, 8) + desc.endpoint(self._int_ep, "interrupt", 8, self.interval_ms) self.idle_rate = 0