Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Install and try a model

This guide installs Esdieff and runs it on a supplied thermostat model. No device is needed, and nothing leaves your machine.

Install Esdieff

Esdieff supports macOS 15+, Linux and Windows. Prebuilt releases are available through Homebrew, apt and dnf, and as direct downloads.

macOS

Install the prebuilt command from the Esdieff Homebrew tap:

brew install pederbe/tap/esdieff

Update with brew update && brew upgrade esdieff. Remove it with brew uninstall esdieff.

Windows

Download the Windows ZIP, extract it, and add the folder containing esdieff.exe to your PATH. Open a new terminal and check the installation:

esdieff --version

To update, replace the extracted files with a newer release. To uninstall, remove the folder and its entry from your PATH.

A WinGet listing is being prepared. WinGet installation will become available after the community listing is approved.

Linux

Install on Debian, Ubuntu or Fedora:

curl -fsSL https://raw.githubusercontent.com/pederbe/esdieff/main/scripts/install.sh | sh

Updates arrive through your package manager.

Direct downloads and Cargo

Download a release archive, extract it and put the command on your PATH. Download a newer archive to update it. Remove the extracted command to uninstall it.

Rust users can install a prebuilt release with:

cargo binstall esdieff

To build from source, install a current Rust toolchain and run:

cargo install --locked esdieff

This builds the esdieff command and puts it in Cargo’s binary directory, normally ~/.cargo/bin. Make sure that directory is on your PATH.

From a checkout of the repository, install the same way with a path instead:

cargo install --locked --path .

Repeat the Cargo installation command to update. Use cargo uninstall esdieff to remove it.

Check that it works

$ esdieff --help
Usage:
esdieff check --plan PLAN [--format human|json]
esdieff acquire --github OWNER/REPOSITORY --commit COMMIT --path REPOSITORY_PATH --sha256 DIGEST --document-output DEST [--report-output DEST] [--format human|json]
esdieff check [--directory DIR] [--recursive] [--exclude RELATIVE_PATH] [--root INPUT] [--assessment initial-v1|expanded-modified-v1|expanded-units-v1|expanded-content-format-v1|expanded-pattern-syntax-v1|expanded-numeric-v1|expanded-consistency-v1|full-baseline] [--registry-knowledge recorded|withheld] [--strict-warnings] [--format human|json|sarif] [INPUT...]

The help continues with one line per command. If the command is not found, check that its installation folder is on your PATH and open a new terminal. Cargo installations normally use ~/.cargo/bin.

Try the thermostat example

The example is a room thermostat with two properties, one action and one event. Its writable targetTemperature accepts numbers from 5 to 30 °C in half-degree steps:

"targetTemperature": {
  "description": "Requested room temperature in half-degree increments",
  "type": "number", "nullable": false, "minimum": 5, "maximum": 30,
  "multipleOf": 0.5, "unit": "Cel", "readable": true, "writable": true
}

Download rich.sdf.json from the repository, or copy it from a checkout, and save it as thermostat.sdf.json in an empty directory. Take the rich model, not the smaller thermostat.sdf.json next to it in the repository: that one has no half-degree step, so it accepts 5.25. Then generate test values from its rules. The command names the document to generate for, followed by every model file to supply, which here is the same file:

$ esdieff generate --document thermostat.sdf.json thermostat.sdf.json
Generated values esdieff.generation/1: passed
Values: 15 admitted, 60 rejected, 0 undetermined
Descriptions: 4

property /sdfObject/Thermostat/sdfProperty/currentTemperature: complete
  ...

property /sdfObject/Thermostat/sdfProperty/targetTemperature: complete
  rejected     null          null (sample.null)
  admitted     minimum       5
  admitted     maximum       30
  rejected     below_minimum 4 (sample.bound)
  rejected     above_maximum 31 (sample.bound)
  rejected     not_multiple  5.25 (sample.multiple)
  rejected     wrong_type    "" (sample.type)
  rejected     wrong_type    true (sample.type)
  rejected     wrong_type    {} (sample.type)
  rejected     wrong_type    [] (sample.type)

action_input /sdfObject/Thermostat/sdfAction/configure: complete
  ...

The output is trimmed to the targetTemperature block. The full report lists 75 values across the two properties, the configure action input and the alarm event output.

Each line is one generated value. The middle column names the rule the value aims at, and the first column says what the model does with it:

  • 5 and 30 aim at the minimum and maximum and are admitted. They are the boundary values a device must accept.
  • 31 aims just above the maximum and is rejected, with the condition code sample.bound naming the rule it breaks.
  • 5.25 is a quarter-degree value, so it is rejected by the half-degree step, sample.multiple.

Admitted and rejected are not guesses. Esdieff checks every generated value against the model the same way it checks a sample you supply, so the labels are what the model actually says. A value nobody can settle would be listed as undetermined.

Next, check the model itself. --assessment full-baseline requests every model obligation Esdieff records from RFC 9880:

$ esdieff check --assessment full-baseline thermostat.sdf.json
Esdieff: passed
Complete: true
Errors: 0; warnings: 0; notices: 0; incomplete checks: 0
Scope: catalogue
Broader RFC coverage: complete
Recorded registry policy: use_recorded
Unit guidance: complete
Assessment area unit-spelling/1: assessed
Assessment area unit-registry-guidance/1: assessed
Unit counts: known_primary 4; known_secondary 0; unknown 0; unavailable 0; legacy 0; uri_names 0
Assessment area content-format-syntax/1: not_applicable
Assessment area content-format-registry/1: not_applicable
Registry iana.senml-units: available; snapshot iana.senml-units/2026-10-05/6246352c590e933b; scope senml-units; entries_count 67
Registry extraction_policy esdieff.unit-section-positives/1; digest 6246352c590e933b72c25ef9f363171e40fea347bb7e23d08b04f9436894ed34; raw_digest 3eff03640c5a5d2abd09e330488aeb9fae0b2766ea69b1114295bbec5c44e754; source_updated 2026-02-02; retrieved_at 2026-10-05T16:49:51Z
Requested assessment: rfc9880/base-1
Baseline: rfc9880/base-1 (RFC 9880)
Acceptance restrictions: A001, A002; strict warnings: false
Requested sources: [1]
Requested inventory: esdieff.full-model/1
Requested coverage (assessment_area): total 13; assessed 10; not applicable 3; unassessed 0; undetermined 0
Requirement inventory: rfc9880.model/1
Requirement coverage: total 54; assessed 28; not applicable 26; unassessed 0; undetermined 0
Source 1 ["input1"]: acquired / usable (1932 retained bytes, content complete: true)
Diagnostic details omitted: 0

The first line is the verdict. Complete: true means nothing was left unfinished, and the coverage lines account for every one of the 54 recorded requirements as assessed or not applicable to this model. The unit lines show that the four Cel unit names were found in the recorded SenML units registry snapshot, so the check needed no network access. The command exits with status 0 for a passed check, 1 for findings, and 3 when something could not be settled.

Next steps