Pinscope on Zephyr

Remote access, logs, metrics, crash reports and firmware updates for any Zephyr board with a network: WiFi, Ethernet or cellular. Two lines in prj.conf, no code.

With an AI assistant. Copy the instructions and paste them into Claude, Cursor, Copilot or ChatGPT in your project: it adds Pinscope the way this page says, and asks you for the project key instead of writing it into your code. Or give it the link: zephyr.md.

Try the sample first

A workspace with Zephyr 4.5 and Pinscope, in one command, then the sample on your board. You need west and the Zephyr SDK, and the project key from Connect a device:

west init -m https://github.com/pinscope-dev/pinscope-zephyr pinscope-ws
cd pinscope-ws && west update
west packages pip --install
export PINSCOPE_KEY=psk_...
west build -b nucleo_f429zi --sysbuild modules/lib/pinscope/sample
west flash

On the nRF7002 DK, fetch the WiFi firmware once (west blobs fetch nrf_wifi) and give the network: WIFI_SSID=... WIFI_PASSWORD=... west build -b nrf7002dk/nrf5340/cpuapp --sysbuild modules/lib/pinscope/sample. The board appears under Devices; open /crash on its page to see a fatal error reported after the reboot.

1. Connect

Add the module

In your west.yml, under projects, then run west update:

- name: pinscope
  url: https://github.com/pinscope-dev/pinscope-zephyr
  path: modules/lib/pinscope

The source is yours to read and to change. Its licence allows use with the Pinscope service only.

Enter your project key

In the dashboard, open your project and the tab Connect a device and copy the project key. Then, in prj.conf:

CONFIG_PINSCOPE=y
CONFIG_PINSCOPE_PROJECT_KEY="psk_..."

That is all. Pinscope switches on what it uses from Zephyr (IPv4, TCP, sockets, DNS, DHCP, settings on the flash storage partition, the cryptographic random generator) and starts by itself once the system is up. Anything it switches on you can still set differently in prj.conf. If the interface has no address yet, it starts DHCP; a static address is left alone.

Your application brings the network up as it always does: on a WiFi board, connect to the access point; on Ethernet, plug in the cable. The device needs to reach connect.pinscope.dev on TCP port 443, outbound only. Build, flash, and the device appears under Devices.

To start it yourself, with settings made in code, set CONFIG_PINSCOPE_AUTOSTART=n and call pinscope_start():

#include "pinscope.h"

pinscope_config_t cfg = { .ports = { 80, 8080 } };
pinscope_start(&cfg);

A storage partition: Pinscope keeps the device's credential in settings, on the flash partition labelled storage_partition. Some boards, the ST Nucleo boards among them, define none; the build then stops and shows the overlay to add in boards/<board>.overlay, in flash no image slot uses. The sample's boards/nucleo_f429zi.overlay is one.

RP2040 boards (Raspberry Pi Pico W, WIZnet W55RP20 and others) have no random generator Zephyr knows. Build with -S pinscope-rp2040; the build tells you so if you forget.

W55RP20 with MCUboot: the board w55rp20_pinscope has its random generator built in, and the build writes build/pinscope.uf2, MCUboot and the signed application in one file. Hold BOOTSEL while plugging the board in, then picotool load -v -x build/pinscope.uf2. Copying the file to the RPI-RP2 drive can leave it written incompletely on macOS, and MCUboot then refuses the image; picotool checks what it wrote.

2. Reach the device

Whatever listens on port 80 of the device is served at the device's own HTTPS address, exactly as on ESP32: pages, JSON APIs and WebSockets, share links, public pages and your own address. See Reach the device. To expose another port, set CONFIG_PINSCOPE_LOCAL_PORT, or list up to four in pinscope_config_t.ports.

3. Logs and metrics

Whatever your application and Zephyr log with LOG_INF, LOG_WRN and LOG_ERR reaches the Logs tab, as ESP_LOG does on ESP32. With CONFIG_LOG_PRINTK=y, printk too. This needs CONFIG_LOG=y in deferred mode, Zephyr's default.

Reported with no code, once a minute:

MetricMeaning
heap_free, heap_minFree heap of the system allocator now, and the lowest it has been
stack/pinscopeStack the Pinscope thread has never used
rssiWiFi signal, on WiFi boards
uptime_sTime since boot

Send your own from any thread:

pinscope_metric("water_level_cm", 42.5);

4. Crashes

When the firmware hits a fatal error, the reason, the thread, the program counter and the code addresses found on the stack are kept in RAM across the reboot and sent on the next connection. They appear in the Crashes tab.

To read function names and line numbers, upload build/zephyr/zephyr.elf of that build (with sysbuild, build/<app>/zephyr/zephyr.elf) under the project's Firmware tab. Builds are matched by their GNU build ID, which Pinscope adds to the image. Pinscope provides k_sys_fatal_error_handler(); if your application defines its own, set CONFIG_PINSCOPE_CRASH_CAPTURE=n.

Boards

Any board Zephyr gives a network to works the same way. The sample is set up for these, and built for each of them on every change. Run on hardware, with updates, rollback and crash reports: the W55RP20 (Ethernet) and the ESP32-C6 (WiFi).

Board-bNetworkBefore building
ST NUCLEO-F429ZInucleo_f429ziEthernet
ST NUCLEO-H563ZInucleo_h563ziEthernet
ST B-L475E-IOT01Adisco_l475_iot1WiFiWiFi
Nordic nRF7002 DKnrf7002dk/nrf5340/cpuappWiFiwest blobs fetch nrf_wifi, WiFi
Nordic nRF5340 DK + W5500nrf5340dk/nrf5340/cpuappEthernet-- -Dsample_SHIELD=wiznet_w5500
Nordic nRF9151 DKnrf9151dk/nrf9151/nsLTE-M, NB-IoTnRF Connect SDK, below
Espressif ESP32-S3-DevKitCesp32s3_devkitc/esp32s3/procpuWiFiwest blobs fetch hal_espressif, WiFi
Espressif ESP32-C3-DevKitMesp32c3_devkitmWiFiwest blobs fetch hal_espressif, WiFi
Espressif ESP32-C6-DevKitCesp32c6_devkitc/esp32c6/hpcoreWiFiwest blobs fetch hal_espressif, WiFi
NXP FRDM-RW612frdm_rw612Ethernet
Raspberry Pi Pico 2 Wrpi_pico2/rp2350a/m33_0/w/mcubootWiFiwest blobs fetch hal_infineon, WiFi
WIZnet W55RP20-EVB-Picow55rp20_pinscopeEthernetload build/pinscope.uf2 with picotool load -v -x
QEMU (Arm MPS2)mps2/an385Ethernetno --sysbuild

WiFi boards take the network from the environment when the build directory is created: WIFI_SSID=... WIFI_PASSWORD=... west build ....

No board? Build for mps2/an385 and run it in QEMU from your system (brew install qemu, apt install qemu-system-arm): qemu-system-arm -M mps2-an385 -nographic -nic user,model=lan9118 -kernel build/zephyr/zephyr.bin. The emulated board has no random generator, so that build uses a predictable one: for trying, never for a device.

nRF91 (LTE). The modem library is part of Nordic's nRF Connect SDK. A workspace with nRF Connect SDK 3.4.2: west init -m https://github.com/pinscope-dev/pinscope-zephyr --mf west-ncs.yml pinscope-ncs, then west update and pip install -r nrf/scripts/requirements-build.txt. LTE comes up at boot; the board needs a SIM with an LTE-M or NB-IoT plan.

Modems that run the IP stack themselves (the nRF91, the B-L475E-IOT01A's WiFi module): logs, metrics, crash reports and updates work as on any board, but the device's own web page cannot be reached through Pinscope, which needs Zephyr's native IP stack.

5. Firmware updates

Updates use MCUboot, Zephyr's own bootloader. Build with sysbuild and MCUboot:

# sysbuild.conf
SB_CONFIG_BOOTLOADER_MCUBOOT=y
SB_CONFIG_BOOT_SIGNATURE_TYPE_ECDSA_P256=y

ECDSA P-256 keeps MCUboot inside the 32 KB many boards give it; with the default RSA it does not fit on the Nucleo boards.

west build -b <board> --sysbuild

The board needs MCUboot partitions (boot_partition, slot0_partition, slot1_partition); most boards with MCUboot support define them. Then upload build/<app>/zephyr/zephyr.signed.bin under Firmware and press Update firmware on a device. The image travels over the existing connection into the second slot, and MCUboot boots it on trial.

The new image is confirmed when it reaches Pinscope. If it has not after five minutes (CONFIG_PINSCOPE_OTA_TRIAL_S), the device restarts and MCUboot goes back to the previous one. The version shown is the one in your application's VERSION file; raise it with each build. Images for other platforms are refused.

Sign with your own key in production: SB_CONFIG_BOOT_SIGNATURE_KEY_FILE. The key Zephyr uses by default is public.

Memory

About 8.6 KB of RAM: the same 4.2 KB of frame buffers, queue and state as on ESP32, the Pinscope thread's stack (4 KB by default, CONFIG_PINSCOPE_STACK_SIZE; the stack/pinscope metric shows how much it uses), and the thread itself. No TLS library: the connection uses the Noise protocol (X25519, ChaCha20-Poly1305). The network buffers of the connection come from Zephyr's shared network pool.

Options

Under Pinscope in menuconfig (west build -t menuconfig):

OptionDefaultWhat it does
CONFIG_PINSCOPE_PROJECT_KEYemptyThe project key from the dashboard
CONFIG_PINSCOPE_DEVICE_NAMEemptyThe name shown in the dashboard
CONFIG_PINSCOPE_LOCAL_PORT80The port served on the device's address
CONFIG_PINSCOPE_AUTOSTARTyStart at boot, with no code
CONFIG_PINSCOPE_START_DHCPyStart DHCP when the interface has no address
CONFIG_PINSCOPE_OTAy with MCUbootFirmware updates from the dashboard
CONFIG_PINSCOPE_LOG_BACKENDySend Zephyr's log
CONFIG_PINSCOPE_SYSTEM_METRICSyThe built-in metrics
CONFIG_PINSCOPE_STACK_SIZE4096Stack of the Pinscope thread

The functions are the same as on ESP32: see Functions.

If it does not work

Most problems stop the build with the fix in the message. On the device, Pinscope logs under the module name pinscope; a device that connected says online at https://….

What you seeWhat to check
The build stops: this board has no storage_partitionAdd a small storage partition in a board overlay (two flash sectors are enough), or choose another settings backend
The build stops: The RP2040 has no random generatorBuild with -S pinscope-rp2040
the project key is missingSet CONFIG_PINSCOPE_PROJECT_KEY
No online at line, no errorThe interface has no address yet (WiFi not connected, no DHCP server, no cable), or the network does not let it reach connect.pinscope.dev on TCP port 443
The device is online, its address shows an error pageNothing listens on the exposed port. If the page says the device is out of connections, raise CONFIG_NET_MAX_CONN
There is no Update firmware buttonThe build has no MCUboot, or CONFIG_PINSCOPE_OTA is off
An update goes back to the old firmwareThe new image did not reach Pinscope within five minutes, or it was signed with another key than the one MCUboot has

Still stuck? Write to support@pinscope.dev with the lines the device logs under pinscope.