# 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
## 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)
| 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
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";
}
*/
package org.openhab.binding.ojelectronics.internal;
+import java.util.Collection;
+import java.util.Collections;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
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;
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;
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);
}
}
+ 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) {
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();
}
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;
}
private void handleConnectionLost() {
+ logger.trace("OJElectronicsCloudHandler.handleConnectionLost()");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
final RefreshService refreshService = this.refreshService;
if (refreshService != null) {
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);
+ }
}
*/
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;
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;
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
@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;
}
/**
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));
}
}
}
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) {
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));
}
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);
}
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);
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;
}
}
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}.
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.
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_OWD5.equals(thingTypeUID)) {
- return new ThermostatHandler(thing);
+ return new ThermostatHandler(thing, timeZoneProvider);
}
return null;
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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 {
+}
--- /dev/null
+/**
+ * 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;
+}
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
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
* @author Christian Kittel - Initial contribution
*/
@NonNullByDefault
-public class GroupContentResponseModel {
+public class GroupContentResponseModel extends ResponseModelBase {
public List<GroupContent> groupContents = new ArrayList<GroupContent>();
-
- public int errorCode;
}
+++ /dev/null
-/**
- * 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;
-}
--- /dev/null
+/**
+ * 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;
+}
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
* @author Christian Kittel - Initial contribution
*/
@NonNullByDefault
-public class PostSignInQueryModel {
-
- @SerializedName("APIKEY")
- public String apiKey = "";
+public class PostSignInQueryModel extends RequestModelBase {
public String userName = "";
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
*
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
* @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;
- }
}
--- /dev/null
+/**
+ * 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());
+ }
+}
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;
/**
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;
/**
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;
*
* @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) {
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) {
if (unauthorized != null) {
unauthorized.run();
}
+ } else if (result.getResponse().getStatus() == HttpStatus.FORBIDDEN_403) {
+ handleConnectionLost();
} else {
handleRefreshDone(getContentAsString());
}
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.
@NonNullByDefault
public class SignInService {
- private final Gson gson = createGson();
+ private final Gson gson = OJGSonBuilder.getGSon();
private final HttpClient httpClient;
private final OJElectronicsBridgeConfiguration config;
}
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;
}
});
}
- 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);
}
}
--- /dev/null
+/**
+ * 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);
+ }
+ }
+ }
+ });
+ }
+}
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
<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>