commit 639f14a438d34619faf4487faf2c69789df672b1 Author: Martin Bauer Date: Sat Jun 1 16:17:51 2019 +0200 Initial commit diff --git a/fhem/README.md b/fhem/README.md new file mode 100644 index 0000000..e69de29 diff --git a/fhem/__init__.py b/fhem/__init__.py new file mode 100644 index 0000000..b9c3f77 --- /dev/null +++ b/fhem/__init__.py @@ -0,0 +1,101 @@ +"""FHEM integration""" + +import logging +import voluptuous as vol +import asyncio + +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' +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 = {} + self._run = False + self._writer = None + + 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._writer = writer + self.connected = True + self.reconnect_time = self.reconnect_time_start + writer.writelines([ + "displayattr .*\n".encode(), + "inform on\n".encode(), + ]) + while self._run: + line = await reader.readline() + line = line.decode() + _LOGGER.debug("FHEM received line: {}".format(line)) + await self._process_line(line) + except OSError: + _LOGGER.warning("Connection to FHEM failed {}:{}".format(self._host, self._port)) + self.connected = False + await asyncio.sleep(self.reconnect_time) + self.reconnect_time = min(2 * self.reconnect_time, self.reconnect_time) + 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) + if device_name in self.devices: + await self.devices[device_name].line_received(command.strip()) + else: # potential response to displayattr + split_line = line.split(" ", 1) + if len(split_line) == 2: + device_name, command = split_line + if device_name in self.devices: + await self.devices[device_name].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()) diff --git a/fhem/cover.py b/fhem/cover.py new file mode 100644 index 0000000..e69de29 diff --git a/fhem/light.py b/fhem/light.py new file mode 100644 index 0000000..83d66fd --- /dev/null +++ b/fhem/light.py @@ -0,0 +1,147 @@ +"""Support for lights from FHEM""" + +import voluptuous as vol +import logging + +from homeassistant.components.light import ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light +from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv + +from . import DATA_FHEM + +_LOGGER = logging.getLogger(__name__) + + +CONF_FHEM_IDS = 'fhem_ids' +CONF_DIMMER = 'dimmer' + +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] + #_LOGGER.error("FHEM platform config\n" + str(config)) + + light = FhemLight(connection, config[CONF_NAME], config[CONF_FHEM_IDS], dimmer=config[CONF_DIMMER]) + for dev_id in config[CONF_FHEM_IDS]: + connection.devices[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 should_poll(self): + """No polling needed.""" + 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 + 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 + + async def async_turn_on(self, **kwargs): + brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness) + if brightness is None: + brightness = 255 + + if self._dimmer: + self.connection.fhem_set(self._ids[0], brightness / 255 * 100) + else: + self.connection.fhem_set(self._ids[0], 'on') + + async def async_turn_off(self, **kwargs): + self.connection.fhem_set(self._ids[0], 'off') + + async def line_received(self, line): + _LOGGER.info("FHEM line received (device): " + self.name + ": " + 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('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:
" + "{0}".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:
" + "{0}".format(self.entity_id), + title="Light overloaded") + elif line.startswith('ResndFail') or line.startswith('MISSING ACK'): + self._available = False + await self.async_update_ha_state() diff --git a/fhem/manifest.json b/fhem/manifest.json new file mode 100644 index 0000000..1d39a3a --- /dev/null +++ b/fhem/manifest.json @@ -0,0 +1,9 @@ + +{ + "domain": "fhem", + "name": "FHEM", + "documentation": "", + "dependencies": [], + "codeowners": ["@mabau"], + "requirements": [] +} \ No newline at end of file diff --git a/fhem/switch.py b/fhem/switch.py new file mode 100644 index 0000000..e69de29 diff --git a/knx_config/Nemsdorf_2019_06.knxproj b/knx_config/Nemsdorf_2019_06.knxproj new file mode 100644 index 0000000..1a3cf63 Binary files /dev/null and b/knx_config/Nemsdorf_2019_06.knxproj differ diff --git a/knx_config/Nemsdorf_2019_07.knxproj b/knx_config/Nemsdorf_2019_07.knxproj new file mode 100644 index 0000000..47c054f Binary files /dev/null and b/knx_config/Nemsdorf_2019_07.knxproj differ diff --git a/knx_config/Nemsdorf_Bewegungsmelder_2019_06.knxproj b/knx_config/Nemsdorf_Bewegungsmelder_2019_06.knxproj new file mode 100644 index 0000000..7677b27 Binary files /dev/null and b/knx_config/Nemsdorf_Bewegungsmelder_2019_06.knxproj differ diff --git a/knx_config/Nemsdorf_Bewegungsmelder_2019_07.knxproj b/knx_config/Nemsdorf_Bewegungsmelder_2019_07.knxproj new file mode 100644 index 0000000..fe79056 Binary files /dev/null and b/knx_config/Nemsdorf_Bewegungsmelder_2019_07.knxproj differ diff --git a/knx_config/export_project1.csv b/knx_config/export_project1.csv new file mode 100644 index 0000000..4295684 --- /dev/null +++ b/knx_config/export_project1.csv @@ -0,0 +1,293 @@ +"Gruppenadressen","0/-/-","","","","" +"SammlungUnterputzAktoren","0/0/-","","","","" +"AussenleuchteHaustüren Schalten","0/0/1","","","","" +"AussenleuchteHaustüren RM Schalten","0/0/2","","","","" +"AussenleuchteObenNW Schalten","0/0/3","","","","" +"AussenleuchteObenNW RM Schalten","0/0/4","","","","" +"TreppenhausObenTemperatur","0/0/5","","","","DPST-9-1" +"TemperaturEsszimmer","0/0/6","","","","" +"AlarmLEDLinksNormal","0/0/7","","","","" +"SzeneEsszimmerUntenLinks","0/0/8","","","","" +"SzeneEsszimmerMitteLinks","0/0/9","","","","" +"SzeneEsszimmerMitteRechts","0/0/10","","","","" +"SzeneEsszimmerObenLinks","0/0/11","","","","" +"SzeneEsszimmerObenRechts","0/0/12","","","","" +"AlarmLEDLinksUeberlagert","0/0/13","","","","" +"AlarmLEDRechtsNormal","0/0/14","","","","" +"KlingelOben Schalten","0/0/15","","","","" +"AlarmLEDRechtsUeberlagert","0/0/16","","","","" +"SzeneTerrassentuerMitteLinks","0/0/17","","","","" +"SzeneTerrassentuerMitteRechts","0/0/18","","","","" +"SzeneEsszimmerUntenRechts","0/0/19","","","","" +"SzeneEingangMitteLinks","0/0/20","","","","" +"SzeneEingangMitteRechts","0/0/21","","","","" +"SzeneEingangUntenLinks","0/0/22","","","","" +"SzeneEingangUntenRechts","0/0/23","","","","" +"TemperaturGang","0/0/24","","","","" +"TemperaturWohnzimmerGangTuer","0/0/25","","","","" +"TemperaturWohnzimmerAussenTuer","0/0/26","","","","" +"SzeneWohnzimmerObenLinks","0/0/27","","","","" +"SzeneWohnzimmerObenRechts","0/0/28","","","","" +"SzeneWohnzimmerMitteLinks","0/0/29","","","","" +"SzeneWohnzimmerMitteRechts","0/0/30","","","","" +"SzeneWohnzimmerUntenLinks","0/0/31","","","","" +"SzeneWohnzimmerUntenRechts","0/0/32","","","","" +"D1","0/1/-","","","","" +"Wohnzimmerlampe Schalten","0/1/3","","","","" +"Wohnzimmerlampe Dimmen","0/1/6","","","","" +"Wohnzimmerlampe Helligkeit","0/1/7","","","","" +"Wohnzimmerlampe RM Schalten","0/1/8","","","","" +"Wohnzimmerlampe RM Helligkeit","0/1/9","","","","" +"Wohnzimmerlampe Kurzschluss","0/1/14","","","","" +"Wohnzimmerlampe Lastausfall","0/1/15","","","","" +"Wohnzimmerlampe BSZ Neustart","0/1/17","","","","" +"Wohnzimmerlampe BSZ Wert","0/1/18","","","","" +"Wohnzimmerlampe BSZ Ablauf","0/1/19","","","","" +"Wohnzimmerlampe Lastart","0/1/20","","","","" +"EsszimmerlampeWest Schalten","0/1/21","","","","" +"EsszimmerlampeWest Dimmen","0/1/24","","","","" +"EsszimmerlampeWest Helligkeit","0/1/25","","","","" +"EsszimmerlampeWest RM Schalten","0/1/26","","","","" +"EsszimmerlampeWest RM Helligkeit","0/1/27","","","","" +"EsszimmerlampeWest Kurzschluss","0/1/32","","","","" +"EsszimmerlampeWest Lastausfall","0/1/33","","","","" +"EsszimmerlampeWest BSZ Neustart","0/1/35","","","","" +"EsszimmerlampeWest BSZ Wert","0/1/36","","","","" +"EsszimmerlampeWest BSZ Ablauf","0/1/37","","","","" +"EsszimmerlampeWest Lastart","0/1/38","","","","" +"EsszimmerlampeMitte Schalten","0/1/39","","","","" +"EsszimmerlampeMitte Dimmen","0/1/42","","","","" +"EsszimmerlampeMitte Helligkeit","0/1/43","","","","" +"EsszimmerlampeMitte RM Schalten","0/1/44","","","","" +"EsszimmerlampeMitte RM Helligkeit","0/1/45","","","","" +"EsszimmerlampeMitte Kurzschluss","0/1/50","","","","" +"EsszimmerlampeMitte Lastausfall","0/1/51","","","","" +"EsszimmerlampeMitte BSZ Neustart","0/1/53","","","","" +"EsszimmerlampeMitte BSZ Wert","0/1/54","","","","" +"EsszimmerlampeMitte BSZ Ablauf","0/1/55","","","","" +"EsszimmerlampeMitte Lastart","0/1/56","","","","" +"Küchenlampe Schalten","0/1/57","","","","" +"Küchenlampe Dimmen","0/1/60","","","","" +"Küchenlampe Helligkeit","0/1/61","","","","" +"Küchenlampe RM Schalten","0/1/62","","","","" +"Küchenlampe RM Helligkeit","0/1/63","","","","" +"Küchenlampe Kurzschluss","0/1/68","","","","" +"Küchenlampe Lastausfall","0/1/69","","","","" +"Küchenlampe BSZ Neustart","0/1/71","","","","" +"Küchenlampe BSZ Wert","0/1/72","","","","" +"Küchenlampe BSZ Ablauf","0/1/73","","","","" +"Küchenlampe Lastart","0/1/74","","","","" +"D2","0/2/-","","","","" +"AussenleuchteUntenSO Schalten","0/2/21","","","","" +"AussenleuchteUntenSO Dimmen","0/2/24","","","","" +"AussenleuchteUntenSO Helligkeit","0/2/25","","","","" +"AussenleuchteUntenSO RM Schalten","0/2/26","","","","" +"AussenleuchteUntenSO RM Helligkeit","0/2/27","","","","" +"AussenleuchteUntenSO Kurzschluss","0/2/32","","","","" +"AussenleuchteUntenSO Lastausfall","0/2/33","","","","" +"AussenleuchteUntenSO BSZ Neustart","0/2/35","","","","" +"AussenleuchteUntenSO BSZ Wert","0/2/36","","","","" +"AussenleuchteUntenSO BSZ Ablauf","0/2/37","","","","" +"AussenleuchteUntenSO Lastart","0/2/38","","","","" +"EsszimmerWandlampe Schalten","0/2/39","","","","" +"EsszimmerWandlampe Dimmen","0/2/42","","","","" +"EsszimmerWandlampe Helligkeit","0/2/43","","","","" +"EsszimmerWandlampe RM Schalten","0/2/44","","","","" +"EsszimmerWandlampe RM Helligkeit","0/2/45","","","","" +"EsszimmerWandlampe Kurzschluss","0/2/50","","","","" +"EsszimmerWandlampe Lastausfall","0/2/51","","","","" +"EsszimmerWandlampe BSZ Neustart","0/2/53","","","","" +"EsszimmerWandlampe BSZ Wert","0/2/54","","","","" +"EsszimmerWandlampe BSZ Ablauf","0/2/55","","","","" +"EsszimmerWandlampe Lastart","0/2/56","","","","" +"D3","0/3/-","","","","" +"Gang Schalten","0/3/3","","","","" +"Gang Dimmen","0/3/6","","","","DPT-3" +"Gang Helligkeit","0/3/7","","","","DPST-5-4" +"Gang RM Schalten","0/3/8","","","","" +"Gang RM Helligkeit","0/3/9","","","","" +"Gang Kurzschluss","0/3/14","","","","" +"Gang Lastausfall","0/3/15","","","","" +"Gang BSZ Neustart","0/3/17","","","","" +"Gang BSZ Wert","0/3/18","","","","" +"Gang BSZ Ablauf","0/3/19","","","","" +"Gang Lastart","0/3/20","","","","" +"Bad Schalten","0/3/21","","","","" +"Bad Dimmen","0/3/24","","","","" +"Bad Helligkeit","0/3/25","","","","" +"Bad RM Schalten","0/3/26","","","","" +"Bad RM Helligkeit","0/3/27","","","","" +"Bad Kurzschluss","0/3/32","","","","" +"Bad Lastausfall","0/3/33","","","","" +"Bad BSZ Neustart","0/3/35","","","","" +"Bad BSZ Wert","0/3/36","","","","" +"Bad BSZ Ablauf","0/3/37","","","","" +"Bad Lastart","0/3/38","","","","" +"GangWindfang Schalten","0/3/39","","","","" +"GangWindfang Dimmen","0/3/42","","","","" +"GangWindfang Helligkeit","0/3/43","","","","" +"GangWindfang RM Schalten","0/3/44","","","","" +"GangWindfang RM Helligkeit","0/3/45","","","","" +"GangWindfang Kurzschluss","0/3/50","","","","" +"GangWindfang Lastausfall","0/3/51","","","","" +"GangWindfang BSZ Neustart","0/3/53","","","","" +"GangWindfang BSZ Wert","0/3/54","","","","" +"GangWindfang BSZ Ablauf","0/3/55","","","","" +"GangWindfang Lastart","0/3/56","","","","" +"LichtWaschküche Schalten","0/3/57","","","","" +"LichtWaschküche Dimmen","0/3/60","","","","" +"LichtWaschküche Helligkeit","0/3/61","","","","" +"LichtWaschküche RM Schalten","0/3/62","","","","" +"LichtWaschküche RM Helligkeit","0/3/63","","","","" +"LichtWaschküche Kurzschluss","0/3/68","","","","" +"LichtWaschküche Lastausfall","0/3/69","","","","" +"LichtWaschküche BSZ Neustart","0/3/71","","","","" +"LichtWaschküche BSZ Wert","0/3/72","","","","" +"LichtWaschküche BSZ Ablauf","0/3/73","","","","" +"LichtWaschküche Lastart","0/3/74","","","","" +"S1","0/4/-","","","","" +"Spülmaschine Schalten","0/4/0","","","","DPST-1-1" +"Spülmaschine RM Schalten","0/4/5","","","","" +"Spülmaschine BSZ Wert","0/4/8","","","","" +"Spülmaschine BSZ Neustart","0/4/9","","","","DPST-1-1" +"Spülmaschine Verbrauch","0/4/11","","","","DPST-9-21" +"Spühlmaschine VerbrauchSumme","0/4/16","","","","" +"Backofen Schalten","0/4/18","","","","DPST-1-1" +"Backofen RM Schalten","0/4/23","","","","" +"Backofen BSZ Wert","0/4/26","","","","" +"Backofen BSZ Neustart","0/4/27","","","","DPST-1-1" +"Backofen Verbrauch","0/4/29","","","","DPST-9-21" +"Backofen VerbrauchSumme","0/4/34","","","","" +"HerdP1 Schalten","0/4/36","","","","DPST-1-1" +"HerdP1 RM Schalten","0/4/41","","","","" +"HerdP1 BSZ Wert","0/4/44","","","","" +"HerdP1 BSZ Neustart","0/4/45","","","","DPST-1-1" +"HerdP1 Verbrauch","0/4/47","","","","DPST-9-21" +"HerdP1 VerbrauchSumme","0/4/52","","","","" +"HerdP2 Schalten","0/4/54","","","","DPST-1-1" +"HerdP2 RM Schalten","0/4/59","","","","" +"HerdP2 BSZ Wert","0/4/62","","","","" +"HerdP2 BSZ Neustart","0/4/63","","","","DPST-1-1" +"HerdP2 Verbrauch","0/4/65","","","","DPST-9-21" +"HerdP2 VerbrauchSumme","0/4/70","","","","" +"HerdP3 Schalten","0/4/72","","","","DPST-1-1" +"HerdP3 RM Schalten","0/4/77","","","","" +"HerdP3 BSZ Wert","0/4/80","","","","" +"HerdP3 BSZ Neustart","0/4/81","","","","DPST-1-1" +"HerdP3 Verbrauch","0/4/83","","","","DPST-9-21" +"HerdP3 VerbrauchSumme","0/4/88","","","","" +"KücheSteckdose1 Schalten","0/4/90","","","","DPST-1-1" +"KücheSteckdose1 RM Schalten","0/4/95","","","","" +"KücheSteckdose1 BSZ Wert","0/4/98","","","","" +"KücheSteckdose1 BSZ Neustart","0/4/99","","","","DPST-1-1" +"KücheSteckdose1 Verbrauch","0/4/101","","","","DPST-9-21" +"KücheSteckdose1 VerbrauchSumme","0/4/106","","","","" +"KücheSteckdose2 Schalten","0/4/108","","","","DPST-1-1" +"KücheSteckdose2 RM Schalten","0/4/113","","","","" +"KücheSteckdose2 BSZ Wert","0/4/116","","","","" +"KücheSteckdose2 BSZ Neustart","0/4/117","","","","DPST-1-1" +"KücheSteckdose2 Verbrauch","0/4/119","","","","DPST-9-21" +"KücheSteckdose2 VerbrauchSumme","0/4/124","","","","" +"ArbeitszimmerSteckdose Schalten","0/4/126","","","","DPST-1-1" +"ArbeitszimmerSteckdose RM Schalten","0/4/131","","","","" +"ArbeitszimmerSteckdose BSZ Wert","0/4/134","","","","" +"ArbeitszimmerSteckdose BSZ Neustart","0/4/135","","","","DPST-1-1" +"ArbeitszimmerSteckdose Verbrauch","0/4/137","","","","DPST-9-21" +"ArbeitszimmerSteckdose VerbrauchSumme","0/4/142","","","","" +"WohnzimmerSteckdose1 Schalten","0/4/144","","","","DPST-1-1" +"WohnzimmerSteckdose1 RM Schalten","0/4/149","","","","" +"WohnzimmerSteckdose1 BSZ Wert","0/4/152","","","","" +"WohnzimmerSteckdose1 BSZ Neustart","0/4/153","","","","DPST-1-1" +"WohnzimmerSteckdose1 Verbrauch","0/4/155","","","","DPST-9-21" +"WohnzimmerSteckdose1 VerbrauchSumme","0/4/160","","","","" +"WohnzimmerSteckdose2 Schalten","0/4/162","","","","DPST-1-1" +"WohnzimmerSteckdose2 RM Schalten","0/4/167","","","","" +"WohnzimmerSteckdose2 BSZ Wert","0/4/170","","","","" +"WohnzimmerSteckdose2 BSZ Neustart","0/4/171","","","","DPST-1-1" +"WohnzimmerSteckdose2 Verbrauch","0/4/173","","","","DPST-9-21" +"WohnzimmerSteckdose2 VerbrauchSumme","0/4/178","","","","" +"S2","0/5/-","","","","" +"Waschmaschine Schalten","0/5/0","","","","DPST-1-1" +"Waschmaschine RM Schalten","0/5/5","","","","" +"Waschmaschine BSZ Wert","0/5/8","","","","" +"Waschmaschine BSZ Neustart","0/5/9","","","","DPST-1-1" +"Waschmaschine Verbrauch","0/5/11","","","","DPST-9-21,DPT-9" +"Waschmaschine VerbrauchSumme","0/5/16","","","","" +"Trockner Schalten","0/5/18","","","","DPST-1-1" +"Trockner RM Schalten","0/5/23","","","","" +"Trockner BSZ Wert","0/5/26","","","","" +"Trockner BSZ Neustart","0/5/27","","","","DPST-1-1" +"Trockner Verbrauch","0/5/29","","","","DPST-9-21" +"Trockner VerbrauchSumme","0/5/34","","","","" +"LampeVorratsraum Schalten","0/5/36","","","","DPST-1-1" +"LampeVorratsraum RM Schalten","0/5/41","","","","" +"LampeVorratsraum BSZ Wert","0/5/44","","","","" +"LampeVorratsraum BSZ Neustart","0/5/45","","","","DPST-1-1" +"LampeVorratsraum Verbrauch","0/5/47","","","","DPST-9-21" +"LampeVorratsraum VerbrauchSumme","0/5/52","","","","" +"VorratsraumSteckdose1 Schalten","0/5/54","","","","DPST-1-1" +"VorratsraumSteckdose1 RM Schalten","0/5/59","","","","" +"VorratsraumSteckdose1 BSZ Wert","0/5/62","","","","" +"VorratsraumSteckdose1 BSZ Neustart","0/5/63","","","","DPST-1-1" +"VorratsraumSteckdose1 Verbrauch","0/5/65","","","","DPST-9-21" +"VorratsraumSteckdose1 VerbrauchSumme","0/5/70","","","","" +"VorratsraumSteckdose2 Schalten","0/5/72","","","","DPST-1-1" +"VorratsraumSteckdose2 RM Schalten","0/5/77","","","","" +"VorratsraumSteckdose2 BSZ Wert","0/5/80","","","","" +"VorratsraumSteckdose2 BSZ Neustart","0/5/81","","","","DPST-1-1" +"VorratsraumSteckdose2 Verbrauch","0/5/83","","","","DPST-9-21" +"VorratsraumSteckdose2 VerbrauchSumme","0/5/88","","","","" +"VorratsraumSteckdose3 Schalten","0/5/90","","","","DPST-1-1" +"VorratsraumSteckdose3 RM Schalten","0/5/95","","","","" +"VorratsraumSteckdose3 BSZ Wert","0/5/98","","","","" +"VorratsraumSteckdose3 BSZ Neustart","0/5/99","","","","DPST-1-1" +"VorratsraumSteckdose3 Verbrauch","0/5/101","","","","DPST-9-21" +"VorratsraumSteckdose3 VerbrauchSumme","0/5/106","","","","" +"Klingel Innen Schalten","0/5/108","","","","DPST-1-1" +"Klingel Innen RM Schalten","0/5/113","","","","" +"Klingel Innen BSZ Wert","0/5/116","","","","" +"Klingel Innen BSZ Neustart","0/5/117","","","","DPST-1-1" +"Klingel Innen Verbrauch","0/5/119","","","","DPST-9-21" +"KlingelInnen VerbrauchSumme","0/5/124","","","","" +"Klingel Aussen Schalten","0/5/126","","","","DPST-1-1" +"Klingel Aussen RM Schalten","0/5/131","","","","" +"Klingel Aussen BSZ Wert","0/5/134","","","","" +"Klingel Aussen BSZ Neustart","0/5/135","","","","DPST-1-1" +"Klingel Aussen Verbrauch","0/5/137","","","","DPST-9-21" +"KlingelAussen VerbrauchSumme","0/5/142","","","","" +"TreppenhausLicht Schalten","0/5/180","","","","DPST-1-1" +"TreppenhausLicht RM Schalten","0/5/185","","","","" +"TreppenhausLicht BSZ Wert","0/5/188","","","","" +"TreppenhausLicht BSZ Neustart","0/5/189","","","","DPST-1-1" +"TreppenhausLicht Verbrauch","0/5/191","","","","DPST-9-21" +"TreppenhausLicht VerbrauchSumme","0/5/196","","","","" +"WCLicht Schalten","0/5/198","","","","DPST-1-1" +"WCLicht RM Schalten","0/5/203","","","","" +"WCLicht BSZ Wert","0/5/206","","","","" +"WCLicht BSZ Neustart","0/5/207","","","","DPST-1-1" +"WCLicht Verbrauch","0/5/209","","","","DPST-9-21" +"WCLicht VerbrauchSumme","0/5/214","","","","" +"Gruppen","0/6/-","","","","" +"KuecheEsszimmerSchalten","0/6/0","","","","" +"KuecheEsszimmerDimmen","0/6/1","","","","" +"R1","0/7/-","","","","" +"Wohnzimmer Fenster Rollo Lang","0/7/10","","","","" +"Wohnzimmer Fenster Rollo Kurz","0/7/11","","","","" +"Wohnzimmer Fenster Rollo RM Position","0/7/24","","","","" +"Wohnzimmer Fenster Rollo RM invalid","0/7/26","","","","" +"Wohnzimmer Fenster Rollo Position","0/7/28","","","","DPST-6-1" +"Terassentür Rollo Lang","0/7/36","","","","" +"Terassentür Rollo Kurz","0/7/37","","","","" +"Terassentür Rollo RM Position","0/7/50","","","","" +"Terassentür Rollo RM invalid","0/7/52","","","","" +"Terassentür Rollo Position","0/7/54","","","","" +"Küchenfenster Rollo Lang","0/7/62","","","","DPST-1-4" +"Küchenfenster Rollo Kurz","0/7/63","","","","" +"Küchenfenster Rollo RM Position","0/7/76","","","","" +"Küchenfenster Rollo RM invalid","0/7/78","","","","" +"Küchenfenster Rollo Position","0/7/80","","","","" +"Esszimmerfenster Rollo Lang","0/7/88","","","","" +"Esszimmerfenster Rollo Kurz","0/7/89","","","","" +"Esszimmerfenster Rollo RM Position","0/7/102","","","","" +"Esszimmerfenster Rollo RM invalid","0/7/104","","","","" +"Esszimmerfenster Rollo Position","0/7/106","","","","" diff --git a/knx_config/export_project2.csv b/knx_config/export_project2.csv new file mode 100644 index 0000000..b3cd16a --- /dev/null +++ b/knx_config/export_project2.csv @@ -0,0 +1,11 @@ +"Middle","0/0/-","","","","" +"BewegungsmelderWest Motion Links","0/0/40","","","","" +"BewegungsmelderWest Motion Rechts","0/0/41","","","","" +"BewegungsmelderWest Helligkeit","0/0/42","","","","" +"BewegungsmelderWest LED","0/0/43","","","","" +"BewegungsmelderMitte Motion","0/0/44","","","","" +"BewegungsmelderMitte Helligkeit","0/0/45","","","","" +"BewegungsmelderMitte LED","0/0/46","","","","" +"BewegungsmelderOst Motion","0/0/47","","","","" +"BewegungsmelderOst Helligkeit","0/0/48","","","","" +"BewegungsmelderOst LED","0/0/49","","","","" diff --git a/knx_config/knx_devices.yaml b/knx_config/knx_devices.yaml new file mode 100644 index 0000000..412f2bc --- /dev/null +++ b/knx_config/knx_devices.yaml @@ -0,0 +1,61 @@ +light: +- platform: knx + address: 0/1/3 + state_address: 0/1/8 + name: Wohnzimmer Deckenlampe + brightness_address: 0/1/7 + brightness_state_address: 0/1/9 +- platform: knx + address: 0/1/21 + state_address: 0/1/26 + name: Esszimmer Deckenlampe West + brightness_address: 0/1/25 + brightness_state_address: 0/1/27 +- platform: knx + address: 0/1/39 + state_address: 0/1/44 + name: Esszimmer Deckenlampe Mitte + brightness_address: 0/1/43 + brightness_state_address: 0/1/45 +- platform: knx + address: 0/2/39 + state_address: 0/2/44 + name: Esszimmer Schrankleuchte + brightness_address: 0/2/43 + brightness_state_address: 0/2/45 +- platform: knx + address: 0/1/57 + state_address: 0/1/62 + name: Küche Deckenlampe + brightness_address: 0/1/61 + brightness_state_address: 0/1/63 +- platform: knx + address: 0/2/21 + state_address: 0/2/26 + name: Aussen Terassenlicht + brightness_address: 0/2/25 + brightness_state_address: 0/2/27 +- platform: knx + address: 0/3/3 + state_address: 0/3/8 + name: Gang Licht + brightness_address: 0/3/7 + brightness_state_address: 0/3/9 +- platform: knx + address: 0/3/21 + state_address: 0/3/26 + name: Bad Licht + brightness_address: 0/3/25 + brightness_state_address: 0/3/27 +- platform: knx + address: 0/3/39 + state_address: 0/3/44 + name: Gang Einganglicht + brightness_address: 0/3/43 + brightness_state_address: 0/3/45 +- platform: knx + address: 0/3/57 + state_address: 0/3/62 + name: Waschküche Licht + brightness_address: 0/3/61 + brightness_state_address: 0/3/63 diff --git a/knx_config/knx_file_creator.py b/knx_config/knx_file_creator.py new file mode 100644 index 0000000..01d5193 --- /dev/null +++ b/knx_config/knx_file_creator.py @@ -0,0 +1,158 @@ +from collections import namedtuple +from typing import List +from ruamel.yaml import YAML + +DeviceInfo = namedtuple("DeviceInfo", ['csv_name', 'display_name']) +yaml = YAML() + + +def first_lower(s): + if len(s) == 0: + return s + else: + return s[0].lower() + s[1:] + + +def import_ets5_csv_file(csv_file): + result = dict() + with open(csv_file, encoding='utf-8') as f: + for line in f: + splitted_line = line.split(",") + name = splitted_line[0].replace('"', "") + group_address = splitted_line[1].replace('"', "") + if '-' not in group_address: + result[name.strip()] = splitted_line[1].replace('"', "").strip() + return result + + +def create_dimmable_lights(device_info : List[DeviceInfo], + csv_contents, + postfix_on_off_write=" Schalten", + postfix_on_off_read=" RM Schalten", + postfix_dim=" Dimmen", + postfix_brightness_write=" Helligkeit", + postfix_brightness_read=" RM Helligkeit"): + + result = [] + for entry in device_info: + try: + display_name = entry.display_name + on_off_write_addr = csv_contents[entry.csv_name + postfix_on_off_write] + on_off_read_addr = csv_contents[entry.csv_name + postfix_on_off_read] + dimm_addr = csv_contents[entry.csv_name + postfix_dim] + brightness_write_addr = csv_contents[entry.csv_name + postfix_brightness_write] + brightness_read_addr = csv_contents[entry.csv_name + postfix_brightness_read] + + result.append({ + 'platform': 'knx', + 'address': on_off_write_addr, + 'state_address': on_off_read_addr, + 'name': display_name, + 'brightness_address': brightness_write_addr, + 'brightness_state_address': brightness_read_addr, + }) + except KeyError as e: + raise ValueError("Skipping %s - Could not find CSV File entry: %s" % (entry.csv_name, str(e))) + + return result + + +def write_yaml(device_dict): + pass + + +if __name__ == '__main__': + knx_dimmable_lights = [ + DeviceInfo('Wohnzimmerlampe', 'Wohnzimmer Deckenlampe'), + DeviceInfo('EsszimmerlampeWest', 'Esszimmer Deckenlampe West'), + DeviceInfo('EsszimmerlampeMitte', 'Esszimmer Deckenlampe Mitte'), + DeviceInfo("EsszimmerWandlampe", 'Esszimmer Schrankleuchte'), + DeviceInfo("Küchenlampe", "Küche Deckenlampe"), + DeviceInfo("AussenleuchteUntenSO", "Aussen Terassenlicht"), + DeviceInfo("Gang", "Gang Licht"), + DeviceInfo("Bad", "Bad Licht"), + DeviceInfo("GangWindfang", "Gang Einganglicht"), + DeviceInfo("LichtWaschküche", "Waschküche Licht"), + ] + imported_csv = import_ets5_csv_file('export_project1.csv') + imported_csv.update(import_ets5_csv_file('export_project2.csv')) + + devices = { + 'light': create_dimmable_lights(knx_dimmable_lights, imported_csv), + } + yaml.dump(devices, open('knx_devices.yaml', 'w')) + + +def createLights(csvContents, idToRoomMap, knxSendClient, knxRecvClient, + postfixWrite=" Schalten", postfixRead=" RM Schalten"): + from pysmarthome.backends.knx.devices import Light + result = [] + + for csvName, info in idToRoomMap.items(): + try: + room = info['room'] + displayName = info['displayName'] + deviceId = info['id'] + onOffWriteAddr = csvContents[csvName + postfixWrite] + onOffReadAddr = csvContents[csvName + postfixRead] + l = Light(deviceId, displayName, room, onOffWriteAddr, onOffReadAddr, knxSendClient, knxRecvClient, ) + result.append(l) + except KeyError as e: + raise ValueError("Skipping %s - Could not find CSV File entry: %s" % (csvName, str(e))) + + return result + + +def createBells(csvContents, idToRoomMap, knxSendClient, knxRecvClient, + postfixWrite=" Schalten", postfixRead=" RM Schalten"): + from pysmarthome.backends.knx.devices import Bell + result = [] + + for csvName, info in idToRoomMap.items(): + try: + room = info['room'] + displayName = info['displayName'] + deviceId = info['id'] + onOffWriteAddr = csvContents[csvName + postfixWrite] + if csvName + postfixRead in csvContents: + onOffReadAddr = csvContents[csvName + postfixRead] + else: + onOffReadAddr = None + + l = Bell(deviceId, displayName, room, onOffWriteAddr, onOffReadAddr, knxSendClient, knxRecvClient, ) + result.append(l) + except KeyError as e: + raise ValueError("Skipping %s - Could not find CSV File entry: %s" % (csvName, str(e))) + + return result + + +def createShutters(csvContents, idToRoomMap, knxSendClient, knxRecvClient, + postfixLongAddr=" Lang", postfixShortAddr=" Kurz", postfixReadPosition=" RM Position", + postfixSetPosition=" Position"): + from pysmarthome.backends.knx.devices import Shutter + result = [] + + for csvName, contents in idToRoomMap.items(): + try: + longAddr = csvContents[csvName + postfixLongAddr] + shortAddr = csvContents[csvName + postfixShortAddr] + readPosAddr = csvContents[csvName + postfixReadPosition] + setPosAddr = csvContents[csvName + postfixSetPosition] + if type(contents) is dict: + displayName = contents['displayName'] + room = contents['room'] + deviceId = contents['id'] + else: + displayName = csvName + room = contents + deviceId = first_lower(csvName) + + s = Shutter(deviceId, displayName, room, shortAddr, longAddr, setPosAddr, readPosAddr, + knxSendClient, knxRecvClient) + result.append(s) + + except KeyError as e: + raise ValueError("Skipping %s - Could not find CSV File entry: %s" % (csvName, str(e))) + + return result diff --git a/playground/asyncio_test.py b/playground/asyncio_test.py new file mode 100644 index 0000000..e9f7406 --- /dev/null +++ b/playground/asyncio_test.py @@ -0,0 +1,24 @@ +import asyncio + + +async def tcp_echo_client(message): + reader, writer = await asyncio.open_connection('server', 7072) + print("Connected") + + lines = [ + "displayattr .*\n".encode(), + "inform on\n".encode(), + ] + writer.writelines(lines) + + while True: + data = await reader.readline() + data = data.decode() + print("Received line", data) + + +#asyncio.run(tcp_echo_client('Hello World!')) + +loop = asyncio.get_event_loop() +loop.run_until_complete(tcp_echo_client("bla")) +loop.close() diff --git a/playground/xknxtest.py b/playground/xknxtest.py new file mode 100644 index 0000000..f9949bf --- /dev/null +++ b/playground/xknxtest.py @@ -0,0 +1,50 @@ +import asyncio +from xknx import XKNX +from xknx.devices import Light +from xknx.io import GatewayScanner, Tunnel +from xknx.knx import DPTBinary, GroupAddress, PhysicalAddress, Telegram + + +def direct(): + async def main(): + xknx = XKNX() + await xknx.start() + light = Light(xknx, + name='GangLicht', + group_address_switch='0/3/3') + await light.set_on() + await asyncio.sleep(1) + await light.set_off() + await xknx.stop() + + + # pylint: disable=invalid-name + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + loop.close() + + +async def tunnel(): + xknx = XKNX() + tunnel = Tunnel( + xknx, + PhysicalAddress("3.7.249"), + local_ip='192.168.178.74', + gateway_ip='192.168.178.80', + gateway_port=3671, + ) + await tunnel.connect_udp() + await tunnel.connect() + + await tunnel.send_telegram(Telegram(GroupAddress('0/3/3'), payload=DPTBinary(1))) + await asyncio.sleep(2) + await tunnel.send_telegram(Telegram(GroupAddress('0/3/3'), payload=DPTBinary(0))) + await asyncio.sleep(2) + + await tunnel.connectionstate() + await tunnel.disconnect() + + +loop = asyncio.get_event_loop() +loop.run_until_complete(tunnel()) +loop.close() diff --git a/todo b/todo new file mode 100644 index 0000000..a8fb0ac --- /dev/null +++ b/todo @@ -0,0 +1,74 @@ +KNX: + - add shutters + - add sensors + - check out read addresses + - in bus monitor + - KNX config files -> add to git + - check out motion detectors in frontend + - check out shutters in frontend + +Config: + - add rooms as groups + - frontend room grouping + -> how to configure frontend? + - set up scenes for living room + + + +FHEM: + - add FHEM shutters + - add + - check FHEM reconnection + - what happens when FHEM stops? + - what happens when stick is pulled? + + + + + + + + + + +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw deviceMsg: 50 (to 62A77D) +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw dim: stop:50 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw level: 50 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw overheat: off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw overload: off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw pct: 50 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw phyLevel: 50 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw reduced: off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw 50 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw timedOn: off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw1_V_01 phyLevel: 50 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw1_V_01 chn:??? phys:50 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw1_V_02 phyLevel: 50 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw1_V_02 chn:??? phys:50 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw deviceMsg: off (to 62A77D) +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw dim: stop:off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw level: 0 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw overheat: off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw overload: off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw pct: 0 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw phyLevel: 0 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw reduced: off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw timedOn: off +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw1_V_01 phyLevel: 0 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw1_V_01 chn:??? phys:0 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw1_V_02 phyLevel: 0 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw1_V_02 chn:??? phys:0 + + + + + + +CUL_HM ArbeitszimmerMartin_Bewegungsmelder brightness: 45 +CUL_HM ArbeitszimmerMartin_Bewegungsmelder motion: on (to 62A77D) +CUL_HM ArbeitszimmerMartin_Bewegungsmelder motionCount: 3_next:29s +CUL_HM ArbeitszimmerMartin_Bewegungsmelder motion +CUL_HM ArbeitszimmerMartin_Bewegungsmelder trigDst_62A77D: noConfig +CUL_HM ArbeitszimmerMartin_Bewegungsmelder trigger_cnt: 3 +CUL_HM ArbeitszimmerMartin_Deckenlampe_Sw deviceMsg: 50 (to 62A77D)