362 lines
16 KiB
Python
362 lines
16 KiB
Python
import os
|
|
import argparse
|
|
from util import DeviceInfo, add_to_group, name_to_id
|
|
from ruamel.yaml import YAML
|
|
import knx_conf as knx
|
|
|
|
script_path = os.path.dirname(os.path.realpath(__file__))
|
|
yaml = YAML()
|
|
yaml.preserve_quotes = True
|
|
|
|
|
|
sensor_exclude_list = [
|
|
'sensor.fritz_box_7490_bytes_received',
|
|
'sensor.fritz_box_7490_bytes_sent',
|
|
# 'sensor.fritz_box_7490_kbyte_sec_received',
|
|
# 'sensor.fritz_box_7490_kbyte_sec_sent',
|
|
'sensor.fritz_box_7490_packets_received',
|
|
'sensor.fritz_box_7490_packets_sec_received',
|
|
'sensor.fritz_box_7490_packets_sec_sent',
|
|
'sensor.fritz_box_7490_packets_sent',
|
|
|
|
'sensor.martin_handy_availability_sensor',
|
|
'sensor.martin_handy_light_sensor',
|
|
'sensor.martin_handy_step_counter_sensor',
|
|
|
|
'sensor.date_time',
|
|
'sensor.date_time_iso',
|
|
'sensor.time',
|
|
'sensor.time_date',
|
|
]
|
|
|
|
|
|
def add_knx_devices(devices, groups):
|
|
imported_csv = knx.import_ets5_csv_file(os.path.join(script_path, 'knx_data/export_project1.csv'))
|
|
imported_csv.update(knx.import_ets5_csv_file(os.path.join(script_path, 'knx_data/export_project2.csv')))
|
|
|
|
lights = [
|
|
# Dimmers
|
|
DeviceInfo('Wohnzimmerlampe', 'Wohnzimmer Deckenlampe', 'living_area'),
|
|
DeviceInfo('EsszimmerlampeWest', 'Esszimmer Deckenlampe West', 'living_area'),
|
|
DeviceInfo('EsszimmerlampeMitte', 'Esszimmer Deckenlampe Mitte', 'living_area'),
|
|
DeviceInfo("EsszimmerWandlampe", 'Esszimmer Schrankleuchte', 'living_area'),
|
|
DeviceInfo("Küchenlampe", "Küche Deckenlampe", 'living_area'),
|
|
DeviceInfo("AussenleuchteUntenSO", "Aussen Terassenlicht", 'outside'),
|
|
DeviceInfo("Gang", "Gang Licht", 'hallway'),
|
|
DeviceInfo("Bad", "Bad Licht", 'bathroom'),
|
|
DeviceInfo("GangWindfang", "Gang Einganglicht", 'hallway'),
|
|
DeviceInfo("LichtWaschküche", "Waschküche Licht", 'hallway'),
|
|
# Normal lights
|
|
DeviceInfo('AussenleuchteHaustüren', 'Haustür Licht', 'outside'),
|
|
DeviceInfo('AussenleuchteObenNW', 'Haustür Licht NW', 'first_floor'),
|
|
DeviceInfo('TreppenhausLicht', "Treppenhaus Licht", 'first_floor'),
|
|
#DeviceInfo('WCLicht', "WC Licht", 'other'),
|
|
DeviceInfo('LampeVorratsraum', "Vorratsraum Licht", 'other'),
|
|
# Bewegungsmelder LEDs
|
|
DeviceInfo("BewegungsmelderMitte LED", "Bewegungsmelder Mitte LED", 'hallway'),
|
|
DeviceInfo("BewegungsmelderWest LED", "Bewegungsmelder West LED", 'hallway'),
|
|
DeviceInfo("BewegungsmelderOst LED", "Bewegungsmelder Ost LED", 'hallway'),
|
|
# Briefkasten
|
|
DeviceInfo("KlingelknoepfeLicht", "Klingelknöpfe Licht", 'outside'),
|
|
DeviceInfo("BriefkastensaeuleBeleuchtung", "Briefkasten Licht", 'outside'),
|
|
]
|
|
|
|
shutters = [
|
|
DeviceInfo('Wohnzimmer Fenster Rollo', 'Wohnzimmer Fenster Rollo', 'living_area'),
|
|
DeviceInfo('Terassentür Rollo', 'Wohnzimmer Terrassentür Rollo', 'living_area'),
|
|
DeviceInfo('Küchenfenster Rollo', 'Küche Fenster Rollo', 'living_area'),
|
|
DeviceInfo('Esszimmerfenster Rollo', 'Esszimmer Fenster Rollo', 'living_area'),
|
|
]
|
|
|
|
switches = [
|
|
# Bells
|
|
DeviceInfo("KlingelOben", "Klingel Oben", 'first_floor'),
|
|
DeviceInfo("Klingel Innen", "Klingel Innentür", 'other'),
|
|
DeviceInfo("Klingel Aussen", "Klingel Außentür", 'other'),
|
|
# Tueroeffner
|
|
DeviceInfo("Tueroeffner", "Türöffner Gartentür", 'other'),
|
|
]
|
|
scene_button_names = ['ObenLinks', 'ObenRechts', 'MitteLinks', 'MitteRechts', 'UntenLinks', 'UntenRechts']
|
|
scene_button_names = [(i, e) for i, e in enumerate(scene_button_names)]
|
|
switches += [DeviceInfo(f"SzeneEsszimmer{n}", f"Esszimmer Szene {i}") for i, n in scene_button_names]
|
|
switches += [DeviceInfo(f"SzeneWohnzimmer{n}", f"Wohnzimmer Szene {i}") for i, n in scene_button_names]
|
|
switches += [DeviceInfo(f"SzeneEingang{n}", f"Eingang Szene {i}") for i, n in scene_button_names[2:]]
|
|
switches += [DeviceInfo(f"SzeneTerrassentuer{n}", f"Wohnzimmer Terrassentür Szene {i}")
|
|
for i, n in scene_button_names[2: 4]]
|
|
switches += [DeviceInfo("AlarmLEDLinksNormal", "alarm_led_links_normal"),
|
|
DeviceInfo("AlarmLEDLinksUeberlagert", "alarm_led_links_ueberlagert"),
|
|
DeviceInfo("AlarmLEDRechtsNormal", "alarm_led_rechts_normal"),
|
|
DeviceInfo("AlarmLEDRechtsUeberlagert", "alarm_led_rechts_ueberlagert"),
|
|
DeviceInfo("WCLicht Schalten", "wc_licht_taster"), ]
|
|
|
|
power_plugs = [
|
|
# Vorratsraum
|
|
DeviceInfo("VorratsraumSteckdose1", "Vorratsraum Steckdose 1", 'other'),
|
|
DeviceInfo("VorratsraumSteckdose2", "Vorratsraum Steckdose 2", 'other'),
|
|
DeviceInfo("VorratsraumSteckdose3", "Gefrierschrank", 'other'),
|
|
# Waschraum
|
|
DeviceInfo("Trockner", "Trockner", 'other'),
|
|
DeviceInfo("Waschmaschine", "Waschmaschine", 'other'),
|
|
# Küche
|
|
DeviceInfo("KücheSteckdose1", "Küche Steckdosen rechts"),
|
|
DeviceInfo("KücheSteckdose2", "Dunstabzug"),
|
|
DeviceInfo("Spülmaschine", "Spülmaschine", 'living_area'),
|
|
DeviceInfo("Backofen", "Backofen", 'living_area'),
|
|
DeviceInfo("HerdP1", "Herd Phase 1", 'living_area'),
|
|
DeviceInfo("HerdP2", "Herd Phase 2", 'living_area'),
|
|
DeviceInfo("HerdP3", "Herd Phase 3", 'living_area'),
|
|
# Rest
|
|
DeviceInfo("ArbeitszimmerSteckdose", "Arbeitszimmer Steckdose", 'office'),
|
|
DeviceInfo("WohnzimmerSteckdose1", "Wohnzimmer Steckdose 1", 'living_area'),
|
|
DeviceInfo("WohnzimmerSteckdose2", "Wohnzimmer Steckdose 2", 'living_area'),
|
|
# Gartenhaus steckdosen (Pumpen)
|
|
DeviceInfo("Garten1", "Gartenhaus Steckdose1", 'outside'),
|
|
DeviceInfo("Garten2", "Gartenhaus Steckdose2", 'outside'),
|
|
]
|
|
|
|
motion_sensors = {
|
|
'sensor': [
|
|
# Brightness
|
|
{'name': 'Gang Bewegungsmelder West Helligkeit',
|
|
'state_address': imported_csv['BewegungsmelderWest Helligkeit'],
|
|
'type': 'illuminance'},
|
|
{'name': 'Gang Bewegungsmelder Mitte Helligkeit',
|
|
'state_address': imported_csv['BewegungsmelderMitte Helligkeit'],
|
|
'type': 'illuminance'},
|
|
{'name': 'Gang Bewegungsmelder Ost Helligkeit',
|
|
'state_address': imported_csv['BewegungsmelderOst Helligkeit'],
|
|
'type': 'illuminance'},
|
|
# Temperature
|
|
{'name': 'Esszimmer Temperatur',
|
|
'state_address': imported_csv['TemperaturEsszimmer'],
|
|
'type': 'temperature'},
|
|
{'name': 'Treppenhaus Oben Temperatur',
|
|
'state_address': imported_csv['TreppenhausObenTemperatur'],
|
|
'type': 'temperature'},
|
|
{'name': 'Eingangsbereich Temperatur',
|
|
'state_address': imported_csv['TemperaturGang'],
|
|
'type': 'temperature'},
|
|
{'name': 'Wohnzimmer Gang Temperatur',
|
|
'state_address': imported_csv['TemperaturWohnzimmerGangTuer'],
|
|
'type': 'temperature'},
|
|
{'name': 'Wohnzimmer Terassentür Temperatur',
|
|
'state_address': imported_csv['TemperaturWohnzimmerAussenTuer'],
|
|
'type': 'temperature'},
|
|
],
|
|
'binary_sensor': [
|
|
{'name': 'Gang Bewegungsmelder Schlafzimmer',
|
|
'state_address': imported_csv['BewegungsmelderWest Motion Links'],
|
|
'device_class': 'motion'},
|
|
{'name': 'Gang Bewegungsmelder Wohnzimmer',
|
|
'state_address': imported_csv['BewegungsmelderWest Motion Rechts'],
|
|
'device_class': 'motion'},
|
|
{'name': 'Gang Bewegungsmelder Mitte',
|
|
'state_address': imported_csv['BewegungsmelderMitte Motion'],
|
|
'device_class': 'motion'},
|
|
{'name': 'Gang Bewegungsmelder Ost',
|
|
'state_address': imported_csv['BewegungsmelderOst Motion'],
|
|
'device_class': 'motion'},
|
|
{'name': 'KlingelknopfGartensaeule',
|
|
'state_address': imported_csv['KlingelknopfGartensaeule'], },
|
|
{'name': 'KlingelknopfHaustuerUnten',
|
|
'state_address': imported_csv['KlingelknopfHaustuerUnten'], },
|
|
{'name': 'KlingelknopfInnentuer',
|
|
'state_address': imported_csv['KlingelknopfInnentuer'], },
|
|
]
|
|
}
|
|
knx.extent(devices, motion_sensors)
|
|
knx.extent(devices, knx.create_lights(lights, imported_csv))
|
|
knx.extent(devices, knx.create_shutters(shutters, imported_csv))
|
|
knx.extent(devices, knx.create_switches(switches, imported_csv))
|
|
|
|
power_plugs, template_sensors = knx.create_power_plug(power_plugs, imported_csv)
|
|
knx.extent(devices, power_plugs)
|
|
|
|
for device_type, devices in [('light', lights), ('cover', shutters), ('switch', switches)]:
|
|
for device in devices:
|
|
add_to_group(groups, device.groups, device.display_name, device_type)
|
|
return template_sensors
|
|
|
|
|
|
def add_meta_groups(groups):
|
|
all_devices = set()
|
|
for group in groups.values():
|
|
all_devices.update(group['entities'])
|
|
first_floor = set(groups['first_floor']['entities'])
|
|
bedroom = set(groups['bedroom']['entities'])
|
|
outside = set(groups['outside']['entities'])
|
|
|
|
groups['all_downstairs'] = {
|
|
'name': 'Unten Alles',
|
|
'entities': [e for e in all_devices - first_floor]
|
|
}
|
|
|
|
groups['all_downstairs_but_outside'] = {
|
|
'name': 'Unten Alles Ausser Draussen',
|
|
'entities': [e for e in all_devices - first_floor - outside]
|
|
}
|
|
|
|
groups['all_downstairs_but_bedroom'] = {
|
|
'name': 'Unten Alles Ausser Schlafzimmer',
|
|
'entities': [e for e in all_devices - first_floor - bedroom],
|
|
}
|
|
|
|
groups['all_downstairs_but_bedroom_and_outside'] = {
|
|
'name': 'Unten Alles Ausser Schlafzimmer und Draussen',
|
|
'entities': [e for e in all_devices - first_floor - bedroom - outside],
|
|
}
|
|
|
|
|
|
def make_sensor_exclude_list(all_devices, name_fragments):
|
|
exclude_list = []
|
|
|
|
def handle_sensor(s):
|
|
name = s['name'] if 'name' in s else s['friendly_name']
|
|
if any(t in name for t in name_fragments):
|
|
exclude_list.append(name_to_id(name, 'sensor'))
|
|
|
|
for sensor_device in all_devices['sensor']:
|
|
if 'platform' in sensor_device and sensor_device['platform'] == 'time_date':
|
|
continue
|
|
elif 'platform' in sensor_device and sensor_device['platform'] == 'template':
|
|
for nested_sensor_dev in sensor_device['sensors'].values():
|
|
handle_sensor(nested_sensor_dev)
|
|
elif 'platform' in sensor_device and sensor_device['platform'] == 'mqtt_room':
|
|
pass
|
|
elif 'name' in sensor_device:
|
|
handle_sensor(sensor_device)
|
|
else:
|
|
raise NotImplementedError(f"Sensor {sensor_device}")
|
|
return exclude_list
|
|
|
|
|
|
def logbook_config(all_devices):
|
|
sensor_excludes = ['Helligkeit', 'Betriebsstunden', 'Verbrauch', 'Bewegungsmelder']
|
|
|
|
exclude_list = ['binary_sensor.updater']
|
|
exclude_list.extend(sensor_exclude_list)
|
|
exclude_list.extend(make_sensor_exclude_list(all_devices, sensor_excludes))
|
|
return {
|
|
'logbook': {
|
|
'exclude': {
|
|
'entities': exclude_list,
|
|
'domains': ['group', 'automation', 'group', 'scene']
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
def recorder_config(all_devices):
|
|
recorder_excludes = ['Betriebsstunden', 'Verbrauch mA']
|
|
exclude_list = ['sun.sun']
|
|
exclude_list.extend(sensor_exclude_list)
|
|
exclude_list.extend(make_sensor_exclude_list(all_devices, recorder_excludes))
|
|
|
|
return {
|
|
'recorder': {
|
|
'db_url': 'mysql://homeassistant:Vc9JpSh0VQqKF1zgwdJG@core-mariadb/homeassistant?charset=utf8',
|
|
'purge_keep_days': 60,
|
|
'exclude': {
|
|
'entities': exclude_list,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
def merge_configs(base_config, override_config):
|
|
"""
|
|
Merge two configuration dictionaries by combining list-type entries.
|
|
|
|
For keys that exist in both configs and have list values in override_config,
|
|
the lists are concatenated. If base_config has a dict value for that key,
|
|
it's first converted to a single-element list before extending.
|
|
|
|
Keys with list values are moved from override_config to base_config.
|
|
Non-list entries in override_config remain unchanged.
|
|
|
|
Args:
|
|
base_config: The base configuration dict to merge into (modified in place)
|
|
override_config: The override configuration dict (list entries are removed)
|
|
|
|
Returns:
|
|
tuple: (base_config, override_config) after merging
|
|
"""
|
|
keys_to_merge = []
|
|
for key in override_config:
|
|
if isinstance(override_config[key], list):
|
|
keys_to_merge.append(key)
|
|
for key in keys_to_merge:
|
|
override_value = override_config[key]
|
|
if key not in base_config:
|
|
base_config[key] = []
|
|
# Handle case where base_config[key] is a dict (convert to list first)
|
|
if isinstance(base_config[key], dict):
|
|
base_config[key] = [base_config[key]]
|
|
base_config[key].extend(override_value)
|
|
del override_config[key]
|
|
return base_config, override_config
|
|
|
|
|
|
def create_config(target_directory, development=False):
|
|
groups_yaml_path = os.path.join(script_path, 'groups.yaml')
|
|
manual_config_path = os.path.join(script_path, 'manual_config.yaml')
|
|
group_dict = yaml.load(open(groups_yaml_path, encoding="utf-8"))
|
|
manual_config_dict = yaml.load(open(manual_config_path, encoding="utf-8"))
|
|
|
|
knx_conf = {
|
|
#'tunneling': {
|
|
# 'host': '192.168.178.65',
|
|
# 'port': 3671
|
|
#}
|
|
}
|
|
template_sensors = add_knx_devices(knx_conf, group_dict)
|
|
|
|
all_devices = {}
|
|
all_devices.update(template_sensors)
|
|
|
|
with open(os.path.join(target_directory, 'configuration.yaml'), 'w', encoding="utf-8") as output:
|
|
output.write("# Dont' edit manually! this is generated!\n\n")
|
|
# Merge list-type configurations from manual_config into all_devices
|
|
keys_to_merge = []
|
|
for key in manual_config_dict:
|
|
if isinstance(manual_config_dict[key], list):
|
|
keys_to_merge.append(key)
|
|
for key in keys_to_merge:
|
|
manual_value = manual_config_dict[key]
|
|
if key not in all_devices:
|
|
all_devices[key] = []
|
|
# Handle case where all_devices[key] is a dict (convert to list first)
|
|
if isinstance(all_devices[key], dict):
|
|
all_devices[key] = [all_devices[key]]
|
|
all_devices[key].extend(manual_value)
|
|
del manual_config_dict[key]
|
|
yaml.dump(manual_config_dict, output)
|
|
yaml.dump(all_devices, output)
|
|
yaml.dump(logbook_config(all_devices), output)
|
|
yaml.dump(recorder_config(all_devices), output)
|
|
output.write("automation manual: !include_dir_merge_list automations\n")
|
|
|
|
add_meta_groups(group_dict)
|
|
|
|
with open(os.path.join(target_directory, 'knx_conf.yaml'), 'w') as output:
|
|
output.write("# Dont' edit manually! this is generated!\n\n")
|
|
yaml.dump(knx_conf, output)
|
|
|
|
with open(os.path.join(target_directory, 'groups.yaml'), 'w') as output:
|
|
output.write("# Dont' edit manually! this is generated!\n\n")
|
|
yaml.dump(group_dict, output)
|
|
|
|
with open(os.path.join(target_directory, 'secrets.yaml'), 'w') as output:
|
|
output.write("# Dont' edit manually! this is generated!\n\n")
|
|
output.write(open(os.path.join(script_path, 'secrets.yaml'), 'r').read())
|
|
output.write("\n\n")
|
|
additional_file = 'secrets_development.yaml' if development else 'secrets_deploy.yaml'
|
|
output.write(open(os.path.join(script_path, additional_file), 'r').read())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("-d", "--dev", help="create config file for development", action="store_true")
|
|
args = parser.parse_args()
|
|
create_config(target_directory=os.getcwd(), development=args.dev)
|