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.nikobus-${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-nikobus" description="Nikobus 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.nikobus/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -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.nikobus.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link NikobusBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "nikobus";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID BRIDGE_TYPE_PCLINK = new ThingTypeUID(BINDING_ID, "pc-link");
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_PUSH_BUTTON = new ThingTypeUID(BINDING_ID, "push-button");
|
||||
public static final ThingTypeUID THING_TYPE_SWITCH_MODULE = new ThingTypeUID(BINDING_ID, "switch-module");
|
||||
public static final ThingTypeUID THING_TYPE_DIMMER_MODULE = new ThingTypeUID(BINDING_ID, "dimmer-module");
|
||||
public static final ThingTypeUID THING_TYPE_ROLLERSHUTTER_MODULE = new ThingTypeUID(BINDING_ID,
|
||||
"rollershutter-module");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_BUTTON = "button";
|
||||
public static final String CHANNEL_OUTPUT_PREFIX = "output-";
|
||||
|
||||
// Configuration parameters
|
||||
public static final String CONFIG_REFRESH_INTERVAL = "refreshInterval";
|
||||
public static final String CONFIG_IMPACTED_MODULES = "impactedModules";
|
||||
public static final String CONFIG_ADDRESS = "address";
|
||||
public static final String CONFIG_PORT_NAME = "port";
|
||||
}
|
||||
@@ -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.nikobus.internal;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusDimmerModuleHandler;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusPcLinkHandler;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusPushButtonHandler;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusRollershutterModuleHandler;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusSwitchModuleHandler;
|
||||
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.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link NikobusHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.nikobus", service = ThingHandlerFactory.class)
|
||||
public class NikobusHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(BRIDGE_TYPE_PCLINK, THING_TYPE_PUSH_BUTTON, THING_TYPE_SWITCH_MODULE,
|
||||
THING_TYPE_DIMMER_MODULE, THING_TYPE_ROLLERSHUTTER_MODULE).collect(Collectors.toSet()));
|
||||
|
||||
private @NonNullByDefault({}) SerialPortManager serialPortManager;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (BRIDGE_TYPE_PCLINK.equals(thingTypeUID)) {
|
||||
return new NikobusPcLinkHandler((Bridge) thing, serialPortManager);
|
||||
}
|
||||
|
||||
if (THING_TYPE_PUSH_BUTTON.equals(thingTypeUID)) {
|
||||
return new NikobusPushButtonHandler(thing);
|
||||
}
|
||||
|
||||
if (THING_TYPE_SWITCH_MODULE.equals(thingTypeUID)) {
|
||||
return new NikobusSwitchModuleHandler(thing);
|
||||
}
|
||||
|
||||
if (THING_TYPE_DIMMER_MODULE.equals(thingTypeUID)) {
|
||||
return new NikobusDimmerModuleHandler(thing);
|
||||
}
|
||||
|
||||
if (THING_TYPE_ROLLERSHUTTER_MODULE.equals(thingTypeUID)) {
|
||||
return new NikobusRollershutterModuleHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nikobus.internal.NikobusBindingConstants;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
|
||||
/**
|
||||
* The {@link NikobusBaseThingHandler} class defines utility logic to be consumed by Nikobus thing(s).
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class NikobusBaseThingHandler extends BaseThingHandler {
|
||||
private @Nullable String address;
|
||||
|
||||
protected NikobusBaseThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
address = (String) getConfig().get(NikobusBindingConstants.CONFIG_ADDRESS);
|
||||
if (address == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Address must be set!");
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
protected @Nullable NikobusPcLinkHandler getPcLink() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
return (NikobusPcLinkHandler) bridge.getHandler();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getAddress() {
|
||||
String address = this.address;
|
||||
if (address == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.handler;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup;
|
||||
import org.openhab.binding.nikobus.internal.utils.Utils;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link NikobusDimmerModuleHandler} is responsible for communication between Nikobus dim-controller and binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusDimmerModuleHandler extends NikobusSwitchModuleHandler {
|
||||
private @Nullable Future<?> requestUpdateFuture;
|
||||
|
||||
public NikobusDimmerModuleHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
Utils.cancel(requestUpdateFuture);
|
||||
requestUpdateFuture = null;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestStatus(SwitchModuleGroup group) {
|
||||
Utils.cancel(requestUpdateFuture);
|
||||
super.requestStatus(group);
|
||||
requestUpdateFuture = scheduler.schedule(() -> super.requestStatus(group), 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int valueFromCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
return Math.round(((PercentType) command).floatValue() / 100f * 255f);
|
||||
}
|
||||
|
||||
return super.valueFromCommand(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State stateFromValue(int value) {
|
||||
int result = Math.round(value * 100f / 255f);
|
||||
return new PercentType(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.handler;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.CHANNEL_OUTPUT_PREFIX;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusCommand;
|
||||
import org.openhab.binding.nikobus.internal.protocol.SwitchModuleCommandFactory;
|
||||
import org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusSwitchModuleHandler} is responsible for communication between Nikobus modules and binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class NikobusModuleHandler extends NikobusBaseThingHandler {
|
||||
private final EnumSet<SwitchModuleGroup> pendingRefresh = EnumSet.noneOf(SwitchModuleGroup.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(NikobusModuleHandler.class);
|
||||
private final Map<String, Integer> cachedStates = new HashMap<>();
|
||||
private final List<ChannelUID> linkedChannels = new ArrayList<>();
|
||||
|
||||
protected NikobusModuleHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
|
||||
synchronized (cachedStates) {
|
||||
cachedStates.clear();
|
||||
}
|
||||
|
||||
synchronized (pendingRefresh) {
|
||||
pendingRefresh.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
refreshChannel(channelUID);
|
||||
} else {
|
||||
processWrite(channelUID, command);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshChannel(ChannelUID channelUID) {
|
||||
logger.debug("Refreshing channel '{}'", channelUID.getId());
|
||||
|
||||
if (!isLinked(channelUID)) {
|
||||
logger.debug("Refreshing channel '{}' skipped since it is not linked", channelUID.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
updateGroup(SwitchModuleGroup.mapFromChannel(channelUID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
synchronized (linkedChannels) {
|
||||
linkedChannels.add(channelUID);
|
||||
}
|
||||
super.channelLinked(channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelUnlinked(ChannelUID channelUID) {
|
||||
synchronized (linkedChannels) {
|
||||
linkedChannels.remove(channelUID);
|
||||
}
|
||||
super.channelUnlinked(channelUID);
|
||||
}
|
||||
|
||||
public void refreshModule() {
|
||||
Set<SwitchModuleGroup> groups = new HashSet<>();
|
||||
synchronized (linkedChannels) {
|
||||
for (ChannelUID channelUID : linkedChannels) {
|
||||
groups.add(SwitchModuleGroup.mapFromChannel(channelUID));
|
||||
}
|
||||
}
|
||||
|
||||
if (groups.isEmpty()) {
|
||||
logger.debug("Nothing to refresh for '{}'", thing.getUID());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Refreshing {} - {}", thing.getUID(), groups);
|
||||
|
||||
for (SwitchModuleGroup group : groups) {
|
||||
updateGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestStatus(SwitchModuleGroup group) {
|
||||
updateGroup(group);
|
||||
}
|
||||
|
||||
private void updateGroup(SwitchModuleGroup group) {
|
||||
synchronized (pendingRefresh) {
|
||||
if (pendingRefresh.contains(group)) {
|
||||
logger.debug("Refresh already scheduled for group {} of module '{}'", group, getAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
pendingRefresh.add(group);
|
||||
}
|
||||
|
||||
logger.debug("Refreshing group {} of switch module '{}'", group, getAddress());
|
||||
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
NikobusCommand command = SwitchModuleCommandFactory.createReadCommand(getAddress(), group,
|
||||
result -> processStatusUpdate(result, group));
|
||||
pcLink.sendCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
private void processStatusUpdate(NikobusCommand.Result result, SwitchModuleGroup group) {
|
||||
try {
|
||||
String responsePayload = result.get();
|
||||
|
||||
logger.debug("processStatusUpdate '{}' for group {} in module '{}'", responsePayload, group, getAddress());
|
||||
|
||||
if (thing.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
// Update channel's statuses based on response.
|
||||
for (int i = 0; i < group.getCount(); i++) {
|
||||
String channelId = CHANNEL_OUTPUT_PREFIX + (i + group.getOffset());
|
||||
String responseDigits = responsePayload.substring(9 + (i * 2), 11 + (i * 2));
|
||||
|
||||
int value = Integer.parseInt(responseDigits, 16);
|
||||
|
||||
updateStateAndCacheValue(channelId, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Processing response for '{}'-{} failed with {}", getAddress(), group, e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} finally {
|
||||
synchronized (pendingRefresh) {
|
||||
pendingRefresh.remove(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStateAndCacheValue(String channelId, int value) {
|
||||
if (value < 0x00 || value > 0xff) {
|
||||
throw new IllegalArgumentException("Invalid range. 0x00 - 0xff expected but got value " + value);
|
||||
}
|
||||
|
||||
logger.debug("setting channel '{}' to {}", channelId, value);
|
||||
|
||||
synchronized (cachedStates) {
|
||||
cachedStates.put(channelId, value);
|
||||
}
|
||||
|
||||
updateState(channelId, stateFromValue(value));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unused", "null" })
|
||||
private void processWrite(ChannelUID channelUID, Command command) {
|
||||
StringBuilder commandPayload = new StringBuilder();
|
||||
SwitchModuleGroup group = SwitchModuleGroup.mapFromChannel(channelUID);
|
||||
|
||||
for (int i = group.getOffset(); i < group.getOffset() + group.getCount(); i++) {
|
||||
String channelId = CHANNEL_OUTPUT_PREFIX + i;
|
||||
Integer digits;
|
||||
|
||||
if (channelId.equals(channelUID.getId())) {
|
||||
digits = valueFromCommand(command);
|
||||
updateStateAndCacheValue(channelId, digits.intValue());
|
||||
} else {
|
||||
synchronized (cachedStates) {
|
||||
digits = cachedStates.get(channelId);
|
||||
}
|
||||
}
|
||||
|
||||
if (digits == null) {
|
||||
commandPayload.append("00");
|
||||
logger.warn("no cached value found for '{}' in module '{}'", channelId, getAddress());
|
||||
} else {
|
||||
commandPayload.append(String.format("%02X", digits.intValue()));
|
||||
}
|
||||
}
|
||||
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
pcLink.sendCommand(SwitchModuleCommandFactory.createWriteCommand(getAddress(), group,
|
||||
commandPayload.toString(), this::processWriteCommandResponse));
|
||||
}
|
||||
}
|
||||
|
||||
private void processWriteCommandResponse(NikobusCommand.Result result) {
|
||||
try {
|
||||
String responsePayload = result.get();
|
||||
|
||||
logger.debug("processWriteCommandResponse '{}'", responsePayload);
|
||||
|
||||
if (thing.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Processing write confirmation failed with {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int valueFromCommand(Command command);
|
||||
|
||||
protected abstract State stateFromValue(int value);
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.handler;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.CONFIG_REFRESH_INTERVAL;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
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.nikobus.internal.NikobusBindingConstants;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusCommand;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusConnection;
|
||||
import org.openhab.binding.nikobus.internal.utils.Utils;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusPcLinkHandler} is responsible for handling commands, which are
|
||||
* sent or received from the PC-Link Nikobus component.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusPcLinkHandler extends BaseBridgeHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(NikobusPcLinkHandler.class);
|
||||
private final Map<String, Runnable> commandListeners = Collections.synchronizedMap(new HashMap<>());
|
||||
private final LinkedList<NikobusCommand> pendingCommands = new LinkedList<>();
|
||||
private final StringBuilder stringBuilder = new StringBuilder();
|
||||
private final SerialPortManager serialPortManager;
|
||||
private @Nullable NikobusConnection connection;
|
||||
private @Nullable NikobusCommand currentCommand;
|
||||
private @Nullable ScheduledFuture<?> scheduledRefreshFuture;
|
||||
private @Nullable ScheduledFuture<?> scheduledSendCommandWatchdogFuture;
|
||||
private @Nullable String ack;
|
||||
private int refreshThingIndex = 0;
|
||||
|
||||
public NikobusPcLinkHandler(Bridge bridge, SerialPortManager serialPortManager) {
|
||||
super(bridge);
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
ack = null;
|
||||
stringBuilder.setLength(0);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
String portName = (String) getConfig().get(NikobusBindingConstants.CONFIG_PORT_NAME);
|
||||
if (portName == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!");
|
||||
return;
|
||||
}
|
||||
|
||||
connection = new NikobusConnection(serialPortManager, portName, this::processReceivedValue);
|
||||
|
||||
int refreshInterval = ((Number) getConfig().get(CONFIG_REFRESH_INTERVAL)).intValue();
|
||||
scheduledRefreshFuture = scheduler.scheduleWithFixedDelay(this::refresh, refreshInterval, refreshInterval,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
|
||||
Utils.cancel(scheduledSendCommandWatchdogFuture);
|
||||
scheduledSendCommandWatchdogFuture = null;
|
||||
|
||||
Utils.cancel(scheduledRefreshFuture);
|
||||
scheduledRefreshFuture = null;
|
||||
|
||||
NikobusConnection connection = this.connection;
|
||||
this.connection = null;
|
||||
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Noop.
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private void processReceivedValue(byte value) {
|
||||
logger.trace("Received {}", value);
|
||||
|
||||
if (value == 13) {
|
||||
String command = stringBuilder.toString();
|
||||
stringBuilder.setLength(0);
|
||||
|
||||
logger.debug("Received command '{}', ack = '{}'", command, ack);
|
||||
|
||||
try {
|
||||
if (command.startsWith("$")) {
|
||||
String ack = this.ack;
|
||||
this.ack = null;
|
||||
|
||||
processResponse(command, ack);
|
||||
} else {
|
||||
Runnable listener = commandListeners.get(command);
|
||||
if (listener != null) {
|
||||
listener.run();
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
logger.debug("Processing command '{}' failed due {}", command, e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
stringBuilder.append((char) value);
|
||||
|
||||
// Take ACK part, i.e. "$0512"
|
||||
if (stringBuilder.length() == 5) {
|
||||
String payload = stringBuilder.toString();
|
||||
if (payload.startsWith("$05")) {
|
||||
ack = payload;
|
||||
logger.debug("Received ack '{}'", ack);
|
||||
stringBuilder.setLength(0);
|
||||
}
|
||||
} else if (stringBuilder.length() > 128) {
|
||||
// Fuse, if for some reason we don't receive \r don't fill buffer.
|
||||
stringBuilder.setLength(0);
|
||||
logger.warn("Resetting read buffer, should not happen, am I connected to Nikobus?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public void addListener(String command, Runnable listener) {
|
||||
if (commandListeners.put(command, listener) != null) {
|
||||
logger.warn("Multiple registrations for '{}'", command);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(String command) {
|
||||
commandListeners.remove(command);
|
||||
}
|
||||
|
||||
private void processResponse(String commandPayload, @Nullable String ack) {
|
||||
NikobusCommand command;
|
||||
synchronized (pendingCommands) {
|
||||
command = currentCommand;
|
||||
}
|
||||
|
||||
if (command == null) {
|
||||
logger.debug("Processing response but no command pending");
|
||||
return;
|
||||
}
|
||||
|
||||
NikobusCommand.ResponseHandler responseHandler = command.getResponseHandler();
|
||||
if (responseHandler == null) {
|
||||
logger.debug("No response expected for current command");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ack == null) {
|
||||
logger.debug("No ack received");
|
||||
return;
|
||||
}
|
||||
|
||||
String requestCommandId = command.getPayload().substring(3, 5);
|
||||
String ackCommandId = ack.substring(3, 5);
|
||||
if (!ackCommandId.equals(requestCommandId)) {
|
||||
logger.debug("Unexpected command's ack '{}' != '{}'", requestCommandId, ackCommandId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if response has expected length.
|
||||
if (commandPayload.length() != responseHandler.getResponseLength()) {
|
||||
logger.debug("Unexpected response length");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!commandPayload.startsWith(responseHandler.getResponseCode())) {
|
||||
logger.debug("Unexpected response command code");
|
||||
return;
|
||||
}
|
||||
|
||||
String requestCommandAddress = command.getPayload().substring(5, 9);
|
||||
String ackCommandAddress = commandPayload.substring(responseHandler.getAddressStart(),
|
||||
responseHandler.getAddressStart() + 4);
|
||||
if (!requestCommandAddress.equals(ackCommandAddress)) {
|
||||
logger.debug("Unexpected response address");
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseHandler.complete(commandPayload)) {
|
||||
resetProcessingAndProcessNext();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendCommand(NikobusCommand command) {
|
||||
synchronized (pendingCommands) {
|
||||
pendingCommands.addLast(command);
|
||||
}
|
||||
|
||||
scheduler.submit(this::processCommand);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unused", "null" })
|
||||
private void processCommand() {
|
||||
NikobusCommand command;
|
||||
synchronized (pendingCommands) {
|
||||
if (currentCommand != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
command = pendingCommands.pollFirst();
|
||||
if (command == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentCommand = command;
|
||||
}
|
||||
sendCommand(command, 3);
|
||||
}
|
||||
|
||||
private void sendCommand(NikobusCommand command, int retry) {
|
||||
logger.debug("Sending retry = {}, command '{}'", retry, command.getPayload());
|
||||
|
||||
NikobusConnection connection = this.connection;
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
connectIfNeeded(connection);
|
||||
|
||||
OutputStream outputStream = connection.getOutputStream();
|
||||
if (outputStream == null) {
|
||||
return;
|
||||
}
|
||||
outputStream.write(command.getPayload().getBytes());
|
||||
outputStream.flush();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Sending command failed due {}", e.getMessage(), e);
|
||||
connection.close();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} finally {
|
||||
NikobusCommand.ResponseHandler responseHandler = command.getResponseHandler();
|
||||
if (responseHandler == null) {
|
||||
resetProcessingAndProcessNext();
|
||||
} else if (retry > 0) {
|
||||
scheduleSendCommandTimeout(() -> {
|
||||
if (!responseHandler.isCompleted()) {
|
||||
sendCommand(command, retry - 1);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
scheduleSendCommandTimeout(() -> processTimeout(responseHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleSendCommandTimeout(Runnable command) {
|
||||
scheduledSendCommandWatchdogFuture = scheduler.schedule(command, 2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void processTimeout(NikobusCommand.ResponseHandler responseHandler) {
|
||||
if (responseHandler.completeExceptionally(new TimeoutException("Waiting for response timed-out."))) {
|
||||
resetProcessingAndProcessNext();
|
||||
}
|
||||
}
|
||||
|
||||
private void resetProcessingAndProcessNext() {
|
||||
Utils.cancel(scheduledSendCommandWatchdogFuture);
|
||||
synchronized (pendingCommands) {
|
||||
currentCommand = null;
|
||||
}
|
||||
scheduler.submit(this::processCommand);
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
List<Thing> things = getThing().getThings().stream()
|
||||
.filter(thing -> thing.getHandler() instanceof NikobusModuleHandler).collect(Collectors.toList());
|
||||
|
||||
if (things.isEmpty()) {
|
||||
logger.debug("Nothing to refresh");
|
||||
return;
|
||||
}
|
||||
|
||||
refreshThingIndex = (refreshThingIndex + 1) % things.size();
|
||||
|
||||
ThingHandler thingHandler = things.get(refreshThingIndex).getHandler();
|
||||
if (thingHandler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
NikobusModuleHandler handler = (NikobusModuleHandler) thingHandler;
|
||||
handler.refreshModule();
|
||||
}
|
||||
|
||||
private synchronized void connectIfNeeded(NikobusConnection connection) throws IOException {
|
||||
if (!connection.isConnected()) {
|
||||
connection.connect();
|
||||
|
||||
// Send connection sequence, mimicking the Nikobus software. If this is not send, PC-Link
|
||||
// sometimes does not forward button presses via serial interface.
|
||||
Stream.of(new String[] { "++++", "ATH0", "ATZ", "$10110000B8CF9D", "#L0", "#E0", "#L0", "#E1" })
|
||||
.map(NikobusCommand::new).forEach(this::sendCommand);
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.nikobus.internal.handler;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.*;
|
||||
import static org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusCommand;
|
||||
import org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup;
|
||||
import org.openhab.binding.nikobus.internal.utils.Utils;
|
||||
import org.openhab.core.common.AbstractUID;
|
||||
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.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusPushButtonHandler} is responsible for handling Nikobus push buttons.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusPushButtonHandler extends NikobusBaseThingHandler {
|
||||
private static class ImpactedModule {
|
||||
private final ThingUID thingUID;
|
||||
private final SwitchModuleGroup group;
|
||||
|
||||
ImpactedModule(ThingUID thingUID, SwitchModuleGroup group) {
|
||||
this.thingUID = thingUID;
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public ThingUID getThingUID() {
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
public SwitchModuleGroup getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "'" + thingUID + "'-" + group;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImpactedModuleUID extends AbstractUID {
|
||||
ImpactedModuleUID(String uid) {
|
||||
super(uid);
|
||||
}
|
||||
|
||||
String getThingTypeId() {
|
||||
return getSegment(0);
|
||||
}
|
||||
|
||||
String getThingId() {
|
||||
return getSegment(1);
|
||||
}
|
||||
|
||||
SwitchModuleGroup getGroup() {
|
||||
if (getSegment(2).equals("1")) {
|
||||
return FIRST;
|
||||
}
|
||||
if (getSegment(2).equals("2")) {
|
||||
return SECOND;
|
||||
}
|
||||
throw new IllegalArgumentException("Unexpected group found " + getSegment(2));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMinimalNumberOfSegments() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String END_OF_TRANSMISSION = "\r#E1";
|
||||
private final Logger logger = LoggerFactory.getLogger(NikobusPushButtonHandler.class);
|
||||
private final List<ImpactedModule> impactedModules = Collections.synchronizedList(new ArrayList<>());
|
||||
private @Nullable Future<?> requestUpdateFuture;
|
||||
|
||||
public NikobusPushButtonHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
|
||||
if (thing.getStatus() == ThingStatus.OFFLINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
impactedModules.clear();
|
||||
|
||||
try {
|
||||
ThingUID bridgeUID = thing.getBridgeUID();
|
||||
if (bridgeUID == null) {
|
||||
throw new IllegalArgumentException("Bridge does not exist!");
|
||||
}
|
||||
|
||||
String[] impactedModulesString = getConfig().get(CONFIG_IMPACTED_MODULES).toString().split(",");
|
||||
for (String impactedModuleString : impactedModulesString) {
|
||||
ImpactedModuleUID impactedModuleUID = new ImpactedModuleUID(impactedModuleString.trim());
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(bridgeUID.getBindingId(),
|
||||
impactedModuleUID.getThingTypeId());
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, impactedModuleUID.getThingId());
|
||||
impactedModules.add(new ImpactedModule(thingUID, impactedModuleUID.getGroup()));
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Impacted modules for {} = {}", thing.getUID(), impactedModules);
|
||||
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
pcLink.addListener(getAddress(), this::commandReceived);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
|
||||
Utils.cancel(requestUpdateFuture);
|
||||
requestUpdateFuture = null;
|
||||
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
pcLink.removeListener(getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("handleCommand '{}' '{}'", channelUID, command);
|
||||
|
||||
if (!CHANNEL_BUTTON.equals(channelUID.getId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Whenever the button receives an ON command,
|
||||
// we send a simulated button press to the Nikobus.
|
||||
if (command == OnOffType.ON) {
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
pcLink.sendCommand(new NikobusCommand(getAddress() + END_OF_TRANSMISSION));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void commandReceived() {
|
||||
if (thing.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
updateState(CHANNEL_BUTTON, OnOffType.ON);
|
||||
|
||||
Utils.cancel(requestUpdateFuture);
|
||||
requestUpdateFuture = scheduler.schedule(this::update, 400, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
for (ImpactedModule module : impactedModules) {
|
||||
NikobusModuleHandler switchModule = getModuleWithId(module.getThingUID());
|
||||
if (switchModule != null) {
|
||||
switchModule.requestStatus(module.getGroup());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable NikobusModuleHandler getModuleWithId(ThingUID thingUID) {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Thing thing = bridge.getThing(thingUID);
|
||||
if (thing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ThingHandler thingHandler = thing.getHandler();
|
||||
if (thingHandler instanceof NikobusModuleHandler) {
|
||||
return (NikobusModuleHandler) thingHandler;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAddress() {
|
||||
return "#N" + super.getAddress();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link NikobusRollershutterModuleHandler} is responsible for communication between Nikobus
|
||||
* rollershutter-controller and binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusRollershutterModuleHandler extends NikobusModuleHandler {
|
||||
public NikobusRollershutterModuleHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int valueFromCommand(Command command) {
|
||||
if (command == UpDownType.DOWN || command == StopMoveType.MOVE) {
|
||||
return 0x02;
|
||||
}
|
||||
if (command == UpDownType.UP) {
|
||||
return 0x01;
|
||||
}
|
||||
if (command == StopMoveType.STOP) {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command '" + command + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State stateFromValue(int value) {
|
||||
if (value == 0x00) {
|
||||
return OnOffType.OFF;
|
||||
}
|
||||
if (value == 0x01) {
|
||||
return UpDownType.UP;
|
||||
}
|
||||
if (value == 0x02) {
|
||||
return UpDownType.DOWN;
|
||||
}
|
||||
throw new IllegalArgumentException("Unexpected value " + value + " received");
|
||||
}
|
||||
}
|
||||
@@ -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.nikobus.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link NikobusSwitchModuleHandler} is responsible for communication between Nikobus switch module and binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusSwitchModuleHandler extends NikobusModuleHandler {
|
||||
public NikobusSwitchModuleHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int valueFromCommand(Command command) {
|
||||
if (command == OnOffType.ON) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if (command == OnOffType.OFF) {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command '" + command + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State stateFromValue(int value) {
|
||||
return value != 0 ? OnOffType.ON : OnOffType.OFF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.protocol;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusCommand} class holds a command that can be send to Nikobus installation.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusCommand {
|
||||
public static class Result {
|
||||
private final Callable<String> callable;
|
||||
|
||||
private Result(String result) {
|
||||
callable = () -> result;
|
||||
}
|
||||
|
||||
private Result(Exception exception) {
|
||||
callable = () -> {
|
||||
throw exception;
|
||||
};
|
||||
}
|
||||
|
||||
public String get() throws Exception {
|
||||
return callable.call();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResponseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(ResponseHandler.class);
|
||||
private final Consumer<Result> resultConsumer;
|
||||
private final int responseLength;
|
||||
private final int addressStart;
|
||||
private final String responseCode;
|
||||
private final AtomicBoolean isCompleted = new AtomicBoolean();
|
||||
|
||||
private ResponseHandler(int responseLength, int addressStart, String responseCode,
|
||||
Consumer<Result> resultConsumer) {
|
||||
this.responseLength = responseLength;
|
||||
this.addressStart = addressStart;
|
||||
this.responseCode = responseCode;
|
||||
this.resultConsumer = resultConsumer;
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return isCompleted.get();
|
||||
}
|
||||
|
||||
public boolean complete(String result) {
|
||||
return complete(new Result(result));
|
||||
}
|
||||
|
||||
public boolean completeExceptionally(Exception exception) {
|
||||
return complete(new Result(exception));
|
||||
}
|
||||
|
||||
private boolean complete(Result result) {
|
||||
if (isCompleted.getAndSet(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
resultConsumer.accept(result);
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Processing result {} failed with {}", result, e.getMessage(), e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getResponseLength() {
|
||||
return responseLength;
|
||||
}
|
||||
|
||||
public int getAddressStart() {
|
||||
return addressStart;
|
||||
}
|
||||
|
||||
public String getResponseCode() {
|
||||
return responseCode;
|
||||
}
|
||||
}
|
||||
|
||||
private final String payload;
|
||||
private final @Nullable ResponseHandler responseHandler;
|
||||
|
||||
public NikobusCommand(String payload) {
|
||||
this.payload = payload + '\r';
|
||||
this.responseHandler = null;
|
||||
}
|
||||
|
||||
public NikobusCommand(String payload, int responseLength, int addressStart, String responseCode,
|
||||
Consumer<Result> resultConsumer) {
|
||||
this.payload = payload + '\r';
|
||||
this.responseHandler = new ResponseHandler(responseLength, addressStart, responseCode, resultConsumer);
|
||||
}
|
||||
|
||||
public String getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public @Nullable ResponseHandler getResponseHandler() {
|
||||
return responseHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.TooManyListenersException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusConnection } is responsible for creating connections to clients.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusConnection implements SerialPortEventListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(NikobusConnection.class);
|
||||
private final SerialPortManager serialPortManager;
|
||||
private final String portName;
|
||||
private final Consumer<Byte> processData;
|
||||
private @Nullable SerialPort serialPort;
|
||||
|
||||
public NikobusConnection(SerialPortManager serialPortManager, String portName, Consumer<Byte> processData) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
this.portName = portName;
|
||||
this.processData = processData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this manager is connected.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return serialPort != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the receiver.
|
||||
*
|
||||
**/
|
||||
public void connect() throws IOException {
|
||||
if (isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SerialPortIdentifier portId = serialPortManager.getIdentifier(portName);
|
||||
if (portId == null) {
|
||||
throw new IOException(String.format("Port '%s' is not known!", portName));
|
||||
}
|
||||
|
||||
logger.info("Connecting to {}", portName);
|
||||
|
||||
try {
|
||||
SerialPort serialPort = portId.open("org.openhab.binding.nikobus.pc-link", 2000);
|
||||
serialPort.addEventListener(this);
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
this.serialPort = serialPort;
|
||||
logger.info("Connected to {}", portName);
|
||||
} catch (PortInUseException e) {
|
||||
throw new IOException(String.format("Port '%s' is in use!", portName), e);
|
||||
} catch (TooManyListenersException e) {
|
||||
throw new IOException(String.format("Cannot attach listener to port '%s'!", portName), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
**/
|
||||
public void close() {
|
||||
SerialPort serialPort = this.serialPort;
|
||||
this.serialPort = null;
|
||||
|
||||
if (serialPort != null) {
|
||||
try {
|
||||
serialPort.removeEventListener();
|
||||
OutputStream outputStream = serialPort.getOutputStream();
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
InputStream inputStream = serialPort.getInputStream();
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing serial port.", e);
|
||||
} finally {
|
||||
serialPort.close();
|
||||
logger.debug("Closed serial port.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an output stream for this connection.
|
||||
*/
|
||||
public @Nullable OutputStream getOutputStream() throws IOException {
|
||||
SerialPort serialPort = this.serialPort;
|
||||
if (serialPort == null) {
|
||||
return null;
|
||||
}
|
||||
return serialPort.getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialEvent(SerialPortEvent event) {
|
||||
if (event.getEventType() != SerialPortEvent.DATA_AVAILABLE) {
|
||||
return;
|
||||
}
|
||||
SerialPort serialPort = this.serialPort;
|
||||
if (serialPort == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
InputStream inputStream = serialPort.getInputStream();
|
||||
if (inputStream == null) {
|
||||
return;
|
||||
}
|
||||
byte[] readBuffer = new byte[64];
|
||||
while (inputStream.available() > 0) {
|
||||
int length = inputStream.read(readBuffer);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
processData.accept(readBuffer[i]);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error reading from serial port: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.nikobus.internal.protocol;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusCommand.Result;
|
||||
import org.openhab.binding.nikobus.internal.utils.CRCUtil;
|
||||
|
||||
/**
|
||||
* The {@link NikobusCommand} class defines factory functions to create commands that can be send to Nikobus
|
||||
* installation.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SwitchModuleCommandFactory {
|
||||
public static NikobusCommand createReadCommand(String address, SwitchModuleGroup group,
|
||||
Consumer<Result> resultConsumer) {
|
||||
checkAddress(address);
|
||||
|
||||
String commandPayload = CRCUtil.appendCRC2("$10" + CRCUtil.appendCRC(group.getStatusRequest() + address));
|
||||
return new NikobusCommand(commandPayload, 27, 3, "$1C", resultConsumer);
|
||||
}
|
||||
|
||||
public static NikobusCommand createWriteCommand(String address, SwitchModuleGroup group, String value,
|
||||
Consumer<Result> resultConsumer) {
|
||||
checkAddress(address);
|
||||
if (value.length() != 12) {
|
||||
throw new IllegalArgumentException(String.format("Value must have 12 chars but got '%s'", value));
|
||||
}
|
||||
|
||||
String payload = group.getStatusUpdate() + address + value + "FF";
|
||||
return new NikobusCommand(CRCUtil.appendCRC2("$1E" + CRCUtil.appendCRC(payload)), 13, 5, "$0E", resultConsumer);
|
||||
}
|
||||
|
||||
private static void checkAddress(String address) {
|
||||
if (address.length() != 4) {
|
||||
throw new IllegalArgumentException(String.format("Address must have 4 chars but got '%s'", address));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.protocol;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.CHANNEL_OUTPUT_PREFIX;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
|
||||
/**
|
||||
* The {@link SwitchModuleGroup} class defines Nikobus module group used for reading status or set its new value.
|
||||
* Nikobus module can always operate only in groups and not per-channel.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum SwitchModuleGroup {
|
||||
|
||||
FIRST("12", "15", 1),
|
||||
SECOND("17", "16", 7);
|
||||
|
||||
private final String statusRequest;
|
||||
private final String statusUpdate;
|
||||
private final int offset;
|
||||
|
||||
private SwitchModuleGroup(String statusRequest, String statusUpdate, int offset) {
|
||||
this.statusRequest = statusRequest;
|
||||
this.statusUpdate = statusUpdate;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public String getStatusRequest() {
|
||||
return statusRequest;
|
||||
}
|
||||
|
||||
public String getStatusUpdate() {
|
||||
return statusUpdate;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
public static SwitchModuleGroup mapFromChannel(ChannelUID channelUID) {
|
||||
if (!channelUID.getIdWithoutGroup().startsWith(CHANNEL_OUTPUT_PREFIX)) {
|
||||
throw new IllegalArgumentException("Unexpected channel " + channelUID.getId());
|
||||
}
|
||||
|
||||
String channelNumber = channelUID.getIdWithoutGroup().substring(CHANNEL_OUTPUT_PREFIX.length());
|
||||
return mapFromChannel(Integer.parseInt(channelNumber));
|
||||
}
|
||||
|
||||
public static SwitchModuleGroup mapFromChannel(int channelNumber) {
|
||||
int max = SECOND.getOffset() + SECOND.getCount();
|
||||
if (channelNumber < FIRST.getOffset() || channelNumber > max) {
|
||||
throw new IllegalArgumentException(String.format("Channel number should be between [%d, %d], but got %d",
|
||||
FIRST.getOffset(), max, channelNumber));
|
||||
}
|
||||
return channelNumber >= SECOND.getOffset() ? SECOND : FIRST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.utils;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
|
||||
/**
|
||||
* The {@link CRCUtil} class defines utility functions to calculate CRC used by the Nikobus communication protocol.
|
||||
*
|
||||
* @author Davy Vanherbergen - Initial contribution
|
||||
* @author Boris Krivonog - Removed dependency to javax.xml.bind.DatatypeConverter
|
||||
*/
|
||||
public class CRCUtil {
|
||||
|
||||
private static final int CRC_INIT = 0xFFFF;
|
||||
|
||||
private static final int POLYNOMIAL = 0x1021;
|
||||
|
||||
/**
|
||||
* Calculate the CRC16-CCITT checksum on the input string and return the
|
||||
* input string with the checksum appended.
|
||||
*
|
||||
* @param input
|
||||
* String representing hex numbers.
|
||||
* @return input string + CRC.
|
||||
*/
|
||||
public static String appendCRC(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int check = CRC_INIT;
|
||||
|
||||
for (byte b : HexUtils.hexToBytes(input)) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (((b >> (7 - i) & 1) == 1) ^ ((check >> 15 & 1) == 1)) {
|
||||
check = check << 1;
|
||||
check = check ^ POLYNOMIAL;
|
||||
} else {
|
||||
check = check << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
check = check & CRC_INIT;
|
||||
String checksum = StringUtils.leftPad(Integer.toHexString(check), 4, "0");
|
||||
return (input + checksum).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the second checksum on the input string and return the
|
||||
* input string with the checksum appended.
|
||||
*
|
||||
* @param input
|
||||
* String representing a nikobus command.
|
||||
* @return input string + CRC.
|
||||
*/
|
||||
public static String appendCRC2(String input) {
|
||||
int check = 0;
|
||||
|
||||
for (byte b : input.getBytes()) {
|
||||
|
||||
check = check ^ b;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
|
||||
if (((check & 0xff) >> 7) != 0) {
|
||||
check = check << 1;
|
||||
check = check ^ 0x99;
|
||||
} else {
|
||||
check = check << 1;
|
||||
}
|
||||
check = check & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
return input + StringUtils.leftPad(Integer.toHexString(check), 2, "0").toUpperCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.utils;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* The {@link Utils} class defines commonly used utility functions.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
public class Utils {
|
||||
public static void cancel(Future<?> future) {
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="nikobus" 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>Nikobus Binding</name>
|
||||
<description>This is the binding for Nikobus.</description>
|
||||
<author>Boris Krivonog</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,228 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="nikobus"
|
||||
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="pc-link">
|
||||
<label>PC-Link</label>
|
||||
<description>PC-Link via serial connection</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="port" type="text" required="true">
|
||||
<label>Port</label>
|
||||
<context>serial-port</context>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
<description>The serial port used to connect to the Nikobus PC Link.</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" max="65535" min="10" required="false">
|
||||
<default>60</default>
|
||||
<label>Refresh Interval</label>
|
||||
<description>Refresh interval in seconds.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="push-button">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="pc-link"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Push Button</label>
|
||||
<description>A single push button</description>
|
||||
|
||||
<channels>
|
||||
<channel id="button" typeId="button"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
<parameter name="impactedModules" type="text">
|
||||
<label>Impacted Modules</label>
|
||||
<description>Comma separated list of impacted modules, i.e. 4C6C-1,4C6C-2</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="button">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Button Event</label>
|
||||
<description>Fires when the button is pressed</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</channel-type>
|
||||
|
||||
<thing-type id="switch-module">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="pc-link"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Switch Module</label>
|
||||
<description>Nikobus Switch module</description>
|
||||
|
||||
<channels>
|
||||
<channel id="output-1" typeId="switch-output">
|
||||
<label>Output 1</label>
|
||||
</channel>
|
||||
<channel id="output-2" typeId="switch-output">
|
||||
<label>Output 2</label>
|
||||
</channel>
|
||||
<channel id="output-3" typeId="switch-output">
|
||||
<label>Output 3</label>
|
||||
</channel>
|
||||
<channel id="output-4" typeId="switch-output">
|
||||
<label>Output 4</label>
|
||||
</channel>
|
||||
<channel id="output-5" typeId="switch-output">
|
||||
<label>Output 5</label>
|
||||
</channel>
|
||||
<channel id="output-6" typeId="switch-output">
|
||||
<label>Output 6</label>
|
||||
</channel>
|
||||
<channel id="output-7" typeId="switch-output">
|
||||
<label>Output 7</label>
|
||||
</channel>
|
||||
<channel id="output-8" typeId="switch-output">
|
||||
<label>Output 8</label>
|
||||
</channel>
|
||||
<channel id="output-9" typeId="switch-output">
|
||||
<label>Output 9</label>
|
||||
</channel>
|
||||
<channel id="output-10" typeId="switch-output">
|
||||
<label>Output 10</label>
|
||||
</channel>
|
||||
<channel id="output-11" typeId="switch-output">
|
||||
<label>Output 11</label>
|
||||
</channel>
|
||||
<channel id="output-12" typeId="switch-output">
|
||||
<label>Output 12</label>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="dimmer-module">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="pc-link"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Dimmer Module</label>
|
||||
<description>Nikobus Dimmer module</description>
|
||||
|
||||
<channels>
|
||||
<channel id="output-1" typeId="dimmer-output">
|
||||
<label>Output 1</label>
|
||||
</channel>
|
||||
<channel id="output-2" typeId="dimmer-output">
|
||||
<label>Output 2</label>
|
||||
</channel>
|
||||
<channel id="output-3" typeId="dimmer-output">
|
||||
<label>Output 3</label>
|
||||
</channel>
|
||||
<channel id="output-4" typeId="dimmer-output">
|
||||
<label>Output 4</label>
|
||||
</channel>
|
||||
<channel id="output-5" typeId="dimmer-output">
|
||||
<label>Output 5</label>
|
||||
</channel>
|
||||
<channel id="output-6" typeId="dimmer-output">
|
||||
<label>Output 6</label>
|
||||
</channel>
|
||||
<channel id="output-7" typeId="dimmer-output">
|
||||
<label>Output 7</label>
|
||||
</channel>
|
||||
<channel id="output-8" typeId="dimmer-output">
|
||||
<label>Output 8</label>
|
||||
</channel>
|
||||
<channel id="output-9" typeId="dimmer-output">
|
||||
<label>Output 9</label>
|
||||
</channel>
|
||||
<channel id="output-10" typeId="dimmer-output">
|
||||
<label>Output 10</label>
|
||||
</channel>
|
||||
<channel id="output-11" typeId="dimmer-output">
|
||||
<label>Output 11</label>
|
||||
</channel>
|
||||
<channel id="output-12" typeId="dimmer-output">
|
||||
<label>Output 12</label>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="rollershutter-module">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="pc-link"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Rollershutter Module</label>
|
||||
<description>Nikobus Rollershutter module</description>
|
||||
|
||||
<channels>
|
||||
<channel id="output-1" typeId="rollershutter-output">
|
||||
<label>Output 1</label>
|
||||
</channel>
|
||||
<channel id="output-2" typeId="rollershutter-output">
|
||||
<label>Output 2</label>
|
||||
</channel>
|
||||
<channel id="output-3" typeId="rollershutter-output">
|
||||
<label>Output 3</label>
|
||||
</channel>
|
||||
<channel id="output-4" typeId="rollershutter-output">
|
||||
<label>Output 4</label>
|
||||
</channel>
|
||||
<channel id="output-5" typeId="rollershutter-output">
|
||||
<label>Output 5</label>
|
||||
</channel>
|
||||
<channel id="output-6" typeId="rollershutter-output">
|
||||
<label>Output 6</label>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="switch-output">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Output</label>
|
||||
<description>Switch Module's Output</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dimmer-output">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Output</label>
|
||||
<description>Dimmer Module's Output</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rollershutter-output">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Output</label>
|
||||
<description>Rollershutter Module's Output</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user