diff --git a/pom.xml b/pom.xml index 2ebf664..435eea0 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 de.malkusch.km200 km200 - 2.0.11-SNAPSHOT + 2.1.0-SNAPSHOT KM200 KM200 API diff --git a/src/main/java/de/malkusch/km200/KM200.java b/src/main/java/de/malkusch/km200/KM200.java index 1bba3f4..6131940 100644 --- a/src/main/java/de/malkusch/km200/KM200.java +++ b/src/main/java/de/malkusch/km200/KM200.java @@ -32,6 +32,30 @@ /** * This is an API for Bosch/Buderus/Junkers heaters with a KM200 gateway. * + * Example: + * + *
+ * {@code
+ * var uri = "http://192.168.0.44";
+ * var gatewayPassword = "1234-5678-90ab-cdef";
+ * var privatePassword = "secretExample";
+ * var timeout = Duration.ofSeconds(5);
+ * var salt = "1234567890aabbccddeeff11223344556677889900aabbccddeeffa0a1a2b2d3";
+ * 
+ * var km200 = new KM200(uri, timeout, gatewayPassword, privatePassword, salt);
+ * 
+ * // Read the heater's time
+ * var time = km200.queryString("/gateway/DateTime");
+ * System.out.println(time);
+ * 
+ * // Update the heater's time
+ * km200.update("/gateway/DateTime", LocalDateTime.now());
+ * 
+ * // Explore the heater's endpoints
+ * km200.endpoints().forEach(System.out::println);
+ * }
+ * 
+ * * Although code wise this class is thread safe, it is highly recommended to not * use it concurrently. Chances are that your KM200 gateway itself is not thread * safe. @@ -45,18 +69,21 @@ public final class KM200 { private final Duration timeout; private final String uri; - private static final int RETRY_COUNT = 3; - private static final Duration RETRY_DELAY_MIN = Duration.ofSeconds(1); - private static final Duration RETRY_DELAY_MAX = Duration.ofSeconds(2); + private final FailsafeExecutor> retryQuery; + private final FailsafeExecutor> retryUpdate; - @SafeVarargs - private static FailsafeExecutor buildRetry(Class... exceptions) { - return Failsafe.with( // - RetryPolicy. builder() // - .handle(exceptions) // - .withMaxRetries(RETRY_COUNT) // - .withDelay(RETRY_DELAY_MIN, RETRY_DELAY_MAX) // - .build()); + public static final int RETRY_DEFAULT = 3; + public static final int RETRY_DISABLED = 0; + + /** + * Configure the KM200 API with a default retry of {@link #RETRY_DEFAULT}. + * + * @see #KM200(String, int, Duration, String, String, String) + **/ + public KM200(String uri, Duration timeout, String gatewayPassword, String privatePassword, String salt) + throws KM200Exception, IOException, InterruptedException { + + this(uri, RETRY_DEFAULT, timeout, gatewayPassword, privatePassword, salt); } /** @@ -65,34 +92,15 @@ RetryPolicy. builder() // * This will also issue a silent query to /system to verify that you * configuration is correct. * - * Example: - * - *
-     * {@code
-     * var uri = "http://192.168.0.44";
-     * var gatewayPassword = "1234-5678-90ab-cdef";
-     * var privatePassword = "secretExample";
-     * var timeout = Duration.ofSeconds(5);
-     * var salt = "1234567890aabbccddeeff11223344556677889900aabbccddeeffa0a1a2b2d3";
-     * 
-     * var km200 = new KM200(uri, timeout, gatewayPassword, privatePassword, salt);
-     * 
-     * // Read the heater's time
-     * var time = km200.queryString("/gateway/DateTime");
-     * System.out.println(time);
-     * 
-     * // Update the heater's time
-     * km200.update("/gateway/DateTime", LocalDateTime.now());
-     * 
-     * // Explore the heater's endpoints
-     * km200.endpoints().forEach(System.out::println);
-     * }
-     * 
- * * @param uri * The base URI of your KM200 e.g. http://192.168.0.44 + * @param retries + * The amount of retries. Set to {@link #RETRY_DISABLED} to + * disable retrying. Retries add a waiting delay between each retry + * of several seconds, because the km200 recovers very slowly. * @param timeout - * An IO timeout for requests to your heater + * An IO timeout for individual requests to your heater. Retries + * might block the API longer than this timeout. * @param gatewayPassword * The constant gateway password which you need to read out from * your heater's display e.g. 1234-5678-90ab-cdef @@ -100,7 +108,7 @@ RetryPolicy. builder() // * The private password which you did assign in the app. If you * forgot your private password you can start the "reset internet * password" flow in the menu of your heater and then reassign a - * new passwort in the app. + * new password in the app. * @param salt * The salt in the hexadecimal representation e.g. "12a0b2…" * @@ -108,7 +116,7 @@ RetryPolicy. builder() // * @throws IOException * @throws InterruptedException */ - public KM200(String uri, Duration timeout, String gatewayPassword, String privatePassword, String salt) + public KM200(String uri, int retries, Duration timeout, String gatewayPassword, String privatePassword, String salt) throws KM200Exception, IOException, InterruptedException { assertNotBlank(uri, "uri must not be blank"); @@ -116,6 +124,7 @@ public KM200(String uri, Duration timeout, String gatewayPassword, String privat assertNotBlank(gatewayPassword, "gatewayPassword must not be blank"); assertNotBlank(privatePassword, "privatePassword must not be blank"); assertNotBlank(salt, "salt must not be blank"); + assertNotNegative(retries, "retries must not be negative"); var device = new KM200Device(); device.setCharSet("UTF-8"); @@ -127,6 +136,8 @@ public KM200(String uri, Duration timeout, String gatewayPassword, String privat this.device = device; this.comm = new KM200Comm(); + this.retryQuery = buildRetry(retries, IOException.class, ServerError.class); + this.retryUpdate = buildRetry(retries, ServerError.class); this.timeout = timeout; this.uri = uri.replaceAll("/*$", ""); this.http = newBuilder().connectTimeout(timeout).cookieHandler(new CookieManager()).followRedirects(ALWAYS) @@ -167,8 +178,6 @@ public void update(String path, BigDecimal value) throws KM200Exception, IOExcep update(path, update); } - private final FailsafeExecutor> retryUpdate = buildRetry(ServerError.class); - private void update(String path, Object update) throws KM200Exception, IOException, InterruptedException { String json = null; try { @@ -189,8 +198,6 @@ private void update(String path, Object update) throws KM200Exception, IOExcepti } } - private final FailsafeExecutor> retryQuery = buildRetry(IOException.class, ServerError.class);; - public String query(String path) throws KM200Exception, IOException, InterruptedException { assertNotBlank(path, "Path must not be blank"); if (!path.startsWith("/")) { @@ -294,9 +301,28 @@ private HttpRequest.Builder request(String path) { .timeout(timeout); } + private static final Duration RETRY_DELAY_MIN = Duration.ofSeconds(1); + private static final Duration RETRY_DELAY_MAX = Duration.ofSeconds(2); + + @SafeVarargs + private static FailsafeExecutor buildRetry(int retries, Class... exceptions) { + return Failsafe.with( // + RetryPolicy. builder() // + .handle(exceptions) // + .withMaxRetries(retries) // + .withDelay(RETRY_DELAY_MIN, RETRY_DELAY_MAX) // + .build()); + } + private static void assertNotBlank(String var, String message) { if (requireNonNull(var).isBlank()) { throw new IllegalArgumentException(message); } } + + private static void assertNotNegative(int var, String message) { + if (var < 0) { + throw new IllegalArgumentException(message); + } + } } diff --git a/src/test/java/de/malkusch/km200/KM200Test.java b/src/test/java/de/malkusch/km200/KM200Test.java index ce461d5..077739d 100644 --- a/src/test/java/de/malkusch/km200/KM200Test.java +++ b/src/test/java/de/malkusch/km200/KM200Test.java @@ -15,6 +15,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED; +import static de.malkusch.km200.KM200.RETRY_DISABLED; import static de.malkusch.km200.KM200.USER_AGENT; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -395,6 +396,26 @@ public void queryShouldWaitWhenRetrying() throws Exception { verify(4, getRequestedFor(urlEqualTo("/retry-wait"))); } + @Test + public void queryShouldNotRetryWhenDisabled() throws Exception { + stubFor(get("/retry-disabled").willReturn(serverError())); + var km200 = new KM200(URI, RETRY_DISABLED, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); + + assertThrows(KM200Exception.class, () -> km200.queryString("/retry-disabled")); + + verify(1, getRequestedFor(urlEqualTo("/retry-disabled"))); + } + + @Test + public void updateShouldNotRetryWhenDisabled() throws Exception { + stubFor(post("/update-retry-disabled").willReturn(serverError())); + var km200 = new KM200(URI, RETRY_DISABLED, TIMEOUT, GATEWAY_PASSWORD, PRIVATE_PASSWORD, SALT); + + assertThrows(KM200Exception.class, () -> km200.update("/update-retry-disabled", 42)); + + verify(1, postRequestedFor(urlEqualTo("/update-retry-disabled"))); + } + private static String loadBody(String path) throws IOException { return resourceToString(path, UTF_8, KM200Test.class.getClassLoader()); }