added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
38
bundles/org.openhab.binding.sensibo/.classpath
Normal file
38
bundles/org.openhab.binding.sensibo/.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 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="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 kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.sensibo/.project
Normal file
23
bundles/org.openhab.binding.sensibo/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.sensibo</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.sensibo/NOTICE
Normal file
13
bundles/org.openhab.binding.sensibo/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/openhab2-addons
|
||||
83
bundles/org.openhab.binding.sensibo/README.md
Normal file
83
bundles/org.openhab.binding.sensibo/README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Sensibo Binding
|
||||
|
||||
This binding integrates the Sensibo Sky aircondition remote control
|
||||
See https://www.sensibo.com/
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding supports Sensibo Sky only.
|
||||
|
||||
* `account` = Sensibo API - the account bridge
|
||||
* `sensibosky` = Sensibo Sky remote control
|
||||
|
||||
## Discovery
|
||||
|
||||
In order to do discovery, add a thing of type Sensibo API and add the API key.
|
||||
API key can be obtained here: https://home.sensibo.com/me/api
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
See full example below for how to configure using thing files.
|
||||
|
||||
### Account
|
||||
|
||||
* `apiKey` = API key obtained here: https://home.sensibo.com/me/api
|
||||
* `refreshInterval` = number of seconds between refresh calls to the server
|
||||
|
||||
### Sensibo Sky
|
||||
|
||||
* `macAddress` = network mac address of device.
|
||||
|
||||
Can be found printed on the back of the device
|
||||
Or you can find it during discovery.
|
||||
|
||||
## Channels
|
||||
|
||||
### Sensibo Sky
|
||||
|
||||
| Channel | Read/write | Item type | Description |
|
||||
| ------------------- | ------------- | --------------------- | ----------- |
|
||||
| currentTemperature | R | Number:Temperature | Measured temperature |
|
||||
| currentHumidity | R | Number:Dimensionless | Measured relative humidity, reported in percent |
|
||||
| targetTemperature | R/W | Number:Temperature | Current target temperature for this room |
|
||||
| masterSwitch | R/W | Switch | Switch AC ON or OFF |
|
||||
| mode | R/W | String | Current mode (cool, heat, etc, actual modes provided provided by the API) being active |
|
||||
| fanLevel | R/W | String | Current fan level (low, auto etc, actual levels provided provided by the API |
|
||||
| swingMode | R/W | String | Current swing mode (actual modes provided provided by the API |
|
||||
| timer | R/W | Number | Number of seconds until AC is switched off automatically. Setting to a value less than 60 seconds will cancel timer |
|
||||
|
||||
## Full Example
|
||||
|
||||
sensibo.things:
|
||||
|
||||
```
|
||||
Bridge sensibo:account:home "Sensibo account" [apiKey="XYZASDASDAD", refreshInterval=120] {
|
||||
Thing sensibosky office "Sensibo Sky Office" [ macAddress="001122334455" ]
|
||||
}
|
||||
```
|
||||
|
||||
sensibo.items:
|
||||
|
||||
```
|
||||
Number:Temperature AC_Office_Room_Current_Temperature "Temperature [%.1f %unit%]" <temperature> {channel="sensibo:sensibosky:home:office:currentTemperature"}
|
||||
Number:Dimensionless AC_Office_Room_Current_Humidity "Relative humidity [%.1f %%]" <humidity > {channel="sensibo:sensibosky:home:office:currentHumidity"}
|
||||
Number:Temperature AC_Office_Room_Target_Temperature "Target temperature [%d %unit%]" <temperature> {channel="sensibo:sensibosky:home:office:targetTemperature"}
|
||||
String AC_Office_Room_Mode "AC mode [%s]" {channel="sensibo:sensibosky:home:office:mode"}
|
||||
String AC_Office_Room_Swing_Mode "AC swing mode [%s]" {channel="sensibo:sensibosky:home:office:swingMode"}
|
||||
Switch AC_Office_Heater_MasterSwitch "AC power [%s]" <switch> {channel="sensibo:sensibosky:home:office:masterSwitch"}
|
||||
String AC_Office_Heater_Fan_Level "Fan level [%s]" <fan> {channel="sensibo:sensibosky:home:office:fanLevel"}
|
||||
Number AC_Office_Heater_Timer "Timer seconds [%d]" <timer> {channel="sensibo:sensibosky:home:office:timer"}
|
||||
```
|
||||
|
||||
sitemap:
|
||||
|
||||
```
|
||||
Switch item=AC_Office_Heater_MasterSwitch
|
||||
Selection item=AC_Office_Room_Mode
|
||||
Setpoint item=AC_Office_Room_Target_Temperature
|
||||
Selection item=AC_Office_Heater_Fan_Level
|
||||
Selection item=AC_Office_Room_Swing_Mode
|
||||
Text item=AC_Office_Room_Current_Temperature
|
||||
Text item=AC_Office_Room_Current_Humidity
|
||||
```
|
||||
|
||||
26
bundles/org.openhab.binding.sensibo/pom.xml
Normal file
26
bundles/org.openhab.binding.sensibo/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.sensibo</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Sensibo 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.sensibo-${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-sensibo" description="Sensibo Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.sensibo/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.sensibo.internal;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.sensibo.internal.handler.SensiboSkyHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.thing.type.ChannelGroupType;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* Channel Type Provider that does a callback the SensiboSkyHandler that initiated it.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CallbackChannelsTypeProvider
|
||||
implements ChannelTypeProvider, ChannelGroupTypeProvider, ThingHandlerService {
|
||||
private @NonNullByDefault({}) SensiboSkyHandler handler;
|
||||
|
||||
@Override
|
||||
public Collection<ChannelType> getChannelTypes(@Nullable final Locale locale) {
|
||||
return handler.getChannelTypes(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ChannelType getChannelType(final ChannelTypeUID channelTypeUID, @Nullable final Locale locale) {
|
||||
return handler.getChannelType(channelTypeUID, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ChannelGroupType getChannelGroupType(final ChannelGroupTypeUID channelGroupTypeUID,
|
||||
@Nullable final Locale locale) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ChannelGroupType> getChannelGroupTypes(@Nullable final Locale locale) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@NonNullByDefault({})
|
||||
@Override
|
||||
public void setThingHandler(final ThingHandler handler) {
|
||||
this.handler = (SensiboSkyHandler) handler;
|
||||
}
|
||||
}
|
||||
@@ -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.sensibo.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link SensiboBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensiboBindingConstants {
|
||||
public static final String BINDING_ID = "sensibo";
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||
public static final ThingTypeUID THING_TYPE_SENSIBOSKY = new ThingTypeUID(BINDING_ID, "sensibosky");
|
||||
// Fixed channels
|
||||
public static final String CHANNEL_CURRENT_TEMPERATURE = "currentTemperature";
|
||||
public static final String CHANNEL_CURRENT_HUMIDITY = "currentHumidity";
|
||||
public static final String CHANNEL_MASTER_SWITCH = "masterSwitch";
|
||||
public static final String CHANNEL_TIMER = "timer";
|
||||
|
||||
// Dynamic channels
|
||||
public static final String CHANNEL_FAN_LEVEL = "fanLevel";
|
||||
public static final String CHANNEL_MODE = "mode";
|
||||
public static final String CHANNEL_SWING_MODE = "swingMode";
|
||||
public static final String CHANNEL_TARGET_TEMPERATURE = "targetTemperature";
|
||||
|
||||
public static final String CHANNEL_TYPE_FAN_LEVEL = "fanLevel";
|
||||
public static final String CHANNEL_TYPE_MODE = "mode";
|
||||
public static final String CHANNEL_TYPE_SWING_MODE = "swing";
|
||||
public static final String CHANNEL_TYPE_TARGET_TEMPERATURE = "targetTemperature";
|
||||
|
||||
public static final Set<String> DYNAMIC_CHANNEL_TYPES = Collections.unmodifiableSet(Stream
|
||||
.of(CHANNEL_TYPE_FAN_LEVEL, CHANNEL_TYPE_MODE, CHANNEL_TYPE_SWING_MODE, CHANNEL_TYPE_TARGET_TEMPERATURE)
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
@@ -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.sensibo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sensibo.internal.dto.AbstractRequest;
|
||||
|
||||
/**
|
||||
* The {@link SensiboCommunicationException} class wraps exceptions raised when communicating with the API
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensiboCommunicationException extends SensiboException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SensiboCommunicationException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public SensiboCommunicationException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SensiboCommunicationException(final AbstractRequest req, final String overallStatus) {
|
||||
super("Server responded with error to request " + req.getClass().getSimpleName() + "/" + req.getRequestUrl()
|
||||
+ ": " + overallStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.sensibo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SensiboConfigurationException} class wraps exceptions raised when due to configuration errors
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensiboConfigurationException extends SensiboException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SensiboConfigurationException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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.sensibo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SensiboException} class wraps exceptions raised when communicating with the API
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class SensiboException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SensiboException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SensiboException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.sensibo.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.sensibo.internal.discovery.SensiboDiscoveryService;
|
||||
import org.openhab.binding.sensibo.internal.handler.SensiboAccountHandler;
|
||||
import org.openhab.binding.sensibo.internal.handler.SensiboSkyHandler;
|
||||
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 SensiboHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.sensibo", service = ThingHandlerFactory.class)
|
||||
public class SensiboHandlerFactory extends BaseThingHandlerFactory {
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
|
||||
Stream.of(SensiboBindingConstants.THING_TYPE_ACCOUNT, SensiboBindingConstants.THING_TYPE_SENSIBOSKY)
|
||||
.collect(Collectors.toSet()));
|
||||
private final HttpClient httpClient;
|
||||
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
|
||||
@Activate
|
||||
public SensiboHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(final Thing thing) {
|
||||
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (SensiboBindingConstants.THING_TYPE_SENSIBOSKY.equals(thingTypeUID)) {
|
||||
return new SensiboSkyHandler(thing);
|
||||
} else if (SensiboBindingConstants.THING_TYPE_ACCOUNT.equals(thingTypeUID)) {
|
||||
final SensiboAccountHandler handler = new SensiboAccountHandler((Bridge) thing, httpClient);
|
||||
registerDeviceDiscoveryService(handler);
|
||||
return handler;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void registerDeviceDiscoveryService(SensiboAccountHandler bridgeHandler) {
|
||||
SensiboDiscoveryService discoveryService = new SensiboDiscoveryService(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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof SensiboAccountHandler) {
|
||||
ThingUID thingUID = thingHandler.getThing().getUID();
|
||||
unregisterDeviceDiscoveryService(thingUID);
|
||||
}
|
||||
super.removeHandler(thingHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.sensibo.internal;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
|
||||
/**
|
||||
* The {@link SensiboTemperatureUnitConverter} converts to/from Sensibo temperature symbols to Unit<Temperature>
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class SensiboTemperatureUnitConverter {
|
||||
public static Unit<Temperature> parseFromSensiboFormat(@Nullable String symbol) {
|
||||
if (symbol == null) {
|
||||
symbol = "C";
|
||||
}
|
||||
switch (symbol) {
|
||||
case "C":
|
||||
return SIUnits.CELSIUS;
|
||||
case "F":
|
||||
return ImperialUnits.FAHRENHEIT;
|
||||
default:
|
||||
throw new IllegalArgumentException("Do not understand temperature unit " + symbol);
|
||||
}
|
||||
}
|
||||
|
||||
public static String toSensiboFormat(@Nullable Unit<Temperature> unit) {
|
||||
if (SIUnits.CELSIUS.equals(unit)) {
|
||||
return "C";
|
||||
} else if (ImperialUnits.FAHRENHEIT.equals(unit)) {
|
||||
return "F";
|
||||
} else {
|
||||
throw new IllegalArgumentException("Do not understand temperature unit " + unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 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.sensibo.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 openHAB 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) {
|
||||
parser = new JsonParser();
|
||||
this.gson = gson;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
private void dump(final Request request, String[] stringsToRemove) {
|
||||
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(getCharset(theRequest.getHeaders()).decode(content).toString()));
|
||||
request.onRequestSuccess(theRequest -> {
|
||||
if (contentBuffer.length() > 0) {
|
||||
group.append("\n");
|
||||
group.append(reformatJson(contentBuffer.toString()));
|
||||
}
|
||||
String dataToLog = group.toString();
|
||||
scrambleAndLog(stringsToRemove, 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(getCharset(theResponse.getHeaders()).decode(content).toString()));
|
||||
request.onResponseSuccess(theResponse -> {
|
||||
if (contentBuffer.length() > 0) {
|
||||
group.append("\n");
|
||||
group.append(reformatJson(contentBuffer.toString()));
|
||||
}
|
||||
|
||||
String dataToLog = group.toString();
|
||||
scrambleAndLog(stringsToRemove, dataToLog);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void scrambleAndLog(String[] stringsToRemove, String dataToLog) {
|
||||
String modifiedData = dataToLog;
|
||||
for (String stringToRemove : stringsToRemove) {
|
||||
modifiedData = modifiedData.replace(stringToRemove, "<HIDDEN>");
|
||||
}
|
||||
logger.debug("{}", modifiedData);
|
||||
}
|
||||
|
||||
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, String[] stringToRemove) {
|
||||
dump(request, stringToRemove);
|
||||
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,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.sensibo.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link SensiboAccountConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensiboAccountConfiguration {
|
||||
/**
|
||||
* API key from https://home.sensibo.com/me/api
|
||||
*/
|
||||
@Nullable
|
||||
public String apiKey;
|
||||
public int refreshInterval = 120;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SensiboAccountConfiguration [apiKey=<not showing>, refreshInterval=" + refreshInterval + "]";
|
||||
}
|
||||
}
|
||||
@@ -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.sensibo.internal.config;
|
||||
|
||||
/**
|
||||
* The {@link SensiboSkyConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SensiboSkyConfiguration {
|
||||
/*
|
||||
* SensiboSky MAC address
|
||||
*/
|
||||
public String macAddress;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SensiboSkyConfiguration [macAddress=" + macAddress + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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.sensibo.internal.discovery;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sensibo.internal.SensiboBindingConstants;
|
||||
import org.openhab.binding.sensibo.internal.handler.SensiboAccountHandler;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboModel;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboSky;
|
||||
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.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensiboDiscoveryService extends AbstractDiscoveryService {
|
||||
public static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections
|
||||
.singleton(SensiboBindingConstants.THING_TYPE_SENSIBOSKY);
|
||||
private static final long REFRESH_INTERVAL_MINUTES = 60;
|
||||
private final Logger logger = LoggerFactory.getLogger(SensiboDiscoveryService.class);
|
||||
private final SensiboAccountHandler accountHandler;
|
||||
private Optional<ScheduledFuture<?>> discoveryJob = Optional.empty();
|
||||
|
||||
public SensiboDiscoveryService(final SensiboAccountHandler accountHandler) {
|
||||
super(DISCOVERABLE_THING_TYPES_UIDS, 10);
|
||||
this.accountHandler = accountHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
discoveryJob = Optional
|
||||
.of(scheduler.scheduleWithFixedDelay(this::startScan, 0, REFRESH_INTERVAL_MINUTES, TimeUnit.MINUTES));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Start scan for Sensibo devices.");
|
||||
synchronized (this) {
|
||||
removeOlderResults(getTimestampOfLastScan(), null, accountHandler.getThing().getUID());
|
||||
final ThingUID accountUID = accountHandler.getThing().getUID();
|
||||
accountHandler.updateModelFromServerAndUpdateThingStatus();
|
||||
final SensiboModel model = accountHandler.getModel();
|
||||
for (final SensiboSky pod : model.getPods()) {
|
||||
final ThingUID podUID = new ThingUID(SensiboBindingConstants.THING_TYPE_SENSIBOSKY, accountUID,
|
||||
String.valueOf(pod.getMacAddress()));
|
||||
Map<String, String> properties = pod.getThingProperties();
|
||||
|
||||
// DiscoveryResult result uses Map<String,Object> as properties while ThingBuilder uses
|
||||
// Map<String,String>
|
||||
Map<String, Object> stringObjectProperties = new HashMap<>();
|
||||
stringObjectProperties.putAll(properties);
|
||||
|
||||
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(podUID).withBridge(accountUID)
|
||||
.withLabel(pod.getProductName()).withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
|
||||
.withProperties(stringObjectProperties).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
stopScan();
|
||||
discoveryJob.ifPresent(job -> {
|
||||
if (!job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
discoveryJob = Optional.empty();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopScan() {
|
||||
logger.debug("Stop scan for Sensibo devices.");
|
||||
super.stopScan();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.sensibo.internal.dto;
|
||||
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public abstract class AbstractRequest {
|
||||
public abstract String getRequestUrl();
|
||||
|
||||
public String getMethod() {
|
||||
return HttpMethod.GET.asString();
|
||||
}
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.deletetimer;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class DeleteTimerReponse {
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.deletetimer;
|
||||
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.sensibo.internal.dto.AbstractRequest;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class DeleteTimerRequest extends AbstractRequest {
|
||||
public final transient String podId; // Transient fields are ignored by gson
|
||||
|
||||
public DeleteTimerRequest(String podId) {
|
||||
this.podId = podId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return String.format("/v1/pods/%s/timer/", podId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return HttpMethod.DELETE.asString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto.poddetails;
|
||||
|
||||
import org.openhab.binding.sensibo.internal.SensiboTemperatureUnitConverter;
|
||||
import org.openhab.binding.sensibo.internal.model.AcState;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class AcStateDTO {
|
||||
public boolean on;
|
||||
public final String fanLevel;
|
||||
public final String temperatureUnit;
|
||||
public final Integer targetTemperature;
|
||||
public final String mode;
|
||||
public final String swing;
|
||||
|
||||
public AcStateDTO(boolean on, String fanLevel, String temperatureUnit, Integer targetTemperature, String mode,
|
||||
String swing) {
|
||||
this.on = on;
|
||||
this.fanLevel = fanLevel;
|
||||
this.temperatureUnit = temperatureUnit;
|
||||
this.targetTemperature = targetTemperature;
|
||||
this.mode = mode;
|
||||
this.swing = swing;
|
||||
}
|
||||
|
||||
public AcStateDTO(AcState acState) {
|
||||
this.on = acState.isOn();
|
||||
this.fanLevel = acState.getFanLevel();
|
||||
this.targetTemperature = acState.getTargetTemperature();
|
||||
this.mode = acState.getMode();
|
||||
this.swing = acState.getSwing();
|
||||
this.temperatureUnit = SensiboTemperatureUnitConverter.toSensiboFormat(acState.getTemperatureUnit());
|
||||
}
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.poddetails;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class ConnectionStatusDTO {
|
||||
@SerializedName("isAlive")
|
||||
public boolean alive;
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.poddetails;
|
||||
|
||||
import org.openhab.binding.sensibo.internal.dto.AbstractRequest;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class GetPodsDetailsRequest extends AbstractRequest {
|
||||
public final String id;
|
||||
|
||||
public GetPodsDetailsRequest(final String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return String.format("/v2/pods/%s", id);
|
||||
}
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.poddetails;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class MeasurementDTO {
|
||||
public Double batteryVoltage;
|
||||
public Double temperature;
|
||||
public Double humidity;
|
||||
@SerializedName("rssi")
|
||||
public Integer wifiSignalStrength;
|
||||
@SerializedName("time")
|
||||
public TimeWrapperDTO measurementTimestamp;
|
||||
|
||||
public ZonedDateTime getMeasurementTimestamp() {
|
||||
if (measurementTimestamp != null) {
|
||||
return measurementTimestamp.time;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.poddetails;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class ModeCapabilityDTO {
|
||||
@SerializedName("swing")
|
||||
public List<String> swingModes = new ArrayList<>();
|
||||
public Map<String, TemperatureDTO> temperatures = new HashMap<>();
|
||||
public List<String> fanLevels = new ArrayList<>();
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.poddetails;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class ModeCapabilityWrapperDTO {
|
||||
public Map<String, ModeCapabilityDTO> modes;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto.poddetails;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class PodDetailsDTO {
|
||||
public String id;
|
||||
public String macAddress;
|
||||
public String firmwareVersion;
|
||||
public String firmwareType;
|
||||
@SerializedName("serial")
|
||||
public String serialNumber;
|
||||
public String temperatureUnit;
|
||||
public String productModel;
|
||||
public AcStateDTO acState;
|
||||
@SerializedName("measurements")
|
||||
public MeasurementDTO lastMeasurement;
|
||||
public ConnectionStatusDTO connectionStatus;
|
||||
public RoomDTO room;
|
||||
public ScheduleDTO[] schedules;
|
||||
public TimerDTO timer;
|
||||
private ModeCapabilityWrapperDTO remoteCapabilities;
|
||||
|
||||
public Map<String, ModeCapabilityDTO> getRemoteCapabilities() {
|
||||
return remoteCapabilities.modes;
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return connectionStatus.alive;
|
||||
}
|
||||
|
||||
public String getRoomName() {
|
||||
return room.name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto.poddetails;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class RoomDTO {
|
||||
public String name;
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.poddetails;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class ScheduleDTO {
|
||||
public String targetTimeLocal;
|
||||
public String nextTime;
|
||||
public String[] recurringDays;
|
||||
public AcStateDTO acState;
|
||||
public boolean enabled;
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.poddetails;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class TemperatureDTO {
|
||||
public boolean isNative;
|
||||
@SerializedName("values")
|
||||
public List<Integer> validValues = new ArrayList<>();
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.poddetails;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class TimeWrapperDTO {
|
||||
public ZonedDateTime time;
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.poddetails;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class TimerDTO {
|
||||
public int targetTimeSecondsFromNow;
|
||||
public AcStateDTO acState;
|
||||
public boolean enabled;
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.pods;
|
||||
|
||||
import org.openhab.binding.sensibo.internal.dto.AbstractRequest;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class GetPodsRequest extends AbstractRequest {
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return "/v2/users/me/pods";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto.pods;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class PodDTO {
|
||||
public String id;
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.setacstateproperty;
|
||||
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.AcStateDTO;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class SetAcStatePropertyReponse {
|
||||
public AcStateDTO acState;
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.setacstateproperty;
|
||||
|
||||
import org.openhab.binding.sensibo.internal.dto.AbstractRequest;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class SetAcStatePropertyRequest extends AbstractRequest {
|
||||
public transient String podId; // Transient fields are ignored by gson
|
||||
public transient String property;
|
||||
public Object newValue;
|
||||
|
||||
public SetAcStatePropertyRequest(String podId, String property, Object value) {
|
||||
this.podId = podId;
|
||||
this.property = property;
|
||||
this.newValue = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return String.format("/v2/pods/%s/acStates/%s", podId, property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return "PATCH";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto.settimer;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
public class SetTimerReponse {
|
||||
|
||||
}
|
||||
@@ -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.sensibo.internal.dto.settimer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.sensibo.internal.dto.AbstractRequest;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.AcStateDTO;
|
||||
|
||||
/**
|
||||
* All classes in the ..binding.sensibo.dto are data transfer classes used by the GSON mapper. This class reflects a
|
||||
* part of a request/response data structure.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetTimerRequest extends AbstractRequest {
|
||||
public final transient String podId; // Transient fields are ignored by gson
|
||||
public final AcStateDTO acState;
|
||||
public final int minutesFromNow;
|
||||
|
||||
public SetTimerRequest(String podId, int minutesFromNow, AcStateDTO acState) {
|
||||
this.podId = podId;
|
||||
this.acState = acState;
|
||||
this.minutesFromNow = minutesFromNow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestUrl() {
|
||||
return String.format("/v1/pods/%s/timer/", podId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return HttpMethod.PUT.asString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* 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.sensibo.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
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.sensibo.internal.SensiboCommunicationException;
|
||||
import org.openhab.binding.sensibo.internal.SensiboConfigurationException;
|
||||
import org.openhab.binding.sensibo.internal.SensiboException;
|
||||
import org.openhab.binding.sensibo.internal.client.RequestLogger;
|
||||
import org.openhab.binding.sensibo.internal.config.SensiboAccountConfiguration;
|
||||
import org.openhab.binding.sensibo.internal.dto.AbstractRequest;
|
||||
import org.openhab.binding.sensibo.internal.dto.deletetimer.DeleteTimerReponse;
|
||||
import org.openhab.binding.sensibo.internal.dto.deletetimer.DeleteTimerRequest;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.AcStateDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.GetPodsDetailsRequest;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.PodDetailsDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.pods.GetPodsRequest;
|
||||
import org.openhab.binding.sensibo.internal.dto.pods.PodDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.setacstateproperty.SetAcStatePropertyReponse;
|
||||
import org.openhab.binding.sensibo.internal.dto.setacstateproperty.SetAcStatePropertyRequest;
|
||||
import org.openhab.binding.sensibo.internal.dto.settimer.SetTimerReponse;
|
||||
import org.openhab.binding.sensibo.internal.dto.settimer.SetTimerRequest;
|
||||
import org.openhab.binding.sensibo.internal.model.AcState;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboModel;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboSky;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* The {@link SensiboAccountHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensiboAccountHandler extends BaseBridgeHandler {
|
||||
private static final int MIN_TIME_BETWEEEN_MODEL_UPDATES_MS = 30_000;
|
||||
private static final int SECONDS_IN_MINUTE = 60;
|
||||
public static String API_ENDPOINT = "https://home.sensibo.com/api";
|
||||
private final Logger logger = LoggerFactory.getLogger(SensiboAccountHandler.class);
|
||||
private final HttpClient httpClient;
|
||||
private final JsonParser jsonParser = new JsonParser();
|
||||
private final RequestLogger requestLogger;
|
||||
private final Gson gson;
|
||||
private SensiboModel model = new SensiboModel(0);
|
||||
private Optional<ScheduledFuture<?>> statusFuture = Optional.empty();
|
||||
private @NonNullByDefault({}) SensiboAccountConfiguration config;
|
||||
|
||||
public SensiboAccountHandler(final Bridge bridge, final HttpClient httpClient) {
|
||||
super(bridge);
|
||||
this.httpClient = httpClient;
|
||||
|
||||
gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new TypeAdapter<ZonedDateTime>() {
|
||||
@Override
|
||||
public void write(final @NonNullByDefault({}) JsonWriter out, final ZonedDateTime value)
|
||||
throws IOException {
|
||||
out.value(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime read(final @NonNullByDefault({}) JsonReader in) throws IOException {
|
||||
return ZonedDateTime.parse(in.nextString());
|
||||
}
|
||||
}).setLenient().setPrettyPrinting().create();
|
||||
|
||||
requestLogger = new RequestLogger(bridge.getUID().getId(), gson);
|
||||
}
|
||||
|
||||
private boolean allowModelUpdate() {
|
||||
final long diffMsSinceLastUpdate = System.currentTimeMillis() - model.getLastUpdated();
|
||||
return diffMsSinceLastUpdate > MIN_TIME_BETWEEEN_MODEL_UPDATES_MS;
|
||||
}
|
||||
|
||||
public SensiboModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
// Ignore commands as none are supported
|
||||
}
|
||||
|
||||
public SensiboAccountConfiguration loadConfigSafely() throws SensiboConfigurationException {
|
||||
SensiboAccountConfiguration loadedConfig = getConfigAs(SensiboAccountConfiguration.class);
|
||||
if (loadedConfig == null) {
|
||||
throw new SensiboConfigurationException("Could not load Sensibo account configuration");
|
||||
}
|
||||
|
||||
return loadedConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
scheduler.execute(this::initializeInternal);
|
||||
}
|
||||
|
||||
private void initializeInternal() {
|
||||
try {
|
||||
config = loadConfigSafely();
|
||||
logger.debug("Initializing Sensibo Account bridge using config {}", config);
|
||||
model = refreshModel();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
initPolling();
|
||||
logger.debug("Initialization of Sensibo account completed successfully for {}", config);
|
||||
} catch (final SensiboConfigurationException e) {
|
||||
logger.info("Error initializing Sensibo data: {}", e.getMessage());
|
||||
model = new SensiboModel(0); // Empty model
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Error fetching initial data: " + e.getMessage());
|
||||
} catch (final SensiboException e) {
|
||||
logger.info("Error initializing Sensibo data: {}", e.getMessage());
|
||||
model = new SensiboModel(0); // Empty model
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Error fetching initial data: " + e.getMessage());
|
||||
// Reschedule init
|
||||
scheduler.schedule(this::initializeInternal, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
stopPolling();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* starts this things polling future
|
||||
*/
|
||||
private void initPolling() {
|
||||
stopPolling();
|
||||
statusFuture = Optional.of(scheduler.scheduleWithFixedDelay(this::updateModelFromServerAndUpdateThingStatus,
|
||||
config.refreshInterval, config.refreshInterval, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
protected SensiboModel refreshModel() throws SensiboException {
|
||||
final SensiboModel updatedModel = new SensiboModel(System.currentTimeMillis());
|
||||
|
||||
final GetPodsRequest getPodsRequest = new GetPodsRequest();
|
||||
final List<PodDTO> pods = sendRequest(buildRequest(getPodsRequest), getPodsRequest,
|
||||
new TypeToken<ArrayList<PodDTO>>() {
|
||||
}.getType());
|
||||
|
||||
for (final PodDTO pod : pods) {
|
||||
final GetPodsDetailsRequest getPodsDetailsRequest = new GetPodsDetailsRequest(pod.id);
|
||||
|
||||
final PodDetailsDTO podDetails = sendRequest(buildGetPodDetailsRequest(getPodsDetailsRequest),
|
||||
getPodsDetailsRequest, new TypeToken<PodDetailsDTO>() {
|
||||
}.getType());
|
||||
|
||||
updatedModel.addPod(new SensiboSky(podDetails));
|
||||
}
|
||||
|
||||
return updatedModel;
|
||||
}
|
||||
|
||||
private <T> T sendRequest(final Request request, final AbstractRequest req, final Type responseType)
|
||||
throws SensiboException {
|
||||
try {
|
||||
final ContentResponse contentResponse = request.send();
|
||||
final String responseJson = contentResponse.getContentAsString();
|
||||
if (contentResponse.getStatus() == HttpStatus.OK_200) {
|
||||
final JsonObject o = jsonParser.parse(responseJson).getAsJsonObject();
|
||||
final String overallStatus = o.get("status").getAsString();
|
||||
if ("success".equals(overallStatus)) {
|
||||
return gson.fromJson(o.get("result"), responseType);
|
||||
} else {
|
||||
throw new SensiboCommunicationException(req, overallStatus);
|
||||
}
|
||||
} else if (contentResponse.getStatus() == HttpStatus.FORBIDDEN_403) {
|
||||
throw new SensiboConfigurationException("Invalid API key");
|
||||
} else {
|
||||
throw new SensiboCommunicationException(
|
||||
"Error sending request to Sensibo server. Server responded with " + contentResponse.getStatus()
|
||||
+ " and payload " + responseJson);
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new SensiboCommunicationException(
|
||||
String.format("Error sending request to Sensibo server: %s", e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this thing's polling future
|
||||
*/
|
||||
private void stopPolling() {
|
||||
statusFuture.ifPresent(future -> {
|
||||
if (!future.isCancelled()) {
|
||||
future.cancel(true);
|
||||
}
|
||||
statusFuture = Optional.empty();
|
||||
});
|
||||
}
|
||||
|
||||
public void updateModelFromServerAndUpdateThingStatus() {
|
||||
if (allowModelUpdate()) {
|
||||
try {
|
||||
model = refreshModel();
|
||||
updateThingStatuses();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (SensiboConfigurationException e) {
|
||||
logger.debug("Error updating Sensibo model do to {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
} catch (SensiboException e) {
|
||||
logger.debug("Error updating Sensibo model do to {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateThingStatuses() {
|
||||
final List<Thing> subThings = getThing().getThings();
|
||||
for (final Thing thing : subThings) {
|
||||
final ThingHandler handler = thing.getHandler();
|
||||
if (handler != null) {
|
||||
final SensiboBaseThingHandler mHandler = (SensiboBaseThingHandler) handler;
|
||||
mHandler.updateState(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Request buildGetPodDetailsRequest(final GetPodsDetailsRequest getPodsDetailsRequest) {
|
||||
final Request req = buildRequest(getPodsDetailsRequest);
|
||||
req.param("fields", "*");
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
private Request buildRequest(final AbstractRequest req) {
|
||||
Request request = httpClient.newRequest(API_ENDPOINT + req.getRequestUrl()).param("apiKey", config.apiKey)
|
||||
.method(req.getMethod());
|
||||
|
||||
if (!req.getMethod().contentEquals(HttpMethod.GET.asString())) { // POST, PATCH
|
||||
final String reqJson = gson.toJson(req);
|
||||
request = request.content(new BytesContentProvider(reqJson.getBytes(StandardCharsets.UTF_8)),
|
||||
"application/json");
|
||||
}
|
||||
|
||||
requestLogger.listenTo(request, new String[] { config.apiKey });
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public void updateSensiboSkyAcState(final String macAddress, String property, Object value,
|
||||
SensiboBaseThingHandler handler) {
|
||||
model.findSensiboSkyByMacAddress(macAddress).ifPresent(pod -> {
|
||||
try {
|
||||
SetAcStatePropertyRequest setAcStatePropertyRequest = new SetAcStatePropertyRequest(pod.getId(),
|
||||
property, value);
|
||||
Request request = buildRequest(setAcStatePropertyRequest);
|
||||
SetAcStatePropertyReponse response = sendRequest(request, setAcStatePropertyRequest,
|
||||
new TypeToken<SetAcStatePropertyReponse>() {
|
||||
}.getType());
|
||||
|
||||
model.updateAcState(macAddress, new AcState(response.acState));
|
||||
handler.updateState(model);
|
||||
} catch (SensiboException e) {
|
||||
logger.debug("Error setting ac state for {}", macAddress, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void updateSensiboSkyTimer(final String macAddress, @Nullable Integer secondsFromNow) {
|
||||
model.findSensiboSkyByMacAddress(macAddress).ifPresent(pod -> {
|
||||
try {
|
||||
if (secondsFromNow != null && secondsFromNow >= SECONDS_IN_MINUTE) {
|
||||
AcStateDTO offState = new AcStateDTO(pod.getAcState().get());
|
||||
offState.on = false;
|
||||
|
||||
SetTimerRequest setTimerRequest = new SetTimerRequest(pod.getId(),
|
||||
secondsFromNow / SECONDS_IN_MINUTE, offState);
|
||||
Request request = buildRequest(setTimerRequest);
|
||||
// No data in response
|
||||
sendRequest(request, setTimerRequest, new TypeToken<SetTimerReponse>() {
|
||||
}.getType());
|
||||
} else {
|
||||
DeleteTimerRequest setTimerRequest = new DeleteTimerRequest(pod.getId());
|
||||
Request request = buildRequest(setTimerRequest);
|
||||
// No data in response
|
||||
sendRequest(request, setTimerRequest, new TypeToken<DeleteTimerReponse>() {
|
||||
}.getType());
|
||||
}
|
||||
} catch (SensiboException e) {
|
||||
logger.debug("Error setting timer for {}", macAddress, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.sensibo.internal.handler;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboModel;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class SensiboBaseThingHandler extends BaseThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(SensiboBaseThingHandler.class);
|
||||
|
||||
public SensiboBaseThingHandler(final Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
public void updateState(final SensiboModel model) {
|
||||
for (final Channel channel : getThing().getChannels()) {
|
||||
handleCommand(channel.getUID(), RefreshType.REFRESH, model);
|
||||
}
|
||||
}
|
||||
|
||||
public SensiboModel getSensiboModel() {
|
||||
final Optional<SensiboAccountHandler> accountHandler = getAccountHandler();
|
||||
if (accountHandler.isPresent()) {
|
||||
return accountHandler.get().getModel();
|
||||
} else {
|
||||
logger.debug(
|
||||
"Thing {} cannot exist without a bridge and account handler - returning empty model. No heaters or rooms will be found",
|
||||
getThing().getUID());
|
||||
return new SensiboModel(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected Optional<SensiboAccountHandler> getAccountHandler() {
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
final SensiboAccountHandler accountHandler = (SensiboAccountHandler) bridge.getHandler();
|
||||
if (accountHandler != null) {
|
||||
return Optional.of(accountHandler);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
protected abstract void handleCommand(ChannelUID uid, Command command, SensiboModel model);
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
/**
|
||||
* 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.sensibo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.sensibo.internal.SensiboBindingConstants.CHANNEL_CURRENT_HUMIDITY;
|
||||
import static org.openhab.binding.sensibo.internal.SensiboBindingConstants.CHANNEL_CURRENT_TEMPERATURE;
|
||||
import static org.openhab.binding.sensibo.internal.SensiboBindingConstants.CHANNEL_FAN_LEVEL;
|
||||
import static org.openhab.binding.sensibo.internal.SensiboBindingConstants.CHANNEL_MASTER_SWITCH;
|
||||
import static org.openhab.binding.sensibo.internal.SensiboBindingConstants.CHANNEL_MODE;
|
||||
import static org.openhab.binding.sensibo.internal.SensiboBindingConstants.CHANNEL_SWING_MODE;
|
||||
import static org.openhab.binding.sensibo.internal.SensiboBindingConstants.CHANNEL_TARGET_TEMPERATURE;
|
||||
import static org.openhab.binding.sensibo.internal.SensiboBindingConstants.CHANNEL_TIMER;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.measure.IncommensurableException;
|
||||
import javax.measure.UnconvertibleException;
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.UnitConverter;
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.sensibo.internal.CallbackChannelsTypeProvider;
|
||||
import org.openhab.binding.sensibo.internal.SensiboBindingConstants;
|
||||
import org.openhab.binding.sensibo.internal.config.SensiboSkyConfiguration;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.TemperatureDTO;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboModel;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboSky;
|
||||
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.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
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.ThingHandlerService;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.type.StateChannelTypeBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tec.uom.se.unit.Units;
|
||||
|
||||
/**
|
||||
* The {@link SensiboSkyHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensiboSkyHandler extends SensiboBaseThingHandler implements ChannelTypeProvider {
|
||||
public static final String SWING_PROPERTY = "swing";
|
||||
public static final String MASTER_SWITCH_PROPERTY = "on";
|
||||
public static final String FAN_LEVEL_PROPERTY = "fanLevel";
|
||||
public static final String MODE_PROPERTY = "mode";
|
||||
public static final String TARGET_TEMPERATURE_PROPERTY = "targetTemperature";
|
||||
public static final String SWING_MODE_LABEL = "Swing Mode";
|
||||
public static final String FAN_LEVEL_LABEL = "Fan Level";
|
||||
public static final String MODE_LABEL = "Mode";
|
||||
public static final String TARGET_TEMPERATURE_LABEL = "Target Temperature";
|
||||
private static final String ITEM_TYPE_STRING = "String";
|
||||
private static final String ITEM_TYPE_NUMBER_TEMPERATURE = "Number:Temperature";
|
||||
private final Logger logger = LoggerFactory.getLogger(SensiboSkyHandler.class);
|
||||
private final Map<ChannelTypeUID, ChannelType> generatedChannelTypes = new HashMap<>();
|
||||
private Optional<SensiboSkyConfiguration> config = Optional.empty();
|
||||
|
||||
public SensiboSkyHandler(final Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
private static String beautify(final String camelCaseWording) {
|
||||
final StringBuilder b = new StringBuilder();
|
||||
for (final String s : StringUtils.splitByCharacterTypeCamelCase(camelCaseWording)) {
|
||||
b.append(" ");
|
||||
b.append(s);
|
||||
}
|
||||
final StringBuilder bs = new StringBuilder();
|
||||
for (final String t : StringUtils.splitByWholeSeparator(b.toString(), " _")) {
|
||||
bs.append(" ");
|
||||
bs.append(t);
|
||||
}
|
||||
|
||||
return WordUtils.capitalizeFully(bs.toString()).trim();
|
||||
}
|
||||
|
||||
private String getMacAddress() {
|
||||
if (config.isPresent()) {
|
||||
return config.get().macAddress;
|
||||
}
|
||||
throw new IllegalArgumentException("No configuration present");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
handleCommand(channelUID, command, getSensiboModel());
|
||||
}
|
||||
|
||||
/*
|
||||
* Package private in order to be reachable from unit test
|
||||
*/
|
||||
void updateAcState(SensiboSky sensiboSky, String property, Object value) {
|
||||
StateChange stateChange = checkStateChangeValid(sensiboSky, property, value);
|
||||
if (stateChange.valid) {
|
||||
getAccountHandler().ifPresent(
|
||||
handler -> handler.updateSensiboSkyAcState(getMacAddress(), property, stateChange.value, this));
|
||||
} else {
|
||||
logger.info("Update command not sent; invalid state change for SensiboSky AC state: {}",
|
||||
stateChange.validationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTimer(@Nullable Integer secondsFromNowUntilSwitchOff) {
|
||||
getAccountHandler()
|
||||
.ifPresent(handler -> handler.updateSensiboSkyTimer(getMacAddress(), secondsFromNowUntilSwitchOff));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleCommand(final ChannelUID channelUID, final Command command, final SensiboModel model) {
|
||||
model.findSensiboSkyByMacAddress(getMacAddress()).ifPresent(sensiboSky -> {
|
||||
if (sensiboSky.isAlive()) {
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
addDynamicChannelsAndProperties(sensiboSky);
|
||||
updateStatus(ThingStatus.ONLINE); // In case it has been offline
|
||||
}
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_CURRENT_HUMIDITY:
|
||||
handleCurrentHumidityCommand(channelUID, command, sensiboSky);
|
||||
break;
|
||||
case CHANNEL_CURRENT_TEMPERATURE:
|
||||
handleCurrentTemperatureCommand(channelUID, command, sensiboSky);
|
||||
break;
|
||||
case CHANNEL_MASTER_SWITCH:
|
||||
handleMasterSwitchCommand(channelUID, command, sensiboSky);
|
||||
break;
|
||||
case CHANNEL_TARGET_TEMPERATURE:
|
||||
handleTargetTemperatureCommand(channelUID, command, sensiboSky);
|
||||
break;
|
||||
case CHANNEL_MODE:
|
||||
handleModeCommand(channelUID, command, sensiboSky);
|
||||
break;
|
||||
case CHANNEL_SWING_MODE:
|
||||
handleSwingCommand(channelUID, command, sensiboSky);
|
||||
break;
|
||||
case CHANNEL_FAN_LEVEL:
|
||||
handleFanLevelCommand(channelUID, command, sensiboSky);
|
||||
break;
|
||||
case CHANNEL_TIMER:
|
||||
handleTimerCommand(channelUID, command, sensiboSky);
|
||||
break;
|
||||
default:
|
||||
logger.debug("Received command on unknown channel {}, ignoring", channelUID.getId());
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Unreachable by Sensibo servers");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleTimerCommand(ChannelUID channelUID, Command command, SensiboSky sensiboSky) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (sensiboSky.getTimer().isPresent() && sensiboSky.getTimer().get().secondsRemaining > 0) {
|
||||
updateState(channelUID, new DecimalType(sensiboSky.getTimer().get().secondsRemaining));
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
} else if (command instanceof DecimalType) {
|
||||
final DecimalType newValue = (DecimalType) command;
|
||||
updateTimer(newValue.intValue());
|
||||
} else {
|
||||
updateTimer(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFanLevelCommand(ChannelUID channelUID, Command command, SensiboSky sensiboSky) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (sensiboSky.getAcState().isPresent() && sensiboSky.getAcState().get().getFanLevel() != null) {
|
||||
updateState(channelUID, new StringType(sensiboSky.getAcState().get().getFanLevel()));
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
} else if (command instanceof StringType) {
|
||||
final StringType newValue = (StringType) command;
|
||||
updateAcState(sensiboSky, FAN_LEVEL_PROPERTY, newValue.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSwingCommand(ChannelUID channelUID, Command command, SensiboSky sensiboSky) {
|
||||
if (command instanceof RefreshType && sensiboSky.getAcState().isPresent()) {
|
||||
if (sensiboSky.getAcState().isPresent() && sensiboSky.getAcState().get().getSwing() != null) {
|
||||
updateState(channelUID, new StringType(sensiboSky.getAcState().get().getSwing()));
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
} else if (command instanceof StringType) {
|
||||
final StringType newValue = (StringType) command;
|
||||
updateAcState(sensiboSky, SWING_PROPERTY, newValue.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleModeCommand(ChannelUID channelUID, Command command, SensiboSky sensiboSky) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (sensiboSky.getAcState().isPresent()) {
|
||||
updateState(channelUID, new StringType(sensiboSky.getAcState().get().getMode()));
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
} else if (command instanceof StringType) {
|
||||
final StringType newValue = (StringType) command;
|
||||
updateAcState(sensiboSky, MODE_PROPERTY, newValue.toString());
|
||||
addDynamicChannelsAndProperties(sensiboSky);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTargetTemperatureCommand(ChannelUID channelUID, Command command, SensiboSky sensiboSky) {
|
||||
if (command instanceof RefreshType) {
|
||||
sensiboSky.getAcState().ifPresent(acState -> {
|
||||
@Nullable
|
||||
Integer targetTemperature = acState.getTargetTemperature();
|
||||
if (targetTemperature != null) {
|
||||
updateState(channelUID, new QuantityType<>(targetTemperature, sensiboSky.getTemperatureUnit()));
|
||||
} else {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
});
|
||||
if (!sensiboSky.getAcState().isPresent()) {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
}
|
||||
} else if (command instanceof QuantityType<?>) {
|
||||
QuantityType<?> newValue = (QuantityType<?>) command;
|
||||
if (!Objects.equals(sensiboSky.getTemperatureUnit(), newValue.getUnit())) {
|
||||
// If quantity is given in celsius when fahrenheit is used or opposite
|
||||
try {
|
||||
UnitConverter temperatureConverter = newValue.getUnit()
|
||||
.getConverterToAny(sensiboSky.getTemperatureUnit());
|
||||
// No decimals supported
|
||||
long convertedValue = (long) temperatureConverter.convert(newValue.longValue());
|
||||
updateAcState(sensiboSky, TARGET_TEMPERATURE_PROPERTY, new DecimalType(convertedValue));
|
||||
} catch (UnconvertibleException | IncommensurableException e) {
|
||||
logger.info("Could not convert {} to {}: {}", newValue, sensiboSky.getTemperatureUnit(),
|
||||
e.getMessage());
|
||||
}
|
||||
} else {
|
||||
updateAcState(sensiboSky, TARGET_TEMPERATURE_PROPERTY, new DecimalType(newValue.intValue()));
|
||||
}
|
||||
} else if (command instanceof DecimalType) {
|
||||
updateAcState(sensiboSky, TARGET_TEMPERATURE_PROPERTY, command);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMasterSwitchCommand(ChannelUID channelUID, Command command, SensiboSky sensiboSky) {
|
||||
if (command instanceof RefreshType) {
|
||||
sensiboSky.getAcState().ifPresent(e -> updateState(channelUID, OnOffType.from(e.isOn())));
|
||||
} else if (command instanceof OnOffType) {
|
||||
updateAcState(sensiboSky, MASTER_SWITCH_PROPERTY, command == OnOffType.ON);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCurrentTemperatureCommand(ChannelUID channelUID, Command command, SensiboSky sensiboSky) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new QuantityType<>(sensiboSky.getTemperature(), SIUnits.CELSIUS));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCurrentHumidityCommand(ChannelUID channelUID, Command command, SensiboSky sensiboSky) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateState(channelUID, new QuantityType<>(sensiboSky.getHumidity(), Units.PERCENT));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(CallbackChannelsTypeProvider.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = Optional.ofNullable(getConfigAs(SensiboSkyConfiguration.class));
|
||||
logger.debug("Initializing SensiboSky using config {}", config);
|
||||
getSensiboModel().findSensiboSkyByMacAddress(getMacAddress()).ifPresent(pod -> {
|
||||
|
||||
if (pod.isAlive()) {
|
||||
addDynamicChannelsAndProperties(pod);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Unreachable by Sensibo servers");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isDynamicChannel(final ChannelTypeUID uid) {
|
||||
return SensiboBindingConstants.DYNAMIC_CHANNEL_TYPES.stream().anyMatch(e -> uid.getId().startsWith(e));
|
||||
}
|
||||
|
||||
private void addDynamicChannelsAndProperties(final SensiboSky sensiboSky) {
|
||||
logger.debug("Updating dynamic channels for {}", sensiboSky.getId());
|
||||
final List<Channel> newChannels = new ArrayList<>();
|
||||
for (final Channel channel : getThing().getChannels()) {
|
||||
final ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
|
||||
if (channelTypeUID != null && !isDynamicChannel(channelTypeUID)) {
|
||||
newChannels.add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
newChannels.addAll(createDynamicChannels(sensiboSky));
|
||||
Map<String, String> properties = sensiboSky.getThingProperties();
|
||||
updateThing(editThing().withChannels(newChannels).withProperties(properties).build());
|
||||
}
|
||||
|
||||
public List<Channel> createDynamicChannels(final SensiboSky sensiboSky) {
|
||||
final List<Channel> newChannels = new ArrayList<>();
|
||||
generatedChannelTypes.clear();
|
||||
|
||||
sensiboSky.getCurrentModeCapabilities().ifPresent(capabilities -> {
|
||||
// Not all modes have swing and fan level
|
||||
final ChannelTypeUID swingModeChannelType = addChannelType(SensiboBindingConstants.CHANNEL_TYPE_SWING_MODE,
|
||||
SWING_MODE_LABEL, ITEM_TYPE_STRING, capabilities.swingModes, null, null);
|
||||
newChannels
|
||||
.add(ChannelBuilder
|
||||
.create(new ChannelUID(getThing().getUID(), SensiboBindingConstants.CHANNEL_SWING_MODE),
|
||||
ITEM_TYPE_STRING)
|
||||
.withLabel(SWING_MODE_LABEL).withType(swingModeChannelType).build());
|
||||
|
||||
final ChannelTypeUID fanLevelChannelType = addChannelType(SensiboBindingConstants.CHANNEL_TYPE_FAN_LEVEL,
|
||||
FAN_LEVEL_LABEL, ITEM_TYPE_STRING, capabilities.fanLevels, null, null);
|
||||
newChannels.add(ChannelBuilder
|
||||
.create(new ChannelUID(getThing().getUID(), SensiboBindingConstants.CHANNEL_FAN_LEVEL),
|
||||
ITEM_TYPE_STRING)
|
||||
.withLabel(FAN_LEVEL_LABEL).withType(fanLevelChannelType).build());
|
||||
});
|
||||
|
||||
final ChannelTypeUID modeChannelType = addChannelType(SensiboBindingConstants.CHANNEL_TYPE_MODE, MODE_LABEL,
|
||||
ITEM_TYPE_STRING, sensiboSky.getRemoteCapabilities().keySet(), null, null);
|
||||
newChannels.add(ChannelBuilder
|
||||
.create(new ChannelUID(getThing().getUID(), SensiboBindingConstants.CHANNEL_MODE), ITEM_TYPE_STRING)
|
||||
.withLabel(MODE_LABEL).withType(modeChannelType).build());
|
||||
|
||||
final ChannelTypeUID targetTemperatureChannelType = addChannelType(
|
||||
SensiboBindingConstants.CHANNEL_TYPE_TARGET_TEMPERATURE, TARGET_TEMPERATURE_LABEL,
|
||||
ITEM_TYPE_NUMBER_TEMPERATURE, sensiboSky.getTargetTemperatures(), "%d %unit%", "TargetTemperature");
|
||||
newChannels.add(ChannelBuilder
|
||||
.create(new ChannelUID(getThing().getUID(), SensiboBindingConstants.CHANNEL_TARGET_TEMPERATURE),
|
||||
ITEM_TYPE_NUMBER_TEMPERATURE)
|
||||
.withLabel(TARGET_TEMPERATURE_LABEL).withType(targetTemperatureChannelType).build());
|
||||
|
||||
return newChannels;
|
||||
}
|
||||
|
||||
private ChannelTypeUID addChannelType(final String channelTypePrefix, final String label, final String itemType,
|
||||
final Collection<?> options, @Nullable final String pattern, @Nullable final String tag) {
|
||||
final ChannelTypeUID channelTypeUID = new ChannelTypeUID(SensiboBindingConstants.BINDING_ID,
|
||||
channelTypePrefix + getThing().getUID().getId());
|
||||
final List<StateOption> stateOptions = options.stream()
|
||||
.map(e -> new StateOption(e.toString(), e instanceof String ? beautify((String) e) : e.toString()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
StateDescriptionFragmentBuilder stateDescription = StateDescriptionFragmentBuilder.create().withReadOnly(false)
|
||||
.withOptions(stateOptions);
|
||||
if (pattern != null) {
|
||||
stateDescription = stateDescription.withPattern(pattern);
|
||||
}
|
||||
final StateChannelTypeBuilder builder = ChannelTypeBuilder.state(channelTypeUID, label, itemType)
|
||||
.withStateDescription(stateDescription.build().toStateDescription());
|
||||
if (tag != null) {
|
||||
builder.withTag(tag);
|
||||
}
|
||||
final ChannelType channelType = builder.build();
|
||||
|
||||
generatedChannelTypes.put(channelTypeUID, channelType);
|
||||
|
||||
return channelTypeUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ChannelType> getChannelTypes(@Nullable final Locale locale) {
|
||||
return generatedChannelTypes.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ChannelType getChannelType(final ChannelTypeUID channelTypeUID, @Nullable final Locale locale) {
|
||||
return generatedChannelTypes.get(channelTypeUID);
|
||||
}
|
||||
|
||||
/*
|
||||
* Package private in order to be reachable from unit test
|
||||
*/
|
||||
StateChange checkStateChangeValid(SensiboSky sensiboSky, String property, Object newPropertyValue) {
|
||||
StateChange stateChange = new StateChange(newPropertyValue);
|
||||
|
||||
sensiboSky.getCurrentModeCapabilities().ifPresent(currentModeCapabilities -> {
|
||||
switch (property) {
|
||||
case TARGET_TEMPERATURE_PROPERTY:
|
||||
Unit<Temperature> temperatureUnit = sensiboSky.getTemperatureUnit();
|
||||
TemperatureDTO validTemperatures = currentModeCapabilities.temperatures
|
||||
.get(temperatureUnit == SIUnits.CELSIUS ? "C" : "F");
|
||||
DecimalType rawValue = (DecimalType) newPropertyValue;
|
||||
stateChange.updateValue(rawValue.intValue());
|
||||
if (!validTemperatures.validValues.contains(rawValue.intValue())) {
|
||||
stateChange.addError(String.format(
|
||||
"Cannot change targetTemperature to '%d', valid targetTemperatures are one of %s",
|
||||
rawValue.intValue(), ToStringBuilder.reflectionToString(
|
||||
validTemperatures.validValues.toArray(), ToStringStyle.SIMPLE_STYLE)));
|
||||
}
|
||||
break;
|
||||
case MODE_PROPERTY:
|
||||
if (!sensiboSky.getRemoteCapabilities().containsKey(newPropertyValue)) {
|
||||
stateChange.addError(
|
||||
String.format("Cannot change mode to %s, valid modes are %s", newPropertyValue,
|
||||
ToStringBuilder.reflectionToString(
|
||||
sensiboSky.getRemoteCapabilities().keySet().toArray(),
|
||||
ToStringStyle.SIMPLE_STYLE)));
|
||||
}
|
||||
break;
|
||||
case FAN_LEVEL_PROPERTY:
|
||||
if (!currentModeCapabilities.fanLevels.contains(newPropertyValue)) {
|
||||
stateChange.addError(String.format("Cannot change fanLevel to %s, valid fanLevels are %s",
|
||||
newPropertyValue, ToStringBuilder.reflectionToString(
|
||||
currentModeCapabilities.fanLevels.toArray(), ToStringStyle.SIMPLE_STYLE)));
|
||||
}
|
||||
break;
|
||||
case MASTER_SWITCH_PROPERTY:
|
||||
// Always allowed
|
||||
break;
|
||||
case SWING_PROPERTY:
|
||||
if (!currentModeCapabilities.swingModes.contains(newPropertyValue)) {
|
||||
stateChange.addError(String.format("Cannot change swing to %s, valid swings are %s",
|
||||
newPropertyValue, ToStringBuilder.reflectionToString(
|
||||
currentModeCapabilities.swingModes.toArray(), ToStringStyle.SIMPLE_STYLE)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
stateChange.addError(String.format("No such ac state property %s", property));
|
||||
}
|
||||
logger.debug("State change request {}", stateChange);
|
||||
});
|
||||
return stateChange;
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
public class StateChange {
|
||||
Object value;
|
||||
|
||||
boolean valid = true;
|
||||
@Nullable
|
||||
String validationMessage;
|
||||
|
||||
public StateChange(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void updateValue(Object updatedValue) {
|
||||
value = updatedValue;
|
||||
}
|
||||
|
||||
public void addError(String validationMessage) {
|
||||
valid = false;
|
||||
this.validationMessage = validationMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StateChange [valid=" + valid + ", validationMessage=" + validationMessage + ", value=" + value
|
||||
+ ", value Class=" + value.getClass() + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.sensibo.internal.model;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.sensibo.internal.SensiboTemperatureUnitConverter;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.AcStateDTO;
|
||||
|
||||
/**
|
||||
* Represents the state of the AC unit.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AcState {
|
||||
private final boolean on;
|
||||
private final @Nullable String fanLevel;
|
||||
private final @Nullable Unit<Temperature> temperatureUnit;
|
||||
private final @Nullable Integer targetTemperature;
|
||||
private final @Nullable String mode;
|
||||
private final @Nullable String swing;
|
||||
|
||||
public AcState(final AcStateDTO dto) {
|
||||
this.on = dto.on;
|
||||
this.fanLevel = dto.fanLevel;
|
||||
this.targetTemperature = dto.targetTemperature;
|
||||
this.mode = dto.mode;
|
||||
this.swing = dto.swing;
|
||||
this.temperatureUnit = SensiboTemperatureUnitConverter.parseFromSensiboFormat(dto.temperatureUnit);
|
||||
}
|
||||
|
||||
public boolean isOn() {
|
||||
return on;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFanLevel() {
|
||||
return fanLevel;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Unit<Temperature> getTemperatureUnit() {
|
||||
return temperatureUnit;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getTargetTemperature() {
|
||||
return targetTemperature;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSwing() {
|
||||
return swing;
|
||||
}
|
||||
}
|
||||
@@ -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.sensibo.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Represents a generic Sensibo controllable thing
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class Pod {
|
||||
protected String id;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
protected Pod(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.sensibo.internal.model;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.ScheduleDTO;
|
||||
|
||||
/**
|
||||
* The {@link SensiboSky} represents a Sensibo Sky schedule
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Schedule {
|
||||
private final LocalTime targetTimeLocal;
|
||||
private final String[] recurringDays;
|
||||
private final AcState acState;
|
||||
private final boolean enabled;
|
||||
private @Nullable ZonedDateTime nextTime;
|
||||
|
||||
public Schedule(ScheduleDTO dto) {
|
||||
this.enabled = dto.enabled;
|
||||
if (enabled) {
|
||||
this.nextTime = ZonedDateTime.parse(nextTime + "Z"); // API field seems to be in Zulu
|
||||
}
|
||||
this.targetTimeLocal = LocalTime.parse(dto.targetTimeLocal);
|
||||
this.recurringDays = dto.recurringDays;
|
||||
this.acState = new AcState(dto.acState);
|
||||
}
|
||||
|
||||
public LocalTime getTargetTimeLocal() {
|
||||
return targetTimeLocal;
|
||||
}
|
||||
|
||||
public @Nullable ZonedDateTime getNextTime() {
|
||||
return nextTime;
|
||||
}
|
||||
|
||||
public String[] getRecurringDays() {
|
||||
return recurringDays;
|
||||
}
|
||||
|
||||
public AcState getAcState() {
|
||||
return acState;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.sensibo.internal.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SensiboModel} represents the home structure as designed by the user in the Sensibo app.
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensiboModel {
|
||||
private final long lastUpdated;
|
||||
private final List<SensiboSky> pods = new ArrayList<>();
|
||||
|
||||
public SensiboModel(final long lastUpdated) {
|
||||
this.lastUpdated = lastUpdated;
|
||||
}
|
||||
|
||||
public void addPod(final SensiboSky pod) {
|
||||
pods.add(pod);
|
||||
}
|
||||
|
||||
public List<SensiboSky> getPods() {
|
||||
return pods;
|
||||
}
|
||||
|
||||
public long getLastUpdated() {
|
||||
return lastUpdated;
|
||||
}
|
||||
|
||||
public Optional<SensiboSky> findSensiboSkyByMacAddress(final String macAddress) {
|
||||
final String macAddressWithoutColons = StringUtils.remove(macAddress, ':');
|
||||
return pods.stream().filter(pod -> macAddressWithoutColons.equals(pod.getMacAddress())).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param macAddress
|
||||
* @param acState
|
||||
*/
|
||||
public void updateAcState(String macAddress, AcState acState) {
|
||||
findSensiboSkyByMacAddress(macAddress).ifPresent(sky -> sky.updateAcState(acState));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* 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.sensibo.internal.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sensibo.internal.SensiboTemperatureUnitConverter;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.ModeCapabilityDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.PodDetailsDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.TemperatureDTO;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* The {@link SensiboSky} represents a Sensibo Sky unit
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SensiboSky extends Pod {
|
||||
private final String macAddress;
|
||||
private final String firmwareVersion;
|
||||
private final String firmwareType;
|
||||
private final String serialNumber;
|
||||
private final String productModel;
|
||||
private final String roomName;
|
||||
private final Unit<Temperature> temperatureUnit;
|
||||
private final String originalTemperatureUnit;
|
||||
private final Double temperature;
|
||||
private final Double humidity;
|
||||
private final boolean alive;
|
||||
private final Map<String, ModeCapabilityDTO> remoteCapabilities;
|
||||
private Schedule[] schedules = new Schedule[0];
|
||||
private Optional<AcState> acState = Optional.empty();
|
||||
private Optional<Timer> timer = Optional.empty();
|
||||
|
||||
public SensiboSky(final PodDetailsDTO dto) {
|
||||
super(dto.id);
|
||||
this.macAddress = StringUtils.remove(dto.macAddress, ':');
|
||||
this.firmwareVersion = dto.firmwareVersion;
|
||||
this.firmwareType = dto.firmwareType;
|
||||
this.serialNumber = dto.serialNumber;
|
||||
this.originalTemperatureUnit = dto.temperatureUnit;
|
||||
this.temperatureUnit = SensiboTemperatureUnitConverter.parseFromSensiboFormat(dto.temperatureUnit);
|
||||
this.productModel = dto.productModel;
|
||||
|
||||
if (dto.acState != null) {
|
||||
this.acState = Optional.of(new AcState(dto.acState));
|
||||
}
|
||||
|
||||
if (dto.timer != null) {
|
||||
this.timer = Optional.of(new Timer(dto.timer));
|
||||
}
|
||||
|
||||
this.temperature = dto.lastMeasurement.temperature;
|
||||
this.humidity = dto.lastMeasurement.humidity;
|
||||
|
||||
this.alive = dto.isAlive();
|
||||
if (dto.getRemoteCapabilities() != null) {
|
||||
this.remoteCapabilities = dto.getRemoteCapabilities();
|
||||
} else {
|
||||
this.remoteCapabilities = new HashMap<>();
|
||||
}
|
||||
this.roomName = dto.getRoomName();
|
||||
|
||||
if (dto.schedules != null) {
|
||||
schedules = Arrays.stream(dto.schedules).map(Schedule::new).toArray(Schedule[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
public String getOriginalTemperatureUnit() {
|
||||
return originalTemperatureUnit;
|
||||
}
|
||||
|
||||
public String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
public Schedule[] getSchedules() {
|
||||
return schedules;
|
||||
}
|
||||
|
||||
public String getMacAddress() {
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
public String getFirmwareVersion() {
|
||||
return firmwareVersion;
|
||||
}
|
||||
|
||||
public String getFirmwareType() {
|
||||
return firmwareType;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public Unit<Temperature> getTemperatureUnit() {
|
||||
return temperatureUnit;
|
||||
}
|
||||
|
||||
public String getProductModel() {
|
||||
return productModel;
|
||||
}
|
||||
|
||||
public Optional<AcState> getAcState() {
|
||||
return acState;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
switch (productModel) {
|
||||
case "skyv2":
|
||||
return String.format("Sensibo Sky %s", roomName);
|
||||
default:
|
||||
return String.format("%s %s", productModel, roomName);
|
||||
}
|
||||
}
|
||||
|
||||
public Double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public Double getHumidity() {
|
||||
return humidity;
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return alive;
|
||||
}
|
||||
|
||||
public Map<String, ModeCapabilityDTO> getRemoteCapabilities() {
|
||||
return remoteCapabilities;
|
||||
}
|
||||
|
||||
public Optional<ModeCapabilityDTO> getCurrentModeCapabilities() {
|
||||
if (acState.isPresent() && acState.get().getMode() != null) {
|
||||
return Optional.ofNullable(remoteCapabilities.get(acState.get().getMode()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Integer> getTargetTemperatures() {
|
||||
Optional<ModeCapabilityDTO> currentModeCapabilities = getCurrentModeCapabilities();
|
||||
if (currentModeCapabilities.isPresent()) {
|
||||
TemperatureDTO selectedTemperatureRange = currentModeCapabilities.get().temperatures
|
||||
.get(originalTemperatureUnit);
|
||||
if (selectedTemperatureRange != null) {
|
||||
return selectedTemperatureRange.validValues;
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newAcState an updated ac state
|
||||
*/
|
||||
public void updateAcState(AcState newAcState) {
|
||||
this.acState = Optional.of(newAcState);
|
||||
}
|
||||
|
||||
public Optional<Timer> getTimer() {
|
||||
return timer;
|
||||
}
|
||||
|
||||
public Map<String, String> getThingProperties() {
|
||||
final Map<String, String> properties = new HashMap<>();
|
||||
properties.put(Thing.PROPERTY_VENDOR, "Sensibo");
|
||||
properties.put("podId", id);
|
||||
properties.put(Thing.PROPERTY_MAC_ADDRESS, macAddress);
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, productModel);
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
|
||||
properties.put("firmwareType", firmwareType);
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
@@ -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.sensibo.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.TimerDTO;
|
||||
|
||||
/**
|
||||
* The {@link Timer} represents a Sensibo Sky unit timer definition
|
||||
*
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Timer {
|
||||
public final int secondsRemaining;
|
||||
public final AcState acState;
|
||||
public final boolean enabled;
|
||||
|
||||
public Timer(TimerDTO dto) {
|
||||
this.secondsRemaining = dto.targetTimeSecondsFromNow;
|
||||
this.acState = new AcState(dto.acState);
|
||||
this.enabled = dto.enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="sensibo" 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>Sensibo Binding</name>
|
||||
<description>This is the binding for Sensibo products</description>
|
||||
<author>Arne Seime</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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:sensibo:account">
|
||||
<parameter name="apiKey" type="text" required="true">
|
||||
<label>API Key</label>
|
||||
<description>Your Sensibo app API key</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="refreshInterval" type="integer" min="30" unit="s">
|
||||
<label>Refresh Interval</label>
|
||||
<description>How often to fetch updates from Sensibo service (polling interval)</description>
|
||||
<default>120</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:sensibo:sensibosky">
|
||||
<parameter name="macAddress" type="text" required="true">
|
||||
<label>MAC Address</label>
|
||||
<description>With or without colons</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="sensibo"
|
||||
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>Sensibo API</label>
|
||||
<description>This bridge represents the gateway to Sensibo API</description>
|
||||
<config-description-ref uri="thing-type:sensibo:account"/>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="sensibo"
|
||||
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="currentHumidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Current Humidity</label>
|
||||
<category>Humidity</category>
|
||||
<tags>
|
||||
<tag>CurrentHumidity</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="masterSwitch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Master Switch</label>
|
||||
<tags>
|
||||
<tag>Switchable</tag>
|
||||
</tags>
|
||||
</channel-type>
|
||||
<channel-type id="timer">
|
||||
<item-type>Number</item-type>
|
||||
<label>Off Timer</label>
|
||||
<description>Number of seconds until turning off</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="sensibo"
|
||||
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="sensibosky">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="account"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>HVAC controller</label>
|
||||
|
||||
<channels>
|
||||
<channel id="currentTemperature" typeId="currentTemperature"/>
|
||||
<channel id="currentHumidity" typeId="currentHumidity"/>
|
||||
<channel id="masterSwitch" typeId="masterSwitch"/>
|
||||
<channel id="timer" typeId="timer"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>macAddress</representation-property>
|
||||
<config-description-ref uri="thing-type:sensibo:sensibosky"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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.sensibo.internal;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.openhab.binding.sensibo.internal.dto.AbstractRequest;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class WireHelper {
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
public WireHelper() {
|
||||
gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new TypeAdapter<ZonedDateTime>() {
|
||||
@Override
|
||||
public void write(final JsonWriter out, final ZonedDateTime value) throws IOException {
|
||||
out.value(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime read(final JsonReader in) throws IOException {
|
||||
return ZonedDateTime.parse(in.nextString());
|
||||
}
|
||||
}).setPrettyPrinting().create();
|
||||
}
|
||||
|
||||
public <T> T deSerializeResponse(final String jsonClasspathName, final Type type) throws IOException {
|
||||
final String json = IOUtils.toString(WireHelper.class.getResourceAsStream(jsonClasspathName));
|
||||
|
||||
final JsonParser parser = new JsonParser();
|
||||
final JsonObject o = parser.parse(json).getAsJsonObject();
|
||||
assertEquals("success", o.get("status").getAsString());
|
||||
|
||||
return gson.fromJson(o.get("result"), type);
|
||||
}
|
||||
|
||||
public <T> T deSerializeFromClasspathResource(final String jsonClasspathName, final Type type) throws IOException {
|
||||
final String json = IOUtils.toString(WireHelper.class.getResourceAsStream(jsonClasspathName));
|
||||
return deSerializeFromString(json, type);
|
||||
}
|
||||
|
||||
public <T> T deSerializeFromString(final String json, final Type type) throws IOException {
|
||||
return gson.fromJson(json, type);
|
||||
}
|
||||
|
||||
public <T> String serialize(final AbstractRequest req) throws IOException {
|
||||
return gson.toJson(req);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto;
|
||||
|
||||
import org.openhab.binding.sensibo.internal.WireHelper;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public abstract class AbstractSerializationDeserializationTest {
|
||||
|
||||
protected WireHelper wireHelper = new WireHelper();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.AcStateDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.MeasurementDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.ModeCapabilityDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.PodDetailsDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.TemperatureDTO;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboSky;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class GetPodDetailsResponseTest extends AbstractSerializationDeserializationTest {
|
||||
|
||||
@Test
|
||||
public void testDeserializeWithSmartModeSetup() throws IOException {
|
||||
final PodDetailsDTO rsp = wireHelper.deSerializeResponse("/get_pod_details_response_smartmode_settings.json",
|
||||
PodDetailsDTO.class);
|
||||
|
||||
assertEquals("34:15:13:AA:AA:AA", rsp.macAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeserializeNullpointerExample() throws IOException {
|
||||
final PodDetailsDTO rsp = wireHelper.deSerializeResponse("/get_pod_details_response_nullpointer.json",
|
||||
PodDetailsDTO.class);
|
||||
SensiboSky internal = new SensiboSky(rsp);
|
||||
|
||||
assertEquals("50175457", internal.getSerialNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeserialize() throws IOException {
|
||||
final PodDetailsDTO rsp = wireHelper.deSerializeResponse("/get_pod_details_response.json", PodDetailsDTO.class);
|
||||
|
||||
assertEquals("MA:C:AD:DR:ES:S0", rsp.macAddress);
|
||||
assertEquals("IN010056", rsp.firmwareVersion);
|
||||
assertEquals("cc3100_stm32f0", rsp.firmwareType);
|
||||
assertEquals("SERIALNUMASSTRING", rsp.serialNumber);
|
||||
assertEquals("C", rsp.temperatureUnit);
|
||||
assertEquals("skyv2", rsp.productModel);
|
||||
assertAcState(rsp.acState);
|
||||
assertMeasurement(rsp.lastMeasurement);
|
||||
assertRemoteCapabilities(rsp.getRemoteCapabilities());
|
||||
}
|
||||
|
||||
private void assertRemoteCapabilities(final Map<String, ModeCapabilityDTO> remoteCapabilities) {
|
||||
assertNotNull(remoteCapabilities);
|
||||
|
||||
assertEquals(5, remoteCapabilities.size());
|
||||
final ModeCapabilityDTO mode = remoteCapabilities.get("heat");
|
||||
|
||||
assertNotNull(mode.swingModes);
|
||||
assertNotNull(mode.fanLevels);
|
||||
assertNotNull(mode.temperatures);
|
||||
final Map<String, TemperatureDTO> temperatures = mode.temperatures;
|
||||
final TemperatureDTO temperature = temperatures.get("C");
|
||||
assertNotNull(temperature);
|
||||
assertNotNull(temperature.validValues);
|
||||
}
|
||||
|
||||
private void assertMeasurement(final MeasurementDTO lastMeasurement) {
|
||||
assertNotNull(lastMeasurement);
|
||||
assertNull(lastMeasurement.batteryVoltage);
|
||||
assertEquals(Double.valueOf("22.5"), lastMeasurement.temperature);
|
||||
assertEquals(Double.valueOf("24.2"), lastMeasurement.humidity);
|
||||
assertEquals(Integer.valueOf("-71"), lastMeasurement.wifiSignalStrength);
|
||||
assertEquals(ZonedDateTime.parse("2019-05-05T07:52:11Z"), lastMeasurement.measurementTimestamp.time);
|
||||
}
|
||||
|
||||
private void assertAcState(final AcStateDTO acState) {
|
||||
assertNotNull(acState);
|
||||
|
||||
assertTrue(acState.on);
|
||||
assertEquals("medium_high", acState.fanLevel);
|
||||
assertEquals("C", acState.temperatureUnit);
|
||||
assertEquals(21, acState.targetTemperature.intValue());
|
||||
assertEquals("heat", acState.mode);
|
||||
assertEquals("rangeFull", acState.swing);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.sensibo.internal.dto.pods.PodDTO;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class GetPodsResponseTest extends AbstractSerializationDeserializationTest {
|
||||
|
||||
@Test
|
||||
public void testDeserialize() throws IOException {
|
||||
final Type type = new TypeToken<ArrayList<PodDTO>>() {
|
||||
}.getType();
|
||||
|
||||
final List<PodDTO> rsp = wireHelper.deSerializeResponse("/get_pods_response.json", type);
|
||||
|
||||
assertEquals(1, rsp.size());
|
||||
assertEquals("PODID", rsp.get(0).id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.sensibo.internal.dto.setacstateproperty.SetAcStatePropertyRequest;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SetAcStatePropertyRequestTest extends AbstractSerializationDeserializationTest {
|
||||
|
||||
@Test
|
||||
public void testSerializeDeserialize() throws IOException {
|
||||
SetAcStatePropertyRequest req = new SetAcStatePropertyRequest("PODID", "targetTemperature", "mode");
|
||||
String serializedJson = wireHelper.serialize(req);
|
||||
|
||||
final SetAcStatePropertyRequest deSerializedRequest = wireHelper.deSerializeFromString(serializedJson,
|
||||
SetAcStatePropertyRequest.class);
|
||||
|
||||
assertEquals("mode", deSerializedRequest.newValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.sensibo.internal.dto;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.sensibo.internal.dto.setacstateproperty.SetAcStatePropertyReponse;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SetAcStatePropertyResponseTest extends AbstractSerializationDeserializationTest {
|
||||
|
||||
@Test
|
||||
public void testDeserialize() throws IOException {
|
||||
final SetAcStatePropertyReponse rsp = wireHelper.deSerializeResponse("/set_acstate_response.json",
|
||||
SetAcStatePropertyReponse.class);
|
||||
|
||||
assertNotNull(rsp.acState);
|
||||
assertTrue(rsp.acState.on);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.sensibo.internal.dto;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.AcStateDTO;
|
||||
import org.openhab.binding.sensibo.internal.dto.settimer.SetTimerRequest;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SetTimerRequestTest extends AbstractSerializationDeserializationTest {
|
||||
|
||||
@Test
|
||||
public void testSerializeDeserialize() throws IOException {
|
||||
AcStateDTO acState = new AcStateDTO(false, "fanLevel", "C", 21, "mode", "swing");
|
||||
SetTimerRequest req = new SetTimerRequest("PODID", 60, acState);
|
||||
String serializedJson = wireHelper.serialize(req);
|
||||
|
||||
final SetTimerRequest deSerializedRequest = wireHelper.deSerializeFromString(serializedJson,
|
||||
SetTimerRequest.class);
|
||||
assertNotNull(deSerializedRequest.acState);
|
||||
assertEquals(60, deSerializedRequest.minutesFromNow);
|
||||
assertFalse(deSerializedRequest.acState.on);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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.sensibo.internal.handler;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.get;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.openhab.binding.sensibo.internal.config.SensiboAccountConfiguration;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboSky;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SensiboAccountHandlerTest {
|
||||
@Rule
|
||||
public WireMockRule wireMockRule = new WireMockRule(WireMockConfiguration.options().dynamicPort());
|
||||
|
||||
@Mock
|
||||
private Bridge sensiboAccountMock;
|
||||
|
||||
private HttpClient httpClient;
|
||||
@Mock
|
||||
private Configuration configuration;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
httpClient = new HttpClient();
|
||||
httpClient.start();
|
||||
SensiboAccountHandler.API_ENDPOINT = "http://localhost:" + wireMockRule.port() + "/api"; // https://home.sensibo.com/api/v2
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdown() throws Exception {
|
||||
httpClient.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitialize1() throws InterruptedException, IOException {
|
||||
testInitialize("/get_pods_response.json", "/get_pod_details_response.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeMarco() throws InterruptedException, IOException {
|
||||
testInitialize("/get_pods_response.json", "/get_pod_details_response_marco.json");
|
||||
}
|
||||
|
||||
private void testInitialize(String podsResponse, String podDetailsResponse)
|
||||
throws InterruptedException, IOException {
|
||||
// Setup account
|
||||
final SensiboAccountConfiguration accountConfig = new SensiboAccountConfiguration();
|
||||
accountConfig.apiKey = "APIKEY";
|
||||
when(configuration.as(eq(SensiboAccountConfiguration.class))).thenReturn(accountConfig);
|
||||
|
||||
// Setup initial response
|
||||
final String getPodsResponse = IOUtils.toString(getClass().getResourceAsStream(podsResponse));
|
||||
stubFor(get(urlEqualTo("/api/v2/users/me/pods?apiKey=APIKEY"))
|
||||
.willReturn(aResponse().withStatus(200).withBody(getPodsResponse)));
|
||||
|
||||
// Setup 2nd response with details
|
||||
final String getPodDetailsResponse = IOUtils.toString(getClass().getResourceAsStream(podDetailsResponse));
|
||||
stubFor(get(urlEqualTo("/api/v2/pods/PODID?apiKey=APIKEY&fields=*"))
|
||||
.willReturn(aResponse().withStatus(200).withBody(getPodDetailsResponse)));
|
||||
|
||||
when(sensiboAccountMock.getConfiguration()).thenReturn(configuration);
|
||||
when(sensiboAccountMock.getUID()).thenReturn(new ThingUID("sensibo:account:thinguid"));
|
||||
|
||||
final SensiboAccountHandler subject = new SensiboAccountHandler(sensiboAccountMock, httpClient);
|
||||
// Async, poll for status
|
||||
subject.initialize();
|
||||
|
||||
// Verify num things found == 1
|
||||
int numPods = 0;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
final List<SensiboSky> things = subject.getModel().getPods();
|
||||
numPods = things.size();
|
||||
if (numPods == 1) {
|
||||
break;
|
||||
} else {
|
||||
// Wait some more
|
||||
Thread.sleep(200);
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(1, numPods);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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.sensibo.internal.handler;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.openhab.binding.sensibo.internal.SensiboBindingConstants;
|
||||
import org.openhab.binding.sensibo.internal.SensiboCommunicationException;
|
||||
import org.openhab.binding.sensibo.internal.WireHelper;
|
||||
import org.openhab.binding.sensibo.internal.dto.poddetails.PodDetailsDTO;
|
||||
import org.openhab.binding.sensibo.internal.handler.SensiboSkyHandler.StateChange;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboModel;
|
||||
import org.openhab.binding.sensibo.internal.model.SensiboSky;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* @author Arne Seime - Initial contribution
|
||||
*/
|
||||
public class SensiboSkyHandlerTest {
|
||||
|
||||
private final WireHelper wireHelper = new WireHelper();
|
||||
|
||||
@Test
|
||||
public void testStateChangeValidation() throws IOException, SensiboCommunicationException {
|
||||
final PodDetailsDTO rsp = wireHelper.deSerializeResponse("/get_pod_details_response.json", PodDetailsDTO.class);
|
||||
SensiboSky sky = new SensiboSky(rsp);
|
||||
Thing thing = Mockito.mock(Thing.class);
|
||||
SensiboSkyHandler handler = new SensiboSkyHandler(thing);
|
||||
|
||||
// Target temperature
|
||||
StateChange stateChangeCheck = handler.checkStateChangeValid(sky, SensiboSkyHandler.TARGET_TEMPERATURE_PROPERTY,
|
||||
new DecimalType(123));
|
||||
assertFalse(stateChangeCheck.valid);
|
||||
assertNotNull(stateChangeCheck.validationMessage);
|
||||
assertTrue(handler.checkStateChangeValid(sky, SensiboSkyHandler.TARGET_TEMPERATURE_PROPERTY,
|
||||
new DecimalType(10)).valid);
|
||||
|
||||
// Mode
|
||||
StateChange stateChangeCheckMode = handler.checkStateChangeValid(sky, "mode", "invalid");
|
||||
assertFalse(stateChangeCheckMode.valid);
|
||||
assertNotNull(stateChangeCheckMode.validationMessage);
|
||||
assertTrue(handler.checkStateChangeValid(sky, "mode", "auto").valid);
|
||||
|
||||
// Swing
|
||||
StateChange stateChangeCheckSwing = handler.checkStateChangeValid(sky, "swing", "invalid");
|
||||
assertFalse(stateChangeCheckSwing.valid);
|
||||
assertNotNull(stateChangeCheckSwing.validationMessage);
|
||||
assertTrue(handler.checkStateChangeValid(sky, "swing", "stopped").valid);
|
||||
|
||||
// FanLevel
|
||||
StateChange stateChangeCheckFanLevel = handler.checkStateChangeValid(sky, "fanLevel", "invalid");
|
||||
assertFalse(stateChangeCheckFanLevel.valid);
|
||||
assertNotNull(stateChangeCheckFanLevel.validationMessage);
|
||||
assertTrue(handler.checkStateChangeValid(sky, "fanLevel", "high").valid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTemperatureConversion() throws IOException {
|
||||
final PodDetailsDTO rsp = wireHelper.deSerializeResponse("/get_pod_details_response.json", PodDetailsDTO.class);
|
||||
SensiboSky sky = new SensiboSky(rsp);
|
||||
Thing thing = Mockito.mock(Thing.class);
|
||||
Mockito.when(thing.getUID()).thenReturn(new ThingUID("sensibo:account:thinguid"));
|
||||
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("macAddress", sky.getMacAddress());
|
||||
Mockito.when(thing.getConfiguration()).thenReturn(new Configuration(config));
|
||||
|
||||
SensiboSkyHandler handler = Mockito.spy(new SensiboSkyHandler(thing));
|
||||
handler.initialize();
|
||||
|
||||
SensiboModel model = new SensiboModel(0);
|
||||
model.addPod(sky);
|
||||
|
||||
// Once with Celcius argument
|
||||
handler.handleCommand(new ChannelUID(thing.getUID(), SensiboBindingConstants.CHANNEL_TARGET_TEMPERATURE),
|
||||
new QuantityType<>(50, ImperialUnits.FAHRENHEIT), model);
|
||||
|
||||
// Once with Fahrenheit
|
||||
handler.handleCommand(new ChannelUID(thing.getUID(), SensiboBindingConstants.CHANNEL_TARGET_TEMPERATURE),
|
||||
new QuantityType<>(10, SIUnits.CELSIUS), model);
|
||||
|
||||
// Once with Decimal directly
|
||||
handler.handleCommand(new ChannelUID(thing.getUID(), SensiboBindingConstants.CHANNEL_TARGET_TEMPERATURE),
|
||||
new DecimalType(10), model);
|
||||
|
||||
ArgumentCaptor<DecimalType> valueCapture = ArgumentCaptor.forClass(DecimalType.class);
|
||||
Mockito.verify(handler, Mockito.times(3)).updateAcState(ArgumentMatchers.eq(sky), ArgumentMatchers.anyString(),
|
||||
valueCapture.capture());
|
||||
assertEquals(new DecimalType(10), valueCapture.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddDynamicChannelsMarco() throws IOException, SensiboCommunicationException {
|
||||
testAddDynamicChannels("/get_pod_details_response_marco.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddDynamicChannels() throws IOException, SensiboCommunicationException {
|
||||
testAddDynamicChannels("/get_pod_details_response.json");
|
||||
}
|
||||
|
||||
private void testAddDynamicChannels(String podDetailsResponse) throws IOException, SensiboCommunicationException {
|
||||
final PodDetailsDTO rsp = wireHelper.deSerializeResponse(podDetailsResponse, PodDetailsDTO.class);
|
||||
SensiboSky sky = new SensiboSky(rsp);
|
||||
Thing thing = Mockito.mock(Thing.class);
|
||||
Mockito.when(thing.getUID()).thenReturn(new ThingUID("sensibo:account:thinguid"));
|
||||
SensiboSkyHandler handler = Mockito.spy(new SensiboSkyHandler(thing));
|
||||
List<Channel> dynamicChannels = handler.createDynamicChannels(sky);
|
||||
assertTrue(!dynamicChannels.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
{
|
||||
"status": "success",
|
||||
"result": {
|
||||
"configGroup": "stable1",
|
||||
"macAddress": "MA:C:AD:DR:ES:S0",
|
||||
"cleanFiltersNotificationEnabled": true,
|
||||
"room": {
|
||||
"name": "Basement",
|
||||
"icon": "den"
|
||||
},
|
||||
"firmwareType": "cc3100_stm32f0",
|
||||
"productModel": "skyv2",
|
||||
"sensorsCalibration": {
|
||||
"temperature": 0,
|
||||
"humidity": 0
|
||||
},
|
||||
"temperatureUnit": "C",
|
||||
"isGeofenceOnExitEnabled": false,
|
||||
"connectionStatus": {
|
||||
"isAlive": true,
|
||||
"lastSeen": {
|
||||
"secondsAgo": 80,
|
||||
"time": "2019-05-05T07:52:11Z"
|
||||
}
|
||||
},
|
||||
"id": "PODID",
|
||||
"acState": {
|
||||
"on": true,
|
||||
"fanLevel": "medium_high",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 21,
|
||||
"mode": "heat",
|
||||
"swing": "rangeFull"
|
||||
},
|
||||
"smartMode": null,
|
||||
"shouldShowFilterCleaningNotification": true,
|
||||
"location": {
|
||||
"latLon": [
|
||||
59.0000000,
|
||||
10.0000000
|
||||
],
|
||||
"updateTime": null,
|
||||
"country": "Norway",
|
||||
"createTime": {
|
||||
"secondsAgo": 17857639,
|
||||
"time": "2018-10-10T15:26:12Z"
|
||||
},
|
||||
"address": [
|
||||
"Streetname",
|
||||
"zip city",
|
||||
"Norway"
|
||||
],
|
||||
"id": "ADDRESSID"
|
||||
},
|
||||
"currentlyAvailableFirmwareVersion": "IN010056",
|
||||
"isClimateReactGeofenceOnExitEnabled": false,
|
||||
"remoteCapabilities": {
|
||||
"modes": {
|
||||
"dry": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86,
|
||||
88
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"auto": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86,
|
||||
88
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"heat": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
10,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
50,
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86,
|
||||
88
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"fan": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"cool": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86,
|
||||
88
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serial": "SERIALNUMASSTRING",
|
||||
"firmwareVersion": "IN010056",
|
||||
"measurements": {
|
||||
"batteryVoltage": null,
|
||||
"temperature": 22.5,
|
||||
"humidity": 24.2,
|
||||
"time": {
|
||||
"secondsAgo": 80,
|
||||
"time": "2019-05-05T07:52:11Z"
|
||||
},
|
||||
"rssi": "-71",
|
||||
"piezo": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
{
|
||||
"status": "success",
|
||||
"result": {
|
||||
"configGroup": "stable",
|
||||
"macAddress": "MA:C:AD:DR:ES:S0",
|
||||
"isGeofenceOnExitEnabled": false,
|
||||
"sensorsCalibration": {
|
||||
"temperature": 0.0,
|
||||
"humidity": 0.0
|
||||
},
|
||||
"cleanFiltersNotificationEnabled": true,
|
||||
"connectionStatus": {
|
||||
"isAlive": true,
|
||||
"lastSeen": {
|
||||
"secondsAgo": 51,
|
||||
"time": "2019-10-08T04:40:03Z"
|
||||
}
|
||||
},
|
||||
"acState": {
|
||||
"on": false,
|
||||
"mode": "dry",
|
||||
"swing": "stopped"
|
||||
},
|
||||
"serial": "serial",
|
||||
"id": "PODID",
|
||||
"firmwareVersion": "IN010056",
|
||||
"firmwareType": "cc3100_stm32f0",
|
||||
"measurements": {
|
||||
"batteryVoltage": null,
|
||||
"temperature": 23.2,
|
||||
"humidity": 59.5,
|
||||
"time": {
|
||||
"secondsAgo": 51,
|
||||
"time": "2019-10-08T04:40:03Z"
|
||||
},
|
||||
"rssi": "-63",
|
||||
"piezo": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"remoteFlavor": "Enormous Stegosaurus",
|
||||
"smartMode": null,
|
||||
"shouldShowFilterCleaningNotification": false,
|
||||
"location": {
|
||||
"latLon": [
|
||||
41.9279707,
|
||||
12.4625373
|
||||
],
|
||||
"updateTime": {
|
||||
"secondsAgo": 75125215,
|
||||
"time": "2017-05-21T16:33:59Z"
|
||||
},
|
||||
"name": "Casa di Marco",
|
||||
"country": "Italia",
|
||||
"createTime": {
|
||||
"secondsAgo": 129385606,
|
||||
"time": "2015-09-01T16:14:08Z"
|
||||
},
|
||||
"address": [
|
||||
"XXX",
|
||||
"XX",
|
||||
"XXX"
|
||||
],
|
||||
"id": "XX"
|
||||
},
|
||||
"currentlyAvailableFirmwareVersion": "IN010056",
|
||||
"tags": [],
|
||||
"productModel": "skyv2",
|
||||
"schedules": [
|
||||
{
|
||||
"nextTime": null,
|
||||
"podUid": "PODUID",
|
||||
"recurringDays": [],
|
||||
"createTimeSecondsAgo": 67070291,
|
||||
"isEnabled": false,
|
||||
"createTime": "2017-08-22T22:02:43",
|
||||
"acState": {
|
||||
"on": true,
|
||||
"fanLevel": "quiet",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 26,
|
||||
"mode": "cool"
|
||||
},
|
||||
"targetTimeLocal": "03:00",
|
||||
"timezone": "Europe/Rome",
|
||||
"nextTimeSecondsFromNow": null,
|
||||
"causedBy": {
|
||||
"username": "XX",
|
||||
"firstName": "XX",
|
||||
"lastName": "XX",
|
||||
"email": "XXX@domain.it"
|
||||
},
|
||||
"id": "XXXXX"
|
||||
},
|
||||
{
|
||||
"nextTime": null,
|
||||
"podUid": "PODUID",
|
||||
"recurringDays": [],
|
||||
"createTimeSecondsAgo": 4605564,
|
||||
"isEnabled": false,
|
||||
"createTime": "2019-08-15T21:21:30",
|
||||
"acState": {
|
||||
"on": false
|
||||
},
|
||||
"targetTimeLocal": "05:30",
|
||||
"timezone": "Europe/Rome",
|
||||
"nextTimeSecondsFromNow": null,
|
||||
"causedBy": {
|
||||
"username": "XX",
|
||||
"firstName": "XX",
|
||||
"lastName": "XX",
|
||||
"email": "XXX@domain.it"
|
||||
},
|
||||
"id": "QwNtU5zq2D"
|
||||
},
|
||||
{
|
||||
"nextTime": null,
|
||||
"podUid": "E69jFpsP",
|
||||
"recurringDays": [],
|
||||
"createTimeSecondsAgo": 67070257,
|
||||
"isEnabled": false,
|
||||
"createTime": "2017-08-22T22:03:17",
|
||||
"acState": {
|
||||
"on": false
|
||||
},
|
||||
"targetTimeLocal": "03:30",
|
||||
"timezone": "Europe/Rome",
|
||||
"nextTimeSecondsFromNow": null,
|
||||
"causedBy": {
|
||||
"username": "XX",
|
||||
"firstName": "XX",
|
||||
"lastName": "XX",
|
||||
"email": "XXX@domain.it"
|
||||
},
|
||||
"id": "RwVeW8U3Hw"
|
||||
},
|
||||
{
|
||||
"nextTime": null,
|
||||
"podUid": "E69jFpsP",
|
||||
"recurringDays": [],
|
||||
"createTimeSecondsAgo": 4605590,
|
||||
"isEnabled": false,
|
||||
"createTime": "2019-08-15T21:21:04",
|
||||
"acState": {
|
||||
"on": true,
|
||||
"fanLevel": "quiet",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 26,
|
||||
"mode": "cool"
|
||||
},
|
||||
"targetTimeLocal": "05:00",
|
||||
"timezone": "Europe/Rome",
|
||||
"nextTimeSecondsFromNow": null,
|
||||
"causedBy": {
|
||||
"username": "XX",
|
||||
"firstName": "XX",
|
||||
"lastName": "XX",
|
||||
"email": "XXX@domain.it"
|
||||
},
|
||||
"id": "ruzhJCVBeW"
|
||||
}
|
||||
],
|
||||
"isClimateReactGeofenceOnExitEnabled": false,
|
||||
"remoteCapabilities": {
|
||||
"modes": {
|
||||
"dry": {
|
||||
"temperatures": {
|
||||
|
||||
},
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull"
|
||||
]
|
||||
},
|
||||
"auto": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium_low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto",
|
||||
"strong"
|
||||
]
|
||||
},
|
||||
"heat": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
50,
|
||||
52,
|
||||
54,
|
||||
55,
|
||||
57,
|
||||
59,
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium_low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto",
|
||||
"strong"
|
||||
]
|
||||
},
|
||||
"fan": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium_low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto",
|
||||
"strong"
|
||||
]
|
||||
},
|
||||
"cool": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium_low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto",
|
||||
"strong"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"remote": {
|
||||
"window": false,
|
||||
"toggle": false
|
||||
},
|
||||
"room": {
|
||||
"name": "Camera da letto",
|
||||
"icon": "Bedroom"
|
||||
},
|
||||
"temperatureUnit": "C",
|
||||
"timer": {
|
||||
"acState": {
|
||||
"on": false,
|
||||
"mode": "dry",
|
||||
"swing": "stopped"
|
||||
},
|
||||
"targetTimeSecondsFromNow": -5275495,
|
||||
"createTimeSecondsAgo": 5277295,
|
||||
"isEnabled": false,
|
||||
"causedBy": {
|
||||
"username": "XX",
|
||||
"firstName": "XX",
|
||||
"lastName": "XX",
|
||||
"email": "XXX@domain.it"
|
||||
},
|
||||
"id": "KWcppTmrbb",
|
||||
"targetTime": "2019-08-08T03:15:59",
|
||||
"createTime": "2019-08-08T02:45:59"
|
||||
},
|
||||
"motionSensors": [],
|
||||
"remoteAlternatives": [
|
||||
"_daikin2b_comfort",
|
||||
"_daikin2f_33",
|
||||
"_daikin2b_33",
|
||||
"_daikin2b_33_comfort_horizontal_swinging",
|
||||
"_daikin2b",
|
||||
"_daikin2c",
|
||||
"_daikin2bf_comfort",
|
||||
"_daikin2_33",
|
||||
"_daikin2f"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,599 @@
|
||||
{
|
||||
|
||||
"status": "success",
|
||||
|
||||
"result": {
|
||||
|
||||
"configGroup": "stable",
|
||||
|
||||
"macAddress": "xxxxxxxx",
|
||||
|
||||
"isGeofenceOnExitEnabled": false,
|
||||
|
||||
"sensorsCalibration": {
|
||||
|
||||
"temperature": 0.0,
|
||||
|
||||
"humidity": 0.0
|
||||
|
||||
},
|
||||
|
||||
"cleanFiltersNotificationEnabled": true,
|
||||
|
||||
"connectionStatus": {
|
||||
|
||||
"isAlive": true,
|
||||
|
||||
"lastSeen": {
|
||||
|
||||
"secondsAgo": 14,
|
||||
|
||||
"time": "2019-08-15T11:44:00Z"
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"acState": {
|
||||
|
||||
"on": true,
|
||||
|
||||
"targetTemperature": 25,
|
||||
|
||||
"temperatureUnit": "C",
|
||||
|
||||
"mode": "cool",
|
||||
|
||||
"fanLevel": "auto"
|
||||
|
||||
},
|
||||
|
||||
"motionSensors": [],
|
||||
|
||||
"id": "aGbsMfYn",
|
||||
|
||||
"firmwareVersion": "IN010056",
|
||||
|
||||
"firmwareType": "cc3100_stm32f0",
|
||||
|
||||
"measurements": {
|
||||
|
||||
"temperature": 29.1,
|
||||
|
||||
"humidity": 71.5,
|
||||
|
||||
"time": {
|
||||
|
||||
"secondsAgo": 14,
|
||||
|
||||
"time": "2019-08-15T11:44:00Z"
|
||||
|
||||
},
|
||||
|
||||
"rssi": "-52",
|
||||
|
||||
"piezo": [
|
||||
|
||||
null,
|
||||
|
||||
null
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"smartMode": {
|
||||
|
||||
"deviceUid": "aGbsMfYn",
|
||||
|
||||
"highTemperatureThreshold": 30.0,
|
||||
|
||||
"type": "temperature",
|
||||
|
||||
"lowTemperatureState": {
|
||||
|
||||
"on": false,
|
||||
|
||||
"fanLevel": "low",
|
||||
|
||||
"temperatureUnit": "C",
|
||||
|
||||
"targetTemperature": 24,
|
||||
|
||||
"mode": "cool"
|
||||
|
||||
},
|
||||
|
||||
"enabled": false,
|
||||
|
||||
"highTemperatureState": {
|
||||
|
||||
"on": true,
|
||||
|
||||
"fanLevel": "low",
|
||||
|
||||
"temperatureUnit": "C",
|
||||
|
||||
"targetTemperature": 24,
|
||||
|
||||
"mode": "cool"
|
||||
|
||||
},
|
||||
|
||||
"lowTemperatureThreshold": 25.0
|
||||
|
||||
},
|
||||
|
||||
"shouldShowFilterCleaningNotification": false,
|
||||
|
||||
"location": {
|
||||
|
||||
"latLon": [
|
||||
|
||||
12.9331702,
|
||||
|
||||
100.9190772
|
||||
|
||||
],
|
||||
|
||||
"updateTime": {
|
||||
|
||||
"secondsAgo": 11618978,
|
||||
|
||||
"time": "2019-04-03T00:14:36Z"
|
||||
|
||||
},
|
||||
|
||||
"name": "Karel\u0027s place",
|
||||
|
||||
"country": "Thailand",
|
||||
|
||||
"createTime": {
|
||||
|
||||
"secondsAgo": 48921206,
|
||||
|
||||
"time": "2018-01-26T06:30:48Z"
|
||||
|
||||
},
|
||||
|
||||
"address": [
|
||||
|
||||
"xxx",
|
||||
|
||||
"xxx",
|
||||
|
||||
"xxx"
|
||||
|
||||
],
|
||||
|
||||
"id": "2Cr6zmCY5W"
|
||||
|
||||
},
|
||||
|
||||
"currentlyAvailableFirmwareVersion": "IN010056",
|
||||
|
||||
"tags": [],
|
||||
|
||||
"productModel": "skyv2",
|
||||
|
||||
"isClimateReactGeofenceOnExitEnabled": false,
|
||||
|
||||
"remoteCapabilities": {
|
||||
|
||||
"modes": {
|
||||
|
||||
"dry": {
|
||||
|
||||
"temperatures": {
|
||||
|
||||
"C": {
|
||||
|
||||
"isNative": true,
|
||||
|
||||
"values": [
|
||||
|
||||
17,
|
||||
|
||||
18,
|
||||
|
||||
19,
|
||||
|
||||
20,
|
||||
|
||||
21,
|
||||
|
||||
22,
|
||||
|
||||
23,
|
||||
|
||||
24,
|
||||
|
||||
25,
|
||||
|
||||
26,
|
||||
|
||||
27,
|
||||
|
||||
28,
|
||||
|
||||
29,
|
||||
|
||||
30
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"F": {
|
||||
|
||||
"isNative": false,
|
||||
|
||||
"values": [
|
||||
|
||||
63,
|
||||
|
||||
64,
|
||||
|
||||
66,
|
||||
|
||||
68,
|
||||
|
||||
70,
|
||||
|
||||
72,
|
||||
|
||||
73,
|
||||
|
||||
75,
|
||||
|
||||
77,
|
||||
|
||||
79,
|
||||
|
||||
81,
|
||||
|
||||
82,
|
||||
|
||||
84,
|
||||
|
||||
86
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"fanLevels": [
|
||||
|
||||
"low",
|
||||
|
||||
"medium",
|
||||
|
||||
"high",
|
||||
|
||||
"auto"
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"heat": {
|
||||
|
||||
"temperatures": {
|
||||
|
||||
"C": {
|
||||
|
||||
"isNative": true,
|
||||
|
||||
"values": [
|
||||
|
||||
17,
|
||||
|
||||
18,
|
||||
|
||||
19,
|
||||
|
||||
20,
|
||||
|
||||
21,
|
||||
|
||||
22,
|
||||
|
||||
23,
|
||||
|
||||
24,
|
||||
|
||||
25,
|
||||
|
||||
26,
|
||||
|
||||
27,
|
||||
|
||||
28,
|
||||
|
||||
29,
|
||||
|
||||
30
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"F": {
|
||||
|
||||
"isNative": false,
|
||||
|
||||
"values": [
|
||||
|
||||
63,
|
||||
|
||||
64,
|
||||
|
||||
66,
|
||||
|
||||
68,
|
||||
|
||||
70,
|
||||
|
||||
72,
|
||||
|
||||
73,
|
||||
|
||||
75,
|
||||
|
||||
77,
|
||||
|
||||
79,
|
||||
|
||||
81,
|
||||
|
||||
82,
|
||||
|
||||
84,
|
||||
|
||||
86
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"fanLevels": [
|
||||
|
||||
"low",
|
||||
|
||||
"medium",
|
||||
|
||||
"high",
|
||||
|
||||
"auto"
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"fan": {
|
||||
|
||||
"temperatures": {
|
||||
|
||||
"C": {
|
||||
|
||||
"isNative": true,
|
||||
|
||||
"values": [
|
||||
|
||||
17,
|
||||
|
||||
18,
|
||||
|
||||
19,
|
||||
|
||||
20,
|
||||
|
||||
21,
|
||||
|
||||
22,
|
||||
|
||||
23,
|
||||
|
||||
24,
|
||||
|
||||
25,
|
||||
|
||||
26,
|
||||
|
||||
27,
|
||||
|
||||
28,
|
||||
|
||||
29,
|
||||
|
||||
30
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"F": {
|
||||
|
||||
"isNative": false,
|
||||
|
||||
"values": [
|
||||
|
||||
63,
|
||||
|
||||
64,
|
||||
|
||||
66,
|
||||
|
||||
68,
|
||||
|
||||
70,
|
||||
|
||||
72,
|
||||
|
||||
73,
|
||||
|
||||
75,
|
||||
|
||||
77,
|
||||
|
||||
79,
|
||||
|
||||
81,
|
||||
|
||||
82,
|
||||
|
||||
84,
|
||||
|
||||
86
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"fanLevels": [
|
||||
|
||||
"low",
|
||||
|
||||
"medium",
|
||||
|
||||
"high",
|
||||
|
||||
"auto"
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"cool": {
|
||||
|
||||
"temperatures": {
|
||||
|
||||
"C": {
|
||||
|
||||
"isNative": true,
|
||||
|
||||
"values": [
|
||||
|
||||
17,
|
||||
|
||||
18,
|
||||
|
||||
19,
|
||||
|
||||
20,
|
||||
|
||||
21,
|
||||
|
||||
22,
|
||||
|
||||
23,
|
||||
|
||||
24,
|
||||
|
||||
25,
|
||||
|
||||
26,
|
||||
|
||||
27,
|
||||
|
||||
28,
|
||||
|
||||
29,
|
||||
|
||||
30
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
"F": {
|
||||
|
||||
"isNative": false,
|
||||
|
||||
"values": [
|
||||
|
||||
63,
|
||||
|
||||
64,
|
||||
|
||||
66,
|
||||
|
||||
68,
|
||||
|
||||
70,
|
||||
|
||||
72,
|
||||
|
||||
73,
|
||||
|
||||
75,
|
||||
|
||||
77,
|
||||
|
||||
79,
|
||||
|
||||
81,
|
||||
|
||||
82,
|
||||
|
||||
84,
|
||||
|
||||
86
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"fanLevels": [
|
||||
|
||||
"low",
|
||||
|
||||
"medium",
|
||||
|
||||
"high",
|
||||
|
||||
"auto"
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"serial": "50175457",
|
||||
|
||||
"remote": {
|
||||
|
||||
"window": false,
|
||||
|
||||
"toggle": false
|
||||
|
||||
},
|
||||
|
||||
"room": {
|
||||
|
||||
"name": "AC_bedroom",
|
||||
|
||||
"icon": "bedroom"
|
||||
|
||||
},
|
||||
|
||||
"temperatureUnit": "C",
|
||||
|
||||
"remoteFlavor": "Unofficial Cobra",
|
||||
|
||||
"remoteAlternatives": []
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
{
|
||||
"status": "success",
|
||||
"result": {
|
||||
"configGroup": "stable",
|
||||
"macAddress": "00:11:22:33:44:55",
|
||||
"isGeofenceOnExitEnabled": false,
|
||||
"sensorsCalibration": {
|
||||
"temperature": 0.0,
|
||||
"humidity": 0.0
|
||||
},
|
||||
"cleanFiltersNotificationEnabled": true,
|
||||
"connectionStatus": {
|
||||
"isAlive": true,
|
||||
"lastSeen": {
|
||||
"secondsAgo": 12,
|
||||
"time": "2019-10-05T15:26:45.199202Z"
|
||||
}
|
||||
},
|
||||
"acState": {
|
||||
"on": true,
|
||||
"fanLevel": "auto",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 10,
|
||||
"mode": "heat",
|
||||
"swing": "rangeFull"
|
||||
},
|
||||
"serial": "000000000",
|
||||
"id": "PODID",
|
||||
"firmwareVersion": "SKY30043",
|
||||
"firmwareType": "esp8266ex",
|
||||
"measurements": {
|
||||
"temperature": 19.1,
|
||||
"humidity": 34.8,
|
||||
"time": {
|
||||
"secondsAgo": 12,
|
||||
"time": "2019-10-05T15:26:45.199202Z"
|
||||
},
|
||||
"rssi": "-68",
|
||||
"piezo": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"remoteFlavor": "Enthusiastic Triceratops",
|
||||
"shouldShowFilterCleaningNotification": false,
|
||||
"location": {
|
||||
"latLon": [
|
||||
59.924477,
|
||||
10.5884129
|
||||
],
|
||||
"name": "Home",
|
||||
"country": "Fantasyland",
|
||||
"createTime": {
|
||||
"secondsAgo": 31104046,
|
||||
"time": "2018-10-10T15:26:12Z"
|
||||
},
|
||||
"address": [
|
||||
"Street",
|
||||
"Zip post",
|
||||
"ENgland"
|
||||
],
|
||||
"id": "XXXXXXXX"
|
||||
},
|
||||
"currentlyAvailableFirmwareVersion": "SKY30043",
|
||||
"tags": [],
|
||||
"productModel": "skyv2",
|
||||
"schedules": [
|
||||
{
|
||||
"nextTime": "2019-10-06T11:00:00",
|
||||
"podUid": "XXXXXX",
|
||||
"recurringDays": [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday"
|
||||
],
|
||||
"createTimeSecondsAgo": 51,
|
||||
"isEnabled": true,
|
||||
"createTime": "2019-10-05T15:26:07",
|
||||
"acState": {
|
||||
"on": true,
|
||||
"fanLevel": "auto",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 10,
|
||||
"mode": "heat"
|
||||
},
|
||||
"targetTimeLocal": "13:00",
|
||||
"timezone": "Europe/Oslo",
|
||||
"nextTimeSecondsFromNow": 70381,
|
||||
"causedBy": {
|
||||
"username": "username",
|
||||
"firstName": "FIrst",
|
||||
"lastName": "Last",
|
||||
"email": "mail@gmail.com"
|
||||
},
|
||||
"id": "scheduleid"
|
||||
}
|
||||
],
|
||||
"isClimateReactGeofenceOnExitEnabled": false,
|
||||
"remoteCapabilities": {
|
||||
"modes": {
|
||||
"dry": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86,
|
||||
88
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"auto": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86,
|
||||
88
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"heat": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
10,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
50,
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86,
|
||||
88
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"fan": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"cool": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"fixedTop",
|
||||
"fixedMiddleTop",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleBottom",
|
||||
"fixedBottom",
|
||||
"rangeFull"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86,
|
||||
88
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"quiet",
|
||||
"low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"remote": {
|
||||
"window": false,
|
||||
"toggle": false
|
||||
},
|
||||
"room": {
|
||||
"name": "Stue",
|
||||
"icon": "Livingroom"
|
||||
},
|
||||
"temperatureUnit": "C",
|
||||
"timer": {
|
||||
"acState": {
|
||||
"on": false,
|
||||
"fanLevel": "auto",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 10,
|
||||
"mode": "heat",
|
||||
"swing": "rangeFull"
|
||||
},
|
||||
"targetTimeSecondsFromNow": 3142,
|
||||
"createTimeSecondsAgo": 38,
|
||||
"isEnabled": true,
|
||||
"causedBy": {
|
||||
"username": "arneseime",
|
||||
"firstName": "Arne",
|
||||
"lastName": "Seime",
|
||||
"email": "arne.seime@gmail.com"
|
||||
},
|
||||
"id": "7AZN7A9amL",
|
||||
"targetTime": "2019-10-05T16:42:11",
|
||||
"createTime": "2019-10-05T15:49:11"
|
||||
},
|
||||
"motionSensors": [],
|
||||
"remoteAlternatives": [
|
||||
"_mitsubishi1f_horizontal_rangeful",
|
||||
"_mitsubishi1_for_ben_ho",
|
||||
"_mitsubishi1_plasma_on",
|
||||
"_mitsubishi1f_fixed_left",
|
||||
"_mitsubishi1_plasma_on_clean_on",
|
||||
"mitsubishi1f",
|
||||
"_mitsubishi1f_fixed_right",
|
||||
"_mitsubishi1_left_swing_fixed_bottom",
|
||||
"_mitsubishi1_fixed_center",
|
||||
"_mitsubishi1f"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
{
|
||||
"status": "success",
|
||||
"result": {
|
||||
"configGroup": "stable",
|
||||
"macAddress": "34:15:13:AA:AA:AA",
|
||||
"cleanFiltersNotificationEnabled": true,
|
||||
"room": {
|
||||
"name": "Living Room",
|
||||
"icon": "Den"
|
||||
},
|
||||
"firmwareType": "cc3100_stm32f0",
|
||||
"productModel": "skyv2",
|
||||
"sensorsCalibration": {
|
||||
"temperature": 0.0,
|
||||
"humidity": 0.0
|
||||
},
|
||||
"temperatureUnit": "C",
|
||||
"isGeofenceOnExitEnabled": true,
|
||||
"connectionStatus": {
|
||||
"isAlive": true,
|
||||
"lastSeen": {
|
||||
"secondsAgo": 17,
|
||||
"time": "2019-05-12T22:24:36Z"
|
||||
}
|
||||
},
|
||||
"id": "PODID",
|
||||
"acState": {
|
||||
"on": false,
|
||||
"fanLevel": "high",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 21,
|
||||
"mode": "cool",
|
||||
"swing": "rangeFull"
|
||||
},
|
||||
"smartMode": {
|
||||
"deviceUid": "PODID",
|
||||
"highTemperatureThreshold": 28.0,
|
||||
"type": "temperature",
|
||||
"lowTemperatureState": {
|
||||
"on": false,
|
||||
"fanLevel": "auto",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 26,
|
||||
"mode": "cool"
|
||||
},
|
||||
"enabled": false,
|
||||
"highTemperatureState": {
|
||||
"on": true,
|
||||
"fanLevel": "auto",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 26,
|
||||
"mode": "cool"
|
||||
},
|
||||
"lowTemperatureThreshold": 20.0
|
||||
},
|
||||
"shouldShowFilterCleaningNotification": false,
|
||||
"location": {
|
||||
"latLon": [
|
||||
47.5274799,
|
||||
19.1127283
|
||||
],
|
||||
"country": "Hungary",
|
||||
"createTime": {
|
||||
"secondsAgo": 2519470,
|
||||
"time": "2019-04-13T18:33:43Z"
|
||||
},
|
||||
"address": [
|
||||
"B",
|
||||
"u",
|
||||
"d"
|
||||
],
|
||||
"id": "w9s6KMRrhR"
|
||||
},
|
||||
"currentlyAvailableFirmwareVersion": "IN010056",
|
||||
"isClimateReactGeofenceOnExitEnabled": false,
|
||||
"remoteCapabilities": {
|
||||
"modes": {
|
||||
"dry": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull",
|
||||
"fixedBottom",
|
||||
"fixedMiddleBottom",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleTop",
|
||||
"fixedTop"
|
||||
],
|
||||
"temperatures": {},
|
||||
"fanLevels": [
|
||||
"low"
|
||||
]
|
||||
},
|
||||
"auto": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull",
|
||||
"fixedBottom",
|
||||
"fixedMiddleBottom",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleTop",
|
||||
"fixedTop"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
59,
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"heat": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull",
|
||||
"fixedBottom",
|
||||
"fixedMiddleBottom",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleTop",
|
||||
"fixedTop"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"low",
|
||||
"medium_low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"fan": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull",
|
||||
"fixedBottom",
|
||||
"fixedMiddleBottom",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleTop",
|
||||
"fixedTop"
|
||||
],
|
||||
"temperatures": {},
|
||||
"fanLevels": [
|
||||
"low",
|
||||
"medium_low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"cool": {
|
||||
"swing": [
|
||||
"stopped",
|
||||
"rangeFull",
|
||||
"fixedBottom",
|
||||
"fixedMiddleBottom",
|
||||
"fixedMiddle",
|
||||
"fixedMiddleTop",
|
||||
"fixedTop"
|
||||
],
|
||||
"temperatures": {
|
||||
"C": {
|
||||
"isNative": true,
|
||||
"values": [
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
]
|
||||
},
|
||||
"F": {
|
||||
"isNative": false,
|
||||
"values": [
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
77,
|
||||
79,
|
||||
81,
|
||||
82,
|
||||
84,
|
||||
86
|
||||
]
|
||||
}
|
||||
},
|
||||
"fanLevels": [
|
||||
"low",
|
||||
"medium_low",
|
||||
"medium",
|
||||
"medium_high",
|
||||
"high",
|
||||
"auto"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serial": "11184989",
|
||||
"firmwareVersion": "IN010056",
|
||||
"measurements": {
|
||||
"temperature": 23.8,
|
||||
"humidity": 58.9,
|
||||
"time": {
|
||||
"secondsAgo": 17,
|
||||
"time": "2019-05-12T22:24:36Z"
|
||||
},
|
||||
"rssi": "-40",
|
||||
"piezo": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"status": "success",
|
||||
"result": [
|
||||
{
|
||||
"id": "PODID"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{ "acState": {
|
||||
"on": true,
|
||||
"fanLevel": "medium_high",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 21,
|
||||
"mode": "heat",
|
||||
"swing": "rangeFull"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"status": "success",
|
||||
"result": {
|
||||
"status": "Success",
|
||||
"reason": "UserRequest",
|
||||
"acState": {
|
||||
"on": true,
|
||||
"fanLevel": "medium_high",
|
||||
"temperatureUnit": "C",
|
||||
"targetTemperature": 21,
|
||||
"mode": "heat",
|
||||
"swing": "rangeFull"
|
||||
},
|
||||
"changedProperties": [],
|
||||
"id": "RESULTID",
|
||||
"failureReason": null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user