added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.ojelectronics-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-ojelectronics" description="OJElectronics Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.ojelectronics/${project.version}</bundle>
</feature>
</features>

View File

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

View File

@@ -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.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.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.types.Command;
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);
}
}

View File

@@ -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.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
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;
/**
* 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<ThingTypeUID> 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;
}
}

View File

@@ -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.openhab.binding.ojelectronics.internal.config.OJElectronicsThermostatConfiguration;
import org.openhab.binding.ojelectronics.internal.models.groups.Thermostat;
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.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
/**
* 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<Integer, String> REGULATION_MODES = createRegulationMap();
private final Map<String, Consumer<Thermostat>> 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<Temperature>(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<Temperature>(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<Temperature>(thermostat.floorTemperature / (double) 100, SIUnits.CELSIUS));
}
private void updateRoomTemperature(Thermostat thermostat) {
updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE,
new QuantityType<Temperature>(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<Integer, String> createRegulationMap() {
HashMap<Integer, String> 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<String, Consumer<Thermostat>> createChannelRefreshActionMap() {
HashMap<String, Consumer<Thermostat>> 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;
}
}

View File

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

View File

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

View File

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

View File

@@ -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<Event> events = new ArrayList<>();
}

View File

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

View File

@@ -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<Thermostat> thermostats = new ArrayList<Thermostat>();
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;
}

View File

@@ -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<GroupContent> groupContents = new ArrayList<GroupContent>();
public int errorCode;
}

View File

@@ -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<Day> days = new ArrayList<Day>();
public boolean modifiedDueToVerification;
}

View File

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

View File

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

View File

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

View File

@@ -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.openhab.binding.ojelectronics.internal.ThermostatHandler;
import org.openhab.binding.ojelectronics.internal.models.groups.GroupContent;
import org.openhab.binding.ojelectronics.internal.models.groups.Thermostat;
import org.openhab.core.thing.Thing;
/**
* Refreshes values of {@link ThermostatHandler}
*
* @author Christian Kittel - Initial Contribution
*/
@NonNullByDefault
public class RefreshGroupContentService {
private final List<GroupContent> groupContentList;
private List<Thing> things;
/**
* Creates a new instance of {@link RefreshGroupContentService}
*
* @param groupContents {@link GroupContent}
* @param things Things
*/
public RefreshGroupContentService(List<GroupContent> groupContents, List<Thing> 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));
}
}

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="ojelectronics"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>OJElectronics Binding</name>
<description>This is the binding for OJElectronics.</description>
<author>Christian Kittel</author>
</binding:binding>

View File

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

View File

@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bindingId="ojelectronics"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Bridge -->
<bridge-type id="ojcloud">
<label>OJ Electronics Cloud</label>
<description>Access to all OJ Electronic devices.</description>
<config-description>
<parameter name="userName" type="text" required="true">
<label>User Name</label>
<description>User Name for access cloud service.</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>Password for access cloud service.</description>
<context>password</context>
</parameter>
<parameter name="apiKey" type="text" required="true" min="36" max="36">
<label>API Key</label>
<description>API-Key from your local distributor</description>
</parameter>
<parameter name="apiUrl" type="text" required="true">
<label>API-URL</label>
<description>URL to cloud API-service.</description>
<context>url</context>
<advanced>true</advanced>
<default>https://OWD5-OJ001-App.ojelectronics.com/api</default>
</parameter>
<parameter name="refreshDelayInSeconds" type="integer" required="true" min="15" unit="s">
<label>Refresh Delay</label>
<description>Refresh delay in seconds.</description>
<advanced>true</advanced>
<default>30</default>
</parameter>
<parameter name="customerId" type="integer" required="true">
<label>Customer ID</label>
<description>Customer ID</description>
<advanced>true</advanced>
<default>1</default>
</parameter>
<parameter name="softwareVersion" type="integer" required="true">
<label>Software Version</label>
<description>Software Version</description>
<advanced>true</advanced>
<default>1060</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="owd5">
<supported-bridge-type-refs>
<bridge-type-ref id="ojcloud"/>
</supported-bridge-type-refs>
<label>OWD5/MWD5 Thermostat</label>
<description>OWD5/MWD5 Thermostat</description>
<category>RadiatorControl</category>
<channels>
<channel id="floorTemperature" typeId="floorTemperature"/>
<channel id="roomTemperature" typeId="roomTemperature"/>
<channel id="groupName" typeId="groupName"/>
<channel id="groupId" typeId="groupId"/>
<channel id="online" typeId="online"/>
<channel id="heating" typeId="heating"/>
<channel id="thermostatName" typeId="thermostatName"/>
<channel id="regulationMode" typeId="regulationMode"/>
<channel id="comfortSetpoint" typeId="comfortSetpoint"/>
<channel id="comfortEndTime" typeId="comfortEndTime"/>
<channel id="boostEndTime" typeId="boostEndTime"/>
<channel id="manualSetpoint" typeId="manualSetpoint"/>
<channel id="vacationEnabled" typeId="vacationEnabled"/>
</channels>
<properties>
<property name="vendor">OJ Electronics</property>
</properties>
<config-description>
<parameter name="serialNumber" type="text" required="true">
<label>Serial Number</label>
<description>Serial number of the thermostat. You can find the serial number in the app or on the thermostat itself.</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="floorTemperature">
<item-type>Number:Temperature</item-type>
<label>Floor Temperature</label>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="groupName">
<item-type>String</item-type>
<label>Group Name</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="groupId">
<item-type>Number</item-type>
<label>Group ID</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="online">
<item-type>Contact</item-type>
<label>Online</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="heating">
<item-type>Contact</item-type>
<label>Heating</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="roomTemperature">
<item-type>Number:Temperature</item-type>
<label>Room Temperature</label>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="thermostatName">
<item-type>String</item-type>
<label>Thermostat Name</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="regulationMode">
<item-type>String</item-type>
<label>Regulation Mode</label>
<state readOnly="true">
<options>
<option value="auto">Auto</option>
<option value="comfort">Comfort</option>
<option value="manual">Manual</option>
<option value="vacation">Vacation</option>
<option value="frostProtection">Frost Protection</option>
<option value="boost">Boost</option>
<option value="eco">Eco</option>
</options>
</state>
</channel-type>
<channel-type id="comfortSetpoint">
<item-type>Number:Temperature</item-type>
<label>Comfort Set Point Temperature</label>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</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"/>
</channel-type>
<channel-type id="vacationEnabled">
<item-type>Switch</item-type>
<label>Vacation Mode Enabled</label>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>