A battery can accept a command and still not deliver the power. The request can wait in a queue, be refused by the PCS, be clamped by the battery's own protection or be overridden by another controller. Record each stage separately. When an aggregator disputes a shortfall, the record then shows which stage failed.
This guide is part of the flexibility series. The BESS roles guide explains what the BMS, PCS and EMS each own.
Decide what counts as delivery
Agree the meter, the sign convention, the window and the tolerance before the first test.
The meter can be at the battery terminals, at the site's connection point or at the grid meter. Each gives a different number. Between the terminals and the connection point, the inverter, transformer and cables take their losses. At the grid meter, every other load on the site moves as well. If the service judges delivery at the grid meter, it needs a baseline: what the site would have done without the command. In the GB dynamic services the operational baseline is a Physical Notification, in GMT all year, at 1-minute resolution or finer, fixed at gate closure 60 minutes before each 30-minute settlement period. Demand response programmes often use a historical baseline instead; the demand response baseline guide covers those methods.
Write the sign convention into the record. PCS register maps commonly treat discharge as positive, which is the generator convention. A site meter in the load convention treats import as positive, so the same discharge appears as negative power or as a fall in import. If one link in the chain inverts the sign, a correct 500 kW discharge reads as a 1,000 kW error against the request.
A service may fix the window and tolerance. NESO's provider guidance for the GB dynamic services sets these limits:
| Service | Maximum initiation time | Maximum time to full delivery | Ramp time upper bound | Performance data |
|---|---|---|---|---|
| Dynamic Containment (DC) | 0.5 s | 1 s | 0.5 s | 20 Hz |
| Dynamic Moderation (DM) | 0.5 s | 1 s | 0.5 s | 20 Hz |
| Dynamic Regulation (DR) | 2 s | 10 s | 8 s | 20 Hz or 2 Hz |
NESO scores each 30-minute settlement period, which is 36,000 rows at 20 Hz. For DC and DM, an error must persist through a 0.2 s rolling window (four samples) to count, and the worst such error in the period sets the performance factor. A unit that flags itself available in less than 99.9 % of the rows gets no availability payment for that period. Missing or badly timestamped rows therefore cost money, in addition to the response itself. These services follow frequency locally, so the command chain is short, but the same stage record applies to the set point the local controller calculates.
For a dispatched service without published rules, put numbers in the contract. For example: full power within 5 s of the command, held for 30 minutes, judged on 1-minute averages within ±3 % of the request, at the battery feeder meter.
The stages of a command
| Stage | Evidence | Still unknown |
|---|---|---|
| Authorised | The requester had permission, under the agreed policy | Nothing has been sent |
| Accepted | The API, broker or gateway accepted the request | Whether it reached the battery |
| Delivered to the controller | The EMS or PCS acknowledged the write | Whether local limits allow it |
| State confirmed | A read-back of the active set point and mode | Whether the power follows |
| Outcome measured | The agreed meter shows the response within tolerance | Behaviour after the window |
Each protocol acknowledgement covers one hop. An HTTP 202 Accepted says that the server queued the request for processing, and that processing is not complete. An MQTT PUBACK exists only at QoS 1. It says that the broker has taken responsibility for the message, not that a subscriber has received it. QoS 0 has no acknowledgement, and QoS 2 uses PUBREC, PUBREL and PUBCOMP.
A Modbus single-register write (function 06) returns an echo of the request. A multiple-register write (function 16) returns the start address and the number of registers written. The echo proves that the device processed the frame. Many PCS accept the write and then clamp or ignore the value, because it is out of range, the unit is in local mode or another source has priority. Read the active set point register back to confirm the state. On many PCS it is a separate register from the requested value. None of these acknowledgements measures battery power.
Keep a correlated record
For each command, record:
- a command ID, the target asset and the requesting system;
- the requested value, units and sign convention;
- the time of issue and the expiry time;
- each acknowledgement, linked to the command ID, with its time;
- the read-back active set point and PCS mode;
- the state of charge and the allowed charge and discharge power at the time;
- the measured power at the agreed meter, stamped at the time of measurement, not the time of arrival.
Synchronise the clocks
The record only correlates if every device uses the same clock. The command time comes from the platform, the write time from the gateway and the sample time from the meter. If those clocks differ by 300 ms, a DC response that started on time appears to start late, against a 0.5 s initiation limit.
On a lightly loaded Ethernet LAN, NTP keeps a clock within about 100 µs. Over an intercontinental internet path the error can be several tens of milliseconds, and asymmetric routes can give 100 ms or more. For 20 Hz data, use a time source at the site: a GNSS receiver, or an NTP server on the site LAN disciplined to GNSS. Stamp readings in UTC at the point of measurement. NESO's performance files reject rows whose timestamps are not exact multiples of the sample interval, which is 50 ms at 20 Hz.
A worked example
An aggregator asks for a 500 kW discharge, to start at once, for 30 minutes. The agreed meter is the battery feeder meter. The agreed criteria are full power within 5 s and 1-minute averages within ±3 % (15 kW). The PCS treats discharge as positive. The figures are illustrative.
| Time (UTC) | Stage | Evidence |
|---|---|---|
| 14:00:00.210 | Accepted | Command c-0412, +500 kW, to be applied by 14:00:30. The API returns 202. |
| 14:00:01.040 | Delivered to the controller | The gateway writes the set point with function 16. The echo arrives 25 ms later. |
| 14:00:01.310 | State confirmed | Read-back: active set point +500 kW, mode remote, allowed discharge 1,000 kW |
| 14:00:02.900 | Outcome measured | The feeder meter passes 475 kW, 95 % of the request |
| 14:00:03.600 | Outcome measured | The feeder meter reads 498 kW |
| 14:02:00 | Outcome measured | The 1-minute average for 14:01 to 14:02 is 497 kW. Pass. |
Over the same minute, import at the grid meter fell by 438 kW, not 500 kW. A 55 kW chiller started at 14:00:40, which its own submeter shows, and about 5 kW went in transformer and cable losses. Judged at the grid meter without a baseline, the same response reads as 88 % delivered.
Now run the same command at 11 % state of charge. The write echo is identical. The read-back shows an active set point of 300 kW and an allowed discharge of 300 kW. The record shows the clamp at the state-confirmed stage, with its reason, about 1 s after the command. Without the read-back, the shortfall appears only at settlement.
Timeouts and retries
A timeout means the outcome is unknown. The battery may have acted and its reply may have been lost. Before any retry, read the active set point and the measured power.
Agree the command handling with the PCS supplier in writing. Give every command an ID. If the PCS has a command-ID or sequence register, write it with the set point and read it back, so a repeated command with the same ID has no second effect. A new command replaces the old one, last writer wins, and the read-back shows which command is in force. Cancel with an explicit command, usually zero power or a return to local mode, not by silence. MQTT QoS 1 delivers at least once, so a subscriber can receive the same command twice, with the DUP flag set on the resend. The receiver discards a command ID that it has already applied.
Never replay an expired command because a connection has come back. A discharge request that arrives ten minutes late is a new, unrequested discharge. Enforce expiry at the receiver, against a synchronised clock, because only the receiver knows when the command arrived. Two MQTT features can deliver a stale set point after a reconnect: a retained message, which the broker sends to each new subscriber, and a persistent session, which holds QoS 1 and QoS 2 messages for a disconnected client. The MQTT 5 Message Expiry Interval makes the broker delete a copy that it has not started to deliver when the interval has passed. It does not stop a message that the broker started to send before expiry, or one that a gateway queued itself. Put the expiry time in the payload and check it at the gateway.
The PCS needs its own limit on a stale set point. Many PCS have a communication watchdog: if the controller does not refresh a heartbeat register within a set time, the PCS reverts to a fallback. SunSpec's immediate-controls model (model 123) gives the power limit its own reversion timeout, WMaxLimPct_RvrtTms. Agree the fallback in writing: zero power, the local EMS schedule, or a hold of the last set point for a fixed time. Hold-last is the dangerous option, because a lost link then keeps a discharge running.
Make constraints visible
Log the PCS's allowed charge and discharge power with every command, not only when something fails. When a battery cannot deliver, show the reason that it reports: state of charge, a power limit, temperature derating, an alarm, maintenance mode or a higher-priority controller. If the battery reports no reason, record the reason as unknown. Do not infer one.
Agree which controller wins when a schedule, an aggregator request and a local limit disagree. Keep the battery's protections and the site interlocks in force. A gateway must never bypass an interlock to make a remote command look successful.
Commissioning test sequence
Run each test with the equipment owner present, and keep the stage log for each one.
- Normal command. A discharge within limits. Pass: every stage is logged against one command ID, and the agreed meter reaches the request within the agreed time and tolerance.
- Constrained command. A request above the allowed discharge power. Pass: the read-back shows the clamped set point, the log records the reason, and the measured power matches the clamp.
- Duplicate. Send the same command ID twice. Pass: the battery acts once.
- Expiry. Deliver a command after its expiry time. Pass: the gateway refuses it and logs the refusal, and the battery stays at its current set point or fallback.
- Link failure. Break the link during a discharge. Pass: the PCS reverts to the agreed fallback within the watchdog time. When the link returns, the battery does not resume the old set point.
- Restart. Restart the gateway or EMS during a command. Pass: after the restart the set point in force is the fallback or an unexpired command, and the log says which.
- Competing controller. A local override during a remote command. Pass: the local override wins, and the remote system records the command as overridden, with the reason.
The local control guide turns these cases into a failure matrix for the whole site.
Battery control with EpiSensor
The ZDR-22 is the battery-control variant of the ZDR demand response controller. It follows grid frequency, sends a changing set point to a battery or UPS over Modbus RTU, and meters the supply at Class 0.5S. It records events at 20 ms with GPS time stamps. The set point it sent and the measured response are therefore on one clock, which covers the command and outcome stages of the table above.
A Gateway running Edge can poll the PCS registers, including the active set point and allowed power, and the site meters over Modbus. It stamps each reading when the Gateway takes it and keeps the history on the Gateway, so a lost platform link does not leave a gap in the local record. Edge Automation runs on the Gateway and applies the expiry rule to its own timed actions. After a restart, it sends a saved end command only up to five minutes past its deadline. After that, it does not replay the command. It reports the expired command and asks for the equipment to be checked.
Common questions
How do I verify that a battery followed a dispatch command?
Log each stage against one command ID. Record the acknowledgement of the request, the Modbus write echo, a read-back of the active set point from the PCS, and the measured power at the agreed meter over the agreed window. Timestamp everything from one synchronised clock. A successful API call or register write proves only the first stages.
Why did my battery not deliver the requested power?
Read the PCS's allowed charge and discharge power registers and its active set point at the time of the command. If the active set point is lower than the request, the PCS clamped it, and its alarm, mode and state-of-charge registers usually show why. If the active set point matches the request but the metered power does not, look at the meter, the sign convention and the other loads behind the meter.