added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.satel-${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-satel" description="Satel Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-serial</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.satel/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.action;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Automation action handler interface for reading Satel event log.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ISatelEventLogActions {
|
||||
|
||||
public Map<String, Object> readEvent(@Nullable Number index);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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.satel.action;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.internal.handler.SatelEventLogHandler;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Automation action handler service for reading Satel event log.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
* @see SatelEventLogHandler
|
||||
*/
|
||||
@ThingActionsScope(name = "satel")
|
||||
@NonNullByDefault
|
||||
public class SatelEventLogActions implements ThingActions, ISatelEventLogActions {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private @Nullable SatelEventLogHandler handler;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof SatelEventLogHandler) {
|
||||
this.handler = (SatelEventLogHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionReadEventLabel", description = "@text/actionReadEventDesc")
|
||||
public @ActionOutput(name = "index", type = "java.lang.Integer", label = "@text/actionOutputIndexLabel", description = "@text/actionOutputIndexDesc") @ActionOutput(name = "prev_index", type = "java.lang.Integer", label = "@text/actionOutputPrevIndexLabel", description = "@text/actionOutputPrevIndexDesc") @ActionOutput(name = "timestamp", type = "java.time.ZonedDateTime", label = "@text/actionOutputTimestampLabel", description = "@text/actionOutputTimestampDesc") @ActionOutput(name = "description", type = "java.lang.String", label = "@text/actionOutputDescriptionLabel", description = "@text/actionOutputDescriptionDesc") @ActionOutput(name = "details", type = "java.lang.String", label = "@text/actionOutputDetailsLabel", description = "@text/actionOutputDetailsDesc") Map<String, Object> readEvent(
|
||||
@ActionInput(name = "index", label = "@text/actionInputIndexLabel", description = "@text/actionInputIndexDesc") @Nullable Number index) {
|
||||
logger.debug("satel.readEvent called with input: index={}", index);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
if (handler != null) {
|
||||
handler.readEvent(index == null ? -1 : index.intValue()).ifPresent(event -> {
|
||||
result.put("index", event.getIndex());
|
||||
result.put("prev_index", event.getPrevIndex());
|
||||
result.put("timestamp", event.getTimestamp());
|
||||
result.put("description", event.getDescription());
|
||||
result.put("details", event.getDetails());
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Map<String, Object> readEvent(@Nullable ThingActions actions, @Nullable Number index) {
|
||||
return invokeMethodOf(actions).readEvent(index);
|
||||
}
|
||||
|
||||
private static ISatelEventLogActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
} else if (actions instanceof ISatelEventLogActions) {
|
||||
return (ISatelEventLogActions) actions;
|
||||
} else if (actions.getClass().getName().equals(SatelEventLogActions.class.getName())) {
|
||||
return (ISatelEventLogActions) Proxy.newProxyInstance(ISatelEventLogActions.class.getClassLoader(),
|
||||
new Class[] { ISatelEventLogActions.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 ISatelEventLogActions");
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal;
|
||||
|
||||
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;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link SatelBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "satel";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_ETHM1 = new ThingTypeUID(BINDING_ID, "ethm-1");
|
||||
public static final ThingTypeUID THING_TYPE_INTRS = new ThingTypeUID(BINDING_ID, "int-rs");
|
||||
public static final ThingTypeUID THING_TYPE_OUTPUT = new ThingTypeUID(BINDING_ID, "output");
|
||||
public static final ThingTypeUID THING_TYPE_PARTITION = new ThingTypeUID(BINDING_ID, "partition");
|
||||
public static final ThingTypeUID THING_TYPE_SHUTTER = new ThingTypeUID(BINDING_ID, "shutter");
|
||||
public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
|
||||
public static final ThingTypeUID THING_TYPE_SYSTEM = new ThingTypeUID(BINDING_ID, "system");
|
||||
public static final ThingTypeUID THING_TYPE_EVENTLOG = new ThingTypeUID(BINDING_ID, "event-log");
|
||||
public static final ThingTypeUID THING_TYPE_ATD100 = new ThingTypeUID(BINDING_ID, "atd-100");
|
||||
|
||||
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Stream.of(THING_TYPE_ETHM1, THING_TYPE_INTRS)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Physical devices
|
||||
public static final Set<ThingTypeUID> DEVICE_THING_TYPES_UIDS = Stream
|
||||
.of(THING_TYPE_OUTPUT, THING_TYPE_PARTITION, THING_TYPE_SHUTTER, THING_TYPE_ZONE, THING_TYPE_ATD100)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Virtual devices
|
||||
public static final Set<ThingTypeUID> VIRTUAL_THING_TYPES_UIDS = Stream.of(THING_TYPE_SYSTEM, THING_TYPE_EVENTLOG)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Channel types
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_LOBATT = new ChannelTypeUID("system", "low-battery");
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_NOCOMM = new ChannelTypeUID(BINDING_ID, "device_nocomm");
|
||||
|
||||
// List of all Channel ids except those covered by state enums
|
||||
public static final String CHANNEL_SHUTTER_STATE = "shutter_state";
|
||||
public static final String CHANNEL_DATE_TIME = "date_time";
|
||||
public static final String CHANNEL_SERVICE_MODE = "service_mode";
|
||||
public static final String CHANNEL_TROUBLES = "troubles";
|
||||
public static final String CHANNEL_TROUBLES_MEMORY = "troubles_memory";
|
||||
public static final String CHANNEL_ACU100_PRESENT = "acu100_present";
|
||||
public static final String CHANNEL_INTRX_PRESENT = "intrx_present";
|
||||
public static final String CHANNEL_GRADE23_SET = "grade23_set";
|
||||
public static final String CHANNEL_USER_CODE = "user_code";
|
||||
public static final String CHANNEL_INDEX = "index";
|
||||
public static final String CHANNEL_PREV_INDEX = "prev_index";
|
||||
public static final String CHANNEL_TIMESTAMP = "timestamp";
|
||||
public static final String CHANNEL_DESCRIPTION = "description";
|
||||
public static final String CHANNEL_DETAILS = "details";
|
||||
public static final String CHANNEL_TEMPERATURE = "temperature";
|
||||
}
|
||||
@@ -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.satel.internal;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.internal.config.SatelThingConfig;
|
||||
import org.openhab.binding.satel.internal.discovery.SatelDeviceDiscoveryService;
|
||||
import org.openhab.binding.satel.internal.handler.Atd100Handler;
|
||||
import org.openhab.binding.satel.internal.handler.Ethm1BridgeHandler;
|
||||
import org.openhab.binding.satel.internal.handler.IntRSBridgeHandler;
|
||||
import org.openhab.binding.satel.internal.handler.SatelBridgeHandler;
|
||||
import org.openhab.binding.satel.internal.handler.SatelEventLogHandler;
|
||||
import org.openhab.binding.satel.internal.handler.SatelOutputHandler;
|
||||
import org.openhab.binding.satel.internal.handler.SatelPartitionHandler;
|
||||
import org.openhab.binding.satel.internal.handler.SatelShutterHandler;
|
||||
import org.openhab.binding.satel.internal.handler.SatelSystemHandler;
|
||||
import org.openhab.binding.satel.internal.handler.SatelZoneHandler;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
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.openhab.core.thing.type.ThingType;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link SatelHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.satel")
|
||||
@NonNullByDefault
|
||||
public class SatelHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.of(BRIDGE_THING_TYPES_UIDS, DEVICE_THING_TYPES_UIDS, VIRTUAL_THING_TYPES_UIDS)
|
||||
.flatMap(uids -> uids.stream()).collect(Collectors.toSet());
|
||||
|
||||
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegistrations = new ConcurrentHashMap<>();
|
||||
|
||||
private @Nullable SerialPortManager serialPortManager;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
|
||||
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
||||
ThingUID effectiveUID = thingUID;
|
||||
if (effectiveUID == null) {
|
||||
if (DEVICE_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
effectiveUID = getDeviceUID(thingTypeUID, thingUID, configuration, bridgeUID);
|
||||
} else if (VIRTUAL_THING_TYPES_UIDS.contains(thingTypeUID) && bridgeUID != null) {
|
||||
effectiveUID = new ThingUID(thingTypeUID, bridgeUID.getId());
|
||||
}
|
||||
}
|
||||
return super.createThing(thingTypeUID, configuration, effectiveUID, bridgeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (Ethm1BridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
SatelBridgeHandler bridgeHandler = new Ethm1BridgeHandler((Bridge) thing);
|
||||
registerDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} else if (IntRSBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
final SerialPortManager serialPortManager = this.serialPortManager;
|
||||
if (serialPortManager != null) {
|
||||
SatelBridgeHandler bridgeHandler = new IntRSBridgeHandler((Bridge) thing, serialPortManager);
|
||||
registerDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"Unable to create INT-RS bridge thing. The serial port manager is missing.");
|
||||
}
|
||||
} else if (SatelZoneHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new SatelZoneHandler(thing);
|
||||
} else if (SatelOutputHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new SatelOutputHandler(thing);
|
||||
} else if (SatelPartitionHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new SatelPartitionHandler(thing);
|
||||
} else if (SatelShutterHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new SatelShutterHandler(thing);
|
||||
} else if (SatelSystemHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new SatelSystemHandler(thing);
|
||||
} else if (SatelEventLogHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new SatelEventLogHandler(thing);
|
||||
} else if (Atd100Handler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new Atd100Handler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
super.removeHandler(thingHandler);
|
||||
|
||||
ServiceRegistration<?> discoveryServiceRegistration = discoveryServiceRegistrations
|
||||
.remove(thingHandler.getThing().getUID());
|
||||
if (discoveryServiceRegistration != null) {
|
||||
discoveryServiceRegistration.unregister();
|
||||
}
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = null;
|
||||
}
|
||||
|
||||
private void registerDiscoveryService(SatelBridgeHandler bridgeHandler) {
|
||||
SatelDeviceDiscoveryService discoveryService = new SatelDeviceDiscoveryService(bridgeHandler,
|
||||
this::resolveThingType);
|
||||
ServiceRegistration<?> discoveryServiceRegistration = bundleContext
|
||||
.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>());
|
||||
discoveryServiceRegistrations.put(bridgeHandler.getThing().getUID(), discoveryServiceRegistration);
|
||||
}
|
||||
|
||||
private ThingUID getDeviceUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration,
|
||||
@Nullable ThingUID bridgeUID) {
|
||||
String deviceId;
|
||||
if (THING_TYPE_SHUTTER.equals(thingTypeUID)) {
|
||||
deviceId = String.format("%s-%s", configuration.get(SatelThingConfig.UP_ID),
|
||||
configuration.get(SatelThingConfig.DOWN_ID));
|
||||
} else {
|
||||
deviceId = String.valueOf(configuration.get(SatelThingConfig.ID));
|
||||
}
|
||||
return bridgeUID != null ? new ThingUID(thingTypeUID, deviceId, bridgeUID.getId())
|
||||
: new ThingUID(thingTypeUID, deviceId);
|
||||
}
|
||||
|
||||
private ThingType resolveThingType(ThingTypeUID thingTypeUID) {
|
||||
final ThingType result = super.getThingTypeByUID(thingTypeUID);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid thing type provided: " + thingTypeUID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Command class for command that clear troubles memory.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ClearTroublesCommand extends ControlCommand {
|
||||
|
||||
public static final byte COMMAND_CODE = (byte) 0x8b;
|
||||
|
||||
/**
|
||||
* Creates new command class instance.
|
||||
*
|
||||
* @param userCode code of the user on behalf the control is made
|
||||
*/
|
||||
public ClearTroublesCommand(String userCode) {
|
||||
super(COMMAND_CODE, userCodeToBytes(userCode));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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.satel.internal.command;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
|
||||
/**
|
||||
* Base class for all commands that return result code in the response.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class ControlCommand extends SatelCommandBase {
|
||||
|
||||
/**
|
||||
* Creates new command class instance.
|
||||
*
|
||||
* @param commandCode command code
|
||||
* @param payload command bytes
|
||||
*/
|
||||
public ControlCommand(byte commandCode, byte[] payload) {
|
||||
super(commandCode, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static byte[] userCodeToBytes(String userCode) {
|
||||
if (StringUtils.isEmpty(userCode)) {
|
||||
throw new IllegalArgumentException("User code is empty");
|
||||
}
|
||||
if (userCode.length() > 8) {
|
||||
throw new IllegalArgumentException("User code too long");
|
||||
}
|
||||
byte[] bytes = new byte[8];
|
||||
int digitsNbr = 2 * bytes.length;
|
||||
for (int i = 0; i < digitsNbr; ++i) {
|
||||
if (i < userCode.length()) {
|
||||
char digit = userCode.charAt(i);
|
||||
if (!Character.isDigit(digit)) {
|
||||
throw new IllegalArgumentException("User code must contain digits only");
|
||||
}
|
||||
if (i % 2 == 0) {
|
||||
bytes[i / 2] = (byte) ((digit - '0') << 4);
|
||||
} else {
|
||||
bytes[i / 2] |= (byte) (digit - '0');
|
||||
}
|
||||
} else if (i % 2 == 0) {
|
||||
bytes[i / 2] = (byte) 0xff;
|
||||
} else if (i == userCode.length()) {
|
||||
bytes[i / 2] |= 0x0f;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.command;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.event.NewStatesEvent;
|
||||
import org.openhab.binding.satel.internal.types.ControlType;
|
||||
|
||||
/**
|
||||
* Command class for commands that control (change) state of Integra objects,
|
||||
* like partitions (arm, disarm), zones (bypass, unbypass) outputs (on, off,
|
||||
* switch), etc.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ControlObjectCommand extends ControlCommand {
|
||||
|
||||
private static final long REFRESH_DELAY = 1000;
|
||||
|
||||
private ControlType controlType;
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
/**
|
||||
* Creates new command class instance for specified type of control.
|
||||
*
|
||||
* @param controlType type of controlled objects
|
||||
* @param objects bits that represents objects to control
|
||||
* @param userCode code of the user on behalf the control is made
|
||||
* @param scheduler scheduler object for scheduling refreshes
|
||||
*/
|
||||
public ControlObjectCommand(ControlType controlType, byte[] objects, String userCode,
|
||||
ScheduledExecutorService scheduler) {
|
||||
super(controlType.getControlCommand(), ArrayUtils.addAll(userCodeToBytes(userCode), objects));
|
||||
this.controlType = controlType;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponseInternal(final EventDispatcher eventDispatcher) {
|
||||
// force refresh states that might have changed
|
||||
final BitSet newStates = controlType.getControlledStates();
|
||||
if (!newStates.isEmpty()) {
|
||||
// add delay to give a chance to process sent command
|
||||
scheduler.schedule(() -> eventDispatcher.dispatchEvent(new NewStatesEvent(newStates)), REFRESH_DELAY,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.command;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.event.IntegraStateEvent;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.openhab.binding.satel.internal.types.StateType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command class for commands that return state of Integra objects, like
|
||||
* partitions (armed, alarm, entry time), zones (violation, tamper, alarm),
|
||||
* outputs and doors (opened, opened long).
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntegraStateCommand extends SatelCommandBase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IntegraStateCommand.class);
|
||||
|
||||
private StateType stateType;
|
||||
|
||||
/**
|
||||
* Constructs new command instance for specified type of state.
|
||||
*
|
||||
* @param stateType type of state
|
||||
* @param extended if <code>true</code> command will be sent as extended (256 zones or outputs)
|
||||
*/
|
||||
public IntegraStateCommand(StateType stateType, boolean extended) {
|
||||
super(stateType.getRefreshCommand(), extended);
|
||||
this.stateType = stateType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if current command is extended (256 zones/outputs)
|
||||
*/
|
||||
public boolean isExtended() {
|
||||
return Arrays.equals(EXTENDED_CMD_PAYLOAD, this.getPayload());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
// validate response
|
||||
if (response.getPayload().length != this.stateType.getPayloadLength(isExtended())) {
|
||||
logger.debug("Invalid payload length for state type {}: {}", this.stateType, response.getPayload().length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponseInternal(final EventDispatcher eventDispatcher) {
|
||||
// dispatch event
|
||||
eventDispatcher.dispatchEvent(
|
||||
new IntegraStateEvent(getResponse().getCommand(), getResponse().getPayload(), isExtended()));
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.command;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.event.IntegraStatusEvent;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command class for command that returns Integra RTC and basic status.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntegraStatusCommand extends SatelCommandBase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IntegraStatusCommand.class);
|
||||
|
||||
public static final byte COMMAND_CODE = 0x1a;
|
||||
|
||||
/**
|
||||
* Creates new command class instance.
|
||||
*/
|
||||
public IntegraStatusCommand() {
|
||||
super(COMMAND_CODE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return date and time
|
||||
*/
|
||||
public Optional<LocalDateTime> getIntegraTime() {
|
||||
// parse current date and time
|
||||
try {
|
||||
final byte[] payload = getResponse().getPayload();
|
||||
return Optional
|
||||
.of(LocalDateTime.of(bcdToInt(payload, 0, 2), bcdToInt(payload, 2, 1), bcdToInt(payload, 3, 1),
|
||||
bcdToInt(payload, 4, 1), bcdToInt(payload, 5, 1), bcdToInt(payload, 6, 1)));
|
||||
} catch (DateTimeException e) {
|
||||
logger.debug("Invalid date/time set in the system", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return first status byte
|
||||
*/
|
||||
public byte getStatusByte1() {
|
||||
return getResponse().getPayload()[7];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return second status byte
|
||||
*/
|
||||
public byte getStatusByte2() {
|
||||
return getResponse().getPayload()[8];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
if (response.getPayload().length != 9) {
|
||||
logger.debug("Invalid payload length: {}", response.getPayload().length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponseInternal(final EventDispatcher eventDispatcher) {
|
||||
// dispatch version event
|
||||
eventDispatcher.dispatchEvent(new IntegraStatusEvent(getIntegraTime(), getStatusByte1(), getStatusByte2()));
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.event.IntegraVersionEvent;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command class for command that returns Integra version and type.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntegraVersionCommand extends SatelCommandBase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IntegraVersionCommand.class);
|
||||
|
||||
public static final byte COMMAND_CODE = 0x7e;
|
||||
|
||||
/**
|
||||
* Creates new command class instance.
|
||||
*/
|
||||
public IntegraVersionCommand() {
|
||||
super(COMMAND_CODE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Integra firmware version and release date
|
||||
*/
|
||||
public String getVersion() {
|
||||
return getVersion(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Integra type
|
||||
*/
|
||||
public byte getType() {
|
||||
return getResponse().getPayload()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return firmware language
|
||||
*/
|
||||
public byte getLanguage() {
|
||||
return getResponse().getPayload()[12];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if alarm settings are stored in flash memory
|
||||
*/
|
||||
public boolean areSettingsInFlash() {
|
||||
return getResponse().getPayload()[13] == (byte) 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
// validate response
|
||||
if (response.getPayload().length != 14) {
|
||||
logger.debug("Invalid payload length: {}", response.getPayload().length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponseInternal(final EventDispatcher eventDispatcher) {
|
||||
// dispatch version event
|
||||
eventDispatcher
|
||||
.dispatchEvent(new IntegraVersionEvent(getType(), getVersion(), getLanguage(), areSettingsInFlash()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.event.ModuleVersionEvent;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command class for command that returns communication module version.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModuleVersionCommand extends SatelCommandBase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ModuleVersionCommand.class);
|
||||
|
||||
public static final byte COMMAND_CODE = 0x7c;
|
||||
|
||||
/**
|
||||
* Creates new command class instance.
|
||||
*/
|
||||
public ModuleVersionCommand() {
|
||||
super(COMMAND_CODE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return communication module firmware version and release date
|
||||
*/
|
||||
public String getVersion() {
|
||||
return getVersion(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the module supports extended (32-bit) payload for zones/outputs
|
||||
*/
|
||||
public boolean hasExtPayloadSupport() {
|
||||
return (getResponse().getPayload()[11] & 0x01) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
// validate response
|
||||
if (response.getPayload().length != 12) {
|
||||
logger.debug("Invalid payload length: {}", response.getPayload().length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponseInternal(final EventDispatcher eventDispatcher) {
|
||||
// dispatch version event
|
||||
eventDispatcher.dispatchEvent(new ModuleVersionEvent(getVersion(), hasExtPayloadSupport()));
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.event.NewStatesEvent;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command class for command that returns list of states changed since last state read.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NewStatesCommand extends SatelCommandBase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NewStatesCommand.class);
|
||||
|
||||
public static final byte COMMAND_CODE = 0x7f;
|
||||
|
||||
/**
|
||||
* Creates new command class instance.
|
||||
*
|
||||
* @param extended if <code>true</code> command will be sent as extended (256 zones or outputs)
|
||||
*/
|
||||
public NewStatesCommand(boolean extended) {
|
||||
super(COMMAND_CODE, extended);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
// validate response
|
||||
if (response.getPayload().length < 5 || response.getPayload().length > 6) {
|
||||
logger.debug("Invalid payload length: {}", response.getPayload().length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponseInternal(final EventDispatcher eventDispatcher) {
|
||||
// dispatch event
|
||||
eventDispatcher.dispatchEvent(new NewStatesEvent(getResponse().getPayload()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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.satel.internal.command;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command class for command that reads information about specific device
|
||||
* (partition, zone, user, etc).
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ReadDeviceInfoCommand extends SatelCommandBase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ReadDeviceInfoCommand.class);
|
||||
|
||||
public static final byte COMMAND_CODE = (byte) 0xee;
|
||||
|
||||
/**
|
||||
* Device type: partition, zone, expander, etc.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*
|
||||
*/
|
||||
public enum DeviceType {
|
||||
PARTITION(0),
|
||||
ZONE(1),
|
||||
USER(2),
|
||||
EXPANDER(3),
|
||||
KEYPAD(3),
|
||||
OUTPUT(4),
|
||||
ZONE_WITH_PARTITION(5, true),
|
||||
TIMER(6),
|
||||
TELEPHONE(7),
|
||||
OBJECT(15),
|
||||
PARTITION_WITH_OBJECT(16, true);
|
||||
|
||||
int code;
|
||||
boolean additionalInfo;
|
||||
|
||||
DeviceType(int code) {
|
||||
this(code, false);
|
||||
}
|
||||
|
||||
DeviceType(int code, boolean additionalInfo) {
|
||||
this.code = code;
|
||||
this.additionalInfo = additionalInfo;
|
||||
}
|
||||
|
||||
int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
boolean hasAdditionalInfo() {
|
||||
return additionalInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new command class instance to read description for given
|
||||
* parameters.
|
||||
*
|
||||
* @param deviceType type of the device
|
||||
* @param deviceNumber device number
|
||||
*/
|
||||
public ReadDeviceInfoCommand(DeviceType deviceType, int deviceNumber) {
|
||||
super(COMMAND_CODE, new byte[] { (byte) deviceType.getCode(), getDeviceNumber(deviceType, deviceNumber) });
|
||||
}
|
||||
|
||||
private static byte getDeviceNumber(DeviceType deviceType, int deviceNumber) {
|
||||
switch (deviceType) {
|
||||
case EXPANDER:
|
||||
if (deviceNumber < 128) {
|
||||
return (byte) (deviceNumber + 128);
|
||||
}
|
||||
break;
|
||||
case KEYPAD:
|
||||
if (deviceNumber < 128) {
|
||||
return (byte) (deviceNumber + 192);
|
||||
}
|
||||
break;
|
||||
case ZONE:
|
||||
case ZONE_WITH_PARTITION:
|
||||
return (byte) (deviceNumber == 256 ? 0 : deviceNumber);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (byte) deviceNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns device model or function depending on device type:
|
||||
* <ul>
|
||||
* <li>partition - partition type</li>
|
||||
* <li>zone - zone reaction</li>
|
||||
* <li>user - 0</li>
|
||||
* <li>object - 0</li>
|
||||
* <li>expander - expander model, CA-64 PP, CA-64 E, etc</li>
|
||||
* <li>LCD - LCD model, INT-KLCD, INT-KLCDR, etc</li>
|
||||
* <li>output - output function</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return kind of the device
|
||||
*/
|
||||
public int getDeviceKind() {
|
||||
return getResponse().getPayload()[2] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of the device decoded using given encoding. Encoding
|
||||
* depends on firmware language and must be specified in the binding
|
||||
* configuration.
|
||||
*
|
||||
* @param encoding encoding for the text
|
||||
* @return device name
|
||||
*/
|
||||
public String getName(Charset encoding) {
|
||||
return new String(getResponse().getPayload(), 3, 16, encoding).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional info for some types of device.
|
||||
*
|
||||
* @return additional info
|
||||
*/
|
||||
public int getAdditionalInfo() {
|
||||
final byte[] payload = getResponse().getPayload();
|
||||
return (payload.length == 20) ? (payload[19] & 0xff) : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
// validate response
|
||||
if (response.getPayload().length < 19 || response.getPayload().length > 20) {
|
||||
logger.debug("Invalid payload length: {}", response.getPayload().length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* 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.satel.internal.command;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command class for command that reads one record from the event log.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ReadEventCommand extends SatelCommandBase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
public static final byte COMMAND_CODE = (byte) 0x8c;
|
||||
|
||||
/**
|
||||
* Event class: zone alarms, partition alarms, arming, troubles, etc.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*
|
||||
*/
|
||||
public enum EventClass {
|
||||
ZONE_ALARMS("zone and tamper alarms"),
|
||||
PARTITION_ALARMS("partition and expander alarms"),
|
||||
ARMING("arming, disarming, alarm clearing"),
|
||||
BYPASSES("zone bypasses and unbypasses"),
|
||||
ACCESS_CONTROL("access control"),
|
||||
TROUBLES("troubles"),
|
||||
USER_FUNCTIONS("user functions"),
|
||||
SYSTEM_EVENTS("system events");
|
||||
|
||||
private String description;
|
||||
|
||||
EventClass(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new command class instance to read a record under given index.
|
||||
*
|
||||
* @param eventIndex index of event record to retrieve, -1 for the most recent one
|
||||
*/
|
||||
public ReadEventCommand(int eventIndex) {
|
||||
super(COMMAND_CODE, getIndexBytes(eventIndex));
|
||||
}
|
||||
|
||||
private static byte[] getIndexBytes(int index) {
|
||||
return new byte[] { (byte) ((index >> 16) & 0xff), (byte) ((index >> 8) & 0xff), (byte) (index & 0xff) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether response data contains valid event record.
|
||||
*
|
||||
* @return <code>true</code> if returned record is empty (likely the last
|
||||
* record in the log)
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return (getResponse().getPayload()[0] & 0x20) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether event record is present in the response data.
|
||||
*
|
||||
* @return <code>true</code> if event data is present in the response
|
||||
*/
|
||||
public boolean isEventPresent() {
|
||||
return (getResponse().getPayload()[0] & 0x10) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns date and time of the event.
|
||||
*
|
||||
* @return date and time of the event
|
||||
*/
|
||||
public LocalDateTime getTimestamp() {
|
||||
final byte[] payload = getResponse().getPayload();
|
||||
final int currentYear = LocalDateTime.now().getYear();
|
||||
final int yearBase = currentYear / 4;
|
||||
final int yearMarker = (payload[0] >> 6) & 0x03;
|
||||
int year = 4 * yearBase + yearMarker;
|
||||
final int minutes = ((payload[2] & 0x0f) << 8) + (payload[3] & 0xff);
|
||||
|
||||
if (year > currentYear) {
|
||||
year -= 4;
|
||||
}
|
||||
LocalDateTime result = LocalDateTime.of(year, (payload[2] >> 4) & 0x0f, payload[1] & 0x1f, minutes / 60,
|
||||
minutes % 60);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class of the event.
|
||||
*
|
||||
* @return event class of the event
|
||||
* @see EventClass
|
||||
*/
|
||||
public EventClass getEventClass() {
|
||||
final int eventClassIdx = (getResponse().getPayload()[1] >> 5) & 0x07;
|
||||
return EventClass.values()[eventClassIdx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of partion the event is about.
|
||||
*
|
||||
* @return partition number
|
||||
*/
|
||||
public int getPartition() {
|
||||
return ((getResponse().getPayload()[4] >> 3) & 0x1f) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of partition keypad related to the event.
|
||||
*
|
||||
* @return partition keypad number
|
||||
*/
|
||||
public int getPartitionKeypad() {
|
||||
return ((getResponse().getPayload()[4] >> 2) & 0x3f) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns event code the describes the event. It can be used to retrieve description text for this event.
|
||||
*
|
||||
* @return event code
|
||||
* @see ReadEventDescCommand
|
||||
*/
|
||||
public int getEventCode() {
|
||||
final byte[] payload = getResponse().getPayload();
|
||||
return ((payload[4] & 0x03) << 8) + (payload[5] & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns state restoration flag.
|
||||
*
|
||||
* @return <code>true</code> if this is restoration of some state (i.e.
|
||||
* arming and disarming have the same code but different restoration
|
||||
* flag)
|
||||
*/
|
||||
public boolean isRestore() {
|
||||
return (getResponse().getPayload()[4] & 0x04) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return source of the event.
|
||||
*
|
||||
* @return event source (zone number, user number, etc depending on event)
|
||||
*/
|
||||
public int getSource() {
|
||||
return getResponse().getPayload()[6] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns object number for the event.
|
||||
*
|
||||
* @return object number (0..7)
|
||||
*/
|
||||
public int getObject() {
|
||||
return (getResponse().getPayload()[7] >> 5) & 0x07;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user control number for the event.
|
||||
*
|
||||
* @return user control number
|
||||
*/
|
||||
public int getUserControlNumber() {
|
||||
return getResponse().getPayload()[7] & 0x1f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return index of previous event in the log. Can be used to iterate over tha event log.
|
||||
*
|
||||
* @return index of previous event record in the log
|
||||
*/
|
||||
public int getNextIndex() {
|
||||
final byte[] payload = getResponse().getPayload();
|
||||
return (payload[8] << 16) + ((payload[9] & 0xff) << 8) + (payload[10] & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current event index.
|
||||
*
|
||||
* @return index of current record echoed by communication module
|
||||
*/
|
||||
public int getCurrentIndex() {
|
||||
final byte[] payload = getResponse().getPayload();
|
||||
return (payload[11] << 16) + ((payload[12] & 0xff) << 8) + (payload[13] & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
// validate response
|
||||
if (response.getPayload().length != 14) {
|
||||
logger.debug("Invalid payload length: {}", response.getPayload().length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.command;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command class for command that reads description for specific event code.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ReadEventDescCommand extends SatelCommandBase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ReadEventDescCommand.class);
|
||||
|
||||
public static final byte COMMAND_CODE = (byte) 0x8f;
|
||||
|
||||
/**
|
||||
* Creates new command class instance to read description for given parameters.
|
||||
*
|
||||
* @param eventCode event code
|
||||
* @param restore <code>true</code> if this is restoration
|
||||
* @param longDescription <code>true</code> for long description, <code>false</code> for short one
|
||||
*/
|
||||
public ReadEventDescCommand(int eventCode, boolean restore, boolean longDescription) {
|
||||
super(COMMAND_CODE, buildPayload(eventCode, restore, longDescription));
|
||||
}
|
||||
|
||||
private static byte[] buildPayload(int eventCode, boolean restore, boolean longDescription) {
|
||||
int firstByte = 0;
|
||||
if (restore) {
|
||||
firstByte |= 0x04;
|
||||
}
|
||||
if (longDescription) {
|
||||
firstByte |= 0x80;
|
||||
}
|
||||
firstByte |= ((eventCode >> 8) & 0x03);
|
||||
return new byte[] { (byte) firstByte, (byte) (eventCode & 0xff) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of requested description, either long or short.
|
||||
*
|
||||
* @return <code>true</code> if long description has been requested
|
||||
*/
|
||||
public boolean isLongDescription() {
|
||||
return (getRequest().getPayload()[0] & 0x80) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns text of the description decoded using given encoding.
|
||||
* Encoding depends on firmware language and must be specified in the binding configuration.
|
||||
*
|
||||
* @param encoding encoding for the text
|
||||
* @return text of the description
|
||||
*/
|
||||
public String getText(Charset encoding) {
|
||||
final int length = isLongDescription() ? 46 : 16;
|
||||
return new String(getResponse().getPayload(), 5, length, encoding).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns kind of description, either short or long, depending on the request.
|
||||
*
|
||||
* @return kind of description
|
||||
*/
|
||||
public int getKind() {
|
||||
final byte[] payload = getResponse().getPayload();
|
||||
if (isLongDescription()) {
|
||||
return payload[2] & 0xff;
|
||||
} else {
|
||||
return ((payload[3] & 0xff) << 8) + (payload[4] & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
// validate response
|
||||
int properLength = isLongDescription() ? 51 : 21;
|
||||
if (response.getPayload().length != properLength) {
|
||||
logger.debug("Invalid payload length: {}", response.getPayload().length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.event.ZoneTemperatureEvent;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Command class for command that reads temperature in a zone.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ReadZoneTemperature extends SatelCommandBase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
public static final byte COMMAND_CODE = (byte) 0x7d;
|
||||
|
||||
/**
|
||||
* Creates new command class instance to read temperature in given zone.
|
||||
*
|
||||
* @param zoneNbr zone number, 1 ... 256
|
||||
*/
|
||||
public ReadZoneTemperature(int zoneNbr) {
|
||||
super(COMMAND_CODE, new byte[] { (byte) (zoneNbr == 256 ? 0 : zoneNbr) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns zone temperature (Celsius degrees).
|
||||
*
|
||||
* @return zone temperature
|
||||
*/
|
||||
public float getTemperature() {
|
||||
final byte[] payload = getResponse().getPayload();
|
||||
int temp = ((payload[1] & 0xff) << 8) + (payload[2] & 0xff);
|
||||
return (temp - 110) / 2.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isResponseValid(SatelMessage response) {
|
||||
// validate response
|
||||
if (response.getPayload().length != 3) {
|
||||
logger.debug("Invalid payload length: {}", response.getPayload().length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponseInternal(final EventDispatcher eventDispatcher) {
|
||||
// dispatch temperature event
|
||||
int zoneNbr = getResponse().getPayload()[0];
|
||||
eventDispatcher.dispatchEvent(new ZoneTemperatureEvent(zoneNbr == 0 ? 256 : zoneNbr, getTemperature()));
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
|
||||
/**
|
||||
* Interface for commands sent to communication module.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SatelCommand {
|
||||
|
||||
/**
|
||||
* State of command.
|
||||
* <ul>
|
||||
* <li>NEW - just created</li>
|
||||
* <li>ENQUEUED - currently waiting in the queue</li>
|
||||
* <li>SENT - sent to communication module</li>
|
||||
* <li>SUCCEEDED - response received and successfully handled</li>
|
||||
* <li>FAILED - either send failed or response is invalid</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*
|
||||
*/
|
||||
public enum State {
|
||||
NEW,
|
||||
ENQUEUED,
|
||||
SENT,
|
||||
SUCCEEDED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current state of the command.
|
||||
*
|
||||
* @return current state
|
||||
*/
|
||||
State getState();
|
||||
|
||||
/**
|
||||
* Sets state of the command.
|
||||
*
|
||||
* @param state new state
|
||||
*/
|
||||
void setState(State state);
|
||||
|
||||
/**
|
||||
* Returns request's message object.
|
||||
*
|
||||
* @return {@link SatelMessage} request object
|
||||
*/
|
||||
SatelMessage getRequest();
|
||||
|
||||
/**
|
||||
* Checks whether a response message matches request enclosed in this object.
|
||||
*
|
||||
* @param response response message
|
||||
* @return <code>true</code> if given response matches the request
|
||||
*/
|
||||
boolean matches(SatelMessage response);
|
||||
|
||||
/**
|
||||
* Handles response received for the command. Usually generates an event with received data.
|
||||
*
|
||||
* @param eventDispatcher event dispatcher
|
||||
* @param response response to handle
|
||||
* @return <code>true</code> if response has been successfully handled
|
||||
*/
|
||||
boolean handleResponse(EventDispatcher eventDispatcher, SatelMessage response);
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* 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.satel.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class for all command classes.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class SatelCommandBase extends SatelMessage implements SatelCommand {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SatelCommandBase.class);
|
||||
|
||||
/**
|
||||
* Used in extended (INT-RS v2.xx) command version.
|
||||
*/
|
||||
protected static final byte[] EXTENDED_CMD_PAYLOAD = { 0x00 };
|
||||
|
||||
private static final byte COMMAND_RESULT_CODE = (byte) 0xef;
|
||||
|
||||
private volatile State state = State.NEW;
|
||||
|
||||
private boolean logResponseError = true;
|
||||
|
||||
private @Nullable SatelMessage response;
|
||||
|
||||
/**
|
||||
* Creates new command basing on command code and extended command flag.
|
||||
*
|
||||
* @param commandCode command code
|
||||
* @param extended if <code>true</code> command will be sent as extended (256 zones or outputs)
|
||||
*/
|
||||
public SatelCommandBase(byte commandCode, boolean extended) {
|
||||
this(commandCode, extended ? EXTENDED_CMD_PAYLOAD : EMPTY_PAYLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance with specified command code and payload.
|
||||
*
|
||||
* @param command command code
|
||||
* @param payload command payload
|
||||
*/
|
||||
public SatelCommandBase(byte commandCode, byte[] payload) {
|
||||
super(commandCode, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(State state) {
|
||||
synchronized (this) {
|
||||
this.state = state;
|
||||
this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SatelMessage getRequest() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean matches(@Nullable SatelMessage response) {
|
||||
return response != null
|
||||
&& (response.getCommand() == getCommand() || response.getCommand() == COMMAND_RESULT_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean handleResponse(EventDispatcher eventDispatcher, SatelMessage response) {
|
||||
// if response is valid, store it for future use
|
||||
if (response.getCommand() == COMMAND_RESULT_CODE) {
|
||||
if (!hasCommandSucceeded(response)) {
|
||||
return false;
|
||||
}
|
||||
} else if (response.getCommand() != getCommand()) {
|
||||
logger.debug("Response code does not match command {}: {}", String.format("%02X", getCommand()), response);
|
||||
return false;
|
||||
} else if (!isResponseValid(response)) {
|
||||
return false;
|
||||
}
|
||||
this.response = response;
|
||||
handleResponseInternal(eventDispatcher);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ignoreResponseError() {
|
||||
this.logResponseError = false;
|
||||
}
|
||||
|
||||
protected SatelMessage getResponse() {
|
||||
final SatelMessage response = this.response;
|
||||
if (response != null) {
|
||||
return response;
|
||||
} else {
|
||||
throw new IllegalStateException("Response not yet received for command. " + this.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether given response is valid for the command.
|
||||
*
|
||||
* @param response message to check
|
||||
* @return <code>true</code> if given message is valid response for the command
|
||||
*/
|
||||
protected abstract boolean isResponseValid(SatelMessage response);
|
||||
|
||||
/**
|
||||
* Overriden in subclasses allows to execute action specific to given command (i.e. dispatch an event).
|
||||
*
|
||||
* @param eventDispatcher event dispatcher
|
||||
*/
|
||||
protected void handleResponseInternal(final EventDispatcher eventDispatcher) {
|
||||
}
|
||||
|
||||
protected static int bcdToInt(byte[] bytes, int offset, int size) {
|
||||
int result = 0, digit;
|
||||
int byteIdx = offset;
|
||||
int digits = size * 2;
|
||||
for (int i = 0; i < digits; ++i) {
|
||||
if (i % 2 == 0) {
|
||||
digit = (bytes[byteIdx] >> 4) & 0x0f;
|
||||
} else {
|
||||
digit = (bytes[byteIdx]) & 0x0f;
|
||||
byteIdx += 1;
|
||||
}
|
||||
result = result * 10 + digit;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected boolean hasCommandSucceeded(SatelMessage response) {
|
||||
// validate response message
|
||||
if (response.getCommand() != COMMAND_RESULT_CODE) {
|
||||
logger.debug("Invalid response code: {}. {}", String.format("%02X", response.getCommand()), getRequest());
|
||||
return false;
|
||||
}
|
||||
if (response.getPayload().length != 1) {
|
||||
logger.debug("Invalid payload length: {}. {}", response.getPayload().length, getRequest());
|
||||
return false;
|
||||
}
|
||||
|
||||
// check result code
|
||||
byte responseCode = response.getPayload()[0];
|
||||
String errorMsg;
|
||||
|
||||
switch (responseCode) {
|
||||
case 0:
|
||||
// success
|
||||
return true;
|
||||
case 0x01:
|
||||
errorMsg = "Requesting user code not found";
|
||||
break;
|
||||
case 0x02:
|
||||
errorMsg = "No access";
|
||||
break;
|
||||
case 0x03:
|
||||
errorMsg = "Selected user does not exist";
|
||||
break;
|
||||
case 0x04:
|
||||
errorMsg = "Selected user already exists";
|
||||
break;
|
||||
case 0x05:
|
||||
errorMsg = "Wrong code or code already exists";
|
||||
break;
|
||||
case 0x06:
|
||||
errorMsg = "Telephone code already exists";
|
||||
break;
|
||||
case 0x07:
|
||||
errorMsg = "Changed code is the same";
|
||||
break;
|
||||
case 0x08:
|
||||
errorMsg = "Other error";
|
||||
break;
|
||||
case 0x11:
|
||||
errorMsg = "Can not arm, but can use force arm";
|
||||
break;
|
||||
case 0x12:
|
||||
errorMsg = "Can not arm";
|
||||
break;
|
||||
case (byte) 0xff:
|
||||
logger.trace("Command accepted");
|
||||
return true;
|
||||
default:
|
||||
if (responseCode >= 0x80 && responseCode <= 0x8f) {
|
||||
errorMsg = String.format("Other error: %02X", responseCode);
|
||||
} else {
|
||||
errorMsg = String.format("Unknown result code: %02X", responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
if (logResponseError) {
|
||||
logger.info("{}. {}", errorMsg, getRequest());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes firmware version and release date from command payload
|
||||
*
|
||||
* @param offset starting offset in payload
|
||||
* @return decoded firmware version and release date as string
|
||||
*/
|
||||
public String getVersion(int offset) {
|
||||
// build version string
|
||||
final byte[] payload = getResponse().getPayload();
|
||||
String verStr = new String(payload, offset, 1) + "." + new String(payload, offset + 1, 2) + " "
|
||||
+ new String(payload, offset + 3, 4) + "-" + new String(payload, offset + 7, 2) + "-"
|
||||
+ new String(payload, offset + 9, 2);
|
||||
return verStr;
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.command;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Command class for command to set RTC clock.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetClockCommand extends ControlCommand {
|
||||
|
||||
public static final byte COMMAND_CODE = (byte) 0x8e;
|
||||
|
||||
private static final DateTimeFormatter DATETIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
/**
|
||||
* Creates new command class instance.
|
||||
*
|
||||
* @param dateTime date and time to set
|
||||
* @param userCode code of the user on behalf the control is made
|
||||
*/
|
||||
public SetClockCommand(LocalDateTime dateTime, String userCode) {
|
||||
super(COMMAND_CODE, ArrayUtils.addAll(userCodeToBytes(userCode), getDateTimeBytes(dateTime)));
|
||||
}
|
||||
|
||||
private static byte[] getDateTimeBytes(LocalDateTime dateTime) {
|
||||
return DATETIME_FORMAT.format(dateTime).getBytes();
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Atd100Config} contains configuration values for ATD-100 things.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Atd100Config {
|
||||
|
||||
private int id;
|
||||
private int refresh;
|
||||
|
||||
/**
|
||||
* @return zone number
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return polling interval in minutes
|
||||
*/
|
||||
public int getRefresh() {
|
||||
return refresh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Ethm1Config} contains configuration values for Satel ETHM-1 bridge.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ethm1Config extends SatelBridgeConfig {
|
||||
|
||||
public static final String HOST = "host";
|
||||
|
||||
private @Nullable String host;
|
||||
private int port;
|
||||
private @Nullable String encryptionKey;
|
||||
|
||||
/**
|
||||
* @return IP or hostname of the bridge
|
||||
*/
|
||||
public String getHost() {
|
||||
final String host = this.host;
|
||||
return host == null ? "" : host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IP port the bridge listens to
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return key used to encrypt messages
|
||||
*/
|
||||
public String getEncryptionKey() {
|
||||
final String encryptionKey = this.encryptionKey;
|
||||
return encryptionKey == null ? "" : encryptionKey;
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link IntRSConfig} contains configuration values for Satel INT-RS bridge.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntRSConfig extends SatelBridgeConfig {
|
||||
|
||||
public static final String PORT = "port";
|
||||
|
||||
private @Nullable String port;
|
||||
|
||||
/**
|
||||
* @return serial port to which the module is connected
|
||||
*/
|
||||
public @Nullable String getPort() {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.config;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link SatelBridgeConfig} contains common configuration values for Satel bridge things.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelBridgeConfig {
|
||||
|
||||
private int timeout;
|
||||
private int refresh;
|
||||
private @Nullable String userCode;
|
||||
private @Nullable String encoding;
|
||||
private boolean extCommands;
|
||||
|
||||
/**
|
||||
* @return value of timeout in milliseconds
|
||||
*/
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return polling interval in milliseconds
|
||||
*/
|
||||
public int getRefresh() {
|
||||
return refresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return user code in behalf of all the commands will be executed
|
||||
*/
|
||||
public String getUserCode() {
|
||||
final String userCode = this.userCode;
|
||||
return userCode == null ? "" : userCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return encoding for texts received from the bridge (names, descriptions)
|
||||
*/
|
||||
public Charset getEncoding() {
|
||||
final String encoding = this.encoding;
|
||||
return encoding == null ? Charset.defaultCharset() : Charset.forName(encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the module supports extended commands
|
||||
*/
|
||||
public boolean hasExtCommandsSupport() {
|
||||
return extCommands;
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SatelThingConfig} contains common configuration values for Satel devices.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelThingConfig {
|
||||
|
||||
public static final String ID = "id";
|
||||
public static final String UP_ID = "upId";
|
||||
public static final String DOWN_ID = "downId";
|
||||
|
||||
private int id;
|
||||
private int upId;
|
||||
private int downId;
|
||||
private boolean invertState;
|
||||
private boolean forceArming;
|
||||
private boolean commandOnly;
|
||||
private boolean wireless;
|
||||
|
||||
/**
|
||||
* @return device identifier
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return for a shutter: output number to control "up" direction
|
||||
*/
|
||||
public int getUpId() {
|
||||
return upId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return for a shutter: output number to control "down" direction
|
||||
*/
|
||||
public int getDownId() {
|
||||
return downId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if <code>true</code>, device's state should be inverted
|
||||
*/
|
||||
public boolean isStateInverted() {
|
||||
return invertState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if <code>true</code>, forces arming a partition
|
||||
*/
|
||||
public boolean isForceArmingEnabled() {
|
||||
return forceArming;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if <code>true</code> the thing should accept only commands, it does not update its state
|
||||
*/
|
||||
public boolean isCommandOnly() {
|
||||
return commandOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if <code>true</code> the thing is a wireless device
|
||||
*/
|
||||
public boolean isWireless() {
|
||||
return wireless;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* 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.satel.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand;
|
||||
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand.DeviceType;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.config.SatelThingConfig;
|
||||
import org.openhab.binding.satel.internal.handler.SatelBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SatelDeviceDiscoveryService} searches for available Satel
|
||||
* devices and modules connected to the API console
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
|
||||
.of(DEVICE_THING_TYPES_UIDS, VIRTUAL_THING_TYPES_UIDS).flatMap(uids -> uids.stream())
|
||||
.collect(Collectors.toSet());
|
||||
private static final int OUTPUT_FUNCTION_SHUTTER = 105;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SatelDeviceDiscoveryService.class);
|
||||
|
||||
private SatelBridgeHandler bridgeHandler;
|
||||
private Function<ThingTypeUID, ThingType> thingTypeProvider;
|
||||
private volatile boolean scanStopped;
|
||||
|
||||
public SatelDeviceDiscoveryService(SatelBridgeHandler bridgeHandler,
|
||||
Function<ThingTypeUID, ThingType> thingTypeProvider) {
|
||||
super(SUPPORTED_THING_TYPES, 60, true);
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
this.thingTypeProvider = thingTypeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
scanStopped = false;
|
||||
if (bridgeHandler.isInitialized()) {
|
||||
// add virtual things by default
|
||||
for (ThingTypeUID thingTypeUID : VIRTUAL_THING_TYPES_UIDS) {
|
||||
ThingType thingType = thingTypeProvider.apply(thingTypeUID);
|
||||
addThing(thingTypeUID, null, thingType.getLabel(), Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.KEYPAD, 8);
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.EXPANDER, 64);
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.PARTITION_WITH_OBJECT, bridgeHandler.getIntegraType().getPartitions());
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.ZONE_WITH_PARTITION, bridgeHandler.getIntegraType().getZones());
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.OUTPUT, bridgeHandler.getIntegraType().getZones());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
scanStopped = true;
|
||||
super.stopScan();
|
||||
}
|
||||
|
||||
private void scanForDevices(DeviceType deviceType, int maxNumber) {
|
||||
logger.debug("Scanning for {} started", deviceType.name());
|
||||
final Charset encoding = bridgeHandler.getEncoding();
|
||||
for (int i = 1; i <= maxNumber && !scanStopped; ++i) {
|
||||
ReadDeviceInfoCommand cmd = new ReadDeviceInfoCommand(deviceType, i);
|
||||
cmd.ignoreResponseError();
|
||||
if (bridgeHandler.sendCommand(cmd, false)) {
|
||||
String name = cmd.getName(encoding);
|
||||
int deviceKind = cmd.getDeviceKind();
|
||||
int info = cmd.getAdditionalInfo();
|
||||
logger.debug("Found device: type={}, id={}, name={}, kind/function={}, info={}", deviceType.name(), i,
|
||||
name, deviceKind, info);
|
||||
if (isDeviceAvailable(deviceType, deviceKind)) {
|
||||
addDevice(deviceType, deviceKind, i, name);
|
||||
}
|
||||
} else if (cmd.getState() != SatelCommand.State.FAILED) {
|
||||
// serious failure, disconnection or so
|
||||
scanStopped = true;
|
||||
logger.error("Unexpected failure during scan for {} using {}", deviceType.name(),
|
||||
bridgeHandler.getThing().getUID().toString());
|
||||
}
|
||||
}
|
||||
if (scanStopped) {
|
||||
logger.debug("Scanning stopped");
|
||||
} else {
|
||||
logger.debug("Scanning for {} finished", deviceType.name());
|
||||
}
|
||||
}
|
||||
|
||||
private void addDevice(DeviceType deviceType, int deviceKind, int deviceId, String deviceName) {
|
||||
ThingTypeUID thingTypeUID = getThingTypeUID(deviceType, deviceKind);
|
||||
|
||||
if (thingTypeUID == null) {
|
||||
logger.warn("Unknown device found: type={}, kind={}, name={}", deviceType.name(), deviceKind, deviceName);
|
||||
} else if (!getSupportedThingTypes().contains(thingTypeUID)) {
|
||||
logger.warn("Unsupported device: {}", thingTypeUID.toString());
|
||||
} else {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
if (THING_TYPE_SHUTTER.equals(thingTypeUID)) {
|
||||
properties.put(SatelThingConfig.UP_ID, deviceId);
|
||||
properties.put(SatelThingConfig.DOWN_ID, deviceId + 1);
|
||||
} else {
|
||||
properties.put(SatelThingConfig.ID, deviceId);
|
||||
}
|
||||
|
||||
addThing(thingTypeUID, String.valueOf(deviceId), deviceName, properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void addThing(ThingTypeUID thingTypeUID, @Nullable String deviceId, String label,
|
||||
Map<String, Object> properties) {
|
||||
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
ThingUID thingUID;
|
||||
if (deviceId == null) {
|
||||
thingUID = new ThingUID(thingTypeUID, bridgeUID.getId());
|
||||
} else {
|
||||
thingUID = new ThingUID(thingTypeUID, bridgeUID, deviceId);
|
||||
}
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
|
||||
.withBridge(bridgeUID).withLabel(label).withProperties(properties).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
|
||||
private static @Nullable ThingTypeUID getThingTypeUID(DeviceType deviceType, int deviceKind) {
|
||||
switch (deviceType) {
|
||||
case OUTPUT:
|
||||
return (deviceKind == OUTPUT_FUNCTION_SHUTTER) ? THING_TYPE_SHUTTER : THING_TYPE_OUTPUT;
|
||||
case PARTITION:
|
||||
case PARTITION_WITH_OBJECT:
|
||||
return THING_TYPE_PARTITION;
|
||||
case ZONE:
|
||||
case ZONE_WITH_PARTITION:
|
||||
return THING_TYPE_ZONE;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDeviceAvailable(DeviceType deviceType, int deviceKind) {
|
||||
switch (deviceType) {
|
||||
case OUTPUT:
|
||||
return deviceKind != 0 && deviceKind != OUTPUT_FUNCTION_SHUTTER
|
||||
&& (deviceKind != OUTPUT_FUNCTION_SHUTTER + 1);
|
||||
case PARTITION:
|
||||
case PARTITION_WITH_OBJECT:
|
||||
case ZONE:
|
||||
case ZONE_WITH_PARTITION:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.event;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Event class describing connection status to Satel module.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConnectionStatusEvent implements SatelEvent {
|
||||
|
||||
private boolean connected;
|
||||
|
||||
private @Nullable String reason;
|
||||
|
||||
/**
|
||||
* Constructs event class with given connection status.
|
||||
*
|
||||
* @param connected value describing connection status
|
||||
*/
|
||||
public ConnectionStatusEvent(boolean connected) {
|
||||
this(connected, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs event class with given connection status and disconnection reason.
|
||||
*
|
||||
* @param connected value describing connection status
|
||||
* @param reason disconnection reason
|
||||
*/
|
||||
public ConnectionStatusEvent(boolean connected, @Nullable String reason) {
|
||||
this.connected = connected;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns status of connection.
|
||||
*
|
||||
* @return a boolean value describing connection status
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns disconnection reason.
|
||||
*
|
||||
* @return optional text description in case of disconnection
|
||||
*/
|
||||
public @Nullable String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s: connected = %b, reason = %s", this.getClass().getName(), this.connected, this.reason);
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.event;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Allows distributing incoming event to all registered listeners. Listeners
|
||||
* must implement {@link SatelEventListener} interface.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EventDispatcher {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EventDispatcher.class);
|
||||
|
||||
private final Set<SatelEventListener> eventListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final Map<Class<? extends SatelEvent>, @Nullable Method> eventHandlers = Stream
|
||||
.of(SatelEventListener.class.getDeclaredMethods())
|
||||
.filter(m -> m.getParameterCount() == 1 && SatelEvent.class.isAssignableFrom(m.getParameterTypes()[0]))
|
||||
.collect(Collectors.toMap(m -> (Class<SatelEvent>) m.getParameterTypes()[0], m -> m));
|
||||
|
||||
/**
|
||||
* Add a listener for Satel events.
|
||||
*
|
||||
* @param eventListener the event listener to add.
|
||||
*/
|
||||
public void addEventListener(SatelEventListener eventListener) {
|
||||
this.eventListeners.add(eventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener for Satel events.
|
||||
*
|
||||
* @param eventListener the event listener to remove.
|
||||
*/
|
||||
public void removeEventListener(SatelEventListener eventListener) {
|
||||
this.eventListeners.remove(eventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch incoming event to all listeners.
|
||||
*
|
||||
* @param event the event to distribute.
|
||||
*/
|
||||
public void dispatchEvent(SatelEvent event) {
|
||||
final Method m = eventHandlers.get(event.getClass());
|
||||
if (m == null) {
|
||||
logger.warn("Missing event handler for event {}. Event discarded.", event.getClass().getName());
|
||||
} else {
|
||||
logger.debug("Distributing event: {}", event);
|
||||
eventListeners.forEach(listener -> {
|
||||
logger.trace("Distributing to {}", listener);
|
||||
try {
|
||||
m.invoke(listener, event);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
logger.warn("Unable to distribute {} to {}", event.getClass().getName(), listener, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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.satel.internal.event;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.types.StateType;
|
||||
|
||||
/**
|
||||
* Event class describing current state of zones/partitions/outputs/doors/troubles.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntegraStateEvent implements SatelEvent {
|
||||
|
||||
private byte command;
|
||||
private BitSet stateBits;
|
||||
private boolean extendedData;
|
||||
private Map<StateType, BitSet> stateBitsMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new event instance from given state type and state bits.
|
||||
*
|
||||
* @param command the command byte
|
||||
* @param stateBits state bits as byte array
|
||||
* @param extendedData whether state bits are for extended command
|
||||
*/
|
||||
public IntegraStateEvent(byte command, byte[] stateBits, boolean extendedData) {
|
||||
this.command = command;
|
||||
this.stateBits = BitSet.valueOf(stateBits);
|
||||
this.extendedData = extendedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether data in the event is valid for given type of state.
|
||||
*
|
||||
* @param stateType state type
|
||||
* @return <code>true</code> if this event is valid for given stae
|
||||
*/
|
||||
public boolean hasDataForState(StateType stateType) {
|
||||
return stateType.getRefreshCommand() == this.command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns state bits as {@link BitSet}.
|
||||
*
|
||||
* @param stateType type of state
|
||||
* @return bits for given state
|
||||
* @throws IllegalArgumentException when wrong state type given
|
||||
*/
|
||||
public BitSet getStateBits(StateType stateType) {
|
||||
if (!hasDataForState(stateType)) {
|
||||
throw new IllegalArgumentException("Event does not have data for " + stateType);
|
||||
}
|
||||
int bitsCount = stateType.getBytesCount(extendedData) * 8;
|
||||
// whole payload is a single state
|
||||
if (stateType.getStartByte() == 0 && bitsCount == stateBits.size()) {
|
||||
return stateBits;
|
||||
}
|
||||
return stateBitsMap.computeIfAbsent(stateType, k -> {
|
||||
int startBit = k.getStartByte() * 8;
|
||||
return stateBits.get(startBit, startBit + bitsCount);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if specified state bit is set for given state.
|
||||
*
|
||||
* @param stateType type of state
|
||||
* @param nbr state bit number
|
||||
* @return <code>true</code> if state bit is set
|
||||
*/
|
||||
public boolean isSet(StateType stateType, int nbr) {
|
||||
return getStateBits(stateType).get(nbr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of state bits that are active for given state.
|
||||
*
|
||||
* @param stateType type of state
|
||||
* @return number of active states
|
||||
*/
|
||||
public int statesSet(StateType stateType) {
|
||||
return getStateBits(stateType).cardinality();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("IntegraStateEvent: command = %02X, extended = %b, active = %s", command, extendedData,
|
||||
stateBits);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.event;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Event class describing basic status bits and current time.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntegraStatusEvent implements SatelEvent {
|
||||
|
||||
private Optional<LocalDateTime> integraTime;
|
||||
private boolean serviceMode;
|
||||
private boolean troubles;
|
||||
private boolean acu100Present;
|
||||
private boolean intRxPresent;
|
||||
private boolean troublesMemory;
|
||||
private boolean grade23Set;
|
||||
private byte integraType;
|
||||
|
||||
/**
|
||||
* Constructs new event.
|
||||
*
|
||||
* @param integraTime current Integra date and time
|
||||
* @param statusByte1 status bits, byte #1
|
||||
* @param statusByte2 status bits, byte #2
|
||||
*/
|
||||
public IntegraStatusEvent(Optional<LocalDateTime> integraTime, byte statusByte1, byte statusByte2) {
|
||||
this.integraTime = integraTime;
|
||||
this.serviceMode = (statusByte1 & 0x80) != 0;
|
||||
this.troubles = (statusByte1 & 0x40) != 0;
|
||||
this.acu100Present = (statusByte2 & 0x80) != 0;
|
||||
this.intRxPresent = (statusByte2 & 0x40) != 0;
|
||||
this.troublesMemory = (statusByte2 & 0x20) != 0;
|
||||
this.grade23Set = (statusByte2 & 0x10) != 0;
|
||||
this.integraType = (byte) (statusByte2 & 0x0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current date and time on connected Integra or <code>Optional.empty()</code> if date/time set in the
|
||||
* system is incorrect
|
||||
*/
|
||||
public Optional<LocalDateTime> getIntegraTime() {
|
||||
return integraTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if service mode is enabled
|
||||
*/
|
||||
public boolean inServiceMode() {
|
||||
return serviceMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if there are troubles in the system
|
||||
*/
|
||||
public boolean troublesPresent() {
|
||||
return troubles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the are troubles in the memory
|
||||
*/
|
||||
public boolean troublesMemory() {
|
||||
return troublesMemory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if ACU-100 module is present in the system
|
||||
*/
|
||||
public boolean isAcu100Present() {
|
||||
return acu100Present;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if INT-RX module is present in the system
|
||||
*/
|
||||
public boolean isIntRxPresent() {
|
||||
return intRxPresent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if Grade 2 or Grade 3 is enabled in Integra configuration
|
||||
*/
|
||||
public boolean isGrade23Set() {
|
||||
return grade23Set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Integra board type
|
||||
*/
|
||||
public int getIntegraType() {
|
||||
return integraType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"IntegraStatusEvent: type = %d, time = %s, service mode = %b, troubles = %b, troubles memory = %b, ACU-100 = %b, INT-RX = %b, grade 2/3 = %b",
|
||||
this.integraType, this.integraTime.map(LocalDateTime::toString).orElse("N/A"), this.serviceMode,
|
||||
this.troubles, this.troublesMemory, this.acu100Present, this.intRxPresent, this.grade23Set);
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.event;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.types.IntegraType;
|
||||
|
||||
/**
|
||||
* Event class describing type and version of connected Integra system.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntegraVersionEvent implements SatelEvent {
|
||||
|
||||
private byte type;
|
||||
private String version;
|
||||
private byte language;
|
||||
private boolean settingsInFlash;
|
||||
|
||||
/**
|
||||
* Constructs new event class.
|
||||
*
|
||||
* @param type Integra type
|
||||
* @param version string describing version number and firmware revision
|
||||
* @param language firmware language: 1 - english
|
||||
* @param settingsInFlash settings stored in flash memory
|
||||
*/
|
||||
public IntegraVersionEvent(byte type, String version, byte language, boolean settingsInFlash) {
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
this.language = language;
|
||||
this.settingsInFlash = settingsInFlash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Integra type
|
||||
* @see IntegraType
|
||||
*/
|
||||
public byte getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return firmware version and date
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return firmware language
|
||||
*/
|
||||
public byte getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if configuration is stored in flash memory
|
||||
*/
|
||||
public boolean getSettingsInflash() {
|
||||
return this.settingsInFlash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("IntegraVersionEvent: type = %d, version = %s, language = %d, settingsInFlash = %b",
|
||||
this.type & 0xFF, this.version, this.language, this.settingsInFlash);
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.event;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Event class describing version of communication module.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModuleVersionEvent implements SatelEvent {
|
||||
|
||||
private String version;
|
||||
private boolean extPayloadSupport;
|
||||
|
||||
/**
|
||||
* Constructs new event class.
|
||||
*
|
||||
* @param version string describing version number and firmware revision
|
||||
* @param extPayloadSupport the module supports extended (32-bit) payload for zones/outputs
|
||||
*/
|
||||
public ModuleVersionEvent(String version, boolean extPayloadSupport) {
|
||||
this.version = version;
|
||||
this.extPayloadSupport = extPayloadSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return firmware version and date
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the module supports extended (32-bit) payload for zones/outputs
|
||||
*/
|
||||
public boolean hasExtPayloadSupport() {
|
||||
return this.extPayloadSupport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ModuleVersionEvent: version = %s, extPayloadSupport = %b", this.version,
|
||||
this.extPayloadSupport);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.event;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Event class describing changes in Integra state since last state read.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NewStatesEvent implements SatelEvent {
|
||||
|
||||
private BitSet newStates;
|
||||
|
||||
/**
|
||||
* Constructs event class from given {@link BitSet}.
|
||||
*
|
||||
* @param newStates changed states as {@link BitSet}
|
||||
*/
|
||||
public NewStatesEvent(BitSet newStates) {
|
||||
this.newStates = newStates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs event class from given byte array.
|
||||
*
|
||||
* @param newStates changed states as byte array
|
||||
*/
|
||||
public NewStatesEvent(byte[] newStates) {
|
||||
this(BitSet.valueOf(newStates));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified state has changed since last read.
|
||||
*
|
||||
* @param nbr state number to check
|
||||
* @return <code>true</code> if state has changed
|
||||
*/
|
||||
public boolean isNew(int nbr) {
|
||||
return newStates.get(nbr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder newStatesStr = new StringBuilder();
|
||||
for (int i = this.newStates.nextSetBit(0); i >= 0; i = this.newStates.nextSetBit(i + 1)) {
|
||||
if (newStatesStr.length() > 0) {
|
||||
newStatesStr.append(",");
|
||||
}
|
||||
newStatesStr.append(String.format("%02X", i));
|
||||
}
|
||||
return String.format("NewStatesEvent: changed = [%s]", newStatesStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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.satel.internal.event;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Simple interface that all event classes must implement.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SatelEvent {
|
||||
}
|
||||
@@ -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.satel.internal.event;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Event listener interface. All classes that want to receive Satel events must
|
||||
* implement this interface.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SatelEventListener {
|
||||
|
||||
/**
|
||||
* Event handler for connection status events.
|
||||
*
|
||||
* @param event incoming event to handle
|
||||
*/
|
||||
default void incomingEvent(ConnectionStatusEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for state events.
|
||||
*
|
||||
* @param event incoming event to handle
|
||||
*/
|
||||
default void incomingEvent(IntegraStateEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for status events.
|
||||
*
|
||||
* @param event incoming event to handle
|
||||
*/
|
||||
default void incomingEvent(IntegraStatusEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for Integra version events.
|
||||
*
|
||||
* @param event incoming event to handle
|
||||
*/
|
||||
default void incomingEvent(IntegraVersionEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for communication module version events.
|
||||
*
|
||||
* @param event incoming event to handle
|
||||
*/
|
||||
default void incomingEvent(ModuleVersionEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for events with list of new states.
|
||||
*
|
||||
* @param event incoming event to handle
|
||||
*/
|
||||
default void incomingEvent(NewStatesEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for zone temperature events.
|
||||
*
|
||||
* @param event incoming event to handle
|
||||
*/
|
||||
default void incomingEvent(ZoneTemperatureEvent event) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.satel.internal.event;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Event class describing current temperature in a zone.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZoneTemperatureEvent implements SatelEvent {
|
||||
|
||||
private int zoneNbr;
|
||||
private float temperature;
|
||||
|
||||
/**
|
||||
* Constructs new event class.
|
||||
*
|
||||
* @param zoneNbr zone number
|
||||
* @param temperature current temperature in the zone
|
||||
*/
|
||||
public ZoneTemperatureEvent(int zoneNbr, float temperature) {
|
||||
this.zoneNbr = zoneNbr;
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return zone number
|
||||
*/
|
||||
public int getZoneNbr() {
|
||||
return zoneNbr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return temperature in the zone
|
||||
*/
|
||||
public float getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ZoneTemperatureEvent: zoneNbr = %d, temperature = %.1f", this.zoneNbr, this.temperature);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.internal.command.ReadZoneTemperature;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.config.Atd100Config;
|
||||
import org.openhab.binding.satel.internal.event.ZoneTemperatureEvent;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Atd100Handler} is responsible for handling commands, which are
|
||||
* sent to one of the channels of a ATD-100 device.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Atd100Handler extends WirelessChannelsHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_ATD100);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
public Atd100Handler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
|
||||
withBridgeHandlerPresent(bridgeHandler -> {
|
||||
final ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if (pollingJob == null || pollingJob.isCancelled()) {
|
||||
Atd100Config config = getConfigAs(Atd100Config.class);
|
||||
Runnable pollingCommand = () -> {
|
||||
if (bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
bridgeHandler.sendCommand(new ReadZoneTemperature(getThingConfig().getId()), true);
|
||||
}
|
||||
};
|
||||
this.pollingJob = scheduler.scheduleWithFixedDelay(pollingCommand, 0, config.getRefresh(),
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing thing handler.");
|
||||
|
||||
final ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if (pollingJob != null && !pollingJob.isCancelled()) {
|
||||
pollingJob.cancel(true);
|
||||
}
|
||||
this.pollingJob = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (CHANNEL_TEMPERATURE.equals(channelUID.getId())) {
|
||||
logger.debug("New command for {}: {}", channelUID, command);
|
||||
|
||||
if (command == RefreshType.REFRESH) {
|
||||
withBridgeHandlerPresent(bridgeHandler -> {
|
||||
bridgeHandler.sendCommand(new ReadZoneTemperature(getThingConfig().getId()), true);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
super.handleCommand(channelUID, command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingEvent(ZoneTemperatureEvent event) {
|
||||
logger.trace("Handling incoming event: {}", event);
|
||||
if (event.getZoneNbr() == getThingConfig().getId()) {
|
||||
updateState(CHANNEL_TEMPERATURE, new QuantityType<>(event.getTemperature(), SIUnits.CELSIUS));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isWirelessDevice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<SatelCommand> convertCommand(@Nullable ChannelUID channel, @Nullable Command command) {
|
||||
// no commands supported
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.THING_TYPE_ETHM1;
|
||||
import static org.openhab.binding.satel.internal.config.Ethm1Config.HOST;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.config.Ethm1Config;
|
||||
import org.openhab.binding.satel.internal.protocol.Ethm1Module;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelModule;
|
||||
import org.openhab.core.config.core.status.ConfigStatusMessage;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link Ethm1BridgeHandler} is a bridge handler for ETHM-1 communication module.
|
||||
* All {@link SatelThingHandler}s use it to receive events and execute commands.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ethm1BridgeHandler extends SatelBridgeHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_ETHM1);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Ethm1BridgeHandler.class);
|
||||
|
||||
public Ethm1BridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing handler");
|
||||
|
||||
Ethm1Config config = getConfigAs(Ethm1Config.class);
|
||||
if (StringUtils.isNotBlank(config.getHost())) {
|
||||
SatelModule satelModule = new Ethm1Module(config.getHost(), config.getPort(), config.getTimeout(),
|
||||
config.getEncryptionKey(), config.hasExtCommandsSupport());
|
||||
super.initialize(satelModule);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"Cannot connect to Satel ETHM-1 module. IP address or host name not set.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigStatusMessage> getConfigStatus() {
|
||||
// The bridge IP address to be used for checks
|
||||
String host = (String) getThing().getConfiguration().get(HOST);
|
||||
Collection<ConfigStatusMessage> configStatusMessages;
|
||||
|
||||
// Check whether an IP address is provided
|
||||
if (StringUtils.isBlank(host)) {
|
||||
configStatusMessages = Collections.singletonList(ConfigStatusMessage.Builder.error(HOST)
|
||||
.withMessageKeySuffix("hostEmpty").withArguments(HOST).build());
|
||||
} else {
|
||||
configStatusMessages = Collections.emptyList();
|
||||
}
|
||||
|
||||
return configStatusMessages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.THING_TYPE_INTRS;
|
||||
import static org.openhab.binding.satel.internal.config.IntRSConfig.PORT;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.config.IntRSConfig;
|
||||
import org.openhab.binding.satel.internal.protocol.IntRSModule;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelModule;
|
||||
import org.openhab.core.config.core.status.ConfigStatusMessage;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link IntRSBridgeHandler} is a bridge handler for INT-RS communication module.
|
||||
* All {@link SatelThingHandler}s use it to receive events and execute commands.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntRSBridgeHandler extends SatelBridgeHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_INTRS);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IntRSBridgeHandler.class);
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
public IntRSBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
|
||||
super(bridge);
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing handler");
|
||||
|
||||
final IntRSConfig config = getConfigAs(IntRSConfig.class);
|
||||
final String port = config.getPort();
|
||||
if (port != null && StringUtils.isNotBlank(port)) {
|
||||
SatelModule satelModule = new IntRSModule(port, serialPortManager, config.getTimeout(),
|
||||
config.hasExtCommandsSupport());
|
||||
super.initialize(satelModule);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"Cannot connect to Satel INT-RS module. Serial port is not set.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigStatusMessage> getConfigStatus() {
|
||||
// The bridge serial port to be used for checks
|
||||
final String port = (String) getThing().getConfiguration().get(PORT);
|
||||
Collection<ConfigStatusMessage> configStatusMessages;
|
||||
|
||||
// Check whether a serial port is provided
|
||||
if (StringUtils.isBlank(port)) {
|
||||
configStatusMessages = Collections.singletonList(ConfigStatusMessage.Builder.error(PORT)
|
||||
.withMessageKeySuffix("portEmpty").withArguments(PORT).build());
|
||||
} else {
|
||||
configStatusMessages = Collections.emptyList();
|
||||
}
|
||||
|
||||
return configStatusMessages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.ZoneId;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.internal.command.NewStatesCommand;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.config.SatelBridgeConfig;
|
||||
import org.openhab.binding.satel.internal.event.ConnectionStatusEvent;
|
||||
import org.openhab.binding.satel.internal.event.SatelEventListener;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelModule;
|
||||
import org.openhab.binding.satel.internal.types.IntegraType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SatelBridgeHandler} is base class for all bridge handlers.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class SatelBridgeHandler extends ConfigStatusBridgeHandler implements SatelEventListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SatelBridgeHandler.class);
|
||||
|
||||
private SatelBridgeConfig config = new SatelBridgeConfig();
|
||||
private @Nullable SatelModule satelModule;
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
private String userCodeOverride = "";
|
||||
private final ZoneId integraZone = ZoneId.systemDefault();
|
||||
|
||||
public SatelBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingEvent(ConnectionStatusEvent event) {
|
||||
final SatelModule satelModule = this.satelModule;
|
||||
if (satelModule != null) {
|
||||
// update bridge status and get new states from the system
|
||||
if (event.isConnected()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
satelModule.sendCommand(new NewStatesCommand(satelModule.hasExtPayloadSupport()));
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, event.getReason());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// the bridge does not support any command at the moment
|
||||
logger.debug("New command for {}: {}", channelUID, command);
|
||||
}
|
||||
|
||||
protected void initialize(final SatelModule satelModule) {
|
||||
logger.debug("Initializing bridge handler");
|
||||
|
||||
final SatelBridgeConfig config = getConfigAs(SatelBridgeConfig.class);
|
||||
this.config = config;
|
||||
this.satelModule = satelModule;
|
||||
satelModule.addEventListener(this);
|
||||
satelModule.open();
|
||||
logger.debug("Satel module opened");
|
||||
|
||||
final ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if (pollingJob == null || pollingJob.isCancelled()) {
|
||||
Runnable pollingCommand = () -> {
|
||||
if (!satelModule.isInitialized()) {
|
||||
logger.debug("Module not initialized yet, skipping refresh");
|
||||
return;
|
||||
}
|
||||
|
||||
// get list of states that have changed
|
||||
logger.trace("Sending 'get new states' command");
|
||||
satelModule.sendCommand(new NewStatesCommand(satelModule.hasExtPayloadSupport()));
|
||||
};
|
||||
this.pollingJob = scheduler.scheduleWithFixedDelay(pollingCommand, 0, config.getRefresh(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing bridge handler.");
|
||||
|
||||
final ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if (pollingJob != null && !pollingJob.isCancelled()) {
|
||||
pollingJob.cancel(true);
|
||||
this.pollingJob = null;
|
||||
}
|
||||
|
||||
final SatelModule satelModule = this.satelModule;
|
||||
if (satelModule != null) {
|
||||
satelModule.close();
|
||||
this.satelModule = null;
|
||||
logger.debug("Satel module closed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds given listener to list of event receivers.
|
||||
*
|
||||
* @param listener listener object to add
|
||||
*/
|
||||
public void addEventListener(SatelEventListener listener) {
|
||||
final SatelModule satelModule = this.satelModule;
|
||||
if (satelModule != null) {
|
||||
satelModule.addEventListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given listener from list of event receivers.
|
||||
*
|
||||
* @param listener listener object to remove
|
||||
*/
|
||||
public void removeEventListener(SatelEventListener listener) {
|
||||
final SatelModule satelModule = this.satelModule;
|
||||
if (satelModule != null) {
|
||||
satelModule.removeEventListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
final SatelModule satelModule = this.satelModule;
|
||||
return satelModule != null && satelModule.isInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return type of Integra system
|
||||
* @see IntegraType
|
||||
*/
|
||||
public IntegraType getIntegraType() {
|
||||
final SatelModule satelModule = this.satelModule;
|
||||
return satelModule != null ? satelModule.getIntegraType() : IntegraType.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current user code, either from the configuration or set later using {@link #setUserCode(String)}
|
||||
*/
|
||||
public String getUserCode() {
|
||||
if (StringUtils.isNotEmpty(userCodeOverride)) {
|
||||
return userCodeOverride;
|
||||
} else {
|
||||
return config.getUserCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userCode new use code that overrides the one in the configuration
|
||||
*/
|
||||
public void setUserCode(String userCode) {
|
||||
this.userCodeOverride = userCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return encoding for texts
|
||||
*/
|
||||
public Charset getEncoding() {
|
||||
try {
|
||||
return config.getEncoding();
|
||||
} catch (Exception e) {
|
||||
logger.info("Invalid or unsupported encoding configured for {}", getThing().getUID());
|
||||
return Charset.defaultCharset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return zone for Integra date and time values
|
||||
*/
|
||||
public ZoneId getZoneId() {
|
||||
return integraZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends given command to communication module.
|
||||
*
|
||||
* @param command a command to send
|
||||
* @param async if <code>false</code> method waits for the response
|
||||
* @return <code>true</code> if send succeeded
|
||||
*/
|
||||
public boolean sendCommand(SatelCommand command, boolean async) {
|
||||
final SatelModule satelModule = this.satelModule;
|
||||
if (satelModule == null) {
|
||||
return false;
|
||||
}
|
||||
if (async) {
|
||||
return satelModule.sendCommand(command);
|
||||
} else if (!satelModule.sendCommand(command, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean interrupted = false;
|
||||
while (!interrupted) {
|
||||
// wait for command state change
|
||||
try {
|
||||
synchronized (command) {
|
||||
command.wait(satelModule.getTimeout());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// ignore, we will leave the loop on next interruption state check
|
||||
interrupted = true;
|
||||
}
|
||||
// check current state
|
||||
switch (command.getState()) {
|
||||
case SUCCEEDED:
|
||||
return true;
|
||||
case FAILED:
|
||||
return false;
|
||||
default:
|
||||
// wait for next change unless interrupted
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.action.SatelEventLogActions;
|
||||
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand;
|
||||
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand.DeviceType;
|
||||
import org.openhab.binding.satel.internal.command.ReadEventCommand;
|
||||
import org.openhab.binding.satel.internal.command.ReadEventDescCommand;
|
||||
import org.openhab.binding.satel.internal.event.ConnectionStatusEvent;
|
||||
import org.openhab.binding.satel.internal.types.IntegraType;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SatelEventLogHandler} is responsible for handling commands, which are
|
||||
* sent to one of the event log channels.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelEventLogHandler extends SatelThingHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_EVENTLOG);
|
||||
|
||||
private static final String NOT_AVAILABLE_TEXT = "N/A";
|
||||
private static final String DETAILS_SEPARATOR = ", ";
|
||||
private static final long CACHE_CLEAR_INTERVAL = TimeUnit.MINUTES.toMillis(30);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SatelEventLogHandler.class);
|
||||
private final Map<String, @Nullable EventDescriptionCacheEntry> eventDescriptions = new ConcurrentHashMap<>();
|
||||
private final Map<String, @Nullable String> deviceNameCache = new ConcurrentHashMap<>();
|
||||
private @Nullable ScheduledFuture<?> cacheExpirationJob;
|
||||
private Charset encoding = Charset.defaultCharset();
|
||||
|
||||
/**
|
||||
* Represents single record of the event log.
|
||||
*
|
||||
* @author Krzysztof Goworek
|
||||
*
|
||||
*/
|
||||
public static class EventLogEntry {
|
||||
|
||||
private final int index;
|
||||
private final int prevIndex;
|
||||
private final ZonedDateTime timestamp;
|
||||
private final String description;
|
||||
private final String details;
|
||||
|
||||
private EventLogEntry(int index, int prevIndex, ZonedDateTime timestamp, String description, String details) {
|
||||
this.index = index;
|
||||
this.prevIndex = prevIndex;
|
||||
this.timestamp = timestamp;
|
||||
this.description = description;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of this record entry
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of the previous record entry in the log
|
||||
*/
|
||||
public int getPrevIndex() {
|
||||
return prevIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return date and time when the event occurred
|
||||
*/
|
||||
public ZonedDateTime getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return description of the event
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return details about zones, partitions, users, etc
|
||||
*/
|
||||
public String getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EventLogEntry [index=" + index + ", prevIndex=" + prevIndex + ", timestamp=" + timestamp
|
||||
+ ", description=" + description + ", details=" + details + "]";
|
||||
}
|
||||
}
|
||||
|
||||
public SatelEventLogHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
|
||||
withBridgeHandlerPresent(bridgeHandler -> {
|
||||
this.encoding = bridgeHandler.getEncoding();
|
||||
});
|
||||
|
||||
final ScheduledFuture<?> cacheExpirationJob = this.cacheExpirationJob;
|
||||
if (cacheExpirationJob == null || cacheExpirationJob.isCancelled()) {
|
||||
// for simplicity all cache entries are cleared every 30 minutes
|
||||
this.cacheExpirationJob = scheduler.scheduleWithFixedDelay(deviceNameCache::clear, CACHE_CLEAR_INTERVAL,
|
||||
CACHE_CLEAR_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
|
||||
final ScheduledFuture<?> cacheExpirationJob = this.cacheExpirationJob;
|
||||
if (cacheExpirationJob != null && !cacheExpirationJob.isCancelled()) {
|
||||
cacheExpirationJob.cancel(true);
|
||||
}
|
||||
this.cacheExpirationJob = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("New command for {}: {}", channelUID, command);
|
||||
|
||||
if (CHANNEL_INDEX.equals(channelUID.getId()) && command instanceof DecimalType) {
|
||||
int eventIndex = ((DecimalType) command).intValue();
|
||||
withBridgeHandlerPresent(bridgeHandler -> readEvent(eventIndex).ifPresent(entry -> {
|
||||
// update items
|
||||
updateState(CHANNEL_INDEX, new DecimalType(entry.getIndex()));
|
||||
updateState(CHANNEL_PREV_INDEX, new DecimalType(entry.getPrevIndex()));
|
||||
updateState(CHANNEL_TIMESTAMP, new DateTimeType(entry.getTimestamp()));
|
||||
updateState(CHANNEL_DESCRIPTION, new StringType(entry.getDescription()));
|
||||
updateState(CHANNEL_DETAILS, new StringType(entry.getDetails()));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingEvent(ConnectionStatusEvent event) {
|
||||
logger.trace("Handling incoming event: {}", event);
|
||||
// we have just connected, change thing's status
|
||||
if (event.isConnected()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(SatelEventLogActions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one record from the event log.
|
||||
*
|
||||
* @param eventIndex record index
|
||||
* @return record data or {@linkplain Optional#empty()} if there is no record under given index
|
||||
*/
|
||||
public Optional<EventLogEntry> readEvent(int eventIndex) {
|
||||
return getEventDescription(eventIndex).flatMap(eventDesc -> {
|
||||
ReadEventCommand readEventCmd = eventDesc.readEventCmd;
|
||||
int currentIndex = readEventCmd.getCurrentIndex();
|
||||
String eventText = eventDesc.getText();
|
||||
boolean upperZone = getBridgeHandler().getIntegraType() == IntegraType.I256_PLUS
|
||||
&& readEventCmd.getUserControlNumber() > 0;
|
||||
String eventDetails;
|
||||
|
||||
switch (eventDesc.getKind()) {
|
||||
case 0:
|
||||
eventDetails = "";
|
||||
break;
|
||||
case 1:
|
||||
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
|
||||
+ DETAILS_SEPARATOR + getZoneExpanderKeypadDescription(readEventCmd.getSource(), upperZone);
|
||||
break;
|
||||
case 2:
|
||||
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
|
||||
+ DETAILS_SEPARATOR + getUserDescription(readEventCmd.getSource());
|
||||
break;
|
||||
case 3:
|
||||
eventDetails = getDeviceDescription(DeviceType.EXPANDER, readEventCmd.getPartitionKeypad())
|
||||
+ DETAILS_SEPARATOR + getUserDescription(readEventCmd.getSource());
|
||||
break;
|
||||
case 4:
|
||||
eventDetails = getZoneExpanderKeypadDescription(readEventCmd.getSource(), upperZone);
|
||||
break;
|
||||
case 5:
|
||||
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition());
|
||||
break;
|
||||
case 6:
|
||||
eventDetails = getDeviceDescription(DeviceType.KEYPAD, readEventCmd.getPartition())
|
||||
+ DETAILS_SEPARATOR + getUserDescription(readEventCmd.getSource());
|
||||
break;
|
||||
case 7:
|
||||
eventDetails = getUserDescription(readEventCmd.getSource());
|
||||
break;
|
||||
case 8:
|
||||
eventDetails = getDeviceDescription(DeviceType.EXPANDER, readEventCmd.getSource());
|
||||
break;
|
||||
case 9:
|
||||
eventDetails = getTelephoneDescription(readEventCmd.getSource());
|
||||
break;
|
||||
case 11:
|
||||
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
|
||||
+ DETAILS_SEPARATOR + getDataBusDescription(readEventCmd.getSource());
|
||||
break;
|
||||
case 12:
|
||||
if (readEventCmd.getSource() <= getBridgeHandler().getIntegraType().getOnMainboard()) {
|
||||
eventDetails = getOutputExpanderDescription(readEventCmd.getSource(), upperZone);
|
||||
} else {
|
||||
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
|
||||
+ DETAILS_SEPARATOR + getOutputExpanderDescription(readEventCmd.getSource(), upperZone);
|
||||
}
|
||||
break;
|
||||
case 13:
|
||||
if (readEventCmd.getSource() <= 128) {
|
||||
eventDetails = getOutputExpanderDescription(readEventCmd.getSource(), upperZone);
|
||||
} else {
|
||||
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
|
||||
+ DETAILS_SEPARATOR + getOutputExpanderDescription(readEventCmd.getSource(), upperZone);
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
eventDetails = getTelephoneDescription(readEventCmd.getPartition()) + DETAILS_SEPARATOR
|
||||
+ getUserDescription(readEventCmd.getSource());
|
||||
break;
|
||||
case 15:
|
||||
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
|
||||
+ DETAILS_SEPARATOR + getDeviceDescription(DeviceType.TIMER, readEventCmd.getSource());
|
||||
break;
|
||||
case 31:
|
||||
// this description consists of two records, so we must read additional record from the log
|
||||
eventDetails = "." + readEventCmd.getSource() + "."
|
||||
+ (readEventCmd.getObject() * 32 + readEventCmd.getUserControlNumber());
|
||||
Optional<EventDescription> eventDescNext = getEventDescription(readEventCmd.getNextIndex());
|
||||
if (!eventDescNext.isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
final EventDescription eventDescNextItem = eventDescNext.get();
|
||||
if (eventDescNextItem.getKind() != 30) {
|
||||
logger.info("Unexpected event record kind {} at index {}", eventDescNextItem.getKind(),
|
||||
readEventCmd.getNextIndex());
|
||||
return Optional.empty();
|
||||
}
|
||||
readEventCmd = eventDescNextItem.readEventCmd;
|
||||
eventText = eventDescNextItem.getText();
|
||||
eventDetails = getDeviceDescription(DeviceType.KEYPAD, readEventCmd.getPartition())
|
||||
+ DETAILS_SEPARATOR + "ip: " + readEventCmd.getSource() + "."
|
||||
+ (readEventCmd.getObject() * 32 + readEventCmd.getUserControlNumber()) + eventDetails;
|
||||
break;
|
||||
case 32:
|
||||
eventDetails = getDeviceDescription(DeviceType.PARTITION, readEventCmd.getPartition())
|
||||
+ DETAILS_SEPARATOR + getDeviceDescription(DeviceType.ZONE, readEventCmd.getSource());
|
||||
break;
|
||||
default:
|
||||
logger.info("Unsupported device kind code {} at index {}", eventDesc.getKind(),
|
||||
readEventCmd.getCurrentIndex());
|
||||
eventDetails = String.join(DETAILS_SEPARATOR, "kind=" + eventDesc.getKind(),
|
||||
"partition=" + readEventCmd.getPartition(), "source=" + readEventCmd.getSource(),
|
||||
"object=" + readEventCmd.getObject(), "ucn=" + readEventCmd.getUserControlNumber());
|
||||
}
|
||||
|
||||
return Optional.of(new EventLogEntry(currentIndex, readEventCmd.getNextIndex(),
|
||||
readEventCmd.getTimestamp().atZone(getBridgeHandler().getZoneId()), eventText, eventDetails));
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<EventDescription> getEventDescription(int eventIndex) {
|
||||
ReadEventCommand readEventCmd = new ReadEventCommand(eventIndex);
|
||||
if (!getBridgeHandler().sendCommand(readEventCmd, false)) {
|
||||
logger.info("Unable to read event record for given index: {}", eventIndex);
|
||||
return Optional.empty();
|
||||
} else if (readEventCmd.isEmpty()) {
|
||||
logger.info("No record under given index: {}", eventIndex);
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(readEventDescription(readEventCmd));
|
||||
}
|
||||
}
|
||||
|
||||
private static class EventDescriptionCacheEntry {
|
||||
private final String eventText;
|
||||
private final int descKind;
|
||||
|
||||
EventDescriptionCacheEntry(String eventText, int descKind) {
|
||||
this.eventText = eventText;
|
||||
this.descKind = descKind;
|
||||
}
|
||||
|
||||
String getText() {
|
||||
return eventText;
|
||||
}
|
||||
|
||||
int getKind() {
|
||||
return descKind;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EventDescription extends EventDescriptionCacheEntry {
|
||||
private final ReadEventCommand readEventCmd;
|
||||
|
||||
EventDescription(ReadEventCommand readEventCmd, String eventText, int descKind) {
|
||||
super(eventText, descKind);
|
||||
this.readEventCmd = readEventCmd;
|
||||
}
|
||||
}
|
||||
|
||||
private EventDescription readEventDescription(ReadEventCommand readEventCmd) {
|
||||
int eventCode = readEventCmd.getEventCode();
|
||||
boolean restore = readEventCmd.isRestore();
|
||||
String mapKey = String.format("%d_%b", eventCode, restore);
|
||||
EventDescriptionCacheEntry mapValue = eventDescriptions.computeIfAbsent(mapKey, k -> {
|
||||
ReadEventDescCommand cmd = new ReadEventDescCommand(eventCode, restore, true);
|
||||
if (!getBridgeHandler().sendCommand(cmd, false)) {
|
||||
logger.info("Unable to read event description: {}, {}", eventCode, restore);
|
||||
return null;
|
||||
}
|
||||
return new EventDescriptionCacheEntry(cmd.getText(encoding), cmd.getKind());
|
||||
});
|
||||
if (mapValue == null) {
|
||||
return new EventDescription(readEventCmd, NOT_AVAILABLE_TEXT, 0);
|
||||
} else {
|
||||
return new EventDescription(readEventCmd, mapValue.getText(), mapValue.getKind());
|
||||
}
|
||||
}
|
||||
|
||||
private String getOutputExpanderDescription(int deviceNumber, boolean upperOutput) {
|
||||
if (deviceNumber == 0) {
|
||||
return "mainboard";
|
||||
} else if (deviceNumber <= 128) {
|
||||
return getDeviceDescription(DeviceType.OUTPUT, upperOutput ? 128 + deviceNumber : deviceNumber);
|
||||
} else if (deviceNumber <= 192) {
|
||||
return getDeviceDescription(DeviceType.EXPANDER, deviceNumber);
|
||||
} else {
|
||||
return "invalid output|expander device: " + deviceNumber;
|
||||
}
|
||||
}
|
||||
|
||||
private String getZoneExpanderKeypadDescription(int deviceNumber, boolean upperZone) {
|
||||
if (deviceNumber == 0) {
|
||||
return "mainboard";
|
||||
} else if (deviceNumber <= 128) {
|
||||
return getDeviceDescription(DeviceType.ZONE, upperZone ? 128 + deviceNumber : deviceNumber);
|
||||
} else if (deviceNumber <= 192) {
|
||||
return getDeviceDescription(DeviceType.EXPANDER, deviceNumber);
|
||||
} else {
|
||||
return getDeviceDescription(DeviceType.KEYPAD, deviceNumber);
|
||||
}
|
||||
}
|
||||
|
||||
private String getUserDescription(int deviceNumber) {
|
||||
return deviceNumber == 0 ? "user: unknown" : getDeviceDescription(DeviceType.USER, deviceNumber);
|
||||
}
|
||||
|
||||
private String getDataBusDescription(int deviceNumber) {
|
||||
return "data bus: " + deviceNumber;
|
||||
}
|
||||
|
||||
private String getTelephoneDescription(int deviceNumber) {
|
||||
return deviceNumber == 0 ? "telephone: unknown" : getDeviceDescription(DeviceType.TELEPHONE, deviceNumber);
|
||||
}
|
||||
|
||||
private String getDeviceDescription(DeviceType deviceType, int deviceNumber) {
|
||||
return String.format("%s: %s", deviceType.name().toLowerCase(), readDeviceName(deviceType, deviceNumber));
|
||||
}
|
||||
|
||||
private String readDeviceName(DeviceType deviceType, int deviceNumber) {
|
||||
String cacheKey = String.format("%s_%d", deviceType, deviceNumber);
|
||||
String result = deviceNameCache.computeIfAbsent(cacheKey, k -> {
|
||||
ReadDeviceInfoCommand cmd = new ReadDeviceInfoCommand(deviceType, deviceNumber);
|
||||
if (!getBridgeHandler().sendCommand(cmd, false)) {
|
||||
logger.info("Unable to read device info: {}, {}", deviceType, deviceNumber);
|
||||
return null;
|
||||
}
|
||||
return cmd.getName(encoding);
|
||||
});
|
||||
return result == null ? NOT_AVAILABLE_TEXT : result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.THING_TYPE_OUTPUT;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.command.ControlObjectCommand;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.types.OutputControl;
|
||||
import org.openhab.binding.satel.internal.types.OutputState;
|
||||
import org.openhab.binding.satel.internal.types.StateType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The {@link SatelOutputHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels of an output device.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelOutputHandler extends WirelessChannelsHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_OUTPUT);
|
||||
|
||||
public SatelOutputHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<SatelCommand> convertCommand(ChannelUID channel, Command command) {
|
||||
if (command instanceof OnOffType && getStateType(channel.getId()) == OutputState.STATE) {
|
||||
final SatelBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
boolean switchOn = (command == OnOffType.ON);
|
||||
int size = bridgeHandler.getIntegraType().hasExtPayload() ? 32 : 16;
|
||||
byte[] outputs = getObjectBitset(size, getThingConfig().getId());
|
||||
boolean newState = switchOn ^ getThingConfig().isStateInverted();
|
||||
return Optional.of(new ControlObjectCommand(newState ? OutputControl.ON : OutputControl.OFF, outputs,
|
||||
bridgeHandler.getUserCode(), scheduler));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StateType getStateType(String channelId) {
|
||||
StateType result = super.getStateType(channelId);
|
||||
if (result == StateType.NONE) {
|
||||
result = OutputState.valueOf(channelId.toUpperCase());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.THING_TYPE_PARTITION;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.command.ControlObjectCommand;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.types.PartitionControl;
|
||||
import org.openhab.binding.satel.internal.types.PartitionState;
|
||||
import org.openhab.binding.satel.internal.types.StateType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The {@link SatelPartitionHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels of a partition device.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelPartitionHandler extends SatelStateThingHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_PARTITION);
|
||||
|
||||
public SatelPartitionHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StateType getStateType(String channelId) {
|
||||
return PartitionState.valueOf(channelId.toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<SatelCommand> convertCommand(ChannelUID channel, Command command) {
|
||||
if (command instanceof OnOffType) {
|
||||
boolean switchOn = (command == OnOffType.ON);
|
||||
StateType stateType = getStateType(channel.getId());
|
||||
byte[] partitions = getObjectBitset(4, getThingConfig().getId());
|
||||
boolean forceArm = getThingConfig().isForceArmingEnabled();
|
||||
PartitionControl action = null;
|
||||
switch ((PartitionState) stateType) {
|
||||
// clear alarms on OFF command
|
||||
case ALARM:
|
||||
case ALARM_MEMORY:
|
||||
case FIRE_ALARM:
|
||||
case FIRE_ALARM_MEMORY:
|
||||
case VERIFIED_ALARMS:
|
||||
case WARNING_ALARMS:
|
||||
action = switchOn ? null : PartitionControl.CLEAR_ALARM;
|
||||
break;
|
||||
|
||||
// arm or disarm, depending on command
|
||||
case ARMED:
|
||||
case REALLY_ARMED:
|
||||
action = switchOn ? (forceArm ? PartitionControl.FORCE_ARM_MODE_0 : PartitionControl.ARM_MODE_0)
|
||||
: PartitionControl.DISARM;
|
||||
break;
|
||||
case ARMED_MODE_1:
|
||||
action = switchOn ? (forceArm ? PartitionControl.FORCE_ARM_MODE_1 : PartitionControl.ARM_MODE_1)
|
||||
: PartitionControl.DISARM;
|
||||
break;
|
||||
case ARMED_MODE_2:
|
||||
action = switchOn ? (forceArm ? PartitionControl.FORCE_ARM_MODE_2 : PartitionControl.ARM_MODE_2)
|
||||
: PartitionControl.DISARM;
|
||||
break;
|
||||
case ARMED_MODE_3:
|
||||
action = switchOn ? (forceArm ? PartitionControl.FORCE_ARM_MODE_3 : PartitionControl.ARM_MODE_3)
|
||||
: PartitionControl.DISARM;
|
||||
break;
|
||||
|
||||
// do nothing for other types of state
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (action != null) {
|
||||
return Optional
|
||||
.of(new ControlObjectCommand(action, partitions, getBridgeHandler().getUserCode(), scheduler));
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.command.ControlObjectCommand;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.event.IntegraStateEvent;
|
||||
import org.openhab.binding.satel.internal.types.OutputControl;
|
||||
import org.openhab.binding.satel.internal.types.OutputState;
|
||||
import org.openhab.binding.satel.internal.types.StateType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SatelShutterHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels of a shutter device.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelShutterHandler extends SatelStateThingHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_SHUTTER);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SatelShutterHandler.class);
|
||||
|
||||
public SatelShutterHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingEvent(IntegraStateEvent event) {
|
||||
logger.trace("Handling incoming event: {}", event);
|
||||
if (getThingConfig().isCommandOnly() || !event.hasDataForState(OutputState.STATE)) {
|
||||
return;
|
||||
}
|
||||
Channel channel = getThing().getChannel(CHANNEL_SHUTTER_STATE);
|
||||
if (channel != null) {
|
||||
int upBitNbr = getThingConfig().getUpId() - 1;
|
||||
int downBitNbr = getThingConfig().getDownId() - 1;
|
||||
if (event.isSet(OutputState.STATE, upBitNbr)) {
|
||||
if (!event.isSet(OutputState.STATE, downBitNbr)) {
|
||||
updateState(channel.getUID(), UpDownType.UP);
|
||||
}
|
||||
} else if (event.isSet(OutputState.STATE, downBitNbr)) {
|
||||
updateState(channel.getUID(), UpDownType.DOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StateType getStateType(String channelId) {
|
||||
return CHANNEL_SHUTTER_STATE.equals(channelId) ? OutputState.STATE : StateType.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<SatelCommand> convertCommand(ChannelUID channel, Command command) {
|
||||
ControlObjectCommand result = null;
|
||||
if (CHANNEL_SHUTTER_STATE.equals(channel.getId())) {
|
||||
final SatelBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
int cmdBytes = bridgeHandler.getIntegraType().hasExtPayload() ? 32 : 16;
|
||||
if (command == UpDownType.UP) {
|
||||
byte[] outputs = getObjectBitset(cmdBytes, getThingConfig().getUpId());
|
||||
result = new ControlObjectCommand(OutputControl.ON, outputs, bridgeHandler.getUserCode(), scheduler);
|
||||
} else if (command == UpDownType.DOWN) {
|
||||
byte[] outputs = getObjectBitset(cmdBytes, getThingConfig().getDownId());
|
||||
result = new ControlObjectCommand(OutputControl.ON, outputs, bridgeHandler.getUserCode(), scheduler);
|
||||
} else if (command == StopMoveType.STOP) {
|
||||
byte[] outputs = getObjectBitset(cmdBytes, getThingConfig().getUpId(), getThingConfig().getDownId());
|
||||
result = new ControlObjectCommand(OutputControl.OFF, outputs, bridgeHandler.getUserCode(), scheduler);
|
||||
}
|
||||
}
|
||||
|
||||
return result == null ? Optional.empty() : Optional.of(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.internal.command.IntegraStateCommand;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.event.ConnectionStatusEvent;
|
||||
import org.openhab.binding.satel.internal.event.IntegraStateEvent;
|
||||
import org.openhab.binding.satel.internal.event.NewStatesEvent;
|
||||
import org.openhab.binding.satel.internal.types.StateType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SatelStateThingHandler} is base thing handler class for all state holding things.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class SatelStateThingHandler extends SatelThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SatelStateThingHandler.class);
|
||||
|
||||
private final AtomicBoolean requiresRefresh = new AtomicBoolean(true);
|
||||
|
||||
public SatelStateThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("New command for {}: {}", channelUID, command);
|
||||
|
||||
if (command == RefreshType.REFRESH) {
|
||||
this.requiresRefresh.set(true);
|
||||
} else {
|
||||
withBridgeHandlerPresent(bridgeHandler -> {
|
||||
if (StringUtils.isEmpty(bridgeHandler.getUserCode())) {
|
||||
logger.info("Cannot control devices without providing valid user code. Command has not been sent.");
|
||||
} else {
|
||||
convertCommand(channelUID, command)
|
||||
.ifPresent(satelCommand -> bridgeHandler.sendCommand(satelCommand, true));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingEvent(ConnectionStatusEvent event) {
|
||||
logger.trace("Handling incoming event: {}", event);
|
||||
// we have just connected, change thing's status and force refreshing
|
||||
if (event.isConnected()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
requiresRefresh.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingEvent(NewStatesEvent event) {
|
||||
logger.trace("Handling incoming event: {}", event);
|
||||
// refresh all states that have changed
|
||||
withBridgeHandlerPresent(bridgeHandler -> {
|
||||
for (SatelCommand command : getRefreshCommands(event)) {
|
||||
bridgeHandler.sendCommand(command, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingEvent(IntegraStateEvent event) {
|
||||
logger.trace("Handling incoming event: {}", event);
|
||||
// update thing's state unless it should accept commands only
|
||||
if (getThingConfig().isCommandOnly()) {
|
||||
return;
|
||||
}
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
if (isLinked(channel.getUID())) {
|
||||
StateType stateType = getStateType(channelUID.getId());
|
||||
if (stateType != StateType.NONE && event.hasDataForState(stateType)) {
|
||||
int bitNbr = getStateBitNbr(stateType);
|
||||
boolean invertState = getThingConfig().isStateInverted();
|
||||
updateSwitch(channelUID, event.isSet(stateType, bitNbr) ^ invertState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bit number of given state type for this thing.
|
||||
* This number addresses the bit in bit set sent to or received from alarm system.
|
||||
* Usually this number is the device identifier.
|
||||
*
|
||||
* @param stateType state type
|
||||
* @return bit number in state bit set
|
||||
*/
|
||||
protected int getStateBitNbr(StateType stateType) {
|
||||
return getThingConfig().getId() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts openHAB command sent to a channel into Satel message.
|
||||
*
|
||||
* @param channel channel the command was sent to
|
||||
* @param command sent command
|
||||
* @return Satel message that reflects sent command
|
||||
*/
|
||||
protected abstract Optional<SatelCommand> convertCommand(ChannelUID channel, Command command);
|
||||
|
||||
/**
|
||||
* Derived handlers must return appropriate state type for channels they support.
|
||||
* If given channel is not supported by a handler, it should return {@linkplain StateType#NONE}.
|
||||
*
|
||||
* @param channelId channel identifier to get state type for
|
||||
* @return object that represents state type
|
||||
* @see #getChannel(StateType)
|
||||
*/
|
||||
protected abstract StateType getStateType(String channelId);
|
||||
|
||||
/**
|
||||
* Returns channel for given state type. Usually channels have the same ID as state type name they represent.
|
||||
*
|
||||
* @param stateType state type to get channel for
|
||||
* @return channel object
|
||||
* @see #getStateType(String)
|
||||
*/
|
||||
protected @Nullable Channel getChannel(StateType stateType) {
|
||||
final String channelId = stateType.toString().toLowerCase();
|
||||
final Channel channel = getThing().getChannel(channelId);
|
||||
if (channel == null) {
|
||||
logger.debug("Missing channel for {}", stateType);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of commands required to update thing state basing on event describing changes since last refresh.
|
||||
*
|
||||
* @param event list of state changes since last refresh
|
||||
* @return collection of {@link IntegraStateCommand}
|
||||
*/
|
||||
protected Collection<SatelCommand> getRefreshCommands(NewStatesEvent event) {
|
||||
final boolean hasExtPayload = getBridgeHandler().getIntegraType().hasExtPayload();
|
||||
final Collection<SatelCommand> result = new LinkedList<>();
|
||||
final boolean forceRefresh = requiresRefresh();
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
StateType stateType = getStateType(channel.getUID().getId());
|
||||
if (stateType != StateType.NONE && isLinked(channel.getUID())) {
|
||||
if (forceRefresh || event.isNew(stateType.getRefreshCommand())) {
|
||||
result.add(new IntegraStateCommand(stateType, hasExtPayload));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this thing requires unconditional refresh of all its channels.
|
||||
* Clears the flag afterwards.
|
||||
*
|
||||
* @return if <code>true</code> this thing requires full refresh
|
||||
*/
|
||||
protected boolean requiresRefresh() {
|
||||
return requiresRefresh.getAndSet(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.command.ClearTroublesCommand;
|
||||
import org.openhab.binding.satel.internal.command.IntegraStatusCommand;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.command.SetClockCommand;
|
||||
import org.openhab.binding.satel.internal.event.IntegraStatusEvent;
|
||||
import org.openhab.binding.satel.internal.event.NewStatesEvent;
|
||||
import org.openhab.binding.satel.internal.types.StateType;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SatelSystemHandler} is responsible for handling commands, which are
|
||||
* sent to one of the system channels.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelSystemHandler extends SatelStateThingHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_SYSTEM);
|
||||
|
||||
private static final Set<String> STATUS_CHANNELS = Stream
|
||||
.of(CHANNEL_DATE_TIME, CHANNEL_SERVICE_MODE, CHANNEL_TROUBLES, CHANNEL_TROUBLES_MEMORY,
|
||||
CHANNEL_ACU100_PRESENT, CHANNEL_INTRX_PRESENT, CHANNEL_GRADE23_SET)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SatelSystemHandler.class);
|
||||
|
||||
public SatelSystemHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (CHANNEL_USER_CODE.equals(channelUID.getId()) && command instanceof StringType) {
|
||||
withBridgeHandlerPresent(bridgeHandler -> {
|
||||
bridgeHandler.setUserCode(command.toFullString());
|
||||
});
|
||||
} else {
|
||||
super.handleCommand(channelUID, command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingEvent(IntegraStatusEvent event) {
|
||||
logger.trace("Handling incoming event: {}", event);
|
||||
if (getThingConfig().isCommandOnly()) {
|
||||
return;
|
||||
}
|
||||
updateState(CHANNEL_DATE_TIME,
|
||||
event.getIntegraTime().map(dt -> (State) new DateTimeType(dt.atZone(getBridgeHandler().getZoneId())))
|
||||
.orElse(UnDefType.UNDEF));
|
||||
updateSwitch(CHANNEL_SERVICE_MODE, event.inServiceMode());
|
||||
updateSwitch(CHANNEL_TROUBLES, event.troublesPresent());
|
||||
updateSwitch(CHANNEL_TROUBLES_MEMORY, event.troublesMemory());
|
||||
updateSwitch(CHANNEL_ACU100_PRESENT, event.isAcu100Present());
|
||||
updateSwitch(CHANNEL_INTRX_PRESENT, event.isIntRxPresent());
|
||||
updateSwitch(CHANNEL_GRADE23_SET, event.isGrade23Set());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StateType getStateType(String channelId) {
|
||||
return StateType.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<SatelCommand> convertCommand(ChannelUID channel, Command command) {
|
||||
final SatelBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
switch (channel.getId()) {
|
||||
case CHANNEL_TROUBLES:
|
||||
case CHANNEL_TROUBLES_MEMORY:
|
||||
if (command == OnOffType.ON) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(new ClearTroublesCommand(bridgeHandler.getUserCode()));
|
||||
}
|
||||
case CHANNEL_DATE_TIME:
|
||||
DateTimeType dateTime = null;
|
||||
if (command instanceof StringType) {
|
||||
dateTime = DateTimeType.valueOf(command.toString());
|
||||
} else if (command instanceof DateTimeType) {
|
||||
dateTime = (DateTimeType) command;
|
||||
}
|
||||
if (dateTime != null) {
|
||||
return Optional.of(new SetClockCommand(dateTime.getZonedDateTime()
|
||||
.withZoneSameInstant(bridgeHandler.getZoneId()).toLocalDateTime(),
|
||||
bridgeHandler.getUserCode()));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// do nothing for other types of status
|
||||
break;
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<SatelCommand> getRefreshCommands(NewStatesEvent event) {
|
||||
Collection<SatelCommand> result = new LinkedList<>();
|
||||
boolean anyStatusChannelLinked = getThing().getChannels().stream()
|
||||
.filter(channel -> STATUS_CHANNELS.contains(channel.getUID().getId()))
|
||||
.anyMatch(channel -> isLinked(channel.getUID().getId()));
|
||||
boolean needRefresh = anyStatusChannelLinked
|
||||
&& (requiresRefresh() || isLinked(CHANNEL_DATE_TIME) || event.isNew(IntegraStatusCommand.COMMAND_CODE));
|
||||
if (needRefresh) {
|
||||
result.add(new IntegraStatusCommand());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.internal.config.SatelThingConfig;
|
||||
import org.openhab.binding.satel.internal.event.SatelEventListener;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
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.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link SatelThingHandler} is base thing handler class for all non-bridge things.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class SatelThingHandler extends BaseThingHandler implements SatelEventListener {
|
||||
|
||||
private @Nullable SatelThingConfig thingConfig;
|
||||
private @Nullable SatelBridgeHandler bridgeHandler;
|
||||
|
||||
public SatelThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
final SatelBridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.removeEventListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
thingConfig = getConfig().as(SatelThingConfig.class);
|
||||
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
final ThingHandler handler = bridge.getHandler();
|
||||
if (handler != null && handler instanceof SatelBridgeHandler) {
|
||||
this.bridgeHandler = (SatelBridgeHandler) handler;
|
||||
this.bridgeHandler.addEventListener(this);
|
||||
}
|
||||
if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SatelThingConfig getThingConfig() {
|
||||
final SatelThingConfig thingConfig = this.thingConfig;
|
||||
if (thingConfig != null) {
|
||||
return thingConfig;
|
||||
}
|
||||
throw new IllegalStateException("Thing handler is not initialized yet for thing " + getThing().getUID());
|
||||
}
|
||||
|
||||
protected void withBridgeHandlerPresent(Consumer<SatelBridgeHandler> action) {
|
||||
final SatelBridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
if (bridgeHandler != null) {
|
||||
action.accept(bridgeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
protected SatelBridgeHandler getBridgeHandler() {
|
||||
final SatelBridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
if (bridgeHandler != null) {
|
||||
return bridgeHandler;
|
||||
}
|
||||
throw new IllegalStateException("Bridge handler is not set for thing " + getThing().getUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates switch channel with given state.
|
||||
*
|
||||
* @param channelID channel ID
|
||||
* @param switchOn if <code>true</code> the channel is updated with ON state, with OFF state otherwise
|
||||
*/
|
||||
protected void updateSwitch(String channelID, boolean switchOn) {
|
||||
ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), channelID);
|
||||
updateSwitch(channelUID, switchOn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates switch channel with given state.
|
||||
*
|
||||
* @param channelUID channel UID
|
||||
* @param switchOn if <code>true</code> the channel is updated with ON state, with OFF state otherwise
|
||||
*/
|
||||
protected void updateSwitch(ChannelUID channelUID, boolean switchOn) {
|
||||
State state = switchOn ? OnOffType.ON : OnOffType.OFF;
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates bitset of given size with particular bits set to 1.
|
||||
*
|
||||
* @param size bitset size in bytes
|
||||
* @param ids bits to set, first bit is 1
|
||||
* @return bitset as array of bytes
|
||||
*/
|
||||
protected byte[] getObjectBitset(int size, int... ids) {
|
||||
byte[] bitset = new byte[size];
|
||||
for (int id : ids) {
|
||||
int bitNbr = id - 1;
|
||||
bitset[bitNbr / 8] |= (byte) (1 << (bitNbr % 8));
|
||||
}
|
||||
return bitset;
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.THING_TYPE_ZONE;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.command.ControlObjectCommand;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.types.StateType;
|
||||
import org.openhab.binding.satel.internal.types.ZoneControl;
|
||||
import org.openhab.binding.satel.internal.types.ZoneState;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The {@link SatelZoneHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels of a zone.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelZoneHandler extends WirelessChannelsHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_ZONE);
|
||||
|
||||
public SatelZoneHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<SatelCommand> convertCommand(ChannelUID channel, Command command) {
|
||||
if (command instanceof OnOffType) {
|
||||
final SatelBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
boolean switchOn = (command == OnOffType.ON);
|
||||
StateType stateType = getStateType(channel.getId());
|
||||
int size = bridgeHandler.getIntegraType().hasExtPayload() ? 32 : 16;
|
||||
byte[] zones = getObjectBitset(size, getThingConfig().getId());
|
||||
ZoneControl action = null;
|
||||
switch ((ZoneState) stateType) {
|
||||
case BYPASS:
|
||||
action = switchOn ? ZoneControl.BYPASS : ZoneControl.UNBYPASS;
|
||||
break;
|
||||
case ISOLATE:
|
||||
action = switchOn ? ZoneControl.ISOLATE : null;
|
||||
break;
|
||||
default:
|
||||
// do nothing for other types of state
|
||||
break;
|
||||
}
|
||||
|
||||
if (action != null) {
|
||||
return Optional.of(new ControlObjectCommand(action, zones, bridgeHandler.getUserCode(), scheduler));
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StateType getStateType(String channelId) {
|
||||
StateType result = super.getStateType(channelId);
|
||||
if (result == StateType.NONE) {
|
||||
result = ZoneState.valueOf(channelId.toUpperCase());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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.satel.internal.handler;
|
||||
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.satel.internal.types.StateType;
|
||||
import org.openhab.binding.satel.internal.types.TroubleState;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
|
||||
/**
|
||||
* The {@link WirelessChannelsHandler} is base thing handler class for things that can be wireless devices.
|
||||
* It adds support for two additional channels:
|
||||
* <ul>
|
||||
* <li><i>device_lobatt</i> - low battery indication</li>
|
||||
* <li><i>device_nocomm</i> - communication problems indication</li>
|
||||
* </ul>
|
||||
* adding them if the device is configured as a wireless device.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class WirelessChannelsHandler extends SatelStateThingHandler {
|
||||
|
||||
public WirelessChannelsHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
|
||||
withBridgeHandlerPresent(bridgeHandler -> {
|
||||
// add/remove channels depending on whether or not the device is wireless
|
||||
final int wirelessDeviceId = getThingConfig().getId() - bridgeHandler.getIntegraType().getOnMainboard();
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
if (isWirelessDevice() && wirelessDeviceId > 0) {
|
||||
// if a wireless device, remove channels for wireless devices
|
||||
if (getChannel(TroubleState.DEVICE_LOBATT) == null) {
|
||||
thingBuilder.withChannel(ChannelBuilder.create(getChannelUID(TroubleState.DEVICE_LOBATT), "Switch")
|
||||
.withType(CHANNEL_TYPE_LOBATT).build());
|
||||
}
|
||||
if (getChannel(TroubleState.DEVICE_NOCOMM) == null) {
|
||||
thingBuilder.withChannel(ChannelBuilder.create(getChannelUID(TroubleState.DEVICE_NOCOMM), "Switch")
|
||||
.withType(CHANNEL_TYPE_NOCOMM).build());
|
||||
}
|
||||
} else {
|
||||
// if not a wireless device, remove channels for wireless devices
|
||||
thingBuilder.withoutChannel(getChannelUID(TroubleState.DEVICE_LOBATT))
|
||||
.withoutChannel(getChannelUID(TroubleState.DEVICE_NOCOMM));
|
||||
}
|
||||
updateThing(thingBuilder.build());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the thing as a wireless or wired device.
|
||||
*
|
||||
* @return <code>true</code> if the thing is, or is configured as a wireless device
|
||||
*/
|
||||
protected boolean isWirelessDevice() {
|
||||
return getThingConfig().isWireless();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getStateBitNbr(StateType stateType) {
|
||||
int bitNbr = getThingConfig().getId() - 1;
|
||||
if (stateType instanceof TroubleState) {
|
||||
// for wireless devices we need to correct bit number
|
||||
switch ((TroubleState) stateType) {
|
||||
case DEVICE_LOBATT1:
|
||||
case DEVICE_NOCOMM1:
|
||||
case OUTPUT_NOCOMM1:
|
||||
bitNbr -= 120;
|
||||
// pass through
|
||||
case DEVICE_LOBATT:
|
||||
case DEVICE_NOCOMM:
|
||||
case OUTPUT_NOCOMM:
|
||||
bitNbr -= getBridgeHandler().getIntegraType().getOnMainboard();
|
||||
break;
|
||||
default:
|
||||
// other states are either not supported or don't need correction
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bitNbr;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StateType getStateType(String channelId) {
|
||||
String stateName = channelId.toUpperCase();
|
||||
if (TroubleState.DEVICE_LOBATT.name().equals(stateName)
|
||||
|| TroubleState.DEVICE_NOCOMM.name().equals(stateName)) {
|
||||
if (getThingConfig().getId() - getBridgeHandler().getIntegraType().getOnMainboard() > 120) {
|
||||
// last 120 ACU-100 devices in INTEGRA 256 PLUS
|
||||
stateName += "1";
|
||||
}
|
||||
return TroubleState.valueOf(stateName);
|
||||
} else {
|
||||
return StateType.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns channel UID for given state type.
|
||||
*
|
||||
* @param stateType state type to get channel UID for
|
||||
* @return channel UID object
|
||||
*/
|
||||
private ChannelUID getChannelUID(StateType stateType) {
|
||||
String channelId = stateType.toString().toLowerCase();
|
||||
return new ChannelUID(getThing().getUID(), channelId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.protocol;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Helper class for encrypting ETHM-1 messages.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EncryptionHelper {
|
||||
private Key key;
|
||||
private Cipher encipher;
|
||||
private Cipher decipher;
|
||||
|
||||
/**
|
||||
* Creates new instance of encryption helper with given key.
|
||||
*
|
||||
* @param keyString key for integration encryption
|
||||
* @throws GeneralSecurityException on JCE errors
|
||||
*/
|
||||
public EncryptionHelper(String keyString) throws GeneralSecurityException {
|
||||
// we have to check if 192bit support is enabled
|
||||
if (Cipher.getMaxAllowedKeyLength("AES") < 192) {
|
||||
throw new GeneralSecurityException("JCE does not support 192-bit keys");
|
||||
}
|
||||
|
||||
// build encryption/decryption key based on given password
|
||||
byte passwordBytes[] = keyString.getBytes();
|
||||
byte[] keyBytes = new byte[24];
|
||||
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
keyBytes[i] = keyBytes[i + 12] = (i < passwordBytes.length) ? passwordBytes[i] : 0x20;
|
||||
}
|
||||
|
||||
// create objects for encryption/decryption
|
||||
this.key = new SecretKeySpec(keyBytes, "AES");
|
||||
this.encipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
this.encipher.init(Cipher.ENCRYPT_MODE, this.key);
|
||||
this.decipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
this.decipher.init(Cipher.DECRYPT_MODE, this.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts given buffer of bytes in place.
|
||||
*
|
||||
* @param buffer
|
||||
* bytes to decrypt
|
||||
* @throws GeneralSecurityException
|
||||
* on decryption errors
|
||||
*/
|
||||
public void decrypt(byte buffer[]) throws GeneralSecurityException {
|
||||
byte[] cv = new byte[16];
|
||||
byte[] c = new byte[16];
|
||||
byte[] temp = new byte[16];
|
||||
int count = buffer.length;
|
||||
|
||||
cv = this.encipher.doFinal(cv);
|
||||
for (int index = 0; count > 0;) {
|
||||
if (count > 15) {
|
||||
count -= 16;
|
||||
System.arraycopy(buffer, index, temp, 0, 16);
|
||||
System.arraycopy(buffer, index, c, 0, 16);
|
||||
c = this.decipher.doFinal(c);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
c[i] ^= cv[i];
|
||||
cv[i] = temp[i];
|
||||
}
|
||||
System.arraycopy(c, 0, buffer, index, 16);
|
||||
index += 16;
|
||||
} else {
|
||||
System.arraycopy(buffer, index, c, 0, count);
|
||||
cv = this.encipher.doFinal(cv);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
c[i] ^= cv[i];
|
||||
}
|
||||
System.arraycopy(c, 0, buffer, index, count);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts given buffer of bytes in place.
|
||||
*
|
||||
* @param buffer bytes to encrypt
|
||||
* @throws GeneralSecurityException on encryption errors
|
||||
*/
|
||||
public void encrypt(byte buffer[]) throws GeneralSecurityException {
|
||||
byte[] cv = new byte[16];
|
||||
byte[] p = new byte[16];
|
||||
int count = buffer.length;
|
||||
|
||||
cv = this.encipher.doFinal(cv);
|
||||
for (int index = 0; count > 0;) {
|
||||
if (count > 15) {
|
||||
count -= 16;
|
||||
System.arraycopy(buffer, index, p, 0, 16);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
p[i] ^= cv[i];
|
||||
}
|
||||
p = this.encipher.doFinal(p);
|
||||
System.arraycopy(p, 0, cv, 0, 16);
|
||||
System.arraycopy(p, 0, buffer, index, 16);
|
||||
index += 16;
|
||||
} else {
|
||||
System.arraycopy(buffer, index, p, 0, count);
|
||||
cv = this.encipher.doFinal(cv);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
p[i] ^= cv[i];
|
||||
}
|
||||
System.arraycopy(p, 0, buffer, index, count);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* 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.satel.internal.protocol;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Represents Satel ETHM-1 module. Implements method required to connect and
|
||||
* communicate with that module over TCP/IP protocol. The module must have
|
||||
* integration protocol enable in DLOADX configuration options.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ethm1Module extends SatelModule {
|
||||
|
||||
private static final ByteArrayInputStream EMPTY_INPUT_STREAM = new ByteArrayInputStream(new byte[0]);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Ethm1Module.class);
|
||||
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final String encryptionKey;
|
||||
|
||||
/**
|
||||
* Creates new instance with host, port, timeout and encryption key set to
|
||||
* specified values.
|
||||
*
|
||||
* @param host host name or IP of ETHM-1 module
|
||||
* @param port TCP port the module listens on
|
||||
* @param timeout timeout value in milliseconds for connect/read/write operations
|
||||
* @param encryptionKey encryption key for encrypted communication
|
||||
* @param extPayloadSupport if <code>true</code>, the module supports extended command payload for reading
|
||||
* INTEGRA 256 state
|
||||
*/
|
||||
public Ethm1Module(String host, int port, int timeout, String encryptionKey, boolean extPayloadSupport) {
|
||||
super(timeout, extPayloadSupport);
|
||||
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.encryptionKey = encryptionKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommunicationChannel connect() throws ConnectionFailureException {
|
||||
logger.info("Connecting to ETHM-1 module at {}:{}", this.host, this.port);
|
||||
|
||||
try {
|
||||
Socket socket = new Socket();
|
||||
socket.connect(new InetSocketAddress(this.host, this.port), this.getTimeout());
|
||||
logger.info("ETHM-1 module connected successfully");
|
||||
|
||||
if (StringUtils.isBlank(this.encryptionKey)) {
|
||||
return new TCPCommunicationChannel(socket);
|
||||
} else {
|
||||
return new EncryptedCommunicationChannel(socket, this.encryptionKey);
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
throw new ConnectionFailureException("Connection timeout", e);
|
||||
} catch (IOException e) {
|
||||
throw new ConnectionFailureException("IO error occurred while connecting socket", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class TCPCommunicationChannel implements CommunicationChannel {
|
||||
|
||||
private Socket socket;
|
||||
|
||||
public TCPCommunicationChannel(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return this.socket.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return this.socket.getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
logger.info("Closing connection to ETHM-1 module");
|
||||
try {
|
||||
this.socket.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("IO error occurred during closing socket", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EncryptedCommunicationChannel extends TCPCommunicationChannel {
|
||||
|
||||
private EncryptionHelper aesHelper;
|
||||
private Random rand;
|
||||
private byte idS;
|
||||
private byte idR;
|
||||
private int rollingCounter;
|
||||
private InputStream inputStream;
|
||||
private OutputStream outputStream;
|
||||
|
||||
public EncryptedCommunicationChannel(final Socket socket, String encryptionKey) throws IOException {
|
||||
super(socket);
|
||||
|
||||
try {
|
||||
this.aesHelper = new EncryptionHelper(encryptionKey);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("General encryption failure", e);
|
||||
}
|
||||
this.rand = new Random();
|
||||
this.idS = 0;
|
||||
this.idR = 0;
|
||||
this.rollingCounter = 0;
|
||||
|
||||
this.inputStream = new InputStream() {
|
||||
private ByteArrayInputStream inputBuffer = EMPTY_INPUT_STREAM;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (inputBuffer.available() == 0) {
|
||||
// read message and decrypt it
|
||||
byte[] data = readMessage(socket.getInputStream());
|
||||
// create new buffer
|
||||
inputBuffer = new ByteArrayInputStream(data, 6, data.length - 6);
|
||||
}
|
||||
return inputBuffer.read();
|
||||
}
|
||||
};
|
||||
|
||||
this.outputStream = new OutputStream() {
|
||||
private ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(256);
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
outputBuffer.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
writeMessage(outputBuffer.toByteArray(), socket.getOutputStream());
|
||||
outputBuffer.reset();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return this.inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
private synchronized byte[] readMessage(InputStream is) throws IOException {
|
||||
logger.trace("Receiving data from ETHM-1");
|
||||
// read number of bytes
|
||||
int bytesCount = is.read();
|
||||
logger.trace("Read count of bytes: {}", bytesCount);
|
||||
if (bytesCount == -1) {
|
||||
throw new IOException("End of input stream reached");
|
||||
}
|
||||
byte[] data = new byte[bytesCount];
|
||||
// read encrypted data
|
||||
int bytesRead = is.read(data);
|
||||
if (bytesCount != bytesRead) {
|
||||
throw new IOException(
|
||||
String.format("Too few bytes read. Read: %d, expected: %d", bytesRead, bytesCount));
|
||||
}
|
||||
// decrypt data
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Decrypting data: {}", HexUtils.bytesToHex(data));
|
||||
}
|
||||
|
||||
try {
|
||||
this.aesHelper.decrypt(data);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Decryption exception", e);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Decrypted data: {}", HexUtils.bytesToHex(data));
|
||||
}
|
||||
|
||||
// validate message
|
||||
this.idR = data[4];
|
||||
if (this.idS != data[5]) {
|
||||
throw new IOException(String.format("Invalid 'idS' value. Got: %d, expected: %d", data[5], this.idS));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private synchronized void writeMessage(byte[] message, OutputStream os) throws IOException {
|
||||
// prepare data for encryption
|
||||
int bytesCount = 6 + message.length;
|
||||
if (bytesCount < 16) {
|
||||
bytesCount = 16;
|
||||
}
|
||||
byte[] data = new byte[bytesCount];
|
||||
int randomValue = this.rand.nextInt();
|
||||
data[0] = (byte) (randomValue >> 8);
|
||||
data[1] = (byte) (randomValue & 0xff);
|
||||
data[2] = (byte) (this.rollingCounter >> 8);
|
||||
data[3] = (byte) (this.rollingCounter & 0xff);
|
||||
data[4] = this.idS = (byte) this.rand.nextInt();
|
||||
data[5] = this.idR;
|
||||
++this.rollingCounter;
|
||||
System.arraycopy(message, 0, data, 6, message.length);
|
||||
|
||||
// encrypt data
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Encrypting data: {}", HexUtils.bytesToHex(data));
|
||||
}
|
||||
|
||||
try {
|
||||
this.aesHelper.encrypt(data);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Encryption exception", e);
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Encrypted data: {}", HexUtils.bytesToHex(data));
|
||||
}
|
||||
|
||||
// write encrypted data to output stream
|
||||
os.write(bytesCount);
|
||||
os.write(data);
|
||||
os.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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.satel.internal.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.TooManyListenersException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEvent;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEventListener;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Represents Satel INT-RS module. Implements methods required to connect and
|
||||
* communicate with that module over serial protocol.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntRSModule extends SatelModule {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IntRSModule.class);
|
||||
|
||||
private final String port;
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
/**
|
||||
* Creates new instance with port and timeout set to specified values.
|
||||
*
|
||||
* @param port serial port the module is connected to
|
||||
* @param serialPortManager serial port manager object
|
||||
* @param timeout timeout value in milliseconds for connect/read/write operations
|
||||
* @param extPayloadSupport if <code>true</code>, the module supports extended command payload for reading
|
||||
* INTEGRA 256 state
|
||||
*/
|
||||
public IntRSModule(String port, SerialPortManager serialPortManager, int timeout, boolean extPayloadSupport) {
|
||||
super(timeout, extPayloadSupport);
|
||||
|
||||
this.port = port;
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommunicationChannel connect() throws ConnectionFailureException {
|
||||
logger.info("Connecting to INT-RS module at {}", this.port);
|
||||
|
||||
try {
|
||||
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(this.port);
|
||||
if (portIdentifier == null) {
|
||||
throw new ConnectionFailureException(String.format("Port %s does not exist", this.port));
|
||||
}
|
||||
SerialPort serialPort = portIdentifier.open("org.openhab.binding.satel", 2000);
|
||||
serialPort.setSerialPortParams(19200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
|
||||
serialPort.enableReceiveTimeout(this.getTimeout());
|
||||
// RXTX serial port library causes high CPU load
|
||||
// Start event listener, which will just sleep and slow down event
|
||||
// loop
|
||||
serialPort.addEventListener(new SerialPortEventListener() {
|
||||
@Override
|
||||
public void serialEvent(SerialPortEvent ev) {
|
||||
try {
|
||||
logger.trace("RXTX library CPU load workaround, sleep forever");
|
||||
Thread.sleep(Long.MAX_VALUE);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
|
||||
logger.info("INT-RS module connected successfuly");
|
||||
return new SerialCommunicationChannel(serialPort);
|
||||
} catch (PortInUseException e) {
|
||||
throw new ConnectionFailureException(String.format("Port %s in use", this.port), e);
|
||||
} catch (UnsupportedCommOperationException e) {
|
||||
throw new ConnectionFailureException(String.format("Unsupported comm operation on port %s", this.port), e);
|
||||
} catch (TooManyListenersException e) {
|
||||
throw new ConnectionFailureException(String.format("Too many listeners on port %s", this.port), e);
|
||||
}
|
||||
}
|
||||
|
||||
private class SerialCommunicationChannel implements CommunicationChannel {
|
||||
|
||||
private SerialPort serialPort;
|
||||
|
||||
public SerialCommunicationChannel(SerialPort serialPort) {
|
||||
this.serialPort = serialPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
final InputStream stream = this.serialPort.getInputStream();
|
||||
if (stream != null) {
|
||||
return stream;
|
||||
}
|
||||
throw new IOException("Selected port doesn't support receiving data: " + this.serialPort.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
final OutputStream stream = this.serialPort.getOutputStream();
|
||||
if (stream != null) {
|
||||
return stream;
|
||||
}
|
||||
throw new IOException("Selected port doesn't support sending data: " + this.serialPort.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
logger.debug("Closing connection to INT-RS module");
|
||||
try {
|
||||
this.serialPort.close();
|
||||
logger.info("Connection to INT-RS module has been closed");
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred during closing serial port", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* 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.satel.internal.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Represents a message sent to (a command) or received from (a response)
|
||||
* {@link SatelModule}. It consists of one byte that specifies command type or
|
||||
* response status and certain number of payload bytes. Number of payload byte
|
||||
* depends on command type. The class allows to serialize a command to bytes and
|
||||
* deserialize a response from given bytes. It also computes and validates
|
||||
* message checksum.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SatelMessage {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SatelMessage.class);
|
||||
|
||||
private byte command;
|
||||
private byte[] payload;
|
||||
|
||||
protected static final byte[] EMPTY_PAYLOAD = new byte[0];
|
||||
|
||||
/**
|
||||
* Creates new instance with specified command code and payload.
|
||||
*
|
||||
* @param command
|
||||
* command code
|
||||
* @param payload
|
||||
* command payload
|
||||
*/
|
||||
public SatelMessage(byte command, byte[] payload) {
|
||||
this.command = command;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance with specified command code and empty payload.
|
||||
*
|
||||
* @param command command code
|
||||
*/
|
||||
public SatelMessage(byte command) {
|
||||
this(command, EMPTY_PAYLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes new message instance from specified byte buffer.
|
||||
*
|
||||
* @param buffer bytes to deserialize a message from
|
||||
* @return deserialized message instance
|
||||
*/
|
||||
public static @Nullable SatelMessage fromBytes(byte[] buffer) {
|
||||
// we need at least command and checksum
|
||||
if (buffer.length < 3) {
|
||||
LOGGER.error("Invalid message length: {}", buffer.length);
|
||||
return null;
|
||||
}
|
||||
|
||||
// check crc
|
||||
int receivedCrc = 0xffff & ((buffer[buffer.length - 2] << 8) | (buffer[buffer.length - 1] & 0xff));
|
||||
int expectedCrc = calculateChecksum(buffer, buffer.length - 2);
|
||||
if (receivedCrc != expectedCrc) {
|
||||
LOGGER.error("Invalid message checksum: received = {}, expected = {}", receivedCrc, expectedCrc);
|
||||
return null;
|
||||
}
|
||||
|
||||
SatelMessage message = new SatelMessage(buffer[0], new byte[buffer.length - 3]);
|
||||
if (message.payload.length > 0) {
|
||||
System.arraycopy(buffer, 1, message.payload, 0, buffer.length - 3);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns command byte.
|
||||
*
|
||||
* @return the command
|
||||
*/
|
||||
public byte getCommand() {
|
||||
return this.command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the payload bytes.
|
||||
*
|
||||
* @return payload as byte array
|
||||
*/
|
||||
public byte[] getPayload() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message serialized as array of bytes with checksum calculated
|
||||
* at last two bytes.
|
||||
*
|
||||
* @return the message as array of bytes
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
byte buffer[] = new byte[this.payload.length + 3];
|
||||
buffer[0] = this.command;
|
||||
if (this.payload.length > 0) {
|
||||
System.arraycopy(this.payload, 0, buffer, 1, this.payload.length);
|
||||
}
|
||||
int checksum = calculateChecksum(buffer, buffer.length - 2);
|
||||
buffer[buffer.length - 2] = (byte) ((checksum >> 8) & 0xff);
|
||||
buffer[buffer.length - 1] = (byte) (checksum & 0xff);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private String getPayloadAsHex() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < this.payload.length; ++i) {
|
||||
if (i > 0) {
|
||||
result.append(" ");
|
||||
}
|
||||
result.append(String.format("%02X", this.payload[i]));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a checksum for the specified buffer.
|
||||
*
|
||||
* @param buffer the buffer to calculate.
|
||||
* @return the checksum value.
|
||||
*/
|
||||
private static int calculateChecksum(byte[] buffer, int length) {
|
||||
int checkSum = 0x147a;
|
||||
for (int i = 0; i < length; i++) {
|
||||
checkSum = ((checkSum << 1) | ((checkSum >> 15) & 1));
|
||||
checkSum ^= 0xffff;
|
||||
checkSum += ((checkSum >> 8) & 0xff) + (buffer[i] & 0xff);
|
||||
}
|
||||
checkSum &= 0xffff;
|
||||
LOGGER.trace("Calculated checksum = {}", String.format("%04X", checkSum));
|
||||
return checkSum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Message: command = %02X, payload = %s", this.command, getPayloadAsHex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!obj.getClass().equals(this.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SatelMessage other = (SatelMessage) obj;
|
||||
|
||||
if (other.command != this.command) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(other.payload, this.payload)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + command;
|
||||
result = prime * result + Arrays.hashCode(payload);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
/**
|
||||
* 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.satel.internal.protocol;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.satel.internal.command.IntegraVersionCommand;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand;
|
||||
import org.openhab.binding.satel.internal.command.SatelCommand.State;
|
||||
import org.openhab.binding.satel.internal.event.ConnectionStatusEvent;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.event.IntegraVersionEvent;
|
||||
import org.openhab.binding.satel.internal.event.SatelEventListener;
|
||||
import org.openhab.binding.satel.internal.types.IntegraType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class represents abstract communication module and is responsible for
|
||||
* exchanging data between the binding and connected physical module.
|
||||
* Communication happens by sending commands and receiving response from the
|
||||
* module. Each command class must extend {@link SatelCommand}.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class SatelModule extends EventDispatcher implements SatelEventListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SatelModule.class);
|
||||
|
||||
private static final byte FRAME_SYNC = (byte) 0xfe;
|
||||
private static final byte FRAME_SYNC_ESC = (byte) 0xf0;
|
||||
private static final byte[] FRAME_START = { FRAME_SYNC, FRAME_SYNC };
|
||||
private static final byte[] FRAME_END = { FRAME_SYNC, (byte) 0x0d };
|
||||
|
||||
private final BlockingQueue<SatelCommand> sendQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
private final int timeout;
|
||||
private volatile IntegraType integraType;
|
||||
private volatile String integraVersion;
|
||||
private final boolean extPayloadSupport;
|
||||
private @Nullable CommunicationChannel channel;
|
||||
private @Nullable CommunicationWatchdog communicationWatchdog;
|
||||
|
||||
/*
|
||||
* Helper interface for connecting and disconnecting to specific module
|
||||
* type. Each module type should implement these methods to provide input
|
||||
* and output streams and way to disconnect from the module.
|
||||
*/
|
||||
protected interface CommunicationChannel {
|
||||
|
||||
InputStream getInputStream() throws IOException;
|
||||
|
||||
OutputStream getOutputStream() throws IOException;
|
||||
|
||||
void disconnect();
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper interface to handle communication timeouts.
|
||||
*/
|
||||
protected interface TimeoutTimer {
|
||||
|
||||
void start();
|
||||
|
||||
void stop();
|
||||
}
|
||||
|
||||
/*
|
||||
* Thrown on connection failures.
|
||||
*/
|
||||
protected static class ConnectionFailureException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 2L;
|
||||
|
||||
public ConnectionFailureException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ConnectionFailureException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of the class.
|
||||
*
|
||||
* @param timeout timeout value in milliseconds for connect/read/write operations
|
||||
* @param extPayloadSupport if <code>true</code>, the module supports extended command payload for reading
|
||||
* INTEGRA256 state
|
||||
*/
|
||||
public SatelModule(int timeout, boolean extPayloadSupport) {
|
||||
this.timeout = timeout;
|
||||
this.integraType = IntegraType.UNKNOWN;
|
||||
this.integraVersion = "";
|
||||
this.extPayloadSupport = extPayloadSupport;
|
||||
|
||||
addEventListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of Integra connected to the module.
|
||||
*
|
||||
* @return Integra type
|
||||
*/
|
||||
public IntegraType getIntegraType() {
|
||||
return this.integraType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns firmware revision of Integra connected to the module.
|
||||
*
|
||||
* @return version of Integra firmware
|
||||
*/
|
||||
public String getIntegraVersion() {
|
||||
return this.integraVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns configured timeout value.
|
||||
*
|
||||
* @return timeout value as milliseconds
|
||||
*/
|
||||
public int getTimeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return this.channel != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns status of initialization.
|
||||
*
|
||||
* @return <code>true</code> if module is properly initialized and ready for sending commands
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return this.integraType != IntegraType.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns extended payload flag.
|
||||
*
|
||||
* @return <code>true</code> if the module supports extended (32-bit) payload for zones/outputs
|
||||
*/
|
||||
public boolean hasExtPayloadSupport() {
|
||||
return this.extPayloadSupport;
|
||||
}
|
||||
|
||||
protected abstract CommunicationChannel connect() throws ConnectionFailureException;
|
||||
|
||||
/**
|
||||
* Starts communication.
|
||||
*/
|
||||
public synchronized void open() {
|
||||
if (this.communicationWatchdog == null) {
|
||||
this.communicationWatchdog = new CommunicationWatchdog();
|
||||
} else {
|
||||
logger.warn("Module is already opened.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops communication by disconnecting from the module and stopping all background tasks.
|
||||
*/
|
||||
public void close() {
|
||||
// first we clear watchdog field in the object
|
||||
CommunicationWatchdog watchdog = null;
|
||||
synchronized (this) {
|
||||
if (this.communicationWatchdog != null) {
|
||||
watchdog = this.communicationWatchdog;
|
||||
this.communicationWatchdog = null;
|
||||
}
|
||||
}
|
||||
// then, if watchdog exists, we close it
|
||||
if (watchdog != null) {
|
||||
watchdog.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues specified command in send queue if not already enqueued.
|
||||
*
|
||||
* @param cmd command to enqueue
|
||||
* @return <code>true</code> if operation succeeded
|
||||
*/
|
||||
public boolean sendCommand(SatelCommand cmd) {
|
||||
return this.sendCommand(cmd, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues specified command in send queue.
|
||||
*
|
||||
* @param cmd command to enqueue
|
||||
* @param force if <code>true</code> enqueues unconditionally
|
||||
* @return <code>true</code> if operation succeeded
|
||||
*/
|
||||
public boolean sendCommand(SatelCommand cmd, boolean force) {
|
||||
try {
|
||||
if (force || !this.sendQueue.contains(cmd)) {
|
||||
this.sendQueue.put(cmd);
|
||||
cmd.setState(State.ENQUEUED);
|
||||
logger.trace("Command enqueued: {}", cmd);
|
||||
} else {
|
||||
logger.debug("Command already in the queue: {}", cmd);
|
||||
}
|
||||
return true;
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingEvent(IntegraVersionEvent event) {
|
||||
IntegraVersionEvent versionEvent = event;
|
||||
this.integraType = IntegraType.valueOf(versionEvent.getType() & 0xFF);
|
||||
this.integraVersion = versionEvent.getVersion();
|
||||
logger.info("Connection to {} initialized. INTEGRA version: {}.", this.integraType.getName(),
|
||||
this.integraVersion);
|
||||
}
|
||||
|
||||
private @Nullable SatelMessage readMessage() throws InterruptedException {
|
||||
final CommunicationChannel channel = this.channel;
|
||||
if (channel == null) {
|
||||
logger.error("Reading attempt on closed channel.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final InputStream is = channel.getInputStream();
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
boolean inMessage = false;
|
||||
int syncBytes = 0;
|
||||
|
||||
while (true) {
|
||||
// if timed out, exit
|
||||
int c = is.read();
|
||||
if (c < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte b = (byte) c;
|
||||
|
||||
if (b == FRAME_SYNC) {
|
||||
if (inMessage) {
|
||||
// syncBytes == 0 means special sequence or end of message
|
||||
// otherwise we discard all received bytes
|
||||
if (syncBytes != 0) {
|
||||
logger.warn("Received frame sync bytes, discarding input: {}", baos.size());
|
||||
// clear gathered bytes, we wait for new message
|
||||
inMessage = false;
|
||||
baos.reset();
|
||||
}
|
||||
}
|
||||
++syncBytes;
|
||||
} else {
|
||||
if (inMessage) {
|
||||
if (syncBytes == 0) {
|
||||
// in sync, we have next message byte
|
||||
baos.write(b);
|
||||
} else if (syncBytes == 1) {
|
||||
if (b == FRAME_SYNC_ESC) {
|
||||
baos.write(FRAME_SYNC);
|
||||
} else if (b == FRAME_END[1]) {
|
||||
// end of message
|
||||
break;
|
||||
} else {
|
||||
logger.warn("Received invalid byte {}, discarding input: {}", String.format("%02X", b),
|
||||
baos.size());
|
||||
// clear gathered bytes, we have new message
|
||||
inMessage = false;
|
||||
baos.reset();
|
||||
}
|
||||
} else {
|
||||
logger.error("Sync bytes in message: {}", syncBytes);
|
||||
}
|
||||
} else if (syncBytes >= 2) {
|
||||
// synced, we have first message byte
|
||||
inMessage = true;
|
||||
baos.write(b);
|
||||
}
|
||||
// otherwise we ignore all bytes until synced
|
||||
syncBytes = 0;
|
||||
}
|
||||
|
||||
// if meanwhile thread has been interrupted, exit the loop
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
// return read message
|
||||
return SatelMessage.fromBytes(baos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
logger.error("Unexpected exception occurred during reading a message", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean writeMessage(SatelMessage message) {
|
||||
final CommunicationChannel channel = this.channel;
|
||||
if (channel == null) {
|
||||
logger.error("Writing attempt on closed channel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final OutputStream os = channel.getOutputStream();
|
||||
|
||||
os.write(FRAME_START);
|
||||
for (byte b : message.getBytes()) {
|
||||
os.write(b);
|
||||
if (b == FRAME_SYNC) {
|
||||
os.write(FRAME_SYNC_ESC);
|
||||
}
|
||||
}
|
||||
os.write(FRAME_END);
|
||||
os.flush();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
logger.error("Unexpected exception occurred during writing a message", e);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private synchronized void disconnect(@Nullable String reason) {
|
||||
// remove all pending commands from the queue
|
||||
// notifying about send failure
|
||||
while (!this.sendQueue.isEmpty()) {
|
||||
SatelCommand cmd = this.sendQueue.poll();
|
||||
cmd.setState(State.FAILED);
|
||||
}
|
||||
final CommunicationChannel channel = this.channel;
|
||||
if (channel != null) {
|
||||
channel.disconnect();
|
||||
this.channel = null;
|
||||
// notify about connection status change
|
||||
this.dispatchEvent(new ConnectionStatusEvent(false, reason));
|
||||
}
|
||||
}
|
||||
|
||||
private void communicationLoop(TimeoutTimer timeoutTimer) {
|
||||
long reconnectionTime = 10 * 1000;
|
||||
boolean receivedResponse = false;
|
||||
SatelCommand command = null;
|
||||
String disconnectReason = null;
|
||||
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
// connect, if not connected yet
|
||||
if (this.channel == null) {
|
||||
long connectStartTime = System.currentTimeMillis();
|
||||
try {
|
||||
synchronized (this) {
|
||||
this.channel = connect();
|
||||
}
|
||||
} catch (ConnectionFailureException e) {
|
||||
logger.debug("Connection failed", e);
|
||||
// notify about connection failure
|
||||
this.dispatchEvent(new ConnectionStatusEvent(false, e.getMessage()));
|
||||
// try to reconnect after a while, if connection hasn't
|
||||
// been established
|
||||
Thread.sleep(reconnectionTime - System.currentTimeMillis() + connectStartTime);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// get next command and send it
|
||||
command = this.sendQueue.take();
|
||||
logger.debug("Sending message: {}", command.getRequest());
|
||||
timeoutTimer.start();
|
||||
boolean sent = this.writeMessage(command.getRequest());
|
||||
timeoutTimer.stop();
|
||||
if (!sent) {
|
||||
break;
|
||||
}
|
||||
command.setState(State.SENT);
|
||||
|
||||
SatelMessage response;
|
||||
do {
|
||||
// command sent, wait for response
|
||||
logger.trace("Waiting for response");
|
||||
timeoutTimer.start();
|
||||
response = this.readMessage();
|
||||
timeoutTimer.stop();
|
||||
if (response == null) {
|
||||
break;
|
||||
}
|
||||
logger.debug("Got response: {}", response);
|
||||
|
||||
if (!receivedResponse) {
|
||||
receivedResponse = true;
|
||||
// notify about connection success after first
|
||||
// response from the module
|
||||
this.dispatchEvent(new ConnectionStatusEvent(true));
|
||||
}
|
||||
if (command.matches(response)) {
|
||||
break;
|
||||
}
|
||||
logger.info("Ignoring response, it does not match command {}: {}",
|
||||
String.format("%02X", command.getRequest().getCommand()), response);
|
||||
} while (!Thread.currentThread().isInterrupted());
|
||||
|
||||
if (response == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (command.handleResponse(this, response)) {
|
||||
command.setState(State.SUCCEEDED);
|
||||
} else {
|
||||
command.setState(State.FAILED);
|
||||
}
|
||||
|
||||
command = null;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// exit thread
|
||||
} catch (Exception e) {
|
||||
// unexpected error, log and exit thread
|
||||
logger.info("Unhandled exception occurred in communication loop, disconnecting.", e);
|
||||
disconnectReason = "Unhandled exception: " + e.toString();
|
||||
} finally {
|
||||
// stop counting if thread interrupted
|
||||
timeoutTimer.stop();
|
||||
}
|
||||
|
||||
// either send or receive failed
|
||||
if (command != null) {
|
||||
command.setState(State.FAILED);
|
||||
}
|
||||
|
||||
disconnect(disconnectReason);
|
||||
}
|
||||
|
||||
/*
|
||||
* Respawns communication thread in case on any error and interrupts it in
|
||||
* case read/write operations take too long.
|
||||
*/
|
||||
private class CommunicationWatchdog extends Timer implements TimeoutTimer {
|
||||
private @Nullable Thread thread;
|
||||
private volatile long lastActivity;
|
||||
|
||||
public CommunicationWatchdog() {
|
||||
this.thread = null;
|
||||
this.lastActivity = 0;
|
||||
|
||||
this.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
CommunicationWatchdog.this.checkThread();
|
||||
}
|
||||
}, 0, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
this.lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.lastActivity = 0;
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
// cancel timer first to prevent reconnect
|
||||
this.cancel();
|
||||
// then stop communication thread
|
||||
final Thread thread = this.thread;
|
||||
if (thread != null) {
|
||||
thread.interrupt();
|
||||
try {
|
||||
thread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startCommunication() {
|
||||
final Thread thread = this.thread;
|
||||
if (thread != null && thread.isAlive()) {
|
||||
logger.error("Start communication canceled: communication thread is still alive");
|
||||
return;
|
||||
}
|
||||
// start new thread
|
||||
this.thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("Communication thread started");
|
||||
SatelModule.this.communicationLoop(CommunicationWatchdog.this);
|
||||
logger.debug("Communication thread stopped");
|
||||
}
|
||||
});
|
||||
this.thread.start();
|
||||
// if module is not initialized yet, send version command
|
||||
if (!SatelModule.this.isInitialized()) {
|
||||
SatelModule.this.sendCommand(new IntegraVersionCommand());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkThread() {
|
||||
final Thread thread = this.thread;
|
||||
logger.trace("Checking communication thread: {}, {}", thread != null,
|
||||
Boolean.toString(thread != null && thread.isAlive()));
|
||||
if (thread != null && thread.isAlive()) {
|
||||
long timePassed = (this.lastActivity == 0) ? 0 : System.currentTimeMillis() - this.lastActivity;
|
||||
|
||||
if (timePassed > SatelModule.this.timeout) {
|
||||
logger.error("Send/receive timeout, disconnecting module.");
|
||||
stop();
|
||||
thread.interrupt();
|
||||
try {
|
||||
// wait for the thread to terminate
|
||||
thread.join(100);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
SatelModule.this.disconnect("Send/receive timeout");
|
||||
}
|
||||
} else {
|
||||
startCommunication();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.types;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Interface for all types of control.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ControlType {
|
||||
|
||||
/**
|
||||
* Returns Satel command to control state for this kind of object type.
|
||||
*
|
||||
* @return command identifier
|
||||
*/
|
||||
byte getControlCommand();
|
||||
|
||||
/**
|
||||
* Returns object type for this kind of control.
|
||||
*
|
||||
* @return Integra object type
|
||||
*/
|
||||
ObjectType getObjectType();
|
||||
|
||||
/**
|
||||
* Returns set of states that may change for this control type.
|
||||
*
|
||||
* @return command identifier
|
||||
*/
|
||||
BitSet getControlledStates();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.types;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* Available door control types.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum DoorControl implements ControlType {
|
||||
OPEN(0x8A, DoorState.OPENED);
|
||||
|
||||
private byte controlCommand;
|
||||
private BitSet stateBits;
|
||||
|
||||
DoorControl(int controlCommand, DoorState... controlledStates) {
|
||||
this.controlCommand = (byte) controlCommand;
|
||||
this.stateBits = StateType.getStatesBitSet(controlledStates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getControlCommand() {
|
||||
return controlCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.DOOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitSet getControlledStates() {
|
||||
return stateBits;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.types;
|
||||
|
||||
/**
|
||||
* Available door states.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum DoorState implements StateType {
|
||||
OPENED(0x18),
|
||||
OPENED_LONG(0x19);
|
||||
|
||||
private byte refreshCommand;
|
||||
|
||||
DoorState(int refreshCommand) {
|
||||
this.refreshCommand = (byte) refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getRefreshCommand() {
|
||||
return refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength(boolean extendedCmd) {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.DOOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartByte() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytesCount(boolean extendedCmd) {
|
||||
return getPayloadLength(extendedCmd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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.satel.internal.types;
|
||||
|
||||
/**
|
||||
* Available Integra types.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum IntegraType {
|
||||
UNKNOWN(-1, "Unknown", 0, 0, 0),
|
||||
I24(0, "Integra 24", 4, 24, 4),
|
||||
I32(1, "Integra 32", 16, 32, 8),
|
||||
I64(2, "Integra 64", 32, 64, 16),
|
||||
I128(3, "Integra 128", 32, 128, 16),
|
||||
I128_SIM300(4, "Integra 128-WRL SIM300", 32, 128, 8),
|
||||
I128_LEON(132, "Integra 128-WRL LEON", 32, 128, 8),
|
||||
I64_PLUS(66, "Integra 64 Plus", 32, 64, 16),
|
||||
I128_PLUS(67, "Integra 128 Plus", 32, 128, 16),
|
||||
I256_PLUS(72, "Integra 256 Plus", 32, 256, 16, true);
|
||||
|
||||
private int code;
|
||||
private String name;
|
||||
private int partitions;
|
||||
private int zones;
|
||||
private int onMainboard;
|
||||
private boolean extPayload;
|
||||
|
||||
IntegraType(int code, String name, int partitions, int zones, int onMainboard) {
|
||||
this(code, name, partitions, zones, onMainboard, false);
|
||||
}
|
||||
|
||||
IntegraType(int code, String name, int partitions, int zones, int onMainboard, boolean extPayload) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.partitions = partitions;
|
||||
this.zones = zones;
|
||||
this.onMainboard = onMainboard;
|
||||
this.extPayload = extPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return name of Integra type
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return max number of partitions
|
||||
*/
|
||||
public int getPartitions() {
|
||||
return partitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return max number of zones
|
||||
*/
|
||||
public int getZones() {
|
||||
return zones;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return wired zones/outputs available on mainboard
|
||||
*/
|
||||
public int getOnMainboard() {
|
||||
return onMainboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if this Integra requires extended message payload
|
||||
*/
|
||||
public boolean hasExtPayload() {
|
||||
return this.extPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Integra type for given code.
|
||||
*
|
||||
* @param code code to get type for
|
||||
* @return Integra type object
|
||||
*/
|
||||
public static IntegraType valueOf(int code) {
|
||||
for (IntegraType val : IntegraType.values()) {
|
||||
if (val.code == code) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.types;
|
||||
|
||||
/**
|
||||
* Available object types:
|
||||
* <ul>
|
||||
* <li>ZONE - various kinds of devices connected to inputs, like reed switches, PIRs, etc</li>
|
||||
* <li>PARTITION - group of zones and outputs</li>
|
||||
* <li>OUTPUT - outputs to various devices like sirens, relays, etc.</li>
|
||||
* <li>DOORS - inputs connected to reed switches mounted on doors</li>
|
||||
* <li>TROUBLE - indication of a particular trouble</li>
|
||||
* <li>TROUBLE_MEMORY - particular trouble state latched</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum ObjectType {
|
||||
ZONE,
|
||||
PARTITION,
|
||||
OUTPUT,
|
||||
DOOR,
|
||||
TROUBLE,
|
||||
TROUBLE_MEMORY
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.types;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* Available output control types:
|
||||
* <ul>
|
||||
* <li>ON - sets an output</li>
|
||||
* <li>OFF - resets an output</li>
|
||||
* <li>TOGGLE - inverts output state</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum OutputControl implements ControlType {
|
||||
ON(0x88),
|
||||
OFF(0x89),
|
||||
TOGGLE(0x91);
|
||||
|
||||
private byte controlCommand;
|
||||
private BitSet stateBits;
|
||||
|
||||
OutputControl(int controlCommand) {
|
||||
this.controlCommand = (byte) controlCommand;
|
||||
this.stateBits = StateType.getStatesBitSet(OutputState.STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getControlCommand() {
|
||||
return controlCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.OUTPUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitSet getControlledStates() {
|
||||
return stateBits;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.types;
|
||||
|
||||
/**
|
||||
* Available output states.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum OutputState implements StateType {
|
||||
STATE(0x17);
|
||||
|
||||
private byte refreshCommand;
|
||||
|
||||
OutputState(int refreshCommand) {
|
||||
this.refreshCommand = (byte) refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getRefreshCommand() {
|
||||
return refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength(boolean extendedCmd) {
|
||||
return extendedCmd ? 32 : 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.OUTPUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartByte() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytesCount(boolean extendedCmd) {
|
||||
return getPayloadLength(extendedCmd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.satel.internal.types;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* Available partition control types.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum PartitionControl implements ControlType {
|
||||
ARM_MODE_0(0x80),
|
||||
ARM_MODE_1(0x81),
|
||||
ARM_MODE_2(0x82),
|
||||
ARM_MODE_3(0x83),
|
||||
DISARM(0x84),
|
||||
CLEAR_ALARM(0x85),
|
||||
FORCE_ARM_MODE_0(0xa0),
|
||||
FORCE_ARM_MODE_1(0xa1),
|
||||
FORCE_ARM_MODE_2(0xa2),
|
||||
FORCE_ARM_MODE_3(0xa3);
|
||||
|
||||
private byte controlCommand;
|
||||
private BitSet stateBits;
|
||||
|
||||
PartitionControl(int controlCommand) {
|
||||
this.controlCommand = (byte) controlCommand;
|
||||
// for simplicity we just add all partition states here
|
||||
this.stateBits = StateType.getStatesBitSet(PartitionState.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getControlCommand() {
|
||||
return controlCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.PARTITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitSet getControlledStates() {
|
||||
return stateBits;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.satel.internal.types;
|
||||
|
||||
/**
|
||||
* Available partition states.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum PartitionState implements StateType {
|
||||
ARMED(0x09),
|
||||
REALLY_ARMED(0x0a),
|
||||
ARMED_MODE_2(0x0b),
|
||||
ARMED_MODE_3(0x0c),
|
||||
FIRST_CODE_ENTERED(0x0d),
|
||||
ENTRY_TIME(0x0e),
|
||||
EXIT_TIME_GT_10(0x0f),
|
||||
EXIT_TIME_LT_10(0x10),
|
||||
TEMPORARY_BLOCKED(0x11),
|
||||
BLOCKED_FOR_GUARD(0x12),
|
||||
ALARM(0x13),
|
||||
FIRE_ALARM(0x14),
|
||||
ALARM_MEMORY(0x15),
|
||||
FIRE_ALARM_MEMORY(0x16),
|
||||
VIOLATED_ZONES(0x25),
|
||||
VERIFIED_ALARMS(0x27),
|
||||
ARMED_MODE_1(0x2a),
|
||||
WARNING_ALARMS(0x2b);
|
||||
|
||||
private byte refreshCommand;
|
||||
|
||||
PartitionState(int refreshCommand) {
|
||||
this.refreshCommand = (byte) refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getRefreshCommand() {
|
||||
return refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength(boolean extendedCmd) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.PARTITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartByte() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytesCount(boolean extendedCmd) {
|
||||
return getPayloadLength(extendedCmd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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.satel.internal.types;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Base of all kinds of Integra state.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface StateType {
|
||||
|
||||
/**
|
||||
* Returns Satel command to get current state for this state type.
|
||||
*
|
||||
* @return command identifier
|
||||
*/
|
||||
byte getRefreshCommand();
|
||||
|
||||
/**
|
||||
* Returns number of payload bytes in refresh command.
|
||||
*
|
||||
* @param extendedCmd if <code>true</code> return number of bytes for extended command
|
||||
* @return payload length
|
||||
*/
|
||||
int getPayloadLength(boolean extendedCmd);
|
||||
|
||||
/**
|
||||
* Returns object type for this kind of state.
|
||||
*
|
||||
* @return Integra object type
|
||||
*/
|
||||
ObjectType getObjectType();
|
||||
|
||||
/**
|
||||
* Returns state's first byte in the response buffer.
|
||||
*
|
||||
* @return start byte in the response
|
||||
*/
|
||||
int getStartByte();
|
||||
|
||||
/**
|
||||
* Returns number of state bytes in the response buffer.
|
||||
*
|
||||
* @param extendedCmd if <code>true</code> return number of bytes for extended command
|
||||
* @return bytes count in the response
|
||||
*/
|
||||
int getBytesCount(boolean extendedCmd);
|
||||
|
||||
/**
|
||||
* Builds bit set based on list of state types. Each bit is addressed by refresh command.
|
||||
*
|
||||
* @param states list of states
|
||||
* @return built bit set
|
||||
*/
|
||||
public static BitSet getStatesBitSet(StateType... states) {
|
||||
BitSet stateBits = new BitSet();
|
||||
for (StateType state : states) {
|
||||
stateBits.set(state.getRefreshCommand());
|
||||
}
|
||||
return stateBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker instance for lack of state type.
|
||||
*/
|
||||
public static final StateType NONE = new StateType() {
|
||||
|
||||
@Override
|
||||
public byte getRefreshCommand() {
|
||||
throw new UnsupportedOperationException("Illegal use of NONE state type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength(boolean extendedCmd) {
|
||||
throw new UnsupportedOperationException("Illegal use of NONE state type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
throw new UnsupportedOperationException("Illegal use of NONE state type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartByte() {
|
||||
throw new UnsupportedOperationException("Illegal use of NONE state type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytesCount(boolean extendedCmd) {
|
||||
throw new UnsupportedOperationException("Illegal use of NONE state type");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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.satel.internal.types;
|
||||
|
||||
/**
|
||||
* Supported memory of troubles.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum TroubleMemoryState implements StateType {
|
||||
// part 1
|
||||
TECHNICAL_ZONE(0x20, 47, 0, 16),
|
||||
EXPANDER_AC(0x20, 47, 16, 8),
|
||||
EXPANDER_BATT(0x20, 47, 24, 8),
|
||||
EXPANDER_NOBATT(0x20, 47, 32, 8),
|
||||
SYSTEM(0x20, 47, 40, 3),
|
||||
PTSA_AC(0x20, 47, 43, 1),
|
||||
PTSA_BATT(0x20, 47, 44, 1),
|
||||
PTSA_NOBATT(0x20, 47, 45, 1),
|
||||
ETHM1(0x20, 47, 46, 1),
|
||||
// part 2
|
||||
PROXIMITY_A(0x21, 39, 0, 8),
|
||||
PROXIMITY_B(0x21, 39, 8, 8),
|
||||
EXPANDER_OVERLOAD(0x21, 39, 16, 8),
|
||||
JAMMED_ACU100(0x21, 39, 24, 2),
|
||||
LCD_RESTART(0x21, 39, 26, 1),
|
||||
EXPANDER_RESTART(0x21, 39, 27, 8),
|
||||
// part 3
|
||||
DEVICE_LOBATT(0x22, 60, 15, 15),
|
||||
DEVICE_NOCOMM(0x22, 60, 30, 15),
|
||||
OUTPUT_NOCOMM(0x22, 60, 45, 15),
|
||||
// part 4
|
||||
EXPANDER_NOCOMM(0x23, 30, 0, 8),
|
||||
EXPANDER_SWITCHEROOED(0x23, 30, 8, 8),
|
||||
KEYPAD_NOCOMM(0x23, 30, 16, 1),
|
||||
KEYPAD_SWITCHEROOED(0x23, 30, 17, 1),
|
||||
ETHM1_NOLAN(0x23, 30, 18, 1),
|
||||
EXPANDER_TAMPER(0x23, 30, 19, 8),
|
||||
KEYPAD_TAMPER(0x23, 30, 27, 1),
|
||||
KEYPAD_INIT(0x23, 30, 28, 1),
|
||||
AUXILIARY_STM(0x23, 30, 29, 1),
|
||||
// part 5
|
||||
LONG_VIOLATION(0x24, 48, 0, 16),
|
||||
NO_VIOLATION(0x24, 48, 16, 16),
|
||||
ZONE_TAMPER(0x24, 48, 32, 16),
|
||||
// part 7 of troubles
|
||||
TECHNICAL_ZONE1(0x2d, 47, 16, 16),
|
||||
// part 6
|
||||
DEVICE_LOBATT1(0x2e, 45, 0, 15),
|
||||
DEVICE_NOCOMM1(0x2e, 45, 15, 15),
|
||||
OUTPUT_NOCOMM1(0x2e, 45, 30, 15),
|
||||
// part 7
|
||||
LONG_VIOLATION1(0x2f, 48, 0, 16),
|
||||
NO_VIOLATION1(0x2f, 48, 16, 16),
|
||||
ZONE_TAMPER1(0x2f, 48, 32, 16),;
|
||||
|
||||
private byte refreshCommand;
|
||||
private int payloadLength;
|
||||
private int startByte;
|
||||
private int bytesCount;
|
||||
|
||||
TroubleMemoryState(int refreshCommand, int payloadLength, int startByte, int bytesCount) {
|
||||
this.refreshCommand = (byte) refreshCommand;
|
||||
this.payloadLength = payloadLength;
|
||||
this.startByte = startByte;
|
||||
this.bytesCount = bytesCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getRefreshCommand() {
|
||||
return refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength(boolean extendedCmd) {
|
||||
return payloadLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.TROUBLE_MEMORY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartByte() {
|
||||
return startByte;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytesCount(boolean isExtended) {
|
||||
return bytesCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.satel.internal.types;
|
||||
|
||||
/**
|
||||
* Supported troubles.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum TroubleState implements StateType {
|
||||
// part 1
|
||||
TECHNICAL_ZONE(0x1b, 47, 0, 16),
|
||||
EXPANDER_AC(0x1b, 47, 16, 8),
|
||||
EXPANDER_BATT(0x1b, 47, 24, 8),
|
||||
EXPANDER_NOBATT(0x1b, 47, 32, 8),
|
||||
SYSTEM(0x1b, 47, 40, 3),
|
||||
PTSA_AC(0x1b, 47, 43, 1),
|
||||
PTSA_BATT(0x1b, 47, 44, 1),
|
||||
PTSA_NOBATT(0x1b, 47, 45, 1),
|
||||
ETHM1(0x1b, 47, 46, 1),
|
||||
// part 2
|
||||
PROXIMITY_A(0x1c, 26, 0, 8),
|
||||
PROXIMITY_B(0x1c, 26, 8, 8),
|
||||
EXPANDER_OVERLOAD(0x1c, 26, 16, 8),
|
||||
JAMMED_ACU100(0x1c, 26, 24, 2),
|
||||
// part 3
|
||||
DEVICE_LOBATT(0x1d, 60, 15, 15),
|
||||
DEVICE_NOCOMM(0x1d, 60, 30, 15),
|
||||
OUTPUT_NOCOMM(0x1d, 60, 45, 15),
|
||||
// part 4
|
||||
EXPANDER_NOCOMM(0x1e, 30, 0, 8),
|
||||
EXPANDER_SWITCHEROOED(0x1e, 30, 8, 8),
|
||||
KEYPAD_NOCOMM(0x1e, 30, 16, 1),
|
||||
KEYPAD_SWITCHEROOED(0x1e, 30, 17, 1),
|
||||
ETHM1_NOLAN(0x1e, 30, 18, 1),
|
||||
EXPANDER_TAMPER(0x1e, 30, 19, 8),
|
||||
KEYPAD_TAMPER(0x1e, 30, 27, 1),
|
||||
KEYPAD_INIT(0x1e, 30, 28, 1),
|
||||
AUXILIARY_STM(0x1e, 30, 29, 1),
|
||||
// part 5
|
||||
MASTER_KEYFOB(0x1f, 31, 0, 1),
|
||||
USER_KEYFOB(0x1f, 31, 1, 30),
|
||||
// part 6
|
||||
DEVICE_LOBATT1(0x2c, 45, 0, 15),
|
||||
DEVICE_NOCOMM1(0x2c, 45, 15, 15),
|
||||
OUTPUT_NOCOMM1(0x2c, 45, 30, 15),
|
||||
// part 7
|
||||
TECHNICAL_ZONE1(0x2d, 47, 0, 16),;
|
||||
|
||||
private byte refreshCommand;
|
||||
private int payloadLength;
|
||||
private int startByte;
|
||||
private int bytesCount;
|
||||
|
||||
TroubleState(int refreshCommand, int payloadLength, int startByte, int bytesCount) {
|
||||
this.refreshCommand = (byte) refreshCommand;
|
||||
this.payloadLength = payloadLength;
|
||||
this.startByte = startByte;
|
||||
this.bytesCount = bytesCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getRefreshCommand() {
|
||||
return refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength(boolean extendedCmd) {
|
||||
return payloadLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.TROUBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartByte() {
|
||||
return startByte;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytesCount(boolean isExtended) {
|
||||
return bytesCount;
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.types;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* Available zone control types.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum ZoneControl implements ControlType {
|
||||
BYPASS(0x86, ZoneState.BYPASS),
|
||||
UNBYPASS(0x87, ZoneState.BYPASS),
|
||||
ISOLATE(0x90, ZoneState.ISOLATE);
|
||||
|
||||
private byte controlCommand;
|
||||
private BitSet stateBits;
|
||||
|
||||
ZoneControl(int controlCommand, ZoneState... controlledStates) {
|
||||
this.controlCommand = (byte) controlCommand;
|
||||
this.stateBits = StateType.getStatesBitSet(controlledStates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getControlCommand() {
|
||||
return controlCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.ZONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitSet getControlledStates() {
|
||||
return stateBits;
|
||||
}
|
||||
}
|
||||
@@ -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.satel.internal.types;
|
||||
|
||||
/**
|
||||
* Available zone states.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public enum ZoneState implements StateType {
|
||||
VIOLATION(0x00),
|
||||
TAMPER(0x01),
|
||||
ALARM(0x02),
|
||||
TAMPER_ALARM(0x03),
|
||||
ALARM_MEMORY(0x04),
|
||||
TAMPER_ALARM_MEMORY(0x05),
|
||||
BYPASS(0x06),
|
||||
NO_VIOLATION_TROUBLE(0x07),
|
||||
LONG_VIOLATION_TROUBLE(0x08),
|
||||
ISOLATE(0x26),
|
||||
MASKED(0x28),
|
||||
MASKED_MEMORY(0x29);
|
||||
|
||||
private byte refreshCommand;
|
||||
|
||||
ZoneState(int refreshCommand) {
|
||||
this.refreshCommand = (byte) refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getRefreshCommand() {
|
||||
return refreshCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength(boolean extendedCmd) {
|
||||
return extendedCmd ? 32 : 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getObjectType() {
|
||||
return ObjectType.ZONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartByte() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytesCount(boolean extendedCmd) {
|
||||
return getPayloadLength(extendedCmd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="satel" 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>Satel Binding</name>
|
||||
<description>This is the binding for Satel INTEGRA alarm systems using either ETHM-1 or INT-RS communication module</description>
|
||||
<author>Krzysztof Goworek</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,19 @@
|
||||
# config status
|
||||
config-status.hostEmpty = Host name or IP of ETHM-1 must be provided.
|
||||
config-status.portEmpty = Serial port of INT-RS must be provided.
|
||||
|
||||
# actions
|
||||
actionReadEventLabel = Read event
|
||||
actionReadEventDesc = Reads a single record from the event log.
|
||||
actionInputIndexLabel = Event index
|
||||
actionInputIndexDesc = Index of the event to read
|
||||
actionOutputIndexLabel = Current Index
|
||||
actionOutputIndexDesc = Index of this record in the event log.
|
||||
actionOutputPrevIndexLabel = Previous Index
|
||||
actionOutputPrevIndexDesc = Index of the previous record in the event log. Use this value to iterate over the log.
|
||||
actionOutputTimestampLabel = Date and Time
|
||||
actionOutputTimestampDesc = Date and time when the event happened.
|
||||
actionOutputDescriptionLabel = Description
|
||||
actionOutputDescriptionDesc = Textual description of the event.
|
||||
actionOutputDetailsLabel = Details
|
||||
actionOutputDetailsDesc = Additional details about the event, like names of partitions, zones, users, etc.
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
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="atd-100">
|
||||
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ethm-1"/>
|
||||
<bridge-type-ref id="int-rs"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>ATD-100</label>
|
||||
<description><![CDATA[Wireless temperature detector designed for operation as part of the ABAX two-way wireless system.]]></description>
|
||||
<category>Sensor</category>
|
||||
|
||||
<channels>
|
||||
<channel typeId="zoneTemperature" id="temperature"/>
|
||||
<channel typeId="system.low-battery" id="device_lobatt"/>
|
||||
<channel typeId="device_nocomm" id="device_nocomm"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="id" type="integer" min="1" max="256" required="true">
|
||||
<label>Zone Number</label>
|
||||
<description>Zone number in the alarm system monitored by this detector</description>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="min" min="1">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Polling interval in minutes.</description>
|
||||
<default>15</default>
|
||||
<unitLabel>Minutes</unitLabel>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="zoneTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Current temperature in the zone</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="device_nocomm">
|
||||
<item-type>Switch</item-type>
|
||||
<label>No Communication</label>
|
||||
<description>Indicates communication troubles with the wireless device</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
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="ethm-1">
|
||||
<label>ETHM-1</label>
|
||||
<description>Ethernet communication module</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>Host or IP</label>
|
||||
<description>Host name or IP address of ETHM-1 module.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" min="1" max="65535">
|
||||
<label>Port Number</label>
|
||||
<description>TCP port for the integration protocol.</description>
|
||||
<default>7094</default>
|
||||
</parameter>
|
||||
<parameter name="timeout" type="integer" unit="ms">
|
||||
<label>Timeout</label>
|
||||
<description>Timeout value in milliseconds for connect, read and write operations.</description>
|
||||
<default>5000</default>
|
||||
<unitLabel>Milliseconds</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="ms">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Polling interval in milliseconds.</description>
|
||||
<default>5000</default>
|
||||
<unitLabel>Milliseconds</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="userCode" type="text" pattern="[0-9]{4,8}">
|
||||
<label>User Code</label>
|
||||
<description>Security code of the user in behalf of all operations will be executed. If empty, only read operations
|
||||
are allowed.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="encryptionKey" type="text">
|
||||
<label>Encryption Key</label>
|
||||
<description>Encryption key used to encrypt data sent and received, if empty communication is not encrypted.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="encoding" type="text">
|
||||
<label>Encoding</label>
|
||||
<description>Encoding used for all the texts received from the module.</description>
|
||||
<default>windows-1250</default>
|
||||
</parameter>
|
||||
<parameter name="extCommands" type="boolean">
|
||||
<label>Extended Commands Support</label>
|
||||
<description>Check this option to enable extended commands supported only by ETHM-1 Plus and newer versions of
|
||||
ETHM-1. Turn off in case of communication timeouts.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
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="event-log">
|
||||
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ethm-1"/>
|
||||
<bridge-type-ref id="int-rs"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Event Log</label>
|
||||
<description><![CDATA[A virtual thing that allows reading records from the alarm system event log.<br>
|
||||
Event log must read sequentially starting from the most recent record.]]></description>
|
||||
|
||||
<channels>
|
||||
<channel typeId="index" id="index"/>
|
||||
<channel typeId="prev_index" id="prev_index"/>
|
||||
<channel typeId="timestamp" id="timestamp"/>
|
||||
<channel typeId="description" id="description"/>
|
||||
<channel typeId="details" id="details"/>
|
||||
</channels>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="index">
|
||||
<item-type>Number</item-type>
|
||||
<label>Current Index</label>
|
||||
<description>Index of the current record in the event log. Send '-1' to get most recent record.</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="prev_index">
|
||||
<item-type>Number</item-type>
|
||||
<label>Previous Index</label>
|
||||
<description>Index of the previous record in the event log. Use this value to iterate over the log.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="timestamp">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date and Time</label>
|
||||
<description>Date and time when the event happened.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="description">
|
||||
<item-type>String</item-type>
|
||||
<label>Description</label>
|
||||
<description>Textual description of the event.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="details">
|
||||
<item-type>String</item-type>
|
||||
<label>Details</label>
|
||||
<description>Additional details about the event, like names of partitions, zones, users, etc.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
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="int-rs">
|
||||
<label>INT-RS</label>
|
||||
<description>RS-232 communication module</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="port" type="text" required="true">
|
||||
<label>Serial Port</label>
|
||||
<description>Serial port connected to the module.</description>
|
||||
<context>serial-port</context>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
</parameter>
|
||||
<parameter name="timeout" type="integer" unit="ms">
|
||||
<label>Timeout</label>
|
||||
<description>Timeout value in milliseconds for connect, read and write operations.</description>
|
||||
<default>5000</default>
|
||||
<unitLabel>Milliseconds</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="ms">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Current state polling interval, in milliseconds.</description>
|
||||
<default>5000</default>
|
||||
<unitLabel>Milliseconds</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="userCode" type="text" pattern="[0-9]{4,8}">
|
||||
<label>User Code</label>
|
||||
<description>Security code of the user in behalf of all operations will be executed.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="encoding" type="text">
|
||||
<label>Encoding</label>
|
||||
<description>Encoding for all the texts received from the module.</description>
|
||||
<default>windows-1250</default>
|
||||
</parameter>
|
||||
<parameter name="extCommands" type="boolean">
|
||||
<label>Extended Commands Support</label>
|
||||
<description>Check this option to enable extended commands supported only by version 2.xx of INT-RS. Turn off in
|
||||
case of communication timeouts.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
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="output">
|
||||
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ethm-1"/>
|
||||
<bridge-type-ref id="int-rs"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Output</label>
|
||||
<description><![CDATA[An output from the alarm system.<br>The system supports the following outputs:<ul>
|
||||
<li>hardwired - on the control panel electronics board and in expanders. The number of available hardwired outputs is determined by the control panel during identification procedure.</li>
|
||||
<li>wireless - in the INTEGRA 128-WRL control panel and/or when the ACU-120, ACU-270, ACU-100 or ACU-250 controller is connected. The number of available wireless outputs depends on the number of wireless devices registered in the system and is determined during the procedure of adding wireless devices.</li>
|
||||
<li>virtual - the outputs which do not exist physically, but can be used e.g. for execution of logical functions.</li></ul>]]>
|
||||
</description>
|
||||
|
||||
<channels>
|
||||
<channel typeId="output_state" id="state"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="id" type="integer" min="1" max="256" required="true">
|
||||
<label>Output Number</label>
|
||||
<description>Number that identifies the output in the alarm system</description>
|
||||
</parameter>
|
||||
<parameter name="invertState" type="boolean" required="false">
|
||||
<label>Invert State</label>
|
||||
<description>Changes active (ON) state to 0</description>
|
||||
</parameter>
|
||||
<parameter name="commandOnly" type="boolean" required="false">
|
||||
<label>Command Only</label>
|
||||
<description>Accepts commands only, does not update state of the thing</description>
|
||||
</parameter>
|
||||
<parameter name="wireless" type="boolean" required="false">
|
||||
<label>Wireless Output</label>
|
||||
<description>This output controls a wireless device like ASP-100 R, ASW-100 E, etc.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="output_state">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Output State</label>
|
||||
<description>Represents state of the output</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
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="partition">
|
||||
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ethm-1"/>
|
||||
<bridge-type-ref id="int-rs"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Partition</label>
|
||||
<description><![CDATA[The partition is a separated area within the premises protected by the security alarm system.<br>
|
||||
The subdivision into partitions enables arming/disarming the system only in part of the protected area, as well as limiting
|
||||
access to some portion of the premises to selected users.]]>
|
||||
</description>
|
||||
|
||||
<channels>
|
||||
<channel typeId="armed" id="armed"/>
|
||||
<channel typeId="really_armed" id="really_armed"/>
|
||||
<channel typeId="armed_mode_1" id="armed_mode_1"/>
|
||||
<channel typeId="armed_mode_2" id="armed_mode_2"/>
|
||||
<channel typeId="armed_mode_3" id="armed_mode_3"/>
|
||||
<channel typeId="first_code_entered" id="first_code_entered"/>
|
||||
<channel typeId="entry_time" id="entry_time"/>
|
||||
<channel typeId="exit_time_gt_10" id="exit_time_gt_10"/>
|
||||
<channel typeId="exit_time_lt_10" id="exit_time_lt_10"/>
|
||||
<channel typeId="temporary_blocked" id="temporary_blocked"/>
|
||||
<channel typeId="blocked_for_guard" id="blocked_for_guard"/>
|
||||
<channel typeId="alarm" id="alarm"/>
|
||||
<channel typeId="alarm_memory" id="alarm_memory"/>
|
||||
<channel typeId="fire_alarm" id="fire_alarm"/>
|
||||
<channel typeId="fire_alarm_memory" id="fire_alarm_memory"/>
|
||||
<channel typeId="verified_alarms" id="verified_alarms"/>
|
||||
<channel typeId="warning_alarms" id="warning_alarms"/>
|
||||
<channel typeId="violated_zones" id="violated_zones"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="id" type="integer" min="1" max="32" required="true">
|
||||
<label>Partition Number</label>
|
||||
<description></description>
|
||||
</parameter>
|
||||
<parameter name="forceArming" type="boolean" required="false">
|
||||
<label>Force Arming</label>
|
||||
<description>Forces arming the partition regardless of ongoing troubles and violations.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="armed">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Armed</label>
|
||||
<description>Active when the partion is armed in any mode or arming is in progress</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="really_armed">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Really Armed</label>
|
||||
<description>Active when the partion is armed in any mode</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="armed_mode_1" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Armed in Mode 1</label>
|
||||
<description>Active when the partion is armed in mode 1</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="armed_mode_2" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Armed in Mode 2</label>
|
||||
<description>Active when the partion is armed in mode 2</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="armed_mode_3" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Armed in Mode 3</label>
|
||||
<description>Active when the partion is armed in mode 3</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="first_code_entered" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>First Code Entered</label>
|
||||
<description>Active when first code of required two codes has been entered</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="entry_time" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Entry Time</label>
|
||||
<description>Active in entry time</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="exit_time_gt_10" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Exit Time > 10s</label>
|
||||
<description>Active when arming is in progress and time to exit is greater than ten seconds</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="exit_time_lt_10" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Exit Time < 10s</label>
|
||||
<description>Active when arming is in progress and time to exit is less than ten seconds</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temporary_blocked" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Temporary Blocked</label>
|
||||
<description>Active when the partition is blocked after arming</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="blocked_for_guard" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Blocked for Guard</label>
|
||||
<description>Active when the partition is blocked for guard round</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alarm">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Alarm</label>
|
||||
<description>Active when there is ongoing alarm in the patition</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alarm_memory">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Alarm Memory</label>
|
||||
<description>Active when there had been an alarm in the patition</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fire_alarm">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Fire Alarm</label>
|
||||
<description>Active when there is ongoing fire alarm in the patition</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fire_alarm_memory">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Fire Alarm Memory</label>
|
||||
<description>Active when there had been a fire alarm in the patition</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="verified_alarms" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Verified Alarms</label>
|
||||
<description>Active when two zones triggered an alarm during verification time</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="warning_alarms" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Warning Alarms</label>
|
||||
<description>Active when there are warning alarms in the partition</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="violated_zones" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Violated Zones</label>
|
||||
<description>Active when there are violated zones in the partition</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
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="shutter">
|
||||
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ethm-1"/>
|
||||
<bridge-type-ref id="int-rs"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Roller Shutter</label>
|
||||
<description>A roller shutter connected to two subsequent outputs.</description>
|
||||
|
||||
<channels>
|
||||
<channel typeId="shutter_state" id="shutter_state"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="upId" type="integer" min="1" max="256" required="true">
|
||||
<label>Up Output</label>
|
||||
<description>Output number for "up" direction.</description>
|
||||
</parameter>
|
||||
<parameter name="downId" type="integer" min="1" max="256" required="true">
|
||||
<label>Down Output</label>
|
||||
<description>Output number for "down" direction.</description>
|
||||
</parameter>
|
||||
<parameter name="commandOnly" type="boolean" required="false">
|
||||
<label>Command Only</label>
|
||||
<description>Accepts commands only, does not update state of the thing</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="shutter_state">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Shutter State</label>
|
||||
<description>Represents state of the roller shutter</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
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="system">
|
||||
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ethm-1"/>
|
||||
<bridge-type-ref id="int-rs"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Alarm System</label>
|
||||
<description>A virtual thing describing general status of the alarm system.</description>
|
||||
|
||||
<channels>
|
||||
<channel typeId="date_time" id="date_time"/>
|
||||
<channel typeId="troubles" id="troubles"/>
|
||||
<channel typeId="troubles_memory" id="troubles_memory"/>
|
||||
<channel typeId="service_mode" id="service_mode"/>
|
||||
<channel typeId="acu100_present" id="acu100_present"/>
|
||||
<channel typeId="intrx_present" id="intrx_present"/>
|
||||
<channel typeId="grade23_set" id="grade23_set"/>
|
||||
<channel typeId="user_code" id="user_code"/>
|
||||
</channels>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="date_time">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date and Time</label>
|
||||
<description>Current date and time in the alarm system</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="troubles">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Troubles</label>
|
||||
<description>Active when the system has troubles (trouble led is blinking on a panel)</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="troubles_memory" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Troubles Memory</label>
|
||||
<description>Memorized state of system troubles</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="service_mode">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Service Mode</label>
|
||||
<description>Active when the system is in service mode</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="acu100_present" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>ACU-100 Present</label>
|
||||
<description>Active when there is an ACU-100 module installed in the system</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="intrx_present" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>INT-RX Present</label>
|
||||
<description>Active when there is an INT-RX module installed in the system</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="grade23_set" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Grade2/Grade3 Set</label>
|
||||
<description>Active when Grade2/Grade3 option is set in the system</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="user_code">
|
||||
<item-type>String</item-type>
|
||||
<label>User Code</label>
|
||||
<description>Accepts string commands that override configured user code. Send empty string to revert user code to the
|
||||
one in the binding configuration.</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,134 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="satel"
|
||||
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="zone">
|
||||
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ethm-1"/>
|
||||
<bridge-type-ref id="int-rs"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Zone</label>
|
||||
<description><![CDATA[A separated part of the protected area that can supervised by a detector connected to the alarm system.<br>The system supports the following zones:<ul>
|
||||
<li>hardwired - on the control panel electronics board, in keypads and expanders. The number of available hardwired zones is determined by the control panel during identification procedure.</li>
|
||||
<li>wireless - in the INTEGRA 128-WRL control panel and/or when the ACU-120, ACU-270, ACU-100 or ACU-250 controller is connected. The number of available wireless zones depends on the number of wireless devices registered in the system and is determined during the procedure of adding the devices.</li>
|
||||
<li>virtual - zones which physically do not exist, but have been programmed as FOLLOW OUTPUT or are controlled by means of a keyfob.</li></ul>]]></description>
|
||||
|
||||
<channels>
|
||||
<channel typeId="violation" id="violation"/>
|
||||
<channel typeId="tamper" id="tamper"/>
|
||||
<channel typeId="alarm" id="alarm"/>
|
||||
<channel typeId="tamper_alarm" id="tamper_alarm"/>
|
||||
<channel typeId="alarm_memory" id="alarm_memory"/>
|
||||
<channel typeId="tamper_alarm_memory" id="tamper_alarm_memory"/>
|
||||
<channel typeId="bypass" id="bypass"/>
|
||||
<channel typeId="no_violation_trouble" id="no_violation_trouble"/>
|
||||
<channel typeId="long_violation_trouble" id="long_violation_trouble"/>
|
||||
<channel typeId="isolate" id="isolate"/>
|
||||
<channel typeId="masked" id="masked"/>
|
||||
<channel typeId="masked_memory" id="masked_memory"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="id" type="integer" min="1" max="256" required="true">
|
||||
<label>Zone Number</label>
|
||||
<description>Number that identifies the zone in the alarm system</description>
|
||||
</parameter>
|
||||
<parameter name="invertState" type="boolean" required="false">
|
||||
<label>Invert State</label>
|
||||
<description>Changes active (ON) state to 0</description>
|
||||
</parameter>
|
||||
<parameter name="wireless" type="boolean" required="false">
|
||||
<label>Wireless Zone</label>
|
||||
<description>This zone is monitored by a wireless detector like APD-100, AFD-100, etc</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="violation">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Violation</label>
|
||||
<description>Active when violation is detected in the zone</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tamper">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Tamper</label>
|
||||
<description>Active when detector in the zone is tampered</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alarm">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Alarm</label>
|
||||
<description>Active when violation is detected in the zone and zone is armed</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tamper_alarm">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Tamper Alarm</label>
|
||||
<description>Active when detector in the zone is tampered and zone is armed</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alarm_memory">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Alarm Memory</label>
|
||||
<description>Memorized alarm state for the zone</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tamper_alarm_memory">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Tamper Alarm Memory</label>
|
||||
<description>Memorized tamper alarm state for the zone</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="bypass">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Bypass</label>
|
||||
<description>Active when zone is bypassed</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="no_violation_trouble" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>No Violation Trouble</label>
|
||||
<description>Active when no violation has been detected in the zone for configured time</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="long_violation_trouble" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Long Violation Trouble</label>
|
||||
<description>Active when violation in the zone lasts longer than configured time</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="isolate" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Isolate</label>
|
||||
<description>Active when zone is isolated</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="masked" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Masked</label>
|
||||
<description>Active when zone is masked</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="masked_memory" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Masked Memory</label>
|
||||
<description>Memorized masked state for the zone</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user