pyhubbledemo 0.0.2__tar.gz → 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyhubbledemo-0.0.2 → pyhubbledemo-0.0.4}/PKG-INFO +2 -1
- {pyhubbledemo-0.0.2 → pyhubbledemo-0.0.4}/pyproject.toml +2 -1
- pyhubbledemo-0.0.4/src/hubbledemo/__init__.py +13 -0
- {pyhubbledemo-0.0.2 → pyhubbledemo-0.0.4}/src/hubbledemo/cli.py +42 -25
- pyhubbledemo-0.0.4/src/hubbledemo/cloud.py +154 -0
- {pyhubbledemo-0.0.2 → pyhubbledemo-0.0.4}/src/hubbledemo/elfmgr.py +18 -100
- pyhubbledemo-0.0.2/src/hubbledemo/__init__.py +0 -10
- {pyhubbledemo-0.0.2 → pyhubbledemo-0.0.4}/.gitignore +0 -0
- {pyhubbledemo-0.0.2 → pyhubbledemo-0.0.4}/LICENSE +0 -0
- {pyhubbledemo-0.0.2 → pyhubbledemo-0.0.4}/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyhubbledemo
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Hubble demo functionality for flashing and provisioning boards
|
|
5
5
|
Author-email: Paul Buckley <paul@hubble.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -9,6 +9,7 @@ Classifier: Operating System :: OS Independent
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Requires-Python: >=3.9
|
|
11
11
|
Requires-Dist: click
|
|
12
|
+
Requires-Dist: intelhex
|
|
12
13
|
Requires-Dist: petname
|
|
13
14
|
Requires-Dist: pyelftools
|
|
14
15
|
Requires-Dist: pyhubblenetwork
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyhubbledemo"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.4"
|
|
8
8
|
requires-python = ">=3.9"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Paul Buckley", email="paul@hubble.com" },
|
|
@@ -22,6 +22,7 @@ dependencies = [
|
|
|
22
22
|
"petname",
|
|
23
23
|
"pylink-square",
|
|
24
24
|
"pyelftools",
|
|
25
|
+
"intelhex",
|
|
25
26
|
"pyhubblenetwork",
|
|
26
27
|
]
|
|
27
28
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# hubbledemo/__init__.py
|
|
2
|
+
|
|
3
|
+
from .elfmgr import flash_elf, patch_elf, probe_device, convert_elf_to_hex
|
|
4
|
+
from .cloud import fetch_elf, fetch_metadata
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"flash_elf",
|
|
8
|
+
"fetch_elf",
|
|
9
|
+
"patch_elf",
|
|
10
|
+
"probe_device",
|
|
11
|
+
"fetch_metadata",
|
|
12
|
+
"convert_elf_to_hex",
|
|
13
|
+
]
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
from typing import Optional
|
|
3
3
|
import click
|
|
4
4
|
import os
|
|
5
|
-
import json
|
|
6
|
-
import time
|
|
7
|
-
import base64
|
|
8
|
-
import sys
|
|
9
5
|
import petname
|
|
10
|
-
|
|
11
|
-
from
|
|
12
|
-
from hubblenetwork import Organization, Credentials
|
|
13
|
-
from hubbledemo import flash_elf, fetch_elf, patch_elf, probe_device
|
|
6
|
+
import hubbledemo
|
|
7
|
+
from hubblenetwork import Organization
|
|
14
8
|
|
|
15
9
|
|
|
16
10
|
def _get_env_or_fail(name: str) -> str:
|
|
@@ -40,7 +34,7 @@ def cli() -> None:
|
|
|
40
34
|
|
|
41
35
|
@cli.command("probe")
|
|
42
36
|
def probe() -> None:
|
|
43
|
-
if probe_device():
|
|
37
|
+
if hubbledemo.probe_device():
|
|
44
38
|
click.echo("[SUCCESS] Device detected.")
|
|
45
39
|
else:
|
|
46
40
|
click.secho(
|
|
@@ -70,7 +64,16 @@ def probe() -> None:
|
|
|
70
64
|
help="Token (if not using HUBBLE_API_TOKEN env var)",
|
|
71
65
|
)
|
|
72
66
|
def flash(board: str, name: str = None, org_id: str = None, token: str = None) -> None:
|
|
73
|
-
|
|
67
|
+
click.secho("[INFO] Getting metadata... ", nl=False)
|
|
68
|
+
metadata = hubbledemo.fetch_metadata()
|
|
69
|
+
click.secho("[SUCCESS]")
|
|
70
|
+
|
|
71
|
+
if board not in metadata:
|
|
72
|
+
click.secho("[ERROR] Unsupported board selected!", fg="red",err=True)
|
|
73
|
+
return 2
|
|
74
|
+
|
|
75
|
+
# If using jlink, we want to make sure early on that a device is connected
|
|
76
|
+
if metadata[board]["method"] == "jlink-flash" and not hubbledemo.probe_device():
|
|
74
77
|
click.secho(
|
|
75
78
|
"[ERROR] Failed to connect to device. Check your connection.",
|
|
76
79
|
fg="red",
|
|
@@ -79,15 +82,15 @@ def flash(board: str, name: str = None, org_id: str = None, token: str = None) -
|
|
|
79
82
|
return 2
|
|
80
83
|
|
|
81
84
|
org_id, token = _get_org_and_token(org_id, token)
|
|
82
|
-
org = Organization(
|
|
85
|
+
org = Organization(org_id=org_id, api_token=token)
|
|
83
86
|
|
|
84
|
-
click.secho(
|
|
87
|
+
click.secho("[INFO] Organization info acquired:")
|
|
85
88
|
click.secho(f"\tID: {org.credentials.org_id}")
|
|
86
89
|
click.secho(f"\tName: {org.name}")
|
|
87
90
|
click.secho(f"\tEnvironment: {org.env.name}")
|
|
88
91
|
|
|
89
|
-
click.secho(
|
|
90
|
-
device = org.register_device()
|
|
92
|
+
click.secho('[INFO] Registering new device"... ', nl=False)
|
|
93
|
+
device = org.register_device(encryption=metadata[board]["encryption"] if "encryption" in metadata[board] else None)
|
|
91
94
|
click.secho("[SUCCESS]")
|
|
92
95
|
click.secho(f"\tDevice ID: {device.id}")
|
|
93
96
|
click.secho(f"\tDevice Key: {device.key}")
|
|
@@ -95,24 +98,39 @@ def flash(board: str, name: str = None, org_id: str = None, token: str = None) -
|
|
|
95
98
|
if not name:
|
|
96
99
|
name = petname.generate(words=3)
|
|
97
100
|
click.secho(f'[INFO] No name supplied. Naming device "{name}"')
|
|
98
|
-
click.secho(
|
|
101
|
+
click.secho("[INFO] Setting device name... ", nl=False)
|
|
99
102
|
org.set_device_name(device_id=device.id, name=name)
|
|
100
103
|
click.secho("[SUCCESS]")
|
|
101
104
|
|
|
102
105
|
click.secho(f"[INFO] Retrieving binary for {board}... ", nl=False)
|
|
103
|
-
buf = fetch_elf(board=board)
|
|
106
|
+
buf = hubbledemo.fetch_elf(board=board)
|
|
104
107
|
click.secho("[SUCCESS]")
|
|
105
108
|
|
|
106
109
|
click.secho("[INFO] Patching key + UTC into binary... ", nl=False)
|
|
107
|
-
patch_elf(buf, device)
|
|
108
|
-
click.secho("[SUCCESS]")
|
|
109
|
-
|
|
110
|
-
click.secho("[INFO] Flashing binary onto device... ", nl=False)
|
|
111
|
-
flash_elf(board=board, buf=buf)
|
|
110
|
+
hubbledemo.patch_elf(buf, device)
|
|
112
111
|
click.secho("[SUCCESS]")
|
|
113
112
|
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
# Depending on method, deliver how we will do provisioning.
|
|
114
|
+
if metadata[board]["method"] == "jlink-flash":
|
|
115
|
+
click.secho("[INFO] Flashing binary onto device... ", nl=False)
|
|
116
|
+
hubbledemo.flash_elf(board=board, buf=buf, jlink_device=metadata[board]["jlink_device"])
|
|
117
|
+
click.secho("[SUCCESS]")
|
|
118
|
+
|
|
119
|
+
click.secho(f"\n{board} successfully flashed and provisioned!")
|
|
120
|
+
elif metadata[board]["method"] == "generate-hex":
|
|
121
|
+
click.secho("[INFO] Generating hex file... ", nl=False)
|
|
122
|
+
hubbledemo.convert_elf_to_hex(buf=buf, filename=name)
|
|
123
|
+
click.secho("[SUCCESS]")
|
|
124
|
+
click.secho(
|
|
125
|
+
f"\nHex file written to \"{os.getcwd()}/{name}.hex\"",
|
|
126
|
+
bg='blue', fg='white')
|
|
127
|
+
else:
|
|
128
|
+
click.secho(
|
|
129
|
+
"[ERROR] Unknown method for provisioning board.",
|
|
130
|
+
fg="red",
|
|
131
|
+
err=True,
|
|
132
|
+
)
|
|
133
|
+
return 2
|
|
116
134
|
|
|
117
135
|
def main(argv: Optional[list[str]] = None) -> int:
|
|
118
136
|
"""
|
|
@@ -130,7 +148,6 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|
|
130
148
|
return 2
|
|
131
149
|
return 0
|
|
132
150
|
|
|
133
|
-
|
|
134
151
|
if __name__ == "__main__":
|
|
135
152
|
# Forward command-line args (excluding the program name) to main()
|
|
136
153
|
raise SystemExit(main())
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from typing import Optional, Any
|
|
2
|
+
import io
|
|
3
|
+
import os
|
|
4
|
+
import requests
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
_ARTIFACT_BASE_URL = (
|
|
10
|
+
"https://raw.githubusercontent.com/HubbleNetwork/hubble-tldm/master/merge"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
_METADATA_FILENAME = "md.json"
|
|
14
|
+
|
|
15
|
+
def fetch_elf(board: str, timeout: float = 20.0) -> io.BytesIO:
|
|
16
|
+
"""
|
|
17
|
+
Download the board-specific ELF from HubbleNetwork/hubble-tldm/merge and
|
|
18
|
+
return it as an io.BytesIO.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
board_name : str
|
|
23
|
+
Board identifier (e.g. 'nrf21540dk', 'xg24_ek2703a', 'xg22_ek4108a').
|
|
24
|
+
timeout : float
|
|
25
|
+
Requests timeout in seconds (connect + read).
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
io.BytesIO
|
|
30
|
+
Raw bytes of the .elf file
|
|
31
|
+
|
|
32
|
+
Raises
|
|
33
|
+
------
|
|
34
|
+
ValueError
|
|
35
|
+
If the board is not supported or name is malformed.
|
|
36
|
+
FileNotFoundError
|
|
37
|
+
If the expected ELF file does not exist in the merge directory.
|
|
38
|
+
ConnectionError
|
|
39
|
+
On network, HTTP, or parsing failures.
|
|
40
|
+
"""
|
|
41
|
+
if not isinstance(board, str) or not board.strip():
|
|
42
|
+
raise ValueError("board must be a non-empty string")
|
|
43
|
+
|
|
44
|
+
# If we have a local override, just use that
|
|
45
|
+
local_file = os.getenv("HUBBLE_DEMO_ELF_FILE")
|
|
46
|
+
if local_file:
|
|
47
|
+
return io.BytesIO(Path(local_file).read_bytes())
|
|
48
|
+
|
|
49
|
+
# Give option (for development) to pull binary from elsewhere
|
|
50
|
+
val = os.getenv("HUBBLE_DEMO_URL_OVERRIDE")
|
|
51
|
+
if val:
|
|
52
|
+
base_url = val
|
|
53
|
+
else:
|
|
54
|
+
base_url = _ARTIFACT_BASE_URL
|
|
55
|
+
|
|
56
|
+
url = f"{base_url}/{board}.elf"
|
|
57
|
+
|
|
58
|
+
_RETRY_STATUS = {429, 500, 502, 503, 504}
|
|
59
|
+
retries = 5
|
|
60
|
+
|
|
61
|
+
last_err: Optional[Exception] = None
|
|
62
|
+
for attempt in range(1, max(1, retries) + 1):
|
|
63
|
+
try:
|
|
64
|
+
resp = requests.get(url, timeout=5)
|
|
65
|
+
|
|
66
|
+
if resp.status_code == 404:
|
|
67
|
+
# Not found is definitive; don't bother retrying
|
|
68
|
+
raise FileNotFoundError(f"No ELF for board '{board}' at {url}")
|
|
69
|
+
|
|
70
|
+
# Retry transient status codes (unless it's the final attempt)
|
|
71
|
+
if resp.status_code in _RETRY_STATUS and attempt < retries:
|
|
72
|
+
sleep_s = (2 ** (attempt - 1))
|
|
73
|
+
time.sleep(sleep_s)
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
# Raise for other non-OK codes
|
|
77
|
+
resp.raise_for_status()
|
|
78
|
+
|
|
79
|
+
# Basic sanity checks: content-type and size
|
|
80
|
+
ctype = (resp.headers.get("Content-Type") or "").lower()
|
|
81
|
+
if "html" in ctype:
|
|
82
|
+
raise ValueError(f"Expected ELF bytes, got {ctype} from {url}")
|
|
83
|
+
|
|
84
|
+
return io.BytesIO(resp.content)
|
|
85
|
+
|
|
86
|
+
except (requests.Timeout, requests.ConnectionError) as e:
|
|
87
|
+
last_err = e
|
|
88
|
+
if attempt < retries:
|
|
89
|
+
sleep_s = (2 ** (attempt - 1))
|
|
90
|
+
time.sleep(sleep_s)
|
|
91
|
+
continue
|
|
92
|
+
raise ConnectionError(f"Failed to download ELF from {url}: {e}") from e
|
|
93
|
+
|
|
94
|
+
except Exception:
|
|
95
|
+
raise
|
|
96
|
+
|
|
97
|
+
# Should not reach here; defensive:
|
|
98
|
+
raise ConnectionError(f"Failed to download ELF from {url}: {last_err}")
|
|
99
|
+
|
|
100
|
+
def fetch_metadata() -> Any:
|
|
101
|
+
"""
|
|
102
|
+
Downloads the metadata about devices and returns it
|
|
103
|
+
"""
|
|
104
|
+
# If we have a local override, just use that
|
|
105
|
+
local_file = os.getenv("HUBBLE_DEMO_METADATA_FILE")
|
|
106
|
+
if local_file:
|
|
107
|
+
with open(local_file, "r", encoding="utf-8") as f:
|
|
108
|
+
return json.load(f)
|
|
109
|
+
|
|
110
|
+
# Give option (for development) to pull binary from elsewhere
|
|
111
|
+
val = os.getenv("HUBBLE_DEMO_URL_OVERRIDE")
|
|
112
|
+
if val:
|
|
113
|
+
base_url = val
|
|
114
|
+
else:
|
|
115
|
+
base_url = _ARTIFACT_BASE_URL
|
|
116
|
+
|
|
117
|
+
url = f"{base_url}/{_METADATA_FILENAME}"
|
|
118
|
+
|
|
119
|
+
_RETRY_STATUS = {429, 500, 502, 503, 504}
|
|
120
|
+
retries = 5
|
|
121
|
+
|
|
122
|
+
last_err: Optional[Exception] = None
|
|
123
|
+
for attempt in range(1, max(1, retries) + 1):
|
|
124
|
+
try:
|
|
125
|
+
resp = requests.get(url, timeout=5)
|
|
126
|
+
|
|
127
|
+
if resp.status_code == 404:
|
|
128
|
+
# Not found is definitive; don't bother retrying
|
|
129
|
+
raise FileNotFoundError("No metadata file found for devices")
|
|
130
|
+
|
|
131
|
+
# Retry transient status codes (unless it's the final attempt)
|
|
132
|
+
if resp.status_code in _RETRY_STATUS and attempt < retries:
|
|
133
|
+
sleep_s = 2 ** (attempt - 1)
|
|
134
|
+
time.sleep(sleep_s)
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
# Raise for other non-OK codes
|
|
138
|
+
resp.raise_for_status()
|
|
139
|
+
|
|
140
|
+
return json.loads(resp.text)
|
|
141
|
+
|
|
142
|
+
except (requests.Timeout, requests.ConnectionError) as e:
|
|
143
|
+
last_err = e
|
|
144
|
+
if attempt < retries:
|
|
145
|
+
sleep_s = (2 ** (attempt - 1))
|
|
146
|
+
time.sleep(sleep_s)
|
|
147
|
+
continue
|
|
148
|
+
raise ConnectionError(f"Failed to download metadata file from {url}: {e}") from e
|
|
149
|
+
|
|
150
|
+
except Exception:
|
|
151
|
+
raise
|
|
152
|
+
|
|
153
|
+
# Should not reach here; defensive:
|
|
154
|
+
raise ConnectionError(f"Failed to download metadata from {url}: {last_err}")
|
|
@@ -5,28 +5,12 @@ from hubblenetwork import Device
|
|
|
5
5
|
import io
|
|
6
6
|
import os
|
|
7
7
|
import base64
|
|
8
|
-
import requests
|
|
9
8
|
import time
|
|
10
9
|
import tempfile
|
|
11
10
|
import pylink
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Dict
|
|
14
11
|
from elftools.elf.elffile import ELFFile
|
|
15
12
|
from elftools.elf.sections import SymbolTableSection
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
_ELF_BASE_URL = (
|
|
19
|
-
"https://raw.githubusercontent.com/HubbleNetwork/hubble-tldm/master/merge"
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
# Map boards to J-Link "device" strings (extend as needed)
|
|
23
|
-
_BOARD_TO_JLINK_DEVICE: Dict[str, str] = {
|
|
24
|
-
"nrf52dk": "nRF52832_xxAA",
|
|
25
|
-
"nrf52840dk": "nRF52840_xxAA",
|
|
26
|
-
"nrf21540dk": "nRF52840_xxAA",
|
|
27
|
-
"xg24_ek2703a": "EFR32MG24BxxxF1536",
|
|
28
|
-
"xg22_ek4108a": "EFR32MG22CxxxF512",
|
|
29
|
-
}
|
|
13
|
+
from intelhex import IntelHex
|
|
30
14
|
|
|
31
15
|
|
|
32
16
|
def _compute_file_offset(sym, sec) -> int:
|
|
@@ -116,7 +100,7 @@ def probe_device() -> bool:
|
|
|
116
100
|
return jlink.num_connected_emulators() > 0
|
|
117
101
|
|
|
118
102
|
|
|
119
|
-
def flash_elf(buf: io.BytesIO, board: str) -> None:
|
|
103
|
+
def flash_elf(buf: io.BytesIO, board: str, jlink_device) -> None:
|
|
120
104
|
"""
|
|
121
105
|
Flash an ELF image (held in a BytesIO) to an nRF52832_xxAA using pylink.
|
|
122
106
|
Creates a temporary .elf on disk (needed by jlink.flash_file) and deletes it afterwards.
|
|
@@ -130,7 +114,6 @@ def flash_elf(buf: io.BytesIO, board: str) -> None:
|
|
|
130
114
|
RuntimeError: on J-Link connection or flashing failure.
|
|
131
115
|
"""
|
|
132
116
|
speed_khz = 4000
|
|
133
|
-
device = _BOARD_TO_JLINK_DEVICE.get(board.strip().lower())
|
|
134
117
|
|
|
135
118
|
# Write the buffer to a real temp file so flash_file can read it (works on all OSes)
|
|
136
119
|
tmp_path = None
|
|
@@ -154,7 +137,7 @@ def flash_elf(buf: io.BytesIO, board: str) -> None:
|
|
|
154
137
|
try:
|
|
155
138
|
jlink.open()
|
|
156
139
|
jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
|
|
157
|
-
jlink.connect(
|
|
140
|
+
jlink.connect(jlink_device, speed=speed_khz)
|
|
158
141
|
|
|
159
142
|
jlink.halt()
|
|
160
143
|
jlink.flash_file(tmp_path, addr=None) # ELF contains its own load addresses
|
|
@@ -174,88 +157,23 @@ def flash_elf(buf: io.BytesIO, board: str) -> None:
|
|
|
174
157
|
except OSError:
|
|
175
158
|
pass
|
|
176
159
|
|
|
160
|
+
def convert_elf_to_hex(buf: io.BytesIO, filename: str):
|
|
161
|
+
ih = IntelHex()
|
|
177
162
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
Download the board-specific ELF from HubbleNetwork/hubble-tldm/merge and
|
|
181
|
-
return it as an io.BytesIO.
|
|
182
|
-
|
|
183
|
-
Parameters
|
|
184
|
-
----------
|
|
185
|
-
board_name : str
|
|
186
|
-
Board identifier (e.g. 'nrf21540dk', 'xg24_ek2703a', 'xg22_ek4108a').
|
|
187
|
-
timeout : float
|
|
188
|
-
Requests timeout in seconds (connect + read).
|
|
189
|
-
|
|
190
|
-
Returns
|
|
191
|
-
-------
|
|
192
|
-
io.BytesIO
|
|
193
|
-
Raw bytes of the .elf file
|
|
194
|
-
|
|
195
|
-
Raises
|
|
196
|
-
------
|
|
197
|
-
ValueError
|
|
198
|
-
If the board is not supported or name is malformed.
|
|
199
|
-
FileNotFoundError
|
|
200
|
-
If the expected ELF file does not exist in the merge directory.
|
|
201
|
-
ConnectionError
|
|
202
|
-
On network, HTTP, or parsing failures.
|
|
203
|
-
"""
|
|
204
|
-
if not isinstance(board, str) or not board.strip():
|
|
205
|
-
raise ValueError("board must be a non-empty string")
|
|
206
|
-
|
|
207
|
-
# If we have a local override, just use that
|
|
208
|
-
local_file = os.getenv("HUBBLE_DEMO_ELF_FILE")
|
|
209
|
-
if local_file:
|
|
210
|
-
return io.BytesIO(Path(local_file).read_bytes())
|
|
211
|
-
|
|
212
|
-
# Give option (for development) to pull binary from elsewhere
|
|
213
|
-
val = os.getenv("HUBBLE_DEMO_ELF_URL_OVERRIDE")
|
|
214
|
-
if val:
|
|
215
|
-
base_url = val
|
|
216
|
-
else:
|
|
217
|
-
base_url = _ELF_BASE_URL
|
|
218
|
-
|
|
219
|
-
url = f"{base_url}/{board}.elf"
|
|
220
|
-
|
|
221
|
-
_RETRY_STATUS = {429, 500, 502, 503, 504}
|
|
222
|
-
retries = 5
|
|
223
|
-
|
|
224
|
-
last_err: Optional[Exception] = None
|
|
225
|
-
for attempt in range(1, max(1, retries) + 1):
|
|
226
|
-
try:
|
|
227
|
-
resp = requests.get(url, timeout=timeout)
|
|
228
|
-
|
|
229
|
-
if resp.status_code == 404:
|
|
230
|
-
# Not found is definitive; don't bother retrying
|
|
231
|
-
raise FileNotFoundError(f"No ELF for board '{board}' at {url}")
|
|
232
|
-
|
|
233
|
-
# Retry transient status codes (unless it's the final attempt)
|
|
234
|
-
if resp.status_code in _RETRY_STATUS and attempt < retries:
|
|
235
|
-
sleep_s = backoff * (2 ** (attempt - 1))
|
|
236
|
-
time.sleep(sleep_s)
|
|
237
|
-
continue
|
|
238
|
-
|
|
239
|
-
# Raise for other non-OK codes
|
|
240
|
-
resp.raise_for_status()
|
|
241
|
-
|
|
242
|
-
# Basic sanity checks: content-type and size
|
|
243
|
-
ctype = (resp.headers.get("Content-Type") or "").lower()
|
|
244
|
-
if "html" in ctype:
|
|
245
|
-
raise ValueError(f"Expected ELF bytes, got {ctype} from {url}")
|
|
163
|
+
buf.seek(0)
|
|
164
|
+
elf = ELFFile(buf)
|
|
246
165
|
|
|
247
|
-
|
|
166
|
+
for segment in elf.iter_segments():
|
|
167
|
+
if segment["p_type"] != "PT_LOAD":
|
|
168
|
+
continue
|
|
248
169
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
sleep_s = backoff * (2 ** (attempt - 1))
|
|
253
|
-
time.sleep(sleep_s)
|
|
254
|
-
continue
|
|
255
|
-
raise ConnectionError(f"Failed to download ELF from {url}: {e}") from e
|
|
170
|
+
data = segment.data()
|
|
171
|
+
# Prefer physical address if present, otherwise virtual
|
|
172
|
+
addr = segment['p_paddr'] if segment['p_paddr'] != 0 else segment['p_vaddr']
|
|
256
173
|
|
|
257
|
-
|
|
258
|
-
|
|
174
|
+
# Store bytes into IntelHex at the proper address
|
|
175
|
+
for offset, b in enumerate(data):
|
|
176
|
+
ih[addr + offset] = b
|
|
177
|
+
#ih.frombytes(data, offset=addr)
|
|
259
178
|
|
|
260
|
-
|
|
261
|
-
raise ConnectionError(f"Failed to download ELF from {url}: {last_err}")
|
|
179
|
+
ih.write_hex_file(str(filename + ".hex"))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|