Playstation 4/PS4 custom component

Official PS4 component? https://github.com/home-assistant/home-assistant/pull/19469

3 Likes

Nice :+1:t2: :+1:t2:

1 Like

So Iā€™m trying to get this working with two PS4ā€™s on my network. Noticed that the docker image (the ps4-waker wrapper docker image) I have was updated today and now I canā€™t get anything running if I select commands. The ps4-waker seems to pull whatā€™s going on with my ps4s so far but starting and stopping them Iā€™m getting errors;

2018-12-24 21:28:53 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
File ā€œ/usr/local/lib/python3.6/site-packages/homeassistant/helpers/service.pyā€, line 277, in _handle_service_platform_call
await getattr(entity, func)(**data)
File ā€œ/usr/local/lib/python3.6/concurrent/futures/thread.pyā€, line 56, in run
result = self.fn(*self.args, **self.kwargs)
File ā€œ/config/custom_components/media_player/ps4.pyā€, line 372, in turn_on
self._ps4.wakeup()
File ā€œ/config/deps/lib/python3.6/site-packages/ps4_python3/ps4.pyā€, line 79, in wakeup
wakeup(self._host, self._credential)
File ā€œ/config/deps/lib/python3.6/site-packages/ps4_python3/ddp.pyā€, line 109, in wakeup
_send_msg(host, broadcast, msg)
File ā€œ/config/deps/lib/python3.6/site-packages/ps4_python3/ddp.pyā€, line 85, in _send_msg
_send_recv_msg(host, broadcast, msg, receive=False)
File ā€œ/config/deps/lib/python3.6/site-packages/ps4_python3/ddp.pyā€, line 77, in _send_recv_msg
sock.sendto(msg.encode(ā€˜utf-8ā€™), (host, DDP_PORT))
socket.gaierror: [Errno -2] Name does not resolve

Any ideas?

OK,

further to the problem above. If I remove one of the PS4s from my setup, I am still getting the playing information from the one I removed. I am still unable to wake up or put in standby from HA interface. However, I can log onto the docker instance and run the ps4-waker manually and it works fine.

Another reportā€¦seems the configuration has changed likely to get things in place for the official addition for this component. Flying a little blind with trial and error here. MIght start from scratch and see what happens. i get to the point where HASS restarts, logs show that there is no configuration for my PS4s so I try to configure them using the HASS interface. However, it never finds the PS4s.

So I did a reinstall from scratch, removed the PS4-waker from the PS4s and started all over again. This time I only added a single PS4. I see activity in HA (what games are being played etc) from my one system I added. But if the second system is on, I get the activity from it. It looks like the component is not using the IP address correctly when it queries whatā€™s going on. Digging into what ps4-waker does, it looks like if you donā€™t include the device IP, itā€™ll just find the first PS4 on your network. I canā€™t validate that 100% but it sure seems like thatā€™s whatā€™s going on.

Iā€™m also stuck with the images not showing up, neither the small one or the main one.

Iā€™m running on HASSIO 0.84.6.

In my config is:

- platform: ps4
  host: !secret ps4_host 
  ps4_ip: !secret ps4_ip_address
  name: PlayStation4
  local_store: games

Iā€™ve tried putting the image files in config/www/games/ as suggested and Iā€™ve also tried config/games/

What am I missing?

EDIT: I updated the custom component to include some additional logging and it shows the filename as

ps4 file name, /local/games/CUSA00127.jpg

so Iā€™m assuming /local is the problem?

@abdel.elbel That seems to have resolved my issues with images not showing up. Great work and thank you!

1 Like

Maybe nit picking here because for me itā€™s all working but I got this error in the logs (Could not detect any matching PS4 device)
and when starting or making the ps4 go to sleep via Google home it returns ā€œsomething went wrongā€ but Itā€™s all working.

Any idea where to start troubleshooting to solve this? Thanks!

Thank you for this. I was struggling to get this working before but your tutorial helped a lot.

Also the fact that home assistant as added the cloud management part has made integrating with google home even easier.

One question though, what is the importance of the (client type ā€œiā€) specifically the ā€œiā€. Mine showed an ā€œaā€ and it worked fine but I did changed it to an ā€œiā€.

Also I do get an error from google home saying there was an error with the PlayStation 4 when waking it up but it still wakes up. The error in the logs for PS4 Bridge Say ā€œLogin connect refused; retrying soonā€

Everything works just more annoying and curious (perfectionism)

Thank You!!!

So excited for pyps4 and the new component!!!

Good morning. is anyone getting this error?

failed to send data using socket, timed out
08:37 deps/lib/python3.6/site-packages/pyps4/ddp.py (ERROR)

Is there a new component? What am i missing?

Iā€™m trying to use this component but i donā€™t know how to make it work, can someone kindly make a short guide? Thank you. Iā€™m running hassio on ubuntu 18.04. I tried to search but I canā€™t find anything online

Iā€™m following this guide, untill now i have set up everything, i get the ps4 media player on the home but it allways remain on off. On the ps4 i can see that ps4 Waker Bridge e is on the devices connected. And i get this in the Log of Hassio
The ps4 is connected via WiFi and hass.io is running on Ubuntu 18.04
Someone have the same issue?

Confg.yaml
media_player:
  #PS4
  - platform: ps4
    host: 192.168.1.194:3031
    ps4_ip: 192.168.1.198
    name: PlayStation 4
    local_store: games
.ps4-wake.credentials.json

{"client-type":"a","auth-type":"C","user-credential":"My Key"}

ps4 Waker Bridge
{
  "port": 3031,
  "devices": [
    {
      "client_type": "i",
      "auth_type": "C",
      "credentials": "My Key"
    }
  ]
}

Log:
starting version 3.2.2
Listening on 0.0.0.0:3030

ps4.py
"""Playstation 4 media_player using ps4-waker."""
import json
import logging
from datetime import timedelta
from urllib.parse import urlparse
import requests
import voluptuous as vol

import homeassistant.util as util
from homeassistant.components.media_player import (
    PLATFORM_SCHEMA,
    MEDIA_TYPE_CHANNEL,
    SUPPORT_TURN_ON,
    SUPPORT_TURN_OFF,
    SUPPORT_STOP,
    SUPPORT_SELECT_SOURCE,
    ENTITY_IMAGE_URL,
    MediaPlayerDevice
)
from homeassistant.const import (
    STATE_IDLE,
    STATE_UNKNOWN,
    STATE_OFF,
    STATE_PLAYING,
    CONF_NAME,
    CONF_HOST
)
from homeassistant.helpers import config_validation as cv

REQUIREMENTS = []

_LOGGER = logging.getLogger(__name__)

SUPPORT_PS4 = SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \
              SUPPORT_STOP | SUPPORT_SELECT_SOURCE

DEFAULT_NAME = 'Playstation 4'
DEFAULT_PORT = ''
ICON = 'mdi:playstation'
CONF_GAMES_FILENAME = 'games_filename'
CONF_IMAGEMAP_JSON = 'imagemap_json'
CONF_CMD = 'cmd'
CONF_LOCAL_STORE = "local_store"
CONF_PS4_IP = "ps4_ip"

PS4_GAMES_FILE = 'ps4-games.json'
MEDIA_IMAGE_DEFAULT = None
MEDIA_IMAGEMAP_JSON = 'https://github.com/hmn/ps4-imagemap/raw/master/games.json'
LOCAL_STORE = None

MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_HOST): cv.string,
    vol.Optional(CONF_PS4_IP): cv.string,
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
    vol.Optional(CONF_GAMES_FILENAME, default=PS4_GAMES_FILE): cv.string,
    vol.Optional(CONF_IMAGEMAP_JSON, default=MEDIA_IMAGEMAP_JSON): cv.string,
    vol.Optional(CONF_LOCAL_STORE, default=LOCAL_STORE): cv.string,
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Setup PS4 platform."""
    if discovery_info is not None:
        ip = urlparse(discovery_info[1]).hostname
    else:
        ip = config.get(CONF_PS4_IP)

    if ip is None:
        _LOGGER.error("No PS4 found in configuration file or with discovery")
        return False

    host = config.get(CONF_HOST)
    name = config.get(CONF_NAME)
    games_filename = hass.config.path(config.get(CONF_GAMES_FILENAME))
    games_map_json = config.get(CONF_IMAGEMAP_JSON)
    local_store = config.get(CONF_LOCAL_STORE)

    ps4 = PS4Waker(host, ip, games_filename)
    add_devices([PS4Device(name, ps4, games_map_json, local_store)], True)


class PS4Device(MediaPlayerDevice):
    """Representation of a PS4."""

    def __init__(self, name, ps4, gamesmap_json, local_store):
        """Initialize the ps4 device."""
        self.ps4 = ps4
        self._name = name
        self._state = STATE_UNKNOWN
        self._media_content_id = None
        self._media_title = None
        self._current_source = None
        self._current_source_id = None
        self._games_map_json = gamesmap_json
        self._games_map = {}
        self._local_store = local_store
        if self._local_store is None:
            self.load_games_map()
        self.update()

    @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
    def update(self):
        """Retrieve the latest data."""
        data = self.ps4.search()
        self._media_title = data.get('running-app-name')
        self._media_content_id = data.get('running-app-titleid')
        self._current_source = data.get('running-app-name')
        self._current_source_id = data.get('running-app-titleid')

        if data.get('status') == 'Ok':
            if self._media_content_id is not None:
                self._state = STATE_PLAYING
            else:
                self._state = STATE_IDLE
        else:
            self._state = STATE_OFF
            self._media_title = None
            self._media_content_id = None
            self._current_source = None
            self._current_source_id = None

    def load_games_map(self):
        try:
            self._games_map = json.loads(requests.get(self._games_map_json, verify=False))
        except Exception as e:
            _LOGGER.error("gamesmap json file could not be loaded, %s" % e)

    @property
    def entity_picture(self):
        if self.state == STATE_OFF:
            return None

        if self._local_store is None:
            image_hash = self.media_image_hash

            if image_hash is None:
                return None

            return ENTITY_IMAGE_URL.format(
                self.entity_id, self.access_token, image_hash)

        if self._media_content_id is None:
            return None

        filename = "/local/%s/%s.jpg" % (self._local_store, self._media_content_id)
        return filename

    @property
    def name(self):
        """Return the name of the device."""
        return self._name

    @property
    def state(self):
        """Return the state of the device."""
        return self._state

    @property
    def icon(self):
        """Icon."""
        return ICON

    @property
    def media_content_id(self):
        """Content ID of current playing media."""
        return self._media_content_id

    @property
    def media_content_type(self):
        """Content type of current playing media."""
        return MEDIA_TYPE_CHANNEL

    @property
    def media_image_url(self):
        """Image url of current playing media."""
        if self._media_content_id is None:
            return MEDIA_IMAGE_DEFAULT
        try:
            return self._games_map[self._media_content_id]
        except KeyError:
            return MEDIA_IMAGE_DEFAULT

    @property
    def media_title(self):
        """Title of current playing media."""
        return self._media_title

    @property
    def supported_features(self):
        """Media player features that are supported."""
        return SUPPORT_PS4

    @property
    def source(self):
        """Return the current input source."""
        return self._current_source

    @property
    def source_list(self):
        """List of available input sources."""
        return sorted(self.ps4.games.values())

    def turn_off(self):
        """Turn off media player."""
        self.ps4.standby()

    def turn_on(self):
        """Turn on the media player."""
        self.ps4.wake()
        self.update()

    def media_pause(self):
        """Send keypress ps to return to menu."""
        self.ps4.remote('ps')
        self.update()

    def media_stop(self):
        """Send keypress ps to return to menu."""
        self.ps4.remote('ps')
        self.update()

    def select_source(self, source):
        """Select input source."""
        for titleid, game in self.ps4.games.items():
            if source == game:
                self.ps4.start(titleid)
                self._current_source_id = titleid
                self._current_source = game
                self._media_content_id = titleid
                self._media_title = game
                self.update()


class PS4Waker(object):
    """Rest client for handling the data retrieval."""

    def __init__(self, url, ip, games_filename):
        """Initialize the data object."""
        self._url = url
        self._ip = ip
        self._games_filename = games_filename
        self.games = {}
        self._load_games()

    def __call(self, command, param=None):
        url = '{0}/ps4/{1}/{2}'.format(self._url, self._ip, command)
        if param is not None:
            url += '/{0}'.format(param)

        try:
            response = requests.get(url, verify=False)
            if 200 != response.status_code:
                raise Exception(response.text)
            return response.text
        except Exception as e:
            _LOGGER.error('Failed to call %s: %s', command, e)
            return None

    def _load_games(self):
        try:
            with open(self._games_filename, 'r') as f:
                self.games = json.load(f)
                f.close()
        except FileNotFoundError:
            self._save_games()
        except ValueError as e:
            _LOGGER.error('Games json file wrong: %s', e)

    def _save_games(self):
        try:
            with open(self._games_filename, 'w') as f:
                json.dump(self.games, f)
                f.close()
        except FileNotFoundError:
            pass

    def wake(self):
        """Wake PS4 up."""
        return self.__call('on')

    def standby(self):
        """Set PS4 into standby mode."""
        return self.__call('off')

    def start(self, title_id):
        """Start game using titleId."""
        return self.__call('start', title_id)

    def remote(self, key):
        """Send remote key press."""
        return self.__call('key', key)

    def search(self):
        """List current info."""
        value = self.__call('info')

        if value is None:
            return {}

        try:
            data = json.loads(value)
        except json.decoder.JSONDecodeError as e:
            _LOGGER.error("Error decoding ps4 json : %s", e)
            data = {}

        """Save current game"""
        if data.get('running-app-titleid'):
            if data.get('running-app-titleid') not in self.games.keys():
                game = {data.get('running-app-titleid'):
                            data.get('running-app-name')}
                self.games.update(game)
                self._save_games()

        return data
Hassio Log
## Log Details (ERROR)

Thu Jan 17 2019 15:48:09 GMT+0100 (Ora standard dellā€™Europa centrale)

Failed to call info: No connection adapters were found for '192.168.1.194:3031/ps4/192.168.1.198/info'

Edit i was missing http:// on the host, now it work. Now iā€™m trying to make the img show up

I can answer your first question : ā€˜iā€™ for iphone , ā€˜aā€™ for android. Depends on what Phone you used when you generated your credentials :wink:

1 Like

heh thats awesome thanks!

Let me know if you get the image to work. I have put it aside after wasting what feels like 100hrs on trying to get it to work.

Take a look at this. But this use a html file to see what img put for each game, but it dosenā€™t have the last game in it and i donā€™t know how to update it. It work with ex. youtube and cod 4 but not with GoW.

1 Like

So- two thingsā€¦

  1. ā€¦I see that there was a PR to make this a default componentā€¦ should I just wait until this happens before trying to add my PS4 to my HA?

  2. ā€¦if the answer to the above ā€˜should I waitā€™ is ā€˜noā€™, would anyone care to link or write up a quick ā€˜dummies guideā€™ for the installation and configurationā€¦ the github instructions say ā€˜pip installā€¦ā€™ but thats where I get lost. As this is a working HA install Iā€™m a bit hesitant to rely on my normal ā€˜google itā€™ method and do the whole ā€˜experiment while I learnā€™ gigā€¦

There is a new PR awaiting review.

I already have the custom component installed, but for some reason it wonā€™t let me control my ps4 from time to time. But It still shows me my launched games.
Iā€™m planning to move to HassOS from Hass.io on Raspbian, but I will do so only when the ps4 component is official.
So, to answer your first question, I would recommend you to wait for the official component. It doesnā€™t look like it will take long to include it to the official branch.