]> git.basschouten.com Git - openhab-addons.git/commitdiff
[ojelectronics] Add discovery; enable writable properties (#9168)
authorChristian Kittel <EvilPingu@users.noreply.github.com>
Tue, 8 Dec 2020 01:23:04 +0000 (02:23 +0100)
committerGitHub <noreply@github.com>
Tue, 8 Dec 2020 01:23:04 +0000 (17:23 -0800)
* Add methods to update via Command; Add discovery
* add vacation channels; rework README
* Fixed code review issues: Corrected TimeZone-Handling

Signed-off-by: Christian Kittel <ckittel@gmx.de>
23 files changed:
bundles/org.openhab.binding.ojelectronics/README.md
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/BindingConstants.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandler.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandler.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandlerFactory.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/common/OJGSonBuilder.java [new file with mode: 0644]
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/RequestModelBase.java [new file with mode: 0644]
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/ResponseModelBase.java [new file with mode: 0644]
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/SimpleResponseModel.java [new file with mode: 0644]
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/Thermostat.java [new file with mode: 0644]
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContent.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentResponseModel.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Thermostat.java [deleted file]
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/thermostat/UpdateThermostatRequestModel.java [new file with mode: 0644]
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/userprofile/PostSignInQueryModel.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/userprofile/PostSignInResponseModel.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/OJDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshGroupContentService.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshService.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/SignInService.java
bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/UpdateService.java [new file with mode: 0644]
bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/i18n/ojelectronics_de_DE.properties
bundles/org.openhab.binding.ojelectronics/src/main/resources/OH-INF/thing/thing-types.xml

index 43e1234e13293a51acc85b628419a9e84920ee29..11b4bb49260513064b56d081a312c4afdfecfd70 100644 (file)
@@ -1,8 +1,6 @@
 # OJElectronics Binding
 
-With this binding it is possible to connect [OWD5/MWD5 Thermostat](https://www.ojelectronics.com/business-areas/wifi-thermostat-owd5-prod400) of OJ Electronics.
-
-At this moment all information is read only.
+With this binding it is possible to connect [OWD5/MWD5 Thermostat](https://ojelectronics.com/floorheating/products/wifi-thermostat-owd5/) of OJ Electronics.
 
 ## Supported Things
 
@@ -15,9 +13,7 @@ There are two things:
 
 ## Discovery
 
-Not supported at the moment.
-
-## Thing Configuration
+After the ojcloud bridge is succesfully initialized all thermostats will be discovered.
 
 ### OJ Electronics Bridge configuration (ojcloud)
 
@@ -54,7 +50,9 @@ Not supported at the moment.
 | comfortEndTime     | Date time          | Date and time when the thermostat switchs back from comfort mode to automatic mode |
 | boostEndTime       | Date time          | Date and time when the thermostat switchs back from boost mode to automatic mode   |
 | manualModeSetpoint | Number:Temperature | Target temperature of the manual mode                                              |
-| vacationEnabled    | Switch             | Vacation is enabled                                                                |
+| vacationEnabled    | Contact            | Vacation is enabled                                                                |
+| vacationBeginDay   | Date time          | Vacation start date                                                                |
+| vacationEndDay     | Date time          | Vacation end date                                                                  |
 
 ## Example
 
index c6cddd7d1005343fdbb6ad0fb67616d551aabf76..83ea4362de633f03b51c31f53c2cacb9a89bae38 100644 (file)
@@ -44,4 +44,6 @@ public class BindingConstants {
     public static final String CHANNEL_OWD5_BOOSTENDTIME = "boostEndTime";
     public static final String CHANNEL_OWD5_MANUALSETPOINT = "manualSetpoint";
     public static final String CHANNEL_OWD5_VACATIONENABLED = "vacationEnabled";
+    public static final String CHANNEL_OWD5_VACATIONBEGINDAY = "vacationBeginDay";
+    public static final String CHANNEL_OWD5_VACATIONENDDAY = "vacationEndDay";
 }
index 0bcdb2b6eca046f10aa75a1d667ba39818ebd4df..1b7c9f2eaecf4ae7f9436b1a9b941eb3e2e9454f 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.ojelectronics.internal;
 
+import java.util.Collection;
+import java.util.Collections;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
@@ -20,15 +22,18 @@ 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.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.SignInService;
+import org.openhab.binding.ojelectronics.internal.services.UpdateService;
 import org.openhab.core.thing.Bridge;
 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;
 import org.slf4j.LoggerFactory;
@@ -45,10 +50,18 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
     private final HttpClient httpClient;
 
     private @Nullable RefreshService refreshService;
+    private @Nullable UpdateService updateService;
     private @Nullable SignInService signInService;
     private OJElectronicsBridgeConfiguration configuration;
     private @Nullable ScheduledFuture<?> signTask;
+    private @Nullable OJDiscoveryService discoveryService;
 
+    /**
+     * Creates a new instance of {@link OJCloudHandler}
+     *
+     * @param bridge {@link Bridge}
+     * @param httpClient HttpClient
+     */
     public OJCloudHandler(Bridge bridge, HttpClient httpClient) {
         super(bridge);
         this.httpClient = httpClient;
@@ -100,9 +113,8 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
     private void handleRefreshDone(@Nullable GroupContentResponseModel groupContentResponse,
             @Nullable String errorMessage) {
         logger.trace("OJElectronicsCloudHandler.handleRefreshDone({})", groupContentResponse);
-
         if (groupContentResponse != null && groupContentResponse.errorCode == 0) {
-            new RefreshGroupContentService(groupContentResponse.groupContents, getThing().getThings()).handle();
+            internalRefreshDone(groupContentResponse);
         } else {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                     (errorMessage == null) ? "Wrong or no result model; Refreshing stoppped" : errorMessage);
@@ -113,6 +125,18 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
         }
     }
 
+    private void internalRefreshDone(GroupContentResponseModel groupContentResponse) {
+        new RefreshGroupContentService(groupContentResponse.groupContents, getThing().getThings()).handle();
+        final OJDiscoveryService discoveryService = this.discoveryService;
+        if (discoveryService != null) {
+            discoveryService.setScanResultForDiscovery(groupContentResponse.groupContents);
+        }
+        final UpdateService updateService = this.updateService;
+        if (updateService != null) {
+            updateService.updateAllThermostats(getThing().getThings());
+        }
+    }
+
     private void handleSignInDone(String sessionId) {
         logger.trace("OJElectronicsCloudHandler.handleSignInDone({})", sessionId);
         if (refreshService == null) {
@@ -125,9 +149,11 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
 
             updateStatus(ThingStatus.ONLINE);
         }
+        this.updateService = new UpdateService(configuration, httpClient, sessionId);
     }
 
     private void handleUnauthorized() {
+        logger.trace("OJElectronicsCloudHandler.handleUnauthorized()");
         final RefreshService refreshService = this.refreshService;
         if (refreshService != null) {
             refreshService.stop();
@@ -136,6 +162,7 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
     }
 
     private void handleUnauthorizedWhileSignIn() {
+        logger.trace("OJElectronicsCloudHandler.handleUnauthorizedWhileSignIn()");
         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                 "Could not sign in. Check user name and password.");
         final RefreshService refreshService = this.refreshService;
@@ -145,6 +172,7 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
     }
 
     private void handleConnectionLost() {
+        logger.trace("OJElectronicsCloudHandler.handleConnectionLost()");
         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
         final RefreshService refreshService = this.refreshService;
         if (refreshService != null) {
@@ -156,4 +184,13 @@ public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler {
     private void restartRefreshServiceAsync(long delayInSeconds) {
         signTask = scheduler.schedule(this::ensureSignIn, delayInSeconds, TimeUnit.SECONDS);
     }
+
+    public void setDiscoveryService(OJDiscoveryService ojDiscoveryService) {
+        this.discoveryService = ojDiscoveryService;
+    }
+
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return Collections.singleton(OJDiscoveryService.class);
+    }
 }
index 7ad55b5c5687c8dbf89a607b2268f8469fa20390..26b68378b614959de4f19d35aac029fabf4e759f 100644 (file)
  */
 package org.openhab.binding.ojelectronics.internal;
 
-import java.time.ZoneId;
 import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.AbstractMap;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Date;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.Map;
 import java.util.function.Consumer;
 
@@ -23,7 +27,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.groups.Thermostat;
+import org.openhab.binding.ojelectronics.internal.models.Thermostat;
+import org.openhab.core.i18n.TimeZoneProvider;
 import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OpenClosedType;
@@ -36,6 +41,8 @@ import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.binding.BaseThingHandler;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * The {@link ThermostatHandler} is responsible for handling commands, which are
@@ -46,19 +53,28 @@ import org.openhab.core.types.RefreshType;
 @NonNullByDefault
 public class ThermostatHandler extends BaseThingHandler {
 
-    private final String serialNumber;
-    private @Nullable Thermostat currentThermostat;
     private static final Map<Integer, String> REGULATION_MODES = createRegulationMap();
+    private static final Map<String, Integer> REVERSE_REGULATION_MODES = createRegulationReverseMap();
+
+    private final String serialNumber;
+    private final Logger logger = LoggerFactory.getLogger(ThermostatHandler.class);
     private final Map<String, Consumer<Thermostat>> channelrefreshActions = createChannelRefreshActionMap();
+    private final Map<String, Consumer<Command>> updateThermostatValueActions = createUpdateThermostatValueActionMap();
+    private final TimeZoneProvider timeZoneProvider;
+
+    private LinkedList<AbstractMap.SimpleImmutableEntry<String, Command>> updatedValues = new LinkedList<>();
+    private @Nullable Thermostat currentThermostat;
 
     /**
      * Creates a new instance of {@link ThermostatHandler}
      *
      * @param thing Thing
+     * @param timeZoneProvider Time zone
      */
-    public ThermostatHandler(Thing thing) {
+    public ThermostatHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
         super(thing);
         serialNumber = getConfigAs(OJElectronicsThermostatConfiguration.class).serialNumber;
+        this.timeZoneProvider = timeZoneProvider;
     }
 
     /**
@@ -78,7 +94,14 @@ public class ThermostatHandler extends BaseThingHandler {
         if (command instanceof RefreshType) {
             final Thermostat thermostat = currentThermostat;
             if (thermostat != null && channelrefreshActions.containsKey(channelUID.getId())) {
-                channelrefreshActions.get(channelUID.getId()).accept(thermostat);
+                final @Nullable Consumer<Thermostat> consumer = channelrefreshActions.get(channelUID.getId());
+                if (consumer != null) {
+                    consumer.accept(thermostat);
+                }
+            }
+        } else {
+            synchronized (this) {
+                updatedValues.add(new AbstractMap.SimpleImmutableEntry<String, Command>(channelUID.getId(), command));
             }
         }
     }
@@ -101,19 +124,65 @@ public class ThermostatHandler extends BaseThingHandler {
         channelrefreshActions.forEach((channelUID, action) -> action.accept(thermostat));
     }
 
+    /**
+     * Gets a {@link Thermostat} with changed values or null if nothing has changed
+     *
+     * @return The changed {@link Thermostat}
+     */
+    public @Nullable Thermostat tryHandleAndGetUpdatedThermostat() {
+        final LinkedList<SimpleImmutableEntry<String, Command>> updatedValues = this.updatedValues;
+        if (updatedValues.size() == 0) {
+            return null;
+        }
+        this.updatedValues = new LinkedList<>();
+        updatedValues.forEach(item -> {
+            if (updateThermostatValueActions.containsKey(item.getKey())) {
+                final @Nullable Consumer<Command> consumer = updateThermostatValueActions.get(item.getKey());
+                if (consumer != null) {
+                    consumer.accept(item.getValue());
+                }
+            }
+        });
+        return currentThermostat;
+    }
+
     private void updateManualSetpoint(Thermostat thermostat) {
         updateState(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT,
                 new QuantityType<Temperature>(thermostat.manualModeSetpoint / (double) 100, SIUnits.CELSIUS));
     }
 
+    private void updateManualSetpoint(Command command) {
+        if (command instanceof QuantityType<?>) {
+            currentThermostat.manualModeSetpoint = (int) (((QuantityType<?>) command).floatValue() * 100);
+        } else {
+            logger.warn("Unable to set value {}", command);
+        }
+    }
+
     private void updateBoostEndTime(Thermostat thermostat) {
-        updateState(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME,
-                new DateTimeType(ZonedDateTime.ofInstant(thermostat.boostEndTime.toInstant(), ZoneId.systemDefault())));
+        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());
+        } else {
+            logger.warn("Unable to set value {}", command);
+        }
     }
 
     private void updateComfortEndTime(Thermostat thermostat) {
         updateState(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, new DateTimeType(
-                ZonedDateTime.ofInstant(thermostat.comfortEndTime.toInstant(), ZoneId.systemDefault())));
+                ZonedDateTime.ofInstant(thermostat.comfortEndTime.toInstant(), timeZoneProvider.getTimeZone())));
+    }
+
+    private void updateComfortEndTime(Command command) {
+        if (command instanceof DateTimeType) {
+            currentThermostat.comfortEndTime = Date.from(((DateTimeType) command).getZonedDateTime().toInstant());
+        } else {
+            logger.warn("Unable to set value {}", command);
+        }
     }
 
     private void updateComfortSetpoint(Thermostat thermostat) {
@@ -121,11 +190,30 @@ public class ThermostatHandler extends BaseThingHandler {
                 new QuantityType<Temperature>(thermostat.comfortSetpoint / (double) 100, SIUnits.CELSIUS));
     }
 
+    private void updateComfortSetpoint(Command command) {
+        if (command instanceof QuantityType<?>) {
+            currentThermostat.comfortSetpoint = (int) (((QuantityType<?>) command).floatValue() * 100);
+        } else {
+            logger.warn("Unable to set value {}", command);
+        }
+    }
+
     private void updateRegulationMode(Thermostat thermostat) {
         updateState(BindingConstants.CHANNEL_OWD5_REGULATIONMODE,
                 StringType.valueOf(getRegulationMode(thermostat.regulationMode)));
     }
 
+    private void updateRegulationMode(Command command) {
+        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;
+            }
+        } else {
+            logger.warn("Unable to set value {}", command);
+        }
+    }
+
     private void updateThermostatName(Thermostat thermostat) {
         updateState(BindingConstants.CHANNEL_OWD5_THERMOSTATNAME, StringType.valueOf(thermostat.thermostatName));
     }
@@ -158,6 +246,43 @@ public class ThermostatHandler extends BaseThingHandler {
         updateState(BindingConstants.CHANNEL_OWD5_GROUPNAME, StringType.valueOf(thermostat.groupName));
     }
 
+    private void updateVacationEnabled(Thermostat thermostat) {
+        updateState(BindingConstants.CHANNEL_OWD5_VACATIONENABLED,
+                thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
+    }
+
+    private void updateVacationBeginDay(Thermostat thermostat) {
+        updateState(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY,
+                new DateTimeType(
+                        ZonedDateTime.ofInstant(thermostat.vacationBeginDay.toInstant(), timeZoneProvider.getTimeZone())
+                                .truncatedTo(ChronoUnit.DAYS)));
+    }
+
+    private void updateVacationBeginDay(Command command) {
+        if (command instanceof DateTimeType) {
+            currentThermostat.vacationBeginDay = Date
+                    .from(((DateTimeType) command).getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS));
+        } else {
+            logger.warn("Unable to set value {}", command);
+        }
+    }
+
+    private void updateVacationEndDay(Thermostat thermostat) {
+        updateState(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY,
+                new DateTimeType(
+                        ZonedDateTime.ofInstant(thermostat.vacationEndDay.toInstant(), timeZoneProvider.getTimeZone())
+                                .truncatedTo(ChronoUnit.DAYS)));
+    }
+
+    private void updateVacationEndDay(Command command) {
+        if (command instanceof DateTimeType) {
+            currentThermostat.vacationEndDay = Date
+                    .from(((DateTimeType) command).getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS));
+        } else {
+            logger.warn("Unable to set value {}", command);
+        }
+    }
+
     private @Nullable String getRegulationMode(int regulationMode) {
         return REGULATION_MODES.get(regulationMode);
     }
@@ -174,6 +299,18 @@ public class ThermostatHandler extends BaseThingHandler {
         return map;
     };
 
+    private static Map<String, Integer> createRegulationReverseMap() {
+        HashMap<String, Integer> map = new HashMap<>();
+        map.put("auto", 1);
+        map.put("comfort", 2);
+        map.put("manual", 3);
+        map.put("vacation", 4);
+        map.put("frostprotection", 6);
+        map.put("boost", 8);
+        map.put("eco", 9);
+        return map;
+    };
+
     private Map<String, Consumer<Thermostat>> createChannelRefreshActionMap() {
         HashMap<String, Consumer<Thermostat>> map = new HashMap<>();
         map.put(BindingConstants.CHANNEL_OWD5_GROUPNAME, this::updateGroupName);
@@ -188,6 +325,21 @@ public class ThermostatHandler extends BaseThingHandler {
         map.put(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, this::updateComfortEndTime);
         map.put(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, this::updateBoostEndTime);
         map.put(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, this::updateManualSetpoint);
+        map.put(BindingConstants.CHANNEL_OWD5_VACATIONENABLED, this::updateVacationEnabled);
+        map.put(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY, this::updateVacationBeginDay);
+        map.put(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY, this::updateVacationEndDay);
+        return map;
+    }
+
+    private Map<String, Consumer<Command>> createUpdateThermostatValueActionMap() {
+        HashMap<String, Consumer<Command>> map = new HashMap<>();
+        map.put(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, this::updateRegulationMode);
+        map.put(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, this::updateManualSetpoint);
+        map.put(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, this::updateBoostEndTime);
+        map.put(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, this::updateComfortEndTime);
+        map.put(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT, this::updateComfortSetpoint);
+        map.put(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY, this::updateVacationBeginDay);
+        map.put(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY, this::updateVacationEndDay);
         return map;
     }
 }
index 0a7abbe5e30f7ce944b02045d3a1b172de102f9e..70843e20a583b46805b4cb64a7df64cd9a869fab 100644 (file)
@@ -19,12 +19,15 @@ import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.i18n.TimeZoneProvider;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.binding.BaseThingHandlerFactory;
 import org.openhab.core.thing.binding.ThingHandler;
 import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
 
 /**
  * The {@link ThermostatHandlerFactory} is responsible for creating {@link OJElectronicsThermostatHandler}.
@@ -36,6 +39,17 @@ import org.osgi.service.component.annotations.Component;
 public class ThermostatHandlerFactory extends BaseThingHandlerFactory {
 
     private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OWD5);
+    private final TimeZoneProvider timeZoneProvider;
+
+    /**
+     * Creates a new factory
+     *
+     * @param httpClientFactory Factory for HttpClient
+     */
+    @Activate
+    public ThermostatHandlerFactory(@Reference TimeZoneProvider timeZoneProvider) {
+        this.timeZoneProvider = timeZoneProvider;
+    }
 
     /**
      * Supported things of this factory.
@@ -50,7 +64,7 @@ public class ThermostatHandlerFactory extends BaseThingHandlerFactory {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
 
         if (THING_TYPE_OWD5.equals(thingTypeUID)) {
-            return new ThermostatHandler(thing);
+            return new ThermostatHandler(thing, timeZoneProvider);
         }
 
         return null;
diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/common/OJGSonBuilder.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/common/OJGSonBuilder.java
new file mode 100644 (file)
index 0000000..acae43d
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010-2020 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 com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Builder for Gson
+ *
+ * @author Christian Kittel - Initial Contribution
+ */
+@NonNullByDefault
+public final class OJGSonBuilder {
+
+    /**
+     * Gets a correct initialized {@link Gson}
+     *
+     * @return {@link GSon}
+     */
+    public static Gson getGSon() {
+        return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).setPrettyPrinting()
+                .setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create();
+    }
+}
diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/RequestModelBase.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/RequestModelBase.java
new file mode 100644 (file)
index 0000000..114f4ad
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Base model for all requests
+ *
+ * @author Christian Kittel - Initial contribution
+ */
+@NonNullByDefault
+public abstract class RequestModelBase {
+
+    @SerializedName("APIKEY")
+    public String apiKey = "";
+
+    /**
+     * Add API-Key
+     *
+     * @param apiKey API-Key
+     * @return Model
+     */
+    public RequestModelBase withApiKey(String apiKey) {
+        this.apiKey = apiKey;
+        return this;
+    }
+}
diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/ResponseModelBase.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/ResponseModelBase.java
new file mode 100644 (file)
index 0000000..6abf160
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Base model for all responses
+ *
+ * @author Christian Kittel - Initial contribution
+ */
+@NonNullByDefault
+public abstract class ResponseModelBase {
+
+    public int errorCode;
+}
diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/SimpleResponseModel.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/SimpleResponseModel.java
new file mode 100644 (file)
index 0000000..3b24017
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Response model without additional properties
+ *
+ * @author Christian Kittel - Initial contribution
+ */
+@NonNullByDefault
+public class SimpleResponseModel extends ResponseModelBase {
+}
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.java
new file mode 100644 (file)
index 0000000..e194cd6
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2010-2020 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.Date;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.ojelectronics.internal.models.groups.Schedule;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Model for a thermostat
+ *
+ * @author Christian Kittel - Initial contribution
+ */
+@NonNullByDefault
+public class Thermostat {
+
+    public int id;
+
+    public int action;
+
+    public String serialNumber = "";
+
+    public String groupName = "";
+
+    public int groupId;
+
+    public int customerId;
+
+    @SerializedName("SWversion")
+    public String softwareVersion = "";
+
+    public boolean online;
+
+    public boolean heating;
+
+    public int roomTemperature;
+
+    public int floorTemperature;
+
+    public int regulationMode;
+
+    public @Nullable Schedule schedule;
+
+    public int comfortSetpoint;
+
+    public Date comfortEndTime = new Date();
+
+    public int manualModeSetpoint;
+
+    public boolean vacationEnabled;
+
+    public Date vacationBeginDay = new Date();
+
+    public Date vacationEndDay = new Date();
+
+    public int vacationTemperature;
+
+    public boolean lastPrimaryModeIsAuto;
+
+    public Date boostEndTime = new Date();
+
+    public int frostProtectionTemperature;
+
+    public int errorCode;
+
+    public String thermostatName = "";
+
+    public boolean openWindow;
+
+    public boolean adaptiveMode;
+
+    public boolean daylightSaving;
+
+    public int sensorAppl;
+
+    public int minSetpoint;
+
+    public int maxSetpoint;
+
+    public int timeZone;
+
+    public boolean daylightSavingActive;
+
+    public int floorType;
+}
index 78fdb1c6500ae4d76d749787f529fa90554a341e..4e4325c1a5a9f2e15f9de8ce68d8063b37c54941 100644 (file)
@@ -17,6 +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;
 
 /**
  * Model for content of a group
index bd9aa9429f912f955cb01a11f0b011fea2cec2b2..fb80e7503935db2849688d7fd675c8e9921a3f18 100644 (file)
@@ -16,6 +16,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.ojelectronics.internal.models.ResponseModelBase;
 
 /**
  * Model for the response of a content group
@@ -23,9 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
  * @author Christian Kittel - Initial contribution
  */
 @NonNullByDefault
-public class GroupContentResponseModel {
+public class GroupContentResponseModel extends ResponseModelBase {
 
     public List<GroupContent> groupContents = new ArrayList<GroupContent>();
-
-    public int errorCode;
 }
diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Thermostat.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Thermostat.java
deleted file mode 100644 (file)
index e08132c..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * Copyright (c) 2010-2020 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.groups;
-
-import java.util.Date;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * Model for a thermostat
- *
- * @author Christian Kittel - Initial contribution
- */
-@NonNullByDefault
-public class Thermostat {
-
-    public int id;
-
-    public int action;
-
-    public String serialNumber = "";
-
-    public String groupName = "";
-
-    public int groupId;
-
-    public int customerId;
-
-    @SerializedName("SWversion")
-    public String softwareVersion = "";
-
-    public boolean online;
-
-    public boolean heating;
-
-    public int roomTemperature;
-
-    public int floorTemperature;
-
-    public int regulationMode;
-
-    public @Nullable Schedule schedule;
-
-    public int comfortSetpoint;
-
-    public Date comfortEndTime = new Date();
-
-    public int manualModeSetpoint;
-
-    public boolean vacationEnabled;
-
-    public Date vacationBeginDay = new Date();
-
-    public Date vacationEndDay = new Date();
-
-    public int vacationTemperature;
-
-    public boolean lastPrimaryModeIsAuto;
-
-    public Date boostEndTime = new Date();
-
-    public int frostProtectionTemperature;
-
-    public int errorCode;
-
-    public String thermostatName = "";
-
-    public boolean openWindow;
-
-    public boolean adaptiveMode;
-
-    public boolean daylightSaving;
-
-    public int sensorAppl;
-
-    public int minSetpoint;
-
-    public int maxSetpoint;
-
-    public int timeZone;
-
-    public boolean daylightSavingActive;
-
-    public int floorType;
-}
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
new file mode 100644 (file)
index 0000000..62585ad
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2020 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;
+import org.openhab.binding.ojelectronics.internal.models.RequestModelBase;
+import org.openhab.binding.ojelectronics.internal.models.Thermostat;
+
+/**
+ * Model for updating a thermostat
+ *
+ * @author Christian Kittel - Initial contribution
+ */
+@NonNullByDefault
+public class UpdateThermostatRequestModel extends RequestModelBase {
+
+    public UpdateThermostatRequestModel(Thermostat thermostat) {
+        setThermostat = thermostat;
+        thermostatID = thermostat.serialNumber;
+    }
+
+    public Thermostat setThermostat;
+
+    public String thermostatID;
+}
index 1c606ae4eb5351a78c963aea6e17fcd6ce08a98d..abbfd3f3e066723a715e39d39754de9152820de9 100644 (file)
@@ -13,8 +13,7 @@
 package org.openhab.binding.ojelectronics.internal.models.userprofile;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-
-import com.google.gson.annotations.SerializedName;
+import org.openhab.binding.ojelectronics.internal.models.RequestModelBase;
 
 /**
  * Model for signing sin
@@ -22,10 +21,7 @@ import com.google.gson.annotations.SerializedName;
  * @author Christian Kittel - Initial contribution
  */
 @NonNullByDefault
-public class PostSignInQueryModel {
-
-    @SerializedName("APIKEY")
-    public String apiKey = "";
+public class PostSignInQueryModel extends RequestModelBase {
 
     public String userName = "";
 
@@ -35,17 +31,6 @@ public class PostSignInQueryModel {
 
     public int clientSWVersion;
 
-    /**
-     * Add API-Key
-     *
-     * @param apiKey API-Key
-     * @return Model
-     */
-    public PostSignInQueryModel withApiKey(String apiKey) {
-        this.apiKey = apiKey;
-        return this;
-    }
-
     /**
      * Add User-Name
      *
index 334a5791c0af49fc9030370639ae66cb61f868aa..60f5c92c2ba2ae3a3babd802486e0f5398ff529c 100644 (file)
@@ -13,6 +13,7 @@
 package org.openhab.binding.ojelectronics.internal.models.userprofile;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.ojelectronics.internal.models.ResponseModelBase;
 
 /**
  * Response-Model after signing in
@@ -20,26 +21,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
  * @author Christian Kittel - Initial Contribution
  */
 @NonNullByDefault
-public class PostSignInResponseModel {
+public class PostSignInResponseModel extends ResponseModelBase {
 
     public String sessionId = "";
 
     public String userName = "";
-
-    public int errorCode;
-
-    public PostSignInResponseModel withSessionId(String sessionId) {
-        this.sessionId = sessionId;
-        return this;
-    }
-
-    public PostSignInResponseModel withUserName(String userName) {
-        this.userName = userName;
-        return this;
-    }
-
-    public PostSignInResponseModel withErrorCode(int errorCode) {
-        this.errorCode = errorCode;
-        return this;
-    }
 }
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
new file mode 100644 (file)
index 0000000..ab1372e
--- /dev/null
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2010-2020 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 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.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * DiscoveryService for OJ Components
+ *
+ * @author Christian Kittel - Initial Contribution
+ */
+@NonNullByDefault
+@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.ojelectronics")
+public final class OJDiscoveryService extends AbstractDiscoveryService
+        implements DiscoveryService, ThingHandlerService {
+
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OJCLOUD);
+    private @Nullable OJCloudHandler bridgeHandler;
+    private @Nullable Collection<GroupContent> groupContents;
+
+    /**
+     * Creates a new instance of {@link OJDiscoveryService}
+     *
+     */
+    public OJDiscoveryService() throws IllegalArgumentException {
+        super(SUPPORTED_THING_TYPES_UIDS, 10);
+    }
+
+    /**
+     * Sets the scan result for discovering
+     *
+     * @param groupContents Content from API
+     */
+    public void setScanResultForDiscovery(List<GroupContent> groupContents) {
+        this.groupContents = groupContents;
+    }
+
+    @Override
+    protected void startScan() {
+        final OJCloudHandler bridgeHandler = this.bridgeHandler;
+        final Collection<GroupContent> groupContents = this.groupContents;
+        if (groupContents != null && bridgeHandler != null) {
+            groupContents.stream().flatMap(content -> content.thermostats.stream())
+                    .forEach(thermostat -> thingDiscovered(bridgeHandler.getThing().getUID(), thermostat.serialNumber));
+        }
+    }
+
+    @Override
+    public void setThingHandler(@Nullable ThingHandler handler) {
+        if (handler instanceof OJCloudHandler) {
+            final OJCloudHandler bridgeHandler = (OJCloudHandler) handler;
+            this.bridgeHandler = bridgeHandler;
+            bridgeHandler.setDiscoveryService(this);
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return bridgeHandler;
+    }
+
+    @Override
+    public void deactivate() {
+        super.deactivate();
+    }
+
+    private void thingDiscovered(ThingUID bridgeUID, String serialNumber) {
+        thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_OWD5, bridgeUID, serialNumber))
+                .withBridge(bridgeUID).withRepresentationProperty("serialNumber")
+                .withProperty("serialNumber", serialNumber).withLabel("Thermostat " + serialNumber).build());
+    }
+}
index e1fe77c913527e5a841724f6c2afd552ce2d46de..6ba6be20af6701d2078fa4c923f107750189b6a7 100644 (file)
@@ -16,8 +16,8 @@ import java.util.List;
 
 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.Thermostat;
 import org.openhab.core.thing.Thing;
 
 /**
index fa489152a75b52fcfe9efa871842ecb65bd7beea..838822b653a0ad30664c96cde6cbc5a4e0aaa440 100644 (file)
@@ -25,14 +25,13 @@ import org.eclipse.jetty.client.api.Result;
 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.config.OJElectronicsBridgeConfiguration;
 import org.openhab.binding.ojelectronics.internal.models.groups.GroupContentResponseModel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
 import com.google.gson.JsonSyntaxException;
 
 /**
@@ -46,7 +45,7 @@ public final class RefreshService implements AutoCloseable {
     private final OJElectronicsBridgeConfiguration config;
     private final Logger logger = LoggerFactory.getLogger(RefreshService.class);
     private final HttpClient httpClient;
-    private final Gson gson = createGson();
+    private final Gson gson = OJGSonBuilder.getGSon();
 
     private final ScheduledExecutorService schedulerService;
 
@@ -62,6 +61,7 @@ public final class RefreshService implements AutoCloseable {
      *
      * @param config Configuration of the bridge
      * @param httpClient HTTP client
+     * @param updateService Service to update the thermostat in the cloud
      */
     public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient,
             ScheduledExecutorService schedulerService) {
@@ -103,11 +103,6 @@ public final class RefreshService implements AutoCloseable {
         this.scheduler = null;
     }
 
-    private Gson createGson() {
-        return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).setPrettyPrinting()
-                .setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create();
-    }
-
     private void refresh() {
         final String sessionId = this.sessionId;
         if (sessionId == null) {
@@ -124,6 +119,8 @@ public final class RefreshService implements AutoCloseable {
                         if (unauthorized != null) {
                             unauthorized.run();
                         }
+                    } else if (result.getResponse().getStatus() == HttpStatus.FORBIDDEN_403) {
+                        handleConnectionLost();
                     } else {
                         handleRefreshDone(getContentAsString());
                     }
index 8897fc8d416bf3a2448d6dd74505e42317976edb..ab36f88801b9edba6527e121aea7ae73ea901824 100644 (file)
@@ -22,13 +22,13 @@ import org.eclipse.jetty.client.api.Result;
 import org.eclipse.jetty.client.util.BufferingResponseListener;
 import org.eclipse.jetty.client.util.StringContentProvider;
 import org.eclipse.jetty.http.HttpHeader;
+import org.openhab.binding.ojelectronics.internal.common.OJGSonBuilder;
 import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConfiguration;
+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 com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
 
 /**
  * Handles the sign in process.
@@ -38,7 +38,7 @@ import com.google.gson.GsonBuilder;
 @NonNullByDefault
 public class SignInService {
 
-    private final Gson gson = createGson();
+    private final Gson gson = OJGSonBuilder.getGSon();
 
     private final HttpClient httpClient;
     private final OJElectronicsBridgeConfiguration config;
@@ -79,7 +79,7 @@ public class SignInService {
                 }
                 PostSignInResponseModel signInModel = gson.fromJson(getContentAsString(),
                         PostSignInResponseModel.class);
-                if (signInModel.errorCode != 0 || signInModel.sessionId.equals("")) {
+                if (signInModel == null || signInModel.errorCode != 0 || signInModel.sessionId.equals("")) {
                     unauthorized.run();
                     return;
                 }
@@ -88,12 +88,8 @@ public class SignInService {
         });
     }
 
-    private Gson createGson() {
-        return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).setPrettyPrinting().create();
-    }
-
-    private PostSignInQueryModel getPostSignInQueryModel() {
-        return new PostSignInQueryModel().withApiKey(config.apiKey).withClientSWVersion(config.softwareVersion)
-                .withCustomerId(config.customerId).withUserName(config.userName).withPassword(config.password);
+    private RequestModelBase getPostSignInQueryModel() {
+        return new PostSignInQueryModel().withClientSWVersion(config.softwareVersion).withCustomerId(config.customerId)
+                .withUserName(config.userName).withPassword(config.password).withApiKey(config.apiKey);
     }
 }
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
new file mode 100644 (file)
index 0000000..0e70d3f
--- /dev/null
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2010-2020 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.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+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.UpdateThermostatRequestModel;
+import org.openhab.core.thing.Thing;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+/**
+ * Handles the update of the devices of a session
+ *
+ * @author Christian Kittel - Initial Contribution
+ */
+@NonNullByDefault
+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;
+
+    public UpdateService(OJElectronicsBridgeConfiguration configuration, HttpClient httpClient, String sessionId) {
+        this.configuration = configuration;
+        this.httpClient = httpClient;
+        this.sessionId = sessionId;
+    }
+
+    /**
+     * Sends all changes of all {@link ThermostatHandler} to the API
+     *
+     * @param things
+     */
+    public void updateAllThermostats(List<Thing> things) {
+        things.stream().filter(thing -> thing.getHandler() instanceof ThermostatHandler)
+                .map(thing -> (ThermostatHandler) thing.getHandler())
+                .map(handler -> handler.tryHandleAndGetUpdatedThermostat()).forEach(this::updateThermostat);
+    }
+
+    private void updateThermostat(@Nullable Thermostat thermostat) {
+        if (thermostat == null) {
+            return;
+        }
+        Request request = httpClient.POST(configuration.apiUrl + "/Thermostat/UpdateThermostat")
+                .param("sessionid", sessionId).header(HttpHeader.CONTENT_TYPE, "application/json")
+                .content(new StringContentProvider(
+                        gson.toJson(new UpdateThermostatRequestModel(thermostat).withApiKey(configuration.apiKey))));
+
+        request.send(new BufferingResponseListener() {
+            @Override
+            public void onComplete(@Nullable Result result) {
+                if (result != null) {
+                    logger.trace("onComplete {}", result);
+                    if (result.isFailed()) {
+                        logger.warn("updateThermostat failed {}", thermostat);
+                    }
+                    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);
+                    }
+                }
+            }
+        });
+    }
+}
index f39dae1806f73e049ef871171793dcfc2b2704d1..eade32637a20a7d4fec728a659c44627017907d6 100644 (file)
@@ -36,3 +36,5 @@ channel-type.ojelectronics.comfortEndTime.label = Komfort-Endzeit
 channel-type.ojelectronics.boostEndTime.label = Boost Endzeit
 channel-type.ojelectronics.manualSetpoint.label = manuelle Endzeit
 channel-type.ojelectronics.vacationEnabled.label = Urlausbmodus aktiviert
+channel-type.ojelectronics.vacationBeginDay.label = Start des Urlausbmodus
+channel-type.ojelectronics.vacationEndDay.label = Ende des Urlausbmodus
index f253b3177b8f128d54b1116a329bc469dc7e4fa7..ba64ef5d99f59acbdc7bb354b40352b07bc6ebd8 100644 (file)
                        <channel id="boostEndTime" typeId="boostEndTime"/>
                        <channel id="manualSetpoint" typeId="manualSetpoint"/>
                        <channel id="vacationEnabled" typeId="vacationEnabled"/>
+                       <channel id="vacationBeginDay" typeId="vacationBeginDay"/>
+                       <channel id="vacationEndDay" typeId="vacationEndDay"/>
                </channels>
                <properties>
                        <property name="vendor">OJ Electronics</property>
                </properties>
+               <representation-property>serialNumber</representation-property>
                <config-description>
                        <parameter name="serialNumber" type="text" required="true">
                                <label>Serial Number</label>
        <channel-type id="regulationMode">
                <item-type>String</item-type>
                <label>Regulation Mode</label>
-               <state readOnly="true">
+               <state>
                        <options>
                                <option value="auto">Auto</option>
                                <option value="comfort">Comfort</option>
                <item-type>Number:Temperature</item-type>
                <label>Comfort Set Point Temperature</label>
                <category>Temperature</category>
-               <state pattern="%.1f %unit%" readOnly="true"/>
+               <state pattern="%.1f %unit%"/>
        </channel-type>
        <channel-type id="comfortEndTime">
                <item-type>DateTime</item-type>
                <label>End Time of Comfort Mode</label>
-               <state readOnly="true"/>
        </channel-type>
        <channel-type id="boostEndTime">
                <item-type>DateTime</item-type>
                <label>End Time of Boost Mode</label>
-               <state readOnly="true"/>
        </channel-type>
        <channel-type id="manualSetpoint">
                <item-type>Number:Temperature</item-type>
                <label>Manual Set Point Temperature</label>
                <category>Temperature</category>
-               <state pattern="%.1f %unit%" readOnly="true"/>
+               <state pattern="%.1f %unit%"/>
        </channel-type>
        <channel-type id="vacationEnabled">
                <item-type>Switch</item-type>
                <label>Vacation Mode Enabled</label>
                <state readOnly="true"/>
        </channel-type>
+       <channel-type id="vacationBeginDay">
+               <item-type>Switch</item-type>
+               <label>Vacation Begin Day</label>
+       </channel-type>
+       <channel-type id="vacationEndDay">
+               <item-type>Switch</item-type>
+               <label>Vacation End Day</label>
+       </channel-type>
 </thing:thing-descriptions>