-
-
Notifications
You must be signed in to change notification settings - Fork 34.1k
Add support for the Unitymedia Horizon HD Recorder #14275
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
Conversation
from einder import Client, keys | ||
from einder.exceptions import AuthenticationError | ||
|
||
host = config.get(CONF_HOST) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please access the config (host, name, port) directly (config[CONF_HOST]
) if it's safe.
client = Client(host, port=port) | ||
except (AuthenticationError, OSError) as msg: | ||
_LOGGER.error("Connection to %s at %s failed: %s", name, host, msg) | ||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could raise a PlatformNotReady exception if there is a chance (OSError
?) for recovery. The AuthenticationError should return False
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing is checking this return value. Either just return
or raise PlatformNotReady
as recommended above.
self._name = name | ||
self._state = False | ||
self._keys = keys | ||
self._source_list = {1: 'Das Erste HD', 2: 'ZDF HD', 3: 'RTL HD', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not happy about using the source_list
as channel list. I've seen this multiple times now. This is just my personal feeling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this dictionary of channels belongs in home assistant code. Why do we need it? Looks like we send the channel ID and not the channel name anyway. This could be moved to the documentation, and instead of using source select to select channel, just use play media service.
self._name = name | ||
self._state = False | ||
self._keys = keys | ||
self._source_list = {1: 'Das Erste HD', 2: 'ZDF HD', 3: 'RTL HD', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this dictionary of channels belongs in home assistant code. Why do we need it? Looks like we send the channel ID and not the channel name anyway. This could be moved to the documentation, and instead of using source select to select channel, just use play media service.
|
||
def play_media(self, media_type, media_id, **kwargs): | ||
"""Play media / switch to channel.""" | ||
if MEDIA_TYPE_CHANNEL == media_type and isinstance(int(media_id), int): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need the type check after the copy to integer?
in self._source_list.items() | ||
if v == source] | ||
|
||
if digits is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When can this happen?
def _select_channel(self, channel): | ||
"""Select a channel (taken from einder library, thx).""" | ||
for i in str(channel): | ||
key = int(i) + 0xe300 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like too much protocol specific code. It should probably be part of the interface library.
self._client.authorize() | ||
except (AuthenticationError, OSError) as msg: | ||
_LOGGER.error("Connection to %s failed: %s", self._name, msg) | ||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing is checking this return value.
client = Client(host, port=port) | ||
except (AuthenticationError, OSError) as msg: | ||
_LOGGER.error("Connection to %s at %s failed: %s", name, host, msg) | ||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing is checking this return value. Either just return
or raise PlatformNotReady
as recommended above.
return self._name | ||
|
||
@property | ||
def client(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this. It's not needed and not used.
return self._state | ||
|
||
@property | ||
def keys(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
I will look at this in the next 1-2 weekends :) Thanks for the comments/tips! |
@syssi & @MartinHjelmare thanks for your reviews, all mentioned things should be fixed now. |
}) | ||
|
||
|
||
# pylint: disable=unused-argument |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already globally disabled.
"""Initialize the remote.""" | ||
self._client = client | ||
self._name = name | ||
self._state = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initialize unknown state as None
.
except OSError as msg: | ||
# occurs if horizon box is offline | ||
_LOGGER.error("Reconnect to %s failed: %s", self._name, msg) | ||
raise PlatformNotReady |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're not inside setup_platform
anymore here, so raising PlatformNotReady
doesn't work. Probably just return
.
thanks for your reviews and your help @MartinHjelmare!! Wont make these mistakes again in my next PR ;) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
Can be merged when build passes. |
) * added new platform for the Unitymedia Horizon HD Recorder * improve connection handling of the horizon platform * remove unneeded parameters and fix spelling in the horizon platform * abort or raise exception if connection to the device could not be established * remove channel/source list and SELECT_SOURCE feature * remove useless type check after cast and use a try block instead * abort or raise exception if reconnect to device fails * remove protocol specific code and restructure sending logic accordingly * fix indentation to be pep8 complaint * remove unused methods/properties * fix unnecessary pylint commands and use a return to abert outside of setup_platform * directly access config values
Description:
A new platform for the Unitymedia Horizon HD Recorder sold by Unitymedia in Germany. The channel list is hardcoded currently but I already reached out to Unitymedia to get a parseable list. As this list doesn't change often, I think that's not a problem. Also working on an async version of this but as this involves the underlying library (einder), this takes some time.
Related issue (if applicable): -
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#5303
Example entry for
configuration.yaml
(if applicable):Checklist:
tox
. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
REQUIREMENTS
variable (example).requirements_all.txt
by runningscript/gen_requirements_all.py
..coveragerc
.If the code does not interact with devices: