From 751b7ec68e092c64177322f426090204bc85b38c Mon Sep 17 00:00:00 2001 From: Marcel Verpaalen Date: Sat, 15 Nov 2014 15:06:01 +0100 Subject: [PATCH] Initial Contribution for OH2 Max!Cube binding Signed-off-by: Marcel Verpaalen --- .../org.openhab.binding.maxcube/.classpath | 7 + .../org.openhab.binding.maxcube/.project | 33 ++ .../ESH-INF/binding/binding.xml | 11 + .../ESH-INF/thing/bridge.xml | 40 ++ .../ESH-INF/thing/thing-types.xml | 94 ++++ .../META-INF/MANIFEST.MF | 25 + .../OSGI-INF/MaxCubeDiscovery.xml | 19 + .../OSGI-INF/MaxCubeHandlerFactory.xml | 19 + .../build.properties | 6 + .../org.openhab.binding.maxcube/pom.xml | 24 + .../binding/maxcube/MaxCubeBinding.java | 57 +++ .../config/MaxCubeBridgeConfiguration.java | 42 ++ .../maxcube/config/MaxCubeConfiguration.java | 30 ++ .../maxcube/internal/MaxCubeBridge.java | 463 ++++++++++++++++++ .../binding/maxcube/internal/Utils.java | 199 ++++++++ .../discovery/MaxCubeBridgeDiscovery.java | 103 ++++ .../discovery/MaxCubeDevicesDiscover.java | 105 ++++ .../internal/discovery/MaxCubeDiscover.java | 220 +++++++++ .../factory/MaxCubeHandlerFactory.java | 123 +++++ .../handler/DeviceStatusListener.java | 44 ++ .../handler/MaxCubeBridgeHandler.java | 253 ++++++++++ .../internal/handler/MaxCubeHandler.java | 209 ++++++++ .../maxcube/internal/message/C_Message.java | 211 ++++++++ .../maxcube/internal/message/Device.java | 253 ++++++++++ .../internal/message/DeviceConfiguration.java | 81 +++ .../internal/message/DeviceInformation.java | 54 ++ .../maxcube/internal/message/DeviceType.java | 68 +++ .../maxcube/internal/message/EcoSwitch.java | 34 ++ .../maxcube/internal/message/H_Message.java | 98 ++++ .../internal/message/HeatingThermostat.java | 150 ++++++ .../maxcube/internal/message/L_Message.java | 59 +++ .../maxcube/internal/message/M_Message.java | 146 ++++++ .../internal/message/MaxTokenizer.java | 63 +++ .../maxcube/internal/message/Message.java | 35 ++ .../maxcube/internal/message/MessageType.java | 19 + .../internal/message/RoomInformation.java | 52 ++ .../maxcube/internal/message/S_Command.java | 110 +++++ .../maxcube/internal/message/SendCommand.java | 124 +++++ .../internal/message/ShutterContact.java | 74 +++ .../internal/message/ThermostatModeType.java | 28 ++ .../internal/message/UnsupportedDevice.java | 27 + .../message/WallMountedThermostat.java | 32 ++ 42 files changed, 3844 insertions(+) create mode 100644 addons/binding/org.openhab.binding.maxcube/.classpath create mode 100644 addons/binding/org.openhab.binding.maxcube/.project create mode 100644 addons/binding/org.openhab.binding.maxcube/ESH-INF/binding/binding.xml create mode 100644 addons/binding/org.openhab.binding.maxcube/ESH-INF/thing/bridge.xml create mode 100644 addons/binding/org.openhab.binding.maxcube/ESH-INF/thing/thing-types.xml create mode 100644 addons/binding/org.openhab.binding.maxcube/META-INF/MANIFEST.MF create mode 100644 addons/binding/org.openhab.binding.maxcube/OSGI-INF/MaxCubeDiscovery.xml create mode 100644 addons/binding/org.openhab.binding.maxcube/OSGI-INF/MaxCubeHandlerFactory.xml create mode 100644 addons/binding/org.openhab.binding.maxcube/build.properties create mode 100644 addons/binding/org.openhab.binding.maxcube/pom.xml create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/MaxCubeBinding.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/config/MaxCubeBridgeConfiguration.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/config/MaxCubeConfiguration.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/MaxCubeBridge.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/Utils.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeBridgeDiscovery.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeDevicesDiscover.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeDiscover.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/factory/MaxCubeHandlerFactory.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/DeviceStatusListener.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/MaxCubeBridgeHandler.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/MaxCubeHandler.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/C_Message.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/Device.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceConfiguration.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceInformation.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceType.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/EcoSwitch.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/H_Message.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/HeatingThermostat.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/L_Message.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/M_Message.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/MaxTokenizer.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/Message.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/MessageType.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/RoomInformation.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/S_Command.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/SendCommand.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/ShutterContact.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/ThermostatModeType.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/UnsupportedDevice.java create mode 100644 addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/WallMountedThermostat.java diff --git a/addons/binding/org.openhab.binding.maxcube/.classpath b/addons/binding/org.openhab.binding.maxcube/.classpath new file mode 100644 index 0000000000000..a95e0906ca013 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/addons/binding/org.openhab.binding.maxcube/.project b/addons/binding/org.openhab.binding.maxcube/.project new file mode 100644 index 0000000000000..978de91639058 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/.project @@ -0,0 +1,33 @@ + + + org.openhab.binding.maxcube + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/addons/binding/org.openhab.binding.maxcube/ESH-INF/binding/binding.xml b/addons/binding/org.openhab.binding.maxcube/ESH-INF/binding/binding.xml new file mode 100644 index 0000000000000..f3f16cc4e77e6 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/ESH-INF/binding/binding.xml @@ -0,0 +1,11 @@ + + + + MaxCube Binding + This is the binding for eQ-3 MaxCube heating Thermostat. + Marcel Verpaalen + + diff --git a/addons/binding/org.openhab.binding.maxcube/ESH-INF/thing/bridge.xml b/addons/binding/org.openhab.binding.maxcube/ESH-INF/thing/bridge.xml new file mode 100644 index 0000000000000..2ae6cfda8e5f6 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/ESH-INF/thing/bridge.xml @@ -0,0 +1,40 @@ + + + + + + This bridge represents the MAX!Cube LAN gateway. + + + + network_address + + The IP address of the MAX!Cube LAN gateway. If none provided, binding will try to discover it. + false + + + port + + + Port of the LAN gateway. If empty, default port 62910 will be used. + + false + + + + The refresh interval in ms which is used to poll given MAX!Cube. + false + + + + The Serial Number identifier identifies one specific device. + false + + + + + + diff --git a/addons/binding/org.openhab.binding.maxcube/ESH-INF/thing/thing-types.xml b/addons/binding/org.openhab.binding.maxcube/ESH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..3daf5e815ad78 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/ESH-INF/thing/thing-types.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + MaxCube HeathingThermostat + + + + + + + + + + + + + The Serial Number identifier identifies one specific device. + true + + + + + + + + + + + + MaxCube All Switches (EcoSwitch, Pushbutton/ShutterContact) + + + + + + + + + + + The Serial Number identifier identifies one specific device. + true + + + + + + Number + + Thermostat Valve Position + + + + String + + Battery charge Level + + + + String + + Thermostat Mode Setting + + + + Number + + Actual measured room temperature + + + + Number + + Thermostat temperature setpoint + + + + + Contact + + Switch state information + + + \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/META-INF/MANIFEST.MF b/addons/binding/org.openhab.binding.maxcube/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000..10d83c0cc81cd --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/META-INF/MANIFEST.MF @@ -0,0 +1,25 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: MaxCube Binding +Bundle-SymbolicName: org.openhab.binding.maxcube;singleton:=true +Bundle-Vendor: openHAB +Bundle-Version: 2.0.0.qualifier +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-ClassPath: . +Import-Package: com.google.common.collect, + org.apache.commons.net.util;version="3.2.0", + org.eclipse.smarthome.config.core, + org.eclipse.smarthome.config.discovery, + org.eclipse.smarthome.core.common.registry, + org.eclipse.smarthome.core.library.types, + org.eclipse.smarthome.core.thing, + org.eclipse.smarthome.core.thing.binding, + org.eclipse.smarthome.core.types, + org.openhab.core.library.types, + org.openhab.core.types, + org.osgi.framework;version="1.8.0", + org.slf4j +Service-Component: OSGI-INF/* +Export-Package: org.openhab.binding.maxcube, + org.openhab.binding.maxcube.internal.handler +Require-Bundle: org.eclipse.osgi.services diff --git a/addons/binding/org.openhab.binding.maxcube/OSGI-INF/MaxCubeDiscovery.xml b/addons/binding/org.openhab.binding.maxcube/OSGI-INF/MaxCubeDiscovery.xml new file mode 100644 index 0000000000000..ec5162047caee --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/OSGI-INF/MaxCubeDiscovery.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/addons/binding/org.openhab.binding.maxcube/OSGI-INF/MaxCubeHandlerFactory.xml b/addons/binding/org.openhab.binding.maxcube/OSGI-INF/MaxCubeHandlerFactory.xml new file mode 100644 index 0000000000000..69de7f7b1cc66 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/OSGI-INF/MaxCubeHandlerFactory.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/addons/binding/org.openhab.binding.maxcube/build.properties b/addons/binding/org.openhab.binding.maxcube/build.properties new file mode 100644 index 0000000000000..66e21b90751a7 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/build.properties @@ -0,0 +1,6 @@ +source.. = src/main/java/ +output.. = target/classes +bin.includes = META-INF/,\ + .,\ + OSGI-INF/,\ + ESH-INF/ \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/pom.xml b/addons/binding/org.openhab.binding.maxcube/pom.xml new file mode 100644 index 0000000000000..19c466b37b6ae --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/pom.xml @@ -0,0 +1,24 @@ + + + + 4.0.0 + + + org.openhab + binding + 2.0.0-SNAPSHOT + + + + org.openhab.binding.maxcube + org.openhab.binding.maxcube + + + org.openhab.binding + org.openhab.binding.maxcube + 2.0.0-SNAPSHOT + + MaxCube Binding + eclipse-plugin + + diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/MaxCubeBinding.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/MaxCubeBinding.java new file mode 100644 index 0000000000000..397241cdc1068 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/MaxCubeBinding.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube; + +import java.util.Collection; +import java.util.Set; + +import org.eclipse.smarthome.core.thing.ThingTypeUID; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +/** + * The {@link MaxCubeBinding} class defines common constants, which are + * used across the whole binding. + * + * @author Marcel Verpaalen - Initial contribution + */ +public class MaxCubeBinding { + + public static final String BINDING_ID = "maxcube"; + + // List of main device types + public static final String DEVICE_THERMOSTAT = "thermostat"; + public static final String DEVICE_SWITCH = "switch"; + public static final String BRIDGE_MAXCUBE = "bridge"; + + // List of all Thing Type UIDs + public final static ThingTypeUID HEATHINGTHERMOSTAT_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_THERMOSTAT); + public final static ThingTypeUID SWITCH_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_SWITCH); + + // List of all Thing Type UIDs + public final static ThingTypeUID CubeBridge_THING_TYPE = new ThingTypeUID(BINDING_ID, BRIDGE_MAXCUBE); + + // List of all Channel ids + public final static String CHANNEL_VALVE = "valve"; + public final static String CHANNEL_BATTERY = "battery"; + public final static String CHANNEL_MODE = "mode"; + public final static String CHANNEL_ACTUALTEMP = "actual_temp"; + public final static String CHANNEL_SETTEMP = "set_temp"; + public final static String CHANNEL_SWITCH_STATE = "state"; + + public final static Collection SUPPORTED_THING_TYPES_UIDS = Lists.newArrayList( + HEATHINGTHERMOSTAT_THING_TYPE, SWITCH_THING_TYPE, CubeBridge_THING_TYPE); + + public final static Set SUPPORTED_DEVICE_THING_TYPES_UIDS =ImmutableSet.of( + HEATHINGTHERMOSTAT_THING_TYPE,SWITCH_THING_TYPE); + + + public final static Set SUPPORTED_BRIDGE_THING_TYPES_UIDS =ImmutableSet.of( + CubeBridge_THING_TYPE); +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/config/MaxCubeBridgeConfiguration.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/config/MaxCubeBridgeConfiguration.java new file mode 100644 index 0000000000000..146fc7cd44c00 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/config/MaxCubeBridgeConfiguration.java @@ -0,0 +1,42 @@ +package org.openhab.binding.maxcube.config; +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + + + +/** + * Configuration class for {@link MaxCubeBinding} bridge + * used to connect to the maxCube device. + * + * @author Marcel Verpaalen - Initial contribution + */ + +public class MaxCubeBridgeConfiguration { + + + public static final String IP_ADDRESS = "ipAddress"; + public static final String PORT = "port"; + public static final String REFRESH_INTERVAL = "refreshInterval"; + + + /** 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 int port = (int) 62910; + + /** The refresh interval in ms which is used to poll given MAX!Cube */ + public Long refreshInterval =(long) 60000; + + /** The unique serial number for a device */ + public String serialNumber; + +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/config/MaxCubeConfiguration.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/config/MaxCubeConfiguration.java new file mode 100644 index 0000000000000..800e63b8d32a6 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/config/MaxCubeConfiguration.java @@ -0,0 +1,30 @@ +package org.openhab.binding.maxcube.config; +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + + + +/** + * Configuration class for {@link MaxCubeBinding} + * used to connect to the maxCube device. + * + * @author Marcel Verpaalen - Initial contribution + */ + +public class MaxCubeConfiguration { + + public static final String SERIAL_NUMBER = "serialNumber"; + public static final String RFADDRESS = "rfAddress"; + public static final String FRIENDLY_NAME = "friendlyName"; + + + /** The unique serial number for a device */ + public String serialNumber; + + +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/MaxCubeBridge.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/MaxCubeBridge.java new file mode 100644 index 0000000000000..dc7076d1b56c7 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/MaxCubeBridge.java @@ -0,0 +1,463 @@ +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal; + +import static org.openhab.binding.maxcube.MaxCubeBinding.CHANNEL_MODE; +import static org.openhab.binding.maxcube.MaxCubeBinding.CHANNEL_SETTEMP; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.ConnectException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ArrayBlockingQueue; + +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.types.Command; +import org.openhab.binding.maxcube.internal.discovery.MaxCubeDiscover; +import org.openhab.binding.maxcube.internal.message.C_Message; +import org.openhab.binding.maxcube.internal.message.Device; +import org.openhab.binding.maxcube.internal.message.DeviceConfiguration; +import org.openhab.binding.maxcube.internal.message.DeviceInformation; +import org.openhab.binding.maxcube.internal.message.H_Message; +import org.openhab.binding.maxcube.internal.message.L_Message; +import org.openhab.binding.maxcube.internal.message.M_Message; +import org.openhab.binding.maxcube.internal.message.Message; +import org.openhab.binding.maxcube.internal.message.MessageType; +import org.openhab.binding.maxcube.internal.message.S_Command; +import org.openhab.binding.maxcube.internal.message.SendCommand; +import org.openhab.binding.maxcube.internal.message.ThermostatModeType; +import org.osgi.service.cm.ConfigurationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MaxCubeBridge} is responsible for connecting to the Max!Cube Lan gateway and read the data for + * each connected device. + * @author Marcel Verpaalen - Initial contribution OH2 version + * @author Andreas Heil (info@aheil.de) OH1 version + */ + +public class MaxCubeBridge { + + /** MaxCube's default off temperature */ + private static final DecimalType DEFAULT_OFF_TEMPERATURE = new DecimalType(4.5); + + /** MaxCubes default on temperature */ + private static final DecimalType DEFAULT_ON_TEMPERATURE = new DecimalType(30.5); + + private ArrayList configurations = new ArrayList(); + private ArrayList devices = new ArrayList(); + + private Logger logger = LoggerFactory.getLogger(MaxCubeBridge.class); + + /** maximum queue size that we're allowing */ + private static final int MAX_COMMANDS = 50; + private ArrayBlockingQueue commandQueue = new ArrayBlockingQueue (MAX_COMMANDS); + + private SendCommand lastCommandId = null; + + /** The IP address of the MAX!Cube LAN gateway */ + private String ipAddress; + + private boolean connectionEstablished = false; + + /** + * The port of the MAX!Cube LAN gateway as provided at + * http://www.elv.de/controller.aspx?cid=824&detail=10&detail2=3484 + */ + private int port = 62910; + + public MaxCubeBridge (String ipAddress){ + this.ipAddress = ipAddress; + if (ipAddress == null) { + try { + logger.info("Discover Max!Cube Lan interface."); + this.ipAddress = discoveryGatewayIp(); + } catch (ConfigurationException e) { + logger.warn("Cannot discover to Max!Cube Lan interface. Configure IP address manually."); + } + } + //TODO: optional configuration to get the actual temperature on a configured interval by changing the valve / temp setting + } + + /** + * Connects to the Max!Cube Lan gateway, reads and decodes the message + * this updates device information for each connected Max!Cube device + */ + public void refreshData() { + Message message; + + for (String raw : getRawMessage()){ + + try { + logger.debug("message block: '{}'",raw); + message = processRawMessage(raw); + message.debug(logger); + processMessage (message); + + } catch (Exception e) { + logger.info("Failed to process message received by MAX! protocol."); + logger.debug(Utils.getStackTrace(e)); + } + } + + + } + + /** + * Takes a command from the command queue and send it to + * {@link executeCommand} for execution. + * + */ + public void sendCommands() { + SendCommand sendCommand = commandQueue.poll(); + if (sendCommand!=null){ + executeCommand (sendCommand); + } + } + + /** + * Connects to the Max!Cube Lan gateway and returns the read data + * corresponding Message. + * + * @return the raw message text as ArrayList of String + */ + private ArrayList getRawMessage() { + //Fake a message for testing purposes + if (ipAddress.equals( "fakeMessage")){ + connectionEstablished = true; + logger.warn("Content based on faked data!!"); + return getRawFAKEMessage(); + } + synchronized (MaxCubeBridge.class){ + Socket socket = null; + BufferedReader reader = null; + ArrayList rawMessage = new ArrayList () ; + + try { + String raw = null; + socket = new Socket(ipAddress, port); + reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + boolean cont = true; + while (cont) { + raw = reader.readLine(); + if (raw == null) { + cont = false; + continue; + } + rawMessage.add(raw); + if (raw.startsWith("L:")) { + socket.close(); + cont = false; + connectionEstablished = true; + } + } + } catch (ConnectException e) { + logger.debug("Connection timed out on {} port {}",ipAddress, port ); + connectionEstablished = false; + } catch(Exception e) { + logger.debug("Exception occurred during execution: {}", e.getMessage(), e); + connectionEstablished = false; + } + + return rawMessage; + }} + /** + * Processes the raw TCP data read from the MAX protocol, returning the + * corresponding Message. + * + * @param raw + * the raw data line read from the MAX protocol + * @return message + * the @Message for the given raw data + */ + private Message processRawMessage(String raw) { + + if (raw.startsWith("H:")) { + return new H_Message(raw); + } else if (raw.startsWith("M:")) { + return new M_Message(raw); + } else if (raw.startsWith("C:")) { + return new C_Message(raw); + } else if (raw.startsWith("L:")) { + return new L_Message(raw); + } else { + logger.debug("Unknown message block: '{}'",raw); + } + return null; + } + + /** + * Processes the message + * @param Message + * the decoded message data + */ + private void processMessage (Message message){ + + if (message != null) { + message.debug(logger); + if (message.getType() == MessageType.M) { + M_Message msg = (M_Message) message; + for (DeviceInformation di : msg.devices) { + DeviceConfiguration c = null; + for (DeviceConfiguration conf : configurations) { + if (conf.getSerialNumber().equalsIgnoreCase(di.getSerialNumber())) { + c = conf; + break; + } + } + + if (c != null) { + configurations.remove(c); + } + + c = DeviceConfiguration.create(di); + configurations.add(c); + + c.setRoomId(di.getRoomId()); + } + } else if (message.getType() == MessageType.C) { + DeviceConfiguration c = null; + for (DeviceConfiguration conf : configurations) { + if (conf.getSerialNumber().equalsIgnoreCase(((C_Message) message).getSerialNumber())) { + c = conf; + break; + } + } + + if (c == null) { + configurations.add(DeviceConfiguration.create(message)); + } else { + c.setValues((C_Message) message); + } + } else if (message.getType() == MessageType.L) { + Collection tempDevices = ((L_Message) message).getDevices(configurations); + + for (Device d : tempDevices) { + Device existingDevice = findDevice(d.getSerialNumber(), devices); + if (existingDevice == null) { + devices.add(d); + } else { + devices.remove(existingDevice); + devices.add(d); + } + } + + logger.debug("{} devices found.", devices.size()); + + + } + } + } + + private Device findDevice(String serialNumber, ArrayList devices) { + for (Device device : devices) { + if (device.getSerialNumber().toUpperCase().equals(serialNumber)) { + return device; + } + } + return null; + } + + /** + * Discovers the MAX!CUbe LAN Gateway IP address. + * + * @return the cube IP if available, a blank string otherwise. + * @throws ConfigurationException + */ + private String discoveryGatewayIp() throws ConfigurationException { + String ip = MaxCubeDiscover.discoverIp(); + if (ip == null) { + throw new ConfigurationException("maxcube:ip", "IP address for MAX!Cube must be set manually"); + } else { + logger.info("Discovered MAX!Cube Lan Gateway at '{}'", ip); + } + return ip; + } + + public String getIp() { + return ipAddress; + } + + public void setIp(String ip) { + this.ipAddress = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + /** + * Returns the MaxCube Device decoded during the last refreshData + * + * @param serialNumber + * the serial number of the device as String + * @return device + * the {@link Device} information decoded in last refreshData + */ + + public Device getDevice(String serialNumber) { + return findDevice (serialNumber,devices); + } + + /** + * Returns the MaxCube Devices Array decoded during the last refresh + * + * @return devices + * the array of {@link Device} information decoded in the last refreshData + */ + public ArrayList getDevices() { + return devices; + } + + /** + * Returns true if the last connection to the Cube was successfull + * + * @return device + * the {@link Device} information decoded in last refreshData + */ + public boolean isConnectionEstablished() { + return connectionEstablished; + } + + /** + * Takes the device command and puts it on the command queue to be processed by the MAX!Cube Lan Gateway. + * Note that if multiple commands for the same item-channel combination are send prior that they are processed + * by the Max!Cube, they will be removed from the queue as they would not be meaningful. This will improve the behavior + * when using sliders in the GUI. + * @param SendCommand + * the SendCommand containing the serial number of the device as String + * the channelUID used to send the command and the the command data + */ + public synchronized void queueCommand(SendCommand sendCommand) { + + if (commandQueue.offer(sendCommand)){ + if (lastCommandId != null){ + if (lastCommandId.getKey().equals (sendCommand.getKey())){ + if (commandQueue.remove (lastCommandId)) + logger.debug("Removed Command id {} ({}) from queue. Superceeded by {}", lastCommandId.getId(),lastCommandId.getKey(),sendCommand.getId()); + }} + lastCommandId = sendCommand; + logger.debug("Command queued id {} ({}).", sendCommand.getId(),sendCommand.getKey()); + + } else{ + logger.debug("Command queued full dropping command id {} ({}).", sendCommand.getId(),sendCommand.getKey()); + } + + } + + + /** + * Processes device command and sends it to the MAX!Cube Lan Gateway. + * + * @param SendCommand + * the SendCommand containing the serial number of the device as String + * the channelUID used to send the command and the the command data + */ + public void executeCommand(SendCommand sendCommand) { + + String serialNumber = sendCommand.getDeviceSerial(); + ChannelUID channelUID= sendCommand.getChannelUID(); + Command command = sendCommand.getCommand(); + + // send command to MAX!Cube LAN Gateway + Device device = findDevice(serialNumber, devices); + + if (device == null) { + logger.debug("Cannot send command to device with serial number {}, device not listed.", serialNumber); + return; + } + + String rfAddress = device.getRFAddress(); + String commandString = null; + + //Temperature setting + if(channelUID.getId().equals(CHANNEL_SETTEMP)) { + + if (command instanceof DecimalType || command instanceof OnOffType) { + DecimalType decimalType = DEFAULT_OFF_TEMPERATURE; + if (command instanceof DecimalType) { + decimalType = (DecimalType) command; + } else if (command instanceof OnOffType) { + decimalType = OnOffType.ON.equals(command) ? DEFAULT_ON_TEMPERATURE : DEFAULT_OFF_TEMPERATURE; + } + + S_Command cmd = new S_Command(rfAddress, device.getRoomId(), decimalType.doubleValue()); + commandString = cmd.getCommandString(); + } + //Mode setting + } else if(channelUID.getId().equals(CHANNEL_MODE)) { + if (command instanceof StringType) { + String commandContent = command.toString().trim().toUpperCase(); + ThermostatModeType commandThermoType = null; + if (commandContent.contentEquals(ThermostatModeType.AUTOMATIC.toString())) { + commandThermoType = ThermostatModeType.AUTOMATIC; + } else if (commandContent.contentEquals(ThermostatModeType.BOOST.toString())) { + commandThermoType = ThermostatModeType.BOOST; + } else { + logger.debug("Only updates to AUTOMATIC & BOOST supported, received value ;'{}'", commandContent); + return; + } + S_Command cmd = new S_Command(rfAddress, device.getRoomId(), commandThermoType); + commandString = cmd.getCommandString(); + } + } + //Actual sending of the data to the Max!Cube Lan Gateway + synchronized (MaxCubeBridge.class){ + if (commandString != null) { + Socket socket = null; + try { + socket = new Socket(ipAddress, port); + DataOutputStream stream = new DataOutputStream(socket.getOutputStream()); + + byte[] b = commandString.getBytes(); + stream.write(b); + socket.close(); + + } catch (UnknownHostException e) { + logger.warn("Cannot establish connection with MAX!cube lan gateway while sending command to '{}'", ipAddress); + logger.debug(Utils.getStackTrace(e)); + } catch (IOException e) { + logger.warn("Cannot write data from MAX!Cube lan gateway while connecting to '{}'", ipAddress); + logger.debug(Utils.getStackTrace(e)); + } + logger.debug("Command {} ({}) sent to Max!Cube at IP: {}", sendCommand.getId(),sendCommand.getKey(),ipAddress); + } else { + logger.debug("Null Command not sent to {}", ipAddress); + } + }} + + + + + private ArrayList getRawFAKEMessage(){ + ArrayList rawMessage = new ArrayList () ; + rawMessage.add ("H:KEQ0565026,0b5951,0113,00000000,72aeba8b,03,32,0e090b,1127,03,0000"); + rawMessage.add ("M:00,01,VgICAQhiYWRrYW1lcgsNowIMU3R1ZGVlcmthbWVyB7bnAgILDaNLRVEwNTQ0MjQyEUJhZGthbWVyIFJhZGlhdG9yAQEHtudLRVEwMTQ1MTcyFVJhZGlhdG9yIFN0dWRlZXJrYW1lcgIB"); + rawMessage.add ("C:0b5951,7QtZUQATAf9LRVEwNTY1MDI2AQsABEAAAAAAAAAAAP///////////////////////////wsABEAAAAAAAAAAQf///////////////////////////2h0dHA6Ly9tYXguZXEtMy5kZTo4MC9jdWJlADAvbG9va3VwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENFVAAACgADAAAOEENFU1QAAwACAAAcIA=="); + rawMessage.add ("C:0b0da3,0gsNowIBEABLRVEwNTQ0MjQyLCQ9CQcYAzAM/wBIYViRSP1ZFE0gTSBNIEUgRSBFIEUgRSBFIEhhWJFQ/VkVUSBRIFEgRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIEhQWFpI/lkUTSBNIE0gRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIA=="); + rawMessage.add ("C:07b6e7,0ge25wECGP9LRVEwMTQ1MTcyKyE9CQcYAzAM/wBEflUaRSBFIEUgRSBFIEUgRSBFIEUgRSBFIER+VRpFIEUgRSBFIEUgRSBFIEUgRSBFIEUgRFRUcEjSVRJJIEkgSSBFIEUgRSBFIEUgRSBEVFRwSNJVEkkgSSBJIEUgRSBFIEUgRSBFIERUVG9U01URSSBJIEkgRSBFIEUgRSBFIEUgRFRUcEjSVRJJIEkgSSBFIEUgRSBFIEUgRSBEVFRwSNJVEkkgSSBJIEUgRSBFIEUgRSBFIA=="); + rawMessage.add ("L:CwsNowkSGQAJAAAACwe25wkSGWAvAAAA"); + return rawMessage; + } + + +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/Utils.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/Utils.java new file mode 100644 index 0000000000000..3bd8c3a78440e --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/Utils.java @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; + +import org.openhab.binding.maxcube.MaxCubeBinding; + +/** +* Utility class for common tasks within the MAX!Cube binding package. +* +* @author Andreas Heil (info@aheil.de) +* @author Marcel Verpaalen +* +* @since 1.4.0 +*/ + +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) + *
+	 * Hex     Binary
+	 * 9D0B    1001110100001011
+     *   MMMDDDDDM YYYYYY
+     *   100     0        = 1000b  = 8d  = month
+     *      11101         = 11101b = 29d = day
+     *             001011 = 1011b  = 11d = year-2000  
+	 * 
+ * + * Time decoding (one byte) + *
+	 * Hex     Decimal
+	 * 1F      31 * 0.5 hours = 15:30
+	 * 
+ * + * @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 0001 0010 would be returnd as 0100 1000. + * 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 ) { + if ( raw == null ) { + return null; + } + 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(); + } + + /** + * Retrieves the stacktrace of an exception as string. + * @param e + * the exception to resolve the stacktrace from + * @return + * the stacktrace from the exception provided + */ + public static String getStackTrace(Exception e) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + e.printStackTrace(printWriter); + return stringWriter.toString(); + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeBridgeDiscovery.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeBridgeDiscovery.java new file mode 100644 index 0000000000000..7ff09342308e3 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeBridgeDiscovery.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.discovery; + +import java.util.HashMap; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; +import org.eclipse.smarthome.config.discovery.DiscoveryResult; +import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.openhab.binding.maxcube.MaxCubeBinding; +import org.openhab.binding.maxcube.config.MaxCubeBridgeConfiguration; +import org.openhab.binding.maxcube.config.MaxCubeConfiguration; +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 + * + */ +public class MaxCubeBridgeDiscovery extends AbstractDiscoveryService { + private final static Logger logger = LoggerFactory.getLogger(MaxCubeBridgeDiscovery.class); + private static final int INITIAL_START_DELAY = 60; + + + public MaxCubeBridgeDiscovery() { + super(15); + } + + @Override + public Set getSupportedThingTypes() { + return MaxCubeBinding.SUPPORTED_BRIDGE_THING_TYPES_UIDS; + } + + private void discoverCube() { + String cubeSerialNumber = null; + + HashMap discoverResults = new HashMap(MaxCubeDiscover.DiscoverCube(null)); + if (discoverResults.containsKey(MaxCubeConfiguration.SERIAL_NUMBER)){ + cubeSerialNumber = discoverResults.get(MaxCubeConfiguration.SERIAL_NUMBER); + } + String ipAddress = null; + if (discoverResults.containsKey(MaxCubeBridgeConfiguration.IP_ADDRESS)){ + ipAddress = discoverResults.get(MaxCubeBridgeConfiguration.IP_ADDRESS); + } + + if(cubeSerialNumber!=null) { + logger.debug("Adding new Max!Cube Lan Gateway on {} with id '{}' to Smarthome inbox", ipAddress, cubeSerialNumber); + + ThingUID uid = new ThingUID( MaxCubeBinding.CubeBridge_THING_TYPE, "MaxCube_" + cubeSerialNumber); + if(uid!=null) { + DiscoveryResult result = DiscoveryResultBuilder.create(uid) + .withProperty(MaxCubeConfiguration.SERIAL_NUMBER,cubeSerialNumber) + .withLabel("MaxCube LAN Gateway on " + ipAddress ) + .build(); + thingDiscovered (result); + } + } + } + + @Override + public void startScan() { + discoverCube(); + } + + + /* (non-Javadoc) + * @see org.eclipse.smarthome.config.discovery.AbstractDiscoveryService#startBackgroundDiscovery() + */ + @Override + protected void startBackgroundDiscovery() { + Timer timer = new Timer(); + // to avoid conflict with normal maxcube starting delay this first discovery + timer.schedule(new TimerTask() { + public void run() { + discoverCube(); + } + }, (INITIAL_START_DELAY * 1000)); + } + + + @Override + public boolean isBackgroundDiscoveryEnabled() { + return false; + } + + +} + diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeDevicesDiscover.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeDevicesDiscover.java new file mode 100644 index 0000000000000..0736b2c5a3076 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeDevicesDiscover.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.discovery; + + +import java.util.Set; + +import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; +import org.eclipse.smarthome.config.discovery.DiscoveryResult; +import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.openhab.binding.maxcube.MaxCubeBinding; +import org.openhab.binding.maxcube.config.MaxCubeConfiguration; +import org.openhab.binding.maxcube.internal.MaxCubeBridge; +import org.openhab.binding.maxcube.internal.handler.DeviceStatusListener; +import org.openhab.binding.maxcube.internal.handler.MaxCubeBridgeHandler; +import org.openhab.binding.maxcube.internal.message.Device; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MaxCubeDevicesDiscover} class is used to discover Max!Cube devices that + * are connected to the Lan gateway. + * + * @author Marcel Verpaalen - Initial contribution + */ +public class MaxCubeDevicesDiscover extends AbstractDiscoveryService implements DeviceStatusListener { + + private final static Logger logger = LoggerFactory.getLogger(MaxCubeDevicesDiscover.class); + + private MaxCubeBridgeHandler maxCubeBridgeHandler; + + public MaxCubeDevicesDiscover( MaxCubeBridgeHandler maxCubeBridgeHandler) { + super(MaxCubeBinding.SUPPORTED_DEVICE_THING_TYPES_UIDS, 10,true); + this.maxCubeBridgeHandler = maxCubeBridgeHandler; + } + + public void activate() { + maxCubeBridgeHandler.registerDeviceStatusListener(this); + } + + public void deactivate() { + maxCubeBridgeHandler.unregisterDeviceStatusListener(this); + } + + @Override + public Set getSupportedThingTypes() { + return MaxCubeBinding.SUPPORTED_DEVICE_THING_TYPES_UIDS; + } + + @Override + public void onDeviceAdded(MaxCubeBridge bridge, Device device) { + logger.debug("Adding new Max!Cube {} with id '{}' to smarthome inbox", device.getType(), device.getSerialNumber()); + ThingUID thingUID = null; + String deviceid = device.getType() + "_" + device.getSerialNumber(); + deviceid = deviceid.replaceAll("\\s+",""); + deviceid = deviceid.replaceAll("\\+","Plus"); + switch (device.getType()) { + case WallMountedThermostat: + case HeatingThermostat: + case HeatingThermostatPlus: + thingUID = new ThingUID(MaxCubeBinding.HEATHINGTHERMOSTAT_THING_TYPE,deviceid); + break; + case ShutterContact: + case EcoSwitch: + + thingUID = new ThingUID(MaxCubeBinding.SWITCH_THING_TYPE, deviceid ); + break; + default: + break; + } + if(thingUID!=null) { + ThingUID bridgeUID = maxCubeBridgeHandler.getThing().getUID(); + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) + .withProperty(MaxCubeConfiguration.SERIAL_NUMBER, device.getSerialNumber()) + .withBridge(bridgeUID) + .withLabel( device.getType() + " " + device.getName() + " (serial: " + device.getSerialNumber() +")") + .build(); + thingDiscovered(discoveryResult); + } else { + logger.debug("Discovered Max!Cube item is unsupported: type '{}' with id '{}'", device.getType(), device.getSerialNumber()); + } + } + + @Override + protected void startScan() { + //this can be ignored here as we discover via the bridge + } + + @Override + public void onDeviceStateChanged(ThingUID bridge, Device device) { + //this can be ignored here + } + + @Override + public void onDeviceRemoved(MaxCubeBridge bridge, Device device) { + //this can be ignored here + } +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeDiscover.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeDiscover.java new file mode 100644 index 0000000000000..f8941a7a2409d --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/discovery/MaxCubeDiscover.java @@ -0,0 +1,220 @@ +/** + * Copyright (c) 2010-2013, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.discovery; + +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.util.Enumeration; +import java.util.HashMap; + +import org.openhab.binding.maxcube.config.MaxCubeBridgeConfiguration; +import org.openhab.binding.maxcube.config.MaxCubeConfiguration; +import org.openhab.binding.maxcube.internal.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Automatic UDP discovery of a MAX!Cube Lan Gateway on the local network. + * + * @author Marcel Verpaalen, based on UDP client code of Michiel De Mey + * @author Marcel Verpaalen, major revision for OH2 allowing discovery of a specific Max!Cube based on serial + * @since 1.4.0 + */ +public final class MaxCubeDiscover { + + static final String MAXCUBE_DISCOVER_STRING ="eQ3Max*\0**********I"; + static final String RFADDRESS ="rfAddress"; + static Logger logger = LoggerFactory.getLogger(MaxCubeDiscover.class); + static boolean discoveryRunning = false; + + /** + * Automatic UDP discovery of a MAX!Cube + * @param + * @return if the cube is found, returns the IP address as a string. Otherwise returns null + */ + public final static String discoverIp () { + HashMap discoverResults = new HashMap(DiscoverCube(null)); + if (discoverResults.containsKey(MaxCubeBridgeConfiguration.IP_ADDRESS)){ + return discoverResults.get(MaxCubeBridgeConfiguration.IP_ADDRESS); + } else { + logger.debug( "No Max!Cube Lan Gateway discovery on the network."); + return null; + } + } + + /** + * Automatic UDP discovery of a MAX!Cube + * @return if the cube is found, returns the HashMap containing the details. + */ + public synchronized final static HashMap DiscoverCube(final String cubeSerialNumber) { + discoveryRunning = true; + HashMap discoverResults = new HashMap(); + String maxCubeIP = null; + String maxCubeName = null; + String serialNumber = null; + String rfAddress = null; + + Thread thread = new Thread("Sendbroadcast"){ + public void run(){ + if (cubeSerialNumber !=null){ + sendBroadcastforDevice(cubeSerialNumber); + } else { + sendBroadcast(); + } + try { + sleep(5000); + } catch (Exception e) { + } + discoveryRunning = false; + logger.trace( "Done sending broadcast discovery messages."); + } + }; + thread.start(); + DatagramSocket bcReceipt = null; + + try{ + bcReceipt = new DatagramSocket(23272); + bcReceipt.setReuseAddress(true); + bcReceipt.setSoTimeout(10000); + + while (discoveryRunning){ + //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()).trim(); + 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)) { + maxCubeIP=receivePacket.getAddress().getHostAddress(); + maxCubeName=message.substring(0, 8); + serialNumber=message.substring(8, 18); + byte[] unknownData=message.substring(18,21).getBytes(); + rfAddress=Utils.getHex(message.substring(21).getBytes()).replace(" ", "").toLowerCase(); + logger.info("Max!Cube found on network"); + logger.debug("Found at : {}", maxCubeIP); + logger.debug("Name : {}", maxCubeName); + logger.debug("Serial : {}", serialNumber); + logger.debug("RF Address: {}", rfAddress); + logger.trace("Unknown : {}", Utils.getHex(unknownData)); + } + } + } catch (SocketTimeoutException ex) { + logger.debug("No further response"); + } catch (IOException ex) { + logger.debug(ex.toString()); + } finally { + //Close the port! + try { + if (bcReceipt !=null) + bcReceipt.close(); + } catch (Exception e) { + logger.debug(e.toString()); + } + } + //TODO add this to a discoverResults array to deal with possible multiple Lan gateways on the network + discoverResults.put(MaxCubeBridgeConfiguration.IP_ADDRESS, maxCubeIP); + discoverResults.put(MaxCubeConfiguration.FRIENDLY_NAME, maxCubeName); + discoverResults.put(MaxCubeConfiguration.SERIAL_NUMBER, serialNumber); + discoverResults.put(RFADDRESS, serialNumber); + return discoverResults; + } + + /** + * Send broadcast message over all active interfaces for a specific device + * @param serialnumber of the Max!Cube lan gateway searched. + */ + private static void sendBroadcastforDevice(String serialnumber) { + sendBroadcastMessage ("eQ3Max*\0" + serialnumber + "I"); + } + + /** + * Send a generic broadcast message over all active interfaces to find all devices + */ + private static void sendBroadcast() { + sendBroadcastMessage (MAXCUBE_DISCOVER_STRING); + } + + /** + * Send broadcast message over all active interfaces + * @param discoverString String to be used for the discovery + */ + private static void sendBroadcastMessage(String discoverString) { + DatagramSocket bcSend = null; + //Find the MaxCube using UDP broadcast + try { + bcSend = new DatagramSocket(); + bcSend.setBroadcast(true); + + byte[] sendData = discoverString.getBytes(); + + // Broadcast the message over all the network interfaces + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface networkInterface = (NetworkInterface) interfaces.nextElement(); + + if (networkInterface.isLoopback() || !networkInterface.isUp()) { + continue; + } + + for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) { + InetAddress broadcast = interfaceAddress.getBroadcast(); + if (broadcast == null) { + continue; + } + + //ugly hack to workaround Java issue of wrong broadcast address for Wlan devices + //http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7158636 + byte[] networkIpAddress = interfaceAddress.getAddress().getAddress(); + byte[] broadcastIpAddress = broadcast.getAddress(); + + if (networkIpAddress[0] != broadcastIpAddress[0]){ + broadcastIpAddress = networkIpAddress; + broadcastIpAddress[3]= (byte) 0xFF; + InetAddress newbroadcast = InetAddress.getByAddress(broadcastIpAddress); + logger.debug( "Strange broadcast address '{}' for IP {}, replaced with '{}' Interface: '{}' '{}'", broadcast.getHostAddress(), interfaceAddress.getAddress(),newbroadcast.getHostAddress(), networkInterface.getDisplayName(), networkInterface.getName()); + broadcast = newbroadcast; + } + // Send the broadcast package! + try { + DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, broadcast, 23272); + logger.debug( "Sending request packet sent to: {} Interface: '{}' '{}'", broadcast.getHostAddress(), networkInterface.getDisplayName(), networkInterface.getName()); + bcSend.send(sendPacket); + } catch (Exception e) { + logger.debug( "Error while sending request packet sent to: {} Interface: '{}' '{}'", broadcast.getHostAddress(), networkInterface.getDisplayName(), networkInterface.getName()); + + logger.debug(e.getMessage()); + logger.debug(Utils.getStackTrace(e)); + } + + logger.debug( "Request packet sent to: {} Interface: {}", broadcast.getHostAddress(), networkInterface.getDisplayName()); + } + } + + } catch (IOException ex) { + logger.debug(ex.toString()); + }finally { + try { + if (bcSend !=null) + bcSend.close(); + } catch (Exception e) { + logger.debug(e.toString()); + }} + + } +} + diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/factory/MaxCubeHandlerFactory.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/factory/MaxCubeHandlerFactory.java new file mode 100644 index 0000000000000..04eca0641645c --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/factory/MaxCubeHandlerFactory.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.factory; + +import java.util.Hashtable; + +import org.eclipse.smarthome.config.core.Configuration; +import org.eclipse.smarthome.config.discovery.DiscoveryService; +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.openhab.binding.maxcube.MaxCubeBinding; +import org.openhab.binding.maxcube.config.MaxCubeBridgeConfiguration; +import org.openhab.binding.maxcube.config.MaxCubeConfiguration; +import org.openhab.binding.maxcube.internal.discovery.MaxCubeDevicesDiscover; +import org.openhab.binding.maxcube.internal.handler.MaxCubeBridgeHandler; +import org.openhab.binding.maxcube.internal.handler.MaxCubeHandler; +import org.osgi.framework.ServiceRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MaxCubeHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Marcel Verpaalen - Initial contribution + */ + +public class MaxCubeHandlerFactory extends BaseThingHandlerFactory { + + private Logger logger = LoggerFactory.getLogger(MaxCubeHandlerFactory.class); + private ServiceRegistration discoveryServiceReg; + + + @Override + public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, + ThingUID thingUID, ThingUID bridgeUID) { + + if (MaxCubeBinding.CubeBridge_THING_TYPE.equals(thingTypeUID)) { + ThingUID cubeBridgeUID = getBridgeThingUID(thingTypeUID, thingUID, configuration); + return super.createThing(thingTypeUID, configuration, cubeBridgeUID, null); + } + if (MaxCubeBinding.HEATHINGTHERMOSTAT_THING_TYPE.equals(thingTypeUID)) { + ThingUID thermostatUID = getMaxCubeDeviceUID(thingTypeUID, thingUID, configuration, bridgeUID); + return super.createThing(thingTypeUID, configuration, thermostatUID , bridgeUID); + } + if (MaxCubeBinding.SWITCH_THING_TYPE.equals(thingTypeUID)) { + ThingUID thermostatUID = getMaxCubeDeviceUID(thingTypeUID, thingUID, configuration, bridgeUID); + return super.createThing(thingTypeUID, configuration, thermostatUID , bridgeUID); + } + throw new IllegalArgumentException("The thing type " + thingTypeUID + + " is not supported by the MaxCube binding."); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return MaxCubeBinding.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + + private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID, + Configuration configuration) { + if (thingUID == null) { + //TODO: Should this look at IP or at serial # + String ipAddress = (String) configuration.get(MaxCubeBridgeConfiguration.IP_ADDRESS); + thingUID = new ThingUID(thingTypeUID, ipAddress); + } + return thingUID; + } + + private ThingUID getMaxCubeDeviceUID(ThingTypeUID thingTypeUID, ThingUID thingUID, + Configuration configuration , ThingUID bridgeUID ) { + String SerialNumber = (String) configuration.get(MaxCubeConfiguration.SERIAL_NUMBER); + + if (thingUID == null) { + thingUID = new ThingUID(thingTypeUID, "Device" + SerialNumber , bridgeUID.getId()); + } + return thingUID; + } + + + + private void registerDeviceDiscoveryService(MaxCubeBridgeHandler maxCubeBridgeHandler) { + MaxCubeDevicesDiscover discoveryService = new MaxCubeDevicesDiscover(maxCubeBridgeHandler); + discoveryService.activate(); + this.discoveryServiceReg = bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable()); + } + + @Override + protected void removeHandler(ThingHandler thingHandler) { + if(this.discoveryServiceReg!=null) { + MaxCubeDevicesDiscover service = (MaxCubeDevicesDiscover) bundleContext.getService(discoveryServiceReg.getReference()); + service.deactivate(); + discoveryServiceReg.unregister(); + discoveryServiceReg = null; + } + } + + @Override + protected ThingHandler createHandler(Thing thing) { + if (thing.getThingTypeUID().equals(MaxCubeBinding.CubeBridge_THING_TYPE)) { + MaxCubeBridgeHandler handler = new MaxCubeBridgeHandler((Bridge) thing); + registerDeviceDiscoveryService(handler); + return handler; + } else if (thing.getThingTypeUID().equals(MaxCubeBinding.SWITCH_THING_TYPE)) { + return new MaxCubeHandler(thing); + } else if (thing.getThingTypeUID().equals(MaxCubeBinding.HEATHINGTHERMOSTAT_THING_TYPE)) { + return new MaxCubeHandler(thing); + } else { + logger.debug("ThingHandler createHandler return null"); + return null; + } + } + +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/DeviceStatusListener.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/DeviceStatusListener.java new file mode 100644 index 0000000000000..57c78058a9b4f --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/DeviceStatusListener.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.handler; + +import org.eclipse.smarthome.core.thing.ThingUID; +import org.openhab.binding.maxcube.internal.MaxCubeBridge; +import org.openhab.binding.maxcube.internal.message.Device; + + +/** + * The {@link DeviceStatusListener} is notified when a device status has changed or a device has been removed or added. + * + * @author Marcel Verpaalen - Initial contribution + * + */ +public interface DeviceStatusListener { + + /** + * This method is called whenever the state of the given device has changed. The new state can be obtained by {@link FullLight#getState()}. + * @param bridge The maxcube bridge the changed device is connected to. + * @param device The device which received the state update. + */ + public void onDeviceStateChanged(ThingUID bridge, Device device); + + /** + * This method us called whenever a device is removed. + * @param bridge The maxcube bridge the removed device was connected to. + * @param device The device which is removed. + */ + public void onDeviceRemoved(MaxCubeBridge bridge, Device device); + + /** + * This method us called whenever a device is added. + * @param bridge The maxcube bridge the added device was connected to. + * @param device The device which is added. + */ + public void onDeviceAdded(MaxCubeBridge bridge, Device device); + +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/MaxCubeBridgeHandler.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/MaxCubeBridgeHandler.java new file mode 100644 index 0000000000000..97a50284764f8 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/MaxCubeBridgeHandler.java @@ -0,0 +1,253 @@ +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.handler; + + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler; +import org.eclipse.smarthome.core.types.Command; +import org.openhab.binding.maxcube.config.MaxCubeBridgeConfiguration; +import org.openhab.binding.maxcube.internal.MaxCubeBridge; +import org.openhab.binding.maxcube.internal.message.Device; +import org.openhab.binding.maxcube.internal.message.SendCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link MaxCubeBridgeHandler} is the handler for a MaxCube Cube and connects it to + * the framework. All {@link MaxCubeHandler}s use the {@link MaxCubeBridgeHandler} + * to execute the actual commands. + * + * @author Marcel Verpaalen - Initial contribution + * + */ +public class MaxCubeBridgeHandler extends BaseBridgeHandler { + + private MaxCubeBridge bridge = null; + + public MaxCubeBridgeHandler(Bridge br) { + super(br); + } + + private Logger logger = LoggerFactory.getLogger(MaxCubeBridgeHandler.class); + + /** The refresh interval which is used to poll given MAX!Cube */ + private long refreshInterval = 10000; + ScheduledFuture refreshJob; + + private ArrayList devices = new ArrayList(); + private HashSet lastActiveDevices = new HashSet(); + + private boolean previousOnline = false; + + private List deviceStatusListeners = new CopyOnWriteArrayList<>(); + + private ScheduledFuture pollingJob; + private Runnable pollingRunnable = new Runnable() { + @Override + public void run() { + refreshData(); } + }; + private ScheduledFuture sendCommandJob; + private long sendCommandInterval = 10000; + private Runnable sendCommandRunnable = new Runnable() { + @Override + public void run() { + sendCommands(); } + }; + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.warn("No bridge commands defined."); + } + + @Override + public void dispose() { + logger.debug("Handler disposed."); + if(pollingJob!=null && !pollingJob.isCancelled()) { + pollingJob.cancel(true); + pollingJob = null; + } + if(sendCommandJob!=null && !sendCommandJob.isCancelled()) { + sendCommandJob.cancel(true); + sendCommandJob = null; + } + if (bridge != null) { + bridge = null; + } + } + + @Override + public void initialize() { + logger.debug("Initializing MaxCube bridge handler."); + + MaxCubeBridgeConfiguration configuration = getConfigAs(MaxCubeBridgeConfiguration.class); + if (configuration.refreshInterval != 0) { + logger.debug("MaxCube refreshInterval {}.", configuration.refreshInterval); + refreshInterval = configuration.refreshInterval;} + startAutomaticRefresh(); + } + + private synchronized void initializeBridge() { + MaxCubeBridgeConfiguration configuration = getConfigAs(MaxCubeBridgeConfiguration.class); + + if (bridge == null) { + bridge = new MaxCubeBridge (configuration.ipAddress); + if (configuration.port != 0) { + logger.trace("MaxCube Port {}.", configuration.port); + bridge.setPort ( configuration.port); + } + if (bridge.getIp()==null) { + bridge = null; + updateStatus(ThingStatus.OFFLINE); + } + } + } + + private synchronized void startAutomaticRefresh() { + if (pollingJob == null || pollingJob.isCancelled()) { + pollingJob = scheduler.scheduleAtFixedRate(pollingRunnable, 0, refreshInterval, TimeUnit.MILLISECONDS); + } + if (sendCommandJob == null || sendCommandJob.isCancelled()) { + sendCommandJob = scheduler.scheduleAtFixedRate(sendCommandRunnable, 0, sendCommandInterval, TimeUnit.MILLISECONDS); + + } + } + + + /** + * initiates send commands to the maxCube bridge + */ + private synchronized void sendCommands() { + if (bridge !=null) + bridge.sendCommands(); + } + /** + * initiates read data from the maxCube bridge + */ + private synchronized void refreshData() { + + if (bridge==null){ + + initializeBridge() ; + } + try { + if (bridge !=null){ + bridge.refreshData(); + if (bridge.isConnectionEstablished()){ + if ( previousOnline == false) { + updateStatus(ThingStatus.ONLINE); + previousOnline = bridge.isConnectionEstablished(); + } + + devices = bridge.getDevices(); + for (Device di : devices){ + if (lastActiveDevices.contains(di.getSerialNumber())) { + for (DeviceStatusListener deviceStatusListener : deviceStatusListeners) { + try { + deviceStatusListener.onDeviceStateChanged(getThing().getUID(), di); + } catch (Exception e) { + logger.error( + "An exception occurred while calling the DeviceStatusListener", e); + } + } } + //New device, not seen before, pass to Discovery + else { + for (DeviceStatusListener deviceStatusListener : deviceStatusListeners) { + try { + deviceStatusListener.onDeviceAdded(bridge, di); + } catch (Exception e) { + logger.error( + "An exception occurred while calling the DeviceStatusListener", e); + } + lastActiveDevices.add (di.getSerialNumber()); + } + } + } + }else if (previousOnline) onConnectionLost (bridge); + } + + } catch(Exception e) { + logger.debug("Exception occurred during execution: {}", e.getMessage(), e); + } + } + + + public Device getDeviceById(String maxCubeDeviceSerial) { + if (bridge !=null){ + bridge.getDevice (maxCubeDeviceSerial); + } + return null; + } + + public void onConnectionLost(MaxCubeBridge bridge) { + logger.info("Bridge connection lost. Updating thing status to OFFLINE."); + previousOnline = false; + this.bridge = null; + updateStatus(ThingStatus.OFFLINE); + } + + public void onConnection(MaxCubeBridge bridge) { + logger.info("Bridge connected. Updating thing status to ONLINE."); + this.bridge = bridge; + updateStatus(ThingStatus.ONLINE); + } + + public boolean registerDeviceStatusListener(DeviceStatusListener deviceStatusListener) { + if (deviceStatusListener == null) { + throw new NullPointerException("It's not allowed to pass a null deviceStatusListener."); + } + boolean result = deviceStatusListeners.add(deviceStatusListener); + if (result) { + // onUpdate(); + } + return result; + } + + public boolean unregisterDeviceStatusListener(DeviceStatusListener deviceStatusListener) { + boolean result = deviceStatusListeners.remove(deviceStatusListener); + if (result) { + // onUpdate(); + } + return result; + } + + public void clearDeviceList(){ + lastActiveDevices=null; + } + + /** + * Processes device command and sends it to the MAX!Cube Lan Gateway. + * + * @param serialNumber + * the serial number of the device as String + * @param channelUID + * the ChannelUID used to send the command + * @param command + * the command data + */ + public void processCommand(SendCommand sendCommand) { + if (bridge !=null){ + bridge.queueCommand (sendCommand); + } else{ + logger.warn("Bridge not connected. Cannot set send command."); + } + + + } + + +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/MaxCubeHandler.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/MaxCubeHandler.java new file mode 100644 index 0000000000000..b7b179d81964d --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/handler/MaxCubeHandler.java @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2014 openHAB UG (haftungsbeschraenkt) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.handler; + +import static org.openhab.binding.maxcube.MaxCubeBinding.CHANNEL_ACTUALTEMP; +import static org.openhab.binding.maxcube.MaxCubeBinding.CHANNEL_BATTERY; +import static org.openhab.binding.maxcube.MaxCubeBinding.CHANNEL_MODE; +import static org.openhab.binding.maxcube.MaxCubeBinding.CHANNEL_SETTEMP; +import static org.openhab.binding.maxcube.MaxCubeBinding.CHANNEL_VALVE; +import static org.openhab.binding.maxcube.MaxCubeBinding.CHANNEL_SWITCH_STATE; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.State; +import org.openhab.binding.maxcube.config.MaxCubeConfiguration; +import org.openhab.binding.maxcube.internal.MaxCubeBridge; +import org.openhab.binding.maxcube.internal.message.Device; +import org.openhab.binding.maxcube.internal.message.EcoSwitch; +import org.openhab.binding.maxcube.internal.message.HeatingThermostat; +import org.openhab.binding.maxcube.internal.message.SendCommand; +import org.openhab.binding.maxcube.internal.message.ShutterContact; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MaxCubeHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Marcel Verpaalen - Initial contribution + */ +public class MaxCubeHandler extends BaseThingHandler implements DeviceStatusListener { + + private Logger logger = LoggerFactory.getLogger(MaxCubeHandler.class); + private int refresh = 60; // refresh every minute as default + ScheduledFuture refreshJob; + private MaxCubeBridgeHandler bridgeHandler; + + private String maxCubeDeviceSerial; + + public MaxCubeHandler(Thing thing) { + super(thing); + } + + /** + * {@inheritDoc} + */ + @Override + public void initialize() { + final String configDeviceId = getConfigAs(MaxCubeConfiguration.class).serialNumber; + if (configDeviceId != null) { + maxCubeDeviceSerial = configDeviceId; + } + if (maxCubeDeviceSerial != null){ + logger.debug("Initialized maxcube device handler for {}.", maxCubeDeviceSerial);} + else { + logger.debug("Initialized maxcube device missing serialNumber configuration... troubles ahead"); + } + //until we get an update put the Thing offline + updateStatus(ThingStatus.OFFLINE); + deviceOnlineWatchdog(); + } + + /* (non-Javadoc) + * @see org.eclipse.smarthome.core.thing.binding.BaseThingHandler#dispose() + */ + @Override + public void dispose() { + // TODO Auto-generated method stub + logger.debug("Thing {} {} disposed.", getThing(), maxCubeDeviceSerial); + if(bridgeHandler!=null) bridgeHandler.clearDeviceList(); + super.dispose(); + } + + private void deviceOnlineWatchdog() { + Runnable runnable = new Runnable() { + public void run() { + try { + MaxCubeBridgeHandler bridgeHandler = getMaxCubeBridgeHandler(); + if(bridgeHandler!=null) { + if ( bridgeHandler.getDeviceById(maxCubeDeviceSerial) == null) { + updateStatus(ThingStatus.OFFLINE); + } else updateStatus(ThingStatus.ONLINE); + + } else { + logger.debug("Bridge for maxcube device {} not found.", maxCubeDeviceSerial); + updateStatus(ThingStatus.OFFLINE); + } + + } catch(Exception e) { + logger.debug("Exception occurred during execution: {}", e.getMessage(), e); + } + + } + }; + + refreshJob = scheduler.scheduleAtFixedRate(runnable, 0, refresh, TimeUnit.SECONDS); + } + + + private synchronized MaxCubeBridgeHandler getMaxCubeBridgeHandler() { + + if(this.bridgeHandler==null) { + Bridge bridge = getBridge(); + if (bridge == null) { + //no bridge is assigned to the device, will register it to the first found bridge + ArrayList maxCubeBridges = new ArrayList(); + Collection allThings = thingRegistry.getAll(); + for ( Thing br : allThings ){ + if (br instanceof Bridge){ + if (br.getHandler() instanceof MaxCubeBridgeHandler) maxCubeBridges.add ((Bridge) br); + } + } + if (!(maxCubeBridges.isEmpty())) bridge = maxCubeBridges.get(0); + logger.debug("maxCube LAN gateway bridge not assigned. registering automatically to {}." , bridge.getUID() ); + //TODO: Before assigning would be good to check if the item actually exists in the bridge. + } + ThingHandler handler = bridge.getHandler(); + if (handler instanceof MaxCubeBridgeHandler) { + this.bridgeHandler = (MaxCubeBridgeHandler) handler; + this.bridgeHandler.registerDeviceStatusListener(this); + } else { + return null; + } + } + return this.bridgeHandler; + } + /** + * {@inheritDoc} + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + MaxCubeBridgeHandler maxCubeBridge = getMaxCubeBridgeHandler(); + if (maxCubeBridge == null) { + logger.warn("maxCube LAN gateway bridge handler not found. Cannot handle command without bridge."); + return; + } + if (maxCubeDeviceSerial == null){ + logger.warn("Serial number missing. Can't send command to device '{}'", getThing()); + return; + } + + if(channelUID.getId().equals(CHANNEL_SETTEMP) || channelUID.getId().equals(CHANNEL_MODE)) { + SendCommand sendCommand = new SendCommand (maxCubeDeviceSerial,channelUID,command); + maxCubeBridge.processCommand (sendCommand); + } + else { + logger.warn("Setting of channel {} not possible. Read-only", channelUID); + } + } + + @Override + public void onDeviceStateChanged(ThingUID bridge, Device device) { + if (device.getSerialNumber().equals (maxCubeDeviceSerial) ){ + logger.debug("Updating states of {} {} ({}) id: {}", device.getType(), device.getName(), device.getSerialNumber(), getThing().getUID() ); + updateStatus(ThingStatus.ONLINE); + //TODO: Could make this more intelligent by checking first if anything has changed, only then make the update + switch (device.getType()) { + case WallMountedThermostat: + case HeatingThermostat: + case HeatingThermostatPlus: + updateState(new ChannelUID(getThing().getUID(), CHANNEL_SETTEMP), (State) ( (HeatingThermostat) device).getTemperatureSetpoint()); + updateState(new ChannelUID(getThing().getUID(), CHANNEL_ACTUALTEMP), (State) ( (HeatingThermostat) device).getTemperatureActual()); + updateState(new ChannelUID(getThing().getUID(), CHANNEL_MODE), (State) ( (HeatingThermostat) device).getModeString()); + updateState(new ChannelUID(getThing().getUID(), CHANNEL_BATTERY), (State) ( (HeatingThermostat) device).getBatteryLow()); + updateState(new ChannelUID(getThing().getUID(), CHANNEL_VALVE), (State) ( (HeatingThermostat) device).getValvePosition()); + break; + case ShutterContact: + updateState(new ChannelUID(getThing().getUID(), CHANNEL_SWITCH_STATE), (State) ( (ShutterContact) device).getShutterState()); + updateState(new ChannelUID(getThing().getUID(), CHANNEL_BATTERY), (State) ( (ShutterContact) device).getBatteryLow()); + break; + case EcoSwitch: + updateState(new ChannelUID(getThing().getUID(), CHANNEL_SWITCH_STATE), (State) ( (EcoSwitch) device).getShutterState()); + updateState(new ChannelUID(getThing().getUID(), CHANNEL_BATTERY), (State) ( (EcoSwitch) device).getBatteryLow()); + break; + default: + logger.debug("Unhandled Device {}.",device.getType()); + break; + + }} + + } + + @Override + public void onDeviceRemoved(MaxCubeBridge bridge, Device device) { + // not relevant here + } + + @Override + public void onDeviceAdded(MaxCubeBridge bridge, Device device) { + // not relevant here + + } +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/C_Message.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/C_Message.java new file mode 100644 index 0000000000000..100f61a41b26e --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/C_Message.java @@ -0,0 +1,211 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import java.io.UnsupportedEncodingException; + +import org.apache.commons.net.util.Base64; +import org.openhab.binding.maxcube.internal.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The C message contains configuration about a MAX! device. + * + * @author Andreas Heil (info@aheil.de) + * @author Marcel Verpaalen - Detailed parsing, OH2 Update + * @since 1.4.0 + */ +public final class C_Message extends Message { + + private static final Logger logger = LoggerFactory.getLogger(C_Message.class); + + private String rfAddress = null; + private int length = 0; + private DeviceType deviceType = null; + private String serialNumber = null; + private String tempComfort= null; + private String tempEco = null; + private String tempSetpointMax= null; + private String tempSetpointMin= null; + private String tempOffset = null; + private String tempOpenWindow = null; + private String durationOpenWindow = null; + private String decalcification = null; + private String valveMaximum = null; + private String valveOffset = null; + private String programData = null; + private String boostDuration = null; + private String boostValve = null; + + + public C_Message(String raw) { + super(raw); + logger.debug(" *** C-Message ***"); + String[] tokens = this.getPayload().split(Message.DELIMETER); + + rfAddress = tokens[0]; + + byte[] bytes = Base64.decodeBase64(tokens[1].getBytes()); + + 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]); + + serialNumber = getSerialNumber(bytes); + if (deviceType == DeviceType.HeatingThermostatPlus || deviceType == DeviceType.HeatingThermostat || deviceType == DeviceType.WallMountedThermostat) parseHeatingThermostatData (bytes); + if (deviceType == DeviceType.EcoSwitch || deviceType == DeviceType.ShutterContact) logger.trace("Device {} type {} Data:", rfAddress, deviceType.toString() , parseData (bytes)); + } + + private String getSerialNumber(byte[] bytes) { + byte[] sn = new byte[10]; + + for (int i = 0; i < 10; i++) { + sn[i] = (byte) bytes[i + 8]; + } + + try { + return new String(sn, "UTF-8"); + } catch (UnsupportedEncodingException e) { + logger.debug("Cannot encode serial number from C message due to encoding issues."); + } + + return ""; + } + + 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] = (byte) bytes[i + DataStart]; + } + logger.trace("DataBytes: " + Utils.getHex(sn)); + try { + return new String(sn, "UTF-8"); + } catch (UnsupportedEncodingException e) { + logger.debug("Cannot encode device string from C message due to encoding issues."); + } + + } catch (Exception e) { + logger.debug(e.getMessage()); + logger.debug(Utils.getStackTrace(e)); + } + + return ""; + } + + private void parseHeatingThermostatData(byte[] bytes) { + try{ + + int plusDataStart = 18; + int programDataStart = 11; + tempComfort= Float.toString( bytes[plusDataStart ]/2); + tempEco = Float.toString( bytes[plusDataStart + 1]/2); + tempSetpointMax= Float.toString( bytes[plusDataStart + 2]/2); + tempSetpointMin= Float.toString( bytes[plusDataStart + 3]/2); + + logger.debug("DeviceType: {}", deviceType.toString()); + logger.debug("RFAddress: {}", rfAddress); + logger.debug("Temp Comfort: {}", tempComfort); + logger.debug("TempEco: {}", tempEco); + logger.debug("Temp Setpoint Max: {}", tempSetpointMax); + logger.debug("Temp Setpoint Min: {}", tempSetpointMin); + + 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 = Double.toString( (bytes[plusDataStart +4 ]/2) - 3.5); + tempOpenWindow = Float.toString( bytes[plusDataStart + 5]/2); + durationOpenWindow = Float.toString( bytes[plusDataStart + 6]); + boostDuration = Float.toString( bytes[plusDataStart + 7]&0xFF >> 5 ); + boostValve = Float.toString( (bytes[plusDataStart + 7]&0x1F)*5); + decalcification = Float.toString( bytes[plusDataStart + 8]); + valveMaximum = Float.toString( bytes[plusDataStart + 9]&0xFF * 100 / 255); + valveOffset = Float.toString( bytes[plusDataStart+ 10]&0xFF * 100 / 255 ); + logger.debug("Temp Offset: {}", tempOffset); + logger.debug("Temp Open Window: {}", tempOpenWindow ); + logger.debug("Duration Open Window: {}", durationOpenWindow); + logger.debug("Duration Boost: {}", boostDuration); + logger.debug("Boost Valve Pos: {}", boostValve); + logger.debug("Decalcification: {}", decalcification); + logger.debug("ValveMaximum: {}", valveMaximum); + logger.debug("ValveOffset: {}", valveOffset); + } + programData = ""; + int ln = 13 * 6; //first day = Sat + String startTime = "00:00h"; + for (int char_idx = plusDataStart + programDataStart; char_idx < (plusDataStart + programDataStart + 26*7); char_idx++) { + if (ln % 13 == 0 ) { programData += "\r\n Day " + Integer.toString((ln / 13) % 7 ) + ": "; startTime = "00:00h"; } + int progTime = (bytes[char_idx+1]&0xFF ) * 5 + (bytes[char_idx]&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[char_idx] /4) + "C "; + startTime = endTime; + char_idx++; + ln++; + } + logger.debug("ProgramData: {}", programData); + + } catch (Exception e) { + logger.debug(e.getMessage()); + logger.debug(Utils.getStackTrace(e)); + } + return ; + } + + public String getSerialNumber() { + return serialNumber; + } + + @Override + public MessageType getType() { + return MessageType.C; + } + + public String getRFAddress() { + return rfAddress; + } + + public DeviceType getDeviceType() { + return deviceType; + } + + @Override + public void debug(Logger logger) { + logger.debug("=== C_Message === "); + logger.trace("\tRAW: {}", this.getPayload()); + logger.debug("DeviceType: {}" , deviceType.toString()); + logger.debug("SerialNumber: {}" , serialNumber); + logger.debug("RFAddress: {}" , rfAddress); + } +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/Device.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/Device.java new file mode 100644 index 0000000000000..3ecda6cb93a63 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/Device.java @@ -0,0 +1,253 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.eclipse.smarthome.core.library.types.OpenClosedType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.openhab.binding.maxcube.internal.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for devices provided by the MAX!Cube protocol. + * + * @author Andreas Heil (info@aheil.de) + * @author Marcel Verpaalen - OH2 update + * @since 1.4.0 + */ +public abstract class Device { + + private final static Logger logger = LoggerFactory.getLogger(Device.class); + + private String serialNumber = ""; + private String rfAddress = ""; + private int roomId = -1; + private DeviceConfiguration config; + + 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; + + public Device(DeviceConfiguration c) { + this.serialNumber = c.getSerialNumber(); + this.rfAddress = c.getRFAddress(); + this.config = c; + } + + public abstract DeviceType getType(); + + public String getName(){ + return config.getName(); + } + + public abstract Calendar getLastUpdate(); + + private static Device create(String rfAddress, List configurations) { + Device returnValue = null; + for (DeviceConfiguration c : configurations) { + if (c.getRFAddress().toUpperCase().equals(rfAddress.toUpperCase())) { + 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); + default: + return new UnsupportedDevice(c); + } + } + } + return returnValue; + } + + public static Device create(byte[] raw, List 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.warn("Can't create device from received message, returning NULL."); + return null; + } + + // 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,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] == false && bits2[0] == false) { + heatingThermostat.setMode(ThermostatModeType.AUTOMATIC); + } else if (bits2[1] == false && bits2[0] == true) { + heatingThermostat.setMode(ThermostatModeType.MANUAL); + } else if (bits2[1] == true && bits2[0] == false) { + heatingThermostat.setMode(ThermostatModeType.VACATION); + } else if (bits2[1] == true && bits2[0] == true) { + heatingThermostat.setMode(ThermostatModeType.BOOST); + } else { + // TODO: handel malformed message + } + + 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, heatingThermostat.getMode()) ; + } + } + logger.debug ("Device {}: Actual Temperature : {}",rfAddress, (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 ("EcoSwitch Device {} status bytes : {}", rfAddress, eCoSwitchData); + case ShutterContact: + ShutterContact shutterContact = (ShutterContact) device; + // xxxx xx10 = shutter open, xxxx xx00 = shutter closed + if (bits2[1] == true && bits2[0] == false) { + shutterContact.setShutterState(OpenClosedType.OPEN); + logger.trace ("Device {} status: Open", rfAddress); + } else if (bits2[1] == false && bits2[0] == false) { + shutterContact.setShutterState(OpenClosedType.CLOSED); + logger.trace ("Device {} status: Closed", rfAddress); + } else { + logger.trace ("Device {} status switch status Unknown (true-true)", rfAddress); + } + + break; + default: + logger.debug("Unhandled Device. DataBytes: " + Utils.getHex(raw)); + break; + + } + return device; + } + + private final void setBatteryLow(boolean batteryLow) { + this.batteryLow = batteryLow; + } + + public final StringType getBatteryLow() { + return new StringType(this.batteryLow ? "low" : "ok"); + } + + 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; + } + + private void setLinkStatusError(boolean linkStatusError) { + this.linkStatusError = linkStatusError; + } + + private void setPanelLocked(boolean panelLocked) { + this.panelLocked = panelLocked; + } + + private void setGatewayKnown(boolean gatewayKnown) { + this.gatewayKnown = gatewayKnown; + } + + private void setDstSettingActive(boolean dstSettingsActive) { + this.DstSettingsActive = dstSettingsActive; + } + + private void setValid(boolean valid) { + this.valid = valid; + } + + private void setError(boolean error) { + this.error = error; + + } + + public String getSerialNumber() { + return serialNumber; + } + + private void setInitialized(boolean initialized) { + this.initialized = initialized; + } + + private void setAnswer(boolean answer) { + this.answer = answer; + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceConfiguration.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceConfiguration.java new file mode 100644 index 0000000000000..1c69a46bfc2e7 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceConfiguration.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + + +/** +* Base class for configuration provided by the MAX!Cube C_Message. +* +* @author Andreas Heil (info@aheil.de) +* @since 1.4.0 +*/ +public final class DeviceConfiguration { + + private DeviceType deviceType = null; + private String rfAddress = null; + private String serialNumber = null; + private String name = null; + private int roomId = -1; + + private DeviceConfiguration() { + } + + public static DeviceConfiguration create(Message message) { + DeviceConfiguration configuration = new DeviceConfiguration(); + configuration.setValues((C_Message) message); + + return configuration; + } + + public static DeviceConfiguration create(DeviceInformation di) { + DeviceConfiguration configuration = new DeviceConfiguration(); + configuration.setValues(di.getRFAddress(), di.getDeviceType(), di.getSerialNumber(), di.getName()); + return configuration; + } + + + public void setValues(C_Message message) { + setValues(message.getRFAddress(), message.getDeviceType(), message.getSerialNumber()); + } + + private void setValues(String rfAddress, DeviceType deviceType, String serialNumber, String name) { + setValues(rfAddress, deviceType, serialNumber); + this.name = name; + } + + private void setValues(String rfAddress, DeviceType deviceType, String serialNumber) { + this.rfAddress = rfAddress; + this.deviceType = deviceType; + this.serialNumber = serialNumber; + } + + public String getRFAddress() { + return rfAddress; + } + + public DeviceType getDeviceType() { + return deviceType; + } + + public String getSerialNumber() { + return serialNumber; + } + + public String getName() { + return name; + } + + public int getRoomId() { + return roomId; + } + + public void setRoomId(int roomId) { + this.roomId = roomId; + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceInformation.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceInformation.java new file mode 100644 index 0000000000000..e6356cbced44c --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceInformation.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.openhab.binding.maxcube.internal.message; + + +/** +* Device information provided by the M message meta information. +* +* @author Andreas Heil (info@aheil.de) +* @since 1.4.0 +*/ +public class DeviceInformation { + + private DeviceType deviceType = DeviceType.Invalid; + private String serialNumber = ""; + private String rfAddress = ""; + private String name = ""; + private int roomId = -1; + + 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; + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceType.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceType.java new file mode 100644 index 0000000000000..6b56af31991f4 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/DeviceType.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +/** +* This enumeration represents the different message types provided by the MAX!Cube protocol. +* +* @author Andreas Heil (info@aheil.de) +* @since 1.4.0 +*/ +public enum DeviceType { + Invalid(256), Cube (0), HeatingThermostat(1), HeatingThermostatPlus(2), WallMountedThermostat( + 3), ShutterContact(4), EcoSwitch(5); + + private 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; + } + } + + 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"; + } + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/EcoSwitch.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/EcoSwitch.java new file mode 100644 index 0000000000000..1518202e6c4b9 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/EcoSwitch.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.openhab.binding.maxcube.internal.message; + +/** +* MAX!Cube EcoSwitch. +* +* @author Marcel Verpaalen +* @since 1.6.0 +*/ + +public class EcoSwitch extends ShutterContact { + + /** + * Class constructor. + * @param c + */ + public EcoSwitch(DeviceConfiguration c) { + super(c); + } + @Override + public DeviceType getType() { + // TODO Auto-generated method stub + return DeviceType.EcoSwitch; + } + +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/H_Message.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/H_Message.java new file mode 100644 index 0000000000000..a838f435c65b3 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/H_Message.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import java.util.Calendar; + +import org.openhab.binding.maxcube.internal.Utils; +import org.slf4j.Logger; + + +/** +* The H message contains information about the MAX!Cube. +* +* @author Andreas Heil (info@aheil.de) +* @author Marcel Verpaalen - Details parsing +* @since 1.4.0 +*/ +public final class H_Message extends Message { + + private Calendar cal = Calendar.getInstance(); + + private String rawSerialNumber = null; + private String rawRfHexAddress = null; + private String rawFirmwareVersion = null; + private String rawConnectionId = null; + private String rawDutyCycle = null; + private String rawFreeMemorySlots = null; + private String rawSystemDate = null; + private String rawSystemTime = null; + private String rawCubeTimeState = null; + private String rawNTPCounter = null; + + // yet unknown fields + private String rawUnknownfield4 = null; + + public H_Message(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]; + rawRfHexAddress = tokens[1]; + rawFirmwareVersion = tokens[2]; + 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]) ) ; + } + + private final void setDateTime(String hexDate, String hexTime) { + + int year = Utils.fromHex(hexDate.substring(0,2)); + int month = Utils.fromHex(hexDate.substring(2, 4)); + int date = Utils.fromHex(hexDate.substring(4, 6)); + + int hours = Utils.fromHex(hexTime.substring(0, 2)); + int minutes = Utils.fromHex(hexTime.substring(2, 4)); + + cal.set(year, month, date, hours, minutes, 0); + } + + @Override + public void debug(Logger logger) { + logger.debug("=== H_Message === "); + logger.trace("\tRAW: : {}", this.getPayload()); + logger.debug("\tReading Time : {}", cal.getTime()); + logger.debug("\tSerial number : {}", rawSerialNumber); + logger.debug("\tRF address (HEX): {}", rawRfHexAddress); + logger.debug("\tFirmware version: {}", rawFirmwareVersion); + logger.debug("\tConnection ID : {}", rawConnectionId); + logger.debug("\tUnknown : {}", rawUnknownfield4); + logger.debug("\tDuty Cycle : {}", rawDutyCycle); + logger.debug("\tFreeMemorySlots : {}", rawFreeMemorySlots); + logger.debug("\tCubeTimeState : {}", rawCubeTimeState); + logger.debug("\tNTPCounter : {}", rawNTPCounter); + } + + @Override + public MessageType getType() { + return MessageType.H; + } +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/HeatingThermostat.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/HeatingThermostat.java new file mode 100644 index 0000000000000..73bc137f5cba6 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/HeatingThermostat.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import java.util.Calendar; +import java.util.Date; + +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.types.State; + +/** + * MAX!Cube heating thermostat. + * + * @author Andreas Heil (info@aheil.de) + * @author Marcel Verpaalen - OH2 update + * @since 1.4.0 + */ +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 termperature setpoint is valid */ + private Date dateSetpoint; + + /** Device type for this thermostat **/ + private DeviceType deviceType = DeviceType.HeatingThermostat; + + 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; + } + + + //@Override + public String getName1() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Calendar getLastUpdate() { + // TODO Auto-generated method stub + return null; + } + + /** + * Returns the current mode of the thermostat. + */ + public StringType getModeString() { + return new StringType (this.mode.toString()); + } + + /** + * Returns the current mode of the thermostat. + */ + public ThermostatModeType getMode() { + return (ThermostatModeType) this.mode; + } + + void setMode(ThermostatModeType mode) { + 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) { + this.valvePosition = valvePosition; + } + + /** + * Returns the current valve position of this thermostat in percent. + * + * @return + * the valve position as DecimalType + */ + public DecimalType getValvePosition() { + return new DecimalType(this.valvePosition); + } + + public void setDateSetpoint(Date date) { + this.dateSetpoint = date; + } + + /** + * 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) { + 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 DecimalType + */ + public State getTemperatureActual() { + return new DecimalType(this.temperatureActual); + } + + /** + * 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) { + this.temperatureSetpoint = value / 2.0; + } + + /** + * Returns the setpoint temperature of this thermostat. + * 4.5°C is displayed as OFF, 30.5°C is displayed as On at the thermostat display. + * + * @return + * the setpoint temperature as DecimalType + */ + public State getTemperatureSetpoint() { + return new DecimalType(this.temperatureSetpoint); + } +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/L_Message.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/L_Message.java new file mode 100644 index 0000000000000..ca3832bda616e --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/L_Message.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.net.util.Base64; +import org.slf4j.Logger; + +/** + * The L message contains real time information about all MAX! devices. + * + * @author Andreas Heil (info@aheil.de) + * @since 1.4.0 + */ +public final class L_Message extends Message { + + public L_Message(String raw) { + super(raw); + } + + public Collection getDevices(List configurations) { + + List devices = new ArrayList(); + + byte[] decodedRawMessage = Base64.decodeBase64(getPayload().getBytes()); + + MaxTokenizer tokenizer = new MaxTokenizer(decodedRawMessage); + + while (tokenizer.hasMoreElements()) { + byte[] token = tokenizer.nextElement(); + Device tempDevice = Device.create(token, configurations); + if (tempDevice != null) { + devices.add(tempDevice); + } + } + + return devices; + } + + @Override + public void debug(Logger logger) { + logger.debug("=== L_Message === "); + logger.trace("\tRAW:" + this.getPayload()); + } + + @Override + public MessageType getType() { + return MessageType.L; + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/M_Message.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/M_Message.java new file mode 100644 index 0000000000000..3cbe694f6c23e --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/M_Message.java @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import java.util.ArrayList; + +import org.apache.commons.net.util.Base64; +import org.openhab.binding.maxcube.MaxCubeBinding; +import org.openhab.binding.maxcube.internal.Utils; +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 + * @since 1.4.0 + */ +public final class M_Message extends Message { + + public ArrayList rooms; + public ArrayList devices; + private Boolean hasConfiguration ; + Logger logger = LoggerFactory.getLogger(MaxCubeBinding.class); + + + public M_Message(String raw) { + super(raw); + hasConfiguration = false; + + String[] tokens = this.getPayload().split(Message.DELIMETER); + + if (tokens.length > 1) try { + byte[] bytes = Base64.decodeBase64(tokens[2].getBytes()); + + 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++]; + String name = ""; + + int nameLength = (int) bytes[byteOffset++] & 0xff; + for (int char_idx = 0; char_idx < nameLength; char_idx++) { + name += (char) bytes[byteOffset++]; + } + + String rfAddress = Utils.toHex(((int)bytes[byteOffset] & 0xff), ((int)bytes[byteOffset+1] & 0xff), ((int)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(((int)bytes[byteOffset]&0xff), ((int)bytes[byteOffset+1]&0xff), ((int)bytes[byteOffset+2]&0xff)); + byteOffset += 3; + + String serialNumber = ""; + + for (int i = 0; i < 10; i++) { + serialNumber += (char) bytes[byteOffset++]; + } + + int nameLength = (int)bytes[byteOffset++] & 0xff; + + String deviceName = ""; + + for (int char_idx = 0; char_idx < nameLength; char_idx++) { + deviceName += (char)bytes[byteOffset++]; + } + + int roomId = (int)bytes[byteOffset++] & 0xff; + devices.add(new DeviceInformation(deviceType, serialNumber, rfAddress, deviceName, roomId)); + } + } catch (Exception e) { + logger.info("Unknown error parsing the M Message"); + logger.info(e.getMessage()); + logger.debug(Utils.getStackTrace(e)); + logger.debug("\tRAW : {}", this.getPayload()); + } + else { + logger.info("No rooms defined. Configure your Max!Cube"); + hasConfiguration = false; + } + } + + @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; + } +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/MaxTokenizer.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/MaxTokenizer.java new file mode 100644 index 0000000000000..3f0ecba324f11 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/MaxTokenizer.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import java.util.Enumeration; + +/** +* The MaxTokenizer parses a L message into the MAX!Cube 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) +* @since 1.4.0 +*/ +public final class MaxTokenizer implements Enumeration { + + private int offset = 0; + + private byte[] decodedRawMessage = null; + + /** + * Creates a new MaxTokenizer. + * @param decodedRawMessage + * The Base64 decoded MAX!Cube protocol L message as byte array + */ + public MaxTokenizer(byte[] decodedRawMessage) { + this.decodedRawMessage = decodedRawMessage; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasMoreElements() { + return offset < decodedRawMessage.length; + } + + /** + * {@inheritDoc} + */ + @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; + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/Message.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/Message.java new file mode 100644 index 0000000000000..4dac1f19fd0e3 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/Message.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import org.slf4j.Logger; + +/** +* Base message received by the MAX!Cube protocol. +* +* @author Andreas Heil (info@aheil.de) +* @since 1.4.0 +*/ +public abstract class Message { + + public static final String DELIMETER = ","; + + private String raw = null; + + 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()); + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/MessageType.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/MessageType.java new file mode 100644 index 0000000000000..ea7b3463b255f --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/MessageType.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +/** +* This enumeration represents the different message types provided by the MAX!Cube protocol. +* +* @author Andreas Heil (info@aheil.de) +* @since 1.4.0 +*/ +public enum MessageType { + H, M, C, L +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/RoomInformation.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/RoomInformation.java new file mode 100644 index 0000000000000..84def61541028 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/RoomInformation.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.openhab.binding.maxcube.internal.message; + +/** +* Room information provided b the M message meta information. +* +* @author Andreas Heil (info@aheil.de) +* @since 1.4.0 +*/ +public class RoomInformation { + private int position = -1; + 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; + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/S_Command.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/S_Command.java new file mode 100644 index 0000000000000..92c5d61eab419 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/S_Command.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import org.apache.commons.net.util.Base64; +import org.openhab.binding.maxcube.internal.Utils; + +/** + * Command to be send via the MAX!Cube protocol. + * + * @author Andreas Heil (info@aheil.de) + * @author Marcel Verpaalen - OH2 update + * @since 1.4.0 + */ +public class S_Command { + + private String baseString = "000440000000"; + private boolean[] bits = null; + + private String rfAddress = null; + private int roomId = -1; + + /** + * 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 S_Command(String rfAddress, int roomId, 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 + + bits[7] = false; // A (MSB) + bits[6] = true; // B + } + + /** + * 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 mode + * the desired mode for the device. + */ + public S_Command(String rfAddress, int roomId, ThermostatModeType mode) { + this.rfAddress = rfAddress; + this.roomId = roomId; + + // default to perm setting + // AB => bit mapping + // 01 = Permanent + // 10 = Temporarily + + switch (mode) { + case VACATION: + case MANUAL: + //not implemented + break; + case AUTOMATIC: + bits = Utils.getBits(0); + break; + case BOOST: + bits = Utils.getBits(255); + break; + default: + // no further modes supported + } + } + + + /** + * Returns the Base64 encoded command string to be sent via the MAX! + * protocol. + * + * @return the string representing the command + */ + public String getCommandString() { + + String commandString = baseString + rfAddress + Utils.toHex(roomId) + Utils.toHex(bits); + + String encodedString = Base64.encodeBase64String(Utils.hexStringToByteArray(commandString)); + + return "s:" + encodedString; + } +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/SendCommand.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/SendCommand.java new file mode 100644 index 0000000000000..801e1421a3e15 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/SendCommand.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.types.Command; + + +/** + * Class for sending a command. + * + * @author Marcel Verpaalen + * + */ +public final class SendCommand { + + private int id ; + private static int commandId = -1; + + private ChannelUID channelUID; + private Command command; + private String serialNumber; + private String key; + + + public SendCommand(String serialNumber,ChannelUID channelUID,Command command) { + commandId +=1; + id = commandId; + this.serialNumber = serialNumber; + this.channelUID=channelUID; + this.command=command; + setKey(); + } + + /** + * Sets the key based on the serial and channel + * This is can be used to find duplicated commands in the queue + */ + private void setKey() { + key = serialNumber+"-"+channelUID.getId() ; + } + + /** + * @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 ; + } + + + /** + * @return the id + */ + public int getId() { + return id; + } + + + + + + /** + * @return the channelUID + */ + public ChannelUID getChannelUID() { + return channelUID; + } + + + + /** + * @param channelUID the channelUID to set + */ + public void setChannelUID(ChannelUID channelUID) { + this.channelUID = channelUID; + setKey(); + } + + + + /** + * @return the command + */ + public Command getCommand() { + return command; + } + + + + /** + * @param command the command to set + */ + public void setCommand(Command command) { + this.command = command; + } + + + + /** + * @return the device + */ + public String getDeviceSerial() { + return serialNumber; + } + + + + /** + * @param device the device to set + */ + public void setDeviceSerial(String device) { + this.serialNumber = device; + setKey(); + } + + +} \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/ShutterContact.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/ShutterContact.java new file mode 100644 index 0000000000000..4aee0efd14e08 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/ShutterContact.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import java.util.Calendar; + +import org.eclipse.smarthome.core.library.types.OpenClosedType; + +/** + * MAX!Cube Shutter contact device. + * + * @author Andreas Heil (info@aheil.de) + * @author Marcel Verpaalen - OH2 update + * @since 1.4.0 + */ +public class ShutterContact extends Device { + + private OpenClosedType shutterState = null; + private boolean linkError; + private boolean panelLocked; + private boolean gatewayOk; + private boolean error; + private boolean valid; + + public ShutterContact(DeviceConfiguration c) { + super(c); + } + + public void setShutterState(OpenClosedType shutterState) { + this.shutterState = shutterState; + } + + public OpenClosedType getShutterState() { + return shutterState; + } + + @Override + public DeviceType getType() { + return DeviceType.ShutterContact; + } + + @Override + public Calendar getLastUpdate() { + // TODO Auto-generated method stub + return null; + } + + void setLinkError(boolean linkError) { + this.linkError = linkError; + } + + void setPanelLocked(boolean panelLock) { + this.panelLocked = panelLock; + } + + void setGatewayOk(boolean gatewayOk) { + this.gatewayOk = gatewayOk; + + } + + void setError(boolean error) { + this.error = error; + } + + void setValid(boolean valid) { + this.valid = valid; + } +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/ThermostatModeType.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/ThermostatModeType.java new file mode 100644 index 0000000000000..ea5e107db32c7 --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/ThermostatModeType.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.PrimitiveType; +import org.eclipse.smarthome.core.types.State; + +/** +* This enumeration represents the different mode types of a MAX!Cube heating thermostat. +* +* @author Andreas Heil (info@aheil.de) +* * @author Marcel Verpaalen - OH2 update +* @since 1.4.0 +*/ +public enum ThermostatModeType implements PrimitiveType, State, Command { + AUTOMATIC, MANUAL, VACATION, BOOST; + + public String format(String pattern) { + return String.format(pattern, this.toString()); + } +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/UnsupportedDevice.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/UnsupportedDevice.java new file mode 100644 index 0000000000000..47ccb2d29d05b --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/UnsupportedDevice.java @@ -0,0 +1,27 @@ +package org.openhab.binding.maxcube.internal.message; + +import java.util.Calendar; + +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"; + } + + @Override + public Calendar getLastUpdate() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/WallMountedThermostat.java b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/WallMountedThermostat.java new file mode 100644 index 0000000000000..2b043ebfd88cd --- /dev/null +++ b/addons/binding/org.openhab.binding.maxcube/src/main/java/org/openhab/binding/maxcube/internal/message/WallMountedThermostat.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2014, openHAB.org and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.maxcube.internal.message; + + +/** + * MAX!Cube wall mounted thermostat. + * + * @author Andreas Heil (info@aheil.de) + * @since 1.4.0 + */ +public class WallMountedThermostat extends HeatingThermostat { + + /** + * Class constructor. + * @param c + */ + public WallMountedThermostat(DeviceConfiguration c) { + super(c); + } + + @Override + public DeviceType getType() { + return DeviceType.WallMountedThermostat; + } +}