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,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.amazondashbutton</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,20 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
== Third-party Content
pcap4J
* License: MIT License
* Project: https://www.pcap4j.org
* Source: https://github.com/kaitoy/pcap4j

View File

@@ -0,0 +1,156 @@
# Amazon Dash Button Binding
The [Amazon Dash Button](https://www.amazon.com/Dash-Buttons/b?node=10667898011) is a cheap and small Wi-Fi connected device to order products from Amazon with the simple press of a button.
This Binding allows you to integrate Dash Buttons into your home automation setup.
The Binding code is inspired by [hortinstein/node-dash-button](https://github.com/hortinstein/node-dash-button).
**Warning:**
The Dash Button will try to contact the Amazon servers every time the button is pressed.
This might not be in line with your privacy preferences but can be prevented.
Please refer to the ["Preventing Communication with Amazon Servers"](#no-phonehome) section for details.
**Response Time:**
Please be aware, that due to the operation method of this binding, the response time for a button press can be rather high (up to five seconds).
You might want to keep that in mind during product selection or task assignment.
## Prerequisites
The Binding uses [Pcap4J](https://www.pcap4j.org/) in order to capture `ARP` and `BOOTP` requests send by the Amazon Dash Button.
Buttons will hence only be usable within the same network as your openHAB instance.
Start with installing libpcap (for Mac/Linux/Unix) or WinPcap (for Windows) on your computer.
They are native libraries that power the core functionalities of Pcap4J.
**Note:**
Pcap4J needs administrator/root privileges.
Instructions for Debian/Ubuntu are given below.
### Installing libpcap on Debian/Ubuntu
Installing [libpcap](https://www.tcpdump.org/) should be as simple as:
```shell
sudo apt-get install libpcap-dev
```
You can run Pcap4J with a non-root openHAB user by granting capabilities `CAP_NET_RAW` and `CAP_NET_ADMIN` to the openHAB java environment by the following command:
```shell
sudo setcap cap_net_raw,cap_net_admin=eip $(realpath /usr/bin/java)
```
Be aware of other capabilities which were previously set by setcap.
**These capabilities will be overwritten!**
You can see which capabilities have already been set with the command:
```shell
sudo getcap $(realpath /usr/bin/java)
```
If you need multiple capabilities (like "cap_net_bind_service" for the Network binding), you have to add them like this:
```shell
sudo setcap 'cap_net_raw,cap_net_admin=+eip cap_net_bind_service=+ep' $(realpath /usr/bin/java)
```
You need to restart openHAB for the capabilities change to take effect.
### Installing WinPcap on Windows
On a Windows system there are two options to go with.
1. The preferred solution is [WinPcap](https://www.winpcap.org) if your network interface is supported.
2. An alternative option is [npcap](https://github.com/nmap/npcap) with the settings "WinPcap 4.1.3 compatibility" and "Raw 802.11 Packet Capture"
### Installing libpcap on Other Operating Systems
The installation methods might differ.
A few known operating systems are:
| Operating System | Command |
|:-----------------|:----------------------------|
| CentOS | `yum install libpcap-devel` |
| Mac | `brew install libpcap` |
## Setup Dash Button
Setting up your Dash Button is as simple as following the instructions provided by [Amazon](https://www.amazon.com/Dash-Buttons/b?node=10667898011) **EXCEPT FOR THE LAST STEP**.
Follow the instructions to set up the Dash Button in their mobile app.
When you get to the step where it asks you to pick which product you want to map it to, just quit the setup process.
{: #no-phonehome}
## Preventing Communication with Amazon Servers
Every time a Dash Button is pressed a request will be sent to the Amazon servers.
If no product was configured for the Button, a notification will be presented by the Amazon app on your smartphone.
To prevent the Dash Button from contacting the Amazon Servers, block Internet access for the device.
Please refer to the documentation of your network's router for details.
If your network doesn't provide that option, you can at least deal with the notifications by either uninstalling the Amazon app or disabling notifications for it (possible on most smartphone OSs).
It has shown that blocking the Dash Button communication with the Amazon servers will provoke reconnection attempts.
This increased amount of communication causes a reduced overall battery life.
The built-in AAA battery can be easily replaced.
Preventing the communication with the Amazon servers or the Amazon app is **not** necessary to integrate the Dash Button in openHAB.
## Supported Things
There is one supported Thing, the "Amazon Dash Button".
## Discovery
Background discovery is not supported as it is not possible to distinguish
between a Dash Button and other Amazon devices like the Kindle,
a Fire TV or an Echo speaker.
You can start the discovery process for Dash Button devices manually.
While openHAB is in the scanning process, press the button on the Dash to be recognized and added to your Inbox.
**Caution:**
You have to be aware that other Amazon devices might pop up in your Inbox if they send an `ARP` request while scanning for Dash Buttons.
You can ignore these devices in your Inbox.
## Thing Configuration
### Amazon Dash Button
- `macAddress` - The MAC address of the Amazon Dash Button.
- `pcapNetworkInterfaceName` - The network interface which receives the packets of the Amazon Dash Button.
- `packetInterval` - 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 was processed.
The parameter is optional and 5000ms by default.
For manual definition of a `dashbutton` Thing the MAC address can either be taken from the discovery output or can e.g. be captured through your router/DHCP frontend or with [Wireshark](https://wireshark.org).
## Channels
- **press:** Trigger channel for recognizing presses on the Amazon Dash Button.
A trigger channel can directly be used in a rule, check the "Full Example" section for one example.
Dispatches a `PRESSED` event when a button is pressed.
The trigger channel `press` is of type `system.rawbutton` to allow the usage of the `rawbutton-toggle-switch` profile.
## Full Example
Things:
```java
Thing amazondashbutton:dashbutton:fc-a6-67-0c-aa-c7 "My Dash Button" @ "Living" [ macAddress="fc:a6:67:0c:aa:c7", pcapNetworkInterfaceName="eth0", packetInterval=5000 ]
```
(Pay attention: The MAC address has to be given in two different formats)
Rules:
```java
rule "My Dash Button pressed"
when
Channel "amazondashbutton:dashbutton:fc-a6-67-0c-aa-c7:press" triggered
then
logInfo("amazondashbutton", "My Dash Button has been pressed")
end
```

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.amazondashbutton</artifactId>
<name>openHAB Add-ons :: Bundles :: Amazon Dash Button Binding</name>
<properties>
<bnd.importpackage>!org.slf4j.impl.*</bnd.importpackage>
<dep.noembedding>jna</dep.noembedding>
</properties>
<dependencies>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.4.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-core</artifactId>
<version>1.8.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-packetfactory-static</artifactId>
<version>1.8.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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>

View File

@@ -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";
}

View File

@@ -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;
}
}

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.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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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.");
}
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

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.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);
}
}
}

View File

@@ -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
*
*/

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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>