{ pkgs, lib, config, ... }: let # allow new devices to join enablePairing = true; device = "/dev/ttyACM0"; dataFolder = "/srv/zigbee/data"; lights = { "light_1" = { id = "0x7cb03eaa0a0347b5"; room = "floor_room_essential"; }; "light_2" = { id = "0x7cb03eaa0a0387b9"; room = "floor_room"; }; "light_3" = { id = "0x7cb03eaa0a033a86"; room = "living_room"; }; "light_4" = { id = "0x7cb03eaa0a04aabf"; room = "bath_room_essential"; }; "light_5" = { id = "0x7cb03eaa0a0346e4"; room = "living_room"; }; "light_6" = { id = "0x7cb03eaa0a034b46"; }; "light_7" = { id = "0x7cb03eaa0a033b4f"; }; "light_8" = { id = "0x7cb03eaa0a0384d3"; room = "bed_room"; }; }; sensors = { buttons = { "button_1" = { id = "0x00158d0002b04f65"; # room = "living_room"; # is where it actually is placed (optional) (default "unknown") # presents = "living_room_present" # presents group to append (optional) (default "unknown") # states = { click = "input_boolean.living_room_button"; } # input state to hold (optional) (default "input_boolean.single_${name}") room = "living_room_present"; }; "button_2" = { id = "0x00158d0002b04f09"; room = "bed_room_present"; states.single = "input_boolean.bed_room_buttons"; }; "button_3" = { id = "0x00158d0002b00e04"; room = "bed_room_present"; states.single = "input_boolean.bed_room_buttons"; }; }; temperature = { "temperature_sensor_1" = { id = "0x00158d0002d79220"; room = "living_room_present"; }; "temperature_sensor_2" = { id = "0x00158d0002d7913d"; room = "living_room_present"; }; }; motion = { "motion_sensor_1" = { id = "0x00158d0002fbd451"; room = "kitchen_room_present"; }; "motion_sensor_2" = { id = "0x00158d0002f9a6b8"; room = "kitchen_room_present"; }; "motion_sensor_3" = { id = "0x00158d0002f04522"; room = "living_room_present"; }; "motion_sensor_4" = { id = "0x00158d0002f9a558"; room = "living_room_present"; }; "motion_sensor_5" = { id = "0x00158d0002f9a56f"; room = "bath_room_essential_present"; }; "motion_sensor_6" = { id = "0x00158d0002f9a5cb"; room = "floor_room_present"; }; "motion_sensor_7" = { id = "0x00158d0002f9a6aa"; room = "bed_room_essential_present"; }; "motion_sensor_8" = { id = "0x00158d0002f04637"; room = "bath_room_essential_present"; }; }; door = { "door_sensor_1" = { id = "0x00158d000312dc52"; }; "door_sensor_2" = { id = "0x00158d000316d5bf"; room = "floor_room_present"; }; "door_sensor_3" = { id = "0x00158d0002f9516f"; }; "door_sensor_4" = { id = "0x00158d00031383b9"; room = "floor_room_present"; }; "door_sensor_5" = { id = "0x00158d0003120d3e"; }; }; }; # todo : generate automatically allSensors = with sensors; buttons // temperature // motion // door; allLights = lights; zigBee2MqttConfig = { # Home Assistant integration (MQTT discovery) homeassistant = false; # homeassistant = true; # allow new devices to join permit_join = enablePairing; # MQTT settings mqtt = { # MQTT base topic for zigbee2mqtt MQTT messages base_topic = "zigbee2mqtt"; # MQTT server URL server = "mqtt://127.0.0.1:1883"; # MQTT server authentication, uncomment if required: user = "zigbee"; password = lib.fileContents ; }; # Serial settings serial = { port = "/dev/ttyACM0"; # Optional: disable LED of CC2531 USB sniffer disable_led = true; }; devices = lib.mapAttrs' (name: { id, ... }: { name = id; value = { retain = false; friendly_name = name; }; }) (allSensors // allLights); }; configurationYaml = pkgs.writeText "configuration.yml" (builtins.toJSON zigBee2MqttConfig); in { imports = [ ./mqtt.nix ]; services.homeAssistantConfig = { # define input_boolean # -------------------- # which get toggled by the buttons input_boolean = let stripEmpty = lib.filter (a: a != { }); in builtins.listToAttrs (stripEmpty (lib.flatten (lib.mapAttrsToList (name: { states ? { }, ... }: [ (lib.optionalAttrs (!lib.hasAttr "single" states) { name = "single_${name}"; value = { icon = "mdi:toggle-switch"; }; }) (lib.optionalAttrs (!lib.hasAttr "double" states) { name = "double_${name}"; value = { icon = "mdi:toggle-switch"; }; }) (lib.optionalAttrs (!lib.hasAttr "hold" states) { name = "hold_${name}"; value = { icon = "mdi:toggle-switch"; }; }) ]) sensors.buttons))); light = lib.mapAttrsToList (name: { ... }: { platform = "mqtt"; name = name; #icon = "mdi:toggle-switch"; state_topic = "zigbee2mqtt/${name}"; availability_topic = "zigbee2mqtt/bridge/state"; command_topic = "zigbee2mqtt/${name}/set"; value_template = "{{ value_json.click }}"; brightness = true; color_temp = true; schema = "json"; }) allLights; # define sensors # -------------- sensor = let # define button sensors buttons = with lib; mapAttrsToList (name: { ... }: [{ platform = "mqtt"; name = name; icon = "mdi:toggle-switch"; state_topic = "zigbee2mqtt/${name}"; availability_topic = "zigbee2mqtt/bridge/state"; value_template = "{{ value_json.click }}"; }]) sensors.buttons; # define temperature sensors temperature = with lib; mapAttrsToList (name: { ... }: [ { platform = "mqtt"; name = name; state_topic = "zigbee2mqtt/${name}"; availability_topic = "zigbee2mqtt/bridge/state"; unit_of_measurement = "°C"; device_class = "temperature"; value_template = "{{ value_json.temperature }}"; } { platform = "mqtt"; name = "humidity_${name}"; state_topic = "zigbee2mqtt/${name}"; availability_topic = "zigbee2mqtt/bridge/state"; unit_of_measurement = "%"; device_class = "humidity"; value_template = "{{ value_json.humidity }}"; } { platform = "mqtt"; name = "pressure_${name}"; state_topic = "zigbee2mqtt/${name}"; availability_topic = "zigbee2mqtt/bridge/state"; unit_of_measurement = "hPa"; device_class = "pressure"; value_template = "{{ value_json.pressure }}"; } ]) sensors.temperature; # define meta information sensors informations = lib.mapAttrsToList (name: { ... }: [ { name = "battery_${name}"; platform = "mqtt"; state_topic = "zigbee2mqtt/${name}"; availability_topic = "zigbee2mqtt/bridge/state"; unit_of_measurement = "%"; icon = "mdi:battery-10"; value_template = "{{ value_json.battery }}"; } { name = "link_${name}"; platform = "mqtt"; state_topic = "zigbee2mqtt/${name}"; availability_topic = "zigbee2mqtt/bridge/state"; icon = "mdi:signal"; unit_of_measurement = "lqi"; value_template = "{{ value_json.linkquality }}"; } ]) (allSensors // allLights); in lib.flatten (buttons ++ temperature ++ informations); # define binary sensors # --------------------- binary_sensor = let # define motion sensors # --------------------- motion = lib.mapAttrsToList (name: { ... }: { name = name; platform = "mqtt"; device_class = "motion"; state_topic = "zigbee2mqtt/${name}"; availability_topic = "zigbee2mqtt/bridge/state"; payload_on = true; payload_off = false; value_template = "{{ value_json.occupancy }}"; }) sensors.motion; # define door sensors # ------------------- door = lib.mapAttrsToList (name: { ... }: { name = name; platform = "mqtt"; device_class = "door"; state_topic = "zigbee2mqtt/${name}"; availability_topic = "zigbee2mqtt/bridge/state"; payload_on = false; payload_off = true; value_template = "{{ value_json.contact}}"; }) sensors.door; in lib.flatten (motion ++ door); # create groups # ------------- # to have nice panels for every device group = let information = name: [ "sensor.battery_${name}" "sensor.link_${name}" ]; lightGroups = lib.mapAttrs' (name: { ... }: { name = name; value = { control = "hidden"; entities = [ "light.${name}" ] ++ (information name); }; }) allLights; sensorButtons = lib.mapAttrs' (name: { states ? { }, ... }: let entityIds = { single ? "input_boolean.single_${name}" , double ? "input_boolean.double_${name}" , hold ? "input_boolean.hold_${name}", ... }: [ single double hold ]; in { name = name; value = { control = "hidden"; entities = [ "sensor.${name}" ] ++ (entityIds states) ++ (information name); }; }) (sensors.buttons); sensorTemperature = lib.mapAttrs' (name: { ... }: { name = name; value = { control = "hidden"; entities = [ "sensor.${name}" "sensor.humidity_${name}" "sensor.pressure_${name}" ] ++ (information name); }; }) (sensors.temperature); sensorMotions = lib.mapAttrs' (name: { ... }: { name = name; value = { control = "hidden"; entities = [ "binary_sensor.${name}" ] ++ (information name); }; }) (sensors.motion); sensorDoors = lib.mapAttrs' (name: { ... }: { name = name; value = { control = "hidden"; entities = [ "binary_sensor.${name}" ] ++ (information name); }; }) (sensors.door); views = { view_sensors = { name = "Sensoren"; control = "hidden"; view = true; entities = lib.mapAttrsToList (name: { ... }: "group.${name}") allSensors; }; }; # sort sensors in room groups # --------------------------- allRoomsGroups = let createGroupEntities = { sensors, entityNameing }: let flatSensors = lib.mapAttrsToList (name: values: { name = name; } // values) sensors; allRooms' = lib.groupBy ({ room ? "unknown", ... }: room) flatSensors; allRooms = lib.mapAttrs (_: devices: map entityNameing devices) allRooms'; in allRooms; groupEntries = { motionGroups = createGroupEntities { sensors = sensors.motion; entityNameing = ({ name, ... }: "binary_sensor.${name}"); }; buttonGroups = createGroupEntities { sensors = sensors.buttons; entityNameing = ({ name , states ? { single = "input_boolean.single_${name}"; }, ... }: states.single); }; doorGroups = createGroupEntities { sensors = sensors.door; entityNameing = ({ name, ... }: "binary_sensor.${name}"); }; lightGroups = createGroupEntities { sensors = allLights; entityNameing = ({ name, ... }: "light.${name}"); }; }; # merge all group entries in lib.mapAttrs (_: entities: { entities = lib.flatten entities; }) (lib.zipAttrs (builtins.attrValues groupEntries)); in lightGroups // views // allRoomsGroups // sensorButtons // sensorMotions // sensorTemperature // sensorDoors // { all_sensors.entities = (lib.mapAttrsToList (name: { ... }: "sensor.${name}") (sensors.buttons // sensors.temperature)) ++ (lib.mapAttrsToList (name: { ... }: "binary_sensor.${name}") (sensors.motion // sensors.door)); all_lights.entities = lib.mapAttrsToList (name: { ... }: "light.${name}") allLights; }; # create automation # ----------------- automation = let # single click toggle_single_button_input = lib.mapAttrsToList (name: { states ? { }, ... }: let entityId = if (lib.hasAttr "single" states) then states.single else "input_boolean.single_${name}"; in { alias = "toggle single click ${name}"; trigger = { platform = "mqtt"; topic = "zigbee2mqtt/${name}"; }; condition = { condition = "template"; value_template = ''{{ "single" == trigger.payload_json.click}}''; }; action = { service = "input_boolean.toggle"; data.entity_id = entityId; }; }) sensors.buttons; # double click toggle_double_button_input = lib.mapAttrsToList (name: { states ? { }, ... }: let entityId = if (lib.hasAttr "double" states) then states.double else "input_boolean.double_${name}"; in { alias = "toggle double click ${name}"; trigger = { platform = "mqtt"; topic = "zigbee2mqtt/${name}"; }; condition = { condition = "template"; value_template = ''{{ "double" == trigger.payload_json.click}}''; }; action = { service = "input_boolean.toggle"; data.entity_id = entityId; }; }) sensors.buttons; # hold toggle_hold_button_input = lib.mapAttrsToList (name: { states ? { }, ... }: let entityId = if (lib.hasAttr "hold" states) then states.hold else "input_boolean.hold_${name}"; in { alias = "toggle hold ${name}"; trigger = { platform = "mqtt"; topic = "zigbee2mqtt/${name}"; }; condition = { condition = "template"; value_template = ''{{ "hold" == trigger.payload_json.action}}''; }; action = { service = "input_boolean.toggle"; data.entity_id = entityId; }; }) sensors.buttons; in lib.flatten (toggle_single_button_input ++ toggle_double_button_input ++ toggle_hold_button_input); }; # manage zigbee2mqtt # ------------------ # todo : einen eigenen container bauen mit dockerTool : https://nixos.wiki/wiki/Docker virtualisation.docker.enable = true; systemd.services."zigbee2mqtt" = { enable = true; description = "Allows you to use your Zigbee devices without the vendors bridge/gateway."; after = [ "docker.service" ]; requires = [ "docker.service" ]; # todo : udev rule erstellen, die diesen service erst startet, dieses wanted by ist labil wantedBy = [ "home-assistant.service" ]; preStart = '' if [ -f ${dataFolder}/configuration.yaml ] then rm ${dataFolder}/configuration.yaml fi mkdir -p ${dataFolder} cat ${configurationYaml} | ${pkgs.yq}/bin/yq --yaml-output '.' > ${dataFolder}/configuration.yaml ''; restartTriggers = [ configurationYaml ]; script = '' # delete old instance to ensure update ${pkgs.docker}/bin/docker stop zigbee2mqtt || true && ${pkgs.docker}/bin/docker rm -f zigbee2mqtt || true # start instance ${pkgs.docker}/bin/docker run \ --network="host" \ --name zigbee2mqtt \ -v ${dataFolder}:/app/data \ --device=${device} \ koenkk/zigbee2mqtt ''; }; }