49
49
50
50
"""
51
51
52
+ import gc
52
53
import time
53
54
from digitalio import Direction
54
- import gc
55
55
56
56
__version__ = "0.0.0-auto.0"
57
57
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_espATcontrol.git"
58
58
59
59
class OKError (Exception ):
60
+ """The exception thrown when we didn't get acknowledgement to an AT command"""
60
61
pass
61
62
62
63
class ESP_ATcontrol_socket :
64
+ """A 'socket' compatible interface thru the ESP AT command set"""
63
65
def __init__ (self , esp ):
64
66
self ._esp = esp
65
67
66
- def getaddrinfo (self , host , port ,
67
- family = 0 , socktype = 0 , proto = 0 , flags = 0 ):
68
- # honestly, we ignore anything but host & port
68
+ def getaddrinfo (self , host , port , # pylint: disable=too-many-arguments
69
+ family = 0 , socktype = 0 , proto = 0 , flags = 0 ): # pylint: disable=unused-argument
70
+ """Given a hostname and a port name, return a 'socket.getaddrinfo'
71
+ compatible list of tuples. Honestly, we ignore anything but host & port"""
69
72
if not isinstance (port , int ):
70
73
raise RuntimeError ("port must be an integer" )
71
- ip = self ._esp .nslookup (host )
72
- return [(family , socktype , proto , '' , (ip , port ))]
74
+ ipaddr = self ._esp .nslookup (host )
75
+ return [(family , socktype , proto , '' , (ipaddr , port ))]
73
76
74
77
class ESP_ATcontrol :
75
78
"""A wrapper for AT commands to a connected ESP8266 or ESP32 module to do
76
79
some very basic internetting. The ESP module must be pre-programmed with
77
80
AT command firmware, you can use esptool or our CircuitPython miniesptool
78
81
to upload firmware"""
82
+ #pylint: disable=too-many-public-methods, too-many-instance-attributes
79
83
MODE_STATION = 1
80
84
MODE_SOFTAP = 2
81
85
MODE_SOFTAPSTATION = 3
@@ -89,9 +93,9 @@ class ESP_ATcontrol:
89
93
USER_AGENT = "esp-idf/1.0 esp32"
90
94
91
95
def __init__ (self , uart , default_baudrate , * , run_baudrate = None ,
92
- rts_pin = None , reset_pin = None , debug = False ):
93
- # this function doesn't try to do any sync'ing, just sets up
94
- # the hardware, that way nothing can unexpectedly fail!
96
+ rts_pin = None , reset_pin = None , debug = False ):
97
+ """This function doesn't try to do any sync'ing, just sets up
98
+ # the hardware, that way nothing can unexpectedly fail!"""
95
99
self ._uart = uart
96
100
if not run_baudrate :
97
101
run_baudrate = default_baudrate
@@ -111,11 +115,15 @@ def __init__(self, uart, default_baudrate, *, run_baudrate=None,
111
115
self ._debug = debug
112
116
self ._versionstrings = []
113
117
self ._version = None
114
- self ._IPDpacket = bytearray (1500 )
118
+ self ._ipdpacket = bytearray (1500 )
115
119
self ._ifconfig = []
116
120
self ._initialized = False
117
121
118
122
def begin (self ):
123
+ """Initialize the module by syncing, resetting if necessary, setting up
124
+ the desired baudrate, turning on single-socket mode, and configuring
125
+ SSL support. Required before using the module but we dont do in __init__
126
+ because this can throw an exception."""
119
127
# Connect and sync
120
128
for _ in range (3 ):
121
129
try :
@@ -179,37 +187,44 @@ def request_url(self, url, ssl=False):
179
187
return (header , data )
180
188
181
189
def connect (self , settings ):
190
+ """Repeatedly try to connect to an access point with the details in
191
+ the passed in 'settings' dictionary. Be sure 'ssid' and 'password' are
192
+ defined in the settings dict! If 'timezone' is set, we'll also configure
193
+ SNTP"""
182
194
# Connect to WiFi if not already
183
195
while True :
184
196
try :
185
197
if not self ._initialized :
186
198
self .begin ()
187
- AP = self .remote_AP
199
+ AP = self .remote_AP # pylint: disable=invalid-name
188
200
print ("Connected to" , AP [0 ])
189
201
if AP [0 ] != settings ['ssid' ]:
190
202
self .join_AP (settings ['ssid' ], settings ['password' ])
191
203
if 'timezone' in settings :
192
- tz = settings ['timezone' ]
204
+ tzone = settings ['timezone' ]
193
205
ntp = None
194
206
if 'ntp_server' in settings :
195
207
ntp = settings ['ntp_server' ]
196
- self .sntp_config (True , tz , ntp )
208
+ self .sntp_config (True , tzone , ntp )
197
209
print ("My IP Address:" , self .local_ip )
198
210
return # yay!
199
- except (RuntimeError , OKError ) as e :
200
- print ("Failed to connect, retrying\n " , e )
211
+ except (RuntimeError , OKError ) as exp :
212
+ print ("Failed to connect, retrying\n " , exp )
201
213
continue
202
214
203
- """*************************** SOCKET SETUP ****************************"""
215
+ # *************************** SOCKET SETUP ****************************
216
+
204
217
@property
205
218
def cipmux (self ):
219
+ """The IP socket multiplexing setting. 0 for one socket, 1 for multi-socket"""
206
220
replies = self .at_response ("AT+CIPMUX?" , timeout = 3 ).split (b'\r \n ' )
207
221
for reply in replies :
208
222
if reply .startswith (b'+CIPMUX:' ):
209
223
return int (reply [8 :])
210
224
raise RuntimeError ("Bad response to CIPMUX?" )
211
225
212
226
def socket (self ):
227
+ """Create a 'socket' object"""
213
228
return ESP_ATcontrol_socket (self )
214
229
215
230
def socket_connect (self , conntype , remote , remote_port , * , keepalive = 10 , retries = 1 ):
@@ -268,6 +283,7 @@ def socket_send(self, buffer, timeout=1):
268
283
return True
269
284
270
285
def socket_receive (self , timeout = 5 ):
286
+ # pylint: disable=too-many-nested-blocks
271
287
"""Check for incoming data over the open socket, returns bytes"""
272
288
incoming_bytes = None
273
289
bundle = b''
@@ -281,38 +297,35 @@ def socket_receive(self, timeout=5):
281
297
if not incoming_bytes :
282
298
self .hw_flow (False ) # stop the flow
283
299
# read one byte at a time
284
- self ._IPDpacket [i ] = self ._uart .read (1 )[0 ]
285
- if chr (self ._IPDpacket [0 ]) != '+' :
300
+ self ._ipdpacket [i ] = self ._uart .read (1 )[0 ]
301
+ if chr (self ._ipdpacket [0 ]) != '+' :
286
302
i = 0 # keep goin' till we start with +
287
303
continue
288
304
i += 1
289
305
# look for the IPD message
290
- if (ipd_start in self ._IPDpacket ) and chr (self ._IPDpacket [i - 1 ]) == ':' :
306
+ if (ipd_start in self ._ipdpacket ) and chr (self ._ipdpacket [i - 1 ]) == ':' :
291
307
try :
292
- s = str (self ._IPDpacket [5 :i - 1 ], 'utf-8' )
293
- incoming_bytes = int (s )
308
+ ipd = str (self ._ipdpacket [5 :i - 1 ], 'utf-8' )
309
+ incoming_bytes = int (ipd )
294
310
if self ._debug :
295
311
print ("Receiving:" , incoming_bytes )
296
312
except ValueError :
297
- raise RuntimeError ("Parsing error during receive" , s )
313
+ raise RuntimeError ("Parsing error during receive" , ipd )
298
314
i = 0 # reset the input buffer now that we know the size
299
315
else :
300
316
self .hw_flow (False ) # stop the flow
301
317
# read as much as we can!
302
318
toread = min (incoming_bytes - i , self ._uart .in_waiting )
303
319
#print("i ", i, "to read:", toread)
304
- self ._IPDpacket [i :i + toread ] = self ._uart .read (toread )
320
+ self ._ipdpacket [i :i + toread ] = self ._uart .read (toread )
305
321
i += toread
306
322
if i == incoming_bytes :
307
- #print(self._IPDpacket [0:i])
323
+ #print(self._ipdpacket [0:i])
308
324
gc .collect ()
309
- bundle += self ._IPDpacket [0 :i ]
325
+ bundle += self ._ipdpacket [0 :i ]
310
326
i = incoming_bytes = 0
311
327
else : # no data waiting
312
328
self .hw_flow (True ) # start the floooow
313
- else :
314
- #print("TIMED OUT")
315
- pass
316
329
return bundle
317
330
318
331
def socket_disconnect (self ):
@@ -322,32 +335,38 @@ def socket_disconnect(self):
322
335
except OKError :
323
336
pass # this is ok, means we didn't have an open socket
324
337
325
- """ *************************** SNTP SETUP ****************************"""
338
+ # *************************** SNTP SETUP ****************************
326
339
327
- def sntp_config (self , en , timezone = None , server = None ):
328
- at = "AT+CIPSNTPCFG="
329
- if en :
330
- at += '1'
340
+ def sntp_config (self , enable , timezone = None , server = None ):
341
+ """Configure the built in ESP SNTP client with a UTC-offset number (timezone)
342
+ and server as IP or hostname."""
343
+ cmd = "AT+CIPSNTPCFG="
344
+ if enable :
345
+ cmd += '1'
331
346
else :
332
- at += '0'
347
+ cmd += '0'
333
348
if timezone is not None :
334
- at += ',%d' % timezone
349
+ cmd += ',%d' % timezone
335
350
if server is not None :
336
- at += ',"%s"' % server
337
- self .at_response (at , timeout = 3 )
351
+ cmd += ',"%s"' % server
352
+ self .at_response (cmd , timeout = 3 )
338
353
339
354
@property
340
355
def sntp_time (self ):
356
+ """Return a string with time/date information using SNTP, may return
357
+ 1970 'bad data' on the first few minutes, without warning!"""
341
358
replies = self .at_response ("AT+CIPSNTPTIME?" , timeout = 5 ).split (b'\r \n ' )
342
359
for reply in replies :
343
360
if reply .startswith (b'+CIPSNTPTIME:' ):
344
361
return reply [13 :]
345
362
return None
346
363
347
- """ *************************** WIFI SETUP ****************************"""
364
+ # *************************** WIFI SETUP ****************************
348
365
349
366
@property
350
367
def is_connected (self ):
368
+ """Initialize module if not done yet, and check if we're connected to
369
+ an access point, returns True or False"""
351
370
if not self ._initialized :
352
371
self .begin ()
353
372
try :
@@ -363,6 +382,7 @@ def is_connected(self):
363
382
364
383
@property
365
384
def status (self ):
385
+ """The IP connection status number (see AT+CIPSTATUS datasheet for meaning)"""
366
386
replies = self .at_response ("AT+CIPSTATUS" , timeout = 5 ).split (b'\r \n ' )
367
387
for reply in replies :
368
388
if reply .startswith (b'STATUS:' ):
@@ -391,14 +411,15 @@ def mode(self, mode):
391
411
392
412
@property
393
413
def local_ip (self ):
394
- """Our local IP address as a dotted-octal string"""
414
+ """Our local IP address as a dotted-quad string"""
395
415
reply = self .at_response ("AT+CIFSR" ).strip (b'\r \n ' )
396
416
for line in reply .split (b'\r \n ' ):
397
417
if line and line .startswith (b'+CIFSR:STAIP,"' ):
398
418
return str (line [14 :- 1 ], 'utf-8' )
399
419
raise RuntimeError ("Couldn't find IP address" )
400
420
401
421
def ping (self , host ):
422
+ """Ping the IP or hostname given, returns ms time or None on failure"""
402
423
reply = self .at_response ('AT+PING="%s"' % host .strip ('"' ), timeout = 5 )
403
424
for line in reply .split (b'\r \n ' ):
404
425
if line and line .startswith (b'+PING:' ):
@@ -409,12 +430,14 @@ def ping(self, host):
409
430
raise RuntimeError ("Couldn't ping" )
410
431
411
432
def nslookup (self , host ):
433
+ """Return a dotted-quad IP address strings that matches the hostname"""
412
434
reply = self .at_response ('AT+CIPDOMAIN="%s"' % host .strip ('"' ), timeout = 3 )
413
435
for line in reply .split (b'\r \n ' ):
414
436
if line and line .startswith (b'+CIPDOMAIN:' ):
415
437
return str (line [11 :], 'utf-8' )
416
438
raise RuntimeError ("Couldn't find IP address" )
417
- """*************************** AP SETUP ****************************"""
439
+
440
+ # *************************** AP SETUP ****************************
418
441
419
442
@property
420
443
def remote_AP (self ): # pylint: disable=invalid-name
@@ -479,10 +502,11 @@ def scan_APs(self, retries=3): # pylint: disable=invalid-name
479
502
routers .append (router )
480
503
return routers
481
504
482
- """ ************************** AT LOW LEVEL ****************************"""
505
+ # ************************** AT LOW LEVEL ****************************
483
506
484
507
@property
485
508
def version (self ):
509
+ """The cached version string retrieved via the AT+GMR command"""
486
510
return self ._version
487
511
488
512
def get_version (self ):
@@ -500,6 +524,7 @@ def get_version(self):
500
524
501
525
502
526
def hw_flow (self , flag ):
527
+ """Turn on HW flow control (if available) on to allow data, or off to stop"""
503
528
if self ._rts_pin :
504
529
self ._rts_pin .value = not flag
505
530
@@ -508,6 +533,7 @@ def at_response(self, at_cmd, timeout=5, retries=3):
508
533
and then cut out the reply lines to return. We can set
509
534
a variable timeout (how long we'll wait for response) and
510
535
how many times to retry before giving up"""
536
+ #pylint: disable=too-many-branches
511
537
for _ in range (retries ):
512
538
self .hw_flow (True ) # allow any remaning data to stream in
513
539
time .sleep (0.1 ) # wait for uart data
@@ -571,9 +597,9 @@ def baudrate(self, baudrate):
571
597
that we're still sync'd."""
572
598
at_cmd = "AT+UART_CUR=" + str (baudrate )+ ",8,1,0,"
573
599
if self ._rts_pin is not None :
574
- at_cmd += "2"
600
+ at_cmd += "2"
575
601
else :
576
- at_cmd += "0"
602
+ at_cmd += "0"
577
603
at_cmd += "\r \n "
578
604
if self ._debug :
579
605
print ("Changing baudrate to:" , baudrate )
@@ -608,6 +634,7 @@ def soft_reset(self):
608
634
return False
609
635
610
636
def factory_reset (self ):
637
+ """Perform a hard reset, then send factory restore settings request"""
611
638
self .hard_reset ()
612
639
self .at_response ("AT+RESTORE" , timeout = 1 )
613
640
self ._initialized = False
0 commit comments