Mastodon Politics, Power, and Science: Graph Specification JSON Format and Bridge Interaction

Tuesday, March 17, 2026

Graph Specification JSON Format and Bridge Interaction

 J. Rogers,  SE Ohio

This document formally defines the JSON format used by Pico devices to declare their graphing requirements to the bridge, and describes how the bridge and Pico cooperate to provide embedded time‑series graphs in web pages.

1. Overview

The system consists of:

  • Pico devices – run the lightweight framework, read sensors, serve current data (/api/data), and serve a static graph specification (/graphlout/). They do not store history or render graphs.

  • Bridge – a persistent process on a local machine (discovered via mDNS) that polls each Pico’s data, logs it according to the graph specification, and serves SVG graphs on demand.

Flow:

  1. Discovery: Bridge discovers Pico via mDNS and fetches its manifest and the graph specification from /graphlout/.

  2. IP Storage: Pico records the bridge’s IP address (from the HTTP request or via a separate discovery response).

  3. Bridge Setup: Bridge parses the JSON, creates database tables for each named graph, and starts logging polled values.

  4. Page Generation: When a client requests a page from the Pico, the Pico’s web server generates HTML. For each graph widget (as defined in the Pico’s layout table), it inserts an <img> tag with src pointing to http://<bridge-ip>/graph/<graph_name>?range=....

  5. Graph Serving: Browser requests the SVG from the bridge; the bridge queries its stored data, renders the graph per the specification, and returns the SVG.

2. Graph Specification Endpoint

Endpoint: GET /graphlout/ on the Pico.

Response: A JSON object conforming to the schema below.

The bridge fetches this endpoint once per device during discovery. If the Pico does not provide this endpoint (404), the bridge assumes no graphing is configured.

3. JSON Schema

The root object contains a "graphs" array. Each element describes one logical graph, which can be referenced by name in image URLs.

3.1 Root Object

FieldTypeDescription
devicestringOptional. Device identifier (e.g., hostname).
graphsarray of objectList of graph definitions.

3.2 Graph Definition Object

FieldTypeRequiredDescription
namestringyesUnique identifier for the graph. Used in URLs: /graph/<name>.
titlestringnoHuman‑readable title (displayed on the graph). Defaults to name.
data_sourcesarray of objectyesList of one or more registry items whose values are plotted. Each source has its own styling and scaling (see below).
time_rangestring / objectnoDefault time window to display. Can be a string like "24h" or an object with start and end (ISO 8601). Web client may override. Default "24h".
sampling_intervalintegernoHow often (in seconds) the bridge should log this graph’s data sources. If omitted, the bridge logs every poll (typically every 10 seconds).
retentionstringnoHow long to keep data, e.g., "30d", "1y". If omitted, bridge keeps data indefinitely (or uses its own default).
axesobjectnoConfiguration for X and Y axes. See below.
legendboolean / objectnoShow legend? Can be true/false or an object with positioning. Default true.
widthintegernoDefault SVG width in pixels. Web client may override. Default 600.
heightintegernoDefault SVG height in pixels. Default 300.
backgroundstringnoBackground color (CSS color). Default "#ffffff".
borderstring / objectnoBorder style (CSS).

3.2.1 data_sources object

Each element corresponds to one series on the graph.

FieldTypeRequiredDescription
idstringyesRegistry item identifier (e.g., "pressure", "temp_a"). Must match an item in the device’s /api/data.
labelstringnoLabel for this series in the legend. Defaults to id.
colorstringnoCSS color for the line/points. If omitted, the bridge picks a color automatically.
unitstringnoOverride the unit from the registry (e.g., "°C"). If not given, the unit from the registry is used.
scaleobjectnoScaling parameters for this series. See below.
typestringnoPlot type: "line", "step", "scatter", "bar". Default "line".
y_axisstringnoWhich Y‑axis to use: "left" (default) or "right". Allows multiple axes in one graph.

3.2.2 scale object

FieldTypeRequiredDescription
minnumbernoMinimum value for this series. If omitted, auto‑scale based on data.
maxnumbernoMaximum value. If omitted, auto‑scale.
lockedbooleannoIf true, the bridge should not auto‑scale even if data goes outside. Default false.

3.2.3 axes object

FieldTypeDescription
xobjectConfiguration for X‑axis (time). See below.
y_leftobjectConfiguration for left Y‑axis.
y_rightobjectConfiguration for right Y‑axis (if any series uses it).

Axis object fields (for x, y_left, y_right):

FieldTypeDescription
labelstringAxis label (e.g., "Time", "Pressure (hPa)").
minnumberFor Y axes: fixed minimum. For X axis: not used (time is handled separately).
maxnumberFixed maximum for Y axis.
tick_formatstringFor X axis: time format (e.g., "%H:%M", "%Y-%m-%d"). For Y axis: number format (e.g., ".1f").
gridbooleanShow grid lines? Default true.
logbooleanUse logarithmic scale? (Y axes only). Default false.

3.3 Example

{
  "device": "weather_station_1",
  "graphs": [
    {
      "name": "pressure_24h",
      "title": "Barometric Pressure (24h)",
      "data_sources": [
        {
          "id": "pressure",
          "label": "Pressure",
          "color": "#1f77b4",
          "unit": "inHg",
          "scale": {
            "min": 28.5,
            "max": 31.5
          }
        }
      ],
      "time_range": "24h",
      "sampling_interval": 300,
      "retention": "30d",
      "axes": {
        "x": {
          "label": "Time",
          "tick_format": "%H:%M",
          "grid": true
        },
        "y_left": {
          "label": "Pressure (inHg)",
          "tick_format": ".2f"
        }
      },
      "width": 800,
      "height": 400
    },
    {
      "name": "temp_humidity_week",
      "title": "Temperature & Humidity (7 days)",
      "data_sources": [
        {
          "id": "temp_a",
          "label": "Temperature",
          "color": "#d62728",
          "unit": "°F",
          "y_axis": "left"
        },
        {
          "id": "humidity_a",
          "label": "Humidity",
          "color": "#2ca02c",
          "unit": "%",
          "y_axis": "right"
        }
      ],
      "time_range": "7d",
      "sampling_interval": 600,
      "retention": "90d",
      "axes": {
        "x": {
          "label": "Date",
          "tick_format": "%m-%d"
        },
        "y_left": {
          "label": "Temperature (°F)",
          "min": 0,
          "max": 120,
          "grid": true
        },
        "y_right": {
          "label": "Humidity (%)",
          "min": 0,
          "max": 100,
          "grid": false
        }
      }
    }
  ]
}

4. Bridge Behavior

Upon discovering a Pico (via mDNS), the bridge:

  • Fetches http://<pico-ip>/graph_definitions/.

  • Parses the JSON. For each graph:

    • Creates a time‑series table (or InfluxDB measurement, etc.) with appropriate retention policy.

    • Starts logging the listed data sources at the requested sampling_interval (or every poll if omitted).

  • Serves SVG graphs at http://<bridge-ip>:<port>/<pico host name>/<name of graph>.svg. Supported query parameters:

    • Other parameters may be added as needed.

When a graph request arrives, the bridge:

  • Retrieves the stored spec for that graph.

  • Queries the logged data for the requested time range.

  • Applies scaling, axis formatting, and styling from the spec.

  • Renders an SVG (e.g., using a library like svgwrite or matplotlib).

  • Returns the SVG with Content-Type: image/svg+xml.

The bridge must handle multiple devices and graphs concurrently.

5. Pico Behavior

The Pico's responsibilities are minimal:

  • Serve the static JSON spec from /graphlout/. The spec is defined in the application code (e.g., as a constant string or generated by a function). This spec contains the graph name, data sources, sampling interval, retention period, and the time range to display (e.g., 24 hours, 100 days).

  • Store the bridge IP address when the bridge first contacts it. This can be done by recording the source IP of the HTTP request to /graph_defintion/ (the bridge's IP).

  • When generating HTML pages (based on its internal layout table), for any graph widget, embed an <img> tag with src pointing to:

    http://<stored-bridge-ip>:port/<pico-hostname>/<graph_name>.svg

    The graph name comes from the graph definition. The time range is not included in the URL—it is already defined in the graph specification that the bridge fetched from /graphlout/. The bridge uses that stored range when rendering the SVG.

For example, if the layout includes a graph widget for "pressure_24h" on a device named "weather_pico", the Pico's web server outputs:

<img src="http://192.168.1.100:5001/weather_pico/pressure_24h.svg">

The IP address is the stored bridge IP; the device hostname and graph name identify which specification to use. The bridge already knows from the spec that this graph should show 24 hours of data.

If no bridge IP has been stored (e.g., bridge not yet discovered), the Pico may omit the image or show a placeholder.

6. Discovery and IP Storage Details

The exact mechanism for IP storage is implementation‑dependent but must be reliable:

  • Option A: Bridge fetches /graphlout/; the Pico records the remote IP from the TCP connection. This requires the bridge to be on the same network and the request to come directly.

  • Option B: Bridge announces itself via mDNS (e.g., _pico-bridge._tcp). Pico listens for these announcements and stores the IP.

  • Option C: Pico provides an endpoint /api/bridge that the bridge can POST its IP to after discovery.

Any method works as long as the Pico ends up with a valid bridge IP to use in image URLs.

7. Embedding Graphs in Pages

The Pico’s layout system (separate from the graph spec) determines where graph widgets appear. When rendering a page, the framework:

  • Iterates through layout containers and maps.

  • For each MAP with widget type GRAPH, it finds the corresponding graph definition by name (the graph name is stored in the map or derived from the registry ID).

  • It then generates an <img> tag using the stored bridge IP, the graph name, and the default time range from the spec.

  • The resulting HTML is sent to the client.

The browser loads the image directly from the bridge, which returns an SVG. The graph is fully rendered server‑side; no JavaScript is required on the client.

8. Extensibility

The JSON schema is designed to be forward‑compatible. Additional fields (e.g., annotations, thresholds, fill_area, theme) can be added without breaking existing implementations. The bridge must ignore unknown fields.

9. Versioning

This document defines version 1.0 of the Graph Specification JSON Format. Future versions may introduce new fields or change semantics; a version field in the root object can be added if needed.


This specification is part of the Pico Discovery Bridge ecosystem.

No comments:

Post a Comment

The “BluePrint” Platform: A Declarative UI & Data-Sync Framework for PicoW J. Rogers, SE Ohio The code is here that this is going to mo...