feat(hass): add firmware config for ESPHome
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-09-05 21:33:06 -04:00
parent d428ee9383
commit e82b9d5306
8 changed files with 246 additions and 114 deletions

View file

@ -1,5 +1,5 @@
{...}: {
imports = [
./home-assistant.nix
./home-assistant
];
}

View file

@ -1,113 +0,0 @@
{
config,
lib,
pkgs,
self,
wakewords-src,
dracul-ha-src,
...
}: {
imports = [self.nixosModules.wyoming-plus];
# TODO: some components / integrations / addons require manual interaction in the GUI, find way to make it all declarative
services = {
home-assistant = {
enable = true;
extraComponents = [
"caldav"
"esphome"
"holiday"
"isal"
"met"
"ollama"
"spotify"
"upnp"
"wyoming"
"yamaha_musiccast"
];
customComponents = builtins.attrValues {
inherit (self.legacyPackages.${pkgs.system}.hass-components) home-llm;
};
config = {
# Proxy settings
http = {
server_host = "0.0.0.0";
trusted_proxies = ["100.64.0.8" "100.64.0.9"];
use_x_forwarded_for = true;
};
# Extra conf that was needed
media_source = {};
# GUI
frontend = {
themes = "!include ${dracul-ha-src}/themes/dracul-ha.yaml";
};
lovelace = {
mode = "yaml";
};
# `default_config` enables too much stuff. this is what I want from it
assist_pipeline = {};
config = {};
conversation = {};
dhcp = {};
history = {};
image_upload = {};
logbook = {};
mobile_app = {};
my = {};
sun = {};
automation = {};
zeroconf = {};
};
};
wyoming = {
piper.servers."en" = {
enable = true;
uri = "tcp://127.0.0.1:10200";
# see https://github.com/rhasspy/rhasspy3/blob/master/programs/tts/piper/script/download.py
voice = "en-us-ryan-low"; # using `hfc male (medium)` in GUI
speaker = 0;
};
openwakeword-docker = {
enable = true;
uri = "127.0.0.1:10400";
customModelsDirectories = ["${wakewords-src}/en/yo_homie"];
preloadModels = ["yo_homie"];
};
};
};
services.esphome = {
enable = true;
address = "100.64.0.10";
port = 6052;
};
# FIXME: https://github.com/NixOS/nixpkgs/issues/339557
systemd.services.esphome = let
inherit (lib) mkForce;
cfg = config.services.esphome;
stateDir = "/var/lib/private/esphome";
esphomeParams =
if cfg.enableUnixSocket
then "--socket /run/esphome/esphome.sock"
else "--address ${cfg.address} --port ${toString cfg.port}";
in {
environment.PLATFORMIO_CORE_DIR = mkForce "${stateDir}/.platformio";
serviceConfig = {
ExecStart = mkForce "${cfg.package}/bin/esphome dashboard ${esphomeParams} ${stateDir}";
WorkingDirectory = mkForce stateDir;
};
};
}

View file

@ -0,0 +1,56 @@
{
pkgs,
self,
wakewords-src,
...
}: {
imports = [
self.nixosModules.esphome-plus
self.nixosModules.wyoming-plus
];
services = {
home-assistant = {
extraComponents = [
"esphome"
"ollama"
"wyoming"
];
customComponents = builtins.attrValues {
inherit (self.legacyPackages.${pkgs.system}.hass-components) home-llm;
};
config = {
assist_pipeline = {};
conversation = {};
media_source = {};
};
};
wyoming = {
piper.servers."en" = {
enable = true;
uri = "tcp://127.0.0.1:10200";
# see https://github.com/rhasspy/rhasspy3/blob/master/programs/tts/piper/script/download.py
voice = "en-us-ryan-low"; # using `hfc male (medium)` in GUI
speaker = 0;
};
openwakeword-docker = {
enable = true;
uri = "127.0.0.1:10400";
customModelsDirectories = ["${wakewords-src}/en/yo_homie"];
preloadModels = ["yo_homie"];
};
};
esphome = {
enable = true;
address = "100.64.0.10";
port = 6052;
};
};
}

View file

@ -0,0 +1,43 @@
{...}: {
imports = [
./assist.nix
./firmware.nix
./frontend.nix
];
# TODO: some components / integrations / addons require manual interaction in the GUI, find way to make it all declarative
services.home-assistant = {
enable = true;
extraComponents = [
"caldav"
"holiday"
"isal"
"met"
"spotify"
"upnp"
"yamaha_musiccast"
];
config = {
# Proxy settings
http = {
server_host = "0.0.0.0";
trusted_proxies = ["100.64.0.8" "100.64.0.9"];
use_x_forwarded_for = true;
};
# `default_config` enables too much stuff. this is what I want from it
config = {};
dhcp = {};
history = {};
image_upload = {};
logbook = {};
mobile_app = {};
my = {};
sun = {};
automation = {};
zeroconf = {};
};
};
}

View file

@ -0,0 +1,40 @@
{...}: {
services.esphome.firmwareConfigs = {
"esp1" = {
packages.remote_package_files = {
url = "https://github.com/esphome/firmware";
files = ["voice-assistant/m5stack-atom-echo.adopted.yaml"];
ref = "0f6fad0860b8bd2c251162abde5064be1ae29546";
};
# Enable Home Assistant API
api.encryption.key = "!secret api_key";
ota = [
{
platform = "esphome";
password = "!secret ota_pass";
}
];
wifi = {
ssid = "!secret wifi_ssid";
password = "!secret wifi_password";
manual_ip = {
# Set this to the IP of the ESP
static_ip = "192.168.0.92";
# Set this to the IP address of the router. Often ends with .1
gateway = "192.168.0.1";
subnet = "255.255.255.0";
};
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap = {
ssid = "Esp1 Fallback Hotspot";
password = "!secret ap_fallback";
};
};
};
};
}

View file

@ -0,0 +1,13 @@
{dracul-ha-src, ...}: {
services.home-assistant = {
config = {
# GUI
frontend = {
themes = "!include ${dracul-ha-src}/themes/dracul-ha.yaml";
};
lovelace = {
mode = "yaml";
};
};
};
}

View file

@ -3,6 +3,7 @@ self: {
borgbackup = import ./borgbackup;
desktop = import ./desktop self;
docker = import ./docker self.inputs.khepri;
esphome-plus = import ./esphome-plus;
kmscon = import ./kmscon;
nvidia = import ./nvidia;
plymouth = import ./plymouth;

View file

@ -0,0 +1,92 @@
{
config,
lib,
pkgs,
...
}: let
inherit
(lib)
concatMapStringsSep
converge
elem
filterAttrsRecursive
getExe
mapAttrsToList
mkForce
mkIf
mkOption
optionalAttrs
optionals
optionalString
types
;
cfg = config.services.esphome;
stateDir = "/var/lib/private/esphome";
esphomeParams =
if cfg.enableUnixSocket
then "--socket /run/esphome/esphome.sock"
else "--address ${cfg.address} --port ${toString cfg.port}";
format = pkgs.formats.yaml {};
# Adapted from https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/home-automation/home-assistant.nix
mkESPConf = name: cfg: let
filteredConfig = converge (filterAttrsRecursive (_: v: ! elem v [null])) cfg;
in {
name = "${name}.yaml";
file = pkgs.runCommandLocal "${name}.yaml" {} ''
cp ${format.generate "${name}.yaml" filteredConfig} $out
sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
sed -i 's/ {}//g' $out
'';
};
in {
options.services.esphome = {
firmwareConfigs = mkOption {
default = {};
type = with types; attrsOf anything;
};
deleteUnmanaged = mkOption {
default = true;
type = types.bool;
};
};
config = mkIf (cfg.enable) {
# FIXME: https://github.com/NixOS/nixpkgs/issues/339557
systemd.services.esphome = {
environment.PLATFORMIO_CORE_DIR = mkForce "${stateDir}/.platformio";
serviceConfig =
(optionalAttrs (cfg.firmwareConfigs != {}) {
ExecStartPre = getExe (pkgs.writeShellApplication {
name = "esphome-exec-start-pre";
runtimeInputs = optionals cfg.deleteUnmanaged [
pkgs.findutils
];
text = ''
if [[ ! -d ${stateDir} ]]; then
mkdir -p ${stateDir}
fi
${optionalString cfg.deleteUnmanaged ''find ${stateDir} -name "*.yaml" ! -name "secrets.yaml" -delete''}
${concatMapStringsSep
"\n"
(dev: ''cp -f "$(realpath "${dev.file}")" ${stateDir}/"${dev.name}"'')
(mapAttrsToList mkESPConf cfg.firmwareConfigs)}
'';
});
})
// {
ExecStart = mkForce "${cfg.package}/bin/esphome dashboard ${esphomeParams} ${stateDir}";
WorkingDirectory = mkForce stateDir;
};
};
};
}