commit e33fb470dfc2a6e143b71334b0c0ab1145ab642f Author: Gerardo Marx Date: Sat Apr 11 10:47:11 2026 -0600 Adding files for RPi-imager diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..b602b03 --- /dev/null +++ b/Readme.md @@ -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.** + + +![Open the file with the Raspberry Pi Imager](imager.jpg) + +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.** + +![Select your device](device.jpg) + +![Select your device](gadget.jpg) + +Finally, you must write the image on the micro-SD and get a customization like this: + +![Image customization](custom.jpg) + +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: + +![USB port for gadget mode](usb.jpg) + + + + + + + + + + + + diff --git a/clients.png b/clients.png new file mode 100644 index 0000000..d81c608 Binary files /dev/null and b/clients.png differ diff --git a/create_local_json.py b/create_local_json.py new file mode 100755 index 0000000..ce777a9 --- /dev/null +++ b/create_local_json.py @@ -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}") + diff --git a/custom.jpg b/custom.jpg new file mode 100644 index 0000000..4e41e9c Binary files /dev/null and b/custom.jpg differ diff --git a/device.jpg b/device.jpg new file mode 100644 index 0000000..a281b2f Binary files /dev/null and b/device.jpg differ diff --git a/gadget.jpg b/gadget.jpg new file mode 100644 index 0000000..1bf1471 Binary files /dev/null and b/gadget.jpg differ diff --git a/imager.jpg b/imager.jpg new file mode 100644 index 0000000..8d7d55b Binary files /dev/null and b/imager.jpg differ diff --git a/usb.jpg b/usb.jpg new file mode 100644 index 0000000..b0fbb1e Binary files /dev/null and b/usb.jpg differ