From 4eb3ef6ff74dc1d611ea695d465adbe3186f52ef Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Wed, 14 Aug 2024 00:00:02 +0200 Subject: [PATCH 01/15] initial commit Signed-off-by: Bernd Weymann --- .../org.openhab.binding.casokitchen/NOTICE | 13 ++ .../org.openhab.binding.casokitchen/README.md | 84 +++++++ .../org.openhab.binding.casokitchen/pom.xml | 17 ++ .../src/main/feature/feature.xml | 9 + .../internal/CasoKitchenBindingConstants.java | 54 +++++ .../internal/CasoKitchenHandlerFactory.java | 69 ++++++ .../config/CasoKitchenConfiguration.java | 30 +++ .../internal/dto/LightRequest.java | 33 +++ .../internal/dto/StatusRequest.java | 31 +++ .../internal/dto/StatusResult.java | 37 +++ .../internal/handler/CasoKitchenHandler.java | 213 ++++++++++++++++++ .../src/main/resources/OH-INF/addon/addon.xml | 10 + .../OH-INF/i18n/casokitchen.properties | 3 + .../OH-INF/thing/bottom-zone-group.xml | 16 ++ .../OH-INF/thing/generic-channel-types.xml | 21 ++ .../resources/OH-INF/thing/generic-group.xml | 15 ++ .../resources/OH-INF/thing/thing-types.xml | 34 +++ .../resources/OH-INF/thing/top-zone-group.xml | 16 ++ .../OH-INF/thing/zone-channel-types.xml | 27 +++ 19 files changed, 732 insertions(+) create mode 100644 bundles/org.openhab.binding.casokitchen/NOTICE create mode 100644 bundles/org.openhab.binding.casokitchen/README.md create mode 100644 bundles/org.openhab.binding.casokitchen/pom.xml create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/CasoKitchenConfiguration.java create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/LightRequest.java create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusRequest.java create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusResult.java create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/addon/addon.xml create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-channel-types.xml create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/zone-channel-types.xml diff --git a/bundles/org.openhab.binding.casokitchen/NOTICE b/bundles/org.openhab.binding.casokitchen/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.casokitchen/README.md b/bundles/org.openhab.binding.casokitchen/README.md new file mode 100644 index 0000000000000..88630da46f640 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/README.md @@ -0,0 +1,84 @@ +# CasoKitchen Binding + +Gives you control on Caso WineCooler. + +## Supported Things + +- `winecooler`: Wine cooler + +## Discovery + +There's no automatic discovery. + +## Thing Configuration + +You need a [Caso Account](https://www.casoapp.com/Account/Create) to get configuration parameters. +After register you'll get the + +- API key +- Device ID + +### `sample` Thing Configuration + +| Name | Type | Description | Default | +|-----------------|---------|------------------------------------------------------|---------| +| apiKey | text | API obtained from thing configuration | N/A | +| deviceId | text | Device Id obtained from thing configuration | N/A | +| refreshInterval | integer | Interval the device is polled in minutes | 5 | + +## Channels + +### Generic + +| Channel | Type | Read/Write | Description | +|---------------|----------|------------|------------------------------| +| light-control | Switch | RW | Control lights for all zones | +| hint | text | R | General command description | +| last-update | DateTime | R | Date and Time of last update | + +### Top Zone + +| Channel | Type | Read/Write | Description | +|------------------|-----------------------|------------|------------------------------| +| temperature | Number:Temperature | R | Current Zone Temperature | +| set-temperature | Number:Temperature | R | Wanted Zone Temperature | +| light-control | Switch | RW | Control lights for this zone | +| power | Switch | R | Zone Power | + +### Bottom Zone + +| Channel | Type | Read/Write | Description | +|------------------|-----------------------|------------|------------------------------| +| temperature | Number:Temperature | R | Current Zone Temperature | +| set-temperature | Number:Temperature | R | Wanted Zone Temperature | +| light-control | Switch | RW | Control lights for this zone | +| power | Switch | R | Zone Power | + +## Full Example + +_Provide a full usage example based on textual configuration files._ +_*.things, *.items examples are mandatory as textual configuration is well used by many users._ +_*.sitemap examples are optional._ + +### Thing Configuration + +```java +Example thing configuration goes here. +``` + +### Item Configuration + +```java +Example item configuration goes here. +``` + +### Sitemap Configuration + +```perl +Optional Sitemap configuration goes here. +Remove this section, if not needed. +``` + +## Any custom content here! + +_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_ diff --git a/bundles/org.openhab.binding.casokitchen/pom.xml b/bundles/org.openhab.binding.casokitchen/pom.xml new file mode 100644 index 0000000000000..41998c0d05e09 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.3.0-SNAPSHOT + + + org.openhab.binding.casokitchen + + openHAB Add-ons :: Bundles :: CasoKitchen Binding + + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/feature/feature.xml b/bundles/org.openhab.binding.casokitchen/src/main/feature/feature.xml new file mode 100644 index 0000000000000..bcdbbbd598d89 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.casokitchen/${project.version} + + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java new file mode 100644 index 0000000000000..aa146c37450cf --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.casokitchen.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +import com.google.gson.Gson; + +/** + * The {@link CasoKitchenBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class CasoKitchenBindingConstants { + private static final String BINDING_ID = "casokitchen"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_WINECOOLER = new ThingTypeUID(BINDING_ID, "winecooler"); + + // List of all Channel Group ids + public static final String TOP = "top"; + public static final String BOTTOM = "bottom"; + public static final String GENERIC = "generic"; + + // List of all Channel ids + public static final String TEMPERATURE = "temperature"; + public static final String TARGET_TEMPERATURE = "set-temperature"; + public static final String POWER = "power"; + public static final String LIGHT = "light-control"; + public static final String HINT = "hint"; + public static final String LAST_UPDATE = "last-update"; + + public static final String EMPTY = ""; + + public static final String BASE_URL = "https://publickitchenapi.casoapp.com"; + public static final String LIGHT_URL = BASE_URL + "/api/v1.1/Winecooler/SetLight"; + public static final String STATUS_URL = BASE_URL + "/api/v1.1/Winecooler/Status"; + public static final String HTTP_HEADER_API_KEY = "x-api-key"; + + public static final Gson GSON = new Gson(); +} diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java new file mode 100644 index 0000000000000..b21dc137f162f --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.casokitchen.internal; + +import static org.openhab.binding.casokitchen.internal.CasoKitchenBindingConstants.THING_TYPE_WINECOOLER; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.casokitchen.internal.handler.CasoKitchenHandler; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link CasoKitchenHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.casokitchen", service = ThingHandlerFactory.class) +public class CasoKitchenHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_WINECOOLER); + private HttpClient httpClient; + private TimeZoneProvider timeZoneProvider; + + @Activate + public CasoKitchenHandlerFactory(final @Reference HttpClientFactory httpClientFactory, + final @Reference TimeZoneProvider tzp) { + httpClient = httpClientFactory.getCommonHttpClient(); + timeZoneProvider = tzp; + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + if (THING_TYPE_WINECOOLER.equals(thingTypeUID)) { + return new CasoKitchenHandler(thing, httpClient, timeZoneProvider); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/CasoKitchenConfiguration.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/CasoKitchenConfiguration.java new file mode 100644 index 0000000000000..6b72f6e42baea --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/CasoKitchenConfiguration.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.casokitchen.internal.config; + +import static org.openhab.binding.casokitchen.internal.CasoKitchenBindingConstants.EMPTY; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CasoKitchenConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class CasoKitchenConfiguration { + + public String apiKey = EMPTY; + public String deviceId = EMPTY; + public int refreshInterval = 5; +} diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/LightRequest.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/LightRequest.java new file mode 100644 index 0000000000000..28268a2957e42 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/LightRequest.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.casokitchen.internal.dto; + +import static org.openhab.binding.casokitchen.internal.CasoKitchenBindingConstants.EMPTY; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LightRequest} class contains fields mapping thing configuration parameters. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class LightRequest { + public String technicalDeviceId = EMPTY; + public int zone = -1; + public boolean lightOn = false; + + public boolean isValid() { + return !technicalDeviceId.equals(EMPTY) && zone >= 0; + } +} diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusRequest.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusRequest.java new file mode 100644 index 0000000000000..19cb986d9dd8c --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusRequest.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.casokitchen.internal.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.casokitchen.internal.CasoKitchenBindingConstants; + +/** + * The {@link StatusRequest} class contains fields mapping thing configuration parameters. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class StatusRequest { + + public StatusRequest(String intialValue) { + technicalDeviceId = intialValue; + } + + public String technicalDeviceId = CasoKitchenBindingConstants.EMPTY; +} diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusResult.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusResult.java new file mode 100644 index 0000000000000..d0fd8d94e64b4 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusResult.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.casokitchen.internal.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.casokitchen.internal.CasoKitchenBindingConstants; + +/** + * The {@link CasoConfiguration2} class contains fields mapping thing configuration parameters. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class StatusResult { + public int temperature1 = -1; + public int targetTemperature1 = -1; + public boolean power1 = false; + public boolean light1 = false; + + public int temperature2 = -1; + public int targetTemperature2 = -1; + public boolean power2 = false; + public boolean light2 = false; + + public String logTimestampUtc = CasoKitchenBindingConstants.EMPTY; + public String hint = CasoKitchenBindingConstants.EMPTY; +} diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java new file mode 100644 index 0000000000000..0111b90e09e11 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java @@ -0,0 +1,213 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.casokitchen.internal.handler; + +import static org.openhab.binding.casokitchen.internal.CasoKitchenBindingConstants.*; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.BufferingResponseListener; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.openhab.binding.casokitchen.internal.config.CasoKitchenConfiguration; +import org.openhab.binding.casokitchen.internal.dto.LightRequest; +import org.openhab.binding.casokitchen.internal.dto.StatusRequest; +import org.openhab.binding.casokitchen.internal.dto.StatusResult; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link CasoKitchenHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class CasoKitchenHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(CasoKitchenHandler.class); + + private Optional configuration = Optional.empty(); + private @Nullable ScheduledFuture refreshJob; + private final HttpClient httpClient; + private final TimeZoneProvider timeZoneProvider; + + public CasoKitchenHandler(Thing thing, HttpClient hc, TimeZoneProvider tzp) { + super(thing); + httpClient = hc; + timeZoneProvider = tzp; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (LIGHT.equals(channelUID.getIdWithoutGroup())) { + logger.info("{} request received for group {}", LIGHT, channelUID.getGroupId()); + LightRequest lr = new LightRequest(); + lr.technicalDeviceId = configuration.get().deviceId; + if (command instanceof OnOffType) { + lr.lightOn = OnOffType.ON.equals(command); + if (TOP.equals(channelUID.getGroupId())) { + lr.zone = 1; + } else if (BOTTOM.equals(channelUID.getGroupId())) { + lr.zone = 2; + } else if (GENERIC.equals(channelUID.getGroupId())) { + // light for all zones + lr.zone = 0; + } + } + if (lr.isValid()) { + Request req = httpClient.POST(LIGHT_URL); + req.header(HttpHeader.CONTENT_TYPE, "application/json"); + req.header(HTTP_HEADER_API_KEY, configuration.get().apiKey); + req.content(new StringContentProvider(GSON.toJson(lr))); + try { + req.send(); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.warn("Call to {} failed with reason {}", LIGHT_URL, e.getMessage()); + } + // force data update + dataUpdate(); + } + } else { + logger.info("Request {} doesn't fit", command); + } + } + + @Override + public void initialize() { + configuration = Optional.of(getConfigAs(CasoKitchenConfiguration.class)); + if (checkConfig(configuration.get())) { + startSchedule(); + } + } + + private void startSchedule() { + ScheduledFuture localRefreshJob = refreshJob; + if (localRefreshJob != null && configuration.isPresent()) { + if (localRefreshJob.isCancelled()) { + refreshJob = scheduler.scheduleWithFixedDelay(this::dataUpdate, 0, configuration.get().refreshInterval, + TimeUnit.MINUTES); + } // else - scheduler is already running! + } else { + refreshJob = scheduler.scheduleWithFixedDelay(this::dataUpdate, 0, configuration.get().refreshInterval, + TimeUnit.MINUTES); + } + } + + @Override + public void dispose() { + ScheduledFuture localRefreshJob = refreshJob; + if (localRefreshJob != null) { + localRefreshJob.cancel(true); + } + } + + /** + * Checks if config is valid - a) not null and b) sensorid is a number + * + * @param c + * @return + */ + private boolean checkConfig(@Nullable CasoKitchenConfiguration c) { + String reason = "Config Empty"; + if (c != null) { + if (c.apiKey != EMPTY) { + if (c.deviceId != EMPTY) { + if (c.refreshInterval > 0) { + updateStatus(ThingStatus.ONLINE); + return true; + } else { + reason = "Refresh Interval " + c.refreshInterval + " not supported"; + } + } else { + reason = "Technical Device ID Missing"; + } + } else { + reason = "API Key Missing"; + } + } + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, reason); + return false; + } + + private void dataUpdate() { + StatusRequest requestContent = new StatusRequest(configuration.get().deviceId); + Request req = httpClient.POST(STATUS_URL); + req.header(HttpHeader.CONTENT_TYPE, "application/json"); + req.header(HTTP_HEADER_API_KEY, configuration.get().apiKey); + req.content(new StringContentProvider(GSON.toJson(requestContent))); + req.timeout(15, TimeUnit.SECONDS).send(new BufferingResponseListener() { + @NonNullByDefault({}) + @Override + public void onComplete(org.eclipse.jetty.client.api.Result result) { + if (result.getResponse().getStatus() != 200) { + String failure; + if (result.getResponse().getReason() != null) { + failure = result.getResponse().getReason(); + } else { + failure = result.getFailure().getMessage(); + } + logger.info("Request to {} failed. Status: {} Reason: {}", STATUS_URL, + result.getResponse().getStatus(), failure); + // todo send failure + } else { + String resultContent = getContentAsString(); + logger.info("Request to {} delivered {}", STATUS_URL, getContentAsString()); + if (resultContent != null) { + updateChannels(resultContent); + } + } + } + }); + } + + private void updateChannels(String json) { + StatusResult result = GSON.fromJson(json, StatusResult.class); + updateState(new ChannelUID(thing.getUID(), GENERIC, LAST_UPDATE), + DateTimeType.valueOf(result.logTimestampUtc).toZone(timeZoneProvider.getTimeZone())); + updateState(new ChannelUID(thing.getUID(), GENERIC, HINT), StringType.valueOf(result.hint)); + updateState(new ChannelUID(thing.getUID(), GENERIC, LIGHT), OnOffType.from(result.light1 && result.light2)); + updateState(new ChannelUID(thing.getUID(), TOP, TEMPERATURE), + QuantityType.valueOf(result.temperature1, SIUnits.CELSIUS)); + updateState(new ChannelUID(thing.getUID(), TOP, TARGET_TEMPERATURE), + QuantityType.valueOf(result.targetTemperature1, SIUnits.CELSIUS)); + updateState(new ChannelUID(thing.getUID(), TOP, POWER), OnOffType.from(result.power1)); + updateState(new ChannelUID(thing.getUID(), TOP, LIGHT), OnOffType.from(result.light1)); + updateState(new ChannelUID(thing.getUID(), BOTTOM, TEMPERATURE), + QuantityType.valueOf(result.temperature2, SIUnits.CELSIUS)); + updateState(new ChannelUID(thing.getUID(), BOTTOM, TARGET_TEMPERATURE), + QuantityType.valueOf(result.targetTemperature2, SIUnits.CELSIUS)); + updateState(new ChannelUID(thing.getUID(), BOTTOM, POWER), OnOffType.from(result.power2)); + updateState(new ChannelUID(thing.getUID(), BOTTOM, LIGHT), OnOffType.from(result.light2)); + } +} diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 0000000000000..93edef4ae2a7b --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,10 @@ + + + + binding + CasoKitchen Binding + This is the binding for CasoKitchen. + + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties new file mode 100644 index 0000000000000..0c2f44015a92d --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties @@ -0,0 +1,3 @@ +# FIXME: please add all English translations to this file so the texts can be translated using Crowdin +# FIXME: to generate the content of this file run: mvn i18n:generate-default-translations +# FIXME: see also: https://www.openhab.org/docs/developer/utils/i18n.html diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml new file mode 100644 index 0000000000000..7aa7ab70b9c62 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml @@ -0,0 +1,16 @@ + + + + + Values for lower zone + + + + + + + + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-channel-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-channel-types.xml new file mode 100644 index 0000000000000..cb31b678fbaec --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-channel-types.xml @@ -0,0 +1,21 @@ + + + + + Switch + + + + String + + + + + DateTime + + + + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml new file mode 100644 index 0000000000000..c502ded8ba462 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml @@ -0,0 +1,15 @@ + + + + + Values for Winecooler + + + + + + + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..217d471539832 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,34 @@ + + + + + + Wine Cooler for 2 Zones + + + + + + + + + + + API Key generated via CASO SMart Kitchen API + + + + Device ID from CASO connected devices + + + + Interval the device is polled in minutes. + 5 + true + + + + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml new file mode 100644 index 0000000000000..fa6bd1fafc93a --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml @@ -0,0 +1,16 @@ + + + + + Values for upper zone + + + + + + + + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/zone-channel-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/zone-channel-types.xml new file mode 100644 index 0000000000000..8b65112cdf85f --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/zone-channel-types.xml @@ -0,0 +1,27 @@ + + + + Number:Temperature + + Current Temperature + + + + Number:Temperature + + Target Temperature to reach + + + + Switch + + + + + Switch + + + From dbc92fbf1a31a0a047ecefcba4ac8364371cca53 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Wed, 14 Aug 2024 01:25:52 +0200 Subject: [PATCH 02/15] channel descriptions Signed-off-by: Bernd Weymann --- .../internal/CasoKitchenBindingConstants.java | 2 +- .../OH-INF/thing/bottom-zone-group.xml | 1 - .../resources/OH-INF/thing/channel-types.xml | 42 +++++++++++++++++++ .../OH-INF/thing/generic-channel-types.xml | 21 ---------- .../resources/OH-INF/thing/generic-group.xml | 3 +- .../resources/OH-INF/thing/top-zone-group.xml | 1 - .../OH-INF/thing/zone-channel-types.xml | 27 ------------ 7 files changed, 44 insertions(+), 53 deletions(-) create mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml delete mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-channel-types.xml delete mode 100644 bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/zone-channel-types.xml diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java index aa146c37450cf..0d1831b3772fe 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java @@ -48,7 +48,7 @@ public class CasoKitchenBindingConstants { public static final String BASE_URL = "https://publickitchenapi.casoapp.com"; public static final String LIGHT_URL = BASE_URL + "/api/v1.1/Winecooler/SetLight"; public static final String STATUS_URL = BASE_URL + "/api/v1.1/Winecooler/Status"; - public static final String HTTP_HEADER_API_KEY = "x-api-key"; + public static final String HTTP_HEADER_API_KEY = "x-api-key"; public static final Gson GSON = new Gson(); } diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml index 7aa7ab70b9c62..3c55b82959fbc 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml @@ -5,7 +5,6 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - Values for lower zone diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml new file mode 100644 index 0000000000000..dd49b66b3e399 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml @@ -0,0 +1,42 @@ + + + + + Number:Temperature + + Current Temperature + + + + Number:Temperature + + Target Temperature to reach + + + + Switch + + Showing if zone is currently powered + + + + Switch + + Switching lights on and off + + + String + + Textual hint for device status + + + + DateTime + + Timestamp of latest device communication + + + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-channel-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-channel-types.xml deleted file mode 100644 index cb31b678fbaec..0000000000000 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-channel-types.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Switch - - - - String - - - - - DateTime - - - - diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml index c502ded8ba462..4a22ce5ff3274 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml @@ -4,8 +4,7 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - Values for Winecooler + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml index fa6bd1fafc93a..79750fbe6f878 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml @@ -5,7 +5,6 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - Values for upper zone diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/zone-channel-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/zone-channel-types.xml deleted file mode 100644 index 8b65112cdf85f..0000000000000 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/zone-channel-types.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - Number:Temperature - - Current Temperature - - - - Number:Temperature - - Target Temperature to reach - - - - Switch - - - - - Switch - - - From fbb4d659b762bde760fe6056d491a4f64b4ea666 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Wed, 14 Aug 2024 01:28:43 +0200 Subject: [PATCH 03/15] channel descriptions Signed-off-by: Bernd Weymann --- .../internal/CasoKitchenBindingConstants.java | 2 +- .../resources/OH-INF/thing/channel-types.xml | 42 +++++++++---------- .../resources/OH-INF/thing/thing-types.xml | 4 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java index 0d1831b3772fe..aa146c37450cf 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java @@ -48,7 +48,7 @@ public class CasoKitchenBindingConstants { public static final String BASE_URL = "https://publickitchenapi.casoapp.com"; public static final String LIGHT_URL = BASE_URL + "/api/v1.1/Winecooler/SetLight"; public static final String STATUS_URL = BASE_URL + "/api/v1.1/Winecooler/Status"; - public static final String HTTP_HEADER_API_KEY = "x-api-key"; + public static final String HTTP_HEADER_API_KEY = "x-api-key"; public static final Gson GSON = new Gson(); } diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml index dd49b66b3e399..5a4621c51f5de 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml @@ -4,39 +4,39 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - Number:Temperature - - Current Temperature - - - - Number:Temperature - - Target Temperature to reach - - - - Switch - - Showing if zone is currently powered - - + + Number:Temperature + + Current Temperature + + + + Number:Temperature + + Target Temperature to reach + + + + Switch + + Showing if zone is currently powered + + Switch - Switching lights on and off + Switching lights on and off String - Textual hint for device status + Textual hint for device status DateTime - Timestamp of latest device communication + Timestamp of latest device communication diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml index 217d471539832..1531dd4cfdbc4 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml @@ -4,9 +4,9 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + - Wine Cooler for 2 Zones + Winecooler with 2 cooling zones From 91938567d07abd83b3f722d0664c2e1d1465de1c Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Wed, 14 Aug 2024 01:42:25 +0200 Subject: [PATCH 04/15] status updates Signed-off-by: Bernd Weymann --- .../internal/handler/CasoKitchenHandler.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java index 0111b90e09e11..9f740a46be0f2 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java @@ -107,6 +107,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { public void initialize() { configuration = Optional.of(getConfigAs(CasoKitchenConfiguration.class)); if (checkConfig(configuration.get())) { + updateStatus(ThingStatus.UNKNOWN); startSchedule(); } } @@ -170,19 +171,17 @@ private void dataUpdate() { @NonNullByDefault({}) @Override public void onComplete(org.eclipse.jetty.client.api.Result result) { - if (result.getResponse().getStatus() != 200) { - String failure; - if (result.getResponse().getReason() != null) { - failure = result.getResponse().getReason(); - } else { - failure = result.getFailure().getMessage(); - } - logger.info("Request to {} failed. Status: {} Reason: {}", STATUS_URL, - result.getResponse().getStatus(), failure); - // todo send failure + int responseStatus = result.getResponse().getStatus(); + String resultContent = getContentAsString(); + if (responseStatus != 200) { + logger.info("Request to {} failed. Status: {} Reason: {}", STATUS_URL, responseStatus, + resultContent); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/casokitchen.winecooler.status.http-status [\"" + responseStatus + " - " + + resultContent + "\"]"); } else { - String resultContent = getContentAsString(); - logger.info("Request to {} delivered {}", STATUS_URL, getContentAsString()); + updateStatus(ThingStatus.ONLINE); + logger.info("Request to {} delivered {}", STATUS_URL, resultContent); if (resultContent != null) { updateChannels(resultContent); } From b27cbea98f8ec2d52c8a05956ce6d43b807c8cc4 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Wed, 14 Aug 2024 01:54:29 +0200 Subject: [PATCH 05/15] add translations Signed-off-by: Bernd Weymann --- .../internal/handler/CasoKitchenHandler.java | 6 +-- .../OH-INF/i18n/casokitchen.properties | 51 +++++++++++++++++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java index 9f740a46be0f2..7df9e0dd90398 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java @@ -148,13 +148,13 @@ private boolean checkConfig(@Nullable CasoKitchenConfiguration c) { updateStatus(ThingStatus.ONLINE); return true; } else { - reason = "Refresh Interval " + c.refreshInterval + " not supported"; + reason = "@text/casokitchen.winecooler.status.refresh-interval [\"" + c.refreshInterval + "\"]"; } } else { - reason = "Technical Device ID Missing"; + reason = "@text/casokitchen.winecooler.status.device-id-missing"; } } else { - reason = "API Key Missing"; + reason = "@text/casokitchen.winecooler.status.api-key-missing"; } } updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, reason); diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties index 0c2f44015a92d..0bdf51854b8c2 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties @@ -1,3 +1,48 @@ -# FIXME: please add all English translations to this file so the texts can be translated using Crowdin -# FIXME: to generate the content of this file run: mvn i18n:generate-default-translations -# FIXME: see also: https://www.openhab.org/docs/developer/utils/i18n.html +# add-on + +addon.casokitchen.name = CasoKitchen Binding +addon.casokitchen.description = This is the binding for CasoKitchen. + +# thing types + +thing-type.casokitchen.winecooler-2z.label = Winecooler 2 Zones +thing-type.casokitchen.winecooler-2z.description = Winecooler with 2 cooling zones + +# thing types config + +thing-type.config.casokitchen.winecooler-2z.apiKey.label = API Key +thing-type.config.casokitchen.winecooler-2z.apiKey.description = API Key generated via CASO SMart Kitchen API +thing-type.config.casokitchen.winecooler-2z.deviceId.label = Device ID +thing-type.config.casokitchen.winecooler-2z.deviceId.description = Device ID from CASO connected devices +thing-type.config.casokitchen.winecooler-2z.refreshInterval.label = Refresh Interval +thing-type.config.casokitchen.winecooler-2z.refreshInterval.description = Interval the device is polled in minutes. + +# channel group types + +channel-group-type.casokitchen.bottom-values.label = Bottom Zone +channel-group-type.casokitchen.generic-values.label = Generic Values +channel-group-type.casokitchen.top-values.label = Top Zone + +# channel types + +channel-type.casokitchen.hint.label = Hint +channel-type.casokitchen.hint.description = Textual hint for device status +channel-type.casokitchen.last-update.label = Last Update +channel-type.casokitchen.last-update.description = Timestamp of latest device communication +channel-type.casokitchen.last-update.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM +channel-type.casokitchen.light-control.label = Light Control +channel-type.casokitchen.light-control.description = Switching lights on and off +channel-type.casokitchen.power.label = Zone is Powered +channel-type.casokitchen.power.description = Showing if zone is currently powered +channel-type.casokitchen.set-temperature.label = Target Temperature +channel-type.casokitchen.set-temperature.description = Target Temperature to reach +channel-type.casokitchen.temperature.label = Temperature +channel-type.casokitchen.temperature.description = Current Temperature + +# status details + +casokitchen.winecoolar.status.api-key-missing = API Key is mandatory +casokitchen.winecoolar.status.device-id-missing = Device ID is mandatory +casokitchen.winecoolar.status.refresh-interval = Refresh interval {0} not supported +casokitchen.winecoolar.status.http-status = HTTP Status Code {0} + From 75fceffdd500e936b3560bf8bd9a02b576a7a69a Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Wed, 14 Aug 2024 02:33:40 +0200 Subject: [PATCH 06/15] bugfix utc conversion Signed-off-by: Bernd Weymann --- .../internal/CasoKitchenBindingConstants.java | 2 +- .../internal/handler/CasoKitchenHandler.java | 6 ++- .../OH-INF/i18n/casokitchen.properties | 8 ++-- .../binding/caso/internal/TestHandler.java | 41 +++++++++++++++++++ .../src/test/resources/StatusResponse.json | 13 ++++++ 5 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java create mode 100644 bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse.json diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java index aa146c37450cf..8ec3376aeafe7 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java @@ -28,7 +28,7 @@ public class CasoKitchenBindingConstants { private static final String BINDING_ID = "casokitchen"; // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_WINECOOLER = new ThingTypeUID(BINDING_ID, "winecooler"); + public static final ThingTypeUID THING_TYPE_WINECOOLER = new ThingTypeUID(BINDING_ID, "winecooler-2z"); // List of all Channel Group ids public static final String TOP = "top"; diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java index 7df9e0dd90398..af8455974e6ac 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java @@ -14,6 +14,7 @@ import static org.openhab.binding.casokitchen.internal.CasoKitchenBindingConstants.*; +import java.time.Instant; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; @@ -192,8 +193,6 @@ public void onComplete(org.eclipse.jetty.client.api.Result result) { private void updateChannels(String json) { StatusResult result = GSON.fromJson(json, StatusResult.class); - updateState(new ChannelUID(thing.getUID(), GENERIC, LAST_UPDATE), - DateTimeType.valueOf(result.logTimestampUtc).toZone(timeZoneProvider.getTimeZone())); updateState(new ChannelUID(thing.getUID(), GENERIC, HINT), StringType.valueOf(result.hint)); updateState(new ChannelUID(thing.getUID(), GENERIC, LIGHT), OnOffType.from(result.light1 && result.light2)); updateState(new ChannelUID(thing.getUID(), TOP, TEMPERATURE), @@ -208,5 +207,8 @@ private void updateChannels(String json) { QuantityType.valueOf(result.targetTemperature2, SIUnits.CELSIUS)); updateState(new ChannelUID(thing.getUID(), BOTTOM, POWER), OnOffType.from(result.power2)); updateState(new ChannelUID(thing.getUID(), BOTTOM, LIGHT), OnOffType.from(result.light2)); + Instant timestamp = Instant.parse(result.logTimestampUtc); + updateState(new ChannelUID(thing.getUID(), GENERIC, LAST_UPDATE), + new DateTimeType(timestamp.atZone(timeZoneProvider.getTimeZone()))); } } diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties index 0bdf51854b8c2..c92cf54aa96a0 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties @@ -41,8 +41,8 @@ channel-type.casokitchen.temperature.description = Current Temperature # status details -casokitchen.winecoolar.status.api-key-missing = API Key is mandatory -casokitchen.winecoolar.status.device-id-missing = Device ID is mandatory -casokitchen.winecoolar.status.refresh-interval = Refresh interval {0} not supported -casokitchen.winecoolar.status.http-status = HTTP Status Code {0} +casokitchen.winecooler-2z.status.api-key-missing = API Key is mandatory +casokitchen.winecooler-2z.status.device-id-missing = Device ID is mandatory +casokitchen.winecooler-2z.status.refresh-interval = Refresh interval {0} not supported +casokitchen.winecooler-2z.status.http-status = HTTP Status Code {0} diff --git a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java new file mode 100644 index 0000000000000..ee19ec2ded273 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.caso.internal; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +/** + * The {@link TestHandler} is testing handler functions + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +class TestHandler { + + @Test + void test() { + String dtUTC = "2024-08-13T23:25:32.2382092Z"; + Instant timestamp = Instant.parse(dtUTC); + ZonedDateTime losAngelesTime = timestamp.atZone(ZoneId.of("Europe/Berlin")); + System.out.println(losAngelesTime); + + // String dtUTC = "2024-08-13T23:25:32.2382092Z"; + // DateTimeType dtt = DateTimeType.valueOf(dtUTC); + // System.out.println(dtt); + } +} diff --git a/bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse.json b/bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse.json new file mode 100644 index 0000000000000..5488dc1b4776c --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse.json @@ -0,0 +1,13 @@ +{ + "temperature1": 9, + "targetTemperature1": 9, + "temperature2": 10, + "targetTemperature2": 10, + "power1": true, + "power2": true, + "light1": false, + "light2": false, + "logTimestampUtc": "2024-08-13T23:25:32.2382092Z", + "temperatureUnit": "C", + "hint": "Note: Device status and content of this message may differ due to synchronization intervals of distributed systems." +} \ No newline at end of file From e36661034ef6151b9fe934a7ac787fa0f7f7350d Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Wed, 14 Aug 2024 03:26:07 +0200 Subject: [PATCH 07/15] quick refresh Signed-off-by: Bernd Weymann --- .../internal/handler/CasoKitchenHandler.java | 87 +++++++++++++++++-- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java index af8455974e6ac..aa20359f85bd5 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java @@ -44,6 +44,7 @@ import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,6 +60,7 @@ public class CasoKitchenHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(CasoKitchenHandler.class); private Optional configuration = Optional.empty(); + private Optional cachedResult = Optional.empty(); private @Nullable ScheduledFuture refreshJob; private final HttpClient httpClient; private final TimeZoneProvider timeZoneProvider; @@ -71,6 +73,73 @@ public CasoKitchenHandler(Thing thing, HttpClient hc, TimeZoneProvider tzp) { @Override public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + cachedResult.ifPresent(result -> { + // update channels from cached result if available + String group = channelUID.getGroupId(); + if (group == null) { + return; // no channels without group defined! + } + String channel = channelUID.getIdWithoutGroup(); + switch (group) { + case GENERIC: + switch (channel) { + case LIGHT: + updateState(new ChannelUID(thing.getUID(), GENERIC, LIGHT), + OnOffType.from(result.light1 && result.light2)); + break; + case LAST_UPDATE: + Instant timestamp = Instant.parse(result.logTimestampUtc); + updateState(new ChannelUID(thing.getUID(), GENERIC, LAST_UPDATE), + new DateTimeType(timestamp.atZone(timeZoneProvider.getTimeZone()))); + break; + case HINT: + updateState(new ChannelUID(thing.getUID(), GENERIC, HINT), + StringType.valueOf(result.hint)); + break; + } + break; + case TOP: + switch (channel) { + case LIGHT: + updateState(new ChannelUID(thing.getUID(), TOP, LIGHT), OnOffType.from(result.light1)); + break; + case POWER: + updateState(new ChannelUID(thing.getUID(), TOP, POWER), OnOffType.from(result.power1)); + break; + case TEMPERATURE: + updateState(new ChannelUID(thing.getUID(), TOP, TEMPERATURE), + QuantityType.valueOf(result.temperature1, SIUnits.CELSIUS)); + break; + case TARGET_TEMPERATURE: + updateState(new ChannelUID(thing.getUID(), TOP, TARGET_TEMPERATURE), + QuantityType.valueOf(result.targetTemperature1, SIUnits.CELSIUS)); + break; + } + break; + case BOTTOM: + switch (channel) { + case LIGHT: + updateState(new ChannelUID(thing.getUID(), BOTTOM, LIGHT), + OnOffType.from(result.light2)); + break; + case POWER: + updateState(new ChannelUID(thing.getUID(), BOTTOM, POWER), + OnOffType.from(result.power2)); + break; + case TEMPERATURE: + updateState(new ChannelUID(thing.getUID(), BOTTOM, TEMPERATURE), + QuantityType.valueOf(result.temperature2, SIUnits.CELSIUS)); + break; + case TARGET_TEMPERATURE: + updateState(new ChannelUID(thing.getUID(), BOTTOM, TARGET_TEMPERATURE), + QuantityType.valueOf(result.targetTemperature2, SIUnits.CELSIUS)); + break; + } + break; + } + }); + } if (LIGHT.equals(channelUID.getIdWithoutGroup())) { logger.info("{} request received for group {}", LIGHT, channelUID.getGroupId()); LightRequest lr = new LightRequest(); @@ -149,13 +218,14 @@ private boolean checkConfig(@Nullable CasoKitchenConfiguration c) { updateStatus(ThingStatus.ONLINE); return true; } else { - reason = "@text/casokitchen.winecooler.status.refresh-interval [\"" + c.refreshInterval + "\"]"; + reason = "@text/casokitchen.winecooler-2z.status.refresh-interval [\"" + c.refreshInterval + + "\"]"; } } else { - reason = "@text/casokitchen.winecooler.status.device-id-missing"; + reason = "@text/casokitchen.winecooler-2z.status.device-id-missing"; } } else { - reason = "@text/casokitchen.winecooler.status.api-key-missing"; + reason = "@text/casokitchen.winecooler-2z.status.api-key-missing"; } } updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, reason); @@ -178,21 +248,24 @@ public void onComplete(org.eclipse.jetty.client.api.Result result) { logger.info("Request to {} failed. Status: {} Reason: {}", STATUS_URL, responseStatus, resultContent); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/casokitchen.winecooler.status.http-status [\"" + responseStatus + " - " + "@text/casokitchen.winecooler-2z.status.http-status [\"" + responseStatus + " - " + resultContent + "\"]"); } else { updateStatus(ThingStatus.ONLINE); logger.info("Request to {} delivered {}", STATUS_URL, resultContent); if (resultContent != null) { - updateChannels(resultContent); + StatusResult statusResult = GSON.fromJson(resultContent, StatusResult.class); + if (statusResult != null) { + cachedResult = Optional.of(statusResult); + updateChannels(statusResult); + } } } } }); } - private void updateChannels(String json) { - StatusResult result = GSON.fromJson(json, StatusResult.class); + private void updateChannels(StatusResult result) { updateState(new ChannelUID(thing.getUID(), GENERIC, HINT), StringType.valueOf(result.hint)); updateState(new ChannelUID(thing.getUID(), GENERIC, LIGHT), OnOffType.from(result.light1 && result.light2)); updateState(new ChannelUID(thing.getUID(), TOP, TEMPERATURE), From d2c7b68e78d06128b3fc37a48e6674bbf1470e43 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Thu, 15 Aug 2024 19:19:09 +0200 Subject: [PATCH 08/15] datetime correction Signed-off-by: Bernd Weymann --- .../internal/handler/CasoKitchenHandler.java | 7 ++++--- .../openhab/binding/caso/internal/TestHandler.java | 10 ++++++---- .../test/resources/StatusResponse-Berlin-1352.json | 13 +++++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse-Berlin-1352.json diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java index aa20359f85bd5..bc2b349a73189 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java @@ -15,6 +15,7 @@ import static org.openhab.binding.casokitchen.internal.CasoKitchenBindingConstants.*; import java.time.Instant; +import java.time.ZonedDateTime; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; @@ -280,8 +281,8 @@ private void updateChannels(StatusResult result) { QuantityType.valueOf(result.targetTemperature2, SIUnits.CELSIUS)); updateState(new ChannelUID(thing.getUID(), BOTTOM, POWER), OnOffType.from(result.power2)); updateState(new ChannelUID(thing.getUID(), BOTTOM, LIGHT), OnOffType.from(result.light2)); - Instant timestamp = Instant.parse(result.logTimestampUtc); - updateState(new ChannelUID(thing.getUID(), GENERIC, LAST_UPDATE), - new DateTimeType(timestamp.atZone(timeZoneProvider.getTimeZone()))); + + ZonedDateTime zdt = Instant.parse(result.logTimestampUtc).atZone(timeZoneProvider.getTimeZone()); + updateState(new ChannelUID(thing.getUID(), GENERIC, LAST_UPDATE), new DateTimeType(zdt)); } } diff --git a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java index ee19ec2ded273..f4389e87c518f 100644 --- a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; +import org.openhab.core.library.types.DateTimeType; /** * The {@link TestHandler} is testing handler functions @@ -29,11 +30,12 @@ class TestHandler { @Test void test() { - String dtUTC = "2024-08-13T23:25:32.2382092Z"; + String dtUTC = "2024-08-14T11:52:43.7170897Z"; Instant timestamp = Instant.parse(dtUTC); - ZonedDateTime losAngelesTime = timestamp.atZone(ZoneId.of("Europe/Berlin")); - System.out.println(losAngelesTime); - + ZoneId zone = ZoneId.of("Europe/Berlin"); + ZonedDateTime zdt = timestamp.atZone(zone); + System.out.println(zdt); + System.out.println(new DateTimeType(zdt)); // String dtUTC = "2024-08-13T23:25:32.2382092Z"; // DateTimeType dtt = DateTimeType.valueOf(dtUTC); // System.out.println(dtt); diff --git a/bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse-Berlin-1352.json b/bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse-Berlin-1352.json new file mode 100644 index 0000000000000..432cb1b25979d --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse-Berlin-1352.json @@ -0,0 +1,13 @@ +{ + "temperature1": 8, + "targetTemperature1": 8, + "temperature2": 10, + "targetTemperature2": 10, + "power1": true, + "power2": true, + "light1": false, + "light2": false, + "logTimestampUtc": "2024-08-14T11:52:43.7170897Z", + "temperatureUnit": "C", + "hint": "Note: Device status and content of this message may differ due to synchronization intervals of distributed systems." +} \ No newline at end of file From 3602c9861d4e6bd048beb42bc83c99a29d178c25 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Sun, 29 Sep 2024 22:46:35 +0200 Subject: [PATCH 09/15] http request timeout to 60 secs Signed-off-by: Bernd Weymann --- .../casokitchen/internal/handler/CasoKitchenHandler.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java index bc2b349a73189..6d5a4828574d4 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java @@ -25,6 +25,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.StringContentProvider; @@ -157,12 +158,15 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } if (lr.isValid()) { + logger.info("Send {} with command {}", LIGHT_URL, GSON.toJson(lr)); Request req = httpClient.POST(LIGHT_URL); req.header(HttpHeader.CONTENT_TYPE, "application/json"); req.header(HTTP_HEADER_API_KEY, configuration.get().apiKey); req.content(new StringContentProvider(GSON.toJson(lr))); try { - req.send(); + ContentResponse response = req.timeout(60, TimeUnit.SECONDS).send(); + logger.info("Call to {} responded with status {} reason {}", LIGHT_URL, response.getStatus(), + response.getReason()); } catch (InterruptedException | TimeoutException | ExecutionException e) { logger.warn("Call to {} failed with reason {}", LIGHT_URL, e.getMessage()); } @@ -239,7 +243,7 @@ private void dataUpdate() { req.header(HttpHeader.CONTENT_TYPE, "application/json"); req.header(HTTP_HEADER_API_KEY, configuration.get().apiKey); req.content(new StringContentProvider(GSON.toJson(requestContent))); - req.timeout(15, TimeUnit.SECONDS).send(new BufferingResponseListener() { + req.timeout(60, TimeUnit.SECONDS).send(new BufferingResponseListener() { @NonNullByDefault({}) @Override public void onComplete(org.eclipse.jetty.client.api.Result result) { From e96b590e479fb22c5449c157e344c9fd09a9f9a5 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Sun, 19 Jan 2025 23:02:30 +0100 Subject: [PATCH 10/15] prepare release version Signed-off-by: Bernd Weymann --- .../org.openhab.binding.casokitchen/README.md | 85 ++++--- .../internal/CasoKitchenBindingConstants.java | 2 +- .../internal/CasoKitchenHandlerFactory.java | 5 +- ...a => TwoZonesWinecoolerConfiguration.java} | 5 +- ...er.java => TwoZonesWinecoolerHandler.java} | 229 ++++++++---------- .../src/main/resources/OH-INF/addon/addon.xml | 5 +- .../OH-INF/i18n/casokitchen.properties | 31 ++- .../OH-INF/thing/bottom-zone-group.xml | 7 +- .../resources/OH-INF/thing/channel-types.xml | 24 +- .../resources/OH-INF/thing/generic-group.xml | 2 +- .../resources/OH-INF/thing/thing-types.xml | 4 +- .../resources/OH-INF/thing/top-zone-group.xml | 7 +- .../binding/caso/internal/TestHandler.java | 153 +++++++++++- .../resources/StatusResponse-Berlin-1352.json | 13 - 14 files changed, 339 insertions(+), 233 deletions(-) rename bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/{CasoKitchenConfiguration.java => TwoZonesWinecoolerConfiguration.java} (83%) rename bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/{CasoKitchenHandler.java => TwoZonesWinecoolerHandler.java} (57%) delete mode 100644 bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse-Berlin-1352.json diff --git a/bundles/org.openhab.binding.casokitchen/README.md b/bundles/org.openhab.binding.casokitchen/README.md index 88630da46f640..5e87a3ca15309 100644 --- a/bundles/org.openhab.binding.casokitchen/README.md +++ b/bundles/org.openhab.binding.casokitchen/README.md @@ -1,10 +1,10 @@ # CasoKitchen Binding -Gives you control on Caso WineCooler. - +Provides access towards CASO Smart Kitchen devices which are connected within the [CASO Control App](https://www.casocontrol.de/). + ## Supported Things -- `winecooler`: Wine cooler +- `winecooler-2z`: Wine cooler with two zones ## Discovery @@ -12,13 +12,15 @@ There's no automatic discovery. ## Thing Configuration -You need a [Caso Account](https://www.casoapp.com/Account/Create) to get configuration parameters. +You need a [CASO Account](https://www.casoapp.com/Account/Create) to get configuration parameters. After register you'll get the - API key - Device ID -### `sample` Thing Configuration +## Wine Cooler with 2 Zones + +### Configuration winecooler-2z | Name | Type | Description | Default | |-----------------|---------|------------------------------------------------------|---------| @@ -26,59 +28,74 @@ After register you'll get the | deviceId | text | Device Id obtained from thing configuration | N/A | | refreshInterval | integer | Interval the device is polled in minutes | 5 | -## Channels +### Channels winecooler-2z + +Channels are separated in 3 groups + +- `generic` group channels for states covering the whole device +- `top` group channels for states covering the top zone +- `generic` group channels for states covering the bottom zone + +#### Group Generic -### Generic +Group name `generic`. | Channel | Type | Read/Write | Description | |---------------|----------|------------|------------------------------| -| light-control | Switch | RW | Control lights for all zones | -| hint | text | R | General command description | +| light-switch | Switch | RW | Control lights for all zones | | last-update | DateTime | R | Date and Time of last update | +| hint | String | R | General command description | -### Top Zone +#### Group Top Zone + +Group name `top`. + +The `set-temperature` channel is holding the desired temperature controlled via buttons on the wine cooler device. +Currently it cannot be changed using the API. | Channel | Type | Read/Write | Description | |------------------|-----------------------|------------|------------------------------| -| temperature | Number:Temperature | R | Current Zone Temperature | -| set-temperature | Number:Temperature | R | Wanted Zone Temperature | -| light-control | Switch | RW | Control lights for this zone | | power | Switch | R | Zone Power | +| temperature | Number:Temperature | R | Current Zone Temperature | +| set-temperature | Number:Temperature | R | Desired Zone Temperature | +| light-switch | Switch | RW | Control lights for this zone | + +#### Group Bottom Zone -### Bottom Zone +Group name `bottom`. + +The `set-temperature` channel is holding the desired temperature controlled via buttons on the wine cooler device. +Currently it cannot be changed using the API. | Channel | Type | Read/Write | Description | |------------------|-----------------------|------------|------------------------------| -| temperature | Number:Temperature | R | Current Zone Temperature | -| set-temperature | Number:Temperature | R | Wanted Zone Temperature | -| light-control | Switch | RW | Control lights for this zone | | power | Switch | R | Zone Power | +| temperature | Number:Temperature | R | Current Zone Temperature | +| set-temperature | Number:Temperature | R | Desired Zone Temperature | +| light-switch | Switch | RW | Control lights for this zone | ## Full Example -_Provide a full usage example based on textual configuration files._ -_*.things, *.items examples are mandatory as textual configuration is well used by many users._ -_*.sitemap examples are optional._ - ### Thing Configuration ```java -Example thing configuration goes here. +Thing casokitchen:winecooler-2z:whiny "Whiny Wine Cooler" [ apiKey="ABC", deviceId="XYZ" ] ``` ### Item Configuration ```java -Example item configuration goes here. +Switch Whiny_Generic_LightSwitch {channel="casokitchen:winecooler-2z:whiny:generic#light-switch" } +DateTime Whiny_Generic_LastUpdate {channel="casokitchen:winecooler-2z:whiny:generic#last-update" } +String Whiny_Generic_Hint {channel="casokitchen:winecooler-2z:whiny:generic#hint" } + +Switch Whiny_Top_Power {channel="casokitchen:winecooler-2z:whiny:top#power" } +Number:Temperature Whiny_Top_CurrentTemperature {channel="casokitchen:winecooler-2z:whiny:top#temperature" } +Number:Temperature Whiny_Top_DesiredTemperature {channel="casokitchen:winecooler-2z:whiny:top#set-temperature" } +Switch Whiny_Top_LightSwitch {channel="casokitchen:winecooler-2z:whiny:top#light-switch" } + +Switch Whiny_Bottom_Power {channel="casokitchen:winecooler-2z:whiny:bottom#power" } +Number:Temperature Whiny_Bottom_CurrentTemperature {channel="casokitchen:winecooler-2z:whiny:bottom#temperature" } +Number:Temperature Whiny_Bottom_DesiredTemperature {channel="casokitchen:winecooler-2z:whiny:bottom#set-temperature" } +Switch Whiny_Bottom_LightSwitch {channel="casokitchen:winecooler-2z:whiny:bottom#light-switch" } ``` - -### Sitemap Configuration - -```perl -Optional Sitemap configuration goes here. -Remove this section, if not needed. -``` - -## Any custom content here! - -_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_ diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java index 8ec3376aeafe7..58c3f699eb6f9 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java @@ -39,7 +39,7 @@ public class CasoKitchenBindingConstants { public static final String TEMPERATURE = "temperature"; public static final String TARGET_TEMPERATURE = "set-temperature"; public static final String POWER = "power"; - public static final String LIGHT = "light-control"; + public static final String LIGHT = "light-switch"; public static final String HINT = "hint"; public static final String LAST_UPDATE = "last-update"; diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java index b21dc137f162f..877933f6f57d2 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java @@ -19,7 +19,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.casokitchen.internal.handler.CasoKitchenHandler; +import org.openhab.binding.casokitchen.internal.handler.TwoZonesWinecoolerHandler; import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Thing; @@ -61,9 +61,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_WINECOOLER.equals(thingTypeUID)) { - return new CasoKitchenHandler(thing, httpClient, timeZoneProvider); + return new TwoZonesWinecoolerHandler(thing, httpClient, timeZoneProvider); } - return null; } } diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/CasoKitchenConfiguration.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/TwoZonesWinecoolerConfiguration.java similarity index 83% rename from bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/CasoKitchenConfiguration.java rename to bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/TwoZonesWinecoolerConfiguration.java index 6b72f6e42baea..a3b0fcbb15fb7 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/CasoKitchenConfiguration.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/TwoZonesWinecoolerConfiguration.java @@ -17,13 +17,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link CasoKitchenConfiguration} class contains fields mapping thing configuration parameters. + * The {@link TwoZonesWinecoolerConfiguration} class contains fields mapping thing configuration parameters. * * @author Bernd Weymann - Initial contribution */ @NonNullByDefault -public class CasoKitchenConfiguration { - +public class TwoZonesWinecoolerConfiguration { public String apiKey = EMPTY; public String deviceId = EMPTY; public int refreshInterval = 5; diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java similarity index 57% rename from bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java rename to bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java index 6d5a4828574d4..c57be402e3011 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/CasoKitchenHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java @@ -23,14 +23,12 @@ import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpHeader; -import org.openhab.binding.casokitchen.internal.config.CasoKitchenConfiguration; +import org.openhab.binding.casokitchen.internal.config.TwoZonesWinecoolerConfiguration; import org.openhab.binding.casokitchen.internal.dto.LightRequest; import org.openhab.binding.casokitchen.internal.dto.StatusRequest; import org.openhab.binding.casokitchen.internal.dto.StatusResult; @@ -51,37 +49,75 @@ import org.slf4j.LoggerFactory; /** - * The {@link CasoKitchenHandler} is responsible for handling commands, which are + * The {@link TwoZonesWinecoolerHandler} is responsible for handling commands, which are * sent to one of the channels. * * @author Bernd Weymann - Initial contribution */ @NonNullByDefault -public class CasoKitchenHandler extends BaseThingHandler { +public class TwoZonesWinecoolerHandler extends BaseThingHandler { - private final Logger logger = LoggerFactory.getLogger(CasoKitchenHandler.class); + private final Logger logger = LoggerFactory.getLogger(TwoZonesWinecoolerHandler.class); - private Optional configuration = Optional.empty(); - private Optional cachedResult = Optional.empty(); - private @Nullable ScheduledFuture refreshJob; - private final HttpClient httpClient; private final TimeZoneProvider timeZoneProvider; + private final HttpClient httpClient; + private Optional> refreshJob = Optional.empty(); + private Optional cachedResult = Optional.empty(); + private TwoZonesWinecoolerConfiguration configuration = new TwoZonesWinecoolerConfiguration(); - public CasoKitchenHandler(Thing thing, HttpClient hc, TimeZoneProvider tzp) { + public TwoZonesWinecoolerHandler(Thing thing, HttpClient hc, TimeZoneProvider tzp) { super(thing); httpClient = hc; timeZoneProvider = tzp; } + @Override + public void initialize() { + configuration = getConfigAs(TwoZonesWinecoolerConfiguration.class); + if (checkConfig()) { + startSchedule(); + } + } + + private boolean checkConfig() { + String reason = "Config Empty"; + if (configuration.apiKey != EMPTY) { + if (configuration.deviceId != EMPTY) { + if (configuration.refreshInterval > 0) { + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, + "@text/casokitchen.winecooler-2z.status.wait-for-response"); + return true; + } else { + reason = "@text/casokitchen.winecooler-2z.status.refresh-interval [\"" + + configuration.refreshInterval + "\"]"; + } + } else { + reason = "@text/casokitchen.winecooler-2z.status.device-id-missing"; + } + } else { + reason = "@text/casokitchen.winecooler-2z.status.api-key-missing"; + } + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, reason); + return false; + } + + private void startSchedule() { + refreshJob.ifPresent(job -> { + job.cancel(false); + }); + refreshJob = Optional.of( + scheduler.scheduleWithFixedDelay(this::dataUpdate, 0, configuration.refreshInterval, TimeUnit.MINUTES)); + } + @Override public void handleCommand(ChannelUID channelUID, Command command) { + String group = channelUID.getGroupId(); + if (group == null) { + return; // no channels without group defined! + } if (command instanceof RefreshType) { cachedResult.ifPresent(result -> { // update channels from cached result if available - String group = channelUID.getGroupId(); - if (group == null) { - return; // no channels without group defined! - } String channel = channelUID.getIdWithoutGroup(); switch (group) { case GENERIC: @@ -102,175 +138,100 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; case TOP: - switch (channel) { - case LIGHT: - updateState(new ChannelUID(thing.getUID(), TOP, LIGHT), OnOffType.from(result.light1)); - break; - case POWER: - updateState(new ChannelUID(thing.getUID(), TOP, POWER), OnOffType.from(result.power1)); - break; - case TEMPERATURE: - updateState(new ChannelUID(thing.getUID(), TOP, TEMPERATURE), - QuantityType.valueOf(result.temperature1, SIUnits.CELSIUS)); - break; - case TARGET_TEMPERATURE: - updateState(new ChannelUID(thing.getUID(), TOP, TARGET_TEMPERATURE), - QuantityType.valueOf(result.targetTemperature1, SIUnits.CELSIUS)); - break; - } - break; case BOTTOM: switch (channel) { case LIGHT: - updateState(new ChannelUID(thing.getUID(), BOTTOM, LIGHT), + updateState(new ChannelUID(thing.getUID(), group, LIGHT), OnOffType.from(result.light2)); break; case POWER: - updateState(new ChannelUID(thing.getUID(), BOTTOM, POWER), + updateState(new ChannelUID(thing.getUID(), group, POWER), OnOffType.from(result.power2)); break; case TEMPERATURE: - updateState(new ChannelUID(thing.getUID(), BOTTOM, TEMPERATURE), + updateState(new ChannelUID(thing.getUID(), group, TEMPERATURE), QuantityType.valueOf(result.temperature2, SIUnits.CELSIUS)); break; case TARGET_TEMPERATURE: - updateState(new ChannelUID(thing.getUID(), BOTTOM, TARGET_TEMPERATURE), + updateState(new ChannelUID(thing.getUID(), group, TARGET_TEMPERATURE), QuantityType.valueOf(result.targetTemperature2, SIUnits.CELSIUS)); break; } break; } }); - } - if (LIGHT.equals(channelUID.getIdWithoutGroup())) { - logger.info("{} request received for group {}", LIGHT, channelUID.getGroupId()); + } else if (LIGHT.equals(channelUID.getIdWithoutGroup())) { LightRequest lr = new LightRequest(); - lr.technicalDeviceId = configuration.get().deviceId; + lr.technicalDeviceId = configuration.deviceId; if (command instanceof OnOffType) { lr.lightOn = OnOffType.ON.equals(command); - if (TOP.equals(channelUID.getGroupId())) { - lr.zone = 1; - } else if (BOTTOM.equals(channelUID.getGroupId())) { - lr.zone = 2; - } else if (GENERIC.equals(channelUID.getGroupId())) { - // light for all zones - lr.zone = 0; + switch (group) { + case GENERIC: + lr.zone = 0; + break; + case TOP: + lr.zone = 1; + break; + case BOTTOM: + lr.zone = 2; + break; } - } - if (lr.isValid()) { logger.info("Send {} with command {}", LIGHT_URL, GSON.toJson(lr)); Request req = httpClient.POST(LIGHT_URL); req.header(HttpHeader.CONTENT_TYPE, "application/json"); - req.header(HTTP_HEADER_API_KEY, configuration.get().apiKey); + req.header(HTTP_HEADER_API_KEY, configuration.apiKey); req.content(new StringContentProvider(GSON.toJson(lr))); try { ContentResponse response = req.timeout(60, TimeUnit.SECONDS).send(); - logger.info("Call to {} responded with status {} reason {}", LIGHT_URL, response.getStatus(), + logger.debug("Call to {} responded with status {} reason {}", LIGHT_URL, response.getStatus(), response.getReason()); } catch (InterruptedException | TimeoutException | ExecutionException e) { - logger.warn("Call to {} failed with reason {}", LIGHT_URL, e.getMessage()); + logger.debug("Call to {} failed with reason {}", LIGHT_URL, e.getMessage()); } // force data update - dataUpdate(); + scheduler.schedule(this::dataUpdate, 2, TimeUnit.SECONDS); } } else { - logger.info("Request {} doesn't fit", command); - } - } - - @Override - public void initialize() { - configuration = Optional.of(getConfigAs(CasoKitchenConfiguration.class)); - if (checkConfig(configuration.get())) { - updateStatus(ThingStatus.UNKNOWN); - startSchedule(); - } - } - - private void startSchedule() { - ScheduledFuture localRefreshJob = refreshJob; - if (localRefreshJob != null && configuration.isPresent()) { - if (localRefreshJob.isCancelled()) { - refreshJob = scheduler.scheduleWithFixedDelay(this::dataUpdate, 0, configuration.get().refreshInterval, - TimeUnit.MINUTES); - } // else - scheduler is already running! - } else { - refreshJob = scheduler.scheduleWithFixedDelay(this::dataUpdate, 0, configuration.get().refreshInterval, - TimeUnit.MINUTES); + logger.debug("Cannot handle command {}", command); } } @Override public void dispose() { - ScheduledFuture localRefreshJob = refreshJob; - if (localRefreshJob != null) { - localRefreshJob.cancel(true); - } - } - - /** - * Checks if config is valid - a) not null and b) sensorid is a number - * - * @param c - * @return - */ - private boolean checkConfig(@Nullable CasoKitchenConfiguration c) { - String reason = "Config Empty"; - if (c != null) { - if (c.apiKey != EMPTY) { - if (c.deviceId != EMPTY) { - if (c.refreshInterval > 0) { - updateStatus(ThingStatus.ONLINE); - return true; - } else { - reason = "@text/casokitchen.winecooler-2z.status.refresh-interval [\"" + c.refreshInterval - + "\"]"; - } - } else { - reason = "@text/casokitchen.winecooler-2z.status.device-id-missing"; - } - } else { - reason = "@text/casokitchen.winecooler-2z.status.api-key-missing"; - } - } - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, reason); - return false; + refreshJob.ifPresent(job -> { + job.cancel(true); + }); } private void dataUpdate() { - StatusRequest requestContent = new StatusRequest(configuration.get().deviceId); + StatusRequest requestContent = new StatusRequest(configuration.deviceId); Request req = httpClient.POST(STATUS_URL); req.header(HttpHeader.CONTENT_TYPE, "application/json"); - req.header(HTTP_HEADER_API_KEY, configuration.get().apiKey); + req.header(HTTP_HEADER_API_KEY, configuration.apiKey); req.content(new StringContentProvider(GSON.toJson(requestContent))); - req.timeout(60, TimeUnit.SECONDS).send(new BufferingResponseListener() { - @NonNullByDefault({}) - @Override - public void onComplete(org.eclipse.jetty.client.api.Result result) { - int responseStatus = result.getResponse().getStatus(); - String resultContent = getContentAsString(); - if (responseStatus != 200) { - logger.info("Request to {} failed. Status: {} Reason: {}", STATUS_URL, responseStatus, - resultContent); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/casokitchen.winecooler-2z.status.http-status [\"" + responseStatus + " - " - + resultContent + "\"]"); - } else { - updateStatus(ThingStatus.ONLINE); - logger.info("Request to {} delivered {}", STATUS_URL, resultContent); - if (resultContent != null) { - StatusResult statusResult = GSON.fromJson(resultContent, StatusResult.class); - if (statusResult != null) { - cachedResult = Optional.of(statusResult); - updateChannels(statusResult); - } - } + try { + ContentResponse contentResponse = req.timeout(60, TimeUnit.SECONDS).send(); + int responseStatus = contentResponse.getStatus(); + String contentResult = contentResponse.getContentAsString(); + if (responseStatus != 200) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/casokitchen.winecooler-2z.status.http-status [\"" + responseStatus + " - " + + contentResult + "\"]"); + } else { + updateStatus(ThingStatus.ONLINE); + StatusResult statusResult = GSON.fromJson(contentResult, StatusResult.class); + if (statusResult != null) { + updateChannels(statusResult); } } - }); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/casokitchen.winecooler-2z.status.http-status [\"" + e.getMessage() + "\"]"); + } } private void updateChannels(StatusResult result) { + cachedResult = Optional.of(result); updateState(new ChannelUID(thing.getUID(), GENERIC, HINT), StringType.valueOf(result.hint)); updateState(new ChannelUID(thing.getUID(), GENERIC, LIGHT), OnOffType.from(result.light1 && result.light2)); updateState(new ChannelUID(thing.getUID(), TOP, TEMPERATURE), diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/addon/addon.xml index 93edef4ae2a7b..392baf398f1f3 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/addon/addon.xml @@ -4,7 +4,8 @@ xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd"> binding - CasoKitchen Binding - This is the binding for CasoKitchen. + CASO Kitchen Binding + Binding to connect CASO Smart Kitchen devices + cloud diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties index c92cf54aa96a0..3d9398cafdc79 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/i18n/casokitchen.properties @@ -1,12 +1,12 @@ # add-on -addon.casokitchen.name = CasoKitchen Binding -addon.casokitchen.description = This is the binding for CasoKitchen. +addon.casokitchen.name = CASO Kitchen Binding +addon.casokitchen.description = Binding to connect CASO Smart Kitchen devices # thing types -thing-type.casokitchen.winecooler-2z.label = Winecooler 2 Zones -thing-type.casokitchen.winecooler-2z.description = Winecooler with 2 cooling zones +thing-type.casokitchen.winecooler-2z.label = Wine Cooler 2 Zones +thing-type.casokitchen.winecooler-2z.description = Wine cooler with 2 cooling zones # thing types config @@ -20,24 +20,31 @@ thing-type.config.casokitchen.winecooler-2z.refreshInterval.description = Interv # channel group types channel-group-type.casokitchen.bottom-values.label = Bottom Zone +channel-group-type.casokitchen.bottom-values.channel.power.label = Zone is Powered +channel-group-type.casokitchen.bottom-values.channel.power.description = Showing if zone is currently powered channel-group-type.casokitchen.generic-values.label = Generic Values channel-group-type.casokitchen.top-values.label = Top Zone +channel-group-type.casokitchen.top-values.channel.power.label = Zone is Powered +channel-group-type.casokitchen.top-values.channel.power.description = Showing if zone is currently powered # channel types channel-type.casokitchen.hint.label = Hint channel-type.casokitchen.hint.description = Textual hint for device status channel-type.casokitchen.last-update.label = Last Update -channel-type.casokitchen.last-update.description = Timestamp of latest device communication +channel-type.casokitchen.last-update.description = Time stamp of latest device communication channel-type.casokitchen.last-update.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM -channel-type.casokitchen.light-control.label = Light Control -channel-type.casokitchen.light-control.description = Switching lights on and off +channel-type.casokitchen.light-switch.label = Light Switch +channel-type.casokitchen.light-switch.description = Switching lights on and off +channel-type.casokitchen.set-temperature.label = Desired Temperature +channel-type.casokitchen.set-temperature.description = Desired Zone Temperature +channel-type.casokitchen.temperature.label = Temperature +channel-type.casokitchen.temperature.description = Current Zone Temperature + +# channel types + channel-type.casokitchen.power.label = Zone is Powered channel-type.casokitchen.power.description = Showing if zone is currently powered -channel-type.casokitchen.set-temperature.label = Target Temperature -channel-type.casokitchen.set-temperature.description = Target Temperature to reach -channel-type.casokitchen.temperature.label = Temperature -channel-type.casokitchen.temperature.description = Current Temperature # status details @@ -45,4 +52,4 @@ casokitchen.winecooler-2z.status.api-key-missing = API Key is mandatory casokitchen.winecooler-2z.status.device-id-missing = Device ID is mandatory casokitchen.winecooler-2z.status.refresh-interval = Refresh interval {0} not supported casokitchen.winecooler-2z.status.http-status = HTTP Status Code {0} - +casokitchen.winecooler-2z.status.wait-for-response = Wait for response diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml index 3c55b82959fbc..bb58e7776698b 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/bottom-zone-group.xml @@ -6,10 +6,13 @@ + + + Showing if zone is currently powered + - - + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml index 5a4621c51f5de..b609596035c99 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml @@ -7,24 +7,22 @@ Number:Temperature - Current Temperature + Current Zone Temperature + Temperature Number:Temperature - - Target Temperature to reach - - - - Switch - - Showing if zone is currently powered - + + Desired Zone Temperature + + SetPoint + + - + Switch - + Switching lights on and off @@ -36,7 +34,7 @@ DateTime - Timestamp of latest device communication + Time stamp of latest device communication diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml index 4a22ce5ff3274..13e5984a7e06d 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/generic-group.xml @@ -6,7 +6,7 @@ - + diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml index 1531dd4cfdbc4..6ecdf3f4df6f7 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml @@ -5,8 +5,8 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - Winecooler with 2 cooling zones + + Wine cooler with 2 cooling zones diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml index 79750fbe6f878..122a6cc00408a 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/top-zone-group.xml @@ -6,10 +6,13 @@ + + + Showing if zone is currently powered + - - + diff --git a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java index f4389e87c518f..f4df820ea28f7 100644 --- a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java @@ -12,13 +12,39 @@ */ package org.openhab.binding.caso.internal; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; import java.time.Instant; import java.time.ZoneId; -import java.time.ZonedDateTime; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; import org.junit.jupiter.api.Test; +import org.openhab.binding.casokitchen.internal.CasoKitchenBindingConstants; +import org.openhab.binding.casokitchen.internal.handler.TwoZonesWinecoolerHandler; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.State; /** * The {@link TestHandler} is testing handler functions @@ -27,17 +53,122 @@ */ @NonNullByDefault class TestHandler { + private static final int FULL_UPDATE_COUNT = 11; + + TimeZoneProvider tzp = new TimeZoneProvider() { + @Override + public ZoneId getTimeZone() { + return ZoneId.systemDefault(); + } + }; + + private HttpClientFactory prepareHttpResponse() { + // Prepare http response + HttpClientFactory httpFactory = mock(HttpClientFactory.class); + HttpClient httpClient = mock(HttpClient.class); + when(httpFactory.getCommonHttpClient()).thenReturn(httpClient); + Request httpStatusRequest = mock(Request.class); + when(httpClient.POST(CasoKitchenBindingConstants.STATUS_URL)).thenReturn(httpStatusRequest); + ContentResponse contentResponse = mock(ContentResponse.class); + when(contentResponse.getStatus()).thenReturn(200); + String content = CasoKitchenBindingConstants.EMPTY; + try { + content = Files.readString(Path.of("src/test/resources/", "StatusResponse.json"), Charset.defaultCharset()); + } catch (IOException e) { + fail(e.getMessage()); + } + when(contentResponse.getContentAsString()).thenReturn(content); + when(httpStatusRequest.timeout(anyLong(), any(TimeUnit.class))).thenReturn(httpStatusRequest); + try { + when(httpStatusRequest.send()).thenReturn(contentResponse); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + fail(e.getMessage()); + } + return httpFactory; + } @Test - void test() { - String dtUTC = "2024-08-14T11:52:43.7170897Z"; - Instant timestamp = Instant.parse(dtUTC); - ZoneId zone = ZoneId.of("Europe/Berlin"); - ZonedDateTime zdt = timestamp.atZone(zone); - System.out.println(zdt); - System.out.println(new DateTimeType(zdt)); - // String dtUTC = "2024-08-13T23:25:32.2382092Z"; - // DateTimeType dtt = DateTimeType.valueOf(dtUTC); - // System.out.println(dtt); + void testConfigErrors() { + ThingImpl thing = new ThingImpl(CasoKitchenBindingConstants.THING_TYPE_WINECOOLER, "test"); + + FactoryMock factory = new FactoryMock(prepareHttpResponse(), tzp); + ThingHandler handler = factory.createHandler(thing); + assertNotNull(handler); + assertTrue(handler instanceof TwoZonesWinecoolerHandler); + TwoZonesWinecoolerHandler winecoolerHandler = (TwoZonesWinecoolerHandler) handler; + CallbackMock callback = new CallbackMock(); + winecoolerHandler.setCallback(callback); + winecoolerHandler.initialize(); + + ThingStatusInfo tsi = thing.getStatusInfo(); + assertEquals(ThingStatus.OFFLINE, tsi.getStatus()); + assertEquals(ThingStatusDetail.CONFIGURATION_ERROR, tsi.getStatusDetail()); + assertEquals("@text/casokitchen.winecooler-2z.status.api-key-missing", tsi.getDescription()); + + Configuration config = new Configuration(); + config.put("apiKey", "abc"); + thing.setConfiguration(config); + winecoolerHandler.initialize(); + tsi = thing.getStatusInfo(); + assertEquals(ThingStatus.OFFLINE, tsi.getStatus()); + assertEquals(ThingStatusDetail.CONFIGURATION_ERROR, tsi.getStatusDetail()); + assertEquals("@text/casokitchen.winecooler-2z.status.device-id-missing", tsi.getDescription()); + + config.put("deviceId", "xyz"); + thing.setConfiguration(config); + winecoolerHandler.initialize(); + tsi = thing.getStatusInfo(); + assertEquals(ThingStatus.UNKNOWN, tsi.getStatus()); + assertEquals(ThingStatusDetail.NONE, tsi.getStatusDetail()); + assertEquals("@text/casokitchen.winecooler-2z.status.wait-for-response", tsi.getDescription()); + } + + @Test + void testHandler() { + // Prepare Thing + ThingImpl thing = new ThingImpl(CasoKitchenBindingConstants.THING_TYPE_WINECOOLER, "test"); + Configuration config = new Configuration(); + config.put("apiKey", "abc"); + config.put("deviceId", "xyz"); + thing.setConfiguration(config); + + // Prepare handler + FactoryMock factory = new FactoryMock(prepareHttpResponse(), tzp); + ThingHandler handler = factory.createHandler(thing); + assertNotNull(handler); + assertTrue(handler instanceof TwoZonesWinecoolerHandler); + TwoZonesWinecoolerHandler winecoolerHandler = (TwoZonesWinecoolerHandler) handler; + CallbackMock callback = new CallbackMock(); + winecoolerHandler.setCallback(callback); + winecoolerHandler.initialize(); + callback.waitForOnline(); + callback.waitForFullUpdate(FULL_UPDATE_COUNT); + + // generic + assertEquals(OnOffType.OFF, callback.states.get("casokitchen:winecooler-2z:test:generic#light-switch")); + State dateTime = callback.states.get("casokitchen:winecooler-2z:test:generic#last-update"); + assertTrue(dateTime instanceof DateTimeType); + Instant lastTimestamp = ((DateTimeType) dateTime).getInstant(); + assertEquals("2024-08-13T23:25:32.238209200Z", lastTimestamp.toString()); + + // top + State currentTopTemp = callback.states.get("casokitchen:winecooler-2z:test:top#temperature"); + assertTrue(currentTopTemp instanceof QuantityType); + assertEquals("9 °C", currentTopTemp.toFullString()); + State currentTopSetTemp = callback.states.get("casokitchen:winecooler-2z:test:top#set-temperature"); + assertTrue(currentTopSetTemp instanceof QuantityType); + assertEquals("9 °C", currentTopSetTemp.toFullString()); + assertEquals(OnOffType.ON, callback.states.get("casokitchen:winecooler-2z:test:top#power")); + assertEquals(OnOffType.OFF, callback.states.get("casokitchen:winecooler-2z:test:top#light-switch")); + + // bottom + State currentBottomTemp = callback.states.get("casokitchen:winecooler-2z:test:bottom#temperature"); + assertTrue(currentBottomTemp instanceof QuantityType); + assertEquals("10 °C", currentBottomTemp.toFullString()); + State currentBottomSetTemp = callback.states.get("casokitchen:winecooler-2z:test:bottom#set-temperature"); + assertTrue(currentBottomSetTemp instanceof QuantityType); + assertEquals("10 °C", currentBottomSetTemp.toFullString()); + assertEquals(OnOffType.ON, callback.states.get("casokitchen:winecooler-2z:test:bottom#power")); + assertEquals(OnOffType.OFF, callback.states.get("casokitchen:winecooler-2z:test:bottom#light-switch")); } } diff --git a/bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse-Berlin-1352.json b/bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse-Berlin-1352.json deleted file mode 100644 index 432cb1b25979d..0000000000000 --- a/bundles/org.openhab.binding.casokitchen/src/test/resources/StatusResponse-Berlin-1352.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "temperature1": 8, - "targetTemperature1": 8, - "temperature2": 10, - "targetTemperature2": 10, - "power1": true, - "power2": true, - "light1": false, - "light2": false, - "logTimestampUtc": "2024-08-14T11:52:43.7170897Z", - "temperatureUnit": "C", - "hint": "Note: Device status and content of this message may differ due to synchronization intervals of distributed systems." -} \ No newline at end of file From a36629317672af3b506d222d6c08d534960fe929 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Sun, 19 Jan 2025 23:02:53 +0100 Subject: [PATCH 11/15] prepare release version Signed-off-by: Bernd Weymann --- .../binding/caso/internal/CallbackMock.java | 168 ++++++++++++++++++ .../binding/caso/internal/FactoryMock.java | 39 ++++ 2 files changed, 207 insertions(+) create mode 100644 bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/CallbackMock.java create mode 100644 bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/FactoryMock.java diff --git a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/CallbackMock.java b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/CallbackMock.java new file mode 100644 index 0000000000000..47a5c45235698 --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/CallbackMock.java @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.caso.internal; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.config.core.ConfigDescription; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.type.ChannelGroupTypeUID; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.TimeSeries; + +/** + * {@link CallbackMock} listener for handler updates + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class CallbackMock implements ThingHandlerCallback { + public Map states = new HashMap<>(); + public ThingStatus thingStatus = ThingStatus.UNINITIALIZED; + + @Override + public void stateUpdated(ChannelUID channelUID, State state) { + states.put(channelUID.toString(), state); + } + + public void waitForFullUpdate(int stateCount) { + synchronized (this) { + Instant startWaiting = Instant.now(); + while (states.size() < stateCount && startWaiting.plus(5, ChronoUnit.SECONDS).isAfter(Instant.now())) { + try { + wait(250); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + } + if (!ThingStatus.ONLINE.equals(thingStatus)) { + fail(thingStatus.toString()); + } + } + } + + @Override + public void postCommand(ChannelUID channelUID, Command command) { + } + + @Override + public void sendTimeSeries(ChannelUID channelUID, TimeSeries timeSeries) { + } + + public void waitForOnline() { + synchronized (this) { + Instant startWaiting = Instant.now(); + while (!ThingStatus.ONLINE.equals(thingStatus) + && startWaiting.plus(5, ChronoUnit.SECONDS).isAfter(Instant.now())) { + try { + wait(250); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + } + if (!ThingStatus.ONLINE.equals(thingStatus)) { + fail(thingStatus.toString()); + } + } + } + + @Override + public void statusUpdated(Thing thing, ThingStatusInfo thingStatusInfo) { + synchronized (this) { + thing.setStatusInfo(thingStatusInfo); + thingStatus = thingStatusInfo.getStatus(); + notifyAll(); + } + } + + @Override + public void thingUpdated(Thing thing) { + } + + @Override + public void validateConfigurationParameters(Thing thing, Map configurationParameters) { + } + + @Override + public void validateConfigurationParameters(Channel channel, Map configurationParameters) { + } + + @Override + public @Nullable ConfigDescription getConfigDescription(ChannelTypeUID channelTypeUID) { + return null; + } + + @Override + public @Nullable ConfigDescription getConfigDescription(ThingTypeUID thingTypeUID) { + return null; + } + + @Override + public void configurationUpdated(Thing thing) { + } + + @Override + public void migrateThingType(Thing thing, ThingTypeUID thingTypeUID, Configuration configuration) { + } + + @Override + public void channelTriggered(Thing thing, ChannelUID channelUID, String event) { + } + + @Override + public ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelTypeUID channelTypeUID) { + return ChannelBuilder.create(new ChannelUID("test"), null); + } + + @Override + public ChannelBuilder editChannel(Thing thing, ChannelUID channelUID) { + return ChannelBuilder.create(new ChannelUID("test"), null); + } + + @Override + public List createChannelBuilders(ChannelGroupUID channelGroupUID, + ChannelGroupTypeUID channelGroupTypeUID) { + return List.of(); + } + + @Override + public boolean isChannelLinked(ChannelUID channelUID) { + return false; + } + + @Override + public @Nullable Bridge getBridge(ThingUID bridgeUID) { + return null; + } +} diff --git a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/FactoryMock.java b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/FactoryMock.java new file mode 100644 index 0000000000000..be151e8f1a91c --- /dev/null +++ b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/FactoryMock.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.caso.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.casokitchen.internal.CasoKitchenHandlerFactory; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.ThingHandler; + +/** + * {@link FactoryMock} for creating unit test handlers + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class FactoryMock extends CasoKitchenHandlerFactory { + + public FactoryMock(HttpClientFactory httpFactory, final TimeZoneProvider tzp) { + super(httpFactory, tzp); + } + + @Override + public @Nullable ThingHandler createHandler(Thing thing) { + return super.createHandler(thing); + } +} From 81845a5f6d58cd6752a44e5d398e9874c5cf0177 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Mon, 20 Jan 2025 00:12:28 +0100 Subject: [PATCH 12/15] 5.0..0 preparation Signed-off-by: Bernd Weymann --- bundles/org.openhab.binding.casokitchen/pom.xml | 2 +- .../casokitchen/internal/CasoKitchenBindingConstants.java | 4 ++-- .../casokitchen/internal/CasoKitchenHandlerFactory.java | 4 ++-- .../internal/config/TwoZonesWinecoolerConfiguration.java | 4 ++-- .../binding/casokitchen/internal/dto/LightRequest.java | 4 ++-- .../binding/casokitchen/internal/dto/StatusRequest.java | 4 ++-- .../binding/casokitchen/internal/dto/StatusResult.java | 4 ++-- .../internal/handler/TwoZonesWinecoolerHandler.java | 4 ++-- .../java/org/openhab/binding/caso/internal/CallbackMock.java | 4 ++-- .../java/org/openhab/binding/caso/internal/FactoryMock.java | 4 ++-- .../java/org/openhab/binding/caso/internal/TestHandler.java | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/bundles/org.openhab.binding.casokitchen/pom.xml b/bundles/org.openhab.binding.casokitchen/pom.xml index 41998c0d05e09..9b49b570f5eea 100644 --- a/bundles/org.openhab.binding.casokitchen/pom.xml +++ b/bundles/org.openhab.binding.casokitchen/pom.xml @@ -7,7 +7,7 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT org.openhab.binding.casokitchen diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java index 58c3f699eb6f9..7f18d661c47f2 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenBindingConstants.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java index 877933f6f57d2..a6f56438a9174 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/CasoKitchenHandlerFactory.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/TwoZonesWinecoolerConfiguration.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/TwoZonesWinecoolerConfiguration.java index a3b0fcbb15fb7..25eba78ac85ae 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/TwoZonesWinecoolerConfiguration.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/config/TwoZonesWinecoolerConfiguration.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/LightRequest.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/LightRequest.java index 28268a2957e42..f195a4e617fd4 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/LightRequest.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/LightRequest.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusRequest.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusRequest.java index 19cb986d9dd8c..f24014d638195 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusRequest.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusRequest.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusResult.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusResult.java index d0fd8d94e64b4..b986a26cced4d 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusResult.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/dto/StatusResult.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java index c57be402e3011..5fd4a683aa27e 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/CallbackMock.java b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/CallbackMock.java index 47a5c45235698..c69f6d01b3207 100644 --- a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/CallbackMock.java +++ b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/CallbackMock.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/FactoryMock.java b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/FactoryMock.java index be151e8f1a91c..80a58a80cdf55 100644 --- a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/FactoryMock.java +++ b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/FactoryMock.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java index f4df820ea28f7..7106383a82d84 100644 --- a/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/test/java/org/openhab/binding/caso/internal/TestHandler.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. From 95dd66cdf93d91019e1fe05565732ffafb67fdcf Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Sun, 9 Feb 2025 17:25:40 +0100 Subject: [PATCH 13/15] light update policy veto Signed-off-by: Bernd Weymann --- .../org.openhab.binding.casokitchen/README.md | 23 ++++--------------- .../handler/TwoZonesWinecoolerHandler.java | 22 ++++++++++-------- .../resources/OH-INF/thing/channel-types.xml | 1 + .../resources/OH-INF/thing/thing-types.xml | 2 +- 4 files changed, 18 insertions(+), 30 deletions(-) diff --git a/bundles/org.openhab.binding.casokitchen/README.md b/bundles/org.openhab.binding.casokitchen/README.md index 5e87a3ca15309..c2d018206fba8 100644 --- a/bundles/org.openhab.binding.casokitchen/README.md +++ b/bundles/org.openhab.binding.casokitchen/README.md @@ -33,10 +33,9 @@ After register you'll get the Channels are separated in 3 groups - `generic` group channels for states covering the whole device -- `top` group channels for states covering the top zone -- `generic` group channels for states covering the bottom zone +- `top` and `bottom` group channels for states covering the top zone -#### Group Generic +#### Generic Group Group name `generic`. @@ -46,23 +45,9 @@ Group name `generic`. | last-update | DateTime | R | Date and Time of last update | | hint | String | R | General command description | -#### Group Top Zone +#### Zone Groups -Group name `top`. - -The `set-temperature` channel is holding the desired temperature controlled via buttons on the wine cooler device. -Currently it cannot be changed using the API. - -| Channel | Type | Read/Write | Description | -|------------------|-----------------------|------------|------------------------------| -| power | Switch | R | Zone Power | -| temperature | Number:Temperature | R | Current Zone Temperature | -| set-temperature | Number:Temperature | R | Desired Zone Temperature | -| light-switch | Switch | RW | Control lights for this zone | - -#### Group Bottom Zone - -Group name `bottom`. +Group `top` and `bottom`. The `set-temperature` channel is holding the desired temperature controlled via buttons on the wine cooler device. Currently it cannot be changed using the API. diff --git a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java index 5fd4a683aa27e..e6d6503434d20 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java +++ b/bundles/org.openhab.binding.casokitchen/src/main/java/org/openhab/binding/casokitchen/internal/handler/TwoZonesWinecoolerHandler.java @@ -176,20 +176,22 @@ public void handleCommand(ChannelUID channelUID, Command command) { lr.zone = 2; break; } - logger.info("Send {} with command {}", LIGHT_URL, GSON.toJson(lr)); Request req = httpClient.POST(LIGHT_URL); req.header(HttpHeader.CONTENT_TYPE, "application/json"); req.header(HTTP_HEADER_API_KEY, configuration.apiKey); req.content(new StringContentProvider(GSON.toJson(lr))); try { ContentResponse response = req.timeout(60, TimeUnit.SECONDS).send(); - logger.debug("Call to {} responded with status {} reason {}", LIGHT_URL, response.getStatus(), - response.getReason()); + int responseStatus = response.getStatus(); + if (responseStatus == 200) { + updateState(new ChannelUID(thing.getUID(), group, LIGHT), OnOffType.from(lr.lightOn)); + } else { + logger.info("Call to {} responded with status {} reason {}", LIGHT_URL, response.getStatus(), + response.getReason()); + } } catch (InterruptedException | TimeoutException | ExecutionException e) { logger.debug("Call to {} failed with reason {}", LIGHT_URL, e.getMessage()); } - // force data update - scheduler.schedule(this::dataUpdate, 2, TimeUnit.SECONDS); } } else { logger.debug("Cannot handle command {}", command); @@ -213,16 +215,16 @@ private void dataUpdate() { ContentResponse contentResponse = req.timeout(60, TimeUnit.SECONDS).send(); int responseStatus = contentResponse.getStatus(); String contentResult = contentResponse.getContentAsString(); - if (responseStatus != 200) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/casokitchen.winecooler-2z.status.http-status [\"" + responseStatus + " - " - + contentResult + "\"]"); - } else { + if (responseStatus == 200) { updateStatus(ThingStatus.ONLINE); StatusResult statusResult = GSON.fromJson(contentResult, StatusResult.class); if (statusResult != null) { updateChannels(statusResult); } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/casokitchen.winecooler-2z.status.http-status [\"" + responseStatus + " - " + + contentResult + "\"]"); } } catch (InterruptedException | TimeoutException | ExecutionException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml index b609596035c99..be8277363e1ff 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/channel-types.xml @@ -24,6 +24,7 @@ Switch Switching lights on and off + veto String diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml index 6ecdf3f4df6f7..44a1eb7345362 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml @@ -23,7 +23,7 @@ Device ID from CASO connected devices - + Interval the device is polled in minutes. 5 From 544f5cbc52f15e188113fdd52721397fca9e8d66 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Sun, 9 Feb 2025 17:44:29 +0100 Subject: [PATCH 14/15] remove refreshInterval advanced parameter Signed-off-by: Bernd Weymann --- bundles/org.openhab.binding.casokitchen/README.md | 4 ++-- .../src/main/resources/OH-INF/thing/thing-types.xml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.casokitchen/README.md b/bundles/org.openhab.binding.casokitchen/README.md index c2d018206fba8..37c91835ad1ff 100644 --- a/bundles/org.openhab.binding.casokitchen/README.md +++ b/bundles/org.openhab.binding.casokitchen/README.md @@ -32,8 +32,8 @@ After register you'll get the Channels are separated in 3 groups -- `generic` group channels for states covering the whole device -- `top` and `bottom` group channels for states covering the top zone +- `generic` group covering states for the whole device +- `top` and `bottom` group covering states related to top or bottom zone #### Generic Group diff --git a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml index 44a1eb7345362..f2f85877ca614 100644 --- a/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.casokitchen/src/main/resources/OH-INF/thing/thing-types.xml @@ -27,7 +27,6 @@ Interval the device is polled in minutes. 5 - true From 732791dee674da414312ac69b241571b1980827a Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Sun, 9 Feb 2025 18:11:57 +0100 Subject: [PATCH 15/15] pom and codeowners Signed-off-by: Bernd Weymann --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 +++++ bundles/pom.xml | 1 + 3 files changed, 7 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 97bd8089e8d23..db1ae3d53c9ef 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -64,6 +64,7 @@ /bundles/org.openhab.binding.bticinosmarther/ @MrRonfo /bundles/org.openhab.binding.buienradar/ @gedejong /bundles/org.openhab.binding.caddx/ @jossuar +/bundles/org.openhab.binding.casokitchen/ @weymann /bundles/org.openhab.binding.cbus/ @jpharvey /bundles/org.openhab.binding.chatgpt/ @kaikreuzer /bundles/org.openhab.binding.chromecast/ @kaikreuzer diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index ea798c3d2c6f5..9561120921a53 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -306,6 +306,11 @@ org.openhab.binding.caddx ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.casokitchen + ${project.version} + org.openhab.addons.bundles org.openhab.binding.cbus diff --git a/bundles/pom.xml b/bundles/pom.xml index 6fed251e59339..69210c0cf3814 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -97,6 +97,7 @@ org.openhab.binding.bticinosmarther org.openhab.binding.buienradar org.openhab.binding.caddx + org.openhab.binding.casokitchen org.openhab.binding.cbus org.openhab.binding.chatgpt org.openhab.binding.chromecast