Skip to content

Add Server functionality #58

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
68 changes: 68 additions & 0 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 All @@ -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)
Expand Down Expand Up @@ -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"""
Expand Down Expand Up @@ -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"""
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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."""
Expand Down
4 changes: 4 additions & 0 deletions adafruit_esp32spi/adafruit_esp32spi_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
35 changes: 31 additions & 4 deletions adafruit_esp32spi/adafruit_esp32spi_wifimanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions examples/esp32spi_server.py
Original file line number Diff line number Diff line change
@@ -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 <a href=\"/H\">here</a> turn the LED on!!!<br>\r\n");
sock.write(b"Click <a href=\"/L\">here</a> turn the LED off!!!!<br>\r\n");

sock.write(b"\r\n")
sock.close()