forked from openhab/openhab-addons
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[network] Add support for Wake-on-LAN thing action (openhab#8336)
Closes openhab#3799 Signed-off-by: Wouter Born <[email protected]>
- Loading branch information
Showing
8 changed files
with
406 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
...ing.network/src/main/java/org/openhab/binding/network/internal/WakeOnLanPacketSender.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/** | ||
* Copyright (c) 2010-2020 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.binding.network.internal; | ||
|
||
import java.io.IOException; | ||
import java.net.DatagramPacket; | ||
import java.net.DatagramSocket; | ||
import java.net.InetAddress; | ||
import java.net.SocketException; | ||
import java.net.UnknownHostException; | ||
import java.util.Arrays; | ||
import java.util.Objects; | ||
import java.util.function.Consumer; | ||
import java.util.stream.Stream; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.eclipse.smarthome.core.net.NetUtil; | ||
import org.eclipse.smarthome.core.util.HexUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* The {@link WakeOnLanPacketSender} broadcasts a magic packet to wake a device. | ||
* | ||
* @author Wouter Born - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public class WakeOnLanPacketSender { | ||
|
||
private static final int WOL_UDP_PORT = 9; | ||
|
||
// Wake-on-LAN magic packet constants | ||
static final int PREFIX_BYTE_SIZE = 6; | ||
static final int MAC_REPETITIONS = 16; | ||
static final int MAC_BYTE_SIZE = 6; | ||
static final int MAGIC_PACKET_BYTE_SIZE = PREFIX_BYTE_SIZE + MAC_REPETITIONS * MAC_BYTE_SIZE; | ||
static final String[] MAC_SEPARATORS = new String[] { ":", "-" }; | ||
|
||
private final Logger logger = LoggerFactory.getLogger(WakeOnLanPacketSender.class); | ||
|
||
private final String macAddress; | ||
private byte @Nullable [] magicPacket; | ||
private final Consumer<byte[]> magicPacketSender; | ||
|
||
public WakeOnLanPacketSender(String macAddress) { | ||
this.macAddress = macAddress; | ||
this.magicPacketSender = this::broadcastMagicPacket; | ||
} | ||
|
||
/** | ||
* Used for testing only. | ||
*/ | ||
WakeOnLanPacketSender(String macAddress, Consumer<byte[]> magicPacketSender) { | ||
this.macAddress = macAddress; | ||
this.magicPacketSender = magicPacketSender; | ||
} | ||
|
||
public void sendPacket() { | ||
byte[] localMagicPacket = magicPacket; | ||
if (localMagicPacket == null) { | ||
localMagicPacket = createMagicPacket(createMacBytes(macAddress)); | ||
magicPacket = localMagicPacket; | ||
} | ||
|
||
magicPacketSender.accept(localMagicPacket); | ||
} | ||
|
||
private byte[] createMacBytes(String macAddress) { | ||
String hexString = macAddress; | ||
for (String macSeparator : MAC_SEPARATORS) { | ||
hexString = hexString.replaceAll(macSeparator, ""); | ||
} | ||
if (hexString.length() != 2 * MAC_BYTE_SIZE) { | ||
throw new IllegalStateException("Invalid MAC address: " + macAddress); | ||
} | ||
return HexUtils.hexToBytes(hexString); | ||
} | ||
|
||
private byte[] createMagicPacket(byte[] macBytes) { | ||
byte[] bytes = new byte[MAGIC_PACKET_BYTE_SIZE]; | ||
Arrays.fill(bytes, 0, PREFIX_BYTE_SIZE, (byte) 0xff); | ||
for (int i = PREFIX_BYTE_SIZE; i < MAGIC_PACKET_BYTE_SIZE; i += MAC_BYTE_SIZE) { | ||
System.arraycopy(macBytes, 0, bytes, i, macBytes.length); | ||
} | ||
return bytes; | ||
} | ||
|
||
private void broadcastMagicPacket(byte[] magicPacket) { | ||
try (DatagramSocket socket = new DatagramSocket()) { | ||
broadcastAddressStream().forEach(broadcastAddress -> { | ||
try { | ||
DatagramPacket packet = new DatagramPacket(magicPacket, MAGIC_PACKET_BYTE_SIZE, broadcastAddress, | ||
WOL_UDP_PORT); | ||
socket.send(packet); | ||
logger.debug("Wake-on-LAN packet sent (MAC address: {}, broadcast address: {})", macAddress, | ||
broadcastAddress.getHostAddress()); | ||
} catch (IOException e) { | ||
logger.debug("Failed to send Wake-on-LAN packet (MAC address: {}, broadcast address: {})", | ||
macAddress, broadcastAddress.getHostAddress(), e); | ||
} | ||
}); | ||
logger.info("Wake-on-LAN packets sent (MAC address: {})", macAddress); | ||
} catch (SocketException e) { | ||
logger.error("Failed to open Wake-on-LAN datagram socket", e); | ||
} | ||
} | ||
|
||
private Stream<InetAddress> broadcastAddressStream() { | ||
return NetUtil.getAllBroadcastAddresses().stream().map(address -> { | ||
try { | ||
return InetAddress.getByName(address); | ||
} catch (UnknownHostException e) { | ||
logger.debug("Failed to get broadcast address '{}' by name", address, e); | ||
return null; | ||
} | ||
}).filter(Objects::nonNull); | ||
} | ||
|
||
} |
26 changes: 26 additions & 0 deletions
26
...ng.network/src/main/java/org/openhab/binding/network/internal/action/INetworkActions.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/** | ||
* Copyright (c) 2010-2020 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.binding.network.internal.action; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
|
||
/** | ||
* The {@link INetworkActions} defines the interface for all thing actions supported by the binding. | ||
* | ||
* @author Wouter Born - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public interface INetworkActions { | ||
|
||
void sendWakeOnLanPacket(); | ||
} |
92 changes: 92 additions & 0 deletions
92
...ing.network/src/main/java/org/openhab/binding/network/internal/action/NetworkActions.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/** | ||
* Copyright (c) 2010-2020 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.binding.network.internal.action; | ||
|
||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Proxy; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.eclipse.smarthome.core.thing.binding.ThingActions; | ||
import org.eclipse.smarthome.core.thing.binding.ThingActionsScope; | ||
import org.eclipse.smarthome.core.thing.binding.ThingHandler; | ||
import org.openhab.binding.network.internal.handler.NetworkHandler; | ||
import org.openhab.core.automation.annotation.RuleAction; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* The class is responsible to call corresponding actions on {@link NetworkHandler}. | ||
* <p> | ||
* <b>Note:</b>The static method <b>invokeMethodOf</b> handles the case where | ||
* the test <i>actions instanceof NetworkActions</i> fails. This test can fail | ||
* due to an issue in openHAB core v2.5.0 where the {@link NetworkActions} class | ||
* can be loaded by a different classloader than the <i>actions</i> instance. | ||
* | ||
* @author Wouter Born - Initial contribution | ||
*/ | ||
@ThingActionsScope(name = "network") | ||
@NonNullByDefault | ||
public class NetworkActions implements ThingActions, INetworkActions { | ||
|
||
private final Logger logger = LoggerFactory.getLogger(NetworkActions.class); | ||
|
||
private @Nullable NetworkHandler handler; | ||
|
||
@Override | ||
public void setThingHandler(@Nullable ThingHandler handler) { | ||
if (handler instanceof NetworkHandler) { | ||
this.handler = (NetworkHandler) handler; | ||
} | ||
} | ||
|
||
@Override | ||
public @Nullable ThingHandler getThingHandler() { | ||
return handler; | ||
} | ||
|
||
@Override | ||
@RuleAction(label = "Send WoL Packet", description = "Send a Wake-on-LAN packet to wake the device") | ||
public void sendWakeOnLanPacket() { | ||
NetworkHandler localHandler = handler; | ||
if (localHandler != null) { | ||
localHandler.sendWakeOnLanPacket(); | ||
} else { | ||
logger.warn("Failed to send Wake-on-LAN packet (handler null)"); | ||
} | ||
} | ||
|
||
public static void sendWakeOnLanPacket(@Nullable ThingActions actions) { | ||
invokeMethodOf(actions).sendWakeOnLanPacket(); | ||
} | ||
|
||
private static INetworkActions invokeMethodOf(@Nullable ThingActions actions) { | ||
if (actions == null) { | ||
throw new IllegalArgumentException("actions cannot be null"); | ||
} | ||
if (actions.getClass().getName().equals(NetworkActions.class.getName())) { | ||
if (actions instanceof INetworkActions) { | ||
return (INetworkActions) actions; | ||
} else { | ||
return (INetworkActions) Proxy.newProxyInstance(INetworkActions.class.getClassLoader(), | ||
new Class[] { INetworkActions.class }, (Object proxy, Method method, Object[] args) -> { | ||
Method m = actions.getClass().getDeclaredMethod(method.getName(), | ||
method.getParameterTypes()); | ||
return m.invoke(actions, args); | ||
}); | ||
} | ||
} | ||
throw new IllegalArgumentException("Actions is not an instance of " + NetworkActions.class.getName()); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.