]> git.basschouten.com Git - openhab-addons.git/blob
3e41b2cf40b59d0a9a0f747583db50a3c8fbd66b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.playstation.internal;
14
15 import static org.openhab.binding.playstation.internal.PlayStationBindingConstants.*;
16
17 import java.io.IOException;
18 import java.net.DatagramPacket;
19 import java.net.DatagramSocket;
20 import java.net.InetAddress;
21 import java.net.InetSocketAddress;
22 import java.net.Socket;
23 import java.net.SocketTimeoutException;
24 import java.nio.channels.SocketChannel;
25 import java.nio.charset.StandardCharsets;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.core.library.types.OnOffType;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.binding.BaseThingHandler;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.util.HexUtils;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * The {@link PS3Handler} is responsible for handling commands, which are
45  * sent to one of the channels.
46  *
47  * @author Fredrik Ahlström - Initial contribution
48  */
49 @NonNullByDefault
50 public class PS3Handler extends BaseThingHandler {
51
52     private final Logger logger = LoggerFactory.getLogger(PS3Handler.class);
53     private static final int SOCKET_TIMEOUT_SECONDS = 2;
54     private boolean isDisposed = false;
55
56     private PS3Configuration config = new PS3Configuration();
57
58     private @Nullable ScheduledFuture<?> refreshTimer;
59
60     public PS3Handler(Thing thing) {
61         super(thing);
62     }
63
64     @Override
65     public void handleCommand(ChannelUID channelUID, Command command) {
66         if (!(command instanceof RefreshType)) {
67             if (CHANNEL_POWER.equals(channelUID.getId()) && command instanceof OnOffType) {
68                 if (command.equals(OnOffType.ON)) {
69                     turnOnPS3();
70                 }
71             }
72         }
73     }
74
75     @Override
76     public void initialize() {
77         config = getConfigAs(PS3Configuration.class);
78         isDisposed = false;
79
80         updateStatus(ThingStatus.ONLINE);
81         setupRefreshTimer();
82     }
83
84     @Override
85     public void dispose() {
86         isDisposed = true;
87         final ScheduledFuture<?> timer = refreshTimer;
88         if (timer != null) {
89             timer.cancel(false);
90             refreshTimer = null;
91         }
92     }
93
94     /**
95      * Sets up a timer for querying the PS3 (using the scheduler) every 10 seconds.
96      */
97     private void setupRefreshTimer() {
98         final ScheduledFuture<?> timer = refreshTimer;
99         if (timer != null) {
100             timer.cancel(false);
101         }
102         refreshTimer = scheduler.scheduleWithFixedDelay(this::updateAllChannels, 0, 10, TimeUnit.SECONDS);
103     }
104
105     /**
106      * This tries to connect to port 5223 on the PS3,
107      * if the connection times out the PS3 is OFF, if connection is refused the PS3 is ON.
108      */
109     private void updateAllChannels() {
110         try (SocketChannel channel = SocketChannel.open()) {
111             Socket socket = channel.socket();
112             socket.setSoTimeout(SOCKET_TIMEOUT_SECONDS * 1000);
113             channel.configureBlocking(true);
114             channel.connect(new InetSocketAddress(config.ipAddress, DEFAULT_PS3_WAKE_ON_LAN_PORT));
115         } catch (IOException e) {
116             String message = e.getMessage();
117             if (message.contains("refused")) {
118                 updateState(CHANNEL_POWER, OnOffType.ON);
119                 updateStatus(ThingStatus.ONLINE);
120             } else if (message.contains("timed out") || message.contains("is down")) {
121                 updateState(CHANNEL_POWER, OnOffType.OFF);
122             } else {
123                 logger.debug("PS3 read power, IOException: {}", e.getMessage());
124             }
125         }
126     }
127
128     private void turnOnPS3() {
129         String macAdr = thing.getProperties().get(Thing.PROPERTY_MAC_ADDRESS);
130         if (macAdr == null) {
131             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No MAC address configured.");
132             return;
133         }
134         try {
135             // send WOL magic packet
136             byte[] magicPacket = makeWOLMagicPacket(macAdr);
137             logger.debug("PS3 wol packet: {}", magicPacket);
138             InetAddress bcAddress = InetAddress.getByName("255.255.255.255");
139             DatagramPacket wakePacket = new DatagramPacket(magicPacket, magicPacket.length, bcAddress,
140                     DEFAULT_PS3_WAKE_ON_LAN_PORT);
141             // send discover
142             byte[] discover = "SRCH".getBytes(StandardCharsets.US_ASCII);
143             DatagramPacket srchPacket = new DatagramPacket(discover, discover.length, bcAddress,
144                     DEFAULT_PS3_WAKE_ON_LAN_PORT);
145             logger.debug("Search message: '{}'", discover);
146
147             // wait for responses
148             byte[] rxbuf = new byte[256];
149             DatagramPacket receivePacket = new DatagramPacket(rxbuf, rxbuf.length);
150             scheduler.execute(() -> wakeMethod(srchPacket, receivePacket, wakePacket, 34));
151         } catch (IOException e) {
152             logger.debug("No PS3 device found. Diagnostic: {}", e.getMessage());
153         }
154     }
155
156     private void wakeMethod(DatagramPacket srchPacket, DatagramPacket receivePacket, DatagramPacket wakePacket,
157             int triesLeft) {
158         try (DatagramSocket searchSocket = new DatagramSocket(); DatagramSocket wakeSocket = new DatagramSocket()) {
159             wakeSocket.setBroadcast(true);
160             searchSocket.setBroadcast(true);
161             searchSocket.setSoTimeout(1000);
162
163             searchSocket.send(srchPacket);
164             try {
165                 searchSocket.receive(receivePacket);
166                 logger.debug("PS3 started?: '{}'", receivePacket);
167                 return;
168             } catch (SocketTimeoutException e) {
169                 // try again
170             }
171             wakeSocket.send(wakePacket);
172             if (triesLeft <= 0 || isDisposed) {
173                 logger.debug("PS3 not started!");
174             } else {
175                 scheduler.execute(() -> wakeMethod(srchPacket, receivePacket, wakePacket, triesLeft - 1));
176             }
177         } catch (IOException e) {
178             logger.debug("No PS3 device found. Diagnostic: {}", e.getMessage());
179         }
180     }
181
182     private byte[] makeWOLMagicPacket(String macAddress) {
183         byte[] wolPacket = new byte[6 * 17];
184         if (macAddress.length() < 17) {
185             return wolPacket;
186         }
187         int pos = 0;
188         for (int i = 0; i < 6; i++) {
189             wolPacket[pos++] = -1; // 0xFF
190         }
191         byte[] macBytes = HexUtils.hexToBytes(macAddress, ":");
192         for (int j = 0; j < 16; j++) {
193             System.arraycopy(macBytes, 0, wolPacket, 6 + j * 6, 6);
194         }
195         return wolPacket;
196     }
197 }