[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 <ckittel@gmx.de>
This commit is contained in:
Christian Kittel 2023-04-08 11:40:59 +02:00 committed by GitHub
parent 6748dfedd7
commit 64723db7aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 716 additions and 207 deletions

View File

@ -11,3 +11,8 @@ https://www.eclipse.org/legal/epl-2.0/.
== Source Code == Source Code
https://github.com/openhab/openhab-addons 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

View File

@ -23,7 +23,6 @@ After the ojcloud bridge is successfully initialized all thermostats will be dis
| password | password from the OJElectronics App (required) | | password | password from the OJElectronics App (required) |
| apiKey | API key. You get the key from your local distributor. | | apiKey | API key. You get the key from your local distributor. |
| apiUrl | URL of the API endpoint. Optional, the default value should always work. | | 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. | | customerId | Customer ID. Optional, the default value should always work. |
| softwareVersion | Software version. Optional, the default value should always work. | | softwareVersion | Software version. Optional, the default value should always work. |

View File

@ -13,4 +13,21 @@
<artifactId>org.openhab.binding.ojelectronics</artifactId> <artifactId>org.openhab.binding.ojelectronics</artifactId>
<name>openHAB Add-ons :: Bundles :: OJElectronics Binding</name> <name>openHAB Add-ons :: Bundles :: OJElectronics Binding</name>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.github.signalr4j/signalr4j -->
<dependency>
<groupId>com.github.signalr4j</groupId>
<artifactId>signalr4j</artifactId>
<version>2.0.4</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project> </project>

View File

@ -13,7 +13,7 @@
package org.openhab.binding.ojelectronics.internal; package org.openhab.binding.ojelectronics.internal;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Set;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -21,10 +21,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConfiguration; 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.models.groups.GroupContentResponseModel;
import org.openhab.binding.ojelectronics.internal.services.OJDiscoveryService; import org.openhab.binding.ojelectronics.internal.services.OJDiscoveryService;
import org.openhab.binding.ojelectronics.internal.services.RefreshGroupContentService; import org.openhab.binding.ojelectronics.internal.services.RefreshGroupContentService;
import org.openhab.binding.ojelectronics.internal.services.RefreshService; 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.SignInService;
import org.openhab.binding.ojelectronics.internal.services.UpdateService; import org.openhab.binding.ojelectronics.internal.services.UpdateService;
import org.openhab.core.thing.Bridge; 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.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler; 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.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -44,7 +45,7 @@ import org.slf4j.LoggerFactory;
* @author Christian Kittel - Initial Contribution * @author Christian Kittel - Initial Contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler { public class OJCloudHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(OJCloudHandler.class); private final Logger logger = LoggerFactory.getLogger(OJCloudHandler.class);
private final HttpClient httpClient; private final HttpClient httpClient;
@ -54,6 +55,7 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
private @Nullable SignInService signInService; private @Nullable SignInService signInService;
private OJElectronicsBridgeConfiguration configuration; private OJElectronicsBridgeConfiguration configuration;
private @Nullable ScheduledFuture<?> signTask; private @Nullable ScheduledFuture<?> signTask;
private @Nullable ScheduledFuture<?> updateTask;
private @Nullable OJDiscoveryService discoveryService; private @Nullable OJDiscoveryService discoveryService;
/** /**
@ -82,9 +84,9 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
*/ */
@Override @Override
public void dispose() { public void dispose() {
final RefreshService refreshService = this.refreshService; final RefreshService localRefreshService = this.refreshService;
if (refreshService != null) { if (localRefreshService != null) {
refreshService.stop(); localRefreshService.stop();
} }
final ScheduledFuture<?> signTask = this.signTask; final ScheduledFuture<?> signTask = this.signTask;
if (signTask != null) { if (signTask != null) {
@ -99,64 +101,90 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
public void handleCommand(ChannelUID channelUID, Command command) { 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() { private void ensureSignIn() {
if (signInService == null) { if (signInService == null) {
signInService = new SignInService(configuration, httpClient); signInService = new SignInService(configuration, httpClient);
} }
final SignInService signInService = this.signInService; final SignInService localSignInService = this.signInService;
if (signInService != null) { if (localSignInService != null) {
signInService.signIn(this::handleSignInDone, this::handleConnectionLost, localSignInService.signIn(this::handleSignInDone, this::handleConnectionLost,
this::handleUnauthorizedWhileSignIn); this::handleUnauthorizedWhileSignIn);
} }
} }
private void handleRefreshDone(@Nullable GroupContentResponseModel groupContentResponse, private void initializationDone(@Nullable GroupContentResponseModel groupContentResponse,
@Nullable String errorMessage) { @Nullable String errorMessage) {
logger.trace("OJElectronicsCloudHandler.handleRefreshDone({})", groupContentResponse); logger.trace("OJElectronicsCloudHandler.initializationDone({})", groupContentResponse);
if (groupContentResponse != null && groupContentResponse.errorCode == 0) { if (groupContentResponse != null && groupContentResponse.errorCode == 0) {
internalRefreshDone(groupContentResponse); internalInitializationDone(groupContentResponse);
} else { } else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
(errorMessage == null) ? "Wrong or no result model; Refreshing stoppped" : errorMessage); (errorMessage == null) ? "Wrong or no result model; Refreshing stoppped" : errorMessage);
final RefreshService refreshService = this.refreshService; final RefreshService localRefreshService = this.refreshService;
if (refreshService != null) { if (localRefreshService != null) {
refreshService.stop(); localRefreshService.stop();
} }
} }
} }
private void internalRefreshDone(GroupContentResponseModel groupContentResponse) { private void refreshDone(@Nullable SignalRResultModel resultModel, @Nullable String errorMessage) {
new RefreshGroupContentService(groupContentResponse.groupContents, getThing().getThings()).handle(); logger.trace("OJElectronicsCloudHandler.refreshDone({})", resultModel);
final OJDiscoveryService discoveryService = this.discoveryService; if (resultModel != null) {
if (discoveryService != null) { new RefreshThermostatsService(resultModel.getThermostats(), resultModel.getThermostatRealTimes(),
discoveryService.setScanResultForDiscovery(groupContentResponse.groupContents); 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) { private void handleSignInDone(String sessionId) {
logger.trace("OJElectronicsCloudHandler.handleSignInDone({})", sessionId); logger.trace("OJElectronicsCloudHandler.handleSignInDone({})", sessionId);
if (refreshService == null) { if (refreshService == null) {
refreshService = new RefreshService(configuration, httpClient, scheduler); refreshService = new RefreshService(configuration, httpClient);
} }
final RefreshService refreshService = this.refreshService; final RefreshService localRefreshService = this.refreshService;
if (refreshService != null) { if (localRefreshService != null) {
refreshService.start(sessionId, this::handleRefreshDone, this::handleConnectionLost, localRefreshService.start(sessionId, this::initializationDone, this::refreshDone,
this::handleUnauthorized); this::handleConnectionLost, this::handleUnauthorized);
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
} }
this.updateService = new UpdateService(configuration, httpClient, sessionId); this.updateService = new UpdateService(configuration, httpClient, this::handleConnectionLost,
this::handleUnauthorized);
} }
private void handleUnauthorized() { private void handleUnauthorized() {
logger.trace("OJElectronicsCloudHandler.handleUnauthorized()"); logger.trace("OJElectronicsCloudHandler.handleUnauthorized()");
final RefreshService refreshService = this.refreshService; final RefreshService localRefreshService = this.refreshService;
if (refreshService != null) { if (localRefreshService != null) {
refreshService.stop(); localRefreshService.stop();
} }
restartRefreshServiceAsync(1); restartRefreshServiceAsync(1);
} }
@ -165,20 +193,29 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
logger.trace("OJElectronicsCloudHandler.handleUnauthorizedWhileSignIn()"); logger.trace("OJElectronicsCloudHandler.handleUnauthorizedWhileSignIn()");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Could not sign in. Check user name and password."); "Could not sign in. Check user name and password.");
final RefreshService refreshService = this.refreshService; final RefreshService localRefreshService = this.refreshService;
if (refreshService != null) { if (localRefreshService != null) {
refreshService.stop(); localRefreshService.stop();
} }
} }
private void handleConnectionLost() { public void reInitialize() {
logger.trace("OJElectronicsCloudHandler.handleConnectionLost()"); logger.trace("OJElectronicsCloudHandler.reInitialize()");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); final RefreshService localRefreshService = this.refreshService;
final RefreshService refreshService = this.refreshService; if (localRefreshService != null) {
if (refreshService != null) { localRefreshService.stop();
refreshService.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) { private void restartRefreshServiceAsync(long delayInSeconds) {
@ -191,6 +228,6 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
@Override @Override
public Collection<Class<? extends ThingHandlerService>> getServices() { public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(OJDiscoveryService.class); return Set.of(OJDiscoveryService.class);
} }
} }

View File

@ -14,7 +14,6 @@ package org.openhab.binding.ojelectronics.internal;
import static org.openhab.binding.ojelectronics.internal.BindingConstants.THING_TYPE_OJCLOUD; import static org.openhab.binding.ojelectronics.internal.BindingConstants.THING_TYPE_OJCLOUD;
import java.util.Collections;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -40,7 +39,7 @@ import org.osgi.service.component.annotations.Reference;
@Component(configurationPid = "binding.ojelectronics", service = ThingHandlerFactory.class) @Component(configurationPid = "binding.ojelectronics", service = ThingHandlerFactory.class)
public class OJCloudHandlerFactory extends BaseThingHandlerFactory { public class OJCloudHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OJCLOUD); private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_OJCLOUD);
private final HttpClient httpClient; private final HttpClient httpClient;

View File

@ -20,6 +20,7 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.measure.quantity.Temperature; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ojelectronics.internal.config.OJElectronicsThermostatConfiguration; 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.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType; 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.QuantityType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler; 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.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -58,12 +62,13 @@ public class ThermostatHandler extends BaseThingHandler {
private final String serialNumber; private final String serialNumber;
private final Logger logger = LoggerFactory.getLogger(ThermostatHandler.class); private final Logger logger = LoggerFactory.getLogger(ThermostatHandler.class);
private final Map<String, Consumer<Thermostat>> channelrefreshActions = createChannelRefreshActionMap(); private final Map<String, Consumer<ThermostatModel>> channelRefreshActions = createChannelRefreshActionMap();
private final Map<String, Consumer<ThermostatRealTimeValuesModel>> channelRealTimeRefreshActions = createRealTimeChannelRefreshActionMap();
private final Map<String, Consumer<Command>> updateThermostatValueActions = createUpdateThermostatValueActionMap(); private final Map<String, Consumer<Command>> updateThermostatValueActions = createUpdateThermostatValueActionMap();
private final TimeZoneProvider timeZoneProvider; private final TimeZoneProvider timeZoneProvider;
private LinkedList<AbstractMap.SimpleImmutableEntry<String, Command>> updatedValues = new LinkedList<>(); private LinkedList<AbstractMap.SimpleImmutableEntry<String, Command>> updatedValues = new LinkedList<>();
private @Nullable Thermostat currentThermostat; private @Nullable ThermostatModel currentThermostat;
/** /**
* Creates a new instance of {@link ThermostatHandler} * Creates a new instance of {@link ThermostatHandler}
@ -92,9 +97,9 @@ public class ThermostatHandler extends BaseThingHandler {
@Override @Override
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) { if (command instanceof RefreshType) {
final Thermostat thermostat = currentThermostat; final ThermostatModel thermostat = currentThermostat;
if (thermostat != null && channelrefreshActions.containsKey(channelUID.getId())) { if (thermostat != null && channelRefreshActions.containsKey(channelUID.getId())) {
final @Nullable Consumer<Thermostat> consumer = channelrefreshActions.get(channelUID.getId()); final @Nullable Consumer<ThermostatModel> consumer = channelRefreshActions.get(channelUID.getId());
if (consumer != null) { if (consumer != null) {
consumer.accept(thermostat); consumer.accept(thermostat);
} }
@ -102,6 +107,14 @@ public class ThermostatHandler extends BaseThingHandler {
} else { } else {
synchronized (this) { synchronized (this) {
updatedValues.add(new AbstractMap.SimpleImmutableEntry<String, Command>(channelUID.getId(), command)); updatedValues.add(new AbstractMap.SimpleImmutableEntry<String, Command>(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 @Override
public void initialize() { 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 * @param thermostat thermostat values
*/ */
public void handleThermostatRefresh(Thermostat thermostat) { public void handleThermostatRefresh(ThermostatModel thermostat) {
if (currentThermostat == null) {
updateStatus(ThingStatus.ONLINE);
}
currentThermostat = thermostat; 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<SimpleImmutableEntry<String, Command>> updatedValues = this.updatedValues; final LinkedList<SimpleImmutableEntry<String, Command>> updatedValues = this.updatedValues;
if (updatedValues.isEmpty()) { if (updatedValues.isEmpty()) {
return null; return null;
@ -146,59 +187,64 @@ public class ThermostatHandler extends BaseThingHandler {
return currentThermostat; return currentThermostat;
} }
private void updateManualSetpoint(Thermostat thermostat) { private ThermostatModel getCurrentThermostat() {
return Objects.requireNonNull(currentThermostat);
}
private void updateManualSetpoint(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, updateState(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT,
new QuantityType<Temperature>(thermostat.manualModeSetpoint / (double) 100, SIUnits.CELSIUS)); new QuantityType<Temperature>(thermostat.manualModeSetpoint / (double) 100, SIUnits.CELSIUS));
} }
private void updateManualSetpoint(Command command) { private void updateManualSetpoint(Command command) {
if (command instanceof QuantityType<?>) { if (command instanceof QuantityType<?>) {
currentThermostat.manualModeSetpoint = (int) (((QuantityType<?>) command).floatValue() * 100); getCurrentThermostat().manualModeSetpoint = (int) (((QuantityType<?>) command).floatValue() * 100);
} else { } else {
logger.warn("Unable to set value {}", command); logger.warn("Unable to set value {}", command);
} }
} }
private void updateBoostEndTime(Thermostat thermostat) { private void updateBoostEndTime(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, new DateTimeType( updateState(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, new DateTimeType(
ZonedDateTime.ofInstant(thermostat.boostEndTime.toInstant(), timeZoneProvider.getTimeZone()))); ZonedDateTime.ofInstant(thermostat.boostEndTime.toInstant(), timeZoneProvider.getTimeZone())));
} }
private void updateBoostEndTime(Command command) { private void updateBoostEndTime(Command command) {
if (command instanceof DateTimeType) { if (command instanceof DateTimeType) {
currentThermostat.boostEndTime = Date.from(((DateTimeType) command).getZonedDateTime().toInstant()); getCurrentThermostat().boostEndTime = Date.from(((DateTimeType) command).getZonedDateTime().toInstant());
} else { } else {
logger.warn("Unable to set value {}", command); logger.warn("Unable to set value {}", command);
} }
} }
private void updateComfortEndTime(Thermostat thermostat) { private void updateComfortEndTime(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, new DateTimeType( updateState(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, new DateTimeType(
ZonedDateTime.ofInstant(thermostat.comfortEndTime.toInstant(), timeZoneProvider.getTimeZone()))); ZonedDateTime.ofInstant(thermostat.comfortEndTime.toInstant(), timeZoneProvider.getTimeZone())));
} }
private void updateComfortEndTime(Command command) { private void updateComfortEndTime(Command command) {
if (command instanceof DateTimeType) { if (command instanceof DateTimeType) {
currentThermostat.comfortEndTime = Date.from(((DateTimeType) command).getZonedDateTime().toInstant()); getCurrentThermostat().comfortEndTime = Objects
.requireNonNull(Date.from(((DateTimeType) command).getZonedDateTime().toInstant()));
} else { } else {
logger.warn("Unable to set value {}", command); logger.warn("Unable to set value {}", command);
} }
} }
private void updateComfortSetpoint(Thermostat thermostat) { private void updateComfortSetpoint(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT, updateState(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT,
new QuantityType<Temperature>(thermostat.comfortSetpoint / (double) 100, SIUnits.CELSIUS)); new QuantityType<Temperature>(thermostat.comfortSetpoint / (double) 100, SIUnits.CELSIUS));
} }
private void updateComfortSetpoint(Command command) { private void updateComfortSetpoint(Command command) {
if (command instanceof QuantityType<?>) { if (command instanceof QuantityType<?>) {
currentThermostat.comfortSetpoint = (int) (((QuantityType<?>) command).floatValue() * 100); getCurrentThermostat().comfortSetpoint = (int) (((QuantityType<?>) command).floatValue() * 100);
} else { } else {
logger.warn("Unable to set value {}", command); logger.warn("Unable to set value {}", command);
} }
} }
private void updateRegulationMode(Thermostat thermostat) { private void updateRegulationMode(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, updateState(BindingConstants.CHANNEL_OWD5_REGULATIONMODE,
StringType.valueOf(getRegulationMode(thermostat.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()))) { if (command instanceof StringType && (REVERSE_REGULATION_MODES.containsKey(command.toString().toLowerCase()))) {
final @Nullable Integer mode = REVERSE_REGULATION_MODES.get(command.toString().toLowerCase()); final @Nullable Integer mode = REVERSE_REGULATION_MODES.get(command.toString().toLowerCase());
if (mode != null) { if (mode != null) {
currentThermostat.regulationMode = mode; getCurrentThermostat().regulationMode = mode;
} }
} else { } else {
logger.warn("Unable to set value {}", command); 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)); updateState(BindingConstants.CHANNEL_OWD5_THERMOSTATNAME, StringType.valueOf(thermostat.thermostatName));
} }
private void updateFloorTemperature(Thermostat thermostat) { private void updateFloorTemperature(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE,
new QuantityType<Temperature>(thermostat.floorTemperature / (double) 100, SIUnits.CELSIUS)); new QuantityType<Temperature>(thermostat.floorTemperature / (double) 100, SIUnits.CELSIUS));
} }
private void updateRoomTemperature(Thermostat thermostat) { private void updateFloorTemperature(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, new QuantityType<Temperature>(
thermostatRealTimeValues.floorTemperature / (double) 100, SIUnits.CELSIUS));
}
private void updateRoomTemperature(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE,
new QuantityType<Temperature>(thermostat.roomTemperature / (double) 100, SIUnits.CELSIUS)); new QuantityType<Temperature>(thermostat.roomTemperature / (double) 100, SIUnits.CELSIUS));
} }
private void updateHeating(Thermostat thermostat) { private void updateRoomTemperature(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, new QuantityType<Temperature>(
thermostatRealTimeValues.roomTemperature / (double) 100, SIUnits.CELSIUS));
}
private void updateHeating(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_HEATING, updateState(BindingConstants.CHANNEL_OWD5_HEATING,
thermostat.heating ? OpenClosedType.OPEN : OpenClosedType.CLOSED); 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, updateState(BindingConstants.CHANNEL_OWD5_ONLINE,
thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED); 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)); 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)); updateState(BindingConstants.CHANNEL_OWD5_GROUPNAME, StringType.valueOf(thermostat.groupName));
} }
private void updateVacationEnabled(Thermostat thermostat) { private void updateVacationEnabled(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_VACATIONENABLED, updateState(BindingConstants.CHANNEL_OWD5_VACATIONENABLED,
thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED); thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
} }
private void updateVacationBeginDay(Thermostat thermostat) { private void updateVacationBeginDay(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY, updateState(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY,
new DateTimeType( new DateTimeType(
ZonedDateTime.ofInstant(thermostat.vacationBeginDay.toInstant(), timeZoneProvider.getTimeZone()) ZonedDateTime.ofInstant(thermostat.vacationBeginDay.toInstant(), timeZoneProvider.getTimeZone())
@ -260,14 +326,14 @@ public class ThermostatHandler extends BaseThingHandler {
private void updateVacationBeginDay(Command command) { private void updateVacationBeginDay(Command command) {
if (command instanceof DateTimeType) { if (command instanceof DateTimeType) {
currentThermostat.vacationBeginDay = Date getCurrentThermostat().vacationBeginDay = Date
.from(((DateTimeType) command).getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS)); .from(((DateTimeType) command).getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS));
} else { } else {
logger.warn("Unable to set value {}", command); logger.warn("Unable to set value {}", command);
} }
} }
private void updateVacationEndDay(Thermostat thermostat) { private void updateVacationEndDay(ThermostatModel thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY, updateState(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY,
new DateTimeType( new DateTimeType(
ZonedDateTime.ofInstant(thermostat.vacationEndDay.toInstant(), timeZoneProvider.getTimeZone()) ZonedDateTime.ofInstant(thermostat.vacationEndDay.toInstant(), timeZoneProvider.getTimeZone())
@ -276,7 +342,7 @@ public class ThermostatHandler extends BaseThingHandler {
private void updateVacationEndDay(Command command) { private void updateVacationEndDay(Command command) {
if (command instanceof DateTimeType) { if (command instanceof DateTimeType) {
currentThermostat.vacationEndDay = Date getCurrentThermostat().vacationEndDay = Date
.from(((DateTimeType) command).getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS)); .from(((DateTimeType) command).getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS));
} else { } else {
logger.warn("Unable to set value {}", command); logger.warn("Unable to set value {}", command);
@ -311,8 +377,8 @@ public class ThermostatHandler extends BaseThingHandler {
return map; return map;
}; };
private Map<String, Consumer<Thermostat>> createChannelRefreshActionMap() { private Map<String, Consumer<ThermostatModel>> createChannelRefreshActionMap() {
HashMap<String, Consumer<Thermostat>> map = new HashMap<>(); HashMap<String, Consumer<ThermostatModel>> map = new HashMap<>();
map.put(BindingConstants.CHANNEL_OWD5_GROUPNAME, this::updateGroupName); map.put(BindingConstants.CHANNEL_OWD5_GROUPNAME, this::updateGroupName);
map.put(BindingConstants.CHANNEL_OWD5_GROUPID, this::updateGroupId); map.put(BindingConstants.CHANNEL_OWD5_GROUPID, this::updateGroupId);
map.put(BindingConstants.CHANNEL_OWD5_ONLINE, this::updateOnline); map.put(BindingConstants.CHANNEL_OWD5_ONLINE, this::updateOnline);
@ -331,6 +397,15 @@ public class ThermostatHandler extends BaseThingHandler {
return map; return map;
} }
private Map<String, Consumer<ThermostatRealTimeValuesModel>> createRealTimeChannelRefreshActionMap() {
HashMap<String, Consumer<ThermostatRealTimeValuesModel>> 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<String, Consumer<Command>> createUpdateThermostatValueActionMap() { private Map<String, Consumer<Command>> createUpdateThermostatValueActionMap() {
HashMap<String, Consumer<Command>> map = new HashMap<>(); HashMap<String, Consumer<Command>> map = new HashMap<>();
map.put(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, this::updateRegulationMode); map.put(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, this::updateRegulationMode);

View File

@ -14,7 +14,6 @@ package org.openhab.binding.ojelectronics.internal;
import static org.openhab.binding.ojelectronics.internal.BindingConstants.THING_TYPE_OWD5; import static org.openhab.binding.ojelectronics.internal.BindingConstants.THING_TYPE_OWD5;
import java.util.Collections;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -38,7 +37,7 @@ import org.osgi.service.component.annotations.Reference;
@Component(configurationPid = "binding.ojelectronics", service = ThingHandlerFactory.class) @Component(configurationPid = "binding.ojelectronics", service = ThingHandlerFactory.class)
public class ThermostatHandlerFactory extends BaseThingHandlerFactory { public class ThermostatHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OWD5); private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_OWD5);
private final TimeZoneProvider timeZoneProvider; private final TimeZoneProvider timeZoneProvider;
/** /**

View File

@ -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;
}
}
}

View File

@ -13,6 +13,7 @@
package org.openhab.binding.ojelectronics.internal.config; package org.openhab.binding.ojelectronics.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/** /**
* The configuration for {@link org.openhab.binding.ojelectronics.internal.OJElectronicsCloudHandler} * The configuration for {@link org.openhab.binding.ojelectronics.internal.OJElectronicsCloudHandler}
@ -40,7 +41,7 @@ public class OJElectronicsBridgeConfiguration {
/** /**
* Url for API * Url for API
*/ */
public String apiUrl = "https://OWD5-OJ001-App.ojelectronics.com/api"; private String apiUrl = "https://OWD5-OJ001-App.ojelectronics.com";
/** /**
* API-Key * API-Key
@ -52,8 +53,29 @@ public class OJElectronicsBridgeConfiguration {
*/ */
public int softwareVersion = 1060; public int softwareVersion = 1060;
/** private @Nullable String restApiUrl;
* Refresh-Delay
/*
* 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;
}
} }

View File

@ -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<GroupContentModel> groups = List.of();
@SerializedName("SequenceNr")
private int sequenceNr;
@SerializedName("ThermostatRealTimes")
private List<ThermostatRealTimeValuesModel> thermostatRealTimes = List.of();
@SerializedName("Thermostats")
private List<ThermostatModel> thermostats = List.of();
public List<GroupContentModel> getGroups() {
return this.groups;
}
public int getSequenceNr() {
return this.sequenceNr;
}
public List<ThermostatRealTimeValuesModel> getThermostatRealTimes() {
return this.thermostatRealTimes;
}
public List<ThermostatModel> getThermostats() {
return this.thermostats;
}
public void setGroups(List<GroupContentModel> paramArrayList) {
this.groups = paramArrayList;
}
public void setSequenceNr(int paramInt) {
this.sequenceNr = paramInt;
}
public void setThermostatRealTimes(List<ThermostatRealTimeValuesModel> paramArrayList) {
this.thermostatRealTimes = paramArrayList;
}
public void setThermostats(List<ThermostatModel> paramArrayList) {
this.thermostats = paramArrayList;
}
}

View File

@ -23,9 +23,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* @author Christian Kittel - Initial contribution * @author Christian Kittel - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class Day { public class DayModel {
public int weekDayGrpNo; public int weekDayGrpNo;
public List<Event> events = new ArrayList<>(); public List<EventModel> events = new ArrayList<>();
} }

View File

@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* @author Christian Kittel - Initial contribution * @author Christian Kittel - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class Event { public class EventModel {
public int scheduleType; public int scheduleType;

View File

@ -17,7 +17,7 @@ import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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 * Model for content of a group
@ -25,7 +25,7 @@ import org.openhab.binding.ojelectronics.internal.models.Thermostat;
* @author Christian Kittel - Initial contribution * @author Christian Kittel - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class GroupContent { public class GroupContentModel {
public int action; public int action;
@ -33,11 +33,11 @@ public class GroupContent {
public String groupName = ""; public String groupName = "";
public List<Thermostat> thermostats = new ArrayList<Thermostat>(); public List<ThermostatModel> thermostats = new ArrayList<ThermostatModel>();
public int regulationMode; public int regulationMode;
public @Nullable Schedule schedule; public @Nullable ScheduleModel schedule;
public int comfortSetpoint; public int comfortSetpoint;

View File

@ -26,5 +26,5 @@ import org.openhab.binding.ojelectronics.internal.models.ResponseModelBase;
@NonNullByDefault @NonNullByDefault
public class GroupContentResponseModel extends ResponseModelBase { public class GroupContentResponseModel extends ResponseModelBase {
public List<GroupContent> groupContents = new ArrayList<GroupContent>(); public List<GroupContentModel> groupContents = new ArrayList<GroupContentModel>();
} }

View File

@ -23,9 +23,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* @author Christian Kittel - Initial contribution * @author Christian Kittel - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class Schedule { public class ScheduleModel {
public List<Day> days = new ArrayList<Day>(); public List<DayModel> days = new ArrayList<DayModel>();
public boolean modifiedDueToVerification; public boolean modifiedDueToVerification;
} }

View File

@ -10,13 +10,13 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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 java.util.Date;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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; import com.google.gson.annotations.SerializedName;
@ -26,14 +26,12 @@ import com.google.gson.annotations.SerializedName;
* @author Christian Kittel - Initial contribution * @author Christian Kittel - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class Thermostat { public class ThermostatModel extends ThermostatModelBase {
public int id; public int id;
public int action; public int action;
public String serialNumber = "";
public String groupName = ""; public String groupName = "";
public int groupId; public int groupId;
@ -53,7 +51,7 @@ public class Thermostat {
public int regulationMode; public int regulationMode;
public @Nullable Schedule schedule; public @Nullable ScheduleModel schedule;
public int comfortSetpoint; public int comfortSetpoint;

View File

@ -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 = "";
}

View File

@ -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;
}

View File

@ -14,7 +14,6 @@ package org.openhab.binding.ojelectronics.internal.models.thermostat;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ojelectronics.internal.models.RequestModelBase; import org.openhab.binding.ojelectronics.internal.models.RequestModelBase;
import org.openhab.binding.ojelectronics.internal.models.Thermostat;
/** /**
* Model for updating a thermostat * Model for updating a thermostat
@ -24,12 +23,12 @@ import org.openhab.binding.ojelectronics.internal.models.Thermostat;
@NonNullByDefault @NonNullByDefault
public class UpdateThermostatRequestModel extends RequestModelBase { public class UpdateThermostatRequestModel extends RequestModelBase {
public UpdateThermostatRequestModel(Thermostat thermostat) { public UpdateThermostatRequestModel(ThermostatModel thermostat) {
setThermostat = thermostat; setThermostat = thermostat;
thermostatID = thermostat.serialNumber; thermostatID = thermostat.serialNumber;
} }
public Thermostat setThermostat; public ThermostatModel setThermostat;
public String thermostatID; public String thermostatID;
} }

View File

@ -15,14 +15,13 @@ package org.openhab.binding.ojelectronics.internal.services;
import static org.openhab.binding.ojelectronics.internal.BindingConstants.*; import static org.openhab.binding.ojelectronics.internal.BindingConstants.*;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ojelectronics.internal.OJCloudHandler; 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.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.config.discovery.DiscoveryService;
@ -39,12 +38,11 @@ import org.osgi.service.component.annotations.Component;
*/ */
@NonNullByDefault @NonNullByDefault
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.ojelectronics") @Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.ojelectronics")
public final class OJDiscoveryService extends AbstractDiscoveryService public final class OJDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
implements DiscoveryService, ThingHandlerService {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OJCLOUD); private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_OJCLOUD);
private @Nullable OJCloudHandler bridgeHandler; private @Nullable OJCloudHandler bridgeHandler;
private @Nullable Collection<GroupContent> groupContents; private @Nullable Collection<GroupContentModel> groupContents;
/** /**
* Creates a new instance of {@link OJDiscoveryService} * Creates a new instance of {@link OJDiscoveryService}
@ -59,14 +57,14 @@ public final class OJDiscoveryService extends AbstractDiscoveryService
* *
* @param groupContents Content from API * @param groupContents Content from API
*/ */
public void setScanResultForDiscovery(List<GroupContent> groupContents) { public void setScanResultForDiscovery(List<GroupContentModel> groupContents) {
this.groupContents = groupContents; this.groupContents = groupContents;
} }
@Override @Override
protected void startScan() { protected void startScan() {
final OJCloudHandler bridgeHandler = this.bridgeHandler; final OJCloudHandler bridgeHandler = this.bridgeHandler;
final Collection<GroupContent> groupContents = this.groupContents; final Collection<GroupContentModel> groupContents = this.groupContents;
if (groupContents != null && bridgeHandler != null) { if (groupContents != null && bridgeHandler != null) {
groupContents.stream().flatMap(content -> content.thermostats.stream()) groupContents.stream().flatMap(content -> content.thermostats.stream())
.forEach(thermostat -> thingDiscovered(bridgeHandler.getThing().getUID(), thermostat.serialNumber)); .forEach(thermostat -> thingDiscovered(bridgeHandler.getThing().getUID(), thermostat.serialNumber));

View File

@ -12,12 +12,13 @@
*/ */
package org.openhab.binding.ojelectronics.internal.services; package org.openhab.binding.ojelectronics.internal.services;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ojelectronics.internal.ThermostatHandler; import org.openhab.binding.ojelectronics.internal.ThermostatHandler;
import org.openhab.binding.ojelectronics.internal.models.Thermostat; import org.openhab.binding.ojelectronics.internal.models.groups.GroupContentModel;
import org.openhab.binding.ojelectronics.internal.models.groups.GroupContent;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -30,17 +31,17 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class RefreshGroupContentService { public class RefreshGroupContentService {
private final List<GroupContent> groupContentList; private final List<GroupContentModel> groupContentList;
private final Logger logger = LoggerFactory.getLogger(RefreshGroupContentService.class); private final Logger logger = LoggerFactory.getLogger(RefreshGroupContentService.class);
private List<Thing> things; private List<Thing> things;
/** /**
* Creates a new instance of {@link RefreshGroupContentService} * Creates a new instance of {@link RefreshGroupContentService}
* *
* @param groupContents {@link GroupContent} * @param groupContents {@link GroupContentModel}
* @param things Things * @param things Things
*/ */
public RefreshGroupContentService(List<GroupContent> groupContents, List<Thing> things) { public RefreshGroupContentService(List<GroupContentModel> groupContents, List<Thing> things) {
this.groupContentList = groupContents; this.groupContentList = groupContents;
this.things = things; this.things = things;
if (this.things.isEmpty()) { if (this.things.isEmpty()) {
@ -52,13 +53,7 @@ public class RefreshGroupContentService {
* Handles the changes to all things. * Handles the changes to all things.
*/ */
public void handle() { public void handle() {
groupContentList.stream().flatMap(entry -> entry.thermostats.stream()).forEach(this::handleThermostat); new RefreshThermostatsService(groupContentList.stream().flatMap(entry -> entry.thermostats.stream())
} .collect(Collectors.toCollection(ArrayList::new)), things).handle();
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));
} }
} }

View File

@ -12,10 +12,9 @@
*/ */
package org.openhab.binding.ojelectronics.internal.services; package org.openhab.binding.ojelectronics.internal.services;
import java.util.concurrent.ScheduledExecutorService; import java.util.Objects;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.HttpMethod;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.ojelectronics.internal.common.OJGSonBuilder; 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.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.models.groups.GroupContentResponseModel;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.Gson;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
@ -47,14 +51,14 @@ public final class RefreshService implements AutoCloseable {
private final HttpClient httpClient; private final HttpClient httpClient;
private final Gson gson = OJGSonBuilder.getGSon(); private final Gson gson = OJGSonBuilder.getGSon();
private final ScheduledExecutorService schedulerService; private @Nullable Consumer<@Nullable String> connectionLost;
private @Nullable BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> initializationDone;
private @Nullable Runnable connectionLost; private @Nullable BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone;
private @Nullable BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone;
private @Nullable ScheduledFuture<?> scheduler;
private @Nullable Runnable unauthorized; private @Nullable Runnable unauthorized;
private @Nullable String sessionId; 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} * Creates a new instance of {@link RefreshService}
@ -63,11 +67,10 @@ public final class RefreshService implements AutoCloseable {
* @param httpClient HTTP client * @param httpClient HTTP client
* @param updateService Service to update the thermostat in the cloud * @param updateService Service to update the thermostat in the cloud
*/ */
public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient, public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient) {
ScheduledExecutorService schedulerService) {
this.config = config; this.config = config;
this.httpClient = httpClient; 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 connectionLosed This method is called if no connection could established.
* @param unauthorized This method is called if the result is unauthorized. * @param unauthorized This method is called if the result is unauthorized.
*/ */
public void start(String sessionId, BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone, public void start(String sessionId,
Runnable connectionLost, Runnable unauthorized) { BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> initializationDone,
BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone,
Consumer<@Nullable String> connectionLost, Runnable unauthorized) {
logger.trace("RefreshService.startService({})", sessionId); logger.trace("RefreshService.startService({})", sessionId);
this.connectionLost = connectionLost; this.connectionLost = connectionLost;
this.initializationDone = initializationDone;
this.refreshDone = refreshDone; this.refreshDone = refreshDone;
this.unauthorized = unauthorized; this.unauthorized = unauthorized;
this.sessionId = sessionId; this.sessionId = sessionId;
long refreshTime = config.refreshDelayInSeconds;
scheduler = schedulerService.scheduleWithFixedDelay(this::refresh, refreshTime, refreshTime, TimeUnit.SECONDS); signalRConnection = createSignalRConnection();
refresh();
destroyed = false; destroyed = false;
isInitializing = false;
initializeGroups(true);
} }
/** /**
@ -96,25 +103,70 @@ public final class RefreshService implements AutoCloseable {
*/ */
public void stop() { public void stop() {
destroyed = true; destroyed = true;
final ScheduledFuture<?> scheduler = this.scheduler; final Connection localSignalRConnection = signalRConnection;
if (scheduler != null) { if (localSignalRConnection != null) {
scheduler.cancel(false); 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; final String sessionId = this.sessionId;
if (sessionId == null) { if (sessionId == null) {
handleConnectionLost(); handleConnectionLost("No session id");
} }
isInitializing = true;
logger.trace("initializeGroups started");
final Runnable unauthorized = this.unauthorized; final Runnable unauthorized = this.unauthorized;
createRequest().send(new BufferingResponseListener() { createRequest().send(new BufferingResponseListener() {
@Override @Override
public void onComplete(@Nullable Result result) { public void onComplete(@Nullable Result result) {
if (!destroyed) { try {
if (result == null || result.isFailed()) { if (destroyed || result == null) {
handleConnectionLost(); return;
}
if (result.isFailed()) {
final Throwable failure = result.getFailure();
logger.error("Error initializing groups", failure);
handleConnectionLost(failure.getLocalizedMessage());
} else { } else {
int status = result.getResponse().getStatus(); int status = result.getResponse().getStatus();
logger.trace("HTTP-Status {}", status); logger.trace("HTTP-Status {}", status);
@ -122,32 +174,40 @@ public final class RefreshService implements AutoCloseable {
if (unauthorized != null) { if (unauthorized != null) {
unauthorized.run(); unauthorized.run();
} else { } else {
handleConnectionLost(); handleConnectionLost(null);
} }
} else if (status == HttpStatus.OK_200) { } else if (status == HttpStatus.OK_200) {
handleRefreshDone(getContentAsString()); initializationDone(Objects.requireNonNull(getContentAsString()));
final Connection localSignalRConnection = signalRConnection;
if (shouldStartSignalRService && localSignalRConnection != null) {
localSignalRConnection.start();
}
} else { } else {
logger.warn("unsupported HTTP-Status {}", status); logger.warn("unsupported HTTP-Status {}", status);
handleConnectionLost(); handleConnectionLost(null);
} }
} }
} finally {
logger.trace("initializeGroups completed");
isInitializing = false;
} }
} }
}); });
} }
private Request createRequest() { private Request createRequest() {
Request request = httpClient.newRequest(config.apiUrl + "/Group/GroupContents").param("sessionid", sessionId) Request request = httpClient.newRequest(config.getRestApiUrl() + "/Group/GroupContents")
.param("apiKey", config.apiKey).method(HttpMethod.GET); .param("sessionid", sessionId).param("apiKey", config.apiKey).method(HttpMethod.GET);
return request; return request;
} }
private void handleRefreshDone(String responseBody) { private void initializationDone(String responseBody) {
BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone = this.refreshDone; BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone = this.initializationDone;
if (refreshDone != null) { if (refreshDone != null) {
logger.trace("refresh {}", responseBody); logger.trace("initializationDone {}", responseBody);
try { try {
GroupContentResponseModel content = gson.fromJson(responseBody, GroupContentResponseModel.class); GroupContentResponseModel content = Objects
.requireNonNull(gson.fromJson(responseBody, GroupContentResponseModel.class));
refreshDone.accept(content, null); refreshDone.accept(content, null);
} catch (JsonSyntaxException exception) { } catch (JsonSyntaxException exception) {
logger.debug("Error mapping Result to model", exception); logger.debug("Error mapping Result to model", exception);
@ -156,15 +216,15 @@ public final class RefreshService implements AutoCloseable {
} }
} }
private void handleConnectionLost() { private void handleConnectionLost(@Nullable String message) {
final Runnable connectionLost = this.connectionLost; final Consumer<@Nullable String> connectionLost = this.connectionLost;
if (connectionLost != null) { if (connectionLost != null) {
connectionLost.run(); connectionLost.accept(message);
} }
} }
@Override @Override
public void close() throws Exception { public void close() {
stop(); stop();
} }
} }

View File

@ -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<ThermostatModel> thermostats;
private final Logger logger = LoggerFactory.getLogger(RefreshThermostatsService.class);
private final List<Thing> things;
private final List<ThermostatRealTimeValuesModel> realTimeValues;
/**
* Creates a new instance of {@link RefreshThermostatsService}
*
* @param thermostats {@link ThermostatModel}
* @param things Things
*/
public RefreshThermostatsService(List<ThermostatModel> thermostats, List<Thing> 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<ThermostatModel> thermostats,
List<ThermostatRealTimeValuesModel> realTimeValues, List<Thing> 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 <T extends ThermostatModelBase> void handleThermostat(T thermostat,
BiConsumer<ThermostatHandler, T> 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);
}
}

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.ojelectronics.internal.services; package org.openhab.binding.ojelectronics.internal.services;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault; 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.RequestModelBase;
import org.openhab.binding.ojelectronics.internal.models.userprofile.PostSignInQueryModel; import org.openhab.binding.ojelectronics.internal.models.userprofile.PostSignInQueryModel;
import org.openhab.binding.ojelectronics.internal.models.userprofile.PostSignInResponseModel; import org.openhab.binding.ojelectronics.internal.models.userprofile.PostSignInResponseModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -39,7 +43,7 @@ import com.google.gson.Gson;
public class SignInService { public class SignInService {
private final Gson gson = OJGSonBuilder.getGSon(); private final Gson gson = OJGSonBuilder.getGSon();
private final Logger logger = LoggerFactory.getLogger(SignInService.class);
private final HttpClient httpClient; private final HttpClient httpClient;
private final OJElectronicsBridgeConfiguration config; private final OJElectronicsBridgeConfiguration config;
@ -61,30 +65,41 @@ public class SignInService {
* @param connectionLosed This method is called if no connection could established. * @param connectionLosed This method is called if no connection could established.
* @param unauthorized This method is called if the result is unauthorized. * @param unauthorized This method is called if the result is unauthorized.
*/ */
public void signIn(Consumer<String> signInDone, Runnable connectionLosed, Runnable unauthorized) { public void signIn(Consumer<String> signInDone, Consumer<@Nullable String> connectionLosed, Runnable unauthorized) {
Request request = httpClient.POST(config.apiUrl + "/UserProfile/SignIn") logger.trace("Trying to sign in");
Request request = httpClient.POST(config.getRestApiUrl() + "/UserProfile/SignIn")
.header(HttpHeader.CONTENT_TYPE, "application/json") .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() { request.send(new BufferingResponseListener() {
@Override @Override
public void onComplete(@Nullable Result result) { public void onComplete(@Nullable Result result) {
if (result == null || result.isFailed()) { if (result == null) {
connectionLosed.run();
return; 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) { if (result.getResponse().getStatus() == 200) {
PostSignInResponseModel signInModel = gson.fromJson(getContentAsString(), PostSignInResponseModel signInModel = Objects
PostSignInResponseModel.class); .requireNonNull(gson.fromJson(getContentAsString(), PostSignInResponseModel.class));
if (signInModel == null || signInModel.errorCode != 0 || signInModel.sessionId.equals("")) { if (signInModel.errorCode != 0 || signInModel.sessionId.equals("")) {
unauthorized.run(); unauthorized.run();
return; return;
} }
logger.trace("Signing in successful {}", getContentAsString());
signInDone.accept(signInModel.sessionId); signInDone.accept(signInModel.sessionId);
return; return;
} }
connectionLosed.run(); connectionLosed.accept(null);
return; return;
} }
}); });

View File

@ -13,6 +13,8 @@
package org.openhab.binding.ojelectronics.internal.services; package org.openhab.binding.ojelectronics.internal.services;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.common.OJGSonBuilder;
import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConfiguration; import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConfiguration;
import org.openhab.binding.ojelectronics.internal.models.SimpleResponseModel; 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.binding.ojelectronics.internal.models.thermostat.UpdateThermostatRequestModel;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -45,14 +47,17 @@ public final class UpdateService {
private final Gson gson = OJGSonBuilder.getGSon(); private final Gson gson = OJGSonBuilder.getGSon();
private final Logger logger = LoggerFactory.getLogger(UpdateService.class); private final Logger logger = LoggerFactory.getLogger(UpdateService.class);
private final String sessionId;
private final HttpClient httpClient; private final HttpClient httpClient;
private final OJElectronicsBridgeConfiguration configuration; 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.configuration = configuration;
this.httpClient = httpClient; this.httpClient = httpClient;
this.sessionId = sessionId; this.unauthorized = unauthorized;
this.connectionLost = connectionLost;
} }
/** /**
@ -61,34 +66,42 @@ public final class UpdateService {
* @param things * @param things
*/ */
public void updateAllThermostats(List<Thing> things) { public void updateAllThermostats(List<Thing> things) {
things.stream().filter(thing -> thing.getHandler() instanceof ThermostatHandler) new SignInService(configuration, httpClient).signIn((sessionId) -> updateAllThermostats(things, sessionId),
.map(thing -> (ThermostatHandler) thing.getHandler()) connectionLost, unauthorized);
.map(handler -> handler.tryHandleAndGetUpdatedThermostat()).forEach(this::updateThermostat);
} }
private void updateThermostat(@Nullable Thermostat thermostat) { private void updateAllThermostats(List<Thing> 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) { if (thermostat == null) {
return; 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") .param("sessionid", sessionId).header(HttpHeader.CONTENT_TYPE, "application/json")
.content(new StringContentProvider( .content(new StringContentProvider(jsonPayload));
gson.toJson(new UpdateThermostatRequestModel(thermostat).withApiKey(configuration.apiKey)))); logger.trace("updateThermostat payload for themostat with serial {} is {}", thermostat.serialNumber,
jsonPayload);
request.send(new BufferingResponseListener() { request.send(new BufferingResponseListener() {
@Override @Override
public void onComplete(@Nullable Result result) { public void onComplete(@Nullable Result result) {
if (result != null) { if (result != null) {
logger.trace("onComplete {}", result); logger.trace("onComplete Http Status {} {}", result.getResponse().getStatus(), result);
if (result.isFailed()) { 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); SimpleResponseModel responseModel = Objects
if (responseModel == null) { .requireNonNull(gson.fromJson(getContentAsString(), SimpleResponseModel.class));
logger.warn("updateThermostat failed with empty result {}", thermostat); if (responseModel.errorCode != 0) {
} else if (responseModel.errorCode != 0) { logger.warn("updateThermostat failed with errorCode {} for thermostat with serial {}",
logger.warn("updateThermostat failed with errorCode {} {}", responseModel.errorCode, responseModel.errorCode, thermostat.serialNumber);
thermostat);
} }
} }
} }

View File

@ -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.label = API Key
thing-type.config.ojelectronics.ojcloud.apiKey.description = API-Key from your local distributor 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.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.label = Customer ID
thing-type.config.ojelectronics.ojcloud.customerId.description = 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.label = Password
thing-type.config.ojelectronics.ojcloud.password.description = Password for access cloud service. 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.label = Software Version
thing-type.config.ojelectronics.ojcloud.softwareVersion.description = Software Version thing-type.config.ojelectronics.ojcloud.softwareVersion.description = Software Version
thing-type.config.ojelectronics.ojcloud.userName.label = User Name thing-type.config.ojelectronics.ojcloud.userName.label = User Name

View File

@ -22,16 +22,10 @@
</parameter> </parameter>
<parameter name="apiUrl" type="text" required="true"> <parameter name="apiUrl" type="text" required="true">
<label>API-URL</label> <label>API-URL</label>
<description>URL to cloud API-service.</description> <description>URL to cloud API-service and Socket-Notification.</description>
<context>url</context> <context>url</context>
<advanced>true</advanced> <advanced>true</advanced>
<default>https://OWD5-OJ001-App.ojelectronics.com/api</default> <default>https://OWD5-OJ001-App.ojelectronics.com</default>
</parameter>
<parameter name="refreshDelayInSeconds" type="integer" required="true" min="15" unit="s">
<label>Refresh Delay</label>
<description>Refresh delay in seconds.</description>
<advanced>true</advanced>
<default>30</default>
</parameter> </parameter>
<parameter name="customerId" type="integer" required="true"> <parameter name="customerId" type="integer" required="true">
<label>Customer ID</label> <label>Customer ID</label>