HDMI CEC - support for devices and commands (#4781)

* cec client object

* cec command structure

* autodetect source

* volume support and native source select

* switch device

* media player device

* detecting of state

* friendly names

* hdmi cec properties

* presence detection

* simplified callbacks

* stable names

* renamed methods

* code cleanup

* name with vendor

* fixed standby call name

* fake standby/poweron

* domain switch

* domain switch

* async updating

* update separated

* cec -> hass event bridge

* fixed name generation

* code cleanup

* code cleanup

* icon constants

* code cleanup

* do not register unavailable devices

* discovery of deevices

* code cleanup

* cec device discovery

* moved method implementation into child

* service descriptions

* service descriptions

* service descriptions

* changed entity init sequence

* logging cleanup

* add remove as job

* closing cec, no service schemas

* correct iterate over dictionary

* Volume by commands

* threading

* logging minimized

* get load out of main thread

* naming cleanup

* get load out of main thread

* optimized discovery

* async where possible

* cleanup logging, constructors first

* pydoc

* formatting

* no async_update from out of loop
no hiding entities
removed redundant device_state_attributes
async updating presence

* no async

* working async cec

* cec in thirdparty lib

* cec initialized oudsice

* working without SIGSEGV

* rollbacked file changed by mistake

* sending of commands

* working with ha

* using hass loop and device driven updates

* version up

* version up

* Command types in pycec, cleanup for HA integration

* Removed media player, state moved to switch

* service descriptions

* requirements: pyCEC

* line width to 79

* doc

* doc

* overindentation solved

* HDMI to uppercase

* minimal dependency on cec

* removed unwanted line

* doc wording

* margin 79

* line continuation indent

* imperative doc

* lint: indentation

* fixed overindented

* fixed overindented

* fixed overindented

* fixed overindented

* order of imports

* PEP8

* keep signature of overriding

* removed redundant blank line

* fixed update call method (#4)

* Preparation for merge to upstream (#5)

* 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

* Dev (#6)

* reordered
* sending nonserialized data through hass.data
* code formatting
* code formatting
* import order

* Dev (#7)

* 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

* Backwards compatibility of hdmi_cec (#10)

* services:
power_on
standby
active_source

* new version of pyCEC (#12)

* newer version of pyCEC

* devices config (#13)

* getting device name from config

* shutdown fix (#14)


* correct call on shutdown

* remove misplaced annotations (#15)

* 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

* pyCEC version 0.3.6 (#18)

* 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)

* support for media_player (#21)

* accept hexadecimal format of commands
* support for media player
* platform customization
* type constants

* Dev (#23)

* accept hexadecimal format of commands
* support for media player
* platform customization

* TCP CEC support (#24)

* accept hexadecimal format of commands
* support for media player
* platform customization
* preparing tcp support

* volume handling (#25)

* Incorporated CR remarks (#26)

* cleanup imports
* cleanup and enhance services description
* removed unwanted file

* implemented CR remarks (#27)

* pyCEC v0.4.6
* pined dependency version
* tighten service schemas

* requirements (#28)
This commit is contained in:
Petr Vraník 2017-01-20 21:39:18 +01:00 committed by Paulus Schoutsen
parent cb47d16282
commit 067e11ea5c
6 changed files with 658 additions and 65 deletions

View File

@ -214,6 +214,7 @@ omit =
homeassistant/components/media_player/emby.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/hdmi_cec.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
@ -356,6 +357,7 @@ omit =
homeassistant/components/switch/digitalloggers.py
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hdmi_cec.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/hook.py
homeassistant/components/switch/kankun.py

View File

@ -1,27 +1,110 @@
"""
CEC component.
HDMI CEC component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/hdmi_cec/
"""
import logging
import multiprocessing
import os
from collections import defaultdict
from functools import reduce
import voluptuous as vol
from homeassistant.const import (EVENT_HOMEASSISTANT_START, CONF_DEVICES)
import homeassistant.helpers.config_validation as cv
from homeassistant import core
from homeassistant.components import discovery
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER
from homeassistant.components.switch import DOMAIN as SWITCH
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (EVENT_HOMEASSISTANT_START, STATE_UNKNOWN,
EVENT_HOMEASSISTANT_STOP, STATE_ON,
STATE_OFF, CONF_DEVICES, CONF_PLATFORM,
CONF_CUSTOMIZE, STATE_PLAYING, STATE_IDLE,
STATE_PAUSED, CONF_HOST)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import Entity
_CEC = None
_LOGGER = logging.getLogger(__name__)
ATTR_DEVICE = 'device'
REQUIREMENTS = ['pyCEC==0.4.6']
DOMAIN = 'hdmi_cec'
MAX_DEPTH = 4
_LOGGER = logging.getLogger(__name__)
ICON_UNKNOWN = 'mdi:help'
ICON_AUDIO = 'mdi:speaker'
ICON_PLAYER = 'mdi:play'
ICON_TUNER = 'mdi:nest-thermostat'
ICON_RECORDER = 'mdi:microphone'
ICON_TV = 'mdi:television'
ICONS_BY_TYPE = {
0: ICON_TV,
1: ICON_RECORDER,
3: ICON_TUNER,
4: ICON_PLAYER,
5: ICON_AUDIO
}
CEC_DEVICES = defaultdict(list)
CMD_UP = 'up'
CMD_DOWN = 'down'
CMD_MUTE = 'mute'
CMD_UNMUTE = 'unmute'
CMD_MUTE_TOGGLE = 'toggle mute'
CMD_PRESS = 'press'
CMD_RELEASE = 'release'
EVENT_CEC_COMMAND_RECEIVED = 'cec_command_received'
EVENT_CEC_KEYPRESS_RECEIVED = 'cec_keypress_received'
ATTR_PHYSICAL_ADDRESS = 'physical_address'
ATTR_TYPE_ID = 'type_id'
ATTR_VENDOR_NAME = 'vendor_name'
ATTR_VENDOR_ID = 'vendor_id'
ATTR_DEVICE = 'device'
ATTR_COMMAND = 'command'
ATTR_TYPE = 'type'
ATTR_KEY = 'key'
ATTR_DUR = 'dur'
ATTR_SRC = 'src'
ATTR_DST = 'dst'
ATTR_CMD = 'cmd'
ATTR_ATT = 'att'
ATTR_RAW = 'raw'
ATTR_DIR = 'dir'
ATTR_ABT = 'abt'
ATTR_NEW = 'new'
_VOL_HEX = vol.Any(vol.Coerce(int), lambda x: int(x, 16))
SERVICE_SEND_COMMAND = 'send_command'
SERVICE_SEND_COMMAND_SCHEMA = vol.Schema({
vol.Optional(ATTR_CMD): _VOL_HEX,
vol.Optional(ATTR_SRC): _VOL_HEX,
vol.Optional(ATTR_DST): _VOL_HEX,
vol.Optional(ATTR_ATT): _VOL_HEX,
vol.Optional(ATTR_RAW): vol.Coerce(str)
}, extra=vol.PREVENT_EXTRA)
SERVICE_VOLUME = 'volume'
SERVICE_VOLUME_SCHEMA = vol.Schema({
vol.Optional(CMD_UP): vol.Any(CMD_PRESS, CMD_RELEASE, vol.Coerce(int)),
vol.Optional(CMD_DOWN): vol.Any(CMD_PRESS, CMD_RELEASE, vol.Coerce(int)),
vol.Optional(CMD_MUTE): None,
vol.Optional(CMD_UNMUTE): None,
vol.Optional(CMD_MUTE_TOGGLE): None
}, extra=vol.PREVENT_EXTRA)
SERVICE_UPDATE_DEVICES = 'update'
SERVICE_UPDATE_DEVICES_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({})
}, extra=vol.PREVENT_EXTRA)
SERVICE_SELECT_DEVICE = 'select_device'
SERVICE_POWER_ON = 'power_on'
SERVICE_SELECT_DEVICE = 'select_device'
SERVICE_STANDBY = 'standby'
# pylint: disable=unnecessary-lambda
@ -30,92 +113,304 @@ DEVICE_SCHEMA = vol.Schema({
cv.string)
})
CUSTOMIZE_SCHEMA = vol.Schema({
vol.Optional(CONF_PLATFORM, default=MEDIA_PLAYER): vol.Any(MEDIA_PLAYER,
SWITCH)
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_DEVICES): DEVICE_SCHEMA
vol.Optional(CONF_DEVICES): vol.Any(DEVICE_SCHEMA,
vol.Schema({
vol.All(cv.string): vol.Any(
cv.string)
})),
vol.Optional(CONF_PLATFORM): vol.Any(SWITCH, MEDIA_PLAYER),
vol.Optional(CONF_HOST): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
def pad_physical_address(addr):
"""Right-pad a physical address."""
return addr + [0] * (4 - len(addr))
def parse_mapping(mapping, parents=None):
"""Parse configuration device mapping."""
if parents is None:
parents = []
for addr, val in mapping.items():
cur = parents + [str(addr)]
if isinstance(val, dict):
yield from parse_mapping(val, cur)
elif isinstance(val, str):
yield (val, cur)
if isinstance(addr, (str,)) and isinstance(val, (str,)):
from pycec.network import PhysicalAddress
yield (addr, PhysicalAddress(val))
else:
cur = parents + [addr]
if isinstance(val, dict):
yield from parse_mapping(val, cur)
elif isinstance(val, str):
yield (val, pad_physical_address(cur))
def pad_physical_address(addr):
"""Right-pad a physical address."""
return addr + ['0'] * (MAX_DEPTH - len(addr))
def setup(hass, config):
def setup(hass: HomeAssistant, base_config):
"""Setup CEC capability."""
global _CEC
try:
import cec
except ImportError:
_LOGGER.error("libcec must be installed")
return False
from pycec.network import HDMINetwork
from pycec.commands import CecCommand, KeyReleaseCommand, KeyPressCommand
from pycec.const import KEY_VOLUME_UP, KEY_VOLUME_DOWN, KEY_MUTE, \
ADDR_AUDIOSYSTEM, ADDR_BROADCAST, ADDR_UNREGISTERED
from pycec.cec import CecAdapter
from pycec.tcp import TcpAdapter
# Parse configuration into a dict of device name to physical address
# represented as a list of four elements.
flat = {}
for pair in parse_mapping(config[DOMAIN].get(CONF_DEVICES, {})):
flat[pair[0]] = pad_physical_address(pair[1])
device_aliases = {}
devices = base_config[DOMAIN].get(CONF_DEVICES, {})
_LOGGER.debug("Parsing config %s", devices)
device_aliases.update(parse_mapping(devices))
_LOGGER.debug("Parsed devices: %s", device_aliases)
# Configure libcec.
cfg = cec.libcec_configuration()
cfg.strDeviceName = 'HASS'
cfg.bActivateSource = 0
cfg.bMonitorOnly = 1
cfg.clientVersion = cec.LIBCEC_VERSION_CURRENT
platform = base_config[DOMAIN].get(CONF_PLATFORM, SWITCH)
# Setup CEC adapter.
_CEC = cec.ICECAdapter.Create(cfg)
loop = (
# Create own thread if more than 1 CPU
hass.loop if multiprocessing.cpu_count() < 2 else None)
host = base_config[DOMAIN].get(CONF_HOST, None)
if host:
adapter = TcpAdapter(host, name="HASS", activate_source=False)
else:
adapter = CecAdapter(name="HASS", activate_source=False)
hdmi_network = HDMINetwork(adapter, loop=loop)
def _power_on(call):
"""Power on all devices."""
_CEC.PowerOnDevices()
def _volume(call):
"""Increase/decrease volume and mute/unmute system."""
for cmd, att in call.data.items():
if cmd == CMD_UP:
_process_volume(KEY_VOLUME_UP, att)
elif cmd == CMD_DOWN:
_process_volume(KEY_VOLUME_DOWN, att)
elif cmd == CMD_MUTE:
hdmi_network.send_command(
KeyPressCommand(KEY_MUTE, dst=ADDR_AUDIOSYSTEM))
hdmi_network.send_command(
KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM))
_LOGGER.info("Audio muted")
else:
_LOGGER.warning("Unknown command %s", cmd)
def _process_volume(cmd, att):
if isinstance(att, (str,)):
att = att.strip()
if att == CMD_PRESS:
hdmi_network.send_command(
KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM))
elif att == CMD_RELEASE:
hdmi_network.send_command(KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM))
else:
att = 1 if att == "" else int(att)
for _ in range(1, att):
hdmi_network.send_command(
KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM))
hdmi_network.send_command(
KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM))
def _tx(call):
"""Send CEC command."""
data = call.data
if ATTR_RAW in data:
command = CecCommand(data[ATTR_RAW])
else:
if ATTR_SRC in data:
src = data[ATTR_SRC]
else:
src = ADDR_UNREGISTERED
if ATTR_DST in data:
dst = data[ATTR_DST]
else:
dst = ADDR_BROADCAST
if ATTR_CMD in data:
cmd = data[ATTR_CMD]
else:
_LOGGER.error("Attribute 'cmd' is missing")
return False
if ATTR_ATT in data:
if isinstance(data[ATTR_ATT], (list,)):
att = data[ATTR_ATT]
else:
att = reduce(lambda x, y: "%s:%x" % (x, y), data[ATTR_ATT])
else:
att = ""
command = CecCommand(cmd, dst, src, att)
hdmi_network.send_command(command)
@callback
def _standby(call):
"""Standby all devices."""
_CEC.StandbyDevices()
hdmi_network.standby()
@callback
def _power_on(call):
hdmi_network.power_on()
def _select_device(call):
"""Select the active device."""
path = flat.get(call.data[ATTR_DEVICE])
if not path:
from pycec.network import PhysicalAddress
addr = call.data[ATTR_DEVICE]
if not addr:
_LOGGER.error("Device not found: %s", call.data[ATTR_DEVICE])
cmds = []
for i in range(1, MAX_DEPTH - 1):
addr = pad_physical_address(path[:i])
cmds.append('1f:82:{}{}:{}{}'.format(*addr))
cmds.append('1f:86:{}{}:{}{}'.format(*addr))
for cmd in cmds:
_CEC.Transmit(_CEC.CommandFromString(cmd))
_LOGGER.info("Selected %s", call.data[ATTR_DEVICE])
return
if addr in device_aliases:
addr = device_aliases[addr]
else:
entity = hass.states.get(addr)
_LOGGER.debug("Selecting entity %s", entity)
if entity is not None:
addr = entity.attributes['physical_address']
_LOGGER.debug("Address acquired: %s", addr)
if addr is None:
_LOGGER.error("Device %s has not physical address.",
call.data[ATTR_DEVICE])
return
if not isinstance(addr, (PhysicalAddress,)):
addr = PhysicalAddress(addr)
hdmi_network.active_source(addr)
_LOGGER.info("Selected %s (%s)", call.data[ATTR_DEVICE], addr)
def _update(call):
"""
Callback called when device update is needed.
- called by service, requests CEC network to update data.
"""
hdmi_network.scan()
@callback
def _new_device(device):
"""Called when new device is detected by HDMI network."""
key = DOMAIN + '.' + device.name
hass.data[key] = device
discovery.load_platform(hass, base_config.get(core.DOMAIN).get(
CONF_CUSTOMIZE, {}).get(key, {}).get(CONF_PLATFORM, platform),
DOMAIN, discovered={ATTR_NEW: [key]},
hass_config=base_config)
def _shutdown(call):
hdmi_network.stop()
def _start_cec(event):
"""Open CEC adapter."""
adapters = _CEC.DetectAdapters()
if len(adapters) == 0:
_LOGGER.error("No CEC adapter found")
return
"""Register services and start HDMI network to watch for devices."""
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))[DOMAIN]
hass.services.register(DOMAIN, SERVICE_SEND_COMMAND, _tx,
descriptions[SERVICE_SEND_COMMAND],
SERVICE_SEND_COMMAND_SCHEMA)
hass.services.register(DOMAIN, SERVICE_VOLUME, _volume,
descriptions[SERVICE_VOLUME],
SERVICE_VOLUME_SCHEMA)
hass.services.register(DOMAIN, SERVICE_UPDATE_DEVICES, _update,
descriptions[SERVICE_UPDATE_DEVICES],
SERVICE_UPDATE_DEVICES_SCHEMA)
hass.services.register(DOMAIN, SERVICE_POWER_ON, _power_on)
hass.services.register(DOMAIN, SERVICE_STANDBY, _standby)
hass.services.register(DOMAIN, SERVICE_SELECT_DEVICE, _select_device)
if _CEC.Open(adapters[0].strComName):
hass.services.register(DOMAIN, SERVICE_POWER_ON, _power_on)
hass.services.register(DOMAIN, SERVICE_STANDBY, _standby)
hass.services.register(DOMAIN, SERVICE_SELECT_DEVICE,
_select_device)
else:
_LOGGER.error("Failed to open adapter")
hdmi_network.set_new_device_callback(_new_device)
hdmi_network.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, _start_cec)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
return True
class CecDevice(Entity):
"""Representation of a HDMI CEC device entity."""
def __init__(self, hass: HomeAssistant, device, logical):
"""Initialize the device."""
self._device = device
self.hass = hass
self._icon = None
self._state = STATE_UNKNOWN
self._logical_address = logical
self.entity_id = "%s.%d" % (DOMAIN, self._logical_address)
device.set_update_callback(self._update)
def update(self):
"""Update device status."""
self._update()
def _update(self, device=None):
"""Update device status."""
if device:
from pycec.const import STATUS_PLAY, STATUS_STOP, STATUS_STILL, \
POWER_OFF, POWER_ON
if device.power_status == POWER_OFF:
self._state = STATE_OFF
elif device.status == STATUS_PLAY:
self._state = STATE_PLAYING
elif device.status == STATUS_STOP:
self._state = STATE_IDLE
elif device.status == STATUS_STILL:
self._state = STATE_PAUSED
elif device.power_status == POWER_ON:
self._state = STATE_ON
else:
_LOGGER.warning("Unknown state: %d", device.power_status)
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of the device."""
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')
else "%s %d" % (self._device.type_name, self._logical_address)
if self._device.osd_name is None
else "%s %d (%s)" % (self._device.type_name, self._logical_address,
self._device.osd_name))
@property
def vendor_id(self):
"""ID of device's vendor."""
return self._device.vendor_id
@property
def vendor_name(self):
"""Name of device's vendor."""
return self._device.vendor
@property
def physical_address(self):
"""Physical address of device in HDMI network."""
return str(self._device.physical_address)
@property
def type(self):
"""String representation of device's type."""
return self._device.type_name
@property
def type_id(self):
"""Type ID of device."""
return self._device.type
@property
def icon(self):
"""Icon for device by its type."""
return (self._icon if self._icon is not None else
ICONS_BY_TYPE.get(self._device.type)
if self._device.type in ICONS_BY_TYPE else ICON_UNKNOWN)
@property
def device_state_attributes(self):
"""Return the state attributes."""
state_attr = {}
if self.vendor_id is not None:
state_attr[ATTR_VENDOR_ID] = self.vendor_id
state_attr[ATTR_VENDOR_NAME] = self.vendor_name
if self.type_id is not None:
state_attr[ATTR_TYPE_ID] = self.type_id
state_attr[ATTR_TYPE] = self.type
if self.physical_address is not None:
state_attr[ATTR_PHYSICAL_ADDRESS] = self.physical_address
return state_attr

View File

@ -0,0 +1,175 @@
"""
Support for HDMI CEC devices as media players.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/hdmi_cec/
"""
import logging
from homeassistant.components.hdmi_cec import ATTR_NEW, CecDevice
from homeassistant.components.media_player import MediaPlayerDevice, DOMAIN, \
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA, SUPPORT_PAUSE, \
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_STOP, \
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_MUTE
from homeassistant.const import STATE_ON, STATE_OFF, STATE_PLAYING, \
STATE_IDLE, STATE_PAUSED
from homeassistant.core import HomeAssistant
DEPENDENCIES = ['hdmi_cec']
_LOGGER = logging.getLogger(__name__)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return HDMI devices as +switches."""
if ATTR_NEW in discovery_info:
_LOGGER.info("Setting up HDMI devices %s", discovery_info[ATTR_NEW])
add_devices(CecPlayerDevice(hass, hass.data.get(device),
hass.data.get(device).logical_address) for
device in discovery_info[ATTR_NEW])
class CecPlayerDevice(CecDevice, MediaPlayerDevice):
"""Representation of a HDMI device as a Media palyer."""
def __init__(self, hass: HomeAssistant, device, logical):
"""Initialize the HDMI device."""
CecDevice.__init__(self, hass, device, logical)
self.entity_id = "%s.%s_%s" % (
DOMAIN, 'hdmi', hex(self._logical_address)[2:])
self.update()
def send_keypress(self, key):
"""Send keypress to CEC adapter."""
from pycec.commands import KeyPressCommand, KeyReleaseCommand
_LOGGER.debug("Sending keypress %s to device %s", hex(key),
hex(self._logical_address))
self._device.send_command(
KeyPressCommand(key, dst=self._logical_address))
self._device.send_command(
KeyReleaseCommand(dst=self._logical_address))
def send_playback(self, key):
"""Send playback status to CEC adapter."""
from pycec.commands import CecCommand
self._device.async_send_command(
CecCommand(key, dst=self._logical_address))
def mute_volume(self, mute):
"""Mute volume."""
from pycec.const import KEY_MUTE
self.send_keypress(KEY_MUTE)
def media_previous_track(self):
"""Go to previous track."""
from pycec.const import KEY_BACKWARD
self.send_keypress(KEY_BACKWARD)
def turn_on(self):
"""Turn device on."""
self._device.turn_on()
self._state = STATE_ON
def clear_playlist(self):
"""Clear players playlist."""
raise NotImplementedError()
def turn_off(self):
"""Turn device off."""
self._device.turn_off()
self._state = STATE_OFF
def media_stop(self):
"""Stop playback."""
from pycec.const import KEY_STOP
self.send_keypress(KEY_STOP)
self._state = STATE_IDLE
def play_media(self, media_type, media_id):
"""Not supported."""
raise NotImplementedError()
def media_next_track(self):
"""Skip to next track."""
from pycec.const import KEY_FORWARD
self.send_keypress(KEY_FORWARD)
def media_seek(self, position):
"""Not supported."""
raise NotImplementedError()
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
raise NotImplementedError()
def media_pause(self):
"""Pause playback."""
from pycec.const import KEY_PAUSE
self.send_keypress(KEY_PAUSE)
self._state = STATE_PAUSED
def select_source(self, source):
"""Not supported."""
raise NotImplementedError()
def media_play(self):
"""Start playback."""
from pycec.const import KEY_PLAY
self.send_keypress(KEY_PLAY)
self._state = STATE_PLAYING
def volume_up(self):
"""Increase volume."""
from pycec.const import KEY_VOLUME_UP
_LOGGER.debug("%s: volume up", self._logical_address)
self.send_keypress(KEY_VOLUME_UP)
def volume_down(self):
"""Decrease volume."""
from pycec.const import KEY_VOLUME_DOWN
_LOGGER.debug("%s: volume down", self._logical_address)
self.send_keypress(KEY_VOLUME_DOWN)
@property
def state(self) -> str:
"""Cached state of device."""
return self._state
def _update(self, device=None):
"""Update device status."""
if device:
from pycec.const import STATUS_PLAY, STATUS_STOP, STATUS_STILL, \
POWER_OFF, POWER_ON
if device.power_status == POWER_OFF:
self._state = STATE_OFF
elif not self.support_pause:
if device.power_status == POWER_ON:
self._state = STATE_ON
elif device.status == STATUS_PLAY:
self._state = STATE_PLAYING
elif device.status == STATUS_STOP:
self._state = STATE_IDLE
elif device.status == STATUS_STILL:
self._state = STATE_PAUSED
else:
_LOGGER.warning("Unknown state: %s", device.status)
self.schedule_update_ha_state()
@property
def supported_media_commands(self):
"""Flag media commands that are supported."""
from pycec.const import TYPE_RECORDER, TYPE_PLAYBACK, TYPE_TUNER, \
TYPE_AUDIO
if self.type_id == TYPE_RECORDER or self.type == TYPE_PLAYBACK:
return (SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA |
SUPPORT_PAUSE | SUPPORT_STOP | SUPPORT_PREVIOUS_TRACK |
SUPPORT_NEXT_TRACK)
if self.type == TYPE_TUNER:
return (SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA |
SUPPORT_PAUSE | SUPPORT_STOP)
if self.type_id == TYPE_AUDIO:
return (SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_VOLUME_STEP |
SUPPORT_VOLUME_MUTE)
return SUPPORT_TURN_ON | SUPPORT_TURN_OFF

View File

@ -153,3 +153,58 @@ verisure:
device_serial:
description: The serial number of the smartcam you want to capture an image from.
example: '2DEU AT5Z'
hdmi_cec:
send_command:
description: Sends CEC command into HDMI CEC capable adapter.
fields:
raw:
description: 'Raw CEC command in format "00:00:00:00" where first two digits are source and destination, second byte is command and optional other bytes are command parameters. If raw command specified, other params are ignored.'
example: '"10:36"'
src:
desctiption: 'Source of command. Could be decimal number or string with hexadeximal notation: "0x10".'
example: '12 or "0xc"'
dst:
description: 'Destination for command. Could be decimal number or string with hexadeximal notation: "0x10".'
example: '5 or "0x5"'
cmd:
description: 'Command itself. Could be decimal number or string with hexadeximal notation: "0x10".'
example: '144 or "0x90"'
att:
description: Optional parameters.
example: [0, 2]
update:
description: Update devices state from network.
volume:
description: Increase or decrease volume of system.
fields:
up:
description: Increases volume x levels.
example: 3
down:
description: Decreases volume x levels.
example: 3
mute: Mutes audio system. Value is ignored.
unmute: Unmutes audio system. Value is ignored.
toggle mute: Toggles mute of audio system. Value is ignored.
select_device:
description: Select HDMI device.
fields:
device:
description: Addres of device to select. Can be entity_id, physical address or alias from confuguration.
example: '"switch.hdmi_1" or "1.1.0.0" or "01:10"'
power_on:
description: Power on all devices which supports it.
standby:
description: Standby all devices which supports it.

View File

@ -0,0 +1,63 @@
"""
Support for HDMI CEC devices as switches.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/hdmi_cec/
"""
import logging
from homeassistant.components.hdmi_cec import CecDevice, ATTR_NEW
from homeassistant.components.switch import SwitchDevice, DOMAIN
from homeassistant.const import STATE_OFF, STATE_STANDBY, STATE_ON
from homeassistant.core import HomeAssistant
DEPENDENCIES = ['hdmi_cec']
_LOGGER = logging.getLogger(__name__)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return HDMI devices as switches."""
if ATTR_NEW in discovery_info:
_LOGGER.info("Setting up HDMI devices %s", discovery_info[ATTR_NEW])
add_devices(CecSwitchDevice(hass, hass.data.get(device),
hass.data.get(device).logical_address) for
device in discovery_info[ATTR_NEW])
class CecSwitchDevice(CecDevice, SwitchDevice):
"""Representation of a HDMI device as a Switch."""
def __init__(self, hass: HomeAssistant, device, logical):
"""Initialize the HDMI device."""
CecDevice.__init__(self, hass, device, logical)
self.entity_id = "%s.%s_%s" % (
DOMAIN, 'hdmi', hex(self._logical_address)[2:])
self.update()
def turn_on(self, **kwargs) -> None:
"""Turn device on."""
self._device.turn_on()
self._state = STATE_ON
def turn_off(self, **kwargs) -> None:
"""Turn device off."""
self._device.turn_off()
self._state = STATE_ON
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self._state == STATE_ON
@property
def is_standby(self):
"""Return true if device is in standby."""
return self._state == STATE_OFF or self._state == STATE_STANDBY
@property
def state(self) -> str:
"""Cached state of device."""
return self._state

View File

@ -377,6 +377,9 @@ pwaqi==1.3
# homeassistant.components.sensor.cpuspeed
py-cpuinfo==0.2.3
# homeassistant.components.hdmi_cec
pyCEC==0.4.6
# homeassistant.components.switch.tplink
pyHS100==0.2.3