3
3
# SPDX-License-Identifier: MIT
4
4
5
5
"""
6
- `adafruit_esp32spi_socket `
6
+ `adafruit_esp32spi_socketpool `
7
7
================================================================================
8
8
9
9
A socket compatible interface thru the ESP SPI command set
10
10
11
11
* Author(s): ladyada
12
12
"""
13
+ from __future__ import annotations
14
+
15
+ try :
16
+ from typing import TYPE_CHECKING , Optional
17
+
18
+ if TYPE_CHECKING :
19
+ from esp32spi .adafruit_esp32spi import ESP_SPIcontrol
20
+ except ImportError :
21
+ pass
13
22
14
- # pylint: disable=no-name-in-module
15
23
16
24
import time
17
25
import gc
18
26
from micropython import const
19
- from adafruit_esp32spi import adafruit_esp32spi
27
+ from adafruit_esp32spi import adafruit_esp32spi as esp32spi
20
28
21
- _the_interface = None # pylint: disable=invalid-name
29
+ _global_socketpool = {}
22
30
23
31
24
- def set_interface (iface ):
25
- """Helper to set the global internet interface"""
26
- global _the_interface # pylint: disable=global-statement, invalid-name
27
- _the_interface = iface
32
+ class SocketPoolContants : # pylint: disable=too-few-public-methods
33
+ """Helper class for the constants that are needed everywhere"""
28
34
35
+ SOCK_STREAM = const (0 )
36
+ SOCK_DGRAM = const (1 )
37
+ AF_INET = const (2 )
38
+ NO_SOCKET_AVAIL = const (255 )
29
39
30
- SOCK_STREAM = const (0 )
31
- SOCK_DGRAM = const (1 )
32
- AF_INET = const (2 )
33
- NO_SOCKET_AVAIL = const (255 )
40
+ MAX_PACKET = const (4000 )
34
41
35
- MAX_PACKET = const (4000 )
36
42
43
+ class SocketPool (SocketPoolContants ):
44
+ """ESP32SPI SocketPool library"""
37
45
38
- # pylint: disable=too-many-arguments, unused-argument
39
- def getaddrinfo (host , port , family = 0 , socktype = 0 , proto = 0 , flags = 0 ):
40
- """Given a hostname and a port name, return a 'socket.getaddrinfo'
41
- compatible list of tuples. Honestly, we ignore anything but host & port"""
42
- if not isinstance (port , int ):
43
- raise ValueError ("Port must be an integer" )
44
- ipaddr = _the_interface .get_host_by_name (host )
45
- return [(AF_INET , socktype , proto , "" , (ipaddr , port ))]
46
+ def __new__ (cls , iface : ESP_SPIcontrol ):
47
+ # We want to make sure to return the same pool for the same interface
48
+ if iface not in _global_socketpool :
49
+ _global_socketpool [iface ] = object .__new__ (cls )
50
+ return _global_socketpool [iface ]
46
51
52
+ def __init__ (self , iface : ESP_SPIcontrol ):
53
+ self ._interface = iface
47
54
48
- # pylint: enable=too-many-arguments, unused-argument
55
+ def getaddrinfo ( # pylint: disable=too-many-arguments,unused-argument
56
+ self , host , port , family = 0 , socktype = 0 , proto = 0 , flags = 0
57
+ ):
58
+ """Given a hostname and a port name, return a 'socket.getaddrinfo'
59
+ compatible list of tuples. Honestly, we ignore anything but host & port"""
60
+ if not isinstance (port , int ):
61
+ raise ValueError ("Port must be an integer" )
62
+ ipaddr = self ._interface .get_host_by_name (host )
63
+ return [(SocketPoolContants .AF_INET , socktype , proto , "" , (ipaddr , port ))]
64
+
65
+ def socket ( # pylint: disable=redefined-builtin
66
+ self ,
67
+ family = SocketPoolContants .AF_INET ,
68
+ type = SocketPoolContants .SOCK_STREAM ,
69
+ proto = 0 ,
70
+ fileno = None ,
71
+ ):
72
+ """Create a new socket and return it"""
73
+ return Socket (self , family , type , proto , fileno )
49
74
50
75
51
- # pylint: disable=unused-argument, redefined-builtin, invalid-name
52
- class socket :
76
+ class Socket :
53
77
"""A simplified implementation of the Python 'socket' class, for connecting
54
78
through an interface to a remote device"""
55
79
56
- # pylint: disable=too-many-arguments
57
- def __init__ (
58
- self , family = AF_INET , type = SOCK_STREAM , proto = 0 , fileno = None , socknum = None
80
+ def __init__ ( # pylint: disable=redefined-builtin,too-many-arguments,unused-argument
81
+ self ,
82
+ socket_pool : SocketPool ,
83
+ family : int = SocketPool .AF_INET ,
84
+ type : int = SocketPool .SOCK_STREAM ,
85
+ proto : int = 0 ,
86
+ fileno : Optional [int ] = None ,
59
87
):
60
- if family != AF_INET :
88
+ if family != SocketPool . AF_INET :
61
89
raise ValueError ("Only AF_INET family supported" )
90
+ self ._socket_pool = socket_pool
91
+ self ._interface = self ._socket_pool ._interface
62
92
self ._type = type
63
93
self ._buffer = b""
64
- self ._socknum = socknum if socknum else _the_interface .get_socket ()
94
+ self ._socknum = self . _interface .get_socket ()
65
95
self .settimeout (0 )
66
96
67
- # pylint: enable=too-many-arguments
68
-
69
97
def __enter__ (self ):
70
98
return self
71
99
72
100
def __exit__ (self , exc_type , exc_val , exc_tb ) -> None :
73
101
self .close ()
74
- while (
75
- _the_interface .socket_status (self ._socknum )
76
- != adafruit_esp32spi .SOCKET_CLOSED
77
- ):
102
+ while self ._interface .socket_status (self ._socknum ) != esp32spi .SOCKET_CLOSED :
78
103
pass
79
104
80
105
def connect (self , address , conntype = None ):
@@ -83,20 +108,20 @@ def connect(self, address, conntype=None):
83
108
depending on the underlying interface"""
84
109
host , port = address
85
110
if conntype is None :
86
- conntype = _the_interface .TCP_MODE
87
- if not _the_interface .socket_connect (
111
+ conntype = self . _interface .TCP_MODE
112
+ if not self . _interface .socket_connect (
88
113
self ._socknum , host , port , conn_mode = conntype
89
114
):
90
115
raise ConnectionError ("Failed to connect to host" , host )
91
116
self ._buffer = b""
92
117
93
- def send (self , data ): # pylint: disable=no-self-use
118
+ def send (self , data ):
94
119
"""Send some data to the socket."""
95
- if self ._type is SOCK_DGRAM :
96
- conntype = _the_interface .UDP_MODE
120
+ if self ._type is SocketPool . SOCK_DGRAM :
121
+ conntype = self . _interface .UDP_MODE
97
122
else :
98
- conntype = _the_interface .TCP_MODE
99
- _the_interface .socket_write (self ._socknum , data , conn_mode = conntype )
123
+ conntype = self . _interface .TCP_MODE
124
+ self . _interface .socket_write (self ._socknum , data , conn_mode = conntype )
100
125
gc .collect ()
101
126
102
127
def recv (self , bufsize : int ) -> bytes :
@@ -140,7 +165,7 @@ def recv_into(self, buffer, nbytes: int = 0):
140
165
num_avail = self ._available ()
141
166
if num_avail > 0 :
142
167
last_read_time = time .monotonic ()
143
- bytes_read = _the_interface .socket_read (
168
+ bytes_read = self . _interface .socket_read (
144
169
self ._socknum , min (num_to_read , num_avail )
145
170
)
146
171
buffer [num_read : num_read + len (bytes_read )] = bytes_read
@@ -162,43 +187,42 @@ def settimeout(self, value):
162
187
163
188
def _available (self ):
164
189
"""Returns how many bytes of data are available to be read (up to the MAX_PACKET length)"""
165
- if self ._socknum != NO_SOCKET_AVAIL :
166
- return min (_the_interface .socket_available (self ._socknum ), MAX_PACKET )
190
+ if self ._socknum != SocketPool .NO_SOCKET_AVAIL :
191
+ return min (
192
+ self ._interface .socket_available (self ._socknum ), SocketPool .MAX_PACKET
193
+ )
167
194
return 0
168
195
169
196
def _connected (self ):
170
197
"""Whether or not we are connected to the socket"""
171
- if self ._socknum == NO_SOCKET_AVAIL :
198
+ if self ._socknum == SocketPool . NO_SOCKET_AVAIL :
172
199
return False
173
200
if self ._available ():
174
201
return True
175
- status = _the_interface .socket_status (self ._socknum )
202
+ status = self . _interface .socket_status (self ._socknum )
176
203
result = status not in (
177
- adafruit_esp32spi .SOCKET_LISTEN ,
178
- adafruit_esp32spi .SOCKET_CLOSED ,
179
- adafruit_esp32spi .SOCKET_FIN_WAIT_1 ,
180
- adafruit_esp32spi .SOCKET_FIN_WAIT_2 ,
181
- adafruit_esp32spi .SOCKET_TIME_WAIT ,
182
- adafruit_esp32spi .SOCKET_SYN_SENT ,
183
- adafruit_esp32spi .SOCKET_SYN_RCVD ,
184
- adafruit_esp32spi .SOCKET_CLOSE_WAIT ,
204
+ esp32spi .SOCKET_LISTEN ,
205
+ esp32spi .SOCKET_CLOSED ,
206
+ esp32spi .SOCKET_FIN_WAIT_1 ,
207
+ esp32spi .SOCKET_FIN_WAIT_2 ,
208
+ esp32spi .SOCKET_TIME_WAIT ,
209
+ esp32spi .SOCKET_SYN_SENT ,
210
+ esp32spi .SOCKET_SYN_RCVD ,
211
+ esp32spi .SOCKET_CLOSE_WAIT ,
185
212
)
186
213
if not result :
187
214
self .close ()
188
- self ._socknum = NO_SOCKET_AVAIL
215
+ self ._socknum = SocketPool . NO_SOCKET_AVAIL
189
216
return result
190
217
191
218
def close (self ):
192
219
"""Close the socket, after reading whatever remains"""
193
- _the_interface .socket_close (self ._socknum )
220
+ self . _interface .socket_close (self ._socknum )
194
221
195
222
196
- class timeout (TimeoutError ):
223
+ class timeout (TimeoutError ): # pylint: disable=invalid-name
197
224
"""TimeoutError class. An instance of this error will be raised by recv_into() if
198
225
the timeout has elapsed and we haven't received any data yet."""
199
226
200
227
def __init__ (self , msg ):
201
228
super ().__init__ (msg )
202
-
203
-
204
- # pylint: enable=unused-argument, redefined-builtin, invalid-name
0 commit comments