Skip to content

Commit d21e3fb

Browse files
North101North101alistairjcbrown
authored
Refactor access-front-door (#5)
* Refactor access-front-door * Use phew as a http server. Make code async * Rewrite `schedule_lock` function to be time based rather than counter based * isconnected -> is_connected for consistency * Fix reported issues * Add docs for flashing esp32 * Cleanup * Updates from testing on device --------- Co-authored-by: North101 <[email protected]> Co-authored-by: Alistair Brown <[email protected]>
1 parent 2403cee commit d21e3fb

15 files changed

+1074
-69
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.venv/
2+
.vscode/
3+
4+
access-front-door/src/env.py

access-front-door/.python-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3

access-front-door/README.md

+93-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,95 @@
11
# Access Front Door
22

3-
This is the code required for opening the front door latch.
3+
This is the code required for opening the front door latch.
4+
5+
6+
## Flashing ESP32
7+
8+
```bash
9+
pip install esptool
10+
11+
# Find the serial port of the connected ESP32
12+
esptool.py flash_id
13+
14+
# Wipe the chip
15+
esptool.py --port [serial_port] erase_flash
16+
17+
# Download the latest firmware from https://micropython.org/download/ESP32_GENERIC/
18+
# Install the firmware
19+
esptool.py --chip esp32 --port [serial_port] --baud 460800 write_flash -z 0x1000 [path_to_downloaded_bin_file]
20+
```
21+
22+
23+
## Setting up environment variables
24+
25+
1. Clone this repository
26+
```sh
27+
git clone [email protected]:FarsetLabs/gate-network.git
28+
cd gate-network
29+
```
30+
1. Create the environment file
31+
```sh
32+
cp ./access-front-door/src/env.example.py ./access-front-door/src/env.py
33+
```
34+
1. Set `WIFI_SSID` and `WIFI_PASSWORD` to your wifi ssid and password
35+
1. Update any other variables
36+
37+
38+
## Micropython type hints in VSCode
39+
40+
### Install these extensions:
41+
* https://marketplace.visualstudio.com/items?itemName=ms-python.python
42+
* https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance
43+
44+
45+
### Setup virtualenv and install stubs
46+
```bash
47+
# setup virtualenv
48+
python3 -m venv .venv
49+
50+
# activate virtualenv
51+
source ./.venv/bin/activate
52+
53+
# install micropython stubs
54+
pip3 install -U micropython-esp32-stubs
55+
56+
# install mpremote (optional, lets you connect via command line)
57+
pip3 install -U mpremote
58+
```
59+
60+
### Configure vscode
61+
`.vscode/settings.json`
62+
```json
63+
{
64+
"python.languageServer": "Pylance",
65+
"python.analysis.typeCheckingMode": "basic",
66+
"python.analysis.diagnosticSeverityOverrides": {
67+
"reportMissingModuleSource": "none"
68+
},
69+
"python.analysis.typeshedPaths": [
70+
// Replace <python_version> with whatever the folder name is in .venv/lib/
71+
".venv/lib/<python_version>/site-packages",
72+
],
73+
"python.analysis.extraPaths": [
74+
// Allow importing from lib/
75+
"access-front-door/src/lib",
76+
],
77+
"pylint.args": [
78+
// Fixes imports
79+
"--init-hook 'import sys; sys.path.append(\".\")'",
80+
],
81+
}
82+
```
83+
84+
85+
### Copy code to device via command line (requires mpremote)
86+
```bash
87+
# make sure you are in the access-front-door directory
88+
cd access-front-door
89+
90+
# list connected devices
91+
./run.sh
92+
93+
# copy code and run main.py on device
94+
./run.sh [device_id]
95+
```

access-front-door/main.py

-68
This file was deleted.

access-front-door/run.sh

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# bin/bash
2+
if [[ -z "$1" ]]; then
3+
echo "usage: $0 [device_id] [flash]"
4+
mpremote devs
5+
exit 1;
6+
fi
7+
8+
echo "Resetting device $1 ..."
9+
mpremote connect id:$1 reset
10+
11+
echo "Copying files to device $1 ..."
12+
cd ./src
13+
mpremote connect id:$1 cp -r lib/* :
14+
mpremote connect id:$1 cp ./hub.py :
15+
mpremote connect id:$1 cp ./wifi.py :
16+
mpremote connect id:$1 cp ./env.py :
17+
18+
if [[ -z "$2" ]]; then
19+
echo "Running main.py remotely ..."
20+
mpremote connect id:$1 run ./main.py
21+
else
22+
echo "Copying main.py into place ..."
23+
mpremote connect id:$1 cp ./main.py :
24+
echo "Device has been updated"
25+
fi

access-front-door/src/env.example.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
WIFI_SSID = ""
2+
WIFI_PASSWORD = ""
3+
SHARED_PASSWORD = "access-front-door-psk"
4+
HUB_IP_ADDRESS = ""
5+
DEFAULT_UNLOCK_DURATION = 10

access-front-door/src/hub.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import urequests
2+
3+
4+
class Hub():
5+
def __init__(self, env):
6+
self.env = env
7+
8+
def register_device(self, id):
9+
url = 'http://{}/register'.format(self.env.HUB_IP_ADDRESS)
10+
data = {'id': id, 'psk': self.env.SHARED_PASSWORD}
11+
encoded_data = '&'.join(["{}={}".format(k, v) for k, v in data.items()])
12+
response = urequests.post(url, data=encoded_data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
13+
response.close()
14+
return response.status_code
15+
16+
def perform_action(self, id, affect, params):
17+
url = 'http://{}/action'.format(self.env.HUB_IP_ADDRESS)
18+
data = {'id': id, 'psk': self.env.SHARED_PASSWORD, affect: affect, params: params}
19+
encoded_data = '&'.join(["{}={}".format(k, v) for k, v in data.items()])
20+
response = urequests.post(url, data=encoded_data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
21+
response.close()
22+
return response.status_code
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
__version__ = "0.0.2"
2+
3+
# highly recommended to set a lowish garbage collection threshold
4+
# to minimise memory fragmentation as we sometimes want to
5+
# allocate relatively large blocks of ram.
6+
import gc, os, machine
7+
gc.threshold(50000)
8+
9+
# phew! the Pico (or Python) HTTP Endpoint Wrangler
10+
from . import logging
11+
12+
# determine if remotely mounted or not, changes some behaviours like
13+
# logging truncation
14+
remote_mount = False
15+
try:
16+
os.statvfs(".") # causes exception if remotely mounted (mpremote/pyboard.py)
17+
except:
18+
remote_mount = True
19+
20+
def get_ip_address():
21+
import network
22+
try:
23+
return network.WLAN(network.STA_IF).ifconfig()[0]
24+
except:
25+
return None
26+
27+
def is_connected_to_wifi():
28+
import network, time
29+
wlan = network.WLAN(network.STA_IF)
30+
return wlan.isconnected()
31+
32+
# helper method to quickly get connected to wifi
33+
def connect_to_wifi(ssid, password, timeout_seconds=30):
34+
import network, time
35+
36+
statuses = {
37+
network.STAT_IDLE: "idle",
38+
network.STAT_CONNECTING: "connecting",
39+
network.STAT_WRONG_PASSWORD: "wrong password",
40+
network.STAT_NO_AP_FOUND: "access point not found",
41+
network.STAT_CONNECT_FAIL: "connection failed",
42+
network.STAT_GOT_IP: "got ip address"
43+
}
44+
45+
wlan = network.WLAN(network.STA_IF)
46+
wlan.active(True)
47+
wlan.connect(ssid, password)
48+
start = time.ticks_ms()
49+
status = wlan.status()
50+
51+
logging.debug(f" - {statuses[status]}")
52+
while not wlan.isconnected() and (time.ticks_ms() - start) < (timeout_seconds * 1000):
53+
new_status = wlan.status()
54+
if status != new_status:
55+
logging.debug(f" - {statuses[status]}")
56+
status = new_status
57+
time.sleep(0.25)
58+
59+
if wlan.status() == network.STAT_GOT_IP:
60+
return wlan.ifconfig()[0]
61+
return None
62+
63+
64+
# helper method to put the pico into access point mode
65+
def access_point(ssid, password = None):
66+
import network
67+
68+
# start up network in access point mode
69+
wlan = network.WLAN(network.AP_IF)
70+
wlan.config(essid=ssid)
71+
if password:
72+
wlan.config(password=password)
73+
else:
74+
wlan.config(security=0) # disable password
75+
wlan.active(True)
76+
77+
return wlan

access-front-door/src/lib/phew/dns.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import uasyncio, usocket
2+
from . import logging
3+
4+
async def _handler(socket, ip_address):
5+
while True:
6+
try:
7+
yield uasyncio.core._io_queue.queue_read(socket)
8+
request, client = socket.recvfrom(256)
9+
response = request[:2] # request id
10+
response += b"\x81\x80" # response flags
11+
response += request[4:6] + request[4:6] # qd/an count
12+
response += b"\x00\x00\x00\x00" # ns/ar count
13+
response += request[12:] # origional request body
14+
response += b"\xC0\x0C" # pointer to domain name at byte 12
15+
response += b"\x00\x01\x00\x01" # type and class (A record / IN class)
16+
response += b"\x00\x00\x00\x3C" # time to live 60 seconds
17+
response += b"\x00\x04" # response length (4 bytes = 1 ipv4 address)
18+
response += bytes(map(int, ip_address.split("."))) # ip address parts
19+
socket.sendto(response, client)
20+
except Exception as e:
21+
logging.error(e)
22+
23+
def run_catchall(ip_address, port=53):
24+
logging.info("> starting catch all dns server on port {}".format(port))
25+
26+
_socket = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM)
27+
_socket.setblocking(False)
28+
_socket.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)
29+
_socket.bind(usocket.getaddrinfo(ip_address, port, 0, usocket.SOCK_DGRAM)[0][-1])
30+
31+
loop = uasyncio.get_event_loop()
32+
loop.create_task(_handler(_socket, ip_address))

0 commit comments

Comments
 (0)