lumnik. / scenarios All scenarios · Français

The freezer probe reads 6.8 °C in the middle of the night

Goal Be alerted as soon as an independent HACCP probe sees a freezer above -18 °C, even when the refrigeration controller says nothing.

In autumn 2026, supermarket refrigeration controllers were changed remotely. In France, on 11 October, no alarm went off. An independent HACCP probe measures the temperature, and ChirpStack publishes its readings to Kafka.

probes.csv
device,store,cabinet,aisle,products,max_temperature_c,on_call
PROBE-07,Store 17 (Brussels),FROZEN-07,Frozen food,Ice cream and frozen vegetables,-18,Sofia Martin
PROBE-08,Store 17 (Brussels),FROZEN-08,Frozen food,Frozen fish,-18,Sofia Martin
PROBE-09,Store 17 (Brussels),FROZEN-09,Frozen food,Frozen meals,-18,Sofia Martin
evening.jsonl
{"deduplicationId": "00000000-0000-4000-8000-000000000001", "time": "2026-11-01T22:00:00Z", "deviceInfo": {"tenantName": "Store 17", "applicationName": "cold-room-probes", "deviceProfileName": "haccp-probe", "deviceName": "PROBE-07", "devEui": "a84041000181c007", "tags": {}}, "devAddr": "01f2a307", "adr": true, "dr": 5, "fCnt": 101, "fPort": 2, "confirmed": false, "data": "AH0=", "object": {"temperature": -21.4}, "rxInfo": [{"gatewayId": "0016c001ff10a235", "rssi": -71, "snr": 9.5}], "txInfo": {"frequency": 868100000}}
{"deduplicationId": "00000000-0000-4000-8000-000000000002", "time": "2026-11-01T22:00:00Z", "deviceInfo": {"tenantName": "Store 17", "applicationName": "cold-room-probes", "deviceProfileName": "haccp-probe", "deviceName": "PROBE-08", "devEui": "a84041000181c008", "tags": {}}, "devAddr": "01f2a308", "adr": true, "dr": 5, "fCnt": 102, "fPort": 2, "confirmed": false, "data": "AH0=", "object": {"temperature": -22.1}, "rxInfo": [{"gatewayId": "0016c001ff10a235", "rssi": -71, "snr": 9.5}], "txInfo": {"frequency": 868100000}}
{"deduplicationId": "00000000-0000-4000-8000-000000000003", "time": "2026-11-01T22:00:00Z", "deviceInfo": {"tenantName": "Store 17", "applicationName": "cold-room-probes", "deviceProfileName": "haccp-probe", "deviceName": "PROBE-09", "devEui": "a84041000181c009", "tags": {}}, "devAddr": "01f2a309", "adr": true, "dr": 5, "fCnt": 103, "fPort": 2, "confirmed": false, "data": "AH0=", "object": {"temperature": -21.8}, "rxInfo": [{"gatewayId": "0016c001ff10a235", "rssi": -71, "snr": 9.5}], "txInfo": {"frequency": 868100000}}
{"deduplicationId": "00000000-0000-4000-8000-000000000004", "time": "2026-11-01T22:05:00Z", "deviceInfo": {"tenantName": "Store 17", "applicationName": "cold-room-probes", "deviceProfileName": "haccp-probe", "deviceName": "PROBE-09", "devEui": "a84041000181c009", "tags": {}}, "margin": 9, "externalPowerSource": false, "batteryLevel": 86.0}
manifests/30-probe-reading.yaml
kind: Entityname: ScenarioProbeReadingAdescription: "A cold-room probe reading with the cabinet it measures."match: [deduplicationId]scopes: [cold-chain]columns:  deduplicationId: { label: "Uplink" }  device: { label: "Probe" }  measured_at: { label: "Measured at" }  temperature_c: { label: "Temperature (°C)" }  store: { label: "Store" }  cabinet: { label: "Cabinet" }  aisle: { label: "Aisle" }  products: { label: "Products" }  max_temperature_c: { label: "Maximum (°C)" }  on_call: { label: "On call" }sources:  - table: t_scenario_probe_events_a    map:      deduplicationId: deduplicationId      device: device      measured_at: measured_at      temperature_c: temperature_cjoins:  - table: t_scenario_probe_registry_a    from: device    to: device    map:      store: store      cabinet: cabinet      aisle: aisle      products: products      max_temperature_c: max_temperature_c      on_call: on_call
manifests/40-frozen-alert.yaml
apiVersion: apps.lumnik.io/v1kind: Processmetadata:  name: scenario-probe-frozen-aspec:  scope: cold-chain  on:    event: data.row.changed    table: connector.t_scenario_probe_events_a  when:    - temperature_c>-18  emit:    type: decision.frozen-too-warm-a    message: "{device} reads {temperature_c} °C at {measured_at}: a frozen cabinet must stay at -18 °C or below"
night.jsonl
{"deduplicationId": "00000000-0000-4000-8000-000000000005", "time": "2026-11-02T02:30:00Z", "deviceInfo": {"tenantName": "Store 17", "applicationName": "cold-room-probes", "deviceProfileName": "haccp-probe", "deviceName": "PROBE-08", "devEui": "a84041000181c008", "tags": {}}, "devAddr": "01f2a308", "adr": true, "dr": 5, "fCnt": 105, "fPort": 2, "confirmed": false, "data": "AH0=", "object": {"temperature": -21.9}, "rxInfo": [{"gatewayId": "0016c001ff10a235", "rssi": -71, "snr": 9.5}], "txInfo": {"frequency": 868100000}}
{"deduplicationId": "00000000-0000-4000-8000-000000000006", "time": "2026-11-02T02:31:00Z", "deviceInfo": {"tenantName": "Store 17", "applicationName": "cold-room-probes", "deviceProfileName": "haccp-probe", "deviceName": "PROBE-07", "devEui": "a84041000181c007", "tags": {}}, "margin": 9, "externalPowerSource": false, "batteryLevel": 86.0}
{"deduplicationId": "00000000-0000-4000-8000-000000000007", "time": "2026-11-02T02:30:00Z", "deviceInfo": {"tenantName": "Store 17", "applicationName": "cold-room-probes", "deviceProfileName": "haccp-probe", "deviceName": "PROBE-07", "devEui": "a84041000181c007", "tags": {}}, "devAddr": "01f2a307", "adr": true, "dr": 5, "fCnt": 107, "fPort": 2, "confirmed": false, "data": "AH0=", "object": {"temperature": 6.8}, "rxInfo": [{"gatewayId": "0016c001ff10a235", "rssi": -71, "snr": 9.5}], "txInfo": {"frequency": 868100000}}
{"deduplicationId": "00000000-0000-4000-8000-000000000008", "time": "2026-11-02T02:30:00Z", "deviceInfo": {"tenantName": "Store 17", "applicationName": "cold-room-probes", "deviceProfileName": "haccp-probe", "deviceName": "PROBE-09", "devEui": "a84041000181c009", "tags": {}}, "devAddr": "01f2a309", "adr": true, "dr": 5, "fCnt": 108, "fPort": 2, "confirmed": false, "data": "AH0=", "object": {"temperature": -21.6}, "rxInfo": [{"gatewayId": "0016c001ff10a235", "rssi": -71, "snr": 9.5}], "txInfo": {"frequency": 868100000}}
$ lm inbox --process scenario-probe-frozen-c
ID   WHEN      PROCESS                  MESSAGE                                                                                       ENTITY_KEY
276  just now  scenario-probe-frozen-c  PROBE-07 reads 6.8 °C at 2026-11-02T02:30:00Z: a frozen cabinet must stay at -18 °C or below  d05be1cf9429dda18cfe3e63a18c0878c3809e42b022bba79279d5bfa6da0bcc

$ lm entity get ScenarioProbeReadingC --filter device=PROBE-07 -o json
[
  {
    "deduplicationId": "00000000-0000-4000-8000-000000000001",
    "device": "PROBE-07",
    "measured_at": "2026-11-01T22:00:00Z",
    "temperature_c": "-21.4",
    "store": "Store 17 (Brussels)",
    "cabinet": "FROZEN-07",
    "aisle": "Frozen food",
    "products": "Ice cream and frozen vegetables",
    "max_temperature_c": "-18",
    "on_call": "Sofia Martin",
    "_conflicts": "{}"
  },
  {
    "deduplicationId": "00000000-0000-4000-8000-000000000006",
    "device": "PROBE-07",
    "measured_at": "2026-11-02T02:31:00Z",
    "temperature_c": null,
    "store": "Store 17 (Brussels)",
    "cabinet": "FROZEN-07",
    "aisle": "Frozen food",
    "products": "Ice cream and frozen vegetables",
    "max_temperature_c": "-18",
    "on_call": "Sofia Martin",
    "_conflicts": "{}"
  },
  {
    "deduplicationId": "00000000-0000-4000-8000-000000000007",
    "device": "PROBE-07",
    "measured_at": "2026-11-02T02:30:00Z",
    "temperature_c": "6.8",
    "store": "Store 17 (Brussels)",
    "cabinet": "FROZEN-07",
    "aisle": "Frozen food",
    "products": "Ice cream and frozen vegetables",
    "max_temperature_c": "-18",
    "on_call": "Sofia Martin",
    "_conflicts": "{}"
  }
]

Limits

  • lumnik sees only what the probe sends: a silent probe, gateway or ChirpStack raises no decision here.
  • The decision message cites the event's columns only: the cabinet and on-call are on the record, not in the message.
  • One threshold, -18 °C, for every cabinet of this Process.
  • lumnik does not block an attack and does not run the cooling.
  • ChirpStack publishes every event type on the same topic: a status arrives as a row with no temperature.
  • Fictional store, synthetic data.

Cold chain · store

At 02:30, a freezer is at 6.8 °C.

The refrigeration controller says nothing.

In France, on 11 October 2026, supermarket controllers changed remotely raised no alarm.

Fictional store · synthetic data

The problem

The alarm lives in the box under attack.

  • The controller runs the cooling and its own alarms.
  • A HACCP probe measures separately, over its own radio.
  • Its reading arrives nested in a ChirpStack event.

Nothing is changed

The probe, ChirpStack and Kafka stay in place.

  • The topic stays ChirpStack's.
  • The cabinet registry stays a CSV file.
  • No application to build.

What had to be written

promote:
  - name: temperature_c
    path: $.object.temperature

One line reads the temperature in the event.

A 76-line scenario, four declaration files.

What the team receives

One decision, for the one probe above -18 °C.

Decision inbox
PROBE-07 reads 6.8 °C at 2026-11-02T02:30:00Z: a frozen cabinet must stay at -18 °C or below

Real message, replay of 3 November 2026.

The record says the rest

The cabinet, the aisle and who is on call.

  • FROZEN-07, frozen-food aisle.
  • Ice cream and frozen vegetables.
  • On call: Sofia Martin.

The limit

lumnik sees only what the probe sends.

  • A silent probe raises nothing here.
  • lumnik does not block the attack.
  • It does not run the cooling.

Try it

The scenario replays end to end.

ChirpStack, Kafka, CSV, entity, Process: every step is published with its output.

This scenario needs lumnik Pro.

See the editions →