FHEM integration

- sensors & binary sensors
- cover
This commit is contained in:
Martin Bauer 2019-06-15 11:50:20 +02:00
parent fab83c86f4
commit 72b4d38e80
9 changed files with 597 additions and 95 deletions

View File

@ -1,5 +1,8 @@
# ---------------------------------- Lights ------------------------------------
light: light:
- name: Schlafzimmer Deckenlampe - name: Schlafzimmer Deckenlampe
dimmer: True dimmer: True
fhem_ids: fhem_ids:
@ -9,6 +12,8 @@ light:
- Schlafzimmer_Deckenlampe_Sw1_V_02 - Schlafzimmer_Deckenlampe_Sw1_V_02
groups: groups:
- bedroom - bedroom
- name: Arbeitszimmer Martin Deckenlampe - name: Arbeitszimmer Martin Deckenlampe
dimmer: True dimmer: True
fhem_ids: fhem_ids:
@ -18,6 +23,8 @@ light:
- ArbeitszimmerMartin_Deckenlampe_Sw1_V_02 - ArbeitszimmerMartin_Deckenlampe_Sw1_V_02
groups: groups:
- office_martin - office_martin
- name: Arbeitszimmer Rebecca Deckenlampe - name: Arbeitszimmer Rebecca Deckenlampe
dimmer: True dimmer: True
fhem_ids: fhem_ids:
@ -28,31 +35,64 @@ light:
groups: groups:
- office_rebecca - office_rebecca
#switch:
#- name: Bad Lüfter # ---------------------------------- Covers ------------------------------------
# dimmer: False
# fhem_ids: cover:
# - Bad_Luefter
#
#cover: - name: Arbeitszimmer Martin Rollo
#- name: Arbeitszimmer Martin Rollo fhem_ids:
# fhem_ids: - ArbeitszimmerMartin_Rollo
# - ArbeitszimmerMartin_Rollo groups:
# groups: - office_martin
# - office_martin
#- name: Schlafzimmer Rollo klein
# fhem_ids: - name: Schlafzimmer Rollo klein
# - Schlafzimmer_RolloKlein fhem_ids:
# groups: - Schlafzimmer_RolloKlein
# - bedroom groups:
#- name: Schlafzimmer Rollo groß - bedroom
# fhem_ids:
# - Schlafzimmer_RolloGross
# groups: - name: Schlafzimmer Rollo groß
# - bedroom fhem_ids:
#- name: Arbeitszimmer Rebecca Rollo - Schlafzimmer_RolloGross
# fhem_ids: groups:
# - ArbeitszimmerRebecca_Rollo - bedroom
# groups:
# - office_rebecca
# - name: Arbeitszimmer Rebecca Rollo
fhem_ids:
- ArbeitszimmerRebecca_Rollo
groups:
- office_rebecca
# ---------------------------------- Switches ------------------------------------
switch:
- name: Bad Lüfter
fhem_ids:
- Bad_Luefter
# ------------------------------ Motion Sensors ------------------------------------
binary_sensor:
- name: Arbeitszimmer Martin Bewegungsmelder Batterie
fhem_ids:
- ArbeitszimmerMartin_Bewegungsmelder
fhem_sensor_type: battery
- name: Arbeitszimmer Martin Bewegungsmelder Bewegung
fhem_ids:
- ArbeitszimmerMartin_Bewegungsmelder
fhem_sensor_type: motion
sensor:
- name: Arbeitszimmer Martin Bewegungsmelder Helligkeit
fhem_ids:
- ArbeitszimmerMartin_Bewegungsmelder
fhem_sensor_type: brightness

View File

@ -1,6 +1,10 @@
resources: resources:
- type: js - type: js
url: /local/custom_ui/state-card-custom-cover.js url: /local/custom_ui/state-card-custom-cover.js
- type: js
url: /local/custom_ui/lovelace-toggle-lock-entity-row/toggle-lock-entity-row.js
- type: module
url: /local/custom_ui/mini-graph-card-bundle.js?v=0.4.3
title: Home title: Home
views: views:
- cards: - cards:
@ -90,14 +94,66 @@ views:
title: Verbrauch title: Verbrauch
type: entities type: entities
- entities: - entities:
- switch.trockner - entity: switch.trockner
- switch.waschmaschine type: 'custom:toggle-lock-entity-row'
- switch.spulmaschine - entity: switch.waschmaschine
- switch.backofen type: 'custom:toggle-lock-entity-row'
- switch.herd_phase_1 - entity: switch.spulmaschine
- switch.herd_phase_2 type: 'custom:toggle-lock-entity-row'
- switch.herd_phase_3 - entity: switch.backofen
type: 'custom:toggle-lock-entity-row'
- entity: switch.herd_phase_1
type: 'custom:toggle-lock-entity-row'
- entity: switch.herd_phase_2
type: 'custom:toggle-lock-entity-row'
- entity: switch.herd_phase_3
type: 'custom:toggle-lock-entity-row'
show_header_toggle: false show_header_toggle: false
title: Sicherheitsabschaltung title: Sicherheitsabschaltung
type: entities type: entities
- animate: true
entities:
- sensor.waschmaschine_verbrauch
- sensor.trockner_verbrauch
name: Waschen & Trocknen
type: 'custom:mini-graph-card'
- animate: true
entities:
- entity: sensor.spulmaschine_verbrauch
name: Spühlmaschine
- entity: sensor.backofen_verbrauch
name: Backofen
- entity: sensor.herd_phase_1_verbrauch
name: Herd P1
- entity: sensor.herd_phase_2_verbrauch
name: Herd P2
- entity: sensor.herd_phase_3_verbrauch
name: Herd P3
hours_to_show: 8
name: Küche
points_per_hour: 4
type: 'custom:mini-graph-card'
- animate: true
entities:
- entity: sensor.fritz_box_7490_kbyte_sec_received
name: Down
- entity: sensor.fritz_box_7490_kbyte_sec_sent
name: Up
hours_to_show: 2
name: Internet
points_per_hour: 30
type: 'custom:mini-graph-card'
title: Admin title: Admin
- cards:
- entities:
- light.arbeitszimmer_martin_deckenlampe
- name: Rollo Arbeitszimmer
entity: cover.arbeitszimmer_martin_rollo
type: 'custom:state-card-custom-cover'
- binary_sensor.arbeitszimmer_martin_bewegungsmelder_bewegung
- sensor.arbeitszimmer_martin_bewegungsmelder_helligkeit
- switch.bad_lufter
show_header_toggle: false
title: Test
type: entities
title: Test

View File

@ -3,12 +3,14 @@
import logging import logging
import voluptuous as vol import voluptuous as vol
import asyncio import asyncio
from collections import defaultdict
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_CUL_DEVICE_NAME = 'cul_device_name' CONF_CUL_DEVICE_NAME = 'cul_device_name'
CONF_FHEM_SENSOR_TYPE = 'fhem_sensor_type'
CONF_FHEM_IDS = 'fhem_ids'
DOMAIN = 'fhem' DOMAIN = 'fhem'
DATA_FHEM = "data_fhem" DATA_FHEM = "data_fhem"
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
@ -39,9 +41,18 @@ class FhemConnection:
self.reconnect_time_start = 1 self.reconnect_time_start = 1
self.reconnect_time_max = 60 self.reconnect_time_max = 60
self.reconnect_time = self.reconnect_time_start self.reconnect_time = self.reconnect_time_start
self.devices = {} self._devices = defaultdict(list)
self._run = False self._run = False
self._writer = None self._writer = None
self._connection_last_state = 'UNKNOWN'
def register_device(self, id, d):
self._devices[id].append(d)
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): async def start(self):
self._run = True self._run = True
@ -56,10 +67,11 @@ class FhemConnection:
try: try:
reader, writer = await asyncio.open_connection(self._host, self._port) reader, writer = await asyncio.open_connection(self._host, self._port)
_LOGGER.info("Connected to FHEM {}:{}".format(self._host, self._port)) _LOGGER.info("Connected to FHEM {}:{}".format(self._host, self._port))
self._connection_last_state = 'CONNECTED'
self._writer = writer self._writer = writer
self.connected = True self.connected = True
for device in self.devices.values(): await self._update_all_devices()
await device.async_update_ha_state()
self.reconnect_time = self.reconnect_time_start self.reconnect_time = self.reconnect_time_start
writer.writelines([ writer.writelines([
@ -72,10 +84,14 @@ class FhemConnection:
_LOGGER.debug("FHEM received line: {}".format(line)) _LOGGER.debug("FHEM received line: {}".format(line))
await self._process_line(line) await self._process_line(line)
except OSError: except OSError:
_LOGGER.warning("Connection to FHEM failed {}:{}".format(self._host, self._port)) 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 self.connected = False
for device in self.devices.values(): await self._update_all_devices()
await device.async_update_ha_state()
await asyncio.sleep(self.reconnect_time) await asyncio.sleep(self.reconnect_time)
self.reconnect_time = min(2 * self.reconnect_time, self.reconnect_time) self.reconnect_time = min(2 * self.reconnect_time, self.reconnect_time)
self.hass.loop.create_task(self._connection()) self.hass.loop.create_task(self._connection())
@ -83,14 +99,15 @@ class FhemConnection:
async def _process_line(self, line): async def _process_line(self, line):
if line.startswith(self._cul_device_name + " "): # Status update message if line.startswith(self._cul_device_name + " "): # Status update message
_, device_name, command = line.split(" ", 2) _, device_name, command = line.split(" ", 2)
if device_name in self.devices: for device in self._devices[device_name]:
await self.devices[device_name].line_received(command.strip()) _LOGGER.debug("FHEM line received (device): " + device_name + ": " + line)
await device.line_received(command.strip())
else: # potential response to displayattr else: # potential response to displayattr
split_line = line.split(" ", 1) split_line = line.split(" ", 1)
if len(split_line) == 2: if len(split_line) == 2:
device_name, command = split_line device_name, command = split_line
if device_name in self.devices: for device in self._devices[device_name]:
await self.devices[device_name].line_received(command.strip()) await device.line_received(command.strip())
def write_line(self, line): def write_line(self, line):
if self._writer: if self._writer:
@ -104,3 +121,20 @@ class FhemConnection:
""" """
arguments = " ".join([str(a) for a in arguments]) arguments = " ".join([str(a) for a in arguments])
self._writer.write("set {} {}\n".format(id, arguments).encode()) 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
fhem/binary_sensor.py Normal file
View 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

View File

@ -0,0 +1,107 @@
"""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'
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)

View File

@ -3,18 +3,16 @@
import voluptuous as vol import voluptuous as vol
import logging import logging
from homeassistant.components.light import ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light from homeassistant.components.light import ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light, ATTR_TRANSITION
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import DATA_FHEM, device_error_reporting, CONF_FHEM_IDS
CONF_DIMMER = 'dimmer'
from . import DATA_FHEM
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_FHEM_IDS = 'fhem_ids'
CONF_DIMMER = 'dimmer'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_FHEM_IDS): vol.All(cv.ensure_list, [cv.string]), vol.Required(CONF_FHEM_IDS): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_DIMMER, default=False): cv.boolean, vol.Optional(CONF_DIMMER, default=False): cv.boolean,
@ -25,10 +23,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up lights for KNX platform.""" """Set up lights for KNX platform."""
connection = hass.data[DATA_FHEM] connection = hass.data[DATA_FHEM]
light = FhemLight(connection, config[CONF_NAME], config[CONF_FHEM_IDS], dimmer=config[CONF_DIMMER]) light = FhemLight(connection, config[CONF_NAME], config[CONF_FHEM_IDS], dimmer=config[CONF_DIMMER])
for dev_id in config[CONF_FHEM_IDS]: for dev_id in config[CONF_FHEM_IDS]:
connection.devices[dev_id] = light connection.register_device(dev_id, light)
async_add_entities([light]) async_add_entities([light])
@ -42,28 +39,15 @@ class FhemLight(Light):
self._name = name self._name = name
self._available = True self._available = True
@property
def name(self):
return self._name
@property @property
def should_poll(self): def should_poll(self):
"""No polling needed.""" """No polling needed."""
return False return False
@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
@property
def name(self):
"""Return the name of the KNX device."""
return self._name
@property @property
def available(self) -> bool: def available(self) -> bool:
return self._available and self.connection.connected return self._available and self.connection.connected
@ -76,21 +60,40 @@ class FhemLight(Light):
flags |= SUPPORT_BRIGHTNESS flags |= SUPPORT_BRIGHTNESS
return flags 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): async def async_turn_on(self, **kwargs):
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness) brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness)
transition_time = kwargs.get(ATTR_TRANSITION, None)
if brightness is None: if brightness is None:
brightness = 255 brightness = 255
if self._dimmer: if self._dimmer:
if transition_time is not None:
# 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], brightness / 255 * 100, 0, transition_time)
else:
self.connection.fhem_set(self._ids[0], brightness / 255 * 100) self.connection.fhem_set(self._ids[0], brightness / 255 * 100)
else: else:
self.connection.fhem_set(self._ids[0], 'on') self.connection.fhem_set(self._ids[0], 'on')
self._brightness = brightness
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
self.connection.fhem_set(self._ids[0], 'off') self.connection.fhem_set(self._ids[0], 'off')
async def line_received(self, line): async def line_received(self, line):
_LOGGER.debug("FHEM line received (device): " + self.name + ": " + line)
if line.startswith('dim:'): if line.startswith('dim:'):
self._available = True self._available = True
_, new_dim_state, new_level = line.split(':') _, new_dim_state, new_level = line.split(':')
@ -123,24 +126,8 @@ class FhemLight(Light):
except ValueError: except ValueError:
pass pass
await self.async_update_ha_state() await self.async_update_ha_state()
elif line.startswith('overheat'):
overheat = line.split(':')[1]
overheat = overheat.strip().lower()
assert overheat == 'on' or overheat == 'off'
if overheat == 'on':
self.hass.components.persistent_notification.async_create(
"FHEM: Light overheated: <br>"
"<b>{0}</b>".format(self.entity_id),
title="Light overheat")
elif line.startswith('overload'):
overload = line.split(':')[1]
overload = overload.strip().lower()
assert overload == 'on' or overload == 'off'
if overload == 'on':
self.hass.components.persistent_notification.async_create(
"FHEM: Light overloaded: <br>"
"<b>{0}</b>".format(self.entity_id),
title="Light overloaded")
elif line.startswith('ResndFail') or line.startswith('MISSING ACK'): elif line.startswith('ResndFail') or line.startswith('MISSING ACK'):
self._available = False self._available = False
await self.async_update_ha_state() await self.async_update_ha_state()
else:
device_error_reporting(self.hass, line, component_type="Light", component_name=self.entity_id)

83
fhem/sensor.py Normal file
View 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 '%'

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

12
todo
View File

@ -9,18 +9,21 @@
- KNX config files -> add to git [ok] - KNX config files -> add to git [ok]
- configure frontend - configure frontend [ok]
- add scenes - add scenes
- add brighter/darker action - add brighter/darker action (service!)
- fix door light [ok] - fix door light [ok]
- check out motion detectors in frontend - check out motion detectors in frontend
- check out shutters in frontend - check out shutters in frontend
Frontend: Frontend:
- change cover state card for half open - change cover state card for half open [ok]
FHEM: FHEM:
- add FHEM shutters - reconnection & message reporting code!
- add FHEM shutters [ok]
- motion detector [ok]
- service for device stuff (up/down time, auto-off stuff)
- check FHEM reconnection - check FHEM reconnection
- what happens when FHEM stops? - what happens when FHEM stops?
- what happens when stick is pulled? - what happens when stick is pulled?
@ -30,6 +33,7 @@ Owntracks
Other: Other:
- grouped motion sensors (last motion where and when)
- media player card: - media player card:
https://github.com/kalkih/mini-media-player https://github.com/kalkih/mini-media-player