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.amazondashbutton-${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-amazondashbutton" description="Amazon Dash Button Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-runtime-jna</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.amazondashbutton/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.amazondashbutton.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link AmazonDashButtonBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AmazonDashButtonBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "amazondashbutton";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID DASH_BUTTON_THING_TYPE = new ThingTypeUID(BINDING_ID, "dashbutton");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String PRESS = "press";
|
||||
|
||||
// Custom Properties
|
||||
public static final String PROPERTY_MAC_ADDRESS = "macAddress";
|
||||
public static final String PROPERTY_NETWORK_INTERFACE_NAME = "pcapNetworkInterfaceName";
|
||||
public static final String PROPERTY_PACKET_INTERVAL = "packetInterval";
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazondashbutton.internal;
|
||||
|
||||
import static org.openhab.binding.amazondashbutton.internal.AmazonDashButtonBindingConstants.DASH_BUTTON_THING_TYPE;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.openhab.binding.amazondashbutton.internal.handler.AmazonDashButtonHandler;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The {@link AmazonDashButtonHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.amazondashbutton")
|
||||
public class AmazonDashButtonHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(DASH_BUTTON_THING_TYPE);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(DASH_BUTTON_THING_TYPE)) {
|
||||
return new AmazonDashButtonHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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.amazondashbutton.internal.capturing;
|
||||
|
||||
import org.pcap4j.util.MacAddress;
|
||||
|
||||
/**
|
||||
* The {@link PacketCapturingHandler} is notified if a packet is captured by {@link PacketCapturingService}.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*
|
||||
*/
|
||||
public interface PacketCapturingHandler {
|
||||
/**
|
||||
* Callback method to handle a captured packet.
|
||||
*
|
||||
* @param macAddress The mac address which sent the packet
|
||||
*/
|
||||
public void packetCaptured(MacAddress sourceMacAddress);
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazondashbutton.internal.capturing;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceWrapper;
|
||||
import org.pcap4j.core.BpfProgram.BpfCompileMode;
|
||||
import org.pcap4j.core.NotOpenException;
|
||||
import org.pcap4j.core.PacketListener;
|
||||
import org.pcap4j.core.PcapHandle;
|
||||
import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode;
|
||||
import org.pcap4j.packet.ArpPacket;
|
||||
import org.pcap4j.packet.EthernetPacket;
|
||||
import org.pcap4j.packet.Packet;
|
||||
import org.pcap4j.packet.UdpPacket;
|
||||
import org.pcap4j.packet.namednumber.ArpOperation;
|
||||
import org.pcap4j.packet.namednumber.UdpPort;
|
||||
import org.pcap4j.util.MacAddress;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link PacketCapturingService} is responsible for capturing packets.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class PacketCapturingService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PacketCapturingService.class);
|
||||
|
||||
private static final int READ_TIMEOUT = 10; // [ms]
|
||||
private static final int SNAPLEN = 65536; // [bytes]
|
||||
|
||||
private final PcapNetworkInterfaceWrapper pcapNetworkInterface;
|
||||
|
||||
private PcapHandle pcapHandle;
|
||||
|
||||
public PacketCapturingService(PcapNetworkInterfaceWrapper pcapNetworkInterface) {
|
||||
this.pcapNetworkInterface = pcapNetworkInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #startCapturing(PacketCapturingHandler, String)} with a null MAC address.
|
||||
*
|
||||
* @param packetCapturingHandler The handler to be called every time packet is captured
|
||||
* @return Returns true, if the capturing has been started successfully, otherwise returns false
|
||||
*/
|
||||
public boolean startCapturing(final PacketCapturingHandler packetCapturingHandler) {
|
||||
return startCapturing(packetCapturingHandler, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the capturing in a dedicated thread, so this method returns immediately. Every time a packet is captured,
|
||||
* the {@link PacketCapturingHandler#packetCaptured(MacAddress)} of the given
|
||||
* {@link PacketCapturingHandler} is called.
|
||||
*
|
||||
* It's possible to capture packets sent by a specific MAC address by providing the given parameter. If the
|
||||
* macAddress is null, all MAC addresses are considered.
|
||||
*
|
||||
* @param packetCapturingHandler The handler to be called every time a packet is captured
|
||||
* @param macAddress The source MAC address of the captured packet, might be null in order to deactivate this filter
|
||||
* criteria
|
||||
* @return Returns true, if the capturing has been started successfully, otherwise returns false
|
||||
* @throws IllegalStateException Thrown if {@link PcapHandle#isOpen()} of {@link #pcapHandle} returns true
|
||||
*/
|
||||
|
||||
public boolean startCapturing(final PacketCapturingHandler packetCapturingHandler, final String macAddress) {
|
||||
if (pcapHandle != null) {
|
||||
if (pcapHandle.isOpen()) {
|
||||
throw new IllegalStateException("There is an open pcap handle.");
|
||||
} else {
|
||||
pcapHandle.close();
|
||||
}
|
||||
}
|
||||
try {
|
||||
pcapHandle = pcapNetworkInterface.openLive(SNAPLEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
|
||||
StringBuilder filterBuilder = new StringBuilder("(arp or port bootps)");
|
||||
if (macAddress != null) {
|
||||
filterBuilder.append(" and ether src " + macAddress);
|
||||
}
|
||||
pcapHandle.setFilter(filterBuilder.toString(), BpfCompileMode.OPTIMIZE);
|
||||
} catch (Exception e) {
|
||||
logger.error("Capturing packets on device {} failed.", pcapNetworkInterface.getName(), e);
|
||||
return false;
|
||||
}
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
executorService.submit(() -> {
|
||||
try {
|
||||
pcapHandle.loop(-1, new PacketListener() {
|
||||
|
||||
@Override
|
||||
public void gotPacket(Packet packet) {
|
||||
if (!packet.contains(EthernetPacket.class)) {
|
||||
return;
|
||||
}
|
||||
final EthernetPacket ethernetPacket = packet.get(EthernetPacket.class);
|
||||
final MacAddress sourceMacAddress = ethernetPacket.getHeader().getSrcAddr();
|
||||
if (shouldCapture(packet)) {
|
||||
packetCapturingHandler.packetCaptured(sourceMacAddress);
|
||||
}
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
if (pcapHandle != null && pcapHandle.isOpen()) {
|
||||
pcapHandle.close();
|
||||
pcapHandle = null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (macAddress == null) {
|
||||
logger.debug("Started capturing ARP and BOOTP requests for network device {}.",
|
||||
pcapNetworkInterface.getName());
|
||||
} else {
|
||||
logger.debug("Started capturing ARP and BOOTP requests for network device {} and MAC address {}.",
|
||||
pcapNetworkInterface.getName(), macAddress);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given {@link Packet} should be captured.
|
||||
*
|
||||
* @param packet The packet to be checked
|
||||
* @return Returns true, if the packet should be captured, otherwise false
|
||||
*/
|
||||
private boolean shouldCapture(final Packet packet) {
|
||||
if (packet.contains(ArpPacket.class)) {
|
||||
ArpPacket arpPacket = packet.get(ArpPacket.class);
|
||||
if (arpPacket.getHeader().getOperation().equals(ArpOperation.REQUEST)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (packet.contains(UdpPacket.class)) {
|
||||
final UdpPacket udpPacket = packet.get(UdpPacket.class);
|
||||
if (UdpPort.BOOTPS == udpPacket.getHeader().getDstPort()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the capturing. This can be called without calling {@link #startCapturing(PacketCapturingHandler)} or
|
||||
* {@link #startCapturing(PacketCapturingHandler, String)} before.
|
||||
*/
|
||||
public void stopCapturing() {
|
||||
if (pcapHandle != null) {
|
||||
if (pcapHandle.isOpen()) {
|
||||
try {
|
||||
pcapHandle.breakLoop();
|
||||
logger.debug("Stopped capturing ARP and BOOTP requests for network device {}.",
|
||||
pcapNetworkInterface.getName());
|
||||
} catch (NotOpenException e) {
|
||||
// Just ignore
|
||||
}
|
||||
} else {
|
||||
pcapHandle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tracked {@link PcapNetworkInterfaceWrapper}.
|
||||
*
|
||||
* @return the tracked {@link PcapNetworkInterfaceWrapper}
|
||||
*/
|
||||
public PcapNetworkInterfaceWrapper getPcapNetworkInterface() {
|
||||
return pcapNetworkInterface;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazondashbutton.internal.config;
|
||||
|
||||
/**
|
||||
* The configuration of the Amazon Dash Button
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class AmazonDashButtonConfig {
|
||||
|
||||
/**
|
||||
* The MAC address of the Amazon Dash Button
|
||||
*/
|
||||
public String macAddress;
|
||||
|
||||
/**
|
||||
* The network interface which receives the packets of the Amazon Dash Button
|
||||
*/
|
||||
public String pcapNetworkInterfaceName;
|
||||
|
||||
/**
|
||||
* Often a single button press is recognized multiple times. You can specify how long any further detected button
|
||||
* pressed should be ignored after one click is handled (in ms).
|
||||
*/
|
||||
public Integer packetInterval;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazondashbutton.internal.config;
|
||||
|
||||
import static org.openhab.binding.amazondashbutton.internal.AmazonDashButtonBindingConstants.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazondashbutton.internal.AmazonDashButtonBindingConstants;
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceService;
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceWrapper;
|
||||
import org.openhab.core.config.core.ConfigOptionProvider;
|
||||
import org.openhab.core.config.core.ParameterOption;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.pcap4j.core.PcapAddress;
|
||||
|
||||
/**
|
||||
* The {@link AmazonDashButtonConfigOptionProvider} is responsible for providing options for the
|
||||
* {@link AmazonDashButtonBindingConstants#PROPERTY_NETWORK_INTERFACE_NAME} property.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*
|
||||
*/
|
||||
@Component(service = ConfigOptionProvider.class)
|
||||
@NonNullByDefault
|
||||
public class AmazonDashButtonConfigOptionProvider implements ConfigOptionProvider {
|
||||
|
||||
@Override
|
||||
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String param, @Nullable Locale locale) {
|
||||
return getParameterOptions(uri, param, null, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String param, @Nullable String context,
|
||||
@Nullable Locale locale) {
|
||||
if ("thing-type".equals(uri.getScheme())) {
|
||||
ThingTypeUID thingtypeUID = new ThingTypeUID(uri.getSchemeSpecificPart());
|
||||
if (thingtypeUID.equals(DASH_BUTTON_THING_TYPE) && PROPERTY_NETWORK_INTERFACE_NAME.equals(param)) {
|
||||
return getPcapNetworkInterfacesOptions();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Collection<ParameterOption> getPcapNetworkInterfacesOptions() {
|
||||
Set<PcapNetworkInterfaceWrapper> pcapNetworkInterfaces = PcapNetworkInterfaceService.instance()
|
||||
.getNetworkInterfaces();
|
||||
List<ParameterOption> options = new ArrayList<>();
|
||||
for (PcapNetworkInterfaceWrapper pcapNetworkInterface : pcapNetworkInterfaces) {
|
||||
String name = pcapNetworkInterface.getName();
|
||||
|
||||
options.add(new ParameterOption(name, getLabel(pcapNetworkInterface)));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private String getLabel(PcapNetworkInterfaceWrapper pcapNetworkInterface) {
|
||||
StringBuilder sb = new StringBuilder(pcapNetworkInterface.getName());
|
||||
List<PcapAddress> addresses = pcapNetworkInterface.getAddresses();
|
||||
final String description = pcapNetworkInterface.getDescription();
|
||||
Set<String> paramStrings = new LinkedHashSet<>();
|
||||
if (description != null && !description.isEmpty()) {
|
||||
paramStrings.add(description);
|
||||
}
|
||||
for (PcapAddress address : addresses) {
|
||||
paramStrings.add(address.getAddress().toString().substring(1));
|
||||
|
||||
}
|
||||
|
||||
boolean hasParams = !paramStrings.isEmpty();
|
||||
if (hasParams) {
|
||||
sb.append(" (");
|
||||
}
|
||||
|
||||
for (Iterator<String> paramIterator = paramStrings.iterator(); paramIterator.hasNext();) {
|
||||
String addressString = paramIterator.next();
|
||||
sb.append(addressString);
|
||||
if (paramIterator.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
if (hasParams) {
|
||||
sb.append(")");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* 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.amazondashbutton.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.amazondashbutton.internal.AmazonDashButtonBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.openhab.binding.amazondashbutton.internal.capturing.PacketCapturingHandler;
|
||||
import org.openhab.binding.amazondashbutton.internal.capturing.PacketCapturingService;
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceListener;
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceService;
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceWrapper;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.pcap4j.core.PcapNetworkInterface;
|
||||
import org.pcap4j.util.MacAddress;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AmazonDashButtonDiscoveryService} is responsible for discovering Amazon Dash Buttons. It does so by
|
||||
* capturing ARP and BOOTP requests from all available network devices.
|
||||
*
|
||||
* While scanning the user has to press the button in order to send an ARP and BOOTP request packet. The
|
||||
* {@link AmazonDashButtonDiscoveryService} captures this packet and checks the device's MAC address which sent the
|
||||
* request against a static list of vendor prefixes ({@link #VENDOR_PREFIXES}).
|
||||
*
|
||||
* If an Amazon MAC address is detected a {@link DiscoveryResult} is built and passed to
|
||||
* {@link #thingDiscovered(DiscoveryResult)}.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*
|
||||
*/
|
||||
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.amazondashbutton")
|
||||
public class AmazonDashButtonDiscoveryService extends AbstractDiscoveryService implements PcapNetworkInterfaceListener {
|
||||
|
||||
private static final int DISCOVER_TIMEOUT_SECONDS = 30;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AmazonDashButtonDiscoveryService.class);
|
||||
|
||||
/**
|
||||
* The Amazon Dash button vendor prefixes
|
||||
*/
|
||||
// @formatter:off
|
||||
private static final Set<String> VENDOR_PREFIXES = Collections.unmodifiableSet(Stream.of(
|
||||
"F0:D2:F1",
|
||||
"88:71:E5",
|
||||
"FC:A1:83",
|
||||
"F0:27:2D",
|
||||
"74:C2:46",
|
||||
"68:37:E9",
|
||||
"78:E1:03",
|
||||
"38:F7:3D",
|
||||
"50:DC:E7",
|
||||
"A0:02:DC",
|
||||
"0C:47:C9",
|
||||
"74:75:48",
|
||||
"AC:63:BE",
|
||||
"FC:A6:67",
|
||||
"18:74:2E",
|
||||
"00:FC:8B",
|
||||
"FC:65:DE",
|
||||
"6C:56:97",
|
||||
"44:65:0D",
|
||||
"50:F5:DA",
|
||||
"68:54:FD",
|
||||
"40:B4:CD",
|
||||
"84:D6:D0",
|
||||
"34:D2:70",
|
||||
"B4:7C:9C"
|
||||
).collect(Collectors.toSet()));
|
||||
// @formatter:on
|
||||
|
||||
/**
|
||||
* Returns true if the passed macAddress is an Amazon MAC address.
|
||||
*
|
||||
* @param macAddress
|
||||
* @return
|
||||
*/
|
||||
private static boolean isAmazonVendor(String macAddress) {
|
||||
String vendorPrefix = macAddress.substring(0, 8).toUpperCase();
|
||||
return VENDOR_PREFIXES.contains(vendorPrefix);
|
||||
}
|
||||
|
||||
private final Map<PcapNetworkInterfaceWrapper, PacketCapturingService> packetCapturingServices = new ConcurrentHashMap<>();
|
||||
|
||||
private boolean explicitScanning = false;
|
||||
private boolean backgroundScanning = false;
|
||||
|
||||
public AmazonDashButtonDiscoveryService() {
|
||||
super(Collections.singleton(DASH_BUTTON_THING_TYPE), DISCOVER_TIMEOUT_SECONDS, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
explicitScanning = true;
|
||||
updateListenerRegistry();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
explicitScanning = false;
|
||||
updateListenerRegistry();
|
||||
super.stopScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
backgroundScanning = true;
|
||||
updateListenerRegistry();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
backgroundScanning = false;
|
||||
updateListenerRegistry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPcapNetworkInterfaceAdded(final PcapNetworkInterfaceWrapper networkInterface) {
|
||||
startCapturing(networkInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPcapNetworkInterfaceRemoved(PcapNetworkInterfaceWrapper networkInterface) {
|
||||
stopCapturing(networkInterface);
|
||||
}
|
||||
|
||||
private void updateListenerRegistry() {
|
||||
boolean shouldListen = explicitScanning || backgroundScanning;
|
||||
if (shouldListen) {
|
||||
PcapNetworkInterfaceService.instance().registerListener(this);
|
||||
// Start capturing for all network interfaces
|
||||
final Set<PcapNetworkInterfaceWrapper> networkInterfaces = PcapNetworkInterfaceService.instance()
|
||||
.getNetworkInterfaces();
|
||||
for (PcapNetworkInterfaceWrapper pcapNetworkInterface : networkInterfaces) {
|
||||
startCapturing(pcapNetworkInterface);
|
||||
}
|
||||
} else {
|
||||
PcapNetworkInterfaceService.instance().unregisterListener(this);
|
||||
// Stop capturing for all network interfaces
|
||||
final Set<PcapNetworkInterfaceWrapper> networkInterfaces = packetCapturingServices.keySet();
|
||||
for (PcapNetworkInterfaceWrapper pcapNetworkInterface : networkInterfaces) {
|
||||
stopCapturing(pcapNetworkInterface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops capturing for packets for the given {@link PcapNetworkInterface}.
|
||||
*
|
||||
* @param pcapNetworkInterface The {@link PcapNetworkInterface} the capturing should be stopped for.
|
||||
*/
|
||||
private void stopCapturing(final PcapNetworkInterfaceWrapper pcapNetworkInterface) {
|
||||
final PacketCapturingService packetCapturingService = packetCapturingServices.remove(pcapNetworkInterface);
|
||||
final String interfaceName = pcapNetworkInterface.getName();
|
||||
if (packetCapturingService != null) {
|
||||
packetCapturingService.stopCapturing();
|
||||
logger.debug("Stopped capturing for {}.", interfaceName);
|
||||
} else {
|
||||
logger.warn("No active PacketCapturingService registered for {}.", interfaceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts capturing for packets for the given {@link PcapNetworkInterface}. If the network interface is already
|
||||
* captured this method returns without doing anything.
|
||||
*
|
||||
* @param pcapNetworkInterface The {@link PcapNetworkInterface} to be captured
|
||||
*/
|
||||
private void startCapturing(final PcapNetworkInterfaceWrapper pcapNetworkInterface) {
|
||||
if (packetCapturingServices.containsKey(pcapNetworkInterface)) {
|
||||
// We already have a tracker
|
||||
return;
|
||||
}
|
||||
|
||||
PacketCapturingService packetCapturingService = new PacketCapturingService(pcapNetworkInterface);
|
||||
|
||||
packetCapturingServices.put(pcapNetworkInterface, packetCapturingService);
|
||||
final String interfaceName = pcapNetworkInterface.getName();
|
||||
final boolean capturingStarted = packetCapturingService.startCapturing(new PacketCapturingHandler() {
|
||||
|
||||
@Override
|
||||
public void packetCaptured(MacAddress macAddress) {
|
||||
String macAdressString = macAddress.toString();
|
||||
|
||||
if (isAmazonVendor(macAdressString)) {
|
||||
logger.debug("Captured a packet from {} which seems to be sent from an Amazon Dash Button device.",
|
||||
macAdressString);
|
||||
ThingUID dashButtonThing = new ThingUID(DASH_BUTTON_THING_TYPE, macAdressString.replace(":", "-"));
|
||||
// @formatter:off
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(dashButtonThing)
|
||||
.withLabel("Dash Button")
|
||||
.withRepresentationProperty(macAdressString)
|
||||
.withProperty(PROPERTY_MAC_ADDRESS, macAdressString)
|
||||
.withProperty(PROPERTY_NETWORK_INTERFACE_NAME, interfaceName)
|
||||
.withProperty(PROPERTY_PACKET_INTERVAL, BigDecimal.valueOf(5000))
|
||||
.build();
|
||||
// @formatter:on
|
||||
thingDiscovered(discoveryResult);
|
||||
} else {
|
||||
logger.trace(
|
||||
"Captured a packet from {} which is ignored as it's not on the list of supported vendor prefixes.",
|
||||
macAdressString);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (capturingStarted) {
|
||||
logger.debug("Started capturing for {}.", interfaceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazondashbutton.internal.handler;
|
||||
|
||||
import static org.openhab.binding.amazondashbutton.internal.AmazonDashButtonBindingConstants.PRESS;
|
||||
import static org.openhab.core.thing.CommonTriggerEvents.PRESSED;
|
||||
|
||||
import org.openhab.binding.amazondashbutton.internal.capturing.PacketCapturingHandler;
|
||||
import org.openhab.binding.amazondashbutton.internal.capturing.PacketCapturingService;
|
||||
import org.openhab.binding.amazondashbutton.internal.config.AmazonDashButtonConfig;
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceListener;
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceService;
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceWrapper;
|
||||
import org.openhab.binding.amazondashbutton.internal.pcap.PcapUtil;
|
||||
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.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.pcap4j.util.MacAddress;
|
||||
|
||||
/**
|
||||
* The {@link AmazonDashButtonHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*/
|
||||
public class AmazonDashButtonHandler extends BaseThingHandler implements PcapNetworkInterfaceListener {
|
||||
private PacketCapturingService packetCapturingService;
|
||||
|
||||
private long lastCommandHandled = 0;
|
||||
|
||||
public AmazonDashButtonHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// There are no commands to be handled
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
PcapNetworkInterfaceService.instance().registerListener(this);
|
||||
AmazonDashButtonConfig dashButtonConfig = getConfigAs(AmazonDashButtonConfig.class);
|
||||
final String pcapNetworkInterfaceName = dashButtonConfig.pcapNetworkInterfaceName;
|
||||
final String macAddress = dashButtonConfig.macAddress;
|
||||
final Integer packetInterval = dashButtonConfig.packetInterval;
|
||||
scheduler.submit(() -> {
|
||||
PcapNetworkInterfaceWrapper pcapNetworkInterface = PcapUtil
|
||||
.getNetworkInterfaceByName(pcapNetworkInterfaceName);
|
||||
if (pcapNetworkInterface == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
|
||||
"The networkinterface " + pcapNetworkInterfaceName + " is not present.");
|
||||
return;
|
||||
}
|
||||
|
||||
packetCapturingService = new PacketCapturingService(pcapNetworkInterface);
|
||||
boolean capturingStarted = packetCapturingService.startCapturing(new PacketCapturingHandler() {
|
||||
@Override
|
||||
public void packetCaptured(MacAddress macAddress) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (lastCommandHandled + packetInterval < now) {
|
||||
triggerChannel(PRESS, PRESSED);
|
||||
lastCommandHandled = now;
|
||||
}
|
||||
}
|
||||
}, macAddress);
|
||||
if (capturingStarted) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
|
||||
"The capturing for " + pcapNetworkInterfaceName + " cannot be started.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (packetCapturingService != null) {
|
||||
packetCapturingService.stopCapturing();
|
||||
packetCapturingService = null;
|
||||
}
|
||||
PcapNetworkInterfaceService.instance().unregisterListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPcapNetworkInterfaceAdded(PcapNetworkInterfaceWrapper newNetworkInterface) {
|
||||
if (packetCapturingService != null) {
|
||||
final PcapNetworkInterfaceWrapper trackedPcapNetworkInterface = packetCapturingService
|
||||
.getPcapNetworkInterface();
|
||||
if (trackedPcapNetworkInterface.equals(newNetworkInterface)) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPcapNetworkInterfaceRemoved(PcapNetworkInterfaceWrapper removedNetworkInterface) {
|
||||
if (packetCapturingService != null) {
|
||||
final PcapNetworkInterfaceWrapper trackedPcapNetworkInterface = packetCapturingService
|
||||
.getPcapNetworkInterface();
|
||||
if (trackedPcapNetworkInterface.equals(removedNetworkInterface)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
|
||||
"The networkinterface " + removedNetworkInterface.getName() + " is not present anymore.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazondashbutton.internal.pcap;
|
||||
|
||||
import org.pcap4j.core.PcapNetworkInterface;
|
||||
|
||||
/**
|
||||
* The {@link PcapNetworkInterfaceListener} is notified whenever a {@link PcapNetworkInterface} is added or removed. A
|
||||
* {@link PcapNetworkInterfaceListener} can be registered by calling
|
||||
* {@link PcapNetworkInterfaceService#registerListener(PcapNetworkInterfaceListener)} and can be unregistered by calling
|
||||
* {@link PcapNetworkInterfaceService#unregisterListener(PcapNetworkInterfaceListener)}.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*
|
||||
*/
|
||||
public interface PcapNetworkInterfaceListener {
|
||||
|
||||
/**
|
||||
* This method is called whenever a new {@link PcapNetworkInterfaceWrapper} is added.
|
||||
*
|
||||
* @param newNetworkInterface The added networkInterface
|
||||
*/
|
||||
public void onPcapNetworkInterfaceAdded(PcapNetworkInterfaceWrapper newNetworkInterface);
|
||||
|
||||
/**
|
||||
* This method is called whenever a {@link PcapNetworkInterfaceWrapper} is removed.
|
||||
*
|
||||
* @param removedNetworkInterface The removed networkInterface
|
||||
*/
|
||||
public void onPcapNetworkInterfaceRemoved(PcapNetworkInterfaceWrapper removedNetworkInterface);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 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.amazondashbutton.internal.pcap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.pcap4j.core.PcapNetworkInterface;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link PcapNetworkInterfaceService} is a singleton which can be obtained by calling {@link #instance()}.
|
||||
* It provides all available {@link PcapNetworkInterface}s which are bound to an address. These network interfaces can
|
||||
* be retrieved by calling {@link #getNetworkInterfaces()}.
|
||||
*
|
||||
* Moreover the {@link PcapNetworkInterfaceService} provided the possibility to register a
|
||||
* {@link PcapNetworkInterfaceListener}s which are notified on new and removed {@link PcapNetworkInterface}s.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class PcapNetworkInterfaceService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PcapNetworkInterfaceService.class);
|
||||
|
||||
private static PcapNetworkInterfaceService instance = null;
|
||||
private static final String THREADPOOL_NAME = "pcapNetworkInterfaceService";
|
||||
|
||||
private final Set<PcapNetworkInterfaceListener> listeners = new CopyOnWriteArraySet<>();
|
||||
private final Set<PcapNetworkInterfaceWrapper> pcapNetworkInterfaces = new CopyOnWriteArraySet<>();
|
||||
|
||||
private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(THREADPOOL_NAME);
|
||||
|
||||
private ScheduledFuture<?> future = null;
|
||||
|
||||
private final Runnable pollingRunnable = () -> {
|
||||
synchronized (pcapNetworkInterfaces) {
|
||||
final Set<PcapNetworkInterfaceWrapper> determinedNetworkInterfaces = determineBoundNetworkInterfaces();
|
||||
final Set<PcapNetworkInterfaceWrapper> currentNetworkInterfaces = new HashSet<>(pcapNetworkInterfaces);
|
||||
|
||||
final Set<PcapNetworkInterfaceWrapper> newNetworkInterfaces = new HashSet<>(determinedNetworkInterfaces);
|
||||
newNetworkInterfaces.removeIf(currentNetworkInterfaces::contains);
|
||||
|
||||
final Set<PcapNetworkInterfaceWrapper> removedNetworkInterfaces = new HashSet<>(currentNetworkInterfaces);
|
||||
removedNetworkInterfaces.removeIf(determinedNetworkInterfaces::contains);
|
||||
|
||||
pcapNetworkInterfaces.clear();
|
||||
pcapNetworkInterfaces.addAll(determinedNetworkInterfaces);
|
||||
|
||||
for (PcapNetworkInterfaceWrapper pcapNetworkInterface : newNetworkInterfaces) {
|
||||
notifyNetworkInterfacesAdded(pcapNetworkInterface);
|
||||
}
|
||||
|
||||
for (PcapNetworkInterfaceWrapper pcapNetworkInterface : removedNetworkInterfaces) {
|
||||
notifyNetworkInterfacesRemoved(pcapNetworkInterface);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private PcapNetworkInterfaceService() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PcapNetworkInterfaceService} singleton instance.
|
||||
*
|
||||
* @return The {@link PcapNetworkInterfaceService} singleton
|
||||
*/
|
||||
public static synchronized PcapNetworkInterfaceService instance() {
|
||||
if (instance == null) {
|
||||
instance = new PcapNetworkInterfaceService();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Set} of {@link PcapNetworkInterface}s which are bound to an address.
|
||||
*
|
||||
* @return the network interface set
|
||||
*/
|
||||
public Set<PcapNetworkInterfaceWrapper> getNetworkInterfaces() {
|
||||
synchronized (pcapNetworkInterfaces) {
|
||||
return Collections.unmodifiableSet(pcapNetworkInterfaces.stream().collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given {@link PcapNetworkInterfaceListener}. If it is already registered, this method returns
|
||||
* immediately.
|
||||
*
|
||||
* @param networkInterfaceListener The {@link PcapNetworkInterfaceListener} to be registered.
|
||||
*/
|
||||
public void registerListener(PcapNetworkInterfaceListener networkInterfaceListener) {
|
||||
final boolean isAdded = listeners.add(networkInterfaceListener);
|
||||
if (isAdded) {
|
||||
updatePollingState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given {@link PcapNetworkInterfaceListener}. If it is already unregistered, this method returns
|
||||
* immediately.
|
||||
*
|
||||
* @param networkInterfaceListener The {@link PcapNetworkInterfaceListener} to be unregistered.
|
||||
*/
|
||||
public void unregisterListener(PcapNetworkInterfaceListener networkInterfaceListener) {
|
||||
final boolean isRemoved = listeners.remove(networkInterfaceListener);
|
||||
if (isRemoved) {
|
||||
updatePollingState();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyNetworkInterfacesAdded(PcapNetworkInterfaceWrapper pcapNetworkInterface) {
|
||||
for (PcapNetworkInterfaceListener listener : listeners) {
|
||||
notifyNetworkInterfacesAdded(listener, pcapNetworkInterface);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyNetworkInterfacesRemoved(PcapNetworkInterfaceWrapper pcapNetworkInterface) {
|
||||
for (PcapNetworkInterfaceListener listener : listeners) {
|
||||
notifyNetworkInterfacesRemoved(listener, pcapNetworkInterface);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyNetworkInterfacesAdded(PcapNetworkInterfaceListener listener,
|
||||
PcapNetworkInterfaceWrapper pcapNetworkInterface) {
|
||||
try {
|
||||
listener.onPcapNetworkInterfaceAdded(pcapNetworkInterface);
|
||||
} catch (Exception e) {
|
||||
logger.error("An exception occurred while calling onPcapNetworkInterfaceAdded for {}", listener, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyNetworkInterfacesRemoved(PcapNetworkInterfaceListener listener,
|
||||
PcapNetworkInterfaceWrapper pcapNetworkInterface) {
|
||||
try {
|
||||
listener.onPcapNetworkInterfaceRemoved(pcapNetworkInterface);
|
||||
} catch (Exception e) {
|
||||
logger.error("An exception occurred while calling onPcapNetworkInterfaceRemoved for {}", listener, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all pcap network interfaces relying on {@link PcapUtil#getAllNetworkInterfaces()}. The list is filtered
|
||||
* as all interfaces which are not bound to an address are excluded.
|
||||
*
|
||||
* @return An {@link Iterable} of all {@link PcapNetworkInterfaceWrapper}s which are bound to an address
|
||||
*/
|
||||
private Set<PcapNetworkInterfaceWrapper> determineBoundNetworkInterfaces() {
|
||||
final Set<PcapNetworkInterfaceWrapper> allNetworkInterfaces = new HashSet<>();
|
||||
PcapUtil.getAllNetworkInterfaces().forEach(allNetworkInterfaces::add);
|
||||
allNetworkInterfaces.removeIf(networkInterface -> {
|
||||
final boolean notSuitable = networkInterface.getAddresses().isEmpty();
|
||||
if (notSuitable) {
|
||||
logger.debug("{} is not a suitable network interfaces as no addresses are bound to it.",
|
||||
networkInterface.getName());
|
||||
}
|
||||
return notSuitable;
|
||||
});
|
||||
return allNetworkInterfaces;
|
||||
}
|
||||
|
||||
private void updatePollingState() {
|
||||
boolean isPolling = future != null;
|
||||
if (isPolling && listeners.isEmpty()) {
|
||||
future.cancel(true);
|
||||
future = null;
|
||||
return;
|
||||
}
|
||||
if (!isPolling && !listeners.isEmpty()) {
|
||||
future = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, 2, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.amazondashbutton.internal.pcap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.pcap4j.core.PcapAddress;
|
||||
import org.pcap4j.core.PcapHandle;
|
||||
import org.pcap4j.core.PcapNativeException;
|
||||
import org.pcap4j.core.PcapNetworkInterface;
|
||||
import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode;
|
||||
|
||||
/**
|
||||
* This wrapper is needed as {@link PcapNetworkInterface#equals(Object)} and {@link PcapNetworkInterface#hashCode()} are
|
||||
* not implemented in an appropriate way. The wrapper delegates all methods calls except {@link #equals(Object)} and
|
||||
* {@link #hashCode()} to {@link #pcapNetworkInterface}.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class PcapNetworkInterfaceWrapper {
|
||||
|
||||
/**
|
||||
* The wrapped object
|
||||
*/
|
||||
private final PcapNetworkInterface pcapNetworkInterface;
|
||||
|
||||
/**
|
||||
* Use this Guava function in order to create a {@link PcapNetworkInterfaceWrapper} instance.
|
||||
*/
|
||||
public static final Function<PcapNetworkInterface, PcapNetworkInterfaceWrapper> TRANSFORMER = new Function<PcapNetworkInterface, PcapNetworkInterfaceWrapper>() {
|
||||
|
||||
@Override
|
||||
public PcapNetworkInterfaceWrapper apply(PcapNetworkInterface pcapNetworkInterface) {
|
||||
return new PcapNetworkInterfaceWrapper(pcapNetworkInterface);
|
||||
}
|
||||
};
|
||||
|
||||
private PcapNetworkInterfaceWrapper(PcapNetworkInterface pcapNetworkInterface) {
|
||||
if (pcapNetworkInterface == null) {
|
||||
throw new IllegalArgumentException("Don't pass null.");
|
||||
}
|
||||
this.pcapNetworkInterface = pcapNetworkInterface;
|
||||
}
|
||||
|
||||
public List<PcapAddress> getAddresses() {
|
||||
return pcapNetworkInterface.getAddresses();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return pcapNetworkInterface.getName();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return pcapNetworkInterface.getDescription();
|
||||
}
|
||||
|
||||
public PcapHandle openLive(int arg0, PromiscuousMode arg1, int arg2) throws PcapNativeException {
|
||||
return pcapNetworkInterface.openLive(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final PcapNetworkInterfaceWrapper other = (PcapNetworkInterfaceWrapper) obj;
|
||||
return Objects.equals(this.getName(), other.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(this.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pcapNetworkInterface.toString();
|
||||
}
|
||||
}
|
||||
@@ -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.amazondashbutton.internal.pcap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.pcap4j.core.PcapNativeException;
|
||||
import org.pcap4j.core.Pcaps;
|
||||
|
||||
/**
|
||||
*
|
||||
* A simple utitlity class which encapsulates {@link Pcaps} and which catches checked exceptions and transforms them
|
||||
* into {@link RuntimeException}s.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class PcapUtil {
|
||||
|
||||
/**
|
||||
* Returns all Pcap network interfaces relying on {@link Pcaps#findAllDevs()}.
|
||||
*
|
||||
* @return A {@link Set} of all {@link PcapNetworkInterfaceWrapper}s
|
||||
*/
|
||||
public static Set<PcapNetworkInterfaceWrapper> getAllNetworkInterfaces() {
|
||||
try {
|
||||
final Set<PcapNetworkInterfaceWrapper> allNetworkInterfaces = Collections.unmodifiableSet(Pcaps
|
||||
.findAllDevs().stream().map(PcapNetworkInterfaceWrapper.TRANSFORMER).collect(Collectors.toSet()));
|
||||
return allNetworkInterfaces;
|
||||
} catch (PcapNativeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Pcap network interface with the given name relying on {@link Pcaps#getDevByName(String)}. If no
|
||||
* interface is found, null is returned.
|
||||
*
|
||||
* @param name The name of the Pcap network interface
|
||||
* @return The network interface with the given name. Returns null, if no interface is found
|
||||
*/
|
||||
public static PcapNetworkInterfaceWrapper getNetworkInterfaceByName(String name) {
|
||||
try {
|
||||
return PcapNetworkInterfaceWrapper.TRANSFORMER.apply(Pcaps.getDevByName(name));
|
||||
} catch (PcapNativeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
@Requirement(namespace = ExtenderNamespace.EXTENDER_NAMESPACE, filter = "(osgi.extender=osgi.serviceloader.registrar)")
|
||||
@Requirement(namespace = ExtenderNamespace.EXTENDER_NAMESPACE, filter = "(&(osgi.extender=osgi.serviceloader.processor)(version>=1.0)(!(version>=2.0)))")
|
||||
@Requirement(namespace = "osgi.serviceloader", filter = "(osgi.serviceloader=org.pcap4j.packet.factory.PacketFactoryBinderProvider)", cardinality = Cardinality.MULTIPLE)
|
||||
@Capability(namespace = "osgi.serviceloader", name = "org.pcap4j.packet.factory.PacketFactoryBinderProvider")
|
||||
package org.openhab.binding.amazondashbutton;
|
||||
|
||||
import org.osgi.annotation.bundle.Capability;
|
||||
import org.osgi.annotation.bundle.Requirement;
|
||||
import org.osgi.annotation.bundle.Requirement.Cardinality;
|
||||
import org.osgi.namespace.extender.ExtenderNamespace;
|
||||
/**
|
||||
* Additional information for AmazonDashButton package
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*
|
||||
*/
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="amazondashbutton" 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>Amazon Dash Button Binding</name>
|
||||
<description>@text/bindingDescription</description>
|
||||
<author>Oliver Libutzki</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,15 @@
|
||||
# binding
|
||||
bindingDescription = This is the binding for the Amazon Dash Button.
|
||||
|
||||
# thing types
|
||||
dashButtonLabel = Amazon Dash Button
|
||||
dashButtonDescription = This is the Amazon Dash Button
|
||||
dashButtonMacAddressLabel = MAC address
|
||||
dashButtonMacAddressDescription = The MAC address of the Amazon Dash Button
|
||||
dashButtonNetworkInterfaceLabel = Network interface
|
||||
dashButtonNetworkInterfaceDescription = The network interface which receives the packets of the Amazon Dash Button
|
||||
dashButtonPacketIntervalLabel = Packet processing interval (in ms)
|
||||
dashButtonPacketIntervalDescription = Often a single button press is recognized multiple times. You can specify how long any further detected button pressed should be ignored after one click is handled (in ms).
|
||||
|
||||
dashButtonPressChannelLabel = Amazon Dash Button press
|
||||
dashButtonPressChannelDescription = Channel for recognizing presses on the Amazon Dash Button
|
||||
@@ -0,0 +1,15 @@
|
||||
# binding
|
||||
bindingDescription = Dies ist das Binding für den Amazon Dash Button.
|
||||
|
||||
# thing types
|
||||
dashButtonLabel = Amazon Dash Button
|
||||
dashButtonDescription = Dies ist der Amazon Dash Button
|
||||
dashButtonMacAddressLabel = MAC-Adresse
|
||||
dashButtonMacAddressDescription = Die MAC-Adresse des Amazon Dash Buttons
|
||||
dashButtonNetworkInterfaceLabel = Netzwerkschnittstelle
|
||||
dashButtonNetworkInterfaceDescription = Die Netzwerkschnittstelle, über den das Paket des Amazon Dash Button empfangen wird
|
||||
dashButtonPacketIntervalLabel = Paketverarbeitungsintervall (in ms)
|
||||
dashButtonPacketIntervalDescription = Häufig führt eine einzelne Button-Betätigung dazu, dass diese mehrfach verarbeitet wird. Es kann angegeben werden, für welchen Zeitraum weitere Betätigungsevents ignoriert werden sollen, nachdem eine Betätigung verarbeitet wurde (in ms).
|
||||
|
||||
dashButtonPressChannelLabel = Eine Betätigung des Amazon Dash Button
|
||||
dashButtonPressChannelDescription = Channel um eine Betätigung des Amazon Dash Button festzustellen
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="amazondashbutton"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="dashbutton">
|
||||
<label>@text/dashButtonLabel</label>
|
||||
<description>@text/dashButtonDescription</description>
|
||||
|
||||
<channels>
|
||||
<channel id="press" typeId="system.rawbutton">
|
||||
<label>@text/dashButtonPressChannelLabel</label>
|
||||
<description>@text/dashButtonPressChannelLabel</description>
|
||||
</channel>
|
||||
</channels>
|
||||
<config-description>
|
||||
<parameter name="pcapNetworkInterfaceName" type="text">
|
||||
<label>@text/dashButtonNetworkInterfaceLabel</label>
|
||||
<description>@text/dashButtonNetworkInterfaceDescription</description>
|
||||
</parameter>
|
||||
<parameter name="macAddress" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
|
||||
<label>@text/dashButtonMacAddressLabel</label>
|
||||
<description>@text/dashButtonMacAddressDescription</description>
|
||||
</parameter>
|
||||
<parameter name="packetInterval" type="integer">
|
||||
<label>@text/dashButtonPacketIntervalLabel</label>
|
||||
<description>@text/dashButtonPacketIntervalDescription</description>
|
||||
<default>5000</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user