Station Freshness

StationDatastreamsLatest ReadingLatest Value

Relay Federation Status


SensorThings on ATProto

Environmental sensor data from Irish monitoring networks, published as structured, cryptographically signed records on the AT Protocol.

What & Why

This application visualises environmental sensor data – water levels, ocean conditions, air quality – that has been published to an ATProto Personal Data Server (PDS) using the OGC SensorThings entity model encoded as ATProto Lexicons.

Each sensor reading is a signed record in a Merkle tree. The data is publicly addressable, verifiable, and replicable by anyone who subscribes to the PDS firehose, without an API key.

Why ATProto for sensor data?
Signed & content-addressed
Every commit is signed by the publisher’s key pair. Consumers can verify data integrity without trusting the transport.
Portable identity
Each data source has a DID (Decentralised Identifier) that survives server migration. If the PDS moves, all references remain valid.
Replication by default
Any firehose subscriber automatically receives and can mirror the full dataset. No backup infrastructure is required.
Schema as contract
The dev.sensorthings.* Lexicons define the data model. Any PDS publishing conformant records participates in the federation.

Entity Model

The data follows the OGC SensorThings API entity model. Six entity types describe what is being measured, by what, where, and when:

Thing Location (embedded) Sensor ObservedProperty Datastream central join Observation ObservationBatch MultiObservation refs Thing + Sensor FeatureOfInterest optional AT-URI reference optional / indirect key entity
Thing

A physical station or platform hosting sensors. Carries a name, location (lat/lon), and open metadata. Example: OPW gauge 26021 on the River Suir at Clonmel.

Sensor

An instrument or procedure that produces observations. Links to a datasheet or SensorML description. Example: Vaisala WXT536 weather transmitter.

ObservedProperty

The phenomenon being measured. Linked to a controlled vocabulary URI (CF Standard Names, QUDT). Example: air_temperature, PM2.5 concentration.

Datastream central join

The pivotal entity: links one Thing + one Sensor + one ObservedProperty. Carries the unit of measurement and a scale factor for decoding integer results. All observations belong to exactly one Datastream.

Observation

A single measurement: a timestamp, a result value, and a reference to its parent Datastream. This is the high-volume record type. Example: 7.34 °C at 10:15 UTC.

FeatureOfInterest

The real-world feature being observed. Often implicit (the Thing’s location). Explicit when the observation target differs from the sensor location. Example: a river reach downstream of the gauge.

Lexicons

Each entity type is an ATProto Lexicon – a schema that defines the record structure and how records reference each other via AT-URIs. The namespace is dev.sensorthings.*.

FieldTypeDescription
namestringHuman-readable station name
descriptionstringOptional longer description
locationgeoPointWGS84 coordinates as latE7/lonE7 (integer × 107, ~1 cm precision)
propertiesobjectOpen metadata (manufacturer, deployment date, etc.)

Record key: human-readable slug, e.g. sandy-mills

at://opw.sensorthings.dev/dev.sensorthings.thing/sandy-mills

FieldTypeDescription
namestringInstrument or procedure name
encodingTypestringIANA media type of the metadata field
metadatastringURL to datasheet or SensorML document

Record key: slug, e.g. sandy-mills-level

at://opw.sensorthings.dev/dev.sensorthings.sensor/sandy-mills-level

FieldTypeDescription
namestringProperty name (e.g. “Water Level”)
definitionURIControlled vocabulary reference – CF Standard Names, QUDT, NERC P07

Record key: slug, e.g. water-level

at://opw.sensorthings.dev/dev.sensorthings.observedProperty/water-level
  → definition: http://vocab.nerc.ac.uk/collection/P07/current/CFSN0052/

The Datastream ties everything together. It references a Thing, Sensor, and ObservedProperty, and carries the unit and scale factor needed to interpret observation values.

FieldTypeDescription
thingAT-URIdev.sensorthings.thing
sensorAT-URIdev.sensorthings.sensor
observedPropertyAT-URIdev.sensorthings.observedProperty
unitOfMeasurementobjectname, symbol, definition (QUDT/UCUM URI)
resultScaleFactorintegerDivides integer results: real = value × 10−n
verticalDatumstringFor water level: datum URI or label (e.g. Malin Head OD)

Record key: descriptive slug, e.g. sandy-mills-level-od

at://opw.sensorthings.dev/dev.sensorthings.datastream/sandy-mills-level-od
  → thing: at://…/thing/sandy-mills
  → sensor: at://…/sensor/sandy-mills-level
  → observedProperty: at://…/observedProperty/water-level
  → unitOfMeasurement: { name: "metre", symbol: "m" }
  → resultScaleFactor: 3
  → verticalDatum: "http://…/EPSG/0/5731"

FieldTypeDescription
datastreamAT-URI→ parent Datastream (carries unit & scale)
phenomenonTimedatetimeWhen the phenomenon was observed (ISO 8601)
resultunionDiscriminated by $type – see below
resultQualitytokengood, suspect, or missing
Result types
numericResult categoryResult booleanResult arrayResult geoPointResult

Most observations use numericResult: a single scaled integer. Decode it using the parent Datastream’s resultScaleFactor.

// Water level observation: 30756 ÷ 10³ = 30.756 m (Malin Head OD)
{
  "datastream": "at://…/datastream/sandy-mills-level-od",
  "phenomenonTime": "2026-02-08T16:00:00Z",
  "result": { "$type": "…#numericResult", "value": 30756 }
}

Packs up to 1,440 observations for a single Datastream into one record, covering a time window (typically one day). Reduces commit overhead for high-frequency or historical data.

FieldTypeDescription
datastreamAT-URI→ parent Datastream
windowStartdatetimeStart of the batch window
windowEnddatetimeEnd of the batch window
observationsarrayArray of { t, result, q } entries

Record key: {datastream-slug}:{compact-date}, e.g. sandy-mills-level-od:20260210T000000Z

Bundles multiple co-produced results from a single act of sensing. Used when measurements have no independent existence – e.g. wave statistics (Hs, Hmax, Tp, mean period, mean direction) computed from the same accelerometer burst.

FieldTypeDescription
thingAT-URIthing (not a Datastream – each entry carries its own property)
sensorAT-URIsensor that co-produced the results
phenomenonTimedatetimeObservation timestamp
entriesarrayUp to 32 entries, each with its own observedProperty, unit, scaleFactor, and result

Used by the Marine Institute buoy network for wave statistics.

How records reference each other

Records point to each other using AT-URIs – stable, globally unique identifiers of the form:

at://{DID or handle}/{collection NSID}/{record key}

For example, an Observation references its Datastream by AT-URI. The Datastream in turn references its Thing, Sensor, and ObservedProperty by AT-URI. You can walk the entire reference graph from any record – try it in the Records browser.

Networks

Four Irish environmental monitoring networks are currently publishing:

OPW Water Level
Hydrology
Source
waterlevel.ie
Stations
~50 active (of ~380 total network)
Cadence
Every 15 minutes
Parameters
Water level (local gauge zero & Ordnance Datum Malin Head)
Handle
opw.sensorthings.dev
Marine Buoys
Oceanography
Source
ERDDAP (Marine Institute)
Stations
5 buoys (M2–M6)
Cadence
Hourly
Parameters
Atmospheric pressure, air & sea temp, humidity, wind, waves (Hs, Hmax, Tp, period, direction)
Handle
marine.sensorthings.dev
EPA Air Quality
Air Quality
Source
airquality.ie
Stations
~114 monitors
Cadence
Hourly
Parameters
PM10, PM2.5, NO2, O3, SO2, CO
Handle
epa.sensorthings.dev
Met Eireann
Weather
Source
met.ie Open Data (CC BY 4.0)
Stations
~25 synoptic stations
Cadence
Hourly
Parameters
Temperature, humidity, pressure, wind speed/direction, rainfall, present weather
Handle
met.sensorthings.dev

Consuming the Data

You don’t need this visualisation app to access the data. It is all publicly available on the PDS.

Browse records (REST)

List all records of a given type using the standard ATProto listRecords endpoint:

GET https://pds.sensorthings.dev/xrpc/
  com.atproto.repo.listRecords
  ?repo=opw.sensorthings.dev
  &collection=dev.sensorthings.thing

Fetch a single record with getRecord:

GET …/com.atproto.repo.getRecord
  ?repo=opw.sensorthings.dev
  &collection=dev.sensorthings.thing
  &rkey=sandy-mills
Subscribe to the firehose (WebSocket)

Receive all new commits in real time – the same mechanism this app uses:

wss://pds.sensorthings.dev/xrpc/
  com.atproto.sync.subscribeRepos

Events arrive as CBOR-encoded commit frames. Filter for dev.sensorthings.* collections to process only sensor data. No authentication required.