From 35fd193ecf8dd3a34f1b96a3c79fba35e54a53fb Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Wed, 24 Apr 2024 16:57:10 -0700 Subject: [PATCH 1/2] Convert to SocketPool --- ...ket.py => adafruit_esp32spi_socketpool.py} | 146 ++++++++++-------- docs/api.rst | 2 +- examples/esp32spi_ipconfig.py | 8 +- examples/esp32spi_tcp_client.py | 8 +- examples/esp32spi_udp_client.py | 8 +- 5 files changed, 98 insertions(+), 74 deletions(-) rename adafruit_esp32spi/{adafruit_esp32spi_socket.py => adafruit_esp32spi_socketpool.py} (55%) diff --git a/adafruit_esp32spi/adafruit_esp32spi_socket.py b/adafruit_esp32spi/adafruit_esp32spi_socketpool.py similarity index 55% rename from adafruit_esp32spi/adafruit_esp32spi_socket.py rename to adafruit_esp32spi/adafruit_esp32spi_socketpool.py index 347b330..c67ef36 100644 --- a/adafruit_esp32spi/adafruit_esp32spi_socket.py +++ b/adafruit_esp32spi/adafruit_esp32spi_socketpool.py @@ -3,78 +3,103 @@ # SPDX-License-Identifier: MIT """ -`adafruit_esp32spi_socket` +`adafruit_esp32spi_socketpool` ================================================================================ A socket compatible interface thru the ESP SPI command set * Author(s): ladyada """ +from __future__ import annotations + +try: + from typing import TYPE_CHECKING, Optional + + if TYPE_CHECKING: + from esp32spi.adafruit_esp32spi import ESP_SPIcontrol +except ImportError: + pass -# pylint: disable=no-name-in-module import time import gc from micropython import const -from adafruit_esp32spi import adafruit_esp32spi +from adafruit_esp32spi import adafruit_esp32spi as esp32spi -_the_interface = None # pylint: disable=invalid-name +_global_socketpool = {} -def set_interface(iface): - """Helper to set the global internet interface""" - global _the_interface # pylint: disable=global-statement, invalid-name - _the_interface = iface +class SocketPoolContants: # pylint: disable=too-few-public-methods + """Helper class for the constants that are needed everywhere""" + SOCK_STREAM = const(0) + SOCK_DGRAM = const(1) + AF_INET = const(2) + NO_SOCKET_AVAIL = const(255) -SOCK_STREAM = const(0) -SOCK_DGRAM = const(1) -AF_INET = const(2) -NO_SOCKET_AVAIL = const(255) + MAX_PACKET = const(4000) -MAX_PACKET = const(4000) +class SocketPool(SocketPoolContants): + """ESP32SPI SocketPool library""" -# pylint: disable=too-many-arguments, unused-argument -def getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0): - """Given a hostname and a port name, return a 'socket.getaddrinfo' - compatible list of tuples. Honestly, we ignore anything but host & port""" - if not isinstance(port, int): - raise ValueError("Port must be an integer") - ipaddr = _the_interface.get_host_by_name(host) - return [(AF_INET, socktype, proto, "", (ipaddr, port))] + def __new__(cls, iface: ESP_SPIcontrol): + # We want to make sure to return the same pool for the same interface + if iface not in _global_socketpool: + _global_socketpool[iface] = object.__new__(cls) + return _global_socketpool[iface] + def __init__(self, iface: ESP_SPIcontrol): + self._interface = iface -# pylint: enable=too-many-arguments, unused-argument + def getaddrinfo( # pylint: disable=too-many-arguments,unused-argument + self, host, port, family=0, socktype=0, proto=0, flags=0 + ): + """Given a hostname and a port name, return a 'socket.getaddrinfo' + compatible list of tuples. Honestly, we ignore anything but host & port""" + if not isinstance(port, int): + raise ValueError("Port must be an integer") + ipaddr = self._interface.get_host_by_name(host) + return [(SocketPoolContants.AF_INET, socktype, proto, "", (ipaddr, port))] + + def socket( # pylint: disable=redefined-builtin + self, + family=SocketPoolContants.AF_INET, + type=SocketPoolContants.SOCK_STREAM, + proto=0, + fileno=None, + ): + """Create a new socket and return it""" + return Socket(self, family, type, proto, fileno) -# pylint: disable=unused-argument, redefined-builtin, invalid-name -class socket: +class Socket: """A simplified implementation of the Python 'socket' class, for connecting through an interface to a remote device""" - # pylint: disable=too-many-arguments - def __init__( - self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None, socknum=None + def __init__( # pylint: disable=redefined-builtin,too-many-arguments,unused-argument + self, + socket_pool: SocketPool, + family: int = SocketPool.AF_INET, + type: int = SocketPool.SOCK_STREAM, + proto: int = 0, + fileno: Optional[int] = None, ): - if family != AF_INET: + if family != SocketPool.AF_INET: raise ValueError("Only AF_INET family supported") + self._socket_pool = socket_pool + self._interface = self._socket_pool._interface self._type = type self._buffer = b"" - self._socknum = socknum if socknum else _the_interface.get_socket() + self._socknum = self._interface.get_socket() self.settimeout(0) - # pylint: enable=too-many-arguments - def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: self.close() - while ( - _the_interface.socket_status(self._socknum) - != adafruit_esp32spi.SOCKET_CLOSED - ): + while self._interface.socket_status(self._socknum) != esp32spi.SOCKET_CLOSED: pass def connect(self, address, conntype=None): @@ -83,20 +108,20 @@ def connect(self, address, conntype=None): depending on the underlying interface""" host, port = address if conntype is None: - conntype = _the_interface.TCP_MODE - if not _the_interface.socket_connect( + conntype = self._interface.TCP_MODE + if not self._interface.socket_connect( self._socknum, host, port, conn_mode=conntype ): raise ConnectionError("Failed to connect to host", host) self._buffer = b"" - def send(self, data): # pylint: disable=no-self-use + def send(self, data): """Send some data to the socket.""" - if self._type is SOCK_DGRAM: - conntype = _the_interface.UDP_MODE + if self._type is SocketPool.SOCK_DGRAM: + conntype = self._interface.UDP_MODE else: - conntype = _the_interface.TCP_MODE - _the_interface.socket_write(self._socknum, data, conn_mode=conntype) + conntype = self._interface.TCP_MODE + self._interface.socket_write(self._socknum, data, conn_mode=conntype) gc.collect() def recv(self, bufsize: int) -> bytes: @@ -140,7 +165,7 @@ def recv_into(self, buffer, nbytes: int = 0): num_avail = self._available() if num_avail > 0: last_read_time = time.monotonic() - bytes_read = _the_interface.socket_read( + bytes_read = self._interface.socket_read( self._socknum, min(num_to_read, num_avail) ) buffer[num_read : num_read + len(bytes_read)] = bytes_read @@ -162,43 +187,42 @@ def settimeout(self, value): def _available(self): """Returns how many bytes of data are available to be read (up to the MAX_PACKET length)""" - if self._socknum != NO_SOCKET_AVAIL: - return min(_the_interface.socket_available(self._socknum), MAX_PACKET) + if self._socknum != SocketPool.NO_SOCKET_AVAIL: + return min( + self._interface.socket_available(self._socknum), SocketPool.MAX_PACKET + ) return 0 def _connected(self): """Whether or not we are connected to the socket""" - if self._socknum == NO_SOCKET_AVAIL: + if self._socknum == SocketPool.NO_SOCKET_AVAIL: return False if self._available(): return True - status = _the_interface.socket_status(self._socknum) + status = self._interface.socket_status(self._socknum) result = status not in ( - adafruit_esp32spi.SOCKET_LISTEN, - adafruit_esp32spi.SOCKET_CLOSED, - adafruit_esp32spi.SOCKET_FIN_WAIT_1, - adafruit_esp32spi.SOCKET_FIN_WAIT_2, - adafruit_esp32spi.SOCKET_TIME_WAIT, - adafruit_esp32spi.SOCKET_SYN_SENT, - adafruit_esp32spi.SOCKET_SYN_RCVD, - adafruit_esp32spi.SOCKET_CLOSE_WAIT, + esp32spi.SOCKET_LISTEN, + esp32spi.SOCKET_CLOSED, + esp32spi.SOCKET_FIN_WAIT_1, + esp32spi.SOCKET_FIN_WAIT_2, + esp32spi.SOCKET_TIME_WAIT, + esp32spi.SOCKET_SYN_SENT, + esp32spi.SOCKET_SYN_RCVD, + esp32spi.SOCKET_CLOSE_WAIT, ) if not result: self.close() - self._socknum = NO_SOCKET_AVAIL + self._socknum = SocketPool.NO_SOCKET_AVAIL return result def close(self): """Close the socket, after reading whatever remains""" - _the_interface.socket_close(self._socknum) + self._interface.socket_close(self._socknum) -class timeout(TimeoutError): +class timeout(TimeoutError): # pylint: disable=invalid-name """TimeoutError class. An instance of this error will be raised by recv_into() if the timeout has elapsed and we haven't received any data yet.""" def __init__(self, msg): super().__init__(msg) - - -# pylint: enable=unused-argument, redefined-builtin, invalid-name diff --git a/docs/api.rst b/docs/api.rst index 0cfe5a8..7726cc1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -7,7 +7,7 @@ .. automodule:: adafruit_esp32spi.adafruit_esp32spi :members: -.. automodule:: adafruit_esp32spi.adafruit_esp32spi_socket +.. automodule:: adafruit_esp32spi.adafruit_esp32spi_socketpool :members: .. automodule:: adafruit_esp32spi.adafruit_esp32spi_wifimanager diff --git a/examples/esp32spi_ipconfig.py b/examples/esp32spi_ipconfig.py index 644de90..f29e9a1 100644 --- a/examples/esp32spi_ipconfig.py +++ b/examples/esp32spi_ipconfig.py @@ -6,7 +6,7 @@ import board import busio from digitalio import DigitalInOut -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socketpool as socketpool from adafruit_esp32spi import adafruit_esp32spi # Get wifi details and more from a settings.toml file @@ -51,9 +51,9 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) -socket.set_interface(esp) +pool = socketpool.SocketPool(esp) -s_in = socket.socket(type=socket.SOCK_DGRAM) +s_in = pool.socket(type=pool.SOCK_DGRAM) s_in.settimeout(UDP_TIMEOUT) print("set hostname:", HOSTNAME) esp.set_hostname(HOSTNAME) @@ -96,7 +96,7 @@ print("My IP address is", esp.pretty_ip(esp.ip_address)) print("udp in addr: ", UDP_IN_ADDR, UDP_IN_PORT) -socketaddr_udp_in = socket.getaddrinfo(UDP_IN_ADDR, UDP_IN_PORT)[0][4] +socketaddr_udp_in = pool.getaddrinfo(UDP_IN_ADDR, UDP_IN_PORT)[0][4] s_in.connect(socketaddr_udp_in, conntype=esp.UDP_MODE) print("connected local UDP") diff --git a/examples/esp32spi_tcp_client.py b/examples/esp32spi_tcp_client.py index 3985278..2ec5d2a 100644 --- a/examples/esp32spi_tcp_client.py +++ b/examples/esp32spi_tcp_client.py @@ -6,7 +6,7 @@ import busio from digitalio import DigitalInOut from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socketpool as socketpool # Get wifi details and more from a settings.toml file # tokens used by this Demo: CIRCUITPY_WIFI_SSID, CIRCUITPY_WIFI_PASSWORD @@ -48,9 +48,9 @@ print("Server ping:", esp.ping(HOST), "ms") # create the socket -socket.set_interface(esp) -socketaddr = socket.getaddrinfo(HOST, PORT)[0][4] -s = socket.socket() +pool = socketpool.SocketPool(esp) +socketaddr = pool.getaddrinfo(HOST, PORT)[0][4] +s = pool.socket() s.settimeout(TIMEOUT) print("Connecting") diff --git a/examples/esp32spi_udp_client.py b/examples/esp32spi_udp_client.py index 53ac2ba..3dace7b 100644 --- a/examples/esp32spi_udp_client.py +++ b/examples/esp32spi_udp_client.py @@ -8,7 +8,7 @@ import busio from digitalio import DigitalInOut from adafruit_esp32spi import adafruit_esp32spi -import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import adafruit_esp32spi.adafruit_esp32spi_socketpool as socketpool # Get wifi details and more from a settings.toml file # tokens used by this Demo: CIRCUITPY_WIFI_SSID, CIRCUITPY_WIFI_PASSWORD @@ -51,9 +51,9 @@ print("Server ping:", esp.ping(HOST), "ms") # create the socket -socket.set_interface(esp) -socketaddr = socket.getaddrinfo(HOST, PORT)[0][4] -s = socket.socket(type=socket.SOCK_DGRAM) +pool = socketpool.SocketPool(esp) +socketaddr = pool.getaddrinfo(HOST, PORT)[0][4] +s = pool.socket(type=pool.SOCK_DGRAM) s.settimeout(TIMEOUT) From 06281a57ee77fdc6416587cf3656b0aff586a72d Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Fri, 26 Apr 2024 14:11:01 -0700 Subject: [PATCH 2/2] Use standard super() for __new__ --- adafruit_esp32spi/adafruit_esp32spi_socketpool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_esp32spi/adafruit_esp32spi_socketpool.py b/adafruit_esp32spi/adafruit_esp32spi_socketpool.py index c67ef36..2f7ca78 100644 --- a/adafruit_esp32spi/adafruit_esp32spi_socketpool.py +++ b/adafruit_esp32spi/adafruit_esp32spi_socketpool.py @@ -46,7 +46,7 @@ class SocketPool(SocketPoolContants): def __new__(cls, iface: ESP_SPIcontrol): # We want to make sure to return the same pool for the same interface if iface not in _global_socketpool: - _global_socketpool[iface] = object.__new__(cls) + _global_socketpool[iface] = super().__new__(cls) return _global_socketpool[iface] def __init__(self, iface: ESP_SPIcontrol):