added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.max/.classpath
Normal file
32
bundles/org.openhab.binding.max/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.max/.project
Normal file
23
bundles/org.openhab.binding.max/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.max</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.max/NOTICE
Normal file
13
bundles/org.openhab.binding.max/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
163
bundles/org.openhab.binding.max/README.md
Normal file
163
bundles/org.openhab.binding.max/README.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# MAX! Binding
|
||||
|
||||
This is the binding for the [eQ-3 MAX! Home Solution](http://www.eq-3.de/).
|
||||
This binding allows you to integrate, view and control the MAX! Thermostats in the openHAB environment
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding support 6 different things types
|
||||
|
||||
| Thing | Type | Description |
|
||||
|----------------|--------|--------------------------------------------------------------------------------------------------------------------|
|
||||
| bridge | Bridge | This is the MAX! Cube LAN gateway. |
|
||||
| thermostat | Thing | This is for the MAX! Heating Thermostat. This is also used for the powerplug switch "Zwischenstecker-Schaltaktor". |
|
||||
| thermostatplus | Thing | This is for the MAX! Heating Thermostat+. This is the type that can hold the program by itself. |
|
||||
| wallthermostat | Thing | MAX! Wall Thermostat. |
|
||||
| ecoswitch | Thing | MAX! Ecoswitch. |
|
||||
| shuttercontact | Thing | MAX! Shuttercontact / Window Contact. |
|
||||
|
||||
|
||||
Generally one does not have to worry about the thing types as they are automatically defined.
|
||||
If for any reason you need to manually define the Things and you are not exactly sure what type of thermostat you have, you can choose `thermostat` for both the thermostat and thermostat+, this will not affect their working.
|
||||
|
||||
## Discovery
|
||||
|
||||
The discovery process for the MAX! binding works in 2 steps.
|
||||
When the binding is started or when manually triggered, the network is queried for the existence of a MAX! Cube LAN gateway.
|
||||
When the Cube is found, it will become available in the discovery inbox. Periodically the network is queried again for a Cube.
|
||||
|
||||
Once the Cube is available in openHAB, all the devices connected to it are discovered and added to the discovery inbox.
|
||||
No scan is needed to trigger this.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
In the openHAB2 version of this binding there are no binding wide settings.
|
||||
All the configuration settings are now per MAX! Cube, hence in case you have multiple Cubes, they can run with alternative settings.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
All the things are identified by their serial number, hence this is mandatory.
|
||||
The Cube (`bridge` thing) also requires the IP address to be defined.
|
||||
All other configuration is optional.
|
||||
|
||||
Note that several configuration options are automatically populated.
|
||||
Later versions of the binding may allow you to update this information.
|
||||
These properties can be found in the `Device Settings` section of parameters.
|
||||
|
||||
## Channels
|
||||
|
||||
Depending on the thing it supports different Channels
|
||||
|
||||
| Channel Type ID | Item Type | Description | Available on thing |
|
||||
|-----------------|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|
|
||||
| mode | String | This channel indicates the mode of a thermostat (AUTOMATIC/MANUAL/BOOST/VACATION). | thermostat, thermostatplus, wallthermostat |
|
||||
| battery_low | Switch | This channel indicates if the device battery is low (ON/OFF). | thermostat, thermostatplus, wallthermostat, ecoswitch, shuttercontact |
|
||||
| set_temp | Number:Temperature | This channel indicates the sets temperature of a thermostat. | thermostat, thermostatplus, wallthermostat |
|
||||
| actual_temp | Number:Temperature | This channel indicates the measured temperature of a thermostat (see below for more details). | thermostat, thermostatplus, wallthermostat |
|
||||
| valve | Number | This channel indicates the valve opening in %. Note this is an advanced setting, normally not visible. | thermostat, thermostatplus, wallthermostat |
|
||||
| locked | Contact | This channel indicates if the thermostat is locked for adjustments (OPEN/CLOSED). Note this is an advanced setting, normally not visible. | thermostat, thermostatplus, wallthermostat |
|
||||
| contact_state | Contact | This channel indicates the contact state for a shutterswitch (OPEN/CLOSED). | shuttercontact |
|
||||
| free_mem | Number | This channel indicates the free available memory on the cube to hold send commands. Note this is an advanced setting, normally not visible. | bridge |
|
||||
| duty_cycle | Number | This channel indicates the duty cycle. Due to regulatory compliance reasons in Europe, the cube is allowed to send at no more than 1% of the time, which sums up to 36 seconds per hour. Once the threshold has been reached, the cube stops sending commands for the remaining time of the hour. The value in this field seems to represent a percentage ranging from 0 to 100, with a value of 100 meaning that the threshold has been reached. Note that this is an advanced setting, normally not visible. | bridge |
|
||||
|
||||
## Full Example
|
||||
|
||||
In most cases no Things need to be defined manually.
|
||||
In case your Cube can't be discovered you need a `max:bridge` definition including the right IP address of the Cube.
|
||||
Only in exceptional cases you would need to define the thermostats etc.
|
||||
|
||||
max.things:
|
||||
|
||||
```java
|
||||
Bridge max:bridge:KEQ0565026 [ ipAddress="192.168.3.9", serialNumber="KEQ0565026" ] {
|
||||
Thing thermostat KEQ0565123 [ serialNumber="KEQ0565123", refreshActualRate=60 ]
|
||||
Thing shuttercontact NEQ1150510 [ serialNumber="NEQ1150510" ]
|
||||
}
|
||||
```
|
||||
|
||||
max.items:
|
||||
|
||||
```java
|
||||
Group gMAX "MAX Heating" <temperature> [ "home-group" ]
|
||||
|
||||
Switch maxBattery "Battery Low" (gMAX) {channel="max:thermostat:KEQ0565026:KEQ0648949:battery_low"}
|
||||
String maxMode "Thermostat Mode Setting" (gMAX) {channel="max:thermostat:KEQ0565026:KEQ0648949:mode"}
|
||||
Number:Temperature maxActual "Actual measured room temperature [%.1f %unit%]" (gMAX) {channel="max:thermostat:KEQ0565026:KEQ0648949:actual_temp"}
|
||||
Number:Temperature maxSetTemp "Thermostat temperature setpoint [%.1f %unit%]" (gMAX) {channel="max:thermostat:KEQ0565026:KEQ0648949:set_temp"}
|
||||
Contact maxShuttercontactState "Contact State" (gMAX) {channel="max:shuttercontact:KEQ0565026:NEQ1150510:contact_state"}
|
||||
Switch maxShuttercontactBattery "Contact Battery Low" <battery> (gMAX) {channel="max:shuttercontact:KEQ0565026:NEQ1150510:battery_low"}
|
||||
```
|
||||
|
||||
demo.sitemap:
|
||||
|
||||
```perl
|
||||
sitemap demo label="Main Menu" {
|
||||
Frame label="MAX Heating System" {
|
||||
Switch item=maxMode icon="climate" mappings=[AUTOMATIC=AUTOMATIC, MANUAL=MANUAL, BOOST=BOOST]
|
||||
Setpoint item=maxSetTemp minValue=4.5 maxValue=32 step=0.5 icon="temperature"
|
||||
Text item=maxActual icon="temperature"
|
||||
Switch item=maxBattery
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Actual Temperature Update
|
||||
|
||||
Please be aware that the actual temperature measure for thermostats is only updated after the valve moved position or the thermostats mode has changed.
|
||||
Hence the temperature you see may be hours old.
|
||||
In that case you can update the temperature by changing the mode, wait approximately 2 minutes and change the mode back.
|
||||
There is an experimental mode that does this automatically.
|
||||
This can be enabled by showing the "advanced settings" (e.g. in HABmin UI).
|
||||
Then the "Actual Temperature Refresh Rate" can be set.
|
||||
Minimum refresh rate once/10 minutes, recommended 60min to avoid excessive battery drain.
|
||||
|
||||
## New Device Inclusion
|
||||
|
||||
When clicking the discovery button for MAX! devices manually in the UI, you will start New Device Inclusion mode for 60seconds.
|
||||
During this time, holding the _boost_ button on your device will link it to the Cube.
|
||||
|
||||
## Device Configuration
|
||||
|
||||
In the _Configuration Parameters_ section of the device Things you can update some of the device configuration parameters.
|
||||
Currently the following parameters can be updated:
|
||||
|
||||
* _name_ Name of the thermostat stored in the Cube (also used by the eQ-3 software).
|
||||
|
||||
_Cube device configurable parameters_
|
||||
|
||||
* _ntpServer1_ The hostname for NTP Server 1 used by the Cube to get the time
|
||||
* _ntpServer2_ The hostname for NTP Server 2 used by the Cube to get the time
|
||||
|
||||
## Action Buttons (visible in HABmin)
|
||||
|
||||
The Action Buttons are deprecated and will be removed in a future version.
|
||||
|
||||
## Thing Actions
|
||||
|
||||
Several Thing Actions are available to trigger special actions on the MAX! Cube
|
||||
|
||||
* `reset()`: _Reset Cube Configuration_ resets the MAX! Cube room and device information. Devices will need to be included again!
|
||||
|
||||
* `reboot()`: _Restart Cube_ triggers the reboot of a Cube. This can be used if a Cube became unresponsive to commands or no connection can be made. (e.g. if you tried to connect to the Cube with multiple applications at the same time)
|
||||
|
||||
On the MAX! devices you can trigger the following action
|
||||
|
||||
* `deleteFromCube()`: _Delete Device from Cube_ deletes the device from the MAX! Cube. Device will need to be included again!
|
||||
|
||||
Note: In Paper UI there are no action buttons.
|
||||
You can trigger these actions changing from 'No Action' in the dropdown to the action value.
|
||||
|
||||
### Example Rule
|
||||
|
||||
demo.rules:
|
||||
|
||||
```java
|
||||
rule "Reboot MAX! Cube"
|
||||
when
|
||||
...
|
||||
then
|
||||
val maxCubeActions = getActions("max-cube", "max:bridge:KEQ0565026")
|
||||
val Boolean success = maxCubeActions.reboot()
|
||||
logInfo("max", "Action 'reboot' returned '{}'", success)
|
||||
end
|
||||
```
|
||||
17
bundles/org.openhab.binding.max/pom.xml
Normal file
17
bundles/org.openhab.binding.max/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?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/xsd/maven-4.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.max</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: MaxCube Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.max-${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-max" description="MAX! Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.max/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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.max.actions;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.actions.IMaxCubeActions;
|
||||
import org.openhab.binding.max.internal.handler.MaxCubeBridgeHandler;
|
||||
import org.openhab.core.automation.annotation.ActionOutput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxCubeActions} class defines rule actions for MAX! Cube
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "max-cube")
|
||||
@NonNullByDefault
|
||||
public class MaxCubeActions implements ThingActions, IMaxCubeActions {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxCubeActions.class);
|
||||
|
||||
private @Nullable MaxCubeBridgeHandler handler;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof MaxCubeBridgeHandler) {
|
||||
this.handler = (MaxCubeBridgeHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Backup Cube Data", description = "Creates a backup of the MAX! Cube data.")
|
||||
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean backup() {
|
||||
MaxCubeBridgeHandler actionsHandler = handler;
|
||||
if (actionsHandler == null) {
|
||||
logger.info("MaxCubeActions: Action service ThingHandler is null!");
|
||||
return false;
|
||||
}
|
||||
actionsHandler.backup();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean backup(@Nullable ThingActions actions) {
|
||||
return invokeMethodOf(actions).backup();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Reset Cube Configuration", description = "Resets the MAX! Cube room and device information. Devices will need to be included again!")
|
||||
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean resetConfig() {
|
||||
MaxCubeBridgeHandler actionsHandler = handler;
|
||||
if (actionsHandler == null) {
|
||||
logger.info("MaxCubeActions: Action service ThingHandler is null!");
|
||||
return false;
|
||||
}
|
||||
actionsHandler.cubeConfigReset();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean reset(@Nullable ThingActions actions) {
|
||||
return invokeMethodOf(actions).resetConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Restart Cube", description = "Restarts the MAX! Cube.")
|
||||
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean reboot() {
|
||||
MaxCubeBridgeHandler actionsHandler = handler;
|
||||
if (actionsHandler == null) {
|
||||
logger.info("MaxCubeActions: Action service ThingHandler is null!");
|
||||
return false;
|
||||
}
|
||||
actionsHandler.cubeReboot();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean reboot(@Nullable ThingActions actions) {
|
||||
return invokeMethodOf(actions).reboot();
|
||||
}
|
||||
|
||||
private static IMaxCubeActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(MaxCubeActions.class.getName())) {
|
||||
if (actions instanceof IMaxCubeActions) {
|
||||
return (IMaxCubeActions) actions;
|
||||
} else {
|
||||
return (IMaxCubeActions) Proxy.newProxyInstance(IMaxCubeActions.class.getClassLoader(),
|
||||
new Class[] { IMaxCubeActions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of MaxCubeActions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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.max.actions;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.actions.IMaxDevicesActions;
|
||||
import org.openhab.binding.max.internal.handler.MaxDevicesHandler;
|
||||
import org.openhab.core.automation.annotation.ActionOutput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxDevicesActions} class defines rule actions for MAX! devices
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "max-devices")
|
||||
@NonNullByDefault
|
||||
public class MaxDevicesActions implements ThingActions, IMaxDevicesActions {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxDevicesActions.class);
|
||||
|
||||
private @Nullable MaxDevicesHandler handler;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof MaxDevicesHandler) {
|
||||
this.handler = (MaxDevicesHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Delete Device from Cube", description = "Deletes the device from the MAX! Cube. Device will need to be included again!")
|
||||
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean deleteFromCube() {
|
||||
MaxDevicesHandler actionsHandler = handler;
|
||||
if (actionsHandler == null) {
|
||||
logger.info("MaxDevicesActions: Action service ThingHandler is null!");
|
||||
return false;
|
||||
}
|
||||
actionsHandler.deviceDelete();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean deleteFromCube(@Nullable ThingActions actions) {
|
||||
return invokeMethodOf(actions).deleteFromCube();
|
||||
}
|
||||
|
||||
private static IMaxDevicesActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(MaxDevicesActions.class.getName())) {
|
||||
if (actions instanceof IMaxDevicesActions) {
|
||||
return (IMaxDevicesActions) actions;
|
||||
} else {
|
||||
return (IMaxDevicesActions) Proxy.newProxyInstance(IMaxDevicesActions.class.getClassLoader(),
|
||||
new Class[] { IMaxDevicesActions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of MaxDevicesActions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.max.internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.core.ConfigConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxBackupUtils} class supports the backup of cube data
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MaxBackupUtils {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxBackupUtils.class);
|
||||
private static final String BACKUP_PATH = "max";
|
||||
|
||||
private final String dbFolderName;
|
||||
private final String backupId;
|
||||
private boolean inProgress = false;
|
||||
private StringBuilder msg = new StringBuilder();
|
||||
private String cube = "";
|
||||
|
||||
public MaxBackupUtils(String backupId) {
|
||||
this.backupId = backupId;
|
||||
dbFolderName = ConfigConstants.getUserDataFolder() + File.separator + BACKUP_PATH;
|
||||
File folder = new File(dbFolderName);
|
||||
if (!folder.exists()) {
|
||||
folder.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
public MaxBackupUtils() {
|
||||
this("auto" + new SimpleDateFormat("MM").format(Calendar.getInstance().getTime()));
|
||||
}
|
||||
|
||||
public void buildBackup(String msgLine) {
|
||||
if (msgLine.startsWith("H:")) {
|
||||
msg = new StringBuilder();
|
||||
cube = msgLine.substring(2).split(",")[0];
|
||||
inProgress = true;
|
||||
}
|
||||
if (inProgress) {
|
||||
msg.append(msgLine);
|
||||
msg.append(System.lineSeparator());
|
||||
}
|
||||
if (inProgress && msgLine.startsWith("L:")) {
|
||||
inProgress = false;
|
||||
saveMsg(msg.toString(), cube);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveMsg(String data, String cube) {
|
||||
File dataFile = new File(dbFolderName + File.separator + "backup-" + backupId + "-" + cube + ".txt");
|
||||
try (FileWriter writer = new FileWriter(dataFile)) {
|
||||
writer.write(data);
|
||||
logger.debug("MAX! backup saved to {}", dataFile.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
logger.warn("MAX! Failed to write backup file '{}': {}", dataFile.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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.max.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 MaxBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MaxBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "max";
|
||||
|
||||
// List of main device types
|
||||
public static final String DEVICE_THERMOSTAT = "thermostat";
|
||||
public static final String DEVICE_THERMOSTATPLUS = "thermostatplus";
|
||||
public static final String DEVICE_WALLTHERMOSTAT = "wallthermostat";
|
||||
public static final String DEVICE_ECOSWITCH = "ecoswitch";
|
||||
public static final String DEVICE_SHUTTERCONTACT = "shuttercontact";
|
||||
public static final String BRIDGE_MAXCUBE = "bridge";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID HEATINGTHERMOSTAT_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_THERMOSTAT);
|
||||
public static final ThingTypeUID HEATINGTHERMOSTATPLUS_THING_TYPE = new ThingTypeUID(BINDING_ID,
|
||||
DEVICE_THERMOSTATPLUS);
|
||||
public static final ThingTypeUID WALLTHERMOSTAT_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_WALLTHERMOSTAT);
|
||||
public static final ThingTypeUID ECOSWITCH_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_ECOSWITCH);
|
||||
public static final ThingTypeUID SHUTTERCONTACT_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_SHUTTERCONTACT);
|
||||
public static final ThingTypeUID CUBEBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, BRIDGE_MAXCUBE);
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_VALVE = "valve";
|
||||
public static final String CHANNEL_BATTERY = "battery_low";
|
||||
public static final String CHANNEL_MODE = "mode";
|
||||
public static final String CHANNEL_ACTUALTEMP = "actual_temp";
|
||||
public static final String CHANNEL_SETTEMP = "set_temp";
|
||||
public static final String CHANNEL_LOCKED = "locked";
|
||||
public static final String CHANNEL_CONTACT_STATE = "contact_state";
|
||||
public static final String CHANNEL_FREE_MEMORY = "free_mem";
|
||||
public static final String CHANNEL_DUTY_CYCLE = "duty_cycle";
|
||||
|
||||
// Custom Properties
|
||||
public static final String PROPERTY_IP_ADDRESS = "ipAddress";
|
||||
public static final String PROPERTY_VENDOR_NAME = "eQ-3 AG";
|
||||
public static final String PROPERTY_RFADDRESS = "rfAddress";
|
||||
public static final String PROPERTY_ROOMNAME = "room";
|
||||
public static final String PROPERTY_ROOMID = "roomId";
|
||||
public static final String PROPERTY_DEVICENAME = "name";
|
||||
public static final String PROPERTY_REFRESH_ACTUAL_RATE = "refreshActualRate";
|
||||
public static final String PROPERTY_NTP_SERVER1 = "ntpServer1";
|
||||
public static final String PROPERTY_NTP_SERVER2 = "ntpServer2";
|
||||
|
||||
// Thermostat settings properties
|
||||
public static final String PROPERTY_THERMO_COMFORT_TEMP = "comfortTemp";
|
||||
public static final String PROPERTY_THERMO_ECO_TEMP = "ecoTemp";
|
||||
public static final String PROPERTY_THERMO_MAX_TEMP_SETPOINT = "maxTempSetpoint";
|
||||
public static final String PROPERTY_THERMO_MIN_TEMP_SETPOINT = "minTempSetpoint";
|
||||
public static final String PROPERTY_THERMO_OFFSET_TEMP = "offsetTemp";
|
||||
public static final String PROPERTY_THERMO_WINDOW_OPEN_TEMP = "windowOpenTemp";
|
||||
public static final String PROPERTY_THERMO_WINDOW_OPEN_DURATION = "windowOpenDuration";
|
||||
public static final String PROPERTY_THERMO_DECALCIFICATION = "decalcification";
|
||||
public static final String PROPERTY_THERMO_VALVE_MAX = "valveMaximum";
|
||||
public static final String PROPERTY_THERMO_VALVE_OFFSET = "valveOffset";
|
||||
public static final String PROPERTY_THERMO_BOOST_DURATION = "boostDuration";
|
||||
public static final String PROPERTY_THERMO_BOOST_VALVEPOS = "boostValvePos";
|
||||
public static final String PROPERTY_THERMO_PROGRAM_DATA = "programData";
|
||||
|
||||
// List of actions
|
||||
public static final String ACTION_CUBE_REBOOT = "action-cubeReboot";
|
||||
public static final String ACTION_CUBE_RESET = "action-cubeReset";
|
||||
public static final String ACTION_DEVICE_DELETE = "action-deviceDelete";
|
||||
public static final String BUTTON_ACTION_VALUE = "1234";
|
||||
public static final int BUTTON_NOACTION_VALUE = -1;
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_DEVICE_THING_TYPES_UIDS = Collections.unmodifiableSet(
|
||||
Stream.of(HEATINGTHERMOSTAT_THING_TYPE, HEATINGTHERMOSTATPLUS_THING_TYPE, WALLTHERMOSTAT_THING_TYPE,
|
||||
ECOSWITCH_THING_TYPE, SHUTTERCONTACT_THING_TYPE).collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(CUBEBRIDGE_THING_TYPE).collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
|
||||
Stream.concat(SUPPORTED_DEVICE_THING_TYPES_UIDS.stream(), SUPPORTED_BRIDGE_THING_TYPES_UIDS.stream())
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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.max.internal;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.handler.MaxCubeBridgeHandler;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link MaxConsoleCommandExtension} class provides additional options through the console command line.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@Component(service = ConsoleCommandExtension.class)
|
||||
@NonNullByDefault
|
||||
public class MaxConsoleCommandExtension extends AbstractConsoleCommandExtension {
|
||||
|
||||
private static final String SUBCMD_BACKUP = "backup";
|
||||
private static final String SUBCMD_REBOOT = "reboot";
|
||||
private final ThingRegistry thingRegistry;
|
||||
|
||||
@Activate
|
||||
public MaxConsoleCommandExtension(@Reference ThingRegistry thingRegistry) {
|
||||
super("max", "Additional EQ3 MAX! commands.");
|
||||
this.thingRegistry = thingRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String[] args, Console console) {
|
||||
if (args.length > 0) {
|
||||
switch (args[0]) {
|
||||
case SUBCMD_BACKUP:
|
||||
handleBackup(console);
|
||||
break;
|
||||
case SUBCMD_REBOOT:
|
||||
handleReboot(args, console);
|
||||
break;
|
||||
default:
|
||||
console.println(String.format("Unknown MAX! sub command '%s'", args[0]));
|
||||
printUsage(console);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
printUsage(console);
|
||||
printMaxDevices(console, SUPPORTED_THING_TYPES_UIDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBackup(Console console) {
|
||||
for (Thing thing : findDevices(SUPPORTED_BRIDGE_THING_TYPES_UIDS)) {
|
||||
MaxCubeBridgeHandler handler = getHandler(thing.getUID().toString());
|
||||
if (handler != null) {
|
||||
handler.backup();
|
||||
console.println(String.format("Creating backup for %s", thing.getUID().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReboot(String[] args, Console console) {
|
||||
if (args.length > 1) {
|
||||
MaxCubeBridgeHandler handler = getHandler(args[1]);
|
||||
if (handler != null) {
|
||||
handler.cubeReboot();
|
||||
} else {
|
||||
console.println(String.format("Could not find MAX! cube %s", args[1]));
|
||||
printMaxDevices(console, SUPPORTED_BRIDGE_THING_TYPES_UIDS);
|
||||
}
|
||||
} else {
|
||||
console.println("Specify MAX! cube to reboot.");
|
||||
printMaxDevices(console, SUPPORTED_BRIDGE_THING_TYPES_UIDS);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Thing> findDevices(Set<ThingTypeUID> deviceTypes) {
|
||||
List<Thing> devs = new ArrayList<>();
|
||||
for (Thing thing : thingRegistry.getAll()) {
|
||||
if (deviceTypes.contains(thing.getThingTypeUID())) {
|
||||
devs.add(thing);
|
||||
}
|
||||
}
|
||||
return devs;
|
||||
}
|
||||
|
||||
private @Nullable MaxCubeBridgeHandler getHandler(String thingId) {
|
||||
MaxCubeBridgeHandler handler = null;
|
||||
try {
|
||||
ThingUID bridgeUID = new ThingUID(thingId);
|
||||
Thing thing = thingRegistry.get(bridgeUID);
|
||||
if ((thing != null) && (thing.getHandler() != null)
|
||||
&& (thing.getHandler() instanceof MaxCubeBridgeHandler)) {
|
||||
handler = (MaxCubeBridgeHandler) thing.getHandler();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handler = null;
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private void printMaxDevices(Console console, Set<ThingTypeUID> deviceTypes) {
|
||||
console.println("Known MAX! devices: ");
|
||||
for (Thing thing : findDevices(deviceTypes)) {
|
||||
console.println(String.format("MAX! %s device: %s%s", thing.getThingTypeUID().getId(),
|
||||
thing.getUID().toString(), ((thing.getHandler() != null) ? "" : " (without handler)")));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(new String[] { buildCommandUsage(SUBCMD_BACKUP, "Backup MAX! cube data"),
|
||||
buildCommandUsage(SUBCMD_REBOOT + " <thingUID>", "Reset MAX! cube") });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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.max.internal;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Utility class for common tasks within the MAX! binding package.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class Utils {
|
||||
|
||||
/**
|
||||
* Returns the integer value of an hexadecimal number (base 16).
|
||||
*
|
||||
* @param hex
|
||||
* the hex value to be parsed into an integer
|
||||
* @return the given hex value as integer
|
||||
*/
|
||||
public static final int fromHex(String hex) {
|
||||
return Integer.parseInt(hex, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hexadecimal number of a number of integer values.
|
||||
*
|
||||
* @param values
|
||||
* the integer values to be converted into hexadecimal numbers
|
||||
* @return the given numbers as hexadecimal number
|
||||
*/
|
||||
public static final String toHex(int... values) {
|
||||
String returnValue = "";
|
||||
for (int v : values) {
|
||||
returnValue += v < 16 ? "0" + Integer.toHexString(v).toUpperCase() : Integer.toHexString(v).toUpperCase();
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hexadecimal number of a bit array .
|
||||
*
|
||||
* @param bits
|
||||
* the boolean array representing the bits to be converted into
|
||||
* hexadecimal numbers
|
||||
* @return the hexadecimal number
|
||||
*/
|
||||
public static final String toHex(boolean[] bits) {
|
||||
int retVal = 0;
|
||||
for (int i = 0; i < bits.length; ++i) {
|
||||
retVal |= (bits[i] ? 1 : 0) << i;
|
||||
}
|
||||
return toHex(retVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Java signed byte into its general (unsigned) value as being
|
||||
* used in other programming languages and platforms.
|
||||
*
|
||||
* @param b
|
||||
* the byte to be converted into its integer value
|
||||
* @return the integer value represented by the given byte
|
||||
*/
|
||||
public static final int fromByte(byte b) {
|
||||
return b & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the date and time based based on a three byte encoded within a
|
||||
* MAX! Cube L message.
|
||||
*
|
||||
* Date decoding (two byte)
|
||||
*
|
||||
* <pre>
|
||||
* Hex Binary
|
||||
* 9D0B 1001110100001011
|
||||
* MMMDDDDDM YYYYYY
|
||||
* 100 0 = 1000b = 8d = month
|
||||
* 11101 = 11101b = 29d = day
|
||||
* 001011 = 1011b = 11d = year-2000
|
||||
* </pre>
|
||||
*
|
||||
* Time decoding (one byte)
|
||||
*
|
||||
* <pre>
|
||||
* Hex Decimal
|
||||
* 1F 31 * 0.5 hours = 15:30
|
||||
* </pre>
|
||||
*
|
||||
* @param date
|
||||
* the date to be converted based on two bytes
|
||||
* @param time
|
||||
* the time to be converted based on a single byte
|
||||
* @return the date time based on the values provided
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static Date resolveDateTime(int date, int time) {
|
||||
int month = ((date & 0xE000) >> 12) + ((date & 0x80) >> 7);
|
||||
int day = (date & 0x1F00) >> 8;
|
||||
int year = (date & 0x0F) + 2000;
|
||||
|
||||
int hours = (int) (time * 0.5);
|
||||
int minutes = (int) (60 * ((time * 0.5) - hours));
|
||||
|
||||
return new Date(year, month, day, hours, minutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bit representation as boolean values in a reversed manner. A
|
||||
* bit string <code>0001 0010</code> would be returnd as
|
||||
* <code>0100 1000</code>. That way, the least significant bit can be
|
||||
* addressed by bits[0], the second by bits[1] and so on.
|
||||
*
|
||||
* @param value
|
||||
* the integer value to be converted in a bit array
|
||||
* @return the bit array of the input value in a reversed manner.
|
||||
*/
|
||||
public static boolean[] getBits(int value) {
|
||||
boolean[] bits = new boolean[8];
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bits[i] = (((value >> i) & 0x1) == 1);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string representation of hexadecimal to a byte array.
|
||||
*
|
||||
* For example: String s = "00010203" returned byte array is {0x00, 0x01,
|
||||
* 0x03}
|
||||
*
|
||||
* @param s
|
||||
* @return byte array equivalent to hex string
|
||||
*/
|
||||
public static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to a string representation of hexadecimals.
|
||||
*
|
||||
* For example: byte array is {0x00, 0x01, 0x03} returned String s =
|
||||
* "00 01 02 03"
|
||||
*
|
||||
* @param byte array
|
||||
* @return String equivalent to hex string
|
||||
*/
|
||||
static final String HEXES = "0123456789ABCDEF";
|
||||
|
||||
public static String getHex(byte[] raw) {
|
||||
final StringBuilder hex = new StringBuilder(3 * raw.length);
|
||||
for (final byte b : raw) {
|
||||
hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))).append(" ");
|
||||
}
|
||||
hex.delete(hex.length() - 1, hex.length());
|
||||
return hex.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.max.internal.actions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.actions.MaxCubeActions;
|
||||
|
||||
/**
|
||||
* The {@link IMaxCubeActions} defines the interface for all thing actions supported by the binding.
|
||||
* These methods, parameters, and return types are explained in {@link MaxCubeActions}.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IMaxCubeActions {
|
||||
|
||||
Boolean backup();
|
||||
|
||||
Boolean resetConfig();
|
||||
|
||||
Boolean reboot();
|
||||
}
|
||||
@@ -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.max.internal.actions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.actions.MaxDevicesActions;
|
||||
|
||||
/**
|
||||
* The {@link IMaxDevicesActions} defines the interface for all thing actions supported by the binding.
|
||||
* These methods, parameters, and return types are explained in {@link MaxDevicesActions}.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IMaxDevicesActions {
|
||||
|
||||
Boolean deleteFromCube();
|
||||
}
|
||||
@@ -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.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ACommand} deletes the device and room configuration from the Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ACommand extends CubeCommand {
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
return "a:" + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "A:";
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link C_CubeCommand} to request configuration of a new MAX! device after inclusion.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CCommand extends CubeCommand {
|
||||
private final String rfAddress;
|
||||
|
||||
public CCommand(String rfAddress) {
|
||||
this.rfAddress = rfAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
String cmd = "c:" + rfAddress + '\r' + '\n';
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "C:";
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link CubeCommand} is the base class for commands to be send to the MAX! Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class CubeCommand {
|
||||
|
||||
/**
|
||||
* @return the String to be send to the MAX! Cube
|
||||
*/
|
||||
public abstract String getCommandString();
|
||||
|
||||
/**
|
||||
* @return the String expected to be received from the Cube to signify the
|
||||
* end of the message
|
||||
*/
|
||||
public abstract String getReturnStrings();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link F_CubeCommand} is used to query and update the NTP servers used by the Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FCommand extends CubeCommand {
|
||||
|
||||
private String ntpServer1 = "";
|
||||
private String ntpServer2 = "";
|
||||
|
||||
/**
|
||||
* Queries the Cube for the NTP info
|
||||
*/
|
||||
public FCommand() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Cube the NTP info
|
||||
*/
|
||||
public FCommand(@Nullable String ntpServer1, @Nullable String ntpServer2) {
|
||||
this.ntpServer1 = ntpServer1 != null ? ntpServer1 : "";
|
||||
this.ntpServer2 = ntpServer2 != null ? ntpServer2 : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final String servers;
|
||||
if (ntpServer1.length() > 0 && ntpServer2.length() > 0) {
|
||||
servers = ntpServer1 + "," + ntpServer2;
|
||||
} else {
|
||||
servers = ntpServer1 + ntpServer2;
|
||||
}
|
||||
return "f:" + servers + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "F:";
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link LCommand} request a status update for MAX! devices.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LCommand extends CubeCommand {
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
return "l:" + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "L:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* 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.max.internal.command;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.RoomInformation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MCommand} Creates the MAX! Cube the room & device name information update message.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MCommand extends CubeCommand {
|
||||
private final Logger logger = LoggerFactory.getLogger(MCommand.class);
|
||||
|
||||
private static final byte MAGIC_NR = 86;
|
||||
private static final byte M_VERSION = 2;
|
||||
private static final int MAX_NAME_LENGTH = 32;
|
||||
private static final int MAX_GROUP_COUNT = 20;
|
||||
private static final int MAX_DEVICES_COUNT = 140;
|
||||
private static final int MAX_MSG_LENGTH = 1900;
|
||||
|
||||
private final List<Device> devices;
|
||||
public final List<RoomInformation> rooms;
|
||||
|
||||
public MCommand(List<Device> devices) {
|
||||
this(devices, new ArrayList<>());
|
||||
}
|
||||
|
||||
public MCommand(List<Device> devices, List<RoomInformation> rooms) {
|
||||
this.devices = new ArrayList<>(devices);
|
||||
this.rooms = new ArrayList<>(rooms);
|
||||
roombuilder();
|
||||
}
|
||||
|
||||
public void listRooms() {
|
||||
for (RoomInformation room : rooms) {
|
||||
logger.debug("M Command room info: {}", room);
|
||||
}
|
||||
}
|
||||
|
||||
public List<RoomInformation> getRooms() {
|
||||
return rooms;
|
||||
}
|
||||
|
||||
private void roombuilder() {
|
||||
for (Device di : devices) {
|
||||
boolean foundRoom = false;
|
||||
|
||||
for (RoomInformation room : rooms) {
|
||||
if (room.getPosition() == di.getRoomId()) {
|
||||
foundRoom = true;
|
||||
}
|
||||
}
|
||||
// Add new rooms based on device information.
|
||||
// TODO check if it is allowed to have any device creating a room, or should it be a thermostat
|
||||
if (!foundRoom && di.getRoomId() != 0 && rooms.size() < MAX_GROUP_COUNT) {
|
||||
RoomInformation room = new RoomInformation(di.getRoomId(), di.getRoomName(), di.getRFAddress());
|
||||
rooms.add(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] concatenateByteArrays(List<byte[]> blocks) {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
for (byte[] b : blocks) {
|
||||
os.write(b, 0, b.length);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
int deviceCount = 0;
|
||||
int roomCount = 0;
|
||||
|
||||
ByteArrayOutputStream message = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
byte[] header = { MAGIC_NR, M_VERSION, (byte) rooms.size() };
|
||||
message.write(header);
|
||||
|
||||
Set<Integer> sortedRooms = new TreeSet<>();
|
||||
for (RoomInformation room : rooms) {
|
||||
sortedRooms.add(room.getPosition());
|
||||
}
|
||||
|
||||
for (Integer roomPos : sortedRooms) {
|
||||
for (RoomInformation room : rooms) {
|
||||
if (room.getPosition() == roomPos) {
|
||||
if (roomCount < MAX_GROUP_COUNT) {
|
||||
byte[] roomName = StringUtils.abbreviate(room.getName(), MAX_NAME_LENGTH)
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] nameLength = new byte[] { (byte) roomName.length };
|
||||
byte[] rfAddress = Utils.hexStringToByteArray(room.getRFAddress());
|
||||
message.write(roomPos.byteValue());
|
||||
message.write(nameLength);
|
||||
message.write(roomName);
|
||||
message.write(rfAddress);
|
||||
} else {
|
||||
logger.warn("{} exceeds max number of rooms ({}). Ignored", room, MAX_GROUP_COUNT);
|
||||
}
|
||||
roomCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Device di : devices) {
|
||||
if (deviceCount < MAX_DEVICES_COUNT) {
|
||||
deviceCount++;
|
||||
} else {
|
||||
logger.warn("{} exceeds max number of devices ({}). Ignored", di, MAX_DEVICES_COUNT);
|
||||
}
|
||||
}
|
||||
message.write((byte) deviceCount);
|
||||
for (Device di : devices) {
|
||||
if (deviceCount > 0) {
|
||||
byte[] deviceType = { (byte) di.getType().getValue() };
|
||||
byte[] rfAddress = Utils.hexStringToByteArray(di.getRFAddress());
|
||||
byte[] deviceName = StringUtils.abbreviate(di.getName(), MAX_NAME_LENGTH)
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] nameLength = { (byte) deviceName.length };
|
||||
byte[] serialNumber = di.getSerialNumber().getBytes(StandardCharsets.UTF_8);
|
||||
byte[] roomId = { (byte) di.getRoomId() };
|
||||
|
||||
message.write(deviceType);
|
||||
message.write(rfAddress);
|
||||
message.write(serialNumber);
|
||||
message.write(nameLength);
|
||||
message.write(deviceName);
|
||||
message.write(roomId);
|
||||
} else {
|
||||
logger.warn("{} exceeds max number of devices ({}). Ignored", di, MAX_DEVICES_COUNT);
|
||||
}
|
||||
deviceCount--;
|
||||
}
|
||||
|
||||
byte[] dst = { 0x01 };
|
||||
message.write(dst);
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error while generating m: command: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
final String encodedString = Base64.getEncoder().encodeToString(message.toByteArray());
|
||||
final StringBuilder commandStringBuilder = new StringBuilder();
|
||||
int parts = (int) Math.round(encodedString.length() / MAX_MSG_LENGTH + 0.5);
|
||||
for (int i = 0; i < parts; i++) {
|
||||
String partString = StringUtils.abbreviate(encodedString.substring((i) * MAX_MSG_LENGTH), MAX_MSG_LENGTH);
|
||||
commandStringBuilder.append("m:").append(String.format("%02d", i)).append(",").append(partString)
|
||||
.append('\r').append('\n');
|
||||
}
|
||||
|
||||
return commandStringBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "A:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link N_CubeCommand} starts the inclusion mode for new MAX! devices.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NCommand extends CubeCommand {
|
||||
// Example n:003c = start inclusion, timeout 003c = 60 sec
|
||||
|
||||
// Timeout for inclusion mode in seconds
|
||||
private static final int DEFAULT_TIMEOUT = 60;
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
return "n:" + String.format("%02x", DEFAULT_TIMEOUT) + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "N:";
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link QCommand} Quits the connection to the MAX! Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QCommand extends CubeCommand {
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
return "q:" + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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.max.internal.command;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.ThermostatModeType;
|
||||
|
||||
/**
|
||||
* {@link SCommand} for setting MAX! thermostat temperature & mode.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update + simplification
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SCommand extends CubeCommand {
|
||||
|
||||
private static final String BASE_STRING_S = "000040000000"; // for single devices
|
||||
private static final String BASE_STRING_G = "000440000000"; // for group/room devices
|
||||
|
||||
private final boolean[] bits;
|
||||
private final String rfAddress;
|
||||
private final int roomId;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the MAX! protocol S command.
|
||||
*
|
||||
* @param rfAddress
|
||||
* the RF address the command is for
|
||||
* @param roomId
|
||||
* the room ID the RF address is mapped to
|
||||
* @param setpointTemperature
|
||||
* the desired setpoint temperature for the device.
|
||||
*/
|
||||
public SCommand(String rfAddress, int roomId, ThermostatModeType mode, double setpointTemperature) {
|
||||
this.rfAddress = rfAddress;
|
||||
this.roomId = roomId;
|
||||
|
||||
// Temperature setpoint, Temp uses 6 bits (bit 0:5),
|
||||
// 20 deg C = bits 101000 = dec 40/2 = 20 deg C,
|
||||
// you need 8 bits to send so add the 2 bits below (sample 10101000 = hex A8)
|
||||
// bit 0,1 = 00 = Auto weekprog (no temp is needed)
|
||||
|
||||
int setpointValue = (int) (setpointTemperature * 2);
|
||||
bits = Utils.getBits(setpointValue);
|
||||
|
||||
// default to perm setting
|
||||
// AB => bit mapping
|
||||
// 01 = Permanent
|
||||
// 10 = Temporarily
|
||||
// 11 = Boost
|
||||
|
||||
switch (mode) {
|
||||
case MANUAL:
|
||||
bits[7] = false; // A (MSB)
|
||||
bits[6] = true; // B
|
||||
break;
|
||||
case AUTOMATIC:
|
||||
bits[7] = false; // A (MSB)
|
||||
bits[6] = false; // B
|
||||
break;
|
||||
case BOOST:
|
||||
bits[7] = true; // A (MSB)
|
||||
bits[6] = true; // B
|
||||
break;
|
||||
case VACATION:
|
||||
// not implemented needs time
|
||||
default:
|
||||
// no further modes supported
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Base64 encoded command string to be sent via the MAX!
|
||||
* protocol.
|
||||
*
|
||||
* @return the string representing the command
|
||||
*/
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final String baseString;
|
||||
if (roomId == 0) {
|
||||
baseString = BASE_STRING_S;
|
||||
} else {
|
||||
baseString = BASE_STRING_G;
|
||||
}
|
||||
|
||||
final String commandString = baseString + rfAddress + Utils.toHex(roomId) + Utils.toHex(bits);
|
||||
|
||||
final String encodedString = Base64.getEncoder().encodeToString(Utils.hexStringToByteArray(commandString));
|
||||
|
||||
return "s:" + encodedString + "\r\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "S:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 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.max.internal.command;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SConfigCommand} for setting MAX! thermostat configuration.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SConfigCommand extends CubeCommand {
|
||||
|
||||
private String baseString = "";
|
||||
private final String rfAddress;
|
||||
private final int roomId;
|
||||
|
||||
private byte[] commandBytes = new byte[0];
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SConfigCommand.class);
|
||||
|
||||
public enum ConfigCommandType {
|
||||
Temperature,
|
||||
Valve,
|
||||
SetRoom,
|
||||
RemoveRoom,
|
||||
ProgramData
|
||||
}
|
||||
|
||||
private final ConfigCommandType configCommandType;
|
||||
|
||||
/**
|
||||
* Creates a base command with rfAddress and roomID.
|
||||
*
|
||||
* @param rfAddress
|
||||
* the RF address the command is for
|
||||
* @param roomId
|
||||
* the room ID the RF address is mapped to
|
||||
* @param configCommandType
|
||||
* the Type of config command to be send
|
||||
*/
|
||||
public SConfigCommand(String rfAddress, int roomId, ConfigCommandType configCommandType) {
|
||||
this.rfAddress = rfAddress;
|
||||
this.roomId = roomId;
|
||||
this.configCommandType = configCommandType;
|
||||
if (configCommandType == ConfigCommandType.Temperature) {
|
||||
setTempConfigDefault();
|
||||
} else if (configCommandType == ConfigCommandType.SetRoom) {
|
||||
baseString = "000022000000";
|
||||
commandBytes = new byte[] { 0 };
|
||||
} else if (configCommandType == ConfigCommandType.RemoveRoom) {
|
||||
baseString = "000023000000";
|
||||
commandBytes = new byte[] { 0 };
|
||||
} else {
|
||||
logger.debug("Config Command {} not implemented", configCommandType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Thermostat temperature configuration
|
||||
*/
|
||||
public SConfigCommand(String rfAddress, int roomId, double tempComfort, double tempEco, double tempSetpointMax,
|
||||
double tempSetpointMin, double tempOffset, double tempOpenWindow, int durationOpenWindow) {
|
||||
this.rfAddress = rfAddress;
|
||||
this.roomId = roomId;
|
||||
this.configCommandType = ConfigCommandType.Temperature;
|
||||
setTempConfig(tempComfort, tempEco, tempSetpointMax, tempSetpointMin, tempOffset, tempOpenWindow,
|
||||
durationOpenWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the thermostat default temperature config.
|
||||
*/
|
||||
public void setTempConfigDefault() {
|
||||
double tempComfort = 21.0; // 0x2a
|
||||
double tempEco = 17.0; // 0x28
|
||||
double tempSetpointMax = 30.0; // 0x3d
|
||||
double tempSetpointMin = 4.5; // 0x09
|
||||
double tempOffset = 3.5; // 0x07
|
||||
double tempOpenWindow = 12.0; // 0x18;
|
||||
int durationOpenWindow = 3; // 0x03;
|
||||
setTempConfig(tempComfort, tempEco, tempSetpointMax, tempSetpointMin, tempOffset, tempOpenWindow,
|
||||
durationOpenWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the thermostat temperature config.
|
||||
*
|
||||
* @param tempComfort
|
||||
* the Comfort temperature
|
||||
* @param tempEco
|
||||
* the ECO temperature
|
||||
* @param tempSetpointMax
|
||||
* the Max temperature
|
||||
* @param tempSetpointMin
|
||||
* the min temperature
|
||||
* @param tempOffset
|
||||
* the offset temperature
|
||||
* @param tempOpenWindow
|
||||
* the window open temperature
|
||||
* @param durationOpenWindow
|
||||
* the window open duration in minutes
|
||||
*/
|
||||
public void setTempConfig(double tempComfort, double tempEco, double tempSetpointMax, double tempSetpointMin,
|
||||
double tempOffset, double tempOpenWindow, int durationOpenWindow) {
|
||||
baseString = "000011000000";
|
||||
|
||||
byte tempComfortByte = (byte) (tempComfort * 2);
|
||||
byte tempEcoByte = (byte) (tempEco * 2);
|
||||
byte tempSetpointMaxByte = (byte) (tempSetpointMax * 2);
|
||||
byte tempSetpointMinByte = (byte) (tempSetpointMin * 2);
|
||||
byte tempOffsetByte = (byte) ((tempOffset + 3.5) * 2);
|
||||
byte tempOpenWindowByte = (byte) (tempOpenWindow * 2);
|
||||
byte durationOpenWindowByte = (byte) (durationOpenWindow / 5);
|
||||
commandBytes = new byte[] { tempComfortByte, tempEcoByte, tempSetpointMaxByte, tempSetpointMinByte,
|
||||
tempOffsetByte, tempOpenWindowByte, durationOpenWindowByte };
|
||||
logger.debug(
|
||||
"Thermostat Config Command: confTemp: {}, ecoTemp: {}, setMax: {}, setMin: {}, offset: {}, windowTemp: {}, windowDur:{}",
|
||||
tempComfort, tempEco, tempSetpointMax, tempSetpointMin, tempOffset, tempOpenWindow, durationOpenWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Base64 encoded command string to be sent via the MAX! Cube.
|
||||
*
|
||||
* @return the string representing the command
|
||||
*/
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final StringBuilder commandConfigString = new StringBuilder();
|
||||
for (byte b : commandBytes) {
|
||||
commandConfigString.append(String.format("%02X", b));
|
||||
}
|
||||
|
||||
String commandString = baseString + rfAddress;
|
||||
if (configCommandType == ConfigCommandType.SetRoom || configCommandType == ConfigCommandType.RemoveRoom) {
|
||||
commandString = commandString + commandConfigString + Utils.toHex(roomId);
|
||||
} else {
|
||||
commandString = commandString + Utils.toHex(roomId) + commandConfigString;
|
||||
}
|
||||
|
||||
String encodedString = Base64.getEncoder().encodeToString(Utils.hexStringToByteArray(commandString));
|
||||
return "s:" + encodedString + "\r\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "S:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.max.internal.command;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* The {@link T_CubeCommand} is used to unlink MAX! devices from the Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TCommand extends CubeCommand {
|
||||
|
||||
private static final int FORCE_UPDATE = 1;
|
||||
private static final int NO_FORCE_UPDATE = 0;
|
||||
|
||||
private final List<String> rfAddresses = new ArrayList<>();
|
||||
private final boolean forceUpdate;
|
||||
|
||||
public TCommand(String rfAddress, boolean forceUpdate) {
|
||||
this.rfAddresses.add(rfAddress);
|
||||
this.forceUpdate = forceUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rooms for deletion
|
||||
*/
|
||||
public void addRoom(String rfAddress) {
|
||||
this.rfAddresses.add(rfAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final int updateForced = forceUpdate ? FORCE_UPDATE : NO_FORCE_UPDATE;
|
||||
byte[] commandArray = null;
|
||||
for (String rfAddress : rfAddresses) {
|
||||
commandArray = ArrayUtils.addAll(Utils.hexStringToByteArray(rfAddress), commandArray);
|
||||
}
|
||||
String encodedString = Base64.getEncoder().encodeToString(commandArray);
|
||||
|
||||
return "t:" + String.format("%02d", rfAddresses.size()) + "," + updateForced + "," + encodedString + "\r\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "A:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* 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.max.internal.command;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link UdpCubeCommand} is responsible for sending UDP commands to the MAX!
|
||||
* Cube LAN gateway.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UdpCubeCommand {
|
||||
|
||||
private static final String MAXCUBE_COMMAND_STRING = "eQ3Max*\0";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(UdpCubeCommand.class);
|
||||
|
||||
protected static boolean commandRunning;
|
||||
|
||||
private final UdpCommandType commandType;
|
||||
private final String serialNumber;
|
||||
private Map<String, String> commandResponse = new HashMap<>();
|
||||
@Nullable
|
||||
private String ipAddress;
|
||||
|
||||
public UdpCubeCommand(UdpCommandType commandType, @Nullable String serialNumber) {
|
||||
this.commandType = commandType;
|
||||
if (serialNumber == null || serialNumber.isEmpty()) {
|
||||
this.serialNumber = "**********";
|
||||
} else {
|
||||
this.serialNumber = serialNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UDP command types
|
||||
* REBOOT - R Reboot
|
||||
* DISCOVERY - I Identify
|
||||
* NETWORK - N Get network address
|
||||
* URL - h get URL information
|
||||
* DEFAULTNET - c get network default info
|
||||
*/
|
||||
public enum UdpCommandType {
|
||||
REBOOT,
|
||||
DISCOVERY,
|
||||
NETWORK,
|
||||
URL,
|
||||
DEFAULTNET
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the composed {@link UdpCubeCommand} command
|
||||
*/
|
||||
public synchronized boolean send() {
|
||||
String commandString;
|
||||
if (commandType.equals(UdpCommandType.REBOOT)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "R";
|
||||
} else if (commandType.equals(UdpCommandType.DISCOVERY)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "I";
|
||||
} else if (commandType.equals(UdpCommandType.NETWORK)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "N";
|
||||
} else if (commandType.equals(UdpCommandType.URL)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "h";
|
||||
} else if (commandType.equals(UdpCommandType.DEFAULTNET)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "c";
|
||||
} else {
|
||||
logger.debug("Unknown Command {}", commandType);
|
||||
return false;
|
||||
}
|
||||
commandResponse.clear();
|
||||
logger.debug("Send {} command to MAX! Cube {}", commandType, serialNumber);
|
||||
sendUdpCommand(commandString, ipAddress);
|
||||
logger.trace("Done sending command.");
|
||||
receiveUdpCommandResponse();
|
||||
logger.debug("Done receiving response.");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void receiveUdpCommandResponse() {
|
||||
commandRunning = true;
|
||||
|
||||
try (DatagramSocket bcReceipt = new DatagramSocket(23272)) {
|
||||
bcReceipt.setReuseAddress(true);
|
||||
bcReceipt.setSoTimeout(5000);
|
||||
|
||||
while (commandRunning) {
|
||||
// Wait for a response
|
||||
byte[] recvBuf = new byte[1500];
|
||||
DatagramPacket receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
|
||||
bcReceipt.receive(receivePacket);
|
||||
|
||||
// We have a response
|
||||
String message = new String(receivePacket.getData(), receivePacket.getOffset(),
|
||||
receivePacket.getLength(), StandardCharsets.UTF_8);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Broadcast response from {} : {} '{}'", receivePacket.getAddress(), message.length(),
|
||||
message);
|
||||
}
|
||||
|
||||
// Check if the message is correct
|
||||
if (message.startsWith("eQ3Max") && !message.equals(MAXCUBE_COMMAND_STRING)) {
|
||||
commandResponse.put("maxCubeIP", receivePacket.getAddress().getHostAddress().toString());
|
||||
commandResponse.put("maxCubeState", message.substring(0, 8));
|
||||
commandResponse.put("serialNumber", message.substring(8, 18));
|
||||
commandResponse.put("msgValidid", message.substring(18, 19));
|
||||
String requestType = message.substring(19, 20);
|
||||
commandResponse.put("requestType", requestType);
|
||||
|
||||
if (requestType.equals("I")) {
|
||||
commandResponse.put("rfAddress",
|
||||
Utils.getHex(message.substring(21, 24).getBytes(StandardCharsets.UTF_8))
|
||||
.replace(" ", "").toLowerCase());
|
||||
commandResponse.put("firmwareVersion", Utils
|
||||
.getHex(message.substring(24, 26).getBytes(StandardCharsets.UTF_8)).replace(" ", "."));
|
||||
} else {
|
||||
// TODO: Further parsing of the other message types
|
||||
commandResponse.put("messageResponse",
|
||||
Utils.getHex(message.substring(20).getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
commandRunning = false;
|
||||
if (logger.isDebugEnabled()) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final Map.Entry<String, String> entry : commandResponse.entrySet()) {
|
||||
builder.append(String.format("%s: %s\n", entry.getKey(), entry.getValue()));
|
||||
}
|
||||
logger.debug("MAX! UDP response {}", builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
logger.trace("No further response");
|
||||
commandRunning = false;
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube response: {}", e.getMessage());
|
||||
commandRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send broadcast message over all active interfaces
|
||||
*
|
||||
* @param commandString string to be used for the discovery
|
||||
* @param ipAddress IP address of the MAX! Cube
|
||||
*
|
||||
*/
|
||||
private void sendUdpCommand(String commandString, @Nullable String ipAddress) {
|
||||
DatagramSocket bcSend = null;
|
||||
try {
|
||||
bcSend = new DatagramSocket();
|
||||
bcSend.setBroadcast(true);
|
||||
|
||||
byte[] sendData = commandString.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// Broadcast the message over all the network interfaces
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
|
||||
continue;
|
||||
}
|
||||
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
|
||||
InetAddress[] broadcast = new InetAddress[3];
|
||||
if (ipAddress != null && !ipAddress.isEmpty()) {
|
||||
broadcast[0] = InetAddress.getByName(ipAddress);
|
||||
} else {
|
||||
broadcast[0] = InetAddress.getByName("224.0.0.1");
|
||||
broadcast[1] = InetAddress.getByName("255.255.255.255");
|
||||
broadcast[2] = interfaceAddress.getBroadcast();
|
||||
}
|
||||
for (InetAddress bc : broadcast) {
|
||||
// Send the broadcast package!
|
||||
if (bc != null) {
|
||||
try {
|
||||
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, bc, 23272);
|
||||
bcSend.send(sendPacket);
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube UDP command sending: {}", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
logger.debug("{}", e.getMessage(), e);
|
||||
}
|
||||
logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
|
||||
networkInterface.getDisplayName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.trace("Done looping over all network interfaces. Now waiting for a reply!");
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube UDP command sending: {}", e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
if (bcSend != null) {
|
||||
bcSend.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IP address to send the command to. The command will be send to the address and broadcasted over all
|
||||
* active interfaces
|
||||
*
|
||||
* @param ipAddress IP address of the MAX! Cube
|
||||
*
|
||||
*/
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of the MAX! Cube on the
|
||||
*
|
||||
*/
|
||||
public Map<String, String> getCommandResponse() {
|
||||
return commandResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* The {@link ZCommand} send a wakeup request to MAX! devices.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZCommand extends CubeCommand {
|
||||
|
||||
public enum WakeUpType {
|
||||
ALL,
|
||||
ROOM,
|
||||
DEVICE
|
||||
}
|
||||
|
||||
private static final int DEFAULT_WAKETIME = 30;
|
||||
|
||||
private final String address;
|
||||
private final WakeUpType wakeUpType;
|
||||
private final int wakeUpTime;
|
||||
|
||||
public ZCommand(WakeUpType wakeUpType, String address, int wakeupTime) {
|
||||
this.address = address;
|
||||
this.wakeUpType = wakeUpType;
|
||||
this.wakeUpTime = wakeupTime;
|
||||
}
|
||||
|
||||
public static ZCommand wakeupRoom(int roomId) {
|
||||
return new ZCommand(WakeUpType.ROOM, String.format("%02d", roomId), DEFAULT_WAKETIME);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupRoom(int roomId, int wakeupTime) {
|
||||
return new ZCommand(WakeUpType.ROOM, String.format("%02d", roomId), wakeupTime);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupDevice(String rfAddress) {
|
||||
return new ZCommand(WakeUpType.DEVICE, rfAddress, DEFAULT_WAKETIME);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupDevice(String rfAddress, int wakeupTime) {
|
||||
return new ZCommand(WakeUpType.DEVICE, rfAddress, wakeupTime);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupAllDevices() {
|
||||
return new ZCommand(WakeUpType.ALL, "0", DEFAULT_WAKETIME);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupAllDevices(int wakeupTime) {
|
||||
return new ZCommand(WakeUpType.ALL, "0", wakeupTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final String commandString;
|
||||
switch (wakeUpType) {
|
||||
case ALL:
|
||||
commandString = "A";
|
||||
break;
|
||||
case ROOM:
|
||||
commandString = "G," + address;
|
||||
break;
|
||||
case DEVICE:
|
||||
commandString = "D," + address;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown wakeup type: " + wakeUpType);
|
||||
}
|
||||
|
||||
return "z:" + Utils.toHex(wakeUpTime) + "," + commandString + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "A:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.config;
|
||||
|
||||
import org.openhab.binding.max.internal.MaxBindingConstants;
|
||||
|
||||
/**
|
||||
* Configuration class for {@link MaxBindingConstants} bridge used to connect to the
|
||||
* maxCube device.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
public class MaxCubeBridgeConfiguration {
|
||||
|
||||
/** The IP address of the MAX! Cube LAN gateway */
|
||||
public String ipAddress;
|
||||
|
||||
/**
|
||||
* The port of the MAX! Cube LAN gateway as provided at
|
||||
* http://www.elv.de/controller.aspx?cid=824&detail=10&detail2=3484
|
||||
*/
|
||||
public Integer port;
|
||||
|
||||
/** The refresh interval in seconds which is used to poll given MAX! Cube */
|
||||
public Integer refreshInterval;
|
||||
|
||||
/** The unique serial number for a device */
|
||||
public String serialNumber;
|
||||
|
||||
/**
|
||||
* If set to true, the binding will leave the connection to the cube open
|
||||
* and just request new informations. This allows much higher poll rates and
|
||||
* causes less load than the non-exclusive polling but has the drawback that
|
||||
* no other apps (i.E. original software) can use the cube while this
|
||||
* binding is running.
|
||||
*/
|
||||
public boolean exclusive = false;
|
||||
|
||||
/**
|
||||
* in exclusive mode, how many requests are allowed until connection is
|
||||
* closed and reopened
|
||||
*/
|
||||
public Integer maxRequestsPerConnection;
|
||||
|
||||
public Integer cubeReboot;
|
||||
|
||||
/** NTP Server 1 hostname */
|
||||
public String ntpServer1;
|
||||
|
||||
/** NTP Server 2 hostname */
|
||||
public String ntpServer2;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
/**
|
||||
* Cube Lan Gateway.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
public class Cube extends Device {
|
||||
|
||||
private boolean portalEnabled;
|
||||
private String portalUrl;
|
||||
private String timeZoneWinter;
|
||||
private String timeZoneDaylightSaving;
|
||||
|
||||
public Cube(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.Cube;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MAX! Cube Lan interface";
|
||||
}
|
||||
|
||||
public boolean isPortalEnabled() {
|
||||
return portalEnabled;
|
||||
}
|
||||
|
||||
public void setPortalEnabled(boolean portalEnabled) {
|
||||
this.portalEnabled = portalEnabled;
|
||||
}
|
||||
|
||||
public String getPortalUrl() {
|
||||
return portalUrl;
|
||||
}
|
||||
|
||||
public void setPortalUrl(String portalUrl) {
|
||||
this.portalUrl = portalUrl;
|
||||
}
|
||||
|
||||
public String getTimeZoneWinter() {
|
||||
return timeZoneWinter;
|
||||
}
|
||||
|
||||
public void setTimeZoneWinter(String timeZoneWinter) {
|
||||
this.timeZoneWinter = timeZoneWinter;
|
||||
}
|
||||
|
||||
public String getTimeZoneDaylightSaving() {
|
||||
return timeZoneDaylightSaving;
|
||||
}
|
||||
|
||||
public void setTimeZoneDaylightSaving(String timeZoneDaylightSaving) {
|
||||
this.timeZoneDaylightSaving = timeZoneDaylightSaving;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* 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.max.internal.device;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class for devices provided by the MAX! protocol.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update + enhancements
|
||||
*/
|
||||
public abstract class Device {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Device.class);
|
||||
|
||||
private final String serialNumber;
|
||||
private String rfAddress;
|
||||
private int roomId;
|
||||
private String roomName;
|
||||
private String name;
|
||||
|
||||
private boolean updated;
|
||||
private boolean batteryLow;
|
||||
|
||||
private boolean initialized;
|
||||
private boolean answer;
|
||||
private boolean error;
|
||||
private boolean valid;
|
||||
private boolean dstSettingsActive;
|
||||
private boolean gatewayKnown;
|
||||
private boolean panelLocked;
|
||||
private boolean linkStatusError;
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
public Device(DeviceConfiguration c) {
|
||||
this.serialNumber = c.getSerialNumber();
|
||||
this.rfAddress = c.getRFAddress();
|
||||
this.roomId = c.getRoomId();
|
||||
this.roomName = c.getRoomName();
|
||||
this.name = c.getName();
|
||||
this.setProperties(new HashMap<>(c.getProperties()));
|
||||
}
|
||||
|
||||
public abstract DeviceType getType();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static Device create(String rfAddress, List<DeviceConfiguration> configurations) {
|
||||
Device returnValue = null;
|
||||
for (DeviceConfiguration c : configurations) {
|
||||
if (c.getRFAddress().toUpperCase().equals(rfAddress.toUpperCase())) {
|
||||
return create(c);
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new device
|
||||
*
|
||||
* @param DeviceConfiguration
|
||||
* @return Device
|
||||
*/
|
||||
public static Device create(DeviceConfiguration c) {
|
||||
{
|
||||
switch (c.getDeviceType()) {
|
||||
case HeatingThermostatPlus:
|
||||
case HeatingThermostat:
|
||||
HeatingThermostat thermostat = new HeatingThermostat(c);
|
||||
thermostat.setType(c.getDeviceType());
|
||||
return thermostat;
|
||||
case EcoSwitch:
|
||||
return new EcoSwitch(c);
|
||||
case ShutterContact:
|
||||
return new ShutterContact(c);
|
||||
case WallMountedThermostat:
|
||||
return new WallMountedThermostat(c);
|
||||
case Cube:
|
||||
return new Cube(c);
|
||||
default:
|
||||
return new UnsupportedDevice(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Device create(byte[] raw, List<DeviceConfiguration> configurations) {
|
||||
if (raw.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String rfAddress = Utils.toHex(raw[0] & 0xFF, raw[1] & 0xFF, raw[2] & 0xFF);
|
||||
|
||||
// Based on the RF address and the corresponding configuration,
|
||||
// create the device based on the type specified in it's configuration
|
||||
|
||||
Device device = Device.create(rfAddress, configurations);
|
||||
if (device == null) {
|
||||
LOGGER.debug("Can't create device from received message, returning 'null'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Device.update(raw, configurations, device);
|
||||
}
|
||||
|
||||
public static Device update(byte[] raw, List<DeviceConfiguration> configurations, Device device) {
|
||||
String rfAddress = device.getRFAddress();
|
||||
|
||||
// byte 4 is skipped
|
||||
|
||||
// multiple device information are encoded in those particular bytes
|
||||
boolean[] bits1 = Utils.getBits(Utils.fromByte(raw[4]));
|
||||
boolean[] bits2 = Utils.getBits(Utils.fromByte(raw[5]));
|
||||
|
||||
device.setInitialized(bits1[1]);
|
||||
device.setAnswer(bits1[2]);
|
||||
device.setError(bits1[3]);
|
||||
device.setValid(bits1[4]);
|
||||
|
||||
device.setDstSettingActive(bits2[3]);
|
||||
device.setGatewayKnown(bits2[4]);
|
||||
device.setPanelLocked(bits2[5]);
|
||||
device.setLinkStatusError(bits2[6]);
|
||||
device.setBatteryLow(bits2[7]);
|
||||
|
||||
LOGGER.trace("Device {} ({}): L Message length: {} content: {}", rfAddress, device.getType(), raw.length,
|
||||
Utils.getHex(raw));
|
||||
|
||||
// TODO move the device specific readings into the sub classes
|
||||
switch (device.getType()) {
|
||||
case WallMountedThermostat:
|
||||
case HeatingThermostat:
|
||||
case HeatingThermostatPlus:
|
||||
HeatingThermostat heatingThermostat = (HeatingThermostat) device;
|
||||
// "xxxx xx00 = automatic, xxxx xx01 = manual, xxxx xx10 = vacation, xxxx xx11 = boost":
|
||||
if (!bits2[1] && !bits2[0]) {
|
||||
heatingThermostat.setMode(ThermostatModeType.AUTOMATIC);
|
||||
} else if (!bits2[1] && bits2[0]) {
|
||||
heatingThermostat.setMode(ThermostatModeType.MANUAL);
|
||||
} else if (bits2[1] && !bits2[0]) {
|
||||
heatingThermostat.setMode(ThermostatModeType.VACATION);
|
||||
} else if (bits2[1] && bits2[0]) {
|
||||
heatingThermostat.setMode(ThermostatModeType.BOOST);
|
||||
} else {
|
||||
LOGGER.debug("Device {} ({}): Unknown mode", rfAddress, device.getType());
|
||||
}
|
||||
|
||||
heatingThermostat.setValvePosition(raw[6] & 0xFF);
|
||||
heatingThermostat.setTemperatureSetpoint(raw[7] & 0x7F);
|
||||
|
||||
// 9 2 858B Date until (05-09-2011) (see Encoding/Decoding
|
||||
// date/time)
|
||||
// B 1 2E Time until (23:00) (see Encoding/Decoding date/time)
|
||||
String hexDate = Utils.toHex(raw[8] & 0xFF, raw[9] & 0xFF);
|
||||
int dateValue = Utils.fromHex(hexDate);
|
||||
int timeValue = raw[10] & 0xFF;
|
||||
Date date = Utils.resolveDateTime(dateValue, timeValue);
|
||||
heatingThermostat.setDateSetpoint(date);
|
||||
|
||||
int actualTemp = 0;
|
||||
if (device.getType() == DeviceType.WallMountedThermostat) {
|
||||
actualTemp = (raw[11] & 0xFF) + (raw[7] & 0x80) * 2;
|
||||
|
||||
} else {
|
||||
if (heatingThermostat.getMode() != ThermostatModeType.VACATION
|
||||
&& heatingThermostat.getMode() != ThermostatModeType.BOOST) {
|
||||
actualTemp = (raw[8] & 0xFF) * 256 + (raw[9] & 0xFF);
|
||||
} else {
|
||||
LOGGER.debug("Device {} ({}): No temperature reading in {} mode", rfAddress, device.getType(),
|
||||
heatingThermostat.getMode());
|
||||
}
|
||||
}
|
||||
LOGGER.debug("Device {} ({}): Actual Temperature : {}", rfAddress, device.getType(),
|
||||
(double) actualTemp / 10);
|
||||
heatingThermostat.setTemperatureActual((double) actualTemp / 10);
|
||||
break;
|
||||
case EcoSwitch:
|
||||
String eCoSwitchData = Utils.toHex(raw[3] & 0xFF, raw[4] & 0xFF, raw[5] & 0xFF);
|
||||
LOGGER.trace("Device {} ({}): Status bytes : {}", rfAddress, device.getType(), eCoSwitchData);
|
||||
EcoSwitch ecoswitch = (EcoSwitch) device;
|
||||
// xxxx xx10 = shutter open, xxxx xx00 = shutter closed
|
||||
if (bits2[1] && !bits2[0]) {
|
||||
ecoswitch.setEcoMode(OnOffType.ON);
|
||||
LOGGER.trace("Device {} ({}): status: ON", rfAddress, device.getType());
|
||||
} else if (!bits2[1] && !bits2[0]) {
|
||||
ecoswitch.setEcoMode(OnOffType.OFF);
|
||||
LOGGER.trace("Device {} ({}): Status: OFF", rfAddress, device.getType());
|
||||
} else {
|
||||
LOGGER.trace("Device {} ({}): Status switch status Unknown (true-true)", rfAddress,
|
||||
device.getType());
|
||||
}
|
||||
break;
|
||||
case ShutterContact:
|
||||
ShutterContact shutterContact = (ShutterContact) device;
|
||||
// xxxx xx10 = shutter open, xxxx xx00 = shutter closed
|
||||
if (bits2[1] && !bits2[0]) {
|
||||
shutterContact.setShutterState(OpenClosedType.OPEN);
|
||||
LOGGER.debug("Device {} ({}): Status: Open", rfAddress, device.getType());
|
||||
} else if (!bits2[1] && !bits2[0]) {
|
||||
shutterContact.setShutterState(OpenClosedType.CLOSED);
|
||||
LOGGER.debug("Device {} ({}): Status: Closed", rfAddress, device.getType());
|
||||
} else {
|
||||
LOGGER.trace("Device {} ({}): Status switch status Unknown (true-true)", rfAddress,
|
||||
device.getType());
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
LOGGER.debug("Unhandled Device. DataBytes: {}", Utils.getHex(raw));
|
||||
break;
|
||||
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
private final void setBatteryLow(boolean batteryLow) {
|
||||
if (this.batteryLow != batteryLow) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.batteryLow = batteryLow;
|
||||
}
|
||||
|
||||
public final OnOffType getBatteryLow() {
|
||||
return (this.batteryLow ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
|
||||
public final String getRFAddress() {
|
||||
return this.rfAddress;
|
||||
}
|
||||
|
||||
public final void setRFAddress(String rfAddress) {
|
||||
this.rfAddress = rfAddress;
|
||||
}
|
||||
|
||||
public final int getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public final void setRoomId(int roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public final String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
public final void setRoomName(String roomName) {
|
||||
this.roomName = roomName;
|
||||
}
|
||||
|
||||
private void setLinkStatusError(boolean linkStatusError) {
|
||||
if (this.linkStatusError != linkStatusError) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.linkStatusError = linkStatusError;
|
||||
}
|
||||
|
||||
private void setPanelLocked(boolean panelLocked) {
|
||||
if (this.panelLocked != panelLocked) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.panelLocked = panelLocked;
|
||||
}
|
||||
|
||||
private void setGatewayKnown(boolean gatewayKnown) {
|
||||
if (this.gatewayKnown != gatewayKnown) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.gatewayKnown = gatewayKnown;
|
||||
}
|
||||
|
||||
private void setDstSettingActive(boolean dstSettingsActive) {
|
||||
if (this.dstSettingsActive != dstSettingsActive) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.dstSettingsActive = dstSettingsActive;
|
||||
}
|
||||
|
||||
public boolean isDstSettingsActive() {
|
||||
return dstSettingsActive;
|
||||
}
|
||||
|
||||
private void setValid(boolean valid) {
|
||||
if (this.valid != valid) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.valid = valid;
|
||||
}
|
||||
|
||||
public void setError(boolean error) {
|
||||
if (this.error != error) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
private void setInitialized(boolean initialized) {
|
||||
if (this.initialized != initialized) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.initialized = initialized;
|
||||
}
|
||||
|
||||
private void setAnswer(boolean answer) {
|
||||
if (this.answer != answer) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.answer = answer;
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public void setUpdated(boolean updated) {
|
||||
this.updated = updated;
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
public boolean isAnswer() {
|
||||
return answer;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public boolean isGatewayKnown() {
|
||||
return gatewayKnown;
|
||||
}
|
||||
|
||||
public boolean isPanelLocked() {
|
||||
return panelLocked;
|
||||
}
|
||||
|
||||
public boolean isLinkStatusError() {
|
||||
return linkStatusError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the properties
|
||||
*/
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param properties the properties to set
|
||||
*/
|
||||
public void setProperties(Map<String, Object> properties) {
|
||||
this.properties = new HashMap<>(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getType().toString() + " (" + rfAddress + ") '" + this.getName() + "'";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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.max.internal.device;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.binding.max.internal.message.CMessage;
|
||||
import org.openhab.binding.max.internal.message.Message;
|
||||
|
||||
/**
|
||||
* Base class for configuration provided by the MAX! Cube C Message.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
public final class DeviceConfiguration {
|
||||
|
||||
private DeviceType deviceType;
|
||||
private String rfAddress;
|
||||
private String serialNumber;
|
||||
private String name;
|
||||
private int roomId = -1;
|
||||
private String roomName;
|
||||
|
||||
/** Extended configuration properties **/
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
private DeviceConfiguration() {
|
||||
}
|
||||
|
||||
public static DeviceConfiguration create(Message message) {
|
||||
final DeviceConfiguration configuration = new DeviceConfiguration();
|
||||
configuration.setValues((CMessage) message);
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public static DeviceConfiguration create(DeviceInformation di) {
|
||||
DeviceConfiguration configuration = new DeviceConfiguration();
|
||||
configuration.setValues(di.getRFAddress(), di.getDeviceType(), di.getSerialNumber(), di.getRoomId(),
|
||||
di.getName());
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void setValues(CMessage message) {
|
||||
setValues(message.getRFAddress(), message.getDeviceType(), message.getSerialNumber(), message.getRoomID());
|
||||
properties = new HashMap<>(message.getProperties());
|
||||
}
|
||||
|
||||
private void setValues(String rfAddress, DeviceType deviceType, String serialNumber, int roomId, String name) {
|
||||
setValues(rfAddress, deviceType, serialNumber, roomId);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
private void setValues(String rfAddress, DeviceType deviceType, String serialNumber, int roomId) {
|
||||
this.rfAddress = rfAddress;
|
||||
this.deviceType = deviceType;
|
||||
this.serialNumber = serialNumber;
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public DeviceType getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (name == null) {
|
||||
return "";
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public void setRoomId(int roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public String getRoomName() {
|
||||
if (roomName == null) {
|
||||
return "";
|
||||
} else {
|
||||
return roomName;
|
||||
}
|
||||
}
|
||||
|
||||
public void setRoomName(String roomName) {
|
||||
this.roomName = roomName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the properties
|
||||
*/
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param properties the properties to set
|
||||
*/
|
||||
public void setProperties(HashMap<String, Object> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.device;
|
||||
|
||||
/**
|
||||
* Device information provided by the M message meta information.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
public class DeviceInformation {
|
||||
|
||||
private final DeviceType deviceType;
|
||||
private final String serialNumber;
|
||||
private final String rfAddress;
|
||||
private final String name;
|
||||
private final int roomId;
|
||||
|
||||
public DeviceInformation(DeviceType deviceType, String serialNumber, String rfAddress, String name, int roomId) {
|
||||
this.deviceType = deviceType;
|
||||
this.serialNumber = serialNumber;
|
||||
this.rfAddress = rfAddress;
|
||||
this.name = name;
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public DeviceType getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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.max.internal.device;
|
||||
|
||||
/**
|
||||
* This enumeration represents the different message types provided by the MAX! Cube protocol.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
public enum DeviceType {
|
||||
Invalid(256),
|
||||
Cube(0),
|
||||
HeatingThermostat(1),
|
||||
HeatingThermostatPlus(2),
|
||||
WallMountedThermostat(3),
|
||||
ShutterContact(4),
|
||||
EcoSwitch(5);
|
||||
|
||||
private final int value;
|
||||
|
||||
private DeviceType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static DeviceType create(int value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return Cube;
|
||||
case 1:
|
||||
return HeatingThermostat;
|
||||
case 2:
|
||||
return HeatingThermostatPlus;
|
||||
case 3:
|
||||
return WallMountedThermostat;
|
||||
case 4:
|
||||
return ShutterContact;
|
||||
case 5:
|
||||
return EcoSwitch;
|
||||
default:
|
||||
return Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return "Cube";
|
||||
case 1:
|
||||
return "Thermostat";
|
||||
case 2:
|
||||
return "Thermostat+";
|
||||
case 3:
|
||||
return "Wallmounted Thermostat";
|
||||
case 4:
|
||||
return "Shutter Contact";
|
||||
case 5:
|
||||
return "Eco Switch";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.device;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
/**
|
||||
* MAX! EcoSwitch.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
public class EcoSwitch extends ShutterContact {
|
||||
|
||||
private OnOffType ecoMode;
|
||||
|
||||
public EcoSwitch(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.EcoSwitch;
|
||||
}
|
||||
|
||||
public OnOffType getEcoMode() {
|
||||
return ecoMode;
|
||||
}
|
||||
|
||||
public void setEcoMode(OnOffType ecoMode) {
|
||||
this.ecoMode = ecoMode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 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.max.internal.device;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MAX! Heating thermostat & Heating thermostat+ .
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*/
|
||||
public class HeatingThermostat extends Device {
|
||||
private ThermostatModeType mode;
|
||||
|
||||
/** Valve position in % */
|
||||
private int valvePosition;
|
||||
|
||||
/** Temperature setpoint in degrees celcius */
|
||||
private double temperatureSetpoint;
|
||||
|
||||
/** Actual Temperature in degrees celcius */
|
||||
private double temperatureActual;
|
||||
|
||||
/** Date setpoint until the temperature setpoint is valid */
|
||||
private Date dateSetpoint;
|
||||
|
||||
/** Device type for this thermostat **/
|
||||
private DeviceType deviceType = DeviceType.HeatingThermostat;
|
||||
|
||||
/** Date/Time the actual temperature was last updated */
|
||||
private Date actualTempLastUpdated;
|
||||
|
||||
public HeatingThermostat(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the DeviceType for this thermostat.
|
||||
*
|
||||
* @param DeviceType as provided by the C message
|
||||
*/
|
||||
void setType(DeviceType type) {
|
||||
this.deviceType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current mode of the thermostat.
|
||||
*/
|
||||
public String getModeString() {
|
||||
return this.mode.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current mode of the thermostat.
|
||||
*/
|
||||
public ThermostatModeType getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
public void setMode(ThermostatModeType mode) {
|
||||
if (this.mode != mode) {
|
||||
setUpdated(true);
|
||||
}
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the valve position for this thermostat.
|
||||
*
|
||||
* @param valvePosition the valve position as provided by the L message
|
||||
*/
|
||||
public void setValvePosition(int valvePosition) {
|
||||
if (this.valvePosition != valvePosition) {
|
||||
setUpdated(true);
|
||||
}
|
||||
this.valvePosition = valvePosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current valve position of this thermostat in percent.
|
||||
*
|
||||
* @return
|
||||
* the valve position as <code>DecimalType</code>
|
||||
*/
|
||||
public int getValvePosition() {
|
||||
return this.valvePosition;
|
||||
}
|
||||
|
||||
public void setDateSetpoint(Date date) {
|
||||
this.dateSetpoint = date;
|
||||
}
|
||||
|
||||
public Date getDateSetpoint() {
|
||||
return dateSetpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the actual temperature for this thermostat.
|
||||
*
|
||||
* @param value the actual temperature raw value as provided by the L message
|
||||
*/
|
||||
public void setTemperatureActual(double value) {
|
||||
if (this.temperatureActual != value) {
|
||||
setUpdated(true);
|
||||
this.actualTempLastUpdated = Calendar.getInstance().getTime();
|
||||
}
|
||||
this.temperatureActual = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the measured temperature of this thermostat.
|
||||
* 0<>C is displayed if no actual is measured. Temperature is only updated after valve position changes
|
||||
*
|
||||
* @return
|
||||
* the actual temperature as <code>QuantityType</code>
|
||||
*/
|
||||
public double getTemperatureActual() {
|
||||
return BigDecimal.valueOf(this.temperatureActual).setScale(1, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setpoint temperature for this thermostat.
|
||||
*
|
||||
* @param value the setpoint temperature raw value as provided by the L message
|
||||
*/
|
||||
public void setTemperatureSetpoint(int value) {
|
||||
if (Math.abs(this.temperatureSetpoint - (value / 2.0)) > 0.1) {
|
||||
setUpdated(true);
|
||||
}
|
||||
this.temperatureSetpoint = value / 2.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setpoint temperature of this thermostat.
|
||||
* 4.5<EFBFBD>C is displayed as OFF, 30.5<EFBFBD>C is displayed as On at the thermostat display.
|
||||
*
|
||||
* @return
|
||||
* the setpoint temperature as <code>QuantityType</code>
|
||||
*/
|
||||
public double getTemperatureSetpoint() {
|
||||
return this.temperatureSetpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Date the actual Temperature was last Updated
|
||||
*/
|
||||
public Date getActualTempLastUpdated() {
|
||||
return actualTempLastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param actualTempLastUpdated the Date the actual Temperature was last Updated
|
||||
*/
|
||||
public void setActualTempLastUpdated(Date actualTempLastUpdated) {
|
||||
this.actualTempLastUpdated = actualTempLastUpdated;
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.device;
|
||||
|
||||
/**
|
||||
* Room information provided by the M message meta information.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen (marcel@verpaalen.com) - OH2 update
|
||||
*/
|
||||
public class RoomInformation {
|
||||
private int position;
|
||||
private String name;
|
||||
private String rfAddress;
|
||||
|
||||
public RoomInformation(int position, String name, String rfAddress) {
|
||||
this.position = position;
|
||||
this.name = name;
|
||||
this.rfAddress = rfAddress;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public void setRFAddress(String rfAddress) {
|
||||
this.rfAddress = rfAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Room " + position + " (" + rfAddress + ") ='" + name + "'";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
|
||||
/**
|
||||
* MAX! Shutter contact device.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*/
|
||||
public class ShutterContact extends Device {
|
||||
|
||||
private OpenClosedType shutterState;
|
||||
|
||||
public ShutterContact(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public void setShutterState(OpenClosedType shutterState) {
|
||||
if (this.shutterState != shutterState) {
|
||||
setUpdated(true);
|
||||
}
|
||||
this.shutterState = shutterState;
|
||||
}
|
||||
|
||||
public OpenClosedType getShutterState() {
|
||||
return shutterState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.ShutterContact;
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.device;
|
||||
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.PrimitiveType;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* This enumeration represents the different mode types of a MAX! heating thermostat.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*/
|
||||
public enum ThermostatModeType implements PrimitiveType, State, Command {
|
||||
AUTOMATIC,
|
||||
MANUAL,
|
||||
VACATION,
|
||||
BOOST;
|
||||
|
||||
@Override
|
||||
public String format(String pattern) {
|
||||
return String.format(pattern, this.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toFullString() {
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.device;
|
||||
|
||||
/**
|
||||
* Unsupported devices.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*/
|
||||
|
||||
public class UnsupportedDevice extends Device {
|
||||
|
||||
public UnsupportedDevice(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.Invalid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Unsupported device";
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.device;
|
||||
|
||||
/**
|
||||
* MAX! wall mounted thermostat.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
public class WallMountedThermostat extends HeatingThermostat {
|
||||
|
||||
public WallMountedThermostat(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.WallMountedThermostat;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* 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.max.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxCubeBridgeDiscovery} is responsible for discovering new MAX!
|
||||
* Cube LAN gateway devices on the network
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.max")
|
||||
public class MaxCubeBridgeDiscovery extends AbstractDiscoveryService {
|
||||
|
||||
private static final String MAXCUBE_DISCOVER_STRING = "eQ3Max*\0**********I";
|
||||
private static final int SEARCH_TIME = 15;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxCubeBridgeDiscovery.class);
|
||||
|
||||
protected static boolean discoveryRunning;
|
||||
|
||||
/** The refresh interval for discovery of MAX! Cubes */
|
||||
private static final long SEARCH_INTERVAL = 600;
|
||||
private ScheduledFuture<?> cubeDiscoveryJob;
|
||||
|
||||
public MaxCubeBridgeDiscovery() {
|
||||
super(SEARCH_TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return SUPPORTED_BRIDGE_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
logger.debug("Start MAX! Cube discovery");
|
||||
discoverCube();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
logger.debug("Stop MAX! Cube background discovery");
|
||||
if (cubeDiscoveryJob != null && !cubeDiscoveryJob.isCancelled()) {
|
||||
cubeDiscoveryJob.cancel(true);
|
||||
cubeDiscoveryJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
logger.debug("Start MAX! Cube background discovery");
|
||||
if (cubeDiscoveryJob == null || cubeDiscoveryJob.isCancelled()) {
|
||||
cubeDiscoveryJob = scheduler.scheduleWithFixedDelay(this::discoverCube, 0, SEARCH_INTERVAL,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void discoverCube() {
|
||||
logger.debug("Run MAX! Cube discovery");
|
||||
sendDiscoveryMessage(MAXCUBE_DISCOVER_STRING);
|
||||
logger.trace("Done sending broadcast discovery messages.");
|
||||
receiveDiscoveryMessage();
|
||||
logger.debug("Done receiving discovery messages.");
|
||||
}
|
||||
|
||||
private void receiveDiscoveryMessage() {
|
||||
try (final DatagramSocket bcReceipt = new DatagramSocket(23272)) {
|
||||
discoveryRunning = true;
|
||||
bcReceipt.setReuseAddress(true);
|
||||
bcReceipt.setSoTimeout(5000);
|
||||
|
||||
while (discoveryRunning) {
|
||||
// Wait for a response
|
||||
final byte[] recvBuf = new byte[1500];
|
||||
final DatagramPacket receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
|
||||
bcReceipt.receive(receivePacket);
|
||||
|
||||
// We have a response
|
||||
final byte[] messageBuf = Arrays.copyOfRange(receivePacket.getData(), receivePacket.getOffset(),
|
||||
receivePacket.getOffset() + receivePacket.getLength());
|
||||
final String message = new String(messageBuf, StandardCharsets.UTF_8);
|
||||
logger.trace("Broadcast response from {} : {} '{}'", receivePacket.getAddress(), message.length(),
|
||||
message);
|
||||
|
||||
// Check if the message is correct
|
||||
if (message.startsWith("eQ3Max") && !message.equals(MAXCUBE_DISCOVER_STRING)) {
|
||||
String maxCubeIP = receivePacket.getAddress().getHostAddress();
|
||||
String maxCubeState = message.substring(0, 8);
|
||||
String serialNumber = message.substring(8, 18);
|
||||
String msgValidid = message.substring(18, 19);
|
||||
String requestType = message.substring(19, 20);
|
||||
String rfAddress = "";
|
||||
logger.debug("MAX! Cube found on network");
|
||||
logger.debug("Found at : {}", maxCubeIP);
|
||||
logger.trace("Cube State: {}", maxCubeState);
|
||||
logger.debug("Serial : {}", serialNumber);
|
||||
logger.trace("Msg Valid : {}", msgValidid);
|
||||
logger.trace("Msg Type : {}", requestType);
|
||||
|
||||
if (requestType.equals("I")) {
|
||||
rfAddress = Utils.getHex(Arrays.copyOfRange(messageBuf, 21, 24)).replace(" ", "").toLowerCase();
|
||||
String firmwareVersion = Utils.getHex(Arrays.copyOfRange(messageBuf, 24, 26)).replace(" ", ".");
|
||||
logger.debug("RF Address: {}", rfAddress);
|
||||
logger.debug("Firmware : {}", firmwareVersion);
|
||||
}
|
||||
discoveryResultSubmission(maxCubeIP, serialNumber, rfAddress);
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
logger.trace("No further response");
|
||||
discoveryRunning = false;
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
|
||||
discoveryRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void discoveryResultSubmission(String IpAddress, String cubeSerialNumber, String rfAddress) {
|
||||
if (cubeSerialNumber != null) {
|
||||
logger.trace("Adding new MAX! Cube Lan Gateway on {} with id '{}' to Smarthome inbox", IpAddress,
|
||||
cubeSerialNumber);
|
||||
Map<String, Object> properties = new HashMap<>(2);
|
||||
properties.put(PROPERTY_IP_ADDRESS, IpAddress);
|
||||
properties.put(PROPERTY_SERIAL_NUMBER, cubeSerialNumber);
|
||||
properties.put(PROPERTY_RFADDRESS, rfAddress);
|
||||
ThingUID uid = new ThingUID(CUBEBRIDGE_THING_TYPE, cubeSerialNumber);
|
||||
thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties)
|
||||
.withRepresentationProperty(PROPERTY_SERIAL_NUMBER).withThingType(CUBEBRIDGE_THING_TYPE)
|
||||
.withLabel("MAX! Cube LAN Gateway").build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send broadcast message over all active interfaces
|
||||
*
|
||||
* @param discoverString
|
||||
* String to be used for the discovery
|
||||
*/
|
||||
private void sendDiscoveryMessage(String discoverString) {
|
||||
// Find the MaxCube using UDP broadcast
|
||||
try (DatagramSocket bcSend = new DatagramSocket()) {
|
||||
bcSend.setBroadcast(true);
|
||||
|
||||
byte[] sendData = discoverString.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// Broadcast the message over all the network interfaces
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
|
||||
continue;
|
||||
}
|
||||
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
|
||||
InetAddress[] broadcast = new InetAddress[3];
|
||||
broadcast[0] = InetAddress.getByName("224.0.0.1");
|
||||
broadcast[1] = InetAddress.getByName("255.255.255.255");
|
||||
broadcast[2] = interfaceAddress.getBroadcast();
|
||||
for (InetAddress bc : broadcast) {
|
||||
// Send the broadcast package!
|
||||
if (bc != null) {
|
||||
try {
|
||||
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, bc, 23272);
|
||||
bcSend.send(sendPacket);
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
logger.debug("{}", e.getMessage(), e);
|
||||
}
|
||||
logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
|
||||
networkInterface.getDisplayName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.trace("Done looping over all network interfaces. Now waiting for a reply!");
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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.max.internal.discovery;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.MaxBindingConstants;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.handler.DeviceStatusListener;
|
||||
import org.openhab.binding.max.internal.handler.MaxCubeBridgeHandler;
|
||||
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.config.discovery.DiscoveryService;
|
||||
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.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxDeviceDiscoveryService} class is used to discover MAX! Cube
|
||||
* devices that are connected to the Lan gateway.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MaxDeviceDiscoveryService extends AbstractDiscoveryService
|
||||
implements DeviceStatusListener, DiscoveryService, ThingHandlerService {
|
||||
|
||||
private static final int SEARCH_TIME = 60;
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxDeviceDiscoveryService.class);
|
||||
|
||||
private @Nullable MaxCubeBridgeHandler maxCubeBridgeHandler;
|
||||
|
||||
public MaxDeviceDiscoveryService() {
|
||||
super(MaxBindingConstants.SUPPORTED_DEVICE_THING_TYPES_UIDS, SEARCH_TIME, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@NonNullByDefault({}) ThingHandler handler) {
|
||||
if (handler instanceof MaxCubeBridgeHandler) {
|
||||
this.maxCubeBridgeHandler = (MaxCubeBridgeHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return maxCubeBridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
MaxCubeBridgeHandler localMaxCubeBridgeHandler = maxCubeBridgeHandler;
|
||||
if (localMaxCubeBridgeHandler != null) {
|
||||
localMaxCubeBridgeHandler.registerDeviceStatusListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
MaxCubeBridgeHandler localMaxCubeBridgeHandler = maxCubeBridgeHandler;
|
||||
if (localMaxCubeBridgeHandler != null) {
|
||||
localMaxCubeBridgeHandler.unregisterDeviceStatusListener(this);
|
||||
removeOlderResults(new Date().getTime(), localMaxCubeBridgeHandler.getThing().getUID());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return MaxBindingConstants.SUPPORTED_DEVICE_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceAdded(Bridge bridge, Device device) {
|
||||
logger.trace("Adding new MAX! {} with id '{}' to smarthome inbox", device.getType(), device.getSerialNumber());
|
||||
ThingUID thingUID = null;
|
||||
switch (device.getType()) {
|
||||
case WallMountedThermostat:
|
||||
thingUID = new ThingUID(MaxBindingConstants.WALLTHERMOSTAT_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
case HeatingThermostat:
|
||||
thingUID = new ThingUID(MaxBindingConstants.HEATINGTHERMOSTAT_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
case HeatingThermostatPlus:
|
||||
thingUID = new ThingUID(MaxBindingConstants.HEATINGTHERMOSTATPLUS_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
case ShutterContact:
|
||||
thingUID = new ThingUID(MaxBindingConstants.SHUTTERCONTACT_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
case EcoSwitch:
|
||||
thingUID = new ThingUID(MaxBindingConstants.ECOSWITCH_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (thingUID != null) {
|
||||
String name = device.getName();
|
||||
if (name.isEmpty()) {
|
||||
name = device.getSerialNumber();
|
||||
}
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperty(Thing.PROPERTY_SERIAL_NUMBER, device.getSerialNumber()).withBridge(bridge.getUID())
|
||||
.withLabel(device.getType() + ": " + name).withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER)
|
||||
.build();
|
||||
thingDiscovered(discoveryResult);
|
||||
} else {
|
||||
logger.debug("Discovered MAX! device is unsupported: type '{}' with id '{}'", device.getType(),
|
||||
device.getSerialNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
MaxCubeBridgeHandler localMaxCubeBridgeHandler = maxCubeBridgeHandler;
|
||||
if (localMaxCubeBridgeHandler != null) {
|
||||
localMaxCubeBridgeHandler.clearDeviceList();
|
||||
localMaxCubeBridgeHandler.deviceInclusion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceStateChanged(ThingUID bridge, Device device) {
|
||||
// this can be ignored here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(MaxCubeBridgeHandler bridge, Device device) {
|
||||
// this can be ignored here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceConfigUpdate(Bridge bridge, Device device) {
|
||||
// this can be ignored here
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* but the processor is currently processing an other message type.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class IncompleteMessageException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -8882867114144637120L;
|
||||
}
|
||||
@@ -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.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* but the processor is currently processing an other message type.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class IncorrectMultilineIndexException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = 3102159039702650238L;
|
||||
}
|
||||
@@ -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.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* but the processor is not yet ready to handle new lines because there is already a message that
|
||||
* has be pulled before.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class MessageIsWaitingException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -7317329978634583853L;
|
||||
}
|
||||
@@ -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.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to pull a message from the message processor,
|
||||
* but the processor does not yet have a complete message.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class NoMessageAvailableException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -7663390696233390452L;
|
||||
}
|
||||
@@ -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.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* the processor detects a known message indicator, but the message could not be parsed correctly.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class UnprocessableMessageException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -9071779402960309265L;
|
||||
}
|
||||
@@ -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.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* but the line starts with an unknown message indicator.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class UnsupportedMessageTypeException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -5163044407682700913L;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.factory;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.handler.MaxCubeBridgeHandler;
|
||||
import org.openhab.binding.max.internal.handler.MaxDevicesHandler;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
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.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxCubeHandlerFactory} is responsible for creating things and
|
||||
* thing handlers.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.max")
|
||||
public class MaxCubeHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxCubeHandlerFactory.class);
|
||||
|
||||
@Override
|
||||
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
|
||||
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
||||
if (CUBEBRIDGE_THING_TYPE.equals(thingTypeUID)) {
|
||||
ThingUID cubeBridgeUID = getBridgeThingUID(thingTypeUID, thingUID, configuration);
|
||||
return super.createThing(thingTypeUID, configuration, cubeBridgeUID, null);
|
||||
}
|
||||
if (supportsThingType(thingTypeUID) && bridgeUID != null) {
|
||||
ThingUID deviceUID = getMaxCubeDeviceUID(thingTypeUID, thingUID, configuration, bridgeUID);
|
||||
return super.createThing(thingTypeUID, configuration, deviceUID, bridgeUID);
|
||||
}
|
||||
throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the binding.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
|
||||
Configuration configuration) {
|
||||
if (thingUID == null) {
|
||||
String serialNumber = (String) configuration.get(Thing.PROPERTY_SERIAL_NUMBER);
|
||||
return new ThingUID(thingTypeUID, serialNumber);
|
||||
}
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
private ThingUID getMaxCubeDeviceUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
|
||||
Configuration configuration, ThingUID bridgeUID) {
|
||||
if (thingUID == null) {
|
||||
String serialNumber = (String) configuration.get(Thing.PROPERTY_SERIAL_NUMBER);
|
||||
return new ThingUID(thingTypeUID, serialNumber, bridgeUID.getId());
|
||||
}
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (CUBEBRIDGE_THING_TYPE.equals(thingTypeUID)) {
|
||||
return new MaxCubeBridgeHandler((Bridge) thing);
|
||||
} else if (SUPPORTED_DEVICE_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
return new MaxDevicesHandler(thing);
|
||||
} else {
|
||||
logger.debug("ThingHandler not found for {}", thingTypeUID);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* The {@link DeviceStatusListener} is notified when a device status has changed
|
||||
* or a device has been removed or added.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface DeviceStatusListener {
|
||||
|
||||
/**
|
||||
* This method is called whenever the state of the given device has changed.
|
||||
*
|
||||
* @param bridge The MAX! Cube bridge the changed device is connected to
|
||||
* @param device The device which received the state update
|
||||
*/
|
||||
void onDeviceStateChanged(ThingUID bridge, Device device);
|
||||
|
||||
/**
|
||||
* This method is called whenever a device is removed.
|
||||
*
|
||||
* @param bridge The MAX! Cube bridge the removed device was connected to
|
||||
* @param device The device which is removed
|
||||
*/
|
||||
void onDeviceRemoved(MaxCubeBridgeHandler bridge, Device device);
|
||||
|
||||
/**
|
||||
* This method is called whenever a device is added.
|
||||
*
|
||||
* @param bridge The MAX! Cube bridge the added device was connected to
|
||||
* @param device The device which is added
|
||||
*/
|
||||
void onDeviceAdded(Bridge bridge, Device device);
|
||||
|
||||
/**
|
||||
* This method is called whenever a device config is updated.
|
||||
*
|
||||
* @param bridgeThe MAX! Cube bridge the device was connected to
|
||||
* @param device The device which config is changed
|
||||
*/
|
||||
void onDeviceConfigUpdate(Bridge bridge, Device device);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,580 @@
|
||||
/**
|
||||
* 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.max.internal.handler;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.max.actions.MaxDevicesActions;
|
||||
import org.openhab.binding.max.internal.command.CCommand;
|
||||
import org.openhab.binding.max.internal.command.QCommand;
|
||||
import org.openhab.binding.max.internal.command.SConfigCommand;
|
||||
import org.openhab.binding.max.internal.command.SConfigCommand.ConfigCommandType;
|
||||
import org.openhab.binding.max.internal.command.ZCommand;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.openhab.binding.max.internal.device.EcoSwitch;
|
||||
import org.openhab.binding.max.internal.device.HeatingThermostat;
|
||||
import org.openhab.binding.max.internal.device.ShutterContact;
|
||||
import org.openhab.binding.max.internal.device.ThermostatModeType;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxDevicesHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
public class MaxDevicesHandler extends BaseThingHandler implements DeviceStatusListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxDevicesHandler.class);
|
||||
private MaxCubeBridgeHandler bridgeHandler;
|
||||
|
||||
private String maxDeviceSerial;
|
||||
private String rfAddress;
|
||||
private boolean propertiesSet;
|
||||
private boolean configSet;
|
||||
|
||||
// actual refresh variables
|
||||
public static final int REFRESH_ACTUAL_MIN_RATE = 10; // minutes
|
||||
public static final int REFRESH_ACTUAL_DURATION = 120; // seconds
|
||||
private static final long COMMUNICATION_DELAY_TIME = 120;
|
||||
private int refreshActualRate;
|
||||
private boolean refreshingActuals;
|
||||
private ScheduledFuture<?> refreshActualsJob;
|
||||
private double originalSetTemp;
|
||||
private ThermostatModeType originalMode;
|
||||
|
||||
public MaxDevicesHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
try {
|
||||
final Configuration config = getThing().getConfiguration();
|
||||
final String configDeviceId = (String) config.get(Thing.PROPERTY_SERIAL_NUMBER);
|
||||
|
||||
try {
|
||||
refreshActualRate = ((BigDecimal) config.get(PROPERTY_REFRESH_ACTUAL_RATE)).intValueExact();
|
||||
} catch (Exception e) {
|
||||
refreshActualRate = 0;
|
||||
}
|
||||
|
||||
if (configDeviceId != null) {
|
||||
maxDeviceSerial = configDeviceId;
|
||||
}
|
||||
if (maxDeviceSerial != null) {
|
||||
logger.debug("Initialized MAX! device handler for {}.", maxDeviceSerial);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Initialized MAX! device missing serialNumber configuration");
|
||||
}
|
||||
propertiesSet = false;
|
||||
configSet = false;
|
||||
getMaxCubeBridgeHandler();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during initialize : {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing MAX! device {} {}.", getThing().getUID(), maxDeviceSerial);
|
||||
if (refreshingActuals) {
|
||||
refreshActualsRestore();
|
||||
}
|
||||
if (refreshActualsJob != null && !refreshActualsJob.isCancelled()) {
|
||||
refreshActualsJob.cancel(true);
|
||||
refreshActualsJob = null;
|
||||
}
|
||||
if (bridgeHandler != null) {
|
||||
logger.trace("Clear MAX! device {} {} from bridge.", getThing().getUID(), maxDeviceSerial);
|
||||
bridgeHandler.clearDeviceList();
|
||||
bridgeHandler.unregisterDeviceStatusListener(this);
|
||||
bridgeHandler = null;
|
||||
}
|
||||
logger.debug("Disposed MAX! device {} {}.", getThing().getUID(), maxDeviceSerial);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thingUpdated(Thing thing) {
|
||||
configSet = false;
|
||||
super.thingUpdated(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
|
||||
logger.debug("MAX! Device {}: Configuration update received", getThing().getUID());
|
||||
boolean temperaturePropertyUpdateNeeded = false;
|
||||
final Device device = getMaxCubeBridgeHandler().getDevice(maxDeviceSerial);
|
||||
|
||||
final Map<String, Object> deviceProperties = device == null ? new HashMap<>() : device.getProperties();
|
||||
final Configuration configuration = editConfiguration();
|
||||
for (final Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
|
||||
logger.debug("MAX! Device {}: Configuration update {} to {}", getThing().getUID(),
|
||||
configurationParameter.getKey(), configurationParameter.getValue());
|
||||
|
||||
// Test if it is a part of the configuration properties.
|
||||
// As paperUI sends all parameters as changed, we need to determine which ones really changed.
|
||||
if (deviceProperties.containsKey(configurationParameter.getKey())) {
|
||||
if (deviceProperties.get(configurationParameter.getKey()).equals(configurationParameter.getValue())) {
|
||||
logger.trace("Device {} Property {} value {} unchanged.", getThing().getUID(),
|
||||
configurationParameter.getKey(), configurationParameter.getValue());
|
||||
} else if (configurationParameter.getValue().getClass() == BigDecimal.class
|
||||
&& ((BigDecimal) deviceProperties.get(configurationParameter.getKey()))
|
||||
.compareTo((BigDecimal) configurationParameter.getValue()) == 0) {
|
||||
logger.trace("Device {} Property {} value {} unchanged.", getThing().getUID(),
|
||||
configurationParameter.getKey(), configurationParameter.getValue());
|
||||
} else {
|
||||
logger.debug("Device {} Property {} value {} -> {} changed.", getThing().getUID(),
|
||||
configurationParameter.getKey(), deviceProperties.get(configurationParameter.getKey()),
|
||||
configurationParameter.getValue());
|
||||
temperaturePropertyUpdateNeeded = true;
|
||||
}
|
||||
}
|
||||
if (configurationParameter.getKey().equals(PROPERTY_DEVICENAME)
|
||||
|| configurationParameter.getKey().equals(PROPERTY_ROOMID)) {
|
||||
updateDeviceName(configurationParameter);
|
||||
}
|
||||
if (configurationParameter.getKey().startsWith("action-")) {
|
||||
if (configurationParameter.getValue().toString().equals(BUTTON_ACTION_VALUE)) {
|
||||
configurationParameter.setValue(BigDecimal.valueOf(BUTTON_NOACTION_VALUE));
|
||||
if (configurationParameter.getKey().equals(ACTION_DEVICE_DELETE)) {
|
||||
deviceDelete();
|
||||
}
|
||||
}
|
||||
}
|
||||
configuration.put(configurationParameter.getKey(), configurationParameter.getValue());
|
||||
}
|
||||
// Persist changes and restart with new parameters
|
||||
updateConfiguration(configuration);
|
||||
if (temperaturePropertyUpdateNeeded) {
|
||||
sendPropertyUpdate(configurationParameters, deviceProperties);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(MaxDevicesActions.class);
|
||||
}
|
||||
|
||||
private void sendPropertyUpdate(Map<String, Object> configurationParameters, Map<String, Object> deviceProperties) {
|
||||
if (getMaxCubeBridgeHandler() == null) {
|
||||
logger.debug("MAX! Cube LAN gateway bridge handler not found. Cannot handle update without bridge.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Device device = getMaxCubeBridgeHandler().getDevice(maxDeviceSerial);
|
||||
rfAddress = device.getRFAddress();
|
||||
int roomId = device.getRoomId();
|
||||
BigDecimal tempComfort = (BigDecimal) configurationParameters.getOrDefault(PROPERTY_THERMO_COMFORT_TEMP,
|
||||
deviceProperties.get(PROPERTY_THERMO_COMFORT_TEMP));
|
||||
BigDecimal tempEco = (BigDecimal) configurationParameters.getOrDefault(PROPERTY_THERMO_ECO_TEMP,
|
||||
deviceProperties.get(PROPERTY_THERMO_ECO_TEMP));
|
||||
BigDecimal tempSetpointMax = (BigDecimal) configurationParameters.getOrDefault(
|
||||
PROPERTY_THERMO_MAX_TEMP_SETPOINT, deviceProperties.get(PROPERTY_THERMO_MAX_TEMP_SETPOINT));
|
||||
BigDecimal tempSetpointMin = (BigDecimal) configurationParameters.getOrDefault(
|
||||
PROPERTY_THERMO_MIN_TEMP_SETPOINT, deviceProperties.get(PROPERTY_THERMO_MIN_TEMP_SETPOINT));
|
||||
BigDecimal tempOffset = (BigDecimal) configurationParameters.getOrDefault(PROPERTY_THERMO_OFFSET_TEMP,
|
||||
deviceProperties.get(PROPERTY_THERMO_OFFSET_TEMP));
|
||||
BigDecimal tempOpenWindow = (BigDecimal) configurationParameters.getOrDefault(
|
||||
PROPERTY_THERMO_WINDOW_OPEN_TEMP, deviceProperties.get(PROPERTY_THERMO_WINDOW_OPEN_TEMP));
|
||||
BigDecimal durationOpenWindow = (BigDecimal) configurationParameters.getOrDefault(
|
||||
PROPERTY_THERMO_WINDOW_OPEN_DURATION, deviceProperties.get(PROPERTY_THERMO_WINDOW_OPEN_DURATION));
|
||||
SConfigCommand cmd = new SConfigCommand(rfAddress, roomId, tempComfort.doubleValue(), tempEco.doubleValue(),
|
||||
tempSetpointMax.doubleValue(), tempSetpointMin.doubleValue(), tempOffset.doubleValue(),
|
||||
tempOpenWindow.doubleValue(), durationOpenWindow.intValue());
|
||||
bridgeHandler.queueCommand(new SendCommand(maxDeviceSerial, cmd, "Update Thermostat Properties"));
|
||||
sendCCommand();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update by sending C command.
|
||||
* This command is delayed as it takes time to have the updates back from the thermostat
|
||||
*/
|
||||
private void sendCCommand() {
|
||||
scheduler.schedule(() -> {
|
||||
CCommand cmd = new CCommand(rfAddress);
|
||||
bridgeHandler.queueCommand(new SendCommand(maxDeviceSerial, cmd, "Refresh Thermostat Properties"));
|
||||
configSet = false;
|
||||
}, COMMUNICATION_DELAY_TIME, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* sends the T command to the Cube to disassociate the device from the MAX! Cube.
|
||||
*/
|
||||
public void deviceDelete() {
|
||||
MaxCubeBridgeHandler maxCubeBridge = getMaxCubeBridgeHandler();
|
||||
if (maxCubeBridge != null) {
|
||||
maxCubeBridge.sendDeviceDelete(maxDeviceSerial);
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the device & roomname
|
||||
*/
|
||||
private void updateDeviceName(Entry<String, Object> configurationParameter) {
|
||||
try {
|
||||
final Device device = getMaxCubeBridgeHandler().getDevice(maxDeviceSerial);
|
||||
if (device == null) {
|
||||
logger.debug("MAX! Cube LAN gateway bridge handler not found. Cannot handle update without bridge.");
|
||||
return;
|
||||
}
|
||||
switch (configurationParameter.getKey()) {
|
||||
case PROPERTY_DEVICENAME:
|
||||
final String name = configurationParameter.getValue().toString();
|
||||
if (!name.equals(device.getName())) {
|
||||
logger.debug("Updating device name for {} to {}", getThing().getUID(), name);
|
||||
device.setName(name);
|
||||
bridgeHandler.sendDeviceAndRoomNameUpdate(name);
|
||||
bridgeHandler.queueCommand(new SendCommand(maxDeviceSerial, new QCommand(), "Reload Data"));
|
||||
}
|
||||
break;
|
||||
|
||||
case PROPERTY_ROOMID: // fall-through
|
||||
case PROPERTY_ROOMNAME:
|
||||
final int roomId = ((BigDecimal) configurationParameter.getValue()).intValue();
|
||||
if (roomId != device.getRoomId()) {
|
||||
logger.debug("Updating room for {} to {}", getThing().getUID().getAsString(), roomId);
|
||||
device.setRoomId(roomId);
|
||||
// TODO: handle if a room has no more devices, probably should be deleted. Also handle if room
|
||||
// rfId
|
||||
// is no longer valid as the related device is movd to another room
|
||||
bridgeHandler.sendDeviceAndRoomNameUpdate(Integer.toString(roomId));
|
||||
SendCommand sendCommand = new SendCommand(maxDeviceSerial,
|
||||
ZCommand.wakeupDevice(device.getRFAddress()),
|
||||
"WakeUp device" + getThing().getUID().getAsString());
|
||||
bridgeHandler.queueCommand(sendCommand);
|
||||
sendCommand = new SendCommand(maxDeviceSerial,
|
||||
new SConfigCommand(device.getRFAddress(), roomId, ConfigCommandType.SetRoom),
|
||||
"Set Room");
|
||||
bridgeHandler.queueCommand(sendCommand);
|
||||
|
||||
sendCommand = new SendCommand(maxDeviceSerial, new QCommand(), "Reload Data");
|
||||
bridgeHandler.queueCommand(sendCommand);
|
||||
sendCCommand();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized MaxCubeBridgeHandler getMaxCubeBridgeHandler() {
|
||||
if (this.bridgeHandler == null) {
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
logger.debug("Required bridge not defined for device {}.", maxDeviceSerial);
|
||||
return null;
|
||||
}
|
||||
final ThingHandler handler = bridge.getHandler();
|
||||
if (!(handler instanceof MaxCubeBridgeHandler)) {
|
||||
logger.debug("No available bridge handler found for {} bridge {} .", maxDeviceSerial, bridge.getUID());
|
||||
return null;
|
||||
}
|
||||
this.bridgeHandler = (MaxCubeBridgeHandler) handler;
|
||||
this.bridgeHandler.registerDeviceStatusListener(this);
|
||||
}
|
||||
return this.bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
final MaxCubeBridgeHandler maxCubeBridge = getMaxCubeBridgeHandler();
|
||||
if (maxCubeBridge == null) {
|
||||
logger.debug("MAX! Cube LAN gateway bridge handler not found. Cannot handle command without bridge.");
|
||||
return;
|
||||
}
|
||||
if (command instanceof RefreshType) {
|
||||
maxCubeBridge.handleCommand(channelUID, command);
|
||||
return;
|
||||
}
|
||||
if (maxDeviceSerial == null) {
|
||||
logger.warn("Serial number missing. Can't send command to device '{}'", getThing());
|
||||
return;
|
||||
}
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_SETTEMP:
|
||||
if (refreshingActuals) {
|
||||
refreshActualsRestore();
|
||||
}
|
||||
maxCubeBridge.queueCommand(new SendCommand(maxDeviceSerial, channelUID, command));
|
||||
break;
|
||||
case CHANNEL_MODE:
|
||||
if (refreshingActuals) {
|
||||
refreshActualsRestore();
|
||||
}
|
||||
maxCubeBridge.queueCommand(new SendCommand(maxDeviceSerial, channelUID, command));
|
||||
break;
|
||||
default:
|
||||
logger.warn("Setting of channel '{}' not possible, channel is read-only.", channelUID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceStateChanged(ThingUID bridge, Device device) {
|
||||
if (!device.getSerialNumber().equals(maxDeviceSerial)) {
|
||||
return;
|
||||
}
|
||||
if (device.isError() || device.isLinkStatusError()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR);
|
||||
} else if (!refreshingActuals) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Updating Actual Temperature");
|
||||
}
|
||||
if (!propertiesSet) {
|
||||
setProperties(device);
|
||||
}
|
||||
if (!configSet) {
|
||||
setDeviceConfiguration(device);
|
||||
}
|
||||
if (refreshActualRate >= REFRESH_ACTUAL_MIN_RATE && (device.getType() == DeviceType.HeatingThermostat
|
||||
|| device.getType() == DeviceType.HeatingThermostatPlus)) {
|
||||
refreshActualCheck((HeatingThermostat) device);
|
||||
}
|
||||
logger.debug("Updating states of {} {} ({}) id: {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), getThing().getUID());
|
||||
switch (device.getType()) {
|
||||
case WallMountedThermostat: // fall-through
|
||||
case HeatingThermostat: // fall-through
|
||||
case HeatingThermostatPlus:
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_LOCKED),
|
||||
((HeatingThermostat) device).isPanelLocked() ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_SETTEMP),
|
||||
new QuantityType<>(((HeatingThermostat) device).getTemperatureSetpoint(), CELSIUS));
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_MODE),
|
||||
new StringType(((HeatingThermostat) device).getModeString()));
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_BATTERY),
|
||||
((HeatingThermostat) device).getBatteryLow());
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_VALVE),
|
||||
new DecimalType(((HeatingThermostat) device).getValvePosition()));
|
||||
double actualTemp = ((HeatingThermostat) device).getTemperatureActual();
|
||||
if (actualTemp != 0) {
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_ACTUALTEMP),
|
||||
new QuantityType<>(actualTemp, CELSIUS));
|
||||
}
|
||||
break;
|
||||
case ShutterContact:
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_CONTACT_STATE),
|
||||
((ShutterContact) device).getShutterState());
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_BATTERY),
|
||||
((ShutterContact) device).getBatteryLow());
|
||||
break;
|
||||
case EcoSwitch:
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_BATTERY), ((EcoSwitch) device).getBatteryLow());
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unhandled Device {}.", device.getType());
|
||||
break;
|
||||
}
|
||||
device.setUpdated(false);
|
||||
}
|
||||
|
||||
private void refreshActualCheck(HeatingThermostat device) {
|
||||
if (device.getActualTempLastUpdated() == null) {
|
||||
Calendar t = Calendar.getInstance();
|
||||
t.add(Calendar.MINUTE, REFRESH_ACTUAL_MIN_RATE * -1);
|
||||
device.setActualTempLastUpdated(t.getTime());
|
||||
logger.debug("Actual date reset for {} {} ({}) id: {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), getThing().getUID());
|
||||
}
|
||||
long timediff = Calendar.getInstance().getTime().getTime() - device.getActualTempLastUpdated().getTime();
|
||||
if (timediff > ((long) refreshActualRate) * 1000 * 60) {
|
||||
if (!refreshingActuals) {
|
||||
logger.debug("Actual needs updating for {} {} ({}) id: {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), getThing().getUID());
|
||||
|
||||
originalSetTemp = device.getTemperatureSetpoint();
|
||||
originalMode = device.getMode();
|
||||
|
||||
if (originalMode == ThermostatModeType.MANUAL || originalMode == ThermostatModeType.AUTOMATIC) {
|
||||
double tempSetTemp = originalSetTemp + 0.5;
|
||||
logger.debug("Actuals Refresh: Setting Temp {}", tempSetTemp);
|
||||
handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SETTEMP),
|
||||
new QuantityType<>(tempSetTemp, CELSIUS));
|
||||
refreshingActuals = true;
|
||||
} else {
|
||||
logger.debug("Defer Actuals refresh. Only manual refresh for mode AUTOMATIC & MANUAL");
|
||||
device.setActualTempLastUpdated(Calendar.getInstance().getTime());
|
||||
}
|
||||
|
||||
if (refreshingActuals) {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Updating Actual Temperature");
|
||||
|
||||
if (refreshActualsJob == null || refreshActualsJob.isCancelled()) {
|
||||
refreshActualsJob = scheduler.schedule(this::refreshActualsRestore, REFRESH_ACTUAL_DURATION,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
device.setActualTempLastUpdated(Calendar.getInstance().getTime());
|
||||
}
|
||||
}
|
||||
logger.debug("Actual Refresh in progress for {} {} ({}) id: {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), getThing().getUID());
|
||||
} else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
|
||||
logger.trace("Actual date for {} {} ({}) : {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), dateFormat.format(device.getActualTempLastUpdated().getTime()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the commands to restore the original settings for mode & temperature
|
||||
* to end the automatic update cycle
|
||||
*/
|
||||
private synchronized void refreshActualsRestore() {
|
||||
try {
|
||||
refreshingActuals = false;
|
||||
if (originalMode == ThermostatModeType.AUTOMATIC || originalMode == ThermostatModeType.MANUAL) {
|
||||
logger.debug("Finished Actuals Refresh: Restoring Temp {}", originalSetTemp);
|
||||
handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SETTEMP),
|
||||
new QuantityType<>(originalSetTemp, CELSIUS));
|
||||
}
|
||||
|
||||
if (refreshActualsJob != null && !refreshActualsJob.isCancelled()) {
|
||||
refreshActualsJob.cancel(true);
|
||||
refreshActualsJob = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during Actuals Refresh : {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(MaxCubeBridgeHandler bridge, Device device) {
|
||||
if (device.getSerialNumber().equals(maxDeviceSerial)) {
|
||||
bridgeHandler.unregisterDeviceStatusListener(this);
|
||||
bridgeHandler = null;
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceAdded(Bridge bridge, Device device) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the properties for this device
|
||||
*/
|
||||
private void setProperties(Device device) {
|
||||
try {
|
||||
logger.debug("MAX! {} {} properties update", device.getType(), device.getSerialNumber());
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, device.getType().toString());
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getSerialNumber());
|
||||
properties.put(Thing.PROPERTY_VENDOR, PROPERTY_VENDOR_NAME);
|
||||
updateProperties(properties);
|
||||
logger.debug("properties updated");
|
||||
propertiesSet = true;
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during property edit: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("Bridge Status updated to {} for device: {}", bridgeStatusInfo.getStatus(), getThing().getUID());
|
||||
if (!bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Configurable properties for this device
|
||||
*/
|
||||
private void setDeviceConfiguration(Device device) {
|
||||
try {
|
||||
boolean config_changed = false;
|
||||
logger.debug("MAX! {} {} configuration update", device.getType(), device.getSerialNumber());
|
||||
Configuration configuration = editConfiguration();
|
||||
if (!device.getRoomName().equalsIgnoreCase((String) getConfig().get(PROPERTY_ROOMNAME))) {
|
||||
configuration.put(PROPERTY_ROOMNAME, device.getRoomName());
|
||||
config_changed = true;
|
||||
}
|
||||
if (getConfig().get(PROPERTY_ROOMID) == null || !(new BigDecimal(device.getRoomId())
|
||||
.compareTo((BigDecimal) getConfig().get(PROPERTY_ROOMID)) == 0)) {
|
||||
configuration.put(PROPERTY_ROOMID, new BigDecimal(device.getRoomId()));
|
||||
config_changed = true;
|
||||
}
|
||||
if (!device.getName().equalsIgnoreCase((String) getConfig().get(PROPERTY_DEVICENAME))) {
|
||||
configuration.put(PROPERTY_DEVICENAME, device.getName());
|
||||
config_changed = true;
|
||||
}
|
||||
if (!device.getRFAddress().equalsIgnoreCase((String) getConfig().get(PROPERTY_RFADDRESS))) {
|
||||
configuration.put(PROPERTY_RFADDRESS, device.getRFAddress());
|
||||
config_changed = true;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : device.getProperties().entrySet()) {
|
||||
configuration.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (config_changed) {
|
||||
updateConfiguration(configuration);
|
||||
logger.debug("Config updated: {}", configuration.getProperties());
|
||||
} else {
|
||||
logger.debug("MAX! {} {} no updated required.", device.getType(), device.getSerialNumber());
|
||||
}
|
||||
configSet = true;
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during configuration edit: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceConfigUpdate(Bridge bridge, Device device) {
|
||||
if (device.getSerialNumber().equals(maxDeviceSerial)) {
|
||||
setDeviceConfiguration(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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.max.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.command.CubeCommand;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* Class for sending a command.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class SendCommand {
|
||||
|
||||
private int id;
|
||||
private static int commandId = -1;
|
||||
|
||||
private @Nullable ChannelUID channelUID;
|
||||
private @Nullable Command command;
|
||||
private @Nullable CubeCommand cubeCommand;
|
||||
private String serialNumber;
|
||||
private String key;
|
||||
private String commandText;
|
||||
|
||||
public SendCommand(String serialNumber, ChannelUID channelUID, Command command) {
|
||||
commandId++;
|
||||
id = commandId;
|
||||
this.serialNumber = serialNumber;
|
||||
this.channelUID = channelUID;
|
||||
this.command = command;
|
||||
key = getKey(serialNumber, channelUID);
|
||||
this.commandText = command.toString();
|
||||
}
|
||||
|
||||
public SendCommand(String serialNumber, CubeCommand cubeCommand, String commandText) {
|
||||
commandId++;
|
||||
id = commandId;
|
||||
this.serialNumber = serialNumber;
|
||||
this.cubeCommand = cubeCommand;
|
||||
key = getKey(serialNumber, cubeCommand);
|
||||
this.commandText = commandText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key based on the serial and channel
|
||||
* This is can be used to find duplicated commands in the queue
|
||||
*/
|
||||
private static String getKey(String serialNumber, ChannelUID channelUID) {
|
||||
return serialNumber + "-" + channelUID.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key based on the serial and channel
|
||||
* This is can be used to find duplicated commands in the queue
|
||||
*/
|
||||
private static String getKey(String serialNumber, CubeCommand cubeCommand) {
|
||||
String key = serialNumber + "-" + cubeCommand.getClass().getSimpleName();
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the key based on the serial and channel
|
||||
* This is can be used to find duplicated commands in the queue
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public @Nullable ChannelUID getChannelUID() {
|
||||
return channelUID;
|
||||
}
|
||||
|
||||
public void setChannelUID(ChannelUID channelUID) {
|
||||
this.channelUID = channelUID;
|
||||
key = getKey(serialNumber, channelUID);
|
||||
}
|
||||
|
||||
public @Nullable Command getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public void setCommand(Command command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public @Nullable CubeCommand getCubeCommand() {
|
||||
return cubeCommand;
|
||||
}
|
||||
|
||||
public String getDeviceSerial() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public void setDeviceSerial(String device) {
|
||||
this.serialNumber = device;
|
||||
final ChannelUID channelUID = this.channelUID;
|
||||
if (channelUID != null) {
|
||||
key = getKey(serialNumber, channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
public String getCommandText() {
|
||||
return commandText;
|
||||
}
|
||||
|
||||
public void setCommandText(String commandText) {
|
||||
this.commandText = commandText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
return sb.append("id: ").append(id).append(", channelUID: ").append(channelUID).append(", command: ")
|
||||
.append(command).append(", cubeCommand: ").append(cubeCommand).append(", serialNumber: ")
|
||||
.append(serialNumber).append(", key: ").append(key).append(", commandText: ").append(commandText)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The {@link AMessage} Acknowledge the execution of a command
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class AMessage extends Message {
|
||||
|
||||
public AMessage(String raw) {
|
||||
super(raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.trace("=== A Message === ");
|
||||
logger.trace("\tRAW : {}", this.getPayload());
|
||||
logger.debug("Cube Command Acknowledged");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.A;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link CMessage} contains configuration about a MAX! device.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - Detailed parsing, OH2 Update
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class CMessage extends Message {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CMessage.class);
|
||||
|
||||
private String rfAddress;
|
||||
private int length;
|
||||
private DeviceType deviceType;
|
||||
private int roomId = -1;
|
||||
private String serialNumber;
|
||||
private BigDecimal tempComfort = BigDecimal.ZERO;
|
||||
private BigDecimal tempEco = BigDecimal.ZERO;
|
||||
private BigDecimal tempSetpointMax = BigDecimal.ZERO;
|
||||
private BigDecimal tempSetpointMin = BigDecimal.ZERO;
|
||||
private BigDecimal tempOffset = BigDecimal.ZERO;
|
||||
private BigDecimal tempOpenWindow = BigDecimal.ZERO;
|
||||
private BigDecimal durationOpenWindow = BigDecimal.ZERO;
|
||||
private BigDecimal decalcification = BigDecimal.ZERO;
|
||||
private BigDecimal valveMaximum = BigDecimal.ZERO;
|
||||
private BigDecimal valveOffset = BigDecimal.ZERO;
|
||||
private BigDecimal boostDuration = BigDecimal.ZERO;
|
||||
private BigDecimal boostValve = BigDecimal.ZERO;
|
||||
private String programData = "";
|
||||
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
public CMessage(String raw) {
|
||||
super(raw);
|
||||
String[] tokens = this.getPayload().split(Message.DELIMETER);
|
||||
|
||||
rfAddress = tokens[0];
|
||||
|
||||
byte[] bytes = Base64.getDecoder().decode(tokens[1].getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
int[] data = new int[bytes.length];
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
data[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
|
||||
length = data[0];
|
||||
if (length != data.length - 1) {
|
||||
logger.debug("C Message malformed: wrong data length. Expected bytes {}, actual bytes {}", length,
|
||||
data.length - 1);
|
||||
}
|
||||
|
||||
String rfAddress2 = Utils.toHex(data[1], data[2], data[3]);
|
||||
if (!rfAddress.toUpperCase().equals(rfAddress2.toUpperCase())) {
|
||||
logger.debug("C Message malformed: wrong RF address. Expected address {}, actual address {}",
|
||||
rfAddress.toUpperCase(), rfAddress2.toUpperCase());
|
||||
}
|
||||
|
||||
deviceType = DeviceType.create(data[4]);
|
||||
roomId = data[5] & 0xFF;
|
||||
|
||||
serialNumber = getSerialNumber(bytes);
|
||||
if (deviceType == DeviceType.HeatingThermostatPlus || deviceType == DeviceType.HeatingThermostat
|
||||
|| deviceType == DeviceType.WallMountedThermostat) {
|
||||
parseHeatingThermostatData(bytes);
|
||||
}
|
||||
|
||||
if (deviceType == DeviceType.Cube) {
|
||||
parseCubeData(bytes);
|
||||
}
|
||||
if (deviceType == DeviceType.EcoSwitch || deviceType == DeviceType.ShutterContact) {
|
||||
logger.trace("Device {} type {} Data: '{}'", rfAddress, deviceType, parseData(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
private String getSerialNumber(byte[] bytes) {
|
||||
byte[] sn = new byte[10];
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
sn[i] = bytes[i + 8];
|
||||
}
|
||||
|
||||
return new String(sn, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String parseData(byte[] bytes) {
|
||||
if (bytes.length <= 18) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
int dataStart = 18;
|
||||
byte[] sn = new byte[bytes.length - dataStart];
|
||||
|
||||
for (int i = 0; i < sn.length; i++) {
|
||||
sn[i] = bytes[i + dataStart];
|
||||
}
|
||||
logger.trace("DataBytes: {}", Utils.getHex(sn));
|
||||
return new String(sn, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during parsing: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void parseCubeData(byte[] bytes) {
|
||||
if (bytes.length != 238) {
|
||||
logger.debug("Unexpected lenght for Cube message {}, expected: 238", bytes.length);
|
||||
}
|
||||
try {
|
||||
properties.put("Portal Enabled", Integer.toString(bytes[0x18] & 0xFF));
|
||||
properties.put("Portal URL",
|
||||
new String(Arrays.copyOfRange(bytes, 0x55, 0xD5), StandardCharsets.UTF_8).trim());
|
||||
properties.put("TimeZone (Winter)",
|
||||
new String(Arrays.copyOfRange(bytes, 0xD6, 0xDA), StandardCharsets.UTF_8).trim());
|
||||
properties.put("TimeZone (Daylight)",
|
||||
new String(Arrays.copyOfRange(bytes, 0x00E2, 0x00E6), StandardCharsets.UTF_8).trim());
|
||||
|
||||
properties.put("Unknown1", Utils.getHex(Arrays.copyOfRange(bytes, 0x13, 0x33))); // Pushbutton Up config
|
||||
// 0=auto, 1=eco, 2=comfort
|
||||
properties.put("Unknown2", Utils.getHex(Arrays.copyOfRange(bytes, 0x34, 0x54))); // Pushbutton down config
|
||||
// 0=auto, 1=eco, 2=comfort
|
||||
properties.put("Winter Time", parseTimeInfo(Arrays.copyOfRange(bytes, 0xDB, 0xE2), "Winter").toString()); // Date
|
||||
// of
|
||||
// wintertime
|
||||
properties.put("Summer Time", parseTimeInfo(Arrays.copyOfRange(bytes, 0xE7, 0xEF), "Summer").toString()); // Date
|
||||
// of
|
||||
// summertime
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during parsing: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Date parseTimeInfo(byte[] bytes, String suffix) {
|
||||
int month = bytes[0] & 0xFF;
|
||||
int weekDay = bytes[1] & 0xFF;
|
||||
int hour = bytes[2] & 0xFF;
|
||||
int utcOffset = new BigInteger(Arrays.copyOfRange(bytes, 0x03, 0x07)).intValue();
|
||||
properties.put("Utc Offset" + " (" + suffix + ")", utcOffset);
|
||||
Calendar pCal = Calendar.getInstance();
|
||||
pCal.set(Calendar.getInstance().get(Calendar.YEAR), month - 1, 15, hour, 0, 0);
|
||||
pCal.set(Calendar.DAY_OF_WEEK, weekDay + 1);
|
||||
pCal.set(Calendar.DAY_OF_WEEK_IN_MONTH, -1);
|
||||
return pCal.getTime();
|
||||
}
|
||||
|
||||
private void parseHeatingThermostatData(byte[] bytes) {
|
||||
try {
|
||||
int plusDataStart = 18;
|
||||
int programDataStart = 11;
|
||||
tempComfort = new BigDecimal((bytes[plusDataStart] & 0xFF) / 2D);
|
||||
tempEco = new BigDecimal((bytes[plusDataStart + 1] & 0xFF) / 2D);
|
||||
tempSetpointMax = new BigDecimal((bytes[plusDataStart + 2] & 0xFF) / 2D);
|
||||
tempSetpointMin = new BigDecimal((bytes[plusDataStart + 3] & 0xFF) / 2D);
|
||||
properties.put(PROPERTY_THERMO_COMFORT_TEMP, tempComfort.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_ECO_TEMP, tempEco.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_MAX_TEMP_SETPOINT, tempSetpointMax.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_MIN_TEMP_SETPOINT, tempSetpointMin.setScale(1, RoundingMode.HALF_DOWN));
|
||||
if (bytes.length < 211) {
|
||||
// Device is a WallMountedThermostat
|
||||
programDataStart = 4;
|
||||
logger.trace("WallThermostat byte {}: {}", bytes.length - 3,
|
||||
Float.toString(bytes[bytes.length - 3] & 0xFF));
|
||||
logger.trace("WallThermostat byte {}: {}", bytes.length - 2,
|
||||
Float.toString(bytes[bytes.length - 2] & 0xFF));
|
||||
logger.trace("WallThermostat byte {}: {}", bytes.length - 1,
|
||||
Float.toString(bytes[bytes.length - 1] & 0xFF));
|
||||
} else {
|
||||
// Device is a HeatingThermostat(+)
|
||||
tempOffset = new BigDecimal((bytes[plusDataStart + 4] & 0xFF) / 2D - 3.5);
|
||||
tempOpenWindow = new BigDecimal((bytes[plusDataStart + 5] & 0xFF) / 2D);
|
||||
durationOpenWindow = new BigDecimal((bytes[plusDataStart + 6] & 0xFF) * 5);
|
||||
boostDuration = new BigDecimal(bytes[plusDataStart + 7] & 0xFF >> 5);
|
||||
boostValve = new BigDecimal((bytes[plusDataStart + 7] & 0x1F) * 5);
|
||||
decalcification = new BigDecimal(bytes[plusDataStart + 8]);
|
||||
valveMaximum = new BigDecimal((bytes[plusDataStart + 9] & 0xFF) * 100 / 255);
|
||||
valveOffset = new BigDecimal((bytes[plusDataStart + 10] & 0xFF) * 100 / 255);
|
||||
properties.put(PROPERTY_THERMO_OFFSET_TEMP, tempOffset.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_WINDOW_OPEN_TEMP, tempOpenWindow.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_WINDOW_OPEN_DURATION,
|
||||
durationOpenWindow.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_BOOST_DURATION, boostDuration.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_BOOST_VALVEPOS, boostValve.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_DECALCIFICATION, decalcification.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_VALVE_MAX, valveMaximum.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_VALVE_OFFSET, valveOffset.setScale(0, RoundingMode.HALF_DOWN));
|
||||
}
|
||||
programData = "";
|
||||
int ln = 13 * 6; // first day = Sat
|
||||
String startTime = "00:00h";
|
||||
for (int charIdx = plusDataStart + programDataStart; charIdx < (plusDataStart + programDataStart
|
||||
+ 26 * 7); charIdx++) {
|
||||
if (ln % 13 == 0) {
|
||||
programData += "\r\n Day " + Integer.toString((ln / 13) % 7) + ": ";
|
||||
startTime = "00:00h";
|
||||
}
|
||||
int progTime = (bytes[charIdx + 1] & 0xFF) * 5 + (bytes[charIdx] & 0x01) * 1280;
|
||||
int progMinutes = progTime % 60;
|
||||
int progHours = (progTime - progMinutes) / 60;
|
||||
String endTime = Integer.toString(progHours) + ":" + String.format("%02d", progMinutes) + "h";
|
||||
programData += startTime + "-" + endTime + " " + Double.toString(bytes[charIdx] / 4) + "C ";
|
||||
startTime = endTime;
|
||||
charIdx++;
|
||||
ln++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during heater data: {}", e.getMessage(), e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.C;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public DeviceType getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public int getRoomID() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.debug("=== C Message === ");
|
||||
logger.trace("\tRAW: {}", this.getPayload());
|
||||
logger.debug("DeviceType: {}", deviceType);
|
||||
logger.debug("SerialNumber: {}", serialNumber);
|
||||
logger.debug("RFAddress: {}", rfAddress);
|
||||
logger.debug("RoomID: {}", roomId);
|
||||
for (String key : properties.keySet()) {
|
||||
if (!key.startsWith("Unknown")) {
|
||||
String propertyName = StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(key), ' ');
|
||||
logger.debug("{}: {}", propertyName, properties.get(key));
|
||||
} else {
|
||||
logger.debug("{}: {}", key, properties.get(key));
|
||||
}
|
||||
}
|
||||
if (programData != null) {
|
||||
logger.trace("ProgramData: {}", programData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The {@link: FMessage} contains information about the Cube NTP Configuration
|
||||
* This is the response to a f: command
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class FMessage extends Message {
|
||||
|
||||
private String ntpServer1 = "";
|
||||
private String ntpServer2 = "";
|
||||
|
||||
/**
|
||||
* The {@link: FMessage} contains information about the Cube NTP Configuration
|
||||
*
|
||||
* @param raw String with raw message
|
||||
*/
|
||||
public FMessage(String raw) {
|
||||
super(raw);
|
||||
|
||||
final String[] servers = this.getPayload().split(",");
|
||||
if (servers.length > 0) {
|
||||
ntpServer1 = servers[0];
|
||||
}
|
||||
if (servers.length > 1) {
|
||||
ntpServer2 = servers[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the NTP Server1 name
|
||||
*/
|
||||
public String getNtpServer1() {
|
||||
return ntpServer1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the NTP Server2 name
|
||||
*/
|
||||
public String getNtpServer2() {
|
||||
return ntpServer2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.debug("=== F Message === ");
|
||||
logger.trace("\tRAW : {}", this.getPayload());
|
||||
logger.debug("\tNTP Server1 : {}", this.ntpServer1);
|
||||
logger.debug("\tNTP Server2 : {}", this.ntpServer2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.F;
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.message;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The H message contains information about the MAX! Cube.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - Details parsing, OH2 version
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class HMessage extends Message {
|
||||
|
||||
private ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.systemDefault());
|
||||
public Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
private String rawSerialNumber;
|
||||
private String rawRFAddress;
|
||||
private String rawFirmwareVersion;
|
||||
private String rawConnectionId;
|
||||
private String rawDutyCycle;
|
||||
private String rawFreeMemorySlots;
|
||||
private String rawCubeTimeState;
|
||||
private String rawNTPCounter;
|
||||
|
||||
// yet unknown fields
|
||||
private String rawUnknownfield4;
|
||||
|
||||
public HMessage(String raw) {
|
||||
super(raw);
|
||||
|
||||
String[] tokens = this.getPayload().split(Message.DELIMETER);
|
||||
|
||||
if (tokens.length < 11) {
|
||||
throw new ArrayIndexOutOfBoundsException("MAX!Cube raw H Message corrupt");
|
||||
}
|
||||
|
||||
rawSerialNumber = tokens[0];
|
||||
rawRFAddress = tokens[1];
|
||||
rawFirmwareVersion = tokens[2].substring(0, 2) + "." + tokens[2].substring(2, 4);
|
||||
rawUnknownfield4 = tokens[3];
|
||||
rawConnectionId = tokens[4];
|
||||
rawDutyCycle = Integer.toString(Utils.fromHex(tokens[5]));
|
||||
rawFreeMemorySlots = Integer.toString(Utils.fromHex(tokens[6]));
|
||||
|
||||
setDateTime(tokens[7], tokens[8]);
|
||||
|
||||
rawCubeTimeState = tokens[9];
|
||||
rawNTPCounter = Integer.toString(Utils.fromHex(tokens[10]));
|
||||
properties.put("Serial number", rawSerialNumber);
|
||||
properties.put("RF address (HEX)", rawRFAddress);
|
||||
properties.put("Firmware version", rawFirmwareVersion);
|
||||
properties.put("Connection ID", rawConnectionId);
|
||||
properties.put("Unknown", rawUnknownfield4);
|
||||
properties.put("Duty Cycle", rawDutyCycle);
|
||||
properties.put("FreeMemorySlots", rawFreeMemorySlots);
|
||||
properties.put("CubeTimeState", rawCubeTimeState);
|
||||
properties.put("NTPCounter", rawNTPCounter);
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return rawSerialNumber;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rawRFAddress;
|
||||
}
|
||||
|
||||
public String getFirmwareVersion() {
|
||||
return rawFirmwareVersion;
|
||||
}
|
||||
|
||||
public String getConnectionId() {
|
||||
return rawConnectionId;
|
||||
}
|
||||
|
||||
public int getDutyCycle() {
|
||||
return Integer.parseInt(rawDutyCycle);
|
||||
}
|
||||
|
||||
public int getFreeMemorySlots() {
|
||||
return Integer.parseInt(rawFreeMemorySlots);
|
||||
}
|
||||
|
||||
public String getCubeTimeState() {
|
||||
return rawCubeTimeState;
|
||||
}
|
||||
|
||||
public String getNTPCounter() {
|
||||
return rawNTPCounter;
|
||||
}
|
||||
|
||||
private final void setDateTime(String hexDate, String hexTime) {
|
||||
// we have to add 2000, otherwise we get a wrong timestamp
|
||||
int year = 2000 + Utils.fromHex(hexDate.substring(0, 2));
|
||||
int month = Utils.fromHex(hexDate.substring(2, 4));
|
||||
int dayOfMonth = Utils.fromHex(hexDate.substring(4, 6));
|
||||
|
||||
int hours = Utils.fromHex(hexTime.substring(0, 2));
|
||||
int minutes = Utils.fromHex(hexTime.substring(2, 4));
|
||||
|
||||
zonedDateTime = ZonedDateTime.of(year, month, dayOfMonth, hours, minutes, 0, 0, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public Date getDateTime() {
|
||||
return Date.from(zonedDateTime.toInstant());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.debug("=== H Message === ");
|
||||
logger.trace("\tRAW: : {}", getPayload());
|
||||
logger.trace("\tReading Time : {}", getDateTime());
|
||||
for (String key : properties.keySet()) {
|
||||
logger.debug("\t{}: {}", key, properties.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.H;
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.message;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.DeviceConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The L message contains real time information about all MAX! devices.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class LMessage extends Message {
|
||||
|
||||
public LMessage(String raw) {
|
||||
super(raw);
|
||||
}
|
||||
|
||||
public Collection<? extends Device> getDevices(List<DeviceConfiguration> configurations) {
|
||||
final List<Device> devices = new ArrayList<>();
|
||||
|
||||
final byte[] decodedRawMessage = Base64.getDecoder()
|
||||
.decode(getPayload().trim().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
final MaxTokenizer tokenizer = new MaxTokenizer(decodedRawMessage);
|
||||
|
||||
while (tokenizer.hasMoreElements()) {
|
||||
byte[] token = tokenizer.nextElement();
|
||||
final Device tempDevice = Device.create(token, configurations);
|
||||
if (tempDevice != null) {
|
||||
devices.add(tempDevice);
|
||||
}
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
public Collection<? extends Device> updateDevices(List<Device> devices, List<DeviceConfiguration> configurations) {
|
||||
byte[] decodedRawMessage = Base64.getDecoder().decode(getPayload().trim().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
MaxTokenizer tokenizer = new MaxTokenizer(decodedRawMessage);
|
||||
|
||||
while (tokenizer.hasMoreElements()) {
|
||||
byte[] token = tokenizer.nextElement();
|
||||
String rfAddress = Utils.toHex(token[0] & 0xFF, token[1] & 0xFF, token[2] & 0xFF);
|
||||
// logger.debug("token: "+token+" rfaddress: "+rfAddress);
|
||||
|
||||
Device foundDevice = null;
|
||||
for (Device device : devices) {
|
||||
// logger.debug(device.getRFAddress().toUpperCase()+ " vs "+rfAddress);
|
||||
if (device.getRFAddress().toUpperCase().equals(rfAddress)) {
|
||||
// logger.debug("Updating device..."+rfAddress);
|
||||
foundDevice = device;
|
||||
}
|
||||
}
|
||||
if (foundDevice != null) {
|
||||
foundDevice = Device.update(token, configurations, foundDevice);
|
||||
// devices.remove(token);
|
||||
// devices.add(foundDevice);
|
||||
} else {
|
||||
Device tempDevice = Device.create(token, configurations);
|
||||
if (tempDevice != null) {
|
||||
devices.add(tempDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.trace("=== L Message === ");
|
||||
logger.trace("\tRAW: {}", this.getPayload());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.L;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.DeviceInformation;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.openhab.binding.max.internal.device.RoomInformation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The M message contains metadata about the MAX! Cube setup.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial Contribution
|
||||
* @author Marcel Verpaalen - Room details parse
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class MMessage extends Message {
|
||||
private final Logger logger = LoggerFactory.getLogger(MMessage.class);
|
||||
|
||||
public List<RoomInformation> rooms = new ArrayList<>();
|
||||
public List<DeviceInformation> devices = new ArrayList<>();
|
||||
private Boolean hasConfiguration;
|
||||
|
||||
public MMessage(String raw) {
|
||||
super(raw);
|
||||
hasConfiguration = false;
|
||||
|
||||
String[] tokens = this.getPayload().split(Message.DELIMETER);
|
||||
|
||||
if (tokens.length <= 1) {
|
||||
logger.debug("No rooms defined. Configure your Max! Cube");
|
||||
hasConfiguration = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
byte[] bytes = Base64.getDecoder().decode(tokens[2].trim().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
hasConfiguration = true;
|
||||
logger.trace("*** M Message trace**** ");
|
||||
logger.trace("\tMagic? (expect 86) : {}", (int) bytes[0]);
|
||||
logger.trace("\tVersion? (expect 2): {}", (int) bytes[1]);
|
||||
logger.trace("\t#defined rooms in M: {}", (int) bytes[2]);
|
||||
|
||||
rooms = new ArrayList<>();
|
||||
devices = new ArrayList<>();
|
||||
|
||||
int roomCount = bytes[2];
|
||||
|
||||
int byteOffset = 3; // start of rooms
|
||||
|
||||
/* process room */
|
||||
|
||||
for (int i = 0; i < roomCount; i++) {
|
||||
|
||||
int position = bytes[byteOffset++];
|
||||
|
||||
int nameLength = bytes[byteOffset++] & 0xff;
|
||||
byte[] data = new byte[nameLength];
|
||||
System.arraycopy(bytes, byteOffset, data, 0, nameLength);
|
||||
byteOffset += nameLength;
|
||||
String name = new String(data, StandardCharsets.UTF_8);
|
||||
|
||||
String rfAddress = Utils.toHex((bytes[byteOffset] & 0xff), (bytes[byteOffset + 1] & 0xff),
|
||||
(bytes[byteOffset + 2] & 0xff));
|
||||
byteOffset += 3;
|
||||
|
||||
rooms.add(new RoomInformation(position, name, rfAddress));
|
||||
}
|
||||
|
||||
/* process devices */
|
||||
|
||||
int deviceCount = bytes[byteOffset++];
|
||||
|
||||
for (int deviceId = 0; deviceId < deviceCount; deviceId++) {
|
||||
DeviceType deviceType = DeviceType.create(bytes[byteOffset++]);
|
||||
|
||||
String rfAddress = Utils.toHex((bytes[byteOffset] & 0xff), (bytes[byteOffset + 1] & 0xff),
|
||||
(bytes[byteOffset + 2] & 0xff));
|
||||
byteOffset += 3;
|
||||
|
||||
final StringBuilder serialNumberBuilder = new StringBuilder(10);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
serialNumberBuilder.append((char) bytes[byteOffset++]);
|
||||
}
|
||||
|
||||
int nameLength = bytes[byteOffset++] & 0xff;
|
||||
byte[] data = new byte[nameLength];
|
||||
System.arraycopy(bytes, byteOffset, data, 0, nameLength);
|
||||
byteOffset += nameLength;
|
||||
String deviceName = new String(data, StandardCharsets.UTF_8);
|
||||
|
||||
int roomId = bytes[byteOffset++] & 0xff;
|
||||
devices.add(new DeviceInformation(deviceType, serialNumberBuilder.toString(), rfAddress, deviceName,
|
||||
roomId));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Unknown error parsing the M Message: {}", e.getMessage(), e);
|
||||
logger.debug("\tRAW : {}", this.getPayload());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.debug("=== M Message === ");
|
||||
if (hasConfiguration) {
|
||||
logger.trace("\tRAW : {}", this.getPayload());
|
||||
for (RoomInformation room : rooms) {
|
||||
logger.debug("\t=== Rooms ===");
|
||||
logger.debug("\tRoom Pos : {}", room.getPosition());
|
||||
logger.debug("\tRoom Name : {}", room.getName());
|
||||
logger.debug("\tRoom RF Adr: {}", room.getRFAddress());
|
||||
for (DeviceInformation device : devices) {
|
||||
if (room.getPosition() == device.getRoomId()) {
|
||||
logger.debug("\t=== Devices ===");
|
||||
logger.debug("\tDevice Type : {}", device.getDeviceType());
|
||||
logger.debug("\tDevice Name : {}", device.getName());
|
||||
logger.debug("\tDevice Serialnr: {}", device.getSerialNumber());
|
||||
logger.debug("\tDevice RF Adr : {}", device.getRFAddress());
|
||||
logger.debug("\tRoom Id : {}", device.getRoomId());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("M Message empty. No Configuration");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.M;
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.message;
|
||||
|
||||
import java.util.Enumeration;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The MaxTokenizer parses a L message into the MAX! devices encoded within. The L message contains
|
||||
* real time information for multiple devices. Each device starts with the length n bytes.
|
||||
* The MaxTokenzier starts with the first device and chops off one device after another from the byte stream.
|
||||
*
|
||||
* The tokens returned consist of the payload solely, and do not contain the first byte holding the
|
||||
* tokens length.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class MaxTokenizer implements Enumeration<byte[]> {
|
||||
|
||||
private int offset;
|
||||
|
||||
private byte[] decodedRawMessage;
|
||||
|
||||
/**
|
||||
* Creates a new MaxTokenizer.
|
||||
*
|
||||
* @param decodedRawMessage
|
||||
* The Base64 decoded MAX! Cube protocol L message as byte array
|
||||
*/
|
||||
public MaxTokenizer(byte[] decodedRawMessage) {
|
||||
this.decodedRawMessage = decodedRawMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return offset < decodedRawMessage.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] nextElement() {
|
||||
byte length = decodedRawMessage[offset++];
|
||||
|
||||
// make sure to get the correct length in case > 127
|
||||
byte[] token = new byte[length & 0xFF];
|
||||
|
||||
for (int i = 0; i < (length & 0xFF); i++) {
|
||||
token[i] = decodedRawMessage[offset++];
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Base message for the messages received from the MAX! Cube.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class Message {
|
||||
|
||||
public static final String DELIMETER = ",";
|
||||
|
||||
private final String raw;
|
||||
|
||||
public Message(String raw) {
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
public abstract void debug(Logger logger);
|
||||
|
||||
public abstract MessageType getType();
|
||||
|
||||
protected final String getPayload() {
|
||||
return raw.substring(2, raw.length());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.exceptions.IncompleteMessageException;
|
||||
import org.openhab.binding.max.internal.exceptions.IncorrectMultilineIndexException;
|
||||
import org.openhab.binding.max.internal.exceptions.MessageIsWaitingException;
|
||||
import org.openhab.binding.max.internal.exceptions.NoMessageAvailableException;
|
||||
import org.openhab.binding.max.internal.exceptions.UnprocessableMessageException;
|
||||
import org.openhab.binding.max.internal.exceptions.UnsupportedMessageTypeException;
|
||||
|
||||
/**
|
||||
* The message processor was introduced to combine multiple received lines to
|
||||
* one single message. There are cases, when the MAX! Cube sends multiple
|
||||
* messages (M-Message for example). The message processor acts as stack for
|
||||
* received messages. Every received line should be added to the processor.
|
||||
* After every added line, the message processor analyses the line. It is not
|
||||
* possible to add additional lines when there is a message ready to be
|
||||
* processed.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageProcessor {
|
||||
|
||||
public static final String SEPARATOR = ":";
|
||||
|
||||
/**
|
||||
* The message that was created from last line received. (Null if no message
|
||||
* available yet)
|
||||
*/
|
||||
|
||||
private @Nullable Message currentMessage;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* If more that one single line is required to create a message
|
||||
* numberOfRequiredLines holds the number of required messages to complete
|
||||
* receivedLines holds the lines received so far
|
||||
* currentMessageType indicates which message type is currently on stack
|
||||
* </pre>
|
||||
*/
|
||||
private @Nullable Integer numberOfRequiredLines;
|
||||
private List<String> receivedLines = new ArrayList<>();
|
||||
private @Nullable MessageType currentMessageType;
|
||||
|
||||
/**
|
||||
* Resets the current status and processed lines. Should be used after
|
||||
* processing a message
|
||||
*/
|
||||
public void reset() {
|
||||
this.currentMessage = null;
|
||||
receivedLines.clear();
|
||||
currentMessageType = null;
|
||||
numberOfRequiredLines = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyses the line and creates a message when possible. If the line
|
||||
* indicates, that additional lines are required to create a complete
|
||||
* message, the message processor keeps the line in memory and awaits
|
||||
* additional lines. If the new line does not fit into current state
|
||||
* (incomplete M: message on stack but L: message line received) a
|
||||
* IncompleteMessageException is thrown.
|
||||
*
|
||||
* @param line
|
||||
* is the new line received
|
||||
* @return true if a message could be created by this line, false in any
|
||||
* other cases (line was stacked, error, ...)
|
||||
* @throws MessageIsWaitingException
|
||||
* when a line was added without pulling the previous message
|
||||
* @throws IncompleteMessageException
|
||||
* when a line was added that does not belong to current message
|
||||
* stack
|
||||
* @throws UnsupportedMessageTypeException
|
||||
* in case the line starts with an unknown message indicator
|
||||
* @throws UnprocessableMessageException
|
||||
* is thrown when there was a known message indicator found, but
|
||||
* message could not be parsed correctly.
|
||||
* @throws IncorrectMultilineIndexException
|
||||
*/
|
||||
public Boolean addReceivedLine(String line) throws IncompleteMessageException, MessageIsWaitingException,
|
||||
UnsupportedMessageTypeException, UnprocessableMessageException, IncorrectMultilineIndexException {
|
||||
if (this.currentMessage != null) {
|
||||
throw new MessageIsWaitingException();
|
||||
}
|
||||
|
||||
MessageType messageType = getMessageType(line);
|
||||
|
||||
if (messageType == null) {
|
||||
throw new UnsupportedMessageTypeException();
|
||||
}
|
||||
|
||||
if ((this.currentMessageType != null) && (!messageType.equals(this.currentMessageType))) {
|
||||
throw new IncompleteMessageException();
|
||||
}
|
||||
|
||||
Boolean result = true;
|
||||
|
||||
switch (messageType) {
|
||||
case H:
|
||||
this.currentMessage = new HMessage(line);
|
||||
break;
|
||||
case C:
|
||||
this.currentMessage = new CMessage(line);
|
||||
break;
|
||||
case L:
|
||||
this.currentMessage = new LMessage(line);
|
||||
break;
|
||||
case S:
|
||||
this.currentMessage = new SMessage(line);
|
||||
break;
|
||||
case M:
|
||||
result = handleMMessageLine(line);
|
||||
break;
|
||||
case N:
|
||||
this.currentMessage = new NMessage(line);
|
||||
break;
|
||||
case F:
|
||||
this.currentMessage = new FMessage(line);
|
||||
break;
|
||||
case A:
|
||||
this.currentMessage = new AMessage(line);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Boolean handleMMessageLine(String line)
|
||||
throws UnprocessableMessageException, IncompleteMessageException, IncorrectMultilineIndexException {
|
||||
Boolean result = false;
|
||||
|
||||
String[] tokens = line.split(Message.DELIMETER); // M:00,01,xyz.....
|
||||
|
||||
try {
|
||||
Integer index = Integer.valueOf(tokens[0].replaceFirst("M:", "")); // M:00
|
||||
Integer counter = Integer.valueOf(tokens[1]); // 01
|
||||
|
||||
if (this.numberOfRequiredLines == null) {
|
||||
switch (counter) {
|
||||
case 0:
|
||||
throw new UnprocessableMessageException();
|
||||
case 1:
|
||||
this.currentMessage = new MMessage(line);
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
this.numberOfRequiredLines = counter;
|
||||
this.currentMessageType = MessageType.M;
|
||||
if (index == 0) {
|
||||
this.receivedLines.add(line);
|
||||
} else {
|
||||
throw new IncorrectMultilineIndexException();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((!counter.equals(this.numberOfRequiredLines)) || (!(index == this.receivedLines.size()))) {
|
||||
throw new IncorrectMultilineIndexException();
|
||||
}
|
||||
|
||||
receivedLines.add(tokens[2]);
|
||||
|
||||
if (index + 1 == receivedLines.size()) {
|
||||
String newLine = "";
|
||||
for (String curLine : receivedLines) {
|
||||
newLine += curLine;
|
||||
}
|
||||
this.currentMessage = new MMessage(newLine);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
} catch (IncorrectMultilineIndexException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
throw new UnprocessableMessageException();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if there is a message waiting to be pulled
|
||||
*/
|
||||
public boolean isMessageAvailable() {
|
||||
return this.currentMessage != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls the message from the stack when there is one available. This needs
|
||||
* to be done before next line can be added into message processor. When
|
||||
* message is pulled, the message processor is reseted and ready to process
|
||||
* next line.
|
||||
*
|
||||
* @return Message
|
||||
* @throws NoMessageAvailableException
|
||||
* when there was no message on the stack
|
||||
*/
|
||||
@Nullable
|
||||
public Message pull() throws NoMessageAvailableException {
|
||||
final Message result = this.currentMessage;
|
||||
if (this.currentMessage == null) {
|
||||
throw new NoMessageAvailableException();
|
||||
}
|
||||
reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the raw TCP data read from the MAX protocol, returning the
|
||||
* corresponding MessageType.
|
||||
*
|
||||
* @param line
|
||||
* the raw data provided read from the MAX protocol
|
||||
* @return MessageType of the line added
|
||||
*/
|
||||
@Nullable
|
||||
private static MessageType getMessageType(String line) {
|
||||
for (MessageType msgType : MessageType.values()) {
|
||||
if (line.startsWith(msgType.name() + SEPARATOR)) {
|
||||
return msgType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This enumeration represents the different message types provided by the MAX! Cube protocol.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum MessageType {
|
||||
H,
|
||||
M,
|
||||
C,
|
||||
L,
|
||||
S,
|
||||
N,
|
||||
F,
|
||||
A
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link: NMessage} contains information about a newly discovered Device
|
||||
* This is the response to a n: command
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class NMessage extends Message {
|
||||
private final Logger logger = LoggerFactory.getLogger(NMessage.class);
|
||||
|
||||
private String decodedPayload = "";
|
||||
private @Nullable DeviceType deviceType;
|
||||
private String rfAddress = "";
|
||||
private String serialnr = "";
|
||||
|
||||
/**
|
||||
* The {@link: NMessage} contains information about a newly discovered Device
|
||||
*
|
||||
* @param raw String with raw message
|
||||
*/
|
||||
public NMessage(String raw) {
|
||||
super(raw);
|
||||
String msgPayload = this.getPayload();
|
||||
|
||||
if (msgPayload.length() > 0) {
|
||||
try {
|
||||
byte[] bytes = Base64.getDecoder().decode(msgPayload.trim());
|
||||
decodedPayload = new String(bytes, StandardCharsets.UTF_8);
|
||||
|
||||
deviceType = DeviceType.create(bytes[0] & 0xFF);
|
||||
rfAddress = Utils.toHex(bytes[1] & 0xFF, bytes[2] & 0xFF, bytes[3] & 0xFF);
|
||||
|
||||
byte[] data = new byte[10];
|
||||
System.arraycopy(bytes, 4, data, 0, 10);
|
||||
serialnr = new String(data, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during parsing of N message: {}", e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("No device found during inclusion");
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable DeviceType getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
public String getRfAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialnr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
if (!this.rfAddress.isEmpty()) {
|
||||
logger.debug("=== N Message === ");
|
||||
logger.trace("\tRAW : {}", this.decodedPayload);
|
||||
logger.debug("\tDevice Type : {}", this.deviceType);
|
||||
logger.debug("\tRF Address : {}", this.rfAddress);
|
||||
logger.debug("\tSerial : {}", this.serialnr);
|
||||
} else {
|
||||
logger.trace("=== N Message === ");
|
||||
logger.trace("\tRAW : {}", this.decodedPayload);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.N;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SMessage} contains information about Command execution results
|
||||
*
|
||||
* @author Bernd Michael Helm (bernd.helm at helmundwalter.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 version + parsing of the message
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class SMessage extends Message {
|
||||
private final Logger logger = LoggerFactory.getLogger(SMessage.class);
|
||||
|
||||
private int dutyCycle;
|
||||
private int freeMemorySlots;
|
||||
private boolean commandDiscarded = false;
|
||||
|
||||
public SMessage(String raw) {
|
||||
super(raw);
|
||||
|
||||
String[] tokens = this.getPayload().split(Message.DELIMETER);
|
||||
|
||||
if (tokens.length == 3) {
|
||||
try {
|
||||
dutyCycle = Integer.parseInt(tokens[0], 16);
|
||||
commandDiscarded = tokens[1].contentEquals("1");
|
||||
freeMemorySlots = Integer.parseInt(tokens[2], 16);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during parsing of S message: {}", e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Unexpected # of tolkens ({}) received in S message: {}", tokens.length, this.getPayload());
|
||||
}
|
||||
}
|
||||
|
||||
public int getDutyCycle() {
|
||||
return dutyCycle;
|
||||
}
|
||||
|
||||
public int getFreeMemorySlots() {
|
||||
return freeMemorySlots;
|
||||
}
|
||||
|
||||
public boolean isCommandDiscarded() {
|
||||
return commandDiscarded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.trace("=== S Message === ");
|
||||
logger.trace("\tRAW : {}", this.getPayload());
|
||||
logger.trace("\tDutyCycle : {}", this.dutyCycle);
|
||||
logger.trace("\tCommand Discarded : {}", this.commandDiscarded);
|
||||
logger.trace("\tFreeMemorySlots : {}", this.freeMemorySlots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.S;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="max" 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>MAX! Binding</name>
|
||||
<description>This is the binding for the eQ-3 MAX! Home Solution.</description>
|
||||
<author>Marcel Verpaalen</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,278 @@
|
||||
# binding
|
||||
binding.max.name = MAX! Heizungssteuerung Binding
|
||||
binding.max.description = Dieses Binding integriert die MAX! Heizungssteuerung (z.B. Heizkörperthermostat, Heizkörperthermostat+, Wandthermostat+, Eco Taster, Fensterkontakt).
|
||||
|
||||
# bridge types
|
||||
thing-type.max.bridge.description = MAX! Cube LAN Gateway.
|
||||
|
||||
# bridge types config groups
|
||||
thing-type.config.max.bridge.group.identification.label = Identifizierung
|
||||
thing-type.config.max.bridge.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.bridge.group.network.label = Netzwerk
|
||||
thing-type.config.max.bridge.group.network.description = Einstellungen für das Netzwerk.
|
||||
|
||||
thing-type.config.max.bridge.group.device.label = Gerät
|
||||
thing-type.config.max.bridge.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.bridge.group.actions.label = Aktionen
|
||||
thing-type.config.max.bridge.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
# bridge types config
|
||||
thing-type.config.max.bridge.ipAddress.label = IP-Adresse
|
||||
thing-type.config.max.bridge.ipAddress.description = Lokale IP-Adresse oder Hostname des MAX! Cube LAN Gateway.
|
||||
|
||||
thing-type.config.max.bridge.port.label = Port
|
||||
thing-type.config.max.bridge.port.description = Port des MAX! Cube LAN Gateway.
|
||||
|
||||
thing-type.config.max.bridge.refreshInterval.label = Abfrageintervall
|
||||
thing-type.config.max.bridge.refreshInterval.description = Intervall zur Abfrage des MAX! Cube LAN Gateway (in Sekunden).
|
||||
|
||||
thing-type.config.max.bridge.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.bridge.serialNumber.description = Seriennummer des MAX! Cube LAN Gateway.
|
||||
|
||||
thing-type.config.max.bridge.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.bridge.rfAddress.description = RF-Addresse des MAX! Cube LAN Gateway.
|
||||
|
||||
thing-type.config.max.bridge.exclusive.label = Exklusivmodus
|
||||
thing-type.config.max.bridge.exclusive.description = Setzt den Exklusivmodus zur Nutzung des MAX! Cube LAN Gateways.
|
||||
|
||||
thing-type.config.max.bridge.maxRequestsPerConnection.label = Verbindingsanzahl
|
||||
thing-type.config.max.bridge.maxRequestsPerConnection.description = Maximale Anzahl an Verbindungen zum MAX! Cube LAN Gateways.
|
||||
|
||||
thing-type.config.max.bridge.ntpServer1.label = 1. NTP-Server
|
||||
thing-type.config.max.bridge.ntpServer1.description = Erster NTP-Server zur Abfrage des Datums und der Zeit.
|
||||
|
||||
thing-type.config.max.bridge.ntpServer2.label = 2. NTP-Server
|
||||
thing-type.config.max.bridge.ntpServer2.description = Zweiter NTP-Server zur Abfrage des Datums und der Zeit.
|
||||
|
||||
thing-type.config.max.bridge.action-cubeReset.label = Werkszustand
|
||||
thing-type.config.max.bridge.action-cubeReset.description = Ermöglicht das MAX Cube LAN Gateway in den Werkszustand zurück zu versetzen.
|
||||
thing-type.config.max.bridge.action-cubeReset.option.1234 = Zurücksetzen
|
||||
thing-type.config.max.bridge.action-cubeReset.option.-1 = Keine Aktion
|
||||
|
||||
thing-type.config.max.bridge.action-cubeReboot.label = Neustart
|
||||
thing-type.config.max.bridge.action-cubeReboot.description = Ermöglicht das MAX Cube LAN Gateway neu zu starten.
|
||||
thing-type.config.max.bridge.action-cubeReboot.option.1234 = Neustarten
|
||||
thing-type.config.max.bridge.action-cubeReboot.option.-1 = Keine Aktion
|
||||
|
||||
# thing types
|
||||
thing-type.max.thermostat.label = MAX! Heizkörperthermostat
|
||||
thing-type.max.thermostat.description = MAX! Heizkörperthermostat und MAX! Heizkörperthermostat basic. Dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
|
||||
|
||||
thing-type.max.thermostatplus.label = MAX! Heizkörperthermostat+
|
||||
thing-type.max.thermostatplus.description = MAX! Heizkörperthermostat+. Dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
|
||||
|
||||
thing-type.max.wallthermostat.label = MAX! Wandthermostat+
|
||||
thing-type.max.wallthermostat.description = MAX! Wandthermostat+. Dient zur Steuerung von Heizkörperthermostaten und liefert Daten wie z.B. Temperatur.
|
||||
|
||||
thing-type.max.ecoswitch.label = MAX! Eco Taster
|
||||
thing-type.max.ecoswitch.description = MAX! Eco Taster. Dient zur Steuerung von Heizkörperthermostaten.
|
||||
|
||||
thing-type.max.shuttercontact.label = MAX! Fensterkontakt
|
||||
thing-type.max.shuttercontact.description = MAX! Fensterkontakt. Liefert Daten wie z.B. Fenster-Zustand.
|
||||
|
||||
# thing types config groups
|
||||
thing-type.config.max.thermostat.group.identification.label = Identifizierung
|
||||
thing-type.config.max.thermostat.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostat.group.device.label = Gerät
|
||||
thing-type.config.max.thermostat.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.thermostat.group.binding.label = Binding
|
||||
thing-type.config.max.thermostat.group.binding.description = Einstellungen für das Binding.
|
||||
|
||||
thing-type.config.max.thermostat.group.actions.label = Aktionen
|
||||
thing-type.config.max.thermostat.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
thing-type.config.max.thermostatplus.group.identification.label = Identifizierung
|
||||
thing-type.config.max.thermostatplus.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostatplus.group.device.label = Gerät
|
||||
thing-type.config.max.thermostatplus.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.thermostatplus.group.actions.label = Aktionen
|
||||
thing-type.config.max.thermostatplus.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
thing-type.config.max.thermostatplus.group.binding.label = Binding
|
||||
thing-type.config.max.thermostatplus.group.binding.description = Einstellungen für das Binding.
|
||||
|
||||
thing-type.config.max.wallthermostat.group.identification.label = Identifizierung
|
||||
thing-type.config.max.wallthermostat.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.wallthermostat.group.device.label = Gerät
|
||||
thing-type.config.max.wallthermostat.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.wallthermostat.group.actions.label = Aktionen
|
||||
thing-type.config.max.wallthermostat.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
thing-type.config.max.ecoswitch.group.identification.label = Identifizierung
|
||||
thing-type.config.max.ecoswitch.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.ecoswitch.group.device.label = Gerät
|
||||
thing-type.config.max.ecoswitch.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.shuttercontact.group.identification.label = Identifizierung
|
||||
thing-type.config.max.shuttercontact.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.group.device.label = Gerät
|
||||
thing-type.config.max.shuttercontact.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.shuttercontact.group.actions.label = Aktionen
|
||||
thing-type.config.max.shuttercontact.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
# thing types config
|
||||
thing-type.config.max.thermostat.room.label = Raum
|
||||
thing-type.config.max.thermostat.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.thermostat.name.label = Name
|
||||
thing-type.config.max.thermostat.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostat.comfortTemp.label = Komforttemperatur
|
||||
thing-type.config.max.thermostat.comfortTemp.description = Gibt die Komforttemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.ecoTemp.label = Absenktemperatur
|
||||
thing-type.config.max.thermostat.ecoTemp.description = Gibt die Absenktemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.offsetTemp.label = Temperatur-Offset
|
||||
thing-type.config.max.thermostat.offsetTemp.description = Gibt den Temperatur-Offset (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.maxTempSetpoint.label = Max. Solltemperatur
|
||||
thing-type.config.max.thermostat.maxTempSetpoint.description = Gibt die maximal einstellbare Solltemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.minTempSetpoint.label = Min. Solltemperatur
|
||||
thing-type.config.max.thermostat.minTempSetpoint.description = Gibt die minimal einstellbare Solltemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.windowOpenTemp.label = Fenster-Auf-Temperatur
|
||||
thing-type.config.max.thermostat.windowOpenTemp.description = Gibt die Temperatur bei geöffnetem Fenster-Zustand (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.windowOpenDuration.label = Dauer Fenster-Auf-Temperatur
|
||||
thing-type.config.max.thermostat.windowOpenDuration.description = Gibt die Dauer der Absenkung auf die Fenster-Auf-Temperatur (in Minuten) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.thermostat.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostat.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.thermostat.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostat.refreshActualRate.label = Aktualisierungsrate
|
||||
|
||||
thing-type.config.max.thermostat.action-deviceDelete.label = Gerät trennen
|
||||
thing-type.config.max.thermostat.action-deviceDelete.description = Ermöglicht das Trennen des Gerätes vom MAX! Cube LAN Gateway.
|
||||
thing-type.config.max.thermostat.action-deviceDelete.option.1234 = Löschen
|
||||
thing-type.config.max.thermostat.action-deviceDelete.option.-1 = Keine Aktion
|
||||
|
||||
thing-type.config.max.thermostatplus.room.label = Raum
|
||||
thing-type.config.max.thermostatplus.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.thermostatplus.name.label = Name
|
||||
thing-type.config.max.thermostatplus.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostatplus.comfortTemp.label = Komforttemperatur
|
||||
thing-type.config.max.thermostatplus.comfortTemp.description = Gibt die Komforttemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.ecoTemp.label = Absenktemperatur
|
||||
thing-type.config.max.thermostatplus.ecoTemp.description = Gibt die Absenktemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.offsetTemp.label = Temperatur-Offset
|
||||
thing-type.config.max.thermostatplus.offsetTemp.description = Gibt den Temperatur-Offset (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.maxTempSetpoint.label = Max. Solltemperatur
|
||||
thing-type.config.max.thermostatplus.maxTempSetpoint.description = Gibt die maximal einstellbare Solltemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.minTempSetpoint.label = Min. Solltemperatur
|
||||
thing-type.config.max.thermostatplus.minTempSetpoint.description = Gibt die minimal einstellbare Solltemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.windowOpenTemp.label = Fenster-Auf-Temperatur
|
||||
thing-type.config.max.thermostatplus.windowOpenTemp.description = Gibt die Temperatur bei geöffnetem Fenster-Zustand (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.windowOpenDuration.label = Dauer Fenster-Auf-Temperatur
|
||||
thing-type.config.max.thermostatplus.windowOpenDuration.description = Gibt die Dauer der Absenkung auf die Fenster-Auf-Temperatur (in Minuten) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.thermostatplus.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostatplus.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.thermostatplus.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostatplus.refreshActualRate.label = Aktualisierungsrate
|
||||
|
||||
thing-type.config.max.thermostatplus.action-deviceDelete.label = Gerät trennen
|
||||
thing-type.config.max.thermostatplus.action-deviceDelete.description = Ermöglicht das Trennen des Gerätes vom MAX! Cube LAN Gateway.
|
||||
thing-type.config.max.thermostatplus.action-deviceDelete.option.1234 = Löschen
|
||||
thing-type.config.max.thermostatplus.action-deviceDelete.option.-1 = Keine Aktion
|
||||
|
||||
thing-type.config.max.wallthermostat.room.label = Raum
|
||||
thing-type.config.max.wallthermostat.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.wallthermostat.name.label = Name
|
||||
thing-type.config.max.wallthermostat.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.wallthermostat.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.wallthermostat.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.wallthermostat.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.wallthermostat.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.wallthermostat.action-deviceDelete.label = Gerät trennen
|
||||
thing-type.config.max.wallthermostat.action-deviceDelete.description = Ermöglicht das Trennen des Gerätes vom MAX! Cube LAN Gateway.
|
||||
thing-type.config.max.wallthermostat.action-deviceDelete.option.1234 = Löschen
|
||||
thing-type.config.max.wallthermostat.action-deviceDelete.option.-1 = Keine Aktion
|
||||
|
||||
thing-type.config.max.ecoswitch.room.label = Raum
|
||||
thing-type.config.max.ecoswitch.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.ecoswitch.name.label = Name
|
||||
thing-type.config.max.ecoswitch.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.ecoswitch.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.ecoswitch.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.ecoswitch.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.ecoswitch.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.room.label = Raum
|
||||
thing-type.config.max.shuttercontact.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.shuttercontact.name.label = Name
|
||||
thing-type.config.max.shuttercontact.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.shuttercontact.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.shuttercontact.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.action-deviceDelete.label = Gerät trennen
|
||||
thing-type.config.max.shuttercontact.action-deviceDelete.description = Ermöglicht das Trennen des Gerätes vom MAX! Cube LAN Gateway.
|
||||
thing-type.config.max.shuttercontact.action-deviceDelete.option.1234 = Löschen
|
||||
thing-type.config.max.shuttercontact.action-deviceDelete.option.-1 = Keine Aktion
|
||||
|
||||
# channel types
|
||||
channel-type.max.free_mem.label = Freie Speicherplätze
|
||||
channel-type.max.free_mem.description = Gibt an, wie viele freie Speicherplätze im MAX! Cube LAN Gateway für Befehle zur Verfügung stehen.
|
||||
|
||||
channel-type.max.duty_cycle.label = Auslastungsgrad
|
||||
channel-type.max.duty_cycle.description = Gibt den Auslastungsgrad für Befehle (in %) an.
|
||||
|
||||
channel-type.max.valve.label = Ventil
|
||||
channel-type.max.valve.description = Gibt die Ventilöffnung des Heizkörperreglers (in %) an.
|
||||
|
||||
channel-type.max.mode.label = Modus des Gerätes
|
||||
channel-type.max.mode.description = Gibt den aktuellen Modus des Gerätes an (MANUAL/AUTOMATIC/BOOST/VACATION).
|
||||
channel-type.max.mode.state.option.AUTOMATIC = Automatisch
|
||||
channel-type.max.mode.state.option.MANUAL = Manuell
|
||||
channel-type.max.mode.state.option.BOOST = Boost
|
||||
channel-type.max.mode.state.option.VACATION = Urlaubsmodus
|
||||
|
||||
channel-type.max.actual_temp.label = Temperatur
|
||||
channel-type.max.actual_temp.description = Gibt die aktuell gemessene Temperatur des Thermostats an.
|
||||
|
||||
channel-type.max.set_temp.label = Solltemperatur
|
||||
channel-type.max.set_temp.description = Gibt die aktuell eingestellte Solltemperatur des Thermostats an.
|
||||
|
||||
channel-type.max.locked.label = Tastensperre
|
||||
channel-type.max.locked.description = Gibt an, ob die Tastensperre am Gerät aktiviert ist.
|
||||
|
||||
channel-type.max.contact_state.label = Fenster-Zustand
|
||||
channel-type.max.contact_state.description = Gibt an, ob ein Fenster offen oder geschlossen ist.
|
||||
@@ -0,0 +1,134 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="max" 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="bridge">
|
||||
<label>MAX! Cube LAN Gateway</label>
|
||||
<description>This bridge represents the MAX! Cube LAN Gateway.
|
||||
</description>
|
||||
|
||||
<channels>
|
||||
<channel id="free_mem" typeId="free_mem"/>
|
||||
<channel id="duty_cycle" typeId="duty_cycle"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
<advanced>false</advanced>
|
||||
</parameter-group>
|
||||
<parameter-group name="network">
|
||||
<label>Connection</label>
|
||||
<description>Connection Settings</description>
|
||||
<advanced>false</advanced>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<context></context>
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="ipAddress" type="text" required="true" groupName="network">
|
||||
<context>network-address</context>
|
||||
<label>MAX! Cube LAN Gateway IP</label>
|
||||
<description>The IP address of the MAX! Cube LAN gateway</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="false" min="1" max="65535" groupName="network">
|
||||
<context>network-address</context>
|
||||
<label>MAX! Cube LAN Gateway Port</label>
|
||||
<description>Port of the LAN gateway</description>
|
||||
<default>62910</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" required="false" min="1" unit="s" groupName="network">
|
||||
<label>Refresh Interval</label>
|
||||
<description>The refresh interval in seconds which is used to poll given
|
||||
MAX! Cube.</description>
|
||||
<default>30</default>
|
||||
</parameter>
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<parameter name="exclusive" type="boolean" required="false" groupName="network">
|
||||
<label>Exclusive Mode</label>
|
||||
<description>If set to true, the binding will leave the connection to the
|
||||
Cube open.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="maxRequestsPerConnection" type="integer" required="false" groupName="network">
|
||||
<label>Max Requests Per Connection</label>
|
||||
<description>In exclusive mode, how many requests are allowed until
|
||||
connection is closed and reopened.</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ntpServer1" type="text" required="false" groupName="device">
|
||||
<label>NTP Server 1</label>
|
||||
<description>The hostname for NTP Server 1 used by the Cube to get the time.</description>
|
||||
<default>ntp.homematic.com</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ntpServer2" type="text" required="false" groupName="device">
|
||||
<label>NTP Server 2</label>
|
||||
<description>The hostname for NTP Server 2 used by the Cube to get the time.</description>
|
||||
<default>ntp.homematic.com</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-cubeReset" type="integer" groupName="actions">
|
||||
<label>Reset Cube Configuration</label>
|
||||
<description>Resets the MAX! Cube room and device information. Devices will
|
||||
need to be included again!</description>
|
||||
<options>
|
||||
<option value="1234">Reset</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<!-- Trigger reboot action from habmin menu -->
|
||||
<parameter name="action-cubeReboot" type="integer" groupName="actions">
|
||||
<label>Restart Cube</label>
|
||||
<description>Restarts the Cube.</description>
|
||||
<options>
|
||||
<option value="1234">Reboot</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
||||
</bridge-type>
|
||||
|
||||
<channel-type id="free_mem" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Free Memory Slots</label>
|
||||
<description>Free memory slots to store commands send to the devices</description>
|
||||
<state pattern="%d" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="duty_cycle" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Duty Cycle</label>
|
||||
<description>Duty Cycle for sending commands to the devices</description>
|
||||
<state pattern="%d %%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,493 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="max" 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="thermostat">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! HeatingThermostat</label>
|
||||
<description>This is a MAX! HeatingThermostat</description>
|
||||
|
||||
<channels>
|
||||
<channel id="valve" typeId="valve"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="actual_temp" typeId="actual_temp"/>
|
||||
<channel id="set_temp" typeId="set_temp"/>
|
||||
<channel id="locked" typeId="locked"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter-group>
|
||||
<parameter-group name="binding">
|
||||
<label>Binding Settings</label>
|
||||
<description>Binding settings for this device</description>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
<!-- Experimental - Enable when also removing rooms is implemented <parameter
|
||||
name="roomId" type="integer" required="false" groupName="device"> <label>Room Id</label>
|
||||
<description>The room name Id.</description> <advanced>true</advanced> </parameter> -->
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="comfortTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Comfort Temperature</label>
|
||||
<description>Set the Comfort Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ecoTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Eco Temperature</label>
|
||||
<description>Set the Eco Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="offsetTemp" type="decimal" min="-3.5" max="3.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>OffSet Temperature</label>
|
||||
<description>Set the Thermostat offset.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="maxTempSetpoint" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Max Temperature</label>
|
||||
<description>Set the Thermostat maximum temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="minTempSetpoint" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Min Temperature</label>
|
||||
<description>Set the Thermostat minimum temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="windowOpenTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Window Open Temp</label>
|
||||
<description>Set the Window Open Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="windowOpenDuration" type="integer" min="0" max="60" step="5" required="false"
|
||||
groupName="device">
|
||||
<label>Window Open Duration</label>
|
||||
<description>Set the Thermostat Window Open Duration.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<parameter name="refreshActualRate" type="integer" required="false" groupName="binding">
|
||||
<label>Actual Temperature Refresh Rate</label>
|
||||
<description>Experimental feature! Rate of the actual refresh in minutes. 0-9=refresh disabled. Minimum refresh rate
|
||||
once/10 minutes, recommended 60min. The settings of the heating thermostats are changed to trigger an update of
|
||||
temperature, as the actual temperature only is updated when the valves changes.</description>
|
||||
<default>0</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-deviceDelete" type="integer" groupName="actions">
|
||||
<label>Delete Device from Cube</label>
|
||||
<description>Deletes the device from the MAX! Cube. Device will need to be
|
||||
included again!</description>
|
||||
<options>
|
||||
<option value="1234">Delete</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="thermostatplus">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! HeatingThermostat+</label>
|
||||
<description>This is a MAX! HeatingThermostat+</description>
|
||||
|
||||
<channels>
|
||||
<channel id="valve" typeId="valve"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="actual_temp" typeId="actual_temp"/>
|
||||
<channel id="set_temp" typeId="set_temp"/>
|
||||
<channel id="locked" typeId="locked"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter-group>
|
||||
<parameter-group name="binding">
|
||||
<label>Binding Settings</label>
|
||||
<description>Binding settings for this device</description>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<context></context>
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
<!-- Experimental - Enable when also removing rooms is implemented <parameter
|
||||
name="roomId" type="integer" required="false" groupName="device"> <label>Room Id</label>
|
||||
<description>The room name Id.</description> <advanced>true</advanced> </parameter> -->
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="comfortTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Comfort Temperature</label>
|
||||
<description>Set the Comfort Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ecoTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Eco Temperature</label>
|
||||
<description>Set the Eco Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="offsetTemp" type="decimal" min="-3.5" max="3.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>OffSet Temperature</label>
|
||||
<description>Set the Thermostat offset.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="maxTempSetpoint" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Max Temperature</label>
|
||||
<description>Set the Thermostat maximum temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="minTempSetpoint" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Min Temperature</label>
|
||||
<description>Set the Thermostat minimum temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="windowOpenTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Window Open Temp</label>
|
||||
<description>Set the Window Open Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="windowOpenDuration" type="integer" min="0" max="60" step="5" required="false"
|
||||
groupName="device">
|
||||
<label>Window Open Duration</label>
|
||||
<description>Set the Thermostat Window Open Duration.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<parameter name="refreshActualRate" type="integer" required="false" groupName="binding">
|
||||
<label>Actual Temperature Refresh Rate</label>
|
||||
<description>Experimental feature! Rate of the actual refresh in minutes. 0-9=refresh disabled. Minimum refresh rate
|
||||
once/10 minutes, recommended 60min. The settings of the heating thermostats are changed to trigger an update of
|
||||
temperature, as the actual temperature only is updated when the valves changes.</description>
|
||||
<default>0</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-deviceDelete" type="integer" groupName="actions">
|
||||
<label>Delete Device from Cube</label>
|
||||
<description>Deletes the device from the MAX! Cube. Device will need to be
|
||||
included again!</description>
|
||||
<options>
|
||||
<option value="1234">Delete</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="wallthermostat">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! WallThermostat+</label>
|
||||
<description>This is a MAX! WallThermostat+</description>
|
||||
|
||||
<channels>
|
||||
<channel id="valve" typeId="valve"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="actual_temp" typeId="actual_temp"/>
|
||||
<channel id="set_temp" typeId="set_temp"/>
|
||||
<channel id="locked" typeId="locked"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
</parameter>
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-deviceDelete" type="integer" groupName="actions">
|
||||
<label>Delete Device from Cube</label>
|
||||
<description>Deletes the device from the MAX! Cube. Device will need to be
|
||||
included again!</description>
|
||||
<options>
|
||||
<option value="1234">Delete</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="ecoswitch">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! Ecoswitch</label>
|
||||
<description>This is a MAX! EcoSwitch</description>
|
||||
|
||||
<channels>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
</parameter>
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shuttercontact">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! Shutter Contact</label>
|
||||
<description>This is a MAX! Shutter Contact</description>
|
||||
|
||||
<channels>
|
||||
<channel id="contact_state" typeId="contact_state"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
</parameter>
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-deviceDelete" type="integer" groupName="actions">
|
||||
<label>Delete Device from Cube</label>
|
||||
<description>Deletes the device from the MAX! Cube. Device will need to be
|
||||
included again!</description>
|
||||
<options>
|
||||
<option value="1234">Delete</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="valve" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Valve Position</label>
|
||||
<description>Thermostat Valve Position</description>
|
||||
<state pattern="%d %%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mode">
|
||||
<item-type>String</item-type>
|
||||
<label>Mode</label>
|
||||
<description>Thermostat Mode Setting</description>
|
||||
<tags>
|
||||
<tag>heating</tag>
|
||||
</tags>
|
||||
<state pattern="%s" readOnly="false">
|
||||
<options>
|
||||
<option value="AUTOMATIC">AUTOMATIC</option>
|
||||
<option value="MANUAL">MANUAL</option>
|
||||
<option value="BOOST">BOOST</option>
|
||||
<option value="VACATION">VACATION</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="actual_temp">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Current Temperature</label>
|
||||
<description>Current measured room temperature</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>heating</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="set_temp">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Setpoint Temperature</label>
|
||||
<description>Thermostat Setpoint temperature</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>heating</tag>
|
||||
</tags>
|
||||
<state min="4.5" max="30.5" step="0.5" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="locked" advanced="true">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Thermostat Locked</label>
|
||||
<description>Thermostat is locked for adjustments</description>
|
||||
<category>Lock</category>
|
||||
<state pattern="%s" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="contact_state">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Contact State</label>
|
||||
<description>Contact state information</description>
|
||||
<category>Contact</category>
|
||||
<state pattern="%s" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -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.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link FCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FCommandTest {
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
FCommand scmd = new FCommand();
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
|
||||
assertEquals("f:", prefix);
|
||||
assertEquals("f:" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
FCommand scmd = new FCommand("ntp.homematic.com", "nl.ntp.pool.org");
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("f:ntp.homematic.com,nl.ntp.pool.org" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fCommandNullTest() {
|
||||
FCommand scmd = new FCommand("ntp.homematic.com", null);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("f:ntp.homematic.com" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = new FCommand(null, "nl.ntp.pool.org");
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("f:nl.ntp.pool.org" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = new FCommand(null, null);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("f:" + '\r' + '\n', commandStr);
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.DeviceConfiguration;
|
||||
import org.openhab.binding.max.internal.device.RoomInformation;
|
||||
import org.openhab.binding.max.internal.message.CMessage;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link MCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MCommandTest {
|
||||
|
||||
private List<DeviceConfiguration> configurations = new ArrayList<>();
|
||||
private List<Device> devices = new ArrayList<>();
|
||||
private List<RoomInformation> rooms = new ArrayList<>();
|
||||
|
||||
String deviceCMsg[] = {
|
||||
"C:0b0da3,0gsNowIBEABLRVEwNTQ0MjQyLCQ9CQcYAzAM/wBIYViRSP1ZFE0gTSBNIEUgRSBFIEUgRSBFIEhhWJFQ/VkVUSBRIFEgRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIEhQWFpI/lkUTSBNIE0gRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIA==",
|
||||
"C:08c1d6,0gjB1gEFGP9LRVEwNjQ5MzEyKyE9CQcYAzAM/wBEeFUgVSBVIFUgVSBVIEUgRSBFIEUgRSBFIER4VRZFIEUgRSBFIEUgRSBFIEUgRSBFIEUgRFFEYkTkTQ9FIEUgRSBFIEUgRSBFIEUgRSBEUURiRORND0UgRSBFIEUgRSBFIEUgRSBFIERRRGJE5E0PRSBFIEUgRSBFIEUgRSBFIEUgRFFEYkTkTQ9FIEUgRSBFIEUgRSBFIEUgRSBEUURiRORRGEUgRSBFIEUgRSBFIEUgRSBFIA==",
|
||||
"C:0e75f6,EQ519gQCEABLRVExMTA0Mzgw",
|
||||
"C:0f1d54,0g8dVAEAEKBMRVEwMTU1NTc4KiI9CQcYAzAM/wBESFUIRSBFIEUgRSBFIEUgRSBFIEUgRSBFIERIVQhFIEUgRSBFIEUgRSBFIEUgRSBFIEUgREhUbETMVRRFIEUgRSBFIEUgRSBFIEUgRSBESFRsRMxVFEUgRSBFIEUgRSBFIEUgRSBFIERIVGxEzFUURSBFIEUgRSBFIEUgRSBFIEUgREhUbETMVRRFIEUgRSBFIEUgRSBFIEUgRSBESFRsRMxVFEUgRSBFIEUgRSBFIEUgRSBFIA==" };
|
||||
|
||||
private void prepareDevices() {
|
||||
// create a devices array
|
||||
for (String cMsg : deviceCMsg) {
|
||||
CMessage msg = new CMessage(cMsg);
|
||||
// DeviceConfiguration c = null;
|
||||
configurations.add(DeviceConfiguration.create(msg));
|
||||
Device di = Device.create(msg.getRFAddress(), configurations);
|
||||
devices.add(di);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
prepareDevices();
|
||||
MCommand mcmd = new MCommand(devices);
|
||||
String commandStr = mcmd.getCommandString();
|
||||
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
assertEquals("m:", prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
prepareDevices();
|
||||
|
||||
MCommand mCmd = new MCommand(devices);
|
||||
rooms = new ArrayList<>(mCmd.getRooms());
|
||||
String commandStr = mCmd.getCommandString();
|
||||
assertEquals(
|
||||
"m:00,VgIDAQALDaMCAA519gUACMHWBAILDaNLRVEwNTQ0MjQyAAEBCMHWS0VRMDY0OTMxMgAFBA519ktFUTExMDQzODAAAgEPHVRMRVEwMTU1NTc4AAAB\r\n",
|
||||
commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addRoomsTest() {
|
||||
prepareDevices();
|
||||
|
||||
MCommand mCmd = new MCommand(devices);
|
||||
rooms = new ArrayList<>(mCmd.getRooms());
|
||||
|
||||
RoomInformation room = new RoomInformation(3, "testroom", "0f1d54");
|
||||
rooms.add(room);
|
||||
mCmd = new MCommand(devices, rooms);
|
||||
String commandStr = mCmd.getCommandString();
|
||||
assertEquals(
|
||||
"m:00,VgIEAQALDaMCAA519gMIdGVzdHJvb20PHVQFAAjB1gQCCw2jS0VRMDU0NDI0MgABAQjB1ktFUTA2NDkzMTIABQQOdfZLRVExMTA0MzgwAAIBDx1UTEVRMDE1NTU3OAAAAQ==\r\n",
|
||||
commandStr);
|
||||
devices.get(3).setRoomId(3);
|
||||
devices.get(3).setName("Testroom");
|
||||
|
||||
mCmd = new MCommand(devices, rooms);
|
||||
commandStr = mCmd.getCommandString();
|
||||
|
||||
assertEquals(
|
||||
"m:00,VgIEAQALDaMCAA519gMIdGVzdHJvb20PHVQFAAjB1gQCCw2jS0VRMDU0NDI0MgABAQjB1ktFUTA2NDkzMTIABQQOdfZLRVExMTA0MzgwAAIBDx1UTEVRMDE1NTU3OAhUZXN0cm9vbQMB\r\n",
|
||||
commandStr);
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.ThermostatModeType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link SCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SCommandTest {
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
SCommand scmd = new SCommand("0b0da3", 1, ThermostatModeType.MANUAL, 20.0);
|
||||
String commandStr = scmd.getCommandString();
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
|
||||
assertEquals("s:", prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
SCommand scmd = new SCommand("0b0da3", 1, ThermostatModeType.MANUAL, 20.0);
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
|
||||
String base64Data = commandStr.substring(2).trim();
|
||||
byte[] bytes = Base64.getDecoder().decode(base64Data.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
int[] data = new int[bytes.length];
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
data[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
|
||||
String decodedString = Utils.toHex(data);
|
||||
assertEquals("s:AARAAAAACw2jAWg=\r\n", commandStr);
|
||||
assertEquals("0004400000000B0DA30168", decodedString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boostModeTest() {
|
||||
SCommand scmd = new SCommand("0b0da3", 1, ThermostatModeType.BOOST, 21.0);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("s:AARAAAAACw2jAeo=\r\n", commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoModeTest() {
|
||||
SCommand scmd = new SCommand("0b0da3", 1, ThermostatModeType.AUTOMATIC, 0);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("s:AARAAAAACw2jAQA=\r\n", commandStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.command.SConfigCommand.ConfigCommandType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link SConfigCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SConfigCommandTest {
|
||||
|
||||
private static final String RF_TEST_ADDRESS = "0e15cc";
|
||||
private static final int TEST_ROOM = 2;
|
||||
|
||||
@Test
|
||||
public void setRoomTest() {
|
||||
CubeCommand cubeCommand = new SConfigCommand(RF_TEST_ADDRESS, TEST_ROOM, ConfigCommandType.SetRoom);
|
||||
String commandString = cubeCommand.getCommandString();
|
||||
assertEquals("s:AAAiAAAADhXMAAI=\r\n", commandString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeRoomTest() {
|
||||
CubeCommand cubeCommand = new SConfigCommand(RF_TEST_ADDRESS, 1, ConfigCommandType.RemoveRoom);
|
||||
String commandString = cubeCommand.getCommandString();
|
||||
assertEquals("s:AAAjAAAADhXMAAE=\r\n", commandString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link TCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TCommandTest {
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
TCommand scmd = new TCommand("0f1d54", false);
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
|
||||
assertEquals("t:", prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
TCommand scmd = new TCommand("0f1d54", false);
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
|
||||
String base64Data = commandStr.split(",")[2];
|
||||
byte[] bytes = Base64.getDecoder().decode(base64Data.trim().getBytes());
|
||||
int[] data = new int[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
data[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
String decodedString = Utils.toHex(data);
|
||||
|
||||
assertEquals("t:01,0,Dx1U\r\n", commandStr);
|
||||
assertEquals("0F1D54", decodedString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addRoomTest() {
|
||||
TCommand scmd = new TCommand("0f1d54", false);
|
||||
scmd.addRoom("0b0da3");
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
|
||||
String base64Data = commandStr.split(",")[2];
|
||||
byte[] bytes = Base64.getDecoder().decode(base64Data.trim().getBytes());
|
||||
int[] data = new int[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
data[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
String decodedString = Utils.toHex(data);
|
||||
|
||||
assertEquals("t:02,0,Cw2jDx1U\r\n", commandStr);
|
||||
assertEquals("0B0DA30F1D54", decodedString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceModeTest() {
|
||||
TCommand scmd = new TCommand("0f1d54", true);
|
||||
String commandStr = scmd.getCommandString();
|
||||
|
||||
assertEquals("t:01,1,Dx1U\r\n", commandStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link ZCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZCommandTest {
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
ZCommand scmd = new ZCommand(ZCommand.WakeUpType.DEVICE, "0b0da3", 30);
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
|
||||
assertEquals("z:", prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
ZCommand scmd = new ZCommand(ZCommand.WakeUpType.DEVICE, "0b0da3", 30);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,D,0b0da3" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wakeAllTest() {
|
||||
ZCommand scmd = new ZCommand(ZCommand.WakeUpType.ALL, "0b0da3", 60);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("z:3C,A" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupAllDevices();
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,A" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupAllDevices(60);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:3C,A" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wakeRoomTest() {
|
||||
ZCommand scmd = new ZCommand(ZCommand.WakeUpType.ROOM, "01", 30);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,G,01" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupRoom(1);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,G,01" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupRoom(2, 60);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:3C,G,02" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wakeDeviceTest() {
|
||||
ZCommand scmd = ZCommand.wakeupDevice("0b0da3");
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,D,0b0da3" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupDevice("0b0da3", 60);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:3C,D,0b0da3" + '\r' + '\n', commandStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link CMessage}.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 Version and updates
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "C:0b0da3,0gsNowIBEABLRVEwNTQ0MjQyLCQ9CQcYAzAM/wBIYViRSP1ZFE0gTSBNIEUgRSBFIEUgRSBFIEhhWJFQ/VkVUSBRIFEgRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIEhQWFpI/lkUTSBNIE0gRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIA==";
|
||||
private final CMessage message = new CMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.C, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRFAddressTest() {
|
||||
String rfAddress = message.getRFAddress();
|
||||
assertEquals("0b0da3", rfAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeviceTypeTest() {
|
||||
DeviceType deviceType = message.getDeviceType();
|
||||
assertEquals(DeviceType.HeatingThermostatPlus, deviceType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSerialNumberTes() {
|
||||
String serialNumber = message.getSerialNumber();
|
||||
assertEquals("KEQ0544242", serialNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.DeviceConfiguration;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link DeviceConfiguration}.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 Version and updates
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConfigurationTest {
|
||||
|
||||
public static final String RAW_DATA = "C:0b0da3,0gsNowIBEABLRVEwNTQ0MjQyLCQ9CQcYAzAM/wBIYViRSP1ZFE0gTSBNIEUgRSBFIEUgRSBFIEhhWJFQ/VkVUSBRIFEgRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIEhQWFpI/lkUTSBNIE0gRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIA==";
|
||||
|
||||
private final CMessage message = new CMessage(RAW_DATA);
|
||||
private @Nullable DeviceConfiguration configuration;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
configuration = DeviceConfiguration.create(message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTest() {
|
||||
assertNotNull(configuration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRfAddressTest() {
|
||||
final DeviceConfiguration configuration = this.configuration;
|
||||
if (configuration != null) {
|
||||
String rfAddress = configuration.getRFAddress();
|
||||
assertEquals("0b0da3", rfAddress);
|
||||
} else {
|
||||
fail("Configuration missing");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeviceTypeTest() {
|
||||
final DeviceConfiguration configuration = this.configuration;
|
||||
if (configuration != null) {
|
||||
DeviceType deviceType = configuration.getDeviceType();
|
||||
assertEquals(DeviceType.HeatingThermostatPlus, deviceType);
|
||||
} else {
|
||||
fail("Configuration missing");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSerialNumberTest() {
|
||||
final DeviceConfiguration configuration = this.configuration;
|
||||
if (configuration != null) {
|
||||
String serialNumber = configuration.getSerialNumber();
|
||||
assertEquals("KEQ0544242", serialNumber);
|
||||
} else {
|
||||
fail("Configuration missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link FMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "F:nl.ntp.pool.org,ntp.homematic.com";
|
||||
|
||||
private final FMessage message = new FMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.F, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServer1Test() {
|
||||
String ntpServer1 = message.getNtpServer1();
|
||||
assertEquals("nl.ntp.pool.org", ntpServer1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServer2Test() {
|
||||
String ntpServer1 = message.getNtpServer2();
|
||||
assertEquals("ntp.homematic.com", ntpServer1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link HMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "H:KEQ0565026,0b5951,0113,00000000,4eed6795,01,32,12080a,070f,03,0000";
|
||||
|
||||
private final HMessage message = new HMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.H, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRFAddressTest() {
|
||||
String rfAddress = message.getRFAddress();
|
||||
assertEquals("0b5951", rfAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFirmwareTest() {
|
||||
String firmware = message.getFirmwareVersion();
|
||||
assertEquals("01.13", firmware);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionIdTest() {
|
||||
String connectionId = message.getConnectionId();
|
||||
assertEquals("4eed6795", connectionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCubeTimeStateTest() {
|
||||
String cubeTimeState = message.getCubeTimeState();
|
||||
assertEquals("03", cubeTimeState);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDateTime() {
|
||||
String[] tokens = RAW_DATA.split(Message.DELIMETER);
|
||||
|
||||
String hexDate = tokens[7];
|
||||
String hexTime = tokens[8];
|
||||
|
||||
int year = Utils.fromHex(hexDate.substring(0, 2));
|
||||
int month = Utils.fromHex(hexDate.substring(2, 4));
|
||||
int dayOfMonth = Utils.fromHex(hexDate.substring(4, 6));
|
||||
assertEquals(18, year);
|
||||
assertEquals(8, month);
|
||||
assertEquals(10, dayOfMonth);
|
||||
|
||||
int hours = Utils.fromHex(hexTime.substring(0, 2));
|
||||
int minutes = Utils.fromHex(hexTime.substring(2, 4));
|
||||
assertEquals(7, hours);
|
||||
assertEquals(15, minutes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDateTime() {
|
||||
Date dateTime = message.getDateTime();
|
||||
assertEquals(Date.from(ZonedDateTime.of(2018, 8, 10, 7, 15, 0, 0, ZoneId.systemDefault()).toInstant()),
|
||||
dateTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNTPCounterTest() {
|
||||
String ntpCounter = message.getNTPCounter();
|
||||
assertEquals("0", ntpCounter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSerialNumberTest() {
|
||||
String serialNumber = message.getSerialNumber();
|
||||
assertEquals("KEQ0565026", serialNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.DeviceConfiguration;
|
||||
import org.openhab.binding.max.internal.device.DeviceInformation;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.openhab.binding.max.internal.device.HeatingThermostat;
|
||||
import org.openhab.binding.max.internal.device.ShutterContact;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link LMessage}.
|
||||
*
|
||||
* @author Dominic Lerbs - Initial contribution
|
||||
* @author Christoph Weitkamp - OH2 Version and updates
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LMessageTest {
|
||||
|
||||
private static final String RAWDATA = "L:BgVPngkSEAsLhBkJEhkLJQDAAAsLhwwJEhkRJwDKAAYO8ZIJEhAGBU+kCRIQCwxuRPEaGQMmAMcACwxuQwkSGQgnAM8ACwQd5t0SGQ0oAMsA";
|
||||
|
||||
private final Map<String, Device> testDevices = new HashMap<>();
|
||||
|
||||
private final LMessage message = new LMessage(RAWDATA);
|
||||
private final List<DeviceConfiguration> configurations = new ArrayList<>();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
createTestDevices();
|
||||
}
|
||||
|
||||
private void createTestDevices() {
|
||||
addShutterContact("054f9e");
|
||||
addShutterContact("0ef192");
|
||||
addShutterContact("054fa4");
|
||||
addHeatingThermostat("0b8419");
|
||||
addHeatingThermostat("0b870c");
|
||||
addHeatingThermostat("0c6e43");
|
||||
addHeatingThermostat("041de6");
|
||||
addHeatingThermostat("0c6e44").setError(true);
|
||||
}
|
||||
|
||||
private ShutterContact addShutterContact(String rfAddress) {
|
||||
ShutterContact device = new ShutterContact(createConfiguration(DeviceType.ShutterContact, rfAddress));
|
||||
testDevices.put(rfAddress, device);
|
||||
return device;
|
||||
}
|
||||
|
||||
private HeatingThermostat addHeatingThermostat(String rfAddress) {
|
||||
HeatingThermostat device = new HeatingThermostat(createConfiguration(DeviceType.HeatingThermostat, rfAddress));
|
||||
testDevices.put(rfAddress, device);
|
||||
return device;
|
||||
}
|
||||
|
||||
private DeviceConfiguration createConfiguration(DeviceType type, String rfAddress) {
|
||||
DeviceConfiguration configuration = DeviceConfiguration
|
||||
.create(new DeviceInformation(type, "", rfAddress, "", 1));
|
||||
configurations.add(configuration);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isCorrectMessageType() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.L, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allDevicesCreatedFromMessage() {
|
||||
Collection<? extends Device> devices = message.getDevices(configurations);
|
||||
assertEquals("Incorrect number of devices created", testDevices.size(), devices.size());
|
||||
for (Device device : devices) {
|
||||
assertTrue("Unexpected device created: " + device.getRFAddress(),
|
||||
testDevices.containsKey(device.getRFAddress()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isCorrectErrorState() {
|
||||
for (Device device : message.getDevices(configurations)) {
|
||||
Device testDevice = testDevices.get(device.getRFAddress());
|
||||
assertEquals("Error set incorrectly in Device", testDevice.isError(), device.isError());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.DeviceInformation;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.openhab.binding.max.internal.device.RoomInformation;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link MMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "M:00,01,VgIFAQhiYWRrYW1lcgsNowIMU3R1ZGVlcmthbWVyB7bnAwlXb29ua2FtZXIL6aIEDFN6b25qYSBLYW1lcgjDSQUGWm9sZGVyCMHWCAILDaNLRVEwNTQ0MjQyEUJhZGthbWVyIFJhZGlhdG9yAQEHtudLRVEwMTQ1MTcyFVJhZGlhdG9yIFN0dWRlZXJrYW1lcgIDDhXMTEVRMDAxNTM0MBlXYWxsIFRoZXJtb3N0YXQgV29vbmthbWVyAwEL6aJLRVE5MDE1NDMyG1BsdWcgQWRhcHRlciBNdXVydmVyd2FybWluZwMFBDNvSkVRMDM4MDg3OBdFY28gU3dpdGNoIFN0dWRlZXJrYW1lcgAEDnX2S0VRMTEwNDM4MBpXaW5kb3cgU2Vuc29yIFN0dWRlZXJrYW1lcgIBCMNJS0VRMDY0ODk0ORJUaGVybW9zdGFhdCBTem9uamEEAQjB1ktFUTA2NDkzMTIRU3R1ZGVlcmthbWVyIElybWEFAQ==";
|
||||
|
||||
private final MMessage message = new MMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.M, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTest() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
|
||||
assertEquals(8, allDevicesInformation.size());
|
||||
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(0);
|
||||
assertEquals("Badkamer Radiator", deviceInformation.getName());
|
||||
assertEquals("0B0DA3", deviceInformation.getRFAddress());
|
||||
assertEquals(1, deviceInformation.getRoomId());
|
||||
assertEquals("KEQ0544242", deviceInformation.getSerialNumber());
|
||||
assertEquals(DeviceType.HeatingThermostatPlus, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTypeTest1() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(1);
|
||||
assertEquals(DeviceType.HeatingThermostat, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTypeTest2() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(2);
|
||||
assertEquals(DeviceType.WallMountedThermostat, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTypeTest3() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(4);
|
||||
assertEquals(DeviceType.EcoSwitch, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTypeTest4() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(5);
|
||||
assertEquals(DeviceType.ShutterContact, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roomInformationTest() {
|
||||
List<RoomInformation> roomInformation = message.rooms;
|
||||
|
||||
assertEquals(5, roomInformation.size());
|
||||
assertEquals("badkamer", roomInformation.get(0).getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.exceptions.IncompleteMessageException;
|
||||
import org.openhab.binding.max.internal.exceptions.IncorrectMultilineIndexException;
|
||||
import org.openhab.binding.max.internal.exceptions.MessageIsWaitingException;
|
||||
import org.openhab.binding.max.internal.exceptions.NoMessageAvailableException;
|
||||
import org.openhab.binding.max.internal.exceptions.UnprocessableMessageException;
|
||||
import org.openhab.binding.max.internal.exceptions.UnsupportedMessageTypeException;
|
||||
|
||||
/**
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageProcessorTest {
|
||||
|
||||
private final MessageProcessor processor = new MessageProcessor();
|
||||
|
||||
private void commonMessageTest(String line, Message expectedMessage) throws Exception {
|
||||
Assert.assertTrue(this.processor.addReceivedLine(line));
|
||||
Assert.assertTrue(this.processor.isMessageAvailable());
|
||||
Message message = this.processor.pull();
|
||||
Assert.assertNotNull(message);
|
||||
Assert.assertEquals(message.getClass().getName(), expectedMessage.getClass().getName());
|
||||
Assert.assertEquals(expectedMessage.getPayload(), message.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testS_Message() throws Exception {
|
||||
String rawData = "S:03,0,30";
|
||||
SMessage expectedMessage = new SMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testN_Message() throws Exception {
|
||||
String rawData = "N:Aw4VzExFUTAwMTUzNDD/";
|
||||
NMessage expectedMessage = new NMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testA_Message() throws Exception {
|
||||
String rawData = "A:";
|
||||
AMessage expectedMessage = new AMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testC_Message() throws Exception {
|
||||
String rawData = "C:0ff1bc,EQ/xvAQJEAJMRVEwNzk0MDA3";
|
||||
CMessage expectedMessage = new CMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testL_Message() throws Exception {
|
||||
String rawData = "L:Bg/xvAkAAA==";
|
||||
LMessage expectedMessage = new LMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testH_Message() throws Exception {
|
||||
String rawData = "H:KHA0007199,081dd4,0113,00000000,0d524351,10,30,0f0407,1130,03,0000";
|
||||
HMessage expectedMessage = new HMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleM_Message() throws Exception {
|
||||
String rawData = "M:00,01,VgIBAQpXb2huemltbWVyAAAAAQMQV6lMRVEwOTgyMTU2DldhbmR0aGVybW9zdGF0AQE=";
|
||||
MMessage expectedMessage = new MMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultilineM_Message() throws Exception {
|
||||
String line1_part1 = "M:00,02,";
|
||||
String line1_part2 = "VgIMAQpXb2huemltbWVyCvMrAgtUb2lsZXR0ZSBFRwrenQMOVG9pbGV0dGUgMS4gT0cK3rgECkJhZGV6aW1tZXIK3qoFDFNjaGxhZnppbW1lcgresQYDSmFuD4lCBwlDaHJpc3RpbmEPiTYIBEZsdXIPiT0KEEJhZGV6aW1tZXIgMi4gT0cPiRwLBULDvHJvD4k/DAxHw6RzdGV6aW1tZXIPiRoJC1dhc2Noa8O8Y2hlD4lXNgQHOCtLRVEwMTg4NjczCFRlcnJhc3NlAQQHMblLRVEwMTg3MTkwCEZsdXJ0w7xyAQIK8ytLRVEwMzc5NTg3C1dhbmRoZWl6dW5nAQIK9P9LRVEwMzgwMDU1DkZlbnN0ZXJoZWl6dW5nAQQHMbtLRVEwMTg3MTg4CEZsdXJ0w7xyAgQHMuxLRVEwMTg2ODg0B0ZlbnN0ZXICAQrenUtFUTA0MDY5NjIHSGVpenVuZwIBCt64S0VRMDQwNjk4OQdIZWl6dW5nAwQIFGdLRVEwMTkwNTc3B0ZlbnN0ZXIDBAc2l0tFUTAxODU5NDUIRmx1cnTDvHIEAQreqktFUTA0MDY5NzUHSGVpenVuZwQBCt8JS0VRMDQwNzA3MA5IYW5kdHVjaGVpenVuZwQEBzhTS0VRMDE4ODcxMAdGZW5zdGVyBAQIFIxLRVEwMTkwNTQzFkZlbnN0ZXIgU3RyYcOfZSByZWNodHMFAQresUtFUTA0MDY5ODIHSGVpenVuZwUEBzHmS0VRMDE4NzE0NhVGZW5zdGVyIFN0cmHDn2UgbGlua3MFAxBXqUxFUTA5ODIxNTYOV2FuZHRoZXJtb3N0YXQBBA/u1ExFUTA3OTQ3NTIIRmx1cnTDvHIGBA/v6kxFUTA3OTQ0NzQNRmVuc3RlciBsaW5rcwYED/HnTEVRMDc5Mzk2NA5GZW5zdGVyIHJlY2h0cwYBD4lCTEVRMTAwNDYwMAdIZWl6dW5nBgQP9BVMRVEwNzkzNDA2CEZsdXJ0w7xyBwQP79FMRVEwNzk0NDk5B0ZlbnN0ZXIHAQ+JNkxFUTEwMDQ1ODgHSGVpenVuZwcBD4k9TEVRMTAwNDU5NQ1IZWl6dW5nIHVudGVuCAEPiRxMRVExMDA0NTYyB0hlaXp1bmcKBA/yTUxFUTA3OTM4NjIHRmVuc3RlcgoED/F+TEVRMDc5NDA2OQhGbHVydMO8cgoBD4k/TEVRMTAwNDU5NwdIZWl6dW5nCwQP8YdMRVEwNzk0MDYwB0ZlbnN0ZXILBA/xSExFUTA3OTQxMjQIRmx1cnTDvHILBA/yVkxFUTA3OTM4NTMURmVuc3RlciBHYXJ0ZW4gbGlua3MMBA/yI0xFUTA3OTM5MDQVRmVuc3RlciBHYXJ0ZW4gcmVjaHRzDAEPiRpMRVExMDA0NTYwB0hlaXp1bmcMBA/vj0xFUTA3OTQ1NjUPRmVuc3RlciBTdHJhw59lDAQP8CtMRVEwNzk0NDA5BFTDvHIDBAgUa0tFUTAxODcwNjkNRmVuc3RlciBTZWl0ZQUEBzagS0VRMDE4NTkzNhVGZW5zdGVyIFN0cmHDn2UgbGlua3MBBA/wI0xFUTA3OTQ0MTYORmVuc3RlciBLw7xjaGUBAxBV50xFUTA5ODI2NzYOV2FuZHRoZXJtb3N0YXQFAxBW2kxFUTA5ODIzNjgOV2FuZHRoZXJtb3N0YXQEAxBV4kxFUTA5ODI2NzEOV2FuZHRoZXJtb3N0YXQHAxBZWExFUTA5ODE3MjkOV2FuZHRoZXJtb3N0YXQMAxBV6ExFUTA5ODI2NzcOV2FuZHRoZXJtb3N0YXQGAxBV40xFUTA5ODI2NzIOV2FuZHRoZXJtb3N0YXQKBAcxoEtFUTAxODcyMTYLV2FzY2hrw7xjaGUF";
|
||||
String line1 = line1_part1 + line1_part2;
|
||||
|
||||
String line2_part1 = "M:01,02,";
|
||||
String line2_part2 = "AxBV8ExFUTA5ODI2ODUOV2FuZHRoZXJtb3N0YXQJBA/v50xFUTA3OTQ0NzcNQmFsa29uZmVuc3RlcgkBD4lXTEVRMTAwNDYyMRZIZWl6dW5nIHVudGVybSBGZW5zdGVyCQQP8llMRVEwNzkzODUwDkZlbnN0ZXIgcmVjaHRzCQQP8bxMRVEwNzk0MDA3DUZlbnN0ZXIgbGlua3MJAQ+JOExFUTEwMDQ1OTAOSGVpenVuZyBCYWxrb24JBA/yLExFUTA3OTM4OTUKQmFsa29udMO8cgkED++zTEVRMDc5NDUyOQhGbHVydMO8cgkB";
|
||||
String line2 = line2_part1 + line2_part2;
|
||||
|
||||
String expectedString = line1 + line2_part2;
|
||||
MMessage expectedMessage = new MMessage(expectedString);
|
||||
|
||||
Assert.assertFalse(this.processor.addReceivedLine(line1));
|
||||
Assert.assertTrue(this.processor.addReceivedLine(line2));
|
||||
Message message = this.processor.pull();
|
||||
Assert.assertNotNull(message);
|
||||
Assert.assertEquals(message.getClass().getName(), MMessage.class.getName());
|
||||
Assert.assertEquals(expectedMessage.getPayload(), message.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongIndexOfMultilineM_Message() throws Exception {
|
||||
String line1 = "M:00,02,VgIMAQpXb2huemltbWVyCvMrAgtUb2lsZXR0ZSBFRwrenQMOVG9pbGV0dGUgMS4gT0cK3rgECkJhZGV6aW1tZXIK3qoFDFNjaGxhZnppbW1lcgresQYDSmFuD4lCBwlDaHJpc3RpbmEPiTYIBEZsdXIPiT0KEEJhZGV6aW1tZXIgMi4gT0cPiRwLBULDvHJvD4k/DAxHw6RzdGV6aW1tZXIPiRoJC1dhc2Noa8O8Y2hlD4lXNgQHOCtLRVEwMTg4NjczCFRlcnJhc3NlAQQHMblLRVEwMTg3MTkwCEZsdXJ0w7xyAQIK8ytLRVEwMzc5NTg3C1dhbmRoZWl6dW5nAQIK9P9LRVEwMzgwMDU1DkZlbnN0ZXJoZWl6dW5nAQQHMbtLRVEwMTg3MTg4CEZsdXJ0w7xyAgQHMuxLRVEwMTg2ODg0B0ZlbnN0ZXICAQrenUtFUTA0MDY5NjIHSGVpenVuZwIBCt64S0VRMDQwNjk4OQdIZWl6dW5nAwQIFGdLRVEwMTkwNTc3B0ZlbnN0ZXIDBAc2l0tFUTAxODU5NDUIRmx1cnTDvHIEAQreqktFUTA0MDY5NzUHSGVpenVuZwQBCt8JS0VRMDQwNzA3MA5IYW5kdHVjaGVpenVuZwQEBzhTS0VRMDE4ODcxMAdGZW5zdGVyBAQIFIxLRVEwMTkwNTQzFkZlbnN0ZXIgU3RyYcOfZSByZWNodHMFAQresUtFUTA0MDY5ODIHSGVpenVuZwUEBzHmS0VRMDE4NzE0NhVGZW5zdGVyIFN0cmHDn2UgbGlua3MFAxBXqUxFUTA5ODIxNTYOV2FuZHRoZXJtb3N0YXQBBA/u1ExFUTA3OTQ3NTIIRmx1cnTDvHIGBA/v6kxFUTA3OTQ0NzQNRmVuc3RlciBsaW5rcwYED/HnTEVRMDc5Mzk2NA5GZW5zdGVyIHJlY2h0cwYBD4lCTEVRMTAwNDYwMAdIZWl6dW5nBgQP9BVMRVEwNzkzNDA2CEZsdXJ0w7xyBwQP79FMRVEwNzk0NDk5B0ZlbnN0ZXIHAQ+JNkxFUTEwMDQ1ODgHSGVpenVuZwcBD4k9TEVRMTAwNDU5NQ1IZWl6dW5nIHVudGVuCAEPiRxMRVExMDA0NTYyB0hlaXp1bmcKBA/yTUxFUTA3OTM4NjIHRmVuc3RlcgoED/F+TEVRMDc5NDA2OQhGbHVydMO8cgoBD4k/TEVRMTAwNDU5NwdIZWl6dW5nCwQP8YdMRVEwNzk0MDYwB0ZlbnN0ZXILBA/xSExFUTA3OTQxMjQIRmx1cnTDvHILBA/yVkxFUTA3OTM4NTMURmVuc3RlciBHYXJ0ZW4gbGlua3MMBA/yI0xFUTA3OTM5MDQVRmVuc3RlciBHYXJ0ZW4gcmVjaHRzDAEPiRpMRVExMDA0NTYwB0hlaXp1bmcMBA/vj0xFUTA3OTQ1NjUPRmVuc3RlciBTdHJhw59lDAQP8CtMRVEwNzk0NDA5BFTDvHIDBAgUa0tFUTAxODcwNjkNRmVuc3RlciBTZWl0ZQUEBzagS0VRMDE4NTkzNhVGZW5zdGVyIFN0cmHDn2UgbGlua3MBBA/wI0xFUTA3OTQ0MTYORmVuc3RlciBLw7xjaGUBAxBV50xFUTA5ODI2NzYOV2FuZHRoZXJtb3N0YXQFAxBW2kxFUTA5ODIzNjgOV2FuZHRoZXJtb3N0YXQEAxBV4kxFUTA5ODI2NzEOV2FuZHRoZXJtb3N0YXQHAxBZWExFUTA5ODE3MjkOV2FuZHRoZXJtb3N0YXQMAxBV6ExFUTA5ODI2NzcOV2FuZHRoZXJtb3N0YXQGAxBV40xFUTA5ODI2NzIOV2FuZHRoZXJtb3N0YXQKBAcxoEtFUTAxODcyMTYLV2FzY2hrw7xjaGUF";
|
||||
String line2 = "M:02,02,AxBV8ExFUTA5ODI2ODUOV2FuZHRoZXJtb3N0YXQJBA/v50xFUTA3OTQ0NzcNQmFsa29uZmVuc3RlcgkBD4lXTEVRMTAwNDYyMRZIZWl6dW5nIHVudGVybSBGZW5zdGVyCQQP8llMRVEwNzkzODUwDkZlbnN0ZXIgcmVjaHRzCQQP8bxMRVEwNzk0MDA3DUZlbnN0ZXIgbGlua3MJAQ+JOExFUTEwMDQ1OTAOSGVpenVuZyBCYWxrb24JBA/yLExFUTA3OTM4OTUKQmFsa29udMO8cgkED++zTEVRMDc5NDUyOQhGbHVydMO8cgkB";
|
||||
|
||||
try {
|
||||
this.processor.addReceivedLine(line2);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (IncorrectMultilineIndexException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
|
||||
try {
|
||||
this.processor.reset();
|
||||
Assert.assertFalse(this.processor.addReceivedLine(line1));
|
||||
this.processor.addReceivedLine(line2);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (IncorrectMultilineIndexException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPullBeforeNewLine() throws Exception {
|
||||
String line1 = "H:KHA0007199,081dd4,0113,00000000,0d524351,10,30,0f0407,1130,03,0000";
|
||||
String line2 = "M:00,01,VgIBAQpXb2huemltbWVyAAAAAQMQV6lMRVEwOTgyMTU2DldhbmR0aGVybW9zdGF0AQE=";
|
||||
|
||||
try {
|
||||
Assert.assertTrue(this.processor.addReceivedLine(line1));
|
||||
this.processor.addReceivedLine(line2);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (MessageIsWaitingException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
|
||||
Message message = this.processor.pull();
|
||||
Assert.assertNotNull(message);
|
||||
Assert.assertEquals(message.getClass().getName(), HMessage.class.getName());
|
||||
this.processor.addReceivedLine(line2);
|
||||
message = this.processor.pull();
|
||||
Assert.assertNotNull(message);
|
||||
Assert.assertEquals(message.getClass().getName(), MMessage.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongIndicatorWhileMultilineProcessing() throws Exception {
|
||||
String line1 = "M:00,02,VgIMAQpXb2huemltbWVyCvMrAgtUb2lsZXR0ZSBFRwrenQMOVG9pbGV0dGUgMS4gT0cK3rgECkJhZGV6aW1tZXIK3qoFDFNjaGxhZnppbW1lcgresQYDSmFuD4lCBwlDaHJpc3RpbmEPiTYIBEZsdXIPiT0KEEJhZGV6aW1tZXIgMi4gT0cPiRwLBULDvHJvD4k/DAxHw6RzdGV6aW1tZXIPiRoJC1dhc2Noa8O8Y2hlD4lXNgQHOCtLRVEwMTg4NjczCFRlcnJhc3NlAQQHMblLRVEwMTg3MTkwCEZsdXJ0w7xyAQIK8ytLRVEwMzc5NTg3C1dhbmRoZWl6dW5nAQIK9P9LRVEwMzgwMDU1DkZlbnN0ZXJoZWl6dW5nAQQHMbtLRVEwMTg3MTg4CEZsdXJ0w7xyAgQHMuxLRVEwMTg2ODg0B0ZlbnN0ZXICAQrenUtFUTA0MDY5NjIHSGVpenVuZwIBCt64S0VRMDQwNjk4OQdIZWl6dW5nAwQIFGdLRVEwMTkwNTc3B0ZlbnN0ZXIDBAc2l0tFUTAxODU5NDUIRmx1cnTDvHIEAQreqktFUTA0MDY5NzUHSGVpenVuZwQBCt8JS0VRMDQwNzA3MA5IYW5kdHVjaGVpenVuZwQEBzhTS0VRMDE4ODcxMAdGZW5zdGVyBAQIFIxLRVEwMTkwNTQzFkZlbnN0ZXIgU3RyYcOfZSByZWNodHMFAQresUtFUTA0MDY5ODIHSGVpenVuZwUEBzHmS0VRMDE4NzE0NhVGZW5zdGVyIFN0cmHDn2UgbGlua3MFAxBXqUxFUTA5ODIxNTYOV2FuZHRoZXJtb3N0YXQBBA/u1ExFUTA3OTQ3NTIIRmx1cnTDvHIGBA/v6kxFUTA3OTQ0NzQNRmVuc3RlciBsaW5rcwYED/HnTEVRMDc5Mzk2NA5GZW5zdGVyIHJlY2h0cwYBD4lCTEVRMTAwNDYwMAdIZWl6dW5nBgQP9BVMRVEwNzkzNDA2CEZsdXJ0w7xyBwQP79FMRVEwNzk0NDk5B0ZlbnN0ZXIHAQ+JNkxFUTEwMDQ1ODgHSGVpenVuZwcBD4k9TEVRMTAwNDU5NQ1IZWl6dW5nIHVudGVuCAEPiRxMRVExMDA0NTYyB0hlaXp1bmcKBA/yTUxFUTA3OTM4NjIHRmVuc3RlcgoED/F+TEVRMDc5NDA2OQhGbHVydMO8cgoBD4k/TEVRMTAwNDU5NwdIZWl6dW5nCwQP8YdMRVEwNzk0MDYwB0ZlbnN0ZXILBA/xSExFUTA3OTQxMjQIRmx1cnTDvHILBA/yVkxFUTA3OTM4NTMURmVuc3RlciBHYXJ0ZW4gbGlua3MMBA/yI0xFUTA3OTM5MDQVRmVuc3RlciBHYXJ0ZW4gcmVjaHRzDAEPiRpMRVExMDA0NTYwB0hlaXp1bmcMBA/vj0xFUTA3OTQ1NjUPRmVuc3RlciBTdHJhw59lDAQP8CtMRVEwNzk0NDA5BFTDvHIDBAgUa0tFUTAxODcwNjkNRmVuc3RlciBTZWl0ZQUEBzagS0VRMDE4NTkzNhVGZW5zdGVyIFN0cmHDn2UgbGlua3MBBA/wI0xFUTA3OTQ0MTYORmVuc3RlciBLw7xjaGUBAxBV50xFUTA5ODI2NzYOV2FuZHRoZXJtb3N0YXQFAxBW2kxFUTA5ODIzNjgOV2FuZHRoZXJtb3N0YXQEAxBV4kxFUTA5ODI2NzEOV2FuZHRoZXJtb3N0YXQHAxBZWExFUTA5ODE3MjkOV2FuZHRoZXJtb3N0YXQMAxBV6ExFUTA5ODI2NzcOV2FuZHRoZXJtb3N0YXQGAxBV40xFUTA5ODI2NzIOV2FuZHRoZXJtb3N0YXQKBAcxoEtFUTAxODcyMTYLV2FzY2hrw7xjaGUF";
|
||||
String line2 = "H:KHA0007199,081dd4,0113,00000000,0d524351,10,30,0f0407,1130,03,0000";
|
||||
|
||||
try {
|
||||
Assert.assertFalse(this.processor.addReceivedLine(line1));
|
||||
this.processor.addReceivedLine(line2);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (IncompleteMessageException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownMessageIndicator() throws Exception {
|
||||
String rawData = "X:0ff1bc,EQ/xvAQJEAJMRVEwNzk0MDA3";
|
||||
|
||||
try {
|
||||
Assert.assertFalse(this.processor.addReceivedLine(rawData));
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (UnsupportedMessageTypeException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMessageAvailable() throws Exception {
|
||||
String rawData = "M:00,01,VgIBAQpXb2huemltbWVyAAAAAQMQV6lMRVEwOTgyMTU2DldhbmR0aGVybW9zdGF0AQE=";
|
||||
MMessage expectedMessage = new MMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
|
||||
try {
|
||||
this.processor.pull();
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (NoMessageAvailableException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnprocessableMessage() throws Exception {
|
||||
String line1 = "M:00"; // Some information are missing.
|
||||
|
||||
try {
|
||||
this.processor.addReceivedLine(line1);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (UnprocessableMessageException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link NMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "N:Aw4VzExFUTAwMTUzNDD/";
|
||||
// public final String rawData = "N:AQe250tFUTAxNDUxNzL/";
|
||||
|
||||
private NMessage message = new NMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.N, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRFAddressTest() {
|
||||
String rfAddress = message.getRfAddress();
|
||||
|
||||
assertEquals("0E15CC", rfAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSerialNumberTest() {
|
||||
String serialNumber = message.getSerialNumber();
|
||||
|
||||
assertEquals("LEQ0015340", serialNumber);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeviceTypeTest() {
|
||||
DeviceType deviceType = message.getDeviceType();
|
||||
|
||||
assertEquals(DeviceType.WallMountedThermostat, deviceType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link FMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SMessageTest {
|
||||
|
||||
private static final String RAW_DATA_1 = "S:01,0,31";
|
||||
private static final String RAW_DATA_2 = "S:00,1,00";
|
||||
|
||||
private final SMessage message1 = new SMessage(RAW_DATA_1);
|
||||
private final SMessage message2 = new SMessage(RAW_DATA_2);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message1).getType();
|
||||
assertEquals(MessageType.S, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDutyCycleTest() {
|
||||
int dutyCycle = message1.getDutyCycle();
|
||||
assertEquals(1, dutyCycle);
|
||||
dutyCycle = message2.getDutyCycle();
|
||||
assertEquals(0, dutyCycle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCommandDiscardedTest() {
|
||||
boolean commandDiscarded = message1.isCommandDiscarded();
|
||||
assertEquals(false, commandDiscarded);
|
||||
commandDiscarded = message2.isCommandDiscarded();
|
||||
assertEquals(true, commandDiscarded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFreeMemTest() {
|
||||
int freeMemory = message1.getFreeMemorySlots();
|
||||
assertEquals(49, freeMemory);
|
||||
freeMemory = message2.getDutyCycle();
|
||||
assertEquals(0, freeMemory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link Utils}.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 Version and updates
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UtilsTest {
|
||||
|
||||
@Test
|
||||
public void fromHexTest() {
|
||||
assertEquals(0x00, Utils.fromHex("00"));
|
||||
assertEquals(0x01, Utils.fromHex("01"));
|
||||
assertEquals(0x1F, Utils.fromHex("1F"));
|
||||
assertEquals(0xFF, Utils.fromHex("FF"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromByteTest() {
|
||||
byte b0 = 0;
|
||||
byte b127 = 127;
|
||||
byte b128 = (byte) 128; // overflow due to
|
||||
byte bn128 = -128; // signed bytes
|
||||
byte bn1 = -1;
|
||||
|
||||
int ar0 = Utils.fromByte(b0);
|
||||
int ar127 = Utils.fromByte(b127);
|
||||
int ar128 = Utils.fromByte(b128);
|
||||
int arn128 = Utils.fromByte(bn128);
|
||||
int arn1 = Utils.fromByte(bn1);
|
||||
|
||||
assertEquals(0, ar0);
|
||||
assertEquals(127, ar127);
|
||||
assertEquals(128, ar128);
|
||||
assertEquals(128, arn128);
|
||||
assertEquals(255, arn1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toHexNoArgTest() {
|
||||
String actualResult = Utils.toHex();
|
||||
assertEquals("", actualResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toHexOneArgTest() {
|
||||
String actualResult = Utils.toHex(15);
|
||||
assertEquals("0F", actualResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toHexMultipleArgTest() {
|
||||
String actualResult = Utils.toHex(4863);
|
||||
assertEquals("12FF", actualResult);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void resolveDateTimeTest() {
|
||||
int date = Utils.fromHex("858B"); // 05-09-2011
|
||||
int time = Utils.fromHex("2E"); // 23:00
|
||||
|
||||
Date result = Utils.resolveDateTime(date, time);
|
||||
|
||||
assertEquals(5, result.getDate());
|
||||
assertEquals(9, result.getMonth());
|
||||
assertEquals(2011, result.getYear());
|
||||
assertEquals(23, result.getHours());
|
||||
assertEquals(00, result.getMinutes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBitsTest() {
|
||||
boolean b1[] = Utils.getBits(0xFF);
|
||||
|
||||
assertEquals(b1.length, 8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
assertEquals(true, b1[i]);
|
||||
}
|
||||
|
||||
boolean b2[] = Utils.getBits(0x5A);
|
||||
|
||||
assertEquals(b2.length, 8);
|
||||
assertEquals(false, b2[0]);
|
||||
assertEquals(true, b2[1]);
|
||||
assertEquals(false, b2[2]);
|
||||
assertEquals(true, b2[3]);
|
||||
assertEquals(true, b2[4]);
|
||||
assertEquals(false, b2[5]);
|
||||
assertEquals(true, b2[6]);
|
||||
assertEquals(false, b2[7]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hexStringToByteArrayTest() {
|
||||
String s = "000102030AFF";
|
||||
|
||||
byte[] result = Utils.hexStringToByteArray(s);
|
||||
|
||||
assertEquals(0, result[0] & 0xFF);
|
||||
assertEquals(1, result[1] & 0xFF);
|
||||
assertEquals(2, result[2] & 0xFF);
|
||||
assertEquals(3, result[3] & 0xFF);
|
||||
assertEquals(10, result[4] & 0xFF);
|
||||
assertEquals(255, result[5] & 0xFF);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user