Skip to content

Repository files navigation

triops

ci license php 8.0+ no composer packages required

triops

A small web server for looking at what your hardware is actually sending.

You have a board on your desk. The firmware posts a sensor reading somewhere. It is not arriving, or it is arriving wrong, and you cannot tell whether the problem is the WiFi, the HTTP client, the JSON encoder, a Content-Type header nobody set, or a payload that got truncated at 512 bytes.

triops is the other end of that connection. Point the board at it and it shows you the bytes it received, the headers that came with them, and when they arrived. Unzip it into a web directory and it runs: no framework, no Composer packages, no database server, no build system, and no third-party service.

If you have somewhere to put a PHP file, you have somewhere to put triops. That includes the shared hosting you already pay for, a Raspberry Pi on the bench, and the NAS in the cupboard.

$ curl -X POST 'http://192.168.1.40/triops/api/ingest.php?channel=lab' \
       -H 'Content-Type: application/json' \
       -d '{"device":"esp32-01","temp_c":22.4}'
{"ok":true,"api":1,"data":{"channel":"lab","bytes":35,"stored":true}}

...and it is on screen, parsed, with the raw bytes one click away.

triops receiving payloads

Where this fits

Somewhere between blinking an LED and having a real backend.

Early on you are not testing your telemetry pipeline, you are testing whether the board can form a correct HTTP request at all. Standing up a real service to find that out is a detour, and pointing the board at a cloud endpoint means the failure is now somewhere in someone else's logs. What you want is a target on your own network that answers immediately and hides nothing.

That is the whole job. triops runs on a Raspberry Pi, a NAS, an old laptop, or whatever shared host you already pay for. It works on a lab network with no internet connection. Nothing leaves your machine.

It is not a production telemetry backend. There is no clustering, no retention policy, no alerting, no TLS termination, no multi-tenancy. It keeps the last few hundred payloads per channel in a ring buffer and drops the rest. When you outgrow it you are supposed to outgrow it — by then you know exactly what your device sends, which was the point.

Install

Four steps, and none of them is "edit a config file".

  1. Download the latest release and unzip it into a web-served directory
  2. Open it in a browser — it asks you to create an account
  3. Point your device at api/ingest.php
  4. Watch it arrive on the View page

Requirements: PHP 8.0 or newer, and a directory the web server can write to. SQLite is used when it is available and a plain append-only text file when it is not, so there is nothing to install either way.

See docs/install.md for nginx, shared hosting, and moving the data directory out of the web root.

No PHP on your machine?

macOS stopped shipping it and Windows never did. There is a Dockerfile for a quick local runner — not a deployment:

docker build -t triops -f docker/Dockerfile .
docker run --rm -p 8080:8080 -v triops-data:/triops/data triops

Open http://localhost:8080/, point a board at http://<your-lan-ip>:8080/api/ingest.php, and when you are done throw the lot away with docker volume rm triops-data — that clears the stored payloads and the account you created in one go. Details in docker/README.md.

What is in it

The debug endpoints return plain text, deliberately. A device with a 2 KB HTTP client should not have to parse JSON to find out whether its clock is right.

Endpoint What it does
timestamp.php Server time to microseconds
sum.php?a=1&b=2 Adds the numbers you pass. Proves your query string survived
ip.php Your address as the server sees it
echo.php Your whole request, played back: method, headers, query, body
headers.php Just the headers — for when a proxy is in the way
delay.php?ms=2000 Answers slowly, so you can test client timeouts
code.php?c=500 Answers with the status you ask for, so you can test error paths
bytes.php?n=4096 Returns exactly N bytes, so you can find your receive buffer limit

The inbox and the JSON API sit alongside them:

Endpoint What it does
api/ingest.php POST anything. The canonical device endpoint
api/read.php Last N entries as JSON
api/version.php Name, version, API integer. No auth — for feature detection
api/status.php Which store is live, is the data directory writable, versions
api/clear.php Empty a channel
view.php The inbox: payloads, headers and timing, newest first, raw always one click away
send.php Post a payload from a form, before the hardware exists
status.php The same diagnosis as api/status.php, rendered

Full reference: docs/endpoints.md and docs/api.md.

Talking to it from a device

// ESP32 / Arduino
HTTPClient http;
http.begin("http://192.168.1.40/triops/api/ingest.php?channel=lab");
http.addHeader("Content-Type", "application/json");
int code = http.POST("{\"temp_c\":22.4}");
Serial.println(http.getString());
# MicroPython
import urequests
urequests.post("http://192.168.1.40/triops/api/ingest.php?channel=lab",
               json={"temp_c": 22.4})

More, including error handling and the plain-text primitives: docs/examples/.

Adding your own page

Copy a template, edit it. A new endpoint is about five lines:

<?php
require __DIR__ . '/lib/triops.php';

t_text("hello\n");

HTML pages get a bitwrench UI, a nav bar, and dark mode without writing any CSS. docs/hacking.md walks through all three templates.

Security

triops is built for a bench, a lab network, or a machine behind a VPN.

Found something exploitable? Do not open a public issue — see SECURITY.md.

It has password-protected accounts, CSRF protection on every form, bcrypt hashing, and it ships with no default credentials. Anything that looks like a credential in a query string or a header is redacted before the request is stored, so a key you paste once to test a board does not become permanent telemetry. But it has no rate limiting, no audit log, and no hardening against a hostile internet. Do not put it on a public address and post real data through it. If you must expose it, set ingest_key in config and put it behind something that terminates TLS.

Built with

The interface is bitwrench — a zero-dependency UI library that builds pages from plain JavaScript objects with no build step, which is why triops has a copy vendored in app/assets/ and still works on a network with no internet. If you like how the UI here goes together, that is where it comes from.

Contributing

Bug reports and small fixes are welcome — see CONTRIBUTING.md. Worth reading first: triops has a deliberate ceiling, and dev/todo.md lists what was cut on purpose so you do not spend an evening on a PR that was never going to land.

License

BSD 2-clause. © 2020–2026 M A Chatterjee.

About

A small PHP server for debugging IoT devices: raw bytes, headers, timing. Unzip and go! Can be used on shared hosts.

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages