See your device health at a glance.
ViviPi (pronounced "VEE-vee-pie", from the Latin viv- in vivere, "to live") is a minimal, glanceable monitoring system for Raspberry Pi Pico display modules. 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.
Note
This project is under active development. Some documented features may not yet be fully functional.
- Supports Pico OLED, LCD, and e-paper modules.
- Supports
PING,HTTP,FTP,TELNET, andSERVICEhealth checks. - Configurable back-off and scheduling policies to avoid overwhelming targets.
- Easy build and deployment via a one-stop shop
buildcommand.
ViviPi runs on the Pico and evaluates checks through two paths: direct network probes (PING, HTTP, FTP, TELNET) and SERVICE probes.
flowchart TB
Pico[Pico runtime]
subgraph Direct[Direct probes from the Pico]
direction LR
Ping[PING]
Http[HTTP]
Ftp[FTP]
Telnet[TELNET]
end
subgraph ServicePath[SERVICE probe path]
direction LR
ServiceProbe[SERVICE]
ServiceAPI[Vivi Service /checks endpoint]
AdbBackend[Default backend: adb service]
Android[Android phone availability]
CustomChecks[Custom checks from any backend]
ServiceProbe -->|GET VIVIPI_SERVICE_BASE_URL| ServiceAPI
ServiceAPI --> AdbBackend
AdbBackend --> Android
ServiceAPI --> CustomChecks
end
Pico --> Ping
Pico --> Http
Pico --> Ftp
Pico --> Telnet
Pico --> ServiceProbe
Direct 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.
| Probe | Performs | Success condition | Failure condition or note |
|---|---|---|---|
PING |
ICMP ping | Response received; latency measured locally | No response or timeout |
HTTP |
HTTP request | Response status is 2xx or 3xx; latency measured locally |
Non-2xx/3xx response or timeout |
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 |
TELNET |
Telnet session with optional credentials | TCP session accepts and optional banner/session output is readable when present | Connection failure, explicit login failure text, or timeout |
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 |
- Board: Raspberry Pi Pico 2W
- Display: 128x64 monochrome OLED
- Display controller: SH1107
- Character grid: 16 columns x 8 rows using 8x8 bitmap cells
- Display interface: 4-wire SPI, mode 3
- Native transport mapping: portrait-native 64x128 SH1107 page stream with inferred column offset 32 for the Waveshare Pico OLED 1.3
| Signal | GPIO |
|---|---|
| DIN | GP11 |
| CLK | GP10 |
| CS | GP9 |
| DC | GP8 |
| RST | GP12 |
| BTN A | GP15 |
| BTN B | GP17 |
This is the shortest useful path from clone to running device.
Requirements:
- Python 3.12+
python3 -m venvadbonly if you want the default service against connected Android devicesmpremoteonly if you want./build deployto copy files onto a Pico 2W
For 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.
- Set Wi-Fi credentials. Add
VIVIPI_SERVICE_BASE_URLonly if you wantSERVICEchecks.
export VIVIPI_WIFI_SSID="your-wifi-name"
export VIVIPI_WIFI_PASSWORD="your-wifi-password"
export VIVIPI_SERVICE_BASE_URL="http://192.168.1.10:8080/checks"- Run the default local workflow.
./build./build with no command is equivalent to ./build ci.
Without VIVIPI_SERVICE_BASE_URL, ViviPi builds only the direct PING, HTTP, FTP, and TELNET checks from config/checks.yaml.
- Start the default Vivi Service only if you want the sample
SERVICEcheck.
./build service --host 0.0.0.0 --port 8080- Run one local pass of all configured health checks from the host.
scripts/vivipulse --mode local- Build and deploy to the Pico when hardware is connected.
./build build-firmware
./build deploy./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.
- Create
config/build-deploy.local.yamlfromconfig/build-deploy.local.example.yamlor export theVIVIPI_WIFI_*environment variables. - Run
./build build-firmware. - In Thonny, connect to
MicroPython (Raspberry Pi Pico). - Open
artifacts/release/vivipi-device-fs/as the local source tree and upload its contents to the device root. - Re-run
./build build-firmwarewhenever config or source changes, then re-upload the updated files.
The generated artifacts/release/vivipi-device-fs/ directory is the exact device filesystem layout expected by the Pico.
The 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.
- Open the repository as a single-folder workspace.
- Install the recommended extensions when prompted.
- Create
config/build-deploy.local.yamlfromconfig/build-deploy.local.example.yamlfor local Wi-Fi and optional service settings. - Run the
ViviPi: Build Firmware Bundletask to regenerateartifacts/release/vivipi-device-fs/. - Run the
ViviPi: Deploy To First Connected Picotask to build and upload to the first connected Pico.
If you have more than one board attached, use ./build deploy --device-port <port> from the integrated terminal.
Use the source checkout when you want the full local workflow:
./build install
./build test
./build build-firmware
./build service --host 0.0.0.0 --port 8080The canonical entrypoint is ./build. Run ./build help for the full CLI surface.
Each GitHub release publishes a small, versioned set of assets. Download the files that match the tag you want to install.
| Asset | Purpose | Contains only what is needed for | How to use it |
|---|---|---|---|
vivipi-device-filesystem-<version>.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 |
pico2w-micropython-<version>.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 |
vivipi-service-bundle-<version>.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 |
vivipi-source-<version>.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 |
vivipi-source-<version>.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 |
- Download
pico2w-micropython-<version>.txtandvivipi-device-filesystem-<version>.zipfrom the release page. - Use the URL in
pico2w-micropython-<version>.txtto install the base MicroPython UF2 on the Pico if the board is blank. - Copy the contents of
vivipi-device-filesystem-<version>.ziponto the Pico withmpremote fs cp, or unzip it locally and use./build deployagainst the unpackedvivipi-device-fs/tree. - Point
VIVIPI_SERVICE_BASE_URLat a reachable host only if you wantSERVICEchecks baked intoconfig.json.
- Download and unzip
vivipi-service-bundle-<version>.zip. - Install the bundled wheel with
python -m pip install vivipi-*.whl. - Start the default service with
vivipi-adb-service --host 0.0.0.0 --port 8080if you want ADB-backed checks. - Or start
custom-service-example.py --host 0.0.0.0 --port 8080and adapt its/checkspayload to expose your own checks. - Set
VIVIPI_SERVICE_BASE_URLin your build configuration tohttp://<host>:8080/checksbefore building the device filesystem.
./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.
| Command | What it does |
|---|---|
./build |
Install dependencies, run Ruff, run pytest, and build firmware assets |
./build install |
Create the local virtual environment and install dev dependencies |
./build lint |
Run Ruff |
./build test |
Run pytest |
./build coverage |
Run pytest with branch coverage output |
./build ci |
Run the full local CI workflow |
./build render-config |
Render artifacts/device/config.json from the build config |
./build build-firmware |
Build the firmware bundle into artifacts/release |
./build release-assets |
Build the versioned GitHub release assets |
./build deploy |
Build the firmware bundle and copy it to the first connected Pico via mpremote |
./build service --host 0.0.0.0 --port 8080 |
Run the default ADB-backed Vivi Service |
scripts/vivipulse --mode local |
Run one local pass of all configured health checks |
scripts/vivipulse --mode plan |
Resolve the host-side probe plan without sending traffic |
Typical examples:
VIVIPI_WIFI_SSID="your-wifi" \
VIVIPI_WIFI_PASSWORD="your-password" \
./build build-firmwareVIVIPI_WIFI_SSID="your-wifi" \
VIVIPI_WIFI_PASSWORD="your-password" \
VIVIPI_SERVICE_BASE_URL="http://192.168.1.10:8080/checks" \
./build build-firmware./build service --host 0.0.0.0 --port 8080Generated artifacts are written under artifacts/.
| Output | Produced by | Purpose |
|---|---|---|
artifacts/device/config.json |
./build render-config |
Rendered runtime config |
artifacts/release/vivipi-device-fs/ |
./build build-firmware |
Unpacked device filesystem tree |
artifacts/release/vivipi-device-filesystem-<version>.zip |
./build build-firmware and ./build release-assets |
Deployable device bundle |
artifacts/release/pico2w-micropython-<version>.txt |
./build build-firmware and ./build release-assets |
Pinned MicroPython download reference |
artifacts/release/vivipi-service-bundle-<version>.zip |
./build release-assets |
Service starter bundle |
artifacts/release/vivipi-source-<version>.zip and artifacts/release/vivipi-source-<version>.tar.gz |
./build release-assets |
Tagged source archives |
The default host-side service discovers connected ADB devices and exposes them as monitoring checks.
./build service --host 0.0.0.0 --port 8080The HTTP endpoint implementation lives in src/vivipi/services/adb_service.py. The sample SERVICE check in config/checks.yaml points at VIVIPI_SERVICE_BASE_URL.
The checked-in local development config in 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.
Install the user-level systemd units once:
./scripts/install_adb_service_user_units.shThat installer:
- enables
vivipi-adb-service.serviceso the HTTP service starts automatically for your user session - enables
vivipi-adb-recover.timersoadb start-serverandadb reconnect offlinerun periodically after boot and resume - starts the service immediately on the current login session
Manual fallback commands:
./scripts/run_adb_service.sh start
./scripts/run_adb_service.sh ensure-adbscripts/vivipulse is the host-side stability, reproduction, mitigation-search, and soak-testing entrypoint for direct ViviPi probes.
It intentionally reuses the Pico's shared lower-level execution seam:
vivipi.runtime.checks.build_runtime_definitions()vivipi.runtime.checks.build_executor()vivipi.core.execution.execute_check()vivipi.core.scheduler.due_checks()vivipi.core.scheduler.probe_host_key()vivipi.core.scheduler.probe_backoff_remaining_s()
It 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.
Use vivipulse when you want to:
- reproduce instability outside the Pico UI/runtime shell
- capture request-level JSONL traces with exact ordering and timing
- identify the last-success and first-failure boundary for a target
- inspect a local
1541ultimatecheckout to guide safer probe profiles - search for a less disruptive same-host execution profile
- soak-test a chosen profile for a fixed wall-clock duration
The 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.
Supported input shapes:
--build-config PATH--runtime-config PATH--checks-config PATH
local runs exactly one host-side pass of all resolved checks and is the simplest way to verify the full local health configuration:
scripts/vivipulse --mode localplan resolves checks, same-host groups, and ordering without sending traffic:
scripts/vivipulse --mode planreproduce runs the shared probes from Linux and writes request-level traces:
scripts/vivipulse --mode reproduce --passes 2 --target http://192.168.1.10/healthsearch inspects the local Ultimate firmware source, then evaluates a small mitigation set in priority order:
scripts/vivipulse \
--mode search \
--passes 2 \
--ultimate-repo ../1541ultimatesoak runs a chosen profile for a wall-clock duration:
scripts/vivipulse --mode soak --duration 2h --same-host-backoff-ms 1000Useful controls:
--check-id IDto restrict execution to one or more checks--target TARGETto restrict execution to an exact target value--same-host-backoff-ms Nto override the configured same-host gap--allow-concurrent-same-hostto disable same-host serialization--interactive-recovery --resume-after-recoveryto stop on transport failure, preserve artifacts, and resume only after explicit confirmation--stop-on-failureto stop after the first transport failure boundary
Each run writes a timestamped directory under artifacts/vivipulse/ containing:
trace.jsonlrun-summary.txtfailure-boundary.txtreuse-map.txtfirmware-research.txtsearch-summary.txtsoak-summary.txt
When --interactive-recovery is enabled and a target becomes transport-unresponsive, vivipulse:
- stops further same-host traffic immediately
- flushes the trace before prompting
- prints last-success and first-failure context
- asks for only the minimum recovery action implied by the failure class
- resumes only when
--resume-after-recoveryis also set and the operator typesresume
- Python 3.12+
- a local ViviPi checkout
- the
1541ultimatesource tree when you want firmware-guidedsearchmode
- running the full Pico firmware shell on Linux
- host-side display parity testing
- button or Wi-Fi bootstrap behavior
- automatic physical recovery without operator confirmation
config/build-deploy.yaml is the build-time source of truth for:
- device metadata and default board wiring
- display selection and layout behavior
- Wi-Fi credentials
- service endpoint defaults
- the path to the checks config
Environment variables are injected into build-deploy.yaml placeholders:
wifi:
ssid: ${VIVIPI_WIFI_SSID}
password: ${VIVIPI_WIFI_PASSWORD}| Variable | Required | Used by | Notes |
|---|---|---|---|
VIVIPI_WIFI_SSID |
Yes | wifi.ssid |
Required to build device config |
VIVIPI_WIFI_PASSWORD |
Yes | wifi.password |
Required to build device config |
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 |
If VIVIPI_SERVICE_BASE_URL is omitted, build-time filtering drops SERVICE checks and keeps the direct checks defined in config/checks.yaml.
| Key | Values | Default | Notes |
|---|---|---|---|
project.name |
string | vivipi |
Project name stored in the rendered runtime config |
device.board |
string | pico2w |
Board identifier used for packaging and install metadata |
device.micropython_port |
auto or path-like string |
auto |
Default device selector for ./build deploy; auto picks the first connected Pico |
device.micropython.version |
string | 1.25.0 |
Pinned MicroPython version reference |
device.micropython.download_page |
absolute URL | Pico 2W download page | Included in the install manifest |
device.buttons.a |
GPIO pin name | GP15 |
Left button pin |
device.buttons.b |
GPIO pin name | GP17 |
Right button pin |
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 |
device.display.mode |
standard, compact |
standard |
Overview layout mode |
device.display.columns |
integer 1 to 4 |
1 |
Number of overview columns; values above 1 require device.display.mode: compact |
device.display.column_separator |
exactly one character | space | Inserted only between overview columns |
device.display.font |
extrasmall, small, medium, large, extralarge |
medium |
Resolves the character cell size from the selected display geometry |
device.display.font.width_px |
integer 6 to 32 |
inferred | Optional backward-compatible override |
device.display.font.height_px |
integer 6 to 32 |
inferred | Optional backward-compatible override |
device.display.page_interval |
integer seconds or Ns |
inferred by display | Use 0s to disable automatic page cycling |
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 |
device.display.failure_color |
color name string | red |
Used for failed-check accent rendering on color-capable displays |
device.display.brightness |
low, medium, high, max, or 0 to 255 |
medium on OLED/LCD |
Unsupported on e-paper display types |
wifi.ssid |
placeholder or string | none | Normally ${VIVIPI_WIFI_SSID} |
wifi.password |
placeholder or string | none | Normally ${VIVIPI_WIFI_PASSWORD} |
service.base_url |
absolute http or https URL |
omitted | Required only when using SERVICE checks |
service.default_prefix |
string | adb |
Default prefix for service-discovered checks |
checks_config |
relative path | checks.yaml |
Path to the checks definition file |
Visible rows and columns are derived automatically from the selected display geometry and the resolved font size.
device.display.page_interval defaults by display family:
| Display family | Default page interval |
|---|---|
| OLED and LCD | 15s |
| 2.13 inch e-paper | 180s |
| 2.7 to 2.9 inch e-paper | 240s |
| 3.7 to 4.2 inch e-paper | 300s |
| 7.5 inch e-paper | 600s |
Example:
device:
display:
type: waveshare-pico-lcd-1.3
mode: compact
columns: 2
font: medium
page_interval: 15sPublished 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.
device.display.type |
The Pi Hut | Waveshare | Published display spec | Notes |
|---|---|---|---|---|
waveshare-pico-oled-1.3 |
Listing | Developer page | 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 |
waveshare-pico-oled-2.23 |
Listing | Developer page | 2.23", 128×32, OLED, SSD1305, SPI/I2C |
Matches the current Pi Hut listing |
waveshare-pico-lcd-0.96 |
Listing | Developer page | 0.96", 160×80, LCD |
Matches the current Pi Hut listing |
waveshare-pico-lcd-1.14 |
Listing | Developer page | 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 |
waveshare-pico-lcd-1.14-v2 |
Listing | Developer page | 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 |
waveshare-pico-lcd-1.3 |
Listing | Developer page | 1.3", 240×240, IPS LCD |
Matches the current Pi Hut listing |
waveshare-pico-lcd-1.44 |
Listing | Developer page | 1.44", 128×128, LCD |
Matches the current Pi Hut listing |
waveshare-pico-lcd-1.8 |
Listing | Developer page | 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 |
waveshare-pico-lcd-2.0 |
Listing | Developer page | 2.0", 320×240, IPS LCD |
Matches the current Pi Hut listing |
waveshare-pico-epaper-2.13-v2 |
Listing | Developer page | 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 |
waveshare-pico-epaper-2.13-v3 |
Listing | Developer page | 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 |
waveshare-pico-epaper-2.13-v4 |
Listing | Developer page | 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 |
waveshare-pico-epaper-2.13-b-v4 |
Listing | Developer page | 2.13", red/black/white, 212×104 |
The current Pi Hut Pico tri-color 2.13-inch listing is 212×104, not 250×122 |
waveshare-pico-epaper-2.7 |
Listing | Developer page | 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 |
waveshare-pico-epaper-2.7-v2 |
Listing | Developer page | 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 |
waveshare-pico-epaper-2.9 |
Listing | Developer page | 2.9", black/white, 296×128 |
Matches the current Pi Hut listing |
waveshare-pico-epaper-3.7 |
Listing | Developer page | 3.7", black/white, 480×280 |
Matches the current Pi Hut listing |
waveshare-pico-epaper-4.2 |
Listing | Developer page | 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 |
waveshare-pico-epaper-4.2-v2 |
Listing | Developer page | 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 |
waveshare-pico-epaper-7.5-b-v2 |
Listing | Developer page | 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 |
config/checks.yaml defines build-time checks.
| Field | Required | Notes |
|---|---|---|
name |
Yes | Label shown on the display |
type |
Yes | ping, http, telnet, ftp, or service |
target |
Yes | Host, URL, or service endpoint target |
interval_s |
Yes | Check cadence in seconds |
timeout_s |
Yes | Per-check timeout in seconds |
method |
HTTP only | Request method, for example GET |
username |
Optional | Used by FTP and TELNET checks when needed |
password |
Optional | Used by FTP and TELNET checks when needed |
prefix |
service only |
Prefix applied to service-discovered checks |
- Unified entrypoint:
./build - Test framework:
pytest - Coverage requirement:
>= 96branch coverage - Linting:
ruff - CI runs on Python 3.12 and 3.13
- CI verifies runtime-config rendering, packaging, and the firmware adapter path through
./build ci
The 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.
The 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.
Tagging with an x.y.z version publishes the same versioned device, service, and source assets listed in Install From GitHub Releases. GitHub's built-in source archive links still appear automatically, but the explicit versioned release assets are the supported downloads.
config/ Build-time configuration
docs/ Specification, traceability, and audits
firmware/ MicroPython entrypoints
firmware/displays/ Display backend registry and hardware drivers
scripts/ Public host-side shell entrypoints
src/vivipi/core/ Pure application logic and rendering model
src/vivipi/services/ Host-side services
src/vivipi/tooling/ Build, deploy, and host-side CLI logic
tests/ All test suites


