]> git.basschouten.com Git - openhab-addons.git/blob
603c640ed1c7af981225def148f84af37081492d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.network.internal;
14
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.DatagramSocket;
18 import java.net.InetAddress;
19 import java.net.SocketException;
20 import java.net.UnknownHostException;
21 import java.util.Arrays;
22 import java.util.Objects;
23 import java.util.function.Consumer;
24 import java.util.stream.Stream;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.core.net.NetUtil;
29 import org.openhab.core.util.HexUtils;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * The {@link WakeOnLanPacketSender} broadcasts a magic packet to wake a device.
35  *
36  * @author Wouter Born - Initial contribution
37  */
38 @NonNullByDefault
39 public class WakeOnLanPacketSender {
40
41     private static final int WOL_UDP_PORT = 9;
42
43     // Wake-on-LAN magic packet constants
44     static final int PREFIX_BYTE_SIZE = 6;
45     static final int MAC_REPETITIONS = 16;
46     static final int MAC_BYTE_SIZE = 6;
47     static final int MAGIC_PACKET_BYTE_SIZE = PREFIX_BYTE_SIZE + MAC_REPETITIONS * MAC_BYTE_SIZE;
48     static final String[] MAC_SEPARATORS = new String[] { ":", "-" };
49
50     private final Logger logger = LoggerFactory.getLogger(WakeOnLanPacketSender.class);
51
52     private final String macAddress;
53     private byte @Nullable [] magicPacket;
54     private final Consumer<byte[]> magicPacketSender;
55
56     public WakeOnLanPacketSender(String macAddress) {
57         this.macAddress = macAddress;
58         this.magicPacketSender = this::broadcastMagicPacket;
59     }
60
61     /**
62      * Used for testing only.
63      */
64     WakeOnLanPacketSender(String macAddress, Consumer<byte[]> magicPacketSender) {
65         this.macAddress = macAddress;
66         this.magicPacketSender = magicPacketSender;
67     }
68
69     public void sendPacket() {
70         byte[] localMagicPacket = magicPacket;
71         if (localMagicPacket == null) {
72             localMagicPacket = createMagicPacket(createMacBytes(macAddress));
73             magicPacket = localMagicPacket;
74         }
75
76         magicPacketSender.accept(localMagicPacket);
77     }
78
79     private byte[] createMacBytes(String macAddress) {
80         String hexString = macAddress;
81         for (String macSeparator : MAC_SEPARATORS) {
82             hexString = hexString.replaceAll(macSeparator, "");
83         }
84         if (hexString.length() != 2 * MAC_BYTE_SIZE) {
85             throw new IllegalStateException("Invalid MAC address: " + macAddress);
86         }
87         return HexUtils.hexToBytes(hexString);
88     }
89
90     private byte[] createMagicPacket(byte[] macBytes) {
91         byte[] bytes = new byte[MAGIC_PACKET_BYTE_SIZE];
92         Arrays.fill(bytes, 0, PREFIX_BYTE_SIZE, (byte) 0xff);
93         for (int i = PREFIX_BYTE_SIZE; i < MAGIC_PACKET_BYTE_SIZE; i += MAC_BYTE_SIZE) {
94             System.arraycopy(macBytes, 0, bytes, i, macBytes.length);
95         }
96         return bytes;
97     }
98
99     private void broadcastMagicPacket(byte[] magicPacket) {
100         try (DatagramSocket socket = new DatagramSocket()) {
101             broadcastAddressStream().forEach(broadcastAddress -> {
102                 try {
103                     DatagramPacket packet = new DatagramPacket(magicPacket, MAGIC_PACKET_BYTE_SIZE, broadcastAddress,
104                             WOL_UDP_PORT);
105                     socket.send(packet);
106                     logger.debug("Wake-on-LAN packet sent (MAC address: {}, broadcast address: {})", macAddress,
107                             broadcastAddress.getHostAddress());
108                 } catch (IOException e) {
109                     logger.debug("Failed to send Wake-on-LAN packet (MAC address: {}, broadcast address: {})",
110                             macAddress, broadcastAddress.getHostAddress(), e);
111                 }
112             });
113             logger.info("Wake-on-LAN packets sent (MAC address: {})", macAddress);
114         } catch (SocketException e) {
115             logger.error("Failed to open Wake-on-LAN datagram socket", e);
116         }
117     }
118
119     private Stream<InetAddress> broadcastAddressStream() {
120         return NetUtil.getAllBroadcastAddresses().stream().map(address -> {
121             try {
122                 return InetAddress.getByName(address);
123             } catch (UnknownHostException e) {
124                 logger.debug("Failed to get broadcast address '{}' by name", address, e);
125                 return null;
126             }
127         }).filter(Objects::nonNull);
128     }
129 }