2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.network.internal;
15 import java.io.IOException;
17 import java.util.Arrays;
18 import java.util.Objects;
19 import java.util.function.Consumer;
20 import java.util.stream.Stream;
22 import org.apache.commons.lang3.StringUtils;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.core.net.NetUtil;
26 import org.openhab.core.util.HexUtils;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * The {@link WakeOnLanPacketSender} broadcasts a magic packet to wake a device.
33 * @author Wouter Born - Initial contribution
36 public class WakeOnLanPacketSender {
38 private static final int WOL_UDP_PORT = 9;
40 // Wake-on-LAN magic packet constants
41 static final int PREFIX_BYTE_SIZE = 6;
42 static final int MAC_REPETITIONS = 16;
43 static final int MAC_BYTE_SIZE = 6;
44 static final int MAGIC_PACKET_BYTE_SIZE = PREFIX_BYTE_SIZE + MAC_REPETITIONS * MAC_BYTE_SIZE;
45 static final String[] MAC_SEPARATORS = new String[] { ":", "-" };
47 private final Logger logger = LoggerFactory.getLogger(WakeOnLanPacketSender.class);
49 private final String macAddress;
52 private final String hostname;
55 private final Integer port;
57 private byte @Nullable [] magicPacket;
58 private final Consumer<byte[]> magicPacketSender;
60 public WakeOnLanPacketSender(String macAddress, @Nullable String hostname, @Nullable Integer port) {
61 this.macAddress = macAddress;
62 this.hostname = hostname;
64 this.magicPacketSender = this::sendMagicPacket;
67 public WakeOnLanPacketSender(String macAddress) {
68 this.macAddress = macAddress;
71 this.magicPacketSender = this::sendMagicPacket;
75 * Used for testing only.
77 WakeOnLanPacketSender(String macAddress, Consumer<byte[]> magicPacketSender) {
78 this.macAddress = macAddress;
81 this.magicPacketSender = magicPacketSender;
84 public void sendPacket() {
85 byte[] localMagicPacket = magicPacket;
86 if (localMagicPacket == null) {
87 localMagicPacket = createMagicPacket(createMacBytes(macAddress));
88 magicPacket = localMagicPacket;
91 magicPacketSender.accept(localMagicPacket);
94 private byte[] createMacBytes(String macAddress) {
95 String hexString = macAddress;
96 for (String macSeparator : MAC_SEPARATORS) {
97 hexString = hexString.replaceAll(macSeparator, "");
99 if (hexString.length() != 2 * MAC_BYTE_SIZE) {
100 throw new IllegalStateException("Invalid MAC address: " + macAddress);
102 return HexUtils.hexToBytes(hexString);
105 private byte[] createMagicPacket(byte[] macBytes) {
106 byte[] bytes = new byte[MAGIC_PACKET_BYTE_SIZE];
107 Arrays.fill(bytes, 0, PREFIX_BYTE_SIZE, (byte) 0xff);
108 for (int i = PREFIX_BYTE_SIZE; i < MAGIC_PACKET_BYTE_SIZE; i += MAC_BYTE_SIZE) {
109 System.arraycopy(macBytes, 0, bytes, i, macBytes.length);
114 private void sendMagicPacket(byte[] magicPacket) {
115 try (DatagramSocket socket = new DatagramSocket()) {
116 if (StringUtils.isEmpty(hostname)) {
117 broadcastMagicPacket(magicPacket, socket);
119 SocketAddress socketAddress = new InetSocketAddress(this.hostname,
120 Objects.requireNonNullElse(this.port, WOL_UDP_PORT));
121 sendMagicPacketToIp(magicPacket, socket, socketAddress);
123 } catch (SocketException e) {
124 logger.error("Failed to open Wake-on-LAN datagram socket", e);
128 private void broadcastMagicPacket(byte[] magicPacket, DatagramSocket socket) {
129 broadcastAddressStream().forEach(broadcastAddress -> {
131 DatagramPacket packet = new DatagramPacket(magicPacket, MAGIC_PACKET_BYTE_SIZE, broadcastAddress,
134 logger.debug("Wake-on-LAN packet sent (MAC address: {}, broadcast address: {})", macAddress,
135 broadcastAddress.getHostAddress());
136 } catch (IOException e) {
137 logger.debug("Failed to send Wake-on-LAN packet (MAC address: {}, broadcast address: {})", macAddress,
138 broadcastAddress.getHostAddress(), e);
141 logger.info("Wake-on-LAN packets sent (MAC address: {})", macAddress);
144 private void sendMagicPacketToIp(byte[] magicPacket, DatagramSocket socket, SocketAddress ip) {
145 DatagramPacket packet = new DatagramPacket(magicPacket, MAGIC_PACKET_BYTE_SIZE, ip);
148 } catch (IOException e) {
149 logger.debug("Failed to send Wake-on-LAN packet (MAC address: {}, address: {})", macAddress, ip, e);
151 logger.info("Wake-on-LAN packets sent (MAC address: {}, IP address: {})", macAddress, ip);
154 private Stream<InetAddress> broadcastAddressStream() {
155 return NetUtil.getAllBroadcastAddresses().stream().map(address -> {
157 return InetAddress.getByName(address);
158 } catch (UnknownHostException e) {
159 logger.debug("Failed to get broadcast address '{}' by name", address, e);
162 }).filter(Objects::nonNull);