Table of Contents

Build an IoT monitor with Native AOT

The greenhouse monitor is an application-shaped Raven project for a full .NET edge device such as a Linux-based Raspberry Pi. It polls simulated telemetry by default or live CO2, temperature, and humidity from an SCD40/SCD41 over I2C, validates each snapshot, models operational states with unions, and produces a changing console report. The same project can publish as a Native AOT binary.

The complete project lives in samples/projects/greenhouse-monitor.

Project layout

greenhouse-monitor/
├── GreenhouseMonitor.rvnproj
├── publish-aot.sh
└── src/
    ├── GreenhouseMonitor.rvn
    └── telemetry.rvn

GreenhouseMonitor.rvn owns the entry point, domain records, state unions, evaluation functions, and report formatting. telemetry.rvn owns the device boundary: its polling interface, simulated and SCD4x implementations, and input validation. Configuration selects the adapter without changing the domain model.

Consume an async telemetry stream

async func Main() -> Task {
    let telemetry = CreateTelemetrySource()

    await for result in telemetry.Poll(CancellationToken.None) {
        match result {
            Ok(let readings) => PrintReport(BuildReport(readings))
            Error(let error) => PrintError(error)
        }
    }
}

The boundary returns IAsyncEnumerable<Result<SensorReading[], TelemetryError>>. That type makes the stream, successful snapshots, and expected device errors visible without hiding them behind callbacks or exceptions.

Model operational states

record SensorReading(
    Zone: string,
    TemperatureCelsius: double,
    HumidityPercent: double,
    CarbonDioxidePpm: int
)

union ZoneHealth {
    case Healthy
    case Attention(reason: string)
    case Critical(reason: string)
}

union TelemetryError {
    case NoReadings
    case InvalidReading(zone: string, reason: string)
    case SensorUnavailable(reason: string)
}

Records describe telemetry data. Unions describe the closed states that the monitor must handle. Pattern matching then keeps report and recovery paths explicit.

Build and run

dotnet build \
  samples/projects/greenhouse-monitor/GreenhouseMonitor.rvnproj \
  --property WarningLevel=0

dotnet \
  samples/projects/greenhouse-monitor/bin/Debug/net10.0/GreenhouseMonitor.dll

The simulated source is the default mock. On a Raspberry Pi with an SCD40 or SCD41 connected to I2C bus 1 at address 0x62, select live telemetry with:

GREENHOUSE_TELEMETRY=scd4x \
GREENHOUSE_ZONE=Propagation \
dotnet \
  samples/projects/greenhouse-monitor/bin/Debug/net10.0/GreenhouseMonitor.dll

See the sample README for wiring, I2C setup, and troubleshooting details.

Publish with Native AOT

From the sample directory:

./publish-aot.sh

On a 64-bit Linux Raspberry Pi, publish and run locally with:

RUN=1 ./publish-aot.sh linux-arm64

Native AOT does not support arbitrary cross-OS publication. Produce the Linux Arm64 executable on a compatible Linux Arm64 build host. See the sample README for artifact locations and rebuild options.