parent
fab83c86f4
commit
72b4d38e80
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
107
fhem/cover.py
107
fhem/cover.py
|
@ -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)
|
|
@ -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)
|
||||||
|
|
|
@ -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 '%'
|
|
@ -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
12
todo
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue