Skip to content

Commit 00a9e82

Browse files
committed
misc: migrate to public repository
1 parent 1e37b1e commit 00a9e82

31 files changed

+8307
-0
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,10 @@ share/python-wheels/
4343

4444
#envrc
4545
.envrc
46+
47+
# mac stuff
48+
.DS_Store
49+
50+
# project specific
51+
logs
52+
.sequence

espota.py

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
###!/usr/bin/env python
2+
#
3+
# Original espota.py by Ivan Grokhotkov:
4+
# https://gist.github.com/igrr/d35ab8446922179dc58c
5+
#
6+
# Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor)
7+
# Modified since 2015-11-09 from Hristo Gochkov (https://github.com/me-no-dev)
8+
# Modified since 2016-01-03 from Matthew O'Gorman (https://githumb.com/mogorman)
9+
#
10+
# This script will push an OTA update to the ESP
11+
# use it like:
12+
# python espota.py -i <ESP_IP_addr> -I <Host_IP_addr> -p <ESP_port> -P <Host_port> [-a password] -f <sketch.bin>
13+
# Or to upload SPIFFS image:
14+
# python espota.py -i <ESP_IP_addr> -I <Host_IP_addr> -p <ESP_port> -P <HOST_port> [-a password] -s -f <spiffs.bin>
15+
#
16+
# Changes
17+
# 2015-09-18:
18+
# - Add option parser.
19+
# - Add logging.
20+
# - Send command to controller to differ between flashing and transmitting SPIFFS image.
21+
#
22+
# Changes
23+
# 2015-11-09:
24+
# - Added digest authentication
25+
# - Enhanced error tracking and reporting
26+
#
27+
# Changes
28+
# 2016-01-03:
29+
# - Added more options to parser.
30+
#
31+
# Changes
32+
# 2023-05-22:
33+
# - Replaced the deprecated optparse module with argparse.
34+
# - Adjusted the code style to conform to PEP 8 guidelines.
35+
# - Used with statement for file handling to ensure proper resource cleanup.
36+
# - Incorporated exception handling to catch and handle potential errors.
37+
# - Made variable names more descriptive for better readability.
38+
# - Introduced constants for better code maintainability.
39+
40+
from __future__ import print_function
41+
42+
import argparse
43+
import hashlib
44+
import logging
45+
import os
46+
import random
47+
import socket
48+
import sys
49+
50+
# Commands
51+
FLASH = 0
52+
SPIFFS = 100
53+
AUTH = 200
54+
55+
# Constants
56+
PROGRESS_BAR_LENGTH = 60
57+
58+
# custom globals
59+
PROGRESS = False
60+
TIMEOUT = 10.0
61+
62+
# update_progress(): Displays or updates a console progress bar
63+
64+
65+
def update_progress(progress): # noqa: D103
66+
if PROGRESS:
67+
status = ""
68+
if isinstance(progress, int):
69+
progress = float(progress)
70+
if not isinstance(progress, float):
71+
progress = 0
72+
status = "Error: progress var must be float\r\n"
73+
if progress < 0:
74+
progress = 0
75+
status = "Halt...\r\n"
76+
if progress >= 1:
77+
progress = 1
78+
status = "Done...\r\n"
79+
block = int(round(PROGRESS_BAR_LENGTH * progress))
80+
text = "\rUploading: [{0}] {1}% {2}".format(
81+
"=" * block + " " * (PROGRESS_BAR_LENGTH - block),
82+
int(progress * 100),
83+
status,
84+
)
85+
sys.stderr.write(text)
86+
sys.stderr.flush()
87+
else:
88+
sys.stderr.write(".")
89+
sys.stderr.flush()
90+
91+
92+
def serve( # noqa: D103
93+
remote_addr, local_addr, remote_port, local_port, password, filename, command=FLASH
94+
): # noqa: C901
95+
# Create a TCP/IP socket
96+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
97+
server_address = (local_addr, local_port)
98+
logging.info("Starting on %s:%s", str(server_address[0]), str(server_address[1]))
99+
try:
100+
sock.bind(server_address)
101+
sock.listen(1)
102+
except Exception as e:
103+
logging.error("Listen Failed: %s", str(e))
104+
return 1
105+
106+
content_size = os.path.getsize(filename)
107+
file_md5 = hashlib.md5(open(filename, "rb").read()).hexdigest()
108+
logging.info("Upload size: %d", content_size)
109+
message = "%d %d %d %s\n" % (command, local_port, content_size, file_md5)
110+
111+
# Wait for a connection
112+
inv_tries = 0
113+
data = ""
114+
msg = "Sending invitation to %s \n" % remote_addr
115+
sys.stderr.write(msg)
116+
sys.stderr.flush()
117+
while inv_tries < 10:
118+
inv_tries += 1
119+
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
120+
remote_address = (remote_addr, int(remote_port))
121+
try:
122+
sent = sock2.sendto(message.encode(), remote_address) # noqa: F841
123+
except: # noqa: E722
124+
sys.stderr.write("failed\n")
125+
sys.stderr.flush()
126+
sock2.close()
127+
logging.error("Host %s Not Found", remote_addr)
128+
return 1
129+
sock2.settimeout(TIMEOUT)
130+
try:
131+
data = sock2.recv(37).decode()
132+
break
133+
except: # noqa: E722
134+
sys.stderr.write(".")
135+
sys.stderr.flush()
136+
sock2.close()
137+
sys.stderr.write("\n")
138+
sys.stderr.flush()
139+
if inv_tries == 10:
140+
logging.error("No response from the ESP")
141+
return 1
142+
if data != "OK":
143+
if data.startswith("AUTH"):
144+
nonce = data.split()[1]
145+
cnonce_text = "%s%u%s%s" % (filename, content_size, file_md5, remote_addr)
146+
cnonce = hashlib.md5(cnonce_text.encode()).hexdigest()
147+
passmd5 = hashlib.md5(password.encode()).hexdigest()
148+
result_text = "%s:%s:%s" % (passmd5, nonce, cnonce)
149+
result = hashlib.md5(result_text.encode()).hexdigest()
150+
sys.stderr.write("Authenticating...")
151+
sys.stderr.flush()
152+
message = "%d %s %s\n" % (AUTH, cnonce, result)
153+
sock2.sendto(message.encode(), remote_address)
154+
sock2.settimeout(10)
155+
try:
156+
data = sock2.recv(32).decode()
157+
except: # noqa: E722
158+
sys.stderr.write("FAIL\n")
159+
logging.error("No Answer to our Authentication")
160+
sock2.close()
161+
return 1
162+
if data != "OK":
163+
sys.stderr.write("FAIL\n")
164+
logging.error("%s", data)
165+
sock2.close()
166+
sys.exit(1)
167+
return 1
168+
sys.stderr.write("OK\n")
169+
else:
170+
logging.error("Bad Answer: %s", data)
171+
sock2.close()
172+
return 1
173+
sock2.close()
174+
175+
logging.info("Waiting for device...")
176+
try:
177+
sock.settimeout(10)
178+
connection, client_address = sock.accept()
179+
sock.settimeout(None)
180+
connection.settimeout(None)
181+
except: # noqa: E722
182+
logging.error("No response from device")
183+
sock.close()
184+
return 1
185+
try:
186+
with open(filename, "rb") as f:
187+
if PROGRESS:
188+
update_progress(0)
189+
else:
190+
sys.stderr.write("Uploading")
191+
sys.stderr.flush()
192+
offset = 0
193+
while True:
194+
chunk = f.read(1024)
195+
if not chunk:
196+
break
197+
offset += len(chunk)
198+
update_progress(offset / float(content_size))
199+
connection.settimeout(10)
200+
try:
201+
connection.sendall(chunk)
202+
res = connection.recv(10)
203+
last_response_contained_ok = "OK" in res.decode()
204+
except Exception as e:
205+
sys.stderr.write("\n")
206+
logging.error("Error Uploading: %s", str(e))
207+
connection.close()
208+
return 1
209+
210+
if last_response_contained_ok:
211+
logging.info("Success")
212+
connection.close()
213+
return 0
214+
215+
sys.stderr.write("\n")
216+
logging.info("Waiting for result...")
217+
count = 0
218+
while count < 5:
219+
count += 1
220+
connection.settimeout(60)
221+
try:
222+
data = connection.recv(32).decode()
223+
logging.info("Result: %s", data)
224+
225+
if "OK" in data:
226+
logging.info("Success")
227+
connection.close()
228+
return 0
229+
230+
except Exception as e:
231+
logging.error("Error receiving result: %s", str(e))
232+
connection.close()
233+
return 1
234+
235+
logging.error("Error response from device")
236+
connection.close()
237+
return 1
238+
239+
finally:
240+
connection.close()
241+
242+
sock.close()
243+
return 1
244+
245+
246+
def parse_args(unparsed_args): # noqa: D103
247+
parser = argparse.ArgumentParser(
248+
description="Transmit image over the air to the ESP32 module with OTA support."
249+
)
250+
251+
# destination ip and port
252+
parser.add_argument(
253+
"-i",
254+
"--ip",
255+
dest="esp_ip",
256+
action="store",
257+
help="ESP32 IP Address.",
258+
default=False,
259+
)
260+
parser.add_argument(
261+
"-I",
262+
"--host_ip",
263+
dest="host_ip",
264+
action="store",
265+
help="Host IP Address.",
266+
default="0.0.0.0",
267+
)
268+
parser.add_argument(
269+
"-p",
270+
"--port",
271+
dest="esp_port",
272+
type=int,
273+
help="ESP32 OTA Port. Default: 3232",
274+
default=3232,
275+
)
276+
parser.add_argument(
277+
"-P",
278+
"--host_port",
279+
dest="host_port",
280+
type=int,
281+
help="Host server OTA Port. Default: random 10000-60000",
282+
default=random.randint(10000, 60000),
283+
)
284+
285+
# authentication
286+
parser.add_argument(
287+
"-a",
288+
"--auth",
289+
dest="auth",
290+
help="Set authentication password.",
291+
action="store",
292+
default="",
293+
)
294+
295+
# image
296+
parser.add_argument(
297+
"-f", "--file", dest="image", help="Image file.", metavar="FILE", default=None
298+
)
299+
parser.add_argument(
300+
"-s",
301+
"--spiffs",
302+
dest="spiffs",
303+
action="store_true",
304+
help="Transmit a SPIFFS image and do not flash the module.",
305+
default=False,
306+
)
307+
308+
# output
309+
parser.add_argument(
310+
"-d",
311+
"--debug",
312+
dest="debug",
313+
action="store_true",
314+
help="Show debug output. Overrides loglevel with debug.",
315+
default=False,
316+
)
317+
parser.add_argument(
318+
"-r",
319+
"--progress",
320+
dest="progress",
321+
action="store_true",
322+
help="Show progress output. Does not work for Arduino IDE.",
323+
default=False,
324+
)
325+
parser.add_argument(
326+
"-t",
327+
"--timeout",
328+
dest="timeout",
329+
type=int,
330+
help="Timeout to wait for the ESP32 to accept invitation.",
331+
default=10,
332+
)
333+
334+
return parser.parse_args(unparsed_args)
335+
336+
337+
def main(args): # noqa: D103
338+
options = parse_args(args)
339+
log_level = logging.WARNING
340+
if options.debug:
341+
log_level = logging.DEBUG
342+
343+
logging.basicConfig(
344+
level=log_level,
345+
format="%(asctime)-8s [%(levelname)s]: %(message)s",
346+
datefmt="%H:%M:%S",
347+
)
348+
logging.debug("Options: %s", str(options))
349+
350+
# check options
351+
global PROGRESS
352+
PROGRESS = options.progress
353+
354+
global TIMEOUT
355+
TIMEOUT = options.timeout
356+
357+
if not options.esp_ip or not options.image:
358+
logging.critical("Not enough arguments.")
359+
return 1
360+
361+
command = FLASH
362+
if options.spiffs:
363+
command = SPIFFS
364+
365+
return serve(
366+
options.esp_ip,
367+
options.host_ip,
368+
options.esp_port,
369+
options.host_port,
370+
options.auth,
371+
options.image,
372+
command,
373+
)
374+
375+
376+
if __name__ == "__main__":
377+
sys.exit(main(sys.argv[1:]))

flash-ui/.eslintrc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": [
3+
"next/core-web-vitals",
4+
"next/typescript"
5+
]
6+
}

0 commit comments

Comments
 (0)