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.UnknownHostException;
23 import java.nio.ByteBuffer;
24 import java.nio.ByteOrder;
25 import java.nio.channels.SocketChannel;
26 import java.nio.charset.StandardCharsets;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Locale;
32 import java.util.concurrent.ScheduledFuture;
33 import java.util.concurrent.TimeUnit;
35 import org.eclipse.jdt.annotation.NonNullByDefault;
36 import org.eclipse.jdt.annotation.Nullable;
37 import org.openhab.binding.playstation.internal.discovery.PlayStationDiscovery;
38 import org.openhab.core.config.core.Configuration;
39 import org.openhab.core.i18n.LocaleProvider;
40 import org.openhab.core.library.types.OnOffType;
41 import org.openhab.core.library.types.RawType;
42 import org.openhab.core.library.types.StringType;
43 import org.openhab.core.net.NetworkAddressService;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.thing.binding.BaseThingHandler;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.RefreshType;
51 import org.openhab.core.types.State;
52 import org.openhab.core.types.UnDefType;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
57 * The {@link PS4Handler} is responsible for handling commands, which are
58 * sent to one of the channels.
60 * @author Fredrik Ahlström - Initial contribution
63 public class PS4Handler extends BaseThingHandler {
65 private final Logger logger = LoggerFactory.getLogger(PS4Handler.class);
66 private final PS4Crypto ps4Crypto = new PS4Crypto();
67 private static final int SOCKET_TIMEOUT_SECONDS = 5;
68 /** Time after connect that we can start to send key events, milli seconds */
69 private static final int POST_CONNECT_SENDKEY_DELAY_MS = 500;
70 /** Minimum delay between sendKey sends, milli seconds */
71 private static final int MIN_SENDKEY_DELAY_MS = 210;
72 /** Minimum delay after Key set, milli seconds */
73 private static final int MIN_HOLDKEY_DELAY_MS = 300;
75 private PS4Configuration config = new PS4Configuration();
77 private final @Nullable LocaleProvider localeProvider;
78 private final @Nullable NetworkAddressService networkAS;
79 private List<ScheduledFuture<?>> scheduledFutures = Collections.synchronizedList(new ArrayList<>());
80 private @Nullable ScheduledFuture<?> refreshTimer;
81 private @Nullable ScheduledFuture<?> timeoutTimer;
82 private @Nullable SocketChannelHandler socketChannelHandler;
83 private @Nullable InetAddress localAddress;
86 private String currentApplication = "";
87 private String currentApplicationId = "";
88 private OnOffType currentPower = OnOffType.OFF;
89 private State currentArtwork = UnDefType.UNDEF;
90 private int currentComPort = DEFAULT_COMMUNICATION_PORT;
92 boolean loggedIn = false;
93 boolean oskOpen = false;
95 public PS4Handler(Thing thing, LocaleProvider locProvider, NetworkAddressService network) {
97 localeProvider = locProvider;
102 public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
103 super.handleConfigurationUpdate(configurationParameters);
105 SocketChannelHandler scHandler = socketChannelHandler;
106 if (!config.pairingCode.isEmpty() && (scHandler == null || !loggedIn)) {
107 // Try to log in then remove pairing code as it's one use only.
108 scheduler.execute(() -> {
110 Configuration editedConfig = editConfiguration();
111 editedConfig.put(PAIRING_CODE, "");
112 updateConfiguration(editedConfig);
115 setupConnectionTimeout(config.connectionTimeout);
119 public void handleCommand(ChannelUID channelUID, Command command) {
120 if (command instanceof RefreshType) {
121 refreshFromState(channelUID);
123 if (command instanceof StringType stringCommand) {
124 switch (channelUID.getId()) {
125 case CHANNEL_APPLICATION_ID:
126 if (!currentApplicationId.equals(stringCommand.toString())) {
127 updateApplicationTitleid(stringCommand.toString());
128 startApplication(currentApplicationId);
131 case CHANNEL_OSK_TEXT:
132 setOSKText(stringCommand.toString());
134 case CHANNEL_SEND_KEY:
136 switch (stringCommand.toString()) {
141 ps4Key = PS4_KEY_DOWN;
144 ps4Key = PS4_KEY_RIGHT;
147 ps4Key = PS4_KEY_LEFT;
150 ps4Key = PS4_KEY_ENTER;
153 ps4Key = PS4_KEY_BACK;
155 case SEND_KEY_OPTION:
156 ps4Key = PS4_KEY_OPTION;
165 sendRemoteKey(ps4Key);
171 } else if (command instanceof OnOffType onOffCommand) {
172 switch (channelUID.getId()) {
174 if (currentPower != onOffCommand) {
175 currentPower = onOffCommand;
176 if (currentPower.equals(OnOffType.ON)) {
178 } else if (currentPower.equals(OnOffType.OFF)) {
183 case CHANNEL_CONNECT:
184 boolean connected = socketChannelHandler != null && socketChannelHandler.isChannelOpen();
185 if (connected && onOffCommand.equals(OnOffType.OFF)) {
187 } else if (!connected && onOffCommand.equals(OnOffType.ON)) {
188 scheduler.execute(() -> login());
199 public void initialize() {
200 config = getConfigAs(PS4Configuration.class);
203 updateStatus(ThingStatus.UNKNOWN);
208 public void dispose() {
210 ScheduledFuture<?> timer = refreshTimer;
215 timer = timeoutTimer;
220 scheduledFutures.forEach(f -> f.cancel(false));
221 scheduledFutures.clear();
225 * Tries to figure out a local IP that can communicate with the PS4.
227 private void figureOutLocalIP() {
228 if (!config.outboundIP.trim().isEmpty()) {
230 localAddress = InetAddress.getByName(config.outboundIP);
231 logger.debug("Outbound local IP.\"{}\"", localAddress);
233 } catch (UnknownHostException e) {
237 NetworkAddressService network = networkAS;
238 String adr = (network != null) ? network.getPrimaryIpv4HostAddress() : null;
241 localAddress = InetAddress.getByName(adr);
242 } catch (UnknownHostException e) {
243 // Ignore, just let the socket use whatever.
249 * Sets up a timer for querying the PS4 (using the scheduler) every 10 seconds.
251 private void setupRefreshTimer() {
252 final ScheduledFuture<?> timer = refreshTimer;
256 refreshTimer = scheduler.scheduleWithFixedDelay(this::updateAllChannels, 0, 10, TimeUnit.SECONDS);
260 * Sets up a timer for stopping the connection to the PS4 (using the scheduler) with the given time.
262 * @param waitTime The time in seconds before the connection is stopped.
264 private void setupConnectionTimeout(int waitTime) {
265 final ScheduledFuture<?> timer = timeoutTimer;
270 timeoutTimer = scheduler.schedule(this::stopConnection, waitTime, TimeUnit.SECONDS);
274 private void refreshFromState(ChannelUID channelUID) {
275 switch (channelUID.getId()) {
277 updateState(channelUID, currentPower);
279 case CHANNEL_APPLICATION_NAME:
280 updateState(channelUID, StringType.valueOf(currentApplication));
282 case CHANNEL_APPLICATION_ID:
283 updateState(channelUID, StringType.valueOf(currentApplicationId));
285 case CHANNEL_APPLICATION_IMAGE:
286 updateApplicationTitleid(currentApplicationId);
287 updateState(channelUID, currentArtwork);
289 case CHANNEL_OSK_TEXT:
290 case CHANNEL_2ND_SCREEN:
291 updateState(channelUID, UnDefType.UNDEF);
293 case CHANNEL_CONNECT:
294 boolean connected = socketChannelHandler != null && socketChannelHandler.isChannelOpen();
295 updateState(channelUID, OnOffType.from(connected));
297 case CHANNEL_SEND_KEY:
300 logger.warn("Channel refresh for {} not implemented!", channelUID.getId());
304 private void updateAllChannels() {
305 try (DatagramSocket socket = new DatagramSocket(0, localAddress)) {
306 socket.setBroadcast(false);
307 socket.setSoTimeout(SOCKET_TIMEOUT_SECONDS * 1000);
308 InetAddress inetAddress = InetAddress.getByName(config.ipAddress);
311 byte[] discover = PS4PacketHandler.makeSearchPacket();
312 DatagramPacket packet = new DatagramPacket(discover, discover.length, inetAddress, DEFAULT_BROADCAST_PORT);
316 byte[] rxbuf = new byte[256];
317 packet = new DatagramPacket(rxbuf, rxbuf.length);
318 socket.receive(packet);
319 parseSearchResponse(packet);
320 } catch (IOException e) {
321 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
325 private void stopConnection() {
326 SocketChannelHandler handler = socketChannelHandler;
327 if (handler != null && handler.isChannelOpen()) {
332 private void wakeUpPS4() {
333 logger.debug("Waking up PS4...");
334 try (DatagramSocket socket = new DatagramSocket(0, localAddress)) {
335 socket.setBroadcast(false);
336 InetAddress inetAddress = InetAddress.getByName(config.ipAddress);
338 byte[] wakeup = PS4PacketHandler.makeWakeupPacket(config.userCredential);
339 DatagramPacket packet = new DatagramPacket(wakeup, wakeup.length, inetAddress, DEFAULT_BROADCAST_PORT);
341 } catch (IOException e) {
342 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
346 private boolean openComs() {
347 try (DatagramSocket socket = new DatagramSocket(0, localAddress)) {
348 socket.setBroadcast(false);
349 InetAddress inetAddress = InetAddress.getByName(config.ipAddress);
351 byte[] launch = PS4PacketHandler.makeLaunchPacket(config.userCredential);
352 DatagramPacket packet = new DatagramPacket(launch, launch.length, inetAddress, DEFAULT_BROADCAST_PORT);
356 } catch (IOException e) {
357 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
358 } catch (InterruptedException e) {
364 private boolean setupConnection(SocketChannel channel) throws IOException {
365 logger.debug("TCP connecting");
367 channel.socket().setSoTimeout(2000);
368 channel.configureBlocking(true);
369 channel.connect(new InetSocketAddress(config.ipAddress, currentComPort));
371 ByteBuffer outPacket = PS4PacketHandler.makeHelloPacket();
372 sendPacketToPS4(outPacket, channel, false, false);
374 // Read hello response
375 final ByteBuffer readBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
377 int responseLength = channel.read(readBuffer);
378 if (responseLength > 0) {
379 ps4Crypto.parseHelloResponsePacket(readBuffer);
384 outPacket = ps4Crypto.makeHandshakePacket();
385 sendPacketToPS4(outPacket, channel, false, false);
389 private class SocketChannelHandler extends Thread {
390 private SocketChannel socketChannel;
392 public SocketChannelHandler() throws IOException {
393 socketChannel = setupChannel();
399 public SocketChannel getChannel() {
400 if (!socketChannel.isOpen()) {
402 socketChannel = setupChannel();
403 } catch (IOException e) {
404 logger.debug("Couldn't open SocketChannel.{}", e.getMessage());
407 return socketChannel;
410 public boolean isChannelOpen() {
411 return socketChannel.isOpen();
414 private SocketChannel setupChannel() throws IOException {
415 SocketChannel channel = SocketChannel.open();
417 throw new IOException("Open coms failed");
419 if (!setupConnection(channel)) {
420 throw new IOException("Setup connection failed");
422 updateState(CHANNEL_CONNECT, OnOffType.ON);
428 SocketChannel channel = socketChannel;
429 final ByteBuffer readBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
431 while (channel.read(readBuffer) > 0) {
432 ByteBuffer messBuffer = ps4Crypto.decryptPacket(readBuffer);
433 readBuffer.position(0);
434 PS4Command lastCommand = parseResponsePacket(messBuffer);
436 if (lastCommand == PS4Command.SERVER_STATUS_RSP) {
437 if (oskOpen && isLinked(CHANNEL_OSK_TEXT)) {
444 } catch (IOException e) {
445 logger.debug("Connection read exception: {}", e.getMessage());
449 } catch (IOException e) {
450 logger.debug("Connection close exception: {}", e.getMessage());
453 updateState(CHANNEL_CONNECT, OnOffType.OFF);
454 logger.debug("SocketHandler done.");
455 ps4Crypto.clearCiphers();
460 private @Nullable PS4Command parseResponsePacket(ByteBuffer rBuffer) {
462 final int buffSize = rBuffer.remaining();
463 final int size = rBuffer.getInt();
464 if (size > buffSize || size < 12) {
465 logger.debug("Response size ({}) not good, buffer size ({}).", size, buffSize);
468 int cmdValue = rBuffer.getInt();
469 int statValue = rBuffer.getInt();
470 PS4ErrorStatus status = PS4ErrorStatus.valueOfTag(statValue);
471 PS4Command command = PS4Command.valueOfTag(cmdValue);
472 byte[] respBuff = new byte[size];
474 rBuffer.get(respBuff);
475 if (command != null) {
476 if (status == null) {
477 logger.debug("Resp; size:{}, command:{}, statValue:{}, data:{}.", size, command, statValue, respBuff);
479 logger.debug("Resp; size:{}, command:{}, status:{}, data:{}.", size, command, status, respBuff);
483 if (status == null) {
484 logger.debug("Unhandled Login status value: {}", statValue);
487 // Read login response
490 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, status.message);
492 if (isLinked(CHANNEL_2ND_SCREEN)) {
493 scheduler.execute(() -> {
494 ByteBuffer outPacket = PS4PacketHandler
495 .makeClientIDPacket("com.playstation.mobile2ndscreen", "18.9.3");
496 sendPacketEncrypted(outPacket, false);
500 case STATUS_NOT_PAIRED:
501 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, status.message);
504 case STATUS_MISSING_PAIRING_CODE:
505 case STATUS_MISSING_PASS_CODE:
506 case STATUS_WRONG_PAIRING_CODE:
507 case STATUS_WRONG_PASS_CODE:
508 case STATUS_WRONG_USER_CREDENTIAL:
509 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_ERROR, status.message);
511 logger.debug("Not logged in: {}", status.message);
513 case STATUS_CAN_NOT_PLAY_NOW:
514 case STATUS_CLOSE_OTHER_APP:
515 case STATUS_COMMAND_NOT_GOOD:
516 case STATUS_COULD_NOT_LOG_IN:
517 case STATUS_DO_LOGIN:
518 case STATUS_MAX_USERS:
519 case STATUS_REGISTER_DEVICE_OVER:
520 case STATUS_RESTART_APP:
521 case STATUS_SOMEONE_ELSE_USING:
522 case STATUS_UPDATE_APP:
523 case STATUS_UPDATE_PS4:
524 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, status.message);
526 logger.debug("Not logged in: {}", status.message);
529 logger.debug("Unhandled Login response status:{}, message:{}", status, status.message);
534 if (status != null && status != PS4ErrorStatus.STATUS_OK) {
535 logger.debug("App start response: {}", status.message);
539 if (status != null && status != PS4ErrorStatus.STATUS_OK) {
540 logger.debug("Standby response: {}", status.message);
543 case SERVER_STATUS_RSP:
544 if ((statValue & 4) != 0) {
548 updateState(CHANNEL_OSK_TEXT, StringType.valueOf(""));
552 logger.debug("Server status value:{}", statValue);
554 case HTTPD_STATUS_RSP:
555 String httpdStat = PS4PacketHandler.parseHTTPdPacket(rBuffer);
556 logger.debug("HTTPd Response; {}", httpdStat);
557 String secondScrStr = "";
558 int httpStatus = rBuffer.getInt(8);
559 int port = rBuffer.getInt(12);
560 if (httpStatus != 0 && port != 0) {
561 secondScrStr = "http://" + config.ipAddress + ":" + port;
563 updateState(CHANNEL_2ND_SCREEN, StringType.valueOf(secondScrStr));
565 case OSK_CHANGE_STRING_REQ:
566 String oskText = PS4PacketHandler.parseOSKStringChangePacket(rBuffer);
567 updateState(CHANNEL_OSK_TEXT, StringType.valueOf(oskText));
570 case OSK_CONTROL_REQ:
571 case COMMENT_VIEWER_START_RESULT:
572 case SCREEN_SHOT_RSP:
577 logger.debug("Unknown response, command:{}. Missing case.", command);
581 logger.debug("Unknown resp-cmd, size:{}, command:{}, status:{}, data:{}.", size, cmdValue, statValue,
587 private SocketChannel getConnection() throws IOException {
588 return getConnection(true);
591 private SocketChannel getConnection(boolean requiresLogin) throws IOException {
592 SocketChannel channel = null;
593 SocketChannelHandler handler = socketChannelHandler;
594 if (handler == null || !handler.isChannelOpen()) {
596 handler = new SocketChannelHandler();
597 socketChannelHandler = handler;
598 } catch (IOException e) {
599 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
603 channel = handler.getChannel();
604 if (!loggedIn && requiresLogin) {
610 private void sendPacketToPS4(ByteBuffer packet, SocketChannel channel, boolean encrypted, boolean restartTimeout) {
611 PS4Command cmd = PS4Command.valueOfTag(packet.getInt(4));
612 logger.debug("Sending {} packet.", cmd);
615 ByteBuffer outPacket = ps4Crypto.encryptPacket(packet);
616 channel.write(outPacket);
618 channel.write(packet);
620 if (restartTimeout) {
621 setupConnectionTimeout(config.connectionTimeout);
623 } catch (IOException e) {
624 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
628 private void sendPacketEncrypted(ByteBuffer packet, SocketChannel channel) {
629 sendPacketToPS4(packet, channel, true, true);
632 private void sendPacketEncrypted(ByteBuffer packet) {
633 sendPacketEncrypted(packet, true);
636 private void sendPacketEncrypted(ByteBuffer packet, boolean requiresLogin) {
638 SocketChannel channel = getConnection(requiresLogin);
639 if (requiresLogin && !loggedIn) {
640 ScheduledFuture<?> future = scheduler.schedule(
641 () -> sendPacketToPS4(packet, channel, true, requiresLogin), 250, TimeUnit.MILLISECONDS);
642 scheduledFutures.add(future);
643 scheduledFutures.removeIf(ScheduledFuture::isDone);
645 sendPacketToPS4(packet, channel, true, requiresLogin);
647 } catch (IOException e) {
648 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
653 * This is used as a heart beat to let the PS4 know that we are still listening.
655 private void sendStatus() {
656 ByteBuffer outPacket = PS4PacketHandler.makeStatusPacket(0);
657 sendPacketEncrypted(outPacket, false);
660 private void login(SocketChannel channel) {
661 // Send login request
662 ByteBuffer outPacket = PS4PacketHandler.makeLoginPacket(config.userCredential, config.passCode,
664 sendPacketEncrypted(outPacket, channel);
667 private void login() {
669 SocketChannel channel = getConnection(false);
671 } catch (IOException e) {
672 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
677 * This closes the connection with the PS4.
679 private void sendByeBye() {
680 ByteBuffer outPacket = PS4PacketHandler.makeByebyePacket();
681 sendPacketEncrypted(outPacket, false);
684 private void turnOnPS4() {
686 ScheduledFuture<?> future = scheduler.schedule(this::waitAndConnectToPS4, 17, TimeUnit.SECONDS);
687 scheduledFutures.add(future);
688 scheduledFutures.removeIf(ScheduledFuture::isDone);
691 private void waitAndConnectToPS4() {
694 } catch (IOException e) {
695 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
699 private void sendStandby() {
700 ByteBuffer outPacket = PS4PacketHandler.makeStandbyPacket();
701 sendPacketEncrypted(outPacket);
705 * Ask PS4 if the OSK is open so we can get and set text.
707 private void sendOSKStart() {
708 ByteBuffer outPacket = PS4PacketHandler.makeOSKStartPacket();
709 sendPacketEncrypted(outPacket);
713 * Sets the entire OSK string on the PS4.
715 * @param text The text to set in the OSK.
717 private void setOSKText(String text) {
718 logger.debug("Sending osk text packet,\"{}\"", text);
719 ByteBuffer outPacket = PS4PacketHandler.makeOSKStringChangePacket(text);
720 sendPacketEncrypted(outPacket);
724 * Tries to start an application on the PS4.
726 * @param applicationId The unique id for the application (CUSAxxxxx).
728 private void startApplication(String applicationId) {
729 ByteBuffer outPacket = PS4PacketHandler.makeApplicationPacket(applicationId);
730 sendPacketEncrypted(outPacket);
733 private void sendRemoteKey(int pushedKey) {
735 SocketChannelHandler scHandler = socketChannelHandler;
736 int preWait = (scHandler == null || !loggedIn) ? POST_CONNECT_SENDKEY_DELAY_MS : 0;
737 SocketChannel channel = getConnection();
739 ScheduledFuture<?> future = scheduler.schedule(() -> {
740 ByteBuffer outPacket = PS4PacketHandler.makeRemoteControlPacket(PS4_KEY_OPEN_RC);
741 sendPacketEncrypted(outPacket, channel);
742 }, preWait, TimeUnit.MILLISECONDS);
743 scheduledFutures.add(future);
745 future = scheduler.schedule(() -> {
747 ByteBuffer keyPacket = PS4PacketHandler.makeRemoteControlPacket(pushedKey);
748 sendPacketEncrypted(keyPacket, channel);
749 }, preWait + MIN_SENDKEY_DELAY_MS, TimeUnit.MILLISECONDS);
750 scheduledFutures.add(future);
752 future = scheduler.schedule(() -> {
753 ByteBuffer offPacket = PS4PacketHandler.makeRemoteControlPacket(PS4_KEY_OFF);
754 sendPacketEncrypted(offPacket, channel);
755 }, preWait + MIN_SENDKEY_DELAY_MS + MIN_HOLDKEY_DELAY_MS, TimeUnit.MILLISECONDS);
756 scheduledFutures.add(future);
758 future = scheduler.schedule(() -> {
759 ByteBuffer closePacket = PS4PacketHandler.makeRemoteControlPacket(PS4_KEY_CLOSE_RC);
760 sendPacketEncrypted(closePacket, channel);
761 }, preWait + MIN_SENDKEY_DELAY_MS * 2 + MIN_HOLDKEY_DELAY_MS, TimeUnit.MILLISECONDS);
762 scheduledFutures.add(future);
764 } catch (IOException e) {
765 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
767 scheduledFutures.removeIf(ScheduledFuture::isDone);
770 private void parseSearchResponse(DatagramPacket packet) {
771 byte[] data = packet.getData();
772 String message = new String(data, StandardCharsets.UTF_8);
773 String applicationName = "";
774 String applicationId = "";
776 String[] rowStrings = message.trim().split("\\r?\\n");
777 for (String row : rowStrings) {
778 int index = row.indexOf(':');
780 OnOffType power = null;
781 if (row.contains("200")) {
782 power = OnOffType.ON;
783 } else if (row.contains("620")) {
784 power = OnOffType.OFF;
787 updateState(CHANNEL_POWER, power);
788 if (!currentPower.equals(power)) {
789 currentPower = power;
790 if (power.equals(OnOffType.ON) && config.autoConnect) {
791 SocketChannelHandler scHandler = socketChannelHandler;
792 if (scHandler == null || !loggedIn) {
793 logger.debug("Trying to login after power on.");
794 ScheduledFuture<?> future = scheduler.schedule(() -> login(), 20, TimeUnit.SECONDS);
795 scheduledFutures.add(future);
796 scheduledFutures.removeIf(ScheduledFuture::isDone);
800 updateStatus(ThingStatus.ONLINE);
802 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
803 "Could not determine power status.");
807 String key = row.substring(0, index);
808 String value = row.substring(index + 1);
810 case RESPONSE_RUNNING_APP_NAME:
811 applicationName = value;
813 case RESPONSE_RUNNING_APP_TITLEID:
814 applicationId = value;
816 case RESPONSE_HOST_REQUEST_PORT:
817 int port = Integer.parseInt(value);
818 if (currentComPort != port) {
819 currentComPort = port;
820 logger.debug("Host request port: {}", port);
823 case RESPONSE_SYSTEM_VERSION:
824 updateProperty(Thing.PROPERTY_FIRMWARE_VERSION, PlayStationDiscovery.formatPS4Version(value));
831 if (!currentApplication.equals(applicationName)) {
832 currentApplication = applicationName;
833 updateState(CHANNEL_APPLICATION_NAME, StringType.valueOf(applicationName));
834 logger.debug("Current application: {}", applicationName);
836 if (!currentApplicationId.equals(applicationId)) {
837 updateApplicationTitleid(applicationId);
842 * Sets the cached TitleId and tries to download artwork
843 * for application if CHANNEL_APPLICATION_IMAGE is linked.
845 * @param titleId Id of application.
847 private void updateApplicationTitleid(String titleId) {
848 currentApplicationId = titleId;
849 updateState(CHANNEL_APPLICATION_ID, StringType.valueOf(titleId));
850 logger.debug("Current application title id: {}", titleId);
851 if (!isLinked(CHANNEL_APPLICATION_IMAGE)) {
854 LocaleProvider lProvider = localeProvider;
855 Locale locale = (lProvider != null) ? lProvider.getLocale() : Locale.US;
857 RawType artWork = PS4ArtworkHandler.fetchArtworkForTitleid(titleId, config.artworkSize, locale);
858 if (artWork != null) {
859 currentArtwork = artWork;
860 updateState(CHANNEL_APPLICATION_IMAGE, artWork);
861 } else if (!titleId.isEmpty()) {
862 logger.debug("Couldn't fetch artwork for title id: {}", titleId);