Skip to content

Commit 2f447c9

Browse files
authored
Merge pull request #1 from gurgleapps/esp
Esp
2 parents ab355a7 + 32b334f commit 2f447c9

File tree

7 files changed

+988
-46
lines changed

7 files changed

+988
-46
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# Raspberry Pi Pico Web Server Control
22

3-
This repository contains code to control Raspberry Pi Pico projects using a browser-based user interface. It allows you to interact with your Pico projects remotely from any device with a web browser, including smartphones, tablets, and computers.
3+
This repository contains code to control Raspberry Pi Pico, ESP8266, ESP32 or other MicroPython projects using a browser-based user interface. It allows you to interact with your Pico projects remotely from any device with a web browser, including smartphones, tablets, and computers.
4+
5+
[![MicroPython Web Server Control](https://gurgleapps.com/assets/image-c/57/57b4760a0b877276a836a75bd107f158576c23b4.webp)](https://gurgleapps.com/learn/projects/micropython-web-server-control-raspberry-pi-pico-projects)
46

57
## Features
68

79
- Serve static web pages from your Raspberry Pi Pico
8-
- Run Python functions from a web browser
9-
- Create dynamic web pages with live data from your Pico
10+
- Run Python functions on your microcontroller device from a web browser
11+
- Create dynamic web pages with live data from your Pico or other Microcontroller
1012

1113
## Setup
1214

board.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Project Name: board.py
3+
File Name: board.py
4+
Author: GurgleApps.com
5+
Date: 2021-04-01
6+
Description: Detects the board type
7+
"""
8+
import sys
9+
import os
10+
11+
class Board:
12+
class BoardType:
13+
PICO_W = 'Raspberry Pi Pico W'
14+
PICO = 'Raspberry Pi Pico'
15+
ESP8266 = 'ESP8266'
16+
ESP32 = 'ESP32'
17+
UNKNOWN = 'Unknown'
18+
19+
def __init__(self):
20+
self.type = self.detect_board_type()
21+
22+
def detect_board_type(self):
23+
sysname = os.uname().sysname.lower()
24+
machine = os.uname().machine.lower()
25+
26+
if sysname == 'rp2' and 'pico w' in machine:
27+
return self.BoardType.PICO_W
28+
elif sysname == 'rp2' and 'pico' in machine:
29+
return self.BoardType.PICO
30+
elif sysname == 'esp8266':
31+
return self.BoardType.ESP8266
32+
# Add more conditions for other boards here
33+
else:
34+
return self.BoardType.UNKNOWN

gurgleapps_webserver.py

Lines changed: 96 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
"""
2+
Project: GurgleApps Web Server
3+
File: gurgleapps_webserver.py
4+
Author: GurgleApps.com
5+
Date: Your Date 2023-04-01
6+
Description: GurgleApps Web Server
7+
"""
18
import network
29
import re
310
import time
@@ -6,24 +13,27 @@
613
import ujson as json
714
from response import Response
815
from request import Request
16+
import gc
17+
import os
18+
919

1020
class GurgleAppsWebserver:
1121

12-
def __init__(self, wifi_ssid, wifi_password, port=80, timeout=20, doc_root="/www"):
22+
def __init__(self, wifi_ssid, wifi_password, port=80, timeout=20, doc_root="/www", log_level=0):
1323
print("GurgleApps.com Webserver")
1424
self.port = port
1525
self.timeout = timeout
1626
self.wifi_ssid = wifi_ssid
1727
self.wifi_password = wifi_password
1828
self.doc_root = doc_root
19-
self.function_routes=[]
29+
self.function_routes = []
30+
self.log_level = log_level
2031
# wifi client in station mode so we can connect to an access point
2132
self.wlan = network.WLAN(network.STA_IF)
2233
# activate the interface
2334
self.wlan.active(True)
2435
# connect to the access point with the ssid and password
2536
self.wlan.connect(self.wifi_ssid, self.wifi_password)
26-
2737
self.html = """<!DOCTYPE html>
2838
<html>
2939
<head> <title>GurgleApps.com Webserver</title> </head>
@@ -40,33 +50,43 @@ def __init__(self, wifi_ssid, wifi_password, port=80, timeout=20, doc_root="/www
4050
print('waiting for connection...')
4151
time.sleep(1)
4252

43-
if self.wlan.status() != 3:
53+
#if self.wlan.status() != 3:
54+
if self.wlan.isconnected() == False:
4455
raise RuntimeError('network connection failed')
4556
else:
4657
print('connected')
4758
status = self.wlan.ifconfig()
4859
print('ip = ' + status[0])
4960
self.serving = True
5061
print('point your browser to http://', status[0])
51-
try:
52-
pass
53-
#asyncio.run(self.start_server())
54-
except OSError as e:
55-
print(e)
56-
finally:
57-
asyncio.new_event_loop()
62+
#asyncio.new_event_loop()
5863
print("exit constructor")
5964

65+
# async def start_server(self):
66+
# print("start_server")
67+
# asyncio.create_task(asyncio.start_server(
68+
# self.serve_request, "0.0.0.0", 80))
69+
# while self.serving:
70+
# await asyncio.sleep(0.1)
71+
6072
async def start_server(self):
61-
asyncio.create_task(asyncio.start_server(self.serve_request, "0.0.0.0", 80))
62-
while self.serving:
63-
await asyncio.sleep(1)
64-
73+
print("start_server")
74+
server_task = asyncio.create_task(asyncio.start_server(
75+
self.serve_request, "0.0.0.0", 80))
76+
await server_task
77+
78+
# async def start_server(self):
79+
# print("start_server")
80+
# server = await asyncio.start_server(
81+
# self.serve_request, "0.0.0.0", 80)
82+
# async with server:
83+
# await server.serve_forever()
84+
6585
def add_function_route(self, route, function):
66-
self.function_routes.append({"route":route, "function":function})
67-
86+
self.function_routes.append({"route": route, "function": function})
6887

6988
async def serve_request(self, reader, writer):
89+
gc.collect()
7090
try:
7191
url = ""
7292
method = ""
@@ -76,18 +96,27 @@ async def serve_request(self, reader, writer):
7696
post_data = None
7797
while True:
7898
line = await reader.readline()
99+
#print("line: "+str(line))
79100
line = line.decode('utf-8').strip()
80101
if line == "":
81102
break
82103
headers.append(line)
83-
request_raw = str("\r".join(headers))
104+
request_raw = str("\r\n".join(headers))
84105
print(request_raw)
85106
request_pattern = re.compile(r"(GET|POST)\s+([^\s]+)\s+HTTP")
86107
match = request_pattern.search(request_raw)
87108
if match:
88109
method = match.group(1)
89110
url = match.group(2)
90111
print(method, url)
112+
else: # regex didn't match, try splitting the request line
113+
request_parts = request_raw.split(" ")
114+
if len(request_parts) > 1:
115+
method = request_parts[0]
116+
url = request_parts[1]
117+
print(method, url)
118+
else:
119+
print("no match")
91120
# extract content length for POST requests
92121
if method == "POST":
93122
content_length_pattern = re.compile(r"Content-Length:\s+(\d+)")
@@ -107,16 +136,20 @@ async def serve_request(self, reader, writer):
107136
print("path_components: "+str(path_components))
108137
route_function, params = self.match_route(path_components)
109138
if route_function:
110-
print("calling function: "+str(route_function)+" with params: "+str(params))
139+
print("calling function: "+str(route_function) +
140+
" with params: "+str(params))
111141
await route_function(request, response, *params)
112142
return
113143
# perhaps it is a file
114-
file = self.get_file(self.doc_root + url)
115-
print("file: "+str(file))
116-
if file:
117-
print("file found so serving it")
118-
print(file)
119-
await response.send(file)
144+
file_path = self.doc_root + url
145+
if self.log_level > 0:
146+
print("file_path: "+str(file_path))
147+
#if uos.stat(file_path)[6] > 0:
148+
if self.file_exists(file_path):
149+
content_type = self.get_content_type(url)
150+
if self.log_level > 1:
151+
print("content_type: "+str(content_type))
152+
await response.send_file(file_path, content_type=content_type)
120153
return
121154
print("file not found")
122155
await response.send(self.html % "page not found "+url, status_code=404)
@@ -125,9 +158,21 @@ async def serve_request(self, reader, writer):
125158
except OSError as e:
126159
print(e)
127160

161+
def dir_exists(self, filename):
162+
try:
163+
return (os.stat(filename)[0] & 0x4000) != 0
164+
except OSError:
165+
return False
166+
167+
def file_exists(self, filename):
168+
try:
169+
return (os.stat(filename)[0] & 0x4000) == 0
170+
except OSError:
171+
return False
172+
128173
def get_file(self, filename):
129174
print("getFile: "+filename)
130-
try :
175+
try:
131176
# Check if the file exists
132177
if uos.stat(filename)[6] > 0:
133178
# Open the file in read mode
@@ -141,20 +186,23 @@ def get_file(self, filename):
141186
# print the error
142187
print(e)
143188
return False
144-
189+
145190
def get_path_components(self, path):
191+
print("get_path_components: "+path)
146192
return tuple(filter(None, path.split('/')))
147-
193+
148194
def match_route(self, path_components):
149195
for route in self.function_routes:
150196
route_pattern = list(filter(None, route["route"].split("/")))
151-
#print("route_pattern: "+str(route_pattern))
197+
if self.log_level > 1:
198+
print("route_pattern: "+str(route_pattern))
152199
if len(route_pattern) != len(path_components):
153200
continue
154201
match = True
155202
params = []
156203
for idx, pattern_component in enumerate(route_pattern):
157-
print("pattern_component: "+pattern_component+" path_component: "+path_components[idx])
204+
if self.log_level > 2:
205+
print("pattern_component: "+str(pattern_component))
158206
if pattern_component.startswith('<') and pattern_component.endswith('>'):
159207
param_value = path_components[idx]
160208
params.append(param_value)
@@ -166,5 +214,23 @@ def match_route(self, path_components):
166214
return route["function"], params
167215
return None, []
168216

217+
def get_file_extension(self, file_path):
218+
file_parts = file_path.split('.')
219+
if len(file_parts) > 1:
220+
return file_parts[-1]
221+
return ''
169222

170223

224+
def get_content_type(self,file_path):
225+
extension = self.get_file_extension(file_path)
226+
content_type_map = {
227+
'html': 'text/html',
228+
'css': 'text/css',
229+
'js': 'application/javascript',
230+
'jpg': 'image/jpeg',
231+
'jpeg': 'image/jpeg',
232+
'png': 'image/png',
233+
'gif': 'image/gif',
234+
'ico': 'image/x-icon'
235+
}
236+
return content_type_map.get(extension, 'text/plain')

main.py

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
1+
"""
2+
Project: GurgleApps Webserver
3+
File: main.py
4+
Author: GurgleApps.com
5+
Date: 2021-04-01
6+
Description: Demonstrates how to use the GurgleApps Webserver
7+
"""
18
import config
29
from gurgleapps_webserver import GurgleAppsWebserver
310
import utime as time
411
import uasyncio as asyncio
512
from machine import Pin
613
import ujson as json
14+
from board import Board
15+
16+
BOARD_TYPE = Board().type
17+
print("Board type: " + BOARD_TYPE)
18+
19+
if BOARD_TYPE == Board.BoardType.PICO_W:
20+
led = Pin("LED", Pin.OUT)
21+
elif BOARD_TYPE == Board.BoardType.PICO:
22+
led = Pin(25, Pin.OUT)
23+
elif BOARD_TYPE == Board.BoardType.ESP8266:
24+
led = Pin(2, Pin.OUT)
25+
else:
26+
led = Pin(2, Pin.OUT)
27+
28+
29+
blink_off_time = 0.5
30+
blink_on_time = 0.5
731

8-
delay = 0.5
932
status = True
10-
led = Pin("LED", Pin.OUT)
1133

1234
async def example_func(request, response, param1, param2):
1335
print("example_func")
@@ -21,14 +43,22 @@ async def say_hello(request, response, name):
2143

2244
async def send_status(request, response):
2345
# send boolean status and number frequency
24-
response_string = json.dumps({"status": status, "delay": delay})
46+
response_string = json.dumps({"status": status, "delay": (blink_off_time + blink_on_time) *0.5, "blink_on_time": blink_on_time, "blink_off_time": blink_off_time})
2547
await response.send_json(response_string, 200)
2648

49+
async def set_blink_pattern(request, response, on, off):
50+
print("on: " + on)
51+
print("off: " + off)
52+
global blink_off_time, blink_on_time
53+
blink_off_time = float(off)
54+
blink_on_time = float(on)
55+
await send_status(request, response)
2756

2857
async def set_delay(request, response, new_delay):
2958
print("new delay: " + new_delay)
30-
global delay
31-
delay = float(new_delay)
59+
global blink_off_time, blink_on_time
60+
blink_off_time = float(new_delay)
61+
blink_on_time = float(new_delay)
3262
await send_status(request, response)
3363

3464
async def stop_flashing(request, response):
@@ -45,20 +75,23 @@ async def main():
4575
await server.start_server()
4676

4777
async def background_task():
48-
global delay, status
4978
while True:
50-
await asyncio.sleep(delay)
5179
if status:
52-
led.toggle()
80+
led.on()
81+
await asyncio.sleep(blink_on_time)
82+
led.off()
83+
await asyncio.sleep(blink_off_time)
5384
else:
5485
led.off()
86+
await asyncio.sleep(0.2)
5587

5688

5789
async def run():
5890
await asyncio.gather(main(), background_task())
5991

60-
server = GurgleAppsWebserver(config.WIFI_SSID, config.WIFI_PASSWORD)
92+
server = GurgleAppsWebserver(config.WIFI_SSID, config.WIFI_PASSWORD, port=80, timeout=20, doc_root="/www", log_level=2)
6193
server.add_function_route("/set-delay/<delay>", set_delay)
94+
server.add_function_route("/set-blink-pattern/<on_time>/<off_time>", set_blink_pattern)
6295
server.add_function_route("/stop", stop_flashing)
6396
server.add_function_route("/start", start_flashing)
6497
server.add_function_route("/status", send_status)

0 commit comments

Comments
 (0)