Station Freshness
| Station | Datastreams | Latest Reading | Latest 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
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.*.
resultScaleFactor (e.g. 3), and the Observation stores an integer (e.g. 2134). The real value is 2134 × 10−3 = 2.134. This is the same pattern used by Protocol Buffers and financial systems.
| Field | Type | Description |
|---|---|---|
name | string | Human-readable station name |
description | string | Optional longer description |
location | geoPoint | WGS84 coordinates as latE7/lonE7 (integer × 107, ~1 cm precision) |
properties | object | Open metadata (manufacturer, deployment date, etc.) |
Record key: human-readable slug, e.g. sandy-mills
at://opw.sensorthings.dev/dev.sensorthings.thing/sandy-mills
| Field | Type | Description |
|---|---|---|
name | string | Instrument or procedure name |
encodingType | string | IANA media type of the metadata field |
metadata | string | URL to datasheet or SensorML document |
Record key: slug, e.g. sandy-mills-level
at://opw.sensorthings.dev/dev.sensorthings.sensor/sandy-mills-level
| Field | Type | Description |
|---|---|---|
name | string | Property name (e.g. “Water Level”) |
definition | URI | Controlled 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.
| Field | Type | Description |
|---|---|---|
thing | AT-URI | → dev.sensorthings.thing |
sensor | AT-URI | → dev.sensorthings.sensor |
observedProperty | AT-URI | → dev.sensorthings.observedProperty |
unitOfMeasurement | object | name, symbol, definition (QUDT/UCUM URI) |
resultScaleFactor | integer | Divides integer results: real = value × 10−n |
verticalDatum | string | For 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"
| Field | Type | Description |
|---|---|---|
datastream | AT-URI | → parent Datastream (carries unit & scale) |
phenomenonTime | datetime | When the phenomenon was observed (ISO 8601) |
result | union | Discriminated by $type – see below |
resultQuality | token | good, suspect, or missing |
Result types
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.
| Field | Type | Description |
|---|---|---|
datastream | AT-URI | → parent Datastream |
windowStart | datetime | Start of the batch window |
windowEnd | datetime | End of the batch window |
observations | array | Array 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.
| Field | Type | Description |
|---|---|---|
thing | AT-URI | → thing (not a Datastream – each entry carries its own property) |
sensor | AT-URI | → sensor that co-produced the results |
phenomenonTime | datetime | Observation timestamp |
entries | array | Up 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.