Dimmer service
This commit is contained in:
70
custom_components/dimmer/__init__.py
Normal file
70
custom_components/dimmer/__init__.py
Normal file
@@ -0,0 +1,70 @@
|
||||
""" Service to increase/decrease light brightness"""
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN, ATTR_TRANSITION
|
||||
from homeassistant.const import SERVICE_TURN_ON, ATTR_ENTITY_ID
|
||||
|
||||
# The domain of your component. Should be equal to the name of your component.
|
||||
from homeassistant.helpers.service import async_extract_entity_ids
|
||||
|
||||
DOMAIN = "dimmer"
|
||||
|
||||
# List of component names (string) your component depends upon.
|
||||
# We depend on group because group will be loaded after all the components that
|
||||
# initialize devices have been setup.
|
||||
DEPENDENCIES = ['group', 'light']
|
||||
|
||||
# Name of the service that we expose.
|
||||
SERVICE_DIM = 'dim'
|
||||
|
||||
# Shortcut for the logger
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Validate that all required config options are given.
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Setup example component."""
|
||||
|
||||
async def async_dim_service(service):
|
||||
params = service.data.copy()
|
||||
|
||||
entity_ids = await async_extract_entity_ids(hass, service, expand_group=True)
|
||||
|
||||
offset = params.get('offset', None)
|
||||
factor = params.get('factor', None)
|
||||
min_brightness = params.get('min_brightness', 6)
|
||||
|
||||
transition = params.get(ATTR_TRANSITION, None)
|
||||
|
||||
if factor is None and offset is None:
|
||||
offset = 50
|
||||
|
||||
assert not (factor is not None and offset is not None)
|
||||
|
||||
def clip_value(level):
|
||||
level = int(level)
|
||||
if level < min_brightness:
|
||||
return min_brightness
|
||||
if level > 255:
|
||||
return 255
|
||||
return level
|
||||
|
||||
for entity_id in entity_ids:
|
||||
brightness = hass.states.get(entity_id).attributes.get('brightness', 0)
|
||||
if factor is not None:
|
||||
data = {ATTR_BRIGHTNESS: clip_value(brightness * factor)}
|
||||
if offset is not None:
|
||||
data = {ATTR_BRIGHTNESS: clip_value(brightness + offset)}
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
if transition:
|
||||
data[ATTR_TRANSITION] = transition
|
||||
await hass.services.async_call(LIGHT_DOMAIN, SERVICE_TURN_ON, data)
|
||||
|
||||
hass.services.async_register(DOMAIN, SERVICE_DIM, async_dim_service)
|
||||
|
||||
return True
|
||||
17
custom_components/dimmer/services.yaml
Normal file
17
custom_components/dimmer/services.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
dim:
|
||||
description: Increases or decreases brightness of lights
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities or groups
|
||||
example: 'light.living_room'
|
||||
offset:
|
||||
description: New light value is old value plus this offset (full brightness is 255).
|
||||
Can be negative to decrease brightness
|
||||
example: 30
|
||||
factor:
|
||||
description: Factor to multiply old light value with. Use either offset or factor, not both!
|
||||
example: 1.3
|
||||
transition:
|
||||
description: transition time in seconds
|
||||
example: 1
|
||||
0
custom_components/fhem/README.md
Normal file
0
custom_components/fhem/README.md
Normal file
147
custom_components/fhem/__init__.py
Normal file
147
custom_components/fhem/__init__.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""FHEM integration"""
|
||||
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONF_CUL_DEVICE_NAME = 'cul_device_name'
|
||||
CONF_FHEM_SENSOR_TYPE = 'fhem_sensor_type'
|
||||
CONF_FHEM_IDS = 'fhem_ids'
|
||||
DOMAIN = 'fhem'
|
||||
DATA_FHEM = "data_fhem"
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_PORT, default=7072): cv.port,
|
||||
vol.Required(CONF_CUL_DEVICE_NAME): cv.string,
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
connection = FhemConnection(hass, config)
|
||||
hass.data[DATA_FHEM] = connection
|
||||
await connection.start()
|
||||
return True
|
||||
|
||||
|
||||
class FhemConnection:
|
||||
|
||||
def __init__(self, hass, config):
|
||||
self.hass = hass
|
||||
self._host = config[DOMAIN][CONF_HOST]
|
||||
self._port = config[DOMAIN][CONF_PORT]
|
||||
self._cul_device_name = config[DOMAIN][CONF_CUL_DEVICE_NAME]
|
||||
|
||||
self.connected = False
|
||||
self.reconnect_time_start = 1
|
||||
self.reconnect_time_max = 60
|
||||
self.reconnect_time = self.reconnect_time_start
|
||||
self._devices = defaultdict(list)
|
||||
self._run = False
|
||||
self._writer = None
|
||||
self._connection_last_state = 'UNKNOWN'
|
||||
|
||||
def register_device(self, id, d):
|
||||
self._devices[id].append(d)
|
||||
if self._writer:
|
||||
self._writer.writelines([
|
||||
"displayattr .*\n".encode(),
|
||||
])
|
||||
|
||||
async def _update_all_devices(self):
|
||||
for device_list in self._devices.values():
|
||||
for device in device_list:
|
||||
await device.async_update_ha_state()
|
||||
|
||||
async def start(self):
|
||||
self._run = True
|
||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop)
|
||||
self.hass.loop.create_task(self._connection())
|
||||
|
||||
async def stop(self):
|
||||
self._run = False
|
||||
self.connected = False
|
||||
|
||||
async def _connection(self):
|
||||
try:
|
||||
reader, writer = await asyncio.open_connection(self._host, self._port)
|
||||
_LOGGER.info("Connected to FHEM {}:{}".format(self._host, self._port))
|
||||
self._connection_last_state = 'CONNECTED'
|
||||
|
||||
self._writer = writer
|
||||
self.connected = True
|
||||
await self._update_all_devices()
|
||||
|
||||
self.reconnect_time = self.reconnect_time_start
|
||||
writer.writelines([
|
||||
"displayattr .*\n".encode(),
|
||||
"inform on\n".encode(),
|
||||
])
|
||||
while self._run:
|
||||
line = await reader.readline()
|
||||
if not line:
|
||||
_LOGGER.warning("FHEM disconnected: {}".format(line))
|
||||
raise OSError("Disconnect")
|
||||
line = line.decode()
|
||||
_LOGGER.debug("FHEM received line: {}".format(line))
|
||||
await self._process_line(line)
|
||||
except OSError:
|
||||
if self._connection_last_state != 'FAILED':
|
||||
self.hass.components.persistent_notification.async_create("FHEM connection failed",
|
||||
title="No FHEM connection")
|
||||
_LOGGER.error("Connection to FHEM failed {}:{}".format(self._host, self._port))
|
||||
self._connection_last_state = 'FAILED'
|
||||
|
||||
self.connected = False
|
||||
await self._update_all_devices()
|
||||
await asyncio.sleep(self.reconnect_time)
|
||||
self.reconnect_time = min(2 * self.reconnect_time, self.reconnect_time_max)
|
||||
self.hass.loop.create_task(self._connection())
|
||||
|
||||
async def _process_line(self, line):
|
||||
if line.startswith(self._cul_device_name + " "): # Status update message
|
||||
_, device_name, command = line.split(" ", 2)
|
||||
for device in self._devices[device_name]:
|
||||
_LOGGER.debug("FHEM line received (device): " + device_name + ": " + line)
|
||||
await device.line_received(command.strip())
|
||||
else: # potential response to displayattr
|
||||
split_line = line.split(" ", 1)
|
||||
if len(split_line) == 2:
|
||||
device_name, command = split_line
|
||||
for device in self._devices[device_name]:
|
||||
await device.line_received(command.strip())
|
||||
|
||||
def write_line(self, line):
|
||||
if self._writer:
|
||||
line += '\n'
|
||||
self._writer.write(line.encode())
|
||||
|
||||
def fhem_set(self, id, *arguments):
|
||||
"""
|
||||
Send command to FHEM using this device
|
||||
:param arguments: string or list of strings containing command parameters
|
||||
"""
|
||||
arguments = " ".join([str(a) for a in arguments])
|
||||
self._writer.write("set {} {}\n".format(id, arguments).encode())
|
||||
|
||||
|
||||
def device_error_reporting(hass, received_line, component_type, component_name):
|
||||
if received_line.startswith('overheat'):
|
||||
overheat = received_line.split(':')[1]
|
||||
overheat = overheat.strip().lower()
|
||||
assert overheat == 'on' or overheat == 'off'
|
||||
if overheat == 'on':
|
||||
text = "FHEM: {} overheated: <br><b>{}</b>".format(component_type, component_name)
|
||||
hass.components.persistent_notification.async_create(text, title="{} overheat".format(component_type))
|
||||
elif received_line.startswith('overload'):
|
||||
overload = received_line.split(':')[1]
|
||||
overload = overload.strip().lower()
|
||||
assert overload == 'on' or overload == 'off'
|
||||
if overload == 'on':
|
||||
text = "FHEM: {} overloaded: <br><b>{}</b>".format(component_type, component_name)
|
||||
hass.components.persistent_notification.async_create(text, title="{} overloaded".format(component_type))
|
||||
116
custom_components/fhem/binary_sensor.py
Normal file
116
custom_components/fhem/binary_sensor.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""Support for covers from FHEM"""
|
||||
|
||||
import voluptuous as vol
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice, \
|
||||
DEVICE_CLASS_MOTION, DEVICE_CLASS_OPENING, DEVICE_CLASS_BATTERY
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.core import callback
|
||||
from . import DATA_FHEM, device_error_reporting, CONF_FHEM_SENSOR_TYPE, CONF_FHEM_IDS
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_FHEM_IDS): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_FHEM_SENSOR_TYPE): vol.In(('motion', 'cover', 'battery')),
|
||||
})
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
connection = hass.data[DATA_FHEM]
|
||||
|
||||
sensor = FhemBinarySensor(connection,
|
||||
config[CONF_NAME],
|
||||
config[CONF_FHEM_IDS],
|
||||
config[CONF_FHEM_SENSOR_TYPE])
|
||||
|
||||
for dev_id in config[CONF_FHEM_IDS]:
|
||||
connection.register_device(dev_id, sensor)
|
||||
async_add_entities([sensor])
|
||||
|
||||
|
||||
class FhemBinarySensor(BinarySensorDevice):
|
||||
|
||||
def __init__(self, connection, name, ids, sensor_type):
|
||||
self._on = None
|
||||
self.connection = connection
|
||||
self._ids = ids
|
||||
self._name = name
|
||||
self._type = sensor_type
|
||||
self._available = True
|
||||
self._state = None
|
||||
self._unsubscribe_motion_off = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return self._available and self.connection.connected
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
return self._state
|
||||
|
||||
async def line_received(self, line):
|
||||
if self._type == 'motion' and line.startswith('motion'):
|
||||
self._available = True
|
||||
self._state = True
|
||||
self._start_motion_off_callback(delay_in_seconds=31)
|
||||
await self.async_update_ha_state()
|
||||
elif self._type == 'cover' and line.startswith('cover'):
|
||||
self._available = True
|
||||
_, new_value = line.split(':')
|
||||
self._state = not (new_value.strip().lower() == 'closed')
|
||||
await self.async_update_ha_state()
|
||||
elif self._type == 'battery' and line.startswith('battery'):
|
||||
self._available = True
|
||||
_, new_value = line.split(':')
|
||||
self._state = not (new_value.strip().lower() == 'ok')
|
||||
await self.async_update_ha_state()
|
||||
elif line.startswith('ResndFail') or line.startswith('MISSING ACK'):
|
||||
self._available = False
|
||||
await self.async_update_ha_state()
|
||||
else:
|
||||
device_error_reporting(self.hass, line, component_type="Switch", component_name=self.entity_id)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
return None
|
||||
|
||||
def _start_motion_off_callback(self, delay_in_seconds):
|
||||
self._stop_motion_off_callback()
|
||||
self._unsubscribe_motion_off = async_call_later(self.hass, delay_in_seconds, self._stop_motion_callback)
|
||||
|
||||
def _stop_motion_off_callback(self):
|
||||
if self._unsubscribe_motion_off is not None:
|
||||
self._unsubscribe_motion_off()
|
||||
self._unsubscribe_motion_off = None
|
||||
|
||||
@callback
|
||||
def _stop_motion_callback(self, now):
|
||||
self._state = False
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
if self._type == 'motion':
|
||||
return DEVICE_CLASS_MOTION
|
||||
elif self._type == 'cover':
|
||||
return DEVICE_CLASS_OPENING
|
||||
elif self._type == 'battery':
|
||||
return DEVICE_CLASS_BATTERY
|
||||
else:
|
||||
return None
|
||||
108
custom_components/fhem/cover.py
Normal file
108
custom_components/fhem/cover.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Support for covers from FHEM"""
|
||||
|
||||
import voluptuous as vol
|
||||
import logging
|
||||
|
||||
from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, \
|
||||
SUPPORT_SET_POSITION, SUPPORT_STOP, ATTR_POSITION
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from . import DATA_FHEM, device_error_reporting, CONF_FHEM_IDS
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_FHEM_IDS): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
})
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
connection = hass.data[DATA_FHEM]
|
||||
|
||||
cover = FhemCover(connection, config[CONF_NAME], config[CONF_FHEM_IDS])
|
||||
for dev_id in config[CONF_FHEM_IDS]:
|
||||
connection.register_device(dev_id, cover)
|
||||
async_add_entities([cover])
|
||||
|
||||
|
||||
class FhemCover(CoverDevice):
|
||||
|
||||
def __init__(self, connection, name, ids):
|
||||
self._position = None
|
||||
self.connection = connection
|
||||
self._ids = ids
|
||||
self._name = name
|
||||
self._available = True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return self._available and self.connection.connected
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION | SUPPORT_STOP
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
return self._position
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed."""
|
||||
if self._position is None:
|
||||
return None
|
||||
return self._position <= 25
|
||||
|
||||
async def async_close_cover(self, **kwargs):
|
||||
await self.async_set_cover_position(**{ATTR_POSITION: 0})
|
||||
|
||||
async def async_open_cover(self, **kwargs):
|
||||
await self.async_set_cover_position(**{ATTR_POSITION: 100})
|
||||
|
||||
async def async_set_cover_position(self, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
if ATTR_POSITION in kwargs:
|
||||
position = kwargs[ATTR_POSITION]
|
||||
self._position = position
|
||||
self.connection.fhem_set(self._ids[0], int(position))
|
||||
|
||||
async def async_stop_cover(self, **kwargs):
|
||||
"""Stop the cover."""
|
||||
self.connection.fhem_set(self._ids[0], 'stop')
|
||||
|
||||
async def line_received(self, line):
|
||||
if line.startswith('motor:'):
|
||||
self._available = True
|
||||
_, new_motor_state, new_position = line.split(':')
|
||||
new_position = new_position.strip().lower()
|
||||
new_motor_state = new_motor_state.strip().lower()
|
||||
assert new_motor_state == 'stop' or new_motor_state == 'up' or new_motor_state == 'down', \
|
||||
'Unknown motor state ' + new_motor_state
|
||||
if new_motor_state == 'stop':
|
||||
if new_position == 'on':
|
||||
self._position = 100
|
||||
elif new_position == 'off':
|
||||
self._position = 0
|
||||
else:
|
||||
new_position = int(float(new_position)) # first convert from string to floating point then truncate
|
||||
assert 0 <= new_position <= 100
|
||||
self._position = new_position
|
||||
await self.async_update_ha_state()
|
||||
|
||||
elif line.startswith('ResndFail') or line.startswith('MISSING ACK'):
|
||||
self._available = False
|
||||
await self.async_update_ha_state()
|
||||
else:
|
||||
device_error_reporting(self.hass, line, component_type="Cover", component_name=self.entity_id)
|
||||
130
custom_components/fhem/light.py
Normal file
130
custom_components/fhem/light.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""Support for lights from FHEM"""
|
||||
|
||||
import voluptuous as vol
|
||||
import logging
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light, ATTR_TRANSITION
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from . import DATA_FHEM, device_error_reporting, CONF_FHEM_IDS
|
||||
|
||||
CONF_DIMMER = 'dimmer'
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_FHEM_IDS): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_DIMMER, default=False): cv.boolean,
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
})
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up lights for KNX platform."""
|
||||
connection = hass.data[DATA_FHEM]
|
||||
light = FhemLight(connection, config[CONF_NAME], config[CONF_FHEM_IDS], dimmer=config[CONF_DIMMER])
|
||||
for dev_id in config[CONF_FHEM_IDS]:
|
||||
connection.register_device(dev_id, light)
|
||||
async_add_entities([light])
|
||||
|
||||
|
||||
class FhemLight(Light):
|
||||
|
||||
def __init__(self, connection, name, ids, dimmer=False):
|
||||
self._brightness = None
|
||||
self.connection = connection
|
||||
self._dimmer = dimmer
|
||||
self._ids = ids
|
||||
self._name = name
|
||||
self._available = True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return self._available and self.connection.connected
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
flags = 0
|
||||
if self._dimmer:
|
||||
flags |= SUPPORT_BRIGHTNESS
|
||||
return flags
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if light is on."""
|
||||
if self._brightness is not None:
|
||||
return self._brightness > 0
|
||||
else:
|
||||
return None
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS, None)
|
||||
transition_time = kwargs.get(ATTR_TRANSITION, 0.5)
|
||||
|
||||
if brightness is None:
|
||||
brightness = 255
|
||||
if self._dimmer:
|
||||
# zero in the middle is the time until light is switched off,
|
||||
# which is disabled here when passing 0
|
||||
self.connection.fhem_set(self._ids[0], int(brightness / 255 * 100), 0, transition_time)
|
||||
else:
|
||||
self.connection.fhem_set(self._ids[0], 'on')
|
||||
self._brightness = brightness
|
||||
await self.async_update_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
self.connection.fhem_set(self._ids[0], 'off')
|
||||
|
||||
async def line_received(self, line):
|
||||
if line.startswith('dim:'):
|
||||
self._available = True
|
||||
_, new_dim_state, new_level = line.split(':')
|
||||
new_level = new_level.strip().lower()
|
||||
new_dim_state = new_dim_state.strip().lower()
|
||||
assert new_dim_state == 'stop' or new_dim_state == 'up' or new_dim_state == 'down'
|
||||
if new_dim_state == 'stop':
|
||||
if new_level == 'on':
|
||||
self._brightness = 255
|
||||
elif new_level == 'off':
|
||||
self._brightness = 0
|
||||
else:
|
||||
new_level = int(float(new_level)) # first convert from string to floating point then truncate
|
||||
assert 0 <= new_level <= 100
|
||||
self._brightness = int(new_level / 100 * 255)
|
||||
await self.async_update_ha_state()
|
||||
elif line.startswith('level:') and not self._dimmer:
|
||||
self._available = True
|
||||
_, new_level = line.split(':')
|
||||
new_level = new_level.strip().lower()
|
||||
if new_level == 'on':
|
||||
self._brightness = 255
|
||||
elif new_level == 'off':
|
||||
self._brightness = 0
|
||||
else:
|
||||
try:
|
||||
new_level = int(float(new_level)) # first convert from string to floating point then truncate
|
||||
assert 0 <= new_level <= 100
|
||||
self._brightness = int(new_level / 100 * 255)
|
||||
except ValueError:
|
||||
pass
|
||||
await self.async_update_ha_state()
|
||||
elif line.startswith('ResndFail') or line.startswith('MISSING ACK'):
|
||||
self._available = False
|
||||
await self.async_update_ha_state()
|
||||
else:
|
||||
device_error_reporting(self.hass, line, component_type="Light", component_name=self.entity_id)
|
||||
9
custom_components/fhem/manifest.json
Normal file
9
custom_components/fhem/manifest.json
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
{
|
||||
"domain": "fhem",
|
||||
"name": "FHEM",
|
||||
"documentation": "",
|
||||
"dependencies": [],
|
||||
"codeowners": ["@mabau"],
|
||||
"requirements": []
|
||||
}
|
||||
83
custom_components/fhem/sensor.py
Normal file
83
custom_components/fhem/sensor.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Support for covers from FHEM"""
|
||||
|
||||
import voluptuous as vol
|
||||
import logging
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from . import DATA_FHEM, device_error_reporting, CONF_FHEM_SENSOR_TYPE, CONF_FHEM_IDS
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_FHEM_IDS): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_FHEM_SENSOR_TYPE, default='brightness'): vol.In(('brightness',)),
|
||||
})
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
connection = hass.data[DATA_FHEM]
|
||||
|
||||
sensor = FhemSensor(connection,
|
||||
config[CONF_NAME],
|
||||
config[CONF_FHEM_IDS],
|
||||
config[CONF_FHEM_SENSOR_TYPE])
|
||||
for dev_id in config[CONF_FHEM_IDS]:
|
||||
connection.register_device(dev_id, sensor)
|
||||
async_add_entities([sensor])
|
||||
|
||||
|
||||
class FhemSensor(Entity):
|
||||
|
||||
def __init__(self, connection, name, ids, sensor_type):
|
||||
self._on = None
|
||||
self.connection = connection
|
||||
self._ids = ids
|
||||
self._name = name
|
||||
self._type = sensor_type
|
||||
self._available = True
|
||||
self._state = None
|
||||
self._unsubscribe_motion_off = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return self._available and self.connection.connected
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._state
|
||||
|
||||
async def line_received(self, line):
|
||||
if self._type == 'brightness' and line.startswith('brightness'):
|
||||
self._available = True
|
||||
_, new_value = line.split(':')
|
||||
self._state = int(float(new_value) / 255 * 100)
|
||||
await self.async_update_ha_state()
|
||||
elif line.startswith('ResndFail') or line.startswith('MISSING ACK'):
|
||||
self._available = False
|
||||
await self.async_update_ha_state()
|
||||
else:
|
||||
device_error_reporting(self.hass, line, component_type="Sensor", component_name=self.entity_id)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
if self._type == 'brightness':
|
||||
return '%'
|
||||
75
custom_components/fhem/switch.py
Normal file
75
custom_components/fhem/switch.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""Support for covers from FHEM"""
|
||||
|
||||
import voluptuous as vol
|
||||
import logging
|
||||
|
||||
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from . import DATA_FHEM, device_error_reporting, CONF_FHEM_IDS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_FHEM_IDS): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
})
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
connection = hass.data[DATA_FHEM]
|
||||
switch = FhemSwitch(connection, config[CONF_NAME], config[CONF_FHEM_IDS])
|
||||
for dev_id in config[CONF_FHEM_IDS]:
|
||||
connection.register_device(dev_id, switch)
|
||||
async_add_entities([switch])
|
||||
|
||||
|
||||
class FhemSwitch(SwitchDevice):
|
||||
|
||||
def __init__(self, connection, name, ids):
|
||||
self._on = None
|
||||
self.connection = connection
|
||||
self._ids = ids
|
||||
self._name = name
|
||||
self._available = True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return self._available and self.connection.connected
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._on
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
self.connection.fhem_set(self._ids[0], 'on')
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
self.connection.fhem_set(self._ids[0], 'off')
|
||||
|
||||
async def line_received(self, line):
|
||||
if line.startswith('level:'):
|
||||
_, new_state = line.split(':')
|
||||
new_state = new_state.strip().lower()
|
||||
if new_state in ('on', '100'):
|
||||
self._on = True
|
||||
if new_state in ('off', '0'):
|
||||
self._on = False
|
||||
await self.async_update_ha_state()
|
||||
elif line.startswith('ResndFail') or line.startswith('MISSING ACK'):
|
||||
self._available = False
|
||||
await self.async_update_ha_state()
|
||||
else:
|
||||
device_error_reporting(self.hass, line, component_type="Switch", component_name=self.entity_id)
|
||||
Reference in New Issue
Block a user