PhloemDocs

Send your own data

Send readings from your own script, rig or service, and they become live channels on this device.

The endpoint

http://127.0.0.1:8078/ingest/<codec>

<codec> is the body format you send — batch, jsonl, mcap and the others below.

DetailValue
Port8078 on a default install
Reachthis device, while the Phloem app runs
Authnone — only this device can reach the address
Limit64 MiB per request

To send from another machine, forward the port over SSH or your VPN.

Before you send, add a Push endpoint input on Inputs (how). Set its Codec to batch for the examples below, or * to accept every codec. Without one, a push gets 403.

If you send first, the input appears on Inputs. Enable accepts it.

Send

Language
import json, time, urllib.request

URL = "http://127.0.0.1:8078/ingest/batch"

for i in range(20):
    body = [{"path": "/demo/temp_c",
             "samples": [{"t_us": time.time_ns() // 1000,
                          "value": {"f64": 21.5 + i}}]}]
    req = urllib.request.Request(
        URL, data=json.dumps(body).encode(),
        headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req) as r:
        print(r.read().decode())
    time.sleep(0.5)

Each POST replies with what it took: {"codec":"batch","batches":1,"samples":1}. A refused list names any path another input already owns.

On Live, a new channel gets its own tile while there are only a few channels. After that, find it in the list on the left and drag it in. On Inputs, the row shows a rate and a last-seen time while you send.

Body formats

Each Codec takes one body shape.

Codec

An array with one entry per channel. The only codec that can mark a channel as a picture or a mask.

[{"path": "/usv/heading_deg",
  "samples": [{"t_us": 1785500000000000, "value": {"f64": 91.4}}]},
 {"path": "/usv/estop_ok",
  "samples": [{"t_us": 1785500000000000, "value": {"bool": true}}]}]

t_us is required: microseconds since 1970. value holds exactly one of {"f64": 21.5} · {"i64": 3} · {"bool": true} · {"text": "drive"} · {"bytes": [255, 216]} · {"null": null}. Use null for a reading that happened but had no valid value.

For bytes, add archetype to say what they are. Phloem cannot tell a picture from its bytes:

[{"path": "/cam/front", "archetype": "image",
  "samples": [{"t_us": 1785500000000000,
               "value": {"bytes": [255, 216, 255, 224]}}]}]

Use image for a camera frame, or segmentation_image for a mask over one. Both get a camera tile. You cannot send waveforms or 3D this way (Name channels and units). Bytes without archetype still record and replay, but nothing draws them.

If your program falls behind and skips samples, say so in the next body. Add a gap item beside the batches:

[{"gap": {"paths": ["/demo/temp_c"], "frontier_us": 1785500000000000,
          "detail": "loop stalled 1.2 s and skipped forward"}},
 {"path": "/demo/temp_c",
  "samples": [{"t_us": 1785500001200000, "value": {"f64": 21.5}}]}]

frontier_us is the time of the last sample you sent. Add "at_least_samples": n if you know how many you skipped; otherwise Phloem estimates it from the channel's rate. Replay shows a stated gap as missing samples with your reason, and an unstated one as unexplained.

Responses

StatusMeansDo
200acceptednothing
400unknown codec, or a body it can't readread error
403no input open for this codecopen one, above
409every channel is owned by another inputsend on different paths
503not storedretry the same body
no replynothing is listeningis the app running? see Reach

A 403 explains itself:

{"error": "no enabled input for push codec 'batch' — enable it in Inputs (it appears there as a discovered candidate)",
 "candidate": true}

Plain TCP. Port 8079 takes the same text codecs: the codec name on the first line, then one record per line.

  • It replies with nothing, and an unknown codec closes the connection. Get it working over HTTP first.
  • When you stop, send a last line that is exactly end.
  • If the connection closes without end, Phloem assumes your program died or lost its link. Every channel it sent gets a gap (producer_cut) until it reconnects.

From a program: the SDK

The Phloem SDK (Python, C, Go, Rust) sends to this device over a local connection instead of HTTP (a socket on macOS and Linux, a named pipe on Windows). It finds it with no setup, says which clock each frame's times come from, and reports gaps. First add an input for codec phlm on Inputs.

import phloem_sdk

with phloem_sdk.Client(producer="bench-pc", clock_id="usb:0x0683-SN-01A2B3C4") as c:
    c.uniform(t0_ns, "source", {"/rig/pressure": readings}, step_hz=1000)

Set clock_id when your times come from the device itself ("source"). Use something that survives a restart, like a serial number or bus address. Phloem learns how that clock drifts from this computer's and remembers it under that name.

Without clock_id, each connection is a new clock, and learning its drift takes minutes. Until then, if the device clock is more than 5 s ahead of this computer, its readings are moved back to the computer's time. Leave clock_id out only when your times are this computer's clock ("host"). An empty clock_id is refused.

In other languages: phloem_connect_with_clock in C, phloem.WithClock(id) in Go, connect_with_clock in Rust.

Updated