diff --git a/adafruit_esp32spi/adafruit_esp32spi.py b/adafruit_esp32spi/adafruit_esp32spi.py index 96bc200..3e16df0 100644 --- a/adafruit_esp32spi/adafruit_esp32spi.py +++ b/adafruit_esp32spi/adafruit_esp32spi.py @@ -54,6 +54,8 @@ # pylint: disable=bad-whitespace _SET_NET_CMD = const(0x10) _SET_PASSPHRASE_CMD = const(0x11) +_SET_AP_NET_CMD = const(0x18) +_SET_AP_PASSPHRASE_CMD = const(0x19) _SET_DEBUG_CMD = const(0x1A) _GET_CONN_STATUS_CMD = const(0x20) @@ -410,6 +412,18 @@ def wifi_set_entenable(self): if resp[0][0] != 1: raise RuntimeError("Failed to enable enterprise mode") + def _wifi_set_ap_network(self, ssid, channel): + """Creates an Access point with SSID and Channel""" + resp = self._send_command_get_response(_SET_AP_NET_CMD, [ssid, channel]) + if resp[0][0] != 1: + raise RuntimeError("Failed to setup AP network") + + def _wifi_set_ap_passphrase(self, ssid, passphrase, channel): + """Creates an Access point with SSID, passphrase, and Channel""" + resp = self._send_command_get_response(_SET_AP_PASSPHRASE_CMD, [ssid, passphrase, channel]) + if resp[0][0] != 1: + raise RuntimeError("Failed to setup AP password") + @property def ssid(self): """The name of the access point we're connected to""" @@ -444,15 +458,30 @@ def is_connected(self): self.reset() return False + @property + def ap_listening(self): + """Returns if the ESP32 is in access point mode and is listening for connections""" + try: + return self.status == WL_AP_LISTENING + except RuntimeError: + self.reset() + return False + def connect(self, secrets): """Connect to an access point using a secrets dictionary that contains a 'ssid' and 'password' entry""" self.connect_AP(secrets['ssid'], secrets['password']) - def connect_AP(self, ssid, password): # pylint: disable=invalid-name - """Connect to an access point with given name and password. - Will retry up to 10 times and return on success or raise - an exception on failure""" + def connect_AP(self, ssid, password, timeout_s=10): # pylint: disable=invalid-name + """ + Connect to an access point with given name and password. + Will wait until specified timeout seconds and return on success + or raise an exception on failure. + + :param ssid: the SSID to connect to + :param passphrase: the password of the access point + :param timeout_s: number of seconds until we time out and fail to create AP + """ if self._debug: print("Connect to AP", ssid, password) if isinstance(ssid, str): @@ -463,17 +492,57 @@ def connect_AP(self, ssid, password): # pylint: disable=invalid-name self.wifi_set_passphrase(ssid, password) else: self.wifi_set_network(ssid) - for _ in range(10): # retries + times = time.monotonic() + while (time.monotonic() - times) < timeout_s: # wait up until timeout stat = self.status if stat == WL_CONNECTED: return stat - time.sleep(1) + time.sleep(0.05) if stat in (WL_CONNECT_FAILED, WL_CONNECTION_LOST, WL_DISCONNECTED): raise RuntimeError("Failed to connect to ssid", ssid) if stat == WL_NO_SSID_AVAIL: raise RuntimeError("No such ssid", ssid) raise RuntimeError("Unknown error 0x%02X" % stat) + def create_AP(self, ssid, password, channel=1, timeout=10): # pylint: disable=invalid-name + """ + Create an access point with the given name, password, and channel. + Will wait until specified timeout seconds and return on success + or raise an exception on failure. + + :param str ssid: the SSID of the created Access Point. Must be less than 32 chars. + :param str password: the password of the created Access Point. Must be 8-63 chars. + :param int channel: channel of created Access Point (1 - 14). + :param int timeout: number of seconds until we time out and fail to create AP + """ + if len(ssid) > 32: + raise RuntimeError("ssid must be no more than 32 characters") + if password and (len(password) < 8 or len(password) > 64): + raise RuntimeError("password must be 8 - 63 characters") + if channel < 1 or channel > 14: + raise RuntimeError("channel must be between 1 and 14") + + if isinstance(channel, int): + channel = bytes(channel) + if isinstance(ssid, str): + ssid = bytes(ssid, 'utf-8') + if password: + if isinstance(password, str): + password = bytes(password, 'utf-8') + self._wifi_set_ap_passphrase(ssid, password, channel) + else: + self._wifi_set_ap_network(ssid, channel) + + times = time.monotonic() + while (time.monotonic() - times) < timeout: # wait up to timeout + stat = self.status + if stat == WL_AP_LISTENING: + return stat + time.sleep(0.05) + if stat == WL_AP_FAILED: + raise RuntimeError("Failed to create AP", ssid) + raise RuntimeError("Unknown error 0x%02x" % stat) + def pretty_ip(self, ip): # pylint: disable=no-self-use, invalid-name """Converts a bytearray IP address to a dotted-quad string for printing""" return "%d.%d.%d.%d" % (ip[0], ip[1], ip[2], ip[3]) diff --git a/adafruit_esp32spi/adafruit_esp32spi_wifimanager.py b/adafruit_esp32spi/adafruit_esp32spi_wifimanager.py index 28b08b0..5719e95 100755 --- a/adafruit_esp32spi/adafruit_esp32spi_wifimanager.py +++ b/adafruit_esp32spi/adafruit_esp32spi_wifimanager.py @@ -36,6 +36,7 @@ from adafruit_esp32spi import adafruit_esp32spi import adafruit_esp32spi.adafruit_esp32spi_requests as requests + class ESPSPI_WiFiManager: """ A class to help manage the Wifi connection @@ -43,8 +44,9 @@ class ESPSPI_WiFiManager: NORMAL = const(1) ENTERPRISE = const(2) -# pylint: disable=too-many-arguments - def __init__(self, esp, secrets, status_pixel=None, attempts=2, connection_type=NORMAL): + # pylint: disable=too-many-arguments + def __init__(self, esp, secrets, status_pixel=None, attempts=2, + connection_type=NORMAL, debug=False): """ :param ESP_SPIcontrol esp: The ESP object we are using :param dict secrets: The WiFi and Adafruit IO secrets dict (See examples) @@ -56,9 +58,9 @@ def __init__(self, esp, secrets, status_pixel=None, attempts=2, connection_type= """ # Read the settings self.esp = esp - self.debug = False + self.debug = debug self.ssid = secrets['ssid'] - self.password = secrets['password'] + self.password = secrets.get('password', None) self.attempts = attempts self._connection_type = connection_type requests.set_interface(self.esp) @@ -78,7 +80,7 @@ def __init__(self, esp, secrets, status_pixel=None, attempts=2, connection_type= self.ent_user = secrets['ent_user'] if secrets.get('ent_password'): self.ent_password = secrets['ent_password'] -# pylint: enable=too-many-arguments + # pylint: enable=too-many-arguments def reset(self): """ @@ -127,6 +129,33 @@ def connect_normal(self): self.reset() continue + def create_ap(self): + """ + Attempt to initialize in Access Point (AP) mode. + Uses SSID and optional passphrase from the current settings + Other WiFi devices will be able to connect to the created Access Point + """ + failure_count = 0 + while not self.esp.ap_listening: + try: + if self.debug: + print("Waiting for AP to be initialized...") + self.pixel_status((100, 0, 0)) + if self.password: + self.esp.create_AP(bytes(self.ssid, 'utf-8'), bytes(self.password, 'utf-8')) + else: + self.esp.create_AP(bytes(self.ssid, 'utf-8'), None) + failure_count = 0 + self.pixel_status((0, 100, 0)) + except (ValueError, RuntimeError) as error: + print("Failed to create access point\n", error) + failure_count += 1 + if failure_count >= self.attempts: + failure_count = 0 + self.reset() + continue + print("Access Point created! Connect to ssid:\n {}".format(self.ssid)) + def connect_enterprise(self): """ Attempt an enterprise style WiFi connection diff --git a/examples/server/esp32spi_wsgiserver.py b/examples/server/esp32spi_wsgiserver.py index c971300..a247c77 100644 --- a/examples/server/esp32spi_wsgiserver.py +++ b/examples/server/esp32spi_wsgiserver.py @@ -45,10 +45,20 @@ # import adafruit_dotstar as dotstar # status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=1) -## Connect to wifi with secrets +## If you want to connect to wifi with secrets: wifi = wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) wifi.connect() +## If you want to create a WIFI hotspot to connect to with secrets: +# secrets = {"ssid": "My ESP32 AP!", "password": "supersecret"} +# wifi = wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) +# wifi.create_ap() + +## To you want to create an un-protected WIFI hotspot to connect to with secrets:" +# secrets = {"ssid": "My ESP32 AP!"} +# wifi = wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) +# wifi.create_ap() + class SimpleWSGIApplication: """ An example of a simple WSGI Application that supports diff --git a/examples/server/static/index.html b/examples/server/static/index.html index 460a37b..11568a2 100755 --- a/examples/server/static/index.html +++ b/examples/server/static/index.html @@ -1,14 +1,10 @@
- - - -