pkgs: Add wordclock-dimmer (including module)

This commit is contained in:
Simon Bruder 2021-01-31 19:47:54 +01:00
parent a02d3cb883
commit cce86ac2c9
Signed by: simon
GPG key ID: 8D3C82F9F309F8EC
6 changed files with 173 additions and 0 deletions

View file

@ -34,6 +34,7 @@ in
# All modules are imported but non-essential modules are activated by
# configuration options
imports = [
../pkgs/modules.nix
./cups.nix
./docker.nix
./fonts.nix

View file

@ -3,4 +3,6 @@ self: super: {
osu-lazer = super.callPackage ./osu-lazer { inherit (super) osu-lazer; };
osu-lazer-container = super.callPackage ./osu-lazer-container { };
wordclock-dimmer = super.python3Packages.callPackage ./wordclock-dimmer { };
}

5
pkgs/modules.nix Normal file
View file

@ -0,0 +1,5 @@
{
imports = [
./wordclock-dimmer/module.nix
];
}

View file

@ -0,0 +1,20 @@
{ lib, buildPythonApplication, python3Packages }:
buildPythonApplication rec {
name = "wordclock-dimmer";
src = ./wordclock-dimmer.py;
propagatedBuildInputs = with python3Packages; [
astral
paho-mqtt
];
dontUnpack = true;
format = "other";
installPhase = ''
install -D $src $out/bin/wordclock-dimmer
'';
meta.license = lib.licenses.mit;
}

View file

@ -0,0 +1,63 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.wordclock-dimmer;
in
{
options.services.wordclock-dimmer = {
enable = lib.mkEnableOption "wordclock-dimmer";
mqtt = {
user = lib.mkOption {
type = lib.types.str;
};
password = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
passwordFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
host = lib.mkOption {
type = lib.types.str;
};
};
};
config = {
assertions = [
{
assertion = cfg.enable -> (
(cfg.mqtt.password != null || cfg.mqtt.passwordFile != null)
&& (cfg.mqtt.password == null || cfg.mqtt.passwordFile == null)
);
message = "One of `services.wordclock-dimmer.mqtt.password` and `services.wordclock-dimmer.mqtt.passwordFile` has to be set.";
}
];
systemd.services.wordclock-dimmer = lib.mkIf cfg.enable {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
environment = with cfg.mqtt; {
WORDCLOCK_MQTT_USER = user;
WORDCLOCK_MQTT_HOST = host;
} // lib.optionalAttrs (password != null) {
WORDCLOCK_MQTT_PASSWORD = password;
} // lib.optionalAttrs (passwordFile != null) {
WORDCLOCK_MQTT_PASSWORD_FILE = passwordFile;
};
serviceConfig = {
ExecStart = "${pkgs.wordclock-dimmer}/bin/wordclock-dimmer";
Restart = "always";
# systemd-analyze --no-pager security wordclock-dimmer.service
CapabilityBoundingSet = null;
DynamicUser = true;
PrivateUsers = true;
ProtectHome = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
SystemCallFilter = "@system-service";
};
};
};
}

View file

@ -0,0 +1,82 @@
#!/usr/bin/env python3
from astral.location import Location, LocationInfo
from math import cos, pi
from time import sleep
import datetime
import os
import paho.mqtt.client as mqtt
import sys
def time_as_float_hours(time: datetime.time) -> float:
return time.hour + time.minute / 60 + time.second / 3600
def set_color(client: mqtt.Client, red: int, green: int, blue: int, retain=True):
# my wordclocks red and green LEDs seem to be switched
client.publish("wordclock/color/red", green, retain=True)
client.publish("wordclock/color/green", red, retain=True)
client.publish("wordclock/color/blue", blue, retain=True)
def get_color_for_time(time: datetime.time, base=(60, 60, 60)) -> (int, int, int):
if time.hour >= 22 or time.hour < 7:
# night mode: dim red
return (3, 0, 0)
else:
# day mode: calculated depending on sun (https://www.desmos.com/calculator/nrseefnaom)
now = time_as_float_hours(time)
# returns at least (3, 3, 3)
min_factors = (3 / base[0], 3 / base[1], 3 / base[2])
rate = 5
location_info = LocationInfo(
timezone="Europe/Berlin", latitude=49.52, longitude=10.18
)
location = Location(location_info)
sunrise = time_as_float_hours(location.sunrise().time())
sunset = time_as_float_hours(location.sunset().time())
factors = []
for min_factor in min_factors:
factor = min_factor
if now > sunrise and now < sunset:
factor = 1 - (
(1 - min_factor) * cos((pi / (sunrise - sunset)) * (now - sunset))
) ** (2 * rate)
factors.append(factor)
return (
round(base[0] * factors[0]),
round(base[1] * factors[1]),
round(base[2] * factors[2]),
)
def update(client: mqtt.Client):
time = datetime.datetime.now().time()
color = get_color_for_time(time)
print(f"{time}: setting color to {color}")
sys.stdout.flush()
set_color(client, *color)
pass
client = mqtt.Client("wordclock.py")
user = os.environ["WORDCLOCK_MQTT_USER"]
try:
password = os.environ["WORDCLOCK_MQTT_PASSWORD"]
except KeyError:
with open(os.environ["WORDCLOCK_MQTT_PASSWORD_FILE"]) as f:
password = f.read().rstrip()
host = os.environ["WORDCLOCK_MQTT_HOST"]
client.username_pw_set(user, password)
client.connect(host, 1883, 60)
while True:
update(client)
sleep(300)