Adding files for RPi-imager
commit
e33fb470df
@ -0,0 +1,68 @@
|
|||||||
|
# Raspberry Pi Initial Setup
|
||||||
|
|
||||||
|
This repository is about the latest procedure for configuring Raspberry Pi boards in 2026. The reported procedure can be used on any Raspberry Pi board.
|
||||||
|
|
||||||
|
The procedure is about flashing the OS image, make ssh connection, both Wi-Fi and USB (Gadget-Mode), and finally upgrading the OS packages.
|
||||||
|
|
||||||
|
# Flashing the OS image
|
||||||
|
|
||||||
|
**Note: This guide is recommended only for the latest OS image or newer; the Trixi image is used in this guide.**
|
||||||
|
|
||||||
|
First you need to download the file `create_local_json.py`; it is recommended to download the file as a repository to organize your data. Then, open a terminal and change the path to the repository or file location:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /path/to/the/file
|
||||||
|
```
|
||||||
|
|
||||||
|
now, the file should be visible using listing commands -like `ls` command-.
|
||||||
|
|
||||||
|
The file must be have executable permission, therefore run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
chmod +x ./create_local_json.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, run the command with the following flags to search online OS images and enable the Gadget mode -USB OTG-:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./create_local_json.py --online --capabilities usb_otg --device-capabilities usb_otg
|
||||||
|
```
|
||||||
|
|
||||||
|
The shell will give an output similar to:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Wrote 184 images to os_list_local.rpi-imager-manifest
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, you have a new RPi-imager file that will allow us to set up the gadget mode.
|
||||||
|
|
||||||
|
**Note: you must double-click to open `Raspberry Pi Imager` App or right-click open with `Raspberry Pi Imager`; the recommended version is `v2.0.7` or newer.**
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Now, you can select your device, the OS type -desktop or lite-, and make the image customization —user, Wi-Fi, remote access (ssh), Pi Connect, and gadget mode—; **particularly, if you want to use Gadget mode, you must turn on remote access and gadget mode.**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Finally, you must write the image on the micro-SD and get a customization like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
After RPi-Imager has finished, insert the micro-SD card into your device and connect it with a USB to micro-USB cable using the USB port; the other port must be used for only supply mode:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
@ -0,0 +1,256 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os.path
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
DEFAULT_REPO_URL = "https://downloads.raspberrypi.com/os_list_imagingutility_v4.json"
|
||||||
|
DEFAULT_OUTPUT_JSON_FILE = "os_list_local.rpi-imager-manifest"
|
||||||
|
CACHE_DIR = "cache"
|
||||||
|
ICONS_DIR = os.path.join(CACHE_DIR, "icons")
|
||||||
|
CHUNK_SIZE = 1024 * 1024 # read files in 1MB chunks
|
||||||
|
|
||||||
|
# Valid OS capabilities (see doc/json-schema/os-list-schema.json)
|
||||||
|
VALID_OS_CAPABILITIES = {
|
||||||
|
"i2c": "Enable I2C interface option",
|
||||||
|
"onewire": "Enable 1-Wire interface option",
|
||||||
|
"passwordless_sudo": "Enable passwordless sudo option in user setup",
|
||||||
|
"rpi_connect": "Enable Raspberry Pi Connect setup",
|
||||||
|
"secure_boot": "Enable secure boot signing",
|
||||||
|
"serial": "Enable serial interface option",
|
||||||
|
"spi": "Enable SPI interface option",
|
||||||
|
"usb_otg": "Enable USB Gadget mode option",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Valid device (hardware) capabilities
|
||||||
|
VALID_DEVICE_CAPABILITIES = {
|
||||||
|
"i2c": "Device supports I2C interface",
|
||||||
|
"onewire": "Device supports 1-Wire interface",
|
||||||
|
"serial": "Device supports serial interface",
|
||||||
|
"serial_on_console_only": "Serial is console-only mode",
|
||||||
|
"spi": "Device supports SPI interface",
|
||||||
|
"usb_otg": "Device supports USB Gadget mode",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fatal_error(reason):
|
||||||
|
print(f"Error: {reason}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def display_warning(reason):
|
||||||
|
print(f"Warning: {reason}")
|
||||||
|
|
||||||
|
def scan_os_list(os_list, os_entries=OrderedDict(), duplicates=set()):
|
||||||
|
for os_entry in os_list:
|
||||||
|
if "subitems" in os_entry:
|
||||||
|
scan_os_list(os_entry["subitems"], os_entries, duplicates)
|
||||||
|
elif "url" in os_entry:
|
||||||
|
image_filename = os.path.basename(os_entry["url"])
|
||||||
|
if image_filename not in duplicates:
|
||||||
|
if image_filename in os_entries:
|
||||||
|
duplicates.add(image_filename)
|
||||||
|
del os_entries[image_filename]
|
||||||
|
os_entries[image_filename] = os_entry
|
||||||
|
return os_entries, duplicates
|
||||||
|
|
||||||
|
def download_file(url, filename):
|
||||||
|
try:
|
||||||
|
#print(f"Downloading {url} to {filename}")
|
||||||
|
with urllib.request.urlopen(url) as response:
|
||||||
|
with open(filename, "wb") as fh:
|
||||||
|
shutil.copyfileobj(response, fh, CHUNK_SIZE)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def download_icon(icon_url):
|
||||||
|
icon_filename = os.path.join(ICONS_DIR, os.path.basename(icon_url))
|
||||||
|
if os.path.exists(icon_filename) or download_file(icon_url, icon_filename):
|
||||||
|
return icon_filename
|
||||||
|
else:
|
||||||
|
display_warning(f"Couldn't download {icon_url}")
|
||||||
|
|
||||||
|
def calculate_checksum(filename):
|
||||||
|
with open(filename, "rb") as f:
|
||||||
|
if hasattr(hashlib, "file_digest"):
|
||||||
|
# only available in python 3.11 or newer
|
||||||
|
hasher = hashlib.file_digest(f, "sha256")
|
||||||
|
else:
|
||||||
|
hasher = hashlib.new("sha256")
|
||||||
|
while True:
|
||||||
|
data = f.read(CHUNK_SIZE)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
hasher.update(data)
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
epilog = "available OS capabilities for --capabilities:\n"
|
||||||
|
epilog += "\n".join(f" {cap:12} {desc}" for cap, desc in VALID_OS_CAPABILITIES.items())
|
||||||
|
epilog += "\n\navailable device capabilities for --device-capabilities:\n"
|
||||||
|
epilog += "\n".join(f" {cap:20} {desc}" for cap, desc in VALID_DEVICE_CAPABILITIES.items())
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog=epilog)
|
||||||
|
parser.add_argument("--repo", default=DEFAULT_REPO_URL, help="custom repository URL")
|
||||||
|
parser.add_argument("--search-dir", default=".", help="directory to search for downloaded images")
|
||||||
|
parser.add_argument("--output-json", default=DEFAULT_OUTPUT_JSON_FILE, help=f"manifest file to create (defaults to {DEFAULT_OUTPUT_JSON_FILE})")
|
||||||
|
parser.add_argument("--online", action="store_true", help="use online URLs from the repository instead of searching for local files")
|
||||||
|
group = parser.add_mutually_exclusive_group()
|
||||||
|
group.add_argument("--dry-run", action="store_true", help="dry run only, don't create manifest file")
|
||||||
|
group.add_argument("--download-icons", action="store_true", help="make a local copy of the device and OS icons")
|
||||||
|
parser.add_argument("--verify-checksums", action="store_true", help="verify the checksums of the downloaded images (ignored with --online)")
|
||||||
|
parser.add_argument("--capabilities", nargs="+", metavar="CAP",
|
||||||
|
help="add OS capabilities to enable features in the customization wizard (see below)")
|
||||||
|
parser.add_argument("--device-capabilities", nargs="+", metavar="CAP",
|
||||||
|
help="add device (hardware) capabilities to all devices (see below)")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Validate OS capabilities if provided
|
||||||
|
if args.capabilities:
|
||||||
|
invalid_caps = set(args.capabilities) - VALID_OS_CAPABILITIES.keys()
|
||||||
|
if invalid_caps:
|
||||||
|
fatal_error(f"Invalid OS capabilities: {', '.join(sorted(invalid_caps))}. Valid capabilities are: {', '.join(VALID_OS_CAPABILITIES.keys())}")
|
||||||
|
|
||||||
|
# Validate device capabilities if provided
|
||||||
|
if args.device_capabilities:
|
||||||
|
invalid_caps = set(args.device_capabilities) - VALID_DEVICE_CAPABILITIES.keys()
|
||||||
|
if invalid_caps:
|
||||||
|
fatal_error(f"Invalid device capabilities: {', '.join(sorted(invalid_caps))}. Valid capabilities are: {', '.join(VALID_DEVICE_CAPABILITIES.keys())}")
|
||||||
|
|
||||||
|
repo_urlparts = urllib.parse.urlparse(args.repo)
|
||||||
|
if not repo_urlparts.netloc or repo_urlparts.scheme not in ("http", "https"):
|
||||||
|
fatal_error("Expected repo to be a http:// or https:// URL")
|
||||||
|
|
||||||
|
repo_filename = os.path.basename(repo_urlparts.path)
|
||||||
|
if os.path.exists(repo_filename):
|
||||||
|
online_json_file = repo_filename
|
||||||
|
else:
|
||||||
|
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||||
|
online_json_file = os.path.join(CACHE_DIR, repo_filename)
|
||||||
|
if os.path.exists(online_json_file):
|
||||||
|
print(f"Info: {online_json_file} already exists, so using local file")
|
||||||
|
else:
|
||||||
|
if not download_file(args.repo, online_json_file):
|
||||||
|
fatal_error(f"Couldn't download {args.repo}")
|
||||||
|
|
||||||
|
with open(online_json_file) as online_fh:
|
||||||
|
online_data = json.load(online_fh)
|
||||||
|
# Find image filenames in the online JSON (discarding duplicates)
|
||||||
|
online_os_entries, duplicate_online_filenames = scan_os_list(online_data["os_list"])
|
||||||
|
if not online_os_entries:
|
||||||
|
fatal_error(f"No matching OSes found in {online_json_file}")
|
||||||
|
if args.download_icons:
|
||||||
|
os.makedirs(ICONS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
local_os_entries = dict()
|
||||||
|
local_device_tags = set()
|
||||||
|
|
||||||
|
if args.online:
|
||||||
|
# Use all OS entries from online manifest with their original URLs
|
||||||
|
for name, os_entry in online_os_entries.items():
|
||||||
|
if args.dry_run:
|
||||||
|
print(f"Including {os_entry['name']}")
|
||||||
|
local_os_entries[name] = os_entry
|
||||||
|
if "devices" in os_entry:
|
||||||
|
for tag in os_entry["devices"]:
|
||||||
|
local_device_tags.add(tag)
|
||||||
|
if args.download_icons and "icon" in os_entry:
|
||||||
|
local_icon_filename = download_icon(os_entry["icon"])
|
||||||
|
if local_icon_filename:
|
||||||
|
os_entry["icon"] = pathlib.Path(os.path.abspath(local_icon_filename)).as_uri()
|
||||||
|
else:
|
||||||
|
# Recursively search for any matching local filenames
|
||||||
|
duplicate_local_filenames = set()
|
||||||
|
abs_search_dir_len = len(os.path.abspath(args.search_dir)) + 1
|
||||||
|
for root, dirs, files in os.walk(args.search_dir):
|
||||||
|
for name in files:
|
||||||
|
abs_local_image_filename = os.path.abspath(os.path.join(root, name))
|
||||||
|
# remove leading search_dir prefix
|
||||||
|
display_filename = abs_local_image_filename[abs_search_dir_len:]
|
||||||
|
if name in duplicate_online_filenames:
|
||||||
|
display_warning(f"Ignoring {display_filename} as it matched multiple filenames in {online_json_file}")
|
||||||
|
elif name in duplicate_local_filenames:
|
||||||
|
pass
|
||||||
|
elif name in local_os_entries:
|
||||||
|
display_warning(f"Ignoring {display_filename} as there are multiple local copies")
|
||||||
|
del local_os_entries[name]
|
||||||
|
duplicate_local_filenames.add(name)
|
||||||
|
elif name in online_os_entries:
|
||||||
|
os_entry = online_os_entries[name]
|
||||||
|
if "image_download_size" in os_entry and os.path.getsize(abs_local_image_filename) != os_entry["image_download_size"]:
|
||||||
|
display_warning(f"Ignoring {display_filename} as its size doesn't match with {online_json_file}")
|
||||||
|
elif args.verify_checksums and "image_download_sha256" in os_entry and calculate_checksum(abs_local_image_filename) != os_entry["image_download_sha256"]:
|
||||||
|
display_warning(f"Ignoring {display_filename} as its checksum doesn't match with {online_json_file}")
|
||||||
|
else:
|
||||||
|
if args.dry_run:
|
||||||
|
print(f"Found {display_filename} ({os_entry['name']})")
|
||||||
|
# point at our local file instead of the online URL
|
||||||
|
os_entry["url"] = pathlib.Path(abs_local_image_filename).as_uri()
|
||||||
|
# Auto-detect bmap sidecar file (e.g., image.img.xz.bmap or image.img.bmap)
|
||||||
|
for bmap_suffix in [abs_local_image_filename + ".bmap",
|
||||||
|
os.path.splitext(abs_local_image_filename)[0] + ".bmap"]:
|
||||||
|
if os.path.exists(bmap_suffix):
|
||||||
|
os_entry["bmap_url"] = pathlib.Path(bmap_suffix).as_uri()
|
||||||
|
if args.dry_run:
|
||||||
|
print(f" Found bmap sidecar: {bmap_suffix[abs_search_dir_len:]}")
|
||||||
|
break
|
||||||
|
local_os_entries[name] = os_entry
|
||||||
|
if "devices" in os_entry:
|
||||||
|
for tag in os_entry["devices"]:
|
||||||
|
local_device_tags.add(tag)
|
||||||
|
if args.download_icons and "icon" in os_entry:
|
||||||
|
local_icon_filename = download_icon(os_entry["icon"])
|
||||||
|
if local_icon_filename:
|
||||||
|
os_entry["icon"] = pathlib.Path(os.path.abspath(local_icon_filename)).as_uri()
|
||||||
|
|
||||||
|
num_images = len(local_os_entries)
|
||||||
|
if num_images < 1:
|
||||||
|
if args.dry_run:
|
||||||
|
fatal_error(f"No matching image files found")
|
||||||
|
else:
|
||||||
|
fatal_error(f"No matching image files found, so not creating {args.output_json}")
|
||||||
|
# Create the output data structure
|
||||||
|
local_data = dict()
|
||||||
|
if "imager" in online_data and "devices" in online_data["imager"]:
|
||||||
|
local_data["imager"] = dict()
|
||||||
|
local_data["imager"]["devices"] = list()
|
||||||
|
for device in online_data["imager"]["devices"]:
|
||||||
|
if "tags" not in device or not device["tags"] or any(tag in local_device_tags for tag in device["tags"]):
|
||||||
|
local_data["imager"]["devices"].append(device)
|
||||||
|
if args.download_icons and "icon" in device:
|
||||||
|
local_icon_filename = download_icon(device["icon"])
|
||||||
|
if local_icon_filename:
|
||||||
|
device["icon"] = pathlib.Path(os.path.abspath(local_icon_filename)).as_uri()
|
||||||
|
# Add device capabilities if specified
|
||||||
|
if args.device_capabilities:
|
||||||
|
existing_caps = set(device.get("capabilities", []))
|
||||||
|
merged_caps = existing_caps | set(args.device_capabilities)
|
||||||
|
device["capabilities"] = sorted(merged_caps)
|
||||||
|
# Sort the output OSes into the same order as the input OSes
|
||||||
|
os_order = list(online_os_entries.keys())
|
||||||
|
local_data["os_list"] = sorted(local_os_entries.values(), key=lambda x: os_order.index(os.path.basename(x["url"])))
|
||||||
|
|
||||||
|
# Add capabilities to OS entries if specified
|
||||||
|
if args.capabilities:
|
||||||
|
for os_entry in local_data["os_list"]:
|
||||||
|
# Merge with existing capabilities if present
|
||||||
|
existing_caps = set(os_entry.get("capabilities", []))
|
||||||
|
merged_caps = existing_caps | set(args.capabilities)
|
||||||
|
os_entry["capabilities"] = sorted(merged_caps)
|
||||||
|
# And finally write the local JSON file
|
||||||
|
if args.dry_run:
|
||||||
|
print(f"Would write {num_images} image{'s' if num_images > 1 else ''} to {args.output_json}")
|
||||||
|
else:
|
||||||
|
with open(args.output_json, "w") as local_fh:
|
||||||
|
json.dump(local_data, local_fh, indent=2)
|
||||||
|
print(f"Wrote {num_images} image{'s' if num_images > 1 else ''} to {args.output_json}")
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
Loading…
Reference in New Issue