added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.elerotransmitterstick-${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-elerotransmitterstick" description="Elero TransmitterStick 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.elerotransmitterstick/${project.version}</bundle>
</feature>
</features>

View File

@@ -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.elerotransmitterstick.internal;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link EleroTransmitterStickBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Volker Bier - Initial contribution
*/
public class EleroTransmitterStickBindingConstants {
public static final String BINDING_ID = "elerotransmitterstick";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_STICK = new ThingTypeUID(BINDING_ID, "elerostick");
public static final ThingTypeUID THING_TYPE_ELERO_CHANNEL = new ThingTypeUID(BINDING_ID, "elerochannel");
public static final String CONTROL_CHANNEL = "control";
public static final String STATUS_CHANNEL = "status";
// channel id property for elero channels.
public static final String PROPERTY_CHANNEL_ID = "channelId";
}

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.elerotransmitterstick.internal;
import static org.openhab.binding.elerotransmitterstick.internal.EleroTransmitterStickBindingConstants.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
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.elerotransmitterstick.internal.discovery.EleroChannelDiscoveryService;
import org.openhab.binding.elerotransmitterstick.internal.handler.EleroChannelHandler;
import org.openhab.binding.elerotransmitterstick.internal.handler.EleroTransmitterStickHandler;
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.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link EleroTransmitterStickHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Volker Bier - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.elerotransmitterstick")
public class EleroTransmitterStickHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_STICK, THING_TYPE_ELERO_CHANNEL).collect(Collectors.toSet()));
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private final SerialPortManager serialPortManager;
@Activate
public EleroTransmitterStickHandlerFactory(final @Reference SerialPortManager serialPortManager) {
this.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 (thingTypeUID.equals(THING_TYPE_STICK)) {
EleroTransmitterStickHandler bridgeHandler = new EleroTransmitterStickHandler((Bridge) thing,
serialPortManager);
EleroChannelDiscoveryService discoveryService = new EleroChannelDiscoveryService(bridgeHandler);
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(), bundleContext
.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
return bridgeHandler;
} else if (thingTypeUID.equals(THING_TYPE_ELERO_CHANNEL)) {
EleroChannelHandler h = new EleroChannelHandler(thing);
return h;
}
return null;
}
}

View File

@@ -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.elerotransmitterstick.internal.config;
import org.openhab.binding.elerotransmitterstick.internal.handler.EleroChannelHandler;
/**
* The {@link EleroChannelConfig} holds configuration data of a {@link EleroChannelHandler}
*
* @author Volker Bier - Initial contribution
*/
public class EleroChannelConfig {
public Integer channelId;
}

View File

@@ -0,0 +1,26 @@
/**
* 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.elerotransmitterstick.internal.config;
import org.openhab.binding.elerotransmitterstick.internal.handler.EleroTransmitterStickHandler;
/**
* The {@link EleroTransmitterStickConfig} holds configuration data of a
* {@link EleroTransmitterStickHandler}
*
* @author Volker Bier - Initial contribution
*/
public class EleroTransmitterStickConfig {
public String portName;
public int updateInterval;
}

View File

@@ -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.elerotransmitterstick.internal.discovery;
import static org.openhab.binding.elerotransmitterstick.internal.EleroTransmitterStickBindingConstants.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.elerotransmitterstick.internal.handler.EleroTransmitterStickHandler;
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.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EleroChannelDiscoveryService} is responsible for discovery of elero channels from an Elero Transmitter
* Stick.
*
* @author Volker Bier - Initial contribution
*/
public class EleroChannelDiscoveryService extends AbstractDiscoveryService {
private static final int DISCOVER_TIMEOUT_SECONDS = 30;
private final Logger logger = LoggerFactory.getLogger(EleroChannelDiscoveryService.class);
private EleroTransmitterStickHandler bridge;
private ScheduledFuture<?> sensorDiscoveryJob;
/**
* Creates the discovery service for the given handler and converter.
*/
public EleroChannelDiscoveryService(EleroTransmitterStickHandler stickHandler) {
super(Collections.singleton(THING_TYPE_ELERO_CHANNEL), DISCOVER_TIMEOUT_SECONDS, true);
bridge = stickHandler;
}
@Override
protected void startScan() {
discoverSensors();
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Start Elero Channel background discovery");
if (sensorDiscoveryJob == null || sensorDiscoveryJob.isCancelled()) {
sensorDiscoveryJob = scheduler.scheduleWithFixedDelay(() -> {
discoverSensors();
}, 0, 2, TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stop Elero Channel background discovery");
if (sensorDiscoveryJob != null && !sensorDiscoveryJob.isCancelled()) {
sensorDiscoveryJob.cancel(true);
sensorDiscoveryJob = null;
}
}
private void discoverSensors() {
if (bridge.getStick() == null) {
logger.debug("Stick not opened, scanning skipped.");
return;
}
ArrayList<Integer> channelIds = bridge.getStick().getKnownIds();
if (channelIds.isEmpty()) {
logger.debug("Could not obtain known channels from the stick, scanning skipped.");
return;
}
for (Integer id : channelIds) {
ThingUID sensorThing = new ThingUID(THING_TYPE_ELERO_CHANNEL, bridge.getThing().getUID(),
String.valueOf(id));
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(sensorThing).withLabel("Channel " + id)
.withRepresentationProperty("id").withBridge(bridge.getThing().getUID())
.withProperty(PROPERTY_CHANNEL_ID, id).build();
thingDiscovered(discoveryResult);
}
}
}

View File

@@ -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.elerotransmitterstick.internal.handler;
import static org.openhab.binding.elerotransmitterstick.internal.EleroTransmitterStickBindingConstants.*;
import java.util.Collections;
import org.openhab.binding.elerotransmitterstick.internal.config.EleroChannelConfig;
import org.openhab.binding.elerotransmitterstick.internal.stick.CommandType;
import org.openhab.binding.elerotransmitterstick.internal.stick.ResponseStatus;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EleroChannelHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Volker Bier - Initial contribution
*/
public class EleroChannelHandler extends BaseThingHandler implements StatusListener {
private final Logger logger = LoggerFactory.getLogger(EleroChannelHandler.class);
protected Integer channelId;
protected EleroTransmitterStickHandler bridge;
public EleroChannelHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
bridge = (EleroTransmitterStickHandler) getBridge().getHandler();
channelId = getConfig().as(EleroChannelConfig.class).channelId;
bridge.addStatusListener(channelId, this);
if (bridge.getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else {
updateStatus(ThingStatus.UNKNOWN);
}
}
@Override
public void dispose() {
if (bridge != null) {
bridge.removeStatusListener(channelId, this);
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
logger.debug("Bridge for Elero channel handler for thing {} ({}) changed status to {}",
getThing().getLabel(), getThing().getUID(), bridgeStatusInfo.getStatus().toString());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else {
updateStatus(ThingStatus.UNKNOWN);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Received command {} for channel {}", command, channelUID);
if (channelUID.getIdWithoutGroup().equals(CONTROL_CHANNEL)) {
if (command == UpDownType.UP) {
bridge.getStick().sendCommand(CommandType.UP, Collections.singletonList(channelId));
} else if (command == UpDownType.DOWN) {
bridge.getStick().sendCommand(CommandType.DOWN, Collections.singletonList(channelId));
} else if (command == StopMoveType.STOP) {
bridge.getStick().sendCommand(CommandType.STOP, Collections.singletonList(channelId));
} else if (command instanceof PercentType) {
CommandType cmd = CommandType.getForPercent(((PercentType) command).intValue());
if (cmd != null) {
bridge.getStick().sendCommand(cmd, Collections.singletonList(channelId));
} else {
logger.debug("Unhandled command {}.", command);
}
} else if (command == RefreshType.REFRESH) {
bridge.getStick().requestUpdate(Collections.singletonList(channelId));
}
}
}
@Override
public void statusChanged(int channelId, ResponseStatus status) {
logger.debug("Received updated state {} for thing {}", status, getThing().getUID().toString());
updateState(STATUS_CHANNEL, new StringType(status.toString()));
int percentage = ResponseStatus.getPercentageFor(status);
if (percentage != -1) {
updateState(CONTROL_CHANNEL, new PercentType(percentage));
}
updateStatus(ThingStatus.ONLINE);
}
}

View File

@@ -0,0 +1,82 @@
/**
* 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.elerotransmitterstick.internal.handler;
import org.openhab.binding.elerotransmitterstick.internal.config.EleroTransmitterStickConfig;
import org.openhab.binding.elerotransmitterstick.internal.stick.TransmitterStick;
import org.openhab.binding.elerotransmitterstick.internal.stick.TransmitterStick.StickListener;
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.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.types.Command;
/**
* The {@link EleroTransmitterStickHandler} is responsible for managing the connection to an elero transmitter stick.
*
* @author Volker Bier - Initial contribution
*/
public class EleroTransmitterStickHandler extends BaseBridgeHandler implements BridgeHandler {
private final SerialPortManager serialPortManager;
private final TransmitterStick stick;
public EleroTransmitterStickHandler(Bridge bridge, SerialPortManager serialPortManager) {
super(bridge);
this.serialPortManager = serialPortManager;
stick = new TransmitterStick(new StickListener() {
@Override
public void connectionEstablished() {
updateStatus(ThingStatus.ONLINE);
}
@Override
public void connectionDropped(Exception e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
});
}
@Override
public void handleCommand(ChannelUID channelUid, Command command) {
}
@Override
public void dispose() {
stick.dispose();
super.dispose();
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
stick.initialize(getConfig().as(EleroTransmitterStickConfig.class), scheduler, serialPortManager);
}
public TransmitterStick getStick() {
return stick;
}
public void addStatusListener(int channelId, StatusListener listener) {
stick.addStatusListener(channelId, listener);
}
public void removeStatusListener(int channelId, StatusListener listener) {
stick.removeStatusListener(channelId, listener);
}
}

View File

@@ -0,0 +1,25 @@
/**
* 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.elerotransmitterstick.internal.handler;
import org.openhab.binding.elerotransmitterstick.internal.stick.ResponseStatus;
/**
* The {@link StatusListener} is an interface for notifying interested listeners about
* status changes of elero channels.
*
* @author Volker Bier - Initial contribution
*/
public interface StatusListener {
void statusChanged(int channelId, ResponseStatus status);
}

View File

@@ -0,0 +1,74 @@
/**
* 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.elerotransmitterstick.internal.stick;
import java.util.Arrays;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* @author Volker Bier - Initial contribution
*/
public class Command implements Delayed {
public static final int TIMED_PRIORITY = 30;
public static final int COMMAND_PRIORITY = 20;
public static final int FAST_INFO_PRIORITY = 10;
public static final int INFO_PRIORITY = 0;
private Integer[] channelId;
private CommandType commandType;
protected int priority = COMMAND_PRIORITY;
public Command(final CommandType cmd, final Integer... channels) {
channelId = channels;
commandType = cmd;
}
protected Command(final CommandType cmd, int priority, final Integer... channels) {
this(cmd, channels);
this.priority = priority;
}
@Override
public String toString() {
return "Command " + commandType + " on channels " + Arrays.toString(channelId) + " with priority " + priority;
}
@Override
public int compareTo(Delayed delayed) {
if (delayed == this) {
return 0;
}
return Long.compare(0, delayed.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public long getDelay(TimeUnit unit) {
return 0;
}
public int getPriority() {
return priority;
}
public Integer[] getChannelIds() {
return channelId;
}
public CommandType getCommandType() {
return commandType;
}
}

View File

@@ -0,0 +1,61 @@
/**
* 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.elerotransmitterstick.internal.stick;
import org.openhab.core.util.HexUtils;
/**
* @author Volker Bier - Initial contribution
*/
public class CommandPacket {
public static final byte EASY_CHECK = (byte) 0x4A;
public static final byte EASY_SEND = (byte) 0x4C;
public static final byte EASY_INFO = (byte) 0x4E;
byte[] data;
public CommandPacket(byte[] bytes) {
data = new byte[bytes.length + 1];
System.arraycopy(bytes, 0, data, 0, bytes.length);
data[bytes.length] = checksum(data);
}
public byte[] getBytes() {
return data;
}
public long getResponseTimeout() {
if (data[2] == EASY_CHECK) {
return 1000;
}
return 4000;
}
private byte checksum(byte[] data) {
long val = 0;
for (byte b : data) {
val += b;
}
val = val % 256;
return (byte) (256 - val);
}
@Override
public String toString() {
return HexUtils.bytesToHex(data);
}
}

View File

@@ -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.elerotransmitterstick.internal.stick;
/**
* The {@link CommandType} is the type of the {@link Command}.
*
* @author Volker Bier - Initial contribution
*/
public enum CommandType {
UP,
INTERMEDIATE,
VENTILATION,
DOWN,
STOP,
INFO,
CHECK,
NONE;
public static CommandType getForPercent(int percentage) {
if (percentage == 0) {
return UP;
}
if (percentage == 25) {
return CommandType.INTERMEDIATE;
}
if (percentage == 75) {
return CommandType.VENTILATION;
}
if (percentage == 100) {
return CommandType.DOWN;
}
return null;
}
}

View File

@@ -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.elerotransmitterstick.internal.stick;
import java.nio.ByteBuffer;
/**
* @author Volker Bier - Initial contribution
*/
public class CommandUtil {
/**
* Create the two channel bytes for the given channel IDs
*
* @param channelIds channel ids (starting from 1)
*/
private static byte[] createChannelBits(Integer... channelIds) {
long channels = 0;
for (int id : channelIds) {
channels = channels + (1 << (id - 1));
}
ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);
buffer.putShort((short) (channels % 32768));
return buffer.array();
}
public static CommandPacket createPacket(Command cmd, Integer channelId) {
return createPacket(cmd.getCommandType(), new Integer[] { channelId });
}
public static CommandPacket createPacket(Command cmd) {
return createPacket(cmd.getCommandType(), cmd.getChannelIds());
}
public static CommandPacket createPacket(CommandType cmd, Integer... channelIds) {
if (cmd == CommandType.INFO) {
byte[] channelBits = createChannelBits(channelIds);
return new CommandPacket(
new byte[] { (byte) 0xAA, 0x04, CommandPacket.EASY_INFO, channelBits[0], channelBits[1] });
}
if (cmd == CommandType.CHECK) {
return new CommandPacket(new byte[] { (byte) 0xaa, (byte) 0x02, CommandPacket.EASY_CHECK });
}
byte[] channelBits = createChannelBits(channelIds);
byte cmdByte = getCommandByte(cmd);
return new CommandPacket(
new byte[] { (byte) 0xAA, 0x05, CommandPacket.EASY_SEND, channelBits[0], channelBits[1], cmdByte });
}
private static byte getCommandByte(CommandType command) {
switch (command) {
case DOWN:
return (byte) 0x40;
case INTERMEDIATE:
return (byte) 0x44;
case STOP:
return (byte) 0x10;
case UP:
return (byte) 0x20;
case VENTILATION:
return (byte) 0x24;
default:
throw new IllegalArgumentException("Unhandled command type " + command);
}
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.elerotransmitterstick.internal.stick;
/**
* The {@link ConnectException} is thrown on errors connecting to the elero transmitter stick.
*
* @author Volker Bier - Initial contribution
*/
public class ConnectException extends Exception {
private static final long serialVersionUID = 946529257121090885L;
public ConnectException(Throwable cause) {
super(cause);
}
public ConnectException(String message) {
super(message);
}
public ConnectException(String message, Exception e) {
super(message, e);
}
}

View File

@@ -0,0 +1,50 @@
/**
* 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.elerotransmitterstick.internal.stick;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* @author Volker Bier - Initial contribution
*/
public class DelayedCommand extends Command {
private final long origin;
private final long delay;
public DelayedCommand(CommandType cmd, long delayInMillis, int priority, Integer... channels) {
super(cmd, priority, channels);
delay = delayInMillis;
origin = System.currentTimeMillis();
}
@Override
public int compareTo(Delayed delayed) {
if (delayed == this) {
return 0;
}
return Long.compare(getDelay(TimeUnit.MILLISECONDS), delayed.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(delay - (System.currentTimeMillis() - origin), TimeUnit.MILLISECONDS);
}
@Override
public String toString() {
return super.toString() + " and delay " + getDelay(TimeUnit.MILLISECONDS);
}
}

View File

@@ -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.elerotransmitterstick.internal.stick;
import java.util.Arrays;
/**
* @author Volker Bier - Initial contribution
*/
public class Response {
private ResponseStatus status;
private int[] channels;
public Response(ResponseStatus status, int[] channels) {
this.status = status;
this.channels = channels;
}
public Response(int[] channels) {
this.channels = channels;
}
public boolean isMoving() {
return status == ResponseStatus.MOVING_DOWN || status == ResponseStatus.MOVING_UP;
}
public int[] getChannelIds() {
return channels;
}
public boolean hasStatus() {
return status != null;
}
public ResponseStatus getStatus() {
return status;
}
@Override
public String toString() {
return status + " for channels " + Arrays.toString(channels);
}
}

View File

@@ -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.elerotransmitterstick.internal.stick;
/**
* @author Volker Bier - Initial contribution
*/
public enum ResponseStatus {
NO_INFORMATION((byte) 0x00),
TOP((byte) 0x01),
BOTTOM((byte) 0x02),
INTERMEDIATE((byte) 0x03),
VENTILATION((byte) 0x04),
BLOCKING((byte) 0x05),
OVERHEATED((byte) 0x06),
TIMEOUT((byte) 0x07),
START_MOVE_UP((byte) 0x08),
START_MOVE_DOWN((byte) 0x09),
MOVING_UP((byte) 0x0a),
MOVING_DOWN((byte) 0x0b),
STOPPED((byte) 0x0d),
TOP_TILT((byte) 0x0e),
BOTTOM_INTERMEDIATE((byte) 0x0f),
SWITCHED_OFF((byte) 0x10),
SWITCHED_ON((byte) 0x11);
public static final byte EASY_CONFIRM = (byte) 0x4B;
public static final byte EASY_ACK = (byte) 0x4D;
private byte statusByte;
private ResponseStatus(byte statusByte) {
this.statusByte = statusByte;
}
public static ResponseStatus getFor(byte statusByte) {
if (statusByte <= MOVING_DOWN.statusByte) {
return ResponseStatus.values()[statusByte];
}
return ResponseStatus.values()[statusByte - 1];
}
public static int getPercentageFor(ResponseStatus status) {
switch (status) {
case BOTTOM:
return 100;
case NO_INFORMATION:
return -1;
case TOP:
return 0;
case INTERMEDIATE:
return 25;
case VENTILATION:
return 75;
default:
return 50;
}
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.elerotransmitterstick.internal.stick;
import java.util.Arrays;
/**
* @author Volker Bier - Initial contribution
*/
public class ResponseUtil {
public static Response createResponse(byte upperChannelByte, byte lowerChannelByte) {
return new Response(getChannelIds(upperChannelByte, lowerChannelByte));
}
public static Response createResponse(byte upperChannelByte, byte lowerChannelByte, byte responseType) {
return new Response(ResponseStatus.getFor(responseType), getChannelIds(upperChannelByte, lowerChannelByte));
}
/**
* returns the list of channels (starting with 1)
*/
private static int[] getChannelIds(byte upperChannelByte, byte lowerChannelByte) {
int[] result = new int[16];
int idx = 0;
byte b = lowerChannelByte;
for (int i = 0; i < 8; i++) {
if ((b & 1) > 0) {
result[idx++] = i + 1;
}
b = (byte) (b >> 1);
}
b = upperChannelByte;
for (int i = 0; i < 8; i++) {
if ((b & 1) > 0) {
result[idx++] = i + 9;
}
b = (byte) (b >> 1);
}
return Arrays.copyOfRange(result, 0, idx);
}
}

View File

@@ -0,0 +1,210 @@
/**
* 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.elerotransmitterstick.internal.stick;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.TooManyListenersException;
import org.apache.commons.lang.ArrayUtils;
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;
/**
* @author Volker Bier - Initial contribution
*/
public class SerialConnection {
private final Logger logger = LoggerFactory.getLogger(SerialConnection.class);
private SerialPort serialPort;
private boolean open;
private String portName;
private final List<Byte> bytes = new ArrayList<>();
private Response response = null;
private final SerialPortManager serialPortManager;
public SerialConnection(String portName, SerialPortManager serialPortManager) {
this.portName = portName;
this.serialPortManager = serialPortManager;
}
public synchronized void open() throws ConnectException {
try {
if (!open) {
logger.debug("Trying to open serial connection to port {}...", portName);
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(portName);
if (portIdentifier == null) {
throw new ConnectException("No such port: " + portName);
}
try {
serialPort = portIdentifier.open("openhab", 3000);
open = true;
logger.debug("Serial connection to port {} opened.", portName);
serialPort.setSerialPortParams(38400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
serialPort.addEventListener(new SerialPortEventListener() {
@Override
public void serialEvent(SerialPortEvent event) {
try {
if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
parseInput();
}
} catch (IOException ex) {
logger.warn("IOException reading from port {}!", portName);
}
}
});
serialPort.notifyOnDataAvailable(true);
} catch (UnsupportedCommOperationException | TooManyListenersException ex) {
close();
throw new ConnectException(ex);
}
}
} catch (PortInUseException ex) {
throw new ConnectException(ex);
}
}
public synchronized boolean isOpen() {
return open;
}
public synchronized void close() {
if (open) {
logger.debug("Closing serial connection to port {}...", portName);
serialPort.notifyOnDataAvailable(false);
serialPort.removeEventListener();
serialPort.close();
open = false;
}
}
// send a packet to the stick and wait for the response
public synchronized Response sendPacket(CommandPacket p) throws IOException {
if (open) {
Response r = response;
synchronized (bytes) {
response = null;
logger.debug("Writing packet to stick: {}", p);
serialPort.getOutputStream().write(p.getBytes());
if (r != null) {
return r;
}
final long responseTimeout = p.getResponseTimeout();
try {
logger.trace("Waiting {} ms for answer from stick...", responseTimeout);
bytes.wait(responseTimeout);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
r = response;
response = null;
}
logger.debug("Stick answered {} for packet {}.", r, p);
return r;
}
logger.warn("Stick skipped packet {}. Connection is not open.", p);
return null;
}
private void parseInput() throws IOException {
logger.trace("parsing input...");
while (serialPort.getInputStream().available() > 0) {
byte b = (byte) serialPort.getInputStream().read();
bytes.add(b);
}
logger.trace("input parsed. buffer contains {} bytes.", bytes.size());
analyzeBuffer();
}
private void analyzeBuffer() {
// drop everything before the beginning of the packet header 0xAA
while (!bytes.isEmpty() && bytes.get(0) != (byte) 0xAA) {
logger.trace("dropping byte {} from buffer", bytes.get(0));
bytes.remove(0);
}
if (logger.isTraceEnabled()) {
logger.trace("buffer contains {} bytes: {}", bytes.size(),
ArrayUtils.toPrimitive(bytes.toArray(new Byte[bytes.size()])));
}
if (bytes.size() > 1) {
// second byte should be length byte (has to be either 0x04 or 0x05)
int len = bytes.get(1);
logger.trace("packet length is {} bytes.", len);
if (len != 4 && len != 5) {
// invalid length, drop packet
bytes.remove(0);
analyzeBuffer();
} else if (bytes.size() > len + 1) {
// we have a complete packet in the buffer, analyze it
// third byte should be response type byte (has to be either EASY_CONFIRM or EASY_ACK)
byte respType = bytes.get(2);
synchronized (bytes) {
if (respType == ResponseStatus.EASY_CONFIRM) {
logger.trace("response type is EASY_CONFIRM.");
long val = bytes.get(0) + bytes.get(1) + bytes.get(2) + bytes.get(3) + bytes.get(4)
+ bytes.get(5);
if (val % 256 == 0) {
response = ResponseUtil.createResponse(bytes.get(3), bytes.get(4));
} else {
logger.warn("invalid response checksum. Skipping response.");
}
bytes.notify();
} else if (respType == ResponseStatus.EASY_ACK) {
logger.trace("response type is EASY_ACK.");
long val = bytes.get(0) + bytes.get(1) + bytes.get(2) + bytes.get(3) + bytes.get(4)
+ bytes.get(5) + bytes.get(6);
if (val % 256 == 0) {
response = ResponseUtil.createResponse(bytes.get(3), bytes.get(4), bytes.get(5));
} else {
logger.warn("invalid response checksum. Skipping response.");
}
bytes.notify();
} else {
logger.warn("invalid response type {}. Skipping response.", respType);
}
}
bytes.remove(0);
analyzeBuffer();
}
}
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.elerotransmitterstick.internal.stick;
/**
* @author Volker Bier - Initial contribution
*/
public class TimedCommand extends Command {
private int duration;
public TimedCommand(CommandType cmd, int cmdDuration, Integer[] channels) {
super(cmd, channels);
duration = cmdDuration;
}
public int getDuration() {
return duration;
}
}

View File

@@ -0,0 +1,416 @@
/**
* 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.elerotransmitterstick.internal.stick;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.openhab.binding.elerotransmitterstick.internal.config.EleroTransmitterStickConfig;
import org.openhab.binding.elerotransmitterstick.internal.handler.StatusListener;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Volker Bier - Initial contribution
*/
public class TransmitterStick {
private final Logger logger = LoggerFactory.getLogger(TransmitterStick.class);
private final HashMap<Integer, ArrayList<StatusListener>> allListeners = new HashMap<>();
private final StickListener listener;
private EleroTransmitterStickConfig config;
private SerialPortManager serialPortManager;
private CommandWorker worker;
public TransmitterStick(StickListener l) {
listener = l;
}
public synchronized void initialize(EleroTransmitterStickConfig stickConfig, ScheduledExecutorService scheduler,
SerialPortManager serialPortManager) {
logger.debug("Initializing Transmitter Stick...");
config = stickConfig;
this.serialPortManager = serialPortManager;
worker = new CommandWorker();
scheduler.schedule(worker, 0, TimeUnit.MILLISECONDS);
logger.debug("Transmitter Stick initialized, worker running.");
}
public synchronized void dispose() {
logger.debug("Disposing Transmitter Stick...");
worker.terminateUpdates();
worker = null;
config = null;
logger.debug("Transmitter Stick disposed.");
}
public synchronized ArrayList<Integer> getKnownIds() {
if (worker != null) {
return worker.validIds;
}
return new ArrayList<>();
}
public synchronized void sendCommand(CommandType cmd, List<Integer> channelIds) {
if (worker != null) {
worker.executeCommand(cmd, channelIds);
}
}
public synchronized void requestUpdate(List<Integer> channelIds) {
if (worker != null) {
worker.requestUpdates(channelIds);
}
}
public void addStatusListener(int channelId, StatusListener listener) {
synchronized (allListeners) {
ArrayList<StatusListener> listeners = allListeners.get(channelId);
if (listeners == null) {
listeners = new ArrayList<>();
allListeners.put(channelId, listeners);
}
listeners.add(listener);
}
}
public void removeStatusListener(int channelId, StatusListener listener) {
synchronized (allListeners) {
ArrayList<StatusListener> listeners = allListeners.get(channelId);
if (listeners != null) {
listeners.remove(listener);
if (listeners.isEmpty()) {
allListeners.remove(channelId);
}
}
}
}
private void notifyListeners(int channelId, ResponseStatus status) {
synchronized (allListeners) {
ArrayList<StatusListener> listeners = allListeners.get(channelId);
if (listeners != null) {
for (StatusListener l : listeners) {
l.statusChanged(channelId, status);
}
}
}
}
/**
* Make sure we have
* - only one INFO for the same channel ids
* - only one other command for the same channel ids
*/
private static boolean prepareAddition(Command newCmd, Collection<Command> coll) {
Iterator<Command> queuedCommands = coll.iterator();
while (queuedCommands.hasNext()) {
Command existingCmd = queuedCommands.next();
if (Arrays.equals(newCmd.getChannelIds(), existingCmd.getChannelIds())) {
// remove pending INFOs for same channel ids
if (newCmd.getCommandType() == CommandType.INFO && existingCmd.getCommandType() == CommandType.INFO) {
if (existingCmd.getPriority() < newCmd.priority) {
// we have an older INFO command with same or lower priority, remove
queuedCommands.remove();
} else {
// existing has higher priority, skip addition
return false;
}
}
if (newCmd.getCommandType() != CommandType.INFO && existingCmd.getCommandType() != CommandType.INFO) {
// we have an older command for the same channels, remove
queuedCommands.remove();
}
}
}
return true;
}
static class DueCommandSet extends TreeSet<Command> {
private static final long serialVersionUID = -3216360253151368826L;
public DueCommandSet() {
super(new Comparator<Command>() {
/**
* Due commands are sorted by priority first and then by delay.
*/
@Override
public int compare(Command o1, Command o2) {
if (o1.equals(o2)) {
return 0;
}
int d = o2.getPriority() - o1.getPriority();
if (d < 0) {
return -1;
}
if (d == 0 && o1.getDelay(TimeUnit.MILLISECONDS) < o2.getDelay(TimeUnit.MILLISECONDS)) {
return -1;
}
return 1;
}
});
}
@Override
public boolean add(Command e) {
if (TransmitterStick.prepareAddition(e, this)) {
return super.add(e);
}
return false;
}
}
class CommandWorker implements Runnable {
private ArrayList<Integer> validIds = new ArrayList<>();
private final AtomicBoolean terminated = new AtomicBoolean();
private final int updateInterval;
private final SerialConnection connection;
private final BlockingQueue<Command> cmdQueue = new DelayQueue<Command>() {
@Override
public boolean add(Command e) {
if (TransmitterStick.prepareAddition(e, this)) {
return super.add(e);
}
return false;
}
};
CommandWorker() {
connection = new SerialConnection(config.portName, serialPortManager);
updateInterval = config.updateInterval;
}
void terminateUpdates() {
terminated.set(true);
// add a NONE command to make the thread exit from the call to take()
cmdQueue.add(new Command(CommandType.NONE));
}
void requestUpdates(List<Integer> channelIds) {
// this is a workaround for a bug in the stick firmware that does not
// handle commands that are sent to multiple channels correctly
if (channelIds.size() > 1) {
for (int channelId : channelIds) {
requestUpdates(Collections.singletonList(channelId));
}
} else if (!channelIds.isEmpty()) {
final Integer[] ids = channelIds.toArray(new Integer[channelIds.size()]);
logger.debug("adding INFO command for channel id {} to queue...", Arrays.toString(ids));
cmdQueue.add(new DelayedCommand(CommandType.INFO, 0, Command.FAST_INFO_PRIORITY, ids));
}
}
void executeCommand(CommandType command, List<Integer> channelIds) {
// this is a workaround for a bug in the stick firmware that does not
// handle commands that are sent to multiple channels correctly
if (channelIds.size() > 1) {
for (int channelId : channelIds) {
executeCommand(command, Collections.singletonList(channelId));
}
} else if (!channelIds.isEmpty()) {
final Integer[] ids = channelIds.toArray(new Integer[channelIds.size()]);
logger.debug("adding command {} for channel ids {} to queue...", command, Arrays.toString(ids));
cmdQueue.add(new Command(command, ids));
}
}
@Override
public void run() {
try {
queryChannels();
doWork();
} catch (Throwable t) {
logger.error("Worker stopped by unexpected exception", t);
} finally {
connection.close();
}
}
private void doWork() {
// list of due commands sorted by priority
final DueCommandSet dueCommands = new DueCommandSet();
logger.debug("worker started.");
while (!terminated.get()) {
waitConnected();
try {
// in case we have no commands that are currently due, wait for a new one
if (dueCommands.isEmpty()) {
logger.debug("No due commands, invoking take on queue...");
dueCommands.add(cmdQueue.take());
logger.trace("take returned {}", dueCommands.first());
}
if (!terminated.get()) {
// take all commands that are due from the queue
logger.trace("Draining all available commands...");
Command cmd;
int drainCount = 0;
while ((cmd = cmdQueue.poll()) != null) {
drainCount++;
dueCommands.remove(cmd);
dueCommands.add(cmd);
}
logger.trace("Drained {} commands, active queue size is {}, queue size is {}", drainCount,
dueCommands.size(), cmdQueue.size());
// process the command with the highest priority
cmd = dueCommands.first();
logger.debug("active command is {}", cmd);
if (cmd.getCommandType() != CommandType.NONE) {
Response response = connection.sendPacket(CommandUtil.createPacket(cmd));
// remove the command now we know it has been correctly processed
dueCommands.pollFirst();
if (response != null && response.hasStatus()) {
for (int id : response.getChannelIds()) {
notifyListeners(id, response.getStatus());
}
}
if (cmd instanceof TimedCommand) {
long delay = 1000 * ((TimedCommand) cmd).getDuration();
logger.debug("adding timed command STOP for channel ids {} to queue with delay {}...",
cmd.getChannelIds(), delay);
cmdQueue.add(new DelayedCommand(CommandType.STOP, delay, Command.TIMED_PRIORITY,
cmd.getChannelIds()));
} else if (response != null && response.isMoving()) {
logger.debug("adding timed command INFO for channel ids {} to queue with delay 2000...",
cmd.getChannelIds());
cmdQueue.add(new DelayedCommand(CommandType.INFO, 2000, Command.FAST_INFO_PRIORITY,
cmd.getChannelIds()));
} else if (cmd.getCommandType() == CommandType.INFO) {
logger.debug("adding timed command INFO for channel ids {} to queue with delay {}...",
cmd.getChannelIds(), updateInterval * 1000);
cmdQueue.add(new DelayedCommand(CommandType.INFO, updateInterval * 1000,
Command.INFO_PRIORITY, cmd.getChannelIds()));
}
} else {
logger.trace("ignoring NONE command.");
}
}
} catch (InterruptedException e) {
logger.error("Got interrupt while waiting for next command time", e);
Thread.currentThread().interrupt();
} catch (IOException e) {
logger.error("Got IOException communicating with the stick", e);
listener.connectionDropped(e);
connection.close();
}
}
logger.debug("worker finished.");
}
private void queryChannels() {
logger.debug("querying available channels...");
while (!terminated.get()) {
waitConnected();
try {
Response r = null;
while (r == null && !terminated.get() && connection.isOpen()) {
logger.debug("sending CHECK packet...");
r = connection.sendPacket(CommandUtil.createPacket(CommandType.CHECK));
if (r == null) {
Thread.sleep(2000);
}
}
if (r != null) {
int[] knownIds = r.getChannelIds();
logger.debug("Worker found channels: {} ", Arrays.toString(knownIds));
for (int id : knownIds) {
if (!validIds.contains(id)) {
validIds.add(id);
}
}
requestUpdates(validIds);
break;
}
} catch (IOException e) {
logger.error("Got IOException communicating with the stick", e);
listener.connectionDropped(e);
connection.close();
} catch (InterruptedException e) {
logger.error("Got interrupt while waiting for next command time", e);
Thread.currentThread().interrupt();
}
}
}
private void waitConnected() {
if (!connection.isOpen()) {
while (!connection.isOpen() && !terminated.get()) {
try {
connection.open();
listener.connectionEstablished();
} catch (ConnectException e1) {
listener.connectionDropped(e1);
}
if (!connection.isOpen() && !terminated.get()) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
logger.error("Got interrupt while waiting for next command time", e);
Thread.currentThread().interrupt();
}
}
}
}
logger.trace("finished waiting. connection open={}, terminated={}", connection.isOpen(), terminated.get());
}
}
public interface StickListener {
void connectionEstablished();
void connectionDropped(Exception e);
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="elerotransmitterstick" 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>Elero Transmitter Stick Binding</name>
<description>This is the binding for Elero Transmitter Sticks.</description>
<author>Volker Bier</author>
</binding:binding>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="elerotransmitterstick"
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="elerostick">
<label>Elero Transmitter Stick</label>
<description>Thing for an Elero Transmitter Stick</description>
<config-description>
<parameter name="portName" type="text" required="true">
<label>Port Name</label>
<description>The name of the port to which the Elero Transmitter Stick is connected.</description>
<context>serial-port</context>
<limitToOptions>false</limitToOptions>
</parameter>
<parameter name="updateInterval" type="integer" min="5" max="3600" unit="s" step="5">
<label>Update Interval</label>
<description>The number of seconds to wait before requesting the status of the elero channels from the Elero
Transmitter Stick.</description>
<default>30</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="elerochannel">
<supported-bridge-type-refs>
<bridge-type-ref id="elerostick"/>
</supported-bridge-type-refs>
<label>Elero Channel</label>
<description>One of the 15 elero channels available on an Elero Transmitter Stick.</description>
<channels>
<channel id="control" typeId="control"/>
<channel id="status" typeId="status"/>
</channels>
<config-description>
<parameter name="channelId" type="integer" min="1" max="15" step="1" readOnly="true">
<label>Channel ID</label>
<description>The id of this channel.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<!-- Control Channel Type -->
<channel-type id="control">
<item-type>Rollershutter</item-type>
<label>Control</label>
<description>Allows to control the devices connected to this elero channel or group by sending UP, DOWN, STOP
commands.</description>
</channel-type>
<!-- Status Channel Type -->
<channel-type id="status">
<item-type>String</item-type>
<label>Status</label>
<description>Read only channel providing the current status of this elero channel or group.</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>