|
16 | 16 |
|
17 | 17 | import java.net.URI; |
18 | 18 | import java.time.DayOfWeek; |
| 19 | +import java.time.Duration; |
19 | 20 | import java.time.LocalTime; |
20 | 21 | import java.time.format.DateTimeFormatter; |
21 | 22 | import java.util.ArrayList; |
| 23 | +import java.util.Arrays; |
22 | 24 | import java.util.EnumSet; |
23 | 25 | import java.util.List; |
24 | 26 | import java.util.Map; |
@@ -64,6 +66,13 @@ public class FroniusBatteryControl { |
64 | 66 | private static final LocalTime BEGIN_OF_DAY = LocalTime.of(0, 0); |
65 | 67 | private static final LocalTime END_OF_DAY = LocalTime.of(23, 59); |
66 | 68 |
|
| 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 | + |
67 | 76 | private final Logger logger = LoggerFactory.getLogger(FroniusBatteryControl.class); |
68 | 77 | private final Gson gson = new Gson(); |
69 | 78 | private final FroniusConfigApiClient configApiClient; |
@@ -157,16 +166,50 @@ public TimeOfUseRecords getTimeOfUse() throws FroniusCommunicationException, Fro |
157 | 166 | } |
158 | 167 |
|
159 | 168 | /** |
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. |
161 | 170 | * |
162 | 171 | * @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 |
164 | 174 | * @throws FroniusUnauthorizedException when the login fails due to invalid credentials |
165 | 175 | */ |
166 | 176 | private void setTimeOfUse(TimeOfUseRecords records) |
167 | 177 | throws FroniusCommunicationException, FroniusUnauthorizedException { |
168 | 178 | postConfig(timeOfUseUri, records, "timeofuse"); |
169 | 179 | 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"); |
170 | 213 | } |
171 | 214 |
|
172 | 215 | /** |
|
0 commit comments