Philips Android TV component

I had the same problem (cannot turn of TV after a while because it gets disconnected from the network) and this is how i fixed it:

  1. You need to have an IR blaster in the same room, otherwise it will not work (i have a Broadlink RM3 Pro)

  2. Create media_player entite as written above, but insert it as a child in an universal media_player. This is my yaml configuration:

customize:
  media_player.phtv:
    hidden: true
  
media_player:
  - platform: philips_2016
    name: phTV
    host: 192.x.x.x
    username: yourusername
    password: yourpassword

  - platform: universal
    name: Philips TV
    children:
      - media_player.phTV
        commands:
        turn_on:
          service: switch.turn_on
          data:
            entity_id: switch.phtvir
switch:
  - platform: broadlink
    host: 192.x.x.x
    mac: 'your:mac'
    timeout: 15

      switches:
        phtvir:
          friendly_name: "Philips TV IR"
          command_on: 'JgAcABweHR07HhweHR0dHhw8HR0dHhweHB4dHhwADQUAAAAAAAAAAAAAAAA='
          command_off: 'JgAaAB0dOx4cHhweHR4cHhw8HR0dHhweOzsdAA0FAAAAAAAAAAAAAAAAAAA='

Using these settings i can fully control my TV remotely (even if it is off and it is out of the network, switching it on using the IR Blaster will turn on the TV and all the media functions will be available after)

Hope it helps :slight_smile:

PS: i have a 55PUS6482 Philips TV

And for you last question:

How can I call a function defined in the custom component (e.g. sendkey) from an automation or switch?
I would like to try adding some functions (especially basic ambilight control) to the component and have a working (proof-of-concept) py-script for that, but struggle on calling it from HA (Hassbian).

You can call the Philips API using CURL commands, for example, this is how i call the Home button (the one that opens Android App menu)
PS: Off command is dummy because i don’t need it but it is required

switch:
  - platform: command_line
    switches:
      philips_home:
        command_on: "curl -X POST --digest --insecure -u philips_tv_username:philips_tv_password https://192.x.x.x:1926/6/input/key -d '{key:Home}'"
        command_off: "curl -X POST --digest http://192.x.x.x:1926/6/input/key -d '{none:none}'" 

You can find other Key values available in this post: Philips Android TV component - #12 by Emilio_Emile

Good luck :slight_smile:

@Molodax - check this solution (it works only if you have an IR Blaster): Philips Android TV component

Thanks @an20dei! Not exactly what I was looking for, but this helps me a lot anyway :slight_smile:

Using 192.x.x.x:1926/6/input/key with {key:AmbilightOnOff} just opens the Ambilight Menu on my TV.
I now use this to directly turn on and off my ambilight:

switch:
  - platform: command_line
    switches:
      philips_ambilight:
        command_on: curl -X POST --digest --insecure -u user:password "https://192.x.x.x:1926/ambilight/currentconfiguration" -d '{"styleName":"FOLLOW_VIDEO","isExpert":"false","menuSetting":"STANDARD"}'
        command_off: curl -X POST --digest --insecure -u user:password "https://192.x.x.x:1926/ambilight/power" -d '{"power":"Off"}'

Did anyone find out how to directly turn on/off “Ambilight + Hue” via API? Since it is possible with the Philips App, there should be way to do it…

1 Like

Is it possible to add another attribute to the custom component? I would like have the Ambilight State as an attribute so I can use it to retrieve the correct state for my Ambilight Switch.

I was able to exchange another attribute (e.g. channel) with the ambilight state, but couldn’t make it to add it as a new attribute.

@DaveHA, try to create a sensor using this method (tried it and it works, it returns the ambilight state(off or on) ):

   - platform: command_line
     command: 'curl -X  GET --digest --insecure -u user:pass https://192.x.x.x:1926/6/ambilight/power'
     name: Ambilight
     value_template: '{{ value_json.power }}'

Then you can create an automation (if this sensor is on, turn on Hue, if off, turn off or whatever you want)

Good luck :slight_smile:

and i also think that you can use the last curl as a state command for your switch… search on the forum eventually how to do it, i think it can be made and in this case there is no need for sensor+automation

From this morning I have started to get this error in the component.
Yesterday evening it was working correctly. Any ideas?

Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 222, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 884, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 884, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/philips_2016.py", line 101, in volume_level
    return self._volume / self._max_volume
TypeError: unsupported operand type(s) for /: 'NoneType' and 'NoneType'

Thanks, the sensor solution with curl works :slight_smile: It’s just quite slow with the updates compared to the 5 sec. of the custom component.

I guess someday I have to dig into the custom component development to see what’s possible.

I can see the same error in my log, but the component still works for me.

Seems that the tv goes offline from time to time and the component begins to flood the log with errors.
After turning on the tv it returned to be silent in logs.

Hi everyone!

Can Soneone help me? I used the script to pair tv, set the component in the right folder and set it in configuration.yaml.
The result? Home Assistant give me an Invalid Config tab in home. What could be? Ty!

Hi all,

I have 55POS9002/12.
I’m running HASSIO on a RPi 3.

Through Putty, I could not find the folder mentioned above:

Nevertheless, I created it and put the script inside.
When trying to run it, it says python is not a command.

What do I need to do to be able to pair the TV?

I also tried the component available in Hassio, but it just says the state of the TV is Unknown.
Through Hassio discovery, and because I have a Google Home and the TV linked to it, I see a media_player for the TV, but only works if the TV is running Youtube or any other Android TV app.

What am I doing wrong?
I’m sorry, maybe I’m just being dumb, but my skills in linux and python and etc. are fairly (to be kind to myself) limited…

Thank you.

1 Like

Hi, this looks like an open discussion. Is there also a working solution for a 49PUS7xxx and a documentation how to use it? Or is this going to be a module inside the standard home assistant?
Regards

1 Like

Thank you for this! that works perfectly, how did you find out how to do this? I cant see it on the jointspace API documentation, I ask because I’m trying to set up an automation to switch the ambilights to follow audio, however swapping to {“styleName”:“FOLLOW_AUDIO”} doesn’t seem to work

Hi!
With
curl -X GET --digest --insecure -u user:password https://192.x.x.x:1926/6/ambilight/currentconfiguration
you can get the correct command for the ambilight setting that is currently activated on your TV. So for follow audio you can use one of the following:

{"styleName":"FOLLOW_AUDIO","isExpert":false,"menuSetting":"ENERGY_ADAPTIVE_BRIGHTNESS"}
{"styleName":"FOLLOW_AUDIO","isExpert":false,"menuSetting":"ENERGY_ADAPTIVE_COLORS"}
{"styleName":"FOLLOW_AUDIO","isExpert":false,"menuSetting":"VU_METER"}
{"styleName":"FOLLOW_AUDIO","isExpert":false,"menuSetting":"SPECTUM_ANALYSER"}
{"styleName":"FOLLOW_AUDIO","isExpert":false,"menuSetting":"KNIGHT_RIDER_ALTERNATING"}
{"styleName":"FOLLOW_AUDIO","isExpert":false,"menuSetting":"RANDOM_PIXEL_FLASH"}
{"styleName":"FOLLOW_AUDIO","isExpert":false,"menuSetting":"MODE_RANDOM"}

Have fun :grinning:

I cant thank you enough for sending that over, I now have a fully working input select that can set the ambilights to all of those functions.
Is there a page somewhere with all the available commands/settings you can send over ‘curl’ not shown on the Jointspace API page? or did you figure this out through some other means, I am also intending to add the functionality to change channel, source, and if its possible, open apps, but am struggling with the curl commands. Thanks again for your help

.

Very nice!

I found the undocumented ambilight commands here:
https://gist.github.com/marcelrv/ee9a7cf97c227d069e4ee88d26691019

Changing channels is possible using input/key. I did not find a way to change the source, the command given in the jointSpace API (sources/current) does not work for me. Since it also not possible with the Philips App, I’m not sure if there is a way to directly switch to a specific source at all. Starting apps might be possible, since it’s possible with the App, but I could not find any documentation about it.

I own a Philips 55PFS8209/12 from sometimes around 2014/2015. It’s already Android but uses a different port (1925) and url (/5/) and doesn’t require user authentication.

Based upon Rookeh’s very fine work, I’ve compiled my own script that I’d like to share here. You might want to have a look on my implementation of wakeonlan as well. I’ve found that you can indeed turn on a Philips TV by sending a magic package (to make the TV listen) and then the standby key. Username/password are still in the script but marked optional as it’s not used anyway.

Add this to your configuration.yaml:

  - platform: philips_2014
    name: Fernseher Wohnzimmer
    host: 192.168.178.30
    mac: 01:23:45:67:89:ab

And drop this as philips_2014.py in your config folder under custom_components/media_player:

"""
Media Player component to integrate TVs exposing the Joint Space API.
Updated to support Android-based Philips TVs manufactured from 2014 but before 2016.
"""
import homeassistant.helpers.config_validation as cv
import argparse
import json
import random
import requests
import string
import sys
import voluptuous as vol
import time
import wakeonlan

from base64 import b64encode,b64decode
from Crypto.Hash import SHA, HMAC
from datetime import timedelta, datetime
from homeassistant.components.media_player import (PLATFORM_SCHEMA, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, MediaPlayerDevice)
from homeassistant.const import (
	CONF_HOST, CONF_MAC, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, STATE_OFF, STATE_ON, STATE_UNKNOWN)
from homeassistant.util import Throttle
from requests.auth import HTTPDigestAuth

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)

SUPPORT_PHILIPS_2014 = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE

DEFAULT_DEVICE = 'default'
DEFAULT_HOST = '127.0.0.1'
DEFAULT_MAC = 'aa:aa:aa:aa:aa:aa'
DEFAULT_USER = 'user'
DEFAULT_PASS = 'pass'
DEFAULT_NAME = 'Philips TV'
BASE_URL = 'http://{0}:1925/5/{1}'
TIMEOUT = 5.0
CONNFAILCOUNT = 5

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
	vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
	vol.Required(CONF_MAC, default=DEFAULT_MAC): cv.string,
	vol.Optional(CONF_USERNAME, default=DEFAULT_USER): cv.string,
	vol.Optional(CONF_PASSWORD, default=DEFAULT_PASS): cv.string,
	vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})

# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
	"""Set up the Philips 2016+ TV platform."""
	name = config.get(CONF_NAME)
	host = config.get(CONF_HOST)
	mac = config.get(CONF_MAC)
	user = config.get(CONF_USERNAME)
	password = config.get(CONF_PASSWORD)
	tvapi = PhilipsTVBase(host, mac, user, password)
	add_devices([PhilipsTV(tvapi, name)])

class PhilipsTV(MediaPlayerDevice):
	"""Representation of a 2014-2015 Philips TV exposing the JointSpace API but not authentication."""

	def __init__(self, tv, name):
		"""Initialize the TV."""
		self._tv = tv
		self._name = name
		self._state = STATE_UNKNOWN
		self._min_volume = None
		self._max_volume = None
		self._volume = None
		self._muted = False
		self._connfail = 0

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

	@property
	def should_poll(self):
		"""Device should be polled."""
		return True

	@property
	def supported_features(self):
		"""Flag media player features that are supported."""
		return SUPPORT_PHILIPS_2014

	@property
	def state(self):
		"""Get the device state. An exception means OFF state."""
		return self._state

	@property
	def volume_level(self):
		"""Volume level of the media player (0..1)."""
		return self._volume

	@property
	def is_volume_muted(self):
		"""Boolean if volume is currently muted."""
		return self._muted

	def turn_on(self):
		"""Turn on the device."""
		i = 0
		while ((not self._tv.on) and (i < 15)):
			self._tv.wol()
			self._tv.sendKey('Standby')
			time.sleep(2)
			i += 1
		if self._tv.on:
			self._state = STATE_OFF

	def turn_off(self):
		"""Turn off the device."""
		i = 0
		while ((self._tv.on) and (i < 15)):
			self._tv.sendKey('Standby')	
			time.sleep(0.5)
			i += 1
		if not self._tv.on:
			self._state = STATE_OFF

	def volume_up(self):
		"""Send volume up command."""
		self._tv.sendKey('VolumeUp')
		if not self._tv.on:
			self._state = STATE_OFF

	def volume_down(self):
		"""Send volume down command."""
		self._tv.sendKey('VolumeDown')
		if not self._tv.on:
			self._state = STATE_OFF

	def mute_volume(self, mute):
		"""Send mute command."""
		self._tv.sendKey('Mute')
		if not self._tv.on:
			self._state = STATE_OFF

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

	@Throttle(MIN_TIME_BETWEEN_UPDATES)
	def update(self):
		"""Get the latest data and update device state."""
		self._tv.update()
		self._min_volume = self._tv.min_volume
		self._max_volume = self._tv.max_volume
		self._volume = self._tv.volume
		self._muted = self._tv.muted
		if self._tv.on:
			self._state = STATE_ON
		else:
			self._state = STATE_OFF

class PhilipsTVBase(object):
	def __init__(self, host, mac, user, password):
		self._host = host
		self._mac = mac
		self._user = user
		self._password = password
		self._connfail = 0
		self.on = None
		self.name = None
		self.min_volume = None
		self.max_volume = None
		self.volume = None
		self.muted = None
		self.sources = None
		self.source_id = None
		self.channels = None
		self.channel_id = None

	def _getReq(self, path):
		try:
			if self._connfail:
				self._connfail -= 1
				return None
			resp = requests.get(BASE_URL.format(self._host, path), timeout=TIMEOUT)
			self.on = True
			return json.loads(resp.text)
		except requests.exceptions.RequestException as err:
			self._connfail = CONNFAILCOUNT
			self.on = False
			return None

	def _postReq(self, path, data):
		try:
			if self._connfail:
				self._connfail -= 1
				return False
			resp = requests.post(BASE_URL.format(self._host, path), data=json.dumps(data))
			self.on = True
			if resp.status_code == 200:
				return True
			else:
				return False
		except requests.exceptions.RequestException as err:
			self._connfail = CONNFAILCOUNT
			self.on = False
			return False

	def update(self):
		self.getName()
		self.getAudiodata()

	def getName(self):
		r = self._getReq('system/name')
		if r:
			self.name = r['name']

	def getAudiodata(self):
		audiodata = self._getReq('audio/volume')
		if audiodata:
			self.min_volume = int(audiodata['min'])
			self.max_volume = int(audiodata['max'])
			self.volume = audiodata['current']
			self.muted = audiodata['muted']
		else:
			self.min_volume = None
			self.max_volume = None
			self.volume = None
			self.muted = None

	def setVolume(self, level):
		if level:
			if self.min_volume != 0 or not self.max_volume:
				self.getAudiodata()
			if not self.on:
				return
			try:
				targetlevel = int(level)
			except ValueError:
				return
			if targetlevel < self.min_volume + 1 or targetlevel > self.max_volume:
				return
			self._postReq('audio/volume', {'current': targetlevel, 'muted': False})
			self.volume = targetlevel

	def sendKey(self, key):
		self._postReq('input/key', {'key': key})

	def wol(self):
		wakeonlan.send_magic_packet(self._mac)

Have fun!

1 Like

any luck with the folder to place the philips.py in hassio yet