From 64723db7aa15437a920d9ae5a2c1ba640f839427 Mon Sep 17 00:00:00 2001 From: Christian Kittel Date: Sat, 8 Apr 2023 11:40:59 +0200 Subject: [PATCH] [OJElectronics] Add SignalR for requesting data from OJ Electronics cloud (#13782) * Fixed some Nullable annotation warnings * Use SignalR instead pooling Signed-off-by: Christian Kittel --- .../org.openhab.binding.ojelectronics/NOTICE | 5 + .../README.md | 1 - .../org.openhab.binding.ojelectronics/pom.xml | 17 ++ .../internal/OJCloudHandler.java | 123 ++++++++++----- .../internal/OJCloudHandlerFactory.java | 3 +- .../internal/ThermostatHandler.java | 147 +++++++++++++----- .../internal/ThermostatHandlerFactory.java | 3 +- .../internal/common/SignalRLogger.java | 50 ++++++ .../OJElectronicsBridgeConfiguration.java | 30 +++- .../internal/models/SignalRResultModel.java | 73 +++++++++ .../models/groups/{Day.java => DayModel.java} | 4 +- .../groups/{Event.java => EventModel.java} | 2 +- ...oupContent.java => GroupContentModel.java} | 8 +- .../groups/GroupContentResponseModel.java | 2 +- .../{Schedule.java => ScheduleModel.java} | 4 +- .../ThermostatModel.java} | 10 +- .../thermostat/ThermostatModelBase.java | 26 ++++ .../ThermostatRealTimeValuesModel.java | 38 +++++ .../UpdateThermostatRequestModel.java | 5 +- .../internal/services/OJDiscoveryService.java | 14 +- .../services/RefreshGroupContentService.java | 21 +-- .../internal/services/RefreshService.java | 138 +++++++++++----- .../services/RefreshThermostatsService.java | 99 ++++++++++++ .../internal/services/SignInService.java | 35 +++-- .../internal/services/UpdateService.java | 51 +++--- .../OH-INF/i18n/ojelectronics.properties | 4 +- .../resources/OH-INF/thing/thing-types.xml | 10 +- 27 files changed, 716 insertions(+), 207 deletions(-) create mode 100644 bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/common/SignalRLogger.java create mode 100644 bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/SignalRResultModel.java rename bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/{Day.java => DayModel.java} (89%) rename bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/{Event.java => EventModel.java} (96%) rename bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/{GroupContent.java => GroupContentModel.java} (83%) rename bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/{Schedule.java => ScheduleModel.java} (88%) rename bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/{Thermostat.java => thermostat/ThermostatModel.java} (90%) create mode 100644 bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatModelBase.java create mode 100644 bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatRealTimeValuesModel.java create mode 100644 bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshThermostatsService.java diff --git a/bundles/org.openhab.binding.ojelectronics/NOTICE b/bundles/org.openhab.binding.ojelectronics/NOTICE index 38d625e3492..fdb4ac8cc8b 100644 --- a/bundles/org.openhab.binding.ojelectronics/NOTICE +++ b/bundles/org.openhab.binding.ojelectronics/NOTICE @@ -11,3 +11,8 @@ https://www.eclipse.org/legal/epl-2.0/. == Source Code https://github.com/openhab/openhab-addons + +Signalr4j +* License: Apache 2.0 License +* Project: https://github.com/sputnikdev/bluetooth-gatt-parser +* Source: https://github.com/sputnikdev/bluetooth-gatt-parser diff --git a/bundles/org.openhab.binding.ojelectronics/README.md b/bundles/org.openhab.binding.ojelectronics/README.md index b14b6bb6028..b7c4fd42c94 100644 --- a/bundles/org.openhab.binding.ojelectronics/README.md +++ b/bundles/org.openhab.binding.ojelectronics/README.md @@ -23,7 +23,6 @@ After the ojcloud bridge is successfully initialized all thermostats will be dis | password | password from the OJElectronics App (required) | | apiKey | API key. You get the key from your local distributor. | | apiUrl | URL of the API endpoint. Optional, the default value should always work. | -| refreshDelayInSeconds | Refresh interval in seconds. Optional, the default value is 30 seconds. | | customerId | Customer ID. Optional, the default value should always work. | | softwareVersion | Software version. Optional, the default value should always work. | diff --git a/bundles/org.openhab.binding.ojelectronics/pom.xml b/bundles/org.openhab.binding.ojelectronics/pom.xml index 91278e06e26..347db883432 100644 --- a/bundles/org.openhab.binding.ojelectronics/pom.xml +++ b/bundles/org.openhab.binding.ojelectronics/pom.xml @@ -13,4 +13,21 @@ org.openhab.binding.ojelectronics openHAB Add-ons :: Bundles :: OJElectronics Binding + + + + + com.github.signalr4j + signalr4j + 2.0.4 + compile + + + + org.java-websocket + Java-WebSocket + 1.5.3 + compile + + diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandler.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandler.java index 5d4532a958c..7f7798e7b23 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandler.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandler.java @@ -13,7 +13,7 @@ package org.openhab.binding.ojelectronics.internal; import java.util.Collection; -import java.util.Collections; +import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -21,10 +21,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConfiguration; +import org.openhab.binding.ojelectronics.internal.models.SignalRResultModel; import org.openhab.binding.ojelectronics.internal.models.groups.GroupContentResponseModel; import org.openhab.binding.ojelectronics.internal.services.OJDiscoveryService; import org.openhab.binding.ojelectronics.internal.services.RefreshGroupContentService; import org.openhab.binding.ojelectronics.internal.services.RefreshService; +import org.openhab.binding.ojelectronics.internal.services.RefreshThermostatsService; import org.openhab.binding.ojelectronics.internal.services.SignInService; import org.openhab.binding.ojelectronics.internal.services.UpdateService; import org.openhab.core.thing.Bridge; @@ -32,7 +34,6 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseBridgeHandler; -import org.openhab.core.thing.binding.BridgeHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.slf4j.Logger; @@ -44,7 +45,7 @@ import org.slf4j.LoggerFactory; * @author Christian Kittel - Initial Contribution */ @NonNullByDefault -public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler { +public class OJCloudHandler extends BaseBridgeHandler { private final Logger logger = LoggerFactory.getLogger(OJCloudHandler.class); private final HttpClient httpClient; @@ -54,6 +55,7 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler { private @Nullable SignInService signInService; private OJElectronicsBridgeConfiguration configuration; private @Nullable ScheduledFuture signTask; + private @Nullable ScheduledFuture updateTask; private @Nullable OJDiscoveryService discoveryService; /** @@ -82,9 +84,9 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler { */ @Override public void dispose() { - final RefreshService refreshService = this.refreshService; - if (refreshService != null) { - refreshService.stop(); + final RefreshService localRefreshService = this.refreshService; + if (localRefreshService != null) { + localRefreshService.stop(); } final ScheduledFuture signTask = this.signTask; if (signTask != null) { @@ -99,64 +101,90 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler { public void handleCommand(ChannelUID channelUID, Command command) { } + public synchronized void updateThinksChannelValuesToCloud() { + final UpdateService localUpdateService = this.updateService; + if (localUpdateService != null) { + final ScheduledFuture localUpdateTask = this.updateTask; + if (localUpdateTask != null) { + localUpdateTask.cancel(false); + } + this.updateTask = scheduler.schedule(() -> { + localUpdateService.updateAllThermostats(getThing().getThings()); + this.updateTask = null; + }, 2, TimeUnit.SECONDS); + } + } + private void ensureSignIn() { if (signInService == null) { signInService = new SignInService(configuration, httpClient); } - final SignInService signInService = this.signInService; - if (signInService != null) { - signInService.signIn(this::handleSignInDone, this::handleConnectionLost, + final SignInService localSignInService = this.signInService; + if (localSignInService != null) { + localSignInService.signIn(this::handleSignInDone, this::handleConnectionLost, this::handleUnauthorizedWhileSignIn); } } - private void handleRefreshDone(@Nullable GroupContentResponseModel groupContentResponse, + private void initializationDone(@Nullable GroupContentResponseModel groupContentResponse, @Nullable String errorMessage) { - logger.trace("OJElectronicsCloudHandler.handleRefreshDone({})", groupContentResponse); + logger.trace("OJElectronicsCloudHandler.initializationDone({})", groupContentResponse); if (groupContentResponse != null && groupContentResponse.errorCode == 0) { - internalRefreshDone(groupContentResponse); + internalInitializationDone(groupContentResponse); } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, (errorMessage == null) ? "Wrong or no result model; Refreshing stoppped" : errorMessage); - final RefreshService refreshService = this.refreshService; - if (refreshService != null) { - refreshService.stop(); + final RefreshService localRefreshService = this.refreshService; + if (localRefreshService != null) { + localRefreshService.stop(); } } } - private void internalRefreshDone(GroupContentResponseModel groupContentResponse) { - new RefreshGroupContentService(groupContentResponse.groupContents, getThing().getThings()).handle(); - final OJDiscoveryService discoveryService = this.discoveryService; - if (discoveryService != null) { - discoveryService.setScanResultForDiscovery(groupContentResponse.groupContents); + private void refreshDone(@Nullable SignalRResultModel resultModel, @Nullable String errorMessage) { + logger.trace("OJElectronicsCloudHandler.refreshDone({})", resultModel); + if (resultModel != null) { + new RefreshThermostatsService(resultModel.getThermostats(), resultModel.getThermostatRealTimes(), + getThing().getThings()).handle(); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + (errorMessage == null) ? "Wrong or no result model; Refreshing stoppped" : errorMessage); + final RefreshService localRefreshService = this.refreshService; + if (localRefreshService != null) { + localRefreshService.stop(); + } } - final UpdateService updateService = this.updateService; - if (updateService != null) { - updateService.updateAllThermostats(getThing().getThings()); + } + + private void internalInitializationDone(GroupContentResponseModel groupContentResponse) { + new RefreshGroupContentService(groupContentResponse.groupContents, getThing().getThings()).handle(); + final OJDiscoveryService localDiscoveryService = this.discoveryService; + if (localDiscoveryService != null) { + localDiscoveryService.setScanResultForDiscovery(groupContentResponse.groupContents); } } private void handleSignInDone(String sessionId) { logger.trace("OJElectronicsCloudHandler.handleSignInDone({})", sessionId); if (refreshService == null) { - refreshService = new RefreshService(configuration, httpClient, scheduler); + refreshService = new RefreshService(configuration, httpClient); } - final RefreshService refreshService = this.refreshService; - if (refreshService != null) { - refreshService.start(sessionId, this::handleRefreshDone, this::handleConnectionLost, - this::handleUnauthorized); + final RefreshService localRefreshService = this.refreshService; + if (localRefreshService != null) { + localRefreshService.start(sessionId, this::initializationDone, this::refreshDone, + this::handleConnectionLost, this::handleUnauthorized); updateStatus(ThingStatus.ONLINE); } - this.updateService = new UpdateService(configuration, httpClient, sessionId); + this.updateService = new UpdateService(configuration, httpClient, this::handleConnectionLost, + this::handleUnauthorized); } private void handleUnauthorized() { logger.trace("OJElectronicsCloudHandler.handleUnauthorized()"); - final RefreshService refreshService = this.refreshService; - if (refreshService != null) { - refreshService.stop(); + final RefreshService localRefreshService = this.refreshService; + if (localRefreshService != null) { + localRefreshService.stop(); } restartRefreshServiceAsync(1); } @@ -165,20 +193,29 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler { logger.trace("OJElectronicsCloudHandler.handleUnauthorizedWhileSignIn()"); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Could not sign in. Check user name and password."); - final RefreshService refreshService = this.refreshService; - if (refreshService != null) { - refreshService.stop(); + final RefreshService localRefreshService = this.refreshService; + if (localRefreshService != null) { + localRefreshService.stop(); } } - private void handleConnectionLost() { - logger.trace("OJElectronicsCloudHandler.handleConnectionLost()"); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); - final RefreshService refreshService = this.refreshService; - if (refreshService != null) { - refreshService.stop(); + public void reInitialize() { + logger.trace("OJElectronicsCloudHandler.reInitialize()"); + final RefreshService localRefreshService = this.refreshService; + if (localRefreshService != null) { + localRefreshService.stop(); } - restartRefreshServiceAsync(configuration.refreshDelayInSeconds); + restartRefreshServiceAsync(1); + } + + private void handleConnectionLost(@Nullable String message) { + logger.trace("OJElectronicsCloudHandler.handleConnectionLost()"); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message); + final RefreshService localRefreshService = this.refreshService; + if (localRefreshService != null) { + localRefreshService.stop(); + } + restartRefreshServiceAsync(30); } private void restartRefreshServiceAsync(long delayInSeconds) { @@ -191,6 +228,6 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler { @Override public Collection> getServices() { - return Collections.singleton(OJDiscoveryService.class); + return Set.of(OJDiscoveryService.class); } } diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandlerFactory.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandlerFactory.java index 04b92d0f825..718494f0fcc 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandlerFactory.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandlerFactory.java @@ -14,7 +14,6 @@ package org.openhab.binding.ojelectronics.internal; import static org.openhab.binding.ojelectronics.internal.BindingConstants.THING_TYPE_OJCLOUD; -import java.util.Collections; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -40,7 +39,7 @@ import org.osgi.service.component.annotations.Reference; @Component(configurationPid = "binding.ojelectronics", service = ThingHandlerFactory.class) public class OJCloudHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OJCLOUD); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_OJCLOUD); private final HttpClient httpClient; diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandler.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandler.java index 5bc823e2a13..2bcfec98fb0 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandler.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandler.java @@ -20,6 +20,7 @@ import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import javax.measure.quantity.Temperature; @@ -27,7 +28,8 @@ import javax.measure.quantity.Temperature; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.ojelectronics.internal.config.OJElectronicsThermostatConfiguration; -import org.openhab.binding.ojelectronics.internal.models.Thermostat; +import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatModel; +import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatRealTimeValuesModel; import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; @@ -35,10 +37,12 @@ import org.openhab.core.library.types.OpenClosedType; 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.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.BridgeHandler; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; @@ -58,12 +62,13 @@ public class ThermostatHandler extends BaseThingHandler { private final String serialNumber; private final Logger logger = LoggerFactory.getLogger(ThermostatHandler.class); - private final Map> channelrefreshActions = createChannelRefreshActionMap(); + private final Map> channelRefreshActions = createChannelRefreshActionMap(); + private final Map> channelRealTimeRefreshActions = createRealTimeChannelRefreshActionMap(); private final Map> updateThermostatValueActions = createUpdateThermostatValueActionMap(); private final TimeZoneProvider timeZoneProvider; private LinkedList> updatedValues = new LinkedList<>(); - private @Nullable Thermostat currentThermostat; + private @Nullable ThermostatModel currentThermostat; /** * Creates a new instance of {@link ThermostatHandler} @@ -92,9 +97,9 @@ public class ThermostatHandler extends BaseThingHandler { @Override public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof RefreshType) { - final Thermostat thermostat = currentThermostat; - if (thermostat != null && channelrefreshActions.containsKey(channelUID.getId())) { - final @Nullable Consumer consumer = channelrefreshActions.get(channelUID.getId()); + final ThermostatModel thermostat = currentThermostat; + if (thermostat != null && channelRefreshActions.containsKey(channelUID.getId())) { + final @Nullable Consumer consumer = channelRefreshActions.get(channelUID.getId()); if (consumer != null) { consumer.accept(thermostat); } @@ -102,6 +107,14 @@ public class ThermostatHandler extends BaseThingHandler { } else { synchronized (this) { updatedValues.add(new AbstractMap.SimpleImmutableEntry(channelUID.getId(), command)); + + BridgeHandler bridgeHandler = Objects.requireNonNull(getBridge()).getHandler(); + if (bridgeHandler != null) { + ((OJCloudHandler) (bridgeHandler)).updateThinksChannelValuesToCloud(); + } else { + currentThermostat = null; + updateStatus(ThingStatus.OFFLINE); + } } } } @@ -111,7 +124,15 @@ public class ThermostatHandler extends BaseThingHandler { */ @Override public void initialize() { - updateStatus(ThingStatus.ONLINE); + @Nullable + Bridge bridge = getBridge(); + if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE && currentThermostat == null) { + @Nullable + OJCloudHandler bridgeHandler = (OJCloudHandler) (bridge.getHandler()); + if (bridgeHandler != null) { + bridgeHandler.reInitialize(); + } + } } /** @@ -119,17 +140,37 @@ public class ThermostatHandler extends BaseThingHandler { * * @param thermostat thermostat values */ - public void handleThermostatRefresh(Thermostat thermostat) { + public void handleThermostatRefresh(ThermostatModel thermostat) { + if (currentThermostat == null) { + updateStatus(ThingStatus.ONLINE); + } currentThermostat = thermostat; - channelrefreshActions.forEach((channelUID, action) -> action.accept(thermostat)); + channelRefreshActions.forEach((channelUID, action) -> action.accept(thermostat)); } /** - * Gets a {@link Thermostat} with changed values or null if nothing has changed + * Sets the values after refreshing the thermostats values * - * @return The changed {@link Thermostat} + * @param thermostat thermostat values */ - public @Nullable Thermostat tryHandleAndGetUpdatedThermostat() { + public void handleThermostatRefresh(ThermostatRealTimeValuesModel thermostat) { + final ThermostatModel currentThermostat = this.currentThermostat; + if (currentThermostat != null) { + currentThermostat.heating = thermostat.heating; + currentThermostat.floorTemperature = thermostat.floorTemperature; + currentThermostat.action = thermostat.action; + currentThermostat.online = thermostat.online; + currentThermostat.roomTemperature = thermostat.roomTemperature; + channelRealTimeRefreshActions.forEach((channelUID, action) -> action.accept(thermostat)); + } + } + + /** + * Gets a {@link ThermostatModel} with changed values or null if nothing has changed + * + * @return The changed {@link ThermostatModel} + */ + public @Nullable ThermostatModel tryHandleAndGetUpdatedThermostat() { final LinkedList> updatedValues = this.updatedValues; if (updatedValues.isEmpty()) { return null; @@ -146,59 +187,64 @@ public class ThermostatHandler extends BaseThingHandler { return currentThermostat; } - private void updateManualSetpoint(Thermostat thermostat) { + private ThermostatModel getCurrentThermostat() { + return Objects.requireNonNull(currentThermostat); + } + + private void updateManualSetpoint(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, new QuantityType(thermostat.manualModeSetpoint / (double) 100, SIUnits.CELSIUS)); } private void updateManualSetpoint(Command command) { if (command instanceof QuantityType) { - currentThermostat.manualModeSetpoint = (int) (((QuantityType) command).floatValue() * 100); + getCurrentThermostat().manualModeSetpoint = (int) (((QuantityType) command).floatValue() * 100); } else { logger.warn("Unable to set value {}", command); } } - private void updateBoostEndTime(Thermostat thermostat) { + private void updateBoostEndTime(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, new DateTimeType( ZonedDateTime.ofInstant(thermostat.boostEndTime.toInstant(), timeZoneProvider.getTimeZone()))); } private void updateBoostEndTime(Command command) { if (command instanceof DateTimeType) { - currentThermostat.boostEndTime = Date.from(((DateTimeType) command).getZonedDateTime().toInstant()); + getCurrentThermostat().boostEndTime = Date.from(((DateTimeType) command).getZonedDateTime().toInstant()); } else { logger.warn("Unable to set value {}", command); } } - private void updateComfortEndTime(Thermostat thermostat) { + private void updateComfortEndTime(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, new DateTimeType( ZonedDateTime.ofInstant(thermostat.comfortEndTime.toInstant(), timeZoneProvider.getTimeZone()))); } private void updateComfortEndTime(Command command) { if (command instanceof DateTimeType) { - currentThermostat.comfortEndTime = Date.from(((DateTimeType) command).getZonedDateTime().toInstant()); + getCurrentThermostat().comfortEndTime = Objects + .requireNonNull(Date.from(((DateTimeType) command).getZonedDateTime().toInstant())); } else { logger.warn("Unable to set value {}", command); } } - private void updateComfortSetpoint(Thermostat thermostat) { + private void updateComfortSetpoint(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT, new QuantityType(thermostat.comfortSetpoint / (double) 100, SIUnits.CELSIUS)); } private void updateComfortSetpoint(Command command) { if (command instanceof QuantityType) { - currentThermostat.comfortSetpoint = (int) (((QuantityType) command).floatValue() * 100); + getCurrentThermostat().comfortSetpoint = (int) (((QuantityType) command).floatValue() * 100); } else { logger.warn("Unable to set value {}", command); } } - private void updateRegulationMode(Thermostat thermostat) { + private void updateRegulationMode(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, StringType.valueOf(getRegulationMode(thermostat.regulationMode))); } @@ -207,51 +253,71 @@ public class ThermostatHandler extends BaseThingHandler { if (command instanceof StringType && (REVERSE_REGULATION_MODES.containsKey(command.toString().toLowerCase()))) { final @Nullable Integer mode = REVERSE_REGULATION_MODES.get(command.toString().toLowerCase()); if (mode != null) { - currentThermostat.regulationMode = mode; + getCurrentThermostat().regulationMode = mode; } } else { logger.warn("Unable to set value {}", command); } } - private void updateThermostatName(Thermostat thermostat) { + private void updateThermostatName(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_THERMOSTATNAME, StringType.valueOf(thermostat.thermostatName)); } - private void updateFloorTemperature(Thermostat thermostat) { + private void updateFloorTemperature(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, new QuantityType(thermostat.floorTemperature / (double) 100, SIUnits.CELSIUS)); } - private void updateRoomTemperature(Thermostat thermostat) { + private void updateFloorTemperature(ThermostatRealTimeValuesModel thermostatRealTimeValues) { + updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, new QuantityType( + thermostatRealTimeValues.floorTemperature / (double) 100, SIUnits.CELSIUS)); + } + + private void updateRoomTemperature(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, new QuantityType(thermostat.roomTemperature / (double) 100, SIUnits.CELSIUS)); } - private void updateHeating(Thermostat thermostat) { + private void updateRoomTemperature(ThermostatRealTimeValuesModel thermostatRealTimeValues) { + updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, new QuantityType( + thermostatRealTimeValues.roomTemperature / (double) 100, SIUnits.CELSIUS)); + } + + private void updateHeating(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_HEATING, thermostat.heating ? OpenClosedType.OPEN : OpenClosedType.CLOSED); } - private void updateOnline(Thermostat thermostat) { + private void updateHeating(ThermostatRealTimeValuesModel thermostatRealTimeValues) { + updateState(BindingConstants.CHANNEL_OWD5_HEATING, + thermostatRealTimeValues.heating ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + } + + private void updateOnline(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_ONLINE, thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED); } - private void updateGroupId(Thermostat thermostat) { + private void updateOnline(ThermostatRealTimeValuesModel thermostatRealTimeValues) { + updateState(BindingConstants.CHANNEL_OWD5_ONLINE, + thermostatRealTimeValues.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + } + + private void updateGroupId(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_GROUPID, new DecimalType(thermostat.groupId)); } - private void updateGroupName(Thermostat thermostat) { + private void updateGroupName(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_GROUPNAME, StringType.valueOf(thermostat.groupName)); } - private void updateVacationEnabled(Thermostat thermostat) { + private void updateVacationEnabled(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_VACATIONENABLED, thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED); } - private void updateVacationBeginDay(Thermostat thermostat) { + private void updateVacationBeginDay(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY, new DateTimeType( ZonedDateTime.ofInstant(thermostat.vacationBeginDay.toInstant(), timeZoneProvider.getTimeZone()) @@ -260,14 +326,14 @@ public class ThermostatHandler extends BaseThingHandler { private void updateVacationBeginDay(Command command) { if (command instanceof DateTimeType) { - currentThermostat.vacationBeginDay = Date + getCurrentThermostat().vacationBeginDay = Date .from(((DateTimeType) command).getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS)); } else { logger.warn("Unable to set value {}", command); } } - private void updateVacationEndDay(Thermostat thermostat) { + private void updateVacationEndDay(ThermostatModel thermostat) { updateState(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY, new DateTimeType( ZonedDateTime.ofInstant(thermostat.vacationEndDay.toInstant(), timeZoneProvider.getTimeZone()) @@ -276,7 +342,7 @@ public class ThermostatHandler extends BaseThingHandler { private void updateVacationEndDay(Command command) { if (command instanceof DateTimeType) { - currentThermostat.vacationEndDay = Date + getCurrentThermostat().vacationEndDay = Date .from(((DateTimeType) command).getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS)); } else { logger.warn("Unable to set value {}", command); @@ -311,8 +377,8 @@ public class ThermostatHandler extends BaseThingHandler { return map; }; - private Map> createChannelRefreshActionMap() { - HashMap> map = new HashMap<>(); + private Map> createChannelRefreshActionMap() { + HashMap> map = new HashMap<>(); map.put(BindingConstants.CHANNEL_OWD5_GROUPNAME, this::updateGroupName); map.put(BindingConstants.CHANNEL_OWD5_GROUPID, this::updateGroupId); map.put(BindingConstants.CHANNEL_OWD5_ONLINE, this::updateOnline); @@ -331,6 +397,15 @@ public class ThermostatHandler extends BaseThingHandler { return map; } + private Map> createRealTimeChannelRefreshActionMap() { + HashMap> map = new HashMap<>(); + map.put(BindingConstants.CHANNEL_OWD5_ONLINE, this::updateOnline); + map.put(BindingConstants.CHANNEL_OWD5_HEATING, this::updateHeating); + map.put(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, this::updateRoomTemperature); + map.put(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, this::updateFloorTemperature); + return map; + } + private Map> createUpdateThermostatValueActionMap() { HashMap> map = new HashMap<>(); map.put(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, this::updateRegulationMode); diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandlerFactory.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandlerFactory.java index 62dc380516c..6e7ea95dedf 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandlerFactory.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandlerFactory.java @@ -14,7 +14,6 @@ package org.openhab.binding.ojelectronics.internal; import static org.openhab.binding.ojelectronics.internal.BindingConstants.THING_TYPE_OWD5; -import java.util.Collections; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -38,7 +37,7 @@ import org.osgi.service.component.annotations.Reference; @Component(configurationPid = "binding.ojelectronics", service = ThingHandlerFactory.class) public class ThermostatHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OWD5); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_OWD5); private final TimeZoneProvider timeZoneProvider; /** diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/common/SignalRLogger.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/common/SignalRLogger.java new file mode 100644 index 00000000000..af85dcd2056 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/common/SignalRLogger.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2023 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.ojelectronics.internal.common; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.LoggerFactory; + +import com.github.signalr4j.client.LogLevel; +import com.github.signalr4j.client.Logger; + +/** + * Logs SignalR information + * + * @author Christian Kittel - Initial Contribution + */ +@NonNullByDefault +public class SignalRLogger implements Logger { + + private final org.slf4j.Logger logger = LoggerFactory.getLogger(SignalRLogger.class); + + @Override + public void log(@Nullable String message, @Nullable LogLevel level) { + if (message == null || level == null) { + return; + } + switch (level) { + case Critical: + logger.warn("Critical SignalR Message: {}", message); + break; + case Information: + logger.info("SignalR information message: {}", message); + break; + case Verbose: + default: + logger.trace("SignalR information message: {}", message); + break; + } + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsBridgeConfiguration.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsBridgeConfiguration.java index a341e63d7a6..9e67b076d31 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsBridgeConfiguration.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsBridgeConfiguration.java @@ -13,6 +13,7 @@ package org.openhab.binding.ojelectronics.internal.config; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * The configuration for {@link org.openhab.binding.ojelectronics.internal.OJElectronicsCloudHandler} @@ -40,7 +41,7 @@ public class OJElectronicsBridgeConfiguration { /** * Url for API */ - public String apiUrl = "https://OWD5-OJ001-App.ojelectronics.com/api"; + private String apiUrl = "https://OWD5-OJ001-App.ojelectronics.com"; /** * API-Key @@ -52,8 +53,29 @@ public class OJElectronicsBridgeConfiguration { */ public int softwareVersion = 1060; - /** - * Refresh-Delay + private @Nullable String restApiUrl; + + /* + * Gets the Api-URL */ - public long refreshDelayInSeconds = 30; + public String getRestApiUrl() { + String localRestApiUrl = restApiUrl; + if (localRestApiUrl == null) { + localRestApiUrl = restApiUrl = apiUrl.replace("/api", "") + "/api"; + } + return localRestApiUrl; + } + + private @Nullable String signalRApiUrl; + + /* + * Gets the SignalR Notification URL + */ + public String getSignalRUrl() { + String localSignalRApiUrl = signalRApiUrl; + if (localSignalRApiUrl == null) { + localSignalRApiUrl = signalRApiUrl = apiUrl.replace("/api", "") + "/ocd5notification"; + } + return localSignalRApiUrl; + } } diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/SignalRResultModel.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/SignalRResultModel.java new file mode 100644 index 00000000000..8b8b62220d6 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/SignalRResultModel.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2023 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.ojelectronics.internal.models; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.ojelectronics.internal.models.groups.GroupContentModel; +import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatModel; +import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatRealTimeValuesModel; + +import com.google.gson.annotations.SerializedName; + +/** + * Model for a SignalR query result + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class SignalRResultModel { + @SerializedName("Groups") + private List groups = List.of(); + @SerializedName("SequenceNr") + private int sequenceNr; + + @SerializedName("ThermostatRealTimes") + private List thermostatRealTimes = List.of(); + + @SerializedName("Thermostats") + private List thermostats = List.of(); + + public List getGroups() { + return this.groups; + } + + public int getSequenceNr() { + return this.sequenceNr; + } + + public List getThermostatRealTimes() { + return this.thermostatRealTimes; + } + + public List getThermostats() { + return this.thermostats; + } + + public void setGroups(List paramArrayList) { + this.groups = paramArrayList; + } + + public void setSequenceNr(int paramInt) { + this.sequenceNr = paramInt; + } + + public void setThermostatRealTimes(List paramArrayList) { + this.thermostatRealTimes = paramArrayList; + } + + public void setThermostats(List paramArrayList) { + this.thermostats = paramArrayList; + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Day.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/DayModel.java similarity index 89% rename from bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Day.java rename to bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/DayModel.java index 6b088a1cade..fca68d44d08 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Day.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/DayModel.java @@ -23,9 +23,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; * @author Christian Kittel - Initial contribution */ @NonNullByDefault -public class Day { +public class DayModel { public int weekDayGrpNo; - public List events = new ArrayList<>(); + public List events = new ArrayList<>(); } diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Event.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/EventModel.java similarity index 96% rename from bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Event.java rename to bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/EventModel.java index e91d00461fe..49cbd4c02bb 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Event.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/EventModel.java @@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; * @author Christian Kittel - Initial contribution */ @NonNullByDefault -public class Event { +public class EventModel { public int scheduleType; diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContent.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentModel.java similarity index 83% rename from bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContent.java rename to bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentModel.java index 9ef2bbcde3f..6d8576302c8 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContent.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentModel.java @@ -17,7 +17,7 @@ import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.ojelectronics.internal.models.Thermostat; +import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatModel; /** * Model for content of a group @@ -25,7 +25,7 @@ import org.openhab.binding.ojelectronics.internal.models.Thermostat; * @author Christian Kittel - Initial contribution */ @NonNullByDefault -public class GroupContent { +public class GroupContentModel { public int action; @@ -33,11 +33,11 @@ public class GroupContent { public String groupName = ""; - public List thermostats = new ArrayList(); + public List thermostats = new ArrayList(); public int regulationMode; - public @Nullable Schedule schedule; + public @Nullable ScheduleModel schedule; public int comfortSetpoint; diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentResponseModel.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentResponseModel.java index b080ca0642c..c54cdeaa90c 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentResponseModel.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentResponseModel.java @@ -26,5 +26,5 @@ import org.openhab.binding.ojelectronics.internal.models.ResponseModelBase; @NonNullByDefault public class GroupContentResponseModel extends ResponseModelBase { - public List groupContents = new ArrayList(); + public List groupContents = new ArrayList(); } diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Schedule.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/ScheduleModel.java similarity index 88% rename from bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Schedule.java rename to bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/ScheduleModel.java index 3ce6028544c..22c63a058a2 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Schedule.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/ScheduleModel.java @@ -23,9 +23,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; * @author Christian Kittel - Initial contribution */ @NonNullByDefault -public class Schedule { +public class ScheduleModel { - public List days = new ArrayList(); + public List days = new ArrayList(); public boolean modifiedDueToVerification; } diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/Thermostat.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatModel.java similarity index 90% rename from bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/Thermostat.java rename to bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatModel.java index 774d5b8d563..b0990bfbb40 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/Thermostat.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatModel.java @@ -10,13 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.ojelectronics.internal.models; +package org.openhab.binding.ojelectronics.internal.models.thermostat; import java.util.Date; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.ojelectronics.internal.models.groups.Schedule; +import org.openhab.binding.ojelectronics.internal.models.groups.ScheduleModel; import com.google.gson.annotations.SerializedName; @@ -26,14 +26,12 @@ import com.google.gson.annotations.SerializedName; * @author Christian Kittel - Initial contribution */ @NonNullByDefault -public class Thermostat { +public class ThermostatModel extends ThermostatModelBase { public int id; public int action; - public String serialNumber = ""; - public String groupName = ""; public int groupId; @@ -53,7 +51,7 @@ public class Thermostat { public int regulationMode; - public @Nullable Schedule schedule; + public @Nullable ScheduleModel schedule; public int comfortSetpoint; diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatModelBase.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatModelBase.java new file mode 100644 index 00000000000..ed874e2a2f1 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatModelBase.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2023 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.ojelectronics.internal.models.thermostat; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Base-Model for thermostat models + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class ThermostatModelBase { + + public String serialNumber = ""; +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatRealTimeValuesModel.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatRealTimeValuesModel.java new file mode 100644 index 00000000000..d41b3bcfc5e --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/ThermostatRealTimeValuesModel.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2023 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.ojelectronics.internal.models.thermostat; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Model for realtime values of a thermostat + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class ThermostatRealTimeValuesModel extends ThermostatModelBase { + + public int action; + + public int floorTemperature; + + public boolean heating; + + public int id; + + public boolean online; + + public int roomTemperature; + + public int sensorAppl; +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/UpdateThermostatRequestModel.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/UpdateThermostatRequestModel.java index c3ccdba3f89..d3b6ec796d8 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/UpdateThermostatRequestModel.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/UpdateThermostatRequestModel.java @@ -14,7 +14,6 @@ package org.openhab.binding.ojelectronics.internal.models.thermostat; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.ojelectronics.internal.models.RequestModelBase; -import org.openhab.binding.ojelectronics.internal.models.Thermostat; /** * Model for updating a thermostat @@ -24,12 +23,12 @@ import org.openhab.binding.ojelectronics.internal.models.Thermostat; @NonNullByDefault public class UpdateThermostatRequestModel extends RequestModelBase { - public UpdateThermostatRequestModel(Thermostat thermostat) { + public UpdateThermostatRequestModel(ThermostatModel thermostat) { setThermostat = thermostat; thermostatID = thermostat.serialNumber; } - public Thermostat setThermostat; + public ThermostatModel setThermostat; public String thermostatID; } diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/OJDiscoveryService.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/OJDiscoveryService.java index 7409752eb8e..d37334601a4 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/OJDiscoveryService.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/OJDiscoveryService.java @@ -15,14 +15,13 @@ package org.openhab.binding.ojelectronics.internal.services; import static org.openhab.binding.ojelectronics.internal.BindingConstants.*; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.ojelectronics.internal.OJCloudHandler; -import org.openhab.binding.ojelectronics.internal.models.groups.GroupContent; +import org.openhab.binding.ojelectronics.internal.models.groups.GroupContentModel; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; @@ -39,12 +38,11 @@ import org.osgi.service.component.annotations.Component; */ @NonNullByDefault @Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.ojelectronics") -public final class OJDiscoveryService extends AbstractDiscoveryService - implements DiscoveryService, ThingHandlerService { +public final class OJDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { - private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OJCLOUD); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_OJCLOUD); private @Nullable OJCloudHandler bridgeHandler; - private @Nullable Collection groupContents; + private @Nullable Collection groupContents; /** * Creates a new instance of {@link OJDiscoveryService} @@ -59,14 +57,14 @@ public final class OJDiscoveryService extends AbstractDiscoveryService * * @param groupContents Content from API */ - public void setScanResultForDiscovery(List groupContents) { + public void setScanResultForDiscovery(List groupContents) { this.groupContents = groupContents; } @Override protected void startScan() { final OJCloudHandler bridgeHandler = this.bridgeHandler; - final Collection groupContents = this.groupContents; + final Collection groupContents = this.groupContents; if (groupContents != null && bridgeHandler != null) { groupContents.stream().flatMap(content -> content.thermostats.stream()) .forEach(thermostat -> thingDiscovered(bridgeHandler.getThing().getUID(), thermostat.serialNumber)); diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshGroupContentService.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshGroupContentService.java index 8cdaf1ea4d4..bba63f40473 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshGroupContentService.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshGroupContentService.java @@ -12,12 +12,13 @@ */ package org.openhab.binding.ojelectronics.internal.services; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.ojelectronics.internal.ThermostatHandler; -import org.openhab.binding.ojelectronics.internal.models.Thermostat; -import org.openhab.binding.ojelectronics.internal.models.groups.GroupContent; +import org.openhab.binding.ojelectronics.internal.models.groups.GroupContentModel; import org.openhab.core.thing.Thing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,17 +31,17 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class RefreshGroupContentService { - private final List groupContentList; + private final List groupContentList; private final Logger logger = LoggerFactory.getLogger(RefreshGroupContentService.class); private List things; /** * Creates a new instance of {@link RefreshGroupContentService} * - * @param groupContents {@link GroupContent} + * @param groupContents {@link GroupContentModel} * @param things Things */ - public RefreshGroupContentService(List groupContents, List things) { + public RefreshGroupContentService(List groupContents, List things) { this.groupContentList = groupContents; this.things = things; if (this.things.isEmpty()) { @@ -52,13 +53,7 @@ public class RefreshGroupContentService { * Handles the changes to all things. */ public void handle() { - groupContentList.stream().flatMap(entry -> entry.thermostats.stream()).forEach(this::handleThermostat); - } - - private void handleThermostat(Thermostat thermostat) { - things.stream().filter(thing -> thing.getHandler() instanceof ThermostatHandler) - .map(thing -> (ThermostatHandler) thing.getHandler()) - .filter(thingHandler -> thingHandler.getSerialNumber().equals(thermostat.serialNumber)) - .forEach(thingHandler -> thingHandler.handleThermostatRefresh(thermostat)); + new RefreshThermostatsService(groupContentList.stream().flatMap(entry -> entry.thermostats.stream()) + .collect(Collectors.toCollection(ArrayList::new)), things).handle(); } } diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshService.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshService.java index 446931de725..39930766081 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshService.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshService.java @@ -12,10 +12,9 @@ */ package org.openhab.binding.ojelectronics.internal.services; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.Objects; import java.util.function.BiConsumer; +import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -26,11 +25,16 @@ import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.openhab.binding.ojelectronics.internal.common.OJGSonBuilder; +import org.openhab.binding.ojelectronics.internal.common.SignalRLogger; import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConfiguration; +import org.openhab.binding.ojelectronics.internal.models.SignalRResultModel; import org.openhab.binding.ojelectronics.internal.models.groups.GroupContentResponseModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.github.signalr4j.client.Connection; +import com.github.signalr4j.client.ConnectionState; +import com.github.signalr4j.client.Platform; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; @@ -47,14 +51,14 @@ public final class RefreshService implements AutoCloseable { private final HttpClient httpClient; private final Gson gson = OJGSonBuilder.getGSon(); - private final ScheduledExecutorService schedulerService; - - private @Nullable Runnable connectionLost; - private @Nullable BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone; - private @Nullable ScheduledFuture scheduler; + private @Nullable Consumer<@Nullable String> connectionLost; + private @Nullable BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> initializationDone; + private @Nullable BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone; private @Nullable Runnable unauthorized; private @Nullable String sessionId; - private static boolean destroyed = false; + private @Nullable Connection signalRConnection; + private boolean destroyed = false; + private boolean isInitializing = false; /** * Creates a new instance of {@link RefreshService} @@ -63,11 +67,10 @@ public final class RefreshService implements AutoCloseable { * @param httpClient HTTP client * @param updateService Service to update the thermostat in the cloud */ - public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient, - ScheduledExecutorService schedulerService) { + public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient) { this.config = config; this.httpClient = httpClient; - this.schedulerService = schedulerService; + Platform.loadPlatformComponent(null); } /** @@ -78,17 +81,21 @@ public final class RefreshService implements AutoCloseable { * @param connectionLosed This method is called if no connection could established. * @param unauthorized This method is called if the result is unauthorized. */ - public void start(String sessionId, BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone, - Runnable connectionLost, Runnable unauthorized) { + public void start(String sessionId, + BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> initializationDone, + BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone, + Consumer<@Nullable String> connectionLost, Runnable unauthorized) { logger.trace("RefreshService.startService({})", sessionId); this.connectionLost = connectionLost; + this.initializationDone = initializationDone; this.refreshDone = refreshDone; this.unauthorized = unauthorized; this.sessionId = sessionId; - long refreshTime = config.refreshDelayInSeconds; - scheduler = schedulerService.scheduleWithFixedDelay(this::refresh, refreshTime, refreshTime, TimeUnit.SECONDS); - refresh(); + + signalRConnection = createSignalRConnection(); destroyed = false; + isInitializing = false; + initializeGroups(true); } /** @@ -96,25 +103,70 @@ public final class RefreshService implements AutoCloseable { */ public void stop() { destroyed = true; - final ScheduledFuture scheduler = this.scheduler; - if (scheduler != null) { - scheduler.cancel(false); + final Connection localSignalRConnection = signalRConnection; + if (localSignalRConnection != null) { + localSignalRConnection.stop(); + signalRConnection = null; } - this.scheduler = null; } - private void refresh() { + private Connection createSignalRConnection() { + Connection signalRConnection = new Connection(config.getSignalRUrl(), new SignalRLogger()); + signalRConnection.setReconnectOnError(false); + signalRConnection.received(json -> { + if (json != null && json.isJsonObject()) { + BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone = this.refreshDone; + if (refreshDone != null) { + logger.trace("refresh {}", json); + try { + SignalRResultModel content = Objects + .requireNonNull(gson.fromJson(json, SignalRResultModel.class)); + refreshDone.accept(content, null); + } catch (JsonSyntaxException exception) { + logger.debug("Error mapping Result to model", exception); + refreshDone.accept(null, exception.getMessage()); + } + } + } + }); + signalRConnection.stateChanged((oldState, newState) -> { + logger.trace("Connection state changed from {} to {}", oldState, newState); + if (newState == ConnectionState.Disconnected && !destroyed) { + handleConnectionLost("Connection broken"); + } + }); + signalRConnection.reconnected(() -> { + initializeGroups(false); + }); + signalRConnection.connected(() -> { + signalRConnection.send(sessionId); + }); + signalRConnection.error(error -> logger.info("SignalR error {}", error.getLocalizedMessage())); + return signalRConnection; + } + + private void initializeGroups(boolean shouldStartSignalRService) { + if (destroyed || isInitializing) { + return; + } final String sessionId = this.sessionId; if (sessionId == null) { - handleConnectionLost(); + handleConnectionLost("No session id"); } + isInitializing = true; + logger.trace("initializeGroups started"); final Runnable unauthorized = this.unauthorized; createRequest().send(new BufferingResponseListener() { @Override public void onComplete(@Nullable Result result) { - if (!destroyed) { - if (result == null || result.isFailed()) { - handleConnectionLost(); + try { + if (destroyed || result == null) { + return; + } + if (result.isFailed()) { + final Throwable failure = result.getFailure(); + logger.error("Error initializing groups", failure); + handleConnectionLost(failure.getLocalizedMessage()); } else { int status = result.getResponse().getStatus(); logger.trace("HTTP-Status {}", status); @@ -122,32 +174,40 @@ public final class RefreshService implements AutoCloseable { if (unauthorized != null) { unauthorized.run(); } else { - handleConnectionLost(); + handleConnectionLost(null); } } else if (status == HttpStatus.OK_200) { - handleRefreshDone(getContentAsString()); + initializationDone(Objects.requireNonNull(getContentAsString())); + final Connection localSignalRConnection = signalRConnection; + if (shouldStartSignalRService && localSignalRConnection != null) { + localSignalRConnection.start(); + } } else { logger.warn("unsupported HTTP-Status {}", status); - handleConnectionLost(); + handleConnectionLost(null); } } + } finally { + logger.trace("initializeGroups completed"); + isInitializing = false; } } }); } private Request createRequest() { - Request request = httpClient.newRequest(config.apiUrl + "/Group/GroupContents").param("sessionid", sessionId) - .param("apiKey", config.apiKey).method(HttpMethod.GET); + Request request = httpClient.newRequest(config.getRestApiUrl() + "/Group/GroupContents") + .param("sessionid", sessionId).param("apiKey", config.apiKey).method(HttpMethod.GET); return request; } - private void handleRefreshDone(String responseBody) { - BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone = this.refreshDone; + private void initializationDone(String responseBody) { + BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone = this.initializationDone; if (refreshDone != null) { - logger.trace("refresh {}", responseBody); + logger.trace("initializationDone {}", responseBody); try { - GroupContentResponseModel content = gson.fromJson(responseBody, GroupContentResponseModel.class); + GroupContentResponseModel content = Objects + .requireNonNull(gson.fromJson(responseBody, GroupContentResponseModel.class)); refreshDone.accept(content, null); } catch (JsonSyntaxException exception) { logger.debug("Error mapping Result to model", exception); @@ -156,15 +216,15 @@ public final class RefreshService implements AutoCloseable { } } - private void handleConnectionLost() { - final Runnable connectionLost = this.connectionLost; + private void handleConnectionLost(@Nullable String message) { + final Consumer<@Nullable String> connectionLost = this.connectionLost; if (connectionLost != null) { - connectionLost.run(); + connectionLost.accept(message); } } @Override - public void close() throws Exception { + public void close() { stop(); } } diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshThermostatsService.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshThermostatsService.java new file mode 100644 index 00000000000..8b3dfa1f0bb --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshThermostatsService.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2023 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.ojelectronics.internal.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.ojelectronics.internal.ThermostatHandler; +import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatModel; +import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatModelBase; +import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatRealTimeValuesModel; +import org.openhab.core.thing.Thing; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Refreshes values of {@link ThermostatHandler} + * + * @author Christian Kittel - Initial Contribution + */ +@NonNullByDefault +public class RefreshThermostatsService { + + private final List thermostats; + private final Logger logger = LoggerFactory.getLogger(RefreshThermostatsService.class); + private final List things; + private final List realTimeValues; + + /** + * Creates a new instance of {@link RefreshThermostatsService} + * + * @param thermostats {@link ThermostatModel} + * @param things Things + */ + public RefreshThermostatsService(List thermostats, List things) { + this(thermostats, new ArrayList<>(), things); + } + + /** + * Creates a new instance of {@link RefreshThermostatsService} + * + * @param thermostats {@link ThermostatModel} + * @param realTimeValues {@link ThermostatRealTimeValuesModel} + * @param things Things + */ + public RefreshThermostatsService(List thermostats, + List realTimeValues, List things) { + this.thermostats = thermostats; + this.things = things; + this.realTimeValues = realTimeValues; + if (this.things.isEmpty()) { + logger.warn("Bridge contains no thermostats."); + } + } + + /** + * Handles the changes to all things. + */ + public synchronized void handle() { + thermostats.forEach(thermostat -> handleThermostat(thermostat, this::handleThermostatRefresh)); + realTimeValues.forEach(thermostat -> handleThermostat(thermostat, this::handleThermostatRealTimeValueRefresh)); + } + + private void handleThermostat(T thermostat, + BiConsumer refreshHandler) { + things.stream().filter(thing -> thing.getHandler() instanceof ThermostatHandler) + .map(thing -> (ThermostatHandler) thing.getHandler()) + .filter(thingHandler -> thingHandler.getSerialNumber().equals(thermostat.serialNumber)) + .forEach(thingHandler -> { + try { + refreshHandler.accept(Objects.requireNonNull(thingHandler), thermostat); + } catch (Exception e) { + logger.info("Error Handling Refresh of thermostat {}", thermostat, e); + } + }); + } + + private void handleThermostatRefresh(ThermostatHandler thingHandler, ThermostatModel thermostat) { + thingHandler.handleThermostatRefresh(thermostat); + } + + private void handleThermostatRealTimeValueRefresh(ThermostatHandler thingHandler, + ThermostatRealTimeValuesModel thermostat) { + thingHandler.handleThermostatRefresh(thermostat); + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/SignInService.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/SignInService.java index dbc95dd3e61..1a364c1d704 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/SignInService.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/SignInService.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.ojelectronics.internal.services; +import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -27,6 +29,8 @@ import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConf import org.openhab.binding.ojelectronics.internal.models.RequestModelBase; import org.openhab.binding.ojelectronics.internal.models.userprofile.PostSignInQueryModel; import org.openhab.binding.ojelectronics.internal.models.userprofile.PostSignInResponseModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.gson.Gson; @@ -39,7 +43,7 @@ import com.google.gson.Gson; public class SignInService { private final Gson gson = OJGSonBuilder.getGSon(); - + private final Logger logger = LoggerFactory.getLogger(SignInService.class); private final HttpClient httpClient; private final OJElectronicsBridgeConfiguration config; @@ -61,30 +65,41 @@ public class SignInService { * @param connectionLosed This method is called if no connection could established. * @param unauthorized This method is called if the result is unauthorized. */ - public void signIn(Consumer signInDone, Runnable connectionLosed, Runnable unauthorized) { - Request request = httpClient.POST(config.apiUrl + "/UserProfile/SignIn") + public void signIn(Consumer signInDone, Consumer<@Nullable String> connectionLosed, Runnable unauthorized) { + logger.trace("Trying to sign in"); + + Request request = httpClient.POST(config.getRestApiUrl() + "/UserProfile/SignIn") .header(HttpHeader.CONTENT_TYPE, "application/json") - .content(new StringContentProvider(gson.toJson(getPostSignInQueryModel()))); + .content(new StringContentProvider(gson.toJson(getPostSignInQueryModel()))) + .timeout(1, TimeUnit.MINUTES); request.send(new BufferingResponseListener() { @Override public void onComplete(@Nullable Result result) { - if (result == null || result.isFailed()) { - connectionLosed.run(); + if (result == null) { return; } + + if (result.isFailed()) { + final Throwable failure = result.getFailure(); + logger.error("Signing in failed", failure); + connectionLosed.accept(failure.getLocalizedMessage()); + return; + } + if (result.getResponse().getStatus() == 200) { - PostSignInResponseModel signInModel = gson.fromJson(getContentAsString(), - PostSignInResponseModel.class); - if (signInModel == null || signInModel.errorCode != 0 || signInModel.sessionId.equals("")) { + PostSignInResponseModel signInModel = Objects + .requireNonNull(gson.fromJson(getContentAsString(), PostSignInResponseModel.class)); + if (signInModel.errorCode != 0 || signInModel.sessionId.equals("")) { unauthorized.run(); return; } + logger.trace("Signing in successful {}", getContentAsString()); signInDone.accept(signInModel.sessionId); return; } - connectionLosed.run(); + connectionLosed.accept(null); return; } }); diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/UpdateService.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/UpdateService.java index 7beb926a0f2..22ac84d6c01 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/UpdateService.java +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/UpdateService.java @@ -13,6 +13,8 @@ package org.openhab.binding.ojelectronics.internal.services; import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -26,7 +28,7 @@ import org.openhab.binding.ojelectronics.internal.ThermostatHandler; import org.openhab.binding.ojelectronics.internal.common.OJGSonBuilder; import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConfiguration; import org.openhab.binding.ojelectronics.internal.models.SimpleResponseModel; -import org.openhab.binding.ojelectronics.internal.models.Thermostat; +import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatModel; import org.openhab.binding.ojelectronics.internal.models.thermostat.UpdateThermostatRequestModel; import org.openhab.core.thing.Thing; import org.slf4j.Logger; @@ -45,14 +47,17 @@ public final class UpdateService { private final Gson gson = OJGSonBuilder.getGSon(); private final Logger logger = LoggerFactory.getLogger(UpdateService.class); - private final String sessionId; private final HttpClient httpClient; private final OJElectronicsBridgeConfiguration configuration; + private final Runnable unauthorized; + private final Consumer<@Nullable String> connectionLost; - public UpdateService(OJElectronicsBridgeConfiguration configuration, HttpClient httpClient, String sessionId) { + public UpdateService(OJElectronicsBridgeConfiguration configuration, HttpClient httpClient, + Consumer<@Nullable String> connectionLost, Runnable unauthorized) { this.configuration = configuration; this.httpClient = httpClient; - this.sessionId = sessionId; + this.unauthorized = unauthorized; + this.connectionLost = connectionLost; } /** @@ -61,34 +66,42 @@ public final class UpdateService { * @param things */ public void updateAllThermostats(List things) { - things.stream().filter(thing -> thing.getHandler() instanceof ThermostatHandler) - .map(thing -> (ThermostatHandler) thing.getHandler()) - .map(handler -> handler.tryHandleAndGetUpdatedThermostat()).forEach(this::updateThermostat); + new SignInService(configuration, httpClient).signIn((sessionId) -> updateAllThermostats(things, sessionId), + connectionLost, unauthorized); } - private void updateThermostat(@Nullable Thermostat thermostat) { + private void updateAllThermostats(List things, String sessionId) { + things.stream().filter(thing -> thing.getHandler() instanceof ThermostatHandler) + .map(thing -> (ThermostatHandler) thing.getHandler()) + .map(handler -> handler.tryHandleAndGetUpdatedThermostat()) + .forEach((thermostat) -> updateThermostat(thermostat, sessionId)); + } + + private void updateThermostat(@Nullable ThermostatModel thermostat, String sessionId) { if (thermostat == null) { return; } - Request request = httpClient.POST(configuration.apiUrl + "/Thermostat/UpdateThermostat") + String jsonPayload = gson.toJson(new UpdateThermostatRequestModel(thermostat).withApiKey(configuration.apiKey)); + Request request = httpClient.POST(configuration.getRestApiUrl() + "/Thermostat/UpdateThermostat") .param("sessionid", sessionId).header(HttpHeader.CONTENT_TYPE, "application/json") - .content(new StringContentProvider( - gson.toJson(new UpdateThermostatRequestModel(thermostat).withApiKey(configuration.apiKey)))); + .content(new StringContentProvider(jsonPayload)); + logger.trace("updateThermostat payload for themostat with serial {} is {}", thermostat.serialNumber, + jsonPayload); request.send(new BufferingResponseListener() { @Override public void onComplete(@Nullable Result result) { if (result != null) { - logger.trace("onComplete {}", result); + logger.trace("onComplete Http Status {} {}", result.getResponse().getStatus(), result); if (result.isFailed()) { - logger.warn("updateThermostat failed {}", thermostat); + logger.warn("updateThermostat failed for themostat with serial {}", thermostat.serialNumber); + return; } - SimpleResponseModel responseModel = gson.fromJson(getContentAsString(), SimpleResponseModel.class); - if (responseModel == null) { - logger.warn("updateThermostat failed with empty result {}", thermostat); - } else if (responseModel.errorCode != 0) { - logger.warn("updateThermostat failed with errorCode {} {}", responseModel.errorCode, - thermostat); + SimpleResponseModel responseModel = Objects + .requireNonNull(gson.fromJson(getContentAsString(), SimpleResponseModel.class)); + if (responseModel.errorCode != 0) { + logger.warn("updateThermostat failed with errorCode {} for thermostat with serial {}", + responseModel.errorCode, thermostat.serialNumber); } } } diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/i18n/ojelectronics.properties b/bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/i18n/ojelectronics.properties index 10910ce7562..177912d5a86 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/i18n/ojelectronics.properties +++ b/bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/i18n/ojelectronics.properties @@ -15,13 +15,11 @@ thing-type.ojelectronics.owd5.description = OWD5/MWD5 Thermostat thing-type.config.ojelectronics.ojcloud.apiKey.label = API Key thing-type.config.ojelectronics.ojcloud.apiKey.description = API-Key from your local distributor thing-type.config.ojelectronics.ojcloud.apiUrl.label = API-URL -thing-type.config.ojelectronics.ojcloud.apiUrl.description = URL to cloud API-service. +thing-type.config.ojelectronics.ojcloud.apiUrl.description = URL to cloud API-service and Socket-Notification. thing-type.config.ojelectronics.ojcloud.customerId.label = Customer ID thing-type.config.ojelectronics.ojcloud.customerId.description = Customer ID thing-type.config.ojelectronics.ojcloud.password.label = Password thing-type.config.ojelectronics.ojcloud.password.description = Password for access cloud service. -thing-type.config.ojelectronics.ojcloud.refreshDelayInSeconds.label = Refresh Delay -thing-type.config.ojelectronics.ojcloud.refreshDelayInSeconds.description = Refresh delay in seconds. thing-type.config.ojelectronics.ojcloud.softwareVersion.label = Software Version thing-type.config.ojelectronics.ojcloud.softwareVersion.description = Software Version thing-type.config.ojelectronics.ojcloud.userName.label = User Name diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/thing/thing-types.xml index ba64ef5d99f..251f6255553 100644 --- a/bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/thing/thing-types.xml @@ -22,16 +22,10 @@ - URL to cloud API-service. + URL to cloud API-service and Socket-Notification. url true - https://OWD5-OJ001-App.ojelectronics.com/api - - - - Refresh delay in seconds. - true - 30 + https://OWD5-OJ001-App.ojelectronics.com