Skip to content

Commit 65a99ab

Browse files
committed
[fronius] Verify written time of use settings by reading them back
Reads shortly after a write can return the previous settings, so a subsequent read-modify-write could silently undo the write. Signed-off-by: Christian Jonak-Möchel <christian@jonak.org>
1 parent d4efa0e commit 65a99ab

1 file changed

Lines changed: 45 additions & 2 deletions

File tree

bundles/org.openhab.binding.fronius/src/main/java/org/openhab/binding/fronius/internal/api/FroniusBatteryControl.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
import java.net.URI;
1818
import java.time.DayOfWeek;
19+
import java.time.Duration;
1920
import java.time.LocalTime;
2021
import java.time.format.DateTimeFormatter;
2122
import java.util.ArrayList;
23+
import java.util.Arrays;
2224
import java.util.EnumSet;
2325
import java.util.List;
2426
import java.util.Map;
@@ -64,6 +66,13 @@ public class FroniusBatteryControl {
6466
private static final LocalTime BEGIN_OF_DAY = LocalTime.of(0, 0);
6567
private static final LocalTime END_OF_DAY = LocalTime.of(23, 59);
6668

69+
/**
70+
* How often written time of use settings are read back for verification before giving up. Measured on a GEN24
71+
* (firmware 1.41.10-1), reads returned the previous settings for up to roughly 200 ms after a write.
72+
*/
73+
private static final int WRITE_VERIFY_ATTEMPTS = 3;
74+
private static final Duration WRITE_VERIFY_RETRY_DELAY = Duration.ofMillis(500);
75+
6776
private final Logger logger = LoggerFactory.getLogger(FroniusBatteryControl.class);
6877
private final Gson gson = new Gson();
6978
private final FroniusConfigApiClient configApiClient;
@@ -157,16 +166,50 @@ public TimeOfUseRecords getTimeOfUse() throws FroniusCommunicationException, Fro
157166
}
158167

159168
/**
160-
* Sets the time of use settings of the Fronius hybrid inverter.
169+
* Sets the time of use settings of the Fronius hybrid inverter and verifies that they became effective.
161170
*
162171
* @param records the time of use settings
163-
* @throws FroniusCommunicationException if an error occurs during communication with the inverter
172+
* @throws FroniusCommunicationException if an error occurs during communication with the inverter or the inverter
173+
* does not confirm the written settings
164174
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
165175
*/
166176
private void setTimeOfUse(TimeOfUseRecords records)
167177
throws FroniusCommunicationException, FroniusUnauthorizedException {
168178
postConfig(timeOfUseUri, records, "timeofuse");
169179
logger.trace("Time of Use settings set successfully");
180+
verifyTimeOfUse(records);
181+
}
182+
183+
/**
184+
* Verifies that the written time of use settings became effective by reading them back until they match. The
185+
* inverter accepts a write and reports success, but reads shortly afterwards can still return the previous
186+
* settings. Without this check, a subsequent read-modify-write (e.g. the next addSchedule call) can be based on
187+
* the stale settings and silently undo the write.
188+
*
189+
* @param expected the previously written time of use settings
190+
* @throws FroniusCommunicationException when the read settings still differ from the written ones after the last
191+
* attempt, e.g. because a concurrent change overwrote them
192+
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
193+
*/
194+
private void verifyTimeOfUse(TimeOfUseRecords expected)
195+
throws FroniusCommunicationException, FroniusUnauthorizedException {
196+
for (int attempt = 1; attempt <= WRITE_VERIFY_ATTEMPTS; attempt++) {
197+
if (Arrays.equals(getTimeOfUse().records(), expected.records())) {
198+
return;
199+
}
200+
logger.debug("The inverter did not confirm the written time of use settings yet (attempt {}/{})", attempt,
201+
WRITE_VERIFY_ATTEMPTS);
202+
if (attempt < WRITE_VERIFY_ATTEMPTS) {
203+
try {
204+
Thread.sleep(WRITE_VERIFY_RETRY_DELAY.toMillis());
205+
} catch (InterruptedException e) {
206+
Thread.currentThread().interrupt();
207+
throw new FroniusCommunicationException("Interrupted while verifying the time of use settings", e);
208+
}
209+
}
210+
}
211+
throw new FroniusCommunicationException(
212+
"The inverter did not confirm the written time of use settings, they may have been overwritten by a concurrent change");
170213
}
171214

172215
/**

0 commit comments

Comments
 (0)