Integrator checklist
About this checklist
This is a self-attested conformance checklist for EMS integrators connecting to the Tensor Cloud battery optimization API. It complements the integrator guide and the AsyncAPI specification by providing concrete pass criteria for each part of the protocol that an integrator can verify against their own gateway before requesting a production handover.
The checklist is structured as independent modules. Read Selecting your modules first to identify which modules apply to your project type and service mix, then work through each applicable module in order. The Connection, Topic addressing, Message envelope, Battery telemetry, Energy reporting, Alerts, and Backfill modules are required for every integrator.
This checklist is not a deliverable. You do not submit it to Tensor Energy — record your results for your own purposes and use them to judge when your gateway is ready for a production handover. Test IDs are stable across revisions, so results recorded against an earlier revision carry over: when a new revision is published, only the added items and any changed pass criteria need revisiting. If an item is ambiguous, or you would like one reviewed, raise it with your Tensor Energy technical contact.
A spreadsheet version of this checklist is available for tracking your test runs: integrator-checklist.xlsx. The columns mirror the modules and tests on this page, plus a Verification column stating how each item can be exercised, and empty Result and Notes columns for you to fill in.
Selecting your modules
Pick your project type, then add modules for each service you offer.
| Project type | Connection, Topic, Envelope, Battery telemetry, Energy, Alerts, Backfill | Solar | Site load | Constraints module |
|---|---|---|---|---|
| Standalone battery | required | - | - | Standalone |
| AC-link battery + PV | required | required | conditional | AC-link |
| DC-link battery + PV | required | required | conditional | DC-link |
| Service you offer | Add this module |
|---|---|
| Energy arbitrage | Arbitrage |
| FCR / ancillary services | FCR |
| Site load reporting | Site load |
| Optional capability | Add this module |
|---|---|
| Batched telemetry, once Tensor Cloud has enabled it for the site | Batched telemetry |
Each item carries a Verification mode, shown as a column in the spreadsheet.
- Live - reproducible against the test server. Tensor Cloud supplies the acknowledgements and commands the item needs.
- Integrator test - not reproducible against Tensor Cloud, either because the item depends on receiving an invalid or rejected command (Tensor Cloud only ever emits valid ones, and your gateway certificate is not authorized to publish on
cmd/..., so neither side can inject one over the real broker) or because it depends on a site condition Tensor Cloud cannot induce, such as a hardware fault. They are provided as specifications for your own unit and integration tests. For the command cases we recommend running those against a mocked MQTT broker; the site-condition cases can also be covered by fault injection during commissioning. - Live + integrator test - the success path is observable live; the failure path within the same item is not, and needs to be covered with unit and/or integration tests.
Items marked Integrator test: A-6, AR-4, AR-6, AR-8, AR-9, AR-11, AR-15, AR-17, FC-3, FC-4, FC-10.
Items marked Live + integrator test: E-4, AR-5, AR-10.
Live verification of anything that depends on receiving a command also requires the battery to be registered as an asset in a Tensor Cloud test workspace and associated with your gateway. Until Tensor Energy has set that up, no commands are published to your site. See Before commands can reach you.
Module: Connection
Applies to: all integrators.
C-1TLS to broker endpoint
Open a TLS connection to the documented broker hostname on port 8883. Validate the server certificate against the Tensor Cloud CA chain.
Pass criteria: connection established, server certificate verified, plaintext on :1883 refused.
C-2Per-gateway certificate uniqueness
Confirm each gateway is provisioned with its own client certificate. Attempt a second concurrent connection using the same certificate from a different host - AWS IoT Core disconnects the older session.
Pass criteria: each gateway has a unique certificate; opening a second concurrent connection with the same certificate disconnects the first.
C-3CA chain validation
EMS validates the broker certificate against the documented CA chain. Reject connections when the chain is broken or expired.
Pass criteria: invalid or self-signed broker certificate is rejected.
C-7QoS and application-level acknowledgement
The EMS uses MQTT QoS 0 or 1 as appropriate for its reliability needs; both are supported by the broker. Regardless of the QoS chosen, the EMS sends an application-level CommandResponse on ack-cmd/{siteId} for every command received, because it carries validation results, not delivery confirmation.
Pass criteria: the EMS publishes at QoS 0 or 1; the EMS sends an application-level CommandResponse for every command received, independent of the MQTT QoS in use.
C-8Reconnect with backoff
After a broker disconnect, the EMS reconnects with an exponential or otherwise capped backoff policy.
Pass criteria: reconnect storms are avoided; subscriptions are re-established before resuming publishes.
C-9Behavior when telemetry cannot be published
When the EMS cannot publish (broker unreachable, TLS error), it buffers telemetry locally rather than dropping it silently.
Pass criteria: buffered telemetry replays per the Backfill module. Cross-references BF-1.
Module: Topic addressing and identity
Applies to: all integrators.
T-1siteId format
Site identifiers are prefixed si_ followed by six lowercase alphanumeric characters (e.g. si_qcf9gn).
Pass criteria: every siteId used in publish or subscribe topics matches ^si_[a-z0-9]{6}$.
T-2gatewayId format
Gateway identifiers are prefixed gw_ followed by six lowercase alphanumeric characters.
Pass criteria: every gatewayId matches ^gw_[a-z0-9]{6}$.
T-3Publish topic patterns per channel
The EMS publishes only on the topics defined by the schema:
dt/{siteId}/{gatewayId}/{metric}/lifetimedt/{siteId}/{gatewayId}/{metric}/energy/{window}dt/{siteId}/{gatewayId}/{metric}/powerdt/{siteId}/{gatewayId}/{metric}/statedt/{siteId}/{gatewayId}/curtailmentdt/{siteId}/{gatewayId}/irradiationdt/{siteId}/{gatewayId}/frequencydt/{siteId}/{gatewayId}/alertdt/{siteId}/{gatewayId}/alert/activeack-cmd/{siteId}
Pass criteria: no publishes to topics outside this list.
T-4Subscribe topic patterns per channel
The EMS subscribes only to:
cmd/{siteId}/battery/powercmd/{siteId}/battery/fcr(FCR integrators only)ack-dt/{siteId}/{gatewayId}
Pass criteria: documented subscriptions match the list above. Wildcards (e.g. cmd/{siteId}/battery/+) are permitted as an alternative to exact topics.
T-5res_topic namespace
The res_topic field on every received command points back to the same site as the command's topic. AWS IoT Core enforces this at the broker layer: the gateway's certificate only authorizes publish into its own site's ack-cmd/{siteId} namespace, so any attempt by the EMS to publish a response to a mismatched res_topic is rejected at the broker.
Pass criteria: publishing a CommandResponse to the res_topic value returned in the command succeeds for legitimate commands; attempts to publish into a foreign site's namespace are rejected by the broker.
T-6ack-cmd and ack-dt namespaces
Command responses are published on ack-cmd/{siteId}. Telemetry feedback is received on ack-dt/{siteId}/{gatewayId}.
Pass criteria: EMS uses the correct namespace for each direction.
T-7ack-dt topic shape
Telemetry feedback arrives on ack-dt/{siteId}/{gatewayId} exactly, with no /telemetry suffix.
Pass criteria: EMS subscribes to ack-dt/{siteId}/{gatewayId}.
T-8Hot-standby acknowledgement
In hot-standby, only the active gateway acks commands and executes schedules. The standby gateway is subscribed to the command topic but stays silent until promoted. Primary/standby coordination is the EMS partner's responsibility (per the integrator guide).
Pass criteria: under hot-standby, exactly one CommandResponse is sent per command, and exactly one gateway executes the schedule.
Module: Message envelope
Applies to: all integrators.
E-1schema_version present on every message
Every published payload includes schema_version, including AlertEvent and AlertState.
Pass criteria: schema_version is present on every message the EMS emits.
E-2schema_version value
schema_version is "2.8.0" for every message emitted under spec revision 2.8.0. A patch revision of the spec does not change the wire value, so the two version numbers can differ between releases. Tensor Cloud remains backwards-compatible with messages emitted under older spec revisions.
Pass criteria: every emitted schema_version is "2.8.0".
E-3message_id format
Every message_id matches ^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$.
Pass criteria: all generated message_id values pass the regex.
E-4command_id correlation
Every CommandResponse carries command_id equal to the originating command's message_id. The one exception is an unparseable command (MESSAGE_MALFORMED), whose message_id cannot be recovered; the response then carries the sentinel command_id: "unknown".
Pass criteria: for a representative sample of commands sent during testing, every response's command_id matches a command's message_id; for a deliberately malformed (non-JSON) command, the response carries command_id: "unknown".
Verification: The success path is reproducible against Tensor Cloud; the failure path is not. Cover the failure path with your own unit or integration tests against a mocked MQTT broker.
E-5Timestamps
All *_ts fields are ISO-8601 timestamps with a timezone offset (e.g. 2024-01-05T00:00:00.000+09:00).
Pass criteria: every emitted timestamp parses as ISO-8601 with offset and does not use bare Z if the documented convention is +09:00.
E-6additionalProperties policy
The schema allows unknown fields on every message family (additionalProperties: true) to preserve forward-compatibility. The EMS should still emit only fields defined in the schema; receivers on both sides should tolerate additions made by future schema revisions.
Pass criteria: every emitted payload contains only fields defined in the schema. The EMS parser does not crash or silently drop messages that include unknown fields.
E-7measurement_ts alignment
On measurements covering a period of time, measurement_ts carries the start of that period, never its end or midpoint: on windowed energy it equals measurement_value.start_ts, and on power or grid frequency sent with aggregation: "average" it is the start of aggregation_window. On point-in-time telemetry it is the instant the value was measured. On curtailment, which is a schedule rather than a measurement, it is the schedule's creation time as issued by the TSO when the TSO data carries it, otherwise the time the EMS received the schedule from the TSO.
Pass criteria: every windowed energy payload has measurement_ts equal to its start_ts; every aggregation: "average" power or frequency payload has measurement_ts at the start of its aggregation_window; curtailment measurement_ts reflects TSO schedule creation or receipt time.
Module: Battery telemetry
Applies to: all integrators.
BT-1Power telemetry shape
Battery power telemetry publishes on dt/{siteId}/{gatewayId}/{metric}/power with a PowerInstant payload.
Pass criteria: publishes validate against the schema; metric parameter is from the battery subset of the metric enum.
BT-2State telemetry shape
Battery state telemetry (SoC, etc.) publishes on dt/{siteId}/{gatewayId}/{metric}/state with a BatteryStateValue payload.
Pass criteria: publishes validate against the schema.
BT-3Telemetry cadence
Telemetry is published at the cadence documented by Tensor (typically: minutely for power and state).
Pass criteria: observed cadence matches documented expectation across a representative window of at least 1 hour of continuous operation.
BT-4Telemetry freshness on reconnect
After a disconnect, the EMS prioritizes the latest sample over backfilled history when publishing resumes.
Pass criteria: the first message published after reconnect carries the freshest sample, not the oldest buffered one. Cross-references Backfill module.
BT-5Telemetry feedback consumption
The EMS subscribes to ack-dt/{siteId}/{gatewayId} and reacts to TelemetryFeedback messages.
Pass criteria: EMS demonstrates handling of status: "ok" and status: "error" feedback.
BT-6Telemetry feedback retry behavior
The EMS has a documented retry / drop policy in response to telemetry feedback errors.
Pass criteria: EMS behavior matches the documented policy.
BT-7TelemetryFeedback conditional fields
TelemetryFeedback carries code and detail when status is error, and omits code, detail, and topic when status is ok. The schema enforces this via if/then/else.
Pass criteria: EMS-side parser handles both shapes (no crash, no silent drop) and treats receipt of code/detail/topic on ok as a protocol violation.
BT-8Malformed telemetry feedback
When Tensor Cloud cannot parse a telemetry message, it acknowledges it with code: "MESSAGE_MALFORMED", correlation_id: "unknown" (the originating message_id is unrecoverable), and a topic field naming the channel the message was published on. correlation_id is therefore not constrained to the msg_ UUID pattern.
Pass criteria: the EMS parser accepts correlation_id: "unknown" without rejecting or dropping the feedback, and surfaces topic so an operator can identify which published message was rejected.
Module: Energy reporting
Applies to: all integrators.
EN-1At least one energy reading stream per required metric
For each energy-flow metric required by the integrator's project type (see the project-type constraints module), the EMS emits either a windowed energy stream, a lifetime counter, or both.
Pass criteria: every required energy-flow metric has at least one active stream across at least 1 hour of continuous operation.
EN-2Lifetime counter monotonicity
Values on dt/{siteId}/{gatewayId}/{metric}/lifetime never decrease across messages (except for documented counter-reset events).
Pass criteria: monotonic non-decreasing across a representative observation window.
EN-3Lifetime counter units
Lifetime payloads use kWh per the EnergyLifetime schema.
Pass criteria: all lifetime values are kWh.
EN-4Windowed bucket boundaries
For dt/{siteId}/{gatewayId}/{metric}/energy/{window}, the start_ts and end_ts boundaries in the payload match the {window} parameter, an ISO-8601 duration (PT5M, PT30M, PT1H, etc.).
Pass criteria: every windowed payload's start_ts/end_ts matches the window length implied by the topic.
EN-5Late-arrival policy
The EMS publishes windowed energy values only after the window has closed. Tensor Cloud has no late-arrival cutoff - samples are accepted regardless of how long after the window closed they arrive.
Pass criteria: EMS publishes windowed values strictly after window close. Late-arriving samples (from backfill or recovery) are still published rather than dropped.
Module: Alerts
Applies to: all integrators.
A-1alertEvent emission
Transient alert events publish on dt/{siteId}/{gatewayId}/alert conforming to AlertEvent.
Pass criteria: every emitted code is a member of AlertCode.
A-2alertState clearing semantics
When an alert resolves, the next alertState snapshot omits it. alertState is a periodic full snapshot (at least every 10 minutes, and at startup), so re-subscribing consumers and recovery from missed events both rely on the next snapshot rather than any retained message.
Pass criteria: after a resolved alert, the next alertState does not include the resolved alert.
A-4AlertCode enumeration coverage
All codes emitted by the EMS appear in the schema's AlertCode enum.
Pass criteria: no proprietary alert codes are emitted on these topics.
A-5alertState snapshot with no active alerts
Observe the alertState topic over a period during which no alert is firing. The snapshot is published on its normal schedule regardless of whether anything is active.
Pass criteria: alertState continues to publish at least every 10 minutes with an empty measurement_value array. The EMS does not suppress the publish when there is nothing to report.
A-6Shared-hardware faults raise every applicable code
Applies to sites with co-located PV and battery. On a DC-linked system, simulate a fault in hardware the PV array and the battery share (hybrid inverter, common power conditioning subsystem, or common control or aggregation unit). On any topology where the EMS reaches both subsystems through a single collector device, simulate a loss of communication with that collector.
Pass criteria: the shared-hardware fault raises both PV_OTHER and BATT_OTHER, and clearing it emits a cleared alertEvent for both. Where the EMS has no per-BMS or per-PVPCS communication visibility, collector communication loss raising both BATT_COMM_FAIL and PV_COMM_FAIL is accepted; where it does have per-device visibility, only the codes for genuinely unreachable devices are raised.
Verification: Not reproducible against Tensor Cloud, which does not induce site faults. Cover this with your own unit or integration tests, or with a site-side fault injection during commissioning.
Module: Backfill and offline recovery
Applies to: all integrators.
BF-1Reconnect replay ordering
After a network outage, the EMS resumes publishing live samples first. Only when no live sample is waiting to be published does it start replaying buffered history.
Pass criteria: observed traffic after reconnect shows live samples ahead of backfill; backfill yields to fresh live samples whenever they arrive.
BF-2Maximum backlog age
The EMS retains telemetry locally for up to 7 days. Buffered samples older than 7 days are dropped or summarized.
Pass criteria: EMS drops or summarizes buffered samples older than 7 days; samples younger than 7 days are replayed.
BF-3Duplicate handling on reconnect
When the EMS resends a sample that may have already been delivered, it reuses the same message_id as the original publish. Tensor Cloud deduplicates on message_id.
Pass criteria: every resend of a given sample carries the same message_id as its first publish attempt. The EMS does not mint a new message_id for a retry.
Module: Batched telemetry
Applies to: integrators using the optional batch topics. Skip this module if the EMS publishes only single messages.
BA-1Batching mode compliance
Batching is enabled per site by Tensor Cloud and is backfill-only by default. The EMS batches live readings only where live batching has been granted for that site, and a live batch then spans at most 60 seconds. Sites whose live telemetry must arrive every second, such as low-voltage batteries in a VPP configuration, publish live readings as single messages.
Pass criteria: on a backfill-only site, every live reading is published as a single message and only backfill uses the batch topics. On a live-enabled site, no live batch spans more than 60 seconds.
BA-2Batch payload shape and ordering
Each batch states unit, aggregation, aggregation_window and pre_qualification once for the whole message, and each entry in measurements carries its own message_id, measurement_ts and value. Entries are in strictly ascending measurement_ts order with no repeated timestamp and no repeated message_id, and no entry's message_id equals the batch's own. A second with no reliable reading is absent from the array rather than published as 0 or null.
Pass criteria: every published batch validates against PowerInstantBatch or GridFrequencyBatch, timestamps are strictly ascending and unique, message_id values are unique within the batch and distinct from the envelope's, and gaps appear as absent entries.
BA-3Split on header change
Because unit, aggregation, aggregation_window and pre_qualification apply to every entry, the EMS starts a new batch whenever one of them changes. In particular a batch never straddles the moment the operator enters or leaves pre-qualification test mode.
Pass criteria: no batch contains readings taken under different header values; entering or leaving pre-qualification mode always begins a new batch.
BA-4Batch size limits
A batch carries at most 900 entries, and its encoded UTF-8 payload is at most 122,880 bytes. The EMS measures the serialized payload and splits before publishing, rather than relying on the entry count alone.
Pass criteria: no published batch exceeds 900 entries or 122,880 encoded bytes; a series that would exceed either limit is split into multiple batches.
BA-5QoS and RETAIN on batch topics
Batch topics are published at QoS 1, and never with the MQTT RETAIN flag set.
Pass criteria: every publish to a batch topic uses QoS 1 with RETAIN unset.
BA-6Deduplication and all-or-nothing resend
Deduplication keys on each entry's message_id, not on the batch's. When resending, the EMS reuses each reading's original message_id, and may re-group the readings into differently-sized batches or resend one in the single-message form. Because a batch is accepted in full or rejected in full, an EMS that receives an error for a batch treats every reading in it as unstored and resends the whole batch once corrected.
Pass criteria: a resent reading carries the same message_id as its first publish attempt in either form; re-grouping the same readings into different batches produces no duplicate stored values; after a rejected batch, the EMS resends all of its readings rather than only the offending one.
Module: Arbitrage (BatteryPowerCommand)
Applies to: integrators offering the energy arbitrage service.
AR-1Receive power command
EMS receives a representative BatteryPowerCommand on cmd/{siteId}/battery/power.
Pass criteria: message delivered, parsed without error.
AR-2Command shape per schema
The payload contains schema_version, message_id, issue_ts, res_topic, and a control object with priority (optional) and schedule (required).
Pass criteria: payload validates against BatteryPowerCommand.
AR-3ack-cmd success response
On accepting a command, the EMS publishes on ack-cmd/{siteId} with status: "ok", command_id equal to the command's message_id, and schema_version: "2.8.0".
Pass criteria: response validates against CommandResponse; errors is absent.
AR-4ack-cmd error response shape
On rejecting a command, the EMS publishes status: "error" with an errors array. Each error has code (from ErrorCode) and detail. Use field_path where applicable.
Pass criteria: response uses detail for each error; every code is from ErrorCode.
Verification: Not reproducible against Tensor Cloud, which only emits valid commands. Cover this with your own unit or integration tests against a mocked MQTT broker.
AR-5errors conditional on status
When status is error, the errors array is present and contains at least one item. When status is ok, the errors field is absent. The schema enforces both via an if/then/else constraint, so this is also a schema-validation check.
Pass criteria: errors present (with ≥ 1 item) on status: "error"; errors absent on status: "ok".
Verification: The success path is reproducible against Tensor Cloud; the failure path is not. Cover the failure path with your own unit or integration tests against a mocked MQTT broker.
AR-6ErrorCode mapping
The EMS uses the ErrorCode value documented in the integrator guide for each class of validation failure: MESSAGE_MALFORMED for unparseable JSON, MISSING_FIELD for absent required fields, TYPE_MISMATCH for wrong types, FIELD_OUT_OF_RANGE for out-of-range numeric values, TIME_WINDOW_INVALID for empty schedules / overlapping intervals / inverted start_ts/end_ts, EXPIRED for past-end schedules, DUPLICATE on message_id collision, RESOURCE_UNAVAILABLE for equipment unavailability, INTERNAL_ERROR for anything else.
Pass criteria: for each failure class above, the EMS emits the documented ErrorCode. The same failure class always produces the same code across runs.
Verification: Not reproducible against Tensor Cloud, which only emits valid commands. Cover this with your own unit or integration tests against a mocked MQTT broker.
AR-7Schedule gaps
Send a schedule with a time gap between intervals.
Pass criteria: during the gap, the EMS falls back to any previously valid command covering that time slot. If none, the EMS uses power_kw = 0 (standby).
AR-8Overlaps within a command
Send a schedule whose intervals overlap each other.
Pass criteria: EMS rejects with a documented ErrorCode and field_path: "control.schedule".
Verification: Not reproducible against Tensor Cloud, which only emits valid commands. Cover this with your own unit or integration tests against a mocked MQTT broker.
AR-9Empty schedule
Send a command with an empty schedule array.
Pass criteria: EMS rejects with a documented ErrorCode and field_path: "control.schedule". The schema also enforces minItems: 1 on schedule, so this is a schema-validation check.
Verification: Not reproducible against Tensor Cloud, which only emits valid commands. Cover this with your own unit or integration tests against a mocked MQTT broker.
AR-10Priority and issue_ts tie-breaking
Send two overlapping commands. With different priority, the higher priority wins. With identical priority, the later issue_ts wins. With identical priority and issue_ts, the last-received command (last over the wire) wins.
Pass criteria: EMS executes the winning command for each tie-break scenario and acknowledges all commands.
Verification: The success path is reproducible against Tensor Cloud; the failure path is not. Cover the failure path with your own unit or integration tests against a mocked MQTT broker.
AR-11Stale-schedule rejection
Send a command whose schedule's last interval end_ts is in the past.
Pass criteria: EMS rejects with a documented ErrorCode. (Old issue_ts alone is not a rejection reason - issue_ts is for tie-breaking only.)
Verification: Not reproducible against Tensor Cloud, which only emits valid commands. Cover this with your own unit or integration tests against a mocked MQTT broker.
AR-12Per-slot supersession
Send a command that overlaps only part of an in-progress schedule. The new command should replace the existing schedule only for the overlapping time slots; non-overlapping intervals of the older command remain in effect.
Pass criteria: at execution time, the EMS uses the new command's setpoints for the overlapping slots and continues with the older command's setpoints for non-overlapping slots. There is no explicit power-command cancellation - to override a power schedule, Tensor Cloud sends a new command (with power_kw = 0 or other values) at appropriate priority.
AR-13Mid-slot command (immediate execution)
Send a command whose first interval has a start_ts in the past and an end_ts in the future - i.e., the covered slot has already started.
Pass criteria: the EMS accepts the command and applies the active interval's setpoint immediately, without waiting for a future start time. Only a schedule whose final end_ts is also in the past is rejected as stale (see AR-11).
AR-14No schedule (standby fallback)
Reach a point in time that no command covers and to which no previously valid command applies.
Pass criteria: the EMS stops the battery and holds power_kw = 0 (standby), neither charging nor discharging, until a command covering that time arrives.
AR-15Mixed valid and invalid schedule items
Send a BatteryPowerCommand whose control.schedule contains both well-formed items and at least one invalid item (for example item 0 with an out-of-range power_kw, item 1 with end_ts before start_ts, and items 2 and 3 entirely valid). FCR integrators run the equivalent test for FCR commands as FC-10.
Pass criteria: the EMS applies none of the items in the command, including the valid ones, and continues executing the previously valid schedule. It responds with status: "error" carrying one errors[] entry per invalid item, each with its own field_path, rather than stopping at the first failure. Inspecting the EMS's stored schedule after the rejection shows no trace of the command.
Verification: Not reproducible against Tensor Cloud, which only emits valid commands. Cover this with your own unit or integration tests against a mocked MQTT broker.
AR-16reference_point interpretation
Send a schedule whose intervals mix reference_point values, and run it while co-located generation is active. Include an interval with "battery", one with "grid", and one omitting the field.
Pass criteria: on "battery" intervals the battery itself tracks power_kw and generation exports untouched, so a 0 interval leaves the battery idle while the site still exports. On "grid" intervals the metering point tracks power_kw, so a 0 interval holds the meter at zero with the battery absorbing whatever is generated. An interval omitting the field behaves identically to "battery". On a DC-coupled system the EMS compensates its inverter setpoint against measured PV to achieve the "battery" intervals rather than passing power_kw straight through.
AR-17Unreachable setpoint clamps and alerts
Drive the EMS into a setpoint it cannot deliver: a "battery" interval whose value plus concurrent generation exceeds the inverter rating, or a "grid" interval that would require charging past a full battery or discharging past an empty one.
Pass criteria: the EMS applies the closest physically achievable value, keeps executing the schedule, and raises SETPOINT_UNREACHABLE naming the limit it hit. It does not reject the command, does not respond with status: "error", and does not stop the battery. The alert clears once the EMS is tracking the commanded value again.
Verification: Not reproducible against Tensor Cloud, which does not deliberately issue unreachable setpoints. Cover this with your own unit or integration tests, or during commissioning.
Module: FCR (BatteryFcrCommand)
Applies to: integrators offering FCR or ancillary services.
FC-1Receive FCR command
EMS receives a representative BatteryFcrCommand on cmd/{siteId}/battery/fcr.
Pass criteria: message delivered, parsed without error.
FC-2action defaults to execute
When the action field is absent on a schedule item, the EMS treats it as execute.
Pass criteria: matches the schema's default.
FC-3capacity_kw required for execute
For schedule items with action: "execute" (the default), the EMS requires capacity_kw and rejects items missing it. The schema enforces this via if/then/else on control.action.
Pass criteria: EMS rejects with a documented ErrorCode and field_path pointing at the offending item.
Verification: Not reproducible against Tensor Cloud, which only emits valid commands. Cover this with your own unit or integration tests against a mocked MQTT broker.
FC-4capacity_kw forbidden on cancel
For action: "cancel", capacity_kw is forbidden on schedule items. The schema enforces this via the same if/then/else on control.action.
Pass criteria: EMS rejects a cancel command carrying capacity_kw with a documented ErrorCode.
Verification: Not reproducible against Tensor Cloud, which only emits valid commands. Cover this with your own unit or integration tests against a mocked MQTT broker.
FC-5FCR Assessment II power telemetry
For every FCR-awarded slot (every 30-minute slot for which an FCR offer command was sent on cmd/{siteId}/battery/fcr), the EMS publishes 1-second-resolution supplied power at 1 Hz on the instantaneous-power topic dt/{siteId}/{gatewayId}/{metric}/power (the PowerInstant message), in kW and ≥ 0, as a 1-second average (aggregation: "average", aggregation_window: "PT1S") with measurement_ts at the start of each whole second. The metric set matches the TSO registration: meter_export_ac/meter_import_ac for a grid-connection-point registration, or battery_discharge_ac/battery_charge_ac for an equipment-point registration. See the integrator guide.
Pass criteria: for a representative FCR-awarded slot, power samples arrive at 1 Hz on the registered metric(s), carry aggregation: "average" with aggregation_window: "PT1S" and whole-second measurement_ts, and time-align (shared measurement_ts boundaries) with the FC-8 frequency samples.
FC-7FCR baseline composition with arbitrage
Arbitrage and FCR commands compose rather than supersede: an FCR command sets the capacity offered for frequency response, while a concurrent BatteryPowerCommand shifts the baseline. If the EMS holds an FCR schedule for 1500 kW and receives a power command of −100 kW for the same time slots, the baseline shifts to −100 kW (per integrator guide).
Pass criteria: when both an FCR schedule and an arbitrage schedule cover the same time slot, the EMS applies the arbitrage value as the baseline and uses the FCR capacity for frequency response on top of it.
FC-8Grid frequency telemetry
For every FCR-awarded slot, the EMS publishes grid frequency at 1 Hz on dt/{siteId}/{gatewayId}/frequency (the GridFrequency message), in Hz, measured at the grid connection point from the live AC waveform to at least 0.0001 Hz resolution, as a 1-second average (aggregation: "average", aggregation_window: "PT1S") time-aligned with the 1 Hz power samples. See the integrator guide.
Pass criteria: for a representative FCR-awarded slot, frequency samples arrive at 1 Hz, carry unit: "Hz" and realistic values, and share measurement_ts boundaries with the power samples.
FC-9Pre-qualification (事前審査) flag
While the EMS operator has the site in FCR pre-qualification (事前審査) test mode, the EMS sets pre_qualification: true on every instantaneous-power (*/power) and grid-frequency (*/frequency) message, and omits it (or sets false) during normal operation.
Pass criteria: in a simulated pre-qualification run, pre_qualification: true is present on all */power and */frequency messages for the test window and absent during normal operation.
FC-10Mixed valid and invalid schedule items (FCR)
Send a BatteryFcrCommand whose control.schedule contains both well-formed items and at least one invalid item, and run it twice. On the action: "execute" run, make item 0 invalid with an out-of-range capacity_kw and item 1 invalid with end_ts before start_ts, leaving items 2 and 3 valid (each carrying a capacity_kw). On the action: "cancel" run, capacity_kw is forbidden on every item, so make item 0 invalid by including one at all and item 1 invalid with end_ts before start_ts, leaving items 2 and 3 as valid time ranges with no capacity_kw. This is the FCR counterpart of AR-15; all-or-nothing validation is a protocol-wide rule, not an arbitrage-only one.
Pass criteria: the EMS applies none of the items in the command, including the valid ones, and continues executing the previously valid FCR schedule. It responds with status: "error" carrying one errors[] entry per invalid item, each with its own field_path. For the cancel case, nothing is cancelled: every offer the command named remains in force.
Verification: Not reproducible against Tensor Cloud, which only emits valid commands. Cover this with your own unit or integration tests against a mocked MQTT broker.
Module: Solar
Applies to: AC-link and DC-link integrators with on-site PV.
SO-1Irradiation telemetry
EMS publishes on dt/{siteId}/{gatewayId}/irradiation conforming to Irradiation.
Pass criteria: publishes validate against the schema.
SO-2Irradiation cadence
EMS publishes irradiation at the documented cadence (typically continuous during daylight, paused or zero at night).
Pass criteria: observed cadence matches documented expectation across at least 1 hour of continuous daylight operation.
SO-3Curtailment reporting
EMS publishes curtailment on dt/{siteId}/{gatewayId}/curtailment conforming to Curtailment.
Pass criteria: publishes validate against the schema; curtailment is reported whenever the inverter is operating below available irradiation.
SO-4Solar power telemetry metrics
PV-related metrics from the metric enum (solar_to_grid_ac, solar_to_battery_ac, etc.) publish on the power topic.
Pass criteria: every PV metric the EMS supports is emitted on dt/{siteId}/{gatewayId}/{metric}/power.
SO-5inverter_net_output_ac definition
EMS emits inverter_net_output_ac as the total AC-side output of the inverter - the sum of inverter_to_load_ac and inverter_to_grid_ac. Values are non-negative.
Pass criteria: emitted values are ≥ 0 and equal inverter_to_load_ac + inverter_to_grid_ac.
SO-6Solar DC voltage and current (DC-link)
DC-link integrators publish PV-array DC voltage and current on dt/{siteId}/{gatewayId}/solar conforming to SolarDcElectrical. measurement_value carries voltage (V) and current (A), each a {value, unit} object with values ≥ 0, as a single aggregate across the whole array measured at the DC link upstream of the inverter. Voltage and current share one measurement_ts (sampled at the same instant) and are published at 1-minute resolution.
Pass criteria: publishes validate against the schema; each message carries both voltage and current at a single measurement_ts; cadence is at least once per minute during daylight operation. AC-link and stand-alone integrators do not publish this channel.
Module: Site load
Applies to: integrators reporting load telemetry (typically AC-link with on-site load).
SL-1Load power telemetry
EMS publishes load-side power metrics on dt/{siteId}/{gatewayId}/{metric}/power.
Pass criteria: publishes validate against PowerInstant.
SL-2Load metric naming
EMS uses inverter_to_load_ac, not inverter_to_load_kwh.
Pass criteria: every load-related metric is the canonical name from the metric enum.
SL-3Grid-charging metric (DC-link)
For DC-link sites where grid charging is possible, the EMS emits grid_to_inverter_ac.
Pass criteria: the metric is emitted whenever the site can be charged from grid.
SL-4Grid-charging metric (AC-link)
For AC-link sites where grid charging is possible, the EMS emits grid_to_battery_ac.
Pass criteria: grid_to_battery_ac is emitted whenever the site can be charged from grid.
Module: Project-type constraints
Applies to: all integrators (run the sub-module that matches your project type).
PT-SStandalone metric set
- Required metrics: battery-only metrics from the metric enum.
- Forbidden metrics: solar metrics, load metrics, DC-link-only metrics.
- Still required: curtailment on
dt/{siteId}/{gatewayId}/curtailment. It is required for every topology, since a stand-alone battery can be subject to TSO output control even with no solar on site. Irradiation is the one solar-linked channel that genuinely does not apply here.
The EMS publishes only metrics from the required + permitted set for standalone batteries. AWS IoT Core enforces topic scoping at the broker layer based on the gateway's certificate, so attempts to publish unauthorized metric topics are rejected at connection time and never reach Tensor Cloud.
Pass criteria: AWS IoT Core enforces topic scoping at the broker layer based on the gateway's certificate; unauthorized metric topics are rejected at connection time.
PT-ACAC-link metric set
- Required metrics: AC-link battery and solar metrics per the integrator guide.
- Conditional metrics: load and grid-charging metrics where applicable.
- Forbidden metrics: DC-link-only metrics.
The EMS publishes only AC-link required + permitted metrics; no DC-link-only metrics.
Pass criteria: only AC-link required and permitted metrics appear on any telemetry topic.
PT-DCDC-link metric set
- Required metrics: DC-link battery and solar metrics per the integrator guide, including
grid_to_inverter_acwhen grid charging is possible. - Conditional metrics: load metrics where applicable.
- Forbidden metrics: AC-link-only metrics.
The EMS publishes only DC-link required + permitted metrics; no AC-link-only metrics. Includes grid_to_inverter_ac when grid charging is possible.
Pass criteria: only DC-link required and permitted metrics appear on any telemetry topic.