I am building a CircuitPython weather station based on Adafruit M4 board stacked with Adafruit ESP32 AirLift module.
Both work fine together when it comes to making http calls (via the Adafruit_requests library). However I would like to build am HTTP server serving weather measures instead.
And here is a problem: all Adafuit examples are based on ESP32 or Raspberry Pico boards natively fitted with Wifi. The Adafuit M4 board doesn't have the same core Python packages since it doesn't support Wifi.
So an example written for Pico doesn't work on the M4 with ESP AirLift, as there are a number of packages missing to do so (like wifi and socket), adafuit_esp package has to be used instead.
This package however seems to be incompatible with Adafuit HTTPServer: https://docs.circuitpython.org/projects/httpserver/en/stable/index.html HttpServer requires a socket object as a parameter to be initialized:
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
socket.set_interface(esp)
server = HTTPServer(socket)
This code fails when I'm trying to start the server:
try:
server.start(esp.pretty_ip(esp.ip_address))
print("Listening on http://%s:80" % esp.pretty_ip(esp.ip_address))
# if the server fails to begin, restart the pico w
except OSError:
time.sleep(5)
print("restarting..")
microcontroller.reset()
And the error is:
>>> %Run -c $EDITOR_CONTENT
Creating display
Creating server socket
ESP32 found and in idle mode
Firmware vers. bytearray(b'1.2.2\x00')
MAC addr: ...
...
Connecting to AP...
Connected to jama RSSI: -27
My IP address is 192.168.0.24
Creating HTTP Server...
starting server..
Traceback (most recent call last):
File "<stdin>", line 175, in <module>
File "adafruit_httpserver/server.py", line 91, in start
AttributeError: 'socket' object has no attribute 'bind'
>>>
So it looks like that's not the right socket. It works for ESP and Requests libs but not for HTTPServer.
Here are imports:
from adafruit_esp32spi import adafruit_esp32spi
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
My question then (or two) is does anyone know a workaround for this issue and/or a different socket or server packages I can use instead?
Here is the full code for the reference, it is not finished and contains more stuff than described here but unrelated to the issue:
import os
import time
import busio
import board
import microcontroller
from digitalio import DigitalInOut, Direction
import adafruit_bh1750
# import adafruit_requests as requests
from adafruit_bme280 import basic as adafruit_bme280
from adafruit_httpserver.server import HTTPServer
from adafruit_httpserver.request import HTTPRequest
from adafruit_httpserver.response import HTTPResponse
from adafruit_httpserver.methods import HTTPMethod
from adafruit_httpserver.mime_type import MIMEType
from adafruit_epd.epd import Adafruit_EPD
from adafruit_epd.il0373 import Adafruit_IL0373
from adafruit_esp32spi import adafruit_esp32spi
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
try:
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in secrets.py, please add them there!")
raise
# onboard LED setup
# led = DigitalInOut(board.LED)
# led.direction = Direction.OUTPUT
# led.value = False
i2c = board.I2C() # uses board.SCL and board.SDA
bh1750 = adafruit_bh1750.BH1750(i2c)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, 0x76)
bme280.sea_level_pressure = 1012.50
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
ecs = DigitalInOut(board.D9)
dc = DigitalInOut(board.D10)
srcs = DigitalInOut(board.D6) # can be None to use internal memory
rst = None #DigitalInOut(board.D1) # can be None to not use this pin
busy = None #DigitalInOut(board.D5) # can be None to not use this pin
esp32_cs = DigitalInOut(board.D13)
esp32_ready = DigitalInOut(board.D11)
esp32_reset = DigitalInOut(board.D12)
print("Creating display")
display = Adafruit_IL0373(
128,
296, # 2.9" Tri-color display
spi,
cs_pin=ecs,
dc_pin=dc,
sramcs_pin=srcs,
rst_pin=rst,
busy_pin=busy,
)
display.set_black_buffer(1, True)
display.set_color_buffer(1, True)
display.rotation = 1
display.fill(Adafruit_EPD.WHITE)
# display.display()
# display.text(splash, 12, 12, Adafruit_EPD.BLACK, size=1)
# connect to network
print("Creating server socket")
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
socket.set_interface(esp)
if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", [hex(i) for i in esp.MAC_address])
for ap in esp.scan_networks():
print("\t%s\t\tRSSI: %d" % (str(ap["ssid"], "utf-8"), ap["rssi"]))
print("Connecting to AP...")
while not esp.is_connected:
try:
esp.connect_AP(secrets["ssid"], secrets["password"])
except OSError as e:
print("could not connect to AP, retrying: ", e)
continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("My IP address is", esp.pretty_ip(esp.ip_address))
print("Creating HTTP Server...")
server = HTTPServer(socket)
# font for HTML
font_family = "monospace"
# the HTML script
# setup as an f string
# this way, can insert string variables from code.py directly
# of note, use {{ and }} if something from html *actually* needs to be in brackets
# i.e. CSS style formatting
def webpage():
html = f"""
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html{{font-family: {font_family}; background-color: lightgrey;
display:inline-block; margin: 0px auto; text-align: center;}}
h1{{color: deeppink; width: 200; word-wrap: break-word; padding: 2vh; font-size: 35px;}}
p{{font-size: 1.5rem; width: 200; word-wrap: break-word;}}
.button{{font-family: {font_family};display: inline-block;
background-color: black; border: none;
border-radius: 4px; color: white; padding: 16px 40px;
text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}}
p.dotted {{margin: auto;
width: 75%; font-size: 25px; text-align: center;}}
</style>
</head>
<body>
<title>Pico W HTTP Server</title>
<h1>Pico W HTTP Server</h1>
<br>
<p class="dotted">This is a Pico W running an HTTP server with CircuitPython.</p>
<br>
<p class="dotted">The current ambient temperature near the Pico W is
<span style="color: deeppink;">{temp_test}°{unit}</span></p><br>
<h1>Control the LED on the Pico W with these buttons:</h1><br>
<form accept-charset="utf-8" method="POST">
<button class="button" name="LED ON" value="ON" type="submit">LED ON</button></a></p></form>
<p><form accept-charset="utf-8" method="POST">
<button class="button" name="LED OFF" value="OFF" type="submit">LED OFF</button></a></p></form>
<h1>Party?</h>
<p><form accept-charset="utf-8" method="POST">
<button class="button" name="party" value="party" type="submit">PARTY!</button></a></p></form>
</body></html>
"""
return html
# route default static IP
@server.route("/")
def base(request: HTTPRequest): # pylint: disable=unused-argument
# serve the HTML f string
# with content type text/html
with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response:
response.send(f"{webpage()}")
# if a button is pressed on the site
@server.route("/", method=HTTPMethod.POST)
def buttonpress(request: HTTPRequest):
# get the raw text
raw_text = request.raw_request.decode("utf8")
print(raw_text)
# if the led on button was pressed
if "ON" in raw_text:
# turn on the onboard LED
led.value = True
# if the led off button was pressed
if "OFF" in raw_text:
# turn the onboard LED off
led.value = False
# reload site
with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response:
response.send(f"{webpage()}")
print("starting server..")
# startup the server
try:
server.start(esp.pretty_ip(esp.ip_address))
print("Listening on http://%s:80" % esp.pretty_ip(esp.ip_address))
# if the server fails to begin, restart the pico w
except OSError:
time.sleep(5)
print("restarting..")
microcontroller.reset()
# text objects for screen
# connected to SSID text
connect_text_area.text = "Connected to:"
ssid_text = "%s" % secrets('ssid')
# ssid_text_area = label.Label(terminalio.FONT, text=ssid_text, color=0xFFFFFF, x=0, y=offset_y+15)
#splash.append(ssid_text_area)
# display ip address
ip_text = "IP: %s" % wifi.radio.ipv4_address
# ip_text_area = label.Label(terminalio.FONT, text=ip_text, color=0xFFFFFF, x=0, y=offset_y+30)
#splash.append(ip_text_area)
# display temp reading
temp_text = "Temperature: %.02f F" % float(temp_test)
# temp_text_area = label.Label(terminalio.FONT, text=temp_text, color=0xFFFFFF, x=0, y=offset_y+45)
#splash.append(temp_text_area)
def get_weather_conditions():
temp = bme280.temperature
print("\nTemperature: %0.1f C" % temp)
hum = bme280.relative_humidity
print("Humidity: %0.1f %%" % hum)
pres = bme280.pressure
print("Pressure: %0.1f hPa" % pres)
alt = bme280.altitude
print("Altitude: %0.2f meters" % alt)
light = bh1750.lux
print("Light: %.2f Lux" % light)
return [temp, hum, pres, alt, light]
clock = time.monotonic() # time.monotonic() holder for server ping
while True:
try:
# every 30 seconds, ping server & update temp reading
if (clock + 30) < time.monotonic():
if wifi.radio.ping(ping_address) is None:
connect_text_area.text = "Disconnected!"
ssid_text_area.text = None
print("lost connection")
else:
connect_text_area.text = "Connected to:"
ssid_text_area.text = "%s" % secrets('ssid')
print("connected")
clock = time.monotonic()
# comment/uncomment for desired units
# temp_test = str(ds18.temperature)
temp_test = str(c_to_f(19.45))
temp_text_area.text = "Temperature: %s F" % temp_test
# display.show(splash)
# poll the server for incoming/outgoing requests
server.poll()
# pylint: disable=broad-except
except Exception as e:
print(e)
continue