2 * Copyright (c) 2010-2023 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.playstation.internal;
15 import static org.openhab.binding.playstation.internal.PlayStationBindingConstants.*;
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;
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;
44 * The {@link PS3Handler} is responsible for handling commands, which are
45 * sent to one of the channels.
47 * @author Fredrik Ahlström - Initial contribution
50 public class PS3Handler extends BaseThingHandler {
52 private final Logger logger = LoggerFactory.getLogger(PS3Handler.class);
53 private static final int SOCKET_TIMEOUT_SECONDS = 2;
54 private boolean isDisposed = false;
56 private PS3Configuration config = new PS3Configuration();
58 private @Nullable ScheduledFuture<?> refreshTimer;
60 public PS3Handler(Thing thing) {
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)) {
76 public void initialize() {
77 config = getConfigAs(PS3Configuration.class);
80 updateStatus(ThingStatus.ONLINE);
85 public void dispose() {
87 final ScheduledFuture<?> timer = refreshTimer;
95 * Sets up a timer for querying the PS3 (using the scheduler) every 10 seconds.
97 private void setupRefreshTimer() {
98 final ScheduledFuture<?> timer = refreshTimer;
102 refreshTimer = scheduler.scheduleWithFixedDelay(this::updateAllChannels, 0, 10, TimeUnit.SECONDS);
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.
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);
123 logger.debug("PS3 read power, IOException: {}", e.getMessage());
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.");
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);
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);
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());
156 private void wakeMethod(DatagramPacket srchPacket, DatagramPacket receivePacket, DatagramPacket wakePacket,
158 try (DatagramSocket searchSocket = new DatagramSocket(); DatagramSocket wakeSocket = new DatagramSocket();) {
159 wakeSocket.setBroadcast(true);
160 searchSocket.setBroadcast(true);
161 searchSocket.setSoTimeout(1000);
163 searchSocket.send(srchPacket);
165 searchSocket.receive(receivePacket);
166 logger.debug("PS3 started?: '{}'", receivePacket);
168 } catch (SocketTimeoutException e) {
171 wakeSocket.send(wakePacket);
172 if (triesLeft <= 0 || isDisposed) {
173 logger.debug("PS3 not started!");
175 scheduler.execute(() -> wakeMethod(srchPacket, receivePacket, wakePacket, triesLeft - 1));
177 } catch (IOException e) {
178 logger.debug("No PS3 device found. Diagnostic: {}", e.getMessage());
182 private byte[] makeWOLMagicPacket(String macAddress) {
183 byte[] wolPacket = new byte[6 * 17];
184 if (macAddress.length() < 17) {
188 for (int i = 0; i < 6; i++) {
189 wolPacket[pos++] = -1; // 0xFF
191 byte[] macBytes = HexUtils.hexToBytes(macAddress, ":");
192 for (int j = 0; j < 16; j++) {
193 System.arraycopy(macBytes, 0, wolPacket, 6 + j * 6, 6);