Skip to content

Add Access point mode #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 75 additions & 6 deletions adafruit_esp32spi/adafruit_esp32spi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -409,6 +411,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"""
Expand Down Expand Up @@ -443,15 +457,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):
Expand All @@ -462,17 +491,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_s=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 ssid: the SSID of the created Access Point. Must be 8 or more characters
:param passphrase: the password of the created Access Point. Must be 8 or more characters.
:param channel: channel of created Access Point (1 - 14).
:param timeout_s: number of seconds until we time out and fail to create AP
"""
if len(ssid) < 8:
raise RuntimeError("ssid must be 8 more more characters")
if len(password) < 8:
raise RuntimeError("password must be 8 or more 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_s: # wait up until 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])
Expand Down
38 changes: 33 additions & 5 deletions adafruit_esp32spi/adafruit_esp32spi_wifimanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@
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
"""
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, line-too-long
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)
Expand All @@ -56,9 +57,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)
Expand All @@ -78,7 +79,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, line-too-long

def reset(self):
"""
Expand Down Expand Up @@ -127,6 +128,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: {}".format(self.ssid))

def connect_enterprise(self):
"""
Attempt an enterprise style WiFi connection
Expand Down