Skip to content

Commit

Permalink
Merge pull request #198 from justmobilize/convert-to-socketpool
Browse files Browse the repository at this point in the history
Convert to SocketPool
  • Loading branch information
dhalbert authored Apr 30, 2024
2 parents 956d6a0 + 06281a5 commit a48d516
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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] = super().__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):
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
2 changes: 1 addition & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions examples/esp32spi_ipconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")

Expand Down
8 changes: 4 additions & 4 deletions examples/esp32spi_tcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
8 changes: 4 additions & 4 deletions examples/esp32spi_udp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit a48d516

Please sign in to comment.