commit f32d5b3946addd830d059cbd22b427f1bc4c3d5b
parent 1c0ea98ef0b305c1be628daec41636cb1ae09aa6
Author: Leah (ctucx) <git@ctu.cx>
Date: Tue, 13 Dec 2022 16:41:48 +0100
parent 1c0ea98ef0b305c1be628daec41636cb1ae09aa6
Author: Leah (ctucx) <git@ctu.cx>
Date: Tue, 13 Dec 2022 16:41:48 +0100
machines/lollo/smarthome/mqtt-webui: add departures
9 files changed, 509 insertions(+), 296 deletions(-)
D
|
254
-------------------------------------------------------------------------------
A
|
320
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
92
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/flake.lock b/flake.lock @@ -131,6 +131,30 @@ "type": "github" } }, + "departures2mqtt": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1670953611, + "narHash": "sha256-Nux7niW2fFY/kAfUrlHre/PumoMq2JryTKvIVnJlhhE=", + "ref": "master", + "rev": "41a0e352cc99ccfb918af7a5316842fb37b1fd4f", + "revCount": 1, + "type": "git", + "url": "https://git.ctu.cx/departures2mqtt" + }, + "original": { + "ref": "master", + "type": "git", + "url": "https://git.ctu.cx/departures2mqtt" + } + }, "dns-nix": { "inputs": { "flake-utils": "flake-utils_2", @@ -379,10 +403,10 @@ ] }, "locked": { - "lastModified": 1670944808, - "narHash": "sha256-NRj7wq9khChJUCf8MXcH1BHaKvdpv9MFUXbG2kY/c1c=", + "lastModified": 1670953149, + "narHash": "sha256-VADBy42h3jxcrlMtwaNIf3Xo1B/gnOt3pj0hRde65Lo=", "ref": "master", - "rev": "0f0b70aed5014799e20bdcbe328d413c99cd922e", + "rev": "06253735ad68b6133a4fbcac5a2b847aae1a7b5a", "revCount": 18, "type": "git", "url": "https://git.ctu.cx/mqtt-webui" @@ -553,6 +577,7 @@ "ctucx-gallery": "ctucx-gallery", "ctucx-things": "ctucx-things", "darwin": "darwin", + "departures2mqtt": "departures2mqtt", "dns-nix": "dns-nix", "dnsmasq-lease-overview": "dnsmasq-lease-overview", "flake-utils": "flake-utils_3",
diff --git a/flake.nix b/flake.nix @@ -21,6 +21,7 @@ inputs.sdm2mqtt.overlay inputs.lacrosse2mqtt.overlay + inputs.departures2mqtt.overlay inputs.mqtt-webui.overlay inputs.ctucx-things.overlay @@ -169,6 +170,14 @@ inputs.flake-utils.follows = "flake-utils"; }; + departures2mqtt = { + type = "git"; + url = "https://git.ctu.cx/departures2mqtt"; + ref = "master"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; + mqtt-webui = { type = "git"; url = "https://git.ctu.cx/mqtt-webui";
diff --git a/machines/lollo/smarthome/default.nix b/machines/lollo/smarthome/default.nix @@ -9,11 +9,12 @@ ./zigbee2mqtt.nix ./sdm2mqtt.nix ./lacrosse2mqtt.nix + ./departures2mqtt.nix ./influxdb2.nix ./telegraf.nix - ./mqtt-webui.nix + ./mqtt-webui ]; }
diff --git a/machines/lollo/smarthome/departures2mqtt.nix b/machines/lollo/smarthome/departures2mqtt.nix @@ -0,0 +1,19 @@ +{ inputs, config, pkgs, ... }: + +{ + + systemd.services.departures2mqtt = { + onFailure = [ "email-notify@%i.service" ]; + startAt = [ + "*-*-* 00..09:00/5" + "*-*-* 10..19:00/2" + "*-*-* 20..23:00/5" + ]; + + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.departures2mqtt}/bin/departures2mqtt --mqtt-host=10.0.0.1 --mqtt-topic=departures2mqtt --stations=1505,2946,2187"; + }; + }; + +}
diff --git a/machines/lollo/smarthome/mqtt-webui-config.nix b/machines/lollo/smarthome/mqtt-webui-config.nix @@ -1,253 +0,0 @@ -let - - Switch = name: topic: { - title = name; - type = "switch"; - icon = "icons/power_button.png"; - topic.get = topic; - topic.set = "${topic}/set"; - transform.get = "return (message.state == 'ON') ? true : false"; - transform.set = "return JSON.stringify({state: (input) ? 'ON' : 'OFF'})"; - }; - - BrighnessSlider = name: topic: { - title = name; - type = "slider"; - icon = "icons/bulb.png"; - sliderMinValue = 0; - sliderMaxValue = 254; - sliderStepValue = 1; - topic.get = topic; - topic.set = "${topic}/set"; - transform.get = "return message.brightness"; - transform.set = "return JSON.stringify({brightness: Number(input)})"; - }; - - ColorTemperatureSlider = name: topic: { - title = name; - type = "slider"; - icon = "icons/bulb.png"; - sliderMinValue = 250; - sliderMaxValue = 454; - sliderStepValue = 1; - topic.get = topic; - topic.set = "${topic}/set"; - transform.get = "return message.color_temp"; - transform.set = "return JSON.stringify({color_temp: Number(input)})"; - }; - - DimmableLamp = name: topic: { - title = name; - items = [ - (Switch "Power" topic) - (BrighnessSlider "Brighness" topic) - ]; - }; - - WhiteSpectrumLamp = name: topic: { - title = name; - items = [ - (Switch "Power" topic) - (BrighnessSlider "Brighness" topic) - (ColorTemperatureSlider "Color Temperature" topic) - ]; - }; - - ColorSpectrumLamp = name: topic: { - title = name; - items = [ - (Switch "Power" topic) - (BrighnessSlider "Brighness" topic) - (ColorTemperatureSlider "Color Temperature" topic) - - { - title = "Color"; - type = "select"; - icon = "icons/bulb.png"; - topic.get = topic; - topic.set = "${topic}/set"; - transform.get = "return message.color.x + ','+message.color.y"; - transform.set = "return JSON.stringify({color: {x: input.split(',')[0], y: input.split(',')[1]}})"; - selectOptions = [ - { - label = "Red"; - value = "0.71,0.26"; - } - { - label = "Green"; - value = "0.19,0.78"; - } - { - label = "Blue"; - value = "0.09,0.13"; - } - ]; - } - ]; - }; - - -in { - - pages = [ - { - id = "mainpage"; - icon = "favicon-512x512.png"; - title = "Smart-Home"; - sections = [ - { - title = "Rooms"; - items = [ - { - title = "Leah's room"; - type = "text"; - icon = "icons/electric_range.png"; - link = "#leah"; - } - { - title = "Isa's room"; - type = "text"; - icon = "icons/electric_range.png"; - link = "#isa"; - } - ]; - } - - (WhiteSpectrumLamp "Hallway: Ceiling Light" "zigbee2mqtt/ikea_lamp_hallway") - - (WhiteSpectrumLamp "Kitchen: Ceiling Light" "zigbee2mqtt/ikea_lamp_kitchen") - - (WhiteSpectrumLamp "Bathroom: Ceiling Light" "zigbee2mqtt/ikea_lamp_bathroom") - - { - title = "Temperature-Sensors"; - items = [ - { - title = "Fridge"; - type = "text"; - topic = "lacrosse2mqtt/33"; - icon = "icons/temperature.png"; - transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'"; - } - { - title = "Bathroom"; - type = "text"; - topic = "lacrosse2mqtt/5"; - icon = "icons/temperature.png"; - transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'"; - } - ]; - } - ]; - } - - { - id = "leah"; - icon = "favicon-512x512.png"; - title = "Leah's room"; - sections = [ - (WhiteSpectrumLamp "Ceiling Light" "zigbee2mqtt/ikea_lamp_l") - - (DimmableLamp "Desk" "zigbee2mqtt/led_stripe_desk") - - (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_l_rgb") - - { - title = "Power-Meter"; - items = [ - { - title = "Voltage"; - type = "text"; - topic = "sdm2mqtt/leah"; - icon = "icons/power.png"; - transform = "return Math.round((message.voltage + Number.EPSILON) * 100) / 100 + ' V'"; - } - { - title = "Power"; - type = "text"; - topic = "sdm2mqtt/leah"; - icon = "icons/power.png"; - transform = "return Math.round((message.power + Number.EPSILON) * 100) / 100 + ' W'"; - } - { - title = "Frequency"; - type = "text"; - topic = "sdm2mqtt/leah"; - icon = "icons/power.png"; - transform = "return message.frequency + ' Hz'"; - } - { - title = "cos φ"; - type = "text"; - topic = "sdm2mqtt/leah"; - icon = "icons/power.png"; - transform = "return Math.round((message.cosphi + Number.EPSILON) * 100) / 100"; - } - { - title = "Total Import"; - type = "text"; - topic = "sdm2mqtt/leah"; - icon = "icons/power.png"; - transform = "return Math.round((message.import + Number.EPSILON) * 100) / 100 + ' kWh'"; - } - ]; - } - - { - title = "Temperature-Sensors"; - items = [ - { - title = "Temperature"; - type = "text"; - topic = "lacrosse2mqtt/3a"; - icon = "icons/temperature.png"; - transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'"; - } - ]; - } - ]; - } - - { - id = "isa"; - icon = "favicon-512x512.png"; - title = "Isa's room"; - sections = [ - (WhiteSpectrumLamp "Ceiling Light" "zigbee2mqtt/ikea_lamp_i") - - (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_i_rgb") - - { - title = "Switches"; - items = [ - (Switch "Desk (L)" "zigbee2mqtt/ikea_control_outlet_i_desk_l") - (Switch "Desk (R)" "zigbee2mqtt/ikea_control_outlet_i_desk_r") - ]; - } - - { - title = "Temperature-Sensors"; - items = [ - { - title = "Temperature"; - type = "text"; - topic = "lacrosse2mqtt/3c"; - icon = "icons/temperature.png"; - transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'"; - } - { - title = "Humidity"; - type = "text"; - topic = "lacrosse2mqtt/3c"; - icon = "icons/thermostat.png"; - transform = "return message.humidity + ' %'"; - } - ]; - } - - ]; - } - - ]; - -}- \ No newline at end of file
diff --git a/machines/lollo/smarthome/mqtt-webui.nix b/machines/lollo/smarthome/mqtt-webui.nix @@ -1,37 +0,0 @@ -{ inputs, lib, pkgs, ... }: - -let - - configFile = pkgs.writeTextDir "config.json" (builtins.toJSON (import ./mqtt-webui-config.nix)); - -in { - - services = { - nginx = { - enable = true; - virtualHosts."smart.home.ctu.cx" = { - enableACME = true; - forceSSL = true; - kTLS = true; - - locations = { - "/" = { - root = "${pkgs.buildEnv { - name = "mqtt-webui-env"; - paths = [ - pkgs.mqtt-webui - configFile - ]; - }}/"; - }; - "/mqtt" = { - proxyPass = "http://127.0.0.1:9005"; - proxyWebsockets = true; - }; - }; - }; - }; - - }; - -}- \ No newline at end of file
diff --git a/machines/lollo/smarthome/mqtt-webui/config.nix b/machines/lollo/smarthome/mqtt-webui/config.nix @@ -0,0 +1,319 @@ +let + + Switch = name: topic: { + title = name; + type = "switch"; + icon = "icons/power_button.png"; + topic.get = topic; + topic.set = "${topic}/set"; + transform.get = "return (message.state == 'ON') ? true : false"; + transform.set = "return JSON.stringify({state: (input) ? 'ON' : 'OFF'})"; + }; + + BrighnessSlider = name: topic: { + title = name; + type = "slider"; + icon = "icons/bulb.png"; + sliderMinValue = 0; + sliderMaxValue = 254; + sliderStepValue = 1; + topic.get = topic; + topic.set = "${topic}/set"; + transform.get = "return message.brightness"; + transform.set = "return JSON.stringify({brightness: Number(input)})"; + }; + + ColorTemperatureSlider = name: topic: { + title = name; + type = "slider"; + icon = "icons/bulb.png"; + sliderMinValue = 250; + sliderMaxValue = 454; + sliderStepValue = 1; + topic.get = topic; + topic.set = "${topic}/set"; + transform.get = "return message.color_temp"; + transform.set = "return JSON.stringify({color_temp: Number(input)})"; + }; + + DimmableLamp = name: topic: { + title = name; + items = [ + (Switch "Power" topic) + (BrighnessSlider "Brighness" topic) + ]; + }; + + WhiteSpectrumLamp = name: topic: { + title = name; + items = [ + (Switch "Power" topic) + (BrighnessSlider "Brighness" topic) + (ColorTemperatureSlider "Color Temperature" topic) + ]; + }; + + ColorSpectrumLamp = name: topic: { + title = name; + items = [ + (Switch "Power" topic) + (BrighnessSlider "Brighness" topic) + (ColorTemperatureSlider "Color Temperature" topic) + + { + title = "Color"; + type = "select"; + icon = "icons/bulb.png"; + topic.get = topic; + topic.set = "${topic}/set"; + transform.get = "return message.color.x + ','+message.color.y"; + transform.set = "return JSON.stringify({color: {x: input.split(',')[0], y: input.split(',')[1]}})"; + selectOptions = [ + { + label = "Red"; + value = "0.71,0.26"; + } + { + label = "Green"; + value = "0.19,0.78"; + } + { + label = "Blue"; + value = "0.09,0.13"; + } + ]; + } + ]; + }; + + +in { + + appName = "Smart-Home"; + extraCSS = "/extra.css"; + pages = [ + { + id = "mainpage"; + icon = "favicon-512x512.png"; + title = "Smart-Home"; + sections = [ + { + title = "Rooms"; + items = [ + { + title = "Leah's room"; + type = "text"; + icon = "icons/electric_range.png"; + link = "#leah"; + } + { + title = "Isa's room"; + type = "text"; + icon = "icons/electric_range.png"; + link = "#isa"; + } + ]; + } + + (WhiteSpectrumLamp "Hallway: Ceiling Light" "zigbee2mqtt/ikea_lamp_hallway") + + (WhiteSpectrumLamp "Kitchen: Ceiling Light" "zigbee2mqtt/ikea_lamp_kitchen") + + (WhiteSpectrumLamp "Bathroom: Ceiling Light" "zigbee2mqtt/ikea_lamp_bathroom") + + { + title = "Temperature-Sensors"; + items = [ + { + title = "Fridge"; + type = "text"; + topic = "lacrosse2mqtt/33"; + icon = "icons/temperature.png"; + transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'"; + } + { + title = "Bathroom"; + type = "text"; + topic = "lacrosse2mqtt/5"; + icon = "icons/temperature.png"; + transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'"; + } + ]; + } + + { + items = [ + { + title = "Departures"; + type = "text"; + icon = "icons/electric_range.png"; + link = "#departures"; + } + { + title = "Grafana-Dashboard"; + type = "text"; + icon = "icons/sun.png"; + link = "https://grafana.ctu.cx/d/FRDYqjEGz/smarthome-influx?orgId=1&refresh=5s"; + } + ]; + } + + ]; + } + + { + id = "leah"; + title = "Leah's room"; + sections = [ + (WhiteSpectrumLamp "Ceiling Light" "zigbee2mqtt/ikea_lamp_l") + + (DimmableLamp "Desk" "zigbee2mqtt/led_stripe_desk") + + (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_l_rgb") + + { + title = "Power-Meter"; + items = [ + { + title = "Voltage"; + type = "text"; + topic = "sdm2mqtt/leah"; + icon = "icons/power.png"; + transform = "return Math.round((message.voltage + Number.EPSILON) * 100) / 100 + ' V'"; + } + { + title = "Power"; + type = "text"; + topic = "sdm2mqtt/leah"; + icon = "icons/power.png"; + transform = "return Math.round((message.power + Number.EPSILON) * 100) / 100 + ' W'"; + } + { + title = "Frequency"; + type = "text"; + topic = "sdm2mqtt/leah"; + icon = "icons/power.png"; + transform = "return message.frequency + ' Hz'"; + } + { + title = "cos φ"; + type = "text"; + topic = "sdm2mqtt/leah"; + icon = "icons/power.png"; + transform = "return Math.round((message.cosphi + Number.EPSILON) * 100) / 100"; + } + { + title = "Total Import"; + type = "text"; + topic = "sdm2mqtt/leah"; + icon = "icons/power.png"; + transform = "return Math.round((message.import + Number.EPSILON) * 100) / 100 + ' kWh'"; + } + ]; + } + + { + title = "Temperature-Sensors"; + items = [ + { + title = "Temperature"; + type = "text"; + topic = "lacrosse2mqtt/3a"; + icon = "icons/temperature.png"; + transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'"; + } + ]; + } + + ]; + } + + { + id = "isa"; + title = "Isa's room"; + sections = [ + (WhiteSpectrumLamp "Ceiling Light" "zigbee2mqtt/ikea_lamp_i") + + (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_i_rgb") + + { + title = "Switches"; + items = [ + (Switch "Desk (L)" "zigbee2mqtt/ikea_control_outlet_i_desk_l") + (Switch "Desk (R)" "zigbee2mqtt/ikea_control_outlet_i_desk_r") + ]; + } + + { + title = "Temperature-Sensors"; + items = [ + { + title = "Temperature"; + type = "text"; + topic = "lacrosse2mqtt/3c"; + icon = "icons/temperature.png"; + transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C'"; + } + { + title = "Humidity"; + type = "text"; + topic = "lacrosse2mqtt/3c"; + icon = "icons/thermostat.png"; + transform = "return message.humidity + ' %'"; + } + ]; + } + + ]; + } + + { + id = "departures"; + title = "Departures"; + sections = [ + { + items = [ + { + type = "html"; + topic = "departures2mqtt"; + html = "<div class=\"loader\"></div>"; + transform = '' + clearInterval(globalThis.departuresUpdater) + globalThis.departuresUpdater = setInterval(() => { + let element = document.querySelector('[data-last-updated]'); + element.textContent = 'Last Update: ' + (Math.floor(Date.now() / 1000) - element.dataset.lastUpdated) + ' seconds ago'; + }, 10000) + + let output = ""; + + Object.entries(message.departures).forEach((data) => { + output += '<div class="departures"><div class="table-column table-title">' + data[0] + '</div><div class="table">'; + + output += '<div class="table-row line-column"><div class="table-column table-heading">Linie</div>'; + data[1].forEach((departure) => output += '<div class="table-column">' + departure.line + '</div>'); + output += '</div>'; + + output += '<div class="table-row direction-column"><div class="table-column table-heading">Richtung</div>'; + data[1].forEach((departure) => output += '<div class="table-column">' + departure.direction + '</div>'); + output += '</div>'; + + output += '<div class="table-row dep-column"><div class="table-column table-heading">Abfahrt</div>'; + data[1].forEach((departure) => output += '<div class="table-column">' + (Number(departure.departure_in) ? departure.departure_in + " min" : "sofort") + '</div>'); + output += '</div>'; + + output += '</div></div>'; + }); + + output += '<div class="lastUpdated" data-last-updated="' + message.lastUpdated + '">Last Update: ' + (Math.floor(Date.now() / 1000) - message.lastUpdated) + ' seconds ago</div>'; + + return output; + ''; + } + ]; + } + ]; + } + + ]; + +}+ \ No newline at end of file
diff --git a/machines/lollo/smarthome/mqtt-webui/default.nix b/machines/lollo/smarthome/mqtt-webui/default.nix @@ -0,0 +1,38 @@ +{ inputs, lib, pkgs, ... }: + +let + + configFile = pkgs.writeTextDir "config.json" (builtins.toJSON (import ./config.nix)); + +in { + + services = { + nginx = { + enable = true; + virtualHosts."smart.home.ctu.cx" = { + enableACME = true; + forceSSL = true; + kTLS = true; + + locations = { + "/" = { + root = "${pkgs.buildEnv { + name = "mqtt-webui-env"; + paths = [ + pkgs.mqtt-webui + configFile + ./extra-css + ]; + }}/"; + }; + "/mqtt" = { + proxyPass = "http://127.0.0.1:9005"; + proxyWebsockets = true; + }; + }; + }; + }; + + }; + +}+ \ No newline at end of file
diff --git a/machines/lollo/smarthome/mqtt-webui/extra-css/extra.css b/machines/lollo/smarthome/mqtt-webui/extra-css/extra.css @@ -0,0 +1,91 @@ +section > div[data-mqtt-topic="departures2mqtt"] { + background: #444; + padding: 0; +} + +section > div[data-mqtt-topic="departures2mqtt"] * { + box-sizing: unset; +} + +section > div[data-mqtt-topic="departures2mqtt"] > .loader { + margin: 0 auto; +} + +.lastUpdated { + text-align: center; + font-style: italic; +} + +.table { + display: flex; + flex-direction: row; +} + +.table-row { + display: flex; + flex-direction: column; + width: 100%; +} + +.table-column { + padding: .5em; + height: 1em; + display: flex; + flex-direction: column; + justify-content: center; +} + +.table-column { + background-color: #ddd; +} + +.line-column { + width: 20%; +} + +.direction-column { + width: 60%; +} + +.dep-column { + width: 20%; +} + +.table { + flex-wrap: wrap; + padding: 20px; + padding-top: 10px; + background: #444; +} + +.table-column { + background: #444; + color: white; +} + +.table-column:not(.table-heading):not(.table-title) { + font-family: monospace; + font-size: 15px; + background: #222; + border-bottom: 1px solid grey; + color: #ffaa00; +} + +.table-row:last-child { + border-bottom: 0; +} + +.table-title { + padding: .85em; + padding-bottom: 0; + font-size: 1.4em; +} + +.table-heading { + border-left: 2px solid white; +} + +.table-empty { + flex-basis: 100%; + text-align: center; +}+ \ No newline at end of file