Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit 9569a73

Browse files
committed
Initial commit (public pybytes release 0.9.0)
1 parent 46a2425 commit 9569a73

15 files changed

+2353
-6
lines changed

esp32/frozen/_OTA.py

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import network
2+
import socket
3+
import ssl
4+
import machine
5+
import ujson
6+
import uhashlib
7+
import ubinascii
8+
import gc
9+
import pycom
10+
import os
11+
from binascii import hexlify
12+
13+
# Try to get version number
14+
# try:
15+
# from OTA_VERSION import VERSION
16+
# except ImportError:
17+
# VERSION = '1.0.0'
18+
19+
20+
class OTA():
21+
# The following two methods need to be implemented in a subclass for the
22+
# specific transport mechanism e.g. WiFi
23+
24+
def connect(self):
25+
raise NotImplementedError()
26+
27+
def get_data(self, req, dest_path=None, hash=False):
28+
raise NotImplementedError()
29+
30+
# OTA methods
31+
32+
def get_current_version(self):
33+
return os.uname().release
34+
35+
def get_update_manifest(self):
36+
current_version = self.get_current_version()
37+
sysname = os.uname().sysname
38+
wmac = hexlify(machine.unique_id()).decode('ascii')
39+
request_template = "manifest.json?current_ver={}&sysname={}&wmac={}"
40+
req = request_template.format(current_version, sysname, wmac)
41+
manifest_data = self.get_data(req).decode()
42+
manifest = ujson.loads(manifest_data)
43+
gc.collect()
44+
return manifest
45+
46+
def reboot(self):
47+
machine.reset()
48+
49+
def update(self):
50+
try:
51+
manifest = self.get_update_manifest()
52+
except Exception as e:
53+
print('Error reading the manifest, aborting: {}'.format(e))
54+
return 0
55+
56+
if manifest is None:
57+
print("Already on the latest version")
58+
return 1
59+
60+
# Download new files and verify hashes
61+
for f in manifest['new'] + manifest['update']:
62+
# Upto 5 retries
63+
for _ in range(5):
64+
try:
65+
self.get_file(f)
66+
break
67+
except Exception as e:
68+
print(e)
69+
msg = "Error downloading `{}` retrying..."
70+
print(msg.format(f['URL']))
71+
return 0
72+
else:
73+
raise Exception("Failed to download `{}`".format(f['URL']))
74+
75+
# Backup old files
76+
# only once all files have been successfully downloaded
77+
for f in manifest['update']:
78+
self.backup_file(f)
79+
80+
# Rename new files to proper name
81+
for f in manifest['new'] + manifest['update']:
82+
new_path = "{}.new".format(f['dst_path'])
83+
dest_path = "{}".format(f['dst_path'])
84+
85+
os.rename(new_path, dest_path)
86+
87+
# `Delete` files no longer required
88+
# This actually makes a backup of the files incase we need to roll back
89+
for f in manifest['delete']:
90+
self.delete_file(f)
91+
92+
# Flash firmware
93+
if "firmware" in manifest:
94+
self.write_firmware(manifest['firmware'])
95+
96+
# Save version number
97+
# try:
98+
# self.backup_file({"dst_path": "/flash/OTA_VERSION.py"})
99+
# except OSError:
100+
# pass # There isnt a previous file to backup
101+
# with open("/flash/OTA_VERSION.py", 'w') as fp:
102+
# fp.write("VERSION = '{}'".format(manifest['version']))
103+
# from OTA_VERSION import VERSION
104+
105+
return 2
106+
107+
def get_file(self, f):
108+
new_path = "{}.new".format(f['dst_path'])
109+
110+
# If a .new file exists from a previously failed update delete it
111+
try:
112+
os.remove(new_path)
113+
except OSError:
114+
pass # The file didnt exist
115+
116+
# Download new file with a .new extension to not overwrite the existing
117+
# file until the hash is verified.
118+
hash = self.get_data(f['URL'].split("/", 3)[-1],
119+
dest_path=new_path,
120+
hash=True)
121+
122+
# Hash mismatch
123+
if hash != f['hash']:
124+
print(hash, f['hash'])
125+
msg = "Downloaded file's hash does not match expected hash"
126+
raise Exception(msg)
127+
128+
def backup_file(self, f):
129+
bak_path = "{}.bak".format(f['dst_path'])
130+
dest_path = "{}".format(f['dst_path'])
131+
132+
# Delete previous backup if it exists
133+
try:
134+
os.remove(bak_path)
135+
except OSError:
136+
pass # There isnt a previous backup
137+
138+
# Backup current file
139+
os.rename(dest_path, bak_path)
140+
141+
def delete_file(self, f):
142+
bak_path = "/{}.bak_del".format(f)
143+
dest_path = "/{}".format(f)
144+
145+
# Delete previous delete backup if it exists
146+
try:
147+
os.remove(bak_path)
148+
except OSError:
149+
pass # There isnt a previous delete backup
150+
151+
# Backup current file
152+
os.rename(dest_path, bak_path)
153+
154+
def write_firmware(self, f):
155+
hash = self.get_data(f['URL'].split("/", 3)[-1],
156+
hash=True,
157+
firmware=True)
158+
# TODO: Add verification when released in future firmware
159+
160+
161+
class WiFiOTA(OTA):
162+
def __init__(self, ssid, password, ip, port):
163+
self.SSID = ssid
164+
self.password = password
165+
self.ip = ip
166+
self.port = port
167+
168+
def connect(self):
169+
self.wlan = network.WLAN(mode=network.WLAN.STA)
170+
if not self.wlan.isconnected() or self.wlan.ssid() != self.SSID:
171+
for net in self.wlan.scan():
172+
if net.ssid == self.SSID:
173+
self.wlan.connect(self.SSID, auth=(network.WLAN.WPA2,
174+
self.password))
175+
while not self.wlan.isconnected():
176+
machine.idle() # save power while waiting
177+
break
178+
else:
179+
raise Exception("Cannot find network '{}'".format(self.SSID))
180+
else:
181+
# Already connected to the correct WiFi
182+
pass
183+
184+
def _http_get(self, path, host):
185+
req_fmt = 'GET /{} HTTP/1.0\r\nHost: {}\r\n\r\n'
186+
req = bytes(req_fmt.format(path, host), 'utf8')
187+
return req
188+
189+
def get_data(self, req, dest_path=None, hash=False, firmware=False):
190+
h = None
191+
192+
useSSL = int(self.port) == 443
193+
194+
# Connect to server
195+
print("Requesting: {} to {}:{} with SSL? {}".format(req, self.ip, self.port, useSSL))
196+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
197+
s.connect(socket.getaddrinfo(self.ip, self.port)[0][-1])
198+
if (int(self.port) == 443):
199+
print("Wrapping socket")
200+
s = ssl.wrap_socket(s)
201+
202+
print("Sending request")
203+
# Request File
204+
s.sendall(self._http_get(req, "{}:{}".format(self.ip, self.port)))
205+
206+
try:
207+
content = bytearray()
208+
fp = None
209+
if dest_path is not None:
210+
print('dest_path {}'.format(dest_path))
211+
if firmware:
212+
raise Exception("Cannot write firmware to a file")
213+
fp = open(dest_path, 'wb')
214+
215+
if firmware:
216+
print('start')
217+
pycom.ota_start()
218+
219+
h = uhashlib.sha1()
220+
221+
# Get data from server
222+
result = s.recv(50)
223+
224+
start_writing = False
225+
while (len(result) > 0):
226+
# Ignore the HTTP headers
227+
if not start_writing:
228+
if "\r\n\r\n" in result:
229+
start_writing = True
230+
result = result.decode().split("\r\n\r\n")[1].encode()
231+
232+
if start_writing:
233+
if firmware:
234+
pycom.ota_write(result)
235+
elif fp is None:
236+
content.extend(result)
237+
else:
238+
fp.write(result)
239+
240+
if hash:
241+
h.update(result)
242+
243+
result = s.recv(50)
244+
245+
s.close()
246+
247+
if fp is not None:
248+
fp.close()
249+
if firmware:
250+
pycom.ota_finish()
251+
252+
except Exception as e:
253+
gc.mem_free()
254+
# Since only one hash operation is allowed at Once
255+
# ensure we close it if there is an error
256+
if h is not None:
257+
h.digest()
258+
raise e
259+
260+
hash_val = ubinascii.hexlify(h.digest()).decode()
261+
262+
if dest_path is None:
263+
if hash:
264+
return (bytes(content), hash_val)
265+
else:
266+
return bytes(content)
267+
elif hash:
268+
return hash_val

esp32/frozen/_flash_control_OTA.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import os
2+
3+
class FCOTA:
4+
def __init__(self):
5+
pass
6+
7+
def update_file_content(self, path, newContent):
8+
print('Updating file [{}]'.format(path))
9+
10+
if '.' in path:
11+
f = open(path, 'w')
12+
f.write(newContent)
13+
f.close()
14+
print('File updated')
15+
return True
16+
else:
17+
print('Cannot write into a folder')
18+
19+
return False
20+
21+
def delete_file(self, path):
22+
print('FCOTA deleting file [{}]'.format(path))
23+
try:
24+
if ('.' in path):
25+
os.remove(path)
26+
else:
27+
targetedFiles = []
28+
maxDepth = 0
29+
currentHierarchy = self.get_flash_hierarchy()
30+
for elem in currentHierarchy:
31+
if path in elem:
32+
targetedFiles.append(elem)
33+
if elem.count('/') > maxDepth:
34+
maxDepth = elem.count('/')
35+
if len(targetedFiles) > 0:
36+
while maxDepth >= 0:
37+
for elem in targetedFiles:
38+
if elem.count('/') == maxDepth:
39+
if '.' in elem:
40+
os.remove(elem)
41+
else:
42+
os.rmdir(elem)
43+
maxDepth -= 1
44+
else:
45+
print('targetedFiles empty, no file to delete')
46+
return True
47+
except Exception as ex:
48+
print('FCOTA file deletion failed: {}'.format(ex))
49+
return False
50+
51+
def convert_bytes(self, num):
52+
for x in ['bytes', 'KB', 'MB', 'GB', 'TB']:
53+
if num < 1024.0:
54+
return "%3.3g %s" % (num, x)
55+
num /= 1024.0
56+
57+
def get_file_size(self, path):
58+
print('FCOTA getting file infos [{}]'.format(path))
59+
if '.' in path:
60+
fileInfo = os.stat(path)
61+
print (fileInfo)
62+
return self.convert_bytes(fileInfo[6])
63+
return 'Unknown'
64+
65+
def get_file_content(self, path):
66+
print('FCOTA reading file [{}]'.format(path))
67+
68+
if '.' in path:
69+
f = open(path, 'r')
70+
content = f.read()
71+
f.close()
72+
else:
73+
content = 'folder: {}'.format(path)
74+
75+
# print('encoding content')
76+
# print(hexlify(content))
77+
# content = hexlify(content)
78+
79+
return content
80+
81+
def get_flash_hierarchy(self):
82+
hierarchy = os.listdir()
83+
folders = []
84+
for elem in hierarchy:
85+
if '.' not in elem:
86+
folders.append(elem)
87+
88+
while len(folders) > 0:
89+
i = 0
90+
checkedFolders = []
91+
foldersToCheck = []
92+
93+
while i < len(folders):
94+
subFolders = os.listdir(folders[i])
95+
96+
if len(subFolders) > 0:
97+
j = 0
98+
while j < len(subFolders):
99+
path = folders[i] + '/' + subFolders[j]
100+
hierarchy.append(path)
101+
102+
if '.' not in path:
103+
foldersToCheck.append(path)
104+
105+
j += 1
106+
107+
checkedFolders.append(folders[i])
108+
i += 1
109+
110+
i = 0
111+
while i < len(checkedFolders):
112+
folders.remove(checkedFolders[i])
113+
i += 1
114+
115+
i = 0
116+
while i < len(foldersToCheck):
117+
folders.append(foldersToCheck[i])
118+
i += 1
119+
120+
return hierarchy

0 commit comments

Comments
 (0)