Skip to content

Commit f56fa2f

Browse files
Rob HofmannRob Hofmann
authored andcommitted
Added all files
1 parent e5f23cc commit f56fa2f

File tree

9 files changed

+383
-1
lines changed

9 files changed

+383
-1
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: ''
5+
labels: ''
6+
assignees: ''
7+
8+
---
9+
10+
**Describe the bug**
11+
A clear and concise description of what the bug is.
12+
13+
**To Reproduce**
14+
Steps to reproduce the behavior:
15+
1. Go to '...'
16+
2. Click on '....'
17+
3. Scroll down to '....'
18+
4. See error
19+
20+
**Configuration**
21+
Share your YAML here
22+
23+
**Expected behavior**
24+
A clear and concise description of what you expected to happen.
25+
26+
**Screenshots**
27+
If applicable, add screenshots to help explain your problem.
28+
29+
**Platform:**
30+
- OS: [e.g. HASSIO, Hassbian]
31+
- Browser [e.g. chrome, safari]
32+
- Version [e.g. 0.92.1]
33+
34+
**Additional context**
35+
Add any other context about the problem here.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Feature request
3+
about: Suggest an idea for this project
4+
title: ''
5+
labels: ''
6+
assignees: ''
7+
8+
---
9+
10+
**Is your feature request related to a problem? Please describe.**
11+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12+
13+
**Describe the solution you'd like**
14+
A clear and concise description of what you want to happen.
15+
16+
**Describe alternatives you've considered**
17+
A clear and concise description of any alternative solutions or features you've considered.
18+
19+
**Additional context**
20+
Add any other context or screenshots about the feature request here.

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,49 @@
11
# HomeAssistant-PhilipsAndroid2014
2-
Component for Philips TV's running Android which are built between 2014 and 2016
2+
Custom component for Philips TV's running Android which are built between 2014 and 2016. Written in Python3 for Home Assistant.
3+
NOTE: Credits fully go to SirGilbot which made this modification based on the original Philips HASS component.
4+
5+
I'll be trying to get this module available through HACS for easy installation.
6+
7+
Tested on:
8+
* Philips 55PUS9109/12 (2014)
9+
* Home-Assistant
10+
- 0.95.x
11+
12+
**If you are experiencing issues please be sure to provide details about your device, Home Assistant version and what exactly went wrong.**
13+
14+
**Sources used:**
15+
- https://community.home-assistant.io/t/philips-android-tv-component/17749/62 (CREDITS GO 100% TO THIS GUY)
16+
17+
## HACS
18+
This component will be submitted to be added to HACS asap.
19+
20+
## Custom Component Installation
21+
!!! PLEASE NOTE !!!: Don't use this method if you are using HACS.
22+
1. Copy the custom_components folder to your own hassio /config folder.
23+
24+
2. In the root of your /config folder, create a file called mediaplayers.yaml
25+
26+
```yaml
27+
- platform: philips_2014
28+
name: My Philips TV
29+
host: <ip of your TV>
30+
mac: <mac address of your TV>
31+
```
32+
33+
3. In your configuration.yaml add the following:
34+
35+
```yaml
36+
media_player: !include mediaplayers.yaml
37+
```
38+
39+
4. OPTIONAL: Add info logging to this component (to see if/how it works)
40+
41+
```yaml
42+
logger:
43+
default: error
44+
logs:
45+
custom_components.philips_2014: debug
46+
custom_components.philips_2014.media_player: debug
47+
```
48+
49+
5. Restart Home Assistant and enjoy!

configuration.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Optional: Enable info logging for the TV Component
2+
logger:
3+
default: error
4+
logs:
5+
custom_components.philips_2014: debug
6+
custom_components.philips_2014.media_player: debug
7+
8+
media_player: !include mediaplayers.yaml

custom_components/philips_2014/__init__.py

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"domain": "philips_2014",
3+
"name": "Philips TV 2014-2016",
4+
"documentation": "https://github.com/RobHofmann/HomeAssistant-PhilipsAndroid2014",
5+
"dependencies": [],
6+
"codeowners": ["@SirGilbot", "@robhofmann"],
7+
"requirements": []
8+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
"""
2+
Media Player component to integrate TVs exposing the Joint Space API.
3+
Updated to support Android-based Philips TVs manufactured from 2014 but before 2016.
4+
"""
5+
import homeassistant.helpers.config_validation as cv
6+
import argparse
7+
import json
8+
import random
9+
import requests
10+
import string
11+
import sys
12+
import voluptuous as vol
13+
import time
14+
import wakeonlan
15+
16+
from base64 import b64encode,b64decode
17+
from Crypto.Hash import SHA, HMAC
18+
from datetime import timedelta, datetime
19+
from homeassistant.components.media_player import (PLATFORM_SCHEMA, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, MediaPlayerDevice)
20+
from homeassistant.const import (
21+
CONF_HOST, CONF_MAC, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, STATE_OFF, STATE_ON, STATE_UNKNOWN)
22+
from homeassistant.util import Throttle
23+
from requests.auth import HTTPDigestAuth
24+
25+
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
26+
27+
SUPPORT_PHILIPS_2014 = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE
28+
29+
DEFAULT_DEVICE = 'default'
30+
DEFAULT_HOST = '127.0.0.1'
31+
DEFAULT_MAC = 'aa:aa:aa:aa:aa:aa'
32+
DEFAULT_USER = 'user'
33+
DEFAULT_PASS = 'pass'
34+
DEFAULT_NAME = 'Philips TV'
35+
BASE_URL = 'http://{0}:1925/5/{1}'
36+
TIMEOUT = 5.0
37+
CONNFAILCOUNT = 5
38+
39+
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
40+
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
41+
vol.Required(CONF_MAC, default=DEFAULT_MAC): cv.string,
42+
vol.Optional(CONF_USERNAME, default=DEFAULT_USER): cv.string,
43+
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASS): cv.string,
44+
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
45+
})
46+
47+
# pylint: disable=unused-argument
48+
def setup_platform(hass, config, add_devices, discovery_info=None):
49+
"""Set up the Philips 2016+ TV platform."""
50+
name = config.get(CONF_NAME)
51+
host = config.get(CONF_HOST)
52+
mac = config.get(CONF_MAC)
53+
user = config.get(CONF_USERNAME)
54+
password = config.get(CONF_PASSWORD)
55+
tvapi = PhilipsTVBase(host, mac, user, password)
56+
add_devices([PhilipsTV(tvapi, name)])
57+
58+
class PhilipsTV(MediaPlayerDevice):
59+
"""Representation of a 2014-2015 Philips TV exposing the JointSpace API but not authentication."""
60+
61+
def __init__(self, tv, name):
62+
"""Initialize the TV."""
63+
self._tv = tv
64+
self._name = name
65+
self._state = STATE_UNKNOWN
66+
self._min_volume = None
67+
self._max_volume = None
68+
self._volume = None
69+
self._muted = False
70+
self._connfail = 0
71+
72+
@property
73+
def name(self):
74+
"""Return the device name."""
75+
return self._name
76+
77+
@property
78+
def should_poll(self):
79+
"""Device should be polled."""
80+
return True
81+
82+
@property
83+
def supported_features(self):
84+
"""Flag media player features that are supported."""
85+
return SUPPORT_PHILIPS_2014
86+
87+
@property
88+
def state(self):
89+
"""Get the device state. An exception means OFF state."""
90+
return self._state
91+
92+
@property
93+
def volume_level(self):
94+
"""Volume level of the media player (0..1)."""
95+
return self._volume
96+
97+
@property
98+
def is_volume_muted(self):
99+
"""Boolean if volume is currently muted."""
100+
return self._muted
101+
102+
def turn_on(self):
103+
"""Turn on the device."""
104+
i = 0
105+
while ((not self._tv.on) and (i < 15)):
106+
self._tv.wol()
107+
self._tv.sendKey('Standby')
108+
time.sleep(2)
109+
i += 1
110+
if self._tv.on:
111+
self._state = STATE_OFF
112+
113+
def turn_off(self):
114+
"""Turn off the device."""
115+
i = 0
116+
while ((self._tv.on) and (i < 15)):
117+
self._tv.sendKey('Standby')
118+
time.sleep(0.5)
119+
i += 1
120+
if not self._tv.on:
121+
self._state = STATE_OFF
122+
123+
def volume_up(self):
124+
"""Send volume up command."""
125+
self._tv.sendKey('VolumeUp')
126+
if not self._tv.on:
127+
self._state = STATE_OFF
128+
129+
def volume_down(self):
130+
"""Send volume down command."""
131+
self._tv.sendKey('VolumeDown')
132+
if not self._tv.on:
133+
self._state = STATE_OFF
134+
135+
def mute_volume(self, mute):
136+
"""Send mute command."""
137+
self._tv.sendKey('Mute')
138+
if not self._tv.on:
139+
self._state = STATE_OFF
140+
141+
@property
142+
def media_title(self):
143+
"""Title of current playing media."""
144+
return None
145+
146+
@Throttle(MIN_TIME_BETWEEN_UPDATES)
147+
def update(self):
148+
"""Get the latest data and update device state."""
149+
self._tv.update()
150+
self._min_volume = self._tv.min_volume
151+
self._max_volume = self._tv.max_volume
152+
self._volume = self._tv.volume
153+
self._muted = self._tv.muted
154+
if self._tv.on:
155+
self._state = STATE_ON
156+
else:
157+
self._state = STATE_OFF
158+
159+
class PhilipsTVBase(object):
160+
def __init__(self, host, mac, user, password):
161+
self._host = host
162+
self._mac = mac
163+
self._user = user
164+
self._password = password
165+
self._connfail = 0
166+
self.on = None
167+
self.name = None
168+
self.min_volume = None
169+
self.max_volume = None
170+
self.volume = None
171+
self.muted = None
172+
self.sources = None
173+
self.source_id = None
174+
self.channels = None
175+
self.channel_id = None
176+
177+
def _getReq(self, path):
178+
try:
179+
if self._connfail:
180+
self._connfail -= 1
181+
return None
182+
resp = requests.get(BASE_URL.format(self._host, path), timeout=TIMEOUT)
183+
self.on = True
184+
return json.loads(resp.text)
185+
except requests.exceptions.RequestException as err:
186+
self._connfail = CONNFAILCOUNT
187+
self.on = False
188+
return None
189+
190+
def _postReq(self, path, data):
191+
try:
192+
if self._connfail:
193+
self._connfail -= 1
194+
return False
195+
resp = requests.post(BASE_URL.format(self._host, path), data=json.dumps(data))
196+
self.on = True
197+
if resp.status_code == 200:
198+
return True
199+
else:
200+
return False
201+
except requests.exceptions.RequestException as err:
202+
self._connfail = CONNFAILCOUNT
203+
self.on = False
204+
return False
205+
206+
def update(self):
207+
self.getName()
208+
self.getAudiodata()
209+
210+
def getName(self):
211+
r = self._getReq('system/name')
212+
if r:
213+
self.name = r['name']
214+
215+
def getAudiodata(self):
216+
audiodata = self._getReq('audio/volume')
217+
if audiodata:
218+
self.min_volume = int(audiodata['min'])
219+
self.max_volume = int(audiodata['max'])
220+
self.volume = audiodata['current']
221+
self.muted = audiodata['muted']
222+
else:
223+
self.min_volume = None
224+
self.max_volume = None
225+
self.volume = None
226+
self.muted = None
227+
228+
def setVolume(self, level):
229+
if level:
230+
if self.min_volume != 0 or not self.max_volume:
231+
self.getAudiodata()
232+
if not self.on:
233+
return
234+
try:
235+
targetlevel = int(level)
236+
except ValueError:
237+
return
238+
if targetlevel < self.min_volume + 1 or targetlevel > self.max_volume:
239+
return
240+
self._postReq('audio/volume', {'current': targetlevel, 'muted': False})
241+
self.volume = targetlevel
242+
243+
def sendKey(self, key):
244+
self._postReq('input/key', {'key': key})
245+
246+
def wol(self):
247+
wakeonlan.send_magic_packet(self._mac)

info.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Custom component for Philips TV's running Android which are built between 2014 and 2016
2+
3+
## Component configuration
4+
5+
To configure this component, add the following configuration to your configuration.yaml (replacing the placeholders).
6+
7+
```
8+
media_player:
9+
- platform: philips_2014
10+
name: My Philips TV
11+
host: <ip of your TV>
12+
mac: <mac address of your TV>
13+
```

mediaplayers.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- platform: philips_2014
2+
name: My Philips TV
3+
host: <ip of your TV>
4+
mac: <mac address of your TV>

0 commit comments

Comments
 (0)