-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
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
HDMI CEC - support for devices and commands #4781
Conversation
@konikvranik, thanks for your PR! By analyzing the history of the files in this pull request, we identified @fabaff, @happyleavesaoc and @balloob to be potential reviewers. |
_LOGGER.info("Creating %s switch %d", DOMAIN, logical) | ||
self._state = False | ||
CecDevice.__init__(self, hass, cec_client, logical) | ||
self.entity_id = "%s.%s_%s" % (DOMAIN, 'hdmi', hex(self._logical_address)[2:]) |
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.
line too long (86 > 79 characters)
if ATTR_NEW in discovery_info: | ||
_LOGGER.info("Setting up devices %s", discovery_info[ATTR_NEW]) | ||
add_devices(CecSwitch(hass, CEC_CLIENT, device) for device in | ||
filter(lambda x: x not in DEVICE_PRESENCE or not DEVICE_PRESENCE[x], discovery_info[ATTR_NEW])) |
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.
line too long (115 > 79 characters)
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 don't use filter
. It does not give readable code.
""" | ||
import logging | ||
|
||
from homeassistant.components.hdmi_cec import CecDevice, CEC_CLIENT, ATTR_NEW, DEVICE_PRESENCE |
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.
line too long (94 > 79 characters)
""" | ||
import logging | ||
|
||
from homeassistant.components.hdmi_cec import CecDevice, CEC_DEVICES, CEC_CLIENT |
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.
line too long (80 > 79 characters)
def cec_key_press_callback(self, key, duration): | ||
"""key press callback""" | ||
_LOGGER.info("[key pressed] " + str(key)) | ||
self.hass.bus.fire(EVENT_CEC_KEYPRESS_RECEIVED, {ATTR_KEY: key, ATTR_DUR: duration}) |
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.
line too long (92 > 79 characters)
self.lib_cec = cec.ICECAdapter.Create(self.cecconfig) | ||
# print libCEC version and compilation information | ||
_LOGGER.info("libCEC version " + self.lib_cec.VersionToString( | ||
self.cecconfig.serverVersion) + " loaded: " + self.lib_cec.GetLibInfo()) |
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.
line too long (84 > 79 characters)
def available(self): | ||
return self._available | ||
|
||
@property |
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.
redefinition of unused 'device_state_attributes' from line 342
|
||
@property | ||
def icon(self): | ||
return icon_by_type(self._cec_type_id) if self._icon is None else self._icon |
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.
line too long (84 > 79 characters)
|
||
@property | ||
def vendor_name(self): | ||
return VENDORS[self._vendor_id] if self._vendor_id in VENDORS else VENDORS[0] |
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.
line too long (85 > 79 characters)
and self.vendor_name != 'Unknown' \ | ||
else "%s %d" % (DEVICE_TYPE_NAMES[self._cec_type_id], self._logical_address) if self._name is None \ | ||
else "%s %d (%s)" % ( | ||
DEVICE_TYPE_NAMES[self._cec_type_id], self._logical_address, self._name) |
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.
line too long (84 > 79 characters)
def name(self): | ||
"""Return the name of the device.""" | ||
return "%s %s" % (self.vendor_name, self._name) if self._name is not None and self.vendor_name is not None \ | ||
and self.vendor_name != 'Unknown' \ |
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.
continuation line over-indented for visual indent
self._physical_address = "%d.%d.%d.%d" % ( | ||
cmd_chain[0] / 0x10, cmd_chain[0] % 0x10, cmd_chain[1] / 0x10, cmd_chain[1] % 0x10) | ||
self._cec_type_id = cmd_chain[2] | ||
_LOGGER.info("Got physical address and type for device %x -> %x, %s, %s", |
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.
line too long (89 > 79 characters)
_LOGGER.info("Got vendor for device %x -> %x, %s: %s", src, dst, self.vendor_id, self.vendor_name) | ||
elif cmd == 0x84: | ||
self._physical_address = "%d.%d.%d.%d" % ( | ||
cmd_chain[0] / 0x10, cmd_chain[0] % 0x10, cmd_chain[1] / 0x10, cmd_chain[1] % 0x10) |
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.
line too long (103 > 79 characters)
for i in cmd_chain: | ||
self._vendor_id *= 0x100 | ||
self._vendor_id += i | ||
_LOGGER.info("Got vendor for device %x -> %x, %s: %s", src, dst, self.vendor_id, self.vendor_name) |
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.
line too long (114 > 79 characters)
self._name = '' | ||
for c in cmd_chain: | ||
self._name += chr(c) | ||
_LOGGER.info("Got name for device %x -> %x, %s", src, dst, self._name) |
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.
line too long (86 > 79 characters)
self._state = STATE_OFF | ||
else: | ||
self._state = STATE_UNKNOWN | ||
_LOGGER.info("Got status for device %x -> %x, %s", src, dst, self._state) |
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.
line too long (89 > 79 characters)
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.
A lot of changes needed. Biggest one is that all the CEC protocol/vendor specific information will have to be extracted into a third party lib.
_LOGGER.debug("CEC setup") | ||
config = base_config.get(DOMAIN) | ||
|
||
global CEC_CLIENT |
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.
Don't use a global but use an entry in the dictionary hass.data
instead.
self._available = False | ||
self._hidden = False | ||
self._name = None | ||
DEVICE_PRESENCE[logical] = True |
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 use hass.data
|
||
MAX_DEPTH = 4 | ||
VENDORS = {0x000039: 'Toshiba', |
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.
There is too much protocol specific logic in this component. This should be moved to a third party lib on which Home Assistant can depend.
dev_type = 'switch' | ||
|
||
while True: | ||
new_devices = filter(lambda x: CEC_CLIENT.lib_cec.PollDevice(x), |
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 rewrite this using for
loops to keep this code understandable.
global CEC_CLIENT | ||
CEC_CLIENT = CecClient(hass) | ||
exclude = config.get(CONF_EXCLUDE) | ||
stop = threading.Event() | ||
|
||
def _start_cec(event): |
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 cannot have an event handler that never returns, you are then stealing a thread from HASS. If you want to run in a thread, start your own.
|
||
def turn_on(self, **kwargs) -> None: | ||
"""Turn device on.""" | ||
_LOGGER.info("***************** turn_on *****************") |
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 remove these logging
_LOGGER.info("***************** turn_on *****************") | ||
self.cec_client.lib_cec.PowerOnDevices(self._logical_address) | ||
self._state = STATE_ON | ||
self.async_update() |
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 can't call async_update
from outside the event loop
|
||
def turn_off(self, **kwargs) -> None: | ||
"""Turn device off.""" | ||
_LOGGER.info("***************** turn_off *****************") |
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 comment
_LOGGER.info("***************** turn_off *****************") | ||
self.cec_client.lib_cec.StandbyDevices(self._logical_address) | ||
self._state = STATE_STANDBY | ||
self.async_update() |
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.
No async_update from outside event loop
self.async_update() | ||
self.schedule_update_ha_state() | ||
|
||
def __init__(self, hass, cec_client, logical): |
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 put init methods on the top of a class definition.
def available(self): | ||
return self._available | ||
|
||
@property |
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.
redefinition of unused 'device_state_attributes' from line 339
return "%s %s" % ( | ||
self.vendor_name, self._device.osd_name) \ | ||
if self._device.osd_name is not None and \ | ||
self.vendor_name is not None and self.vendor_name != 'Unknown' \ |
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.
continuation line over-indented for visual indent
As you asked, I moved CEC specific stuff into separate library https://github.com/konikvranik/pycec |
Thanks a lot for your work, I'm eagerly waiting for this to get merged into dev. It'd be nice if you'd merge related commits (even for this published PR, if it's okay for HASS developers) or at least for the future ones. :) |
@rytilahti Thank you. I created PR to master. So should I create PR into dev branch? |
We can squash before we merge, but you should really be running local tests! Try the shortcut:
If you add new files without tests, you should add them to |
The PR is against |
@kellerza So I added also switch/hdmi_cec into .coveragerc.
Now passing without error, but I have had to modify it to check python files only. It failed on "syntax error" in services.yaml. I also run whole tox lint and also passed. Only I suspect to miss now is to slightly update documentation on home-assistant.github.io |
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 have not looked at your custom library, but you should make sure that the component you created can function (and most importantly that you can test it!) outside of HASS
|
||
CONFIG_SCHEMA = vol.Schema({ |
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 should always have a CONFIG_SCHEMA
in the component to validate your config.
CONFIG_SCHEMA
allows extra keys
@@ -172,6 +172,7 @@ omit = | |||
homeassistant/components/feedreader.py | |||
homeassistant/components/foursquare.py | |||
homeassistant/components/hdmi_cec.py | |||
homeassistant/components/switch/hdmi_cec.py |
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.
Will you please sort 😄
vol.Optional(CMD_UNMUTE): None, | ||
vol.Optional(CMD_MUTE_TOGGLE): None | ||
}) | ||
}, extra=vol.REMOVE_EXTRA) |
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.
For services SCHEMAS you probably want to fail on additional keys?
Not sure if you really need the DOMAIN
DOMAIN: vol.Schema({ | ||
vol.Optional(ATTR_DST): vol.Coerce(int), | ||
}) | ||
}, extra=vol.REMOVE_EXTRA) |
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 the Volume schema
def _new_device(device): | ||
"""Called when new device is detected by HDMI network.""" | ||
discovery.load_platform(hass, "switch", DOMAIN, | ||
discovered={ATTR_NEW: [device]}, |
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.
Anything sent over the hass bus, like device
must be serializable (i.e. a str/int/dict). It does not seem so from here
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.
So should I put device into hasss.data with my unique key and then retrieve it by this key which will be send over load_platform?
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.
Yes, you can make your DOMAIN
part of the key, then you know it should be unique
hass.services.register(DOMAIN, SERVICE_VOLUME, _volume, | ||
descriptions[SERVICE_VOLUME]) | ||
hass.services.register(DOMAIN, SERVICE_UPDATE_DEVICES, _update, | ||
descriptions[SERVICE_UPDATE_DEVICES]) |
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 should add the schemas you defined when registering these services
self._state = STATE_UNKNOWN | ||
self._logical_address = logical | ||
self.entity_id = "%s.%d" % (DOMAIN, self._logical_address) | ||
device.set_update_callback(self._update) |
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 also indicates the data you sent over the buss is not serializable.
Rather send non-serializable data in the hass.data
dictionary.
@@ -4,7 +4,7 @@ | |||
# performs roughly what this test did in the past. | |||
|
|||
if [ "$1" = "--changed" ]; then | |||
export files="`git diff upstream/dev --name-only | grep -v requirements_all.txt`" | |||
export files="`git diff upstream/dev --name-only | grep -e '\.py$'`" |
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 like this, but do you mind doing it in a separate 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.
OK, in #5018
def _volume(call): | ||
"""Increase/decrease volume and mute/unmute system.""" | ||
for cmd, att in call.data.items(): | ||
att = int(att) |
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.
Once you have the SCHEMAs validating your input, this type of coercing variables should not be needed
* newer version of pyCEC * updated services.yaml * fixed lint scrpt to operate only on python files * pycec version up * update services * no coverage report * exclude non python files from lint * lint only on python files
* newer version of pyCEC * updated services.yaml * fixed lint scrpt to operate only on python files * pycec version up * update services * no coverage report * exclude non python files from lint * lint only on python files * reordered * sending nonserialized data through hass.data * import order * fixed object handling * code formatting
* services: power_on standby active_source
* newer version of pyCEC
* getting device name from config
* correct call on shutdown
* Preparation for merge to upstream (#5) * newer version of pyCEC * updated services.yaml * reordered * sending nonserialized data through hass.data * services: power_on standby active_source * code formatting * getting device name from config * correct call on shutdown
* newer version of pyCEC * updated services.yaml * sending nonserialized data through hass.data * services: ** power_on ** standby ** active_source * getting device name from config * correct call on shutdown * fork new thread on multicore machines * support both config schemas: original and new (#16) * volume press and release support (#17)
* accept hexadecimal format of commands * support for media player * platform customization * type constants
* accept hexadecimal format of commands * support for media player * platform customization * preparing tcp support
* cleanup imports * cleanup and enhance services description * removed unwanted file
* pyCEC v0.4.6 * pined dependency version * tighten service schemas
@rytilahti: pycec dependency is present in version 0.4.6. Otherwise checks fail. |
@konikvranik Thanks for the PR, it took a while to get it in, but you added many features and worth it in the end! 🐬 !! |
Hey could you tell me when this giant feature is available in a new release.... Great... thank you guys |
It should be the coming weekend |
Description:
This pullrequest extends functionality of previous hdmi_cec platform so now detected HDMI devices are handled as switch entites, reports name, vendor, address and state, could be turned on/off.
Also adds services to pass arbitraty CEC command and control volume.
Pull request in home-assistant.github.io with documentation: home-assistant/home-assistant.io#1642
Example entry for
configuration.yaml
(if applicable):hdmi_cec:
Checklist:
If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
tox
run successfully. Your PR cannot be merged unless tests passREQUIREMENTS
variable ([example][ex-requir]).requirements_all.txt
by runningscript/gen_requirements_all.py
..coveragerc
.