HEART Signal Tables#
Every signal in a product, declared once, checked by a person
Author: Milind K. Patil, Syncaissa Systems Inc. Status: Working draft 1, November 2026. To become part of Part 8 (Product Fit) of the HEART Standards Family. Version: 1.0 (draft) Schema: schemas/1.0/signal-table.schema.json · Example: examples/signal-table.json Track: Intermediate: all of it, if your product serves many signals (Modbus, CAN, firmware variables).
In one minute#
A product is full of signals: temperatures, speeds, currents, switch positions, counters. A HEART that knows them can notice problems early, explain them, and give an AI model the facts it needs.
A signal table (signals.json) lists every signal the HEART may read, with its unit, range, privacy and whether it may ever be changed. Three rules make it safe:
- Declared or invisible. A signal not in the table is never served or sent anywhere.
- Careful by default. Nothing is writable unless a person says so. A safety signal is never writable.
- A person checks it. A HEART MUST NOT load a table that has not been reviewed.
Nobody has to write the table by hand. The generator drafts it from files engineers already have: a Modbus register map, a CAN database, or the firmware's own variables. A person then checks the draft and seals it.
1. What a signal is#
| Field | Meaning | Example |
|---|---|---|
name | The system ID, then a lower-case signal name | com.example.cool-air.room_temp |
title, description | For people, and for AI models | "Room temperature" |
type | number, integer, boolean, string or enum | number |
unit | The UCUM code, the standard list of units | Cel (degrees Celsius) |
range | The possible values. Outside them, the sensor is probably broken. | {"min": -20, "max": 60} |
source | Where it comes from: a bus address, or a pin or variable on the chip | Modbus input register 0 (table: input, register: 0), scale 0.1 |
sampleSec, historySec | How often to read it, and how much history to keep | 5, 3600 |
privacy | public, internal, private or never-share (Part 5) | internal |
writable | May HEART ever change it? Default: no. | false |
physical | Does changing it move, heat, power or switch something? | true for a setpoint |
safety | Is it part of a safety function? If so: read-only, always. | true for a pressure trip |
derive | Summaries to keep, from the shared statistic list: value (latest), min, max, mean, stddev, trend (units per minute, least squares), overshoot (% of the setpoint step), count (events in the window) | ["mean", "trend"] |
alerts | When to raise a fault code by itself | out of range, sensors.out-of-range |
A table MAY also carry $comment, free text for people such as a licence line. HEART ignores it.
Where signals can come from#
protocol | Fields | Example |
|---|---|---|
modbus-tcp, modbus-rtu | table (holding, input, coil, discrete), register (the 0-based protocol address within table, as sent on the wire; old reference numbers such as 30001 or 40021 MUST NOT be used), scale, offset | An air conditioner's control board |
can | frameId, startBit, bits, byteOrder, signed, scale, offset | A vehicle, a chiller, a battery |
on-chip | symbol (a firmware variable) or channel (an ADC channel or pin) | A microcontroller reading its own sensors |
| Any adapter of API Standard 14.3 | Following that adapter's request format | BACnet, OPC UA, I²C |
2. The rules#
- Declared or invisible. A HEART MUST NOT read, serve or send a signal that is not in its table.
- Reviewed before use. A table is usable only when it has a
reviewedentry (who, and when) and no signal is still markeddraft. - Writable only on purpose.
writabledefaults tofalse. Writing a signal passes the approval gate like any action (Part 6), and writing aphysicalsignal counts as a physical action. - Safety signals are read-only. A signal marked
safetyMUST NOT be writable, and the schema rejects one that is. HEART watches safety signals; it never controls them (Part 6, Section 9). - Watching never disturbs control. A HEART reads signals at low priority, from copies or shadow registers where possible, never from inside a control loop.
- Personal signals are private. A signal about a person, such as occupancy, location, voice or camera, MUST be
privateornever-share. - Budgets are fixed. Sampling and history have fixed limits, so watching never fills the device's memory or wears out its flash.
3. From what you have to a sealed table#
register map (CSV) ─┐
CAN database (.dbc) ├──► draft ──► a person checks ──► seal ──► use
firmware header (.h)┘ (all signals marked draft, (reviewed: who, when)
nothing writable)
The generator, heart-templates/tools/heart_signals.py, has four steps:
| Step | Command | What it does |
|---|---|---|
| Draft | heart_signals.py draft --system com.acme.chiller --modbus regs.csv --dbc bus.dbc --header signals.h > signals.json | Turns every register, CAN signal and annotated variable into a draft signal. Converts units to UCUM. |
| Check | heart_signals.py check signals.json | Lists errors, and every decision still to make |
| Seal | heart_signals.py seal signals.json --by "A. Engineer" | Marks the table reviewed. Refused while anything is a draft or wrong. |
| Export | heart_signals.py export signals.json --out custom/ | Writes property definitions that heartd loads (Modbus signals, today) |
| Export as C | heart_signals.py export signals.json --out main/heart_signals.h | Writes every signal as a C table for a device, so the chip needs no JSON parser. Used by the esp32-full template. |
What the generator does on its own, so the reviewer doesn't have to start from nothing:
- Every signal is a draft, and nothing is writable, even if the register map says it can be written. The reviewer is told, and decides.
- Names that suggest a person (occupancy, location, voice) are made private.
- Names that suggest a safety function (trip, interlock, emergency stop) are marked safety, so they can never be made writable.
- Names that suggest an actuator (motor, valve, setpoint) get a note: "if made writable, also set physical".
- A missing range gets a note, because without one a broken sensor cannot be noticed.
Annotating firmware variables#
The generator reads simple C declarations, with optional hints after // heart:
volatile float room_temp_c; // heart: unit=Cel range=-40..85 title="Room temperature"
volatile uint16_t fan_rpm; // heart: unit=rpm range=0..3000
volatile float fan_speed_target; // heart: unit=% range=0..100 writable
writable here is only a suggestion to the reviewer. It never makes a signal writable by itself.
4. How a signal table fits the rest of HEART#
| HEART part | How signals appear |
|---|---|
| Part 3 (Information Model) | Each signal is a property: readable at /heart/v1/properties/{name}, with its unit, quality and time |
| Part 3, fault codes | alerts raise standard codes such as sensors.out-of-range and sensors.stuck |
| Part 2 (Autonomy Levels) | Signals give the evidence for "why" at A2, and the trends a Playbook watches at A4 |
| Part 5 (Privacy) | Each signal's privacy decides who may read it, and whether it may ever reach an AI model |
| Part 6 (Human Control) | Writing a signal is an action, and physical writes need a person's proof of presence |
| Templates | The Full starter templates (first: esp32-full) use the sealed table, exported as C, and serve each signal as a property |
5. Open items#
| Item | Why it matters |
|---|---|
physical on property definitions | The custom definition schema does not yet list physical for properties, and the reference implementation does not yet gate a physical property write as a physical action. Both must be fixed before writable physical signals are used. |
| Export for CAN and on-chip sources | Export writes Modbus definitions today. CAN definitions need the CAN adapter's request format to be published. |
| More source formats | OPC UA node sets, BACnet object lists, EtherCAT ESI files and ELF symbol tables would cover most other products. |
| Derived signals on small chips | mean, trend and stddev need fixed-memory formulas for microcontrollers. |