diff --git a/CODEOWNERS b/CODEOWNERS index e129d743c8bf3..9e60ad6b66254 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -147,6 +147,7 @@ /bundles/org.openhab.binding.ntp/ @marcelrv /bundles/org.openhab.binding.nuki/ @mkatter /bundles/org.openhab.binding.oceanic/ @kgoderis +/bundles/org.openhab.binding.ojelectronics/ @EvilPingu /bundles/org.openhab.binding.omnikinverter/ @hansbogert /bundles/org.openhab.binding.onebusaway/ @sdwilsh /bundles/org.openhab.binding.onewiregpio/ @aogorek diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index c210a5b03f7a5..37c5b8dbaea60 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -724,6 +724,11 @@ org.openhab.binding.oceanic ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.ojelectronics + ${project.version} + org.openhab.addons.bundles org.openhab.binding.omnikinverter diff --git a/bundles/org.openhab.binding.ojelectronics/.classpath b/bundles/org.openhab.binding.ojelectronics/.classpath new file mode 100644 index 0000000000000..615608997a6c5 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.ojelectronics/.project b/bundles/org.openhab.binding.ojelectronics/.project new file mode 100644 index 0000000000000..c0196c4e93bbf --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/.project @@ -0,0 +1,23 @@ + + + org.openhab.binding.ojelectronics + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bundles/org.openhab.binding.ojelectronics/NOTICE b/bundles/org.openhab.binding.ojelectronics/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.ojelectronics/README.md b/bundles/org.openhab.binding.ojelectronics/README.md new file mode 100644 index 0000000000000..43e1234e13293 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/README.md @@ -0,0 +1,86 @@ +# 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. + +## Supported Things + +There are two things: + +| Thing | Type | Description | +|----------------------|--------|-------------------------------------| +| ojcloud | Bridge | OJ Electronics Cloud Connector | +| owd5 | Thing | OJ Electronics OWD5/MWD5 Thermostat | + +## Discovery + +Not supported at the moment. + +## Thing Configuration + +### OJ Electronics Bridge configuration (ojcloud) + +| Parameter | Description | +|-----------------------|--------------------------------------------------------------------------| +| userName | user name from the OJElectronics App (required) | +| password | password from the OJElectronics App (required) | +| apiKey | API key. You get the key from your local distributor. | +| apiUrl | URL of the API endpoint. Optional, the default value should always work. | +| refreshDelayInSeconds | Refresh interval in seconds. Optional, the default value is 30 seconds. | +| customerId | Customer ID. Optional, the default value should always work. | +| softwareVersion | Software version. Optional, the default value should always work. | + +### OJ Electronics OWD5/MWD5 Thermostat configuration (owd5) + +| Parameter | Description | +|-----------------------|--------------------------------------------------------------------------| +| serialNumber | serial number from the OJElectronics App or the thermostat (required) | + +## Channels + +| Channel | Type | Description | +|--------------------|--------------------|------------------------------------------------------------------------------------| +| floorTemperature | Number:Temperature | Floor temperature | +| groupName | Text | Group name | +| groupId | Number | Group Id | +| online | Contact | Online | +| heating | Contact | Heating | +| roomTemperature | Number:Temperature | Room temperature | +| thermostatName | Text | Thermostat name | +| regulationMode | Text | Regulation mode | +| serialNumber | Text | Serial number | +| comfortSetpoint | Number:Temperature | Target comfort temperature | +| 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 | + +## Example + +This example shows how to configure the OJElecttronics binding. + +### demo.things + +``` +Binding ojelectronics:ojcloud:myCloud "My Cloud" @ "My Home" [ userName="MyUserName" password="MyPassword" apiKey="The Key" ] { + Thing owd5 myThermostat [ serialNumber="123" ] +} +``` + +### demo.items + +``` +Number Bath_Floor_Temperature "Bathroom: Floor Temperature" {channel="ojelectronics:owd5:myThermostat:floorTemperature"} +String Bath_Mode "Bathroom: Mode" {channel="ojelectronics:owd5:myThermostat:regulationMode"} +``` + +### demo.sitemap + +``` +sitemap myHome label="my Home"{ + Text item=Bath_Floor_Temperature + Text item=Bath_Mode +} +``` + diff --git a/bundles/org.openhab.binding.ojelectronics/pom.xml b/bundles/org.openhab.binding.ojelectronics/pom.xml new file mode 100644 index 0000000000000..add24ff8da2ae --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/pom.xml @@ -0,0 +1,16 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 2.5.7-SNAPSHOT + + + org.openhab.binding.ojelectronics + + openHAB Add-ons :: Bundles :: OJElectronics Binding + diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/feature/feature.xml b/bundles/org.openhab.binding.ojelectronics/src/main/feature/feature.xml new file mode 100644 index 0000000000000..1bda37db28d3c --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.ojelectronics/${project.version} + + diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/BindingConstants.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/BindingConstants.java new file mode 100644 index 0000000000000..2a6a59c7506dc --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/BindingConstants.java @@ -0,0 +1,47 @@ +/** + * 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; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.thing.ThingTypeUID; + +/** + * The {@link OJElectronicsBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class BindingConstants { + + private static final String BINDING_ID = "ojelectronics"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_OJCLOUD = new ThingTypeUID(BINDING_ID, "ojcloud"); + public static final ThingTypeUID THING_TYPE_OWD5 = new ThingTypeUID(BINDING_ID, "owd5"); + + // List of all Channel ids + public static final String CHANNEL_OWD5_FLOORTEMPERATURE = "floorTemperature"; + public static final String CHANNEL_OWD5_GROUPNAME = "groupName"; + public static final String CHANNEL_OWD5_GROUPID = "groupId"; + public static final String CHANNEL_OWD5_ONLINE = "online"; + public static final String CHANNEL_OWD5_HEATING = "heating"; + public static final String CHANNEL_OWD5_ROOMTEMPERATURE = "roomTemperature"; + public static final String CHANNEL_OWD5_THERMOSTATNAME = "thermostatName"; + public static final String CHANNEL_OWD5_REGULATIONMODE = "regulationMode"; + public static final String CHANNEL_OWD5_COMFORTSETPOINT = "comfortSetpoint"; + public static final String CHANNEL_OWD5_COMFORTENDTIME = "comfortEndTime"; + public static final String CHANNEL_OWD5_BOOSTENDTIME = "boostEndTime"; + public static final String CHANNEL_OWD5_MANUALSETPOINT = "manualSetpoint"; + public static final String CHANNEL_OWD5_VACATIONENABLED = "vacationEnabled"; +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandler.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandler.java new file mode 100644 index 0000000000000..0a1f4ae60456c --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandler.java @@ -0,0 +1,159 @@ +/** + * 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; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler; +import org.eclipse.smarthome.core.thing.binding.BridgeHandler; +import org.eclipse.smarthome.core.types.Command; +import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConfiguration; +import org.openhab.binding.ojelectronics.internal.models.groups.GroupContentResponseModel; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles all traffic with OJ Electronics cloud + * + * @author Christian Kittel - Initial Contribution + */ +@NonNullByDefault +public class OJCloudHandler extends BaseBridgeHandler implements BridgeHandler { + + private final Logger logger = LoggerFactory.getLogger(OJCloudHandler.class); + private final HttpClient httpClient; + + private @Nullable RefreshService refreshService; + private @Nullable SignInService signInService; + private OJElectronicsBridgeConfiguration configuration; + private @Nullable ScheduledFuture signTask; + + public OJCloudHandler(Bridge bridge, HttpClient httpClient) { + super(bridge); + this.httpClient = httpClient; + this.configuration = new OJElectronicsBridgeConfiguration(); + } + + /** + * Initializes the binding. + */ + @Override + public void initialize() { + configuration = getConfigAs(OJElectronicsBridgeConfiguration.class); + ensureSignIn(); + } + + /** + * Disposes the binding. + */ + @Override + public void dispose() { + final RefreshService refreshService = this.refreshService; + if (refreshService != null) { + refreshService.stop(); + } + final ScheduledFuture signTask = this.signTask; + if (signTask != null) { + signTask.cancel(true); + } + this.refreshService = null; + signInService = null; + super.dispose(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } + + private void ensureSignIn() { + if (signInService == null) { + signInService = new SignInService(configuration, httpClient); + } + final SignInService signInService = this.signInService; + if (signInService != null) { + signInService.signIn(this::handleSignInDone, this::handleConnectionLost, + this::handleUnauthorizedWhileSignIn); + } + } + + 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(); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + (errorMessage == null) ? "Wrong or no result model; Refreshing stoppped" : errorMessage); + final RefreshService refreshService = this.refreshService; + if (refreshService != null) { + refreshService.stop(); + } + } + } + + private void handleSignInDone(String sessionId) { + logger.trace("OJElectronicsCloudHandler.handleSignInDone({})", sessionId); + if (refreshService == null) { + refreshService = new RefreshService(configuration, httpClient, scheduler); + } + final RefreshService refreshService = this.refreshService; + if (refreshService != null) { + refreshService.start(sessionId, this::handleRefreshDone, this::handleConnectionLost, + this::handleUnauthorized); + + updateStatus(ThingStatus.ONLINE); + } + } + + private void handleUnauthorized() { + final RefreshService refreshService = this.refreshService; + if (refreshService != null) { + refreshService.stop(); + } + restartRefreshServiceAsync(1); + } + + private void handleUnauthorizedWhileSignIn() { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Could not sign in. Check user name and password."); + final RefreshService refreshService = this.refreshService; + if (refreshService != null) { + refreshService.stop(); + } + } + + private void handleConnectionLost() { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + final RefreshService refreshService = this.refreshService; + if (refreshService != null) { + refreshService.stop(); + } + restartRefreshServiceAsync(configuration.refreshDelayInSeconds); + } + + private void restartRefreshServiceAsync(long delayInSeconds) { + signTask = scheduler.schedule(this::ensureSignIn, delayInSeconds, TimeUnit.SECONDS); + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandlerFactory.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandlerFactory.java new file mode 100644 index 0000000000000..92f0300afcb93 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/OJCloudHandlerFactory.java @@ -0,0 +1,73 @@ +/** + * 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; + +import static org.openhab.binding.ojelectronics.internal.BindingConstants.THING_TYPE_OJCLOUD; + +import java.util.Collections; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; +import org.eclipse.smarthome.io.net.http.HttpClientFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * Factory to create {@link OJCloudHandler} + * + * @author Christian Kittel - Initial Contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.ojelectronics", service = ThingHandlerFactory.class) +public class OJCloudHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OJCLOUD); + + private final HttpClient httpClient; + + /** + * Creates a new factory + * + * @param httpClientFactory Factory for HttpClient + */ + @Activate + public OJCloudHandlerFactory(@Reference HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + } + + /** + * Supported things for this factory + */ + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + if (SUPPORTED_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) { + OJCloudHandler handler = new OJCloudHandler((Bridge) thing, httpClient); + return handler; + } + return null; + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandler.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandler.java new file mode 100644 index 0000000000000..56553d7b6605f --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandler.java @@ -0,0 +1,193 @@ +/** + * 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; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.DateTimeType; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.OpenClosedType; +import org.eclipse.smarthome.core.library.types.QuantityType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.library.unit.SIUnits; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.RefreshType; +import org.openhab.binding.ojelectronics.internal.config.OJElectronicsThermostatConfiguration; +import org.openhab.binding.ojelectronics.internal.models.groups.Thermostat; + +/** + * The {@link ThermostatHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class ThermostatHandler extends BaseThingHandler { + + private final String serialNumber; + private @Nullable Thermostat currentThermostat; + private static final Map REGULATION_MODES = createRegulationMap(); + private final Map> channelrefreshActions = createChannelRefreshActionMap(); + + /** + * Creates a new instance of {@link ThermostatHandler} + * + * @param thing Thing + */ + public ThermostatHandler(Thing thing) { + super(thing); + serialNumber = getConfigAs(OJElectronicsThermostatConfiguration.class).serialNumber; + } + + /** + * Gets the thing's serial number. + * + * @return serial number + */ + public String getSerialNumber() { + return serialNumber; + } + + /** + * Handles commands to this thing. + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + final Thermostat thermostat = currentThermostat; + if (thermostat != null && channelrefreshActions.containsKey(channelUID.getId())) { + channelrefreshActions.get(channelUID.getId()).accept(thermostat); + } + } + } + + /** + * Initializes the thing handler. + */ + @Override + public void initialize() { + updateStatus(ThingStatus.ONLINE); + } + + /** + * Sets the values after refreshing the thermostats values + * + * @param thermostat thermostat values + */ + public void handleThermostatRefresh(Thermostat thermostat) { + currentThermostat = thermostat; + channelrefreshActions.forEach((channelUID, action) -> action.accept(thermostat)); + } + + private void updateManualSetpoint(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, + new QuantityType(thermostat.manualModeSetpoint / (double) 100, SIUnits.CELSIUS)); + } + + private void updateBoostEndTime(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, + new DateTimeType(ZonedDateTime.ofInstant(thermostat.boostEndTime.toInstant(), ZoneId.systemDefault()))); + } + + private void updateComfortEndTime(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, new DateTimeType( + ZonedDateTime.ofInstant(thermostat.comfortEndTime.toInstant(), ZoneId.systemDefault()))); + } + + private void updateComfortSetpoint(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT, + new QuantityType(thermostat.comfortSetpoint / (double) 100, SIUnits.CELSIUS)); + } + + private void updateRegulationMode(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, + StringType.valueOf(getRegulationMode(thermostat.regulationMode))); + } + + private void updateThermostatName(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_THERMOSTATNAME, StringType.valueOf(thermostat.thermostatName)); + } + + private void updateFloorTemperature(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, + new QuantityType(thermostat.floorTemperature / (double) 100, SIUnits.CELSIUS)); + } + + private void updateRoomTemperature(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, + new QuantityType(thermostat.roomTemperature / (double) 100, SIUnits.CELSIUS)); + } + + private void updateHeating(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_HEATING, + thermostat.heating ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + } + + private void updateOnline(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_ONLINE, + thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + } + + private void updateGroupId(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_GROUPID, new DecimalType(thermostat.groupId)); + } + + private void updateGroupName(Thermostat thermostat) { + updateState(BindingConstants.CHANNEL_OWD5_GROUPNAME, StringType.valueOf(thermostat.groupName)); + } + + private String getRegulationMode(int regulationMode) { + return REGULATION_MODES.get(regulationMode); + } + + private static Map createRegulationMap() { + HashMap map = new HashMap<>(); + map.put(1, "auto"); + map.put(2, "comfort"); + map.put(3, "manual"); + map.put(4, "vacation"); + map.put(6, "frostProtection"); + map.put(8, "boost"); + map.put(9, "eco"); + return map; + }; + + private Map> createChannelRefreshActionMap() { + HashMap> map = new HashMap<>(); + map.put(BindingConstants.CHANNEL_OWD5_GROUPNAME, this::updateGroupName); + map.put(BindingConstants.CHANNEL_OWD5_GROUPID, this::updateGroupId); + map.put(BindingConstants.CHANNEL_OWD5_ONLINE, this::updateOnline); + map.put(BindingConstants.CHANNEL_OWD5_HEATING, this::updateHeating); + map.put(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, this::updateRoomTemperature); + map.put(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, this::updateFloorTemperature); + map.put(BindingConstants.CHANNEL_OWD5_THERMOSTATNAME, this::updateThermostatName); + map.put(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, this::updateRegulationMode); + map.put(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT, this::updateComfortSetpoint); + map.put(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, this::updateComfortEndTime); + map.put(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, this::updateBoostEndTime); + map.put(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, this::updateManualSetpoint); + return map; + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandlerFactory.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandlerFactory.java new file mode 100644 index 0000000000000..e2b9000927db7 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/ThermostatHandlerFactory.java @@ -0,0 +1,58 @@ +/** + * 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; + +import static org.openhab.binding.ojelectronics.internal.BindingConstants.THING_TYPE_OWD5; + +import java.util.Collections; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Component; + +/** + * The {@link ThermostatHandlerFactory} is responsible for creating {@link OJElectronicsThermostatHandler}. + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.ojelectronics", service = ThingHandlerFactory.class) +public class ThermostatHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_OWD5); + + /** + * Supported things of this factory. + */ + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_OWD5.equals(thingTypeUID)) { + return new ThermostatHandler(thing); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsBridgeConfiguration.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsBridgeConfiguration.java new file mode 100644 index 0000000000000..dce7231a357c3 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsBridgeConfiguration.java @@ -0,0 +1,59 @@ +/** + * 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.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The configuration for {@link org.openhab.binding.ojelectronics.internal.OJElectronicsCloudHandler} + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class OJElectronicsBridgeConfiguration { + + /** + * Password + */ + public String password = ""; + + /** + * Customer-ID + */ + public int customerId = 1; + + /** + * User Name + */ + public String userName = ""; + + /** + * Url for API + */ + public String apiUrl = "https://OWD5-OJ001-App.ojelectronics.com/api"; + + /** + * API-Key + */ + public String apiKey = ""; + + /** + * Software Version + */ + public int softwareVersion = 1060; + + /** + * Refresh-Delay + */ + public long refreshDelayInSeconds = 30; +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsThermostatConfiguration.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsThermostatConfiguration.java new file mode 100644 index 0000000000000..a11600f6efdb9 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/config/OJElectronicsThermostatConfiguration.java @@ -0,0 +1,29 @@ +/** + * 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.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The configuration for {@link org.openhab.binding.ojelectronics.internal.OJElectronicsThermostatHandler} + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class OJElectronicsThermostatConfiguration { + + /** + * serial number of thermostat + */ + public String serialNumber = ""; +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Day.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Day.java new file mode 100644 index 0000000000000..daf784a670c93 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Day.java @@ -0,0 +1,31 @@ +/** + * 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.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Model for a day + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class Day { + + public int weekDayGrpNo; + + public List events = new ArrayList<>(); +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Event.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Event.java new file mode 100644 index 0000000000000..b3ede9a86eba0 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Event.java @@ -0,0 +1,34 @@ +/** + * 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 org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Model for events + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class Event { + + public int scheduleType; + + public String clock = ""; + + public int temperature; + + public boolean active; + + public boolean eventIsOnNextDay; +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContent.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContent.java new file mode 100644 index 0000000000000..78fdb1c6500ae --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContent.java @@ -0,0 +1,60 @@ +/** + * 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.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Model for content of a group + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class GroupContent { + + public int action; + + public int groupId; + + public String groupName = ""; + + public List thermostats = new ArrayList(); + + public int regulationMode; + + public @Nullable Schedule schedule; + + public int comfortSetpoint; + + public String comfortEndTime = ""; + + public int manualModeSetpoint; + + public boolean vacationEnabled; + + public String vacationBeginDay = ""; + + public String vacationEndDay = ""; + + public int vacationTemperature; + + public boolean lastPrimaryModeIsAuto; + + public String boostEndTime = ""; + + public int frostProtectionTemperature; +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentResponseModel.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentResponseModel.java new file mode 100644 index 0000000000000..bd9aa9429f912 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/GroupContentResponseModel.java @@ -0,0 +1,31 @@ +/** + * 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.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Model for the response of a content group + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class GroupContentResponseModel { + + public List groupContents = new ArrayList(); + + public int errorCode; +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Schedule.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Schedule.java new file mode 100644 index 0000000000000..3172b1bb8c7a2 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Schedule.java @@ -0,0 +1,31 @@ +/** + * 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.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Model for a schedule + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class Schedule { + + public List days = new ArrayList(); + + public boolean modifiedDueToVerification; +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Thermostat.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Thermostat.java new file mode 100644 index 0000000000000..e08132cad9874 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/groups/Thermostat.java @@ -0,0 +1,98 @@ +/** + * 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/userprofile/PostSignInQueryModel.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/userprofile/PostSignInQueryModel.java new file mode 100644 index 0000000000000..1c606ae4eb535 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/userprofile/PostSignInQueryModel.java @@ -0,0 +1,92 @@ +/** + * 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.userprofile; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.annotations.SerializedName; + +/** + * Model for signing sin + * + * @author Christian Kittel - Initial contribution + */ +@NonNullByDefault +public class PostSignInQueryModel { + + @SerializedName("APIKEY") + public String apiKey = ""; + + public String userName = ""; + + public String password = ""; + + public int customerId; + + 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 + * + * @param userName User-Name for API access + * @return Model + */ + public PostSignInQueryModel withUserName(String userName) { + this.userName = userName; + return this; + } + + /** + * Add Password + * + * @param password Password for API access + * @return Model + */ + public PostSignInQueryModel withPassword(String password) { + this.password = password; + return this; + } + + /** + * Add customer ID + * + * @param customerId Customer Id + * @return Model + */ + public PostSignInQueryModel withCustomerId(int customerId) { + this.customerId = customerId; + return this; + } + + /** + * Add Software Version + * + * @param clientSWVersion Software Version + * @return Model + */ + public PostSignInQueryModel withClientSWVersion(int clientSWVersion) { + this.clientSWVersion = clientSWVersion; + return this; + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/userprofile/PostSignInResponseModel.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/userprofile/PostSignInResponseModel.java new file mode 100644 index 0000000000000..334a5791c0af4 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/models/userprofile/PostSignInResponseModel.java @@ -0,0 +1,45 @@ +/** + * 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.userprofile; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Response-Model after signing in + * + * @author Christian Kittel - Initial Contribution + */ +@NonNullByDefault +public class PostSignInResponseModel { + + 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/RefreshGroupContentService.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshGroupContentService.java new file mode 100644 index 0000000000000..2cd0ade35e536 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshGroupContentService.java @@ -0,0 +1,58 @@ +/** + * 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.smarthome.core.thing.Thing; +import org.openhab.binding.ojelectronics.internal.ThermostatHandler; +import org.openhab.binding.ojelectronics.internal.models.groups.GroupContent; +import org.openhab.binding.ojelectronics.internal.models.groups.Thermostat; + +/** + * Refreshes values of {@link ThermostatHandler} + * + * @author Christian Kittel - Initial Contribution + */ +@NonNullByDefault +public class RefreshGroupContentService { + + private final List groupContentList; + private List things; + + /** + * Creates a new instance of {@link RefreshGroupContentService} + * + * @param groupContents {@link GroupContent} + * @param things Things + */ + public RefreshGroupContentService(List groupContents, List things) { + this.groupContentList = groupContents; + this.things = things; + } + + /** + * Handles the changes to all things. + */ + public void handle() { + groupContentList.stream().flatMap(entry -> entry.thermostats.stream()).forEach(this::handleThermostat); + } + + private void handleThermostat(Thermostat thermostat) { + things.stream().filter(thing -> thing.getHandler() instanceof ThermostatHandler) + .map(thing -> (ThermostatHandler) thing.getHandler()) + .filter(thingHandler -> thingHandler.getSerialNumber().equals(thermostat.serialNumber)) + .forEach(thingHandler -> thingHandler.handleThermostatRefresh(thermostat)); + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshService.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshService.java new file mode 100644 index 0000000000000..fa489152a75b5 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/RefreshService.java @@ -0,0 +1,166 @@ +/** + * 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.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; + +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.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +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; + +/** + * Handles the refreshing of the devices of a session + * + * @author Christian Kittel - Initial Contribution + */ +@NonNullByDefault +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 ScheduledExecutorService schedulerService; + + private @Nullable Runnable connectionLost; + private @Nullable BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone; + private @Nullable ScheduledFuture scheduler; + private @Nullable Runnable unauthorized; + private @Nullable String sessionId; + private static boolean destroyed = false; + + /** + * Creates a new instance of {@link RefreshService} + * + * @param config Configuration of the bridge + * @param httpClient HTTP client + */ + public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient, + ScheduledExecutorService schedulerService) { + this.config = config; + this.httpClient = httpClient; + this.schedulerService = schedulerService; + } + + /** + * Starts refreshing all thing values + * + * @param sessionId Session-Id + * @param refreshDone This method is called if refreshing is done. + * @param connectionLosed This method is called if no connection could established. + * @param unauthorized This method is called if the result is unauthorized. + */ + public void start(String sessionId, BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone, + Runnable connectionLost, Runnable unauthorized) { + logger.trace("RefreshService.startService({})", sessionId); + this.connectionLost = connectionLost; + this.refreshDone = refreshDone; + this.unauthorized = unauthorized; + this.sessionId = sessionId; + long refreshTime = config.refreshDelayInSeconds; + scheduler = schedulerService.scheduleWithFixedDelay(this::refresh, refreshTime, refreshTime, TimeUnit.SECONDS); + refresh(); + destroyed = false; + } + + /** + * Stops refreshing. + */ + public void stop() { + destroyed = true; + final ScheduledFuture scheduler = this.scheduler; + if (scheduler != null) { + scheduler.cancel(false); + } + 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) { + handleConnectionLost(); + } + final Runnable unauthorized = this.unauthorized; + createRequest().send(new BufferingResponseListener() { + @Override + public void onComplete(@Nullable Result result) { + if (!destroyed) { + if (result == null || result.isFailed()) { + handleConnectionLost(); + } else if (result.getResponse().getStatus() == HttpStatus.FORBIDDEN_403) { + if (unauthorized != null) { + unauthorized.run(); + } + } else { + handleRefreshDone(getContentAsString()); + } + } + } + }); + } + + private Request createRequest() { + Request request = httpClient.newRequest(config.apiUrl + "/Group/GroupContents").param("sessionid", sessionId) + .param("apiKey", config.apiKey).method(HttpMethod.GET); + return request; + } + + private void handleRefreshDone(String responseBody) { + BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone = this.refreshDone; + if (refreshDone != null) { + logger.trace("refresh {}", responseBody); + try { + GroupContentResponseModel content = gson.fromJson(responseBody, GroupContentResponseModel.class); + refreshDone.accept(content, null); + } catch (JsonSyntaxException exception) { + logger.debug("Error mapping Result to model", exception); + refreshDone.accept(null, exception.getMessage()); + } + } + } + + private void handleConnectionLost() { + final Runnable connectionLost = this.connectionLost; + if (connectionLost != null) { + connectionLost.run(); + } + } + + @Override + public void close() throws Exception { + stop(); + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/SignInService.java b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/SignInService.java new file mode 100644 index 0000000000000..8897fc8d416bf --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/java/org/openhab/binding/ojelectronics/internal/services/SignInService.java @@ -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.services; + +import java.util.function.Consumer; + +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.config.OJElectronicsBridgeConfiguration; +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. + * + * @author Christian Kittel - Initial Contribution + */ +@NonNullByDefault +public class SignInService { + + private final Gson gson = createGson(); + + private final HttpClient httpClient; + private final OJElectronicsBridgeConfiguration config; + + /** + * Creates a new instance of {@link SignInService} + * + * @param config configuration {@link OJElectronicsBridgeConfiguration} + * @param httpClient HTTP client + */ + public SignInService(OJElectronicsBridgeConfiguration config, HttpClient httpClient) { + this.config = config; + this.httpClient = httpClient; + } + + /** + * Signing in + * + * @param signInDone This method is called if sign in process was successful. + * @param connectionLosed This method is called if no connection could established. + * @param unauthorized This method is called if the result is unauthorized. + */ + public void signIn(Consumer signInDone, Runnable connectionLosed, Runnable unauthorized) { + Request request = httpClient.POST(config.apiUrl + "/UserProfile/SignIn") + .header(HttpHeader.CONTENT_TYPE, "application/json") + .content(new StringContentProvider(gson.toJson(getPostSignInQueryModel()))); + + request.send(new BufferingResponseListener() { + @Override + public void onComplete(@Nullable Result result) { + if (result == null || result.isFailed()) { + connectionLosed.run(); + return; + } + if (result.getResponse().getStatus() != 200) { + unauthorized.run(); + return; + } + PostSignInResponseModel signInModel = gson.fromJson(getContentAsString(), + PostSignInResponseModel.class); + if (signInModel.errorCode != 0 || signInModel.sessionId.equals("")) { + unauthorized.run(); + return; + } + signInDone.accept(signInModel.sessionId); + } + }); + } + + 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); + } +} diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.ojelectronics/src/main/resources/ESH-INF/binding/binding.xml new file mode 100644 index 0000000000000..4bcdb5112e9ae --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/resources/ESH-INF/binding/binding.xml @@ -0,0 +1,8 @@ + + + OJElectronics Binding + This is the binding for OJElectronics. + Christian Kittel + diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/resources/ESH-INF/i18n/ojelectronics_de_DE.properties b/bundles/org.openhab.binding.ojelectronics/src/main/resources/ESH-INF/i18n/ojelectronics_de_DE.properties new file mode 100644 index 0000000000000..f39dae1806f73 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/resources/ESH-INF/i18n/ojelectronics_de_DE.properties @@ -0,0 +1,38 @@ +# binding +binding.ojelectronics.name = Binding für OJElectronics +binding.ojelectronics.description = Binding für OJElectronics. + +# thing types +thing-type.ojelectronics.owd5.label = OWD5/MWD5 Thermostate +thing-type.ojelectronics.owd5.description = OWD5/MWD5 Thermostate +thing-type.ojelectronics.ojcloud.label = OJ Electronics Cloud +thing-type.ojelectronics.ojcloud.description = Zugriff auf alle OJ Electronic Geräte. + +# thing type config description +thing-type.config.ojelectronics.ojcloud.userName.label = Nutzername +thing-type.config.ojelectronics.ojcloud.userName.description = Nutzername für den Zugriff auf die Cloud. +thing-type.config.ojelectronics.ojcloud.password.label = Passwort +thing-type.config.ojelectronics.ojcloud.password.description = Passwort für den Zugriff auf die Cloud. +thing-type.config.ojelectronics.ojcloud.apiKey.label = API-Key +thing-type.config.ojelectronics.ojcloud.apiKey.description = API-Key von deinem Händler + +# channel types +channel-type.ojelectronics.floorTemperature.label = Bodentemperatur +channel-type.ojelectronics.roomTemperature.label = Zimmertemperatur +channel-type.ojelectronics.groupName.label = Gruppenname +channel-type.ojelectronics.online.label = Ist Online +channel-type.ojelectronics.heating.label = Heizt +channel-type.ojelectronics.thermostatName.label = Name des Thermostats +channel-type.ojelectronics.regulationMode.label = Regelungsmodus +channel-type.ojelectronics.regulationMode.state.option.auto = Automatisch +channel-type.ojelectronics.regulationMode.state.option.comfort = Komfort +channel-type.ojelectronics.regulationMode.state.option.manual = Manuell +channel-type.ojelectronics.regulationMode.state.option.vacation = Urlaub +channel-type.ojelectronics.regulationMode.state.option.frostProtection = Frostschutz +channel-type.ojelectronics.regulationMode.state.option.boost = Boost +channel-type.ojelectronics.regulationMode.state.option.eco = Spar +channel-type.ojelectronics.comfortSetpoint.label = Komfort-Sollwert +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 diff --git a/bundles/org.openhab.binding.ojelectronics/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.ojelectronics/src/main/resources/ESH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..f253b3177b8f1 --- /dev/null +++ b/bundles/org.openhab.binding.ojelectronics/src/main/resources/ESH-INF/thing/thing-types.xml @@ -0,0 +1,161 @@ + + + + + + Access to all OJ Electronic devices. + + + + User Name for access cloud service. + + + + Password for access cloud service. + password + + + + API-Key from your local distributor + + + + URL to cloud API-service. + url + true + https://OWD5-OJ001-App.ojelectronics.com/api + + + + Refresh delay in seconds. + true + 30 + + + + Customer ID + true + 1 + + + + Software Version + true + 1060 + + + + + + + + + OWD5/MWD5 Thermostat + RadiatorControl + + + + + + + + + + + + + + + + + OJ Electronics + + + + + Serial number of the thermostat. You can find the serial number in the app or on the thermostat itself. + + + + + Number:Temperature + + Temperature + + + + String + + + + + Number + + + + + Contact + + + + + Contact + + + + + Number:Temperature + + Temperature + + + + String + + + + + String + + + + + + + + + + + + + + + Number:Temperature + + Temperature + + + + DateTime + + + + + DateTime + + + + + Number:Temperature + + Temperature + + + + Switch + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 96090ffaacecf..6586283452a17 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -183,6 +183,7 @@ org.openhab.binding.ntp org.openhab.binding.nuki org.openhab.binding.oceanic + org.openhab.binding.ojelectronics org.openhab.binding.omnikinverter org.openhab.binding.onebusaway org.openhab.binding.onewiregpio