From 60e6640abb6ba0b8baf5423cd426b4c55d3adf84 Mon Sep 17 00:00:00 2001 From: David Mozart Date: Thu, 1 Oct 2026 16:40:34 +0200 Subject: [PATCH 1/7] docs: point data models to srcful-data-models Field definitions live in srcful-data-models (v2.0.0) and DER/device types in the device-support API. Replace the stale telemetry tables (pv, v2x_charger, connector_status) with a link and a rule summary, and move the hierarchy from Wallet to Organization -> Site -> Device -> DER. Co-Authored-By: Claude Opus 5.5 --- docs/technical/data-models.md | 347 +++++------------------------- docs/technical/ems.md | 2 +- docs/technical/hardware/README.md | 2 +- 3 files changed, 51 insertions(+), 300 deletions(-) diff --git a/docs/technical/data-models.md b/docs/technical/data-models.md index c72c7373..287ab88b 100644 --- a/docs/technical/data-models.md +++ b/docs/technical/data-models.md @@ -6,24 +6,25 @@ pagination_prev: null # Data Models -This document describes Sourceful's data model architecture, covering both the logical hierarchy of resources and the telemetry data structures for distributed energy resources. +This document describes Sourceful's data model architecture: the logical hierarchy of resources, and where the telemetry and control field definitions for distributed energy resources live. ## Platform Hierarchy Sourceful organizes energy resources in a four-level hierarchy: -### 1. WALLET → 2. SITE → 3. DEVICE → 4. DER +### 1. ORGANIZATION → 2. SITE → 3. DEVICE → 4. DER -### WALLET (Permission Layer) +Users and API clients act through identities that belong to an Organization (Identity → Organization → Site → Device → DER). -The **Wallet** is the top-level authentication and authorization entity. It represents: +### ORGANIZATION (Permission Layer) -- User identity and ownership -- Permission boundaries (OAuth-style scopes) -- Access control for all resources beneath it +The **Organization** is the top-level owner of resources. It represents: + +- Ownership of everything beneath it +- The permission boundary for access to its resources - The entity that grants or revokes access to applications -A Wallet can own multiple Sites. +An Organization can own multiple Sites. ### SITE (Logical Grouping) @@ -77,318 +78,68 @@ A **DER** is the logical representation of an energy resource or function: - **DEVICE**: The inverter itself (communication/control point via Modbus) - **DER #1**: Solar PV (generation capability) - **DER #2**: Battery (storage capability) -- **DER #3**: Grid connection (import/export capability) +- **DER #3**: Meter (the inverter's AC side, import/export) -You control the **Device** (inverter), but you represent its capabilities as separate **DERs** (PV, battery). You cannot directly control the battery - you control the inverter which manages the battery - but you still model the battery as a distinct DER for optimization purposes. +You control the **Device** (inverter), but you represent its capabilities as separate **DERs** (solar, battery, meter). You cannot directly control the battery - you control the inverter which manages the battery - but you still model the battery as a distinct DER for optimization purposes. **DER Types:** -- **PV (Photovoltaic)**: Solar generation -- **Battery**: Energy storage -- **Meter**: Grid import/export measurement -- **Charger**: EV charger (uni- or bi-directional) -- **Flexible Load**: Controllable consumption (heat pumps, HVAC, etc.) +DER types and device types are owned by the device-support API (`GET /der-types`, `GET /device-types`): + +- **DER types**: `solar`, `battery`, `meter`, `ev_charger_port` +- **Device types**: `inverter`, `battery`, `energy_meter`, `ev_charger`, `v2x_charger` + +Each device type allows a fixed set of DER types. There is no inverter DER: the AC side of an inverter is a `meter` DER on the inverter device. ## Hierarchy Example ``` -WALLET: user_abc123 +ORGANIZATION: org_abc123 └─ SITE: home_main_street - ├─ DEVICE: hybrid_inverter_01 (Modbus-TCP) - │ ├─ DER: pv_rooftop (Solar PV) - │ └─ DER: battery_01 (Home Battery) - ├─ DEVICE: v2x_charger_01 (ISO 15118) - │ └─ DER: tesla_model3 (V2X Charger) - └─ DEVICE: smart_meter_01 (P1) - └─ DER: grid_meter (Meter) + ├─ DEVICE: hybrid_inverter_01 (inverter, Modbus-TCP) + │ ├─ DER: pv_rooftop (solar) + │ ├─ DER: battery_01 (battery) + │ └─ DER: inverter_ac (meter) + ├─ DEVICE: v2x_charger_01 (v2x_charger, ISO 15118) + │ └─ DER: port_1 (ev_charger_port) + └─ DEVICE: smart_meter_01 (energy_meter, P1) + └─ DER: grid_meter (meter) ``` In this example: -- The hybrid inverter is one physical device, but exposes two DER capabilities +- The hybrid inverter is one physical device, but exposes three DERs - Each Device may use a different protocol - The Site optimizes across all DERs as a coordinated system -- The Wallet controls access permissions for the entire hierarchy +- The Organization controls access permissions for the entire hierarchy --- -## Telemetry Data Models - -The following sections describe the standardized telemetry data structures for DER types. Each DER type inherits from a common base structure while adding resource-specific fields. - -- [Base Structure](#base-structure) - - [DERData Root Structure](#derdata-root-structure) - - [Inheritance Model](#inheritance-model) -- [Units and Conventions](#units-and-conventions) - - [Units](#units) - - [Sign Conventions](#sign-conventions) -- [Device Types](#device-types) - - [BaseDeviceData](#basedevicedata) - - [PV Data Model](#pv-data-model) - - [Battery Data Model](#battery-data-model) - - [Meter Data Model](#meter-data-model) - - [V2X Charger Data Model](#ev-charger-data-model-v2x-enabled) - -## Base Structure - -### DERData Root Structure - -The root data structure can contain up to four subsystems: - -- **pv**: Photovoltaic system data -- **battery**: Battery storage system data -- **meter**: Meter data -- **v2x_charger**: Vehicle-to-grid charger data - -### Inheritance Model - -All device types inherit from `BaseDeviceData`: - -```json -{ - "type": "pv", - "make": "Deye", - "timestamp": 1755701251122, - "read_time_ms": 42 -} -``` - -## Units and Conventions +## Telemetry and Control Data Models -### Units +The fields of each DER type (names, units, sign convention, shape) and the control command and acknowledgement are defined in one place: **[srcful-data-models](https://github.com/srcfl/srcful-data-models)** (v2.0.0). It publishes JSON Schema plus TypeScript, Go, Rust and Python types generated from the same schemas. -All measurements use base SI units: +**Field reference:** [srcful-data-models `docs/REFERENCE.md`](https://github.com/srcfl/srcful-data-models/blob/main/docs/REFERENCE.md) lists every field per DER type. This page does not repeat it. -- **Power**: W (watts) -- **Energy**: Wh (watt-hours) -- **Voltage**: V (volts) -- **Current**: A (amperes) -- **Frequency**: Hz (hertz) -- **Temperature**: °C (Celsius) -- **State of Charge**: fraction (0.0 = empty, 1.0 = full) -- **Time**: s (seconds), ms (milliseconds for timestamps) +### Rules in Brief -### Sign Conventions +- One flat JSON object per DER per reading, with `type` set to the DER type (`solar`, `battery`, `meter`, `ev_charger_port`). +- Field names are bare and carry the unit (`_W`, `_Wh`, `_V`, `_A`, `_Hz`, `_C`, `_fract`), case-exact: `W`, `L1_V`, `SoC_nom_fract`, `total_import_Wh`. +- `timestamp` is Unix epoch **milliseconds**; `read_time_ms` is how long the read took. +- Leave out values that were not read. Never send 0 for them. +- MPPT inputs are flat: `mppt1_V`, `mppt1_A`, `mppt1_W` … up to `mppt4_*`. +- Limits are scalars for battery and solar, and `[min, 0, max]` bands for EV charger ports. +- Base units only: W, Wh, V, A, Hz, °C, and fractions from 0.0 to 1.0. -- **Generation**: Negative power (PV: `W < 0`) -- **Charging**: Positive power/current (Battery: `W > 0`, `A > 0`) -- **Discharging**: Negative power/current (Battery: `W < 0`, `A < 0`) -- **Import**: Positive power (Meter: `W > 0`) -- **Export**: Negative power (Meter: `W < 0`) -- **Energy Totals**: Always positive values +### Sign Convention -## Device Types - -### BaseDeviceData - -Common fields shared by all device types: - -| Field | Unit | Data Type | Description | -| -------------- | ------------ | --------- | -------------------------------------- | -| `type` | - | string | Object type ("pv", "battery", "meter", "v2x_charger") | -| `make` | - | string | Manufacturer/brand name (optional) | -| `timestamp` | milliseconds | integer | Timestamp of reading start | -| `read_time_ms` | milliseconds | integer | Time taken to complete the reading | - -### PV Data Model - -Photovoltaic system data with solar generation metrics: - -**Example:** - -```json -{ - "type": "pv", - "make": "Deye", - "W": -1500, - "rated_power_W": 3000, - "mppt1_V": 400, - "mppt1_A": -3.75, - "mppt2_V": 380, - "mppt2_A": -3.68, - "heatsink_C": 45, - "total_generation_Wh": 15000 -} -``` - -**Fields:** - -| Field | Unit | Data Type | Description | -| --------------------- | ---- | --------- | --------------------------------------------------------------- | -| `W` | W | integer | Power Generation (always negative) | -| `rated_power_W` | W | integer | System Rated Power | -| `mppt1_V` | V | float | MPPT1 Voltage | -| `mppt1_A` | A | float | MPPT1 Current | -| `mppt2_V` | V | float | MPPT2 Voltage | -| `mppt2_A` | A | float | MPPT2 Current | -| `heatsink_C` | °C | float | Inverter Temperature | -| `total_generation_Wh` | Wh | integer | Total Energy Generated | - -### Battery Data Model - -Battery storage system data with charge/discharge metrics: - -**Example:** - -```json -{ - "type": "battery", - "make": "Tesla", - "W": 500, - "A": 10.5, - "V": 48.2, - "SoC_nom_fract": 0.75, - "heatsink_C": 25, - "total_charge_Wh": 8000, - "total_discharge_Wh": 7200 -} -``` - -**Fields:** - -| Field | Unit | Data Type | Description | -| -------------------- | -------- | --------- | ------------------------------------------------------------------------------------------------------- | -| `W` | W | integer | Active Power (+ charge, - discharge) | -| `A` | A | float | Current (+ charge, - discharge) | -| `V` | V | float | Voltage | -| `SoC_nom_fract` | fraction | float | State of Charge (0.0-1.0) | -| `heatsink_C` | °C | float | Battery Temperature | -| `total_charge_Wh` | Wh | integer | Total Energy Charged | -| `total_discharge_Wh` | Wh | integer | Total Energy Discharged | - -### Meter Data Model - -Grid meter data with import/export and phase-level measurements: - -**Example:** - -```json -{ - "type": "meter", - "make": "Kamstrup", - "W": 1200, - "Hz": 50.0, - "L1_V": 230, - "L1_A": 5.2, - "L1_W": 400, - "L2_V": 229, - "L2_A": 5.1, - "L2_W": 380, - "L3_V": 231, - "L3_A": 5.3, - "L3_W": 420, - "total_import_Wh": 25000, - "total_export_Wh": 18000 -} -``` - -**Fields:** - -| Field | Unit | Data Type | Description | -| ----------------- | ---- | --------- | --------------------------------------- | -| `W` | W | integer | Total Active Power (+ import, - export) | -| `Hz` | Hz | float | Grid Frequency | -| `L1_V` | V | float | L1 Phase Voltage | -| `L1_A` | A | float | L1 Phase Current | -| `L1_W` | W | float | L1 Phase Power | -| `L2_V` | V | float | L2 Phase Voltage | -| `L2_A` | A | float | L2 Phase Current | -| `L2_W` | W | float | L2 Phase Power | -| `L3_V` | V | float | L3 Phase Voltage | -| `L3_A` | A | float | L3 Phase Current | -| `L3_W` | W | float | L3 Phase Power | -| `total_import_Wh` | Wh | integer | Total Energy Imported | -| `total_export_Wh` | Wh | integer | Total Energy Exported | - -### EV Charger Data Model (V2X Enabled) - -**Example:** - -```json -{ - "type": "v2x_charger", - "make": "Ferroamp", - "connector_status": "occupied", - "charging_state": "charging", - "protocol": "ISO_15118_20", - "control_mode": "bi_dir", - "plug_connected": true, - "W": 5300, - "ac_W": 5300, - "A": 23.0, - "V": 230.5, - "Hz": 49.98, - "L1_V": 232.8, - "L1_A": 23.0, - "L1_W": 5354, - "dc_W": 5100, - "dc_V": 400.0, - "dc_A": 12.75, - "vehicle_soc_fract": 0.55, - "session_charge_Wh": 1500, - "session_discharge_Wh": 0, - "total_charge_Wh": 142000, - "total_discharge_Wh": 5100 -} -``` +Positive = power into the DER (import, charge), negative = power out of it (export, discharge, generation). -**Fields Definition:** - -| Field | Unit | Data Type | Description | -|-------|------|-----------|-------------| -| `type` | - | string | Always `"v2x_charger"` | -| `make` | - | string | Manufacturer brand (e.g., Ferroamp, Ambibox) | -| `connector_status` | - | string | Connector availability (see [Connector Status](#connector-status)) | -| `charging_state` | - | string | Vehicle/session charging state (see [Charging State](#charging-state)) | -| `status` | - | string | **Deprecated.** Legacy flat status string. Use `connector_status` + `charging_state` instead | -| `protocol` | - | string | Active protocol (e.g., ISO_15118_2, ISO_15118_20, DIN) | -| `control_mode` | - | string | `"unknown"`, `"bi_dir"` (bidirectional), or `"uni_dir"` (charge only) | -| `plug_connected` | - | boolean | **Deprecated.** Use `connector_status` instead — derivable from status != `"available"` | -| `W` | W | integer | Active Power, derived from `ac_W` or `dc_W * 0.95`. (+) Charging, (-) V2G | -| `ac_W` | W | integer | AC Active Power (+) Charging, (-) V2G | -| `A` | A | float | AC Grid Current (Total) | -| `V` | V | float | AC Grid Voltage (Average) | -| `Hz` | Hz | float | Grid Frequency | -| `L1_V` / `L2_V` / `L3_V` | V | float | Phase Voltage | -| `L1_A` / `L2_A` / `L3_A` | A | float | Phase Current | -| `L1_W` / `L2_W` / `L3_W` | W | float | Phase Power | -| `dc_W` | W | integer | DC Battery Power: Actual power entering/leaving car battery | -| `dc_V` | V | float | DC Voltage level of the EV battery | -| `dc_A` | A | float | DC Current flow | -| `vehicle_soc_fract` | fraction | float | Vehicle State of Charge (0.0 - 1.0) | -| `ev_target_energy_req_Wh` | Wh | integer | Demand: Energy needed to reach driver's target SoC | -| `ev_max_energy_req_Wh` | Wh | integer | Capacity: Empty space in battery available for charging | -| `ev_min_energy_req_Wh` | Wh | integer | V2G Potential: Energy available for export (negative = V2G) | -| `session_charge_Wh` | Wh | integer | Energy imported by EV during this session | -| `session_discharge_Wh` | Wh | integer | Energy exported by EV during this session | -| `total_charge_Wh` | Wh | integer | Lifetime Energy delivered to EV | -| `total_discharge_Wh` | Wh | integer | Lifetime Energy exported from EV (V2G) | - -#### Connector Status - -Describes the physical connector's availability. Based on OCPP 2.0.1 `ConnectorStatusEnumType`, with `preparing` and `finishing` retained from OCPP 1.6 for backward compatibility. - -| Value | Meaning | -|-------|---------| -| `"unknown"` | State not yet determined | -| `"available"` | Ready, nothing plugged in | -| `"preparing"` | Cable plugged in, session not started yet | -| `"finishing"` | Session ending, cable still plugged in | -| `"reserved"` | Locked for a specific user | -| `"unavailable"` | Intentionally offline (maintenance, admin) | -| `"faulted"` | Hardware/software fault | -| `"occupied"` | In use (charging, discharging, or connected) | - -#### Charging State - -Describes the vehicle/charging session state. Based on OCPP 2.0.1 `ChargingStateEnumType`, extended with `discharging` for V2G. - -| Value | Meaning | -|-------|---------| -| `"unknown"` | State not yet determined | -| `"charging"` | Energy actively flowing to vehicle | -| `"discharging"` | Energy flowing from vehicle to grid (V2G) | -| `"suspended_ev"` | Vehicle not accepting power (e.g., battery full) | -| `"suspended_evse"` | Charger not delivering power (e.g., smart charging limit, pending auth) | -| `"ev_connected"` | Cable plugged in, session open, no energy flowing yet | -| `"idle"` | Transaction open but no EV connected (e.g., cable removed mid-session) | +| DER | + | − | +|---|---|---| +| solar | never | generating | +| battery | charging | discharging | +| meter (grid) | import | export | +| meter (inverter AC) | absorbing | delivering | +| ev_charger_port | charging the vehicle | V2G | +| control `power_w` | charge / consume | discharge / produce | diff --git a/docs/technical/ems.md b/docs/technical/ems.md index 91be0922..977360e0 100644 --- a/docs/technical/ems.md +++ b/docs/technical/ems.md @@ -129,6 +129,6 @@ While the EMS itself is not open, developers can access: ## Related Documentation -- [Data Models](/technical/data-models) - Understanding the WALLET > SITE > DEVICE > DER hierarchy +- [Data Models](/technical/data-models) - Understanding the ORGANIZATION > SITE > DEVICE > DER hierarchy - [Zap for Developers](/technical/zap-for-developers) - Edge execution platform - [Price API](/technical/price-api) - Accessing pricing data for optimization diff --git a/docs/technical/hardware/README.md b/docs/technical/hardware/README.md index 8f49083f..e41f5d6f 100644 --- a/docs/technical/hardware/README.md +++ b/docs/technical/hardware/README.md @@ -58,5 +58,5 @@ This hardware section is primarily for: ## Related Documentation - [Zap for Developers](/technical/zap-for-developers) - Understanding the Zap platform and capabilities -- [Data Models](/technical/data-models) - WALLET > SITE > DEVICE > DER hierarchy +- [Data Models](/technical/data-models) - ORGANIZATION > SITE > DEVICE > DER hierarchy - [EMS Documentation](/technical/ems) - How Zaps integrate with the Energy Management System From 7ff5304929d92225f1d6363bb9c94b71e7672dc2 Mon Sep 17 00:00:00 2001 From: David Mozart Date: Thu, 1 Oct 2026 17:07:41 +0200 Subject: [PATCH 2/7] docs: inverter and meter are separate DERs srcful-data-models 2.1.0 adds the inverter DER type (the inverter's AC output), registered in NovaCore alongside solar, battery, meter and ev_charger_port. Drop the "inverter AC side is a meter DER" mapping, list inverter among the DER types, and add its sign row (+ absorbing AC / - delivering AC). Co-Authored-By: Claude Opus 5.5 --- docs/technical/data-models.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/technical/data-models.md b/docs/technical/data-models.md index 287ab88b..d7b8838d 100644 --- a/docs/technical/data-models.md +++ b/docs/technical/data-models.md @@ -78,18 +78,18 @@ A **DER** is the logical representation of an energy resource or function: - **DEVICE**: The inverter itself (communication/control point via Modbus) - **DER #1**: Solar PV (generation capability) - **DER #2**: Battery (storage capability) -- **DER #3**: Meter (the inverter's AC side, import/export) +- **DER #3**: Inverter (the inverter's AC output) -You control the **Device** (inverter), but you represent its capabilities as separate **DERs** (solar, battery, meter). You cannot directly control the battery - you control the inverter which manages the battery - but you still model the battery as a distinct DER for optimization purposes. +You control the **Device** (inverter), but you represent its capabilities as separate **DERs** (solar, battery, inverter). You cannot directly control the battery - you control the inverter which manages the battery - but you still model the battery as a distinct DER for optimization purposes. **DER Types:** DER types and device types are owned by the device-support API (`GET /der-types`, `GET /device-types`): -- **DER types**: `solar`, `battery`, `meter`, `ev_charger_port` +- **DER types**: `solar`, `battery`, `inverter`, `meter`, `ev_charger_port` - **Device types**: `inverter`, `battery`, `energy_meter`, `ev_charger`, `v2x_charger` -Each device type allows a fixed set of DER types. There is no inverter DER: the AC side of an inverter is a `meter` DER on the inverter device. +Each device type allows a fixed set of DER types. Inverter and meter are separate DERs: `inverter` is the inverter's AC output, `meter` is an energy meter (typically the grid connection). ## Hierarchy Example @@ -99,7 +99,7 @@ ORGANIZATION: org_abc123 ├─ DEVICE: hybrid_inverter_01 (inverter, Modbus-TCP) │ ├─ DER: pv_rooftop (solar) │ ├─ DER: battery_01 (battery) - │ └─ DER: inverter_ac (meter) + │ └─ DER: inverter_ac (inverter) ├─ DEVICE: v2x_charger_01 (v2x_charger, ISO 15118) │ └─ DER: port_1 (ev_charger_port) └─ DEVICE: smart_meter_01 (energy_meter, P1) @@ -117,13 +117,13 @@ In this example: ## Telemetry and Control Data Models -The fields of each DER type (names, units, sign convention, shape) and the control command and acknowledgement are defined in one place: **[srcful-data-models](https://github.com/srcfl/srcful-data-models)** (v2.0.0). It publishes JSON Schema plus TypeScript, Go, Rust and Python types generated from the same schemas. +The fields of each DER type (names, units, sign convention, shape) and the control command and acknowledgement are defined in one place: **[srcful-data-models](https://github.com/srcfl/srcful-data-models)** (v2.1.0). It publishes JSON Schema plus TypeScript, Go, Rust and Python types generated from the same schemas. **Field reference:** [srcful-data-models `docs/REFERENCE.md`](https://github.com/srcfl/srcful-data-models/blob/main/docs/REFERENCE.md) lists every field per DER type. This page does not repeat it. ### Rules in Brief -- One flat JSON object per DER per reading, with `type` set to the DER type (`solar`, `battery`, `meter`, `ev_charger_port`). +- One flat JSON object per DER per reading, with `type` set to the DER type (`solar`, `battery`, `inverter`, `meter`, `ev_charger_port`). - Field names are bare and carry the unit (`_W`, `_Wh`, `_V`, `_A`, `_Hz`, `_C`, `_fract`), case-exact: `W`, `L1_V`, `SoC_nom_fract`, `total_import_Wh`. - `timestamp` is Unix epoch **milliseconds**; `read_time_ms` is how long the read took. - Leave out values that were not read. Never send 0 for them. @@ -139,7 +139,7 @@ Positive = power into the DER (import, charge), negative = power out of it (expo |---|---|---| | solar | never | generating | | battery | charging | discharging | +| inverter | absorbing AC | delivering AC | | meter (grid) | import | export | -| meter (inverter AC) | absorbing | delivering | | ev_charger_port | charging the vehicle | V2G | | control `power_w` | charge / consume | discharge / produce | From 3875dc0f372df90c7a8bd7ba9d5b2267b99bd6ab Mon Sep 17 00:00:00 2001 From: David Mozart Date: Thu, 1 Oct 2026 17:33:54 +0200 Subject: [PATCH 3/7] docs: follow srcful-data-models v3.0.0 field rules v3.0.0 names quantities that can be AC or DC with a lowercase _ac/_dc postfix (W_ac, W_dc, total_charge_Wh_dc, ...), sends both when both are measured, requires at least one of W_ac/W_dc, and uses power_W_dc / actual_power_W_dc for control. Replace the earlier bare-names rule, restate the sign convention (+ import / - export seen from the DER), and note NovaCore still accepts bare names for now. Co-Authored-By: Claude Opus 5.5 --- docs/technical/data-models.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/technical/data-models.md b/docs/technical/data-models.md index d7b8838d..4623bea4 100644 --- a/docs/technical/data-models.md +++ b/docs/technical/data-models.md @@ -117,29 +117,34 @@ In this example: ## Telemetry and Control Data Models -The fields of each DER type (names, units, sign convention, shape) and the control command and acknowledgement are defined in one place: **[srcful-data-models](https://github.com/srcfl/srcful-data-models)** (v2.1.0). It publishes JSON Schema plus TypeScript, Go, Rust and Python types generated from the same schemas. +The fields of each DER type (names, units, sign convention, shape) and the control command and acknowledgement are defined in one place: **[srcful-data-models](https://github.com/srcfl/srcful-data-models)** (v3.0.0). It publishes JSON Schema plus TypeScript, Go, Rust and Python types generated from the same schemas. **Field reference:** [srcful-data-models `docs/REFERENCE.md`](https://github.com/srcfl/srcful-data-models/blob/main/docs/REFERENCE.md) lists every field per DER type. This page does not repeat it. ### Rules in Brief -- One flat JSON object per DER per reading, with `type` set to the DER type (`solar`, `battery`, `inverter`, `meter`, `ev_charger_port`). -- Field names are bare and carry the unit (`_W`, `_Wh`, `_V`, `_A`, `_Hz`, `_C`, `_fract`), case-exact: `W`, `L1_V`, `SoC_nom_fract`, `total_import_Wh`. -- `timestamp` is Unix epoch **milliseconds**; `read_time_ms` is how long the read took. -- Leave out values that were not read. Never send 0 for them. -- MPPT inputs are flat: `mppt1_V`, `mppt1_A`, `mppt1_W` … up to `mppt4_*`. +- One flat JSON object per DER per reading, with `type` set to the device-support DER type (`solar`, `battery`, `inverter`, `meter`, `ev_charger_port`). +- Field names carry the unit, case-exact as the physical symbol (`_W`, `_Wh`, `_V`, `_A`, `_Hz`, `_C`, `_fract`). +- A quantity that can be AC or DC carries a lowercase `_ac` / `_dc` postfix: `W_ac`, `W_dc`, `V_dc`, `A_dc`, `total_charge_Wh_dc`, `total_import_Wh_ac`, `upper_limit_W_dc`, `rated_power_W_ac`. When a device measures both sides, send both. +- A quantity that can only be one side has no postfix: `Hz`, `VA`, `VAR`, per-phase `L1_V` / `L1_A` / `L1_W`, `mppt1_V` … `mppt4_*` (always DC). EV charger DC values are `W_dc`, `V_dc`, `A_dc`. +- Every DER payload carries at least one of `W_ac` / `W_dc`. +- `timestamp` is Unix epoch **milliseconds**; `read_time_ms` is how long the read took (a duration, not a time). +- Missing values: leave the field out. **Never send 0 for a value that was not read.** - Limits are scalars for battery and solar, and `[min, 0, max]` bands for EV charger ports. -- Base units only: W, Wh, V, A, Hz, °C, and fractions from 0.0 to 1.0. +- Inverter and meter are separate DERs: `inverter` is the inverter's AC output, `meter` is an energy meter (typically the grid connection). +- Control: `power_W_dc` is the battery's DC power target; the ack reports `actual_power_W_dc`. +- Base units only: W, Wh, V, A, Hz, °C, and fractions from 0.0 to 1.0. Never kW or kWh. +- NovaCore still accepts the bare names (`W` etc.) for backwards compatibility for now. ### Sign Convention -Positive = power into the DER (import, charge), negative = power out of it (export, discharge, generation). +Sourceful sign convention: **+ import, − export**, seen from the DER. Power into the DER (charge, consume) is positive; power out of it (discharge, generation, delivery) is negative. Applies to `_ac` and `_dc` alike. | DER | + | − | -|---|---|---| +|-----|---|---| | solar | never | generating | | battery | charging | discharging | | inverter | absorbing AC | delivering AC | | meter (grid) | import | export | | ev_charger_port | charging the vehicle | V2G | -| control `power_w` | charge / consume | discharge / produce | +| control `power_W_dc` | charge | discharge | From 20e718ccdd1fe861aabcc0bbcda1df7fb0f31e18 Mon Sep 17 00:00:00 2001 From: David Mozart Date: Thu, 1 Oct 2026 17:47:59 +0200 Subject: [PATCH 4/7] docs: follow final srcful-data-models v3.0.0 naming Every field is always present and an unread value is null, never 0. Everything that is not a unit is lowercase (soc_nom_fract, l1_V) while units keep physical casing (W, Wh, VA, var). No aliases: consumers move to the 3.0 names; NovaCore ingest still accepts the old names for now. W_ac/W_dc is required for solar, battery, inverter and meter. Co-Authored-By: Claude Opus 5.5 --- docs/technical/data-models.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/technical/data-models.md b/docs/technical/data-models.md index 4623bea4..9f9fd19b 100644 --- a/docs/technical/data-models.md +++ b/docs/technical/data-models.md @@ -124,17 +124,17 @@ The fields of each DER type (names, units, sign convention, shape) and the contr ### Rules in Brief - One flat JSON object per DER per reading, with `type` set to the device-support DER type (`solar`, `battery`, `inverter`, `meter`, `ev_charger_port`). -- Field names carry the unit, case-exact as the physical symbol (`_W`, `_Wh`, `_V`, `_A`, `_Hz`, `_C`, `_fract`). +- Everything in a field name that is not a unit is lowercase (`soc_nom_fract`, `soh_fract`, `l1_V`, `total_charge_Wh_dc`); units keep their physical casing (`W`, `Wh`, `V`, `A`, `Hz`, `VA`, `var`, `C`). - A quantity that can be AC or DC carries a lowercase `_ac` / `_dc` postfix: `W_ac`, `W_dc`, `V_dc`, `A_dc`, `total_charge_Wh_dc`, `total_import_Wh_ac`, `upper_limit_W_dc`, `rated_power_W_ac`. When a device measures both sides, send both. -- A quantity that can only be one side has no postfix: `Hz`, `VA`, `VAR`, per-phase `L1_V` / `L1_A` / `L1_W`, `mppt1_V` … `mppt4_*` (always DC). EV charger DC values are `W_dc`, `V_dc`, `A_dc`. -- Every DER payload carries at least one of `W_ac` / `W_dc`. +- A quantity that can only be one side has no postfix: `Hz`, `VA`, `var`, `heatsink_C`, per-phase `l1_V` / `l1_A` / `l1_W`, `mppt1_V` … `mppt4_*` (always DC). EV charger DC values are `W_dc`, `V_dc`, `A_dc`. +- solar, battery, inverter and meter payloads carry at least one of `W_ac` / `W_dc` (optional for ev_charger_port). - `timestamp` is Unix epoch **milliseconds**; `read_time_ms` is how long the read took (a duration, not a time). -- Missing values: leave the field out. **Never send 0 for a value that was not read.** +- Every field is always present. A value that was not read is JSON `null`. **Never send 0 for a value that was not read.** - Limits are scalars for battery and solar, and `[min, 0, max]` bands for EV charger ports. - Inverter and meter are separate DERs: `inverter` is the inverter's AC output, `meter` is an energy meter (typically the grid connection). - Control: `power_W_dc` is the battery's DC power target; the ack reports `actual_power_W_dc`. - Base units only: W, Wh, V, A, Hz, °C, and fractions from 0.0 to 1.0. Never kW or kWh. -- NovaCore still accepts the bare names (`W` etc.) for backwards compatibility for now. +- No aliases: consumers move to the 3.0 names. NovaCore ingest still accepts the pre-3.0 names (`W`, `SoC_nom_fract` …) for now. ### Sign Convention From b5aff1681538f62401f6e65ddb3bb745628d03c6 Mon Sep 17 00:00:00 2001 From: David Mozart Date: Tue, 6 Oct 2026 10:40:57 +0200 Subject: [PATCH 5/7] docs: postfix every W/V/A/Wh field per merged data-models v3.0.0 srcful-data-models v3.0.0 merged on main with an _ac/_dc postfix on every W, V, A and Wh field, even where only one side is possible (l1_V_ac, mppt1_V_dc, capacity_Wh_dc, installed_power_W_dc). Only Hz, VA and var have none. Update the rule summary and examples. Co-Authored-By: Claude Opus 5.5 --- docs/technical/data-models.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/technical/data-models.md b/docs/technical/data-models.md index 9f9fd19b..6a4602ea 100644 --- a/docs/technical/data-models.md +++ b/docs/technical/data-models.md @@ -124,9 +124,9 @@ The fields of each DER type (names, units, sign convention, shape) and the contr ### Rules in Brief - One flat JSON object per DER per reading, with `type` set to the device-support DER type (`solar`, `battery`, `inverter`, `meter`, `ev_charger_port`). -- Everything in a field name that is not a unit is lowercase (`soc_nom_fract`, `soh_fract`, `l1_V`, `total_charge_Wh_dc`); units keep their physical casing (`W`, `Wh`, `V`, `A`, `Hz`, `VA`, `var`, `C`). -- A quantity that can be AC or DC carries a lowercase `_ac` / `_dc` postfix: `W_ac`, `W_dc`, `V_dc`, `A_dc`, `total_charge_Wh_dc`, `total_import_Wh_ac`, `upper_limit_W_dc`, `rated_power_W_ac`. When a device measures both sides, send both. -- A quantity that can only be one side has no postfix: `Hz`, `VA`, `var`, `heatsink_C`, per-phase `l1_V` / `l1_A` / `l1_W`, `mppt1_V` … `mppt4_*` (always DC). EV charger DC values are `W_dc`, `V_dc`, `A_dc`. +- Everything in a field name that is not a unit is lowercase (`soc_nom_fract`, `soh_fract`, `l1_V_ac`, `total_charge_Wh_dc`); units keep their physical casing (`W`, `Wh`, `V`, `A`, `Hz`, `VA`, `var`, `C`). +- Every field in W, V, A or Wh carries a lowercase `_ac` / `_dc` postfix, even where only one side is physically possible: `W_ac`, `W_dc`, `V_dc`, `total_charge_Wh_dc`, `upper_limit_W_dc`, per-phase `l1_V_ac` / `l1_A_ac` / `l1_W_ac`, `mppt1_V_dc` … `mppt4_*_dc`, `rated_power_W_ac` / `rated_power_W_dc`, `capacity_Wh_dc`, `installed_power_W_dc`. Where a device can report either side, both fields exist and both are sent when measured. +- Only `Hz`, `VA` and `var` have no postfix. Other units such as `_C` and `_fract` have none either (`heatsink_C`, `soc_nom_fract`). - solar, battery, inverter and meter payloads carry at least one of `W_ac` / `W_dc` (optional for ev_charger_port). - `timestamp` is Unix epoch **milliseconds**; `read_time_ms` is how long the read took (a duration, not a time). - Every field is always present. A value that was not read is JSON `null`. **Never send 0 for a value that was not read.** From 493430a9ecbaf13c56ef6b5b1c7b62a50c12d2ca Mon Sep 17 00:00:00 2001 From: David Mozart Date: Tue, 6 Oct 2026 14:41:45 +0200 Subject: [PATCH 6/7] docs: describe srcful-data-models v2 (package 2.0.0) The data model is published as v2 on main, not v3. Cite v2, and describe the final contract: json.v2 subjects next to the legacy json.v1 with no translation between them, the DER type as the default topic segment (solar, was pv), an _ac/_dc postfix on every W/V/A/VA/Wh field with only Hz left bare, reactive_VA_ac and apparent_VA_ac, the inverter DER as the AC output stage only, the electrical definitions, and v: 2 control with power_W_dc and actual_power_W_dc. Co-Authored-By: Claude Opus 5.5 --- docs/technical/data-models.md | 37 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/docs/technical/data-models.md b/docs/technical/data-models.md index 6a4602ea..ea63a8c3 100644 --- a/docs/technical/data-models.md +++ b/docs/technical/data-models.md @@ -78,7 +78,7 @@ A **DER** is the logical representation of an energy resource or function: - **DEVICE**: The inverter itself (communication/control point via Modbus) - **DER #1**: Solar PV (generation capability) - **DER #2**: Battery (storage capability) -- **DER #3**: Inverter (the inverter's AC output) +- **DER #3**: Inverter (the inverter's AC output stage) You control the **Device** (inverter), but you represent its capabilities as separate **DERs** (solar, battery, inverter). You cannot directly control the battery - you control the inverter which manages the battery - but you still model the battery as a distinct DER for optimization purposes. @@ -89,7 +89,7 @@ DER types and device types are owned by the device-support API (`GET /der-types` - **DER types**: `solar`, `battery`, `inverter`, `meter`, `ev_charger_port` - **Device types**: `inverter`, `battery`, `energy_meter`, `ev_charger`, `v2x_charger` -Each device type allows a fixed set of DER types. Inverter and meter are separate DERs: `inverter` is the inverter's AC output, `meter` is an energy meter (typically the grid connection). +Each device type allows a fixed set of DER types. Inverter and meter are separate DERs: `inverter` is the inverter's AC output stage (its DC side is the `solar` and `battery` DERs), `meter` is an energy meter (typically the grid connection). ## Hierarchy Example @@ -117,24 +117,25 @@ In this example: ## Telemetry and Control Data Models -The fields of each DER type (names, units, sign convention, shape) and the control command and acknowledgement are defined in one place: **[srcful-data-models](https://github.com/srcfl/srcful-data-models)** (v3.0.0). It publishes JSON Schema plus TypeScript, Go, Rust and Python types generated from the same schemas. +The fields of each DER type (names, units, sign convention, shape) and the control command and acknowledgement are defined in one place: **[srcful-data-models](https://github.com/srcfl/srcful-data-models)** (v2, package 2.0.0). It publishes JSON Schema plus TypeScript, Go, Rust and Python types generated from the same schemas. -**Field reference:** [srcful-data-models `docs/REFERENCE.md`](https://github.com/srcfl/srcful-data-models/blob/main/docs/REFERENCE.md) lists every field per DER type. This page does not repeat it. +**Rules and field reference:** the srcful-data-models [README](https://github.com/srcfl/srcful-data-models/blob/main/README.md) has the rules (subjects, naming, sign, electrical definitions), and [`docs/REFERENCE.md`](https://github.com/srcfl/srcful-data-models/blob/main/docs/REFERENCE.md) lists every field per DER type. This page does not repeat the field lists. ### Rules in Brief - One flat JSON object per DER per reading, with `type` set to the device-support DER type (`solar`, `battery`, `inverter`, `meter`, `ev_charger_port`). -- Everything in a field name that is not a unit is lowercase (`soc_nom_fract`, `soh_fract`, `l1_V_ac`, `total_charge_Wh_dc`); units keep their physical casing (`W`, `Wh`, `V`, `A`, `Hz`, `VA`, `var`, `C`). -- Every field in W, V, A or Wh carries a lowercase `_ac` / `_dc` postfix, even where only one side is physically possible: `W_ac`, `W_dc`, `V_dc`, `total_charge_Wh_dc`, `upper_limit_W_dc`, per-phase `l1_V_ac` / `l1_A_ac` / `l1_W_ac`, `mppt1_V_dc` … `mppt4_*_dc`, `rated_power_W_ac` / `rated_power_W_dc`, `capacity_Wh_dc`, `installed_power_W_dc`. Where a device can report either side, both fields exist and both are sent when measured. -- Only `Hz`, `VA` and `var` have no postfix. Other units such as `_C` and `_fract` have none either (`heatsink_C`, `soc_nom_fract`). +- Subjects are versioned. v2 payloads are published on `gateways.{gateway_id}.devices.{hardware_id}.ders.{der_name}.telemetry.json.v2` (MQTT uses the same path with `/`). `json.v1` keeps the legacy 1.x format side by side. Nothing translates between the two: a consumer reads the version it subscribes to, so moving to v2 is opt-in. +- The `der_name` segment is the DER's provisioned name. In v2 it defaults to the DER type: `solar` (it was `pv` in v1), `battery`, `inverter`, `meter`. NovaCore temporarily resolves a v2 `solar` segment to a DER provisioned as `pv`, until srcfl/srcful-novacore#185 removes that fallback. +- Everything in a field name that is not a unit is lowercase (`soc_nom_fract`, `soh_fract`, `l1_V_ac`, `total_charge_Wh_dc`); units keep their physical casing (`W`, `Wh`, `V`, `A`, `VA`, `Hz`, `C`). +- Every field in W, V, A, VA or Wh carries a lowercase `_ac` / `_dc` postfix, even where only one side is physically possible: `W_ac`, `W_dc`, `V_dc`, `total_charge_Wh_dc`, `upper_limit_W_dc`, per-phase `l1_V_ac` / `l1_A_ac` / `l1_W_ac`, `mppt1_V_dc` … `mppt4_*_dc`, `rated_power_W_ac` / `rated_power_W_dc`, `capacity_Wh_dc`, `installed_power_W_dc`. Where a device can report either side, both fields exist and both are sent when measured. Only frequency (`Hz`) has no postfix; non-electrical units such as `_C` and `_fract` have none either. +- Reactive power is `reactive_VA_ac`, signed: + the DER absorbs reactive power (inductive), − it delivers (capacitive). Apparent power is `apparent_VA_ac` (≥ 0). - solar, battery, inverter and meter payloads carry at least one of `W_ac` / `W_dc` (optional for ev_charger_port). - `timestamp` is Unix epoch **milliseconds**; `read_time_ms` is how long the read took (a duration, not a time). - Every field is always present. A value that was not read is JSON `null`. **Never send 0 for a value that was not read.** - Limits are scalars for battery and solar, and `[min, 0, max]` bands for EV charger ports. -- Inverter and meter are separate DERs: `inverter` is the inverter's AC output, `meter` is an energy meter (typically the grid connection). -- Control: `power_W_dc` is the battery's DC power target; the ack reports `actual_power_W_dc`. -- Base units only: W, Wh, V, A, Hz, °C, and fractions from 0.0 to 1.0. Never kW or kWh. -- No aliases: consumers move to the 3.0 names. NovaCore ingest still accepts the pre-3.0 names (`W`, `SoC_nom_fract` …) for now. +- Inverter and meter are separate DERs. `inverter` is the inverter's AC output stage only: its DC side is the `solar` and `battery` DERs, so nothing is counted twice. `meter` is an energy meter (typically the grid connection). +- Control: a `v: 2` command carries `power_W_dc` (the battery's DC power target), and the gateway acks on `…control.ack.json.v2` with `actual_power_W_dc`. `v: 1` commands (`power_w`) are the legacy format. +- Base units only: W, Wh, V, A, VA, Hz, °C, and fractions from 0.0 to 1.0. Never kW or kWh. ### Sign Convention @@ -148,3 +149,17 @@ Sourceful sign convention: **+ import, − export**, seen from the DER. Power in | meter (grid) | import | export | | ev_charger_port | charging the vehicle | V2G | | control `power_W_dc` | charge | discharge | + +### Electrical Definitions + +| Quantity | Definition | +|----------|------------| +| Per-phase voltage `l1_V_ac` … | RMS, phase-to-neutral. A device that only reports phase-to-phase voltage sends `null` | +| Per-phase current `l1_A_ac` … | RMS magnitude (≥ 0). Direction comes from the phase's active power `l1_W_ac` | +| AC current `A_ac` | Sum of the phases' RMS current magnitudes (≥ 0) | +| AC voltage `V_ac` | Mean of the available phases' RMS phase-to-neutral voltages | +| Active power `W_ac` | Sum of the phases' active power, signed as above | +| DC current `A_dc`, `mpptN_A_dc` | Signed: + into the DER, − out of it (a generating PV input is negative) | +| Aggregates | Formed from the phases the device reports; `null` if they cannot be formed, never estimated | + +The full definitions, including reactive and apparent power, are in the srcful-data-models [README](https://github.com/srcfl/srcful-data-models/blob/main/README.md). From 1676cc5b7ac92c959e8ae91ddfc20a2777d2494e Mon Sep 17 00:00:00 2001 From: David Mozart Date: Wed, 7 Oct 2026 07:46:11 +0200 Subject: [PATCH 7/7] docs: data models follow srcful-data-models 2.3.0 Point to the data-models README/REFERENCE for the rules instead of copying them; keep the hierarchy, a short summary and the sign table. Control acks executed/nack/failed and NovaCore's command status from the final ack. Example DER names are the v2 defaults. Co-Authored-By: Claude Opus 5.5 --- docs/technical/data-models.md | 51 ++++++++++------------------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/docs/technical/data-models.md b/docs/technical/data-models.md index ea63a8c3..e0c1354d 100644 --- a/docs/technical/data-models.md +++ b/docs/technical/data-models.md @@ -52,7 +52,7 @@ A **Device** represents the physical hardware you communicate with and control: - The actual communication endpoint (Modbus address, MQTT client, P1 port) - Often the electrical connection point -- What the Zap directly talks to via protocols +- What the gateway talks to directly via protocols **Examples:** @@ -97,16 +97,16 @@ Each device type allows a fixed set of DER types. Inverter and meter are separat ORGANIZATION: org_abc123 └─ SITE: home_main_street ├─ DEVICE: hybrid_inverter_01 (inverter, Modbus-TCP) - │ ├─ DER: pv_rooftop (solar) - │ ├─ DER: battery_01 (battery) - │ └─ DER: inverter_ac (inverter) + │ ├─ DER: solar (solar) + │ ├─ DER: battery (battery) + │ └─ DER: inverter (inverter) ├─ DEVICE: v2x_charger_01 (v2x_charger, ISO 15118) - │ └─ DER: port_1 (ev_charger_port) + │ └─ DER: ev_charger_port (ev_charger_port) └─ DEVICE: smart_meter_01 (energy_meter, P1) - └─ DER: grid_meter (meter) + └─ DER: meter (meter) ``` -In this example: +In this example (DER names are the v2 defaults, the DER type): - The hybrid inverter is one physical device, but exposes three DERs - Each Device may use a different protocol @@ -117,29 +117,20 @@ In this example: ## Telemetry and Control Data Models -The fields of each DER type (names, units, sign convention, shape) and the control command and acknowledgement are defined in one place: **[srcful-data-models](https://github.com/srcfl/srcful-data-models)** (v2, package 2.0.0). It publishes JSON Schema plus TypeScript, Go, Rust and Python types generated from the same schemas. +The fields of each DER type (names, units, sign convention, shape) and the control command and acknowledgement are defined in one place: **[srcful-data-models](https://github.com/srcfl/srcful-data-models)** (v2, package 2.3.0). It publishes JSON Schema plus TypeScript, Go, Rust and Python types generated from the same schemas. -**Rules and field reference:** the srcful-data-models [README](https://github.com/srcfl/srcful-data-models/blob/main/README.md) has the rules (subjects, naming, sign, electrical definitions), and [`docs/REFERENCE.md`](https://github.com/srcfl/srcful-data-models/blob/main/docs/REFERENCE.md) lists every field per DER type. This page does not repeat the field lists. +**Rules and field reference:** the srcful-data-models [README](https://github.com/srcfl/srcful-data-models/blob/main/README.md) has the rules (subjects, naming, sign, electrical definitions, SoC window, derived values), and [`docs/REFERENCE.md`](https://github.com/srcfl/srcful-data-models/blob/main/docs/REFERENCE.md) lists every field per DER type. This page does not repeat them. -### Rules in Brief +### In Brief - One flat JSON object per DER per reading, with `type` set to the device-support DER type (`solar`, `battery`, `inverter`, `meter`, `ev_charger_port`). -- Subjects are versioned. v2 payloads are published on `gateways.{gateway_id}.devices.{hardware_id}.ders.{der_name}.telemetry.json.v2` (MQTT uses the same path with `/`). `json.v1` keeps the legacy 1.x format side by side. Nothing translates between the two: a consumer reads the version it subscribes to, so moving to v2 is opt-in. -- The `der_name` segment is the DER's provisioned name. In v2 it defaults to the DER type: `solar` (it was `pv` in v1), `battery`, `inverter`, `meter`. NovaCore temporarily resolves a v2 `solar` segment to a DER provisioned as `pv`, until srcfl/srcful-novacore#185 removes that fallback. -- Everything in a field name that is not a unit is lowercase (`soc_nom_fract`, `soh_fract`, `l1_V_ac`, `total_charge_Wh_dc`); units keep their physical casing (`W`, `Wh`, `V`, `A`, `VA`, `Hz`, `C`). -- Every field in W, V, A, VA or Wh carries a lowercase `_ac` / `_dc` postfix, even where only one side is physically possible: `W_ac`, `W_dc`, `V_dc`, `total_charge_Wh_dc`, `upper_limit_W_dc`, per-phase `l1_V_ac` / `l1_A_ac` / `l1_W_ac`, `mppt1_V_dc` … `mppt4_*_dc`, `rated_power_W_ac` / `rated_power_W_dc`, `capacity_Wh_dc`, `installed_power_W_dc`. Where a device can report either side, both fields exist and both are sent when measured. Only frequency (`Hz`) has no postfix; non-electrical units such as `_C` and `_fract` have none either. -- Reactive power is `reactive_VA_ac`, signed: + the DER absorbs reactive power (inductive), − it delivers (capacitive). Apparent power is `apparent_VA_ac` (≥ 0). -- solar, battery, inverter and meter payloads carry at least one of `W_ac` / `W_dc` (optional for ev_charger_port). -- `timestamp` is Unix epoch **milliseconds**; `read_time_ms` is how long the read took (a duration, not a time). -- Every field is always present. A value that was not read is JSON `null`. **Never send 0 for a value that was not read.** -- Limits are scalars for battery and solar, and `[min, 0, max]` bands for EV charger ports. -- Inverter and meter are separate DERs. `inverter` is the inverter's AC output stage only: its DC side is the `solar` and `battery` DERs, so nothing is counted twice. `meter` is an energy meter (typically the grid connection). -- Control: a `v: 2` command carries `power_W_dc` (the battery's DC power target), and the gateway acks on `…control.ack.json.v2` with `actual_power_W_dc`. `v: 1` commands (`power_w`) are the legacy format. -- Base units only: W, Wh, V, A, VA, Hz, °C, and fractions from 0.0 to 1.0. Never kW or kWh. +- Subjects are versioned: v2 payloads go on `gateways.{gateway_id}.devices.{hardware_id}.ders.{der_name}.telemetry.json.v2` (MQTT uses `/`). `json.v1` keeps the legacy 1.x format side by side; nothing translates between them, so moving to v2 is opt-in. +- Every field is always present. A value that was not read is `null`, **never 0**. +- Control: a `v: 2` command carries `power_W_dc` and an optional `execute_at`; the gateway acks on `…control.ack.json.v2` with status `executed`, `nack` or `failed`. NovaCore stores the final ack on the command (`status`, `acked_at`, `completed_at`, `error_message`). ### Sign Convention -Sourceful sign convention: **+ import, − export**, seen from the DER. Power into the DER (charge, consume) is positive; power out of it (discharge, generation, delivery) is negative. Applies to `_ac` and `_dc` alike. +Sourceful sign convention: **+ import, − export**, seen from the DER. Power into the DER (charge, consume) is positive; power out of it (discharge, generation, delivery) is negative. | DER | + | − | |-----|---|---| @@ -149,17 +140,3 @@ Sourceful sign convention: **+ import, − export**, seen from the DER. Power in | meter (grid) | import | export | | ev_charger_port | charging the vehicle | V2G | | control `power_W_dc` | charge | discharge | - -### Electrical Definitions - -| Quantity | Definition | -|----------|------------| -| Per-phase voltage `l1_V_ac` … | RMS, phase-to-neutral. A device that only reports phase-to-phase voltage sends `null` | -| Per-phase current `l1_A_ac` … | RMS magnitude (≥ 0). Direction comes from the phase's active power `l1_W_ac` | -| AC current `A_ac` | Sum of the phases' RMS current magnitudes (≥ 0) | -| AC voltage `V_ac` | Mean of the available phases' RMS phase-to-neutral voltages | -| Active power `W_ac` | Sum of the phases' active power, signed as above | -| DC current `A_dc`, `mpptN_A_dc` | Signed: + into the DER, − out of it (a generating PV input is negative) | -| Aggregates | Formed from the phases the device reports; `null` if they cannot be formed, never estimated | - -The full definitions, including reactive and apparent power, are in the srcful-data-models [README](https://github.com/srcfl/srcful-data-models/blob/main/README.md).