diff --git a/adafruit_esp32spi/adafruit_esp32spi.py b/adafruit_esp32spi/adafruit_esp32spi.py index e5f109a..8ca576c 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) @@ -64,6 +66,7 @@ _GET_CURR_ENCT_CMD = const(0x26) _SCAN_NETWORKS = const(0x27) +_START_SERVER_TCP_CMD = const(0x28) _GET_SOCKET_CMD = const(0x3F) _GET_STATE_TCP_CMD = const(0x29) _DATA_SENT_TCP_CMD = const(0x2A) @@ -409,6 +412,19 @@ 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): + """TODO Docs""" + 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): + """TODO Docs""" + """ TODO: Why does this command refuse to work? creating AP w/out password works fine""" + 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""" @@ -443,6 +459,15 @@ def is_connected(self): self.reset() return False + @property + def ap_listening(self): + """Whether 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""" @@ -473,6 +498,25 @@ def connect_AP(self, ssid, password): # pylint: disable=invalid-name raise RuntimeError("No such ssid", ssid) raise RuntimeError("Unknown error 0x%02X" % stat) + def create_AP(self, ssid, password, channel=b'\x01'): + """Create an access point with the given name and password.""" + 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) + for _ in range(10): # retries + stat = self.status + if stat == WL_AP_LISTENING: + return stat + time.sleep(1) + 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]) @@ -619,6 +663,30 @@ def socket_close(self, socket_num): if resp[0][0] != 1: raise RuntimeError("Failed to close socket") + def start_server(self, port, socket_num, conn_mode=TCP_MODE, ip=None): + if self._debug: + print("*** starting server") + self._socknum_ll[0][0] = socket_num + port_param = struct.pack('>H', port) + if ip: # use the 4 arg version + resp = self._send_command_get_response(_START_SERVER_TCP_CMD, + (ip, + port_param, + self._socknum_ll[0], + (conn_mode,))) + else: # use the 3 arg version + resp = self._send_command_get_response(_START_SERVER_TCP_CMD, + (port_param, + self._socknum_ll[0], + (conn_mode,))) + if resp[0][0] != 1: + raise RuntimeError("Could not start server") + + def get_server_state(self, socket_num): + self._socknum_ll[0][0] = socket_num + resp = self._send_command_get_response(_GET_STATE_TCP_CMD, self._socknum_ll) + return resp[0][0] + def set_esp_debug(self, enabled): """Enable/disable debug mode on the ESP32. Debug messages will be written to the ESP32's UART.""" diff --git a/adafruit_esp32spi/adafruit_esp32spi_socket.py b/adafruit_esp32spi/adafruit_esp32spi_socket.py index ccf5b4f..9aef70b 100644 --- a/adafruit_esp32spi/adafruit_esp32spi_socket.py +++ b/adafruit_esp32spi/adafruit_esp32spi_socket.py @@ -66,6 +66,7 @@ def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None): raise RuntimeError("Only SOCK_STREAM type supported") self._buffer = b'' self._socknum = _the_interface.get_socket() + print("socknum: ", self._socknum) self.settimeout(0) def connect(self, address, conntype=None): @@ -148,6 +149,9 @@ def settimeout(self, value): """Set the read timeout for sockets, if value is 0 it will block""" self._timeout = value + def get_sock_num(self): + return self._socknum + def close(self): """Close the socket, after reading whatever remains""" _the_interface.socket_close(self._socknum) diff --git a/adafruit_esp32spi/adafruit_esp32spi_wifimanager.py b/adafruit_esp32spi/adafruit_esp32spi_wifimanager.py index f0acae2..9d7dd3f 100755 --- a/adafruit_esp32spi/adafruit_esp32spi_wifimanager.py +++ b/adafruit_esp32spi/adafruit_esp32spi_wifimanager.py @@ -31,14 +31,15 @@ # pylint: disable=no-name-in-module -from adafruit_esp32spi import adafruit_esp32spi import adafruit_esp32spi.adafruit_esp32spi_requests as requests +from adafruit_esp32spi import adafruit_esp32spi + class ESPSPI_WiFiManager: """ A class to help manage the Wifi connection """ - def __init__(self, esp, secrets, status_pixel=None, attempts=2): + def __init__(self, esp, secrets, status_pixel=None, attempts=2, debug=False): """ :param ESP_SPIcontrol esp: The ESP object we are using :param dict secrets: The WiFi and Adafruit IO secrets dict (See examples) @@ -49,9 +50,9 @@ def __init__(self, esp, secrets, status_pixel=None, attempts=2): """ # 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 requests.set_interface(self._esp) self.statuspix = status_pixel @@ -93,6 +94,32 @@ def connect(self): self.reset() continue + def create_ap(self): + """ + Attempt to initialize in Access Point (AP) mode. + 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 get(self, url, **kw): """ Pass the Get request to requests and update status LED diff --git a/examples/esp32spi_server.py b/examples/esp32spi_server.py new file mode 100644 index 0000000..bc722d3 --- /dev/null +++ b/examples/esp32spi_server.py @@ -0,0 +1,86 @@ +import board +import busio +import time +from digitalio import DigitalInOut +from secrets import secrets + +from adafruit_esp32spi import adafruit_esp32spi +import adafruit_esp32spi.adafruit_esp32spi_requests as requests +import adafruit_esp32spi.adafruit_esp32spi_wifimanager as wifimanager +import adafruit_esp32spi.adafruit_esp32spi_socket as socket + +print("ESP32 SPI web server test!!!!!!") + +esp32_cs = DigitalInOut(board.D10) +esp32_ready = DigitalInOut(board.D9) +esp32_reset = DigitalInOut(board.D7) +esp32_gpio0 = DigitalInOut(board.D12) + + +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset, gpio0_pin=esp32_gpio0, debug=False) + +## Create Access Point from SSID and optional password in secrets +wifi = wifimanager.ESPSPI_WiFiManager(esp, secrets, debug=True) +wifi.create_ap() +time.sleep(10) + +socket.set_interface(esp) +sock = socket.socket() # Request a socket for the server +curr_sock = sock +sockNum = sock.get_sock_num() +print("server status: ", esp.get_server_state(sockNum)) + +# Start the server on port 80 with the socket number we just requested for it. +esp.start_server(80, sockNum) + +print("socket num: ", sockNum) +print("server status: ", esp.get_server_state(sockNum)) +print("IP addr: ", esp.pretty_ip(esp.ip_address)) +print("info: ", esp.network_data) +print("done!") + + +status = 0 +last_sock = 255 +def server_avail(): # TODO: make a server helper class + global last_sock + sock = 255; + + if (curr_sock != 255): + # if (last_sock != 255): + # TODO: if last sock, check that last_sock is still connected and available + # sock = last_sock + if (sock == 255): + sock = esp.socket_available(sockNum) + if (sock != 255): + last_sock = sock + return sock + + return 255 + +while True: + if status != esp.status: # TODO: Move device connected check to server class ? + status = esp.status + + if status == 8: + print("Device connected! status=", status) + else: + print("Device disconnected! status=", status) + + + avail = server_avail() + if (avail != 255): + sock.set_sock_num(avail) # TODO: Server class should return a new client socket + data = sock.read() + if (len(data)): + print(data) + sock.write(b"HTTP/1.1 200 OK\r\n"); + sock.write(b"Content-type:text/html\r\n"); + sock.write(b"\r\n"); + + sock.write(b"Click here turn the LED on!!!
\r\n"); + sock.write(b"Click here turn the LED off!!!!
\r\n"); + + sock.write(b"\r\n") + sock.close()