added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.max-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-max" description="MAX! Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.max/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.actions;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.actions.IMaxCubeActions;
|
||||
import org.openhab.binding.max.internal.handler.MaxCubeBridgeHandler;
|
||||
import org.openhab.core.automation.annotation.ActionOutput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxCubeActions} class defines rule actions for MAX! Cube
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "max-cube")
|
||||
@NonNullByDefault
|
||||
public class MaxCubeActions implements ThingActions, IMaxCubeActions {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxCubeActions.class);
|
||||
|
||||
private @Nullable MaxCubeBridgeHandler handler;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof MaxCubeBridgeHandler) {
|
||||
this.handler = (MaxCubeBridgeHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Backup Cube Data", description = "Creates a backup of the MAX! Cube data.")
|
||||
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean backup() {
|
||||
MaxCubeBridgeHandler actionsHandler = handler;
|
||||
if (actionsHandler == null) {
|
||||
logger.info("MaxCubeActions: Action service ThingHandler is null!");
|
||||
return false;
|
||||
}
|
||||
actionsHandler.backup();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean backup(@Nullable ThingActions actions) {
|
||||
return invokeMethodOf(actions).backup();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Reset Cube Configuration", description = "Resets the MAX! Cube room and device information. Devices will need to be included again!")
|
||||
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean resetConfig() {
|
||||
MaxCubeBridgeHandler actionsHandler = handler;
|
||||
if (actionsHandler == null) {
|
||||
logger.info("MaxCubeActions: Action service ThingHandler is null!");
|
||||
return false;
|
||||
}
|
||||
actionsHandler.cubeConfigReset();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean reset(@Nullable ThingActions actions) {
|
||||
return invokeMethodOf(actions).resetConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Restart Cube", description = "Restarts the MAX! Cube.")
|
||||
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean reboot() {
|
||||
MaxCubeBridgeHandler actionsHandler = handler;
|
||||
if (actionsHandler == null) {
|
||||
logger.info("MaxCubeActions: Action service ThingHandler is null!");
|
||||
return false;
|
||||
}
|
||||
actionsHandler.cubeReboot();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean reboot(@Nullable ThingActions actions) {
|
||||
return invokeMethodOf(actions).reboot();
|
||||
}
|
||||
|
||||
private static IMaxCubeActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(MaxCubeActions.class.getName())) {
|
||||
if (actions instanceof IMaxCubeActions) {
|
||||
return (IMaxCubeActions) actions;
|
||||
} else {
|
||||
return (IMaxCubeActions) Proxy.newProxyInstance(IMaxCubeActions.class.getClassLoader(),
|
||||
new Class[] { IMaxCubeActions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of MaxCubeActions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.actions;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.actions.IMaxDevicesActions;
|
||||
import org.openhab.binding.max.internal.handler.MaxDevicesHandler;
|
||||
import org.openhab.core.automation.annotation.ActionOutput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxDevicesActions} class defines rule actions for MAX! devices
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "max-devices")
|
||||
@NonNullByDefault
|
||||
public class MaxDevicesActions implements ThingActions, IMaxDevicesActions {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxDevicesActions.class);
|
||||
|
||||
private @Nullable MaxDevicesHandler handler;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof MaxDevicesHandler) {
|
||||
this.handler = (MaxDevicesHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Delete Device from Cube", description = "Deletes the device from the MAX! Cube. Device will need to be included again!")
|
||||
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean deleteFromCube() {
|
||||
MaxDevicesHandler actionsHandler = handler;
|
||||
if (actionsHandler == null) {
|
||||
logger.info("MaxDevicesActions: Action service ThingHandler is null!");
|
||||
return false;
|
||||
}
|
||||
actionsHandler.deviceDelete();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean deleteFromCube(@Nullable ThingActions actions) {
|
||||
return invokeMethodOf(actions).deleteFromCube();
|
||||
}
|
||||
|
||||
private static IMaxDevicesActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(MaxDevicesActions.class.getName())) {
|
||||
if (actions instanceof IMaxDevicesActions) {
|
||||
return (IMaxDevicesActions) actions;
|
||||
} else {
|
||||
return (IMaxDevicesActions) Proxy.newProxyInstance(IMaxDevicesActions.class.getClassLoader(),
|
||||
new Class[] { IMaxDevicesActions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of MaxDevicesActions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.core.ConfigConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxBackupUtils} class supports the backup of cube data
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MaxBackupUtils {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxBackupUtils.class);
|
||||
private static final String BACKUP_PATH = "max";
|
||||
|
||||
private final String dbFolderName;
|
||||
private final String backupId;
|
||||
private boolean inProgress = false;
|
||||
private StringBuilder msg = new StringBuilder();
|
||||
private String cube = "";
|
||||
|
||||
public MaxBackupUtils(String backupId) {
|
||||
this.backupId = backupId;
|
||||
dbFolderName = ConfigConstants.getUserDataFolder() + File.separator + BACKUP_PATH;
|
||||
File folder = new File(dbFolderName);
|
||||
if (!folder.exists()) {
|
||||
folder.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
public MaxBackupUtils() {
|
||||
this("auto" + new SimpleDateFormat("MM").format(Calendar.getInstance().getTime()));
|
||||
}
|
||||
|
||||
public void buildBackup(String msgLine) {
|
||||
if (msgLine.startsWith("H:")) {
|
||||
msg = new StringBuilder();
|
||||
cube = msgLine.substring(2).split(",")[0];
|
||||
inProgress = true;
|
||||
}
|
||||
if (inProgress) {
|
||||
msg.append(msgLine);
|
||||
msg.append(System.lineSeparator());
|
||||
}
|
||||
if (inProgress && msgLine.startsWith("L:")) {
|
||||
inProgress = false;
|
||||
saveMsg(msg.toString(), cube);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveMsg(String data, String cube) {
|
||||
File dataFile = new File(dbFolderName + File.separator + "backup-" + backupId + "-" + cube + ".txt");
|
||||
try (FileWriter writer = new FileWriter(dataFile)) {
|
||||
writer.write(data);
|
||||
logger.debug("MAX! backup saved to {}", dataFile.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
logger.warn("MAX! Failed to write backup file '{}': {}", dataFile.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link MaxBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MaxBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "max";
|
||||
|
||||
// List of main device types
|
||||
public static final String DEVICE_THERMOSTAT = "thermostat";
|
||||
public static final String DEVICE_THERMOSTATPLUS = "thermostatplus";
|
||||
public static final String DEVICE_WALLTHERMOSTAT = "wallthermostat";
|
||||
public static final String DEVICE_ECOSWITCH = "ecoswitch";
|
||||
public static final String DEVICE_SHUTTERCONTACT = "shuttercontact";
|
||||
public static final String BRIDGE_MAXCUBE = "bridge";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID HEATINGTHERMOSTAT_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_THERMOSTAT);
|
||||
public static final ThingTypeUID HEATINGTHERMOSTATPLUS_THING_TYPE = new ThingTypeUID(BINDING_ID,
|
||||
DEVICE_THERMOSTATPLUS);
|
||||
public static final ThingTypeUID WALLTHERMOSTAT_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_WALLTHERMOSTAT);
|
||||
public static final ThingTypeUID ECOSWITCH_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_ECOSWITCH);
|
||||
public static final ThingTypeUID SHUTTERCONTACT_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_SHUTTERCONTACT);
|
||||
public static final ThingTypeUID CUBEBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, BRIDGE_MAXCUBE);
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_VALVE = "valve";
|
||||
public static final String CHANNEL_BATTERY = "battery_low";
|
||||
public static final String CHANNEL_MODE = "mode";
|
||||
public static final String CHANNEL_ACTUALTEMP = "actual_temp";
|
||||
public static final String CHANNEL_SETTEMP = "set_temp";
|
||||
public static final String CHANNEL_LOCKED = "locked";
|
||||
public static final String CHANNEL_CONTACT_STATE = "contact_state";
|
||||
public static final String CHANNEL_FREE_MEMORY = "free_mem";
|
||||
public static final String CHANNEL_DUTY_CYCLE = "duty_cycle";
|
||||
|
||||
// Custom Properties
|
||||
public static final String PROPERTY_IP_ADDRESS = "ipAddress";
|
||||
public static final String PROPERTY_VENDOR_NAME = "eQ-3 AG";
|
||||
public static final String PROPERTY_RFADDRESS = "rfAddress";
|
||||
public static final String PROPERTY_ROOMNAME = "room";
|
||||
public static final String PROPERTY_ROOMID = "roomId";
|
||||
public static final String PROPERTY_DEVICENAME = "name";
|
||||
public static final String PROPERTY_REFRESH_ACTUAL_RATE = "refreshActualRate";
|
||||
public static final String PROPERTY_NTP_SERVER1 = "ntpServer1";
|
||||
public static final String PROPERTY_NTP_SERVER2 = "ntpServer2";
|
||||
|
||||
// Thermostat settings properties
|
||||
public static final String PROPERTY_THERMO_COMFORT_TEMP = "comfortTemp";
|
||||
public static final String PROPERTY_THERMO_ECO_TEMP = "ecoTemp";
|
||||
public static final String PROPERTY_THERMO_MAX_TEMP_SETPOINT = "maxTempSetpoint";
|
||||
public static final String PROPERTY_THERMO_MIN_TEMP_SETPOINT = "minTempSetpoint";
|
||||
public static final String PROPERTY_THERMO_OFFSET_TEMP = "offsetTemp";
|
||||
public static final String PROPERTY_THERMO_WINDOW_OPEN_TEMP = "windowOpenTemp";
|
||||
public static final String PROPERTY_THERMO_WINDOW_OPEN_DURATION = "windowOpenDuration";
|
||||
public static final String PROPERTY_THERMO_DECALCIFICATION = "decalcification";
|
||||
public static final String PROPERTY_THERMO_VALVE_MAX = "valveMaximum";
|
||||
public static final String PROPERTY_THERMO_VALVE_OFFSET = "valveOffset";
|
||||
public static final String PROPERTY_THERMO_BOOST_DURATION = "boostDuration";
|
||||
public static final String PROPERTY_THERMO_BOOST_VALVEPOS = "boostValvePos";
|
||||
public static final String PROPERTY_THERMO_PROGRAM_DATA = "programData";
|
||||
|
||||
// List of actions
|
||||
public static final String ACTION_CUBE_REBOOT = "action-cubeReboot";
|
||||
public static final String ACTION_CUBE_RESET = "action-cubeReset";
|
||||
public static final String ACTION_DEVICE_DELETE = "action-deviceDelete";
|
||||
public static final String BUTTON_ACTION_VALUE = "1234";
|
||||
public static final int BUTTON_NOACTION_VALUE = -1;
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_DEVICE_THING_TYPES_UIDS = Collections.unmodifiableSet(
|
||||
Stream.of(HEATINGTHERMOSTAT_THING_TYPE, HEATINGTHERMOSTATPLUS_THING_TYPE, WALLTHERMOSTAT_THING_TYPE,
|
||||
ECOSWITCH_THING_TYPE, SHUTTERCONTACT_THING_TYPE).collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(CUBEBRIDGE_THING_TYPE).collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
|
||||
Stream.concat(SUPPORTED_DEVICE_THING_TYPES_UIDS.stream(), SUPPORTED_BRIDGE_THING_TYPES_UIDS.stream())
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.handler.MaxCubeBridgeHandler;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link MaxConsoleCommandExtension} class provides additional options through the console command line.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@Component(service = ConsoleCommandExtension.class)
|
||||
@NonNullByDefault
|
||||
public class MaxConsoleCommandExtension extends AbstractConsoleCommandExtension {
|
||||
|
||||
private static final String SUBCMD_BACKUP = "backup";
|
||||
private static final String SUBCMD_REBOOT = "reboot";
|
||||
private final ThingRegistry thingRegistry;
|
||||
|
||||
@Activate
|
||||
public MaxConsoleCommandExtension(@Reference ThingRegistry thingRegistry) {
|
||||
super("max", "Additional EQ3 MAX! commands.");
|
||||
this.thingRegistry = thingRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String[] args, Console console) {
|
||||
if (args.length > 0) {
|
||||
switch (args[0]) {
|
||||
case SUBCMD_BACKUP:
|
||||
handleBackup(console);
|
||||
break;
|
||||
case SUBCMD_REBOOT:
|
||||
handleReboot(args, console);
|
||||
break;
|
||||
default:
|
||||
console.println(String.format("Unknown MAX! sub command '%s'", args[0]));
|
||||
printUsage(console);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
printUsage(console);
|
||||
printMaxDevices(console, SUPPORTED_THING_TYPES_UIDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBackup(Console console) {
|
||||
for (Thing thing : findDevices(SUPPORTED_BRIDGE_THING_TYPES_UIDS)) {
|
||||
MaxCubeBridgeHandler handler = getHandler(thing.getUID().toString());
|
||||
if (handler != null) {
|
||||
handler.backup();
|
||||
console.println(String.format("Creating backup for %s", thing.getUID().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReboot(String[] args, Console console) {
|
||||
if (args.length > 1) {
|
||||
MaxCubeBridgeHandler handler = getHandler(args[1]);
|
||||
if (handler != null) {
|
||||
handler.cubeReboot();
|
||||
} else {
|
||||
console.println(String.format("Could not find MAX! cube %s", args[1]));
|
||||
printMaxDevices(console, SUPPORTED_BRIDGE_THING_TYPES_UIDS);
|
||||
}
|
||||
} else {
|
||||
console.println("Specify MAX! cube to reboot.");
|
||||
printMaxDevices(console, SUPPORTED_BRIDGE_THING_TYPES_UIDS);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Thing> findDevices(Set<ThingTypeUID> deviceTypes) {
|
||||
List<Thing> devs = new ArrayList<>();
|
||||
for (Thing thing : thingRegistry.getAll()) {
|
||||
if (deviceTypes.contains(thing.getThingTypeUID())) {
|
||||
devs.add(thing);
|
||||
}
|
||||
}
|
||||
return devs;
|
||||
}
|
||||
|
||||
private @Nullable MaxCubeBridgeHandler getHandler(String thingId) {
|
||||
MaxCubeBridgeHandler handler = null;
|
||||
try {
|
||||
ThingUID bridgeUID = new ThingUID(thingId);
|
||||
Thing thing = thingRegistry.get(bridgeUID);
|
||||
if ((thing != null) && (thing.getHandler() != null)
|
||||
&& (thing.getHandler() instanceof MaxCubeBridgeHandler)) {
|
||||
handler = (MaxCubeBridgeHandler) thing.getHandler();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handler = null;
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private void printMaxDevices(Console console, Set<ThingTypeUID> deviceTypes) {
|
||||
console.println("Known MAX! devices: ");
|
||||
for (Thing thing : findDevices(deviceTypes)) {
|
||||
console.println(String.format("MAX! %s device: %s%s", thing.getThingTypeUID().getId(),
|
||||
thing.getUID().toString(), ((thing.getHandler() != null) ? "" : " (without handler)")));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(new String[] { buildCommandUsage(SUBCMD_BACKUP, "Backup MAX! cube data"),
|
||||
buildCommandUsage(SUBCMD_REBOOT + " <thingUID>", "Reset MAX! cube") });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Utility class for common tasks within the MAX! binding package.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class Utils {
|
||||
|
||||
/**
|
||||
* Returns the integer value of an hexadecimal number (base 16).
|
||||
*
|
||||
* @param hex
|
||||
* the hex value to be parsed into an integer
|
||||
* @return the given hex value as integer
|
||||
*/
|
||||
public static final int fromHex(String hex) {
|
||||
return Integer.parseInt(hex, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hexadecimal number of a number of integer values.
|
||||
*
|
||||
* @param values
|
||||
* the integer values to be converted into hexadecimal numbers
|
||||
* @return the given numbers as hexadecimal number
|
||||
*/
|
||||
public static final String toHex(int... values) {
|
||||
String returnValue = "";
|
||||
for (int v : values) {
|
||||
returnValue += v < 16 ? "0" + Integer.toHexString(v).toUpperCase() : Integer.toHexString(v).toUpperCase();
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hexadecimal number of a bit array .
|
||||
*
|
||||
* @param bits
|
||||
* the boolean array representing the bits to be converted into
|
||||
* hexadecimal numbers
|
||||
* @return the hexadecimal number
|
||||
*/
|
||||
public static final String toHex(boolean[] bits) {
|
||||
int retVal = 0;
|
||||
for (int i = 0; i < bits.length; ++i) {
|
||||
retVal |= (bits[i] ? 1 : 0) << i;
|
||||
}
|
||||
return toHex(retVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Java signed byte into its general (unsigned) value as being
|
||||
* used in other programming languages and platforms.
|
||||
*
|
||||
* @param b
|
||||
* the byte to be converted into its integer value
|
||||
* @return the integer value represented by the given byte
|
||||
*/
|
||||
public static final int fromByte(byte b) {
|
||||
return b & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the date and time based based on a three byte encoded within a
|
||||
* MAX! Cube L message.
|
||||
*
|
||||
* Date decoding (two byte)
|
||||
*
|
||||
* <pre>
|
||||
* Hex Binary
|
||||
* 9D0B 1001110100001011
|
||||
* MMMDDDDDM YYYYYY
|
||||
* 100 0 = 1000b = 8d = month
|
||||
* 11101 = 11101b = 29d = day
|
||||
* 001011 = 1011b = 11d = year-2000
|
||||
* </pre>
|
||||
*
|
||||
* Time decoding (one byte)
|
||||
*
|
||||
* <pre>
|
||||
* Hex Decimal
|
||||
* 1F 31 * 0.5 hours = 15:30
|
||||
* </pre>
|
||||
*
|
||||
* @param date
|
||||
* the date to be converted based on two bytes
|
||||
* @param time
|
||||
* the time to be converted based on a single byte
|
||||
* @return the date time based on the values provided
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static Date resolveDateTime(int date, int time) {
|
||||
int month = ((date & 0xE000) >> 12) + ((date & 0x80) >> 7);
|
||||
int day = (date & 0x1F00) >> 8;
|
||||
int year = (date & 0x0F) + 2000;
|
||||
|
||||
int hours = (int) (time * 0.5);
|
||||
int minutes = (int) (60 * ((time * 0.5) - hours));
|
||||
|
||||
return new Date(year, month, day, hours, minutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bit representation as boolean values in a reversed manner. A
|
||||
* bit string <code>0001 0010</code> would be returnd as
|
||||
* <code>0100 1000</code>. That way, the least significant bit can be
|
||||
* addressed by bits[0], the second by bits[1] and so on.
|
||||
*
|
||||
* @param value
|
||||
* the integer value to be converted in a bit array
|
||||
* @return the bit array of the input value in a reversed manner.
|
||||
*/
|
||||
public static boolean[] getBits(int value) {
|
||||
boolean[] bits = new boolean[8];
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bits[i] = (((value >> i) & 0x1) == 1);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string representation of hexadecimal to a byte array.
|
||||
*
|
||||
* For example: String s = "00010203" returned byte array is {0x00, 0x01,
|
||||
* 0x03}
|
||||
*
|
||||
* @param s
|
||||
* @return byte array equivalent to hex string
|
||||
*/
|
||||
public static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to a string representation of hexadecimals.
|
||||
*
|
||||
* For example: byte array is {0x00, 0x01, 0x03} returned String s =
|
||||
* "00 01 02 03"
|
||||
*
|
||||
* @param byte array
|
||||
* @return String equivalent to hex string
|
||||
*/
|
||||
static final String HEXES = "0123456789ABCDEF";
|
||||
|
||||
public static String getHex(byte[] raw) {
|
||||
final StringBuilder hex = new StringBuilder(3 * raw.length);
|
||||
for (final byte b : raw) {
|
||||
hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))).append(" ");
|
||||
}
|
||||
hex.delete(hex.length() - 1, hex.length());
|
||||
return hex.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.actions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.actions.MaxCubeActions;
|
||||
|
||||
/**
|
||||
* The {@link IMaxCubeActions} defines the interface for all thing actions supported by the binding.
|
||||
* These methods, parameters, and return types are explained in {@link MaxCubeActions}.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IMaxCubeActions {
|
||||
|
||||
Boolean backup();
|
||||
|
||||
Boolean resetConfig();
|
||||
|
||||
Boolean reboot();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.actions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.actions.MaxDevicesActions;
|
||||
|
||||
/**
|
||||
* The {@link IMaxDevicesActions} defines the interface for all thing actions supported by the binding.
|
||||
* These methods, parameters, and return types are explained in {@link MaxDevicesActions}.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IMaxDevicesActions {
|
||||
|
||||
Boolean deleteFromCube();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ACommand} deletes the device and room configuration from the Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ACommand extends CubeCommand {
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
return "a:" + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "A:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link C_CubeCommand} to request configuration of a new MAX! device after inclusion.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CCommand extends CubeCommand {
|
||||
private final String rfAddress;
|
||||
|
||||
public CCommand(String rfAddress) {
|
||||
this.rfAddress = rfAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
String cmd = "c:" + rfAddress + '\r' + '\n';
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "C:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link CubeCommand} is the base class for commands to be send to the MAX! Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class CubeCommand {
|
||||
|
||||
/**
|
||||
* @return the String to be send to the MAX! Cube
|
||||
*/
|
||||
public abstract String getCommandString();
|
||||
|
||||
/**
|
||||
* @return the String expected to be received from the Cube to signify the
|
||||
* end of the message
|
||||
*/
|
||||
public abstract String getReturnStrings();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link F_CubeCommand} is used to query and update the NTP servers used by the Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FCommand extends CubeCommand {
|
||||
|
||||
private String ntpServer1 = "";
|
||||
private String ntpServer2 = "";
|
||||
|
||||
/**
|
||||
* Queries the Cube for the NTP info
|
||||
*/
|
||||
public FCommand() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Cube the NTP info
|
||||
*/
|
||||
public FCommand(@Nullable String ntpServer1, @Nullable String ntpServer2) {
|
||||
this.ntpServer1 = ntpServer1 != null ? ntpServer1 : "";
|
||||
this.ntpServer2 = ntpServer2 != null ? ntpServer2 : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final String servers;
|
||||
if (ntpServer1.length() > 0 && ntpServer2.length() > 0) {
|
||||
servers = ntpServer1 + "," + ntpServer2;
|
||||
} else {
|
||||
servers = ntpServer1 + ntpServer2;
|
||||
}
|
||||
return "f:" + servers + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "F:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link LCommand} request a status update for MAX! devices.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LCommand extends CubeCommand {
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
return "l:" + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "L:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.RoomInformation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MCommand} Creates the MAX! Cube the room & device name information update message.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MCommand extends CubeCommand {
|
||||
private final Logger logger = LoggerFactory.getLogger(MCommand.class);
|
||||
|
||||
private static final byte MAGIC_NR = 86;
|
||||
private static final byte M_VERSION = 2;
|
||||
private static final int MAX_NAME_LENGTH = 32;
|
||||
private static final int MAX_GROUP_COUNT = 20;
|
||||
private static final int MAX_DEVICES_COUNT = 140;
|
||||
private static final int MAX_MSG_LENGTH = 1900;
|
||||
|
||||
private final List<Device> devices;
|
||||
public final List<RoomInformation> rooms;
|
||||
|
||||
public MCommand(List<Device> devices) {
|
||||
this(devices, new ArrayList<>());
|
||||
}
|
||||
|
||||
public MCommand(List<Device> devices, List<RoomInformation> rooms) {
|
||||
this.devices = new ArrayList<>(devices);
|
||||
this.rooms = new ArrayList<>(rooms);
|
||||
roombuilder();
|
||||
}
|
||||
|
||||
public void listRooms() {
|
||||
for (RoomInformation room : rooms) {
|
||||
logger.debug("M Command room info: {}", room);
|
||||
}
|
||||
}
|
||||
|
||||
public List<RoomInformation> getRooms() {
|
||||
return rooms;
|
||||
}
|
||||
|
||||
private void roombuilder() {
|
||||
for (Device di : devices) {
|
||||
boolean foundRoom = false;
|
||||
|
||||
for (RoomInformation room : rooms) {
|
||||
if (room.getPosition() == di.getRoomId()) {
|
||||
foundRoom = true;
|
||||
}
|
||||
}
|
||||
// Add new rooms based on device information.
|
||||
// TODO check if it is allowed to have any device creating a room, or should it be a thermostat
|
||||
if (!foundRoom && di.getRoomId() != 0 && rooms.size() < MAX_GROUP_COUNT) {
|
||||
RoomInformation room = new RoomInformation(di.getRoomId(), di.getRoomName(), di.getRFAddress());
|
||||
rooms.add(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] concatenateByteArrays(List<byte[]> blocks) {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
for (byte[] b : blocks) {
|
||||
os.write(b, 0, b.length);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
int deviceCount = 0;
|
||||
int roomCount = 0;
|
||||
|
||||
ByteArrayOutputStream message = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
byte[] header = { MAGIC_NR, M_VERSION, (byte) rooms.size() };
|
||||
message.write(header);
|
||||
|
||||
Set<Integer> sortedRooms = new TreeSet<>();
|
||||
for (RoomInformation room : rooms) {
|
||||
sortedRooms.add(room.getPosition());
|
||||
}
|
||||
|
||||
for (Integer roomPos : sortedRooms) {
|
||||
for (RoomInformation room : rooms) {
|
||||
if (room.getPosition() == roomPos) {
|
||||
if (roomCount < MAX_GROUP_COUNT) {
|
||||
byte[] roomName = StringUtils.abbreviate(room.getName(), MAX_NAME_LENGTH)
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] nameLength = new byte[] { (byte) roomName.length };
|
||||
byte[] rfAddress = Utils.hexStringToByteArray(room.getRFAddress());
|
||||
message.write(roomPos.byteValue());
|
||||
message.write(nameLength);
|
||||
message.write(roomName);
|
||||
message.write(rfAddress);
|
||||
} else {
|
||||
logger.warn("{} exceeds max number of rooms ({}). Ignored", room, MAX_GROUP_COUNT);
|
||||
}
|
||||
roomCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Device di : devices) {
|
||||
if (deviceCount < MAX_DEVICES_COUNT) {
|
||||
deviceCount++;
|
||||
} else {
|
||||
logger.warn("{} exceeds max number of devices ({}). Ignored", di, MAX_DEVICES_COUNT);
|
||||
}
|
||||
}
|
||||
message.write((byte) deviceCount);
|
||||
for (Device di : devices) {
|
||||
if (deviceCount > 0) {
|
||||
byte[] deviceType = { (byte) di.getType().getValue() };
|
||||
byte[] rfAddress = Utils.hexStringToByteArray(di.getRFAddress());
|
||||
byte[] deviceName = StringUtils.abbreviate(di.getName(), MAX_NAME_LENGTH)
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] nameLength = { (byte) deviceName.length };
|
||||
byte[] serialNumber = di.getSerialNumber().getBytes(StandardCharsets.UTF_8);
|
||||
byte[] roomId = { (byte) di.getRoomId() };
|
||||
|
||||
message.write(deviceType);
|
||||
message.write(rfAddress);
|
||||
message.write(serialNumber);
|
||||
message.write(nameLength);
|
||||
message.write(deviceName);
|
||||
message.write(roomId);
|
||||
} else {
|
||||
logger.warn("{} exceeds max number of devices ({}). Ignored", di, MAX_DEVICES_COUNT);
|
||||
}
|
||||
deviceCount--;
|
||||
}
|
||||
|
||||
byte[] dst = { 0x01 };
|
||||
message.write(dst);
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error while generating m: command: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
final String encodedString = Base64.getEncoder().encodeToString(message.toByteArray());
|
||||
final StringBuilder commandStringBuilder = new StringBuilder();
|
||||
int parts = (int) Math.round(encodedString.length() / MAX_MSG_LENGTH + 0.5);
|
||||
for (int i = 0; i < parts; i++) {
|
||||
String partString = StringUtils.abbreviate(encodedString.substring((i) * MAX_MSG_LENGTH), MAX_MSG_LENGTH);
|
||||
commandStringBuilder.append("m:").append(String.format("%02d", i)).append(",").append(partString)
|
||||
.append('\r').append('\n');
|
||||
}
|
||||
|
||||
return commandStringBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "A:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link N_CubeCommand} starts the inclusion mode for new MAX! devices.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NCommand extends CubeCommand {
|
||||
// Example n:003c = start inclusion, timeout 003c = 60 sec
|
||||
|
||||
// Timeout for inclusion mode in seconds
|
||||
private static final int DEFAULT_TIMEOUT = 60;
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
return "n:" + String.format("%02x", DEFAULT_TIMEOUT) + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "N:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link QCommand} Quits the connection to the MAX! Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QCommand extends CubeCommand {
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
return "q:" + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.ThermostatModeType;
|
||||
|
||||
/**
|
||||
* {@link SCommand} for setting MAX! thermostat temperature & mode.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update + simplification
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SCommand extends CubeCommand {
|
||||
|
||||
private static final String BASE_STRING_S = "000040000000"; // for single devices
|
||||
private static final String BASE_STRING_G = "000440000000"; // for group/room devices
|
||||
|
||||
private final boolean[] bits;
|
||||
private final String rfAddress;
|
||||
private final int roomId;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the MAX! protocol S command.
|
||||
*
|
||||
* @param rfAddress
|
||||
* the RF address the command is for
|
||||
* @param roomId
|
||||
* the room ID the RF address is mapped to
|
||||
* @param setpointTemperature
|
||||
* the desired setpoint temperature for the device.
|
||||
*/
|
||||
public SCommand(String rfAddress, int roomId, ThermostatModeType mode, double setpointTemperature) {
|
||||
this.rfAddress = rfAddress;
|
||||
this.roomId = roomId;
|
||||
|
||||
// Temperature setpoint, Temp uses 6 bits (bit 0:5),
|
||||
// 20 deg C = bits 101000 = dec 40/2 = 20 deg C,
|
||||
// you need 8 bits to send so add the 2 bits below (sample 10101000 = hex A8)
|
||||
// bit 0,1 = 00 = Auto weekprog (no temp is needed)
|
||||
|
||||
int setpointValue = (int) (setpointTemperature * 2);
|
||||
bits = Utils.getBits(setpointValue);
|
||||
|
||||
// default to perm setting
|
||||
// AB => bit mapping
|
||||
// 01 = Permanent
|
||||
// 10 = Temporarily
|
||||
// 11 = Boost
|
||||
|
||||
switch (mode) {
|
||||
case MANUAL:
|
||||
bits[7] = false; // A (MSB)
|
||||
bits[6] = true; // B
|
||||
break;
|
||||
case AUTOMATIC:
|
||||
bits[7] = false; // A (MSB)
|
||||
bits[6] = false; // B
|
||||
break;
|
||||
case BOOST:
|
||||
bits[7] = true; // A (MSB)
|
||||
bits[6] = true; // B
|
||||
break;
|
||||
case VACATION:
|
||||
// not implemented needs time
|
||||
default:
|
||||
// no further modes supported
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Base64 encoded command string to be sent via the MAX!
|
||||
* protocol.
|
||||
*
|
||||
* @return the string representing the command
|
||||
*/
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final String baseString;
|
||||
if (roomId == 0) {
|
||||
baseString = BASE_STRING_S;
|
||||
} else {
|
||||
baseString = BASE_STRING_G;
|
||||
}
|
||||
|
||||
final String commandString = baseString + rfAddress + Utils.toHex(roomId) + Utils.toHex(bits);
|
||||
|
||||
final String encodedString = Base64.getEncoder().encodeToString(Utils.hexStringToByteArray(commandString));
|
||||
|
||||
return "s:" + encodedString + "\r\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "S:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SConfigCommand} for setting MAX! thermostat configuration.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SConfigCommand extends CubeCommand {
|
||||
|
||||
private String baseString = "";
|
||||
private final String rfAddress;
|
||||
private final int roomId;
|
||||
|
||||
private byte[] commandBytes = new byte[0];
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SConfigCommand.class);
|
||||
|
||||
public enum ConfigCommandType {
|
||||
Temperature,
|
||||
Valve,
|
||||
SetRoom,
|
||||
RemoveRoom,
|
||||
ProgramData
|
||||
}
|
||||
|
||||
private final ConfigCommandType configCommandType;
|
||||
|
||||
/**
|
||||
* Creates a base command with rfAddress and roomID.
|
||||
*
|
||||
* @param rfAddress
|
||||
* the RF address the command is for
|
||||
* @param roomId
|
||||
* the room ID the RF address is mapped to
|
||||
* @param configCommandType
|
||||
* the Type of config command to be send
|
||||
*/
|
||||
public SConfigCommand(String rfAddress, int roomId, ConfigCommandType configCommandType) {
|
||||
this.rfAddress = rfAddress;
|
||||
this.roomId = roomId;
|
||||
this.configCommandType = configCommandType;
|
||||
if (configCommandType == ConfigCommandType.Temperature) {
|
||||
setTempConfigDefault();
|
||||
} else if (configCommandType == ConfigCommandType.SetRoom) {
|
||||
baseString = "000022000000";
|
||||
commandBytes = new byte[] { 0 };
|
||||
} else if (configCommandType == ConfigCommandType.RemoveRoom) {
|
||||
baseString = "000023000000";
|
||||
commandBytes = new byte[] { 0 };
|
||||
} else {
|
||||
logger.debug("Config Command {} not implemented", configCommandType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Thermostat temperature configuration
|
||||
*/
|
||||
public SConfigCommand(String rfAddress, int roomId, double tempComfort, double tempEco, double tempSetpointMax,
|
||||
double tempSetpointMin, double tempOffset, double tempOpenWindow, int durationOpenWindow) {
|
||||
this.rfAddress = rfAddress;
|
||||
this.roomId = roomId;
|
||||
this.configCommandType = ConfigCommandType.Temperature;
|
||||
setTempConfig(tempComfort, tempEco, tempSetpointMax, tempSetpointMin, tempOffset, tempOpenWindow,
|
||||
durationOpenWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the thermostat default temperature config.
|
||||
*/
|
||||
public void setTempConfigDefault() {
|
||||
double tempComfort = 21.0; // 0x2a
|
||||
double tempEco = 17.0; // 0x28
|
||||
double tempSetpointMax = 30.0; // 0x3d
|
||||
double tempSetpointMin = 4.5; // 0x09
|
||||
double tempOffset = 3.5; // 0x07
|
||||
double tempOpenWindow = 12.0; // 0x18;
|
||||
int durationOpenWindow = 3; // 0x03;
|
||||
setTempConfig(tempComfort, tempEco, tempSetpointMax, tempSetpointMin, tempOffset, tempOpenWindow,
|
||||
durationOpenWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the thermostat temperature config.
|
||||
*
|
||||
* @param tempComfort
|
||||
* the Comfort temperature
|
||||
* @param tempEco
|
||||
* the ECO temperature
|
||||
* @param tempSetpointMax
|
||||
* the Max temperature
|
||||
* @param tempSetpointMin
|
||||
* the min temperature
|
||||
* @param tempOffset
|
||||
* the offset temperature
|
||||
* @param tempOpenWindow
|
||||
* the window open temperature
|
||||
* @param durationOpenWindow
|
||||
* the window open duration in minutes
|
||||
*/
|
||||
public void setTempConfig(double tempComfort, double tempEco, double tempSetpointMax, double tempSetpointMin,
|
||||
double tempOffset, double tempOpenWindow, int durationOpenWindow) {
|
||||
baseString = "000011000000";
|
||||
|
||||
byte tempComfortByte = (byte) (tempComfort * 2);
|
||||
byte tempEcoByte = (byte) (tempEco * 2);
|
||||
byte tempSetpointMaxByte = (byte) (tempSetpointMax * 2);
|
||||
byte tempSetpointMinByte = (byte) (tempSetpointMin * 2);
|
||||
byte tempOffsetByte = (byte) ((tempOffset + 3.5) * 2);
|
||||
byte tempOpenWindowByte = (byte) (tempOpenWindow * 2);
|
||||
byte durationOpenWindowByte = (byte) (durationOpenWindow / 5);
|
||||
commandBytes = new byte[] { tempComfortByte, tempEcoByte, tempSetpointMaxByte, tempSetpointMinByte,
|
||||
tempOffsetByte, tempOpenWindowByte, durationOpenWindowByte };
|
||||
logger.debug(
|
||||
"Thermostat Config Command: confTemp: {}, ecoTemp: {}, setMax: {}, setMin: {}, offset: {}, windowTemp: {}, windowDur:{}",
|
||||
tempComfort, tempEco, tempSetpointMax, tempSetpointMin, tempOffset, tempOpenWindow, durationOpenWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Base64 encoded command string to be sent via the MAX! Cube.
|
||||
*
|
||||
* @return the string representing the command
|
||||
*/
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final StringBuilder commandConfigString = new StringBuilder();
|
||||
for (byte b : commandBytes) {
|
||||
commandConfigString.append(String.format("%02X", b));
|
||||
}
|
||||
|
||||
String commandString = baseString + rfAddress;
|
||||
if (configCommandType == ConfigCommandType.SetRoom || configCommandType == ConfigCommandType.RemoveRoom) {
|
||||
commandString = commandString + commandConfigString + Utils.toHex(roomId);
|
||||
} else {
|
||||
commandString = commandString + Utils.toHex(roomId) + commandConfigString;
|
||||
}
|
||||
|
||||
String encodedString = Base64.getEncoder().encodeToString(Utils.hexStringToByteArray(commandString));
|
||||
return "s:" + encodedString + "\r\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "S:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* The {@link T_CubeCommand} is used to unlink MAX! devices from the Cube.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TCommand extends CubeCommand {
|
||||
|
||||
private static final int FORCE_UPDATE = 1;
|
||||
private static final int NO_FORCE_UPDATE = 0;
|
||||
|
||||
private final List<String> rfAddresses = new ArrayList<>();
|
||||
private final boolean forceUpdate;
|
||||
|
||||
public TCommand(String rfAddress, boolean forceUpdate) {
|
||||
this.rfAddresses.add(rfAddress);
|
||||
this.forceUpdate = forceUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rooms for deletion
|
||||
*/
|
||||
public void addRoom(String rfAddress) {
|
||||
this.rfAddresses.add(rfAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final int updateForced = forceUpdate ? FORCE_UPDATE : NO_FORCE_UPDATE;
|
||||
byte[] commandArray = null;
|
||||
for (String rfAddress : rfAddresses) {
|
||||
commandArray = ArrayUtils.addAll(Utils.hexStringToByteArray(rfAddress), commandArray);
|
||||
}
|
||||
String encodedString = Base64.getEncoder().encodeToString(commandArray);
|
||||
|
||||
return "t:" + String.format("%02d", rfAddresses.size()) + "," + updateForced + "," + encodedString + "\r\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "A:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link UdpCubeCommand} is responsible for sending UDP commands to the MAX!
|
||||
* Cube LAN gateway.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UdpCubeCommand {
|
||||
|
||||
private static final String MAXCUBE_COMMAND_STRING = "eQ3Max*\0";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(UdpCubeCommand.class);
|
||||
|
||||
protected static boolean commandRunning;
|
||||
|
||||
private final UdpCommandType commandType;
|
||||
private final String serialNumber;
|
||||
private Map<String, String> commandResponse = new HashMap<>();
|
||||
@Nullable
|
||||
private String ipAddress;
|
||||
|
||||
public UdpCubeCommand(UdpCommandType commandType, @Nullable String serialNumber) {
|
||||
this.commandType = commandType;
|
||||
if (serialNumber == null || serialNumber.isEmpty()) {
|
||||
this.serialNumber = "**********";
|
||||
} else {
|
||||
this.serialNumber = serialNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UDP command types
|
||||
* REBOOT - R Reboot
|
||||
* DISCOVERY - I Identify
|
||||
* NETWORK - N Get network address
|
||||
* URL - h get URL information
|
||||
* DEFAULTNET - c get network default info
|
||||
*/
|
||||
public enum UdpCommandType {
|
||||
REBOOT,
|
||||
DISCOVERY,
|
||||
NETWORK,
|
||||
URL,
|
||||
DEFAULTNET
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the composed {@link UdpCubeCommand} command
|
||||
*/
|
||||
public synchronized boolean send() {
|
||||
String commandString;
|
||||
if (commandType.equals(UdpCommandType.REBOOT)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "R";
|
||||
} else if (commandType.equals(UdpCommandType.DISCOVERY)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "I";
|
||||
} else if (commandType.equals(UdpCommandType.NETWORK)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "N";
|
||||
} else if (commandType.equals(UdpCommandType.URL)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "h";
|
||||
} else if (commandType.equals(UdpCommandType.DEFAULTNET)) {
|
||||
commandString = MAXCUBE_COMMAND_STRING + serialNumber + "c";
|
||||
} else {
|
||||
logger.debug("Unknown Command {}", commandType);
|
||||
return false;
|
||||
}
|
||||
commandResponse.clear();
|
||||
logger.debug("Send {} command to MAX! Cube {}", commandType, serialNumber);
|
||||
sendUdpCommand(commandString, ipAddress);
|
||||
logger.trace("Done sending command.");
|
||||
receiveUdpCommandResponse();
|
||||
logger.debug("Done receiving response.");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void receiveUdpCommandResponse() {
|
||||
commandRunning = true;
|
||||
|
||||
try (DatagramSocket bcReceipt = new DatagramSocket(23272)) {
|
||||
bcReceipt.setReuseAddress(true);
|
||||
bcReceipt.setSoTimeout(5000);
|
||||
|
||||
while (commandRunning) {
|
||||
// Wait for a response
|
||||
byte[] recvBuf = new byte[1500];
|
||||
DatagramPacket receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
|
||||
bcReceipt.receive(receivePacket);
|
||||
|
||||
// We have a response
|
||||
String message = new String(receivePacket.getData(), receivePacket.getOffset(),
|
||||
receivePacket.getLength(), StandardCharsets.UTF_8);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Broadcast response from {} : {} '{}'", receivePacket.getAddress(), message.length(),
|
||||
message);
|
||||
}
|
||||
|
||||
// Check if the message is correct
|
||||
if (message.startsWith("eQ3Max") && !message.equals(MAXCUBE_COMMAND_STRING)) {
|
||||
commandResponse.put("maxCubeIP", receivePacket.getAddress().getHostAddress().toString());
|
||||
commandResponse.put("maxCubeState", message.substring(0, 8));
|
||||
commandResponse.put("serialNumber", message.substring(8, 18));
|
||||
commandResponse.put("msgValidid", message.substring(18, 19));
|
||||
String requestType = message.substring(19, 20);
|
||||
commandResponse.put("requestType", requestType);
|
||||
|
||||
if (requestType.equals("I")) {
|
||||
commandResponse.put("rfAddress",
|
||||
Utils.getHex(message.substring(21, 24).getBytes(StandardCharsets.UTF_8))
|
||||
.replace(" ", "").toLowerCase());
|
||||
commandResponse.put("firmwareVersion", Utils
|
||||
.getHex(message.substring(24, 26).getBytes(StandardCharsets.UTF_8)).replace(" ", "."));
|
||||
} else {
|
||||
// TODO: Further parsing of the other message types
|
||||
commandResponse.put("messageResponse",
|
||||
Utils.getHex(message.substring(20).getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
commandRunning = false;
|
||||
if (logger.isDebugEnabled()) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final Map.Entry<String, String> entry : commandResponse.entrySet()) {
|
||||
builder.append(String.format("%s: %s\n", entry.getKey(), entry.getValue()));
|
||||
}
|
||||
logger.debug("MAX! UDP response {}", builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
logger.trace("No further response");
|
||||
commandRunning = false;
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube response: {}", e.getMessage());
|
||||
commandRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send broadcast message over all active interfaces
|
||||
*
|
||||
* @param commandString string to be used for the discovery
|
||||
* @param ipAddress IP address of the MAX! Cube
|
||||
*
|
||||
*/
|
||||
private void sendUdpCommand(String commandString, @Nullable String ipAddress) {
|
||||
DatagramSocket bcSend = null;
|
||||
try {
|
||||
bcSend = new DatagramSocket();
|
||||
bcSend.setBroadcast(true);
|
||||
|
||||
byte[] sendData = commandString.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// Broadcast the message over all the network interfaces
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
|
||||
continue;
|
||||
}
|
||||
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
|
||||
InetAddress[] broadcast = new InetAddress[3];
|
||||
if (ipAddress != null && !ipAddress.isEmpty()) {
|
||||
broadcast[0] = InetAddress.getByName(ipAddress);
|
||||
} else {
|
||||
broadcast[0] = InetAddress.getByName("224.0.0.1");
|
||||
broadcast[1] = InetAddress.getByName("255.255.255.255");
|
||||
broadcast[2] = interfaceAddress.getBroadcast();
|
||||
}
|
||||
for (InetAddress bc : broadcast) {
|
||||
// Send the broadcast package!
|
||||
if (bc != null) {
|
||||
try {
|
||||
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, bc, 23272);
|
||||
bcSend.send(sendPacket);
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube UDP command sending: {}", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
logger.debug("{}", e.getMessage(), e);
|
||||
}
|
||||
logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
|
||||
networkInterface.getDisplayName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.trace("Done looping over all network interfaces. Now waiting for a reply!");
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube UDP command sending: {}", e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
if (bcSend != null) {
|
||||
bcSend.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IP address to send the command to. The command will be send to the address and broadcasted over all
|
||||
* active interfaces
|
||||
*
|
||||
* @param ipAddress IP address of the MAX! Cube
|
||||
*
|
||||
*/
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of the MAX! Cube on the
|
||||
*
|
||||
*/
|
||||
public Map<String, String> getCommandResponse() {
|
||||
return commandResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* The {@link ZCommand} send a wakeup request to MAX! devices.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZCommand extends CubeCommand {
|
||||
|
||||
public enum WakeUpType {
|
||||
ALL,
|
||||
ROOM,
|
||||
DEVICE
|
||||
}
|
||||
|
||||
private static final int DEFAULT_WAKETIME = 30;
|
||||
|
||||
private final String address;
|
||||
private final WakeUpType wakeUpType;
|
||||
private final int wakeUpTime;
|
||||
|
||||
public ZCommand(WakeUpType wakeUpType, String address, int wakeupTime) {
|
||||
this.address = address;
|
||||
this.wakeUpType = wakeUpType;
|
||||
this.wakeUpTime = wakeupTime;
|
||||
}
|
||||
|
||||
public static ZCommand wakeupRoom(int roomId) {
|
||||
return new ZCommand(WakeUpType.ROOM, String.format("%02d", roomId), DEFAULT_WAKETIME);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupRoom(int roomId, int wakeupTime) {
|
||||
return new ZCommand(WakeUpType.ROOM, String.format("%02d", roomId), wakeupTime);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupDevice(String rfAddress) {
|
||||
return new ZCommand(WakeUpType.DEVICE, rfAddress, DEFAULT_WAKETIME);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupDevice(String rfAddress, int wakeupTime) {
|
||||
return new ZCommand(WakeUpType.DEVICE, rfAddress, wakeupTime);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupAllDevices() {
|
||||
return new ZCommand(WakeUpType.ALL, "0", DEFAULT_WAKETIME);
|
||||
}
|
||||
|
||||
public static ZCommand wakeupAllDevices(int wakeupTime) {
|
||||
return new ZCommand(WakeUpType.ALL, "0", wakeupTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandString() {
|
||||
final String commandString;
|
||||
switch (wakeUpType) {
|
||||
case ALL:
|
||||
commandString = "A";
|
||||
break;
|
||||
case ROOM:
|
||||
commandString = "G," + address;
|
||||
break;
|
||||
case DEVICE:
|
||||
commandString = "D," + address;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown wakeup type: " + wakeUpType);
|
||||
}
|
||||
|
||||
return "z:" + Utils.toHex(wakeUpTime) + "," + commandString + '\r' + '\n';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReturnStrings() {
|
||||
return "A:";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.config;
|
||||
|
||||
import org.openhab.binding.max.internal.MaxBindingConstants;
|
||||
|
||||
/**
|
||||
* Configuration class for {@link MaxBindingConstants} bridge used to connect to the
|
||||
* maxCube device.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
public class MaxCubeBridgeConfiguration {
|
||||
|
||||
/** The IP address of the MAX! Cube LAN gateway */
|
||||
public String ipAddress;
|
||||
|
||||
/**
|
||||
* The port of the MAX! Cube LAN gateway as provided at
|
||||
* http://www.elv.de/controller.aspx?cid=824&detail=10&detail2=3484
|
||||
*/
|
||||
public Integer port;
|
||||
|
||||
/** The refresh interval in seconds which is used to poll given MAX! Cube */
|
||||
public Integer refreshInterval;
|
||||
|
||||
/** The unique serial number for a device */
|
||||
public String serialNumber;
|
||||
|
||||
/**
|
||||
* If set to true, the binding will leave the connection to the cube open
|
||||
* and just request new informations. This allows much higher poll rates and
|
||||
* causes less load than the non-exclusive polling but has the drawback that
|
||||
* no other apps (i.E. original software) can use the cube while this
|
||||
* binding is running.
|
||||
*/
|
||||
public boolean exclusive = false;
|
||||
|
||||
/**
|
||||
* in exclusive mode, how many requests are allowed until connection is
|
||||
* closed and reopened
|
||||
*/
|
||||
public Integer maxRequestsPerConnection;
|
||||
|
||||
public Integer cubeReboot;
|
||||
|
||||
/** NTP Server 1 hostname */
|
||||
public String ntpServer1;
|
||||
|
||||
/** NTP Server 2 hostname */
|
||||
public String ntpServer2;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
/**
|
||||
* Cube Lan Gateway.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
public class Cube extends Device {
|
||||
|
||||
private boolean portalEnabled;
|
||||
private String portalUrl;
|
||||
private String timeZoneWinter;
|
||||
private String timeZoneDaylightSaving;
|
||||
|
||||
public Cube(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.Cube;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MAX! Cube Lan interface";
|
||||
}
|
||||
|
||||
public boolean isPortalEnabled() {
|
||||
return portalEnabled;
|
||||
}
|
||||
|
||||
public void setPortalEnabled(boolean portalEnabled) {
|
||||
this.portalEnabled = portalEnabled;
|
||||
}
|
||||
|
||||
public String getPortalUrl() {
|
||||
return portalUrl;
|
||||
}
|
||||
|
||||
public void setPortalUrl(String portalUrl) {
|
||||
this.portalUrl = portalUrl;
|
||||
}
|
||||
|
||||
public String getTimeZoneWinter() {
|
||||
return timeZoneWinter;
|
||||
}
|
||||
|
||||
public void setTimeZoneWinter(String timeZoneWinter) {
|
||||
this.timeZoneWinter = timeZoneWinter;
|
||||
}
|
||||
|
||||
public String getTimeZoneDaylightSaving() {
|
||||
return timeZoneDaylightSaving;
|
||||
}
|
||||
|
||||
public void setTimeZoneDaylightSaving(String timeZoneDaylightSaving) {
|
||||
this.timeZoneDaylightSaving = timeZoneDaylightSaving;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class for devices provided by the MAX! protocol.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update + enhancements
|
||||
*/
|
||||
public abstract class Device {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Device.class);
|
||||
|
||||
private final String serialNumber;
|
||||
private String rfAddress;
|
||||
private int roomId;
|
||||
private String roomName;
|
||||
private String name;
|
||||
|
||||
private boolean updated;
|
||||
private boolean batteryLow;
|
||||
|
||||
private boolean initialized;
|
||||
private boolean answer;
|
||||
private boolean error;
|
||||
private boolean valid;
|
||||
private boolean dstSettingsActive;
|
||||
private boolean gatewayKnown;
|
||||
private boolean panelLocked;
|
||||
private boolean linkStatusError;
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
public Device(DeviceConfiguration c) {
|
||||
this.serialNumber = c.getSerialNumber();
|
||||
this.rfAddress = c.getRFAddress();
|
||||
this.roomId = c.getRoomId();
|
||||
this.roomName = c.getRoomName();
|
||||
this.name = c.getName();
|
||||
this.setProperties(new HashMap<>(c.getProperties()));
|
||||
}
|
||||
|
||||
public abstract DeviceType getType();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static Device create(String rfAddress, List<DeviceConfiguration> configurations) {
|
||||
Device returnValue = null;
|
||||
for (DeviceConfiguration c : configurations) {
|
||||
if (c.getRFAddress().toUpperCase().equals(rfAddress.toUpperCase())) {
|
||||
return create(c);
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new device
|
||||
*
|
||||
* @param DeviceConfiguration
|
||||
* @return Device
|
||||
*/
|
||||
public static Device create(DeviceConfiguration c) {
|
||||
{
|
||||
switch (c.getDeviceType()) {
|
||||
case HeatingThermostatPlus:
|
||||
case HeatingThermostat:
|
||||
HeatingThermostat thermostat = new HeatingThermostat(c);
|
||||
thermostat.setType(c.getDeviceType());
|
||||
return thermostat;
|
||||
case EcoSwitch:
|
||||
return new EcoSwitch(c);
|
||||
case ShutterContact:
|
||||
return new ShutterContact(c);
|
||||
case WallMountedThermostat:
|
||||
return new WallMountedThermostat(c);
|
||||
case Cube:
|
||||
return new Cube(c);
|
||||
default:
|
||||
return new UnsupportedDevice(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Device create(byte[] raw, List<DeviceConfiguration> configurations) {
|
||||
if (raw.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String rfAddress = Utils.toHex(raw[0] & 0xFF, raw[1] & 0xFF, raw[2] & 0xFF);
|
||||
|
||||
// Based on the RF address and the corresponding configuration,
|
||||
// create the device based on the type specified in it's configuration
|
||||
|
||||
Device device = Device.create(rfAddress, configurations);
|
||||
if (device == null) {
|
||||
LOGGER.debug("Can't create device from received message, returning 'null'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Device.update(raw, configurations, device);
|
||||
}
|
||||
|
||||
public static Device update(byte[] raw, List<DeviceConfiguration> configurations, Device device) {
|
||||
String rfAddress = device.getRFAddress();
|
||||
|
||||
// byte 4 is skipped
|
||||
|
||||
// multiple device information are encoded in those particular bytes
|
||||
boolean[] bits1 = Utils.getBits(Utils.fromByte(raw[4]));
|
||||
boolean[] bits2 = Utils.getBits(Utils.fromByte(raw[5]));
|
||||
|
||||
device.setInitialized(bits1[1]);
|
||||
device.setAnswer(bits1[2]);
|
||||
device.setError(bits1[3]);
|
||||
device.setValid(bits1[4]);
|
||||
|
||||
device.setDstSettingActive(bits2[3]);
|
||||
device.setGatewayKnown(bits2[4]);
|
||||
device.setPanelLocked(bits2[5]);
|
||||
device.setLinkStatusError(bits2[6]);
|
||||
device.setBatteryLow(bits2[7]);
|
||||
|
||||
LOGGER.trace("Device {} ({}): L Message length: {} content: {}", rfAddress, device.getType(), raw.length,
|
||||
Utils.getHex(raw));
|
||||
|
||||
// TODO move the device specific readings into the sub classes
|
||||
switch (device.getType()) {
|
||||
case WallMountedThermostat:
|
||||
case HeatingThermostat:
|
||||
case HeatingThermostatPlus:
|
||||
HeatingThermostat heatingThermostat = (HeatingThermostat) device;
|
||||
// "xxxx xx00 = automatic, xxxx xx01 = manual, xxxx xx10 = vacation, xxxx xx11 = boost":
|
||||
if (!bits2[1] && !bits2[0]) {
|
||||
heatingThermostat.setMode(ThermostatModeType.AUTOMATIC);
|
||||
} else if (!bits2[1] && bits2[0]) {
|
||||
heatingThermostat.setMode(ThermostatModeType.MANUAL);
|
||||
} else if (bits2[1] && !bits2[0]) {
|
||||
heatingThermostat.setMode(ThermostatModeType.VACATION);
|
||||
} else if (bits2[1] && bits2[0]) {
|
||||
heatingThermostat.setMode(ThermostatModeType.BOOST);
|
||||
} else {
|
||||
LOGGER.debug("Device {} ({}): Unknown mode", rfAddress, device.getType());
|
||||
}
|
||||
|
||||
heatingThermostat.setValvePosition(raw[6] & 0xFF);
|
||||
heatingThermostat.setTemperatureSetpoint(raw[7] & 0x7F);
|
||||
|
||||
// 9 2 858B Date until (05-09-2011) (see Encoding/Decoding
|
||||
// date/time)
|
||||
// B 1 2E Time until (23:00) (see Encoding/Decoding date/time)
|
||||
String hexDate = Utils.toHex(raw[8] & 0xFF, raw[9] & 0xFF);
|
||||
int dateValue = Utils.fromHex(hexDate);
|
||||
int timeValue = raw[10] & 0xFF;
|
||||
Date date = Utils.resolveDateTime(dateValue, timeValue);
|
||||
heatingThermostat.setDateSetpoint(date);
|
||||
|
||||
int actualTemp = 0;
|
||||
if (device.getType() == DeviceType.WallMountedThermostat) {
|
||||
actualTemp = (raw[11] & 0xFF) + (raw[7] & 0x80) * 2;
|
||||
|
||||
} else {
|
||||
if (heatingThermostat.getMode() != ThermostatModeType.VACATION
|
||||
&& heatingThermostat.getMode() != ThermostatModeType.BOOST) {
|
||||
actualTemp = (raw[8] & 0xFF) * 256 + (raw[9] & 0xFF);
|
||||
} else {
|
||||
LOGGER.debug("Device {} ({}): No temperature reading in {} mode", rfAddress, device.getType(),
|
||||
heatingThermostat.getMode());
|
||||
}
|
||||
}
|
||||
LOGGER.debug("Device {} ({}): Actual Temperature : {}", rfAddress, device.getType(),
|
||||
(double) actualTemp / 10);
|
||||
heatingThermostat.setTemperatureActual((double) actualTemp / 10);
|
||||
break;
|
||||
case EcoSwitch:
|
||||
String eCoSwitchData = Utils.toHex(raw[3] & 0xFF, raw[4] & 0xFF, raw[5] & 0xFF);
|
||||
LOGGER.trace("Device {} ({}): Status bytes : {}", rfAddress, device.getType(), eCoSwitchData);
|
||||
EcoSwitch ecoswitch = (EcoSwitch) device;
|
||||
// xxxx xx10 = shutter open, xxxx xx00 = shutter closed
|
||||
if (bits2[1] && !bits2[0]) {
|
||||
ecoswitch.setEcoMode(OnOffType.ON);
|
||||
LOGGER.trace("Device {} ({}): status: ON", rfAddress, device.getType());
|
||||
} else if (!bits2[1] && !bits2[0]) {
|
||||
ecoswitch.setEcoMode(OnOffType.OFF);
|
||||
LOGGER.trace("Device {} ({}): Status: OFF", rfAddress, device.getType());
|
||||
} else {
|
||||
LOGGER.trace("Device {} ({}): Status switch status Unknown (true-true)", rfAddress,
|
||||
device.getType());
|
||||
}
|
||||
break;
|
||||
case ShutterContact:
|
||||
ShutterContact shutterContact = (ShutterContact) device;
|
||||
// xxxx xx10 = shutter open, xxxx xx00 = shutter closed
|
||||
if (bits2[1] && !bits2[0]) {
|
||||
shutterContact.setShutterState(OpenClosedType.OPEN);
|
||||
LOGGER.debug("Device {} ({}): Status: Open", rfAddress, device.getType());
|
||||
} else if (!bits2[1] && !bits2[0]) {
|
||||
shutterContact.setShutterState(OpenClosedType.CLOSED);
|
||||
LOGGER.debug("Device {} ({}): Status: Closed", rfAddress, device.getType());
|
||||
} else {
|
||||
LOGGER.trace("Device {} ({}): Status switch status Unknown (true-true)", rfAddress,
|
||||
device.getType());
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
LOGGER.debug("Unhandled Device. DataBytes: {}", Utils.getHex(raw));
|
||||
break;
|
||||
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
private final void setBatteryLow(boolean batteryLow) {
|
||||
if (this.batteryLow != batteryLow) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.batteryLow = batteryLow;
|
||||
}
|
||||
|
||||
public final OnOffType getBatteryLow() {
|
||||
return (this.batteryLow ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
|
||||
public final String getRFAddress() {
|
||||
return this.rfAddress;
|
||||
}
|
||||
|
||||
public final void setRFAddress(String rfAddress) {
|
||||
this.rfAddress = rfAddress;
|
||||
}
|
||||
|
||||
public final int getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public final void setRoomId(int roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public final String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
public final void setRoomName(String roomName) {
|
||||
this.roomName = roomName;
|
||||
}
|
||||
|
||||
private void setLinkStatusError(boolean linkStatusError) {
|
||||
if (this.linkStatusError != linkStatusError) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.linkStatusError = linkStatusError;
|
||||
}
|
||||
|
||||
private void setPanelLocked(boolean panelLocked) {
|
||||
if (this.panelLocked != panelLocked) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.panelLocked = panelLocked;
|
||||
}
|
||||
|
||||
private void setGatewayKnown(boolean gatewayKnown) {
|
||||
if (this.gatewayKnown != gatewayKnown) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.gatewayKnown = gatewayKnown;
|
||||
}
|
||||
|
||||
private void setDstSettingActive(boolean dstSettingsActive) {
|
||||
if (this.dstSettingsActive != dstSettingsActive) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.dstSettingsActive = dstSettingsActive;
|
||||
}
|
||||
|
||||
public boolean isDstSettingsActive() {
|
||||
return dstSettingsActive;
|
||||
}
|
||||
|
||||
private void setValid(boolean valid) {
|
||||
if (this.valid != valid) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.valid = valid;
|
||||
}
|
||||
|
||||
public void setError(boolean error) {
|
||||
if (this.error != error) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
private void setInitialized(boolean initialized) {
|
||||
if (this.initialized != initialized) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.initialized = initialized;
|
||||
}
|
||||
|
||||
private void setAnswer(boolean answer) {
|
||||
if (this.answer != answer) {
|
||||
this.updated = true;
|
||||
}
|
||||
this.answer = answer;
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public void setUpdated(boolean updated) {
|
||||
this.updated = updated;
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
public boolean isAnswer() {
|
||||
return answer;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public boolean isGatewayKnown() {
|
||||
return gatewayKnown;
|
||||
}
|
||||
|
||||
public boolean isPanelLocked() {
|
||||
return panelLocked;
|
||||
}
|
||||
|
||||
public boolean isLinkStatusError() {
|
||||
return linkStatusError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the properties
|
||||
*/
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param properties the properties to set
|
||||
*/
|
||||
public void setProperties(Map<String, Object> properties) {
|
||||
this.properties = new HashMap<>(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getType().toString() + " (" + rfAddress + ") '" + this.getName() + "'";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.binding.max.internal.message.CMessage;
|
||||
import org.openhab.binding.max.internal.message.Message;
|
||||
|
||||
/**
|
||||
* Base class for configuration provided by the MAX! Cube C Message.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
public final class DeviceConfiguration {
|
||||
|
||||
private DeviceType deviceType;
|
||||
private String rfAddress;
|
||||
private String serialNumber;
|
||||
private String name;
|
||||
private int roomId = -1;
|
||||
private String roomName;
|
||||
|
||||
/** Extended configuration properties **/
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
private DeviceConfiguration() {
|
||||
}
|
||||
|
||||
public static DeviceConfiguration create(Message message) {
|
||||
final DeviceConfiguration configuration = new DeviceConfiguration();
|
||||
configuration.setValues((CMessage) message);
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public static DeviceConfiguration create(DeviceInformation di) {
|
||||
DeviceConfiguration configuration = new DeviceConfiguration();
|
||||
configuration.setValues(di.getRFAddress(), di.getDeviceType(), di.getSerialNumber(), di.getRoomId(),
|
||||
di.getName());
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void setValues(CMessage message) {
|
||||
setValues(message.getRFAddress(), message.getDeviceType(), message.getSerialNumber(), message.getRoomID());
|
||||
properties = new HashMap<>(message.getProperties());
|
||||
}
|
||||
|
||||
private void setValues(String rfAddress, DeviceType deviceType, String serialNumber, int roomId, String name) {
|
||||
setValues(rfAddress, deviceType, serialNumber, roomId);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
private void setValues(String rfAddress, DeviceType deviceType, String serialNumber, int roomId) {
|
||||
this.rfAddress = rfAddress;
|
||||
this.deviceType = deviceType;
|
||||
this.serialNumber = serialNumber;
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public DeviceType getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (name == null) {
|
||||
return "";
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public void setRoomId(int roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public String getRoomName() {
|
||||
if (roomName == null) {
|
||||
return "";
|
||||
} else {
|
||||
return roomName;
|
||||
}
|
||||
}
|
||||
|
||||
public void setRoomName(String roomName) {
|
||||
this.roomName = roomName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the properties
|
||||
*/
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param properties the properties to set
|
||||
*/
|
||||
public void setProperties(HashMap<String, Object> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
/**
|
||||
* Device information provided by the M message meta information.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
public class DeviceInformation {
|
||||
|
||||
private final DeviceType deviceType;
|
||||
private final String serialNumber;
|
||||
private final String rfAddress;
|
||||
private final String name;
|
||||
private final int roomId;
|
||||
|
||||
public DeviceInformation(DeviceType deviceType, String serialNumber, String rfAddress, String name, int roomId) {
|
||||
this.deviceType = deviceType;
|
||||
this.serialNumber = serialNumber;
|
||||
this.rfAddress = rfAddress;
|
||||
this.name = name;
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public DeviceType getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
/**
|
||||
* This enumeration represents the different message types provided by the MAX! Cube protocol.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
public enum DeviceType {
|
||||
Invalid(256),
|
||||
Cube(0),
|
||||
HeatingThermostat(1),
|
||||
HeatingThermostatPlus(2),
|
||||
WallMountedThermostat(3),
|
||||
ShutterContact(4),
|
||||
EcoSwitch(5);
|
||||
|
||||
private final int value;
|
||||
|
||||
private DeviceType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static DeviceType create(int value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return Cube;
|
||||
case 1:
|
||||
return HeatingThermostat;
|
||||
case 2:
|
||||
return HeatingThermostatPlus;
|
||||
case 3:
|
||||
return WallMountedThermostat;
|
||||
case 4:
|
||||
return ShutterContact;
|
||||
case 5:
|
||||
return EcoSwitch;
|
||||
default:
|
||||
return Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return "Cube";
|
||||
case 1:
|
||||
return "Thermostat";
|
||||
case 2:
|
||||
return "Thermostat+";
|
||||
case 3:
|
||||
return "Wallmounted Thermostat";
|
||||
case 4:
|
||||
return "Shutter Contact";
|
||||
case 5:
|
||||
return "Eco Switch";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
/**
|
||||
* MAX! EcoSwitch.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
public class EcoSwitch extends ShutterContact {
|
||||
|
||||
private OnOffType ecoMode;
|
||||
|
||||
public EcoSwitch(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.EcoSwitch;
|
||||
}
|
||||
|
||||
public OnOffType getEcoMode() {
|
||||
return ecoMode;
|
||||
}
|
||||
|
||||
public void setEcoMode(OnOffType ecoMode) {
|
||||
this.ecoMode = ecoMode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MAX! Heating thermostat & Heating thermostat+ .
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*/
|
||||
public class HeatingThermostat extends Device {
|
||||
private ThermostatModeType mode;
|
||||
|
||||
/** Valve position in % */
|
||||
private int valvePosition;
|
||||
|
||||
/** Temperature setpoint in degrees celcius */
|
||||
private double temperatureSetpoint;
|
||||
|
||||
/** Actual Temperature in degrees celcius */
|
||||
private double temperatureActual;
|
||||
|
||||
/** Date setpoint until the temperature setpoint is valid */
|
||||
private Date dateSetpoint;
|
||||
|
||||
/** Device type for this thermostat **/
|
||||
private DeviceType deviceType = DeviceType.HeatingThermostat;
|
||||
|
||||
/** Date/Time the actual temperature was last updated */
|
||||
private Date actualTempLastUpdated;
|
||||
|
||||
public HeatingThermostat(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the DeviceType for this thermostat.
|
||||
*
|
||||
* @param DeviceType as provided by the C message
|
||||
*/
|
||||
void setType(DeviceType type) {
|
||||
this.deviceType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current mode of the thermostat.
|
||||
*/
|
||||
public String getModeString() {
|
||||
return this.mode.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current mode of the thermostat.
|
||||
*/
|
||||
public ThermostatModeType getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
public void setMode(ThermostatModeType mode) {
|
||||
if (this.mode != mode) {
|
||||
setUpdated(true);
|
||||
}
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the valve position for this thermostat.
|
||||
*
|
||||
* @param valvePosition the valve position as provided by the L message
|
||||
*/
|
||||
public void setValvePosition(int valvePosition) {
|
||||
if (this.valvePosition != valvePosition) {
|
||||
setUpdated(true);
|
||||
}
|
||||
this.valvePosition = valvePosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current valve position of this thermostat in percent.
|
||||
*
|
||||
* @return
|
||||
* the valve position as <code>DecimalType</code>
|
||||
*/
|
||||
public int getValvePosition() {
|
||||
return this.valvePosition;
|
||||
}
|
||||
|
||||
public void setDateSetpoint(Date date) {
|
||||
this.dateSetpoint = date;
|
||||
}
|
||||
|
||||
public Date getDateSetpoint() {
|
||||
return dateSetpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the actual temperature for this thermostat.
|
||||
*
|
||||
* @param value the actual temperature raw value as provided by the L message
|
||||
*/
|
||||
public void setTemperatureActual(double value) {
|
||||
if (this.temperatureActual != value) {
|
||||
setUpdated(true);
|
||||
this.actualTempLastUpdated = Calendar.getInstance().getTime();
|
||||
}
|
||||
this.temperatureActual = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the measured temperature of this thermostat.
|
||||
* 0<>C is displayed if no actual is measured. Temperature is only updated after valve position changes
|
||||
*
|
||||
* @return
|
||||
* the actual temperature as <code>QuantityType</code>
|
||||
*/
|
||||
public double getTemperatureActual() {
|
||||
return BigDecimal.valueOf(this.temperatureActual).setScale(1, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setpoint temperature for this thermostat.
|
||||
*
|
||||
* @param value the setpoint temperature raw value as provided by the L message
|
||||
*/
|
||||
public void setTemperatureSetpoint(int value) {
|
||||
if (Math.abs(this.temperatureSetpoint - (value / 2.0)) > 0.1) {
|
||||
setUpdated(true);
|
||||
}
|
||||
this.temperatureSetpoint = value / 2.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setpoint temperature of this thermostat.
|
||||
* 4.5<EFBFBD>C is displayed as OFF, 30.5<EFBFBD>C is displayed as On at the thermostat display.
|
||||
*
|
||||
* @return
|
||||
* the setpoint temperature as <code>QuantityType</code>
|
||||
*/
|
||||
public double getTemperatureSetpoint() {
|
||||
return this.temperatureSetpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Date the actual Temperature was last Updated
|
||||
*/
|
||||
public Date getActualTempLastUpdated() {
|
||||
return actualTempLastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param actualTempLastUpdated the Date the actual Temperature was last Updated
|
||||
*/
|
||||
public void setActualTempLastUpdated(Date actualTempLastUpdated) {
|
||||
this.actualTempLastUpdated = actualTempLastUpdated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
/**
|
||||
* Room information provided by the M message meta information.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen (marcel@verpaalen.com) - OH2 update
|
||||
*/
|
||||
public class RoomInformation {
|
||||
private int position;
|
||||
private String name;
|
||||
private String rfAddress;
|
||||
|
||||
public RoomInformation(int position, String name, String rfAddress) {
|
||||
this.position = position;
|
||||
this.name = name;
|
||||
this.rfAddress = rfAddress;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public void setRFAddress(String rfAddress) {
|
||||
this.rfAddress = rfAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Room " + position + " (" + rfAddress + ") ='" + name + "'";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
|
||||
/**
|
||||
* MAX! Shutter contact device.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*/
|
||||
public class ShutterContact extends Device {
|
||||
|
||||
private OpenClosedType shutterState;
|
||||
|
||||
public ShutterContact(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public void setShutterState(OpenClosedType shutterState) {
|
||||
if (this.shutterState != shutterState) {
|
||||
setUpdated(true);
|
||||
}
|
||||
this.shutterState = shutterState;
|
||||
}
|
||||
|
||||
public OpenClosedType getShutterState() {
|
||||
return shutterState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.ShutterContact;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.PrimitiveType;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* This enumeration represents the different mode types of a MAX! heating thermostat.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*/
|
||||
public enum ThermostatModeType implements PrimitiveType, State, Command {
|
||||
AUTOMATIC,
|
||||
MANUAL,
|
||||
VACATION,
|
||||
BOOST;
|
||||
|
||||
@Override
|
||||
public String format(String pattern) {
|
||||
return String.format(pattern, this.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toFullString() {
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
/**
|
||||
* Unsupported devices.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*/
|
||||
|
||||
public class UnsupportedDevice extends Device {
|
||||
|
||||
public UnsupportedDevice(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.Invalid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Unsupported device";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.device;
|
||||
|
||||
/**
|
||||
* MAX! wall mounted thermostat.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
public class WallMountedThermostat extends HeatingThermostat {
|
||||
|
||||
public WallMountedThermostat(DeviceConfiguration c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getType() {
|
||||
return DeviceType.WallMountedThermostat;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxCubeBridgeDiscovery} is responsible for discovering new MAX!
|
||||
* Cube LAN gateway devices on the network
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.max")
|
||||
public class MaxCubeBridgeDiscovery extends AbstractDiscoveryService {
|
||||
|
||||
private static final String MAXCUBE_DISCOVER_STRING = "eQ3Max*\0**********I";
|
||||
private static final int SEARCH_TIME = 15;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxCubeBridgeDiscovery.class);
|
||||
|
||||
protected static boolean discoveryRunning;
|
||||
|
||||
/** The refresh interval for discovery of MAX! Cubes */
|
||||
private static final long SEARCH_INTERVAL = 600;
|
||||
private ScheduledFuture<?> cubeDiscoveryJob;
|
||||
|
||||
public MaxCubeBridgeDiscovery() {
|
||||
super(SEARCH_TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return SUPPORTED_BRIDGE_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
logger.debug("Start MAX! Cube discovery");
|
||||
discoverCube();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
logger.debug("Stop MAX! Cube background discovery");
|
||||
if (cubeDiscoveryJob != null && !cubeDiscoveryJob.isCancelled()) {
|
||||
cubeDiscoveryJob.cancel(true);
|
||||
cubeDiscoveryJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
logger.debug("Start MAX! Cube background discovery");
|
||||
if (cubeDiscoveryJob == null || cubeDiscoveryJob.isCancelled()) {
|
||||
cubeDiscoveryJob = scheduler.scheduleWithFixedDelay(this::discoverCube, 0, SEARCH_INTERVAL,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void discoverCube() {
|
||||
logger.debug("Run MAX! Cube discovery");
|
||||
sendDiscoveryMessage(MAXCUBE_DISCOVER_STRING);
|
||||
logger.trace("Done sending broadcast discovery messages.");
|
||||
receiveDiscoveryMessage();
|
||||
logger.debug("Done receiving discovery messages.");
|
||||
}
|
||||
|
||||
private void receiveDiscoveryMessage() {
|
||||
try (final DatagramSocket bcReceipt = new DatagramSocket(23272)) {
|
||||
discoveryRunning = true;
|
||||
bcReceipt.setReuseAddress(true);
|
||||
bcReceipt.setSoTimeout(5000);
|
||||
|
||||
while (discoveryRunning) {
|
||||
// Wait for a response
|
||||
final byte[] recvBuf = new byte[1500];
|
||||
final DatagramPacket receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
|
||||
bcReceipt.receive(receivePacket);
|
||||
|
||||
// We have a response
|
||||
final byte[] messageBuf = Arrays.copyOfRange(receivePacket.getData(), receivePacket.getOffset(),
|
||||
receivePacket.getOffset() + receivePacket.getLength());
|
||||
final String message = new String(messageBuf, StandardCharsets.UTF_8);
|
||||
logger.trace("Broadcast response from {} : {} '{}'", receivePacket.getAddress(), message.length(),
|
||||
message);
|
||||
|
||||
// Check if the message is correct
|
||||
if (message.startsWith("eQ3Max") && !message.equals(MAXCUBE_DISCOVER_STRING)) {
|
||||
String maxCubeIP = receivePacket.getAddress().getHostAddress();
|
||||
String maxCubeState = message.substring(0, 8);
|
||||
String serialNumber = message.substring(8, 18);
|
||||
String msgValidid = message.substring(18, 19);
|
||||
String requestType = message.substring(19, 20);
|
||||
String rfAddress = "";
|
||||
logger.debug("MAX! Cube found on network");
|
||||
logger.debug("Found at : {}", maxCubeIP);
|
||||
logger.trace("Cube State: {}", maxCubeState);
|
||||
logger.debug("Serial : {}", serialNumber);
|
||||
logger.trace("Msg Valid : {}", msgValidid);
|
||||
logger.trace("Msg Type : {}", requestType);
|
||||
|
||||
if (requestType.equals("I")) {
|
||||
rfAddress = Utils.getHex(Arrays.copyOfRange(messageBuf, 21, 24)).replace(" ", "").toLowerCase();
|
||||
String firmwareVersion = Utils.getHex(Arrays.copyOfRange(messageBuf, 24, 26)).replace(" ", ".");
|
||||
logger.debug("RF Address: {}", rfAddress);
|
||||
logger.debug("Firmware : {}", firmwareVersion);
|
||||
}
|
||||
discoveryResultSubmission(maxCubeIP, serialNumber, rfAddress);
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
logger.trace("No further response");
|
||||
discoveryRunning = false;
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
|
||||
discoveryRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void discoveryResultSubmission(String IpAddress, String cubeSerialNumber, String rfAddress) {
|
||||
if (cubeSerialNumber != null) {
|
||||
logger.trace("Adding new MAX! Cube Lan Gateway on {} with id '{}' to Smarthome inbox", IpAddress,
|
||||
cubeSerialNumber);
|
||||
Map<String, Object> properties = new HashMap<>(2);
|
||||
properties.put(PROPERTY_IP_ADDRESS, IpAddress);
|
||||
properties.put(PROPERTY_SERIAL_NUMBER, cubeSerialNumber);
|
||||
properties.put(PROPERTY_RFADDRESS, rfAddress);
|
||||
ThingUID uid = new ThingUID(CUBEBRIDGE_THING_TYPE, cubeSerialNumber);
|
||||
thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties)
|
||||
.withRepresentationProperty(PROPERTY_SERIAL_NUMBER).withThingType(CUBEBRIDGE_THING_TYPE)
|
||||
.withLabel("MAX! Cube LAN Gateway").build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send broadcast message over all active interfaces
|
||||
*
|
||||
* @param discoverString
|
||||
* String to be used for the discovery
|
||||
*/
|
||||
private void sendDiscoveryMessage(String discoverString) {
|
||||
// Find the MaxCube using UDP broadcast
|
||||
try (DatagramSocket bcSend = new DatagramSocket()) {
|
||||
bcSend.setBroadcast(true);
|
||||
|
||||
byte[] sendData = discoverString.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// Broadcast the message over all the network interfaces
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
|
||||
continue;
|
||||
}
|
||||
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
|
||||
InetAddress[] broadcast = new InetAddress[3];
|
||||
broadcast[0] = InetAddress.getByName("224.0.0.1");
|
||||
broadcast[1] = InetAddress.getByName("255.255.255.255");
|
||||
broadcast[2] = interfaceAddress.getBroadcast();
|
||||
for (InetAddress bc : broadcast) {
|
||||
// Send the broadcast package!
|
||||
if (bc != null) {
|
||||
try {
|
||||
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, bc, 23272);
|
||||
bcSend.send(sendPacket);
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
logger.debug("{}", e.getMessage(), e);
|
||||
}
|
||||
logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
|
||||
networkInterface.getDisplayName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.trace("Done looping over all network interfaces. Now waiting for a reply!");
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error during MAX! Cube discovery: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.discovery;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.MaxBindingConstants;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.handler.DeviceStatusListener;
|
||||
import org.openhab.binding.max.internal.handler.MaxCubeBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxDeviceDiscoveryService} class is used to discover MAX! Cube
|
||||
* devices that are connected to the Lan gateway.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MaxDeviceDiscoveryService extends AbstractDiscoveryService
|
||||
implements DeviceStatusListener, DiscoveryService, ThingHandlerService {
|
||||
|
||||
private static final int SEARCH_TIME = 60;
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxDeviceDiscoveryService.class);
|
||||
|
||||
private @Nullable MaxCubeBridgeHandler maxCubeBridgeHandler;
|
||||
|
||||
public MaxDeviceDiscoveryService() {
|
||||
super(MaxBindingConstants.SUPPORTED_DEVICE_THING_TYPES_UIDS, SEARCH_TIME, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@NonNullByDefault({}) ThingHandler handler) {
|
||||
if (handler instanceof MaxCubeBridgeHandler) {
|
||||
this.maxCubeBridgeHandler = (MaxCubeBridgeHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return maxCubeBridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
MaxCubeBridgeHandler localMaxCubeBridgeHandler = maxCubeBridgeHandler;
|
||||
if (localMaxCubeBridgeHandler != null) {
|
||||
localMaxCubeBridgeHandler.registerDeviceStatusListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
MaxCubeBridgeHandler localMaxCubeBridgeHandler = maxCubeBridgeHandler;
|
||||
if (localMaxCubeBridgeHandler != null) {
|
||||
localMaxCubeBridgeHandler.unregisterDeviceStatusListener(this);
|
||||
removeOlderResults(new Date().getTime(), localMaxCubeBridgeHandler.getThing().getUID());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return MaxBindingConstants.SUPPORTED_DEVICE_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceAdded(Bridge bridge, Device device) {
|
||||
logger.trace("Adding new MAX! {} with id '{}' to smarthome inbox", device.getType(), device.getSerialNumber());
|
||||
ThingUID thingUID = null;
|
||||
switch (device.getType()) {
|
||||
case WallMountedThermostat:
|
||||
thingUID = new ThingUID(MaxBindingConstants.WALLTHERMOSTAT_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
case HeatingThermostat:
|
||||
thingUID = new ThingUID(MaxBindingConstants.HEATINGTHERMOSTAT_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
case HeatingThermostatPlus:
|
||||
thingUID = new ThingUID(MaxBindingConstants.HEATINGTHERMOSTATPLUS_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
case ShutterContact:
|
||||
thingUID = new ThingUID(MaxBindingConstants.SHUTTERCONTACT_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
case EcoSwitch:
|
||||
thingUID = new ThingUID(MaxBindingConstants.ECOSWITCH_THING_TYPE, bridge.getUID(),
|
||||
device.getSerialNumber());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (thingUID != null) {
|
||||
String name = device.getName();
|
||||
if (name.isEmpty()) {
|
||||
name = device.getSerialNumber();
|
||||
}
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperty(Thing.PROPERTY_SERIAL_NUMBER, device.getSerialNumber()).withBridge(bridge.getUID())
|
||||
.withLabel(device.getType() + ": " + name).withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER)
|
||||
.build();
|
||||
thingDiscovered(discoveryResult);
|
||||
} else {
|
||||
logger.debug("Discovered MAX! device is unsupported: type '{}' with id '{}'", device.getType(),
|
||||
device.getSerialNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
MaxCubeBridgeHandler localMaxCubeBridgeHandler = maxCubeBridgeHandler;
|
||||
if (localMaxCubeBridgeHandler != null) {
|
||||
localMaxCubeBridgeHandler.clearDeviceList();
|
||||
localMaxCubeBridgeHandler.deviceInclusion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceStateChanged(ThingUID bridge, Device device) {
|
||||
// this can be ignored here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(MaxCubeBridgeHandler bridge, Device device) {
|
||||
// this can be ignored here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceConfigUpdate(Bridge bridge, Device device) {
|
||||
// this can be ignored here
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* but the processor is currently processing an other message type.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class IncompleteMessageException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -8882867114144637120L;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* but the processor is currently processing an other message type.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class IncorrectMultilineIndexException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = 3102159039702650238L;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* but the processor is not yet ready to handle new lines because there is already a message that
|
||||
* has be pulled before.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class MessageIsWaitingException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -7317329978634583853L;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to pull a message from the message processor,
|
||||
* but the processor does not yet have a complete message.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class NoMessageAvailableException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -7663390696233390452L;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* the processor detects a known message indicator, but the message could not be parsed correctly.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class UnprocessableMessageException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -9071779402960309265L;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.exceptions;
|
||||
|
||||
/**
|
||||
* Will be thrown when there is an attempt to put a new message line into the message processor,
|
||||
* but the line starts with an unknown message indicator.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
public class UnsupportedMessageTypeException extends Exception {
|
||||
|
||||
/**
|
||||
* required variable to avoid IncorrectMultilineIndexException warning
|
||||
*/
|
||||
private static final long serialVersionUID = -5163044407682700913L;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.factory;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.handler.MaxCubeBridgeHandler;
|
||||
import org.openhab.binding.max.internal.handler.MaxDevicesHandler;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxCubeHandlerFactory} is responsible for creating things and
|
||||
* thing handlers.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.max")
|
||||
public class MaxCubeHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxCubeHandlerFactory.class);
|
||||
|
||||
@Override
|
||||
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
|
||||
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
||||
if (CUBEBRIDGE_THING_TYPE.equals(thingTypeUID)) {
|
||||
ThingUID cubeBridgeUID = getBridgeThingUID(thingTypeUID, thingUID, configuration);
|
||||
return super.createThing(thingTypeUID, configuration, cubeBridgeUID, null);
|
||||
}
|
||||
if (supportsThingType(thingTypeUID) && bridgeUID != null) {
|
||||
ThingUID deviceUID = getMaxCubeDeviceUID(thingTypeUID, thingUID, configuration, bridgeUID);
|
||||
return super.createThing(thingTypeUID, configuration, deviceUID, bridgeUID);
|
||||
}
|
||||
throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the binding.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
|
||||
Configuration configuration) {
|
||||
if (thingUID == null) {
|
||||
String serialNumber = (String) configuration.get(Thing.PROPERTY_SERIAL_NUMBER);
|
||||
return new ThingUID(thingTypeUID, serialNumber);
|
||||
}
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
private ThingUID getMaxCubeDeviceUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
|
||||
Configuration configuration, ThingUID bridgeUID) {
|
||||
if (thingUID == null) {
|
||||
String serialNumber = (String) configuration.get(Thing.PROPERTY_SERIAL_NUMBER);
|
||||
return new ThingUID(thingTypeUID, serialNumber, bridgeUID.getId());
|
||||
}
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (CUBEBRIDGE_THING_TYPE.equals(thingTypeUID)) {
|
||||
return new MaxCubeBridgeHandler((Bridge) thing);
|
||||
} else if (SUPPORTED_DEVICE_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
return new MaxDevicesHandler(thing);
|
||||
} else {
|
||||
logger.debug("ThingHandler not found for {}", thingTypeUID);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* The {@link DeviceStatusListener} is notified when a device status has changed
|
||||
* or a device has been removed or added.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface DeviceStatusListener {
|
||||
|
||||
/**
|
||||
* This method is called whenever the state of the given device has changed.
|
||||
*
|
||||
* @param bridge The MAX! Cube bridge the changed device is connected to
|
||||
* @param device The device which received the state update
|
||||
*/
|
||||
void onDeviceStateChanged(ThingUID bridge, Device device);
|
||||
|
||||
/**
|
||||
* This method is called whenever a device is removed.
|
||||
*
|
||||
* @param bridge The MAX! Cube bridge the removed device was connected to
|
||||
* @param device The device which is removed
|
||||
*/
|
||||
void onDeviceRemoved(MaxCubeBridgeHandler bridge, Device device);
|
||||
|
||||
/**
|
||||
* This method is called whenever a device is added.
|
||||
*
|
||||
* @param bridge The MAX! Cube bridge the added device was connected to
|
||||
* @param device The device which is added
|
||||
*/
|
||||
void onDeviceAdded(Bridge bridge, Device device);
|
||||
|
||||
/**
|
||||
* This method is called whenever a device config is updated.
|
||||
*
|
||||
* @param bridgeThe MAX! Cube bridge the device was connected to
|
||||
* @param device The device which config is changed
|
||||
*/
|
||||
void onDeviceConfigUpdate(Bridge bridge, Device device);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,580 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.handler;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.max.actions.MaxDevicesActions;
|
||||
import org.openhab.binding.max.internal.command.CCommand;
|
||||
import org.openhab.binding.max.internal.command.QCommand;
|
||||
import org.openhab.binding.max.internal.command.SConfigCommand;
|
||||
import org.openhab.binding.max.internal.command.SConfigCommand.ConfigCommandType;
|
||||
import org.openhab.binding.max.internal.command.ZCommand;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.openhab.binding.max.internal.device.EcoSwitch;
|
||||
import org.openhab.binding.max.internal.device.HeatingThermostat;
|
||||
import org.openhab.binding.max.internal.device.ShutterContact;
|
||||
import org.openhab.binding.max.internal.device.ThermostatModeType;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MaxDevicesHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
public class MaxDevicesHandler extends BaseThingHandler implements DeviceStatusListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MaxDevicesHandler.class);
|
||||
private MaxCubeBridgeHandler bridgeHandler;
|
||||
|
||||
private String maxDeviceSerial;
|
||||
private String rfAddress;
|
||||
private boolean propertiesSet;
|
||||
private boolean configSet;
|
||||
|
||||
// actual refresh variables
|
||||
public static final int REFRESH_ACTUAL_MIN_RATE = 10; // minutes
|
||||
public static final int REFRESH_ACTUAL_DURATION = 120; // seconds
|
||||
private static final long COMMUNICATION_DELAY_TIME = 120;
|
||||
private int refreshActualRate;
|
||||
private boolean refreshingActuals;
|
||||
private ScheduledFuture<?> refreshActualsJob;
|
||||
private double originalSetTemp;
|
||||
private ThermostatModeType originalMode;
|
||||
|
||||
public MaxDevicesHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
try {
|
||||
final Configuration config = getThing().getConfiguration();
|
||||
final String configDeviceId = (String) config.get(Thing.PROPERTY_SERIAL_NUMBER);
|
||||
|
||||
try {
|
||||
refreshActualRate = ((BigDecimal) config.get(PROPERTY_REFRESH_ACTUAL_RATE)).intValueExact();
|
||||
} catch (Exception e) {
|
||||
refreshActualRate = 0;
|
||||
}
|
||||
|
||||
if (configDeviceId != null) {
|
||||
maxDeviceSerial = configDeviceId;
|
||||
}
|
||||
if (maxDeviceSerial != null) {
|
||||
logger.debug("Initialized MAX! device handler for {}.", maxDeviceSerial);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Initialized MAX! device missing serialNumber configuration");
|
||||
}
|
||||
propertiesSet = false;
|
||||
configSet = false;
|
||||
getMaxCubeBridgeHandler();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during initialize : {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing MAX! device {} {}.", getThing().getUID(), maxDeviceSerial);
|
||||
if (refreshingActuals) {
|
||||
refreshActualsRestore();
|
||||
}
|
||||
if (refreshActualsJob != null && !refreshActualsJob.isCancelled()) {
|
||||
refreshActualsJob.cancel(true);
|
||||
refreshActualsJob = null;
|
||||
}
|
||||
if (bridgeHandler != null) {
|
||||
logger.trace("Clear MAX! device {} {} from bridge.", getThing().getUID(), maxDeviceSerial);
|
||||
bridgeHandler.clearDeviceList();
|
||||
bridgeHandler.unregisterDeviceStatusListener(this);
|
||||
bridgeHandler = null;
|
||||
}
|
||||
logger.debug("Disposed MAX! device {} {}.", getThing().getUID(), maxDeviceSerial);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thingUpdated(Thing thing) {
|
||||
configSet = false;
|
||||
super.thingUpdated(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
|
||||
logger.debug("MAX! Device {}: Configuration update received", getThing().getUID());
|
||||
boolean temperaturePropertyUpdateNeeded = false;
|
||||
final Device device = getMaxCubeBridgeHandler().getDevice(maxDeviceSerial);
|
||||
|
||||
final Map<String, Object> deviceProperties = device == null ? new HashMap<>() : device.getProperties();
|
||||
final Configuration configuration = editConfiguration();
|
||||
for (final Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
|
||||
logger.debug("MAX! Device {}: Configuration update {} to {}", getThing().getUID(),
|
||||
configurationParameter.getKey(), configurationParameter.getValue());
|
||||
|
||||
// Test if it is a part of the configuration properties.
|
||||
// As paperUI sends all parameters as changed, we need to determine which ones really changed.
|
||||
if (deviceProperties.containsKey(configurationParameter.getKey())) {
|
||||
if (deviceProperties.get(configurationParameter.getKey()).equals(configurationParameter.getValue())) {
|
||||
logger.trace("Device {} Property {} value {} unchanged.", getThing().getUID(),
|
||||
configurationParameter.getKey(), configurationParameter.getValue());
|
||||
} else if (configurationParameter.getValue().getClass() == BigDecimal.class
|
||||
&& ((BigDecimal) deviceProperties.get(configurationParameter.getKey()))
|
||||
.compareTo((BigDecimal) configurationParameter.getValue()) == 0) {
|
||||
logger.trace("Device {} Property {} value {} unchanged.", getThing().getUID(),
|
||||
configurationParameter.getKey(), configurationParameter.getValue());
|
||||
} else {
|
||||
logger.debug("Device {} Property {} value {} -> {} changed.", getThing().getUID(),
|
||||
configurationParameter.getKey(), deviceProperties.get(configurationParameter.getKey()),
|
||||
configurationParameter.getValue());
|
||||
temperaturePropertyUpdateNeeded = true;
|
||||
}
|
||||
}
|
||||
if (configurationParameter.getKey().equals(PROPERTY_DEVICENAME)
|
||||
|| configurationParameter.getKey().equals(PROPERTY_ROOMID)) {
|
||||
updateDeviceName(configurationParameter);
|
||||
}
|
||||
if (configurationParameter.getKey().startsWith("action-")) {
|
||||
if (configurationParameter.getValue().toString().equals(BUTTON_ACTION_VALUE)) {
|
||||
configurationParameter.setValue(BigDecimal.valueOf(BUTTON_NOACTION_VALUE));
|
||||
if (configurationParameter.getKey().equals(ACTION_DEVICE_DELETE)) {
|
||||
deviceDelete();
|
||||
}
|
||||
}
|
||||
}
|
||||
configuration.put(configurationParameter.getKey(), configurationParameter.getValue());
|
||||
}
|
||||
// Persist changes and restart with new parameters
|
||||
updateConfiguration(configuration);
|
||||
if (temperaturePropertyUpdateNeeded) {
|
||||
sendPropertyUpdate(configurationParameters, deviceProperties);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(MaxDevicesActions.class);
|
||||
}
|
||||
|
||||
private void sendPropertyUpdate(Map<String, Object> configurationParameters, Map<String, Object> deviceProperties) {
|
||||
if (getMaxCubeBridgeHandler() == null) {
|
||||
logger.debug("MAX! Cube LAN gateway bridge handler not found. Cannot handle update without bridge.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Device device = getMaxCubeBridgeHandler().getDevice(maxDeviceSerial);
|
||||
rfAddress = device.getRFAddress();
|
||||
int roomId = device.getRoomId();
|
||||
BigDecimal tempComfort = (BigDecimal) configurationParameters.getOrDefault(PROPERTY_THERMO_COMFORT_TEMP,
|
||||
deviceProperties.get(PROPERTY_THERMO_COMFORT_TEMP));
|
||||
BigDecimal tempEco = (BigDecimal) configurationParameters.getOrDefault(PROPERTY_THERMO_ECO_TEMP,
|
||||
deviceProperties.get(PROPERTY_THERMO_ECO_TEMP));
|
||||
BigDecimal tempSetpointMax = (BigDecimal) configurationParameters.getOrDefault(
|
||||
PROPERTY_THERMO_MAX_TEMP_SETPOINT, deviceProperties.get(PROPERTY_THERMO_MAX_TEMP_SETPOINT));
|
||||
BigDecimal tempSetpointMin = (BigDecimal) configurationParameters.getOrDefault(
|
||||
PROPERTY_THERMO_MIN_TEMP_SETPOINT, deviceProperties.get(PROPERTY_THERMO_MIN_TEMP_SETPOINT));
|
||||
BigDecimal tempOffset = (BigDecimal) configurationParameters.getOrDefault(PROPERTY_THERMO_OFFSET_TEMP,
|
||||
deviceProperties.get(PROPERTY_THERMO_OFFSET_TEMP));
|
||||
BigDecimal tempOpenWindow = (BigDecimal) configurationParameters.getOrDefault(
|
||||
PROPERTY_THERMO_WINDOW_OPEN_TEMP, deviceProperties.get(PROPERTY_THERMO_WINDOW_OPEN_TEMP));
|
||||
BigDecimal durationOpenWindow = (BigDecimal) configurationParameters.getOrDefault(
|
||||
PROPERTY_THERMO_WINDOW_OPEN_DURATION, deviceProperties.get(PROPERTY_THERMO_WINDOW_OPEN_DURATION));
|
||||
SConfigCommand cmd = new SConfigCommand(rfAddress, roomId, tempComfort.doubleValue(), tempEco.doubleValue(),
|
||||
tempSetpointMax.doubleValue(), tempSetpointMin.doubleValue(), tempOffset.doubleValue(),
|
||||
tempOpenWindow.doubleValue(), durationOpenWindow.intValue());
|
||||
bridgeHandler.queueCommand(new SendCommand(maxDeviceSerial, cmd, "Update Thermostat Properties"));
|
||||
sendCCommand();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update by sending C command.
|
||||
* This command is delayed as it takes time to have the updates back from the thermostat
|
||||
*/
|
||||
private void sendCCommand() {
|
||||
scheduler.schedule(() -> {
|
||||
CCommand cmd = new CCommand(rfAddress);
|
||||
bridgeHandler.queueCommand(new SendCommand(maxDeviceSerial, cmd, "Refresh Thermostat Properties"));
|
||||
configSet = false;
|
||||
}, COMMUNICATION_DELAY_TIME, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* sends the T command to the Cube to disassociate the device from the MAX! Cube.
|
||||
*/
|
||||
public void deviceDelete() {
|
||||
MaxCubeBridgeHandler maxCubeBridge = getMaxCubeBridgeHandler();
|
||||
if (maxCubeBridge != null) {
|
||||
maxCubeBridge.sendDeviceDelete(maxDeviceSerial);
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the device & roomname
|
||||
*/
|
||||
private void updateDeviceName(Entry<String, Object> configurationParameter) {
|
||||
try {
|
||||
final Device device = getMaxCubeBridgeHandler().getDevice(maxDeviceSerial);
|
||||
if (device == null) {
|
||||
logger.debug("MAX! Cube LAN gateway bridge handler not found. Cannot handle update without bridge.");
|
||||
return;
|
||||
}
|
||||
switch (configurationParameter.getKey()) {
|
||||
case PROPERTY_DEVICENAME:
|
||||
final String name = configurationParameter.getValue().toString();
|
||||
if (!name.equals(device.getName())) {
|
||||
logger.debug("Updating device name for {} to {}", getThing().getUID(), name);
|
||||
device.setName(name);
|
||||
bridgeHandler.sendDeviceAndRoomNameUpdate(name);
|
||||
bridgeHandler.queueCommand(new SendCommand(maxDeviceSerial, new QCommand(), "Reload Data"));
|
||||
}
|
||||
break;
|
||||
|
||||
case PROPERTY_ROOMID: // fall-through
|
||||
case PROPERTY_ROOMNAME:
|
||||
final int roomId = ((BigDecimal) configurationParameter.getValue()).intValue();
|
||||
if (roomId != device.getRoomId()) {
|
||||
logger.debug("Updating room for {} to {}", getThing().getUID().getAsString(), roomId);
|
||||
device.setRoomId(roomId);
|
||||
// TODO: handle if a room has no more devices, probably should be deleted. Also handle if room
|
||||
// rfId
|
||||
// is no longer valid as the related device is movd to another room
|
||||
bridgeHandler.sendDeviceAndRoomNameUpdate(Integer.toString(roomId));
|
||||
SendCommand sendCommand = new SendCommand(maxDeviceSerial,
|
||||
ZCommand.wakeupDevice(device.getRFAddress()),
|
||||
"WakeUp device" + getThing().getUID().getAsString());
|
||||
bridgeHandler.queueCommand(sendCommand);
|
||||
sendCommand = new SendCommand(maxDeviceSerial,
|
||||
new SConfigCommand(device.getRFAddress(), roomId, ConfigCommandType.SetRoom),
|
||||
"Set Room");
|
||||
bridgeHandler.queueCommand(sendCommand);
|
||||
|
||||
sendCommand = new SendCommand(maxDeviceSerial, new QCommand(), "Reload Data");
|
||||
bridgeHandler.queueCommand(sendCommand);
|
||||
sendCCommand();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized MaxCubeBridgeHandler getMaxCubeBridgeHandler() {
|
||||
if (this.bridgeHandler == null) {
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
logger.debug("Required bridge not defined for device {}.", maxDeviceSerial);
|
||||
return null;
|
||||
}
|
||||
final ThingHandler handler = bridge.getHandler();
|
||||
if (!(handler instanceof MaxCubeBridgeHandler)) {
|
||||
logger.debug("No available bridge handler found for {} bridge {} .", maxDeviceSerial, bridge.getUID());
|
||||
return null;
|
||||
}
|
||||
this.bridgeHandler = (MaxCubeBridgeHandler) handler;
|
||||
this.bridgeHandler.registerDeviceStatusListener(this);
|
||||
}
|
||||
return this.bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
final MaxCubeBridgeHandler maxCubeBridge = getMaxCubeBridgeHandler();
|
||||
if (maxCubeBridge == null) {
|
||||
logger.debug("MAX! Cube LAN gateway bridge handler not found. Cannot handle command without bridge.");
|
||||
return;
|
||||
}
|
||||
if (command instanceof RefreshType) {
|
||||
maxCubeBridge.handleCommand(channelUID, command);
|
||||
return;
|
||||
}
|
||||
if (maxDeviceSerial == null) {
|
||||
logger.warn("Serial number missing. Can't send command to device '{}'", getThing());
|
||||
return;
|
||||
}
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_SETTEMP:
|
||||
if (refreshingActuals) {
|
||||
refreshActualsRestore();
|
||||
}
|
||||
maxCubeBridge.queueCommand(new SendCommand(maxDeviceSerial, channelUID, command));
|
||||
break;
|
||||
case CHANNEL_MODE:
|
||||
if (refreshingActuals) {
|
||||
refreshActualsRestore();
|
||||
}
|
||||
maxCubeBridge.queueCommand(new SendCommand(maxDeviceSerial, channelUID, command));
|
||||
break;
|
||||
default:
|
||||
logger.warn("Setting of channel '{}' not possible, channel is read-only.", channelUID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceStateChanged(ThingUID bridge, Device device) {
|
||||
if (!device.getSerialNumber().equals(maxDeviceSerial)) {
|
||||
return;
|
||||
}
|
||||
if (device.isError() || device.isLinkStatusError()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR);
|
||||
} else if (!refreshingActuals) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Updating Actual Temperature");
|
||||
}
|
||||
if (!propertiesSet) {
|
||||
setProperties(device);
|
||||
}
|
||||
if (!configSet) {
|
||||
setDeviceConfiguration(device);
|
||||
}
|
||||
if (refreshActualRate >= REFRESH_ACTUAL_MIN_RATE && (device.getType() == DeviceType.HeatingThermostat
|
||||
|| device.getType() == DeviceType.HeatingThermostatPlus)) {
|
||||
refreshActualCheck((HeatingThermostat) device);
|
||||
}
|
||||
logger.debug("Updating states of {} {} ({}) id: {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), getThing().getUID());
|
||||
switch (device.getType()) {
|
||||
case WallMountedThermostat: // fall-through
|
||||
case HeatingThermostat: // fall-through
|
||||
case HeatingThermostatPlus:
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_LOCKED),
|
||||
((HeatingThermostat) device).isPanelLocked() ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_SETTEMP),
|
||||
new QuantityType<>(((HeatingThermostat) device).getTemperatureSetpoint(), CELSIUS));
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_MODE),
|
||||
new StringType(((HeatingThermostat) device).getModeString()));
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_BATTERY),
|
||||
((HeatingThermostat) device).getBatteryLow());
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_VALVE),
|
||||
new DecimalType(((HeatingThermostat) device).getValvePosition()));
|
||||
double actualTemp = ((HeatingThermostat) device).getTemperatureActual();
|
||||
if (actualTemp != 0) {
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_ACTUALTEMP),
|
||||
new QuantityType<>(actualTemp, CELSIUS));
|
||||
}
|
||||
break;
|
||||
case ShutterContact:
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_CONTACT_STATE),
|
||||
((ShutterContact) device).getShutterState());
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_BATTERY),
|
||||
((ShutterContact) device).getBatteryLow());
|
||||
break;
|
||||
case EcoSwitch:
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_BATTERY), ((EcoSwitch) device).getBatteryLow());
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unhandled Device {}.", device.getType());
|
||||
break;
|
||||
}
|
||||
device.setUpdated(false);
|
||||
}
|
||||
|
||||
private void refreshActualCheck(HeatingThermostat device) {
|
||||
if (device.getActualTempLastUpdated() == null) {
|
||||
Calendar t = Calendar.getInstance();
|
||||
t.add(Calendar.MINUTE, REFRESH_ACTUAL_MIN_RATE * -1);
|
||||
device.setActualTempLastUpdated(t.getTime());
|
||||
logger.debug("Actual date reset for {} {} ({}) id: {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), getThing().getUID());
|
||||
}
|
||||
long timediff = Calendar.getInstance().getTime().getTime() - device.getActualTempLastUpdated().getTime();
|
||||
if (timediff > ((long) refreshActualRate) * 1000 * 60) {
|
||||
if (!refreshingActuals) {
|
||||
logger.debug("Actual needs updating for {} {} ({}) id: {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), getThing().getUID());
|
||||
|
||||
originalSetTemp = device.getTemperatureSetpoint();
|
||||
originalMode = device.getMode();
|
||||
|
||||
if (originalMode == ThermostatModeType.MANUAL || originalMode == ThermostatModeType.AUTOMATIC) {
|
||||
double tempSetTemp = originalSetTemp + 0.5;
|
||||
logger.debug("Actuals Refresh: Setting Temp {}", tempSetTemp);
|
||||
handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SETTEMP),
|
||||
new QuantityType<>(tempSetTemp, CELSIUS));
|
||||
refreshingActuals = true;
|
||||
} else {
|
||||
logger.debug("Defer Actuals refresh. Only manual refresh for mode AUTOMATIC & MANUAL");
|
||||
device.setActualTempLastUpdated(Calendar.getInstance().getTime());
|
||||
}
|
||||
|
||||
if (refreshingActuals) {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Updating Actual Temperature");
|
||||
|
||||
if (refreshActualsJob == null || refreshActualsJob.isCancelled()) {
|
||||
refreshActualsJob = scheduler.schedule(this::refreshActualsRestore, REFRESH_ACTUAL_DURATION,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
device.setActualTempLastUpdated(Calendar.getInstance().getTime());
|
||||
}
|
||||
}
|
||||
logger.debug("Actual Refresh in progress for {} {} ({}) id: {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), getThing().getUID());
|
||||
} else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
|
||||
logger.trace("Actual date for {} {} ({}) : {}", device.getType(), device.getName(),
|
||||
device.getSerialNumber(), dateFormat.format(device.getActualTempLastUpdated().getTime()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the commands to restore the original settings for mode & temperature
|
||||
* to end the automatic update cycle
|
||||
*/
|
||||
private synchronized void refreshActualsRestore() {
|
||||
try {
|
||||
refreshingActuals = false;
|
||||
if (originalMode == ThermostatModeType.AUTOMATIC || originalMode == ThermostatModeType.MANUAL) {
|
||||
logger.debug("Finished Actuals Refresh: Restoring Temp {}", originalSetTemp);
|
||||
handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SETTEMP),
|
||||
new QuantityType<>(originalSetTemp, CELSIUS));
|
||||
}
|
||||
|
||||
if (refreshActualsJob != null && !refreshActualsJob.isCancelled()) {
|
||||
refreshActualsJob.cancel(true);
|
||||
refreshActualsJob = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during Actuals Refresh : {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(MaxCubeBridgeHandler bridge, Device device) {
|
||||
if (device.getSerialNumber().equals(maxDeviceSerial)) {
|
||||
bridgeHandler.unregisterDeviceStatusListener(this);
|
||||
bridgeHandler = null;
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceAdded(Bridge bridge, Device device) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the properties for this device
|
||||
*/
|
||||
private void setProperties(Device device) {
|
||||
try {
|
||||
logger.debug("MAX! {} {} properties update", device.getType(), device.getSerialNumber());
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, device.getType().toString());
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getSerialNumber());
|
||||
properties.put(Thing.PROPERTY_VENDOR, PROPERTY_VENDOR_NAME);
|
||||
updateProperties(properties);
|
||||
logger.debug("properties updated");
|
||||
propertiesSet = true;
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during property edit: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("Bridge Status updated to {} for device: {}", bridgeStatusInfo.getStatus(), getThing().getUID());
|
||||
if (!bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Configurable properties for this device
|
||||
*/
|
||||
private void setDeviceConfiguration(Device device) {
|
||||
try {
|
||||
boolean config_changed = false;
|
||||
logger.debug("MAX! {} {} configuration update", device.getType(), device.getSerialNumber());
|
||||
Configuration configuration = editConfiguration();
|
||||
if (!device.getRoomName().equalsIgnoreCase((String) getConfig().get(PROPERTY_ROOMNAME))) {
|
||||
configuration.put(PROPERTY_ROOMNAME, device.getRoomName());
|
||||
config_changed = true;
|
||||
}
|
||||
if (getConfig().get(PROPERTY_ROOMID) == null || !(new BigDecimal(device.getRoomId())
|
||||
.compareTo((BigDecimal) getConfig().get(PROPERTY_ROOMID)) == 0)) {
|
||||
configuration.put(PROPERTY_ROOMID, new BigDecimal(device.getRoomId()));
|
||||
config_changed = true;
|
||||
}
|
||||
if (!device.getName().equalsIgnoreCase((String) getConfig().get(PROPERTY_DEVICENAME))) {
|
||||
configuration.put(PROPERTY_DEVICENAME, device.getName());
|
||||
config_changed = true;
|
||||
}
|
||||
if (!device.getRFAddress().equalsIgnoreCase((String) getConfig().get(PROPERTY_RFADDRESS))) {
|
||||
configuration.put(PROPERTY_RFADDRESS, device.getRFAddress());
|
||||
config_changed = true;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : device.getProperties().entrySet()) {
|
||||
configuration.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (config_changed) {
|
||||
updateConfiguration(configuration);
|
||||
logger.debug("Config updated: {}", configuration.getProperties());
|
||||
} else {
|
||||
logger.debug("MAX! {} {} no updated required.", device.getType(), device.getSerialNumber());
|
||||
}
|
||||
configSet = true;
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during configuration edit: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceConfigUpdate(Bridge bridge, Device device) {
|
||||
if (device.getSerialNumber().equals(maxDeviceSerial)) {
|
||||
setDeviceConfiguration(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.command.CubeCommand;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* Class for sending a command.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class SendCommand {
|
||||
|
||||
private int id;
|
||||
private static int commandId = -1;
|
||||
|
||||
private @Nullable ChannelUID channelUID;
|
||||
private @Nullable Command command;
|
||||
private @Nullable CubeCommand cubeCommand;
|
||||
private String serialNumber;
|
||||
private String key;
|
||||
private String commandText;
|
||||
|
||||
public SendCommand(String serialNumber, ChannelUID channelUID, Command command) {
|
||||
commandId++;
|
||||
id = commandId;
|
||||
this.serialNumber = serialNumber;
|
||||
this.channelUID = channelUID;
|
||||
this.command = command;
|
||||
key = getKey(serialNumber, channelUID);
|
||||
this.commandText = command.toString();
|
||||
}
|
||||
|
||||
public SendCommand(String serialNumber, CubeCommand cubeCommand, String commandText) {
|
||||
commandId++;
|
||||
id = commandId;
|
||||
this.serialNumber = serialNumber;
|
||||
this.cubeCommand = cubeCommand;
|
||||
key = getKey(serialNumber, cubeCommand);
|
||||
this.commandText = commandText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key based on the serial and channel
|
||||
* This is can be used to find duplicated commands in the queue
|
||||
*/
|
||||
private static String getKey(String serialNumber, ChannelUID channelUID) {
|
||||
return serialNumber + "-" + channelUID.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key based on the serial and channel
|
||||
* This is can be used to find duplicated commands in the queue
|
||||
*/
|
||||
private static String getKey(String serialNumber, CubeCommand cubeCommand) {
|
||||
String key = serialNumber + "-" + cubeCommand.getClass().getSimpleName();
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the key based on the serial and channel
|
||||
* This is can be used to find duplicated commands in the queue
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public @Nullable ChannelUID getChannelUID() {
|
||||
return channelUID;
|
||||
}
|
||||
|
||||
public void setChannelUID(ChannelUID channelUID) {
|
||||
this.channelUID = channelUID;
|
||||
key = getKey(serialNumber, channelUID);
|
||||
}
|
||||
|
||||
public @Nullable Command getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public void setCommand(Command command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public @Nullable CubeCommand getCubeCommand() {
|
||||
return cubeCommand;
|
||||
}
|
||||
|
||||
public String getDeviceSerial() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public void setDeviceSerial(String device) {
|
||||
this.serialNumber = device;
|
||||
final ChannelUID channelUID = this.channelUID;
|
||||
if (channelUID != null) {
|
||||
key = getKey(serialNumber, channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
public String getCommandText() {
|
||||
return commandText;
|
||||
}
|
||||
|
||||
public void setCommandText(String commandText) {
|
||||
this.commandText = commandText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
return sb.append("id: ").append(id).append(", channelUID: ").append(channelUID).append(", command: ")
|
||||
.append(command).append(", cubeCommand: ").append(cubeCommand).append(", serialNumber: ")
|
||||
.append(serialNumber).append(", key: ").append(key).append(", commandText: ").append(commandText)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The {@link AMessage} Acknowledge the execution of a command
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class AMessage extends Message {
|
||||
|
||||
public AMessage(String raw) {
|
||||
super(raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.trace("=== A Message === ");
|
||||
logger.trace("\tRAW : {}", this.getPayload());
|
||||
logger.debug("Cube Command Acknowledged");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.A;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.openhab.binding.max.internal.MaxBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link CMessage} contains configuration about a MAX! device.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - Detailed parsing, OH2 Update
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class CMessage extends Message {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CMessage.class);
|
||||
|
||||
private String rfAddress;
|
||||
private int length;
|
||||
private DeviceType deviceType;
|
||||
private int roomId = -1;
|
||||
private String serialNumber;
|
||||
private BigDecimal tempComfort = BigDecimal.ZERO;
|
||||
private BigDecimal tempEco = BigDecimal.ZERO;
|
||||
private BigDecimal tempSetpointMax = BigDecimal.ZERO;
|
||||
private BigDecimal tempSetpointMin = BigDecimal.ZERO;
|
||||
private BigDecimal tempOffset = BigDecimal.ZERO;
|
||||
private BigDecimal tempOpenWindow = BigDecimal.ZERO;
|
||||
private BigDecimal durationOpenWindow = BigDecimal.ZERO;
|
||||
private BigDecimal decalcification = BigDecimal.ZERO;
|
||||
private BigDecimal valveMaximum = BigDecimal.ZERO;
|
||||
private BigDecimal valveOffset = BigDecimal.ZERO;
|
||||
private BigDecimal boostDuration = BigDecimal.ZERO;
|
||||
private BigDecimal boostValve = BigDecimal.ZERO;
|
||||
private String programData = "";
|
||||
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
public CMessage(String raw) {
|
||||
super(raw);
|
||||
String[] tokens = this.getPayload().split(Message.DELIMETER);
|
||||
|
||||
rfAddress = tokens[0];
|
||||
|
||||
byte[] bytes = Base64.getDecoder().decode(tokens[1].getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
int[] data = new int[bytes.length];
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
data[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
|
||||
length = data[0];
|
||||
if (length != data.length - 1) {
|
||||
logger.debug("C Message malformed: wrong data length. Expected bytes {}, actual bytes {}", length,
|
||||
data.length - 1);
|
||||
}
|
||||
|
||||
String rfAddress2 = Utils.toHex(data[1], data[2], data[3]);
|
||||
if (!rfAddress.toUpperCase().equals(rfAddress2.toUpperCase())) {
|
||||
logger.debug("C Message malformed: wrong RF address. Expected address {}, actual address {}",
|
||||
rfAddress.toUpperCase(), rfAddress2.toUpperCase());
|
||||
}
|
||||
|
||||
deviceType = DeviceType.create(data[4]);
|
||||
roomId = data[5] & 0xFF;
|
||||
|
||||
serialNumber = getSerialNumber(bytes);
|
||||
if (deviceType == DeviceType.HeatingThermostatPlus || deviceType == DeviceType.HeatingThermostat
|
||||
|| deviceType == DeviceType.WallMountedThermostat) {
|
||||
parseHeatingThermostatData(bytes);
|
||||
}
|
||||
|
||||
if (deviceType == DeviceType.Cube) {
|
||||
parseCubeData(bytes);
|
||||
}
|
||||
if (deviceType == DeviceType.EcoSwitch || deviceType == DeviceType.ShutterContact) {
|
||||
logger.trace("Device {} type {} Data: '{}'", rfAddress, deviceType, parseData(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
private String getSerialNumber(byte[] bytes) {
|
||||
byte[] sn = new byte[10];
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
sn[i] = bytes[i + 8];
|
||||
}
|
||||
|
||||
return new String(sn, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String parseData(byte[] bytes) {
|
||||
if (bytes.length <= 18) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
int dataStart = 18;
|
||||
byte[] sn = new byte[bytes.length - dataStart];
|
||||
|
||||
for (int i = 0; i < sn.length; i++) {
|
||||
sn[i] = bytes[i + dataStart];
|
||||
}
|
||||
logger.trace("DataBytes: {}", Utils.getHex(sn));
|
||||
return new String(sn, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during parsing: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void parseCubeData(byte[] bytes) {
|
||||
if (bytes.length != 238) {
|
||||
logger.debug("Unexpected lenght for Cube message {}, expected: 238", bytes.length);
|
||||
}
|
||||
try {
|
||||
properties.put("Portal Enabled", Integer.toString(bytes[0x18] & 0xFF));
|
||||
properties.put("Portal URL",
|
||||
new String(Arrays.copyOfRange(bytes, 0x55, 0xD5), StandardCharsets.UTF_8).trim());
|
||||
properties.put("TimeZone (Winter)",
|
||||
new String(Arrays.copyOfRange(bytes, 0xD6, 0xDA), StandardCharsets.UTF_8).trim());
|
||||
properties.put("TimeZone (Daylight)",
|
||||
new String(Arrays.copyOfRange(bytes, 0x00E2, 0x00E6), StandardCharsets.UTF_8).trim());
|
||||
|
||||
properties.put("Unknown1", Utils.getHex(Arrays.copyOfRange(bytes, 0x13, 0x33))); // Pushbutton Up config
|
||||
// 0=auto, 1=eco, 2=comfort
|
||||
properties.put("Unknown2", Utils.getHex(Arrays.copyOfRange(bytes, 0x34, 0x54))); // Pushbutton down config
|
||||
// 0=auto, 1=eco, 2=comfort
|
||||
properties.put("Winter Time", parseTimeInfo(Arrays.copyOfRange(bytes, 0xDB, 0xE2), "Winter").toString()); // Date
|
||||
// of
|
||||
// wintertime
|
||||
properties.put("Summer Time", parseTimeInfo(Arrays.copyOfRange(bytes, 0xE7, 0xEF), "Summer").toString()); // Date
|
||||
// of
|
||||
// summertime
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during parsing: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Date parseTimeInfo(byte[] bytes, String suffix) {
|
||||
int month = bytes[0] & 0xFF;
|
||||
int weekDay = bytes[1] & 0xFF;
|
||||
int hour = bytes[2] & 0xFF;
|
||||
int utcOffset = new BigInteger(Arrays.copyOfRange(bytes, 0x03, 0x07)).intValue();
|
||||
properties.put("Utc Offset" + " (" + suffix + ")", utcOffset);
|
||||
Calendar pCal = Calendar.getInstance();
|
||||
pCal.set(Calendar.getInstance().get(Calendar.YEAR), month - 1, 15, hour, 0, 0);
|
||||
pCal.set(Calendar.DAY_OF_WEEK, weekDay + 1);
|
||||
pCal.set(Calendar.DAY_OF_WEEK_IN_MONTH, -1);
|
||||
return pCal.getTime();
|
||||
}
|
||||
|
||||
private void parseHeatingThermostatData(byte[] bytes) {
|
||||
try {
|
||||
int plusDataStart = 18;
|
||||
int programDataStart = 11;
|
||||
tempComfort = new BigDecimal((bytes[plusDataStart] & 0xFF) / 2D);
|
||||
tempEco = new BigDecimal((bytes[plusDataStart + 1] & 0xFF) / 2D);
|
||||
tempSetpointMax = new BigDecimal((bytes[plusDataStart + 2] & 0xFF) / 2D);
|
||||
tempSetpointMin = new BigDecimal((bytes[plusDataStart + 3] & 0xFF) / 2D);
|
||||
properties.put(PROPERTY_THERMO_COMFORT_TEMP, tempComfort.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_ECO_TEMP, tempEco.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_MAX_TEMP_SETPOINT, tempSetpointMax.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_MIN_TEMP_SETPOINT, tempSetpointMin.setScale(1, RoundingMode.HALF_DOWN));
|
||||
if (bytes.length < 211) {
|
||||
// Device is a WallMountedThermostat
|
||||
programDataStart = 4;
|
||||
logger.trace("WallThermostat byte {}: {}", bytes.length - 3,
|
||||
Float.toString(bytes[bytes.length - 3] & 0xFF));
|
||||
logger.trace("WallThermostat byte {}: {}", bytes.length - 2,
|
||||
Float.toString(bytes[bytes.length - 2] & 0xFF));
|
||||
logger.trace("WallThermostat byte {}: {}", bytes.length - 1,
|
||||
Float.toString(bytes[bytes.length - 1] & 0xFF));
|
||||
} else {
|
||||
// Device is a HeatingThermostat(+)
|
||||
tempOffset = new BigDecimal((bytes[plusDataStart + 4] & 0xFF) / 2D - 3.5);
|
||||
tempOpenWindow = new BigDecimal((bytes[plusDataStart + 5] & 0xFF) / 2D);
|
||||
durationOpenWindow = new BigDecimal((bytes[plusDataStart + 6] & 0xFF) * 5);
|
||||
boostDuration = new BigDecimal(bytes[plusDataStart + 7] & 0xFF >> 5);
|
||||
boostValve = new BigDecimal((bytes[plusDataStart + 7] & 0x1F) * 5);
|
||||
decalcification = new BigDecimal(bytes[plusDataStart + 8]);
|
||||
valveMaximum = new BigDecimal((bytes[plusDataStart + 9] & 0xFF) * 100 / 255);
|
||||
valveOffset = new BigDecimal((bytes[plusDataStart + 10] & 0xFF) * 100 / 255);
|
||||
properties.put(PROPERTY_THERMO_OFFSET_TEMP, tempOffset.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_WINDOW_OPEN_TEMP, tempOpenWindow.setScale(1, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_WINDOW_OPEN_DURATION,
|
||||
durationOpenWindow.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_BOOST_DURATION, boostDuration.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_BOOST_VALVEPOS, boostValve.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_DECALCIFICATION, decalcification.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_VALVE_MAX, valveMaximum.setScale(0, RoundingMode.HALF_DOWN));
|
||||
properties.put(PROPERTY_THERMO_VALVE_OFFSET, valveOffset.setScale(0, RoundingMode.HALF_DOWN));
|
||||
}
|
||||
programData = "";
|
||||
int ln = 13 * 6; // first day = Sat
|
||||
String startTime = "00:00h";
|
||||
for (int charIdx = plusDataStart + programDataStart; charIdx < (plusDataStart + programDataStart
|
||||
+ 26 * 7); charIdx++) {
|
||||
if (ln % 13 == 0) {
|
||||
programData += "\r\n Day " + Integer.toString((ln / 13) % 7) + ": ";
|
||||
startTime = "00:00h";
|
||||
}
|
||||
int progTime = (bytes[charIdx + 1] & 0xFF) * 5 + (bytes[charIdx] & 0x01) * 1280;
|
||||
int progMinutes = progTime % 60;
|
||||
int progHours = (progTime - progMinutes) / 60;
|
||||
String endTime = Integer.toString(progHours) + ":" + String.format("%02d", progMinutes) + "h";
|
||||
programData += startTime + "-" + endTime + " " + Double.toString(bytes[charIdx] / 4) + "C ";
|
||||
startTime = endTime;
|
||||
charIdx++;
|
||||
ln++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during heater data: {}", e.getMessage(), e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.C;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public DeviceType getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public int getRoomID() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.debug("=== C Message === ");
|
||||
logger.trace("\tRAW: {}", this.getPayload());
|
||||
logger.debug("DeviceType: {}", deviceType);
|
||||
logger.debug("SerialNumber: {}", serialNumber);
|
||||
logger.debug("RFAddress: {}", rfAddress);
|
||||
logger.debug("RoomID: {}", roomId);
|
||||
for (String key : properties.keySet()) {
|
||||
if (!key.startsWith("Unknown")) {
|
||||
String propertyName = StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(key), ' ');
|
||||
logger.debug("{}: {}", propertyName, properties.get(key));
|
||||
} else {
|
||||
logger.debug("{}: {}", key, properties.get(key));
|
||||
}
|
||||
}
|
||||
if (programData != null) {
|
||||
logger.trace("ProgramData: {}", programData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The {@link: FMessage} contains information about the Cube NTP Configuration
|
||||
* This is the response to a f: command
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class FMessage extends Message {
|
||||
|
||||
private String ntpServer1 = "";
|
||||
private String ntpServer2 = "";
|
||||
|
||||
/**
|
||||
* The {@link: FMessage} contains information about the Cube NTP Configuration
|
||||
*
|
||||
* @param raw String with raw message
|
||||
*/
|
||||
public FMessage(String raw) {
|
||||
super(raw);
|
||||
|
||||
final String[] servers = this.getPayload().split(",");
|
||||
if (servers.length > 0) {
|
||||
ntpServer1 = servers[0];
|
||||
}
|
||||
if (servers.length > 1) {
|
||||
ntpServer2 = servers[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the NTP Server1 name
|
||||
*/
|
||||
public String getNtpServer1() {
|
||||
return ntpServer1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the NTP Server2 name
|
||||
*/
|
||||
public String getNtpServer2() {
|
||||
return ntpServer2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.debug("=== F Message === ");
|
||||
logger.trace("\tRAW : {}", this.getPayload());
|
||||
logger.debug("\tNTP Server1 : {}", this.ntpServer1);
|
||||
logger.debug("\tNTP Server2 : {}", this.ntpServer2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.F;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The H message contains information about the MAX! Cube.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - Details parsing, OH2 version
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class HMessage extends Message {
|
||||
|
||||
private ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.systemDefault());
|
||||
public Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
private String rawSerialNumber;
|
||||
private String rawRFAddress;
|
||||
private String rawFirmwareVersion;
|
||||
private String rawConnectionId;
|
||||
private String rawDutyCycle;
|
||||
private String rawFreeMemorySlots;
|
||||
private String rawCubeTimeState;
|
||||
private String rawNTPCounter;
|
||||
|
||||
// yet unknown fields
|
||||
private String rawUnknownfield4;
|
||||
|
||||
public HMessage(String raw) {
|
||||
super(raw);
|
||||
|
||||
String[] tokens = this.getPayload().split(Message.DELIMETER);
|
||||
|
||||
if (tokens.length < 11) {
|
||||
throw new ArrayIndexOutOfBoundsException("MAX!Cube raw H Message corrupt");
|
||||
}
|
||||
|
||||
rawSerialNumber = tokens[0];
|
||||
rawRFAddress = tokens[1];
|
||||
rawFirmwareVersion = tokens[2].substring(0, 2) + "." + tokens[2].substring(2, 4);
|
||||
rawUnknownfield4 = tokens[3];
|
||||
rawConnectionId = tokens[4];
|
||||
rawDutyCycle = Integer.toString(Utils.fromHex(tokens[5]));
|
||||
rawFreeMemorySlots = Integer.toString(Utils.fromHex(tokens[6]));
|
||||
|
||||
setDateTime(tokens[7], tokens[8]);
|
||||
|
||||
rawCubeTimeState = tokens[9];
|
||||
rawNTPCounter = Integer.toString(Utils.fromHex(tokens[10]));
|
||||
properties.put("Serial number", rawSerialNumber);
|
||||
properties.put("RF address (HEX)", rawRFAddress);
|
||||
properties.put("Firmware version", rawFirmwareVersion);
|
||||
properties.put("Connection ID", rawConnectionId);
|
||||
properties.put("Unknown", rawUnknownfield4);
|
||||
properties.put("Duty Cycle", rawDutyCycle);
|
||||
properties.put("FreeMemorySlots", rawFreeMemorySlots);
|
||||
properties.put("CubeTimeState", rawCubeTimeState);
|
||||
properties.put("NTPCounter", rawNTPCounter);
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return rawSerialNumber;
|
||||
}
|
||||
|
||||
public String getRFAddress() {
|
||||
return rawRFAddress;
|
||||
}
|
||||
|
||||
public String getFirmwareVersion() {
|
||||
return rawFirmwareVersion;
|
||||
}
|
||||
|
||||
public String getConnectionId() {
|
||||
return rawConnectionId;
|
||||
}
|
||||
|
||||
public int getDutyCycle() {
|
||||
return Integer.parseInt(rawDutyCycle);
|
||||
}
|
||||
|
||||
public int getFreeMemorySlots() {
|
||||
return Integer.parseInt(rawFreeMemorySlots);
|
||||
}
|
||||
|
||||
public String getCubeTimeState() {
|
||||
return rawCubeTimeState;
|
||||
}
|
||||
|
||||
public String getNTPCounter() {
|
||||
return rawNTPCounter;
|
||||
}
|
||||
|
||||
private final void setDateTime(String hexDate, String hexTime) {
|
||||
// we have to add 2000, otherwise we get a wrong timestamp
|
||||
int year = 2000 + Utils.fromHex(hexDate.substring(0, 2));
|
||||
int month = Utils.fromHex(hexDate.substring(2, 4));
|
||||
int dayOfMonth = Utils.fromHex(hexDate.substring(4, 6));
|
||||
|
||||
int hours = Utils.fromHex(hexTime.substring(0, 2));
|
||||
int minutes = Utils.fromHex(hexTime.substring(2, 4));
|
||||
|
||||
zonedDateTime = ZonedDateTime.of(year, month, dayOfMonth, hours, minutes, 0, 0, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public Date getDateTime() {
|
||||
return Date.from(zonedDateTime.toInstant());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.debug("=== H Message === ");
|
||||
logger.trace("\tRAW: : {}", getPayload());
|
||||
logger.trace("\tReading Time : {}", getDateTime());
|
||||
for (String key : properties.keySet()) {
|
||||
logger.debug("\t{}: {}", key, properties.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.H;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.DeviceConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The L message contains real time information about all MAX! devices.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 update
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class LMessage extends Message {
|
||||
|
||||
public LMessage(String raw) {
|
||||
super(raw);
|
||||
}
|
||||
|
||||
public Collection<? extends Device> getDevices(List<DeviceConfiguration> configurations) {
|
||||
final List<Device> devices = new ArrayList<>();
|
||||
|
||||
final byte[] decodedRawMessage = Base64.getDecoder()
|
||||
.decode(getPayload().trim().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
final MaxTokenizer tokenizer = new MaxTokenizer(decodedRawMessage);
|
||||
|
||||
while (tokenizer.hasMoreElements()) {
|
||||
byte[] token = tokenizer.nextElement();
|
||||
final Device tempDevice = Device.create(token, configurations);
|
||||
if (tempDevice != null) {
|
||||
devices.add(tempDevice);
|
||||
}
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
public Collection<? extends Device> updateDevices(List<Device> devices, List<DeviceConfiguration> configurations) {
|
||||
byte[] decodedRawMessage = Base64.getDecoder().decode(getPayload().trim().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
MaxTokenizer tokenizer = new MaxTokenizer(decodedRawMessage);
|
||||
|
||||
while (tokenizer.hasMoreElements()) {
|
||||
byte[] token = tokenizer.nextElement();
|
||||
String rfAddress = Utils.toHex(token[0] & 0xFF, token[1] & 0xFF, token[2] & 0xFF);
|
||||
// logger.debug("token: "+token+" rfaddress: "+rfAddress);
|
||||
|
||||
Device foundDevice = null;
|
||||
for (Device device : devices) {
|
||||
// logger.debug(device.getRFAddress().toUpperCase()+ " vs "+rfAddress);
|
||||
if (device.getRFAddress().toUpperCase().equals(rfAddress)) {
|
||||
// logger.debug("Updating device..."+rfAddress);
|
||||
foundDevice = device;
|
||||
}
|
||||
}
|
||||
if (foundDevice != null) {
|
||||
foundDevice = Device.update(token, configurations, foundDevice);
|
||||
// devices.remove(token);
|
||||
// devices.add(foundDevice);
|
||||
} else {
|
||||
Device tempDevice = Device.create(token, configurations);
|
||||
if (tempDevice != null) {
|
||||
devices.add(tempDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.trace("=== L Message === ");
|
||||
logger.trace("\tRAW: {}", this.getPayload());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.L;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.DeviceInformation;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.openhab.binding.max.internal.device.RoomInformation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The M message contains metadata about the MAX! Cube setup.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial Contribution
|
||||
* @author Marcel Verpaalen - Room details parse
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class MMessage extends Message {
|
||||
private final Logger logger = LoggerFactory.getLogger(MMessage.class);
|
||||
|
||||
public List<RoomInformation> rooms = new ArrayList<>();
|
||||
public List<DeviceInformation> devices = new ArrayList<>();
|
||||
private Boolean hasConfiguration;
|
||||
|
||||
public MMessage(String raw) {
|
||||
super(raw);
|
||||
hasConfiguration = false;
|
||||
|
||||
String[] tokens = this.getPayload().split(Message.DELIMETER);
|
||||
|
||||
if (tokens.length <= 1) {
|
||||
logger.debug("No rooms defined. Configure your Max! Cube");
|
||||
hasConfiguration = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
byte[] bytes = Base64.getDecoder().decode(tokens[2].trim().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
hasConfiguration = true;
|
||||
logger.trace("*** M Message trace**** ");
|
||||
logger.trace("\tMagic? (expect 86) : {}", (int) bytes[0]);
|
||||
logger.trace("\tVersion? (expect 2): {}", (int) bytes[1]);
|
||||
logger.trace("\t#defined rooms in M: {}", (int) bytes[2]);
|
||||
|
||||
rooms = new ArrayList<>();
|
||||
devices = new ArrayList<>();
|
||||
|
||||
int roomCount = bytes[2];
|
||||
|
||||
int byteOffset = 3; // start of rooms
|
||||
|
||||
/* process room */
|
||||
|
||||
for (int i = 0; i < roomCount; i++) {
|
||||
|
||||
int position = bytes[byteOffset++];
|
||||
|
||||
int nameLength = bytes[byteOffset++] & 0xff;
|
||||
byte[] data = new byte[nameLength];
|
||||
System.arraycopy(bytes, byteOffset, data, 0, nameLength);
|
||||
byteOffset += nameLength;
|
||||
String name = new String(data, StandardCharsets.UTF_8);
|
||||
|
||||
String rfAddress = Utils.toHex((bytes[byteOffset] & 0xff), (bytes[byteOffset + 1] & 0xff),
|
||||
(bytes[byteOffset + 2] & 0xff));
|
||||
byteOffset += 3;
|
||||
|
||||
rooms.add(new RoomInformation(position, name, rfAddress));
|
||||
}
|
||||
|
||||
/* process devices */
|
||||
|
||||
int deviceCount = bytes[byteOffset++];
|
||||
|
||||
for (int deviceId = 0; deviceId < deviceCount; deviceId++) {
|
||||
DeviceType deviceType = DeviceType.create(bytes[byteOffset++]);
|
||||
|
||||
String rfAddress = Utils.toHex((bytes[byteOffset] & 0xff), (bytes[byteOffset + 1] & 0xff),
|
||||
(bytes[byteOffset + 2] & 0xff));
|
||||
byteOffset += 3;
|
||||
|
||||
final StringBuilder serialNumberBuilder = new StringBuilder(10);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
serialNumberBuilder.append((char) bytes[byteOffset++]);
|
||||
}
|
||||
|
||||
int nameLength = bytes[byteOffset++] & 0xff;
|
||||
byte[] data = new byte[nameLength];
|
||||
System.arraycopy(bytes, byteOffset, data, 0, nameLength);
|
||||
byteOffset += nameLength;
|
||||
String deviceName = new String(data, StandardCharsets.UTF_8);
|
||||
|
||||
int roomId = bytes[byteOffset++] & 0xff;
|
||||
devices.add(new DeviceInformation(deviceType, serialNumberBuilder.toString(), rfAddress, deviceName,
|
||||
roomId));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Unknown error parsing the M Message: {}", e.getMessage(), e);
|
||||
logger.debug("\tRAW : {}", this.getPayload());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.debug("=== M Message === ");
|
||||
if (hasConfiguration) {
|
||||
logger.trace("\tRAW : {}", this.getPayload());
|
||||
for (RoomInformation room : rooms) {
|
||||
logger.debug("\t=== Rooms ===");
|
||||
logger.debug("\tRoom Pos : {}", room.getPosition());
|
||||
logger.debug("\tRoom Name : {}", room.getName());
|
||||
logger.debug("\tRoom RF Adr: {}", room.getRFAddress());
|
||||
for (DeviceInformation device : devices) {
|
||||
if (room.getPosition() == device.getRoomId()) {
|
||||
logger.debug("\t=== Devices ===");
|
||||
logger.debug("\tDevice Type : {}", device.getDeviceType());
|
||||
logger.debug("\tDevice Name : {}", device.getName());
|
||||
logger.debug("\tDevice Serialnr: {}", device.getSerialNumber());
|
||||
logger.debug("\tDevice RF Adr : {}", device.getRFAddress());
|
||||
logger.debug("\tRoom Id : {}", device.getRoomId());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("M Message empty. No Configuration");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.M;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import java.util.Enumeration;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The MaxTokenizer parses a L message into the MAX! devices encoded within. The L message contains
|
||||
* real time information for multiple devices. Each device starts with the length n bytes.
|
||||
* The MaxTokenzier starts with the first device and chops off one device after another from the byte stream.
|
||||
*
|
||||
* The tokens returned consist of the payload solely, and do not contain the first byte holding the
|
||||
* tokens length.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class MaxTokenizer implements Enumeration<byte[]> {
|
||||
|
||||
private int offset;
|
||||
|
||||
private byte[] decodedRawMessage;
|
||||
|
||||
/**
|
||||
* Creates a new MaxTokenizer.
|
||||
*
|
||||
* @param decodedRawMessage
|
||||
* The Base64 decoded MAX! Cube protocol L message as byte array
|
||||
*/
|
||||
public MaxTokenizer(byte[] decodedRawMessage) {
|
||||
this.decodedRawMessage = decodedRawMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return offset < decodedRawMessage.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] nextElement() {
|
||||
byte length = decodedRawMessage[offset++];
|
||||
|
||||
// make sure to get the correct length in case > 127
|
||||
byte[] token = new byte[length & 0xFF];
|
||||
|
||||
for (int i = 0; i < (length & 0xFF); i++) {
|
||||
token[i] = decodedRawMessage[offset++];
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Base message for the messages received from the MAX! Cube.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class Message {
|
||||
|
||||
public static final String DELIMETER = ",";
|
||||
|
||||
private final String raw;
|
||||
|
||||
public Message(String raw) {
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
public abstract void debug(Logger logger);
|
||||
|
||||
public abstract MessageType getType();
|
||||
|
||||
protected final String getPayload() {
|
||||
return raw.substring(2, raw.length());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.exceptions.IncompleteMessageException;
|
||||
import org.openhab.binding.max.internal.exceptions.IncorrectMultilineIndexException;
|
||||
import org.openhab.binding.max.internal.exceptions.MessageIsWaitingException;
|
||||
import org.openhab.binding.max.internal.exceptions.NoMessageAvailableException;
|
||||
import org.openhab.binding.max.internal.exceptions.UnprocessableMessageException;
|
||||
import org.openhab.binding.max.internal.exceptions.UnsupportedMessageTypeException;
|
||||
|
||||
/**
|
||||
* The message processor was introduced to combine multiple received lines to
|
||||
* one single message. There are cases, when the MAX! Cube sends multiple
|
||||
* messages (M-Message for example). The message processor acts as stack for
|
||||
* received messages. Every received line should be added to the processor.
|
||||
* After every added line, the message processor analyses the line. It is not
|
||||
* possible to add additional lines when there is a message ready to be
|
||||
* processed.
|
||||
*
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageProcessor {
|
||||
|
||||
public static final String SEPARATOR = ":";
|
||||
|
||||
/**
|
||||
* The message that was created from last line received. (Null if no message
|
||||
* available yet)
|
||||
*/
|
||||
|
||||
private @Nullable Message currentMessage;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* If more that one single line is required to create a message
|
||||
* numberOfRequiredLines holds the number of required messages to complete
|
||||
* receivedLines holds the lines received so far
|
||||
* currentMessageType indicates which message type is currently on stack
|
||||
* </pre>
|
||||
*/
|
||||
private @Nullable Integer numberOfRequiredLines;
|
||||
private List<String> receivedLines = new ArrayList<>();
|
||||
private @Nullable MessageType currentMessageType;
|
||||
|
||||
/**
|
||||
* Resets the current status and processed lines. Should be used after
|
||||
* processing a message
|
||||
*/
|
||||
public void reset() {
|
||||
this.currentMessage = null;
|
||||
receivedLines.clear();
|
||||
currentMessageType = null;
|
||||
numberOfRequiredLines = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyses the line and creates a message when possible. If the line
|
||||
* indicates, that additional lines are required to create a complete
|
||||
* message, the message processor keeps the line in memory and awaits
|
||||
* additional lines. If the new line does not fit into current state
|
||||
* (incomplete M: message on stack but L: message line received) a
|
||||
* IncompleteMessageException is thrown.
|
||||
*
|
||||
* @param line
|
||||
* is the new line received
|
||||
* @return true if a message could be created by this line, false in any
|
||||
* other cases (line was stacked, error, ...)
|
||||
* @throws MessageIsWaitingException
|
||||
* when a line was added without pulling the previous message
|
||||
* @throws IncompleteMessageException
|
||||
* when a line was added that does not belong to current message
|
||||
* stack
|
||||
* @throws UnsupportedMessageTypeException
|
||||
* in case the line starts with an unknown message indicator
|
||||
* @throws UnprocessableMessageException
|
||||
* is thrown when there was a known message indicator found, but
|
||||
* message could not be parsed correctly.
|
||||
* @throws IncorrectMultilineIndexException
|
||||
*/
|
||||
public Boolean addReceivedLine(String line) throws IncompleteMessageException, MessageIsWaitingException,
|
||||
UnsupportedMessageTypeException, UnprocessableMessageException, IncorrectMultilineIndexException {
|
||||
if (this.currentMessage != null) {
|
||||
throw new MessageIsWaitingException();
|
||||
}
|
||||
|
||||
MessageType messageType = getMessageType(line);
|
||||
|
||||
if (messageType == null) {
|
||||
throw new UnsupportedMessageTypeException();
|
||||
}
|
||||
|
||||
if ((this.currentMessageType != null) && (!messageType.equals(this.currentMessageType))) {
|
||||
throw new IncompleteMessageException();
|
||||
}
|
||||
|
||||
Boolean result = true;
|
||||
|
||||
switch (messageType) {
|
||||
case H:
|
||||
this.currentMessage = new HMessage(line);
|
||||
break;
|
||||
case C:
|
||||
this.currentMessage = new CMessage(line);
|
||||
break;
|
||||
case L:
|
||||
this.currentMessage = new LMessage(line);
|
||||
break;
|
||||
case S:
|
||||
this.currentMessage = new SMessage(line);
|
||||
break;
|
||||
case M:
|
||||
result = handleMMessageLine(line);
|
||||
break;
|
||||
case N:
|
||||
this.currentMessage = new NMessage(line);
|
||||
break;
|
||||
case F:
|
||||
this.currentMessage = new FMessage(line);
|
||||
break;
|
||||
case A:
|
||||
this.currentMessage = new AMessage(line);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Boolean handleMMessageLine(String line)
|
||||
throws UnprocessableMessageException, IncompleteMessageException, IncorrectMultilineIndexException {
|
||||
Boolean result = false;
|
||||
|
||||
String[] tokens = line.split(Message.DELIMETER); // M:00,01,xyz.....
|
||||
|
||||
try {
|
||||
Integer index = Integer.valueOf(tokens[0].replaceFirst("M:", "")); // M:00
|
||||
Integer counter = Integer.valueOf(tokens[1]); // 01
|
||||
|
||||
if (this.numberOfRequiredLines == null) {
|
||||
switch (counter) {
|
||||
case 0:
|
||||
throw new UnprocessableMessageException();
|
||||
case 1:
|
||||
this.currentMessage = new MMessage(line);
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
this.numberOfRequiredLines = counter;
|
||||
this.currentMessageType = MessageType.M;
|
||||
if (index == 0) {
|
||||
this.receivedLines.add(line);
|
||||
} else {
|
||||
throw new IncorrectMultilineIndexException();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((!counter.equals(this.numberOfRequiredLines)) || (!(index == this.receivedLines.size()))) {
|
||||
throw new IncorrectMultilineIndexException();
|
||||
}
|
||||
|
||||
receivedLines.add(tokens[2]);
|
||||
|
||||
if (index + 1 == receivedLines.size()) {
|
||||
String newLine = "";
|
||||
for (String curLine : receivedLines) {
|
||||
newLine += curLine;
|
||||
}
|
||||
this.currentMessage = new MMessage(newLine);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
} catch (IncorrectMultilineIndexException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
throw new UnprocessableMessageException();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if there is a message waiting to be pulled
|
||||
*/
|
||||
public boolean isMessageAvailable() {
|
||||
return this.currentMessage != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls the message from the stack when there is one available. This needs
|
||||
* to be done before next line can be added into message processor. When
|
||||
* message is pulled, the message processor is reseted and ready to process
|
||||
* next line.
|
||||
*
|
||||
* @return Message
|
||||
* @throws NoMessageAvailableException
|
||||
* when there was no message on the stack
|
||||
*/
|
||||
@Nullable
|
||||
public Message pull() throws NoMessageAvailableException {
|
||||
final Message result = this.currentMessage;
|
||||
if (this.currentMessage == null) {
|
||||
throw new NoMessageAvailableException();
|
||||
}
|
||||
reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the raw TCP data read from the MAX protocol, returning the
|
||||
* corresponding MessageType.
|
||||
*
|
||||
* @param line
|
||||
* the raw data provided read from the MAX protocol
|
||||
* @return MessageType of the line added
|
||||
*/
|
||||
@Nullable
|
||||
private static MessageType getMessageType(String line) {
|
||||
for (MessageType msgType : MessageType.values()) {
|
||||
if (line.startsWith(msgType.name() + SEPARATOR)) {
|
||||
return msgType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This enumeration represents the different message types provided by the MAX! Cube protocol.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum MessageType {
|
||||
H,
|
||||
M,
|
||||
C,
|
||||
L,
|
||||
S,
|
||||
N,
|
||||
F,
|
||||
A
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link: NMessage} contains information about a newly discovered Device
|
||||
* This is the response to a n: command
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class NMessage extends Message {
|
||||
private final Logger logger = LoggerFactory.getLogger(NMessage.class);
|
||||
|
||||
private String decodedPayload = "";
|
||||
private @Nullable DeviceType deviceType;
|
||||
private String rfAddress = "";
|
||||
private String serialnr = "";
|
||||
|
||||
/**
|
||||
* The {@link: NMessage} contains information about a newly discovered Device
|
||||
*
|
||||
* @param raw String with raw message
|
||||
*/
|
||||
public NMessage(String raw) {
|
||||
super(raw);
|
||||
String msgPayload = this.getPayload();
|
||||
|
||||
if (msgPayload.length() > 0) {
|
||||
try {
|
||||
byte[] bytes = Base64.getDecoder().decode(msgPayload.trim());
|
||||
decodedPayload = new String(bytes, StandardCharsets.UTF_8);
|
||||
|
||||
deviceType = DeviceType.create(bytes[0] & 0xFF);
|
||||
rfAddress = Utils.toHex(bytes[1] & 0xFF, bytes[2] & 0xFF, bytes[3] & 0xFF);
|
||||
|
||||
byte[] data = new byte[10];
|
||||
System.arraycopy(bytes, 4, data, 0, 10);
|
||||
serialnr = new String(data, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during parsing of N message: {}", e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("No device found during inclusion");
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable DeviceType getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
public String getRfAddress() {
|
||||
return rfAddress;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialnr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
if (!this.rfAddress.isEmpty()) {
|
||||
logger.debug("=== N Message === ");
|
||||
logger.trace("\tRAW : {}", this.decodedPayload);
|
||||
logger.debug("\tDevice Type : {}", this.deviceType);
|
||||
logger.debug("\tRF Address : {}", this.rfAddress);
|
||||
logger.debug("\tSerial : {}", this.serialnr);
|
||||
} else {
|
||||
logger.trace("=== N Message === ");
|
||||
logger.trace("\tRAW : {}", this.decodedPayload);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.N;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SMessage} contains information about Command execution results
|
||||
*
|
||||
* @author Bernd Michael Helm (bernd.helm at helmundwalter.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 version + parsing of the message
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class SMessage extends Message {
|
||||
private final Logger logger = LoggerFactory.getLogger(SMessage.class);
|
||||
|
||||
private int dutyCycle;
|
||||
private int freeMemorySlots;
|
||||
private boolean commandDiscarded = false;
|
||||
|
||||
public SMessage(String raw) {
|
||||
super(raw);
|
||||
|
||||
String[] tokens = this.getPayload().split(Message.DELIMETER);
|
||||
|
||||
if (tokens.length == 3) {
|
||||
try {
|
||||
dutyCycle = Integer.parseInt(tokens[0], 16);
|
||||
commandDiscarded = tokens[1].contentEquals("1");
|
||||
freeMemorySlots = Integer.parseInt(tokens[2], 16);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during parsing of S message: {}", e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Unexpected # of tolkens ({}) received in S message: {}", tokens.length, this.getPayload());
|
||||
}
|
||||
}
|
||||
|
||||
public int getDutyCycle() {
|
||||
return dutyCycle;
|
||||
}
|
||||
|
||||
public int getFreeMemorySlots() {
|
||||
return freeMemorySlots;
|
||||
}
|
||||
|
||||
public boolean isCommandDiscarded() {
|
||||
return commandDiscarded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Logger logger) {
|
||||
logger.trace("=== S Message === ");
|
||||
logger.trace("\tRAW : {}", this.getPayload());
|
||||
logger.trace("\tDutyCycle : {}", this.dutyCycle);
|
||||
logger.trace("\tCommand Discarded : {}", this.commandDiscarded);
|
||||
logger.trace("\tFreeMemorySlots : {}", this.freeMemorySlots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType getType() {
|
||||
return MessageType.S;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="max" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>MAX! Binding</name>
|
||||
<description>This is the binding for the eQ-3 MAX! Home Solution.</description>
|
||||
<author>Marcel Verpaalen</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,278 @@
|
||||
# binding
|
||||
binding.max.name = MAX! Heizungssteuerung Binding
|
||||
binding.max.description = Dieses Binding integriert die MAX! Heizungssteuerung (z.B. Heizkörperthermostat, Heizkörperthermostat+, Wandthermostat+, Eco Taster, Fensterkontakt).
|
||||
|
||||
# bridge types
|
||||
thing-type.max.bridge.description = MAX! Cube LAN Gateway.
|
||||
|
||||
# bridge types config groups
|
||||
thing-type.config.max.bridge.group.identification.label = Identifizierung
|
||||
thing-type.config.max.bridge.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.bridge.group.network.label = Netzwerk
|
||||
thing-type.config.max.bridge.group.network.description = Einstellungen für das Netzwerk.
|
||||
|
||||
thing-type.config.max.bridge.group.device.label = Gerät
|
||||
thing-type.config.max.bridge.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.bridge.group.actions.label = Aktionen
|
||||
thing-type.config.max.bridge.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
# bridge types config
|
||||
thing-type.config.max.bridge.ipAddress.label = IP-Adresse
|
||||
thing-type.config.max.bridge.ipAddress.description = Lokale IP-Adresse oder Hostname des MAX! Cube LAN Gateway.
|
||||
|
||||
thing-type.config.max.bridge.port.label = Port
|
||||
thing-type.config.max.bridge.port.description = Port des MAX! Cube LAN Gateway.
|
||||
|
||||
thing-type.config.max.bridge.refreshInterval.label = Abfrageintervall
|
||||
thing-type.config.max.bridge.refreshInterval.description = Intervall zur Abfrage des MAX! Cube LAN Gateway (in Sekunden).
|
||||
|
||||
thing-type.config.max.bridge.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.bridge.serialNumber.description = Seriennummer des MAX! Cube LAN Gateway.
|
||||
|
||||
thing-type.config.max.bridge.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.bridge.rfAddress.description = RF-Addresse des MAX! Cube LAN Gateway.
|
||||
|
||||
thing-type.config.max.bridge.exclusive.label = Exklusivmodus
|
||||
thing-type.config.max.bridge.exclusive.description = Setzt den Exklusivmodus zur Nutzung des MAX! Cube LAN Gateways.
|
||||
|
||||
thing-type.config.max.bridge.maxRequestsPerConnection.label = Verbindingsanzahl
|
||||
thing-type.config.max.bridge.maxRequestsPerConnection.description = Maximale Anzahl an Verbindungen zum MAX! Cube LAN Gateways.
|
||||
|
||||
thing-type.config.max.bridge.ntpServer1.label = 1. NTP-Server
|
||||
thing-type.config.max.bridge.ntpServer1.description = Erster NTP-Server zur Abfrage des Datums und der Zeit.
|
||||
|
||||
thing-type.config.max.bridge.ntpServer2.label = 2. NTP-Server
|
||||
thing-type.config.max.bridge.ntpServer2.description = Zweiter NTP-Server zur Abfrage des Datums und der Zeit.
|
||||
|
||||
thing-type.config.max.bridge.action-cubeReset.label = Werkszustand
|
||||
thing-type.config.max.bridge.action-cubeReset.description = Ermöglicht das MAX Cube LAN Gateway in den Werkszustand zurück zu versetzen.
|
||||
thing-type.config.max.bridge.action-cubeReset.option.1234 = Zurücksetzen
|
||||
thing-type.config.max.bridge.action-cubeReset.option.-1 = Keine Aktion
|
||||
|
||||
thing-type.config.max.bridge.action-cubeReboot.label = Neustart
|
||||
thing-type.config.max.bridge.action-cubeReboot.description = Ermöglicht das MAX Cube LAN Gateway neu zu starten.
|
||||
thing-type.config.max.bridge.action-cubeReboot.option.1234 = Neustarten
|
||||
thing-type.config.max.bridge.action-cubeReboot.option.-1 = Keine Aktion
|
||||
|
||||
# thing types
|
||||
thing-type.max.thermostat.label = MAX! Heizkörperthermostat
|
||||
thing-type.max.thermostat.description = MAX! Heizkörperthermostat und MAX! Heizkörperthermostat basic. Dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
|
||||
|
||||
thing-type.max.thermostatplus.label = MAX! Heizkörperthermostat+
|
||||
thing-type.max.thermostatplus.description = MAX! Heizkörperthermostat+. Dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
|
||||
|
||||
thing-type.max.wallthermostat.label = MAX! Wandthermostat+
|
||||
thing-type.max.wallthermostat.description = MAX! Wandthermostat+. Dient zur Steuerung von Heizkörperthermostaten und liefert Daten wie z.B. Temperatur.
|
||||
|
||||
thing-type.max.ecoswitch.label = MAX! Eco Taster
|
||||
thing-type.max.ecoswitch.description = MAX! Eco Taster. Dient zur Steuerung von Heizkörperthermostaten.
|
||||
|
||||
thing-type.max.shuttercontact.label = MAX! Fensterkontakt
|
||||
thing-type.max.shuttercontact.description = MAX! Fensterkontakt. Liefert Daten wie z.B. Fenster-Zustand.
|
||||
|
||||
# thing types config groups
|
||||
thing-type.config.max.thermostat.group.identification.label = Identifizierung
|
||||
thing-type.config.max.thermostat.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostat.group.device.label = Gerät
|
||||
thing-type.config.max.thermostat.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.thermostat.group.binding.label = Binding
|
||||
thing-type.config.max.thermostat.group.binding.description = Einstellungen für das Binding.
|
||||
|
||||
thing-type.config.max.thermostat.group.actions.label = Aktionen
|
||||
thing-type.config.max.thermostat.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
thing-type.config.max.thermostatplus.group.identification.label = Identifizierung
|
||||
thing-type.config.max.thermostatplus.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostatplus.group.device.label = Gerät
|
||||
thing-type.config.max.thermostatplus.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.thermostatplus.group.actions.label = Aktionen
|
||||
thing-type.config.max.thermostatplus.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
thing-type.config.max.thermostatplus.group.binding.label = Binding
|
||||
thing-type.config.max.thermostatplus.group.binding.description = Einstellungen für das Binding.
|
||||
|
||||
thing-type.config.max.wallthermostat.group.identification.label = Identifizierung
|
||||
thing-type.config.max.wallthermostat.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.wallthermostat.group.device.label = Gerät
|
||||
thing-type.config.max.wallthermostat.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.wallthermostat.group.actions.label = Aktionen
|
||||
thing-type.config.max.wallthermostat.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
thing-type.config.max.ecoswitch.group.identification.label = Identifizierung
|
||||
thing-type.config.max.ecoswitch.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.ecoswitch.group.device.label = Gerät
|
||||
thing-type.config.max.ecoswitch.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.shuttercontact.group.identification.label = Identifizierung
|
||||
thing-type.config.max.shuttercontact.group.identification.description = Einstellungen für die Identifizierung des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.group.device.label = Gerät
|
||||
thing-type.config.max.shuttercontact.group.device.description = Einstellungen für das Gerät.
|
||||
|
||||
thing-type.config.max.shuttercontact.group.actions.label = Aktionen
|
||||
thing-type.config.max.shuttercontact.group.actions.description = Einstellungen für Aktionen.
|
||||
|
||||
# thing types config
|
||||
thing-type.config.max.thermostat.room.label = Raum
|
||||
thing-type.config.max.thermostat.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.thermostat.name.label = Name
|
||||
thing-type.config.max.thermostat.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostat.comfortTemp.label = Komforttemperatur
|
||||
thing-type.config.max.thermostat.comfortTemp.description = Gibt die Komforttemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.ecoTemp.label = Absenktemperatur
|
||||
thing-type.config.max.thermostat.ecoTemp.description = Gibt die Absenktemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.offsetTemp.label = Temperatur-Offset
|
||||
thing-type.config.max.thermostat.offsetTemp.description = Gibt den Temperatur-Offset (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.maxTempSetpoint.label = Max. Solltemperatur
|
||||
thing-type.config.max.thermostat.maxTempSetpoint.description = Gibt die maximal einstellbare Solltemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.minTempSetpoint.label = Min. Solltemperatur
|
||||
thing-type.config.max.thermostat.minTempSetpoint.description = Gibt die minimal einstellbare Solltemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.windowOpenTemp.label = Fenster-Auf-Temperatur
|
||||
thing-type.config.max.thermostat.windowOpenTemp.description = Gibt die Temperatur bei geöffnetem Fenster-Zustand (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.windowOpenDuration.label = Dauer Fenster-Auf-Temperatur
|
||||
thing-type.config.max.thermostat.windowOpenDuration.description = Gibt die Dauer der Absenkung auf die Fenster-Auf-Temperatur (in Minuten) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostat.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.thermostat.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostat.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.thermostat.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostat.refreshActualRate.label = Aktualisierungsrate
|
||||
|
||||
thing-type.config.max.thermostat.action-deviceDelete.label = Gerät trennen
|
||||
thing-type.config.max.thermostat.action-deviceDelete.description = Ermöglicht das Trennen des Gerätes vom MAX! Cube LAN Gateway.
|
||||
thing-type.config.max.thermostat.action-deviceDelete.option.1234 = Löschen
|
||||
thing-type.config.max.thermostat.action-deviceDelete.option.-1 = Keine Aktion
|
||||
|
||||
thing-type.config.max.thermostatplus.room.label = Raum
|
||||
thing-type.config.max.thermostatplus.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.thermostatplus.name.label = Name
|
||||
thing-type.config.max.thermostatplus.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostatplus.comfortTemp.label = Komforttemperatur
|
||||
thing-type.config.max.thermostatplus.comfortTemp.description = Gibt die Komforttemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.ecoTemp.label = Absenktemperatur
|
||||
thing-type.config.max.thermostatplus.ecoTemp.description = Gibt die Absenktemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.offsetTemp.label = Temperatur-Offset
|
||||
thing-type.config.max.thermostatplus.offsetTemp.description = Gibt den Temperatur-Offset (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.maxTempSetpoint.label = Max. Solltemperatur
|
||||
thing-type.config.max.thermostatplus.maxTempSetpoint.description = Gibt die maximal einstellbare Solltemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.minTempSetpoint.label = Min. Solltemperatur
|
||||
thing-type.config.max.thermostatplus.minTempSetpoint.description = Gibt die minimal einstellbare Solltemperatur (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.windowOpenTemp.label = Fenster-Auf-Temperatur
|
||||
thing-type.config.max.thermostatplus.windowOpenTemp.description = Gibt die Temperatur bei geöffnetem Fenster-Zustand (in °C) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.windowOpenDuration.label = Dauer Fenster-Auf-Temperatur
|
||||
thing-type.config.max.thermostatplus.windowOpenDuration.description = Gibt die Dauer der Absenkung auf die Fenster-Auf-Temperatur (in Minuten) des Heizkörperthermostats an.
|
||||
|
||||
thing-type.config.max.thermostatplus.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.thermostatplus.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostatplus.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.thermostatplus.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.thermostatplus.refreshActualRate.label = Aktualisierungsrate
|
||||
|
||||
thing-type.config.max.thermostatplus.action-deviceDelete.label = Gerät trennen
|
||||
thing-type.config.max.thermostatplus.action-deviceDelete.description = Ermöglicht das Trennen des Gerätes vom MAX! Cube LAN Gateway.
|
||||
thing-type.config.max.thermostatplus.action-deviceDelete.option.1234 = Löschen
|
||||
thing-type.config.max.thermostatplus.action-deviceDelete.option.-1 = Keine Aktion
|
||||
|
||||
thing-type.config.max.wallthermostat.room.label = Raum
|
||||
thing-type.config.max.wallthermostat.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.wallthermostat.name.label = Name
|
||||
thing-type.config.max.wallthermostat.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.wallthermostat.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.wallthermostat.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.wallthermostat.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.wallthermostat.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.wallthermostat.action-deviceDelete.label = Gerät trennen
|
||||
thing-type.config.max.wallthermostat.action-deviceDelete.description = Ermöglicht das Trennen des Gerätes vom MAX! Cube LAN Gateway.
|
||||
thing-type.config.max.wallthermostat.action-deviceDelete.option.1234 = Löschen
|
||||
thing-type.config.max.wallthermostat.action-deviceDelete.option.-1 = Keine Aktion
|
||||
|
||||
thing-type.config.max.ecoswitch.room.label = Raum
|
||||
thing-type.config.max.ecoswitch.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.ecoswitch.name.label = Name
|
||||
thing-type.config.max.ecoswitch.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.ecoswitch.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.ecoswitch.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.ecoswitch.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.ecoswitch.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.room.label = Raum
|
||||
thing-type.config.max.shuttercontact.room.description = Benutzerspezifischer Namen des Raumes, dem das Gerät zugeordnet ist.
|
||||
|
||||
thing-type.config.max.shuttercontact.name.label = Name
|
||||
thing-type.config.max.shuttercontact.name.description = Benutzerspezifischer Namen des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.serialNumber.label = Seriennummer
|
||||
thing-type.config.max.shuttercontact.serialNumber.description = Seriennummer des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.rfAddress.label = RF-Addresse
|
||||
thing-type.config.max.shuttercontact.rfAddress.description = RF-Addresse des Gerätes.
|
||||
|
||||
thing-type.config.max.shuttercontact.action-deviceDelete.label = Gerät trennen
|
||||
thing-type.config.max.shuttercontact.action-deviceDelete.description = Ermöglicht das Trennen des Gerätes vom MAX! Cube LAN Gateway.
|
||||
thing-type.config.max.shuttercontact.action-deviceDelete.option.1234 = Löschen
|
||||
thing-type.config.max.shuttercontact.action-deviceDelete.option.-1 = Keine Aktion
|
||||
|
||||
# channel types
|
||||
channel-type.max.free_mem.label = Freie Speicherplätze
|
||||
channel-type.max.free_mem.description = Gibt an, wie viele freie Speicherplätze im MAX! Cube LAN Gateway für Befehle zur Verfügung stehen.
|
||||
|
||||
channel-type.max.duty_cycle.label = Auslastungsgrad
|
||||
channel-type.max.duty_cycle.description = Gibt den Auslastungsgrad für Befehle (in %) an.
|
||||
|
||||
channel-type.max.valve.label = Ventil
|
||||
channel-type.max.valve.description = Gibt die Ventilöffnung des Heizkörperreglers (in %) an.
|
||||
|
||||
channel-type.max.mode.label = Modus des Gerätes
|
||||
channel-type.max.mode.description = Gibt den aktuellen Modus des Gerätes an (MANUAL/AUTOMATIC/BOOST/VACATION).
|
||||
channel-type.max.mode.state.option.AUTOMATIC = Automatisch
|
||||
channel-type.max.mode.state.option.MANUAL = Manuell
|
||||
channel-type.max.mode.state.option.BOOST = Boost
|
||||
channel-type.max.mode.state.option.VACATION = Urlaubsmodus
|
||||
|
||||
channel-type.max.actual_temp.label = Temperatur
|
||||
channel-type.max.actual_temp.description = Gibt die aktuell gemessene Temperatur des Thermostats an.
|
||||
|
||||
channel-type.max.set_temp.label = Solltemperatur
|
||||
channel-type.max.set_temp.description = Gibt die aktuell eingestellte Solltemperatur des Thermostats an.
|
||||
|
||||
channel-type.max.locked.label = Tastensperre
|
||||
channel-type.max.locked.description = Gibt an, ob die Tastensperre am Gerät aktiviert ist.
|
||||
|
||||
channel-type.max.contact_state.label = Fenster-Zustand
|
||||
channel-type.max.contact_state.description = Gibt an, ob ein Fenster offen oder geschlossen ist.
|
||||
@@ -0,0 +1,134 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="max" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="bridge">
|
||||
<label>MAX! Cube LAN Gateway</label>
|
||||
<description>This bridge represents the MAX! Cube LAN Gateway.
|
||||
</description>
|
||||
|
||||
<channels>
|
||||
<channel id="free_mem" typeId="free_mem"/>
|
||||
<channel id="duty_cycle" typeId="duty_cycle"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
<advanced>false</advanced>
|
||||
</parameter-group>
|
||||
<parameter-group name="network">
|
||||
<label>Connection</label>
|
||||
<description>Connection Settings</description>
|
||||
<advanced>false</advanced>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<context></context>
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="ipAddress" type="text" required="true" groupName="network">
|
||||
<context>network-address</context>
|
||||
<label>MAX! Cube LAN Gateway IP</label>
|
||||
<description>The IP address of the MAX! Cube LAN gateway</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="false" min="1" max="65535" groupName="network">
|
||||
<context>network-address</context>
|
||||
<label>MAX! Cube LAN Gateway Port</label>
|
||||
<description>Port of the LAN gateway</description>
|
||||
<default>62910</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" required="false" min="1" unit="s" groupName="network">
|
||||
<label>Refresh Interval</label>
|
||||
<description>The refresh interval in seconds which is used to poll given
|
||||
MAX! Cube.</description>
|
||||
<default>30</default>
|
||||
</parameter>
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<parameter name="exclusive" type="boolean" required="false" groupName="network">
|
||||
<label>Exclusive Mode</label>
|
||||
<description>If set to true, the binding will leave the connection to the
|
||||
Cube open.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="maxRequestsPerConnection" type="integer" required="false" groupName="network">
|
||||
<label>Max Requests Per Connection</label>
|
||||
<description>In exclusive mode, how many requests are allowed until
|
||||
connection is closed and reopened.</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ntpServer1" type="text" required="false" groupName="device">
|
||||
<label>NTP Server 1</label>
|
||||
<description>The hostname for NTP Server 1 used by the Cube to get the time.</description>
|
||||
<default>ntp.homematic.com</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ntpServer2" type="text" required="false" groupName="device">
|
||||
<label>NTP Server 2</label>
|
||||
<description>The hostname for NTP Server 2 used by the Cube to get the time.</description>
|
||||
<default>ntp.homematic.com</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-cubeReset" type="integer" groupName="actions">
|
||||
<label>Reset Cube Configuration</label>
|
||||
<description>Resets the MAX! Cube room and device information. Devices will
|
||||
need to be included again!</description>
|
||||
<options>
|
||||
<option value="1234">Reset</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<!-- Trigger reboot action from habmin menu -->
|
||||
<parameter name="action-cubeReboot" type="integer" groupName="actions">
|
||||
<label>Restart Cube</label>
|
||||
<description>Restarts the Cube.</description>
|
||||
<options>
|
||||
<option value="1234">Reboot</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
||||
</bridge-type>
|
||||
|
||||
<channel-type id="free_mem" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Free Memory Slots</label>
|
||||
<description>Free memory slots to store commands send to the devices</description>
|
||||
<state pattern="%d" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="duty_cycle" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Duty Cycle</label>
|
||||
<description>Duty Cycle for sending commands to the devices</description>
|
||||
<state pattern="%d %%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,493 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="max" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="thermostat">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! HeatingThermostat</label>
|
||||
<description>This is a MAX! HeatingThermostat</description>
|
||||
|
||||
<channels>
|
||||
<channel id="valve" typeId="valve"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="actual_temp" typeId="actual_temp"/>
|
||||
<channel id="set_temp" typeId="set_temp"/>
|
||||
<channel id="locked" typeId="locked"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter-group>
|
||||
<parameter-group name="binding">
|
||||
<label>Binding Settings</label>
|
||||
<description>Binding settings for this device</description>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
<!-- Experimental - Enable when also removing rooms is implemented <parameter
|
||||
name="roomId" type="integer" required="false" groupName="device"> <label>Room Id</label>
|
||||
<description>The room name Id.</description> <advanced>true</advanced> </parameter> -->
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="comfortTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Comfort Temperature</label>
|
||||
<description>Set the Comfort Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ecoTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Eco Temperature</label>
|
||||
<description>Set the Eco Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="offsetTemp" type="decimal" min="-3.5" max="3.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>OffSet Temperature</label>
|
||||
<description>Set the Thermostat offset.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="maxTempSetpoint" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Max Temperature</label>
|
||||
<description>Set the Thermostat maximum temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="minTempSetpoint" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Min Temperature</label>
|
||||
<description>Set the Thermostat minimum temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="windowOpenTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Window Open Temp</label>
|
||||
<description>Set the Window Open Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="windowOpenDuration" type="integer" min="0" max="60" step="5" required="false"
|
||||
groupName="device">
|
||||
<label>Window Open Duration</label>
|
||||
<description>Set the Thermostat Window Open Duration.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<parameter name="refreshActualRate" type="integer" required="false" groupName="binding">
|
||||
<label>Actual Temperature Refresh Rate</label>
|
||||
<description>Experimental feature! Rate of the actual refresh in minutes. 0-9=refresh disabled. Minimum refresh rate
|
||||
once/10 minutes, recommended 60min. The settings of the heating thermostats are changed to trigger an update of
|
||||
temperature, as the actual temperature only is updated when the valves changes.</description>
|
||||
<default>0</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-deviceDelete" type="integer" groupName="actions">
|
||||
<label>Delete Device from Cube</label>
|
||||
<description>Deletes the device from the MAX! Cube. Device will need to be
|
||||
included again!</description>
|
||||
<options>
|
||||
<option value="1234">Delete</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="thermostatplus">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! HeatingThermostat+</label>
|
||||
<description>This is a MAX! HeatingThermostat+</description>
|
||||
|
||||
<channels>
|
||||
<channel id="valve" typeId="valve"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="actual_temp" typeId="actual_temp"/>
|
||||
<channel id="set_temp" typeId="set_temp"/>
|
||||
<channel id="locked" typeId="locked"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter-group>
|
||||
<parameter-group name="binding">
|
||||
<label>Binding Settings</label>
|
||||
<description>Binding settings for this device</description>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<context></context>
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
<!-- Experimental - Enable when also removing rooms is implemented <parameter
|
||||
name="roomId" type="integer" required="false" groupName="device"> <label>Room Id</label>
|
||||
<description>The room name Id.</description> <advanced>true</advanced> </parameter> -->
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="comfortTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Comfort Temperature</label>
|
||||
<description>Set the Comfort Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ecoTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Eco Temperature</label>
|
||||
<description>Set the Eco Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="offsetTemp" type="decimal" min="-3.5" max="3.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>OffSet Temperature</label>
|
||||
<description>Set the Thermostat offset.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="maxTempSetpoint" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Max Temperature</label>
|
||||
<description>Set the Thermostat maximum temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="minTempSetpoint" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Min Temperature</label>
|
||||
<description>Set the Thermostat minimum temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="windowOpenTemp" type="decimal" min="4.5" max="30.5" step="0.5" required="false"
|
||||
groupName="device">
|
||||
<label>Window Open Temp</label>
|
||||
<description>Set the Window Open Temperature.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="windowOpenDuration" type="integer" min="0" max="60" step="5" required="false"
|
||||
groupName="device">
|
||||
<label>Window Open Duration</label>
|
||||
<description>Set the Thermostat Window Open Duration.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<parameter name="refreshActualRate" type="integer" required="false" groupName="binding">
|
||||
<label>Actual Temperature Refresh Rate</label>
|
||||
<description>Experimental feature! Rate of the actual refresh in minutes. 0-9=refresh disabled. Minimum refresh rate
|
||||
once/10 minutes, recommended 60min. The settings of the heating thermostats are changed to trigger an update of
|
||||
temperature, as the actual temperature only is updated when the valves changes.</description>
|
||||
<default>0</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-deviceDelete" type="integer" groupName="actions">
|
||||
<label>Delete Device from Cube</label>
|
||||
<description>Deletes the device from the MAX! Cube. Device will need to be
|
||||
included again!</description>
|
||||
<options>
|
||||
<option value="1234">Delete</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="wallthermostat">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! WallThermostat+</label>
|
||||
<description>This is a MAX! WallThermostat+</description>
|
||||
|
||||
<channels>
|
||||
<channel id="valve" typeId="valve"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="actual_temp" typeId="actual_temp"/>
|
||||
<channel id="set_temp" typeId="set_temp"/>
|
||||
<channel id="locked" typeId="locked"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
</parameter>
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-deviceDelete" type="integer" groupName="actions">
|
||||
<label>Delete Device from Cube</label>
|
||||
<description>Deletes the device from the MAX! Cube. Device will need to be
|
||||
included again!</description>
|
||||
<options>
|
||||
<option value="1234">Delete</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="ecoswitch">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! Ecoswitch</label>
|
||||
<description>This is a MAX! EcoSwitch</description>
|
||||
|
||||
<channels>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
</parameter>
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shuttercontact">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>MAX! Shutter Contact</label>
|
||||
<description>This is a MAX! Shutter Contact</description>
|
||||
|
||||
<channels>
|
||||
<channel id="contact_state" typeId="contact_state"/>
|
||||
<channel id="battery_low" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="identification">
|
||||
<label>Identification</label>
|
||||
<description>Hardware & location identification</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="device">
|
||||
<label>Device Settings</label>
|
||||
<description>Device parameter settings</description>
|
||||
</parameter-group>
|
||||
<!-- Trigger actions from habmin -->
|
||||
<parameter-group name="actions">
|
||||
<label>Actions</label>
|
||||
<description>Action Buttons</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="room" type="text" required="false" groupName="device">
|
||||
<label>Room</label>
|
||||
<description>The room name.</description>
|
||||
</parameter>
|
||||
<parameter name="name" type="text" required="false" groupName="device">
|
||||
<label>Device Name</label>
|
||||
<description>The device description.</description>
|
||||
</parameter>
|
||||
<parameter name="serialNumber" type="text" required="true" groupName="identification">
|
||||
<label>Serial Number</label>
|
||||
<description>The Serial Number identifies one specific device.</description>
|
||||
</parameter>
|
||||
<parameter name="rfAddress" type="text" required="false" groupName="identification">
|
||||
<label>RF Address</label>
|
||||
<description>The RF Address used for communication between the devices.</description>
|
||||
</parameter>
|
||||
<!-- Trigger reset action from habmin menu -->
|
||||
<parameter name="action-deviceDelete" type="integer" groupName="actions">
|
||||
<label>Delete Device from Cube</label>
|
||||
<description>Deletes the device from the MAX! Cube. Device will need to be
|
||||
included again!</description>
|
||||
<options>
|
||||
<option value="1234">Delete</option>
|
||||
<option value="-1">No Action</option>
|
||||
</options>
|
||||
<default>-1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="valve" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Valve Position</label>
|
||||
<description>Thermostat Valve Position</description>
|
||||
<state pattern="%d %%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mode">
|
||||
<item-type>String</item-type>
|
||||
<label>Mode</label>
|
||||
<description>Thermostat Mode Setting</description>
|
||||
<tags>
|
||||
<tag>heating</tag>
|
||||
</tags>
|
||||
<state pattern="%s" readOnly="false">
|
||||
<options>
|
||||
<option value="AUTOMATIC">AUTOMATIC</option>
|
||||
<option value="MANUAL">MANUAL</option>
|
||||
<option value="BOOST">BOOST</option>
|
||||
<option value="VACATION">VACATION</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="actual_temp">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Current Temperature</label>
|
||||
<description>Current measured room temperature</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>heating</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="set_temp">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Setpoint Temperature</label>
|
||||
<description>Thermostat Setpoint temperature</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>heating</tag>
|
||||
</tags>
|
||||
<state min="4.5" max="30.5" step="0.5" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="locked" advanced="true">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Thermostat Locked</label>
|
||||
<description>Thermostat is locked for adjustments</description>
|
||||
<category>Lock</category>
|
||||
<state pattern="%s" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="contact_state">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Contact State</label>
|
||||
<description>Contact state information</description>
|
||||
<category>Contact</category>
|
||||
<state pattern="%s" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link FCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FCommandTest {
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
FCommand scmd = new FCommand();
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
|
||||
assertEquals("f:", prefix);
|
||||
assertEquals("f:" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
FCommand scmd = new FCommand("ntp.homematic.com", "nl.ntp.pool.org");
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("f:ntp.homematic.com,nl.ntp.pool.org" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fCommandNullTest() {
|
||||
FCommand scmd = new FCommand("ntp.homematic.com", null);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("f:ntp.homematic.com" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = new FCommand(null, "nl.ntp.pool.org");
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("f:nl.ntp.pool.org" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = new FCommand(null, null);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("f:" + '\r' + '\n', commandStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.DeviceConfiguration;
|
||||
import org.openhab.binding.max.internal.device.RoomInformation;
|
||||
import org.openhab.binding.max.internal.message.CMessage;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link MCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MCommandTest {
|
||||
|
||||
private List<DeviceConfiguration> configurations = new ArrayList<>();
|
||||
private List<Device> devices = new ArrayList<>();
|
||||
private List<RoomInformation> rooms = new ArrayList<>();
|
||||
|
||||
String deviceCMsg[] = {
|
||||
"C:0b0da3,0gsNowIBEABLRVEwNTQ0MjQyLCQ9CQcYAzAM/wBIYViRSP1ZFE0gTSBNIEUgRSBFIEUgRSBFIEhhWJFQ/VkVUSBRIFEgRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIEhQWFpI/lkUTSBNIE0gRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIA==",
|
||||
"C:08c1d6,0gjB1gEFGP9LRVEwNjQ5MzEyKyE9CQcYAzAM/wBEeFUgVSBVIFUgVSBVIEUgRSBFIEUgRSBFIER4VRZFIEUgRSBFIEUgRSBFIEUgRSBFIEUgRFFEYkTkTQ9FIEUgRSBFIEUgRSBFIEUgRSBEUURiRORND0UgRSBFIEUgRSBFIEUgRSBFIERRRGJE5E0PRSBFIEUgRSBFIEUgRSBFIEUgRFFEYkTkTQ9FIEUgRSBFIEUgRSBFIEUgRSBEUURiRORRGEUgRSBFIEUgRSBFIEUgRSBFIA==",
|
||||
"C:0e75f6,EQ519gQCEABLRVExMTA0Mzgw",
|
||||
"C:0f1d54,0g8dVAEAEKBMRVEwMTU1NTc4KiI9CQcYAzAM/wBESFUIRSBFIEUgRSBFIEUgRSBFIEUgRSBFIERIVQhFIEUgRSBFIEUgRSBFIEUgRSBFIEUgREhUbETMVRRFIEUgRSBFIEUgRSBFIEUgRSBESFRsRMxVFEUgRSBFIEUgRSBFIEUgRSBFIERIVGxEzFUURSBFIEUgRSBFIEUgRSBFIEUgREhUbETMVRRFIEUgRSBFIEUgRSBFIEUgRSBESFRsRMxVFEUgRSBFIEUgRSBFIEUgRSBFIA==" };
|
||||
|
||||
private void prepareDevices() {
|
||||
// create a devices array
|
||||
for (String cMsg : deviceCMsg) {
|
||||
CMessage msg = new CMessage(cMsg);
|
||||
// DeviceConfiguration c = null;
|
||||
configurations.add(DeviceConfiguration.create(msg));
|
||||
Device di = Device.create(msg.getRFAddress(), configurations);
|
||||
devices.add(di);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
prepareDevices();
|
||||
MCommand mcmd = new MCommand(devices);
|
||||
String commandStr = mcmd.getCommandString();
|
||||
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
assertEquals("m:", prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
prepareDevices();
|
||||
|
||||
MCommand mCmd = new MCommand(devices);
|
||||
rooms = new ArrayList<>(mCmd.getRooms());
|
||||
String commandStr = mCmd.getCommandString();
|
||||
assertEquals(
|
||||
"m:00,VgIDAQALDaMCAA519gUACMHWBAILDaNLRVEwNTQ0MjQyAAEBCMHWS0VRMDY0OTMxMgAFBA519ktFUTExMDQzODAAAgEPHVRMRVEwMTU1NTc4AAAB\r\n",
|
||||
commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addRoomsTest() {
|
||||
prepareDevices();
|
||||
|
||||
MCommand mCmd = new MCommand(devices);
|
||||
rooms = new ArrayList<>(mCmd.getRooms());
|
||||
|
||||
RoomInformation room = new RoomInformation(3, "testroom", "0f1d54");
|
||||
rooms.add(room);
|
||||
mCmd = new MCommand(devices, rooms);
|
||||
String commandStr = mCmd.getCommandString();
|
||||
assertEquals(
|
||||
"m:00,VgIEAQALDaMCAA519gMIdGVzdHJvb20PHVQFAAjB1gQCCw2jS0VRMDU0NDI0MgABAQjB1ktFUTA2NDkzMTIABQQOdfZLRVExMTA0MzgwAAIBDx1UTEVRMDE1NTU3OAAAAQ==\r\n",
|
||||
commandStr);
|
||||
devices.get(3).setRoomId(3);
|
||||
devices.get(3).setName("Testroom");
|
||||
|
||||
mCmd = new MCommand(devices, rooms);
|
||||
commandStr = mCmd.getCommandString();
|
||||
|
||||
assertEquals(
|
||||
"m:00,VgIEAQALDaMCAA519gMIdGVzdHJvb20PHVQFAAjB1gQCCw2jS0VRMDU0NDI0MgABAQjB1ktFUTA2NDkzMTIABQQOdfZLRVExMTA0MzgwAAIBDx1UTEVRMDE1NTU3OAhUZXN0cm9vbQMB\r\n",
|
||||
commandStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
import org.openhab.binding.max.internal.device.ThermostatModeType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link SCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SCommandTest {
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
SCommand scmd = new SCommand("0b0da3", 1, ThermostatModeType.MANUAL, 20.0);
|
||||
String commandStr = scmd.getCommandString();
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
|
||||
assertEquals("s:", prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
SCommand scmd = new SCommand("0b0da3", 1, ThermostatModeType.MANUAL, 20.0);
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
|
||||
String base64Data = commandStr.substring(2).trim();
|
||||
byte[] bytes = Base64.getDecoder().decode(base64Data.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
int[] data = new int[bytes.length];
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
data[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
|
||||
String decodedString = Utils.toHex(data);
|
||||
assertEquals("s:AARAAAAACw2jAWg=\r\n", commandStr);
|
||||
assertEquals("0004400000000B0DA30168", decodedString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boostModeTest() {
|
||||
SCommand scmd = new SCommand("0b0da3", 1, ThermostatModeType.BOOST, 21.0);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("s:AARAAAAACw2jAeo=\r\n", commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoModeTest() {
|
||||
SCommand scmd = new SCommand("0b0da3", 1, ThermostatModeType.AUTOMATIC, 0);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("s:AARAAAAACw2jAQA=\r\n", commandStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.command.SConfigCommand.ConfigCommandType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link SConfigCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SConfigCommandTest {
|
||||
|
||||
private static final String RF_TEST_ADDRESS = "0e15cc";
|
||||
private static final int TEST_ROOM = 2;
|
||||
|
||||
@Test
|
||||
public void setRoomTest() {
|
||||
CubeCommand cubeCommand = new SConfigCommand(RF_TEST_ADDRESS, TEST_ROOM, ConfigCommandType.SetRoom);
|
||||
String commandString = cubeCommand.getCommandString();
|
||||
assertEquals("s:AAAiAAAADhXMAAI=\r\n", commandString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeRoomTest() {
|
||||
CubeCommand cubeCommand = new SConfigCommand(RF_TEST_ADDRESS, 1, ConfigCommandType.RemoveRoom);
|
||||
String commandString = cubeCommand.getCommandString();
|
||||
assertEquals("s:AAAjAAAADhXMAAE=\r\n", commandString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link TCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TCommandTest {
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
TCommand scmd = new TCommand("0f1d54", false);
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
|
||||
assertEquals("t:", prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
TCommand scmd = new TCommand("0f1d54", false);
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
|
||||
String base64Data = commandStr.split(",")[2];
|
||||
byte[] bytes = Base64.getDecoder().decode(base64Data.trim().getBytes());
|
||||
int[] data = new int[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
data[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
String decodedString = Utils.toHex(data);
|
||||
|
||||
assertEquals("t:01,0,Dx1U\r\n", commandStr);
|
||||
assertEquals("0F1D54", decodedString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addRoomTest() {
|
||||
TCommand scmd = new TCommand("0f1d54", false);
|
||||
scmd.addRoom("0b0da3");
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
|
||||
String base64Data = commandStr.split(",")[2];
|
||||
byte[] bytes = Base64.getDecoder().decode(base64Data.trim().getBytes());
|
||||
int[] data = new int[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
data[i] = bytes[i] & 0xFF;
|
||||
}
|
||||
String decodedString = Utils.toHex(data);
|
||||
|
||||
assertEquals("t:02,0,Cw2jDx1U\r\n", commandStr);
|
||||
assertEquals("0B0DA30F1D54", decodedString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceModeTest() {
|
||||
TCommand scmd = new TCommand("0f1d54", true);
|
||||
String commandStr = scmd.getCommandString();
|
||||
|
||||
assertEquals("t:01,1,Dx1U\r\n", commandStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link ZCommand}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZCommandTest {
|
||||
|
||||
@Test
|
||||
public void prefixTest() {
|
||||
ZCommand scmd = new ZCommand(ZCommand.WakeUpType.DEVICE, "0b0da3", 30);
|
||||
|
||||
String commandStr = scmd.getCommandString();
|
||||
String prefix = commandStr.substring(0, 2);
|
||||
|
||||
assertEquals("z:", prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void baseCommandTest() {
|
||||
ZCommand scmd = new ZCommand(ZCommand.WakeUpType.DEVICE, "0b0da3", 30);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,D,0b0da3" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wakeAllTest() {
|
||||
ZCommand scmd = new ZCommand(ZCommand.WakeUpType.ALL, "0b0da3", 60);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("z:3C,A" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupAllDevices();
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,A" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupAllDevices(60);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:3C,A" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wakeRoomTest() {
|
||||
ZCommand scmd = new ZCommand(ZCommand.WakeUpType.ROOM, "01", 30);
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,G,01" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupRoom(1);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,G,01" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupRoom(2, 60);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:3C,G,02" + '\r' + '\n', commandStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wakeDeviceTest() {
|
||||
ZCommand scmd = ZCommand.wakeupDevice("0b0da3");
|
||||
String commandStr = scmd.getCommandString();
|
||||
assertEquals("z:1E,D,0b0da3" + '\r' + '\n', commandStr);
|
||||
|
||||
scmd = ZCommand.wakeupDevice("0b0da3", 60);
|
||||
commandStr = scmd.getCommandString();
|
||||
assertEquals("z:3C,D,0b0da3" + '\r' + '\n', commandStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link CMessage}.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 Version and updates
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "C:0b0da3,0gsNowIBEABLRVEwNTQ0MjQyLCQ9CQcYAzAM/wBIYViRSP1ZFE0gTSBNIEUgRSBFIEUgRSBFIEhhWJFQ/VkVUSBRIFEgRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIEhQWFpI/lkUTSBNIE0gRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIA==";
|
||||
private final CMessage message = new CMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.C, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRFAddressTest() {
|
||||
String rfAddress = message.getRFAddress();
|
||||
assertEquals("0b0da3", rfAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeviceTypeTest() {
|
||||
DeviceType deviceType = message.getDeviceType();
|
||||
assertEquals(DeviceType.HeatingThermostatPlus, deviceType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSerialNumberTes() {
|
||||
String serialNumber = message.getSerialNumber();
|
||||
assertEquals("KEQ0544242", serialNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.DeviceConfiguration;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link DeviceConfiguration}.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 Version and updates
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConfigurationTest {
|
||||
|
||||
public static final String RAW_DATA = "C:0b0da3,0gsNowIBEABLRVEwNTQ0MjQyLCQ9CQcYAzAM/wBIYViRSP1ZFE0gTSBNIEUgRSBFIEUgRSBFIEhhWJFQ/VkVUSBRIFEgRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIEhQWFpI/lkUTSBNIE0gRSBFIEUgRSBFIEUgSFBYWkj+WRRNIE0gTSBFIEUgRSBFIEUgRSBIUFhaSP5ZFE0gTSBNIEUgRSBFIEUgRSBFIA==";
|
||||
|
||||
private final CMessage message = new CMessage(RAW_DATA);
|
||||
private @Nullable DeviceConfiguration configuration;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
configuration = DeviceConfiguration.create(message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTest() {
|
||||
assertNotNull(configuration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRfAddressTest() {
|
||||
final DeviceConfiguration configuration = this.configuration;
|
||||
if (configuration != null) {
|
||||
String rfAddress = configuration.getRFAddress();
|
||||
assertEquals("0b0da3", rfAddress);
|
||||
} else {
|
||||
fail("Configuration missing");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeviceTypeTest() {
|
||||
final DeviceConfiguration configuration = this.configuration;
|
||||
if (configuration != null) {
|
||||
DeviceType deviceType = configuration.getDeviceType();
|
||||
assertEquals(DeviceType.HeatingThermostatPlus, deviceType);
|
||||
} else {
|
||||
fail("Configuration missing");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSerialNumberTest() {
|
||||
final DeviceConfiguration configuration = this.configuration;
|
||||
if (configuration != null) {
|
||||
String serialNumber = configuration.getSerialNumber();
|
||||
assertEquals("KEQ0544242", serialNumber);
|
||||
} else {
|
||||
fail("Configuration missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link FMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "F:nl.ntp.pool.org,ntp.homematic.com";
|
||||
|
||||
private final FMessage message = new FMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.F, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServer1Test() {
|
||||
String ntpServer1 = message.getNtpServer1();
|
||||
assertEquals("nl.ntp.pool.org", ntpServer1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServer2Test() {
|
||||
String ntpServer1 = message.getNtpServer2();
|
||||
assertEquals("ntp.homematic.com", ntpServer1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link HMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "H:KEQ0565026,0b5951,0113,00000000,4eed6795,01,32,12080a,070f,03,0000";
|
||||
|
||||
private final HMessage message = new HMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.H, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRFAddressTest() {
|
||||
String rfAddress = message.getRFAddress();
|
||||
assertEquals("0b5951", rfAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFirmwareTest() {
|
||||
String firmware = message.getFirmwareVersion();
|
||||
assertEquals("01.13", firmware);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionIdTest() {
|
||||
String connectionId = message.getConnectionId();
|
||||
assertEquals("4eed6795", connectionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCubeTimeStateTest() {
|
||||
String cubeTimeState = message.getCubeTimeState();
|
||||
assertEquals("03", cubeTimeState);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDateTime() {
|
||||
String[] tokens = RAW_DATA.split(Message.DELIMETER);
|
||||
|
||||
String hexDate = tokens[7];
|
||||
String hexTime = tokens[8];
|
||||
|
||||
int year = Utils.fromHex(hexDate.substring(0, 2));
|
||||
int month = Utils.fromHex(hexDate.substring(2, 4));
|
||||
int dayOfMonth = Utils.fromHex(hexDate.substring(4, 6));
|
||||
assertEquals(18, year);
|
||||
assertEquals(8, month);
|
||||
assertEquals(10, dayOfMonth);
|
||||
|
||||
int hours = Utils.fromHex(hexTime.substring(0, 2));
|
||||
int minutes = Utils.fromHex(hexTime.substring(2, 4));
|
||||
assertEquals(7, hours);
|
||||
assertEquals(15, minutes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDateTime() {
|
||||
Date dateTime = message.getDateTime();
|
||||
assertEquals(Date.from(ZonedDateTime.of(2018, 8, 10, 7, 15, 0, 0, ZoneId.systemDefault()).toInstant()),
|
||||
dateTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNTPCounterTest() {
|
||||
String ntpCounter = message.getNTPCounter();
|
||||
assertEquals("0", ntpCounter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSerialNumberTest() {
|
||||
String serialNumber = message.getSerialNumber();
|
||||
assertEquals("KEQ0565026", serialNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.Device;
|
||||
import org.openhab.binding.max.internal.device.DeviceConfiguration;
|
||||
import org.openhab.binding.max.internal.device.DeviceInformation;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.openhab.binding.max.internal.device.HeatingThermostat;
|
||||
import org.openhab.binding.max.internal.device.ShutterContact;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link LMessage}.
|
||||
*
|
||||
* @author Dominic Lerbs - Initial contribution
|
||||
* @author Christoph Weitkamp - OH2 Version and updates
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LMessageTest {
|
||||
|
||||
private static final String RAWDATA = "L:BgVPngkSEAsLhBkJEhkLJQDAAAsLhwwJEhkRJwDKAAYO8ZIJEhAGBU+kCRIQCwxuRPEaGQMmAMcACwxuQwkSGQgnAM8ACwQd5t0SGQ0oAMsA";
|
||||
|
||||
private final Map<String, Device> testDevices = new HashMap<>();
|
||||
|
||||
private final LMessage message = new LMessage(RAWDATA);
|
||||
private final List<DeviceConfiguration> configurations = new ArrayList<>();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
createTestDevices();
|
||||
}
|
||||
|
||||
private void createTestDevices() {
|
||||
addShutterContact("054f9e");
|
||||
addShutterContact("0ef192");
|
||||
addShutterContact("054fa4");
|
||||
addHeatingThermostat("0b8419");
|
||||
addHeatingThermostat("0b870c");
|
||||
addHeatingThermostat("0c6e43");
|
||||
addHeatingThermostat("041de6");
|
||||
addHeatingThermostat("0c6e44").setError(true);
|
||||
}
|
||||
|
||||
private ShutterContact addShutterContact(String rfAddress) {
|
||||
ShutterContact device = new ShutterContact(createConfiguration(DeviceType.ShutterContact, rfAddress));
|
||||
testDevices.put(rfAddress, device);
|
||||
return device;
|
||||
}
|
||||
|
||||
private HeatingThermostat addHeatingThermostat(String rfAddress) {
|
||||
HeatingThermostat device = new HeatingThermostat(createConfiguration(DeviceType.HeatingThermostat, rfAddress));
|
||||
testDevices.put(rfAddress, device);
|
||||
return device;
|
||||
}
|
||||
|
||||
private DeviceConfiguration createConfiguration(DeviceType type, String rfAddress) {
|
||||
DeviceConfiguration configuration = DeviceConfiguration
|
||||
.create(new DeviceInformation(type, "", rfAddress, "", 1));
|
||||
configurations.add(configuration);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isCorrectMessageType() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.L, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allDevicesCreatedFromMessage() {
|
||||
Collection<? extends Device> devices = message.getDevices(configurations);
|
||||
assertEquals("Incorrect number of devices created", testDevices.size(), devices.size());
|
||||
for (Device device : devices) {
|
||||
assertTrue("Unexpected device created: " + device.getRFAddress(),
|
||||
testDevices.containsKey(device.getRFAddress()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isCorrectErrorState() {
|
||||
for (Device device : message.getDevices(configurations)) {
|
||||
Device testDevice = testDevices.get(device.getRFAddress());
|
||||
assertEquals("Error set incorrectly in Device", testDevice.isError(), device.isError());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.DeviceInformation;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
import org.openhab.binding.max.internal.device.RoomInformation;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link MMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "M:00,01,VgIFAQhiYWRrYW1lcgsNowIMU3R1ZGVlcmthbWVyB7bnAwlXb29ua2FtZXIL6aIEDFN6b25qYSBLYW1lcgjDSQUGWm9sZGVyCMHWCAILDaNLRVEwNTQ0MjQyEUJhZGthbWVyIFJhZGlhdG9yAQEHtudLRVEwMTQ1MTcyFVJhZGlhdG9yIFN0dWRlZXJrYW1lcgIDDhXMTEVRMDAxNTM0MBlXYWxsIFRoZXJtb3N0YXQgV29vbmthbWVyAwEL6aJLRVE5MDE1NDMyG1BsdWcgQWRhcHRlciBNdXVydmVyd2FybWluZwMFBDNvSkVRMDM4MDg3OBdFY28gU3dpdGNoIFN0dWRlZXJrYW1lcgAEDnX2S0VRMTEwNDM4MBpXaW5kb3cgU2Vuc29yIFN0dWRlZXJrYW1lcgIBCMNJS0VRMDY0ODk0ORJUaGVybW9zdGFhdCBTem9uamEEAQjB1ktFUTA2NDkzMTIRU3R1ZGVlcmthbWVyIElybWEFAQ==";
|
||||
|
||||
private final MMessage message = new MMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.M, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTest() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
|
||||
assertEquals(8, allDevicesInformation.size());
|
||||
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(0);
|
||||
assertEquals("Badkamer Radiator", deviceInformation.getName());
|
||||
assertEquals("0B0DA3", deviceInformation.getRFAddress());
|
||||
assertEquals(1, deviceInformation.getRoomId());
|
||||
assertEquals("KEQ0544242", deviceInformation.getSerialNumber());
|
||||
assertEquals(DeviceType.HeatingThermostatPlus, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTypeTest1() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(1);
|
||||
assertEquals(DeviceType.HeatingThermostat, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTypeTest2() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(2);
|
||||
assertEquals(DeviceType.WallMountedThermostat, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTypeTest3() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(4);
|
||||
assertEquals(DeviceType.EcoSwitch, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceInformationTypeTest4() {
|
||||
List<DeviceInformation> allDevicesInformation = message.devices;
|
||||
DeviceInformation deviceInformation = allDevicesInformation.get(5);
|
||||
assertEquals(DeviceType.ShutterContact, deviceInformation.getDeviceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roomInformationTest() {
|
||||
List<RoomInformation> roomInformation = message.rooms;
|
||||
|
||||
assertEquals(5, roomInformation.size());
|
||||
assertEquals("badkamer", roomInformation.get(0).getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.exceptions.IncompleteMessageException;
|
||||
import org.openhab.binding.max.internal.exceptions.IncorrectMultilineIndexException;
|
||||
import org.openhab.binding.max.internal.exceptions.MessageIsWaitingException;
|
||||
import org.openhab.binding.max.internal.exceptions.NoMessageAvailableException;
|
||||
import org.openhab.binding.max.internal.exceptions.UnprocessableMessageException;
|
||||
import org.openhab.binding.max.internal.exceptions.UnsupportedMessageTypeException;
|
||||
|
||||
/**
|
||||
* @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageProcessorTest {
|
||||
|
||||
private final MessageProcessor processor = new MessageProcessor();
|
||||
|
||||
private void commonMessageTest(String line, Message expectedMessage) throws Exception {
|
||||
Assert.assertTrue(this.processor.addReceivedLine(line));
|
||||
Assert.assertTrue(this.processor.isMessageAvailable());
|
||||
Message message = this.processor.pull();
|
||||
Assert.assertNotNull(message);
|
||||
Assert.assertEquals(message.getClass().getName(), expectedMessage.getClass().getName());
|
||||
Assert.assertEquals(expectedMessage.getPayload(), message.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testS_Message() throws Exception {
|
||||
String rawData = "S:03,0,30";
|
||||
SMessage expectedMessage = new SMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testN_Message() throws Exception {
|
||||
String rawData = "N:Aw4VzExFUTAwMTUzNDD/";
|
||||
NMessage expectedMessage = new NMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testA_Message() throws Exception {
|
||||
String rawData = "A:";
|
||||
AMessage expectedMessage = new AMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testC_Message() throws Exception {
|
||||
String rawData = "C:0ff1bc,EQ/xvAQJEAJMRVEwNzk0MDA3";
|
||||
CMessage expectedMessage = new CMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testL_Message() throws Exception {
|
||||
String rawData = "L:Bg/xvAkAAA==";
|
||||
LMessage expectedMessage = new LMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testH_Message() throws Exception {
|
||||
String rawData = "H:KHA0007199,081dd4,0113,00000000,0d524351,10,30,0f0407,1130,03,0000";
|
||||
HMessage expectedMessage = new HMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleM_Message() throws Exception {
|
||||
String rawData = "M:00,01,VgIBAQpXb2huemltbWVyAAAAAQMQV6lMRVEwOTgyMTU2DldhbmR0aGVybW9zdGF0AQE=";
|
||||
MMessage expectedMessage = new MMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultilineM_Message() throws Exception {
|
||||
String line1_part1 = "M:00,02,";
|
||||
String line1_part2 = "VgIMAQpXb2huemltbWVyCvMrAgtUb2lsZXR0ZSBFRwrenQMOVG9pbGV0dGUgMS4gT0cK3rgECkJhZGV6aW1tZXIK3qoFDFNjaGxhZnppbW1lcgresQYDSmFuD4lCBwlDaHJpc3RpbmEPiTYIBEZsdXIPiT0KEEJhZGV6aW1tZXIgMi4gT0cPiRwLBULDvHJvD4k/DAxHw6RzdGV6aW1tZXIPiRoJC1dhc2Noa8O8Y2hlD4lXNgQHOCtLRVEwMTg4NjczCFRlcnJhc3NlAQQHMblLRVEwMTg3MTkwCEZsdXJ0w7xyAQIK8ytLRVEwMzc5NTg3C1dhbmRoZWl6dW5nAQIK9P9LRVEwMzgwMDU1DkZlbnN0ZXJoZWl6dW5nAQQHMbtLRVEwMTg3MTg4CEZsdXJ0w7xyAgQHMuxLRVEwMTg2ODg0B0ZlbnN0ZXICAQrenUtFUTA0MDY5NjIHSGVpenVuZwIBCt64S0VRMDQwNjk4OQdIZWl6dW5nAwQIFGdLRVEwMTkwNTc3B0ZlbnN0ZXIDBAc2l0tFUTAxODU5NDUIRmx1cnTDvHIEAQreqktFUTA0MDY5NzUHSGVpenVuZwQBCt8JS0VRMDQwNzA3MA5IYW5kdHVjaGVpenVuZwQEBzhTS0VRMDE4ODcxMAdGZW5zdGVyBAQIFIxLRVEwMTkwNTQzFkZlbnN0ZXIgU3RyYcOfZSByZWNodHMFAQresUtFUTA0MDY5ODIHSGVpenVuZwUEBzHmS0VRMDE4NzE0NhVGZW5zdGVyIFN0cmHDn2UgbGlua3MFAxBXqUxFUTA5ODIxNTYOV2FuZHRoZXJtb3N0YXQBBA/u1ExFUTA3OTQ3NTIIRmx1cnTDvHIGBA/v6kxFUTA3OTQ0NzQNRmVuc3RlciBsaW5rcwYED/HnTEVRMDc5Mzk2NA5GZW5zdGVyIHJlY2h0cwYBD4lCTEVRMTAwNDYwMAdIZWl6dW5nBgQP9BVMRVEwNzkzNDA2CEZsdXJ0w7xyBwQP79FMRVEwNzk0NDk5B0ZlbnN0ZXIHAQ+JNkxFUTEwMDQ1ODgHSGVpenVuZwcBD4k9TEVRMTAwNDU5NQ1IZWl6dW5nIHVudGVuCAEPiRxMRVExMDA0NTYyB0hlaXp1bmcKBA/yTUxFUTA3OTM4NjIHRmVuc3RlcgoED/F+TEVRMDc5NDA2OQhGbHVydMO8cgoBD4k/TEVRMTAwNDU5NwdIZWl6dW5nCwQP8YdMRVEwNzk0MDYwB0ZlbnN0ZXILBA/xSExFUTA3OTQxMjQIRmx1cnTDvHILBA/yVkxFUTA3OTM4NTMURmVuc3RlciBHYXJ0ZW4gbGlua3MMBA/yI0xFUTA3OTM5MDQVRmVuc3RlciBHYXJ0ZW4gcmVjaHRzDAEPiRpMRVExMDA0NTYwB0hlaXp1bmcMBA/vj0xFUTA3OTQ1NjUPRmVuc3RlciBTdHJhw59lDAQP8CtMRVEwNzk0NDA5BFTDvHIDBAgUa0tFUTAxODcwNjkNRmVuc3RlciBTZWl0ZQUEBzagS0VRMDE4NTkzNhVGZW5zdGVyIFN0cmHDn2UgbGlua3MBBA/wI0xFUTA3OTQ0MTYORmVuc3RlciBLw7xjaGUBAxBV50xFUTA5ODI2NzYOV2FuZHRoZXJtb3N0YXQFAxBW2kxFUTA5ODIzNjgOV2FuZHRoZXJtb3N0YXQEAxBV4kxFUTA5ODI2NzEOV2FuZHRoZXJtb3N0YXQHAxBZWExFUTA5ODE3MjkOV2FuZHRoZXJtb3N0YXQMAxBV6ExFUTA5ODI2NzcOV2FuZHRoZXJtb3N0YXQGAxBV40xFUTA5ODI2NzIOV2FuZHRoZXJtb3N0YXQKBAcxoEtFUTAxODcyMTYLV2FzY2hrw7xjaGUF";
|
||||
String line1 = line1_part1 + line1_part2;
|
||||
|
||||
String line2_part1 = "M:01,02,";
|
||||
String line2_part2 = "AxBV8ExFUTA5ODI2ODUOV2FuZHRoZXJtb3N0YXQJBA/v50xFUTA3OTQ0NzcNQmFsa29uZmVuc3RlcgkBD4lXTEVRMTAwNDYyMRZIZWl6dW5nIHVudGVybSBGZW5zdGVyCQQP8llMRVEwNzkzODUwDkZlbnN0ZXIgcmVjaHRzCQQP8bxMRVEwNzk0MDA3DUZlbnN0ZXIgbGlua3MJAQ+JOExFUTEwMDQ1OTAOSGVpenVuZyBCYWxrb24JBA/yLExFUTA3OTM4OTUKQmFsa29udMO8cgkED++zTEVRMDc5NDUyOQhGbHVydMO8cgkB";
|
||||
String line2 = line2_part1 + line2_part2;
|
||||
|
||||
String expectedString = line1 + line2_part2;
|
||||
MMessage expectedMessage = new MMessage(expectedString);
|
||||
|
||||
Assert.assertFalse(this.processor.addReceivedLine(line1));
|
||||
Assert.assertTrue(this.processor.addReceivedLine(line2));
|
||||
Message message = this.processor.pull();
|
||||
Assert.assertNotNull(message);
|
||||
Assert.assertEquals(message.getClass().getName(), MMessage.class.getName());
|
||||
Assert.assertEquals(expectedMessage.getPayload(), message.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongIndexOfMultilineM_Message() throws Exception {
|
||||
String line1 = "M:00,02,VgIMAQpXb2huemltbWVyCvMrAgtUb2lsZXR0ZSBFRwrenQMOVG9pbGV0dGUgMS4gT0cK3rgECkJhZGV6aW1tZXIK3qoFDFNjaGxhZnppbW1lcgresQYDSmFuD4lCBwlDaHJpc3RpbmEPiTYIBEZsdXIPiT0KEEJhZGV6aW1tZXIgMi4gT0cPiRwLBULDvHJvD4k/DAxHw6RzdGV6aW1tZXIPiRoJC1dhc2Noa8O8Y2hlD4lXNgQHOCtLRVEwMTg4NjczCFRlcnJhc3NlAQQHMblLRVEwMTg3MTkwCEZsdXJ0w7xyAQIK8ytLRVEwMzc5NTg3C1dhbmRoZWl6dW5nAQIK9P9LRVEwMzgwMDU1DkZlbnN0ZXJoZWl6dW5nAQQHMbtLRVEwMTg3MTg4CEZsdXJ0w7xyAgQHMuxLRVEwMTg2ODg0B0ZlbnN0ZXICAQrenUtFUTA0MDY5NjIHSGVpenVuZwIBCt64S0VRMDQwNjk4OQdIZWl6dW5nAwQIFGdLRVEwMTkwNTc3B0ZlbnN0ZXIDBAc2l0tFUTAxODU5NDUIRmx1cnTDvHIEAQreqktFUTA0MDY5NzUHSGVpenVuZwQBCt8JS0VRMDQwNzA3MA5IYW5kdHVjaGVpenVuZwQEBzhTS0VRMDE4ODcxMAdGZW5zdGVyBAQIFIxLRVEwMTkwNTQzFkZlbnN0ZXIgU3RyYcOfZSByZWNodHMFAQresUtFUTA0MDY5ODIHSGVpenVuZwUEBzHmS0VRMDE4NzE0NhVGZW5zdGVyIFN0cmHDn2UgbGlua3MFAxBXqUxFUTA5ODIxNTYOV2FuZHRoZXJtb3N0YXQBBA/u1ExFUTA3OTQ3NTIIRmx1cnTDvHIGBA/v6kxFUTA3OTQ0NzQNRmVuc3RlciBsaW5rcwYED/HnTEVRMDc5Mzk2NA5GZW5zdGVyIHJlY2h0cwYBD4lCTEVRMTAwNDYwMAdIZWl6dW5nBgQP9BVMRVEwNzkzNDA2CEZsdXJ0w7xyBwQP79FMRVEwNzk0NDk5B0ZlbnN0ZXIHAQ+JNkxFUTEwMDQ1ODgHSGVpenVuZwcBD4k9TEVRMTAwNDU5NQ1IZWl6dW5nIHVudGVuCAEPiRxMRVExMDA0NTYyB0hlaXp1bmcKBA/yTUxFUTA3OTM4NjIHRmVuc3RlcgoED/F+TEVRMDc5NDA2OQhGbHVydMO8cgoBD4k/TEVRMTAwNDU5NwdIZWl6dW5nCwQP8YdMRVEwNzk0MDYwB0ZlbnN0ZXILBA/xSExFUTA3OTQxMjQIRmx1cnTDvHILBA/yVkxFUTA3OTM4NTMURmVuc3RlciBHYXJ0ZW4gbGlua3MMBA/yI0xFUTA3OTM5MDQVRmVuc3RlciBHYXJ0ZW4gcmVjaHRzDAEPiRpMRVExMDA0NTYwB0hlaXp1bmcMBA/vj0xFUTA3OTQ1NjUPRmVuc3RlciBTdHJhw59lDAQP8CtMRVEwNzk0NDA5BFTDvHIDBAgUa0tFUTAxODcwNjkNRmVuc3RlciBTZWl0ZQUEBzagS0VRMDE4NTkzNhVGZW5zdGVyIFN0cmHDn2UgbGlua3MBBA/wI0xFUTA3OTQ0MTYORmVuc3RlciBLw7xjaGUBAxBV50xFUTA5ODI2NzYOV2FuZHRoZXJtb3N0YXQFAxBW2kxFUTA5ODIzNjgOV2FuZHRoZXJtb3N0YXQEAxBV4kxFUTA5ODI2NzEOV2FuZHRoZXJtb3N0YXQHAxBZWExFUTA5ODE3MjkOV2FuZHRoZXJtb3N0YXQMAxBV6ExFUTA5ODI2NzcOV2FuZHRoZXJtb3N0YXQGAxBV40xFUTA5ODI2NzIOV2FuZHRoZXJtb3N0YXQKBAcxoEtFUTAxODcyMTYLV2FzY2hrw7xjaGUF";
|
||||
String line2 = "M:02,02,AxBV8ExFUTA5ODI2ODUOV2FuZHRoZXJtb3N0YXQJBA/v50xFUTA3OTQ0NzcNQmFsa29uZmVuc3RlcgkBD4lXTEVRMTAwNDYyMRZIZWl6dW5nIHVudGVybSBGZW5zdGVyCQQP8llMRVEwNzkzODUwDkZlbnN0ZXIgcmVjaHRzCQQP8bxMRVEwNzk0MDA3DUZlbnN0ZXIgbGlua3MJAQ+JOExFUTEwMDQ1OTAOSGVpenVuZyBCYWxrb24JBA/yLExFUTA3OTM4OTUKQmFsa29udMO8cgkED++zTEVRMDc5NDUyOQhGbHVydMO8cgkB";
|
||||
|
||||
try {
|
||||
this.processor.addReceivedLine(line2);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (IncorrectMultilineIndexException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
|
||||
try {
|
||||
this.processor.reset();
|
||||
Assert.assertFalse(this.processor.addReceivedLine(line1));
|
||||
this.processor.addReceivedLine(line2);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (IncorrectMultilineIndexException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPullBeforeNewLine() throws Exception {
|
||||
String line1 = "H:KHA0007199,081dd4,0113,00000000,0d524351,10,30,0f0407,1130,03,0000";
|
||||
String line2 = "M:00,01,VgIBAQpXb2huemltbWVyAAAAAQMQV6lMRVEwOTgyMTU2DldhbmR0aGVybW9zdGF0AQE=";
|
||||
|
||||
try {
|
||||
Assert.assertTrue(this.processor.addReceivedLine(line1));
|
||||
this.processor.addReceivedLine(line2);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (MessageIsWaitingException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
|
||||
Message message = this.processor.pull();
|
||||
Assert.assertNotNull(message);
|
||||
Assert.assertEquals(message.getClass().getName(), HMessage.class.getName());
|
||||
this.processor.addReceivedLine(line2);
|
||||
message = this.processor.pull();
|
||||
Assert.assertNotNull(message);
|
||||
Assert.assertEquals(message.getClass().getName(), MMessage.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongIndicatorWhileMultilineProcessing() throws Exception {
|
||||
String line1 = "M:00,02,VgIMAQpXb2huemltbWVyCvMrAgtUb2lsZXR0ZSBFRwrenQMOVG9pbGV0dGUgMS4gT0cK3rgECkJhZGV6aW1tZXIK3qoFDFNjaGxhZnppbW1lcgresQYDSmFuD4lCBwlDaHJpc3RpbmEPiTYIBEZsdXIPiT0KEEJhZGV6aW1tZXIgMi4gT0cPiRwLBULDvHJvD4k/DAxHw6RzdGV6aW1tZXIPiRoJC1dhc2Noa8O8Y2hlD4lXNgQHOCtLRVEwMTg4NjczCFRlcnJhc3NlAQQHMblLRVEwMTg3MTkwCEZsdXJ0w7xyAQIK8ytLRVEwMzc5NTg3C1dhbmRoZWl6dW5nAQIK9P9LRVEwMzgwMDU1DkZlbnN0ZXJoZWl6dW5nAQQHMbtLRVEwMTg3MTg4CEZsdXJ0w7xyAgQHMuxLRVEwMTg2ODg0B0ZlbnN0ZXICAQrenUtFUTA0MDY5NjIHSGVpenVuZwIBCt64S0VRMDQwNjk4OQdIZWl6dW5nAwQIFGdLRVEwMTkwNTc3B0ZlbnN0ZXIDBAc2l0tFUTAxODU5NDUIRmx1cnTDvHIEAQreqktFUTA0MDY5NzUHSGVpenVuZwQBCt8JS0VRMDQwNzA3MA5IYW5kdHVjaGVpenVuZwQEBzhTS0VRMDE4ODcxMAdGZW5zdGVyBAQIFIxLRVEwMTkwNTQzFkZlbnN0ZXIgU3RyYcOfZSByZWNodHMFAQresUtFUTA0MDY5ODIHSGVpenVuZwUEBzHmS0VRMDE4NzE0NhVGZW5zdGVyIFN0cmHDn2UgbGlua3MFAxBXqUxFUTA5ODIxNTYOV2FuZHRoZXJtb3N0YXQBBA/u1ExFUTA3OTQ3NTIIRmx1cnTDvHIGBA/v6kxFUTA3OTQ0NzQNRmVuc3RlciBsaW5rcwYED/HnTEVRMDc5Mzk2NA5GZW5zdGVyIHJlY2h0cwYBD4lCTEVRMTAwNDYwMAdIZWl6dW5nBgQP9BVMRVEwNzkzNDA2CEZsdXJ0w7xyBwQP79FMRVEwNzk0NDk5B0ZlbnN0ZXIHAQ+JNkxFUTEwMDQ1ODgHSGVpenVuZwcBD4k9TEVRMTAwNDU5NQ1IZWl6dW5nIHVudGVuCAEPiRxMRVExMDA0NTYyB0hlaXp1bmcKBA/yTUxFUTA3OTM4NjIHRmVuc3RlcgoED/F+TEVRMDc5NDA2OQhGbHVydMO8cgoBD4k/TEVRMTAwNDU5NwdIZWl6dW5nCwQP8YdMRVEwNzk0MDYwB0ZlbnN0ZXILBA/xSExFUTA3OTQxMjQIRmx1cnTDvHILBA/yVkxFUTA3OTM4NTMURmVuc3RlciBHYXJ0ZW4gbGlua3MMBA/yI0xFUTA3OTM5MDQVRmVuc3RlciBHYXJ0ZW4gcmVjaHRzDAEPiRpMRVExMDA0NTYwB0hlaXp1bmcMBA/vj0xFUTA3OTQ1NjUPRmVuc3RlciBTdHJhw59lDAQP8CtMRVEwNzk0NDA5BFTDvHIDBAgUa0tFUTAxODcwNjkNRmVuc3RlciBTZWl0ZQUEBzagS0VRMDE4NTkzNhVGZW5zdGVyIFN0cmHDn2UgbGlua3MBBA/wI0xFUTA3OTQ0MTYORmVuc3RlciBLw7xjaGUBAxBV50xFUTA5ODI2NzYOV2FuZHRoZXJtb3N0YXQFAxBW2kxFUTA5ODIzNjgOV2FuZHRoZXJtb3N0YXQEAxBV4kxFUTA5ODI2NzEOV2FuZHRoZXJtb3N0YXQHAxBZWExFUTA5ODE3MjkOV2FuZHRoZXJtb3N0YXQMAxBV6ExFUTA5ODI2NzcOV2FuZHRoZXJtb3N0YXQGAxBV40xFUTA5ODI2NzIOV2FuZHRoZXJtb3N0YXQKBAcxoEtFUTAxODcyMTYLV2FzY2hrw7xjaGUF";
|
||||
String line2 = "H:KHA0007199,081dd4,0113,00000000,0d524351,10,30,0f0407,1130,03,0000";
|
||||
|
||||
try {
|
||||
Assert.assertFalse(this.processor.addReceivedLine(line1));
|
||||
this.processor.addReceivedLine(line2);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (IncompleteMessageException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownMessageIndicator() throws Exception {
|
||||
String rawData = "X:0ff1bc,EQ/xvAQJEAJMRVEwNzk0MDA3";
|
||||
|
||||
try {
|
||||
Assert.assertFalse(this.processor.addReceivedLine(rawData));
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (UnsupportedMessageTypeException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMessageAvailable() throws Exception {
|
||||
String rawData = "M:00,01,VgIBAQpXb2huemltbWVyAAAAAQMQV6lMRVEwOTgyMTU2DldhbmR0aGVybW9zdGF0AQE=";
|
||||
MMessage expectedMessage = new MMessage(rawData);
|
||||
commonMessageTest(rawData, expectedMessage);
|
||||
|
||||
try {
|
||||
this.processor.pull();
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (NoMessageAvailableException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnprocessableMessage() throws Exception {
|
||||
String line1 = "M:00"; // Some information are missing.
|
||||
|
||||
try {
|
||||
this.processor.addReceivedLine(line1);
|
||||
Assert.fail("Expected exception was not thrown.");
|
||||
} catch (UnprocessableMessageException e) {
|
||||
// OK, correct Exception was thrown
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.device.DeviceType;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link NMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NMessageTest {
|
||||
|
||||
public static final String RAW_DATA = "N:Aw4VzExFUTAwMTUzNDD/";
|
||||
// public final String rawData = "N:AQe250tFUTAxNDUxNzL/";
|
||||
|
||||
private NMessage message = new NMessage(RAW_DATA);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message).getType();
|
||||
assertEquals(MessageType.N, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRFAddressTest() {
|
||||
String rfAddress = message.getRfAddress();
|
||||
|
||||
assertEquals("0E15CC", rfAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSerialNumberTest() {
|
||||
String serialNumber = message.getSerialNumber();
|
||||
|
||||
assertEquals("LEQ0015340", serialNumber);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeviceTypeTest() {
|
||||
DeviceType deviceType = message.getDeviceType();
|
||||
|
||||
assertEquals(DeviceType.WallMountedThermostat, deviceType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link FMessage}.
|
||||
*
|
||||
* @author Marcel Verpaalen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SMessageTest {
|
||||
|
||||
private static final String RAW_DATA_1 = "S:01,0,31";
|
||||
private static final String RAW_DATA_2 = "S:00,1,00";
|
||||
|
||||
private final SMessage message1 = new SMessage(RAW_DATA_1);
|
||||
private final SMessage message2 = new SMessage(RAW_DATA_2);
|
||||
|
||||
@Test
|
||||
public void getMessageTypeTest() {
|
||||
MessageType messageType = ((Message) message1).getType();
|
||||
assertEquals(MessageType.S, messageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDutyCycleTest() {
|
||||
int dutyCycle = message1.getDutyCycle();
|
||||
assertEquals(1, dutyCycle);
|
||||
dutyCycle = message2.getDutyCycle();
|
||||
assertEquals(0, dutyCycle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCommandDiscardedTest() {
|
||||
boolean commandDiscarded = message1.isCommandDiscarded();
|
||||
assertEquals(false, commandDiscarded);
|
||||
commandDiscarded = message2.isCommandDiscarded();
|
||||
assertEquals(true, commandDiscarded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFreeMemTest() {
|
||||
int freeMemory = message1.getFreeMemorySlots();
|
||||
assertEquals(49, freeMemory);
|
||||
freeMemory = message2.getDutyCycle();
|
||||
assertEquals(0, freeMemory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.max.internal.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.max.internal.Utils;
|
||||
|
||||
/**
|
||||
* Tests cases for {@link Utils}.
|
||||
*
|
||||
* @author Andreas Heil (info@aheil.de) - Initial contribution
|
||||
* @author Marcel Verpaalen - OH2 Version and updates
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UtilsTest {
|
||||
|
||||
@Test
|
||||
public void fromHexTest() {
|
||||
assertEquals(0x00, Utils.fromHex("00"));
|
||||
assertEquals(0x01, Utils.fromHex("01"));
|
||||
assertEquals(0x1F, Utils.fromHex("1F"));
|
||||
assertEquals(0xFF, Utils.fromHex("FF"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromByteTest() {
|
||||
byte b0 = 0;
|
||||
byte b127 = 127;
|
||||
byte b128 = (byte) 128; // overflow due to
|
||||
byte bn128 = -128; // signed bytes
|
||||
byte bn1 = -1;
|
||||
|
||||
int ar0 = Utils.fromByte(b0);
|
||||
int ar127 = Utils.fromByte(b127);
|
||||
int ar128 = Utils.fromByte(b128);
|
||||
int arn128 = Utils.fromByte(bn128);
|
||||
int arn1 = Utils.fromByte(bn1);
|
||||
|
||||
assertEquals(0, ar0);
|
||||
assertEquals(127, ar127);
|
||||
assertEquals(128, ar128);
|
||||
assertEquals(128, arn128);
|
||||
assertEquals(255, arn1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toHexNoArgTest() {
|
||||
String actualResult = Utils.toHex();
|
||||
assertEquals("", actualResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toHexOneArgTest() {
|
||||
String actualResult = Utils.toHex(15);
|
||||
assertEquals("0F", actualResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toHexMultipleArgTest() {
|
||||
String actualResult = Utils.toHex(4863);
|
||||
assertEquals("12FF", actualResult);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void resolveDateTimeTest() {
|
||||
int date = Utils.fromHex("858B"); // 05-09-2011
|
||||
int time = Utils.fromHex("2E"); // 23:00
|
||||
|
||||
Date result = Utils.resolveDateTime(date, time);
|
||||
|
||||
assertEquals(5, result.getDate());
|
||||
assertEquals(9, result.getMonth());
|
||||
assertEquals(2011, result.getYear());
|
||||
assertEquals(23, result.getHours());
|
||||
assertEquals(00, result.getMinutes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBitsTest() {
|
||||
boolean b1[] = Utils.getBits(0xFF);
|
||||
|
||||
assertEquals(b1.length, 8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
assertEquals(true, b1[i]);
|
||||
}
|
||||
|
||||
boolean b2[] = Utils.getBits(0x5A);
|
||||
|
||||
assertEquals(b2.length, 8);
|
||||
assertEquals(false, b2[0]);
|
||||
assertEquals(true, b2[1]);
|
||||
assertEquals(false, b2[2]);
|
||||
assertEquals(true, b2[3]);
|
||||
assertEquals(true, b2[4]);
|
||||
assertEquals(false, b2[5]);
|
||||
assertEquals(true, b2[6]);
|
||||
assertEquals(false, b2[7]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hexStringToByteArrayTest() {
|
||||
String s = "000102030AFF";
|
||||
|
||||
byte[] result = Utils.hexStringToByteArray(s);
|
||||
|
||||
assertEquals(0, result[0] & 0xFF);
|
||||
assertEquals(1, result[1] & 0xFF);
|
||||
assertEquals(2, result[2] & 0xFF);
|
||||
assertEquals(3, result[3] & 0xFF);
|
||||
assertEquals(10, result[4] & 0xFF);
|
||||
assertEquals(255, result[5] & 0xFF);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user