# RMon Data Model ## Signal A **signal** is a named scalar time-series. Every signal has a unique `SignalId` and associated metadata. ```rust /// Globally unique signal identifier. /// Convention: "{source_type}://{source_name}/{path/to/signal}" /// Examples: /// "udp://imu_stream/accel_x" /// "csv:///mnt/data/temperatures.csv/sensor_3" /// "epics://PV:SYS:TEMP:READBACK" type SignalId = String; struct SignalInfo { id: SignalId, label: String, // human-readable display name unit: String, // e.g. "m/s²", "°C", "" scale: f64, // raw_value * scale + offset = engineering_value offset: f64, sampling_period: Option,// None if irregular/event-driven source_type: SourceType, path: Vec, // tree path for UI grouping, e.g. ["imu", "accel"] } enum SourceType { Csv, Udp, Epics, Custom(String) } ``` Signals are **read-only** from the UI's perspective. The agent owns all signal definitions (from its config file). ## Sample ```rust struct Sample { /// Nanoseconds since Unix epoch (UTC). /// All source timestamps are normalized to this by the adapter. timestamp: i64, value: f64, } ``` All timestamps are normalized at ingestion time inside the adapter. The adapter knows the source-specific format (string, u64 seconds, etc.) and converts it. The rest of the system only sees `i64` nanoseconds. ## Timestamp Normalization Examples | Source format | Example | Agent conversion | |--------------|---------|-----------------| | `%Y.%m.%d %H:%M:%S` string | `"2024.01.15 14:32:07"` | parse → UTC `i64` ns | | u64 microseconds since epoch | `1705329127000000` | multiply by 1000 | | u64 seconds since epoch | `1705329127` | multiply by 1_000_000_000 | | Relative milliseconds | `12345` | add agent start epoch | Adapters declare their `TimestampFormat` in config; the normalization logic lives in a shared utility in `rmon-agent::sources`. ## Storage Modes The agent can store signal data in two modes per signal group: ### Continuous Storage ```rust StorageMode::Continuous ``` - Data is appended to an on-disk log starting when recording is requested - Data is **kept until explicitly deleted** (either via UI command or manual deletion) - File format: binary append log of `(i64 timestamp, f64 value)` pairs, one file per signal - File path: `{storage_dir}/{signal_id_escaped}/{start_timestamp}.bin` ### Circular (Loop) Storage ```rust StorageMode::Circular { window: Duration } ``` - Agent maintains an in-memory ring buffer of the last `window` duration - On UI request, can also persist to disk (same format as continuous) - `window` is configured per signal group in the agent config ### Storage Location Configured per agent in `config.toml`. Defaults to `~/.rmon/data/`. The agent never deletes data automatically (except from in-memory circular buffers). ## Signal Tree For UI navigation, signals are organized into a tree using the `path` field of `SignalInfo`. Each element of the `Vec` is a tree level. Example paths: ``` ["system", "imu", "accelerometer"] → system > imu > accelerometer > {signals} ["temperatures", "coolant"] → temperatures > coolant > {signals} ``` The UI renders this as a collapsible tree panel. Signals can be dragged from the tree onto plot panels. ## UI-Side Signal Cache The UI maintains a per-signal ring buffer for display (configurable, default: last 60 seconds). Incoming `DataBatch` messages are appended to this buffer. Historical query responses fill in older portions. The display buffer is separate from the agent's storage — it only holds what the UI needs to render the current view.