{"id":48648997,"url":"https://github.com/chrisgleissner/vivipi","last_synced_at":"2026-05-09T10:58:46.199Z","repository":{"id":349421272,"uuid":"1201948391","full_name":"chrisgleissner/vivipi","owner":"chrisgleissner","description":"Network status monitor for a Raspberry Pi Pico 2W with configurable probes and Waveshare display support","archived":false,"fork":false,"pushed_at":"2026-04-17T12:48:28.000Z","size":4170,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-17T14:37:40.249Z","etag":null,"topics":["dashboard","health-check","network","oled-display","probe","raspberry-pi","raspberry-pi-pico","sh1107","status"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chrisgleissner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-05T11:39:09.000Z","updated_at":"2026-04-17T12:43:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chrisgleissner/vivipi","commit_stats":null,"previous_names":["chrisgleissner/vivipi"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/chrisgleissner/vivipi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisgleissner%2Fvivipi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisgleissner%2Fvivipi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisgleissner%2Fvivipi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisgleissner%2Fvivipi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chrisgleissner","download_url":"https://codeload.github.com/chrisgleissner/vivipi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisgleissner%2Fvivipi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32203362,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-23T20:19:26.138Z","status":"ssl_error","status_checked_at":"2026-04-23T20:19:23.520Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dashboard","health-check","network","oled-display","probe","raspberry-pi","raspberry-pi-pico","sh1107","status"],"created_at":"2026-04-10T08:26:57.057Z","updated_at":"2026-05-09T10:58:46.171Z","avatar_url":"https://github.com/chrisgleissner.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ViviPi\n\nSee your device health at a glance.\n\n[![Build](https://github.com/chrisgleissner/vivipi/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/chrisgleissner/vivipi/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/chrisgleissner/vivipi/graph/badge.svg)](https://codecov.io/gh/chrisgleissner/vivipi)\n[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)\n[![Hardware](https://img.shields.io/badge/hardware-Raspberry%20Pi%20Pico-blue)](https://github.com/chrisgleissner/vivipi/releases)\n[![Runtime](https://img.shields.io/badge/runtime-MicroPython%20%7C%20Python-blue)](https://github.com/chrisgleissner/vivipi)\n\nViviPi (pronounced \"VEE-vee-pie\", from the Latin *viv-* in *vivere*, \"to live\") is a minimal, glanceable monitoring system for Raspberry Pi Pico display modules. It is built around a deterministic fixed-width UI that lets you read health state at a glance and inspect details with two hardware buttons. The default target is a Pico 2W with a 128x64 SH1107 OLED, but the runtime and build pipeline also support Waveshare Pico OLED, LCD, and e-paper modules.\n\n## Features\n\n- Fixed-width, event-driven UI designed for glanceable device health.\n- Two-button navigation for overview, detail, diagnostics, and about screens.\n- Display support for Pico OLED, LCD, and e-paper modules; verified on the Waveshare Pico OLED 1.3.\n- Built-in `PING`, `TELNET`, `FTP`, `HTTP`, and `SERVICE` checks.\n- Dedicated `IDENT` and `DMA` probes for Ultimate 64 and Commodore 64 Ultimate targets.\n- Configurable scheduling and back-off so repeated checks do not overwhelm targets.\n- One-command local workflow for install, test, firmware build, and deploy.\n\n![Boot Logo](./docs/img/vivipi_boot_logo.jpg)\n![Checks all OK](./docs/img/vivipi_checks_all_ok.png)\n![Checks partially OK](./docs/img/vivipi_checks_partially_ok.png)\n\n## System Architecture\n\nViviPi runs on the Pico and evaluates checks through two paths: direct network probes (`PING`, `IDENT`, `DMA`, `TELNET`, `FTP`, `HTTP`) and `SERVICE` probes.\n\n```mermaid\nflowchart TB\n  Pico[Pico runtime]\n\n  subgraph Direct[Direct probes from the Pico]\n    direction LR\n    Ping[PING]\n    Ident[IDENT]\n    Dma[DMA]\n    Telnet[TELNET]\n    Ftp[FTP]\n    Http[HTTP]\n  end\n\n  subgraph ServicePath[SERVICE probe path]\n    direction LR\n    ServiceProbe[SERVICE]\n    ServiceAPI[Vivi Service /checks endpoint]\n    AdbBackend[Default backend: adb service]\n    Android[Android phone availability]\n    CustomChecks[Custom checks from any backend]\n\n    ServiceProbe --\u003e|GET VIVIPI_SERVICE_BASE_URL| ServiceAPI\n    ServiceAPI --\u003e AdbBackend\n    AdbBackend --\u003e Android\n    ServiceAPI --\u003e CustomChecks\n  end\n\n  Pico --\u003e Ping\n  Pico --\u003e Ident\n  Pico --\u003e Dma\n  Pico --\u003e Telnet\n  Pico --\u003e Ftp\n  Pico --\u003e Http\n  Pico --\u003e ServiceProbe\n```\n\nDirect probes run from the Pico itself. `SERVICE` is the extension point for any kind of check driven not by the Pico, but by another device.\n\n### Probe Freshness Verification\n\nEach standard overview row ends with a dedicated freshness cell to the right of the status text.\n\n- A full white bar means the latest scheduled probe completed on time.\n- Shorter bars mean one or more probe intervals were missed.\n- A single sentinel pixel means the probe is fully overdue.\n\nThe bottom scanline shows a 3-pixel health indicator that moves from left to right and then wraps back to the left. It advances when a probe completes, so continued movement means the device is healthy and still issuing probes.\n\n### Probe Reference\n\n| Probe | Performs | Success condition | Failure condition or note |\n| --- | --- | --- | --- |\n| `PING` | ICMP ping | Response received; latency measured locally | No response or timeout |\n| `IDENT` | UDP/64 JSON discovery request | Returns a JSON device identity payload with a matching echo token | Invalid JSON, missing identity fields, echo mismatch, connection failure, or timeout |\n| `DMA` | TCP/64 DMA command session with optional password | Authenticates when configured, identifies the device, reads the debug register, and returns flash metadata | Authentication failure, invalid reply payloads, connection failure, or timeout |\n| `TELNET` | Telnet session with optional credentials | `OK` after Telnet returns a non-empty response with no clear failure markers and the probe drains that response before closing the session | Connection failure, explicit failure-marker text such as denied/incorrect/failed/invalid, no non-empty response before idle/close, incomplete response consumption before timeout/chunk limit, or immediate close/reset before any non-empty response is received |\n| `FTP` | FTP control session with optional credentials | A valid FTP greeting is received and the control socket stays usable long enough to quit cleanly | Missing or invalid greeting, connection failure, or timeout |\n| `HTTP` | HTTP request | Response status is `2xx` or `3xx`; latency measured locally | Non-`2xx`/`3xx` response or timeout |\n| `SERVICE` | HTTP request to a `/checks` endpoint | Response returns a valid checks payload; each returned check becomes an independent ViviPi check | Default backend uses `adb` to report Android availability, but any backend can return checks through the same schema |\n\n## Default Hardware Target\n\n- Board: Raspberry Pi Pico 2W\n- Display: 128x64 monochrome OLED\n- Display controller: SH1107\n- Character grid: 16 columns x 8 rows using 8x8 bitmap cells\n- Display interface: 4-wire SPI, mode 3\n- Native transport mapping: portrait-native 64x128 SH1107 page stream with inferred column offset 32 for the Waveshare Pico OLED 1.3\n\n### Pin Mapping\n\n| Signal | GPIO |\n| --- | --- |\n| DIN | GP11 |\n| CLK | GP10 |\n| CS | GP9 |\n| DC | GP8 |\n| RST | GP12 |\n| BTN A / Key 0 | GP15 |\n| BTN B / Key 1 | GP17 |\n\nOn the main tested Waveshare Pico OLED 1.3 hardware, `GP15` is User Key 0 and `GP17` is User Key 1. In the config and code these are named `a` and `b`. By default, `a: GP15` and `b: GP17` use pull-up inputs, so a press pulls the line away from its idle state. If your wiring needs it, each button can be configured explicitly as `{ pin: GP15, pull: up }` or `{ pin: GP15, pull: down }`.\n\n## On-Device Controls\n\nViviPi is meant to be usable directly from the Pico display module without any extra menu system.\n\n### Main Tested Screen\n\nOn the main tested screen, the controls work like this:\n\n| Physical key | GPIO | Internal button name | What it does |\n| --- | --- | --- | --- |\n| `Key 1` | `GP17` | `b` / `Button.B` | Enter details for the current check. Press it again to exit details and return to the main screen. |\n| `Key 0` | `GP15` | `a` / `Button.A` | Cycle through the detail pages. |\n\nAdditional input behavior:\n\n- Presses are debounced with a 20-50 ms window; the default controller uses `30 ms`.\n- Holding `Key 0` repeats every `500 ms`, which lets you move through detail pages quickly.\n- `Key 1` is single-step only and does not auto-repeat when held.\n- In code and build config, `Key 0` maps to `a` and `Button.A`, while `Key 1` maps to `b` and `Button.B`.\n\n### Screen Flow\n\n1. Start on the main screen.\n2. Press `Key 1` to enter details for the currently selected check.\n3. Press `Key 0` to cycle through detail pages.\n4. Press `Key 1` again to leave details and return to the main screen.\n\n## Quick Start\n\nThis is the shortest useful path from clone to a running local workflow and a deployable device bundle.\n\nRequirements:\n\n- Python 3.12+\n- `python3 -m venv`\n- `adb` only if you want the default service against connected Android devices\n- `mpremote` only if you want `./build deploy` to copy files onto a Pico 2W\n\nFor day-to-day editor workflows, copy `config/build-deploy.local.example.yaml` to `config/build-deploy.local.yaml` and put your Wi-Fi credentials there. `./build render-config`, `./build build-firmware`, and `./build deploy` automatically prefer that local file when it exists. Pass `--config config/build-deploy.yaml` when you need to bypass a sibling local override. If a local target such as a C64U moves to a new IP address, update `wifi.host_aliases` in `config/build-deploy.local.yaml`; the checked-in `config/checks.local.yaml` targets are written against those aliases so you only need one config edit before rebuilding and deploying.\n\n1. Set Wi-Fi credentials. Add `VIVIPI_SERVICE_BASE_URL` only if you want `SERVICE` checks.\n\n```bash\nexport VIVIPI_WIFI_SSID=\"your-wifi-name\"\nexport VIVIPI_WIFI_PASSWORD=\"your-wifi-password\"\nexport VIVIPI_SERVICE_BASE_URL=\"http://192.168.1.10:8080/checks\"\n```\n\n1. Run the default local workflow.\n\n```bash\n./build\n```\n\n`./build` with no command is equivalent to `./build ci`.\n\nWithout `VIVIPI_SERVICE_BASE_URL`, ViviPi builds only the default direct `PING`, `HTTP`, `FTP`, and `TELNET` checks from [config/checks.yaml](config/checks.yaml). The Pico runtime also supports optional `IDENT` and `DMA` direct checks when you add them explicitly to a custom checks config, but they are not enabled in the shipped default setup.\n\n1. Start the default Vivi Service only if you want the sample `SERVICE` check.\n\n```bash\n./build service --host 0.0.0.0 --port 8080\n```\n\n1. Run one local pass of all configured health checks from the host.\n\n```bash\nscripts/vivipulse --mode local\n```\n\n1. Build and deploy to the Pico when hardware is connected.\n\n```bash\n./build build-firmware\n./build deploy\n```\n\n`./build deploy` uses `mpremote connect auto` to copy the prepared filesystem to the first connected Pico. Use `--device-port` only when you want a specific board. It does not flash a MicroPython UF2 onto a blank board.\n\n## Editor Workflows\n\n### Thonny\n\n1. Create `config/build-deploy.local.yaml` from `config/build-deploy.local.example.yaml` or export the `VIVIPI_WIFI_*` environment variables.\n2. Run `./build build-firmware`.\n3. In Thonny, connect to `MicroPython (Raspberry Pi Pico)`.\n4. Open `artifacts/release/vivipi-device-fs/` as the local source tree and upload its contents to the device root.\n5. Re-run `./build build-firmware` whenever config or source changes, then re-upload the updated files.\n\nThe generated `artifacts/release/vivipi-device-fs/` directory is the exact device filesystem layout expected by the Pico.\n\n### VS Code\n\nThe official Raspberry Pi Pico extension handles Pico toolchain setup, and its MicroPython workflow relies on the MicroPico extension. This repository includes workspace recommendations for both extensions plus ready-made tasks in `.vscode/tasks.json`.\n\n1. Open the repository as a single-folder workspace.\n2. Install the recommended extensions when prompted.\n3. Create `config/build-deploy.local.yaml` from `config/build-deploy.local.example.yaml` for local Wi-Fi and optional service settings.\n4. Run the `ViviPi: Build Firmware Bundle` task to regenerate `artifacts/release/vivipi-device-fs/`.\n5. Run the `ViviPi: Deploy To First Connected Pico` task to build and upload to the first connected Pico.\n\nIf you have more than one board attached, use `./build deploy --device-port \u003cport\u003e` from the integrated terminal.\n\n## Install Paths\n\n### Develop From Source\n\nUse the source checkout when you want the full local workflow:\n\n```bash\n./build install\n./build test\n./build build-firmware\n./build service --host 0.0.0.0 --port 8080\n```\n\nThe canonical entrypoint is `./build`. Run `./build help` for the full CLI surface.\n\n### Install From GitHub Releases\n\nEach GitHub release publishes a small, versioned set of assets. Download the files that match the tag you want to install.\n\n| Asset | Purpose | Contains only what is needed for | How to use it |\n| --- | --- | --- | --- |\n| `vivipi-device-filesystem-\u003cversion\u003e.zip` | Device update bundle | Copying ViviPi onto a Pico after the base MicroPython UF2 is already installed | Unzip or copy the contents onto the Pico with `mpremote fs cp` |\n| `pico2w-micropython-\u003cversion\u003e.txt` | Pinned board bootstrap reference | Finding the exact MicroPython download page and default board port used for the release | Read it first when preparing a blank Pico |\n| `vivipi-service-bundle-\u003cversion\u003e.zip` | Local service starter kit | Running the default ADB-backed service or a minimal custom `SERVICE` endpoint | Unzip it, install the bundled wheel, then run either `vivipi-adb-service` or `custom-service-example.py` |\n| `vivipi-source-\u003cversion\u003e.zip` | Tagged source snapshot | Inspecting or rebuilding the exact source used for the release | Download if you want a ZIP source archive with the release tag in the filename |\n| `vivipi-source-\u003cversion\u003e.tar.gz` | Tagged source snapshot | Inspecting or rebuilding the exact source used for the release | Download if you want a tarball source archive with the release tag in the filename |\n\n#### Device Install From A Release\n\n1. Download `pico2w-micropython-\u003cversion\u003e.txt` and `vivipi-device-filesystem-\u003cversion\u003e.zip` from the release page.\n2. Use the URL in `pico2w-micropython-\u003cversion\u003e.txt` to install the base MicroPython UF2 on the Pico if the board is blank.\n3. Copy the contents of `vivipi-device-filesystem-\u003cversion\u003e.zip` onto the Pico with `mpremote fs cp`, or unzip it locally and use `./build deploy` against the unpacked `vivipi-device-fs/` tree.\n4. Point `VIVIPI_SERVICE_BASE_URL` at a reachable host only if you want `SERVICE` checks baked into `config.json`.\n\n#### Service Install From A Release\n\n1. Download and unzip `vivipi-service-bundle-\u003cversion\u003e.zip`.\n2. Install the bundled wheel with `python -m pip install vivipi-*.whl`.\n3. Start the default service with `vivipi-adb-service --host 0.0.0.0 --port 8080` if you want ADB-backed checks.\n4. Or start `custom-service-example.py --host 0.0.0.0 --port 8080` and adapt its `/checks` payload to expose your own checks.\n5. Set `VIVIPI_SERVICE_BASE_URL` in your build configuration to `http://\u003chost\u003e:8080/checks` before building the device filesystem.\n\n## Build, Test, and Package\n\n`./build` is the canonical entrypoint. Running it with no command is equivalent to `./build ci`, and `./build all` is an alias for the same workflow.\n\n### Common Commands\n\n| Command | What it does |\n| --- | --- |\n| `./build` | Install dependencies, run Ruff, run pytest, and build firmware assets |\n| `./build install` | Create the local virtual environment and install dev dependencies |\n| `./build lint` | Run Ruff |\n| `./build test` | Run pytest |\n| `./build coverage` | Run pytest with branch coverage output |\n| `./build ci` | Run the full local CI workflow |\n| `./build render-config` | Render `artifacts/device/config.json` from the build config |\n| `./build build-firmware` | Build the firmware bundle into `artifacts/release` |\n| `./build release-assets` | Build the versioned GitHub release assets |\n| `./build deploy` | Build the firmware bundle and copy it to the first connected Pico via `mpremote` |\n| `./build service --host 0.0.0.0 --port 8080` | Run the default ADB-backed Vivi Service |\n| `scripts/vivipulse --mode local` | Run one local pass of all configured health checks |\n| `scripts/vivipulse --mode plan` | Resolve the host-side probe plan without sending traffic |\n\nTypical examples:\n\n```bash\nVIVIPI_WIFI_SSID=\"your-wifi\" \\\nVIVIPI_WIFI_PASSWORD=\"your-password\" \\\n./build build-firmware\n```\n\n```bash\nVIVIPI_WIFI_SSID=\"your-wifi\" \\\nVIVIPI_WIFI_PASSWORD=\"your-password\" \\\nVIVIPI_SERVICE_BASE_URL=\"http://192.168.1.10:8080/checks\" \\\n./build build-firmware\n```\n\n```bash\n./build service --host 0.0.0.0 --port 8080\n```\n\nGenerated artifacts are written under `artifacts/`.\n\n### Key Outputs\n\n| Output | Produced by | Purpose |\n| --- | --- | --- |\n| `artifacts/device/config.json` | `./build render-config` | Rendered runtime config |\n| `artifacts/release/vivipi-device-fs/` | `./build build-firmware` | Unpacked device filesystem tree |\n| `artifacts/release/vivipi-device-filesystem-\u003cversion\u003e.zip` | `./build build-firmware` and `./build release-assets` | Deployable device bundle |\n| `artifacts/release/pico2w-micropython-\u003cversion\u003e.txt` | `./build build-firmware` and `./build release-assets` | Pinned MicroPython download reference |\n| `artifacts/release/vivipi-service-bundle-\u003cversion\u003e.zip` | `./build release-assets` | Service starter bundle |\n| `artifacts/release/vivipi-source-\u003cversion\u003e.zip` and `artifacts/release/vivipi-source-\u003cversion\u003e.tar.gz` | `./build release-assets` | Tagged source archives |\n\n## Default Vivi Service\n\nThe default host-side service discovers connected ADB devices and exposes them as monitoring checks.\n\n```bash\n./build service --host 0.0.0.0 --port 8080\n```\n\nThe HTTP endpoint implementation lives in [src/vivipi/services/adb_service.py](src/vivipi/services/adb_service.py). The sample `SERVICE` check in [config/checks.yaml](config/checks.yaml) points at `VIVIPI_SERVICE_BASE_URL`.\n\n### Kubuntu ADB Auto-Start\n\nThe checked-in local development config in [config/checks.local.yaml](config/checks.local.yaml) probes the Pixel 4 through `http://mickey:8081/vivipi/probe/adb/9B081FFAZ001WX`, so this machine needs the ADB-backed service listening on port `8081`.\n\nInstall the user-level systemd units once:\n\n```bash\n./scripts/install_adb_service_user_units.sh\n```\n\nThat installer:\n\n- enables `vivipi-adb-service.service` so the HTTP service starts automatically for your user session\n- enables `vivipi-adb-recover.timer` so `adb start-server` and `adb reconnect offline` run periodically after boot and resume\n- starts the service immediately on the current login session\n\nManual fallback commands:\n\n```bash\n./scripts/run_adb_service.sh start\n./scripts/run_adb_service.sh ensure-adb\n```\n\n## Vivipulse\n\n`scripts/vivipulse` is the host-side stability, reproduction, mitigation-search, and soak-testing entrypoint for direct ViviPi probes.\n\nIt intentionally reuses the Pico's shared lower-level execution seam:\n\n- `vivipi.runtime.checks.build_runtime_definitions()`\n- `vivipi.runtime.checks.build_executor()`\n- `vivipi.core.execution.execute_check()`\n- `vivipi.core.scheduler.due_checks()`\n- `vivipi.core.scheduler.probe_host_key()`\n- `vivipi.core.scheduler.probe_backoff_remaining_s()`\n\nIt intentionally does not run the Pico shell on Linux. `vivipulse` does not reuse `firmware.runtime.run_forever()` as its host loop, `RuntimeApp`, display rendering, button handling, Wi-Fi bootstrap, or firmware display backends.\n\n### Purpose\n\nUse `vivipulse` when you want to:\n\n- reproduce instability outside the Pico UI/runtime shell\n- capture request-level JSONL traces with exact ordering and timing\n- identify the last-success and first-failure boundary for a target\n- inspect a local `1541ultimate` checkout to guide safer probe profiles\n- search for a less disruptive same-host execution profile\n- soak-test a chosen profile for a fixed wall-clock duration\n\n### Inputs\n\nThe canonical repository input is `--build-config`, which defaults to `config/build-deploy.yaml` and prefers `config/build-deploy.local.yaml` automatically when no explicit input option is supplied.\n\nSupported input shapes:\n\n- `--build-config PATH`\n- `--runtime-config PATH`\n- `--checks-config PATH`\n\n### Modes\n\n`local` runs exactly one host-side pass of all resolved checks and is the simplest way to verify the full local health configuration:\n\n```bash\nscripts/vivipulse --mode local\n```\n\n`plan` resolves checks, same-host groups, and ordering without sending traffic:\n\n```bash\nscripts/vivipulse --mode plan\n```\n\n`reproduce` runs the shared probes from Linux and writes request-level traces:\n\n```bash\nscripts/vivipulse --mode reproduce --passes 2 --target http://192.168.1.10/health\n```\n\n`search` inspects the local Ultimate firmware source, then evaluates a small mitigation set in priority order:\n\n```bash\nscripts/vivipulse \\\n  --mode search \\\n  --passes 2 \\\n  --ultimate-repo ../1541ultimate\n```\n\n`soak` runs a chosen profile for a wall-clock duration:\n\n```bash\nscripts/vivipulse --mode soak --duration 2h --same-host-backoff-ms 1000\n```\n\nUseful controls:\n\n- `--check-id ID` to restrict execution to one or more checks\n- `--target TARGET` to restrict execution to an exact target value\n- `--same-host-backoff-ms N` to override the configured same-host gap\n- `--allow-concurrent-same-host` to disable same-host serialization\n- `--interactive-recovery --resume-after-recovery` to stop on transport failure, preserve artifacts, and resume only after explicit confirmation\n- `--stop-on-failure` to stop after the first transport failure boundary\n\n### Artifacts\n\nEach run writes a timestamped directory under `artifacts/vivipulse/` containing:\n\n- `trace.jsonl`\n- `run-summary.txt`\n- `failure-boundary.txt`\n- `reuse-map.txt`\n- `firmware-research.txt`\n- `search-summary.txt`\n- `soak-summary.txt`\n\n### Recovery Flow\n\nWhen `--interactive-recovery` is enabled and a target becomes transport-unresponsive, `vivipulse`:\n\n- stops further same-host traffic immediately\n- flushes the trace before prompting\n- prints last-success and first-failure context\n- asks for only the minimum recovery action implied by the failure class\n- resumes only when `--resume-after-recovery` is also set and the operator types `resume`\n\n### Prerequisites\n\n- Python 3.12+\n- a local ViviPi checkout\n- the `1541ultimate` source tree when you want firmware-guided `search` mode\n\n### Intentionally Unsupported\n\n- running the full Pico firmware shell on Linux\n- host-side display parity testing\n- button or Wi-Fi bootstrap behavior\n- automatic physical recovery without operator confirmation\n\n## Configuration\n\n[config/build-deploy.yaml](config/build-deploy.yaml) is the build-time source of truth for:\n\n- device metadata and default board wiring\n- display selection and layout behavior\n- Wi-Fi credentials\n- service endpoint defaults\n- the path to the checks config\n\nEnvironment variables are injected into `build-deploy.yaml` placeholders:\n\n```yaml\nwifi:\n  ssid: ${VIVIPI_WIFI_SSID}\n  password: ${VIVIPI_WIFI_PASSWORD}\n```\n\n### Environment Variables\n\n| Variable | Required | Used by | Notes |\n| --- | --- | --- | --- |\n| `VIVIPI_WIFI_SSID` | Yes | `wifi.ssid` | Required to build device config |\n| `VIVIPI_WIFI_PASSWORD` | Yes | `wifi.password` | Required to build device config |\n| `VIVIPI_SERVICE_BASE_URL` | No | `service.base_url`, sample `SERVICE` checks | Must be reachable from the Pico over Wi-Fi, for example `http://192.168.1.10:8080/checks` |\n\nIf `VIVIPI_SERVICE_BASE_URL` is omitted, build-time filtering drops `SERVICE` checks and keeps the direct checks defined in [config/checks.yaml](config/checks.yaml).\n\n### build-deploy.yaml Reference\n\n| Key | Values | Default | Notes |\n| --- | --- | --- | --- |\n| `project.name` | string | `vivipi` | Project name stored in the rendered runtime config |\n| `device.board` | string | `pico2w` | Board identifier used for packaging and install metadata |\n| `device.micropython_port` | `auto` or path-like string | `auto` | Default device selector for `./build deploy`; `auto` picks the first connected Pico |\n| `device.micropython.version` | string | `1.25.0` | Pinned MicroPython version reference |\n| `device.micropython.download_page` | absolute URL | Pico 2W download page | Included in the install manifest |\n| `device.buttons.a` | GPIO pin name | `GP15` | Left button pin |\n| `device.buttons.b` | GPIO pin name | `GP17` | Right button pin |\n| `device.display.type` | see display matrix below | `waveshare-pico-oled-1.3` | Selects the backend and infers controller, SPI mode, geometry, default pins, and default page interval |\n| `device.display.mode` | `standard`, `compact` | `standard` | Overview layout mode |\n| `device.display.columns` | integer `1` to `4` | `1` | Number of overview columns; values above `1` require `device.display.mode: compact` |\n| `device.display.column_separator` | exactly one character | space | Inserted only between overview columns |\n| `device.display.font` | `extrasmall`, `small`, `medium`, `large`, `extralarge` | `medium` | Resolves the character cell size from the selected display geometry |\n| `device.display.font.width_px` | integer `6` to `32` | inferred | Optional backward-compatible override |\n| `device.display.font.height_px` | integer `6` to `32` | inferred | Optional backward-compatible override |\n| `device.display.page_interval` | integer seconds or `Ns` | inferred by display | Use `0s` to disable automatic page cycling |\n| `device.display.column_offset` | non-negative integer | inferred by display | Advanced override for controller-native visible window alignment; the Waveshare Pico OLED 1.3 infers `32` |\n| `device.display.failure_color` | color name string | `red` | Used for failed-check accent rendering on color-capable displays |\n| `device.display.brightness` | `low`, `medium`, `high`, `max`, or `0` to `255` | `medium` on OLED/LCD | Unsupported on e-paper display types |\n| `wifi.ssid` | placeholder or string | none | Normally `${VIVIPI_WIFI_SSID}` |\n| `wifi.password` | placeholder or string | none | Normally `${VIVIPI_WIFI_PASSWORD}` |\n| `service.base_url` | absolute `http` or `https` URL | omitted | Required only when using `SERVICE` checks |\n| `service.default_prefix` | string | `adb` | Default prefix for service-discovered checks |\n| `checks_config` | relative path | `checks.yaml` | Path to the checks definition file |\n\nVisible rows and columns are derived automatically from the selected display geometry and the resolved font size.\nWhen configured checks exceed the visible rows, the overview cycles across pages using `device.display.page_interval`.\n\n`device.display.page_interval` defaults by display family:\n\n| Display family | Default page interval |\n| --- | --- |\n| OLED and LCD | `20s` |\n| 2.13 inch e-paper | `180s` |\n| 2.7 to 2.9 inch e-paper | `240s` |\n| 3.7 to 4.2 inch e-paper | `300s` |\n| 7.5 inch e-paper | `600s` |\n\nExample:\n\n```yaml\ndevice:\n  display:\n    type: waveshare-pico-lcd-1.3\n    mode: compact\n    columns: 2\n    font: medium\n    page_interval: 20s\n```\n\n### Supported Display Types\n\nPublished specs below are based on current The Pi Hut Waveshare product listings. The Waveshare column links directly to the corresponding developer wiki/manual page used for specs and code samples. Some retailer listings use portrait or raw-panel orientation, and some group multiple Waveshare hardware revisions under one product listing.\n\n| `device.display.type` | The Pi Hut | Waveshare | Published display spec | Notes |\n| --- | --- | --- | --- | --- |\n| `waveshare-pico-oled-1.3` | [Listing](https://thepihut.com/products/1-3-oled-display-module-for-raspberry-pi-pico-64x128) | [Developer page](https://www.waveshare.com/wiki/Pico-OLED-1.3) | `1.3\"`, `64×128`, OLED, `SH1107`, SPI/I2C | The Pi Hut publishes this module as `64×128`; ViviPi renders it as `128×64` landscape and uses the validated SH1107 native column offset `32` |\n| `waveshare-pico-oled-2.23` | [Listing](https://thepihut.com/products/2-23-oled-display-module-for-raspberry-pi-pico) | [Developer page](https://www.waveshare.com/wiki/Pico-OLED-2.23) | `2.23\"`, `128×32`, OLED, `SSD1305`, SPI/I2C | Matches the current Pi Hut listing |\n| `waveshare-pico-lcd-0.96` | [Listing](https://thepihut.com/products/0-96-lcd-display-module-for-raspberry-pi-pico-160x80) | [Developer page](https://www.waveshare.com/wiki/Pico-LCD-0.96) | `0.96\"`, `160×80`, LCD | Matches the current Pi Hut listing |\n| `waveshare-pico-lcd-1.14` | [Listing](https://thepihut.com/products/1-14-ips-lcd-display-module-240x135) | [Developer page](https://www.waveshare.com/wiki/Pico-LCD-1.14) | `1.14\"`, `240×135`, IPS LCD | Pi Hut does not split separate Pico `1.14` and `1.14-v2` retail listings; Waveshare uses a shared `Pico-LCD-1.14` page |\n| `waveshare-pico-lcd-1.14-v2` | [Listing](https://thepihut.com/products/1-14-ips-lcd-display-module-240x135) | [Developer page](https://www.waveshare.com/wiki/Pico-LCD-1.14) | `1.14\"`, `240×135`, IPS LCD | Pi Hut does not split separate Pico `1.14` and `1.14-v2` retail listings; Waveshare uses a shared `Pico-LCD-1.14` page |\n| `waveshare-pico-lcd-1.3` | [Listing](https://thepihut.com/products/1-3-ips-lcd-display-module-for-raspberry-pi-pico-240x240) | [Developer page](https://www.waveshare.com/wiki/Pico-LCD-1.3) | `1.3\"`, `240×240`, IPS LCD | Matches the current Pi Hut listing |\n| `waveshare-pico-lcd-1.44` | [Listing](https://thepihut.com/products/1-44-lcd-display-module-for-raspberry-pi-pico-65k-colors-128x128) | [Developer page](https://www.waveshare.com/wiki/Pico-LCD-1.44) | `1.44\"`, `128×128`, LCD | Matches the current Pi Hut listing |\n| `waveshare-pico-lcd-1.8` | [Listing](https://thepihut.com/products/1-8-lcd-display-for-raspberry-pi-pico) | [Developer page](https://www.waveshare.com/wiki/Pico-LCD-1.8) | `1.8\"`, `160×128`, LCD, `ST7735S`, SPI | The Pi Hut's Features section says `160×128`; its Specifications block says `160x129`, which appears to be a store typo |\n| `waveshare-pico-lcd-2.0` | [Listing](https://thepihut.com/products/2-ips-lcd-display-for-raspberry-pi-pico) | [Developer page](https://www.waveshare.com/wiki/Pico-LCD-2) | `2.0\"`, `320×240`, IPS LCD | Matches the current Pi Hut listing |\n| `waveshare-pico-epaper-2.13-v2` | [Listing](https://thepihut.com/products/2-13-black-white-e-ink-e-paper-display-module-for-raspberry-pi-pico-250x122) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-2.13) | `2.13\"`, black/white, `250×122` | Pi Hut groups the black/white 2.13-inch Pico module without separating `V2`/`V3`/`V4`; Waveshare publishes one shared `Pico-ePaper-2.13` page |\n| `waveshare-pico-epaper-2.13-v3` | [Listing](https://thepihut.com/products/2-13-black-white-e-ink-e-paper-display-module-for-raspberry-pi-pico-250x122) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-2.13) | `2.13\"`, black/white, `250×122` | Pi Hut groups the black/white 2.13-inch Pico module without separating `V2`/`V3`/`V4`; Waveshare publishes one shared `Pico-ePaper-2.13` page |\n| `waveshare-pico-epaper-2.13-v4` | [Listing](https://thepihut.com/products/2-13-black-white-e-ink-e-paper-display-module-for-raspberry-pi-pico-250x122) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-2.13) | `2.13\"`, black/white, `250×122` | Pi Hut groups the black/white 2.13-inch Pico module without separating `V2`/`V3`/`V4`; Waveshare publishes one shared `Pico-ePaper-2.13` page |\n| `waveshare-pico-epaper-2.13-b-v4` | [Listing](https://thepihut.com/products/2-13-red-black-white-e-ink-e-paper-display-module-for-raspberry-pi-pico-212x104) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-2.13-B) | `2.13\"`, red/black/white, `212×104` | The current Pi Hut Pico tri-color 2.13-inch listing is `212×104`, not `250×122` |\n| `waveshare-pico-epaper-2.7` | [Listing](https://thepihut.com/products/2-7-e-paper-display-module-for-raspberry-pi-pico-264x176) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-2.7) | `2.7\"`, black/white, `264×176` | Pi Hut does not separate `2.7` and `2.7-v2` retail listings; Waveshare uses a shared `Pico-ePaper-2.7` page |\n| `waveshare-pico-epaper-2.7-v2` | [Listing](https://thepihut.com/products/2-7-e-paper-display-module-for-raspberry-pi-pico-264x176) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-2.7) | `2.7\"`, black/white, `264×176` | Pi Hut does not separate `2.7` and `2.7-v2` retail listings; Waveshare uses a shared `Pico-ePaper-2.7` page |\n| `waveshare-pico-epaper-2.9` | [Listing](https://thepihut.com/products/2-9-black-white-e-ink-e-paper-display-module-for-raspberry-pi-pico-296x128) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-2.9) | `2.9\"`, black/white, `296×128` | Matches the current Pi Hut listing |\n| `waveshare-pico-epaper-3.7` | [Listing](https://thepihut.com/products/3-7-e-paper-e-ink-display-for-raspberry-pi-pico-480x280) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-3.7) | `3.7\"`, black/white, `480×280` | Matches the current Pi Hut listing |\n| `waveshare-pico-epaper-4.2` | [Listing](https://thepihut.com/products/4-2-e-paper-display-module-for-raspberry-pi-pico-black-white-400x300) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-4.2) | `4.2\"`, black/white, `400×300` | Pi Hut does not separate `4.2` and `4.2-v2` black/white retail listings; Waveshare uses a shared `Pico-ePaper-4.2` page |\n| `waveshare-pico-epaper-4.2-v2` | [Listing](https://thepihut.com/products/4-2-e-paper-display-module-for-raspberry-pi-pico-black-white-400x300) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-4.2) | `4.2\"`, black/white, `400×300` | Pi Hut does not separate `4.2` and `4.2-v2` black/white retail listings; Waveshare uses a shared `Pico-ePaper-4.2` page |\n| `waveshare-pico-epaper-7.5-b-v2` | [Listing](https://thepihut.com/products/7-5-e-paper-display-module-for-raspberry-pi-pico-800x480-red-black-white) | [Developer page](https://www.waveshare.com/wiki/Pico-ePaper-7.5-B) | `7.5\"`, red/black/white, `800×480` | Pi Hut does not label the retail listing as `V2`; Waveshare documents this family on `Pico-ePaper-7.5-B` |\n\n### Checks\n\n[config/checks.yaml](config/checks.yaml) defines build-time checks.\n\n| Field | Required | Notes |\n| --- | --- | --- |\n| `name` | Yes | Label shown on the display |\n| `type` | Yes | `ping`, `ident`, `dma`, `telnet`, `ftp`, `http`, or `service` |\n| `target` | Yes | Host, URL, or service endpoint target |\n| `interval_s` | Yes | Check cadence in seconds |\n| `timeout_s` | Yes | Per-check timeout in seconds |\n| `method` | HTTP only | Request method, for example `GET` |\n| `username` | Optional | Used by FTP and TELNET checks when needed |\n| `password` | Optional | Used by FTP and TELNET checks when needed |\n| `prefix` | `service` only | Prefix applied to service-discovered checks |\n\n## Testing, Releases, and Architecture Notes\n\n- Unified entrypoint: `./build`\n- Test framework: `pytest`\n- Coverage requirement: `\u003e= 96` branch coverage\n- Linting: `ruff`\n- CI runs on Python 3.12 and 3.13\n- CI verifies runtime-config rendering, packaging, and the firmware adapter path through `./build ci`\n\nThe firmware adapters and runtime loop are exercised on CPython, so the same modules used on the board stay covered in the normal development workflow. `./build` and `./build ci` validate the core, runtime, tooling, and firmware adapters together.\n\nThe display backend boundary lives under `firmware/displays/`, while rendering intent stays in `src/vivipi/core/`. New panel support should be added by registering a display type and backend rather than branching through the core renderer.\n\nTagging with an `x.y.z` version publishes the same versioned device, service, and source assets listed in [Install From GitHub Releases](#install-from-github-releases). GitHub's built-in source archive links still appear automatically, but the explicit versioned release assets are the supported downloads.\n\n## Repository Layout\n\n```text\nconfig/                  Build-time configuration\ndocs/                    Specification, traceability, and audits\nfirmware/                MicroPython entrypoints\nfirmware/displays/       Display backend registry and hardware drivers\nscripts/                 Public host-side shell entrypoints\nsrc/vivipi/core/         Pure application logic and rendering model\nsrc/vivipi/services/     Host-side services\nsrc/vivipi/tooling/      Build, deploy, and host-side CLI logic\ntests/                   All test suites\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisgleissner%2Fvivipi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchrisgleissner%2Fvivipi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisgleissner%2Fvivipi/lists"}