Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple devices for PS4 component #21302

Merged
merged 48 commits into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
affe7b8
Support multiple devices.
ktnrg45 Feb 21, 2019
38ef7c1
Revert "Support multiple devices."
ktnrg45 Feb 21, 2019
7ccdd12
Support multiple devices
ktnrg45 Feb 21, 2019
4cbedd8
Bump to 0.3.3
ktnrg45 Feb 21, 2019
a9ab8ab
bump 0.3.4
ktnrg45 Feb 21, 2019
755f0d8
Add tests for multiple devices.
ktnrg45 Feb 22, 2019
5dcbeec
Update Requirements
ktnrg45 Feb 22, 2019
de40c40
Update config_flow.py
ktnrg45 Feb 22, 2019
5f09b99
Update config_flow.py
ktnrg45 Feb 22, 2019
a7c04c9
fixed typo
ktnrg45 Feb 22, 2019
75a95f3
Reordered functions
ktnrg45 Feb 22, 2019
7cacfd4
Added multiple flow implementation test.
ktnrg45 Feb 22, 2019
1d7f929
fix
ktnrg45 Feb 22, 2019
2aee076
typo
ktnrg45 Feb 22, 2019
a1e3d18
fix tests
ktnrg45 Feb 23, 2019
0d0e202
bump 0.4.0
ktnrg45 Feb 24, 2019
a899879
Bump 0.4.0
ktnrg45 Feb 25, 2019
cc862ea
0.4.0
ktnrg45 Feb 25, 2019
1fd0428
bump version
ktnrg45 Feb 25, 2019
8eedd98
bump version
ktnrg45 Feb 25, 2019
e82ecf1
bump version
ktnrg45 Feb 25, 2019
98adca8
Add keep alive feature with multiple devices
ktnrg45 Feb 25, 2019
ff4b315
bump version
ktnrg45 Feb 25, 2019
3b64e61
bump version
ktnrg45 Feb 25, 2019
948f843
bump version
ktnrg45 Feb 25, 2019
e620545
bump 0.4.7
ktnrg45 Feb 27, 2019
16aef13
bump 0.4.7
ktnrg45 Feb 27, 2019
211a147
bump 0.4.7
ktnrg45 Feb 27, 2019
9defe0c
Edited tests.
ktnrg45 Feb 27, 2019
9609457
bump/pylint
ktnrg45 Feb 27, 2019
722ab9b
pylint
ktnrg45 Feb 27, 2019
e117e68
bump/pylint
ktnrg45 Feb 27, 2019
c8bd1c9
bump/pylint
ktnrg45 Feb 27, 2019
5fe447f
Change to add additional entry
ktnrg45 Feb 27, 2019
a26211a
Changed to multiple entries
ktnrg45 Feb 27, 2019
d0a587f
pylint
ktnrg45 Feb 27, 2019
592abaf
Corrections to manage multiple devices.
ktnrg45 Mar 1, 2019
3bc45fa
lint
ktnrg45 Mar 1, 2019
45c496c
comments
ktnrg45 Mar 1, 2019
46ae86b
Removed redundant for loop
ktnrg45 Mar 1, 2019
fffaa11
Shorthand correction
ktnrg45 Mar 3, 2019
38003cb
Remove reference to private object
ktnrg45 Mar 4, 2019
88373b3
Test fix
ktnrg45 Mar 4, 2019
e3bd44d
Revert changes. Test failure.
ktnrg45 Mar 4, 2019
d6f69f0
Test fix
ktnrg45 Mar 4, 2019
f718c51
test fix
ktnrg45 Mar 4, 2019
3ee81cc
unindent assertions
ktnrg45 Mar 4, 2019
0066a91
pylint
ktnrg45 Mar 4, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/ps4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

_LOGGER = logging.getLogger(__name__)

REQUIREMENTS = ['pyps4-homeassistant==0.3.0']
REQUIREMENTS = ['pyps4-homeassistant==0.4.8']


async def async_setup(hass, config):
Expand Down
22 changes: 18 additions & 4 deletions homeassistant/components/ps4/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,16 @@ def __init__(self):

async def async_step_user(self, user_input=None):
"""Handle a user config flow."""
# Abort if device is configured.
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason='devices_configured')

# Check if able to bind to ports: UDP 987, TCP 997.
ports = PORT_MSG.keys()
failed = await self.hass.async_add_executor_job(
self.helper.port_bind, ports)
if failed in ports:
reason = PORT_MSG[failed]
return self.async_abort(reason=reason)
# Skip Creds Step if a device is configured.
if self.hass.config_entries.async_entries(DOMAIN):
return await self.async_step_link()
return await self.async_step_creds()

async def async_step_creds(self, user_input=None):
Expand Down Expand Up @@ -78,6 +77,21 @@ async def async_step_link(self, user_input=None):
device_list = [
device['host-ip'] for device in devices]

# If entry exists check that devices found aren't configured.
if self.hass.config_entries.async_entries(DOMAIN):
_entries = []
for entry in self.hass.config_entries.async_entries(DOMAIN):
_entries.append(entry)
for _entry in _entries:
ktnrg45 marked this conversation as resolved.
Show resolved Hide resolved
conf_devices = _entry.data['devices']
for c_device in conf_devices:
if c_device['host'] in device_list:
# Remove configured device from search list.
device_list.remove(c_device['host'])
# If list is empty then all devices are configured.
if not device_list:
return self.async_abort(reason='devices_configured')

# Login to PS4 with user data.
if user_input is not None:
self.region = user_input[CONF_REGION]
Expand Down
26 changes: 24 additions & 2 deletions homeassistant/components/ps4/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def __init__(self, name, host, region, ps4, games_file):
self._retry = 0
self._info = None
self._unique_id = None
self._power_on = False

async def async_added_to_hass(self):
"""Subscribe PS4 events."""
Expand All @@ -144,6 +145,7 @@ def update(self):
try:
status = self._ps4.get_status()
if self._info is None:
# Add entity to registry
self.get_device_info(status)
self._games = self.load_games()
if self._games is not None:
Expand All @@ -153,6 +155,17 @@ def update(self):
if status is not None:
self._retry = 0
if status.get('status') == 'Ok':
# Check if only 1 device in Hass.
if len(self.hass.data[PS4_DATA].devices) == 1:
# Enable keep alive feature for PS4 Connection.
# Only 1 device is supported, Since have to use port 997.
self._ps4.keep_alive = True
else:
self._ps4.keep_alive = False
if self._power_on is True:
ktnrg45 marked this conversation as resolved.
Show resolved Hide resolved
# Auto Login after Turn On.
self._ps4.open()
self._power_on = False
title_id = status.get('running-app-titleid')
name = status.get('running-app-name')
if title_id and name is not None:
Expand Down Expand Up @@ -268,6 +281,10 @@ def get_device_info(self, status):
}
self._unique_id = status['host-id']

async def async_will_remove_from_hass(self):
"""Remove Entity from Hass."""
self.hass.data[PS4_DATA].devices.remove(self)

@property
def device_info(self):
"""Return information about the device."""
Expand Down Expand Up @@ -346,15 +363,16 @@ def turn_off(self):

def turn_on(self):
"""Turn on the media player."""
self._power_on = True
self._ps4.wakeup()

def media_pause(self):
"""Send keypress ps to return to menu."""
self._ps4.remote_control('ps')
self.send_remote_control('ps')

def media_stop(self):
"""Send keypress ps to return to menu."""
self._ps4.remote_control('ps')
self.send_remote_control('ps')

def select_source(self, source):
"""Select input source."""
Expand All @@ -369,4 +387,8 @@ def select_source(self, source):

def send_command(self, command):
"""Send Button Command."""
self.send_remote_control(command)

def send_remote_control(self, command):
"""Send RC command."""
self._ps4.remote_control(command)
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ pypoint==1.1.1
pypollencom==2.2.2

# homeassistant.components.ps4
pyps4-homeassistant==0.3.0
pyps4-homeassistant==0.4.8

# homeassistant.components.qwikswitch
pyqwikswitch==0.8
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ pyopenuv==1.0.4
pyotp==2.2.6

# homeassistant.components.ps4
pyps4-homeassistant==0.3.0
pyps4-homeassistant==0.4.8

# homeassistant.components.qwikswitch
pyqwikswitch==0.8
Expand Down
171 changes: 165 additions & 6 deletions tests/components/ps4/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Define tests for the PlayStation 4 config flow."""
from unittest.mock import patch

from homeassistant import data_entry_flow
from homeassistant import config_entries, data_entry_flow
ktnrg45 marked this conversation as resolved.
Show resolved Hide resolved
from homeassistant.components import ps4
from homeassistant.components.ps4.const import (
DEFAULT_NAME, DEFAULT_REGION)
Expand All @@ -14,20 +14,32 @@
MOCK_CODE = '12345678'
MOCK_CREDS = '000aa000'
MOCK_HOST = '192.0.0.0'
MOCK_HOST_ADDITIONAL = '192.0.0.1'
MOCK_DEVICE = {
CONF_HOST: MOCK_HOST,
CONF_NAME: DEFAULT_NAME,
CONF_REGION: DEFAULT_REGION
}
MOCK_DEVICE_ADDITIONAL = {
CONF_HOST: MOCK_HOST_ADDITIONAL,
CONF_NAME: DEFAULT_NAME,
CONF_REGION: DEFAULT_REGION
}
MOCK_CONFIG = {
CONF_IP_ADDRESS: MOCK_HOST,
CONF_NAME: DEFAULT_NAME,
CONF_REGION: DEFAULT_REGION,
CONF_CODE: MOCK_CODE
}
MOCK_CONFIG_ADDITIONAL = {
CONF_IP_ADDRESS: MOCK_HOST_ADDITIONAL,
CONF_NAME: DEFAULT_NAME,
CONF_REGION: DEFAULT_REGION,
CONF_CODE: MOCK_CODE
}
MOCK_DATA = {
CONF_TOKEN: MOCK_CREDS,
'devices': MOCK_DEVICE
'devices': [MOCK_DEVICE]
}
MOCK_UDP_PORT = int(987)
MOCK_TCP_PORT = int(997)
Expand All @@ -38,6 +50,11 @@ async def test_full_flow_implementation(hass):
flow = ps4.PlayStation4FlowHandler()
flow.hass = hass

# Init config manager.
manager = config_entries.ConfigEntries(hass, {})
manager._entries = [] # noqa: pylint: disable=protected-access
hass.config_entries = manager
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved

# User Step Started, results in Step Creds
with patch('pyps4_homeassistant.Helper.port_bind',
return_value=None):
Expand Down Expand Up @@ -65,6 +82,110 @@ async def test_full_flow_implementation(hass):
assert result['data']['devices'] == [MOCK_DEVICE]
assert result['title'] == MOCK_TITLE

# Add entry using result data.
mock_data = {
CONF_TOKEN: result['data'][CONF_TOKEN],
'devices': result['data']['devices']}
entry = MockConfigEntry(domain=ps4.DOMAIN, data=mock_data)
entry.add_to_manager(manager)

# Check if entry exists.
assert len(manager.async_entries()) == 1
# Check if there is a device config in entry.
assert len(entry.data['devices']) == 1


async def test_multiple_flow_implementation(hass):
"""Test multiple device flows."""
flow = ps4.PlayStation4FlowHandler()
flow.hass = hass

# Init config manager.
manager = config_entries.ConfigEntries(hass, {})
manager._entries = [] # noqa: pylint: disable=protected-access
ktnrg45 marked this conversation as resolved.
Show resolved Hide resolved
hass.config_entries = manager

# User Step Started, results in Step Creds
with patch('pyps4_homeassistant.Helper.port_bind',
return_value=None):
result = await flow.async_step_user()
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result assertions can be moved out of the context manager indentation.

assert result['step_id'] == 'creds'

# Step Creds results with form in Step Link.
with patch('pyps4_homeassistant.Helper.get_creds',
return_value=MOCK_CREDS), \
patch('pyps4_homeassistant.Helper.has_devices',
return_value=[{'host-ip': MOCK_HOST},
{'host-ip': MOCK_HOST_ADDITIONAL}]):
result = await flow.async_step_creds({})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'link'

# User Input results in created entry.
with patch('pyps4_homeassistant.Helper.link',
return_value=(True, True)), \
patch('pyps4_homeassistant.Helper.has_devices',
return_value=[{'host-ip': MOCK_HOST},
{'host-ip': MOCK_HOST_ADDITIONAL}]):
result = await flow.async_step_link(MOCK_CONFIG)
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result['data'][CONF_TOKEN] == MOCK_CREDS
assert result['data']['devices'] == [MOCK_DEVICE]
assert result['title'] == MOCK_TITLE

await hass.async_block_till_done()
ktnrg45 marked this conversation as resolved.
Show resolved Hide resolved

# Add entry using result data.
mock_data = {
CONF_TOKEN: result['data'][CONF_TOKEN],
'devices': result['data']['devices']}
entry = MockConfigEntry(domain=ps4.DOMAIN, data=mock_data)
entry.add_to_manager(manager)

# Check if entry exists.
assert len(manager.async_entries()) == 1
# Check if there is a device config in entry.
assert len(entry.data['devices']) == 1

# Test additional flow.

# User Step Started, results in Step Link:
with patch('pyps4_homeassistant.Helper.port_bind',
return_value=None), \
patch('pyps4_homeassistant.Helper.has_devices',
return_value=[{'host-ip': MOCK_HOST},
{'host-ip': MOCK_HOST_ADDITIONAL}]):
result = await flow.async_step_user()
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'link'

# Step Link
with patch('pyps4_homeassistant.Helper.has_devices',
return_value=[{'host-ip': MOCK_HOST},
{'host-ip': MOCK_HOST_ADDITIONAL}]), \
patch('pyps4_homeassistant.Helper.link',
return_value=(True, True)):
result = await flow.async_step_link(user_input=MOCK_CONFIG_ADDITIONAL)
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result['data'][CONF_TOKEN] == MOCK_CREDS
assert len(result['data']['devices']) == 1
ktnrg45 marked this conversation as resolved.
Show resolved Hide resolved
assert result['title'] == MOCK_TITLE

mock_data = {
CONF_TOKEN: result['data'][CONF_TOKEN],
'devices': result['data']['devices']}

# Update config entries with result data
entry = MockConfigEntry(domain=ps4.DOMAIN, data=mock_data)
entry.add_to_manager(manager)
manager.async_update_entry(entry)

# Check if there are 2 entries.
assert len(manager.async_entries()) == 2
# Check if there is device config in entry.
assert len(entry.data['devices']) == 1


async def test_port_bind_abort(hass):
"""Test that flow aborted when cannot bind to ports 987, 997."""
Expand All @@ -87,14 +208,52 @@ async def test_port_bind_abort(hass):


async def test_duplicate_abort(hass):
"""Test that Flow aborts when already configured."""
"""Test that Flow aborts when found devices already configured."""
MockConfigEntry(domain=ps4.DOMAIN, data=MOCK_DATA).add_to_hass(hass)
flow = ps4.PlayStation4FlowHandler()
flow.hass = hass

result = await flow.async_step_user(user_input=None)
assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
assert result['reason'] == 'devices_configured'
with patch('pyps4_homeassistant.Helper.has_devices',
return_value=[{'host-ip': MOCK_HOST}]):
result = await flow.async_step_link(user_input=None)
assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
assert result['reason'] == 'devices_configured'


async def test_additional_device(hass):
"""Test that Flow can configure another device."""
flow = ps4.PlayStation4FlowHandler()
flow.hass = hass
flow.creds = MOCK_CREDS

# Init config manager.
manager = config_entries.ConfigEntries(hass, {})
manager._entries = [] # noqa: pylint: disable=protected-access
hass.config_entries = manager

# Mock existing entry.
entry = MockConfigEntry(domain=ps4.DOMAIN, data=MOCK_DATA)
entry.add_to_manager(manager)
# Check that only 1 entry exists
assert len(manager.async_entries()) == 1

with patch('pyps4_homeassistant.Helper.has_devices',
return_value=[{'host-ip': MOCK_HOST},
{'host-ip': MOCK_HOST_ADDITIONAL}]), \
patch('pyps4_homeassistant.Helper.link',
return_value=(True, True)):
result = await flow.async_step_link(user_input=MOCK_CONFIG_ADDITIONAL)
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result['data'][CONF_TOKEN] == MOCK_CREDS
assert len(result['data']['devices']) == 1
ktnrg45 marked this conversation as resolved.
Show resolved Hide resolved
assert result['title'] == MOCK_TITLE

# Add New Entry
entry = MockConfigEntry(domain=ps4.DOMAIN, data=MOCK_DATA)
entry.add_to_manager(manager)

# Check that there are 2 entries
assert len(manager.async_entries()) == 2


async def test_no_devices_found_abort(hass):
Expand Down