| Internet-Draft | coreconf-m2m | September 2026 |
| Toutain | Expires 20 March 2027 | [Page] |
The document addresses the specific challenges of M2M interactions where both endpoints may be constrained nodes, and explores the use of CORECONF primitives.¶
This document describes the use of CORECONF (CoAP Management Interface) for Machine-to-Machine (M2M) communication in constrained IoT environments. It defines a YANG data model enabling remote management and configuration of constrained devices using CoAP, CBOR, and YANG SID identifiers. The serialization in CBOR of this data model limits the payload size. It documents also how the YANG data model can interact with common IoT ontologies such as SOSA or SAREF. The same CORECONF/SID serialization also enables full interoperability between constrained devices and AI agents, by exposing device actions and data through an MCP (Model Context Protocol) server without requiring an intermediate, device-specific translation layer.¶
This note is to be removed before publishing as an RFC.¶
Status information for this document may be found at https://datatracker.ietf.org/doc/draft-toutain-t2trg-coreconf-m2m/.¶
Discussion of this document takes place on the T2TRG Research Group mailing list (mailto:t2trg@irtf.org), which is archived at https://mailarchive.ietf.org/arch/browse/t2trg/. Subscribe at https://www.ietf.org/mailman/listinfo/t2trg/.¶
Source for this draft and an issue tracker can be found at https://github.com/ltn22/coreconf-m2m.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 20 March 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document.¶
This document proposes a YANG data model designed for constrained devices and low-power networks. By combining YANG's strong typing with CBOR's compact binary serialization and CoAP's lightweight transport, the model enables efficient Machine-to-Machine (M2M) data exchange while remaining within the bandwidth and energy budgets of constrained environments. SCHC header compression [RFC8724] may further be used to reduce the overhead of IPv6, UDP, and CoAP headers on the most constrained links.¶
Some data models and protocols already exist for M2M communication in IoT environments, but none is fully adapted to the constraints of low-power networks in terms of message size, energy consumption, and interaction patterns. This section reviews the main existing approaches and explains why a new model is needed.¶
SenML [RFC8428] has become a widely adopted format for Machine-to-Machine (M2M) data exchange in IoT environments, enabling constrained devices to report sensor measurements and time series in JSON or CBOR. However, SenML is primarily a data serialization format: it structures payloads but does not enforce strong type checking, schema validation, or support for configuration and remote operations.¶
SenML is also part of the LwM2M framework [OMA-LwM2M], which defines a broader device management protocol built on CoAP and SenML for operator-to-device interactions. However, LwM2M relies on periodic reporting and registration messages that impose a non-trivial overhead, particularly on Low-Power Wide-Area Networks (LPWANs) [RFC8376] where bandwidth and energy budgets are severely constrained.¶
In some ways, SenML may be described by a YANG Data Model [I-D.gudi-t2trg-senml-as-coreconf], but the integration in the YANG ecosystem remains limited.¶
The CORECONF protocol stack using YANG [RFC7950] for Data Modeling, CoAP [RFC7252] for data transport, and CBOR [RFC8949] and YANG SID identifiers [I-D.ietf-core-sid] for the compact data serialization provides the richer foundation that SenML lacks: a strongly typed data model, schema validation, and support for full CRUD operations and actions. However, CORECONF has so far been designed for operator-to-device management, leaving peer-to-peer M2M communication — where both endpoints may themselves be constrained nodes — largely unaddressed.¶
Some YANG Data Models have been defined for telemetry. [RFC9232] introduces Network Telemetry used to collect vast amounts of data to supervise a network. [RFC8639] allows subscribing to a datastore filtered through XPath and receiving notifications. [I-D.birkholz-yang-core-telemetry] proposes to extend telemetry to CORECONF, but using a traditional approach. None of these attempt to limit the size of the exchanged data, which is essential on the constrained links this document targets.¶
This document adopts a different approach. The goal is to define a YANG Data Model that will benefit from CBOR serialization to optimize the bandwidth to extend CORECONF for M2M use cases over low-power links. The data model turns around transducers held by a device. A transducer is the abstract representation of a sensor and/or an actuator, transforming an analog value into a digital value, or a digital value into a physical action. This document focuses on transducer management: resource discovery, value polling, statistical computation, threshold alerts, and time-series history notifications. This is an early-stage work; future revisions will explore other categories of measurements and interaction patterns.¶
Beyond its use as a wire format, the structure defined by the YANG Data Model can be mapped into a Semantic Web ontology, giving each container, list, and leaf a counterpart class or property that common IoT ontologies such as SOSA or SensorThings already understand. Section 7.1 details this mapping and how it lets a device modeled with coreconf-m2m be integrated into an existing ontology-based deployment without a device-specific adaptation layer.¶
The targeted use cases are remote sensors installed in the field and connected with LPWAN or Satellite connectivity. SCHC [RFC8724] [RFC8824] is used to compress headers such as IPv6, UDP and CoAP for CORECONF traffic. The payload results from the serialization of the datastore in CBOR.¶
The model covers the following use cases:¶
resource discovery: sensors or actuators, regrouped under the name transducers, are discovered with their characteristics (units, precision)¶
simple query: each transducer can be individually queried or set.¶
statistical computation: the sensor can compute some statistical values, such as mean, variance, min and max. They can be reset.¶
alert notification: when a value reaches a threshold (minimum and maximum) a notification message is sent¶
time series: values are collected by the device and sent when a limit is reached (number of samples, duration, message size)¶
CBOR is designed to be concise to represent numerical information since it is directly coded in binary and not represented in ASCII. CBOR also uses binary representation to encode structures such as Maps and Arrays. The length of a numerical value depends on its value; for instance, numbers between -24 and 23 are coded on a single byte, values between -255 and 255 on two bytes,...¶
Nevertheless, some representations may be less efficient numerically or less
precise. CBOR defines three IEEE 754 encodings on 3, 5, or 9 bytes. The smallest
representation introduces a close to 1% error. CBOR also provides a decimal
fraction type (tag 4) encoding a value as a [exponent, mantissa] pair, which
avoids floating-point rounding. However, this representation requires the
exponent (the divider) to be repeated alongside every individual quantity,
adding overhead for each encoded value.¶
The assumption leading to this YANG module is to avoid floating-point numbers for their size or precision and rely on integers with a precision parameter indicating, if positive, the number of digits after the decimal point, or the power of 10 if negative.¶
The module also introduces the notion of time series to record several measurements during a period of time and send them in a single message using a notification. Time series values may further be compressed depending on the nature of the data. This version proposes a compression based on delta encoding: instead of transmitting absolute values, each sample is encoded as the difference from the previous one, which significantly reduces the CBOR payload size for slowly-varying measurements.¶
The device can also send an alert when a measured value reaches a threshold, allowing the receiver to react promptly without waiting for a scheduled report.¶
Since the targeted networks are constrained in bandwidth, measurements are expected to be reported infrequently, so the second is chosen as the time unit. To limit the size of the timestamps, they are expressed as the number of seconds elapsed since the equipment was started, rather than as an absolute date and time. If the equipment is able to do so, it can indicate its startup epoch to reconstitute an absolute time.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
The following terminology is used in this document:¶
CORECONF: : CoAP Management Interface, as defined in [I-D.ietf-core-comi].¶
M2M: : Machine-to-Machine communication, referring to direct data exchanges between devices without human intervention.¶
SID: : YANG Schema Item iDentifier, a compact numeric identifier for YANG data nodes, as defined in [I-D.ietf-core-sid].¶
Constrained Device: : A device with limited processing, memory, and energy resources, as characterized in [RFC7228].¶
Device: : A piece of equipment containing one or more transducers.¶
Transducer: : an interface between the analog and digital world. Transducers are sensors reporting values or actuators having an action on the physical world.¶
Quantity: : values manipulated by transducers.¶
Value: : other information stored in the datastore including quantities¶
coreconf-m2m YANG module is a framework: it defines the generic structure of a CORECONF server hosting one or several transducers, a generic name for a sensor or an actuator, but it does not define the transducers themselves. Each transducer is identified by a unique identity, and if several transducers report the same kind of measurement, several instances of the same identity have to be defined.¶
A device manufacturer using coreconf-m2m MUST therefore define, or reuse, a YANG module describing the transducers embedded in its device, as identities extending the base "transducer-type" identity defined in coreconf-m2m (see Section 3.1).¶
The identity data model defines all the transceivers that may be used inside the hosts, this can be a generic data model or a model specific to the hosts. This module indicates default parameters used for that transceiver (units, precision, nature). These values are mandatory and may be overridden by the coreconf-m2m.¶
A manufacturer module defines the concrete transducer-type identities for a given product family; coreconf-m2m only defines the base "transducer-type" identity that these modules extend. The ATMOS41 weather station example used throughout this document is defined this way, in a separate "atmos" module that imports coreconf-m2m and extends its base identity with atmospheric and weather-related transducer types.¶
coreconf-m2m defines the template for identities extensions. Figure 1 gives an example of an identity definition, where "ccm2m" refers to the coreconf-m2m module.¶
identity air-temperature {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "Cel";
ccm2m:default-precision "2";
description "Air temperature measurement (°C).";
}
Identity names and descriptions SHOULD be explicit, since this information may be used by a human, e.g. through an interface, or by an AI agent, to formulate requests.¶
The coreconf-m2m module is organized into three sub-modules:¶
"bootstrap" contains the context needed by the client to interact with the server and its transducers. It mainly contains the time reference and the transducer list. These values cannot be changed by the client (config false); only a reboot, which also resets the communications, allows their modification¶
"transducers" contains the runtime values of all the sensors and actuators maintained by the host. It also contains parameters to control notifications issued by the device.¶
an RPC, now limited to resetting all statistics¶
two notification types: one to collect time-series history, and one to alert when a quantity reaches a minimum or maximum threshold.¶
Figure 28 in Appendix A gives an overview of the module YANG tree.¶
module: coreconf-m2m
+--ro bootstrap
+--ro reference-epoch? uint64
+--ro uptime uint64
+--ro minimal-step? uint32
+--ro inventory* [type]
+--ro type identityref
+--ro unit-override? string
+--ro precision-override? uint8
+--ro category-override? enumeration
The bootstrap sub-tree contains information determined at bootstrap:¶
reference-epoch: the absolute time at bootstrap, if a reference time source (battery-powered clock, GPS, ...) is available on the device. This leaf is absent if no reference time is available.¶
uptime: the number of seconds elapsed since bootstrap. A client can combine reference-epoch and uptime to estimate clock drift or propagation delay.¶
minimal-step: the minimum number of seconds between two transceiver readings. For instance, if a node updates its reading every 2 minutes, querying it more frequently only increases battery drain without yielding fresher data.¶
inventory: the list of transducers known to the device, identified by their "type" and their configuration overrides (see Section 3.2.3).¶
These values can only change on a reboot, which also resets the communications and forces the client into a new bootstrap phase to recover the data.¶
module: coreconf-m2m
+--rw characteristics
+--rw geo-location
| +--rw reference-frame
| | +--rw alternate-system? string {alternate-systems}?
| | +--rw astronomical-body? string
| | +--rw geodetic-system
| | +--rw geodetic-datum? string
| | +--rw coord-accuracy? decimal64
| | +--rw height-accuracy? decimal64
| +--rw (location)?
| | +--:(ellipsoid)
| | | +--rw latitude? decimal64
| | | +--rw longitude? decimal64
| | | +--rw height? decimal64
| | +--:(cartesian)
| | +--rw x? decimal64
| | +--rw y? decimal64
| | +--rw z? decimal64
| +--rw velocity
| | +--rw v-north? decimal64
| | +--rw v-east? decimal64
| | +--rw v-up? decimal64
| +--rw timestamp? yang:date-and-time
| +--rw valid-until? yang:date-and-time
+--rw name? string
+--rw version? string
+--rw identifier? string
+--rw description? string
+--rw manufacturer? string
+--rw model? string
+--rw hosted-by? string
+--rw installation-time? uint64
This sub-tree contains stable information about the device that is not expected to change across reboots:¶
geo-location: the fixed geographic position of the device, set once at deployment and not expected to change during normal operation. The module reuses the "ietf-geo-location" YANG module [RFC9179], of which only the "ellipsoid" choice is normally needed. Note that its coordinates are typed as "decimal64", encoded in CBOR as a tagged array containing an exponent and a mantissa. This encoding was not chosen for transducer values, in order to avoid repeating the exponent (called "precision" in this model) with every quantity.¶
If the device is mobile, its coordinates should instead be exposed through a transducer.¶
name: a human-readable name for the device.¶
version: the firmware or software version running on the device.¶
identifier: a unique identifier for the device, such as a serial number or EUI.¶
description: a free-text description of the device, its purpose, or its deployment context.¶
manufacturer: the name of the device manufacturer.¶
model: the commercial model name or number of the device.¶
hosted-by: an identifier of the platform hosting this device, when applicable.¶
installation-time: the absolute date and time (Unix epoch, in seconds) at which the device was installed or commissioned. Unlike bootstrap/reference-epoch, this is an absolute value, not a reference point for the device's internal clock.¶
module: coreconf-m2m
+--rw transducers
+--rw transducer* [type]
+--rw type identityref
+--ro quantity
| +--ro value? int64
| +--ro timestamp? uint64
| +--ro timestamp-source? enumeration
+--ro statistics
+--ro min? int64
+--ro max? int64
+--ro mean? int64
+--ro median? int64
+--ro stdev? uint64
+--ro sample-count? uint64
The transducers sub-tree contains the list of transducers (i.e., sensors and actuators) maintained by the device, listed in "/bootstrap/inventory". A transducer is identified by its "type", the same identityref used in the "/bootstrap/inventory" entry.¶
This branch holds two kinds of information: "quantity" carries the values manipulated by the transducer, while "notification-parameters" configures notifications through which the device can proactively inform the client.¶
Quantity contains:¶
"value": the raw integer value, computed as
value = measurement * 10^precision,¶
"timestamp": the relative time of the measurement, in seconds,¶
the entity in charge of the timestamp, which can be the device itself or the receiver.¶
"statistics" is a sibling of "quantity" and holds the main statistics locally computed for a specific transducer, accumulated over a window of samples rather than tied to the current measurement. Statistics can be reset for a single transducer via the "reset-stats" action, or for all transducers at once via the "reset-stats" RPC.¶
module: coreconf-m2m
+--rw transducers
+--rw transducer* [type]
+--rw notification-parameters
+--rw history
| +--ro active? boolean
| +--rw step? uint32
| +--rw precision? uint8
| +--rw max-samples? uint32
| +--rw time-period? uint32
| +--rw encoding? encoding-type
| +--rw max-payload? uint32
+--rw sensor-alert
| +--ro active? boolean
| +--rw t-min? int32
| +--rw t-max? int32
| +--rw hysteresis? uint8
| +--rw dampening? uint32
+--rw check-interval? uint16
Notification Parameters supports two kinds of notifications:¶
"sensor-alert" will send a notification when the measured quantity reaches one or two limits, minimal and maximal, or goes back to a value between these two bounds. To avoid fluctuations, two mechanisms are in place:¶
"hysteresis" defines a percentage, by default 5% around the limit, so if a maximum limit is set to 100, an alert message will be triggered when the quantity is higher than 105 and another alert will be sent when the quantity becomes lower than 95%. The value is sent in the notification message, so the client is able to know the state of the alert.¶
"dampening" limits the number of messages sent. When a crossing is
suppressed because of dampening, the notification eventually sent
MUST carry a None value, so the client can distinguish a dampened
event from an actual measured value.¶
"history" builds time series:¶
"step" parameter defines at which interval samples are taken.¶
"precision" allows overriding the quantity precision defined in the transducer. By default the precision is the one associated with the transducer for "quantity".¶
"encoding" indicates how information is stored in the time series:¶
A notification is sent when a number of measurements is reached, either:¶
the number of samples in the time series reaches "max-samples",¶
"max-payload" is based on the size of the time series. Since small numbers take less space than large numbers in CBOR, a time series may contain a different number of samples.¶
the "time-period" after which the collection should be sent.¶
"check-interval" controls how often a CON (confirmable) notification is sent instead of NON (non-confirmable), for both the history and sensor-alert streams of this transducer: 0 (default) disables it, so all notifications are NON; 1 makes every notification CON; N sends one CON every N notifications, all others being NON.¶
The read-only active flag in both notification types is set when one or more
clients observe a notification. Parameters are common to all observations of a
particular transducer.¶
As seen in the previous section, each transducer has a "notification-parameters" branch holding its notification settings. A client MAY FETCH this branch to retrieve its content and check the current status of the notification (e.g., the "active" flag).¶
A client wishing to start a notification MAY first send an iPATCH to configure its parameters. Parameters changed while a notification is already active take effect immediately. These parameters are global to the transducer: a modification impacts every notification currently active for that transducer, regardless of which client configured it.¶
Notification is started by sending a FETCH+Observe request on the notification
stream resource (/s), with a body identifying the SID of the transducer to
observe. Multiple clients may observe the same transducer simultaneously; each
receives an independent copy of every notification.¶
The notification ends when either:¶
the client explicitly sends a termination message (a GET/FETCH with the Observe option set to 1, i.e. deregister, using the same token as the original observation), or¶
the client sends a RST in response to a notification, or¶
the server sends Confirmable notifications and does not receive an acknowledgment, or¶
an ICMPv6 unreachable message is received by the server in response to the IPv6/UDP/CoAP packet carrying the notification's token.¶
CORECONF defines mappings for all CoAP methods, but this document uses only two:¶
FETCH is used instead of GET to retrieve values from the YANG Data Model. Unlike GET, FETCH carries a body specifying the exact SIDs to retrieve, enabling precise and bandwidth-efficient queries. Combined with the CoAP Observe option, FETCH also serves to subscribe to notification streams.¶
iPATCH is used instead of PUT or POST to modify quantities and notification parameters. It supports partial updates: only the specified nodes are modified, leaving others unchanged, following the merge semantics of [RFC7396]. Setting a node to an empty value with iPATCH is the preferred way to clear a parameter, making DELETE unnecessary for datastore modifications.¶
The recommended CoAP Content-Format for all exchanges is Content-Format
140 (application/yang-data+cbor;id=sid [RFC9254]), used uniformly
for FETCH request bodies (which carry the list of SIDs to retrieve),
response bodies, and iPATCH payloads, where data nodes are identified
by their SID.¶
Using a single content format ensures maximum interoperability with CORECONF implementations and keeps the payloads as compact as possible. Limiting exchanges to a single well-known packet format also benefits SCHC compression [RFC8724]: the fewer distinct header patterns in use, the more efficiently SCHC rules can compress the CoAP headers, reducing overhead on the most constrained links.¶
FETCH and iPATCH requests MUST be sent as Non-Confirmable (NON) CoAP messages. This leaves the application free to implement its own retransmission strategy and timer management, which is essential on constrained networks where the default CoAP confirmable retransmission behavior may be inappropriate or wasteful.¶
For notification streams (Observe), Confirmable (CON) messages MAY be used. A CON notification allows the server to detect that the observer is no longer reachable when no ACK is received, and to cancel the Observe subscription accordingly. A RST response from the client is also a valid way to signal that the subscription should be terminated.¶
The coreconf-m2m model is intended to be assigned in the IETF experimental SID space, with a model entry point of 62000. Appendix C gives the complete SID mapping for this model.¶
The atmos module, however, cannot be assigned SIDs in the IETF or another SDO space, unless this commercial module is itself standardized. For commercial modules, we assume that a registrar allocates SIDs and implements the constraints defined in [I-D.ietf-core-comi]. In this example, we assume that the registrar owns a 10 million SID mega-range. Appendix D gives the atmos.yang model, and Appendix E gives the corresponding SID mapping, composed of the identities representing the transducers.¶
This section illustrates different traffic patterns used in coreconf-m2m.¶
The client does not know the transducers managed by the device. It sends a FETCH on "/bootstrap" (SID 62002) as shown in Figure 6.¶
CoAP Request:
Non-Confirmable, FETCH, MID:39919
Token: 2aaf
Opt #1: Uri-Path: c
Opt #2: Content-Format: 140 (application/yang-data+cbor;id=sid)
Opt #3: Accept: 140 (application/yang-data+cbor;id=sid)
Payload: 3 bytes
19 F2 32 # unsigned(62002) : /bootstrap
CoAP Response:
Non-Confirmable, 2.05 Content, MID:57745
Token: 2aaf
Opt #1: Content-Format: 140 (application/yang-data+cbor;id=sid)
Payload: 118 bytes
{62002:
{7: 1788334280, 8: 30172, 6: 120,
1: [{3: 10000010}, {3: 10000008}, {3: 10000011}, {3: 10000002},
{3: 10000014}, {3: 10000016}, {3: 10000015}, {3: 10000012},
{3: 10000001}, {3: 10000013}, {3: 10000003}, {3: 10000009},
{3: 10000006}, {3: 10000004}]}}
In the request, the payload 19 F2 32 is the CBOR encoding of the unsigned
integer 62002, which is the SID of "/bootstrap". In the response, the outer
key 62002 identifies the bootstrap branch, and the inner keys are delta
SIDs relative to 62002, as defined in [RFC9254]: 7 (reference-epoch), 8
(uptime), 6 (minimal-step), and 1 (the "inventory" list). Each entry in
the inventory list is itself a map whose key 3 is a delta SID relative to
the list SID (62003), i.e. "type", carrying the identity SID of one
transducer known to the device; no override is present here, meaning every
transducer uses the default-unit, default-precision and default-category
declared on its identity.¶
The client MUST have access to the corresponding YANG files to discover the transducers' default parameters, indicating the unit, the precision and the nature of the transducer.¶
coreconf-m2m:bootstrap
reference-epoch 1788334280 (2026-09-02 09:31:20) ← origin of every timestamp in the model
uptime 30172 s
minimal-step 120 s ← floor for history/step
coreconf-m2m:bootstrap/inventory — SID → module → YANG defaults resolution
SID Identity Module Unit Prec. Category
────────────────────────────────────────────────────────────────────────────────────────────
10000010 atmos:solar-radiation atmos W/m2 0 sensor
10000008 atmos:precipitation atmos mm 3 sensor
10000011 atmos:strike-count atmos count 0 sensor
10000002 atmos:average-distance atmos km 0 sensor
10000014 atmos:wind-direction atmos deg 0 sensor
10000016 atmos:wind-speed atmos m/s 2 sensor
10000015 atmos:wind-gust atmos m/s 2 sensor
10000012 atmos:tilt atmos deg 1 sensor
10000001 atmos:air-temperature atmos Cel 1 sensor
10000013 atmos:vapor-pressure atmos kPa 2 sensor
10000003 atmos:barometric-pressure atmos kPa 2 sensor
10000009 atmos:relative-humidity atmos %RH 1 sensor
10000006 atmos:humidity-sensor-temperature atmos Cel 1 sensor
10000004 atmos:compass-heading atmos deg 0 sensor
────────────────────────────────────────────────────────────────────────────────────────────
* override sent by the device; without a star, default value read from the YANG module.
The inventory completed this way stays local: bootstrap is config false and is never sent back.
Connected. 14 sensor(s) discovered, minimal step 120 s.
# Type Unit Prec. Filter
──────────────────────────────────────────────────────────────────────────────
1 solar-radiation W/m2 0 [type='atmos:solar-radiation']
2 precipitation mm 3 [type='atmos:precipitation']
3 strike-count count 0 [type='atmos:strike-count']
4 average-distance km 0 [type='atmos:average-distance']
5 wind-direction deg 0 [type='atmos:wind-direction']
6 wind-speed m/s 2 [type='atmos:wind-speed']
7 wind-gust m/s 2 [type='atmos:wind-gust']
8 tilt deg 1 [type='atmos:tilt']
9 air-temperature Cel 1 [type='atmos:air-temperature']
10 vapor-pressure kPa 2 [type='atmos:vapor-pressure']
11 barometric-pressure kPa 2 [type='atmos:barometric-pressure']
12 relative-humidity %RH 1 [type='atmos:relative-humidity']
13 humidity-sensor-temperature Cel 1 [type='atmos:humidity-sensor-temperature']
14 compass-heading deg 0 [type='atmos:compass-heading']
A specific quantity may be requested through a FETCH. Figure 8 shows the exchange for the current value of air-temperature.¶
CoAP Request:
Non-Confirmable, FETCH, MID:12229
Token: 2dae
Opt #1: Uri-Path: c
Opt #2: Content-Format: 140 (application/yang-data+cbor;id=sid)
Opt #3: Accept: 140 (application/yang-data+cbor;id=sid)
Payload: 9 bytes
[62077, 10000001]
CoAP Response:
Non-Confirmable, 2.05 Content, MID:57779
Token: 2dae
Opt #1: Content-Format: 140 (application/yang-data+cbor;id=sid)
Payload: 6 bytes
{62077: 196}
The FETCH body [62077, 10000001] is a CORECONF instance-identifier: the
first element (62077) is the SID of "/transducers/transducer/quantity/value",
followed by the list key value identifying the transducer (type=10000001,
i.e. atmos:air-temperature). The response carries only that leaf: the
value 196, combined with the precision=1 declared on the air-temperature
identity, decodes to 19.6°C. Fetching the "value" leaf directly, rather
than the whole "quantity" sub-tree, avoids returning timestamp and
timestamp-source when only the current reading is needed.¶
Figure 9 shows the FETCH for the full statistics table of the same transducer:¶
CoAP Request:
Non-Confirmable, FETCH, MID:18878
Token: 5420
Opt #1: Uri-Path: c
Opt #2: Content-Format: 140 (application/yang-data+cbor;id=sid)
Opt #3: Accept: 140 (application/yang-data+cbor;id=sid)
Payload: 9 bytes
[62081, 10000001]
CoAP Response:
Non-Confirmable, 2.05 Content, MID:32290
Token: 5420
Opt #1: Content-Format: 140 (application/yang-data+cbor;id=sid)
Payload: 21 bytes
{62081: {4: 196, 1: 217, 2: 203, 3: 202, 6: 7, 5: 8}}
The FETCH body [62081, 10000001] requests the statistics sub-tree (SID
62081) for air-temperature (10000001). In the response, the inner map
keys are delta SIDs relative to 62081, each encoding the difference
between consecutive SIDs to minimize CBOR size. The six values correspond
to the statistics leaves: min (196), max (217), mean (203), median (202),
stdev (7), and sample-count (8), all scaled by the transducer precision
except sample-count.¶
The client decodes the response and displays the statistics as shown in Figure 10:¶
[9] Statistics — air-temperature:
min: 19.6 Cel
max: 21.7 Cel
mean: 20.3 Cel
median: 20.2 Cel
σ: 0.7 Cel
n: 8
The client first sends an iPATCH to configure the history notification parameters for the air-temperature transducer, as shown in Figure 11.¶
CoAP Request:
Non-Confirmable, iPATCH, MID:42310
Token: 248e
Opt #1: Uri-Path: c
Opt #2: Content-Format: 140 (application/yang-data+cbor;id=sid)
Payload: 22 bytes
{[62060, 10000001]: {62060: {6: 120, 4: 10, 2: 1}}}
CoAP Response:
Non-Confirmable, 2.04 Changed, MID:65147
Token: 248e
The iPATCH key [62060, 10000001] is an instance-identifier targeting the
notification-parameters/history node (SID 62060) for air-temperature (type
10000001). The value {62060: {6: 120, 4: 10, 2: 1}} sets three history
parameters using delta SIDs relative to 62060: step=120 s, max-samples=10,
and encoding=delta (value 1).¶
An iPATCH is used the same way to configure a sensor-alert: the client sets the low and high thresholds, and activates the alert, as shown in Figure 12.¶
CoAP Request:
Non-Confirmable, iPATCH, MID:42312
Token: 2490
Opt #1: Uri-Path: c
Opt #2: Content-Format: 140 (application/yang-data+cbor;id=sid)
Payload: 24 bytes
{[62068, 10000001]: {62068: {1: true, 4: 350, 5: 50}}}
CoAP Response:
Non-Confirmable, 2.04 Changed, MID:65150
Token: 2490
The iPATCH key [62068, 10000001] targets the notification-parameters/
sensor-alert node (SID 62068) for air-temperature. The value
{62068: {1: true, 4: 350, 5: 50}} sets three leaves using delta SIDs
relative to 62068: active=true (delta 1), t-max=350 (delta 4), and
t-min=50 (delta 5). Since air-temperature has precision=1 (as in
Figure 8), these raw integers represent 35.0 Cel and
5.0 Cel: the device raises an alert once the temperature leaves that
range.¶
The client then initiates an Observe subscription with a FETCH on the
notification stream resource /s, as shown in Figure 13.¶
CoAP Request:
Non-Confirmable, FETCH, MID:42311
Token: 248f
Opt #1: Observe: 0
Opt #2: Uri-Path: s
Opt #3: Content-Format: 140 (application/yang-data+cbor;id=sid)
Opt #4: Accept: 140 (application/yang-data+cbor;id=sid)
Payload: 9 bytes
[62048, 10000001]
CoAP Response (subscription acknowledgment):
Non-Confirmable, 2.05 Content, MID:65148
Token: 248f
Opt #1: Observe: 0
Opt #2: Content-Format: 140 (application/yang-data+cbor;id=sid)
Payload: 1 byte
{}
CoAP Notification:
Non-Confirmable, 2.05 Content, MID:65149
Token: 248f
Opt #1: Observe: 1
Opt #2: Content-Format: 140 (application/yang-data+cbor;id=sid)
Payload: 16 bytes
{62048: [189, 14, -3, 1, 5, 7, 3, 2, 1, 2]}
The FETCH body [62048, 10000001] subscribes directly to the
time-series "values" leaf-list (SID 62048) for air-temperature, rather
than the whole time-series entry: since the subscription is already
transducer-specific, echoing the "type" back in every notification would
only repeat what the client already knows. The server acknowledges with
an empty map {}. The notification then carries the values list under
its absolute SID, [189, 14, -3, 1, 5, 7, 3, 2, 1, 2], using delta
encoding: 189 is the absolute reference (18.9°C, precision=1), and each
subsequent value is the difference from the previous one.¶
The client decodes the delta-encoded time-series values and displays the result as shown in Figure 14:¶
[9] air-temperature: 18.9 Cel (09:57:42) [9] air-temperature: 20.3 Cel (09:59:42) [9] air-temperature: 20.0 Cel (10:01:42) [9] air-temperature: 20.1 Cel (10:03:42) [9] air-temperature: 20.6 Cel (10:05:42) [9] air-temperature: 21.3 Cel (10:07:42) [9] air-temperature: 21.6 Cel (10:09:42) [9] air-temperature: 21.8 Cel (10:11:42) [9] air-temperature: 21.9 Cel (10:13:42) [9] air-temperature: 22.1 Cel (10:15:42)
The whole batch of samples is decoded from a single notification message. Since the device timestamp is not carried in this payload, the client reconstructs one from the configured "step" (120 s): the last sample is stamped with the local time at which the notification was received, and earlier samples are stepped back by 120 s each, oldest first.¶
The examples throughout this document use CBOR payloads keyed by SIDs
(application/yang-data+cbor;id=sid), which is the compact
representation intended for constrained links. Ontologies generally use a
textual representation, and interacting with a human user likewise
requires presenting the information with understandable identifiers (see
the example in Figure 7).¶
To perform this translation, the recipient of a coreconf-m2m message needs access to the .sid file and, in some cases, the YANG file, in order to recover the transducers' default values. [I-D.toutain-core-sid-encoding] defines a way to use DNS to locate a SID and a YANG file.¶
An experimental service running with the "sid.yt" suffix has been deployed. It allows finding the SID and YANG files associated with a specific SID.¶
The goal of coreconf-m2m is to discover the resources managed by the device and to provide a lightweight transport for information coming from a sensor or dedicated to an actuator; it is not itself an ontology. This section discusses how the elements it carries relate to some existing ontologies and APIs, so that a gateway or application can align them with a richer semantic model when needed, once translated to textual identifiers as described in Section 6.¶
Before looking at how YANG information can be included into an existing ontology, it may also be important to enrich the ontology with coreconf-m2m information concerning the device's CORECONF capabilities; this may be helpful for future interactions with the device.¶
This document therefore assumes a
small ccm2m: extension vocabulary, applicable regardless of which
ontology (SOSA, SAREF, SensorThings, ...) a device or transducer is
otherwise mapped to. Appendix F gives its full definition,
as an OWL ontology in Turtle.¶
The ccm2m ontology defines a single datatype, ccm2m:SID: a non-negative
integer that uniquely identifies a YANG node, identity, or feature within
a SID file, used instead of a plain xsd:nonNegativeInteger so that the
nature of the value is self-documenting.¶
It defines properties attached to a sosa:Platform, giving the coordinates needed to reach and query the device:¶
ccm2m:coapEndpoint is the CoAP access address of the device (e.g.
coap://[::1]);¶
ccm2m:accessProtocol is the default applicative protocol used on
that platform (e.g. "coreconf");¶
ccm2m:bootstrapSid is the SID used at bootstrap to discover the YANG
model exposed by the platform (see Section 5.1); it is
multi-valued, since several bootstrap SIDs are possible.¶
It also defines properties attached to a sosa:Sensor, tying it back to its YANG identity:¶
ccm2m:type carries the transducer's identity SID (see
Figure 7);¶
ccm2m:id is the instance identifier of the sensor on its platform,
distinguishing multiple sensors of the same type;¶
ccm2m:precision and ccm2m:outputUnit carry the default-precision
and default-unit declared on that identity, or their overrides (see
Section 3.2.3), and are declared once rather than repeated
on every observation.¶
Finally, it models the applicative operations that can be performed on a device or transducer, i.e. its CORECONF capabilities, deliberately kept outside SOSA/SSN's Sampling/Sensing semantics:¶
ccm2m:Control is an applicative operation exposed over CORECONF/CoAP
(a single-value read, a history-buffer notification subscription, or a
threshold-alert notification subscription), linked to a Platform or
Sensor via ccm2m:hasControl. Its ccm2m:controlType names the
operation (e.g. "read-single", "subscribe-history",
"reset-all-stat"), ccm2m:targetSid gives the SID of the YANG
leaf/container it targets, and ccm2m:coapMethod, ccm2m:coapPath,
and ccm2m:contentFormat describe how to invoke it over CoAP;¶
ccm2m:SetupNotification is a Control that configures notification
parameters on a transducer before subscribing (an iPATCH on /c); its
ccm2m:hasParam links to one or more ccm2m:NotificationParam, each
giving, via ccm2m:paramName and ccm2m:paramSid, the YANG leaf name
and SID of one configurable parameter (e.g. "step", "encoding"; see
Figure 5).¶
Without these properties, a graph would describe what a platform
observes, but not how to obtain the corresponding coreconf-m2m data, nor
how to act on it. The following subsections use this ccm2m: vocabulary
to bridge the elements of coreconf-m2m to specific ontologies.¶
The Sensor, Observation, Sample, and Actuator (SOSA) ontology is maintained jointly by the W3C and the Open Geospatial Consortium (OGC), as part of the broader SSN (Semantic Sensor Network) ontology [SOSA]. It defines a sosa:Platform as an entity that hosts other objects, such as sensors, actuators, or samplers. The device described by the "characteristics" sub-tree (Section 3.2.2) maps to a sosa:Platform, and each of its transducers maps to a sosa:Sensor or a sosa:Actuator hosted by that platform.¶
SOSA also defines a sosa:Sampler type, but it corresponds more to sample-taking than to measurement, so it may be present in the ontology without requiring a link with the coreconf-m2m YANG data model.¶
Figure 15 shows a generic, abstract example of such a platform, expressed in the Turtle RDF syntax, independently of any particular naming scheme.¶
@prefix sosa: <http://www.w3.org/ns/sosa/> .
<platform> a sosa:Platform ;
sosa:hosts <object> .
<object> a sosa:Sensor ;
sosa:isHostedBy <platform> ;
sosa:observes <property> .
To turn this generic pattern into a concrete graph, the platform's IRI
can be derived from the device's DNS name, e.g. station.example.com,
and the IRI of each hosted object can in turn be derived from that name
and from its entry in "/bootstrap/inventory", e.g.
station.example.com/sensor/air-temperature.¶
Figure 16 gives the CDDL [RFC8610] for constructing such an object IRI from the platform's DNS name and an inventory entry, using the ".cat" control operator [RFC9165] to express the concatenation of its text fragments.¶
category = "sensor" / "actuator"
object-iri = platform-name
.cat "/"
.cat category
.cat "/"
.cat transducer-name
platform-name = tstr
transducer-name = tstr
For example, with platform-name = "station.example.com", category =
"sensor", and transducer-name = "air-temperature", the resulting
object-iri is station.example.com/sensor/air-temperature, matching the
example already given above. Figure 17 shows this
instantiation in Turtle.¶
@prefix sosa: <http://www.w3.org/ns/sosa/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix ccm2m: <https://ccm2m.example/ns#> .
@prefix unit: <http://qudt.org/vocab/unit/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix geo: <http://www.w3.org/2003/01/geo/wgs84_pos#> .
<https://station.example.com> a sosa:Platform ;
sosa:hosts <https://station.example.com/sensor/air-temperature> ;
geo:lat "48.1173"^^xsd:decimal ;
geo:long "-1.6778"^^xsd:decimal ;
ccm2m:accessProtocol "coreconf" ;
ccm2m:coapEndpoint "coap://[2001:db8::1]"^^xsd:anyURI ;
ccm2m:bootstrapSid "62002"^^ccm2m:SID .
<https://station.example.com/sensor/air-temperature> a sosa:Sensor ;
sosa:isHostedBy <https://station.example.com> ;
sosa:observes <https://station.example.com/property/air-temperature> ;
ccm2m:type "10000001"^^ccm2m:SID ;
ccm2m:precision 1 ;
ccm2m:outputUnit unit:DEG_C .
<https://station.example.com/property/air-temperature> a sosa:ObservableProperty ;
rdfs:label "air-temperature" ;
sosa:isObservedBy <https://station.example.com/sensor/air-temperature> .
The ccm2m: properties used above are not part of SOSA; see
Section 7.1 for their meaning. SOSA has no
native geo-location property either; geo:lat/geo:long, from the
lightweight W3C Basic Geo (WGS84) vocabulary, are commonly paired with
SOSA for this purpose, and correspond to
"characteristics/geo-location" (Section 3.2.2) in the
YANG model.¶
A sosa:Sensor or sosa:Actuator is not limited to being observed or
acted upon: the actions actually available on the corresponding
transducer are attached to it as ccm2m:Control instances, via
ccm2m:hasControl. These cover reading the current value
("read-single"), reading or resetting its statistics ("read-stat",
"reset-stat", "reset-all-stat"), configuring and subscribing to history
or threshold-alert notifications ("subscribe-history", "subscribe-threshold"),
and, for an actuator, writing a new value ("instant-write").¶
Figure 18 shows the air-temperature sosa:Sensor with its
ccm2m:Control instances attached via ccm2m:hasControl, using the
SIDs introduced in Section 5.2 and Section 5.3.¶
@prefix sosa: <http://www.w3.org/ns/sosa/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix ccm2m: <https://ccm2m.example/ns#> .
@prefix unit: <http://qudt.org/vocab/unit/> .
@prefix ex: <https://station.example.com/sensor/air-temperature/> .
ex: a sosa:Sensor ;
sosa:isHostedBy <https://station.example.com> ;
sosa:observes <https://station.example.com/property/air-temperature> ;
ccm2m:type "10000001"^^ccm2m:SID ;
ccm2m:precision 1 ;
ccm2m:outputUnit unit:DEG_C ;
ccm2m:hasControl ex:control/read-single ,
ex:control/reset-stat ,
ex:control/setup-history ,
ex:control/subscribe-history .
ex:control/read-single a ccm2m:Control ;
ccm2m:controlType "read-single" ;
ccm2m:coapMethod "FETCH" ;
ccm2m:coapPath "/c" ;
ccm2m:targetSid "62077"^^ccm2m:SID .
ex:control/reset-stat a ccm2m:Control ;
rdfs:label "reset statistics" ;
ccm2m:controlType "reset-stat" ;
ccm2m:coapMethod "POST" ;
ccm2m:coapPath "/c" ;
ccm2m:targetSid "62078"^^ccm2m:SID .
ex:control/setup-history a ccm2m:SetupNotification ;
ccm2m:controlType "setup-history" ;
ccm2m:coapMethod "iPATCH" ;
ccm2m:coapPath "/c" ;
ccm2m:targetSid "62060"^^ccm2m:SID ;
ccm2m:hasParam ex:control/setup-history/step ,
ex:control/setup-history/encoding .
ex:control/setup-history/step a ccm2m:NotificationParam ;
ccm2m:paramName "step" ;
ccm2m:paramSid "62066"^^ccm2m:SID .
ex:control/setup-history/encoding a ccm2m:NotificationParam ;
ccm2m:paramName "encoding" ;
ccm2m:paramSid "62062"^^ccm2m:SID .
ex:control/subscribe-history a ccm2m:Control ;
rdfs:label "start history notifications" ;
ccm2m:controlType "subscribe-history" ;
ccm2m:coapMethod "FETCH+Observe" ;
ccm2m:coapPath "/s" ;
ccm2m:targetSid "62048"^^ccm2m:SID .
ex:control/read-single is the same as described in Figure 8
(SID 62077, "/transducers/transducer/quantity/value"). ex:control/reset-stat
is the same as the "reset-stats" action (SID 62078,
"/transducers/transducer/reset-stats"), invoked with a POST to reset the
statistics of this single transducer, as opposed to the "reset-all-stat"
control on the platform which resets every transducer at once via the
top-level "reset-stats" RPC. ex:control/setup-history is the same as
the iPATCH described in Figure 11 (SID 62060,
"/transducers/transducer/notification-parameters/history"), with its two
configurable parameters "step" (SID 62066) and "encoding" (SID 62062);
it only configures the notification and does not itself start it.
ex:control/subscribe-history is the control that actually starts the
history stream: it is the same as the FETCH+Observe described in
Figure 13 (SID 62048, "/history/time-series/values").
A client that already knows this graph can therefore issue the
corresponding CoAP requests without a
separate FETCH+parse round trip on the YANG/SID files.¶
ccm2m:Control has no dedicated "stop" instance: as described in
Section 3.3, a subscription started by subscribe-history ends when
the client explicitly deregisters (a GET/FETCH with the Observe option
set to 1, using the same token as subscribe-history), or when the server
stops receiving acknowledgments for its Confirmable notifications. No
separate SID-targeted control is needed for this, since it operates at
the CoAP transport level rather than on a specific YANG resource.¶
"category" is one of the two roles a transducer can be given, taken from its default-category (or category-override, see Section 3.1). A transducer whose category is "sensor-actuator" yields two object IRIs, one under each role, since it is instantiated as both a sosa:Sensor and a sosa:Actuator.¶
Each value received from a transducer, whether from a plain FETCH or decoded from a history notification, is stored as a sosa:Observation. SOSA distinguishes two timestamps: sosa:phenomenonTime, when the device actually took the measurement, and sosa:resultTime, when the result was obtained (here, stored) by the client.¶
For a plain FETCH of "quantity", "timestamp-source" indicates which one to use as phenomenonTime: if it is "source", the device's own "timestamp" is used; if it is "receiver", the device did not supply one, and the receiver's own local time at reception is used instead. For a single FETCH the two nearly coincide.¶
For a history notification, phenomenonTime is reconstructed per sample from "step" as described in Section 3.3, while resultTime is the single instant at which the whole batch was received. Figure 19 shows three such observations, taken from the air-temperature values already decoded in Figure 14: all three share the same resultTime, since they were delivered together in one notification.¶
@prefix sosa: <http://www.w3.org/ns/sosa/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ex: <https://station.example.com/sensor/air-temperature/> .
ex:observation/1 a sosa:Observation ;
sosa:madeBySensor ex: ;
sosa:observedProperty <https://station.example.com/property/air-temperature> ;
sosa:hasSimpleResult "18.9"^^xsd:decimal ;
sosa:phenomenonTime "2026-09-02T09:57:42Z"^^xsd:dateTime ;
sosa:resultTime "2026-09-02T10:15:45Z"^^xsd:dateTime .
ex:observation/2 a sosa:Observation ;
sosa:madeBySensor ex: ;
sosa:observedProperty <https://station.example.com/property/air-temperature> ;
sosa:hasSimpleResult "20.3"^^xsd:decimal ;
sosa:phenomenonTime "2026-09-02T09:59:42Z"^^xsd:dateTime ;
sosa:resultTime "2026-09-02T10:15:45Z"^^xsd:dateTime .
ex:observation/3 a sosa:Observation ;
sosa:madeBySensor ex: ;
sosa:observedProperty <https://station.example.com/property/air-temperature> ;
sosa:hasSimpleResult "20.0"^^xsd:decimal ;
sosa:phenomenonTime "2026-09-02T10:01:42Z"^^xsd:dateTime ;
sosa:resultTime "2026-09-02T10:15:45Z"^^xsd:dateTime .
A value written to a transducer via the "instant-write" control (see Section 7.2.2) is not an observation: it is a command sent to the device, not a measurement received from it. SOSA models this with sosa:Actuation rather than sosa:Observation: sosa:hasResult carries the value that was written, sosa:actsOnProperty identifies the actuatable property that was acted upon, and sosa:resultTime is when the command was issued by the client — there is no phenomenonTime, since nothing was measured.¶
None of the ATMOS41 transducers used throughout this document is an
actuator (all declare default-category: "sensor"), so
Figure 20 illustrates the pattern with a fictitious
device instead: a pump whose rotation speed is set through an
"instant-write" command.¶
@prefix sosa: <http://www.w3.org/ns/sosa/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ccm2m: <https://ccm2m.example/ns#> .
@prefix unit: <http://qudt.org/vocab/unit/> .
@prefix ex: <https://pump.example.com/actuator/rotation-speed/> .
ex: a sosa:Actuator ;
sosa:isHostedBy <https://pump.example.com> ;
sosa:actsOnProperty <https://pump.example.com/property/rotation-speed> ;
ccm2m:type "PPPPPP"^^ccm2m:SID ;
ccm2m:precision 0 ;
ccm2m:outputUnit unit:REV-PER-MIN ;
ccm2m:hasControl ex:control/instant-write .
ex:control/instant-write a ccm2m:Control ;
ccm2m:controlType "instant-write" ;
ccm2m:coapMethod "iPATCH" ;
ccm2m:coapPath "/c" ;
ccm2m:targetSid "62077"^^ccm2m:SID .
ex:actuation/1 a sosa:Actuation ;
sosa:madeByActuator ex: ;
sosa:actsOnProperty <https://pump.example.com/property/rotation-speed> ;
sosa:hasResult "1500"^^xsd:integer ;
sosa:resultTime "2026-09-07T14:22:10Z"^^xsd:dateTime .
"PPPPPP" is a placeholder: this pump does not exist in the atmos module
used throughout this document, so its identity has no real SID (unlike
10000001 for air-temperature, defined in atmos). ccm2m:targetSid,
however, is the real SID 62077 for "/transducers/transducer/quantity/value":
this leaf is defined in coreconf-m2m itself, not in a device-specific
module, so it is the same structural node used to read air-temperature's
current value in Section 7.2.2 — writing to it with an iPATCH, targeting
this pump's own instance via its (fictitious) type, is the write
counterpart of that same "read-single" FETCH.¶
The pattern is otherwise symmetrical to Figure 19: sosa:Observation, sosa:observedProperty and sosa:hasSimpleResult become sosa:Actuation, sosa:actsOnProperty and sosa:hasResult, and sosa:madeBySensor becomes sosa:madeByActuator.¶
Unlike SOSA/SSN, the OGC SensorThings API [SensorThings] is not a
Semantic Web ontology: it is a structured, relational-like data model,
exposed through a REST API inspired by OData, using plain JSON rather
than RDF or JSON-LD. It defines a fixed set of entity types (Thing,
Location, HistoricalLocation, Datastream, Sensor, ObservedProperty,
Observation, FeatureOfInterest, and, for actuation, Actuator, Task, and
TaskingCapability) linked by navigation properties, much like foreign
keys between tables; most implementations are in fact backed by a
relational database. Queries use OData-style $filter, $expand, and
$select, rather than SPARQL. Its data model is nonetheless closely
related to SOSA/SSN's concepts (Datastream/Sensor/Observation mirror
sosa:Sensor/sosa:Observation), so the mapping from coreconf-m2m largely
follows the same pattern as [SOSA], but produces structured, tabular
entities rather than RDF triples.¶
Figure 21, Figure 24, and Figure 25
transpose the SOSA example of Section 7.2.2 and Figure 19
into SensorThings JSON entities, using the same station.example.com
platform and air-temperature transducer. SensorThings has no equivalent
of RDF's open, arbitrary predicates, so the ccm2m: extension is added
as ancillary data instead: each entity's properties field is a
free-form JSON object, into which the same ccm2m: keys used in the
SOSA examples are placed as ordinary object members.¶
GET /Things(1)
{
"@iot.id": 1,
"name": "station.example.com",
"description": "CORECONF-M2M platform station.example.com",
"properties": {
"ccm2m:accessProtocol": "coreconf",
"ccm2m:bootstrapSid": 62002,
"ccm2m:coapEndpoint": "coap://[2001:db8::1]"
}
}
A Thing's properties bag is where the ccm2m: extension lives: there
is no dedicated Thing property for a CoAP endpoint or a bootstrap SID, so
they are carried as ancillary data instead, the same role played by
ccm2m:coapEndpoint and ccm2m:bootstrapSid on a sosa:Platform.¶
Unlike sosa:hosts, which links a sosa:Platform directly to the sosa:Sensor instances it hosts, SensorThings has no direct Thing-to-Sensor navigation link: a Sensor is only reachable through the Datastream that uses it. Finding the sensors (and, symmetrically, the actuators) associated with a Thing therefore takes an extra hop:¶
i.e. Thing → Datastream → Sensor, rather than Thing → Sensor directly.
The symmetric path for actuation is Thing → TaskingCapability →
Actuator, via /Things(1)/TaskingCapabilities and
/TaskingCapabilities(x)/Actuator.¶
Unlike SOSA, SensorThings has a dedicated entity for geo-location:
Location, reached via Thing/Locations (a Thing can have more than
one, and a Location can be shared by several Things).¶
GET /Things(1)/Locations
{
"value": [{
"@iot.id": 1,
"name": "station.example.com location",
"encodingType": "application/vnd.geo+json",
"location": {
"type": "Point",
"coordinates": [-1.6778, 48.1173]
}
}]
}
This corresponds to the same "characteristics/geo-location" (Section 3.2.2) as the geo:lat/geo:long pair used in Figure 17. HistoricalLocations additionally tracks changes of Location over time, for a mobile device.¶
GET /Sensors(1)
{
"@iot.id": 1,
"name": "air-temperature",
"description": "air-temperature on station.example.com",
"encodingType": "application/pdf",
"metadata": "https://metergroup.com/documents/ATMOS41-Manual.pdf",
"properties": {
"ccm2m:type": 10000001,
"ccm2m:precision": 1
}
}
GET /Datastreams(1)
{
"@iot.id": 1,
"name": "station.example.com/air-temperature",
"description": "air-temperature measured by station.example.com",
"observationType":
"http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement",
"unitOfMeasurement": {
"definition": "http://qudt.org/vocab/unit/DEG_C",
"name": "http://qudt.org/vocab/unit/DEG_C",
"symbol": "Cel"
}
}
A Sensor's encodingType/metadata pair describes its datasheet, not
its identity: metadata is a URL to that document (or the document
itself, for a text-based encodingType), and encodingType says what
format it is in — application/pdf and SensorML are the two most common
in practice. The Sensor's ccm2m:type plays the same role as on a sosa:Sensor.
Unlike SOSA, where ccm2m:outputUnit had to be added because SOSA has no
native concept of unit, a Datastream already carries unitOfMeasurement
natively (a QUDT IRI, symbol, and name), so no ccm2m: property is
needed for it here. An ObservedProperty entity (not shown), linked from
the Datastream, plays the role of sosa:ObservableProperty.¶
GET /Datastreams(1)/Observations?$top=3&$orderby=phenomenonTime desc
{
"value": [
{
"@iot.id": 1,
"phenomenonTime": "2026-09-02T09:57:42Z",
"resultTime": "2026-09-02T10:15:45Z",
"result": 18.9
},
{
"@iot.id": 2,
"phenomenonTime": "2026-09-02T09:59:42Z",
"resultTime": "2026-09-02T10:15:45Z",
"result": 20.3
},
{
"@iot.id": 3,
"phenomenonTime": "2026-09-02T10:01:42Z",
"resultTime": "2026-09-02T10:15:45Z",
"result": 20.0
}
]
}
These are the same three samples as Figure 19: result
plays the role of sosa:hasSimpleResult, already a plain JSON number
rather than a typed literal, and phenomenonTime/resultTime carry the
same distinction as sosa:phenomenonTime/sosa:resultTime — all three
share one resultTime, since they were delivered together in one history
notification, while phenomenonTime is reconstructed per sample from
"step".¶
For actuation, SensorThings has a separate set of entities (the "Tasking Core" profile): Actuator, TaskingCapability, and Task, mirroring Sensor, Datastream, and Observation respectively. Figure 26 transposes the fictitious pump of Figure 20 into these entities.¶
GET /Actuators(1)
{
"@iot.id": 1,
"name": "rotation-speed",
"description": "rotation-speed actuator on pump.example.com",
"encodingType": "application/pdf",
"metadata": "https://pump.example.com/docs/pump-datasheet.pdf",
"properties": {
"ccm2m:type": "PPPPPP"
}
}
GET /TaskingCapabilities(1)
{
"@iot.id": 1,
"name": "pump.example.com/rotation-speed",
"description": "Set the pump's rotation speed",
"taskingParameters": {
"type": "object",
"properties": {
"rotation-speed": { "type": "integer" }
}
},
"properties": {
"ccm2m:targetSid": 62077
}
}
GET /TaskingCapabilities(1)/Tasks
{
"value": [{
"@iot.id": 1,
"creationTime": "2026-09-07T14:22:10Z",
"taskingParameters": { "rotation-speed": 1500 }
}]
}
The Actuator plays the same role as the Sensor of Figure 24,
including the same application/pdf/metadata pattern for its
datasheet. ccm2m:targetSid on the TaskingCapability plays the role of
ccm2m:targetSid on the SOSA ccm2m:Control for "instant-write": both
name the SID to iPATCH to actually issue the command. A Task's
taskingParameters plays the role of sosa:hasResult, and creationTime
of sosa:resultTime; as with the Sensor's ccm2m:type, "PPPPPP" is a
placeholder, since this pump has no real identity SID.¶
CORECONF/CoAP and the SensorThings/FROST database are both, in essence, REST-style interfaces: a small set of operations (read, write, query, subscribe) applied to addressable resources. Each can therefore be documented as a set of typed, named operations and exposed to an AI agent as an MCP (Model Context Protocol) server, rather than requiring the agent to construct raw CoAP/CBOR or FROST query requests itself.¶
This document's companion implementation uses two such servers, kept separate because they act on two different things: one performs live CoAP actions against the physical device, the other queries and edits the structured historical data already stored in FROST.¶
The "coreconf-m2m" MCP server wraps live device actions, each tool resolving what it needs (the platform's CoAP endpoint, the sensor's identity/instance SIDs, the target field SID) from the FROST database before issuing the corresponding CoAP request:¶
read_instant(hostname, sensor) — FETCH the current value, already
scaled by the sensor's ccm2m:precision;¶
read_stat(hostname, sensor) — FETCH accumulated statistics
(min/max/mean/median/stdev/sample-count), scaled;¶
start_history_notify(hostname, sensor, step_ms, max_samples,
check_interval) — iPATCH to configure, then FETCH+Observe to start, a
history-notification subscription; incoming samples are written to
FROST as Observations as they arrive;¶
stop_history_notify(hostname, sensor) — cancel a running
subscription;¶
list_active_history_subscriptions() — list every subscription
currently running, across clients.¶
The "frost-sensorthings" MCP server wraps the SensorThings REST API itself, generically (not tied to the ccm2m ontology), respecting FROST's own query syntax, on any of the entity sets of Section 7.3:¶
list_service_capabilities() — the entity sets this FROST-Server
instance exposes;¶
query_entities(entity_set, filter, expand, select, orderby, top,
skip, count) — list/filter an entity set, e.g. query_entities(
"Observations", filter="phenomenonTime gt 2026-07-01T00:00:00Z",
orderby="phenomenonTime desc", top=20);¶
get_entity(entity_set, entity_id, expand, select) — get one entity
by its @iot.id;¶
get_related(entity_set, entity_id, relation, ...) — navigate a
relationship, e.g. get_related("Things", 1, "Datastreams") (see
Figure 22);¶
create_entity, update_entity, delete_entity — write access.¶
Because both servers resolve identities (SIDs, hostnames, sensor names)
against the same FROST database, an agent can freely mix tools from
both — e.g. list a Thing's Datastreams via get_related, then call
read_instant for one of them — without separately tracking SID files
or CoAP endpoints itself.¶
Figure 27 illustrates this with a concrete question: "what is the temperature right now in Rennes?" The agent first uses FROST to turn "Rennes" into a device and a sensor, then uses coreconf-m2m to read that sensor live; the result is written back to FROST as it comes in, so a later question about the same value would find it already there instead of triggering a new CoAP exchange.¶
Device MCP CoreCONF MCP Frost LLM
| | | |
query_entities("Things",
filter: near Rennes)
| | <-------------------|
| | | |
Thing, ccm2m:coapEndpoint
| | |------------------->
| | | |
get_related(Thing,
"Datastreams")
| | <-------------------|
| | | |
Sensor SID, precision
| | |------------------->
| | | |
read_instant(hostname, sensor)
| <---------------------------------------|
| | | |
resolve endpoint/SIDs
| |-------------------> |
| | | |
endpoint, type SID
| <-------------------| |
| | | |
FETCH quantity/value
<-----------------| | |
| | | |
2.05 Content (raw value)
|-----------------> | |
| | | |
create Observation
(store result)
| |-------------------> |
| | | |
scaled value + unit
| |--------------------------------------->
| | | |
The LLM then answers the user directly from the value returned in the last step, e.g. "It's 19.6 C in Rennes".¶
It should be noted that the LLM selects the appropriate sensor from its name, "air-temperature": without the mapping between the identityref SID and the associated name, the LLM would not have been able to retrieve the information. More exhaustively, the LLM may also have access to the YANG data model describing the device, and use its descriptions to gain a better understanding of the device.¶
Official YANG SIDs [I-D.ietf-core-sid] are globally unique integers, but that global scope makes them larger than strictly necessary on a link where only two endpoints ever talk to each other and no third-party CORECONF implementation needs to be interoperable with. [I-D.toutain-core-private-sid-translation] defines a mechanism for such closed deployments: each official SID is transformed, purely arithmetically, into a small negative integer, as a "private SID", which encodes in a smaller CBOR sequence.¶
The transformation is:¶
privateSID = (offset - 1) - (current_sid - entry_point)¶
where entry_point is the lowest SID officially allocated to the
YANG Data Model the SID belongs to, and offset is a non-positive
integer that shifts the model's private range so that it does not
overlap with another translated model's range. With offset = 0, the
node at entry_point maps to -1, the next one to -2, and so on:
offset lets a second, third, etc. model be translated in the same
session by shifting each one's private range further into negative
territory, without changing the formula itself or which module a
given negative SID belongs to. Both endpoints MUST share the same
entry_point and offset per model for the translation to be
consistent.¶
More generally, a coreconf-m2m deployment involves at least two kinds of YANG Data Models translated together: the coreconf-m2m model itself, together with augmentations it may receive, and one or more device-description models, such as atmos in this document's examples. If coreconf-m2m is eventually standardized as an IETF model, its SIDs will sit in the low thousands, while device-description models, being commercial and vendor-specific, are expected to be assigned SIDs in a Registrar's block or a Private Enterprise Number range [RFC9371] both starting much higher. The device-description model SHOULD therefore be given the smaller offset (typically 0, the deepest range), and the coreconf-m2m model the larger one (or not translated). Appendix H works out this recommendation on two concrete examples.¶
A SID that falls outside every configured (entry_point, offset)
pair is simply sent as its official value, without breaking any SID that is left out.¶
The traffic profile given in Section 4.1 recommends limiting exchanges to FETCH and iPATCH. This limits the number of rules needed to compress coreconf-m2m traffic with SCHC [RFC8724]. Appendix G gives an example where two rules are used to cover the bidirectional traffic for FETCH and iPATCH (Rules 0 and 1). Another rule is related to notifications, carrying an Observe option (Rule 2).¶
It is important to also cover error messages. The example defines one generic CoAP rule to cover any TYPE and CODE, without options, corresponding to error notifications (Rule 3), and one rule for ICMP traffic, to inform the server that the client is no longer active (Rule 4).¶
This document has shown that it is possible to seamlessly interconnect a YANG data model to the ontologies used by domain professionals.¶
Devices cannot directly use such an ontology representation themselves, since it is too verbose for a constrained device.¶
RESTCONF and NETCONF, using JSON or XML, are also too verbose, so the benefit of mapping directly from them to an ontology is limited.¶
CORECONF is constrained, and this constrained representation is what lets a device benefit from the ontology mapping without paying its verbosity cost.¶
The ontology is used to store all the information concerning the systems.¶
CORECONF uses the ontology information to form its requests.¶
Naming is very important, and DNS will play an important role in automatically mapping a SID into a name.¶
Names are used both to form names in the ontology and to allow an LLM to identify resources.¶
Interoperability is possible since coreconf-m2m offers a strict data representation of the information, and allows a systematic conversion into the ontology.¶
Based on the coreconf-m2m model, a device manufacturer only has to publish its data model, with its identityref identities, for that device to be included into the ontology and be understood by humans or by an LLM.¶
CORECONF operations over CoAP MUST be secured using either DTLS [RFC6347] or OSCORE [RFC8613]. In M2M scenarios where a central manager is absent, the trust model requires particular attention.¶
This document registers the following YANG module in the "YANG Module Names" registry [RFC7950]:¶
| Name | Namespace | Prefix | Reference |
|---|---|---|---|
| coreconf-m2m | urn:ietf:params:xml:ns:yang:coreconf-m2m | cm2m | This document |
This document requests the SID range starting at entry-point TBD with a size of 100, as defined in [I-D.ietf-core-sid].¶
Transducer identites MUST be sperated from this document and defined in anjother YANG module.¶
module: coreconf-m2m
+--ro bootstrap
| +--ro reference-epoch? uint64
| +--ro uptime uint64
| +--ro minimal-step? uint32
| +--ro inventory* [type]
| +--ro type identityref
| +--ro unit-override? string
| +--ro precision-override? uint8
| +--ro category-override? enumeration
+--rw characteristics
| +--rw geo-location
| | +--rw reference-frame
| | | +--rw alternate-system? string {alternate-systems}?
| | | +--rw astronomical-body? string
| | | +--rw geodetic-system
| | | +--rw geodetic-datum? string
| | | +--rw coord-accuracy? decimal64
| | | +--rw height-accuracy? decimal64
| | +--rw (location)?
| | | +--:(ellipsoid)
| | | | +--rw latitude? decimal64
| | | | +--rw longitude? decimal64
| | | | +--rw height? decimal64
| | | +--:(cartesian)
| | | +--rw x? decimal64
| | | +--rw y? decimal64
| | | +--rw z? decimal64
| | +--rw velocity
| | | +--rw v-north? decimal64
| | | +--rw v-east? decimal64
| | | +--rw v-up? decimal64
| | +--rw timestamp? yang:date-and-time
| | +--rw valid-until? yang:date-and-time
| +--rw name? string
| +--rw version? string
| +--rw identifier? string
| +--rw description? string
| +--rw manufacturer? string
| +--rw model? string
| +--rw hosted-by? string
| +--rw installation-time? uint64
+--rw transducers
+--rw transducer* [type]
+--rw type identityref
+--ro quantity
| +--ro value? int64
| +--ro timestamp? uint64
| +--ro timestamp-source? enumeration
+--ro statistics
| +--ro min? int64
| +--ro max? int64
| +--ro mean? int64
| +--ro median? int64
| +--ro stdev? uint64
| +--ro sample-count? uint64
+--rw notification-parameters
| +--rw history
| | +--ro active? boolean
| | +--rw step? uint32
| | +--rw precision? uint8
| | +--rw max-samples? uint32
| | +--rw time-period? uint32
| | +--rw encoding? encoding-type
| | +--rw max-payload? uint32
| +--rw sensor-alert
| | +--ro active? boolean
| | +--rw t-min? int32
| | +--rw t-max? int32
| | +--rw hysteresis? uint8
| | +--rw dampening? uint32
| +--rw check-interval? uint16
+---x reset-stats
rpcs:
+---x reset-stats
notifications:
+---n history
| +--ro last? boolean
| +--ro time-series* [type]
| +--ro type identityref
| +--ro values* int64
| +--ro internal
| +--ro last-update? uint64
| +--ro start-time? uint64
| +--ro messages-sent? uint64
+---n sensor-alert
+--ro target* [type]
+--ro type identityref
+--ro value? int64
<CODE BEGINS> file "coreconf-m2m@2026-09-01.yang"
module coreconf-m2m {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:coreconf-m2m";
prefix ccm2m;
import ietf-geo-location {
prefix geo;
reference "RFC 9179";
}
organization "IETF";
contact
"Internet Engineering Task Force (IETF)
https://www.ietf.org/";
description
"YANG data model for a generic M2M CoMI weather station.
This model defines the operational state data for sensor readings.";
revision 2026-09-01 {
description
"Move container statistics out of container quantity, making it a
sibling of quantity under list transducer. Statistics accumulate
over a window and are not part of the current measurement, so
nesting them under quantity conflated two different lifetimes and
cost one extra level of delta encoding on every statistics access.
This reverts the nesting introduced in revision 2026-03-29.";
}
revision 2026-08-28 {
description
"Rename the 'states' container to 'bootstrap', since it holds
operational data determined once at bootstrap time.
Split the transducer list: unit-override, precision-override and
category-override are moved out of /transducers/transducer into a
new /bootstrap/inventory list (also keyed by type). This list
describes the fixed configuration of each transducer instance,
determined at bootstrap and not expected to change afterwards,
separately from the transducer's runtime data (quantity,
statistics, notification parameters) which stays in
/transducers/transducer.
Hoist check-interval from notification-parameters/history and
notification-parameters/sensor-alert to notification-parameters:
it was defined identically in both containers, and a single
CON/NON cadence now applies to both notification streams.";
}
revision 2026-08-24 {
description
"Remove type from list keys; id alone now identifies a transducer
instance, distinguishing multiple actuators of the same type. This
reduces the key size.
Add a reference-epoch leaf in the state container; other epochs in
the model are relative to this one.
Move all concrete transducer-type identities (solar-radiation,
precipitation, air-temperature, relative-humidity,
barometric-pressure, vapor-pressure, wind-speed, wind-direction,
wind-gust, north-wind-speed, east-wind-speed, strike-count,
average-distance, tilt, x-orientation, y-orientation,
humidity-sensor-temperature, compass-heading) out of coreconf-m2m and
into the new atmos module; coreconf-m2m now only defines the base
transducer-type identity, which product-specific modules extend.
Rename transducer/unit and transducer/precision to unit-override and
precision-override (both now optional, no default): they only need
to be set when they differ from the default-unit/default-precision
extension declared on the transducer's type identity.
Add default-unit and default-precision extensions, for use by
product-specific modules (e.g. atmos) annotating the transducer-type
identities they define.
Add SOSA/SAREF-aligned leafs to the characteristics container
(description, manufacturer, model, hosted-by, installation-time);
move uptime back into states, as bootstrap operational data.
Add default-category extension, for product-specific modules to mark
each transducer-type identity as 'sensor', 'actuator' or
'sensor-actuator', and a matching category-override leaf on the
transducer list for per-instance overrides.
Remove u-timestamp: all timestamps are now second-resolution only;
sub-second precision would require a model update.
Change history/step units from milliseconds to seconds, for the
same reason.
Add minimal-step leaf in states and a must constraint requiring
history/step >= states/minimal-step.
Move geo-location from states into characteristics: it is
configuration data set once at deployment, not runtime state.
Make states/uptime mandatory.
Revert the id-based key: transducer, transducers-list/time-series
and sensor-alert/target are again keyed on type alone, and the id
leaf is removed from all three lists (only one transducer per
type is supported).";
}
revision 2026-06-07 {
description
"Add humidity-sensor-temperature and compass-heading transducer-type identities.";
}
revision 2026-05-26 {
description
"Add check-interval leaf to history and sensor-alert notification parameters.";
}
revision 2026-03-29 {
description
"Move container statistics inside container quantity.";
}
revision 2026-03-25 {
description
"Add dampening leaf to sensor-alert notification parameters.";
}
revision 2026-03-23 {
description
"Make active leaf config false (reflects observe subscription state).
Add must constraint t-min < t-max on sensor-alert.
Add timestamp-source enum in quantity to indicate timestamp origin
(source = timestamped by the sensor, receiver = timestamped on receipt).";
}
revision 2026-03-22 {
description
"Rename branch measurement to transducer
(container, list, identity, notification).";
}
revision 2026-03-08 {
description
"Module renamed to coreconf-m2m for generic M2M CoMI use.";
}
revision 2026-03-02 {
description
"Major update for CoMI compliance:
- Added multi-sensor support (id key).
- Measurements are now configurable (writable).
- Added standard deviation (stdev).
- Added uptime to state.
- Removed tilt from fixed state (now a measurement type).
- Removed legacy RPCs (subscribe, get-stats) in favor of CoMI/Observe.
- Cleaned up measurement lists and notifications.";
}
revision 2026-02-27 {
description
"Initial revision for testing.";
}
/* ------------------------------------------------------------------ */
/* Extensions */
/* ------------------------------------------------------------------ */
extension default-unit {
argument "senml-unit";
description
"Recommended default value for a transducer's unit-override leaf,
expressed as a SenML (RFC 8428) unit string. Applied by
product-specific modules to the transducer-type identities they
define. Intended as guidance for clients populating
/transducers/transducer/unit-override; not enforced by the schema.";
}
extension default-precision {
argument "decimal-places";
description
"Recommended default value for a transducer's precision-override leaf
(number of decimal places, i.e. real value = raw_value * 10^-precision)
when using the default-unit. Applied by product-specific modules to
the transducer-type identities they define. Intended as guidance for
clients populating /transducers/transducer/precision-override; not
enforced by the schema.";
}
extension default-category {
argument "category";
description
"Recommended default value for a transducer's category-override leaf.
Extension arguments are plain strings and cannot be constrained to
an enumeration by the YANG language itself; the value SHOULD be one
of the category-override enums: 'sensor' (the transducer-type
identity represents an observed property), 'actuator' (it
represents a property acted upon), or 'sensor-actuator' (both).
Applied by
product-specific modules to the transducer-type identities they
define, since this role is a property of the measurement type, not
of the device as a whole (a device can carry a mix of sensor- and
actuator-typed transducers).
For example, in the SOSA/SSN ontology 'sensor' corresponds to
sosa:Sensor/sosa:ObservableProperty and 'actuator' to
sosa:Actuator; in SAREF they correspond to the SensingFunction and
ActuatingFunction classes respectively.";
}
/* ------------------------------------------------------------------ */
/* Measurement-type identity hierarchy */
/* ------------------------------------------------------------------ */
identity transducer-type {
description
"Base identity for all measurement types. Concrete transducer types
are defined by product-specific modules, e.g. atmos.";
}
/* ------------------------------------------------------------------ */
/* Typedefs */
/* ------------------------------------------------------------------ */
typedef encoding-type {
type enumeration {
enum direct {
value 0;
description "Value is encoded directly as an integer.";
}
enum delta {
value 1;
description "Value is encoded as a delta from the previous value.";
}
}
description "Encoding used for the measurement values.";
}
/* ------------------------------------------------------------------ */
/* Shared grouping used by notifications */
/* ------------------------------------------------------------------ */
grouping transducers-list {
list time-series {
key "type";
description
"List of measurements included in this notification.";
leaf type {
type identityref {
base transducer-type;
}
description "The type of measurement (e.g., air-temperature).";
}
leaf-list values {
type int64;
ordered-by user;
description
"List of encoded measurement values.";
}
}
}
/* ------------------------------------------------------------------ */
/* State data */
/* ------------------------------------------------------------------ */
container bootstrap {
config false;
description
"Operational data determined once at bootstrap time.";
leaf reference-epoch {
type uint64;
units "seconds";
description
"Epoch timestamp used as the reference point for the device's
internal clock. Other epoch/timestamp values in this model are
relative to this reference epoch.";
}
leaf uptime {
type uint64;
units "seconds";
mandatory true;
description "Time elapsed since the last boot.";
}
leaf minimal-step {
type uint32;
units "seconds";
description
"Minimum interval at which the underlying system refreshes
transducer values, i.e. the smallest meaningful value for
history/step. Determined by the hardware/firmware and not
configurable (e.g. an atmospheric station may have a
minimal-step of 120 seconds).";
}
list inventory {
key "type";
description
"List of transducers known to the device, with their
per-instance configuration overrides. Determined once at
bootstrap and not expected to change afterwards.";
leaf type {
type identityref {
base transducer-type;
}
description "The type of measurement.";
}
leaf unit-override {
type string;
description
"Unit of measurement (e.g., 'Cel', 'm/s', '%'), overriding the
default-unit extension declared on the transducer's type identity
(if any). When absent, clients should use that identity's
default-unit; if the identity declares no default-unit, the unit
is undefined.";
}
leaf precision-override {
type uint8;
description
"Number of decimal places for the measurement value
(real value = raw_value * 10^-precision), overriding the
default-precision extension declared on the transducer's type
identity (if any). Applies to the current measurement value
(polling/iPATCH), not to the time-series encoding in
notifications. When absent, clients should use that identity's
default-precision; if the identity declares no default-precision,
the precision is undefined.";
}
leaf category-override {
type enumeration {
enum sensor {
description "The transducer represents an observed property.";
}
enum actuator {
description "The transducer represents a property acted upon.";
}
enum sensor-actuator {
description
"The transducer both observes and can be acted upon
(e.g. a setpoint that also reports the measured value).";
}
}
description
"Role of this transducer instance, overriding the
default-category extension declared on the transducer's type
identity (if any). When absent, clients should use that
identity's default-category; if the identity declares no
default-category, the role is undefined.";
}
}
}
/* ------------------------------------------------------------------ */
/* Device Characteristics */
/* ------------------------------------------------------------------ */
container characteristics {
description
"Static descriptive information about the device.";
uses geo:geo-location {
description
"Fixed geographic position of the weather station.
Populated once at deployment; not expected to change
during normal operation.";
refine "geo-location/reference-frame/geodetic-system/geodetic-datum" {
default "wgs-84";
}
}
leaf name {
type string;
description "Human-readable name of the device.";
}
leaf version {
type string;
description "Firmware or software version of the device.";
}
leaf identifier {
type string;
description "Unique identifier of the device (e.g., serial number or EUI).";
}
leaf description {
type string;
description
"Free-text description of the device, its purpose or its
deployment context. For example, in SOSA/SSN this corresponds to
rdfs:comment on the sosa:Sensor/sosa:Platform individual, and in
SAREF to saref:hasDescription.";
}
leaf manufacturer {
type string;
description
"Name of the device manufacturer. For example, in SAREF this
corresponds to saref:hasManufacturer.";
}
leaf model {
type string;
description
"Commercial model name/number of the device. For example, in
SAREF this corresponds to saref:hasModel.";
}
leaf hosted-by {
type string;
description
"Identifier of the platform hosting this device, when applicable.
Left as an opaque string here; resolution to an actual platform
resource is deployment-specific. For example, in SOSA this
corresponds to the sosa:isHostedBy relation
(device -> sosa:Platform).";
}
leaf installation-time {
type uint64;
units "seconds";
description
"Absolute Unix epoch timestamp (UTC) at which the device was
installed/commissioned. Unlike bootstrap/reference-epoch, this is
an absolute epoch value, not a relative reference point for the
device's internal clock.";
}
}
/* ------------------------------------------------------------------ */
/* Measurements Configuration & Data */
/* ------------------------------------------------------------------ */
container transducers {
// config true (default);
description
"Current measurement values accessible via polling or configuration.";
list transducer {
key "type";
description
"List of available measurements.";
leaf type {
type identityref {
base transducer-type;
}
description "The type of measurement.";
}
container quantity {
config false;
description "Operational state for this transducer.";
leaf value {
type int64;
description
"The current raw integer value of the transducer.";
}
leaf timestamp {
type uint64;
units "seconds";
description "Epoch timestamp (seconds) of the last transducer update.";
}
leaf timestamp-source {
type enumeration {
enum source {
value 0;
description
"The timestamp was generated by the sensor itself.";
}
enum receiver {
value 1;
description
"The timestamp was applied by the receiver upon message arrival.";
}
}
description
"Indicates the origin of the timestamp associated with this measurement.";
}
}
container statistics {
config false;
description "Read-only accumulated statistics for this measurement.";
leaf min {
type int64;
description "Minimum value observed since last reset.";
}
leaf max {
type int64;
description "Maximum value observed since last reset.";
}
leaf mean {
type int64;
description "Mean value observed since last reset.";
}
leaf median {
type int64;
description
"Estimated median value observed since last reset.
This median is computed incrementally from the current stored
statistics and the newest sample, without keeping full history.";
}
leaf stdev {
type uint64;
description
"Standard deviation of values observed since last reset.
Scaled by same precision as value.";
}
leaf sample-count {
type uint64;
description "Number of samples used for statistics calculation.";
}
}
container notification-parameters {
description
"Configuration parameters controlling how this transducer
is reported in CoMI notification streams.";
container history {
description "Parameters for the history time-series stream.";
leaf active {
type boolean;
config false;
description
"Read-only. True when a FETCH+Observe subscription is active on /s
for this transducer history stream. False when no observer is registered.";
}
leaf step {
type uint32;
units "seconds";
must ". >= /bootstrap/minimal-step or not(/bootstrap/minimal-step)" {
error-message
"step must be greater than or equal to bootstrap/minimal-step.";
}
description
"Time interval between samples in the notification stream.
Must be at least bootstrap/minimal-step, the system's minimum
refresh interval.";
}
leaf precision {
type uint8;
default 1;
description
"Number of decimal places for values encoded in time-series notifications.
Real value = raw_value * 10^-precision.";
}
leaf max-samples {
type uint32;
description "Maximum number of samples kept in the time-series buffer.";
}
leaf time-period {
type uint32;
units "seconds";
description "Duration of the time window covered by the history buffer.";
}
leaf encoding {
type encoding-type;
default "delta";
description "Encoding used for the values list in notifications.";
}
leaf max-payload {
type uint32;
units "bytes";
description
"Maximum size in bytes of the encoded time-series payload in
a single history notification. When the encoded buffer would
exceed this limit, the notification is sent early (before
max-samples or time-period is reached). 0 means no size limit.";
}
}
container sensor-alert {
description "Parameters for threshold-based sensor-alert notifications.";
must "not(t-min and t-max) or t-min < t-max" {
error-message "t-min must be strictly less than t-max.";
}
leaf active {
type boolean;
config false;
description
"Read-only. True when a FETCH+Observe subscription is active on /s
for this transducer sensor-alert stream. False when no observer is registered.";
}
leaf t-min {
type int32;
description
"Minimum threshold value. A sensor-alert is raised when the
transducer value falls below this value.";
}
leaf t-max {
type int32;
description
"Maximum threshold value. A sensor-alert is raised when the
transducer value exceeds this value.";
}
leaf hysteresis {
type uint8 {
range "0..100";
}
default 5;
units "percent";
description
"Hysteresis applied to threshold crossings, expressed as a
percentage of the threshold value.";
}
leaf dampening {
type uint32;
default 0;
units "milliseconds";
description
"Minimum time that must elapse between two consecutive sensor-alert
notifications for the same transducer. A new alert is suppressed
until the dampening period has expired since the last sent alert.
0 (default) means no dampening.";
}
}
leaf check-interval {
type uint16;
default 0;
description
"Controls how often a CON (confirmable) notification is sent
instead of NON (non-confirmable), for both the history and
sensor-alert streams of this transducer.
0 = disabled, all notifications are NON.
1 = every notification is CON.
N = one CON every N notifications, all others are NON.";
}
}
action reset-stats {
description
"Reset the accumulated statistics (min, max, mean, median, stdev)
for this measurement. Counters restart from the next sample received.";
}
}
}
/* ------------------------------------------------------------------ */
/* RPCs */
/* ------------------------------------------------------------------ */
rpc reset-stats {
description
"Reset the accumulated statistics for all measurements.
The counters restart from the next sample received after the call.";
}
/* ------------------------------------------------------------------ */
/* Notifications */
/* ------------------------------------------------------------------ */
notification history {
description
"Measurement event sent to observers of the notification stream.
To receive this, a CoLA/CoMI client must Observe the stream resource.";
leaf last {
type boolean;
default "false";
description "True if this is the last notification for the current series.";
}
uses transducers-list {
augment "time-series" {
container internal {
description "Internal operational state for this time-series entry.";
leaf last-update {
type uint64;
units "seconds";
description "Epoch timestamp of the last sample appended to this time-series.";
}
leaf start-time {
type uint64;
units "seconds";
description "Epoch timestamp of the first sample; set when the Observe subscription is created.";
}
leaf messages-sent {
type uint64;
description "Number of notification messages sent for this time-series since the Observe subscription was created.";
}
}
}
}
}
notification sensor-alert {
description
"Alert notification when a value exceeds a threshold.";
list target {
key "type";
description "Measurement instances that triggered this alert.";
leaf type {
type identityref {
base transducer-type;
}
}
leaf value {
type int64;
description "The measurement value that triggered this alert.";
}
}
}
}
<CODE ENDS>
The following table lists the SID assignments for the coreconf-m2m module (assignment range: 62000-62399, revision 2026-09-01). The transducer-type identities defined by manufacturer modules such as atmos are assigned separately and are not yet included in this table.¶
SID,Namespace,Identifier 62000,module,coreconf-m2m 62001,identity,transducer-type 62002,data,/coreconf-m2m:bootstrap 62003,data,/coreconf-m2m:bootstrap/inventory 62004,data,/coreconf-m2m:bootstrap/inventory/category-override 62005,data,/coreconf-m2m:bootstrap/inventory/precision-override 62006,data,/coreconf-m2m:bootstrap/inventory/type 62007,data,/coreconf-m2m:bootstrap/inventory/unit-override 62008,data,/coreconf-m2m:bootstrap/minimal-step 62009,data,/coreconf-m2m:bootstrap/reference-epoch 62010,data,/coreconf-m2m:bootstrap/uptime 62011,data,/coreconf-m2m:characteristics 62012,data,/coreconf-m2m:characteristics/description 62013,data,/coreconf-m2m:characteristics/geo-location 62014,data,/coreconf-m2m:characteristics/geo-location/height 62015,data,/coreconf-m2m:characteristics/geo-location/latitude 62016,data,/coreconf-m2m:characteristics/geo-location/longitude 62017,data,/coreconf-m2m:characteristics/geo-location/reference-frame 62018,data,/coreconf-m2m:characteristics/geo-location/reference-frame/alternate-system 62019,data,/coreconf-m2m:characteristics/geo-location/reference-frame/astronomical-body 62020,data,/coreconf-m2m:characteristics/geo-location/reference-frame/geodetic-system 62021,data,/coreconf-m2m:characteristics/geo-location/reference-frame/geodetic-system/coord-accuracy 62022,data,/coreconf-m2m:characteristics/geo-location/reference-frame/geodetic-system/geodetic-datum 62023,data,/coreconf-m2m:characteristics/geo-location/reference-frame/geodetic-system/height-accuracy 62024,data,/coreconf-m2m:characteristics/geo-location/timestamp 62025,data,/coreconf-m2m:characteristics/geo-location/valid-until 62026,data,/coreconf-m2m:characteristics/geo-location/velocity 62027,data,/coreconf-m2m:characteristics/geo-location/velocity/v-east 62028,data,/coreconf-m2m:characteristics/geo-location/velocity/v-north 62029,data,/coreconf-m2m:characteristics/geo-location/velocity/v-up 62030,data,/coreconf-m2m:characteristics/geo-location/x 62031,data,/coreconf-m2m:characteristics/geo-location/y 62032,data,/coreconf-m2m:characteristics/geo-location/z 62033,data,/coreconf-m2m:characteristics/hosted-by 62034,data,/coreconf-m2m:characteristics/identifier 62035,data,/coreconf-m2m:characteristics/installation-time 62036,data,/coreconf-m2m:characteristics/manufacturer 62037,data,/coreconf-m2m:characteristics/model 62038,data,/coreconf-m2m:characteristics/name 62039,data,/coreconf-m2m:characteristics/version 62040,data,/coreconf-m2m:history 62041,data,/coreconf-m2m:history/last 62042,data,/coreconf-m2m:history/time-series 62043,data,/coreconf-m2m:history/time-series/internal 62044,data,/coreconf-m2m:history/time-series/internal/last-update 62045,data,/coreconf-m2m:history/time-series/internal/messages-sent 62046,data,/coreconf-m2m:history/time-series/internal/start-time 62047,data,/coreconf-m2m:history/time-series/type 62048,data,/coreconf-m2m:history/time-series/values 62049,data,/coreconf-m2m:reset-stats 62050,data,/coreconf-m2m:reset-stats/input 62051,data,/coreconf-m2m:reset-stats/output 62052,data,/coreconf-m2m:sensor-alert 62053,data,/coreconf-m2m:sensor-alert/target 62054,data,/coreconf-m2m:sensor-alert/target/type 62055,data,/coreconf-m2m:sensor-alert/target/value 62056,data,/coreconf-m2m:transducers 62057,data,/coreconf-m2m:transducers/transducer 62058,data,/coreconf-m2m:transducers/transducer/notification-parameters 62059,data,/coreconf-m2m:transducers/transducer/notification-parameters/check-interval 62060,data,/coreconf-m2m:transducers/transducer/notification-parameters/history 62061,data,/coreconf-m2m:transducers/transducer/notification-parameters/history/active 62062,data,/coreconf-m2m:transducers/transducer/notification-parameters/history/encoding 62063,data,/coreconf-m2m:transducers/transducer/notification-parameters/history/max-payload 62064,data,/coreconf-m2m:transducers/transducer/notification-parameters/history/max-samples 62065,data,/coreconf-m2m:transducers/transducer/notification-parameters/history/precision 62066,data,/coreconf-m2m:transducers/transducer/notification-parameters/history/step 62067,data,/coreconf-m2m:transducers/transducer/notification-parameters/history/time-period 62068,data,/coreconf-m2m:transducers/transducer/notification-parameters/sensor-alert 62069,data,/coreconf-m2m:transducers/transducer/notification-parameters/sensor-alert/active 62070,data,/coreconf-m2m:transducers/transducer/notification-parameters/sensor-alert/dampening 62071,data,/coreconf-m2m:transducers/transducer/notification-parameters/sensor-alert/hysteresis 62072,data,/coreconf-m2m:transducers/transducer/notification-parameters/sensor-alert/t-max 62073,data,/coreconf-m2m:transducers/transducer/notification-parameters/sensor-alert/t-min 62074,data,/coreconf-m2m:transducers/transducer/quantity 62075,data,/coreconf-m2m:transducers/transducer/quantity/timestamp 62076,data,/coreconf-m2m:transducers/transducer/quantity/timestamp-source 62077,data,/coreconf-m2m:transducers/transducer/quantity/value 62078,data,/coreconf-m2m:transducers/transducer/reset-stats 62079,data,/coreconf-m2m:transducers/transducer/reset-stats/input 62080,data,/coreconf-m2m:transducers/transducer/reset-stats/output 62081,data,/coreconf-m2m:transducers/transducer/statistics 62082,data,/coreconf-m2m:transducers/transducer/statistics/max 62083,data,/coreconf-m2m:transducers/transducer/statistics/mean 62084,data,/coreconf-m2m:transducers/transducer/statistics/median 62085,data,/coreconf-m2m:transducers/transducer/statistics/min 62086,data,/coreconf-m2m:transducers/transducer/statistics/sample-count 62087,data,/coreconf-m2m:transducers/transducer/statistics/stdev 62088,data,/coreconf-m2m:transducers/transducer/type
<CODE BEGINS> file "atmos@2026-08-24.yang"
module atmos {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:atmos";
prefix atmos;
import coreconf-m2m {
prefix ccm2m;
}
organization "METER Group, Inc.";
contact
"Technical Support
https://www.metergroup.com/";
description
"Atmospheric/weather transducer-type identities for use with the
coreconf-m2m generic M2M CoMI data model.";
revision 2026-08-24 {
description
"Initial revision. Extracted all concrete transducer-type identities
from coreconf-m2m: atmospheric/weather (solar-radiation,
precipitation, air-temperature, relative-humidity,
barometric-pressure, vapor-pressure, wind-speed, wind-direction,
wind-gust, north-wind-speed, east-wind-speed), lightning
(strike-count, average-distance), orientation (tilt, x-orientation,
y-orientation, compass-heading), and humidity-sensor-temperature.
coreconf-m2m now only defines the base transducer-type identity;
atmos is the product-specific module extending it.
Applied the ccm2m:default-unit and ccm2m:default-precision
extensions (defined in coreconf-m2m) to each identity, to advertise
the recommended SenML (RFC 8428) unit and decimal precision for a
transducer of that type.";
}
/* ------------------------------------------------------------------ */
/* Environmental */
/* ------------------------------------------------------------------ */
identity solar-radiation {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "W/m2";
ccm2m:default-precision "0";
description "Solar radiation measurement (W/m2).";
}
identity precipitation {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "mm";
ccm2m:default-precision "1";
description "Precipitation measurement (mm).";
}
identity air-temperature {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "Cel";
ccm2m:default-precision "2";
description "Air temperature measurement (°C).";
}
identity relative-humidity {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "%RH";
ccm2m:default-precision "1";
description "Relative humidity measurement (%).";
}
identity barometric-pressure {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "Pa";
ccm2m:default-precision "0";
description "Barometric pressure measurement (kPa).";
}
identity vapor-pressure {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "Pa";
ccm2m:default-precision "0";
description "Vapor pressure measurement (kPa).";
}
/* ------------------------------------------------------------------ */
/* Wind */
/* ------------------------------------------------------------------ */
identity wind-speed {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "m/s";
ccm2m:default-precision "1";
description "Horizontal wind speed measurement (m/s).";
}
identity wind-direction {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "deg";
ccm2m:default-precision "0";
description "Wind direction measurement (degrees).";
}
identity wind-gust {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "m/s";
ccm2m:default-precision "1";
description "Wind gust measurement (m/s).";
}
identity north-wind-speed {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "m/s";
ccm2m:default-precision "1";
description "North wind speed component (m/s).";
}
identity east-wind-speed {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "m/s";
ccm2m:default-precision "1";
description "East wind speed component (m/s).";
}
/* ------------------------------------------------------------------ */
/* Lightning */
/* ------------------------------------------------------------------ */
identity strike-count {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "count";
ccm2m:default-precision "0";
description "Lightning strike count.";
}
identity average-distance {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "km";
ccm2m:default-precision "1";
description "Average lightning distance (km).";
}
/* ------------------------------------------------------------------ */
/* Orientation */
/* ------------------------------------------------------------------ */
identity tilt {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "deg";
ccm2m:default-precision "1";
description "Sensor tilt measurement (degrees).";
}
identity x-orientation {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
description "X-axis orientation (raw accelerometer data).";
}
identity y-orientation {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
description "Y-axis orientation (raw accelerometer data).";
}
identity compass-heading {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "deg";
ccm2m:default-precision "0";
description "Compass heading clockwise from north reference (degrees).";
}
/* ------------------------------------------------------------------ */
/* Internal */
/* ------------------------------------------------------------------ */
identity humidity-sensor-temperature {
base ccm2m:transducer-type;
ccm2m:default-category "sensor";
ccm2m:default-unit "Cel";
ccm2m:default-precision "2";
description "Internal temperature of the humidity sensor (°C).";
}
}
<CODE ENDS>
The following table illustrates the SID assignments for the atmos module, assuming a registrar-owned range with an illustrative entry point of 10000000 (out of a 10 million SID mega-range assumed for this example). These SIDs are commercial/vendor assignments, not IETF or SDO ones, and are provided here for illustration only.¶
SID,Namespace,Identifier 10000000,module,atmos 10000001,identity,air-temperature 10000002,identity,average-distance 10000003,identity,barometric-pressure 10000004,identity,compass-heading 10000005,identity,east-wind-speed 10000006,identity,humidity-sensor-temperature 10000007,identity,north-wind-speed 10000008,identity,precipitation 10000009,identity,relative-humidity 10000010,identity,solar-radiation 10000011,identity,strike-count 10000012,identity,tilt 10000013,identity,vapor-pressure 10000014,identity,wind-direction 10000015,identity,wind-gust 10000016,identity,wind-speed 10000017,identity,x-orientation 10000018,identity,y-orientation
@prefix ccm2m: <https://ccm2m.example/ns#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix sosa: <http://www.w3.org/ns/sosa/> .
@prefix qudt: <http://qudt.org/schema/qudt/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
# ── Ontology header ───────────────────────────────────────────────────────────
<https://ccm2m.example/ns>
a owl:Ontology ;
rdfs:label "CCM2M — CORECONF-M2M vocabulary for SOSA/SSN" ;
rdfs:comment """Complementary vocabulary to SOSA/SSN for exposing
CORECONF/CoAP semantics (endpoints, applicative controls, bootstrap SIDs,
sensor identity/id) that have no direct equivalent in SOSA.""" ;
dcterms:created "2026-06-27"^^xsd:date ;
owl:versionInfo "0.2" .
# ── Datatype ──────────────────────────────────────────────────────────────────
ccm2m:SID
a rdfs:Datatype ;
rdfs:label "SID" ;
rdfs:comment """A CORECONF Schema Identifier (SID) — a non-negative integer
that uniquely identifies a YANG node, identity, or feature within a SID
file (RFC 9254). Using this datatype instead of xsd:nonNegativeInteger
makes the nature of the value self-documenting.""" .
# ── Classes ───────────────────────────────────────────────────────────────────
ccm2m:Control
a owl:Class ;
rdfs:label "Control" ;
rdfs:comment """An applicative operation exposed by a Platform over
CORECONF/CoAP: single-value read, history-buffer notification subscription,
or threshold-alert notification subscription.
Intentionally outside SOSA/SSN Sampling/Sensing semantics.""" .
ccm2m:SetupNotification
a owl:Class ;
rdfs:subClassOf ccm2m:Control ;
rdfs:label "SetupNotification" ;
rdfs:comment """A Control that configures notification parameters on a
transducer before subscribing (iPATCH /c). Carries the target SID of the
notification-parameters container and the list of configurable parameters
with their individual SIDs.""" .
ccm2m:NotificationParam
a owl:Class ;
rdfs:label "NotificationParam" ;
rdfs:comment """A single configurable parameter within a
SetupNotification (e.g. 'step', 'encoding'). Carries the YANG leaf name
and its SID.""" .
# ── Object properties ─────────────────────────────────────────────────────────
ccm2m:hasControl
a owl:ObjectProperty ;
rdfs:label "hasControl" ;
rdfs:comment "Links a Platform or Sensor to a Control it exposes." ;
rdfs:range ccm2m:Control .
ccm2m:hasParam
a owl:ObjectProperty ;
rdfs:label "hasParam" ;
rdfs:comment "Links a SetupNotification to one of its configurable parameters." ;
rdfs:domain ccm2m:SetupNotification ;
rdfs:range ccm2m:NotificationParam .
ccm2m:outputUnit
a owl:ObjectProperty ;
rdfs:label "outputUnit" ;
rdfs:comment """Fixed output unit of a Sensor, declared once from the
'units' statement of the corresponding YANG leaf. Never repeated per
Observation.""" ;
rdfs:domain sosa:Sensor ;
rdfs:range qudt:Unit .
# ── Datatype properties — Platform ────────────────────────────────────────────
ccm2m:coapEndpoint
a owl:DatatypeProperty ;
rdfs:label "coapEndpoint" ;
rdfs:comment "CoAP access address of the resource (e.g. coap://[::1])." ;
rdfs:domain sosa:Platform ;
rdfs:range xsd:anyURI .
ccm2m:accessProtocol
a owl:DatatypeProperty ;
rdfs:label "accessProtocol" ;
rdfs:comment "Default applicative protocol on this Platform (e.g. 'coreconf')." ;
rdfs:domain sosa:Platform ;
rdfs:range xsd:string .
ccm2m:bootstrapSid
a owl:DatatypeProperty ;
rdfs:label "bootstrapSid" ;
rdfs:comment """SID used at bootstrap to discover the YANG model exposed
by the Platform. Multi-valued (several bootstrap SIDs are possible,
unordered). Discovered via DNS TXT record.""" ;
rdfs:domain sosa:Platform ;
rdfs:range ccm2m:SID .
# ── Datatype properties — shared (Platform and Control) ───────────────────────
ccm2m:coapMethod
a owl:DatatypeProperty ;
rdfs:label "coapMethod" ;
rdfs:comment """CoAP method to use (FETCH, FETCH+Observe, GET, iPATCH…).
Usable on a Platform (default) or on a Control (override).
Domain intentionally unconstrained.""" ;
rdfs:range xsd:string .
ccm2m:coapPath
a owl:DatatypeProperty ;
rdfs:label "coapPath" ;
rdfs:comment """CoAP URI path of the resource targeted by this Control
(e.g. '/c' for datastore, '/s' for stream/notification).""" ;
rdfs:range xsd:string .
ccm2m:contentFormat
a owl:DatatypeProperty ;
rdfs:label "contentFormat" ;
rdfs:comment """CoAP Content-Format used (e.g.
application/yang-data+cbor). Usable on a Platform (default) or on a
Control (override). Domain intentionally unconstrained.""" ;
rdfs:range xsd:string .
# ── Datatype properties — Control ─────────────────────────────────────────────
ccm2m:paramName
a owl:DatatypeProperty ;
rdfs:label "paramName" ;
rdfs:comment "YANG leaf name of this notification parameter (e.g. 'step', 'encoding')." ;
rdfs:domain ccm2m:NotificationParam ;
rdfs:range xsd:string .
ccm2m:paramSid
a owl:DatatypeProperty ;
rdfs:label "paramSid" ;
rdfs:comment "SID of this notification parameter's YANG leaf." ;
rdfs:domain ccm2m:NotificationParam ;
rdfs:range ccm2m:SID .
ccm2m:controlType
a owl:DatatypeProperty ;
rdfs:label "controlType" ;
rdfs:comment """Functional type of the Control. Current controlled values:
'instant-write' — single-value write (iPATCH /c, same SID as read-single),
'read-single' — single-value polling (FETCH /c),
'read-stat' — statistics polling (FETCH /c, statistics container),
'reset-stat' — reset statistics for one sensor (POST /c, per-sensor),
'reset-all-stat' — reset all sensors statistics (POST /c, platform-level),
'subscribe-history' — history-buffer notification subscription (Observe /s),
'subscribe-threshold' — threshold-alert notification subscription (Observe /s).""" ;
rdfs:domain ccm2m:Control ;
rdfs:range xsd:string .
ccm2m:targetSid
a owl:DatatypeProperty ;
rdfs:label "targetSid" ;
rdfs:comment "SID of the YANG leaf/container targeted by this Control." ;
rdfs:domain ccm2m:Control ;
rdfs:range ccm2m:SID .
# ── Datatype properties — Sensor ──────────────────────────────────────────────
ccm2m:type
a owl:DatatypeProperty ;
rdfs:label "type" ;
rdfs:comment """SID of the YANG identity describing the sensor type
(e.g. 100001 for air-temperature). Named 'type' to match the YANG leaf name.
Looked up from the device's .sid file.""" ;
rdfs:domain sosa:Sensor ;
rdfs:range ccm2m:SID .
ccm2m:id
a owl:DatatypeProperty ;
rdfs:label "id" ;
rdfs:comment """Instance identifier of the sensor on its Platform.
Distinguishes multiple sensors of the same type (default 0).""" ;
rdfs:domain sosa:Sensor ;
rdfs:range xsd:nonNegativeInteger .
ccm2m:precision
a owl:DatatypeProperty ;
rdfs:label "precision" ;
rdfs:comment """Number of decimal places for the raw integer value.
Real value = raw_value × 10^(-precision).
Declared once on the Sensor; not repeated per Observation.""" ;
rdfs:domain sosa:Sensor ;
rdfs:range xsd:integer .
The following SCHC [RFC8724] Rule Set compresses the IPv6/UDP/CoAP traffic exchanged with the ATMOS41 weather station used throughout this document. These rules are just examples that can be adapted to other scenarios. It is not the final version of this rule set and is expected to change as the model evolves.¶
The compression of IPv6 and UDP results in the port number being sent as residue. Traffic Class and Flow Label are elided, and the application and device IPv6 addresses are supposed to be known (aaaa::2/64 and dddd::6/64, respectively).¶
Field names (FID) are the identities defined by the "ietf-schc" YANG module [RFC9363] [SCHC-TOWARD-9363BIS], without their common "fid-" prefix (e.g. "ipv6-version" for "fid-ipv6-version"), rather than the plain-text FID names of [RFC8724] itself. CoAP options use the Universal Option identity "coap-option" [SCHC-TOWARD-9363BIS], common to every option, with the actual option number given in parentheses, rather than the twenty per-option FIDs deprecated by that identity. Matching operators (MO) and compression/decompression actions (CDA) are as defined in [RFC8724]; FL is the Field Length, DI the Direction Indicator, and TV the Target Value (the Field Position, FP, is omitted from the tables below for compactness; it defaults to 1 unless a field is repeated, which does not happen in this Rule Set).¶
Below each rule table, the resulting residue format is given for the Down (network to device) and Up (device to network) directions: the ordered list of fields that still carry a residue after compression — i.e. those whose CDA is not "not-sent", "compute-length", or "compute-checksum" — together with the number of bits sent for that field ("var" when the size is not fixed), followed by the total residue size for that direction.¶
/---------------------+----------+----+-----------+---------+------------\
| FID | FL | DI | TV | MO | CDA |
+=====================+==========+====+===========+=========+============+
| ipv6-version | 4 | bi | 6 | equal | not-sent |
| ipv6-trafficclass | 8 | bi | 0 | ignore | not-sent |
| ipv6-flowlabel | 20 | bi | 0 | ignore | not-sent |
| ipv6-payload-length | 16 | bi | | ignore | compute |
| ipv6-nextheader | 8 | bi | 17 | equal | not-sent |
| ipv6-hoplimit | 8 | bi | 255 | ignore | not-sent |
| ipv6-devprefix | 64 | bi | dddd::/64 | equal | not-sent |
| ipv6-deviid | 64 | bi | ::6/64 | equal | not-sent |
| ipv6-appprefix | 64 | bi | aaaa::/64 | equal | not-sent |
| ipv6-appiid | 64 | bi | ::2/64 | equal | not-sent |
| udp-dev-port | 16 | bi | 5683 | equal | not-sent |
| udp-app-port | 16 | bi | | ignore | value-sent |
| udp-length | 16 | bi | 0 | ignore | compute |
| udp-checksum | 16 | bi | 0 | ignore | compute |
| coap-version | 2 | bi | 1 | equal | not-sent |
| coap-type | 2 | bi | 1 | equal | not-sent |
| coap-tkl | 4 | bi | 0 | ignore | value-sent |
| coap-code | 8 | bi | 5,69 | match- | mapping- |
| | | | | mapping | sent |
| coap-mid | 16 | bi | 0 | ignore | value-sent |
| coap-token | length- | bi | | ignore | value-sent |
| | byte(16) | | | | |
| coap-option(11) | var | dw | "c" | equal | not-sent |
| coap-option(12) | var | bi | 140 | equal | not-sent |
| coap-option(17) | var | dw | 140 | equal | not-sent |
\---------------------+----------+----+-----------+---------+------------/
Residue (Down): udp-app-port(16b) | coap-tkl(4b) | coap-code(1b) |
coap-mid(16b) | coap-token(var)
Total: 37b+var
Residue (Up): udp-app-port(16b) | coap-tkl(4b) | coap-code(1b) |
coap-mid(16b) | coap-token(var)
Total: 37b+var
This rule is used for FETCH: the client sends one or more SID values,
potentially with keys, and receives a CORECONF structure in return.
Errors are handled by Rule 3 (empty message) and Rule 4 (ICMPv6
destination unreachable), not by this rule. The Content-Format is
always 140
(application/yang-data+cbor;id=sid [RFC9254]). Messages are
Non-Confirmable (NON), and the Token is mandatory to associate the
response with its query.¶
Figure 35 applies Rule 0 to two of the exchanges shown
earlier: the bootstrap discovery of Figure 6 and the
air-temperature query of Figure 8. Each message crosses two
SCHC instances: a compressor/decompressor at the constrained device and
its peer at the network edge, next to the application. Only the
residue fields of Figure 34 (app-port, coap-tkl,
coap-code, coap-mid, coap-token — 53 bits, plus the 5-bit RuleID)
cross the constrained link between the two SCHC instances; the full
IPv6/UDP/CoAP message, shown here with its CoAP method, MID, Token,
Content-Format, and payload (when small enough to display), is
exchanged unchanged on the Device-SCHC and SCHC-App segments. The
application port (app-port) is an arbitrary ephemeral port, chosen
here as 52344 for illustration.¶
Device SCHC SCHC App
Bootstrap discovery -- request (Down):
| | | |
| | |<---- Pkt 64B -----|
| | Rule 0/5 | |
| | residue: 53b | |
| | payload: 3B | |
| |<- 11B (pad 6b) ---| |
|<--- Pkt 64B ----| | |
IPv6(40B)+UDP(8B)+CoAP(16B) = 64 B
FETCH /bootstrap MID=39919 Token=2aaf
Payload: 19 F2 32 (3 B)
Bootstrap discovery -- response (Up):
| | | |
|--- Pkt 175B --->| | |
| | Rule 0/5 | |
| | residue: 53b | |
| | payload: 118B | |
| |-- 126B (pad 6b) ->| |
| | |---- Pkt 175B ---->|
IPv6(40B)+UDP(8B)+CoAP(127B) = 175 B
2.05 Content MID=39919 Token=2aaf
Payload: 118 B (not shown)
Air-temperature query -- request (Down):
| | | |
| | |<---- Pkt 70B -----|
| | Rule 0/5 | |
| | residue: 53b | |
| | payload: 9B | |
| |<- 17B (pad 6b) ---| |
|<--- Pkt 70B ----| | |
IPv6(40B)+UDP(8B)+CoAP(22B) = 70 B
FETCH /c MID=12229 Token=2dae
Payload: [62077, 10000001] (9 B)
Air-temperature query -- response (Up):
| | | |
|---- Pkt 63B --->| | |
| | Rule 0/5 | |
| | residue: 53b | |
| | payload: 6B | |
| |-- 14B (pad 6b) -->| |
| | |----- Pkt 63B ---->|
IPv6(40B)+UDP(8B)+CoAP(15B) = 63 B
2.05 Content MID=12229 Token=2dae
Payload: {62077: 196} (6 B)
The compression ratio is best on small responses (bootstrap discovery request: 64 -> 11 bytes, 83%) and degrades as the payload grows, since SCHC only compresses the fixed IPv6/UDP/CoAP header fields, not the CoAP payload (bootstrap response: 175 -> 126 bytes, 28%, dominated by the 118-byte payload).¶
Figure 36 shows the other side of Rule 0: a FETCH targeting a SID the device does not have. The 4.04 Not Found response carries no payload and no Content-Format option, so it does not match Rule 0's CODE mapping or its fixed Up-direction options; it falls through to Rule 3 instead, where TYPE, TKL, CODE and MID are all left uncompressed.¶
Device SCHC SCHC App FETCH on a non-existent SID -- request (Down): | | | | | | |<---- Pkt 64B -----| | | Rule 0/5 | | | | residue: 53b | | | | payload: 3B | | | |<- 11B (pad 6b) ---| | |<--- Pkt 64B ----| | | IPv6(40B)+UDP(8B)+CoAP(16B) = 64 B FETCH /c MID=12230 Token=2daf Payload: 19 F2 93 (SID 62099, 3 B) 4.04 Not Found, no options -- response (Up, Rule 3): | | | | |---- Pkt 54B --->| | | | | Rule 3/5 | | | | residue: 62b | | | | payload: 0B | | | |--- 9B (pad 5b) -->| | | | |----- Pkt 54B ---->| IPv6(40B)+UDP(8B)+CoAP(6B) = 54 B 4.04 Not Found MID=12230 Token=2daf Payload: (none)
/---------------------+----------+----+-----------+---------+------------\
| FID | FL | DI | TV | MO | CDA |
+=====================+==========+====+===========+=========+============+
| ipv6-version | 4 | bi | 6 | equal | not-sent |
| ipv6-trafficclass | 8 | bi | 0 | ignore | not-sent |
| ipv6-flowlabel | 20 | bi | 0 | ignore | not-sent |
| ipv6-payload-length | 16 | bi | | ignore | compute |
| ipv6-nextheader | 8 | bi | 17 | equal | not-sent |
| ipv6-hoplimit | 8 | bi | 255 | ignore | not-sent |
| ipv6-devprefix | 64 | bi | dddd::/64 | equal | not-sent |
| ipv6-deviid | 64 | bi | ::6/64 | equal | not-sent |
| ipv6-appprefix | 64 | bi | aaaa::/64 | equal | not-sent |
| ipv6-appiid | 64 | bi | ::2/64 | equal | not-sent |
| udp-dev-port | 16 | bi | 5683 | equal | not-sent |
| udp-app-port | 16 | bi | | ignore | value-sent |
| udp-length | 16 | bi | 0 | ignore | compute |
| udp-checksum | 16 | bi | 0 | ignore | compute |
| coap-version | 2 | bi | 1 | equal | not-sent |
| coap-type | 2 | bi | 1 | equal | not-sent |
| coap-tkl | 4 | bi | 0 | ignore | value-sent |
| coap-code | 8 | dw | 7 | equal | not-sent |
| coap-code | 8 | up | | ignore | value-sent |
| coap-mid | 16 | bi | 0 | ignore | value-sent |
| coap-token | length- | bi | | ignore | value-sent |
| | byte(16) | | | | |
| coap-option(11) | var | dw | "c" | equal | not-sent |
| coap-option(12) | var | dw | 140 | equal | not-sent |
\---------------------+----------+----+-----------+---------+------------/
Residue (Down): udp-app-port(16b) | coap-tkl(4b) | coap-mid(16b) |
coap-token(var)
Total: 36b+var
Residue (Up): udp-app-port(16b) | coap-tkl(4b) | coap-code(8b) |
coap-mid(16b) | coap-token(var)
Total: 44b+var
The request side of this rule does not change from Rule 0: the CoAP
CODE is fixed to 7 (iPATCH), and the request carries the SID(s) being
updated together with their new values, using the same Content-Format
140. The response, however, is simpler than a FETCH response: it is
only a success/failure notification, with no CORECONF structure in the
payload, so CODE is left uncompressed (value-sent) rather than
mapped as in Rule 0 — it carries 2.04 Changed on success, or a CoAP
error code (e.g. 4.00, 4.04) when the iPATCH itself fails; either way
the message still has the same structure (a bare CODE, no payload), so
this rule covers both cases. Rule 3, which is identical in both
directions (DI bi throughout, including CODE), is used instead when
the message does not follow this structure at all — an empty/generic
message such as an ACK with no code or an RST, in either direction. An
unreachable device is signaled out-of-band by an ICMPv6 destination
unreachable message, handled by Rule 4.¶
Figure 38 applies Rule 1 to the sensor-alert configuration of Figure 12: the request, 83 bytes uncompressed, crosses the constrained link as a 32-byte compressed message (5-bit RuleID + 52-bit residue + the 24-byte iPATCH payload, padded to a byte boundary). The response — 2.04 Changed, with no payload — collapses to 9 bytes (5-bit RuleID + 60-bit residue, padded), against 54 bytes uncompressed.¶
Device SCHC SCHC App
Alert configuration -- request (Down):
| | | |
| | |<---- Pkt 83B -----|
| | Rule 1/5 | |
| | residue: 52b | |
| | payload: 24B | |
| |<- 32B (pad 7b) ---| |
|<--- Pkt 83B ----| | |
IPv6(40B)+UDP(8B)+CoAP(35B) = 83 B
iPATCH /c MID=42312 Token=2490
Payload: {[62068,..]: {...}} (24 B)
Alert configuration -- response (Up):
| | | |
|---- Pkt 54B --->| | |
| | Rule 1/5 | |
| | residue: 60b | |
| | payload: 0B | |
| |--- 9B (pad 7b) -->| |
| | |----- Pkt 54B ---->|
IPv6(40B)+UDP(8B)+CoAP(6B) = 54 B
2.04 Changed MID=42312 Token=2490
Payload: (none)
/---------------------+--------+----+-----------+---------+------------\
| FID | FL | DI | TV | MO | CDA |
+=====================+========+====+===========+=========+============+
| ipv6-version | 4 | bi | 6 | equal | not-sent |
| ipv6-trafficclass | 8 | bi | 0 | ignore | not-sent |
| ipv6-flowlabel | 20 | bi | 0 | ignore | not-sent |
| ipv6-payload-length | 16 | bi | | ignore | compute |
| ipv6-nextheader | 8 | bi | 17 | equal | not-sent |
| ipv6-hoplimit | 8 | bi | 255 | ignore | not-sent |
| ipv6-devprefix | 64 | bi | dddd::/64 | equal | not-sent |
| ipv6-deviid | 64 | bi | ::6/64 | equal | not-sent |
| ipv6-appprefix | 64 | bi | aaaa::/64 | equal | not-sent |
| ipv6-appiid | 64 | bi | ::2/64 | equal | not-sent |
| udp-dev-port | 16 | bi | 5683 | equal | not-sent |
| udp-app-port | 16 | bi | | ignore | value-sent |
| udp-length | 16 | bi | 0 | ignore | compute |
| udp-checksum | 16 | bi | 0 | ignore | compute |
| coap-version | 2 | bi | 1 | equal | not-sent |
| coap-type | 2 | bi | 0,1 | match- | mapping- |
| | | | | mapping | sent |
| coap-tkl | 4 | bi | 0 | ignore | value-sent |
| coap-code | 8 | dw | 5 | equal | not-sent |
| coap-code | 8 | up | 69 | equal | not-sent |
| coap-mid | 16 | bi | 0 | ignore | value-sent |
| coap-token | length | bi | | ignore | value-sent |
| | -byte( | | | | |
| | 16) | | | | |
| coap-option(6) | var | bi | | ignore | value-sent |
| coap-option(11) | var | dw | "s" | equal | not-sent |
| coap-option(12) | var | bi | 140 | equal | not-sent |
| coap-option(17) | var | dw | 140 | equal | not-sent |
\---------------------+--------+----+-----------+---------+------------/
Residue (Down): udp-app-port(16b) | coap-type(1b) | coap-tkl(4b) |
coap-mid(16b) | coap-token(var) |
coap-option(6)(var)
Total: 37b+var
Residue (Up): udp-app-port(16b) | coap-type(1b) | coap-tkl(4b) |
coap-mid(16b) | coap-token(var) |
coap-option(6)(var)
Total: 37b+var
This rule is the same as Rule 0, except that the Uri-Path targets "/s" instead of "/c", and an Observe option (option 6) is present in both directions to carry the subscription/notification counter. CODE is fixed per direction, not mapped: FETCH (5) Down for the subscription request, 2.05 Content (69) Up for the acknowledgment and every notification, so it contributes no residue at all. TYPE is restricted to a two-value mapping, CON (0) or NON (1): an ACK or RST — which carries no CODE and no Observe option — does not match this rule's fixed fields and falls through to Rule 3 instead.¶
Figure 40 applies Rule 2 (and Rule 3 for the ACK) to an Observe subscription on "/s" with three notifications: the first two are Non-Confirmable, the third is Confirmable and is acknowledged by an empty ACK, which does not match Rule 2's fixed CODE/Observe structure and therefore falls through to Rule 3.¶
Device SCHC SCHC App
1. Subscribe -- FETCH /s + Observe:0 (Down):
| | | |
| | |<---- Pkt 71B -----|
| | Rule 2/5 | |
| | residue: 53b | |
| | payload: 9B | |
| |<- 17B (pad 6b) ---| |
|<--- Pkt 71B ----| | |
IPv6(40B)+UDP(8B)+CoAP(23B) = 71 B
FETCH MID=42311 Token=248f Observe:0
Payload: [62048, 10000001] (9 B)
2. Subscription ack -- Observe:0, NON (Up):
| | | |
|---- Pkt 59B --->| | |
| | Rule 2/5 | |
| | residue: 53b | |
| | payload: 1B | |
| |--- 9B (pad 6b) -->| |
| | |----- Pkt 59B ---->|
IPv6(40B)+UDP(8B)+CoAP(11B) = 59 B
2.05 Content MID=65148 Token=248f Observe:0
Payload: {} (1 B)
3. Notification #1 -- Observe:1, NON (Up):
| | | |
|---- Pkt 75B --->| | |
| | Rule 2/5 | |
| | residue: 61b | |
| | payload: 16B | |
| |-- 25B (pad 6b) -->| |
| | |----- Pkt 75B ---->|
IPv6(40B)+UDP(8B)+CoAP(27B) = 75 B
2.05 Content MID=65149 Token=248f Observe:1
Payload: 16 B
4. Notification #2 -- Observe:2, NON (Up):
| | | |
|---- Pkt 75B --->| | |
| | Rule 2/5 | |
| | residue: 61b | |
| | payload: 16B | |
| |-- 25B (pad 6b) -->| |
| | |----- Pkt 75B ---->|
IPv6(40B)+UDP(8B)+CoAP(27B) = 75 B
2.05 Content MID=65150 Token=248f Observe:2
Payload: 16 B
5. Notification #3 -- Observe:3, CON (Up):
| | | |
|---- Pkt 75B --->| | |
| | Rule 2/5 | |
| | residue: 61b | |
| | payload: 16B | |
| |-- 25B (pad 6b) -->| |
| | |----- Pkt 75B ---->|
IPv6(40B)+UDP(8B)+CoAP(27B) = 75 B
2.05 Content MID=65151 Token=248f Observe:3
Payload: 16 B -- CON, needs an ACK
6. ACK for #3's CON, empty (Down, Rule 3):
| | | |
| | |<---- Pkt 52B -----|
| | Rule 3/5 | |
| | residue: 46b | |
| | payload: 0B | |
| |<-- 7B (pad 5b) ---| |
|<--- Pkt 52B ----| | |
IPv6(40B)+UDP(8B)+CoAP(4B) = 52 B
Empty ACK MID=65151 TKL=0
Payload: (none)
The notification stream ends when the client explicitly deregisters with a FETCH carrying the same token and an Observe option set to 1, when the client sends a RST, when a Confirmable notification receives no ACK after a number of retransmission attempts, or when an ICMPv6 destination unreachable message is received for the client — all of which the device interprets as a lost subscriber.¶
/---------------------+----------+----+-----------+---------+------------\
| FID | FL | DI | TV | MO | CDA |
+=====================+==========+====+===========+=========+============+
| ipv6-version | 4 | bi | 6 | equal | not-sent |
| ipv6-trafficclass | 8 | bi | 0 | ignore | not-sent |
| ipv6-flowlabel | 20 | bi | 0 | ignore | not-sent |
| ipv6-payload-length | 16 | bi | | ignore | compute |
| ipv6-nextheader | 8 | bi | 17 | equal | not-sent |
| ipv6-hoplimit | 8 | bi | 255 | ignore | not-sent |
| ipv6-devprefix | 64 | bi | dddd::/64 | equal | not-sent |
| ipv6-deviid | 64 | bi | ::6/64 | equal | not-sent |
| ipv6-appprefix | 64 | bi | aaaa::/64 | equal | not-sent |
| ipv6-appiid | 64 | bi | ::2/64 | equal | not-sent |
| udp-dev-port | 16 | bi | 5683 | equal | not-sent |
| udp-app-port | 16 | bi | | ignore | value-sent |
| udp-length | 16 | bi | 0 | ignore | compute |
| udp-checksum | 16 | bi | 0 | ignore | compute |
| coap-version | 2 | bi | 1 | equal | not-sent |
| coap-type | 2 | bi | | ignore | value-sent |
| coap-tkl | 4 | bi | | ignore | value-sent |
| coap-code | 8 | bi | | ignore | value-sent |
| coap-mid | 16 | bi | | ignore | value-sent |
| coap-token | length- | bi | | ignore | value-sent |
| | byte(16) | | | | |
\---------------------+----------+----+-----------+---------+------------/
Residue (Down): udp-app-port(16b) | coap-type(2b) | coap-tkl(4b) |
coap-code(8b) | coap-mid(16b) |
coap-token(var)
Total: 46b+var
Residue (Up): udp-app-port(16b) | coap-type(2b) | coap-tkl(4b) |
coap-code(8b) | coap-mid(16b) |
coap-token(var)
Total: 46b+var
Beyond empty ACKs and RSTs, this rule is also the fallback for an error response to a FETCH (Rule 0) or an Observe exchange (Rule 2) whose CODE is not 2.05 Content: since TYPE, TKL, CODE and MID are all left uncompressed here, any CoAP error code (e.g. 4.04, 4.05, 5.00) matches, and any diagnostic payload that may follow the header is carried unchanged, exactly as for a normal FETCH response.¶
/---------------------+--------+----+-----------+---------+------------\ | FID | FL | DI | TV | MO | CDA | +=====================+========+====+===========+=========+============+ | ipv6-version | 4 | bi | 6 | equal | not-sent | | ipv6-trafficclass | 8 | bi | 0 | ignore | not-sent | | ipv6-flowlabel | 20 | bi | 0 | ignore | not-sent | | ipv6-payload-length | 16 | bi | | ignore | compute | | ipv6-nextheader | 8 | bi | 58 | equal | not-sent | | ipv6-hoplimit | 8 | bi | 255 | ignore | not-sent | | ipv6-devprefix | 64 | bi | dddd::/64 | equal | not-sent | | ipv6-deviid | 64 | bi | ::6/64 | equal | not-sent | | ipv6-appprefix | 64 | bi | aaaa::/64 | equal | not-sent | | ipv6-appiid | 64 | bi | ::2/64 | equal | not-sent | | icmpv6-type | 8 | bi | 1 | equal | not-sent | | icmpv6-code | 8 | bi | | ignore | value-sent | | icmpv6-checksum | 16 | bi | 0 | ignore | compute | | unused | 32 | bi | | ignore | not-sent | | ipv6-version | 4 | bi | 6 | equal | not-sent | | ipv6-trafficclass | 8 | bi | 0 | ignore | not-sent | | ipv6-flowlabel | 20 | bi | 0 | ignore | not-sent | | ipv6-payload-length | 16 | bi | | ignore | compute | | ipv6-nextheader | 8 | bi | 17 | equal | not-sent | | ipv6-hoplimit | 8 | bi | 255 | ignore | not-sent | | ipv6-devprefix | 64 | bi | dddd::/64 | equal | not-sent | | ipv6-deviid | 64 | bi | ::6/64 | equal | not-sent | | ipv6-appprefix | 64 | bi | aaaa::/64 | equal | not-sent | | ipv6-appiid | 64 | bi | ::2/64 | equal | not-sent | | udp-dev-port | 16 | bi | 5683 | equal | not-sent | | udp-app-port | 16 | bi | | ignore | value-sent | | udp-length | 16 | bi | 0 | ignore | compute | | udp-checksum | 16 | bi | 0 | ignore | compute | | payload | var | bi | | ignore | not-sent | \---------------------+--------+----+-----------+---------+------------/ Residue (Down): icmpv6-code(8b) | udp-app-port(16b) Total: 24b Residue (Up): icmpv6-code(8b) | udp-app-port(16b) Total: 24b
ICMPv6 Type is fixed to 1 (Destination Unreachable), but Code is left uncompressed so that this rule covers every Destination Unreachable reason (0: no route, 1: administratively prohibited, 3: address unreachable, 4: port unreachable), not just the port-unreachable case that triggers when the CORECONF UDP endpoint itself is down.¶
[I-D.toutain-core-private-sid-translation] defines a mechanism for
translating official YANG SIDs into small negative integers ("private
SIDs"), each of which SHOULD end up encoding in a single CBOR byte
(range -1 to -24). Two modules are in use in this document: coreconf-m2m
(entry_point = 62000, official range of 400 SIDs) and atmos
(entry_point = 10000000, official range of 100 SIDs). Each module is
assigned a translation offset: the first module uses offset = 0,
and the second uses offset equal to the negative of the first
module's official range size, so that the two private ranges never
overlap.¶
Which module goes first matters: the first module gets the deepest,
single-byte-encoding range, so it should be the one whose SIDs appear
most often on the wire. Applying the allocation strategy in
[I-D.toutain-core-private-sid-translation] to the resource discovery
exchange of Figure 6 — which references fourteen
distinct atmos identityref values (one per discovered transducer)
against a single coreconf-m2m absolute SID (62002, the "/bootstrap"
node, appearing twice — once in the request, once as the response's
outer key) — the module with the higher count of on-wire absolute-SID
occurrences, atmos, is given offset = 0, and coreconf-m2m is shifted
by offset = -100. This is a property of the traffic pattern, not of
the modules themselves: a session dominated by coreconf-m2m structural
exchanges (e.g. repeated FETCH/iPATCH on notification-parameters)
would favor the opposite ordering.¶
For SCHC to actually perform this compression, Rules 0, 1, and 2
(Appendix G) each need an additional entry covering the CoAP
payload, using the sid-translation CDA defined in
[I-D.toutain-core-private-sid-translation] together with the
"fl-remaining" Field Length function [SCHC-TOWARD-9363BIS] — which
consumes whatever bytes are left in the packet, since the payload has
no fixed length. Since the Function Arguments MAY be repeated, both
models are covered by a single entry, shown in
Figure 43: entry_point = 10000000 (atmos) with
offset = 0, followed by entry_point = 62000 (coreconf-m2m) with
offset = -100.¶
/------------+--------------+----+-----+---------+-------------------\ | FID | FL | DI | TV | MO | CDA | +============+==============+====+=====+=========+===================+ | payload | fl-remaining | bi | | ignore | sid-translation( | | | | | | | 10000000, 0, | | | | | | | 62000, -100) | \------------+--------------+----+-----+---------+-------------------/
Figure 44 shows Figure 6 translated
with atmos offset = 0 and coreconf-m2m offset = -100. Delta keys
(the structural fields 7, 8, 6, 1, and 3 relative to their parent) keep
their magnitude but flip sign, per
p(child) - p(parent) = -(child - parent); the coreconf-m2m absolute
SID 62002 becomes -103; and every atmos identityref value becomes its
private SID in the -2 to -17 range.¶
Request payload, original (3 bytes):
19 F2 32 # unsigned(62002)
Request payload, translated (2 bytes):
38 66 # negative(-103)
Response payload, original (118 bytes):
{62002:
{7: 1788334280, 8: 30172, 6: 120,
1: [{3: 10000010}, {3: 10000008}, {3: 10000011}, {3: 10000002},
{3: 10000014}, {3: 10000016}, {3: 10000015}, {3: 10000012},
{3: 10000001}, {3: 10000013}, {3: 10000003}, {3: 10000009},
{3: 10000006}, {3: 10000004}]}}
Response payload, translated (61 bytes):
{-103:
{-7: 1788334280, -8: 30172, -6: 120,
-1: [{-3: -11}, {-3: -9}, {-3: -12}, {-3: -3},
{-3: -15}, {-3: -17}, {-3: -16}, {-3: -13},
{-3: -2}, {-3: -14}, {-3: -4}, {-3: -10},
{-3: -7}, {-3: -5}]}}
reference-epoch (1788334280), uptime (30172), and minimal-step (120) are plain integers, not SIDs, and are therefore left untranslated. Overall, this exchange shrinks from 121 bytes (3 + 118) to 63 bytes (2 + 61), a reduction of 48%, entirely attributable to the fourteen atmos identityref values collapsing from 5 bytes to 1 byte each.¶
Figure 13 exercises the opposite balance: it
references coreconf-m2m absolute SID 62048
("/history/time-series/values") twice (once in the FETCH+Observe
request, once as the notification's outer key) against a single atmos
identityref value (10000001, air-temperature) in the request. The
maximum delta any coreconf-m2m SID currently reaches from its
entry_point is 88 (Appendix C), so shifting coreconf-m2m from
offset = 0 to offset = -100 changes its encoded deltas from
0-88 to 100-188 — both comfortably inside CBOR's single 2-byte bracket
(24-255). Putting coreconf-m2m second therefore costs nothing here,
while atmos still gains its full 5-to-1-byte improvement:¶
Request payload, original (9 bytes):
82 19 F2 60 1A 00 98 96 81
# [62048, 10000001]
Request payload, translated (4 bytes, atmos offset=0,
coreconf-m2m offset=-100):
82 38 94 21
# [-149, -2]
Subscription ack, original and translated (1 byte, unchanged):
A0 # {}
Notification payload, original (16 bytes):
{62048: [189, 14, -3, 1, 5, 7, 3, 2, 1, 2]}
Notification payload, translated (15 bytes):
{-149: [189, 14, -3, 1, 5, 7, 3, 2, 1, 2]}
The history values themselves (189, 14, -3, 1, 5, 7, ...) are a delta-encoded time series, not SIDs, and are left untranslated. This exchange shrinks from 26 bytes (9 + 1 + 16) to 20 bytes (4 + 1 + 15), a 23% reduction — smaller than the first example's 48%, since only one atmos SID is involved here, but still a clear net gain, and with coreconf-m2m paying no penalty for going second. Across both examples, putting atmos first is never worse and sometimes substantially better, confirming it as the right default ordering for this document's traffic mix.¶
This work has been supported by the SCHC Chair from IMT Atlantique and Afnic.¶
Claude (Anthropic) assisted with editing this document.¶