local devices = require("automation:devices") local utils = require("automation:utils") local secrets = require("automation:secrets") local helper = require("config.helper") local presence = require("config.presence") local windows = require("config.windows") local ntfy = require("config.ntfy") local light = require("config.light") local battery = require("config.battery") local hue_bridge = require("config.hue_bridge") local hallway_automation = require("config.hallway_automation") local debug = require("config.debug") hue_bridge.setup() print(_VERSION) local host = utils.get_hostname() print("Running @" .. host) local kitchen_lights = devices.HueGroup.new({ identifier = "kitchen_lights", ip = hue_bridge.ip, login = hue_bridge.token, group_id = 7, scene_id = "7MJLG27RzeRAEVJ", }) local living_lights = devices.HueGroup.new({ identifier = "living_lights", ip = hue_bridge.ip, login = hue_bridge.token, group_id = 1, scene_id = "SNZw7jUhQ3cXSjkj", }) local living_lights_relax = devices.HueGroup.new({ identifier = "living_lights", ip = hue_bridge.ip, login = hue_bridge.token, group_id = 1, scene_id = "eRJ3fvGHCcb6yNw", }) local hallway_top_light = devices.HueGroup.new({ identifier = "hallway_top_light", ip = hue_bridge.ip, login = hue_bridge.token, group_id = 83, scene_id = "QeufkFDICEHWeKJ7", }) local hallway_bottom_lights = devices.HueGroup.new({ identifier = "hallway_bottom_lights", ip = hue_bridge.ip, login = hue_bridge.token, group_id = 81, scene_id = "3qWKxGVadXFFG4o", }) hallway_automation.add_callback(function(on) hallway_bottom_lights:set_on(on) end) local bedroom_lights = devices.HueGroup.new({ identifier = "bedroom_lights", ip = hue_bridge.ip, login = hue_bridge.token, group_id = 3, scene_id = "PvRs-lGD4VRytL9", }) local bedroom_lights_relax = devices.HueGroup.new({ identifier = "bedroom_lights", ip = hue_bridge.ip, login = hue_bridge.token, group_id = 3, scene_id = "60tfTyR168v2csz", }) local bedroom_air_filter = devices.AirFilter.new({ name = "Air Filter", room = "Bedroom", url = "http://10.0.0.103", }) local function create_devs(mqtt_client) local devs = {} function devs:add(device) table.insert(self, device) end devs:add(devices.HueSwitch.new({ name = "Switch", room = "Living", client = mqtt_client, topic = helper.mqtt_z2m("living/switch"), left_callback = function() kitchen_lights:set_on(not kitchen_lights:on()) end, right_callback = function() living_lights:set_on(not living_lights:on()) end, right_hold_callback = function() living_lights_relax:set_on(true) end, battery_callback = battery.callback, })) devs:add(devices.WakeOnLAN.new({ name = "Zeus", room = "Living Room", topic = helper.mqtt_automation("appliance/living_room/zeus"), client = mqtt_client, mac_address = "30:9c:23:60:9c:13", broadcast_ip = "10.0.3.255", })) local living_mixer = devices.OutletOnOff.new({ name = "Mixer", room = "Living Room", topic = helper.mqtt_z2m("living/mixer"), client = mqtt_client, }) presence.turn_off_when_away(living_mixer) devs:add(living_mixer) local living_speakers = devices.OutletOnOff.new({ name = "Speakers", room = "Living Room", topic = helper.mqtt_z2m("living/speakers"), client = mqtt_client, }) presence.turn_off_when_away(living_speakers) devs:add(living_speakers) devs:add(devices.IkeaRemote.new({ name = "Remote", room = "Living Room", client = mqtt_client, topic = helper.mqtt_z2m("living/remote"), single_button = true, callback = function(_, on) if on then if living_mixer:on() then living_mixer:set_on(false) living_speakers:set_on(false) else living_mixer:set_on(true) living_speakers:set_on(true) end else if not living_mixer:on() then living_mixer:set_on(true) else living_speakers:set_on(not living_speakers:on()) end end end, battery_callback = battery.callback, })) --- @type OutletPower local kettle = devices.OutletPower.new({ outlet_type = "Kettle", name = "Kettle", room = "Kitchen", topic = helper.mqtt_z2m("kitchen/kettle"), client = mqtt_client, callback = helper.auto_off(), }) presence.turn_off_when_away(kettle) devs:add(kettle) --- @param on boolean local function set_kettle(_, on) kettle:set_on(on) end devs:add(devices.IkeaRemote.new({ name = "Remote", room = "Bedroom", client = mqtt_client, topic = helper.mqtt_z2m("bedroom/remote"), single_button = true, callback = set_kettle, battery_callback = battery.callback, })) devs:add(devices.IkeaRemote.new({ name = "Remote", room = "Kitchen", client = mqtt_client, topic = helper.mqtt_z2m("kitchen/remote"), single_button = true, callback = set_kettle, battery_callback = battery.callback, })) local bathroom_light = devices.LightOnOff.new({ name = "Light", room = "Bathroom", topic = helper.mqtt_z2m("bathroom/light"), client = mqtt_client, callback = helper.off_timeout(debug.debug_mode and 60 or 45 * 60), }) devs:add(bathroom_light) devs:add(devices.Washer.new({ identifier = "bathroom_washer", topic = helper.mqtt_z2m("bathroom/washer"), client = mqtt_client, threshold = 1, done_callback = function() ntfy.device:send_notification({ title = "Laundy is done", message = "Don't forget to hang it!", tags = { "womans_clothes" }, priority = "high", }) end, })) devs:add(devices.OutletOnOff.new({ name = "Charger", room = "Workbench", topic = helper.mqtt_z2m("workbench/charger"), client = mqtt_client, callback = helper.off_timeout(debug.debug_mode and 5 or 20 * 3600), })) local workbench_outlet = devices.OutletOnOff.new({ name = "Outlet", room = "Workbench", topic = helper.mqtt_z2m("workbench/outlet"), client = mqtt_client, }) presence.turn_off_when_away(workbench_outlet) devs:add(workbench_outlet) local workbench_light = devices.LightColorTemperature.new({ name = "Light", room = "Workbench", topic = helper.mqtt_z2m("workbench/light"), client = mqtt_client, }) presence.turn_off_when_away(workbench_light) devs:add(workbench_light) local delay_color_temp = utils.Timeout.new() devs:add(devices.IkeaRemote.new({ name = "Remote", room = "Workbench", client = mqtt_client, topic = helper.mqtt_z2m("workbench/remote"), callback = function(_, on) delay_color_temp:cancel() if on then workbench_light:set_brightness(82) -- NOTE: This light does NOT support changing both the brightness and color -- temperature at the same time, so we first change the brightness and once -- that is complete we change the color temperature, as that is less likely -- to have to actually change. delay_color_temp:start(0.5, function() workbench_light:set_color_temperature(3333) end) else workbench_light:set_on(false) end end, battery_callback = battery.callback, })) devs:add(devices.HueSwitch.new({ name = "SwitchBottom", room = "Hallway", client = mqtt_client, topic = helper.mqtt_z2m("hallway/switchbottom"), left_callback = function() hallway_top_light:set_on(not hallway_top_light:on()) end, battery_callback = battery.callback, })) devs:add(devices.HueSwitch.new({ name = "SwitchTop", room = "Hallway", client = mqtt_client, topic = helper.mqtt_z2m("hallway/switchtop"), left_callback = function() hallway_top_light:set_on(not hallway_top_light:on()) end, battery_callback = battery.callback, })) local hallway_storage = devices.LightBrightness.new({ name = "Storage", room = "Hallway", topic = helper.mqtt_z2m("hallway/storage"), client = mqtt_client, callback = hallway_automation.light_callback, }) presence.turn_off_when_away(hallway_storage) devs:add(hallway_storage) hallway_automation.add_callback(function(on) if on then hallway_storage:set_brightness(80) else hallway_storage:set_on(false) end end) devs:add(devices.IkeaRemote.new({ name = "Remote", room = "Hallway", client = mqtt_client, topic = helper.mqtt_z2m("hallway/remote"), callback = hallway_automation.switch_callback, battery_callback = battery.callback, })) ---@param duration number ---@return fun(_, open: boolean) local function frontdoor_presence(duration) local timeout = utils.Timeout.new() return function(_, open) if open then timeout:cancel() if presence.device and not presence.device:overall_presence() then mqtt_client:send_message(helper.mqtt_automation("presence/contact/frontdoor"), { state = true, updated = utils.get_epoch(), }) end else timeout:start(duration, function() mqtt_client:send_message(helper.mqtt_automation("presence/contact/frontdoor"), nil) end) end end end local hallway_frontdoor = devices.ContactSensor.new({ name = "Frontdoor", room = "Hallway", sensor_type = "Door", topic = helper.mqtt_z2m("hallway/frontdoor"), client = mqtt_client, callback = { frontdoor_presence(debug.debug_mode and 10 or 15 * 60), hallway_automation.door_callback, }, battery_callback = battery.callback, }) devs:add(hallway_frontdoor) windows.add(hallway_frontdoor) hallway_automation.set_door(hallway_frontdoor) local hallway_trash = devices.ContactSensor.new({ name = "Trash", room = "Hallway", sensor_type = "Drawer", topic = helper.mqtt_z2m("hallway/trash"), client = mqtt_client, callback = hallway_automation.trash_callback, battery_callback = battery.callback, }) devs:add(hallway_trash) hallway_automation.set_trash(hallway_trash) local guest_light = devices.LightOnOff.new({ name = "Light", room = "Guest Room", topic = helper.mqtt_z2m("guest/light"), client = mqtt_client, }) presence.turn_off_when_away(guest_light) devs:add(guest_light) devs:add(devices.HueSwitch.new({ name = "Switch", room = "Bedroom", client = mqtt_client, topic = helper.mqtt_z2m("bedroom/switch"), left_callback = function() bedroom_lights:set_on(not bedroom_lights:on()) end, left_hold_callback = function() bedroom_lights_relax:set_on(true) end, battery_callback = battery.callback, })) local balcony = devices.ContactSensor.new({ name = "Balcony", room = "Living Room", sensor_type = "Door", topic = helper.mqtt_z2m("living/balcony"), client = mqtt_client, battery_callback = battery.callback, }) devs:add(balcony) windows.add(balcony) local living_window = devices.ContactSensor.new({ name = "Window", room = "Living Room", topic = helper.mqtt_z2m("living/window"), client = mqtt_client, battery_callback = battery.callback, }) devs:add(living_window) windows.add(living_window) local bedroom_window = devices.ContactSensor.new({ name = "Window", room = "Bedroom", topic = helper.mqtt_z2m("bedroom/window"), client = mqtt_client, battery_callback = battery.callback, }) devs:add(bedroom_window) windows.add(bedroom_window) local guest_window = devices.ContactSensor.new({ name = "Window", room = "Guest Room", topic = helper.mqtt_z2m("guest/window"), client = mqtt_client, battery_callback = battery.callback, }) devs:add(guest_window) windows.add(guest_window) local storage_light = devices.LightBrightness.new({ name = "Light", room = "Storage", topic = helper.mqtt_z2m("storage/light"), client = mqtt_client, }) presence.turn_off_when_away(storage_light) devs:add(storage_light) devs:add(devices.ContactSensor.new({ name = "Door", room = "Storage", sensor_type = "Door", topic = helper.mqtt_z2m("storage/door"), client = mqtt_client, callback = function(_, open) if open then storage_light:set_brightness(100) else storage_light:set_on(false) end end, battery_callback = battery.callback, })) devs.add = nil return devs end --- @type MqttConfig local mqtt_config = { host = ((host == "zeus" or host == "hephaestus") and "olympus.lan.huizinga.dev") or "mosquitto", port = 8883, client_name = "automation-" .. host, username = "mqtt", password = secrets.mqtt_password, tls = host == "zeus" or host == "hephaestus", } ---@type Config return { fulfillment = { openid_url = "https://login.huizinga.dev/api/oidc", }, mqtt = mqtt_config, devices = { create_devs, ntfy.device, presence.setup, light.setup, hue_bridge.setup, debug.setup, windows.setup, kitchen_lights, living_lights, living_lights_relax, hallway_top_light, hallway_bottom_lights, bedroom_lights, bedroom_lights_relax, bedroom_air_filter, }, schedule = { ["0 0 19 * * *"] = function() bedroom_air_filter:set_on(true) end, ["0 0 20 * * *"] = function() bedroom_air_filter:set_on(false) end, ["0 0 21 */1 * *"] = battery.notify_low_battery, }, }