Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,34 @@ hard-reset is performed (by calling `machine.reset()`). By setting the flag `sof
soft-reset (by calling `machine.soft_reset()`). After the reset the `ota_update`-method called in the boot.py performs the actual update. This method accepts
the timeout setting, too, by default it is set to 5 seconds.

## File manifest

If you do not wish to hard code the firmware file names in your code, you may use a manifest file. Include a file named `manifest` on your server alongside your other firmware files.

```
server-root/
|- <project_name>/
| |- version
| |- <version_subdir>
| |- manifest
| |- <filename1>
| |- <filename2>
| |- ...
```

The contents of the manifest file are identical to the file names you would supply the call to `ota_update()` with one file listed per line.

```
file1.py
file2.py
dir1/
dir1/file3.py
...
```

Note that using a manifest supports adding subdirectories. In order to use/create a subdirectory, the directory name MUST end with a forward-slash and be listed above any files that are to reside within that directory.


## HTTP(S) Basic Authentication

`ota_update()` and `check_for_ota_update()` methods allow optional `user` and `passwd` parameters. When specified the library performs a basic authentication
Expand Down
59 changes: 51 additions & 8 deletions micropython_ota.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,19 @@ def check_version(host, project, auth=None, timeout=5) -> (bool, str):
print(f'Something went wrong: {ex}')
return False, current_version


def fetch_manifest(host, project, remote_version, prefix_or_path_separator, auth=None, timeout=5):
if auth:
response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}manifest', headers={'Authorization': f'Basic {auth}'}, timeout=timeout)
else:
response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}manifest', timeout=timeout)
response_status_code = response.status_code
response_text = response.text
response.close()
if response_status_code != 200:
print(f'Remote manifest file {host}/{project}/{remote_version}{prefix_or_path_separator}manifest not found')
raise Exception(f"Missing manifest for {remote_version}")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise Exception(f"Missing manifest for {remote_version}")
raise Exception(f'Missing manifest for {remote_version}')

return response_text.split()

def generate_auth(user=None, passwd=None) -> str | None:
if not user and not passwd:
return None
Expand All @@ -37,7 +49,7 @@ def generate_auth(user=None, passwd=None) -> str | None:
return auth_bytes.decode().strip()


def ota_update(host, project, filenames, use_version_prefix=True, user=None, passwd=None, hard_reset_device=True, soft_reset_device=False, timeout=5) -> None:
def ota_update(host, project, filenames=None, use_version_prefix=True, user=None, passwd=None, hard_reset_device=True, soft_reset_device=False, timeout=5) -> None:
all_files_found = True
auth = generate_auth(user, passwd)
prefix_or_path_separator = '_' if use_version_prefix else '/'
Expand All @@ -46,28 +58,58 @@ def ota_update(host, project, filenames, use_version_prefix=True, user=None, pas
if version_changed:
try:
uos.mkdir('tmp')
except:
pass
except OSError as e:
if e.errno != 17:
raise
if filenames is None:
filenames = fetch_manifest(host, project, remote_version, prefix_or_path_separator, auth=auth, timeout=timeout)
for filename in filenames:
if filename.endswith('/'):
dir_path="tmp"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dir_path="tmp"
dir_path='tmp'

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could define dir_path at the beginning of the method and reference this var in the uos.mkdir call.

for dir in filename.split('/'):
if len(dir) > 0:
built_path=f"{dir_path}/{dir}"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
built_path=f"{dir_path}/{dir}"
built_path=f'{dir_path}/{dir}'

try:
uos.mkdir(built_path)
except OSError as e:
if e.errno != 17:
raise
continue
if auth:
response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}{filename}', headers={'Authorization': f'Basic {auth}'}, timeout=timeout)
else:
response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}{filename}', timeout=timeout)
response_status_code = response.status_code
response_text = response.text
response_content = response.content
response.close()
if response_status_code != 200:
print(f'Remote source file {host}/{project}/{remote_version}{prefix_or_path_separator}{filename} not found')
all_files_found = False
continue
with open(f'tmp/{filename}', 'w') as source_file:
source_file.write(response_text)
with open(f'tmp/{filename}', 'wb') as source_file:
source_file.write(response_content)
if all_files_found:
dirs=[]
for filename in filenames:
with open(f'tmp/{filename}', 'r') as source_file, open(filename, 'w') as target_file:
if filename.endswith('/'):
dir_path=""
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dir_path=""
dir_path=''

for dir in filename.split('/'):
if len(dir) > 0:
built_path=f"{dir_path}/{dir}"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
built_path=f"{dir_path}/{dir}"
built_path=f'{dir_path}/{dir}'

try:
uos.mkdir(built_path)
except OSError as e:
if e.errno != 17:
raise
dirs.append(f"tmp/{built_path}")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dirs.append(f"tmp/{built_path}")
dirs.append(f'tmp/{built_path}')

continue
#print(f"tmp/{filename} -> {filename}")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover?

with open(f'tmp/{filename}', 'rb') as source_file, open(filename, 'wb') as target_file:
target_file.write(source_file.read())
uos.remove(f'tmp/{filename}')
try:
while len(dirs) > 0:
uos.rmdir(dirs.pop())
uos.rmdir('tmp')
except:
pass
Expand All @@ -81,6 +123,7 @@ def ota_update(host, project, filenames, use_version_prefix=True, user=None, pas
machine.reset()
except Exception as ex:
print(f'Something went wrong: {ex}')
raise ex


def check_for_ota_update(host, project, user=None, passwd=None, timeout=5, soft_reset_device=False):
Expand Down