Philips Android TV component


#61

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.


#62

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!


#63

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


#64

reload the core

if using hassio folder in config/custom_components/philipstv_2016.py


#65

Hi,

No, I haven’t had the time to “play” with this.
But I would be really interested in doing it in the near future.

I want to learn a lot about Home Assistant and Hass.io and get it really useful.
For now, it is working almost out-of-the box, as I only know how to do a limited amount of things (basically what is in the instructions of the Help).

Those menus for the TV and Ambilight above, the dashboards, the tabs (I know these are explained somewhere in the Help), I want to learn a lot. I love these thongs, although my programming does not go further than VBA :joy: In there I’m really confortable.:sunglasses:

Would you be able to help out?
I hope you can and I’m getting excited I’ll finally get this to work.


#66

Thanks for great work. I can confirm that your script is working on my philips 2014. Is there any API or script to change a tv channels?


#67

Why I get white screen with text: Forbidden, when I open tv IP address?


#68

import hmac
from hashlib import sha256

def create_signature(secret_key, to_sign):
sign = hmac.new(secret_key, to_sign, sha256)
return str(b64encode(sign.hexdigest().encode()))

just change these things if you like me dont want to download visual studio things on windows,
use hmac and hashlib instead of Crypto.


#69

Thank you very much for the great work. I can confirm it works on my new philips 9002 oled. I fisrt thought it wouldnt be possible then i discovered this thread and got reliable tv status control. Thank you


#71

Hi everyone. :slight_smile:

I’m new here and don’t understand about api’s and stuff. The reason I came here is because I googled how to install jointspace on my Philips TV. (55PUS7181/12)

I have a Samsung Galaxy Note 8 and installed the Philips New Ambilight + Hue app on it. But it gives me a message saying ‘something went wrong’. I uploaded a screenshot, hopefully you can see it below.

My TV needs to be enabled with jointspace and I have no idea how to do it.

If anyone can help I’d really appreciate it.

Many thanks in advance. :slight_smile:


#72

Hi.

I don’t think the app is working on the new tv’s from philips, because on my 9002 oled it also say it doesn’t find the tv. But you can go in the menu and find ambilight+hue and pair the hue bridge and it’s working just fine. I read about the jointspace api on forums, but it seems that you cannot enable it because it’s already enabled.

I hope i’m right. If somebody knows more, feel free to answer.


#73

For anyone interested, I have successfully set up an automation which sets the Ambilights of a 2016+ Philips Android TV to follow (copy) the colour of another light from Home Assistant, say you have RGB LED strips around the room, and want the TV ambilights, at certain times, to automatically match the colour of the target lights.
Things to note:

  • This method uses Hue and Saturation to set the colour of the ambilights, so the ‘hs_color’ attribute of the light component is used to update the parameters, this may be labelled differently for your lights
  • The scaling of the Hue and Saturation within home assistant will probably be different to the scale used within the Phillips API, and thus, a scaling factor is needed

Through experimenting with the Jointspace API, I could set all the Ambilight LED’s to one colour via the terminal command:

shell_command:
name_of_shell_command:
curl -X POST --digest --insecure -u XXXXX https://192.168.X.XXX(TVs_local_IP):1926/6/ambilight/currentconfiguration -d ‘{“styleName”:“FOLLOW_COLOR”,“isExpert”:true,“algorithm”:“MANUAL_HUE”,“colorSettings”:{“color”:{“hue”:{{ ((state_attr(‘light.[enitity_id of target light]’, ‘hs_color’)[0]) * ([Scaling Factor]) |int }},“saturation”:{{ ((state_attr(‘light.[enitity_id of target light]’, ‘hs_color’)[1]) ([Scaling Factor])) |int }},“brightness”:{{ state_attr(‘light.[enitity_id of target light]’, ‘brightness’) |int }}},“colorDelta”:{“hue”:0,“saturation”:0,“brightness”:0},“speed”:255}}’

As this is very messy, I will give an example:

  • The IP of the TV being 192.168.1.101
  • The entity_id of the light I want the tv to copy is light.bedroom_light
  • The scaling of the HS(&B) values within home assistant are:
    Hue:(0-360)
    Saturation:(0-100),
    Brightness:(0-255)
  • The scaling of the HS(&B) values for the Philips TV (yours will most likely be the same) are:
    Hue:(0-255),
    Saturation:(0-255),
    Brightness:(0-255)

The following shell_command will be added to config.yaml:

curl -X POST --digest --insecure -u XXXXXXXXXXXX https://192.168.1.101:1926/6/ambilight/currentconfiguration -d '{“styleName”:“FOLLOW_COLOR”,“isExpert”:true,“algorithm”:“MANUAL_HUE”,“colorSettings”:{“color”:{“hue”:{{ ((state_attr('light.bedroom_light, ‘hs_color’)[0]) * (255/360)) |int }},“saturation”:{{ ((state_attr('light.bedroom_light, ‘hs_color’)[1]) * (255/100)) |int }},“brightness”:{{ state_attr(‘light.bedroom_light, ‘brightness’) |int }}},“colorDelta”:{“hue”:0,“saturation”:0,“brightness”:0},“speed”:255}}’

Then, an automation can be created to run the shell command any time the state of the target light is changed, thus keeping the ambilights in sync:

– alias: Follow Room Colour
trigger:
– entity_id: light.bedroom_light
platform: state
condition:
– condition: state
entity_id: switch.ambilight
state: ‘on’
action:
– service: shell_command.set_ambilights_to_strips

This automation can be turned on and off, to allow the ambilights to continue following video or audio if this function is not needed. I did look into expanding this into a fully functional RGB light from within Home Assistant, but could not find any shell_command light component that would work (although there seems to be an MQTT light component which may be similar), if anyone knows of a more elegant solution to this I would love to hear it, otherwise, this is a very nice way of tying in your TV to your current RGB lights, controlled by Home Assistant


#74

Thank you for your job! Really nice feature!
Do you know if it would be possible to do it reversed, i.e. get the current colour of the TV ambilight and match a colour of any RGB lamp via automation?


#75

Unfortunately not at the moment, as this is automation based, and simply sends a command to match the target light when it is changed, I am starting to look at creating a light component for the ambilights separately, which would be much better, as it would work both ways as you say, and the ambilights could then be controlled by any automation or integration such as alexa, google home and siri etc, I have never written a component so I cant promise anything, but ill be sure to post it here if I get anywhere :slight_smile:


#76

First of all good job!
Guys python absolute newbie here but learning.
Is there a way to add the channels list to this? For the 2016 Philips tv’s the channel list is found with https://tvip:1926/6/channeldb/tv/channelLists/all

I’ve been trying to use the current component from here but cannot understand the code nor how the list can be obtained.
Tried similarly to the current channel function but with no success.

I really want to learn. Anyone please?
Thank you


#77

Guys,

I managed to hack my way to what I wanted. Now Channel list is available e you can choose your tv channel in the source list.

Here is the file philips_2016_b.xml (8.6 KB)
Just change .xml to .py

Lots of doubts in my mind still as I cannot understand much of the how to build a component, specially from ground up.


#78

Guys,

here follows the work in progress regarding this TV.

My model is 32PFS6402/12.

Here is the file philips_2016_b1.xml (10.0 KB)
Just change .xml to .py
Now the component can differentiate between watching TV and using an android APP.
When watching TV, forward and rewind buttons change the channel.
Channel title includes program number.
When using an APP, the name of the app appears in the media title.
Some warnings that appeared in the logs about volume error don’t appear anymore.
Cannot find a way to change from TV to APP. Tried several ways to POST but with no luck.
To change from APP to TV simply choose a channel in the source list.


Philips Android TV Ambilights (Light) component
#79

Great work :smiley: Love it.
It would be great to filter channels list with favorites “channeldb/tv/favoriteLists/allter” and maybe support for ambilight, but it require at least few endpoints “ambilight/power”, “ambilight/currentconfiguration” and probably something else, unfortunately I’m don’t know. I tried to do it myself but I failed :frowning:
And i have a question, do you have list with all working endpoints or do you know how to get it?


#80

Thanks.

Don’t know them. Found a couple by trial and error. for instance post the json load for activities/tv and the channel will change but do the same logic for applications/current and no such luck.

I think that it will be something in the lines of activites/app or apps but these no luck. Having the right one with be a charm.


#81

I found some endpoint in https://github.com/suborb/philips_android_tv/blob/master/philips.py
It is possible to find some more by Reverse Engineering Philips app from android https://play.google.com/store/apps/details?id=com.tpvision.philipstvapp
I have never done that unfortunately :frowning: