added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
38
bundles/org.openhab.binding.millheat/.classpath
Normal file
38
bundles/org.openhab.binding.millheat/.classpath
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.millheat/.project
Normal file
23
bundles/org.openhab.binding.millheat/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.millheat</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
bundles/org.openhab.binding.millheat/NOTICE
Normal file
13
bundles/org.openhab.binding.millheat/NOTICE
Normal file
@@ -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
|
||||
143
bundles/org.openhab.binding.millheat/README.md
Normal file
143
bundles/org.openhab.binding.millheat/README.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Millheat Binding
|
||||
|
||||
This binding integrates the Mill Wi-Fi enabled panel heaters.
|
||||
See https://www.millheat.com/mill-wifi/
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding supports all Wi-Fi enabled heaters as well as the Wi-Fi socket.
|
||||
|
||||
* `account` = Mill Heating API - the account bridge
|
||||
* `heater` = Panel/standalone heater
|
||||
* `room` = A room defined in the mobile app
|
||||
* `home` = A home defined in the mobile app
|
||||
|
||||
## Discovery
|
||||
|
||||
The binding will discover homes with rooms and heaters.
|
||||
|
||||
In order to do discovery, add a thing of type Mill Heating API and add username and password.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
See full example below for how to configure using thing files.
|
||||
|
||||
### Account
|
||||
|
||||
* `username` = email address used in app
|
||||
* `password` = password used in app
|
||||
* `refreshInterval` = number of seconds between refresh calls to the server
|
||||
|
||||
### Home
|
||||
|
||||
* `homeId` = id of home, type number (not string). Use auto discovery to find this value
|
||||
|
||||
### Room
|
||||
|
||||
* `roomId` = id of room, type number (not string). Use auto discovery to find this value
|
||||
|
||||
### Heater
|
||||
|
||||
* `macAddress` = network mac address of device.
|
||||
Can be found in the app by viewing devices.
|
||||
Or you can find it during discovery.
|
||||
Used for heaters connected to a room.
|
||||
* `heaterId` = id of device/heater, type number (not string)
|
||||
Use auto discovery to find this value.
|
||||
Used to identify independent heaters or heaters connected to a room.
|
||||
* `power` = number of watts this heater is consuming when active.
|
||||
Used to provide data for the currentPower channel.
|
||||
|
||||
Either `macAddres` or `heaterId` must be specified.
|
||||
|
||||
## Channels
|
||||
|
||||
### Home channels
|
||||
|
||||
| Channel | Read/write | Item type | Description |
|
||||
| ------------------- | ------------- | ------------------- | ----------- |
|
||||
| vacationMode | R/W | Switch | Vacation mode active. Note: In order to activate vacation mode, both vacationModeStart and vacationModeEnd must be set to valid values |
|
||||
| vacationModeAdvanced | R/W | Switch | Vacation mode advanced active. Can only be activated after vacation mode is active |
|
||||
| vacationModeTargetTemperature | R/W | Number:Temperature | Temperature to use when activating vacation mode. Note: If advanced vacation mode is set, this temperature is ignored and the away temperature for each room is used instead |
|
||||
| vacationModeStart | R/W | DateTime | Vacation mode start |
|
||||
| vacationModeEnd | R/W | DateTime | Vacation mode end |
|
||||
|
||||
### Room channels
|
||||
|
||||
| Channel | Read/write | Item type | Description |
|
||||
| ------------------- | ------------- | --------------------- | ----------- |
|
||||
| currentTemperature | R | Number:Temperature | Measured temperature in your room (if more than one heater then it is the average of all heaters) |
|
||||
| currentMode | R | String | Current mode (comfort, away, sleep etc) being active |
|
||||
| targetTemperature | R | Number:Temperature | Current target temperature for this room (managed by the room program and set by comfort- away- and sleepTemperature) |
|
||||
| comfortTemperature | R/W | Number:Temperature | Comfort mode temperature |
|
||||
| awayTemperature | R/W | Number:Temperature | Away mode temperature |
|
||||
| sleepTemperature | R/W | Number:Temperature | Sleep mode temperature |
|
||||
| heatingActive | R | Switch | Whether the heaters in this room are active |
|
||||
| program | R | String | Name of program used in this room |
|
||||
|
||||
|
||||
### Heater channels
|
||||
|
||||
| Channel | Read/write | Item type | Description |
|
||||
| ------------------- | ------------- | ------------------ | ----------- |
|
||||
| currentTemperature | R | Number:Temperature | Measured temperature by this heater |
|
||||
| targetTemperature | R/W | Number:Temperature | Target temperature for this heater. Channel available only if heater is not connected to a room |
|
||||
| currentPower | R | Number:Power | Current power usage in watts. Note that the power attribute of the heater thing config must be set for this channel to be active |
|
||||
| heatingActive | R | Switch | Whether the heater is active/heating |
|
||||
| fanActive | R/W | Switch | Whether the fan (if available) is active (UNTESTED) |
|
||||
| independent | R | Switch | Whether this heater is controlled independently or part of a room setup |
|
||||
| window | R | Contact | Whether this heater has detected that a window nearby is open/detection of cold air (UNTESTED) |
|
||||
| masterSwitch | R/W | Switch | Turn heater ON/OFF. Channel available only if heater is not connected to a room |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
millheat.things:
|
||||
|
||||
```
|
||||
Bridge millheat:account:home "Millheat account" [username="email@address.com",password="topsecret"] {
|
||||
Thing home monaco "Penthouse Monaco" [ homeId=100000000000000 ] // Note: numeric value
|
||||
Thing room office "Office room" [ roomId=200000000000000 ] Note: numeric value
|
||||
Thing heater office "Office panel heater" [ macAddress="F0XXXXXXXXX", power=900, heaterId=12345 ] Note: heaterId is a numeric value
|
||||
}
|
||||
```
|
||||
|
||||
millheat.items:
|
||||
|
||||
```
|
||||
// Items connected to HOME channels
|
||||
Number:Temperature Vacation_Target_Temperature "Vacation target temp [%d %unit%]" <temperature> {channel="millheat:home:home:monaco:vacationModeTargetTemperature"}
|
||||
Switch Vacation_Mode "Vacation mode" <vacation> {channel="millheat:home:home:monaco:vacationMode"}
|
||||
Switch Vacation_Mode_Advanced "Use room away temperatures" <vacation> {channel="millheat:home:home:monaco:vacationModeAdvanced"}
|
||||
DateTime Vacation_Mode_Start "Vacation mode start [%1$td.%1$tm.%1$ty %1$tH:%1$tM]" <vacation> {channel="millheat:home:home:monaco:vacationModeStart"}
|
||||
DateTime Vacation_Mode_End "Vacation mode end [%1$td.%1$tm.%1$ty %1$tH:%1$tM]" <vacation> {channel="millheat:home:home:monaco:vacationModeStart"}
|
||||
|
||||
// Items connected to ROOM channels
|
||||
Number:Temperature Heating_Office_Room_Current_Temperature "Office current [%.1f %unit%]" <temperature> {channel="millheat:room:home:office:currentTemperature"}
|
||||
Number:Temperature Heating_Office_Room_Target_Temperature "Office target [%.1f %unit%]" <temperature> {channel="millheat:room:home:office:targetTemperature"}
|
||||
Number:Temperature Heating_Office_Room_Sleep_Temperature "Office sleep [%.1f %unit%]" <temperature> {channel="millheat:room:home:office:sleepTemperature"}
|
||||
Number:Temperature Heating_Office_Room_Away_Temperature "Office away [%.1f %unit%]" <temperature> {channel="millheat:room:home:office:awayTemperature"}
|
||||
Number:Temperature Heating_Office_Room_Comfort_Temperature "Office comfort [%.1f %unit%]" <temperature> {channel="millheat:room:home:office:comfortTemperature"}
|
||||
Switch Heating_Office_Room_Heater_Active "Office active [%s]" <fire> {channel="millheat:room:home:office:heatingActive"}
|
||||
String Heating_Office_Room_Mode "Office current mode [%s]" {channel="millheat:room:home:office:currentMode"}
|
||||
String Heating_Office_Room_Program "Office program [%s]" {channel="millheat:room:home:office:program"}
|
||||
|
||||
// Items connected to HEATER channels
|
||||
Number:Power Heating_Office_Heater_Current_Energy "Energy usage [%d W]" <energy> {channel="millheat:heater:home:office:currentEnergy"}
|
||||
Number:Temperature Heating_Office_Heater_Current_Temperature "Heater current [%.1f %unit%]" <temperature> {channel="millheat:heater:home:office:currentTemperature"}
|
||||
Number:Temperature Heating_Office_Heater_Target_Temperature "Heater target [%.1f %unit%]" <temperature> {channel="millheat:heater:home:office:targetTemperature"}
|
||||
Switch Heating_Office_Heater_Heater_Active "Heater active [%s]" <fire> {channel="millheat:heater:home:office:heatingActive"}
|
||||
Switch Heating_Office_Heater_Fan_Active "Fan active [%s]" <fan> {channel="millheat:heater:home:office:fanActive"}
|
||||
Contact Heating_Office_Heater_Window "Window status [%s]" <window> {channel="millheat:heater:home:office:window"}
|
||||
Switch Heating_Office_Heater_Independent "Heater independent [%s]" <switch> {channel="millheat:heater:home:office:independent"}
|
||||
Switch Heating_Office_Heater_MasterSwitch "Heater masterswitch [%s]" <switch> {channel="millheat:heater:home:office:masterSwitch"}
|
||||
```
|
||||
|
||||
## Setting up vacation mode
|
||||
|
||||
In order to activate vacation mode, follow these steps in a rule:
|
||||
|
||||
* Set start time (DateTime) on `DateTime` item linked to channel type `vacationModeStart`
|
||||
* Set end time (DateTime) on `DateTime` item linked to channel type `vacationModeEnd`
|
||||
* Activate vacation mode on `Switch` item linked to channel type `vacationMode`
|
||||
* Optional - set advanced vacation mode on `Switch` item linked to channel type `vacationModeAdvanced`
|
||||
26
bundles/org.openhab.binding.millheat/pom.xml
Normal file
26
bundles/org.openhab.binding.millheat/pom.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.millheat</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Millheat Binding</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-standalone</artifactId>
|
||||
<version>2.23.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.millheat-${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-millheat" description="Millheat Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.millheat/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.millheat.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link MillheatBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MillheatBindingConstants {
|
||||
private static final String BINDING_ID = "millheat";
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||
public static final ThingTypeUID THING_TYPE_HOME = new ThingTypeUID(BINDING_ID, "home");
|
||||
public static final ThingTypeUID THING_TYPE_ROOM = new ThingTypeUID(BINDING_ID, "room");
|
||||
public static final ThingTypeUID THING_TYPE_HEATER = new ThingTypeUID(BINDING_ID, "heater");
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_CURRENT_TEMPERATURE = "currentTemperature";
|
||||
public static final String CHANNEL_COMFORT_TEMPERATURE = "comfortTemperature";
|
||||
public static final String CHANNEL_SLEEP_TEMPERATURE = "sleepTemperature";
|
||||
public static final String CHANNEL_AWAY_TEMPERATURE = "awayTemperature";
|
||||
public static final String CHANNEL_HEATING_ACTIVE = "heatingActive";
|
||||
public static final String CHANNEL_FAN_ACTIVE = "fanActive";
|
||||
public static final String CHANNEL_TARGET_TEMPERATURE = "targetTemperature";
|
||||
public static final String CHANNEL_CURRENT_POWER = "currentEnergy";
|
||||
public static final String CHANNEL_CURRENT_MODE = "currentMode";
|
||||
public static final String CHANNEL_PROGRAM = "program";
|
||||
public static final String CHANNEL_INDEPENDENT = "independent";
|
||||
public static final String CHANNEL_WINDOW_STATE = "window";
|
||||
public static final String CHANNEL_MASTER_SWITCH = "masterSwitch";
|
||||
|
||||
// Vacation mode channels
|
||||
public static final String CHANNEL_HOME_VACATION_TARGET_TEMPERATURE = "vacationModeTargetTemperature";
|
||||
public static final String CHANNEL_HOME_VACATION_MODE = "vacationMode";
|
||||
public static final String CHANNEL_HOME_VACATION_MODE_ADVANCED = "vacationModeAdvanced";
|
||||
public static final String CHANNEL_HOME_VACATION_MODE_START = "vacationModeStart";
|
||||
public static final String CHANNEL_HOME_VACATION_MODE_END = "vacationModeEnd";
|
||||
|
||||
public static final String CHANNEL_TYPE_MASTER_SWITCH = "masterSwitch";
|
||||
public static final String CHANNEL_TYPE_TARGET_TEMPERATURE_HEATER = "targetTemperatureHeater";
|
||||
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_MASTER_SWITCH_UID = new ChannelTypeUID(BINDING_ID,
|
||||
CHANNEL_TYPE_MASTER_SWITCH);
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_TARGET_TEMPERATURE_HEATER_UID = new ChannelTypeUID(BINDING_ID,
|
||||
CHANNEL_TYPE_TARGET_TEMPERATURE_HEATER);
|
||||
}
|
||||
@@ -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.millheat.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.millheat.internal.dto.AbstractRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.AbstractResponse;
|
||||
|
||||
/**
|
||||
* The {@link MillheatCommunicationException} class wraps exceptions raised when communicating with the API
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MillheatCommunicationException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private int errorCode = 0;
|
||||
|
||||
public MillheatCommunicationException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MillheatCommunicationException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MillheatCommunicationException(final AbstractRequest request, final AbstractResponse response) {
|
||||
super("Server responded with error to request " + request.getClass().getSimpleName() + "/"
|
||||
+ request.getRequestUrl() + ": " + response.errorCode + "/" + response.errorName + "/"
|
||||
+ response.errorDescription);
|
||||
this.errorCode = response.errorCode;
|
||||
}
|
||||
|
||||
public int getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 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.millheat.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.millheat.internal.discovery.MillheatDiscoveryService;
|
||||
import org.openhab.binding.millheat.internal.handler.MillheatAccountHandler;
|
||||
import org.openhab.binding.millheat.internal.handler.MillheatHeaterHandler;
|
||||
import org.openhab.binding.millheat.internal.handler.MillheatHomeHandler;
|
||||
import org.openhab.binding.millheat.internal.handler.MillheatRoomHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
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.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link MillheatHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.millheat", service = ThingHandlerFactory.class)
|
||||
public class MillheatHandlerFactory extends BaseThingHandlerFactory {
|
||||
private HttpClient httpClient;
|
||||
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
|
||||
.of(MillheatBindingConstants.THING_TYPE_ACCOUNT, MillheatBindingConstants.THING_TYPE_HEATER,
|
||||
MillheatBindingConstants.THING_TYPE_ROOM, MillheatBindingConstants.THING_TYPE_HOME)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
@Activate
|
||||
public MillheatHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(final Thing thing) {
|
||||
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (MillheatBindingConstants.THING_TYPE_HEATER.equals(thingTypeUID)) {
|
||||
return new MillheatHeaterHandler(thing);
|
||||
} else if (MillheatBindingConstants.THING_TYPE_ROOM.equals(thingTypeUID)) {
|
||||
return new MillheatRoomHandler(thing);
|
||||
} else if (MillheatBindingConstants.THING_TYPE_HOME.equals(thingTypeUID)) {
|
||||
return new MillheatHomeHandler(thing);
|
||||
} else if (MillheatBindingConstants.THING_TYPE_ACCOUNT.equals(thingTypeUID)) {
|
||||
final MillheatAccountHandler handler = new MillheatAccountHandler((Bridge) thing, httpClient,
|
||||
bundleContext);
|
||||
registerDeviceDiscoveryService(handler);
|
||||
return handler;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof MillheatAccountHandler) {
|
||||
ThingUID thingUID = thingHandler.getThing().getUID();
|
||||
unregisterDeviceDiscoveryService(thingUID);
|
||||
}
|
||||
super.removeHandler(thingHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
private void registerDeviceDiscoveryService(MillheatAccountHandler bridgeHandler) {
|
||||
MillheatDiscoveryService discoveryService = new MillheatDiscoveryService(bridgeHandler);
|
||||
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
|
||||
private void unregisterDeviceDiscoveryService(ThingUID thingUID) {
|
||||
if (discoveryServiceRegs.containsKey(thingUID)) {
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegs.get(thingUID);
|
||||
serviceReg.unregister();
|
||||
discoveryServiceRegs.remove(thingUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.millheat.internal.client;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
/**
|
||||
* The {@link BooleanSerializer} serializes and parses 1/0 to true/false from JSON files
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class BooleanSerializer implements JsonSerializer<Boolean>, JsonDeserializer<Boolean> {
|
||||
@Override
|
||||
public Boolean deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context)
|
||||
throws JsonParseException {
|
||||
return element.getAsInt() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(final Boolean argument, final Type type, final JsonSerializationContext context) {
|
||||
return new JsonPrimitive(Boolean.TRUE.equals(argument) ? 1 : 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 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.millheat.internal.client;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* Logs HttpClient request/response traffic.
|
||||
*
|
||||
* @author Gili Tzabari - Initial contribution https://stackoverflow.com/users/14731/gili
|
||||
* https://stackoverflow.com/questions/50318736/how-to-log-httpclient-requests-response-including-body
|
||||
* @author Arne Seime - adapted for Millheat binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class RequestLogger {
|
||||
private final Logger logger = LoggerFactory.getLogger(RequestLogger.class);
|
||||
private final AtomicLong nextId = new AtomicLong();
|
||||
private final JsonParser parser;
|
||||
private final Gson gson;
|
||||
private final String prefix;
|
||||
|
||||
public RequestLogger(final String prefix, final Gson gson) {
|
||||
this.parser = new JsonParser();
|
||||
this.gson = gson;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
private void dump(final Request request) {
|
||||
final long idV = nextId.getAndIncrement();
|
||||
if (logger.isDebugEnabled()) {
|
||||
final String id = prefix + "-" + idV;
|
||||
final StringBuilder group = new StringBuilder();
|
||||
request.onRequestBegin(theRequest -> group.append(
|
||||
String.format("Request %s\n%s > %s %s\n", id, id, theRequest.getMethod(), theRequest.getURI())));
|
||||
request.onRequestHeaders(theRequest -> {
|
||||
for (final HttpField header : theRequest.getHeaders()) {
|
||||
group.append(String.format("%s > %s\n", id, header));
|
||||
}
|
||||
});
|
||||
final StringBuilder contentBuffer = new StringBuilder();
|
||||
request.onRequestContent((theRequest, content) -> contentBuffer
|
||||
.append(reformatJson(getCharset(theRequest.getHeaders()).decode(content).toString())));
|
||||
request.onRequestSuccess(theRequest -> {
|
||||
if (contentBuffer.length() > 0) {
|
||||
group.append("\n");
|
||||
group.append(contentBuffer);
|
||||
}
|
||||
String dataToLog = group.toString();
|
||||
logger.debug(dataToLog);
|
||||
contentBuffer.delete(0, contentBuffer.length());
|
||||
group.delete(0, group.length());
|
||||
});
|
||||
request.onResponseBegin(theResponse -> {
|
||||
group.append(String.format("Response %s\n%s < %s %s", id, id, theResponse.getVersion(),
|
||||
theResponse.getStatus()));
|
||||
if (theResponse.getReason() != null) {
|
||||
group.append(" ");
|
||||
group.append(theResponse.getReason());
|
||||
}
|
||||
group.append("\n");
|
||||
});
|
||||
request.onResponseHeaders(theResponse -> {
|
||||
for (final HttpField header : theResponse.getHeaders()) {
|
||||
group.append(String.format("%s < %s\n", id, header));
|
||||
}
|
||||
});
|
||||
request.onResponseContent((theResponse, content) -> contentBuffer
|
||||
.append(reformatJson(getCharset(theResponse.getHeaders()).decode(content).toString())));
|
||||
request.onResponseSuccess(theResponse -> {
|
||||
if (contentBuffer.length() > 0) {
|
||||
group.append("\n");
|
||||
group.append(contentBuffer);
|
||||
}
|
||||
String dataToLog = group.toString();
|
||||
logger.debug(dataToLog);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Charset getCharset(final HttpFields headers) {
|
||||
final String contentType = headers.get(HttpHeader.CONTENT_TYPE);
|
||||
if (contentType == null) {
|
||||
return StandardCharsets.UTF_8;
|
||||
}
|
||||
final String[] tokens = contentType.toLowerCase(Locale.US).split("charset=");
|
||||
if (tokens.length != 2) {
|
||||
return StandardCharsets.UTF_8;
|
||||
}
|
||||
|
||||
final String encoding = tokens[1].replaceAll("[;\"]", "");
|
||||
return Charset.forName(encoding);
|
||||
}
|
||||
|
||||
public Request listenTo(final Request request) {
|
||||
dump(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
private String reformatJson(final String jsonString) {
|
||||
try {
|
||||
final JsonElement json = parser.parse(jsonString);
|
||||
return gson.toJson(json);
|
||||
} catch (final JsonSyntaxException e) {
|
||||
logger.debug("Could not reformat malformed JSON due to '{}'", e.getMessage());
|
||||
return jsonString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.millheat.internal.config;
|
||||
|
||||
/**
|
||||
* The {@link MillheatAccountConfiguration} class contains account thing configuration parameters.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class MillheatAccountConfiguration {
|
||||
/**
|
||||
* Username/email address used in app
|
||||
*/
|
||||
public String username;
|
||||
public String password;
|
||||
public int refreshInterval = 120;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.millheat.internal.config;
|
||||
|
||||
/**
|
||||
* The {@link MillheatHeaterConfiguration} class contains heater thing configuration parameters.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class MillheatHeaterConfiguration {
|
||||
/*
|
||||
* Wi-Fi mac address
|
||||
*/
|
||||
public String macAddress;
|
||||
/*
|
||||
* Wi-Fi heater id - found in logs
|
||||
*/
|
||||
public Long heaterId;
|
||||
/*
|
||||
* Nominal heater panel power
|
||||
*/
|
||||
public Integer power;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MillheatHeaterConfiguration [macAddress=" + macAddress + ", heaterId=" + heaterId + ", power=" + power
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.millheat.internal.config;
|
||||
|
||||
/**
|
||||
* The {@link MillheatHomeConfiguration} class contains home thing configuration parameters.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class MillheatHomeConfiguration {
|
||||
|
||||
public Long homeId;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MillheatHomeConfiguration [homeId=" + homeId + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.millheat.internal.config;
|
||||
|
||||
/**
|
||||
* The {@link MillheatRoomConfiguration} class contains room thing configuration parameters.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class MillheatRoomConfiguration {
|
||||
/*
|
||||
* Room ID
|
||||
*/
|
||||
public Long roomId;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MillheatRoomConfiguration [roomId=" + roomId + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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.millheat.internal.discovery;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.openhab.binding.millheat.internal.MillheatBindingConstants;
|
||||
import org.openhab.binding.millheat.internal.handler.MillheatAccountHandler;
|
||||
import org.openhab.binding.millheat.internal.model.Heater;
|
||||
import org.openhab.binding.millheat.internal.model.Home;
|
||||
import org.openhab.binding.millheat.internal.model.MillheatModel;
|
||||
import org.openhab.binding.millheat.internal.model.Room;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class does discovery of discoverable things
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class MillheatDiscoveryService extends AbstractDiscoveryService {
|
||||
private static final long REFRESH_INTERVAL_MINUTES = 60;
|
||||
public static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections.unmodifiableSet(
|
||||
Stream.of(MillheatBindingConstants.THING_TYPE_HEATER, MillheatBindingConstants.THING_TYPE_ROOM,
|
||||
MillheatBindingConstants.THING_TYPE_HOME).collect(Collectors.toSet()));
|
||||
private final Logger logger = LoggerFactory.getLogger(MillheatDiscoveryService.class);
|
||||
private ScheduledFuture<?> discoveryJob;
|
||||
private final MillheatAccountHandler accountHandler;
|
||||
|
||||
public MillheatDiscoveryService(final MillheatAccountHandler accountHandler) {
|
||||
super(DISCOVERABLE_THING_TYPES_UIDS, 10);
|
||||
this.accountHandler = accountHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
discoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 0, REFRESH_INTERVAL_MINUTES, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void startScan() {
|
||||
try {
|
||||
final ThingUID accountUID = accountHandler.getThing().getUID();
|
||||
logger.debug("Start scan for Millheat devices on account {}", accountUID.toString());
|
||||
accountHandler.updateModelFromServerWithRetry(false);
|
||||
final MillheatModel model = accountHandler.getModel();
|
||||
for (final Home home : model.getHomes()) {
|
||||
final ThingUID homeUID = new ThingUID(MillheatBindingConstants.THING_TYPE_HOME, accountUID,
|
||||
String.valueOf(home.getId()));
|
||||
final DiscoveryResult discoveryResultHome = DiscoveryResultBuilder.create(homeUID)
|
||||
.withBridge(accountUID).withLabel(home.getName()).withProperty("homeId", home.getId())
|
||||
.withRepresentationProperty("homeId").build();
|
||||
thingDiscovered(discoveryResultHome);
|
||||
|
||||
for (final Room room : home.getRooms()) {
|
||||
final ThingUID roomUID = new ThingUID(MillheatBindingConstants.THING_TYPE_ROOM, accountUID,
|
||||
String.valueOf(room.getId()));
|
||||
final DiscoveryResult discoveryResultRoom = DiscoveryResultBuilder.create(roomUID)
|
||||
.withBridge(accountUID).withLabel(room.getName()).withProperty("roomId", room.getId())
|
||||
.withRepresentationProperty("roomId").build();
|
||||
thingDiscovered(discoveryResultRoom);
|
||||
for (final Heater heater : room.getHeaters()) {
|
||||
final ThingUID heaterUID = new ThingUID(MillheatBindingConstants.THING_TYPE_HEATER, accountUID,
|
||||
String.valueOf(heater.getId()));
|
||||
final DiscoveryResult discoveryResultHeater = DiscoveryResultBuilder.create(heaterUID)
|
||||
.withBridge(accountUID).withLabel(heater.getName())
|
||||
.withProperty("heaterId", heater.getId()).withRepresentationProperty("macAddress")
|
||||
.withProperty("macAddress", heater.getMacAddress()).build();
|
||||
thingDiscovered(discoveryResultHeater);
|
||||
}
|
||||
}
|
||||
for (final Heater heater : home.getIndependentHeaters()) {
|
||||
final ThingUID heaterUID = new ThingUID(MillheatBindingConstants.THING_TYPE_HEATER, accountUID,
|
||||
String.valueOf(heater.getId()));
|
||||
final DiscoveryResult discoveryResultHeater = DiscoveryResultBuilder.create(heaterUID)
|
||||
.withBridge(accountUID).withLabel(heater.getName()).withRepresentationProperty("heaterId")
|
||||
.withProperty("heaterId", heater.getId()).build();
|
||||
thingDiscovered(discoveryResultHeater);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
removeOlderResults(getTimestampOfLastScan(), null, accountHandler.getThing().getUID());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
stopScan();
|
||||
if (discoveryJob != null && !discoveryJob.isCancelled()) {
|
||||
discoveryJob.cancel(true);
|
||||
discoveryJob = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
/**
|
||||
* The {@link AbstractRequest} class is implemented by all service requests
|
||||
**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public interface AbstractRequest {
|
||||
String getRequestUrl();
|
||||
}
|
||||
@@ -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.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link AbstractResponse} class is the base class for all decoded JSON responses from the API
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public abstract class AbstractResponse {
|
||||
public static final int ERROR_CODE_ACCESS_TOKEN_EXPIRED = 3515;
|
||||
public static final int ERROR_CODE_INVALID_SIGNATURE = 3015;
|
||||
public static final int ERROR_CODE_AUTHENTICATION_FAILURE = 1025;
|
||||
public int errorCode;
|
||||
@SerializedName("error")
|
||||
public String errorName;
|
||||
@SerializedName("description")
|
||||
public String errorDescription;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link DeviceDTO} class represents a heater device
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class DeviceDTO {
|
||||
public boolean heaterFlag;
|
||||
public int subDomainId;
|
||||
public int controlType;
|
||||
public double currentTemp;
|
||||
public boolean canChangeTemp;
|
||||
public long deviceId;
|
||||
public String deviceName;
|
||||
@SerializedName("mac")
|
||||
public String macAddress;
|
||||
public int deviceStatus;
|
||||
public int holidayTemp;
|
||||
public boolean fanStatus;
|
||||
@SerializedName("open")
|
||||
public boolean openWindow;
|
||||
public boolean powerStatus;
|
||||
@SerializedName("isHoliday")
|
||||
public boolean holiday;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the selectHomeList request
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class GetHomesRequest implements AbstractRequest {
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return "selectHomeList";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the selectHomeList response
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class GetHomesResponse extends AbstractResponse {
|
||||
public Integer hourSystem;
|
||||
@SerializedName("homeList")
|
||||
public HomeDTO[] homes = new HomeDTO[0];
|
||||
}
|
||||
@@ -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.millheat.internal.dto;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the get independent devices request
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class GetIndependentDevicesByHomeRequest implements AbstractRequest {
|
||||
public final Long homeId;
|
||||
|
||||
public GetIndependentDevicesByHomeRequest(final Long homeId, final String timeZone) {
|
||||
this.homeId = homeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return "getIndependentDevices";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the get independent devices response
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class GetIndependentDevicesByHomeResponse extends AbstractResponse {
|
||||
@SerializedName("deviceInfo")
|
||||
public DeviceDTO devices[] = new DeviceDTO[0];
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the home dto json structure
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class HomeDTO {
|
||||
public long homeId;
|
||||
@SerializedName("homeAlways")
|
||||
public boolean alwaysHome;
|
||||
@SerializedName("homeName")
|
||||
public String name;
|
||||
@SerializedName("isHoliday")
|
||||
public boolean holiday;
|
||||
public Long holidayStartTime;
|
||||
public String timeZone;
|
||||
public Integer modeMinute;
|
||||
public Long modeStartTime;
|
||||
public Integer holidayTemp;
|
||||
public Integer modeHour;
|
||||
public Integer currentMode;
|
||||
public Long holidayEndTime;
|
||||
public Integer homeType;
|
||||
public String programId;
|
||||
public int holidayTempType;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the login request
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class LoginRequest implements AbstractRequest {
|
||||
public final String account;
|
||||
public final String password;
|
||||
|
||||
public LoginRequest(final String username, final String password) {
|
||||
this.account = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return "login";
|
||||
}
|
||||
}
|
||||
@@ -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.millheat.internal.dto;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the login response
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class LoginResponse extends AbstractResponse {
|
||||
public String email;
|
||||
@SerializedName("nickName")
|
||||
public String nickname;
|
||||
public String phone;
|
||||
public String refreshToken;
|
||||
public Date refreshTokenExpire;
|
||||
public String token;
|
||||
public Date tokenExpire;
|
||||
public Integer userId;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the room json structure
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class RoomDTO {
|
||||
public long roomId;
|
||||
@SerializedName("roomName")
|
||||
public String name;
|
||||
public int comfortTemp;
|
||||
public int sleepTemp;
|
||||
public int awayTemp;
|
||||
@SerializedName("avgTemp")
|
||||
public double currentTemp;
|
||||
public String roomProgram;
|
||||
public int currentMode = 0;
|
||||
public boolean heatStatus = false;
|
||||
@SerializedName("onLineDeviceNum")
|
||||
public int onlineDeviceCount = 0;
|
||||
@SerializedName("offLineDeviceNum")
|
||||
public int offLineDeviceCount = 0;
|
||||
@SerializedName("total")
|
||||
public int totalCount = 0;
|
||||
public int independentCount = 0;
|
||||
@SerializedName("isOffline")
|
||||
public boolean offline = true;
|
||||
public String controlSource;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the select device by room request
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SelectDeviceByRoomRequest implements AbstractRequest {
|
||||
public final Long roomId;
|
||||
@SerializedName("timeZoneNum")
|
||||
public final String timeZone;
|
||||
|
||||
public SelectDeviceByRoomRequest(final Long roomId, final String timeZone) {
|
||||
this.roomId = roomId;
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return "selectDevicebyRoom";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the select device by home response
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SelectDeviceByRoomResponse extends AbstractResponse {
|
||||
@SerializedName("deviceInfo")
|
||||
public DeviceDTO[] devices = new DeviceDTO[0];
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the select room by home request
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SelectRoomByHomeRequest implements AbstractRequest {
|
||||
public final Long homeId;
|
||||
@SerializedName("timeZoneNum")
|
||||
public final String timeZone;
|
||||
|
||||
public SelectRoomByHomeRequest(final Long homeId, final String timeZone) {
|
||||
this.homeId = homeId;
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return "selectRoombyHome";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the select room by home response
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SelectRoomByHomeResponse extends AbstractResponse {
|
||||
@SerializedName("roomInfo")
|
||||
public RoomDTO[] rooms = new RoomDTO[0];
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import org.openhab.binding.millheat.internal.model.Heater;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the set device temp request
|
||||
*
|
||||
* @see SetRoomTempRequest
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SetDeviceTempRequest implements AbstractRequest {
|
||||
public final int subDomain;
|
||||
public final long deviceId;
|
||||
public final boolean testStatus = true;
|
||||
public final int operation;
|
||||
public final boolean status;
|
||||
public final boolean windStatus;
|
||||
public final int holdTemp;
|
||||
public final int tempType = 0; // FIXED?
|
||||
public final int powerLevel = 0; // FIXED?
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return "deviceControl";
|
||||
}
|
||||
|
||||
public SetDeviceTempRequest(final Heater heater, final int targetTemperature, final boolean masterSwitch,
|
||||
final boolean fanActive) {
|
||||
this.subDomain = heater.getSubDomain();
|
||||
this.deviceId = heater.getId();
|
||||
this.holdTemp = targetTemperature;
|
||||
this.status = masterSwitch;
|
||||
this.windStatus = fanActive;
|
||||
if (fanActive != heater.fanActive()) {
|
||||
// Changed
|
||||
operation = 4;
|
||||
} else if (heater.getTargetTemp() != targetTemperature) {
|
||||
operation = 1;
|
||||
} else {
|
||||
operation = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the set device temp response
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SetDeviceTempResponse extends AbstractResponse {
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the set holiday parameter request
|
||||
*
|
||||
* @see HomeDTO
|
||||
* @see GetHomesResponse
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SetHolidayParameterRequest implements AbstractRequest {
|
||||
|
||||
public static final String PROP_TEMP = "holidayTemp";
|
||||
public static final String PROP_MODE = "isHoliday";
|
||||
public static final String PROP_MODE_ADVANCED = "holidayTempType";
|
||||
public static final String PROP_START = "holidayStartTime";
|
||||
public static final String PROP_END = "holidayEndTime";
|
||||
|
||||
// {"timeZoneNum":"-01:00","value":11,"homeList":[{"homeId":XXXXXXXXXXXX}],"key":"holidayTemp"}
|
||||
public List<HomeID> homeList = new ArrayList<>();
|
||||
@SerializedName("timeZoneNum")
|
||||
public final String timeZone;
|
||||
public final String key;
|
||||
public final Object value;
|
||||
|
||||
/*
|
||||
* Valid parameters: holidayTemp (degrees), holidayStartTime (secs since epoch), holidayEndTime (secs since epoch),
|
||||
* isHoliday (boolean), holidayTempType (0 == advanced vacation mode - room uses it's own away temp, 1 == uses
|
||||
* holidayTemp)
|
||||
*/
|
||||
public SetHolidayParameterRequest(Long homeId, String timeZone, String parameter, Object value) {
|
||||
homeList.add(new HomeID(homeId));
|
||||
this.timeZone = timeZone;
|
||||
this.key = parameter;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return "holidayChooseHome";
|
||||
}
|
||||
|
||||
private class HomeID {
|
||||
@SuppressWarnings("unused")
|
||||
public Long homeId;
|
||||
|
||||
public HomeID(Long homeId) {
|
||||
super();
|
||||
this.homeId = homeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.millheat.internal.dto;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the set room temp response
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SetHolidayParameterResponse extends AbstractResponse {
|
||||
public String success;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return "true".contentEquals(success);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
import org.openhab.binding.millheat.internal.model.Home;
|
||||
import org.openhab.binding.millheat.internal.model.Room;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the set room temp request
|
||||
*
|
||||
* @see SetDeviceTempRequest
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SetRoomTempRequest implements AbstractRequest {
|
||||
public final long roomId;
|
||||
public int comfortTemp;
|
||||
public int sleepTemp;
|
||||
public int awayTemp;
|
||||
public final int homeType;
|
||||
|
||||
public SetRoomTempRequest(final Home home, final Room room) {
|
||||
roomId = room.getId();
|
||||
homeType = home.getType();
|
||||
comfortTemp = room.getComfortTemp();
|
||||
sleepTemp = room.getSleepTemp();
|
||||
awayTemp = room.getAwayTemp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return "changeRoomModeTempInfo";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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.millheat.internal.dto;
|
||||
|
||||
/**
|
||||
* This DTO class wraps the set room temp response
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SetRoomTempResponse extends AbstractResponse {
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
/**
|
||||
* 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.millheat.internal.handler;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.millheat.internal.MillheatCommunicationException;
|
||||
import org.openhab.binding.millheat.internal.client.BooleanSerializer;
|
||||
import org.openhab.binding.millheat.internal.client.RequestLogger;
|
||||
import org.openhab.binding.millheat.internal.config.MillheatAccountConfiguration;
|
||||
import org.openhab.binding.millheat.internal.dto.AbstractRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.AbstractResponse;
|
||||
import org.openhab.binding.millheat.internal.dto.DeviceDTO;
|
||||
import org.openhab.binding.millheat.internal.dto.GetHomesRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.GetHomesResponse;
|
||||
import org.openhab.binding.millheat.internal.dto.GetIndependentDevicesByHomeRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.GetIndependentDevicesByHomeResponse;
|
||||
import org.openhab.binding.millheat.internal.dto.HomeDTO;
|
||||
import org.openhab.binding.millheat.internal.dto.LoginRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.LoginResponse;
|
||||
import org.openhab.binding.millheat.internal.dto.RoomDTO;
|
||||
import org.openhab.binding.millheat.internal.dto.SelectDeviceByRoomRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.SelectDeviceByRoomResponse;
|
||||
import org.openhab.binding.millheat.internal.dto.SelectRoomByHomeRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.SelectRoomByHomeResponse;
|
||||
import org.openhab.binding.millheat.internal.dto.SetDeviceTempRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.SetHolidayParameterRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.SetHolidayParameterResponse;
|
||||
import org.openhab.binding.millheat.internal.dto.SetRoomTempRequest;
|
||||
import org.openhab.binding.millheat.internal.dto.SetRoomTempResponse;
|
||||
import org.openhab.binding.millheat.internal.model.Heater;
|
||||
import org.openhab.binding.millheat.internal.model.Home;
|
||||
import org.openhab.binding.millheat.internal.model.MillheatModel;
|
||||
import org.openhab.binding.millheat.internal.model.ModeType;
|
||||
import org.openhab.binding.millheat.internal.model.Room;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
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.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* The {@link MillheatAccountHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MillheatAccountHandler extends BaseBridgeHandler {
|
||||
private static final String SHA_1_ALGORITHM = "SHA-1";
|
||||
private static final int MIN_TIME_BETWEEEN_MODEL_UPDATES_MS = 30_000;
|
||||
private static final int NUM_NONCE_CHARS = 16;
|
||||
private static final String CONTENT_TYPE = "application/x-zc-object";
|
||||
private static final String ALLOWED_NONCE_CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
private static final int ALLOWED_NONCE_CHARACTERS_LENGTH = ALLOWED_NONCE_CHARACTERS.length();
|
||||
private static final String REQUEST_TIMEOUT = "300";
|
||||
public static String authEndpoint = "https://eurouter.ablecloud.cn:9005/zc-account/v1/";
|
||||
public static String serviceEndpoint = "https://eurouter.ablecloud.cn:9005/millService/v1/";
|
||||
private final Logger logger = LoggerFactory.getLogger(MillheatAccountHandler.class);
|
||||
private @Nullable String userId;
|
||||
private @Nullable String token;
|
||||
private final HttpClient httpClient;
|
||||
private final RequestLogger requestLogger;
|
||||
private final Gson gson;
|
||||
private MillheatModel model = new MillheatModel(0);
|
||||
private @Nullable ScheduledFuture<?> statusFuture;
|
||||
private @NonNullByDefault({}) MillheatAccountConfiguration config;
|
||||
|
||||
private static String getRandomString(final int sizeOfRandomString) {
|
||||
final Random random = new Random();
|
||||
final StringBuilder sb = new StringBuilder(sizeOfRandomString);
|
||||
for (int i = 0; i < sizeOfRandomString; ++i) {
|
||||
sb.append(ALLOWED_NONCE_CHARACTERS.charAt(random.nextInt(ALLOWED_NONCE_CHARACTERS_LENGTH)));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public MillheatAccountHandler(final Bridge bridge, final HttpClient httpClient, final BundleContext context) {
|
||||
super(bridge);
|
||||
this.httpClient = httpClient;
|
||||
final BooleanSerializer serializer = new BooleanSerializer();
|
||||
|
||||
gson = new GsonBuilder().setPrettyPrinting().setDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||
.registerTypeAdapter(Boolean.class, serializer).registerTypeAdapter(boolean.class, serializer)
|
||||
.setLenient().create();
|
||||
requestLogger = new RequestLogger(bridge.getUID().getId(), gson);
|
||||
}
|
||||
|
||||
private boolean allowModelUpdate() {
|
||||
final long timeSinceLastUpdate = System.currentTimeMillis() - model.getLastUpdated();
|
||||
if (timeSinceLastUpdate > MIN_TIME_BETWEEEN_MODEL_UPDATES_MS) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public MillheatModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
logger.debug("Bridge does not support any commands, but received command {} for channelUID {}", command,
|
||||
channelUID);
|
||||
}
|
||||
|
||||
public boolean doLogin() {
|
||||
try {
|
||||
final LoginResponse rsp = sendLoginRequest(new LoginRequest(config.username, config.password),
|
||||
LoginResponse.class);
|
||||
final int errorCode = rsp.errorCode;
|
||||
if (errorCode != 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
String.format("Error login in: code=%s, type=%s, message=%s", errorCode, rsp.errorName,
|
||||
rsp.errorDescription));
|
||||
} else {
|
||||
// No error provided on login, proceed to find token and userid
|
||||
String localToken = rsp.token.trim();
|
||||
userId = rsp.userId == null ? null : rsp.userId.toString();
|
||||
if (localToken == null || localToken.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"error login in, no token provided");
|
||||
} else if (userId == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"error login in, no userId provided");
|
||||
} else {
|
||||
token = localToken;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (final MillheatCommunicationException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error login: " + e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(MillheatAccountConfiguration.class);
|
||||
scheduler.execute(() -> {
|
||||
if (doLogin()) {
|
||||
try {
|
||||
model = refreshModel();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
initPolling();
|
||||
} catch (final MillheatCommunicationException e) {
|
||||
model = new MillheatModel(0); // Empty model
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"error fetching initial data " + e.getMessage());
|
||||
logger.debug("Error initializing Millheat data", e);
|
||||
// Reschedule init
|
||||
scheduler.schedule(() -> {
|
||||
initialize();
|
||||
}, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
});
|
||||
logger.debug("Finished initializing!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
stopPolling();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* starts this things polling future
|
||||
*/
|
||||
private void initPolling() {
|
||||
stopPolling();
|
||||
statusFuture = scheduler.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
updateModelFromServerWithRetry(true);
|
||||
} catch (final RuntimeException e) {
|
||||
logger.debug("Error refreshing model", e);
|
||||
}
|
||||
}, config.refreshInterval, config.refreshInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private <T> T sendLoginRequest(final AbstractRequest req, final Class<T> responseType)
|
||||
throws MillheatCommunicationException {
|
||||
final Request request = httpClient.newRequest(authEndpoint + req.getRequestUrl());
|
||||
addStandardHeadersAndPayload(request, req);
|
||||
return sendRequest(request, req, responseType);
|
||||
}
|
||||
|
||||
private <T> T sendLoggedInRequest(final AbstractRequest req, final Class<T> responseType)
|
||||
throws MillheatCommunicationException {
|
||||
try {
|
||||
final Request request = buildLoggedInRequest(req);
|
||||
return sendRequest(request, req, responseType);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new MillheatCommunicationException("Error building Millheat request: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T sendRequest(final Request request, final AbstractRequest req, final Class<T> responseType)
|
||||
throws MillheatCommunicationException {
|
||||
try {
|
||||
final ContentResponse contentResponse = request.send();
|
||||
final String responseJson = contentResponse.getContentAsString();
|
||||
if (contentResponse.getStatus() == HttpStatus.OK_200) {
|
||||
final AbstractResponse rsp = (AbstractResponse) gson.fromJson(responseJson, responseType);
|
||||
if (rsp == null) {
|
||||
return (T) null;
|
||||
} else if (rsp.errorCode == 0) {
|
||||
return (T) rsp;
|
||||
} else {
|
||||
throw new MillheatCommunicationException(req, rsp);
|
||||
}
|
||||
} else {
|
||||
throw new MillheatCommunicationException(
|
||||
"Error sending request to Millheat server. Server responded with " + contentResponse.getStatus()
|
||||
+ " and payload " + responseJson);
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new MillheatCommunicationException("Error sending request to Millheat server: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public MillheatModel refreshModel() throws MillheatCommunicationException {
|
||||
final MillheatModel model = new MillheatModel(System.currentTimeMillis());
|
||||
final GetHomesResponse homesRsp = sendLoggedInRequest(new GetHomesRequest(), GetHomesResponse.class);
|
||||
for (final HomeDTO dto : homesRsp.homes) {
|
||||
model.addHome(new Home(dto));
|
||||
}
|
||||
for (final Home home : model.getHomes()) {
|
||||
final SelectRoomByHomeResponse roomRsp = sendLoggedInRequest(
|
||||
new SelectRoomByHomeRequest(home.getId(), home.getTimezone()), SelectRoomByHomeResponse.class);
|
||||
for (final RoomDTO dto : roomRsp.rooms) {
|
||||
home.addRoom(new Room(dto, home));
|
||||
}
|
||||
|
||||
for (final Room room : home.getRooms()) {
|
||||
final SelectDeviceByRoomResponse deviceRsp = sendLoggedInRequest(
|
||||
new SelectDeviceByRoomRequest(room.getId(), home.getTimezone()),
|
||||
SelectDeviceByRoomResponse.class);
|
||||
for (final DeviceDTO dto : deviceRsp.devices) {
|
||||
room.addHeater(new Heater(dto, room));
|
||||
}
|
||||
}
|
||||
final GetIndependentDevicesByHomeResponse independentRsp = sendLoggedInRequest(
|
||||
new GetIndependentDevicesByHomeRequest(home.getId(), home.getTimezone()),
|
||||
GetIndependentDevicesByHomeResponse.class);
|
||||
for (final DeviceDTO dto : independentRsp.devices) {
|
||||
home.addHeater(new Heater(dto));
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this thing's polling future
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
private void stopPolling() {
|
||||
if (statusFuture != null && !statusFuture.isCancelled()) {
|
||||
statusFuture.cancel(true);
|
||||
statusFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateModelFromServerWithRetry(boolean forceUpdate) {
|
||||
if (allowModelUpdate() || forceUpdate) {
|
||||
try {
|
||||
updateModel();
|
||||
} catch (final MillheatCommunicationException e) {
|
||||
try {
|
||||
if (AbstractResponse.ERROR_CODE_ACCESS_TOKEN_EXPIRED == e.getErrorCode()
|
||||
|| AbstractResponse.ERROR_CODE_INVALID_SIGNATURE == e.getErrorCode()
|
||||
|| AbstractResponse.ERROR_CODE_AUTHENTICATION_FAILURE == e.getErrorCode()) {
|
||||
logger.debug("Token expired, will refresh token, then retry state refresh", e);
|
||||
if (doLogin()) {
|
||||
updateModel();
|
||||
}
|
||||
} else {
|
||||
logger.debug("Initiating retry due to error {}", e.getMessage(), e);
|
||||
updateModel();
|
||||
}
|
||||
} catch (MillheatCommunicationException e1) {
|
||||
logger.debug("Retry failed, waiting for next refresh cycle: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateModel() throws MillheatCommunicationException {
|
||||
model = refreshModel();
|
||||
updateThingStatuses();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
private void updateThingStatuses() {
|
||||
final List<Thing> subThings = getThing().getThings();
|
||||
for (final Thing thing : subThings) {
|
||||
final ThingHandler handler = thing.getHandler();
|
||||
if (handler != null) {
|
||||
final MillheatBaseThingHandler mHandler = (MillheatBaseThingHandler) handler;
|
||||
mHandler.updateState(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Request buildLoggedInRequest(final AbstractRequest req) throws NoSuchAlgorithmException {
|
||||
final String nonce = getRandomString(NUM_NONCE_CHARS);
|
||||
final String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
final String signatureBasis = REQUEST_TIMEOUT + timestamp + nonce + token;
|
||||
MessageDigest md = MessageDigest.getInstance(SHA_1_ALGORITHM);
|
||||
byte[] sha1Hash = md.digest(signatureBasis.getBytes(StandardCharsets.UTF_8));
|
||||
final String signature = HexUtils.bytesToHex(sha1Hash).toLowerCase();
|
||||
final String reqJson = gson.toJson(req);
|
||||
|
||||
final Request request = httpClient.newRequest(serviceEndpoint + req.getRequestUrl());
|
||||
|
||||
return addStandardHeadersAndPayload(request, req).header("X-Zc-Timestamp", timestamp)
|
||||
.header("X-Zc-Timeout", REQUEST_TIMEOUT).header("X-Zc-Nonce", nonce).header("X-Zc-User-Id", userId)
|
||||
.header("X-Zc-User-Signature", signature).header("X-Zc-Content-Length", "" + reqJson.length());
|
||||
}
|
||||
|
||||
private Request addStandardHeadersAndPayload(final Request req, final AbstractRequest payload) {
|
||||
requestLogger.listenTo(req);
|
||||
|
||||
return req.header("Connection", "Keep-Alive").header("X-Zc-Major-Domain", "seanywell")
|
||||
.header("X-Zc-Msg-Name", "millService").header("X-Zc-Sub-Domain", "milltype").header("X-Zc-Seq-Id", "1")
|
||||
.header("X-Zc-Version", "1").method(HttpMethod.POST).timeout(5, TimeUnit.SECONDS)
|
||||
.content(new BytesContentProvider(gson.toJson(payload).getBytes(StandardCharsets.UTF_8)), CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public void updateRoomTemperature(final Long roomId, final Command command, final ModeType mode) {
|
||||
final Optional<Home> optionalHome = model.findHomeByRoomId(roomId);
|
||||
final Optional<Room> optionalRoom = model.findRoomById(roomId);
|
||||
if (optionalHome.isPresent() && optionalRoom.isPresent()) {
|
||||
final SetRoomTempRequest req = new SetRoomTempRequest(optionalHome.get(), optionalRoom.get());
|
||||
if (command instanceof QuantityType<?>) {
|
||||
final int newTemp = (int) ((QuantityType<?>) command).longValue();
|
||||
switch (mode) {
|
||||
case SLEEP:
|
||||
req.sleepTemp = newTemp;
|
||||
break;
|
||||
case AWAY:
|
||||
req.awayTemp = newTemp;
|
||||
break;
|
||||
case COMFORT:
|
||||
req.comfortTemp = newTemp;
|
||||
break;
|
||||
default:
|
||||
logger.info("Cannot set room temp for mode {}", mode);
|
||||
}
|
||||
try {
|
||||
sendLoggedInRequest(req, SetRoomTempResponse.class);
|
||||
} catch (final MillheatCommunicationException e) {
|
||||
logger.debug("Error updating temperature for room {}", roomId, e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Error updating temperature for room {}, expected QuantityType but got {}", roomId,
|
||||
command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateIndependentHeaterProperties(@Nullable final String macAddress, @Nullable final Long heaterId,
|
||||
@Nullable final Command temperatureCommand, @Nullable final Command masterOnOffCommand,
|
||||
@Nullable final Command fanCommand) {
|
||||
model.findHeaterByMacOrId(macAddress, heaterId).ifPresent(heater -> {
|
||||
int setTemp = heater.getTargetTemp();
|
||||
if (temperatureCommand instanceof QuantityType<?>) {
|
||||
setTemp = (int) ((QuantityType<?>) temperatureCommand).longValue();
|
||||
}
|
||||
boolean masterOnOff = heater.powerStatus();
|
||||
if (masterOnOffCommand != null) {
|
||||
masterOnOff = masterOnOffCommand == OnOffType.ON;
|
||||
}
|
||||
boolean fanActive = heater.fanActive();
|
||||
if (fanCommand != null) {
|
||||
fanActive = fanCommand == OnOffType.ON;
|
||||
}
|
||||
final SetDeviceTempRequest req = new SetDeviceTempRequest(heater, setTemp, masterOnOff, fanActive);
|
||||
try {
|
||||
sendLoggedInRequest(req, SetRoomTempResponse.class);
|
||||
heater.setTargetTemp(setTemp);
|
||||
heater.setPowerStatus(masterOnOff);
|
||||
heater.setFanActive(fanActive);
|
||||
} catch (final MillheatCommunicationException e) {
|
||||
logger.debug("Error updating temperature for heater {}", macAddress, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void updateVacationProperty(Home home, String property, Command command) {
|
||||
try {
|
||||
switch (property) {
|
||||
case SetHolidayParameterRequest.PROP_START: {
|
||||
long epoch = ((DateTimeType) command).getZonedDateTime().toEpochSecond();
|
||||
SetHolidayParameterRequest req = new SetHolidayParameterRequest(home.getId(), home.getTimezone(),
|
||||
SetHolidayParameterRequest.PROP_START, epoch);
|
||||
if (sendLoggedInRequest(req, SetHolidayParameterResponse.class).isSuccess()) {
|
||||
home.setVacationModeStart(epoch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SetHolidayParameterRequest.PROP_END: {
|
||||
long epoch = ((DateTimeType) command).getZonedDateTime().toEpochSecond();
|
||||
SetHolidayParameterRequest req = new SetHolidayParameterRequest(home.getId(), home.getTimezone(),
|
||||
SetHolidayParameterRequest.PROP_END, epoch);
|
||||
if (sendLoggedInRequest(req, SetHolidayParameterResponse.class).isSuccess()) {
|
||||
home.setVacationModeEnd(epoch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SetHolidayParameterRequest.PROP_TEMP: {
|
||||
int holidayTemp = ((QuantityType<?>) command).intValue();
|
||||
SetHolidayParameterRequest req = new SetHolidayParameterRequest(home.getId(), home.getTimezone(),
|
||||
SetHolidayParameterRequest.PROP_TEMP, holidayTemp);
|
||||
if (sendLoggedInRequest(req, SetHolidayParameterResponse.class).isSuccess()) {
|
||||
home.setHolidayTemp(holidayTemp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SetHolidayParameterRequest.PROP_MODE_ADVANCED: {
|
||||
if (home.getMode().getMode() == ModeType.VACATION) {
|
||||
int value = OnOffType.ON == command ? 0 : 1;
|
||||
SetHolidayParameterRequest req = new SetHolidayParameterRequest(home.getId(),
|
||||
home.getTimezone(), SetHolidayParameterRequest.PROP_MODE_ADVANCED, value);
|
||||
if (sendLoggedInRequest(req, SetHolidayParameterResponse.class).isSuccess()) {
|
||||
home.setVacationModeAdvanced((OnOffType) command);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Must enable vaction mode before advanced vacation mode can be enabled");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SetHolidayParameterRequest.PROP_MODE: {
|
||||
if (home.getVacationModeStart() != null && home.getVacationModeEnd() != null) {
|
||||
int value = OnOffType.ON == command ? 1 : 0;
|
||||
SetHolidayParameterRequest req = new SetHolidayParameterRequest(home.getId(),
|
||||
home.getTimezone(), SetHolidayParameterRequest.PROP_MODE, value);
|
||||
if (sendLoggedInRequest(req, SetHolidayParameterResponse.class).isSuccess()) {
|
||||
updateModelFromServerWithRetry(true);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Cannot enable vacation mode unless start and end time is already set");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (MillheatCommunicationException e) {
|
||||
logger.debug("Failure trying to set holiday properties: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.millheat.internal.handler;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.millheat.internal.model.MillheatModel;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class for heater and room handlers
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class MillheatBaseThingHandler extends BaseThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(MillheatBaseThingHandler.class);
|
||||
|
||||
public MillheatBaseThingHandler(final Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
public void updateState(final MillheatModel model) {
|
||||
for (final Channel channel : getThing().getChannels()) {
|
||||
handleCommand(channel.getUID(), RefreshType.REFRESH, model);
|
||||
}
|
||||
}
|
||||
|
||||
protected MillheatModel getMillheatModel() {
|
||||
final Optional<MillheatAccountHandler> accountHandler = getAccountHandler();
|
||||
if (accountHandler.isPresent()) {
|
||||
return accountHandler.get().getModel();
|
||||
} else {
|
||||
logger.warn(
|
||||
"Thing {} cannot exist without a bridge and account handler - returning empty model. No heaters or rooms will be found",
|
||||
getThing().getUID());
|
||||
return new MillheatModel(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected Optional<MillheatAccountHandler> getAccountHandler() {
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
MillheatAccountHandler handler = (MillheatAccountHandler) bridge.getHandler();
|
||||
if (handler != null) {
|
||||
return Optional.of(handler);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
protected abstract void handleCommand(ChannelUID uid, Command command, MillheatModel model);
|
||||
}
|
||||
@@ -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.millheat.internal.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.millheat.internal.MillheatBindingConstants;
|
||||
import org.openhab.binding.millheat.internal.config.MillheatHeaterConfiguration;
|
||||
import org.openhab.binding.millheat.internal.model.Heater;
|
||||
import org.openhab.binding.millheat.internal.model.MillheatModel;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MillheatHeaterHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MillheatHeaterHandler extends MillheatBaseThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(MillheatHeaterHandler.class);
|
||||
private @NonNullByDefault({}) MillheatHeaterConfiguration config;
|
||||
|
||||
public MillheatHeaterHandler(final Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
handleCommand(channelUID, command, getMillheatModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleCommand(final ChannelUID channelUID, final Command command, final MillheatModel model) {
|
||||
final Optional<Heater> optionalHeater = model.findHeaterByMacOrId(config.macAddress, config.heaterId);
|
||||
if (optionalHeater.isPresent()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
final Heater heater = optionalHeater.get();
|
||||
if (MillheatBindingConstants.CHANNEL_CURRENT_TEMPERATURE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new QuantityType<>(heater.getCurrentTemp(), SIUnits.CELSIUS));
|
||||
}
|
||||
} else if (MillheatBindingConstants.CHANNEL_HEATING_ACTIVE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, heater.isHeatingActive() ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
} else if (MillheatBindingConstants.CHANNEL_FAN_ACTIVE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, heater.fanActive() ? OnOffType.ON : OnOffType.OFF);
|
||||
} else if (heater.canChangeTemp() && heater.getRoom() == null) {
|
||||
updateIndependentHeaterProperties(null, null, command);
|
||||
} else {
|
||||
logger.debug("Heater {} cannot change temperature and is in a room", getThing().getUID());
|
||||
}
|
||||
} else if (MillheatBindingConstants.CHANNEL_WINDOW_STATE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, heater.windowOpen() ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
|
||||
}
|
||||
} else if (MillheatBindingConstants.CHANNEL_INDEPENDENT.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, heater.getRoom() == null ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
} else if (MillheatBindingConstants.CHANNEL_CURRENT_POWER.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (config.power != null) {
|
||||
if (heater.isHeatingActive()) {
|
||||
updateState(channelUID, new QuantityType<>(config.power, SmartHomeUnits.WATT));
|
||||
} else {
|
||||
updateState(channelUID, new QuantityType<>(0, SmartHomeUnits.WATT));
|
||||
}
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
logger.debug(
|
||||
"Cannot update power for heater as the nominal power has not been configured for thing {}",
|
||||
getThing().getUID());
|
||||
}
|
||||
}
|
||||
} else if (MillheatBindingConstants.CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (heater.canChangeTemp() && heater.getTargetTemp() != null) {
|
||||
updateState(channelUID, new QuantityType<>(heater.getTargetTemp(), SIUnits.CELSIUS));
|
||||
} else if (heater.getRoom() != null) {
|
||||
final Integer targetTemperature = heater.getRoom().getTargetTemperature();
|
||||
if (targetTemperature != null) {
|
||||
updateState(channelUID, new QuantityType<>(targetTemperature, SIUnits.CELSIUS));
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
} else {
|
||||
logger.debug(
|
||||
"Heater {} is neither connected to a room nor marked as standalone. Someting is wrong, heater data: {}",
|
||||
getThing().getUID(), heater);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
}
|
||||
} else {
|
||||
if (heater.canChangeTemp() && heater.getRoom() == null) {
|
||||
updateIndependentHeaterProperties(command, null, null);
|
||||
}
|
||||
}
|
||||
} else if (MillheatBindingConstants.CHANNEL_MASTER_SWITCH.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, heater.powerStatus() ? OnOffType.ON : OnOffType.OFF);
|
||||
} else {
|
||||
if (heater.canChangeTemp() && heater.getRoom() == null) {
|
||||
updateIndependentHeaterProperties(null, command, null);
|
||||
} else {
|
||||
// Just overwrite with old state
|
||||
updateState(channelUID, heater.powerStatus() ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Received command {} on channel {}, but this channel is not handled or supported by {}",
|
||||
channelUID.getId(), command.toString(), this.getThing().getUID());
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIndependentHeaterProperties(@Nullable final Command temperatureCommand,
|
||||
@Nullable final Command masterOnOffCommand, @Nullable final Command fanCommand) {
|
||||
getAccountHandler().ifPresent(handler -> {
|
||||
handler.updateIndependentHeaterProperties(config.macAddress, config.heaterId, temperatureCommand,
|
||||
masterOnOffCommand, fanCommand);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(MillheatHeaterConfiguration.class);
|
||||
logger.debug("Initializing Millheat heater using config {}", config);
|
||||
if (config.heaterId == null && config.macAddress == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
} else {
|
||||
final Optional<Heater> heater = getMillheatModel().findHeaterByMacOrId(config.macAddress, config.heaterId);
|
||||
if (heater.isPresent()) {
|
||||
addOptionalChannels(heater.get());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addOptionalChannels(final Heater heater) {
|
||||
final List<Channel> newChannels = new ArrayList<>();
|
||||
newChannels.addAll(getThing().getChannels());
|
||||
if (heater.canChangeTemp() && heater.getRoom() == null) {
|
||||
// Add power switch channel
|
||||
newChannels
|
||||
.add(ChannelBuilder
|
||||
.create(new ChannelUID(getThing().getUID(), MillheatBindingConstants.CHANNEL_MASTER_SWITCH),
|
||||
"Switch")
|
||||
.withType(MillheatBindingConstants.CHANNEL_TYPE_MASTER_SWITCH_UID).build());
|
||||
// Add independent heater target temperature
|
||||
newChannels.add(ChannelBuilder
|
||||
.create(new ChannelUID(getThing().getUID(), MillheatBindingConstants.CHANNEL_TARGET_TEMPERATURE),
|
||||
"Number:Temperature")
|
||||
.withType(MillheatBindingConstants.CHANNEL_TYPE_TARGET_TEMPERATURE_HEATER_UID).build());
|
||||
}
|
||||
|
||||
updateThing(editThing().withChannels(newChannels).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 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.millheat.internal.handler;
|
||||
|
||||
import static org.openhab.binding.millheat.internal.MillheatBindingConstants.*;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.millheat.internal.config.MillheatHomeConfiguration;
|
||||
import org.openhab.binding.millheat.internal.dto.SetHolidayParameterRequest;
|
||||
import org.openhab.binding.millheat.internal.model.Home;
|
||||
import org.openhab.binding.millheat.internal.model.MillheatModel;
|
||||
import org.openhab.binding.millheat.internal.model.ModeType;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
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.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MillheatHomeHandler} is responsible for handling home commands, for now vacation mode properties
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MillheatHomeHandler extends MillheatBaseThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(MillheatHomeHandler.class);
|
||||
private @NonNullByDefault({}) MillheatHomeConfiguration config;
|
||||
|
||||
public MillheatHomeHandler(final Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
handleCommand(channelUID, command, getMillheatModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleCommand(final ChannelUID channelUID, final Command command, final MillheatModel model) {
|
||||
final Optional<Home> optionalHome = model.findHomeById(config.homeId);
|
||||
if (optionalHome.isPresent()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
final Home home = optionalHome.get();
|
||||
if (CHANNEL_HOME_VACATION_TARGET_TEMPERATURE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new QuantityType<>(home.getHolidayTemp(), SIUnits.CELSIUS));
|
||||
} else if (command instanceof QuantityType<?>) {
|
||||
updateVacationModeProperty(home, SetHolidayParameterRequest.PROP_TEMP, command);
|
||||
} else if (command instanceof DecimalType) {
|
||||
updateVacationModeProperty(home, SetHolidayParameterRequest.PROP_TEMP,
|
||||
new QuantityType<>((DecimalType) command, SIUnits.CELSIUS));
|
||||
}
|
||||
} else if (CHANNEL_HOME_VACATION_MODE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, OnOffType.from(home.getMode().getMode() == ModeType.VACATION));
|
||||
} else if (command instanceof OnOffType) {
|
||||
updateVacationModeProperty(home, SetHolidayParameterRequest.PROP_MODE, command);
|
||||
}
|
||||
} else if (CHANNEL_HOME_VACATION_MODE_ADVANCED.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, OnOffType.from(home.isAdvancedVacationMode()));
|
||||
} else if (command instanceof OnOffType) {
|
||||
updateVacationModeProperty(home, SetHolidayParameterRequest.PROP_MODE_ADVANCED, command);
|
||||
}
|
||||
} else if (CHANNEL_HOME_VACATION_MODE_START.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (home.getVacationModeStart() != null) {
|
||||
updateState(channelUID,
|
||||
new DateTimeType(home.getVacationModeStart().atZone(ZoneId.systemDefault())));
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
} else if (command instanceof DateTimeType) {
|
||||
updateVacationModeProperty(home, SetHolidayParameterRequest.PROP_START, command);
|
||||
}
|
||||
} else if (CHANNEL_HOME_VACATION_MODE_END.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (home.getVacationModeEnd() != null) {
|
||||
updateState(channelUID,
|
||||
new DateTimeType(home.getVacationModeEnd().atZone(ZoneId.systemDefault())));
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
} else if (command instanceof DateTimeType) {
|
||||
updateVacationModeProperty(home, SetHolidayParameterRequest.PROP_END, command);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Received command {} on channel {}, but this channel is not handled or supported by {}",
|
||||
channelUID.getId(), command.toString(), this.getThing().getUID());
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVacationModeProperty(Home home, String property, Command command) {
|
||||
getAccountHandler().ifPresent(handler -> {
|
||||
handler.updateVacationProperty(home, property, command);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(MillheatHomeConfiguration.class);
|
||||
logger.debug("Initializing Millheat home using config {}", config);
|
||||
final Optional<Home> room = getMillheatModel().findHomeById(config.homeId);
|
||||
if (room.isPresent()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 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.millheat.internal.handler;
|
||||
|
||||
import static org.openhab.binding.millheat.internal.MillheatBindingConstants.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.millheat.internal.config.MillheatRoomConfiguration;
|
||||
import org.openhab.binding.millheat.internal.model.MillheatModel;
|
||||
import org.openhab.binding.millheat.internal.model.ModeType;
|
||||
import org.openhab.binding.millheat.internal.model.Room;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
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.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MillheatRoomHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MillheatRoomHandler extends MillheatBaseThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(MillheatRoomHandler.class);
|
||||
private @NonNullByDefault({}) MillheatRoomConfiguration config;
|
||||
|
||||
public MillheatRoomHandler(final Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
handleCommand(channelUID, command, getMillheatModel());
|
||||
}
|
||||
|
||||
private void updateRoomTemperature(final Long roomId, final Command command, final ModeType modeType) {
|
||||
getAccountHandler().ifPresent(handler -> {
|
||||
handler.updateRoomTemperature(config.roomId, command, modeType);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleCommand(final ChannelUID channelUID, final Command command, final MillheatModel model) {
|
||||
final Optional<Room> optionalRoom = model.findRoomById(config.roomId);
|
||||
if (optionalRoom.isPresent()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
final Room room = optionalRoom.get();
|
||||
if (CHANNEL_CURRENT_TEMPERATURE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new QuantityType<>(room.getCurrentTemp(), SIUnits.CELSIUS));
|
||||
}
|
||||
} else if (CHANNEL_CURRENT_MODE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new StringType(room.getMode().toString()));
|
||||
}
|
||||
} else if (CHANNEL_PROGRAM.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new StringType(room.getRoomProgramName()));
|
||||
}
|
||||
} else if (CHANNEL_COMFORT_TEMPERATURE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new QuantityType<>(room.getComfortTemp(), SIUnits.CELSIUS));
|
||||
} else {
|
||||
updateRoomTemperature(config.roomId, command, ModeType.COMFORT);
|
||||
}
|
||||
} else if (CHANNEL_SLEEP_TEMPERATURE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new QuantityType<>(room.getSleepTemp(), SIUnits.CELSIUS));
|
||||
} else {
|
||||
updateRoomTemperature(config.roomId, command, ModeType.SLEEP);
|
||||
}
|
||||
} else if (CHANNEL_AWAY_TEMPERATURE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new QuantityType<>(room.getAwayTemp(), SIUnits.CELSIUS));
|
||||
} else {
|
||||
updateRoomTemperature(config.roomId, command, ModeType.AWAY);
|
||||
}
|
||||
} else if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
final Integer targetTemperature = room.getTargetTemperature();
|
||||
if (targetTemperature != null) {
|
||||
updateState(channelUID, new QuantityType<>(targetTemperature, SIUnits.CELSIUS));
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
} else if (CHANNEL_HEATING_ACTIVE.equals(channelUID.getId())) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, room.isHeatingActive() ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Received command {} on channel {}, but this channel is not handled or supported by {}",
|
||||
channelUID.getId(), command.toString(), this.getThing().getUID());
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(MillheatRoomConfiguration.class);
|
||||
logger.debug("Initializing Millheat room using config {}", config);
|
||||
final Optional<Room> room = getMillheatModel().findRoomById(config.roomId);
|
||||
if (room.isPresent()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* 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.millheat.internal.model;
|
||||
|
||||
import org.openhab.binding.millheat.internal.dto.DeviceDTO;
|
||||
|
||||
/**
|
||||
* The {@link Heater} represents a heater, either connected to a room or independent
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class Heater {
|
||||
private Room room;
|
||||
private final Long id;
|
||||
private final String name;
|
||||
private final String macAddress;
|
||||
private final boolean heatingActive;
|
||||
private boolean canChangeTemp = true;
|
||||
private final int subDomain;
|
||||
private final int currentTemp;
|
||||
private Integer targetTemp;
|
||||
private boolean fanActive;
|
||||
private boolean powerStatus;
|
||||
private final boolean windowOpen;
|
||||
|
||||
public Heater(final DeviceDTO dto) {
|
||||
id = dto.deviceId;
|
||||
name = dto.deviceName;
|
||||
macAddress = dto.macAddress;
|
||||
heatingActive = dto.heaterFlag;
|
||||
canChangeTemp = dto.holiday;
|
||||
subDomain = dto.subDomainId;
|
||||
currentTemp = (int) dto.currentTemp;
|
||||
setTargetTemp(dto.holidayTemp);
|
||||
setFanActive(dto.fanStatus);
|
||||
setPowerStatus(dto.powerStatus);
|
||||
windowOpen = dto.openWindow;
|
||||
}
|
||||
|
||||
public Heater(final DeviceDTO dto, final Room room) {
|
||||
this.room = room;
|
||||
id = dto.deviceId;
|
||||
name = dto.deviceName;
|
||||
macAddress = dto.macAddress;
|
||||
heatingActive = dto.heaterFlag;
|
||||
canChangeTemp = dto.canChangeTemp;
|
||||
subDomain = dto.subDomainId;
|
||||
currentTemp = (int) dto.currentTemp;
|
||||
if (room != null && room.getMode() != null) {
|
||||
switch (room.getMode()) {
|
||||
case COMFORT:
|
||||
setTargetTemp(room.getComfortTemp());
|
||||
break;
|
||||
case SLEEP:
|
||||
setTargetTemp(room.getSleepTemp());
|
||||
break;
|
||||
case AWAY:
|
||||
setTargetTemp(room.getAwayTemp());
|
||||
break;
|
||||
case OFF:
|
||||
setTargetTemp(null);
|
||||
break;
|
||||
default:
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
setFanActive(dto.fanStatus);
|
||||
setPowerStatus(dto.powerStatus);
|
||||
windowOpen = dto.openWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Heater [room=" + room + ", id=" + id + ", name=" + name + ", macAddress=" + macAddress
|
||||
+ ", heatingActive=" + heatingActive + ", canChangeTemp=" + canChangeTemp + ", subDomain=" + subDomain
|
||||
+ ", currentTemp=" + currentTemp + ", targetTemp=" + getTargetTemp() + ", fanActive=" + fanActive()
|
||||
+ ", powerStatus=" + powerStatus() + ", windowOpen=" + windowOpen + "]";
|
||||
}
|
||||
|
||||
public Room getRoom() {
|
||||
return room;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getMacAddress() {
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
public boolean isHeatingActive() {
|
||||
return heatingActive;
|
||||
}
|
||||
|
||||
public boolean canChangeTemp() {
|
||||
return canChangeTemp;
|
||||
}
|
||||
|
||||
public int getSubDomain() {
|
||||
return subDomain;
|
||||
}
|
||||
|
||||
public int getCurrentTemp() {
|
||||
return currentTemp;
|
||||
}
|
||||
|
||||
public Integer getTargetTemp() {
|
||||
return targetTemp;
|
||||
}
|
||||
|
||||
public boolean fanActive() {
|
||||
return fanActive;
|
||||
}
|
||||
|
||||
public boolean powerStatus() {
|
||||
return powerStatus;
|
||||
}
|
||||
|
||||
public boolean windowOpen() {
|
||||
return windowOpen;
|
||||
}
|
||||
|
||||
public void setTargetTemp(final Integer targetTemp) {
|
||||
this.targetTemp = targetTemp;
|
||||
}
|
||||
|
||||
public void setFanActive(final boolean fanActive) {
|
||||
this.fanActive = fanActive;
|
||||
}
|
||||
|
||||
public void setPowerStatus(final boolean powerStatus) {
|
||||
this.powerStatus = powerStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 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.millheat.internal.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.millheat.internal.dto.HomeDTO;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
/**
|
||||
* The {@link Home} represents a home
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class Home {
|
||||
private final long id;
|
||||
private final String name;
|
||||
private final int type;
|
||||
private final String zoneOffset;
|
||||
private int holidayTemp;
|
||||
private Mode mode;
|
||||
private final String program = null;
|
||||
private final List<Room> rooms = new ArrayList<>();
|
||||
private final List<Heater> independentHeaters = new ArrayList<>();
|
||||
private LocalDateTime vacationModeStart;
|
||||
private LocalDateTime vacationModeEnd;
|
||||
private boolean advancedVacationMode;
|
||||
|
||||
public Home(final HomeDTO dto) {
|
||||
id = dto.homeId;
|
||||
name = dto.name;
|
||||
type = dto.homeType;
|
||||
zoneOffset = dto.timeZone;
|
||||
holidayTemp = dto.holidayTemp;
|
||||
advancedVacationMode = dto.holidayTempType == 0;
|
||||
if (dto.holidayStartTime != 0) {
|
||||
vacationModeStart = convertFromEpoch(dto.holidayStartTime);
|
||||
}
|
||||
if (dto.holidayEndTime != 0) {
|
||||
vacationModeEnd = convertFromEpoch(dto.holidayEndTime);
|
||||
}
|
||||
|
||||
if (dto.holiday) {
|
||||
mode = new Mode(ModeType.VACATION, vacationModeStart, vacationModeEnd);
|
||||
} else if (dto.alwaysHome) {
|
||||
mode = new Mode(ModeType.ALWAYSHOME, null, null);
|
||||
} else {
|
||||
final LocalDateTime modeStart = LocalDateTime.ofEpochSecond(dto.modeStartTime, 0,
|
||||
ZoneOffset.of(zoneOffset));
|
||||
final LocalDateTime modeEnd = modeStart.withHour(dto.modeHour).withMinute(dto.modeMinute);
|
||||
mode = new Mode(ModeType.valueOf(dto.currentMode), modeStart, modeEnd);
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDateTime convertFromEpoch(long epoch) {
|
||||
return LocalDateTime.ofEpochSecond(epoch, 0, ZoneOffset.of(zoneOffset));
|
||||
}
|
||||
|
||||
public void addRoom(final Room room) {
|
||||
rooms.add(room);
|
||||
}
|
||||
|
||||
public void addHeater(final Heater heater) {
|
||||
independentHeaters.add(heater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Home [id=" + id + ", name=" + name + ", type=" + type + ", zoneOffset=" + zoneOffset + ", holidayTemp="
|
||||
+ holidayTemp + ", mode=" + mode + ", rooms=" + rooms + ", independentHeaters=" + independentHeaters
|
||||
+ ", program=" + program + "]";
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getTimezone() {
|
||||
return zoneOffset;
|
||||
}
|
||||
|
||||
public int getHolidayTemp() {
|
||||
return holidayTemp;
|
||||
}
|
||||
|
||||
public Mode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public String getProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
public List<Room> getRooms() {
|
||||
return rooms;
|
||||
}
|
||||
|
||||
public List<Heater> getIndependentHeaters() {
|
||||
return independentHeaters;
|
||||
}
|
||||
|
||||
public LocalDateTime getVacationModeStart() {
|
||||
return vacationModeStart;
|
||||
}
|
||||
|
||||
public LocalDateTime getVacationModeEnd() {
|
||||
return vacationModeEnd;
|
||||
}
|
||||
|
||||
public void setVacationModeStart(long epoch) {
|
||||
vacationModeStart = convertFromEpoch(epoch);
|
||||
updateVacationMode();
|
||||
}
|
||||
|
||||
public void setVacationModeEnd(long epoch) {
|
||||
vacationModeEnd = convertFromEpoch(epoch);
|
||||
updateVacationMode();
|
||||
}
|
||||
|
||||
public void setHolidayTemp(int holidayTemp) {
|
||||
this.holidayTemp = holidayTemp;
|
||||
updateVacationMode();
|
||||
}
|
||||
|
||||
private void updateVacationMode() {
|
||||
if (mode.getMode() == ModeType.VACATION) {
|
||||
mode = new Mode(ModeType.VACATION, vacationModeStart, vacationModeEnd);
|
||||
}
|
||||
}
|
||||
|
||||
public void setVacationModeAdvanced(OnOffType command) {
|
||||
advancedVacationMode = (OnOffType.ON == command);
|
||||
}
|
||||
|
||||
public boolean isAdvancedVacationMode() {
|
||||
return advancedVacationMode;
|
||||
}
|
||||
|
||||
public void setAdvancedVacationMode(boolean advancedVacationMode) {
|
||||
this.advancedVacationMode = advancedVacationMode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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.millheat.internal.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link MillheatModel} represents the home structure as designed by the user in the Millheat app.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MillheatModel {
|
||||
private final long lastUpdated;
|
||||
private final List<Home> homes = new ArrayList<>();
|
||||
|
||||
public MillheatModel(final long lastUpdated) {
|
||||
this.lastUpdated = lastUpdated;
|
||||
}
|
||||
|
||||
public void addHome(final Home home) {
|
||||
homes.add(home);
|
||||
}
|
||||
|
||||
public List<Home> getHomes() {
|
||||
return homes;
|
||||
}
|
||||
|
||||
public long getLastUpdated() {
|
||||
return lastUpdated;
|
||||
}
|
||||
|
||||
public Optional<Heater> findHeaterById(final Long id) {
|
||||
return findHeaters().filter(heater -> id.equals(heater.getId())).findFirst();
|
||||
}
|
||||
|
||||
public Optional<Heater> findHeaterByMac(final String macAddress) {
|
||||
return findHeaters().filter(heater -> macAddress.equals(heater.getMacAddress())).findFirst();
|
||||
}
|
||||
|
||||
public Optional<Heater> findHeaterByMacOrId(@Nullable final String macAddress, @Nullable final Long id) {
|
||||
Optional<Heater> heater = Optional.empty();
|
||||
|
||||
if (macAddress != null) {
|
||||
heater = findHeaterByMac(macAddress);
|
||||
}
|
||||
if (!heater.isPresent() && id != null) {
|
||||
heater = findHeaterById(id);
|
||||
}
|
||||
return heater;
|
||||
}
|
||||
|
||||
private Stream<Heater> findHeaters() {
|
||||
return Stream.concat(
|
||||
homes.stream().flatMap(home -> home.getRooms().stream()).flatMap(room -> room.getHeaters().stream()),
|
||||
homes.stream().flatMap(room -> room.getIndependentHeaters().stream()));
|
||||
}
|
||||
|
||||
public Optional<Room> findRoomById(final Long id) {
|
||||
return homes.stream().flatMap(home -> home.getRooms().stream()).filter(room -> id.equals(room.getId()))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public Optional<Home> findHomeByRoomId(final Long id) {
|
||||
for (final Home home : homes) {
|
||||
for (final Room room : home.getRooms()) {
|
||||
if (id.equals(room.getId())) {
|
||||
return Optional.of(home);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<Home> findHomeById(Long homeId) {
|
||||
return homes.stream().filter(e -> e.getId().equals(homeId)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.millheat.internal.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* The {@link Mode} represents a mode with start and end time
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class Mode {
|
||||
private final ModeType mode;
|
||||
private final LocalDateTime start;
|
||||
private final LocalDateTime end;
|
||||
|
||||
public Mode(final ModeType mode, final LocalDateTime start, final LocalDateTime end) {
|
||||
this.mode = mode;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public ModeType getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public LocalDateTime getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public LocalDateTime getEnd() {
|
||||
return end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.millheat.internal.model;
|
||||
|
||||
/**
|
||||
* The {@link ModeType} represents a type of mode the user can set in the app.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public enum ModeType {
|
||||
ALWAYSHOME(-1),
|
||||
COMFORT(1),
|
||||
SLEEP(2),
|
||||
AWAY(3),
|
||||
VACATION(4),
|
||||
OFF(5);
|
||||
|
||||
public static ModeType valueOf(final int modeVal) {
|
||||
for (final ModeType mode : ModeType.values()) {
|
||||
if (mode.value == modeVal) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final int value;
|
||||
|
||||
ModeType(final int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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.millheat.internal.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.millheat.internal.dto.RoomDTO;
|
||||
|
||||
/**
|
||||
* The {@link Room} represents a room in a home as designed by the end user in the Millheat app.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class Room {
|
||||
private final Home home;
|
||||
private final long id;
|
||||
private final String name;
|
||||
private final int currentTemp;
|
||||
private final int comfortTemp;
|
||||
private final int sleepTemp;
|
||||
private final int awayTemp;
|
||||
private final boolean heatingActive;
|
||||
private final ModeType mode;
|
||||
private final String roomProgramName;
|
||||
private final List<Heater> heaters = new ArrayList<>();
|
||||
|
||||
public Room(final RoomDTO dto, final Home home) {
|
||||
this.home = home;
|
||||
id = dto.roomId;
|
||||
name = dto.name;
|
||||
currentTemp = (int) dto.currentTemp;
|
||||
comfortTemp = dto.comfortTemp;
|
||||
sleepTemp = dto.sleepTemp;
|
||||
awayTemp = dto.awayTemp;
|
||||
heatingActive = dto.heatStatus;
|
||||
mode = ModeType.valueOf(dto.currentMode);
|
||||
roomProgramName = dto.roomProgram;
|
||||
}
|
||||
|
||||
public void addHeater(final Heater h) {
|
||||
heaters.add(h);
|
||||
}
|
||||
|
||||
public List<Heater> getHeaters() {
|
||||
return heaters;
|
||||
}
|
||||
|
||||
public Integer getTargetTemperature() {
|
||||
switch (mode) {
|
||||
case VACATION:
|
||||
return home.getHolidayTemp();
|
||||
case SLEEP:
|
||||
return sleepTemp;
|
||||
case COMFORT:
|
||||
return comfortTemp;
|
||||
case AWAY:
|
||||
return awayTemp;
|
||||
case OFF:
|
||||
case ALWAYSHOME:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Room [home=" + home.getId() + ", id=" + id + ", name=" + name + ", currentTemp=" + currentTemp
|
||||
+ ", comfortTemp=" + comfortTemp + ", sleepTemp=" + sleepTemp + ", awayTemp=" + awayTemp
|
||||
+ ", heatingActive=" + heatingActive + ", mode=" + mode + ", roomProgramName=" + roomProgramName
|
||||
+ ", heaters=" + heaters + "]";
|
||||
}
|
||||
|
||||
public Home getHome() {
|
||||
return home;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getCurrentTemp() {
|
||||
return currentTemp;
|
||||
}
|
||||
|
||||
public int getComfortTemp() {
|
||||
return comfortTemp;
|
||||
}
|
||||
|
||||
public int getSleepTemp() {
|
||||
return sleepTemp;
|
||||
}
|
||||
|
||||
public int getAwayTemp() {
|
||||
return awayTemp;
|
||||
}
|
||||
|
||||
public boolean isHeatingActive() {
|
||||
return heatingActive;
|
||||
}
|
||||
|
||||
public ModeType getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public String getRoomProgramName() {
|
||||
return roomProgramName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="millheat" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Millheat Binding</name>
|
||||
<description>This is the binding for Mill Heat Wi-Fi enabled heaters. See https://www.millheat.com/mill-wifi/</description>
|
||||
<author>Arne Seime</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
|
||||
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:millheat:account">
|
||||
<parameter name="username" type="text" required="true">
|
||||
<label>Username</label>
|
||||
<description>Your Millheat app username (email)</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="password" type="text" required="true">
|
||||
<label>Password</label>
|
||||
<description>Your Millheat app password</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
|
||||
<parameter name="refreshInterval" type="integer" min="30" unit="s">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh time in seconds for polling temperature updates from Millheat service</description>
|
||||
<default>120</default>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:millheat:heater">
|
||||
<parameter name="macAddress" type="text">
|
||||
<label>MAC Address</label>
|
||||
<description>Either MAC address or heaterId is required</description>
|
||||
</parameter>
|
||||
<parameter name="heaterId" type="integer">
|
||||
<label>Heater ID</label>
|
||||
<description>Either MAC address or heaterId is required</description>
|
||||
</parameter>
|
||||
<parameter name="power" type="integer">
|
||||
<label>Heating Power</label>
|
||||
<description>Number of watts this heater is consuming when it is heating. This value is sent to the currentPower
|
||||
channel when the heater is heating in order to track energy usage</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:millheat:room">
|
||||
<parameter name="roomId" type="integer" required="true">
|
||||
<label>Room ID</label>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:millheat:home">
|
||||
<parameter name="homeId" type="integer" required="true">
|
||||
<label>Home ID</label>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="millheat"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="account">
|
||||
<label>Mill Heating API</label>
|
||||
<description>This bridge represents the gateway to Mill Heating API</description>
|
||||
<config-description-ref uri="thing-type:millheat:account"/>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="millheat"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="currentTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Current Temperature</label>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>CurrentTemperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="comfortTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature Comfort Mode</label>
|
||||
<category>Heating</category>
|
||||
<tags>
|
||||
<tag>TargetTemperature</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" min="5" max="35" step="1"/>
|
||||
</channel-type>
|
||||
<channel-type id="sleepTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature Sleep Mode</label>
|
||||
<category>Heating</category>
|
||||
<tags>
|
||||
<tag>TargetTemperature</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" min="5" max="35" step="1"/>
|
||||
</channel-type>
|
||||
<channel-type id="awayTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature Away Mode</label>
|
||||
<description>Set temperature away mode</description>
|
||||
<category>Heating</category>
|
||||
<tags>
|
||||
<tag>TargetTemperature</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" min="5" max="35" step="1"/>
|
||||
</channel-type>
|
||||
<channel-type id="targetTemperatureHeater">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Target Temperature</label>
|
||||
<category>Heating</category>
|
||||
<tags>
|
||||
<tag>TargetTemperature</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" min="5" max="35" step="1"/>
|
||||
</channel-type>
|
||||
<channel-type id="targetTemperatureRoom">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Target Temperature</label>
|
||||
<category>Heating</category>
|
||||
<tags>
|
||||
<tag>TargetTemperature</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" readOnly="true" min="5" max="35" step="1"/>
|
||||
</channel-type>
|
||||
<channel-type id="heatingActive">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Heating Active</label>
|
||||
<description>Current state of the heater or heaters in room</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="independent">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Independent Heater</label>
|
||||
<description>ON if heater is an independent heater and not connected to a room</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="masterSwitch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Master Switch</label>
|
||||
<description>Master ON/OFF switch for independent heater</description>
|
||||
</channel-type>
|
||||
<channel-type id="fanActive">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Fan Active</label>
|
||||
<description>Current state of heater fan (if available, OFF if not found)</description>
|
||||
<category>Flow</category>
|
||||
</channel-type>
|
||||
<channel-type id="window">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Window State</label>
|
||||
<description>Open window/cold air flow detection</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="currentEnergy">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Energy Usage</label>
|
||||
<description>Actual energy usage in watts</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%d W"></state>
|
||||
</channel-type>
|
||||
<channel-type id="currentMode">
|
||||
<item-type>String</item-type>
|
||||
<label>Current Room Program Mode</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="Comfort">Comfort</option>
|
||||
<option value="Sleep">Sleep</option>
|
||||
<option value="Away">Away</option>
|
||||
<option value="Off">Off</option>
|
||||
<option value="AdvancedAway">Vacation away</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="program">
|
||||
<item-type>String</item-type>
|
||||
<label>Program</label>
|
||||
<description>Program associated with room</description>
|
||||
<state readOnly="true" pattern="%s"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="vacationModeTargetTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Target Temperature Vacation</label>
|
||||
<category>Heating</category>
|
||||
<tags>
|
||||
<tag>TargetTemperature</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" min="5" max="35" step="1"/>
|
||||
</channel-type>
|
||||
<channel-type id="vacationMode">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Vacation Mode</label>
|
||||
<description>Toggles vacation mode. Start and end time must be preset before activating</description>
|
||||
</channel-type>
|
||||
<channel-type id="vacationModeAdvanced">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Advanced Vacation Mode</label>
|
||||
<description>Use room Away mode temperatures instead of home global temperature</description>
|
||||
</channel-type>
|
||||
<channel-type id="vacationModeStart">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Start of Vacation</label>
|
||||
</channel-type>
|
||||
<channel-type id="vacationModeEnd">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>End of Vacation</label>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="millheat"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="heater">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="account"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Wi-Fi Enabled Heater</label>
|
||||
|
||||
<channels>
|
||||
<channel id="currentTemperature" typeId="currentTemperature"/>
|
||||
<channel id="heatingActive" typeId="heatingActive"/>
|
||||
<channel id="fanActive" typeId="fanActive"/>
|
||||
<channel id="currentEnergy" typeId="currentEnergy"/>
|
||||
<channel id="independent" typeId="independent"/>
|
||||
<channel id="window" typeId="window"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>macAddress</representation-property>
|
||||
<config-description-ref uri="thing-type:millheat:heater"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="millheat"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="home">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="account"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Home</label>
|
||||
|
||||
<channels>
|
||||
<channel id="vacationModeTargetTemperature" typeId="vacationModeTargetTemperature"/>
|
||||
<channel id="vacationMode" typeId="vacationMode"/>
|
||||
<channel id="vacationModeAdvanced" typeId="vacationModeAdvanced"/>
|
||||
<channel id="vacationModeStart" typeId="vacationModeStart"/>
|
||||
<channel id="vacationModeEnd" typeId="vacationModeEnd"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>homeId</representation-property>
|
||||
<config-description-ref uri="thing-type:millheat:home"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="millheat"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="room">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="account"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Room with one or more Wi-Fi enabled heaters</label>
|
||||
|
||||
<channels>
|
||||
<channel id="currentTemperature" typeId="currentTemperature"/>
|
||||
<channel id="targetTemperature" typeId="targetTemperatureRoom"/>
|
||||
<channel id="comfortTemperature" typeId="comfortTemperature"/>
|
||||
<channel id="sleepTemperature" typeId="sleepTemperature"/>
|
||||
<channel id="awayTemperature" typeId="awayTemperature"/>
|
||||
<channel id="heatingActive" typeId="heatingActive"/>
|
||||
<channel id="currentMode" typeId="currentMode"/>
|
||||
<channel id="program" typeId="program"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>roomId</representation-property>
|
||||
<config-description-ref uri="thing-type:millheat:room"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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.millheat.internal;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.openhab.binding.millheat.internal.config.MillheatAccountConfiguration;
|
||||
import org.openhab.binding.millheat.internal.handler.MillheatAccountHandler;
|
||||
import org.openhab.binding.millheat.internal.model.MillheatModel;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class MillHeatAccountHandlerTest {
|
||||
@Rule
|
||||
public WireMockRule wireMockRule = new WireMockRule(WireMockConfiguration.options().dynamicPort());
|
||||
|
||||
@Mock
|
||||
private Bridge millheatAccountMock;
|
||||
|
||||
private HttpClient httpClient;
|
||||
@Mock
|
||||
private Configuration configuration;
|
||||
|
||||
@Mock
|
||||
private BundleContext bundleContext;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
httpClient = new HttpClient();
|
||||
httpClient.start();
|
||||
MillheatAccountHandler.authEndpoint = "http://localhost:" + wireMockRule.port() + "/zc-account/v1/";
|
||||
MillheatAccountHandler.serviceEndpoint = "http://localhost:" + wireMockRule.port() + "/millService/v1/";
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdown() throws Exception {
|
||||
httpClient.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateModel() throws InterruptedException, IOException, MillheatCommunicationException {
|
||||
final String getHomesResponse = IOUtils.toString(getClass().getResourceAsStream("/select_home_list_ok.json"));
|
||||
final String getRoomsByHomeResponse = IOUtils
|
||||
.toString(getClass().getResourceAsStream("/get_rooms_by_home_ok.json"));
|
||||
final String getDeviceByRoomResponse = IOUtils
|
||||
.toString(getClass().getResourceAsStream("/get_device_by_room_ok.json"));
|
||||
final String getIndependentDevicesResponse = IOUtils
|
||||
.toString(getClass().getResourceAsStream("/get_independent_devices_ok.json"));
|
||||
|
||||
stubFor(post(urlEqualTo("/millService/v1/selectHomeList"))
|
||||
.willReturn(aResponse().withStatus(200).withBody(getHomesResponse)));
|
||||
stubFor(post(urlEqualTo("/millService/v1/selectRoombyHome"))
|
||||
.willReturn(aResponse().withStatus(200).withBody(getRoomsByHomeResponse)));
|
||||
stubFor(post(urlEqualTo("/millService/v1/selectDevicebyRoom"))
|
||||
.willReturn(aResponse().withStatus(200).withBody(getDeviceByRoomResponse)));
|
||||
stubFor(post(urlEqualTo("/millService/v1/getIndependentDevices"))
|
||||
.willReturn(aResponse().withStatus(200).withBody(getIndependentDevicesResponse)));
|
||||
|
||||
when(millheatAccountMock.getConfiguration()).thenReturn(configuration);
|
||||
when(millheatAccountMock.getUID()).thenReturn(new ThingUID("millheat:account:thinguid"));
|
||||
|
||||
final MillheatAccountConfiguration accountConfig = new MillheatAccountConfiguration();
|
||||
accountConfig.username = "username";
|
||||
accountConfig.password = "password";
|
||||
when(configuration.as(eq(MillheatAccountConfiguration.class))).thenReturn(accountConfig);
|
||||
|
||||
final MillheatAccountHandler subject = new MillheatAccountHandler(millheatAccountMock, httpClient,
|
||||
bundleContext);
|
||||
MillheatModel model = subject.refreshModel();
|
||||
Assert.assertEquals(1, model.getHomes().size());
|
||||
|
||||
verify(postRequestedFor(urlMatching("/millService/v1/selectHomeList")));
|
||||
verify(postRequestedFor(urlMatching("/millService/v1/selectRoombyHome")));
|
||||
verify(postRequestedFor(urlMatching("/millService/v1/selectDevicebyRoom")));
|
||||
verify(postRequestedFor(urlMatching("/millService/v1/getIndependentDevices")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"always": 0,
|
||||
"backMinute": 0,
|
||||
"roomProgramId": 3242342342324,
|
||||
"controlSource": "0,0,0",
|
||||
"comfortTemp": 20,
|
||||
"roomProgram": "Kontor",
|
||||
"awayTemp": 10,
|
||||
"holidayTemp": 10,
|
||||
"avgTemp": 15.0,
|
||||
"roomId": 23423423423423,
|
||||
"roomName": "Kontor",
|
||||
"deviceInfo": [
|
||||
{
|
||||
"heaterFlag": 0,
|
||||
"subDomainId": 242424,
|
||||
"controlType": 0,
|
||||
"currentTemp": 15.0,
|
||||
"canChangeTemp": 0,
|
||||
"deviceId": 12334,
|
||||
"deviceName": "Kontor",
|
||||
"mac": "F0XXXXXXXXX",
|
||||
"deviceStatus": 0
|
||||
}
|
||||
],
|
||||
"backHour": 0,
|
||||
"currentMode": 4,
|
||||
"heatStatus": 0,
|
||||
"offLineDeviceNum": 0,
|
||||
"total": 1,
|
||||
"independentCount": 0,
|
||||
"sleepTemp": 13,
|
||||
"onlineDeviceNum": 1,
|
||||
"isOffline": 1,
|
||||
"programMode": 0
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"deviceInfo": []
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"backMinute": 0,
|
||||
"offLineDeviceNum": 0,
|
||||
"mode": 0,
|
||||
"homeAlways": 0,
|
||||
"homeName": "Hjemme",
|
||||
"isHoliday": 0,
|
||||
"onlineDeviceNum": 3,
|
||||
"programList": [
|
||||
{
|
||||
"programName": "Standard Program",
|
||||
"homeId": 20190260000,
|
||||
"programId": 20190260000
|
||||
},
|
||||
{
|
||||
"programName": "Barnerom",
|
||||
"homeId": 20190260000,
|
||||
"programId": 20171231209984
|
||||
},
|
||||
{
|
||||
"programName": "Kontor",
|
||||
"homeId": 20190260000,
|
||||
"programId": 2019129984
|
||||
}
|
||||
],
|
||||
"homeType": 0,
|
||||
"backHour": 0,
|
||||
"roomInfo": [
|
||||
{
|
||||
"controlSource": "0,0,0",
|
||||
"comfortTemp": 20,
|
||||
"roomProgram": "Barnerom",
|
||||
"awayTemp": 10,
|
||||
"avgTemp": 19.0,
|
||||
"roomId": 201900000,
|
||||
"roomName": "Bedroom1",
|
||||
"currentMode": 2,
|
||||
"heatStatus": 0,
|
||||
"offLineDeviceNum": 0,
|
||||
"total": 1,
|
||||
"independentCount": 0,
|
||||
"sleepTemp": 18,
|
||||
"onlineDeviceNum": 1,
|
||||
"isOffline": 1
|
||||
},
|
||||
{
|
||||
"controlSource": "0,0,0",
|
||||
"comfortTemp": 20,
|
||||
"roomProgram": "Barnerom",
|
||||
"awayTemp": 10,
|
||||
"avgTemp": 19.0,
|
||||
"roomId": 20190207000,
|
||||
"roomName": "Bedroom2",
|
||||
"currentMode": 2,
|
||||
"heatStatus": 0,
|
||||
"offLineDeviceNum": 0,
|
||||
"total": 1,
|
||||
"independentCount": 0,
|
||||
"sleepTemp": 17,
|
||||
"onlineDeviceNum": 1,
|
||||
"isOffline": 1
|
||||
},
|
||||
{
|
||||
"controlSource": "0,0,0",
|
||||
"comfortTemp": 20,
|
||||
"roomProgram": "Kontor",
|
||||
"awayTemp": 10,
|
||||
"avgTemp": 16.0,
|
||||
"roomId": 20190000,
|
||||
"roomName": "Kontor",
|
||||
"currentMode": 5,
|
||||
"heatStatus": 0,
|
||||
"offLineDeviceNum": 0,
|
||||
"total": 1,
|
||||
"independentCount": 0,
|
||||
"sleepTemp": 13,
|
||||
"onlineDeviceNum": 1,
|
||||
"isOffline": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"description": "password is not set",
|
||||
"error": "invalid param",
|
||||
"errorCode": 3002
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"email": "email@gmail.com",
|
||||
"nickName": "Nikky",
|
||||
"phone": "",
|
||||
"refreshToken": "refreshToken",
|
||||
"refreshTokenExpire": "2019-03-20 18:13:30",
|
||||
"token": "token",
|
||||
"tokenExpire": "2019-02-18 20:13:30",
|
||||
"userId": 234324,
|
||||
"userProfile": {
|
||||
"nick_name": "Nikky",
|
||||
"privacyPolicy": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"hourSystem": 1,
|
||||
"homeList": [
|
||||
{
|
||||
"homeAlways": 0,
|
||||
"homeName": "Hjemme",
|
||||
"isHoliday": 0,
|
||||
"holidayStartTime": 1549145440,
|
||||
"timeZone": "+01:00",
|
||||
"modeMinute": 0,
|
||||
"modeStartTime": 0,
|
||||
"holidayTemp": 10,
|
||||
"modeHour": 0,
|
||||
"currentMode": 0,
|
||||
"holidayEndTime": 1549875200,
|
||||
"homeType": 0,
|
||||
"homeId": 2019000,
|
||||
"programId": 201960000
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user