diff --git a/.babelrc b/.babelrc index 56f3104..8b51aa3 100644 --- a/.babelrc +++ b/.babelrc @@ -1,7 +1,10 @@ { "presets": [ ["@babel/preset-env", { - modules: false + modules: false, + corejs: "3.6", + useBuiltIns: "entry", + targets: "last 3 years" }], "@babel/preset-react", "@babel/preset-flow" diff --git a/.eslintrc.js b/.eslintrc.js index 78a98d6..84615c5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,7 +7,8 @@ module.exports = { "extends": [ "eslint:recommended", "plugin:flowtype/recommended", - "plugin:react/recommended" + "plugin:react/recommended", + "plugin:react-hooks/recommended" ], "parserOptions": { "ecmaFeatures": { @@ -161,6 +162,6 @@ module.exports = { "fp/no-throw": "warn", "fp/no-unused-expression": "warn", "fp/no-valueof-field": "warn", - "no-var": "warn" + "no-var": "warn" } }; diff --git a/.flowconfig b/.flowconfig index 2f7d7b8..671c6f5 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,5 +1,8 @@ [ignore] +[untyped] +.*/node_modules/react-leaflet + [include] [libs] @@ -7,9 +10,8 @@ types/types.js types/mqtt.js [options] -esproposal.export_star_as=enable module.system.node.resolve_dirname=node_modules -module.system.node.resolve_dirname=./src +module.system.node.resolve_dirname=src [lints] all=warn diff --git a/.travis.yml b/.travis.yml index 7c6000a..a688e44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,6 @@ node_js: script: - yarn travis -before_install: yarn global add greenkeeper-lockfile@1 -before_script: greenkeeper-lockfile-update -after_script: greenkeeper-lockfile-upload - after_success: - ./travis-upload-artifacts.sh diff --git a/README.md b/README.md index eca38da..9eddf0e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # MQTT Control Map [![Build Status](https://travis-ci.org/uwap/mqtt-control-map.svg?branch=master)](https://travis-ci.org/uwap/mqtt-control-map) -[![Greenkeeper badge](https://badges.greenkeeper.io/uwap/mqtt-control-map.svg)](https://greenkeeper.io/) ## Development / Configuration @@ -12,33 +11,6 @@ your the mqtt control map for the given CONFIG everytime something changes. for the given config. 4. run `yarn build CONFIG` to generate all files for production use. -## Config +## Documentation -See `config/`. - -The Config format consists out of two sections. Topics and Controls. - -### Topics - -The topics section defines the mqtt interfaces. - -### Controls - -The Controls define the UI Controls. - -| Name | Type | Optional? | Default | Description | -|-----------------|-------------------|------------|-----------------|-------------| -| type | "toggle" \| "dropDown" \| "slider" | No | | The type of the UI element. | -| text | string | No | | The text displayed right next to the UI element. | -| topic | string | No | | The topic the UI element is supposed to change and/or receive its status from. | -| enableCondition | (key: string, value: *) => boolean | Yes | () => true | This option allows you to enable or disable UI elements, depending on the current state. The first parameter is the internal representation of the value. For example "off". The second parameter is the actual value that was received via MQTT. Return true to enable the element and false to disable it. | -| **Toggle Options** | -| on | string | Yes | "on" | If the state is equal to the value of this option the toggle will be toggled on (if the toggled function is not overriden). This is also the value that will be sent when the button is toggled on. | -| off | string | Yes | "off" | If the state is equal to the value of this option the toggle will be toggled off (if the toggled function is not overriden). This is also the value that will be sent when the button is toggled off. | -| toggled | (key: string, value: *) => boolean | Yes | Use the on and off options | This is the function that decides whether the button should be in a toggled state or not. The parameters are equivalent to those of `enableCondition`. Return true to set the button to a toggled state. Return false to set it to the untoggled state. | -| **DropDown Options** | -| options | Map| Yes | {} | This is an attribute set that will map all values defined in the topics section to a description text. For example `{ on: "Lights On", off: "Lights Off" }` will give the drop down element two options, one that is named `Lights On` and one that is named `Lights Off`. | -| **Slider Options** | -| min | number | Yes | 0 | The minimum value of that slider. | -| max | number | Yes | 1 | The maximum value of that slider. | -| step | number | Yes | 1 | The smallest step of the slider. | +The documentation can be found in our [mqtt-control-map wiki](https://github.com/uwap/mqtt-control-map/wiki). diff --git a/config/entropia/index.js b/config/entropia/index.js index c3237a8..593bd97 100644 --- a/config/entropia/index.js +++ b/config/entropia/index.js @@ -1,8 +1,8 @@ // @flow import type { Config } from "config/flowtypes"; -import { hex } from "config/colors"; import * as types from "config/types"; -import { mdi } from "config/icon"; +import * as icons from "@mdi/js"; +import { svg } from "config/icon"; const config: Config = { space: { @@ -32,20 +32,19 @@ const config: Config = { hauptraumTableLight: { name: "Hauptraum Tisch", position: [450, 450], - icon: mdi("white-balance-iridescent"), - iconColor: () => hex("#000000"), + icon: svg(icons.mdiWhiteBalanceIridescent), ui: [ { type: "toggle", text: "Licht", topic: "hauptraumTableLight", - icon: mdi("power") + icon: svg(icons.mdiPower) }, { type: "toggle", text: "Licht", topic: "hauptraumTableLightOnHack", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] } diff --git a/config/rzl/index.js b/config/rzl/index.js index e688571..9c2ef78 100644 --- a/config/rzl/index.js +++ b/config/rzl/index.js @@ -2,8 +2,9 @@ import type { Config } from "config/flowtypes"; import * as types from "config/types"; import { hex, rainbow } from "config/colors"; -import { mdi, rawMdi } from "config/icon"; +import { svg, withState } from "config/icon"; import { esper, tasmota } from "./utils"; +import * as icons from "@mdi/js"; import * as onkyo from "./onkyo"; import * as olymp from "./olymp"; @@ -225,6 +226,35 @@ const config: Config = { type: types.option({ on: "ON", off: "OFF" }) }, defaultValue: "off" + }, + whirlpoolTemperatureSetpoint: { + state: { + name: "/service/whirlpool/state", + type: types.json("temperatureSetpointC") + }, + command: { + name: "/service/whirlpool/set/temperature", + type: types.string + }, + defaultValue: "0" + }, + whirlpoolBubbles: { + state: { + name: "/service/whirlpool/state", + type: types.json("bubbles") + }, + command: { + name: "/service/whirlpool/set/bubbles", + type: types.string + }, + defaultValue: "0" + }, + whirlpoolTemperature: { + state: { + name: "/service/whirlpool/state", + type: types.json("waterTemperatureC") + }, + defaultValue: "0" } }, //Tasmota-Dosen @@ -246,29 +276,28 @@ const config: Config = { ledStahltrager: { name: "LED Stahlträger", position: [340, 590], - icon: mdi("white-balance-iridescent"), - iconColor: ({ledStahltraeger}) => - (ledStahltraeger === "on" ? rainbow : hex("#000000")), + icon: svg(icons.mdiWhiteBalanceIridescent).color(({ledStahltraeger}) => + (ledStahltraeger === "on" ? rainbow : hex("#000000"))), ui: [ { type: "toggle", text: "Stahlträger LED", topic: "ledStahltraeger", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] }, snackbar: { name: "Snackbar", position: [510, 500], - icon: mdi("fridge"), - iconColor: tasmota.iconColor("snackbar", hex("#E20074")), + icon: svg(icons.mdiFridge).color( + tasmota.iconColor("snackbar", hex("#E20074"))), ui: [ { type: "toggle", text: "Snackbar", topic: "snackbar", - icon: mdi("power") + icon: svg(icons.mdiPower) }, { type: "section", @@ -278,7 +307,7 @@ const config: Config = { type: "text", text: "LED-Streifen", topic: "snackbarLedOnline", - icon: mdi("white-balance-iridescent") + icon: svg(icons.mdiWhiteBalanceIridescent) }, { type: "dropDown", @@ -297,7 +326,7 @@ const config: Config = { "11": "Rainbow Pattern", "12": "Fire Pattern" }, - icon: mdi("settings"), + icon: svg(icons.mdiCog), enableCondition: ({ snackbarLedOnline }) => snackbarLedOnline === "on" }, { @@ -306,7 +335,7 @@ const config: Config = { topic: "snackbarDimmmer", min: 0, max: 100, - icon: mdi("brightness-7"), + icon: svg(icons.mdiBrightness7), enableCondition: ({ snackbarLedOnline }) => snackbarLedOnline === "on" }, { @@ -315,7 +344,7 @@ const config: Config = { topic: "snackbarSpeed", min: 0, max: 20, - icon: mdi("speedometer"), + icon: svg(icons.mdiSpeedometer), enableCondition: ({ snackbarLedOnline }) => snackbarLedOnline === "on" } ] @@ -323,155 +352,151 @@ const config: Config = { twinkle: { name: "Twinkle", position: [530, 560], - icon: ({twinkle}) => - (twinkle === "on" ? rawMdi("led-on flip-v") : rawMdi("led-off flip-v")), - iconColor: ({twinkle}) => (twinkle === "on" ? rainbow : hex("#000000")), + icon: withState(({twinkle}) => + (twinkle === "on" ? svg(icons.mdiLedOn).flipV().color(rainbow) + : svg(icons.mdiLedOff).flipV()) + ), ui: [ { type: "toggle", text: "Twinkle", topic: "twinkle", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] }, fan: { name: "Ventilator", position: [530, 440], - icon: mdi("fan"), - iconColor: ({fan}) => (fan === "on" ? hex("#00FF00") : hex("#000000")), + icon: svg(icons.mdiFan).color(({fan}) => + (fan === "on" ? hex("#00FF00") : hex("#000000"))), ui: [ { type: "toggle", text: "Ventilator", topic: "fan", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] }, cashdesk: { name: "Cashdesk", position: [510, 467], - icon: mdi("coin"), + icon: svg(icons.mdiCurrencyUsdCircle), ui: [ { type: "link", - link: "http://cashdesk.rzl:8081/", + link: "http://cashdesk.rzl:8000/", text: "Open Cashdesk", - icon: mdi("open-in-new") + icon: svg(icons.mdiOpenInNew) } ] }, flyfry: { name: "Fliegenbratgerät", position: [450, 570], - icon: mdi("fire"), - iconColor: ({flyfry}) => - (flyfry === "on" ? hex("#6666FF") : hex("#000000")), + icon: svg(icons.mdiFire).color(({flyfry}) => + (flyfry === "on" ? hex("#6666FF") : hex("#000000"))), ui: esper.statistics("flyfry", [ { type: "toggle", text: "Fliegenbratgerät", topic: "flyfry", - icon: mdi("power") + icon: svg(icons.mdiPower) } ]) }, projector: { name: "Beamer", position: [380, 590], - icon: mdi("projector flip-v"), - iconColor: ({projector}) => + icon: svg(icons.mdiProjector).flipV().color(({projector}) => ({ transientOn: hex("#b3b300"), transientOff: hex("#b3b300"), on: hex("#00ff00"), off: hex("#000000"), unknown: hex("#888888") - })[projector], + })[projector]), ui: [ { type: "toggle", text: "Beamer", topic: "projector", toggled: (val) => val === "transientOn" || val === "on", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] }, loetarbeitsplatz4: { name: "Lötarbeitsplatz", position: [205, 455], - icon: mdi("eyedropper-variant"), - iconColor: ({loetarbeitsplatz4}) => - (loetarbeitsplatz4 === "on" ? hex("#FF0000") : hex("#000000")), + icon: svg(icons.mdiEyedropperVariant).color(({loetarbeitsplatz4}) => + (loetarbeitsplatz4 === "on" ? hex("#FF0000") : hex("#000000"))), ui: [ { type: "text", text: "Status", topic: "loetarbeitsplatz4", - icon: mdi("eyedropper-variant") + icon: svg(icons.mdiEyedropperVariant) } ] }, loetarbeitsplatz5: { name: "Lötarbeitsplatz", position: [205, 405], - icon: mdi("eyedropper-variant"), - iconColor: ({loetarbeitsplatz5}) => - (loetarbeitsplatz5 === "on" ? hex("#FF0000") : hex("#000000")), + icon: svg(icons.mdiEyedropperVariant).color(({loetarbeitsplatz4}) => + (loetarbeitsplatz4 === "on" ? hex("#FF0000") : hex("#000000"))), ui: [ { type: "text", text: "Status", topic: "loetarbeitsplatz5", - icon: mdi("eyedropper-variant") + icon: svg(icons.mdiEyedropperVariant) } ] }, door: { name: "Tür", position: [455, 350], - icon: mdi("swap-vertical"), - iconColor: ({doorStatus}) => - (doorStatus === "on" ? hex("#00FF00") : hex("#FF0000")), + icon: svg(icons.mdiSwapVertical).color(({doorStatus}) => + (doorStatus === "on" ? hex("#00FF00") : hex("#FF0000"))), ui: [ { type: "link", link: "http://s.rzl.so", text: "Open Status Page", - icon: mdi("open-in-new") + icon: svg(icons.mdiOpenInNew) }, { type: "link", // eslint-disable-next-line max-len link: "http://kunterbunt.vm.rzl/dashboard/db/allgemeines-copy-ranlvor?orgId=1", text: "RZL-Dashboard", - icon: mdi("open-in-new") + icon: svg(icons.mdiOpenInNew) }, { type: "text", text: "Anwesend", topic: "presenceStatus", - icon: mdi("account") + icon: svg(icons.mdiAccount) }, { type: "text", text: "Devices", topic: "devicesStatus", - icon: mdi("wifi") + icon: svg(icons.mdiWifi) }, { type: "toggle", text: "Deko", topic: "deko", - icon: mdi("invert-colors") + icon: svg(icons.mdiInvertColors) }, { type: "text", text: "Power Hauptraum", topic: "powerConsumption", - icon: mdi("speedometer") + icon: svg(icons.mdiSpeedometer) } ] @@ -479,56 +504,56 @@ const config: Config = { infoscreen: { name: "Infoscreen", position: [255, 495], - icon: mdi("television-guide flip-v"), - iconColor: tasmota.iconColor("infoscreen", hex("#4444FF")), + icon: svg(icons.mdiTelevisionGuide).flipV().color( + tasmota.iconColor("infoscreen", hex("#4444FF")) + ), ui: [ { type: "toggle", text: "Infoscreen", topic: "infoscreen", - icon: mdi("power") + icon: svg(icons.mdiPower) }, { type: "link", link: "http://cashdesk.rzl:3030/rzl", text: "Open Infoscreen", - icon: mdi("open-in-new") + icon: svg(icons.mdiOpenInNew) } ] }, pilze: { name: "Pilze", position: [48, 499], - icon: ({pilze}) => - (pilze === "on" ? rawMdi("led-on") : rawMdi("led-off")), - iconColor: tasmota.iconColor("pilze", rainbow), + icon: withState(({pilze}) => + (pilze === "on" ? svg(icons.mdiLedOn) : svg(icons.mdiLedOff))).color( + tasmota.iconColor("pilze", rainbow)), ui: [ { type: "toggle", text: "Pilze", topic: "pilze", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] }, printer3D: { name: "Ultimaker 3", position: [754, 560], - icon: mdi("printer-3d"), - iconColor: ({printer3DStatus}) => + icon: svg(icons.mdiPrinter3d).color(({printer3DStatus}) => ({ awaitingInteraction: hex("#b3b300"), printing: hex("#00ff00"), idle: hex("#000000"), unavailable: hex("#888888"), error: hex("#ff0000") - })[printer3DStatus], + })[printer3DStatus]), ui: [ { type: "link", link: "http://ultimaker.rzl/", text: "Open Webinterface", - icon: mdi("open-in-new") + icon: svg(icons.mdiOpenInNew) }, { type: "section", @@ -536,7 +561,7 @@ const config: Config = { }, { type: "progress", - icon: mdi("rotate-right"), + icon: svg(icons.mdiRotateRight), min: 0, max: 1, text: "Printing Progress", @@ -545,7 +570,7 @@ const config: Config = { { type: "text", text: "Time Left", - icon: mdi("clock"), + icon: svg(icons.mdiClock), topic: "printer3Dremaining" } ] @@ -553,51 +578,87 @@ const config: Config = { partkeepr: { name: "Partkeepr", position: [48, 450], - icon: mdi("chip"), + icon: svg(icons.mdiChip), ui: [ { type: "link", link: "http://partkeepr.rzl/", text: "Open Partkeepr", - icon: mdi("open-in-new") + icon: svg(icons.mdiOpenInNew) } ] }, printerAnnette: { name: "Drucker", position: [800, 350], - icon: mdi("printer"), - iconColor: tasmota.iconColor("printerAnnette"), + icon: svg(icons.mdiPrinter).color(tasmota.iconColor("printerAnnette")), ui: [ { type: "toggle", text: "Drucker", topic: "printerAnnette", - icon: mdi("power") + icon: svg(icons.mdiPower) }, { type: "link", link: "http://annette.rzl/", text: "Open Annette", - icon: mdi("open-in-new") + icon: svg(icons.mdiOpenInNew) } ] }, nebenraumPowerStatus: { name: "Strom Fablab", position: [613, 537], - icon: ({nebenraumPowerStatus}) => - (nebenraumPowerStatus === "on" ? rawMdi("flash") : rawMdi("flash-off")), - iconColor: ({nebenraumPowerStatus}) => - (nebenraumPowerStatus === "on" ? hex("#00ff00") : hex("#000000")), + icon: withState(({nebenraumPowerStatus}) => + (nebenraumPowerStatus === "on" ? + svg(icons.mdiFlash).color(hex("#00FF00")) : svg(icons.mdiFlashOff))), ui: [ { type: "text", - icon: mdi("power"), + icon: svg(icons.mdiPower), text: "Strom Fablab", topic: "nebenraumPowerStatus" } ] + }, + whirlpool: { + name: "Vorstandswhirlpool", + position: [1413, 500], + icon: svg(icons.mdiPool).color( + ({whirlpoolBubbles}) => + (parseInt(whirlpoolBubbles, 10) > 0 ? hex("#00ff00") + : hex("#000000"))), + ui: [ + { + type: "text", + icon: svg(icons.mdiOilTemperature), + text: "Temperatur", + topic: "whirlpoolTemperature" + }, + { + type: "text", + icon: svg(icons.mdiOilTemperature), + text: "Temperatur Sollwert", + topic: "whirlpoolTemperatureSetpoint" + }, + { + type: "slider", + min: 4, + max: 100, + text: "Temperatur Sollwert", + icon: svg(icons.mdiOilTemperature), + topic: "whirlpoolTemperatureSetpoint" + }, + { + type: "slider", + min: 0, + max: 9, + text: "Bubbles", + icon: svg(icons.mdiChartBubble), + topic: "whirlpoolBubbles" + } + ] } }, layers: [ diff --git a/config/rzl/kitchen.js b/config/rzl/kitchen.js index 942328d..89fd23a 100644 --- a/config/rzl/kitchen.js +++ b/config/rzl/kitchen.js @@ -1,9 +1,10 @@ // @flow import type { Topics, Controls } from "config/flowtypes"; -import { mdi, mdiBattery } from "config/icon"; +import { svg, mdiBattery } from "config/icon"; import { hex } from "config/colors"; import * as types from "config/types"; -import { floalt, tradfri } from "./utils"; +import { floalt, tradfri, tasmota } from "./utils"; +import * as icons from "@mdi/js"; export const topics: Topics = { //Kuechen-Floalts @@ -20,6 +21,8 @@ export const topics: Topics = { ...tradfri.remote.topics("65542"), ...tradfri.remote.topics("65546"), + ...tasmota.topics("10", "lichtDunstabzug"), + kitchenLightColor: { state: { name: "/service/openhab/out/kitchen_light_all_color_temperature" @@ -63,7 +66,7 @@ export const controls: Controls = { kitchenLight: { name: "Deckenlicht Küche", position: [325, 407], - icon: mdi("ceiling-light"), + icon: svg(icons.mdiCeilingLight), ui: [ { type: "toggle", @@ -72,14 +75,14 @@ export const controls: Controls = { toggled: (n) => parseInt(n, 10) > 0, topic: "kitchenLightBrightness", text: "Ein/Ausschalten", - icon: mdi("power") + icon: svg(icons.mdiPower) }, { type: "slider", min: 0, max: 100, text: "Helligkeit", - icon: mdi("brightness-7"), + icon: svg(icons.mdiBrightness7), topic: "kitchenLightBrightness" }, { @@ -87,7 +90,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Farbtemperatur", - icon: mdi("weather-sunset-down"), + icon: svg(icons.mdiWeatherSunsetDown), topic: "kitchenLightColor" }, { @@ -99,7 +102,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Helligkeit", - icon: mdi("brightness-7"), + icon: svg(icons.mdiBrightness7), topic: floalt.brightness("65537") }, { @@ -107,7 +110,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Farbtemperatur", - icon: mdi("weather-sunset-down"), + icon: svg(icons.mdiWeatherSunsetDown), topic: floalt.color("65537") }, { @@ -119,7 +122,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Helligkeit", - icon: mdi("brightness-7"), + icon: svg(icons.mdiBrightness7), topic: floalt.brightness("65538") }, { @@ -127,7 +130,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Farbtemperatur", - icon: mdi("weather-sunset-down"), + icon: svg(icons.mdiWeatherSunsetDown), topic: floalt.color("65538") }, { @@ -139,7 +142,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Helligkeit", - icon: mdi("brightness-7"), + icon: svg(icons.mdiBrightness7), topic: floalt.brightness("65539") }, { @@ -147,7 +150,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Farbtemperatur", - icon: mdi("weather-sunset-down"), + icon: svg(icons.mdiWeatherSunsetDown), topic: floalt.color("65539") }, { @@ -159,7 +162,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Helligkeit", - icon: mdi("brightness-7"), + icon: svg(icons.mdiBrightness7), topic: floalt.brightness("65540") }, { @@ -167,7 +170,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Farbtemperatur", - icon: mdi("weather-sunset-down"), + icon: svg(icons.mdiWeatherSunsetDown), topic: floalt.color("65540") } ] @@ -175,7 +178,7 @@ export const controls: Controls = { kitchenSinkLight: { name: "Licht Spüle", position: [300, 345], - icon: mdi("wall-sconce-flat"), + icon: svg(icons.mdiWallSconceFlat), ui: [ { type: "toggle", @@ -184,14 +187,14 @@ export const controls: Controls = { toggled: (n) => parseInt(n, 10) > 0, topic: "kitchenSinkLightBrightness", text: "Ein/Ausschalten", - icon: mdi("power") + icon: svg(icons.mdiPower) }, { type: "slider", min: 0, max: 100, text: "Helligkeit", - icon: mdi("brightness-7"), + icon: svg(icons.mdiBrightness7), topic: "kitchenSinkLightBrightness" } ] @@ -199,7 +202,7 @@ export const controls: Controls = { kitchenCounterLight: { name: "Deckenlicht Theke", position: [400, 440], - icon: mdi("ceiling-light"), + icon: svg(icons.mdiCeilingLight), ui: [ { type: "section", @@ -210,7 +213,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Helligkeit", - icon: mdi("brightness-7"), + icon: svg(icons.mdiBrightness7), topic: floalt.brightness("65544") }, { @@ -218,7 +221,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Farbtemperatur", - icon: mdi("weather-sunset-down"), + icon: svg(icons.mdiWeatherSunsetDown), topic: floalt.color("65544") }, { @@ -230,7 +233,7 @@ export const controls: Controls = { min: 0, max: 100, text: "Helligkeit", - icon: mdi("brightness-7"), + icon: svg(icons.mdiBrightness7), topic: floalt.brightness("65543") }, { @@ -238,15 +241,29 @@ export const controls: Controls = { min: 0, max: 100, text: "Farbtemperatur", - icon: mdi("weather-sunset-down"), + icon: svg(icons.mdiWeatherSunsetDown), topic: floalt.color("65543") } ] }, + lichtDunstabzug: { + name: "Licht Dunstabzugshaube", + position: [252, 405], + icon: svg(icons.mdiCeilingLight), + iconColor: tasmota.iconColor("lichtDunstabzug"), + ui: [ + { + type: "toggle", + text: "Licht Dunstabzugshaube", + topic: "lichtDunstabzug", + icon: svg(icons.mdiPower) + } + ] + }, remotes: { name: "Fernbedinungen", position: [400, 344], - icon: mdi("light-switch"), + icon: svg(icons.mdiLightSwitch), iconColor: (state) => //if any remote is low make icon red (["65536", "65542", "65546", "65547"] .some((x) => state[tradfri.remote.low(x)] === "true") diff --git a/config/rzl/olymp.js b/config/rzl/olymp.js index e3810bc..8b8e989 100644 --- a/config/rzl/olymp.js +++ b/config/rzl/olymp.js @@ -1,11 +1,14 @@ // @flow import type { Topics, Controls } from "config/flowtypes"; -import { mdi } from "config/icon"; +import { svg } from "config/icon"; import { hex, rainbow } from "config/colors"; import * as types from "config/types"; import { tasmota, esper } from "./utils"; +import * as icons from "@mdi/js"; export const topics: Topics = { + ...tasmota.topics("8", "ledOlymp"), + ...esper.topics("afba45", "alarm"), videogames: { state: { name: "/service/openhab/out/pca301_videogames/state", @@ -38,75 +41,70 @@ export const topics: Topics = { type: types.option({ on: "ON", off: "OFF" }) }, defaultValue: "off" - }, - ...tasmota.topics("8", "ledOlymp"), - ...esper.topics("afba45", "alarm") + } }; export const controls: Controls = { ledOlymp: { name: "LED Olymp", position: [196, 154], - icon: mdi("white-balance-iridescent rotate-45"), - iconColor: tasmota.iconColor("ledOlymp", rainbow), + icon: svg(icons.mdiWhiteBalanceIridescent).rotate(45).color( + tasmota.iconColor("ledOlymp", rainbow)), ui: [ { type: "toggle", text: "LED Olymp", topic: "ledOlymp", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] }, videogames: { name: "Videospiele", position: [100, 100], - icon: mdi("gamepad-variant"), - iconColor: ({videogames}) => - (videogames === "on" ? hex("#00FF00") : hex("#000000")), + icon: svg(icons.mdiGamepadVariant).color(({videogames}) => + (videogames === "on" ? hex("#00FF00") : hex("#000000"))), ui: [ { type: "toggle", text: "Videospiele", topic: "videogames", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] }, olympPC: { name: "Rechner", position: [297, 90], - icon: mdi("desktop-classic"), - iconColor: ({olympPC}) => - (olympPC === "on" ? hex("#00FF00") : hex("#000000")), + icon: svg(icons.mdiDesktopClassic).color(({olympPC}) => + (olympPC === "on" ? hex("#00FF00") : hex("#000000"))), ui: [ { type: "toggle", text: "Rechner", topic: "olympPC", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] }, rundumleuchte: { name: "Rundumleuchte", position: [310, 275], - icon: mdi("alarm-light"), - iconColor: ({rundumleuchte}) => - (rundumleuchte === "on" ? hex("#F0DF10") : hex("#000000")), + icon: svg(icons.mdiAlarmLight).color(({rundumleuchte}) => + (rundumleuchte === "on" ? hex("#F0DF10") : hex("#000000"))), ui: [ { type: "toggle", text: "Rundumleuchte", topic: "rundumleuchte", - icon: mdi("power") + icon: svg(icons.mdiPower) } ] }, alarm: { name: "Alarm", position: [340, 250], - icon: mdi("alarm-bell"), + icon: svg(icons.mdiAlarmBell), iconColor: () => hex("#000000"), ui: esper.statistics("alarm") } diff --git a/config/rzl/onkyo.js b/config/rzl/onkyo.js index edc8733..d1bca0a 100644 --- a/config/rzl/onkyo.js +++ b/config/rzl/onkyo.js @@ -1,8 +1,9 @@ // @flow import type { Topics, Controls } from "config/flowtypes"; -import { mdi } from "config/icon"; +import { svg } from "config/icon"; import { hex } from "config/colors"; import * as types from "config/types"; +import * as icons from "@mdi/js"; export const topics: Topics = { onkyoConnection: { @@ -94,6 +95,10 @@ export const topics: Topics = { NPR08: "somafmLush", NPR09: "somafmBeatblender", NPR0a: "ponyville", + NPR0b: "deutschlandradio", + NPR0c: "somafmSuburbsOfGoa", + NPR0d: "somafmSonicUniverse", + NPR0e: "somafmChrismasLounge", otherwise: "unknown" }) }, @@ -110,6 +115,10 @@ export const topics: Topics = { somafmLush: "NPR08", somafmBeatblender: "NPR09", ponyville: "NPR0a", + deutschlandradio: "NPR0b", + somafmSuburbsOfGoa: "NPR0c", + somafmSonicUniverse: "NPR0d", + somafmChrismasLounge: "NPR0e", otherwise: "NPR00" }) }, @@ -124,12 +133,12 @@ export const controls: Controls = { iconColor: ({onkyoConnection, onkyoPower}) => (onkyoConnection !== "connected" ? hex("#888888") : (onkyoPower === "on" ? hex("#00FF00") : hex("#000000"))), - icon: mdi("audio-video"), + icon: svg(icons.mdiAudioVideo), ui: [ { type: "toggle", text: "Power", - icon: mdi("power"), + icon: svg(icons.mdiPower), topic: "onkyoPower", enableCondition: (state) => state.onkyoConnection === "connected" }, @@ -143,14 +152,14 @@ export const controls: Controls = { topic: "onkyoVolume", min: 0, max: 50, - icon: mdi("volume-high"), + icon: svg(icons.mdiVolumeHigh), enableCondition: (state) => state.onkyoConnection === "connected" }, { type: "toggle", text: "Mute", topic: "onkyoMute", - icon: mdi("volume-off"), + icon: svg(icons.mdiVolumeOff), enableCondition: (state) => state.onkyoConnection === "connected" }, { @@ -168,7 +177,7 @@ export const controls: Controls = { pult: "Pult", front: "Front HDMI" }, - icon: mdi("usb"), + icon: svg(icons.mdiUsb), enableCondition: (state) => state.onkyoConnection === "connected" }, { @@ -186,9 +195,13 @@ export const controls: Controls = { somafmLush: "Lush (SomaFM)", somafmBeatblender: "Beat Blender (Soma FM)", ponyville: "Ponyville FM", + deutschlandradio: "Deutschlandradio", + somafmSuburbsOfGoa: "Suburbs of Goa (SomaFM)", + somafmSonicUniverse: "Sonic Universe (SomaFM)", + somafmChrismasLounge: "Christmas Lounge (SomaFM)", unknown: "Unknown" }, - icon: mdi("radio"), + icon: svg(icons.mdiRadio), enableCondition: (state) => state.onkyoConnection === "connected" && state.onkyoInputs === "netzwerk" }, @@ -200,7 +213,7 @@ export const controls: Controls = { type: "link", link: "http://mpd.rzl/mpd/player/index.php", text: "Open MPD Interface", - icon: mdi("open-in-new") + icon: svg(icons.mdiOpenInNew) } ] } diff --git a/config/rzl/utils.js b/config/rzl/utils.js index f1b5c60..5daf02f 100644 --- a/config/rzl/utils.js +++ b/config/rzl/utils.js @@ -1,11 +1,12 @@ // @flow -import type { ControlUI } from "config/flowtypes"; -import { mdi } from "config/icon"; -import { hex } from "config/colors"; +import type { ControlUI, Topics } from "config/flowtypes"; +import { svg } from "config/icon"; +import { hex, type Color } from "config/colors"; import * as types from "config/types"; +import * as icons from "@mdi/js"; export const tasmota = { - topics: (id: string, name: string) => ({ + topics: (id: string, name: string): Topics => ({ [name]: { state: { name: `stat/sonoff${id}/POWER`, @@ -26,20 +27,20 @@ export const tasmota = { defaultValue: "off" } }), - iconColor: (name: string, onColor: Color = hex("#00FF00")) => - (state: State) => { + iconColor: (name: string, onCol: Color = hex("#00FF00")): (State => Color) => + (state: State): Color => { if (state[`${name}_online`] === "off") { return hex("#888888"); } else if (state[name] === "on") { - return onColor; + return onCol; } return hex("#000000"); } }; export const floalt = { - color: (lightId: string) => `floalt_${lightId}_color`, - brightness: (lightId: string) => `floalt_${lightId}_brightness`, - topics: (lightId: string) => ({ + color: (lightId: string): string => `floalt_${lightId}_color`, + brightness: (lightId: string): string => `floalt_${lightId}_brightness`, + topics: (lightId: string): Topics => ({ [`floalt_${lightId}_color`]: { state: { name: `/service/openhab/out/tradfri_0220_gwb8d7af2b448f_${lightId}` + @@ -70,9 +71,9 @@ export const floalt = { }; const tradfriRemote = { - level: (remoteId: string) => `tradfri_remote_${remoteId}_level`, - low: (remoteId: string) => `tradfri_remote_${remoteId}_low`, - topics: (remoteId: string) => ({ + level: (remoteId: string): string => `tradfri_remote_${remoteId}_level`, + low: (remoteId: string): string => `tradfri_remote_${remoteId}_low`, + topics: (remoteId: string): Topics => ({ [`tradfri_remote_${remoteId}_level`]: { state: { name: `/service/openhab/out/tradfri_0830_gwb8d7af2b448f_${remoteId}` + @@ -106,36 +107,36 @@ const esperStatistics = (name: string, { type: "text", text: "Device Variant", - icon: mdi("chart-donut"), + icon: svg(icons.mdiChartDonut), topic: `esper_${name}_device` }, { type: "text", text: "Version", - icon: mdi("source-branch"), + icon: svg(icons.mdiSourceBranch), topic: `esper_${name}_version` }, { type: "text", text: "IP", - icon: mdi("access-point-network"), + icon: svg(icons.mdiAccessPointNetwork), topic: `esper_${name}_ip` }, { type: "text", text: "RSSI", - icon: mdi("wifi"), + icon: svg(icons.mdiWifi), topic: `esper_${name}_rssi` }, { type: "text", text: "Running since…", - icon: mdi("av-timer"), + icon: svg(icons.mdiAvTimer), topic: `esper_${name}_uptime` } ]) ); -const esperTopics = (chipId: string, name: string) => ({ +const esperTopics = (chipId: string, name: string): Topics => ({ [`esper_${name}_version`]: { state: { name: `/service/esper/${chipId}/info`, diff --git a/config/uwap-home/assets/layers/rooms.svg b/config/uwap-home/assets/layers/rooms.svg new file mode 100644 index 0000000..693b62c --- /dev/null +++ b/config/uwap-home/assets/layers/rooms.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/config/uwap-home/assets/raw/uwap-home.draw.io b/config/uwap-home/assets/raw/uwap-home.draw.io new file mode 100644 index 0000000..c304779 --- /dev/null +++ b/config/uwap-home/assets/raw/uwap-home.draw.io @@ -0,0 +1 @@ +3Vxbk9o2GP01POLRxZLtx3Q3bR7SmbQ7TdqnjBcL8MQgxpjA9tdXBgksW7BiY9lKkxkWC+HLOZ++u5jgh9XhtzLdLH/nGSsmCGSHCX6cIARDGIs/9cjLaSQG5DSwKPNMTroMPOX/MjkI5Oguz9hWm1hxXlT5Rh+c8fWazSptLC1LvtenzXmhX3WTLlhn4GmWFt3RL3lWLeVTEHAZ/8DyxVJdGQL5ySpVk+XAdplmfN8Ywu8n+KHkvDq9Wx0eWFGDp3A5fe/XK5+eb6xk68rmCx/4u8/Tp93u4Us6e2EIPqZ//DXFp7N8T4udfGB5s9WLQmC/zCv2tEln9fFesDzBvyyrVSGOoHibbjcn3Of5gYlL/SLPyMqKHa7eKjwDICSH8RWryhcxRX5BQSZlBith2F8YgIqBZRN9NZhK1hfnU1+AEW8kNnfgFP4MOIV0dJyi13Eq+W6d1Qg8AoHCK6j1ANJZepQ0JV2UsAmlELhCKfEPJUR1lGIDSMgA0hnd3kFSJ/YKJR0k1AVJSE0XJJQ4Awn6B1IY6ygRYFBL8bAwIf9gaq84k/Y2qSXkDCQLZ2DkBUcNnsBZajQLFztDibyOUv3IufAhP6bPrPjEt3mV87X46JlXFV/pIKm574p8Uc+peI1iKo9mAidWigHhPW7qs68Oi9rRDuYF5+WmSNfBs3A0q92zmFPyKpVXmiagHwKmyGIxm8Q0ckYA9YyAbb7+9rUneYcqPLpTKYTO0Lbw6MZBuyHt9Wp3Az8iVvBj4gr+2DP4K54XrHKkbHDLU4/t0KeRI/SRhapZCJO4uQsPmapIn9UpwN044Ug3i6FBKSuZbCKVUFdyahHSDCqns2WayxmnrAt4s8zeEI3rioQGIQjJ5T/WCEsC2vVjIpNeCSiJYIhi+epK0C1irfHpy/JSrJvTVfds+3Yt9BZGIxLEGqN+E2oRF/pF6JqXNXADMhqiAJAmoy3bj4M4bnLVIViQCbsUoyi40Fu/OrNOFkGtXxxv+c4vjqckCkjS/Bd6x7JFVD4+y95Y1imGAWkyo1wlXxSzRSVhfDo9trTeE2yRH1JslMeK0FgrKQooSq6rRkQC3MSrCzMEoTiFIcygcUAMX+0fa9+SE88s+5rx3TGycxIhI50kQ5UGUlM61Fk2DluYpzFCZEI1oISkd5FStzpI2hJbeGtugLrB2/W0Lw4iguOQQvka6sqh6yYNiybyLS/G5/N8xr66do5OUnQjgRYADCKaIPmqsQa7awAL/9dAs5Zaxq44tMgZDWQpX4EVxkEDUxDp2TdTdwrAATIQ0cTVWQ8G9i2Zs+flt63iSudwXuSbz/I6w/FJYHCW+OOrxqehqh/SINH4NCwTZ3RaRAwGw+HYvkKgL4LIgBrEQ9YFsYXj7QKnG6R10RsPHd+qppYG8wf1ALkpxKFeb4LddW8qtbqyh9gimrG0h05Ra/XTmRY+NODmzt755gveb++c8oX1mBF3+Rq0owD7VirMSrbdHuc0PZDeegraPYORpaF0RkDom39oJKC3jIlSDyoTgA2ZADCkng99q81l4s2fpwd30qYOWw2hFHQpiEzOUOiKAYtcjD8MOFgUSaIRQnA3nxMZil6Rq/aR0LciV03IRzYfio+zAlI2Gnc7n4ZdIb51uc74bvb20vErJkK1hysTbXJpiWE9hLEr+H2L1mQI7EL2cSuTgMKu7JtavN3t9fJyE5Oeo4K0244CATKIaOIKJYuo66dBCTozbBbBzhgbB3WcTBssjebG1ZIjFiEJW2fv6g29k7pXbF0royzdLo/SBXXE2CGv/q6FLohRLI//OR4DqD5/PEipPB68NA4+sTIXT1UrxEZczrLOPuEW2OJm+a6csRuPCa8U3V4JxdVYyQqhfL/r92HiQl7hE8/FHV4LRGPaYvN0+/JLF0K752lvXGrvtanScsGqzomOknF+6h8QFh/jp2vOYh+LNdKLr4aiP6VDLlYfo6cBnXWStJz1xOCsm6InVy1HxMfoySac7c2D72w5RYaAdsj4ifTtQ7qQY6gnhqnB8TZWQJx53sS35shLZrIPvFt207iV0pSJpM4Uh29R/nFzH3Ij7rRVtyLEbndZ6Ax93+pWZbpesPbeyt7gB3rOkVJkBT9xlYYnvtWhSjYv8wUr04q3aiG91aLCWO8Iiwy7Wkz9o4qr3kmgFoHf/9mXPIv82ZfsNiFQgy/pzCTQnym4crIFPG6nIw3hVtxlJHKlp6iFd39HbmQt7qlOjsAAQKwG6uzIFAQAnEcu+ZHj0Uvz6O0ZklNm4MbDQmAmZ5gMCWmFEaph9t4MSdTKtLR/I8lxgoRauNFvkxhhw1sSA0cWmGRcedHNafuneUaXF3F4+YHB0/TLzzTi9/8B \ No newline at end of file diff --git a/config/uwap-home/index.js b/config/uwap-home/index.js new file mode 100644 index 0000000..4a283a0 --- /dev/null +++ b/config/uwap-home/index.js @@ -0,0 +1,1673 @@ +// @flow +import type { Config } from "config/flowtypes"; +import * as types from "config/types"; +import { svg, withState } from "config/icon"; +import { hex } from "config/colors"; +import * as icons from "@mdi/js"; +//import { Buffer } from "bl"; + +const topicBulbHomeRust = (bulb: string, argument: string) => ({ + [`${bulb}${argument}`]: { + state: { + name: `home-rust/bulb/${bulb}/${argument}`, + type: types.string + }, + command: { + name: `home-rust/bulb/${bulb}/${argument}/set`, + type: types.string + }, + defaultValue: "0" + } +}); + +const topicZigbeeState = (topic: string, bulb: string) => ({ + [`${topic}State`]: { + state: { + name: `zigbee2mqtt/${bulb}/state`, + type: types.option({ + OFF: "off", + ON: "on" + }) + }, + command: { + name: `zigbee2mqtt/${bulb}/set/state`, + type: types.string + }, + defaultValue: "OFF" + } +}); +const topicBulbState = (bulb: string) => topicZigbeeState(bulb, `bulb_${bulb}`); +const topicGroupState = (bulb: string) => topicZigbeeState(bulb, `group_${bulb}`); + +const topicZigbeeNumber = (topic: string, bulb: string, parameter: string) => ({ + [`${topic}${parameter}`]: { + state: { + name: `zigbee2mqtt/${bulb}/${parameter}`, + type: types.string + }, + command: { + name: `zigbee2mqtt/${bulb}/set/${parameter}`, + type: types.string + }, + defaultValue: "0" + } +}); +const topicBulbNumber = (bulb: string, parameter: string) => topicZigbeeNumber(bulb, `bulb_${bulb}`, parameter); +const topicGroupNumber = (bulb: string, parameter: string) => topicZigbeeNumber(bulb, `group_${bulb}`, parameter); + +const topicHomeBoolean = (name: string, topic: string, + defaultValue: boolean = false) => topicBoolean(name, `home-rust/${topic}`, defaultValue); + +const topicBoolean = (name: string, topic: string, + defaultValue: boolean = false) => ({ + [`${name}`]: { + state: { + name: `${topic}`, + type: types.option({ true: "on", false: "off" }) + }, + command: { + name: `${topic}/set`, + type: types.option({ on: "true", off: "false" }) + }, + defaultValue: defaultValue ? "on" : "off" + } +}); + +const topicHomeNumber = (name: string, topic: string, + defaultValue: number = 0) => ({ + [`${name}`]: { + state: { + name: `home-rust/${topic}`, + type: types.string + }, + command: { + name: `home-rust/${topic}/set`, + type: types.string + }, + defaultValue: defaultValue + } +}); + +const topicTasmota = (name: string, topic: string) => ({ + [`${name}State`]: { + state: { + name: `stat/${topic}/POWER`, + type: types.option({ + OFF: "off", + ON: "on" + }) + }, + command: { + name: `cmnd/${topic}/POWER`, + type: types.string + }, + defaultValue: "off" + } +}); + +const topicTasmotaPower = (name: string, topic: string) => ({ + [`${name}Power`]: { + state: { + name: `tele/${topic}/SENSOR`, + type: types.json("ENERGY.Power") + }, + defaultValue: "no Data" + } +}); + +const topicHeating = (name: string) => ({ + [`heater${name}Tsoll`]: { + state: { + name: `tele/home-rust/fritzbox/device/${name}`, + type: (msg) => { + const json = JSON.parse(msg.toString()); + if (!json || !json["tsoll"]) { + return "126.5"; + } else { + const tsoll = json["tsoll"] / 2; + if (!json["offset"] || tsoll > 50) { + return tsoll.toString(); + } else { + return (tsoll - json["offset"]/10).toString(); + } + } + } + }, + command: { + name: `home-rust/fritzbox/device/${name}/tsoll/set`, + //TODO: add offset before writing out new value + type: (msg) => (Buffer.from((parseFloat(msg) * 2).toString())) + }, + defaultValue: "126.5" + }, + [`heater${name}WindowEnd`]: { + state: { + name: `tele/home-rust/fritzbox/device/${name}`, + type: (msg) => { + const json = JSON.parse(msg.toString()); + if (!json || !json["windowopenactiveendtime"]) { + return "inactive"; + } else { + return new Date(json["windowopenactiveendtime"] * 1000).toLocaleTimeString(); + } + } + }, + defaultValue: "unavailable" + }, + [`heater${name}BoostEnd`]: { + state: { + name: `tele/home-rust/fritzbox/device/${name}`, + type: (msg) => { + const json = JSON.parse(msg.toString()); + if (!json || !json["boostactiveendtime"]) { + return "inactive"; + } else { + return new Date(json["boostactiveendtime"] * 1000).toLocaleTimeString(); + } + } + }, + defaultValue: "unavailable" + } +}); + + +const sliderRGB = (bulb: string, argument: string) => ( + [{ + type: "slider", + min: 0, + max: 255, + text: argument, + icon: svg(icons.mdiBrightness7), + topic: `${bulb}${argument}` + }] +); +const sliderH = (bulb: string, argument: string) => ( + [{ + type: "slider", + min: 0, + max: 360, + text: argument, + icon: svg(icons.mdiBrightness7), + topic: `${bulb}${argument}` + }] +); +const sliderSVXY = (bulb: string, argument: string) => ( + [{ + type: "slider", + min: 0, + max: 1, + step: 0.01, + text: argument, + icon: svg(icons.mdiBrightness7), + topic: `${bulb}${argument}` + }] +); +const radiatorUI = (name: string) => ([ + { + type: "toggle", + topic: `heater${name}Tsoll`, + text: "Volle Power", + icon: svg(icons.mdiRadiator), + on: "127", + off: "22" + }, + { + type: "toggle", + topic: `heater${name}Tsoll`, + text: "Ausschalten", + icon: svg(icons.mdiRadiatorDisabled), + on: "126.5", + off: "22" + }, + { + type: "slider", + min: 8, + max: 33, + step: 0.5, + text: "Zieltemperatur", + icon: svg(icons.mdiOilTemperature), + topic: `heater${name}Tsoll`, + marks: [ + { value: 3, label: "3°C" }, + { value: 22, label: "22°C" }, + { value: 33, label: "33°C" } + ] + }, + { + type: "text", + text: "Window open mode till", + icon: svg(icons.mdiSortClockAscending), + topic: `heater${name}WindowEnd`, + }, + { + type: "text", + text: "Boost mode till", + icon: svg(icons.mdiSortClockDescending), + topic: `heater${name}BoostEnd`, + } +]); + +const config: Config = { + space: { + name: "Home", + color: "teal", + mqtt: "ws://192.168.0.12:1884" + }, + topics: [ + { + ...topicBulbHomeRust("livingroom", "r"), + ...topicBulbHomeRust("livingroom", "g"), + ...topicBulbHomeRust("livingroom", "b"), + ...topicBulbHomeRust("livingroom", "h"), + ...topicBulbHomeRust("livingroom", "s"), + ...topicBulbHomeRust("livingroom", "v"), + ...topicBulbHomeRust("livingroom", "x"), + ...topicBulbHomeRust("livingroom", "y"), + ...topicBulbHomeRust("livingroom", "animation-speed"), + ...topicBulbHomeRust("livingroom", "mode"), + ...topicBulbNumber("livingroom", "brightness"), + ...topicBulbState("livingroom"), + + ...topicBulbHomeRust("ledstrip_livingroom", "r"), + ...topicBulbHomeRust("ledstrip_livingroom", "g"), + ...topicBulbHomeRust("ledstrip_livingroom", "b"), + ...topicBulbHomeRust("ledstrip_livingroom", "h"), + ...topicBulbHomeRust("ledstrip_livingroom", "s"), + ...topicBulbHomeRust("ledstrip_livingroom", "v"), + ...topicBulbHomeRust("ledstrip_livingroom", "x"), + ...topicBulbHomeRust("ledstrip_livingroom", "y"), + ...topicBulbNumber("ledstrip_livingroom", "brightness"), + ...topicBulbState("ledstrip_livingroom"), + + ...topicBulbHomeRust("office", "r"), + ...topicBulbHomeRust("office", "g"), + ...topicBulbHomeRust("office", "b"), + ...topicBulbHomeRust("office", "h"), + ...topicBulbHomeRust("office", "s"), + ...topicBulbHomeRust("office", "v"), + ...topicBulbHomeRust("office", "x"), + ...topicBulbHomeRust("office", "y"), + ...topicBulbNumber("office", "brightness"), + ...topicBulbNumber("office", "color_temp"), + ...topicBulbState("office"), + + + ...topicBulbNumber("bedroom", "brightness"), + ...topicBulbNumber("bedroom", "color_temp"), + ...topicBulbState("bedroom"), + + ...topicGroupNumber("kitchen", "brightness"), + ...topicGroupNumber("kitchen", "color_temp"), + ...topicGroupState("kitchen"), + + ...topicBulbNumber("office_window", "brightness"), + ...topicBulbNumber("office_window", "color_temp"), + ...topicBulbState("office_window"), + + ...topicBulbNumber("3d_printer", "brightness"), + ...topicBulbState("3d_printer"), + + ...topicBulbNumber("hallway", "brightness"), + ...topicBulbState("hallway"), + + ...topicBulbNumber("hallway2", "brightness"), + ...topicBulbState("hallway2"), + + ...topicBulbNumber("ledstrip_storeroom", "brightness"), + ...topicBulbState("ledstrip_storeroom"), + + ...topicBulbNumber("diningroom", "brightness"), + ...topicBulbState("diningroom"), + + ...topicZigbeeNumber("officeBlindLeft", "blinds_office_left", "position"), + ...topicZigbeeNumber("officeBlindRight", "blinds_office_right", "position"), + ...topicHomeBoolean("officeBlindsVirtualZero", "office-blinds-virtual-zero-position", + true), + + ...topicTasmota("speakerOffice", "sonoff-office-speaker"), + ...topicHomeBoolean("officeSwitchPollingActive", "switch/office/polling", + true), + + ...topicBoolean("wledOfficeAuto", "wled/office/automatic"), + + ...topicTasmota("fanBedroom", "sonoff-bedroom-fan"), + ...topicTasmota("fanOffice", "sonoff-office-fan"), + ...topicTasmota("tasmotaProjector", "tasmota-projector"), + ...topicTasmota("tasmotaDishwasher", "tasmota-dishwasher"), + ...topicTasmotaPower("tasmotaDishwasher", "tasmota-dishwasher"), + ...topicHomeBoolean("fanBedroomAuto", "temperature-control/bedroom"), + ...topicHomeBoolean("fanOfficeAuto", "temperature-control/office"), + ...topicHomeNumber("fanBedroomTarget", + "temperature-control/bedroom/target", 21.5), + ...topicHomeNumber("fanOfficeTarget", + "temperature-control/office/target", 21.5), + + ...topicHomeNumber("heaterOfficeTarget", + "temperature-control/office_heating/target", 21.5), + ...topicHomeBoolean("heaterOfficeAuto", + "temperature-control/office_heating"), + ...topicHomeBoolean("heaterOfficeSelfControl", + "temperature-control/office_heating/self_control"), + + ...topicHomeNumber("heaterLivingroomTarget", + "temperature-control/livingroom_heating/target", 21.5), + ...topicHomeBoolean("heaterLivingroomAuto", + "temperature-control/livingroom_heating"), + + ...topicHomeBoolean("livingroomKodiControlled", + "bulb/livingroom/kodi-controlled"), + ...topicHomeBoolean("projectorControlsSoundbar", + "projector-controls-soundbar", true), + ...topicHomeBoolean("bedroomWakeup", "wakeup"), + ...topicHomeBoolean("lueftenHint", "lueften"), + ...topicHomeBoolean("windowOpenWarning", "window_open_warning", true), + ...topicHomeBoolean("printerLight", + "bulb/bulb_3d_printer/auto"), + ...topicHomeNumber("temperatureWarningKitchen", + "temperature-warning/kitchen/setpoint", 15.0), + ...topicHomeNumber("temperatureWarningOffice", + "temperature-warning/office/setpoint", 15.0), + ...topicHomeNumber("temperatureWarningBedroom", + "temperature-warning/bedroom/setpoint", 15.0), + + ...topicZigbeeState("powerFountain", "power_fountain"), + + temperatureKitchen: { + state: { + name: "tele/sonoff-kittchen/SENSOR", + type: types.json("DS18B20-5674FF.Temperature") + //type: types.json("$..[?(@.Address==\"28FF7456B5013CBB\")].Temperature") + }, + defaultValue: "0" + }, + nasPower: { + state: { + name: "nas/online", + type: types.string + }, + command: { + name: "home-rust/wake/nas", + type: types.string + }, + defaultValue: "OFF" + }, + + ...topicHeating("diningroom"), + ...topicHeating("bedroom"), + ...topicHeating("office"), + + heaterBedroomSummermode: { + state: { + name: "tele/home-rust/fritzbox/device/bedroom", + type: types.json("summeractive") + }, + defaultValue: "1" + }, + heaterOfficeNachtabsenkung: { + state: { + name: "home-rust/temperature-control/office_heating/heat_request/4", + type: types.option({ true: "off", false: "on" }) + }, + command: { + name: "home-rust/temperature-control/office_heating/heat_request/4", + type: types.option({ off: "true", on: "false" }) + }, + defaultValue: "on" + }, + tucanaPower: { + state: { + name: "home-rust/switch/office/8", + type: types.option({ + "0": "Link Down", + "6": "1000M", + "5": "100M", + "4": "100M (Half Duplex)", + "3": "10M", + "2": "10M (Half Duplex)" + }) + }, + command: { + name: "home-rust/wake/tucana", + type: types.string + }, + defaultValue: "0" + }, + tasmotaProjectorAutoOff: { + state: { + name: "tele/tasmota-projector/auto-off", + type: types.option({ OFF: "off", ON: "on" }) + }, + command: { + name: "cmnd/tasmota-projector/backlog", + type: types.option({ + off: "Rule2 off; RuleTimer1 0; Publish2 tele/tasmota-projector/auto-off OFF;", + on: "Rule2 on; Publish2 tele/tasmota-projector/auto-off ON;" + }) + }, + defaultValue: "on" + }, + printer3DProgresss: { + state: { + name: "tele/octoPrint/progress/printing", + type: (msg) => JSON.parse(msg.toString()).printer_data.progress.completion || "0" + }, + defaultValue: "0" + }, + printer3Dremaining: { + state: { + name: "tele/octoPrint/progress/printing", + type: (msg) => { + const json = JSON.parse(msg.toString()); + if (!json) { + return "unavailable"; + } + const secondsLeft = json.printer_data.progress.printTimeLeft; + return new Date(secondsLeft * 1000).toISOString().substr(11, 8); + } + }, + defaultValue: "unavailable" + }, + kodi: { + state: { + name: "kodi/connected", + type: types.string + }, + command: { + name: "kodi/command/shutdown", + type: types.string + }, + defaultValue: "0" + }, + wled_livingroom_brightness: { + state: { + name: "wled/livingroom/g", + type: types.string + }, + command: { + name: "wled/livingroom", + type: types.string + }, + defaultValue: "0" + }, + wled_office_brightness: { + state: { + name: "wled/office/g", + type: types.string + }, + command: { + name: "wled/office", + type: types.string + }, + defaultValue: "0" + }, + wled_office_lwt: { + state: { + name: "home-rust/led-strip-office/lwt", + type: types.string + }, + defaultValue: "unknown" + }, + twitch_status_uwap: { + state: { + name: "tele/twitch/uwap", + type: (msg) => { return msg.toString().endsWith("off") ? "off" : "on"; } + }, + defaultValue: "off" + }, + tadpole_freshness: { + state: { + name: "stat/tadpole/freshness", + type: types.string + }, + defaultValue: "unkown" + }, + tadpole_mic: { + state: { + name: "stat/tadpole/mic", + type: types.string + }, + defaultValue: "unkown" + }, + tadpole_webcam: { + state: { + name: "stat/tadpole/webcam", + type: types.string + }, + defaultValue: "unkown" + }, + livingroomSoundbarPower: { + state: { + name: "media/cec/5", + type: types.option({ off: "off", on: "on" }) + }, + command: { + name: "media/cec/5/cmd", + type: types.option({ off: "off", on: "on" }) + }, + defaultValue: "off" + }, + livingroomSoundbarHDMI: { + command: { + name: "media/cec/tx", + type: types.option({ off: "1f:82:11:00", on: "1f:82:11:00" }) + }, + defaultValue: "off" + }, + livingroomSoundbarVolume: { + state: { + name: "media/cec/volume", + type: types.string + }, + command: { + name: "media/cec/volume/set", + type: types.string + }, + defaultValue: "0" + } + } + ], + controls: { + bedroomLight: { + name: "Schlafzimmer", + position: [180, 130], + icon: svg(icons.mdiCeilingLight).color(({bedroomState}) => + (bedroomState === "on" ? hex("#00FF00") : hex("#000000"))), + ui: [ + { + type: "toggle", + topic: "bedroomState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "bedroombrightness" + }, + { + type: "toggle", + topic: "bedroomWakeup", + text: "Lichtwecker", + icon: svg(icons.mdiWeatherSunsetUp) + }, + { + type: "slider", + min: 250, + max: 454, + text: "Farbtemperatur", + icon: svg(icons.mdiWeatherSunsetDown), + topic: "bedroomcolor_temp" + } + ] + }, + bedroomFan: { + name: "Lüftung/Heizung Schlafzimmer", + position: [140, 25], + icon: withState((s) => ( + s["heaterBedroomSummermode"] === "1" ? + + //Sommermodus => Lüftungsstatus anzeigen + svg(icons.mdiFan).color(({fanBedroomState}) => + (fanBedroomState === "on" ? hex("#00FF00") : hex("#000000"))) + + //Wintermodus => Heizungsstatus anzeigen + : s["heaterbedroomTsoll"] === "126.5" ? + //Solltemperatur == aus + svg(icons.mdiRadiatorDisabled) + //Normalbetrieb + : svg(icons.mdiRadiator) + )), + ui: [ + { + type: "section", + text: "Lüftung" + }, + { + type: "toggle", + topic: "fanBedroomState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "toggle", + topic: "fanBedroomAuto", + text: "Automatik", + icon: svg(icons.mdiAirConditioner) + }, + { + type: "slider", + min: 15, + max: 25, + step: 0.1, + text: "Zieltemperatur", + icon: svg(icons.mdiOilTemperature), + topic: "fanBedroomTarget", + marks: [ + { value: 15, label: "15°C" }, + { value: 20, label: "20°C" }, + { value: 25, label: "25°C" } + ] + }, + { + type: "section", + text: "Heizung" + }].concat(radiatorUI("bedroom")) + }, + officeSpeaker: { + name: "Lautsprecher", + position: [245, 658], + icon: withState(({speakerOfficeState}) => + (speakerOfficeState !== "on" ? svg(icons.mdiVolumeOff) + : svg(icons.mdiVolumeHigh).color(hex("#00FF00"))) + ), + ui: [ + { + type: "toggle", + topic: "speakerOfficeState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + } + ] + }, + officeBlinds: { + name: "Jalousien Büro", + position: [170,658], + icon: withState((s) => ( + (parseInt(s["officeBlindLeftposition"]) <= 9 && + parseInt(s["officeBlindRightposition"]) <= 9) ? + svg(icons.mdiBlindsOpen) : + svg(icons.mdiBlinds) + )), + ui: [ + { + type: "slider", + min: 0, + max: 100, + step: 1, + text: "Links", + icon: svg(icons.mdiArrowUpDown), + topic: "officeBlindLeftposition", + marks: [ + { value: 100, label: "Unten" }, + { value: 0, label: "Oben" }, + { value: 61, label: "Meow" } + ] + }, + { + type: "slider", + min: 0, + max: 100, + step: 1, + text: "Rechts", + icon: svg(icons.mdiArrowUpDown), + topic: "officeBlindRightposition", + marks: [ + { value: 100, label: "Unten" }, + { value: 0, label: "Oben" }, + { value: 69, label: "Meow" } + ] + }, + { + type: "toggle", + topic: "officeBlindsVirtualZero", + text: "Hide Holes Mode", + icon: svg(icons.mdiBlindsOpen) + } + ], + }, + officeFan: { + name: "Lüftung/Heizung Büro", + position: [140, 658], + icon: withState(({heaterOfficeAuto}) => ( + + heaterOfficeAuto === "on" ? + + svg(icons.mdiRadiator).color(({heaterofficeTsoll}) => + (heaterofficeTsoll === "127" ? hex("#FF0000") : hex("#000000"))) + + : svg(icons.mdiFan).color(({fanOfficeState}) => + (fanOfficeState === "on" ? hex("#00FF00") : hex("#000000"))) + )), + ui: [ + { + type: "section", + text: "Lüftung" + }, + { + type: "toggle", + topic: "fanOfficeState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "toggle", + topic: "fanOfficeAuto", + text: "Automatik", + icon: svg(icons.mdiAirConditioner) + }, + { + type: "slider", + min: 15, + max: 25, + step: 0.1, + text: "Zieltemperatur", + icon: svg(icons.mdiOilTemperature), + topic: "fanOfficeTarget", + marks: [ + { value: 15, label: "15°C" }, + { value: 20, label: "20°C" }, + { value: 25, label: "25°C" } + ] + }, + { + type: "section", + text: "Heizung (Automatik)" + }, + { + type: "toggle", + topic: "heaterOfficeAuto", + text: "Automatik", + icon: svg(icons.mdiRadiator) + }, + { + type: "toggle", + topic: "heaterOfficeNachtabsenkung", + text: "Nachtabsekung", + icon: svg(icons.mdiWeatherNight) + }, + { + type: "toggle", + topic: "heaterOfficeSelfControl", + text: "Run control on valve", + icon: svg(icons.mdiRadiator) + }, + { + type: "slider", + min: 15, + max: 25, + step: 0.1, + text: "Zieltemperatur", + icon: svg(icons.mdiOilTemperature), + topic: "heaterOfficeTarget", + marks: [ + { value: 15, label: "15°C" }, + { value: 21.5, label: "21.5°C" }, + { value: 25, label: "25°C" } + ] + }, + { + type: "section", + text: "Heizung (Manuell)" + }].concat(radiatorUI("office")) + }, + tucana: { + name: "tucana", + position: [110, 658], + icon: svg(icons.mdiDesktopTower).color(({tucanaPower}) => + ({ + "Link Down": hex("#888888"), + "1000M": hex("#00ff00"), + "10M": hex("#000000") + })[tucanaPower] || hex("#ff0000")), + ui: [ + { + type: "toggle", + topic: "tucanaPower", + text: "Einschalten", + icon: svg(icons.mdiPower), + on: "1000M" + }, + { + type: "text", + text: "Link Speed", + icon: svg(icons.mdiEthernet), + topic: "tucanaPower" + } + ] + }, + officeSwitch: { + name: "Switch Büro", + position: [280, 658], + icon: svg(icons.mdiLan), + ui: [ + { + type: "toggle", + topic: "officeSwitchPollingActive", + text: "Poll switch status", + icon: svg(icons.mdiPower) + }, + { + type: "link", + link: "http://192.168.0.189/", + text: "Open Webinterface", + icon: svg(icons.mdiOpenInNew) + } + + ] + }, + officeLight: { + name: "Büro", + position: [210, 570], + icon: svg(icons.mdiCeilingLight).color(({officeState}) => + (officeState === "on" ? hex("#00FF00") : hex("#000000"))), + ui: ([ + { + type: "toggle", + topic: "officeState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "officebrightness" + }, + { + type: "slider", + min: 250, + max: 454, + text: "Farbtemperatur", + icon: svg(icons.mdiWeatherSunsetDown), + topic: "officecolor_temp" + }, + { + type: "section", + text: "RGB" + } + ]).concat(sliderRGB("office", "r")) + .concat(sliderRGB("office", "g")) + .concat(sliderRGB("office", "b")) + .concat([ + { + type: "section", + text: "HSV" + } + ]).concat(sliderH("office", "h")) + .concat(sliderSVXY("office", "s")) + .concat(sliderSVXY("office", "v")) + .concat([ + { + type: "section", + text: "XY" + } + ]).concat(sliderSVXY("office", "x")) + .concat(sliderSVXY("office", "y")) + .concat([ + { + type: "link", + link: "http://192.168.0.12:8080/#/device/0x0c4314fffe9bc8c1/exposes", + text: "Open Webinterface", + icon: svg(icons.mdiOpenInNew) + } + ]) + }, + officeLedStripWled: { + name: "Ledstreifen Büro", + position: [316, 570], + /* eslint-disable camelcase */ + icon: svg(icons.mdiWhiteBalanceIridescent).color( + ({wled_office_brightness}) => + (wled_office_brightness !== "0" ? hex("#00FF00") : hex("#000000"))), + /* eslint-enable camelcase */ + ui: ([ + { + type: "toggle", + topic: "wled_office_brightness", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower), + on: "107", + off: "0", + toggled: (n) => parseInt(n, 10) > 0 + }, + { + type: "toggle", + topic: "wledOfficeAuto", + text: "Automatik", + icon: svg(icons.mdiAutoDownload) + }, + { + type: "slider", + min: 1, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "wled_office_brightness" + }, + { + type: "text", + text: "Status", + icon: svg(icons.mdiRefresh), + topic: "wled_office_lwt" + }, + { + type: "link", + link: "http://192.168.0.66/", + text: "Open Webinterface", + icon: svg(icons.mdiOpenInNew) + } + ]) + }, + hallwayLight: { + name: "Flur", + position: [520, 370], + icon: svg(icons.mdiCeilingLight).color(({hallwayState}) => + (hallwayState === "on" ? hex("#00FF00") : hex("#000000"))), + ui: [ + { + type: "toggle", + topic: "hallwayState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "hallwaybrightness" + } + ] + }, + hallway2Light: { + name: "Flur", + position: [250, 370], + icon: withState((s) => ( + svg(icons.mdiCeilingLight).color( + s["hallway2State"] === "on" ? + (s["tadpole_webcam"] == "on" ? hex("#FF0000") : s["tadpole_mic"] == "on" ? hex("#0000FF") : s["twitch_status_uwap"] === "on" ? hex("#FF00FF") : hex("#00FF00")) + : + (s["tadpole_webcam"] == "on" ? hex("#990000") : s["tadpole_mic"] == "on" ? hex("#000099") : s["twitch_status_uwap"] === "on" ? hex("#990099") : hex("#000000")) + ))), + ui: [ + { + type: "toggle", + topic: "hallway2State", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "hallway2brightness" + }, + { + type: "text", + text: "Twitch Uwap", + icon: svg(icons.mdiTwitch), + topic: "twitch_status_uwap" + }, + { + type: "section", + text: "Laptop tadpole" + }, + { + type: "text", + text: "Datenstand", + icon: svg(icons.mdiRefresh), + topic: "tadpole_freshness" + }, + { + type: "text", + text: "Mikrofon", + icon: svg(icons.mdiMicrophone), + topic: "tadpole_mic" + }, + { + type: "text", + text: "Webcam", + icon: svg(icons.mdiVideo), + topic: "tadpole_webcam" + } + ] + }, + dishwasher: { + name: "Spülmaschine", + position: [575, 449], + icon: withState((s) => ( + ( s["tasmotaDishwasherState"] === "off" ) + ? svg(icons.mdiDishwasherOff) + : + ( parseFloat(s["tasmotaDishwasherPower"]) + < 2 + ) + ? svg(icons.mdiDishwasherAlert).color(hex("#FF8700")) + : svg(icons.mdiDishwasher).color(hex("#00FF00")) + )), + ui: [ + { + type: "toggle", + topic: "tasmotaDishwasherState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "text", + text: "Stromverbrauch (W)", + icon: svg(icons.mdiPowerSocketDe), + topic: "tasmotaDishwasherPower" + }, + ] + }, + temperatureWarningKitchen: { + name: "Untertemperatur-Warnung", + position: [625, 660], + icon: withState((s) => ( + + ( parseFloat(s["temperatureKitchen"]) + < parseFloat(s["temperatureWarningKitchen"]) + ) + ? svg(icons.mdiThermometerAlert).color(hex("#FF0000")) + : svg(icons.mdiThermometer) + )), + ui: [ + { + type: "text", + text: "Istwert:", + icon: svg(icons.mdiThermometer), + topic: "temperatureKitchen" + }, + { + type: "slider", + min: 0, + max: 20, + step: 1, + text: "Schwellwert", + icon: svg(icons.mdiThermometerChevronDown), + topic: "temperatureWarningKitchen", + marks: [ + { value: 0, label: "0°C" }, + { value: 15, label: "15°C" }, + { value: 20, label: "20°C" } + ] + }, + { + type: "toggle", + topic: "windowOpenWarning", + text: "Fenster Offen Warnung", + icon: svg(icons.mdiWindowOpenVariant) + }, + ] + }, + diningroomLight: { + name: "Esszimmer", + position: [410, 570], + icon: svg(icons.mdiCeilingLight).color(({diningroomState}) => + (diningroomState === "on" ? hex("#00FF00") : hex("#000000"))), + ui: [ + { + type: "toggle", + topic: "diningroomState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "diningroombrightness" + } + ] + }, + kitchenLight: { + name: "Küche", + position: [550, 570], + icon: svg(icons.mdiCeilingLight).color(({kitchenState}) => + (kitchenState === "on" ? hex("#00FF00") : hex("#000000"))), + ui: [ + { + type: "toggle", + topic: "kitchenState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "kitchenbrightness" + }, + { + type: "slider", + min: 250, + max: 454, + text: "Farbtemperatur", + icon: svg(icons.mdiWeatherSunsetDown), + topic: "kitchencolor_temp" + } + ] + }, + diningroomHeater: { + name: "Heizung Esszimmer", + position: [410, 658], + icon: withState(({heaterdiningroomTsoll}) => ( + heaterdiningroomTsoll === "126.5" ? + svg(icons.mdiRadiatorDisabled) : svg(icons.mdiRadiator) + )), + ui: [ + { + type: "section", + text: "Heizung (Automatik)" + }, + { + type: "toggle", + topic: "heaterLivingroomAuto", + text: "Automatik", + icon: svg(icons.mdiRadiator) + }, + { + type: "slider", + min: 15, + max: 25, + step: 0.1, + text: "Zieltemperatur", + icon: svg(icons.mdiOilTemperature), + topic: "heaterLivingroomTarget", + marks: [ + { value: 15, label: "15°C" }, + { value: 21.5, label: "21.5°C" }, + { value: 25, label: "25°C" } + ] + }, + { + type: "section", + text: "Heizung (Manuell)" + }, + ] . concat(radiatorUI("diningroom")) + }, + pi: { + name: "Pi", + position: [550, 75], + icon: svg(icons.mdiRaspberryPi), + ui: [ + { + type: "toggle", + topic: "lueftenHint", + text: "Lüften Erinnerung", + icon: svg(icons.mdiFan) + }, + { + type: "link", + link: "http://192.168.0.12:3000/", + text: "Grafana", + icon: svg(icons.mdiOpenInNew) + }, + { + type: "link", + link: "http://192.168.0.12:1780/", + text: "Snapcast", + icon: svg(icons.mdiOpenInNew) + }, + { + type: "link", + link: "http://carina.fritz.box:9981/", + text: "tvheadend", + icon: svg(icons.mdiOpenInNew) + }, + { + type: "section", + text: "Kälte-Warnung" + }, + { + type: "slider", + min: 0, + max: 20, + step: 1, + text: "Küche", + icon: svg(icons.mdiThermometerChevronDown), + topic: "temperatureWarningKitchen", + marks: [ + { value: 0, label: "0°C" }, + { value: 15, label: "15°C" }, + { value: 20, label: "20°C" } + ] + }, + { + type: "slider", + min: 0, + max: 20, + step: 1, + text: "Büro", + icon: svg(icons.mdiThermometerChevronDown), + topic: "temperatureWarningOffice", + marks: [ + { value: 0, label: "0°C" }, + { value: 15, label: "15°C" }, + { value: 20, label: "20°C" } + ] + }, + { + type: "slider", + min: 0, + max: 20, + step: 1, + text: "Schlafzimmer", + icon: svg(icons.mdiThermometerChevronDown), + topic: "temperatureWarningBedroom", + marks: [ + { value: 0, label: "0°C" }, + { value: 15, label: "15°C" }, + { value: 20, label: "20°C" } + ] + }, + ] + }, + nas: { + name: "NAS", + position: [310, 500], + icon: svg(icons.mdiNas).color(({nasPower}) => + (nasPower === "on" ? hex("#00FF00") : hex("#000000"))), + ui: [ + { + type: "toggle", + topic: "nasPower", + text: "Einschalten", + icon: svg(icons.mdiPower), + } + ] + }, + officeWindowLight: { + name: "Büro Fenster", + position: [310, 658], + /* eslint-disable camelcase */ + icon: svg(icons.mdiDeskLamp).color(({office_windowState}) => + (office_windowState === "on" ? hex("#00FF00") : hex("#000000"))), + /* eslint-enable camelcase */ + ui: [ + { + type: "section", + text: "Beleuchtung" + }, + { + type: "toggle", + topic: "office_windowState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "office_windowbrightness" + }, + { + type: "slider", + min: 250, + max: 454, + text: "Farbtemperatur", + icon: svg(icons.mdiWeatherSunsetDown), + topic: "office_windowcolor_temp" + }, + ] + }, + printer3D: { + name: "3D-Drucker", + position: [310, 430], + icon: svg(icons.mdiPrinter3d), + ui: [ + { + type: "link", + link: "http://octopi.fritz.box/", + text: "Open Webinterface", + icon: svg(icons.mdiOpenInNew) + }, + { + type: "section", + text: "Current Job" + }, + { + type: "progress", + icon: svg(icons.mdiRotateRight), + min: 0, + max: 100, + text: "Printing Progress", + topic: "printer3DProgresss" + }, + { + type: "text", + text: "Time Left", + icon: svg(icons.mdiClock), + topic: "printer3Dremaining" + }, + { + type: "section", + text: "Beleuchtung" + }, + { + type: "toggle", + topic: "3d_printerState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "toggle", + topic: "printerLight", + text: "Sync to 3D printer", + icon: svg(icons.mdiBrightnessAuto) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "3d_printerbrightness" + }, + ] + }, + storeRoomStrip: { + name: "LED-Leisten Lager", + position: [310, 465], + icon: svg(icons.mdiWhiteBalanceIridescent), + icon: svg(icons.mdiWhiteBalanceIridescent).color(({ledstrip_storeroomState}) => + (ledstrip_storeroomState === "on" ? hex("#00FF00") : hex("#000000"))), + ui: [ + { + type: "toggle", + topic: "ledstrip_storeroomState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "ledstrip_storeroombrightness" + } + ] + }, + projector: { + name: "Beamer", + position: [410, 230], + icon: svg(icons.mdiProjector).color(({tasmotaProjectorState}) => + (tasmotaProjectorState === "on" ? hex("#00FF00") : hex("#000000"))), + ui: [ + { + type: "toggle", + topic: "tasmotaProjectorState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "toggle", + topic: "tasmotaProjectorAutoOff", + text: "Automatik", + icon: svg(icons.mdiAutoDownload) + }, + { + type: "section", + text: "Kodi" + }, + { + type: "toggle", + topic: "kodi", + text: "Kodi herunterfahren", + icon: svg(icons.mdiPower), + on: "2", + off: "0", + }, + { + type: "link", + link: "http://192.168.0.10:8080/", + text: "Open Webinterface", + icon: svg(icons.mdiOpenInNew) + } + ] + }, + livingroomLight: { + name: "Wohnzimmer", + position: [450, 200], + icon: svg(icons.mdiCeilingLight).color(({livingroomState}) => + (livingroomState === "on" ? hex("#00FF00") : hex("#000000"))), + ui: ([ + { + type: "toggle", + topic: "livingroomState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "toggle", + topic: "livingroomKodiControlled", + text: "Kodi Einbindung", + icon: svg(icons.mdiBrightnessAuto) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + marks: [ + { value: 1, label: "Dunkel" }, + { value: 120, label: "Medium" }, + { value: 254, label: "Hell" } + ], + icon: svg(icons.mdiBrightness7), + topic: "livingroombrightness" + }, + { + type: "slider", + max: 1, + min: 300, + step: -1, + text: "Speed", + icon: svg(icons.mdiSpeedometer), + topic: "livingroomanimation-speed" + }, + { + type: "dropDown", + text: "Modus", + topic: "livingroommode", + options: { + "-1": "Cancel Animation", + "0": "Pink", + "1": "Kodi", + "2": "Sleep", + "3": "RGB Fade", + "4": "Work", + "5": "Winter" + }, + icon: svg(icons.mdiCog) + }, + { + type: "section", + text: "RGB" + } + ]).concat(sliderRGB("livingroom", "r")) + .concat(sliderRGB("livingroom", "g")) + .concat(sliderRGB("livingroom", "b")) + .concat([ + { + type: "section", + text: "HSV" + } + ]).concat(sliderH("livingroom", "h")) + .concat(sliderSVXY("livingroom", "s")) + .concat(sliderSVXY("livingroom", "v")) + .concat([ + { + type: "section", + text: "XY" + } + ]).concat(sliderSVXY("livingroom", "x")) + .concat(sliderSVXY("livingroom", "y")) + }, + livingroomLedStrip: { + name: "Ledstreifen Wohnzimmer", + position: [450, 73], + /* eslint-disable camelcase */ + icon: svg(icons.mdiWhiteBalanceIridescent).color( + ({ledstrip_livingroomState}) => + (ledstrip_livingroomState === "on" ? hex("#00FF00") : hex("#000000"))), + /* eslint-enable camelcase */ + ui: ([ + { + type: "toggle", + topic: "ledstrip_livingroomState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "slider", + min: 0, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "ledstrip_livingroombrightness" + }, + { + type: "dropDown", + text: "Modus", + topic: "livingroommode", + options: { + "-1": "Cancel Animation", + "0": "Pink", + "1": "Kodi", + "2": "Sleep", + "3": "RGB Fade", + "4": "Work", + "5": "Winter" + }, + icon: svg(icons.mdiCog) + }, + { + type: "section", + text: "RGB" + } + ]).concat(sliderRGB("ledstrip_livingroom", "r")) + .concat(sliderRGB("ledstrip_livingroom", "g")) + .concat(sliderRGB("ledstrip_livingroom", "b")) + .concat([ + { + type: "section", + text: "HSV" + } + ]).concat(sliderH("ledstrip_livingroom", "h")) + .concat(sliderSVXY("ledstrip_livingroom", "s")) + .concat(sliderSVXY("ledstrip_livingroom", "v")) + .concat([ + { + type: "section", + text: "XY" + } + ]).concat(sliderSVXY("ledstrip_livingroom", "x")) + .concat(sliderSVXY("ledstrip_livingroom", "y")) + }, + livingroomFountain: { + name: "Brunnen Wohnzimmer", + position: [550, 240], + icon: svg(icons.mdiFountain).color( + ({powerFountainState}) => + (powerFountainState === "on" ? hex("#00FF00") : hex("#000000"))), + ui: ([ + { + type: "toggle", + topic: "powerFountainState", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + ]), + }, + livingroomSoundbar: { + name: "Soundbar Wohnzimmer", + position: [550, 200], + icon: svg(icons.mdiSpeaker).color( + ({livingroomSoundbarPower}) => + (livingroomSoundbarPower === "on" ? hex("#00FF00") : hex("#000000"))), + ui: ([ + { + type: "toggle", + topic: "livingroomSoundbarPower", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower) + }, + { + type: "toggle", + topic: "livingroomSoundbarHDMI", + text: "Input: HDMI", + icon: svg(icons.mdiVideoInputHdmi) + }, + { + type: "slider", + min: 0, + max: 100, + text: "Lautstärke", + icon: svg(icons.mdiVolumeHigh), + topic: "livingroomSoundbarVolume" + }, + { + type: "toggle", + topic: "projectorControlsSoundbar", + text: "Projector Controlled", + icon: svg(icons.mdiProjector) + }, + ]) + }, + livingroomLedStripWled: { + name: "Ledstreifen Wohnzimmer", + position: [550, 160], + /* eslint-disable camelcase */ + icon: svg(icons.mdiWhiteBalanceIridescent).color( + ({wled_livingroom_brightness}) => + (wled_livingroom_brightness !== "0" ? hex("#00FF00") : hex("#000000"))), + /* eslint-enable camelcase */ + ui: ([ + { + type: "toggle", + topic: "wled_livingroom_brightness", + text: "Ein/Ausschalten", + icon: svg(icons.mdiPower), + on: "107", + off: "0", + toggled: (n) => parseInt(n, 10) > 0 + }, + { + type: "slider", + min: 1, + max: 255, + text: "Helligkeit", + icon: svg(icons.mdiBrightness7), + topic: "wled_livingroom_brightness" + }, + { + type: "link", + link: "http://192.168.0.61/", + text: "Open Webinterface", + icon: svg(icons.mdiOpenInNew) + } + ]) + } + }, + layers: [ + { + image: require("./assets/layers/rooms.svg"), + baseLayer: true, + name: "Rooms", + defaultVisibility: "visible", + opacity: 0.7, + bounds: { + topLeft: [0, 0], + bottomRight: [720, 680] + } + } + ] +}; + +window.config = config; diff --git a/index.ejs b/index.ejs index bede683..af4cf0c 100644 --- a/index.ejs +++ b/index.ejs @@ -2,7 +2,6 @@ - <%= htmlWebpackPlugin.options.title %> diff --git a/package.json b/package.json index 593abcf..e3281c7 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "mqtt-control-map", "version": "1.0.0", - "author": "uwap ", - "description": "control devices via mqtt on a beautiful map of your space", + "author": "uwap ", + "description": "Control Devices via mqtt, visualized on a Map", "scripts": { - "build": "webpack --bail --config webpack.config.js -p --env", + "build": "webpack --bail --config webpack.config.js --mode production --env", "dev": "webpack --bail --config webpack.config.js --mode development --env", "watch": "webpack-dev-server --open --config webpack.config.js --mode development --env", "travis": "./travis.sh", @@ -12,45 +12,52 @@ "precommit": "yarn lint" }, "dependencies": { - "@material-ui/core": "^3.0.1", - "@material-ui/lab": "^3.0.0-alpha.16", - "@mdi/font": "^3.0.39", - "leaflet": "^1.3.1", - "lodash-es": "^4.17.4", - "mqtt": "^2.14.0", - "react": "^16.0.0", - "react-dom": "^16.0.0", - "react-leaflet": "^2.0.0", - "redux": "^3.7.2" + "@emotion/react": "^11.10.5", + "@emotion/styled": "^11.10.5", + "@fontsource/roboto": "^4.5.8", + "@mdi/react": "^1.4.0", + "@mui/material": "^5.10.15", + "@mui/styles": "^5.10.15", + "leaflet": "^1.5.1", + "mqtt": "^4.2.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-leaflet": "^4.1.0" }, "devDependencies": { - "@babel/cli": "^7.0.0-rc.1", - "@babel/core": "^7.0.0-rc.1", - "@babel/plugin-proposal-class-properties": "^7.0.0-rc.1", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0-rc.1", - "@babel/polyfill": "^7.0.0-rc.1", - "@babel/preset-env": "^7.0.0-rc.1", + "@babel/cli": "^7.5.5", + "@babel/core": "^7.5.5", + "@babel/plugin-proposal-class-properties": "^7.5.5", + "@babel/plugin-proposal-object-rest-spread": "^7.5.5", + "@babel/preset-env": "^7.5.5", "@babel/preset-flow": "^7.0.0-rc.1", "@babel/preset-react": "^7.0.0-rc.1", - "babel-eslint": "^10.0.0", - "babel-loader": "^8.0.0-beta", - "clean-webpack-plugin": "^2.0.0", - "css-loader": "^2.0.0", - "eslint": "^5.0.1", - "eslint-plugin-flowtype": "^3.0.0", + "@mdi/js": "^7.0.96", + "babel-eslint": "^10.0.2", + "babel-loader": "^9.1.0", + "buffer": "^6.0.3", + "clean-webpack-plugin": "^4.0.0", + "core-js": "^3.6.0", + "css-loader": "^6.7.2", + "eslint": "^8.28.0", + "eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-fp": "^2.3.0", - "eslint-plugin-react": "^7.6.1", - "file-loader": "^3.0.0", + "eslint-plugin-react": "^7.14.3", + "eslint-plugin-react-hooks": "^4.1.2", + "file-loader": "^6.1.0", "flow": "^0.2.3", - "flow-bin": "^0.101.0", - "flow-typed": "^2.3.0", - "html-webpack-plugin": "^3.1.0", - "husky": "^2.0.0", - "style-loader": "^0.23.0", - "webpack": "^4.3.0", - "webpack-cli": "^3.0.0", - "webpack-dev-server": "^3.1.1", - "webpack-shell-plugin": "^0.5.0" + "flow-bin": "^0.193.0", + "flow-typed": "^3.2.1", + "html-webpack-plugin": "^5.5.0", + "husky": "^8.0.2", + "lodash-es": "^4.17.15", + "process": "^0.11.10", + "style-loader": "^3.3.1", + "url": "^0.11.0", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.0", + "webpack-dev-server": "^4.11.1", + "webpack-shell-plugin-next": "^2.3.1" }, "license": "MIT" } diff --git a/src/components/App.js b/src/components/App.js index 6d8ed79..e40b018 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -9,13 +9,10 @@ import throttle from "lodash/throttle"; import type { Config, Control, Topics } from "config/flowtypes"; -import MuiThemeProvider from "@material-ui/core/styles/MuiThemeProvider"; -import createMuiTheme from "@material-ui/core/styles/createMuiTheme"; -import withStyles from "@material-ui/core/styles/withStyles"; -import * as Colors from "@material-ui/core/colors"; -import Snackbar from "@material-ui/core/Snackbar"; -import IconButton from "@material-ui/core/IconButton"; -import Typography from "@material-ui/core/Typography"; +import { withStyles } from "@mui/styles"; +import Snackbar from "@mui/material/Snackbar"; +import IconButton from "@mui/material/IconButton"; +import Typography from "@mui/material/Typography"; import SideBar from "components/SideBar"; import ControlMap from "components/ControlMap"; @@ -39,6 +36,14 @@ export type AppState = { error: ?string }; +/* + *const App = (props: AppProps) => { + * const topics = Array.isArray(props.config.topics) ? + * Object.assign({}, ...props.config.topics) : props.config.topics; + * const [mqttConnected, setMqttConnected] = useState(false); + *}; + */ + class App extends React.PureComponent { controlMap: React.Node @@ -55,7 +60,8 @@ class App extends React.PureComponent { onDisconnect: () => this.setState({ mqttConnected: false }), subscribe: map( filter(keys(this.topics), (x) => this.topics[x].state != null), - (x) => this.topics[x].state.name) + (x) => (this.topics[x].state != null ? this.topics[x].state.name : "") + ) }), mqttConnected: false, search: "", @@ -94,14 +100,6 @@ class App extends React.PureComponent { }; } - static theme(config: Config) { - return createMuiTheme({ - palette: { - primary: Colors[config.space.color] - } - }); - } - receiveMessage(rawTopic: string, message: Buffer) { try { const topics = filter( @@ -115,8 +113,8 @@ class App extends React.PureComponent { for (let i in topics) { const topic = topics[i]; const stateTopic = this.topics[topic].state; - const parseVal = stateTopic ? stateTopic.type : null; - const val = parseVal == null ? message.toString() : parseVal(message); + const typeConversion = stateTopic?.type?.from ?? stateTopic?.type; + const val = (typeConversion ?? ((x: Buffer) => x.toString()))(message); this.setMqttStateDebounced( {mqttState: Object.assign({}, merge(this.state.mqttState, { [topic]: val}))}); @@ -136,16 +134,16 @@ class App extends React.PureComponent { this.setState({drawerOpened: false}); } - changeState = (topic: string, value: string) => { + changeState = (topic: string, val: string) => { try { - if (this.topics[topic].command == null) { + const commandTopic = this.topics[topic].command; + if (commandTopic == null) { return; } - const rawTopic = this.topics[topic].command.name; - const transformValue = this.topics[topic].command.type; - const val = - transformValue == null ? value : transformValue(Buffer.from(value)); - this.state.mqttSend(rawTopic, Buffer.from(val)); + const rawTopic = commandTopic.name; + const typeConversion = commandTopic?.type?.to ?? commandTopic.type; + const value = (typeConversion ?? Buffer.from)(val); + this.state.mqttSend(rawTopic, value); } catch (err) { this.setState({ error: err.toString() }); } @@ -170,7 +168,7 @@ class App extends React.PureComponent { control={this.state.selectedControl} onCloseRequest={this.closeDrawer} icon={this.state.selectedControl == null ? null : - this.state.selectedControl.icon(this.state.mqttState)} + this.state.selectedControl.icon.render(this.state.mqttState)} > {this.state.selectedControl == null || } @@ -204,11 +202,6 @@ class App extends React.PureComponent { } } -export default (props: AppProps) => { - const StyledApp = withStyles(App.styles)(App); - return ( - - - - ); -}; + + +export default withStyles(App.styles)(App); diff --git a/src/components/ControlMap.js b/src/components/ControlMap.js index 6532002..e567e4c 100644 --- a/src/components/ControlMap.js +++ b/src/components/ControlMap.js @@ -1,12 +1,15 @@ // @flow import React from "react"; -import { Map, ImageOverlay, Marker, LayersControl } from "react-leaflet"; +import { MapContainer, ImageOverlay, Marker, LayersControl } from "react-leaflet"; import { CRS, point, divIcon } from "leaflet"; import map from "lodash/map"; import filter from "lodash/filter"; import reduce from "lodash/reduce"; import MqttContext from "mqtt/context"; -import type { Controls, Control, UIControl, ControlUI } from "config/flowtypes"; +import type { + Controls, Control, UIControl, ControlUI, Layer +} from "config/flowtypes"; +import { renderToString } from "react-dom/server"; export type Point = [number, number]; @@ -28,21 +31,11 @@ const center = (props: ControlMapProps): Point => props.height / 2 ]); -const iconColor = (control: Control, state: State): string => { - if (control.iconColor != null) { - return control.iconColor(state); - } - return "#000"; -}; - const createLeafletIcon = (control: Control, state: State) => { - const icon = control.icon(state); - const iconClass = `${icon} mdi-36px`; return divIcon({ iconSize: point(36, 36), iconAnchor: point(18, 18), - html: `` + html: renderToString(control.icon.render(state)) }); }; @@ -52,7 +45,9 @@ const renderMarker = (props: ControlMapProps) => {({ state }) => ( props.onChangeControl(control)} + eventHandlers={{ + click: () => props.onChangeControl(control) + }} > )} @@ -125,13 +120,13 @@ const renderLayers = (props: ControlMapProps) => ( ); const ControlMap = (props: ControlMapProps) => ( - {renderMarkers(props)} {renderLayers(props)} - + ); export default ControlMap; diff --git a/src/components/SideBar.js b/src/components/SideBar.js index 8a2de0c..18092cd 100644 --- a/src/components/SideBar.js +++ b/src/components/SideBar.js @@ -1,62 +1,63 @@ // @flow import * as React from "react"; -import withStyles from "@material-ui/core/styles/withStyles"; -import Drawer from "@material-ui/core/Drawer"; -import Typography from "@material-ui/core/Typography"; -import IconButton from "@material-ui/core/IconButton"; -import AppBar from "@material-ui/core/AppBar"; -import Toolbar from "@material-ui/core/Toolbar"; -import List from "@material-ui/core/List"; -import { renderRawIcon } from "config/icon"; +import { makeStyles } from "@mui/styles"; +import Drawer from "@mui/material/Drawer"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import AppBar from "@mui/material/AppBar"; +import Toolbar from "@mui/material/Toolbar"; +import List from "@mui/material/List"; +import ReactIcon from "@mdi/react"; +import { mdiClose } from "@mdi/js"; -import type { RawIcon } from "config/icon"; import type { Control } from "config/flowtypes"; export type SideBarProps = { control: ?Control, open: boolean, onCloseRequest: () => void, - icon?: ?RawIcon, + icon?: ?React.Node, children?: React.Node }; -type Props = SideBarProps & Classes; - -const SideBar = (props: Props) => ( - - - - - {props.icon == null || renderRawIcon(props.icon, "mdi-36px")} - - - {props.control == null ? "" : props.control.name} - - - - - - - - {props.children} - - -); - -const styles = (theme) => ({ +const useStyles = makeStyles((theme) => ({ drawerPaper: { width: 340 }, title: { flex: 1, - marginLeft: theme.spacing.unit + marginLeft: theme.spacing(1) } -}); +})); -export default withStyles(styles)(SideBar); +const SideBar = (props: SideBarProps) => { + const classes = useStyles(); + return ( + + + + + {props.icon == null || props.icon} + + + {props.control == null ? "" : props.control.name} + + + + + + + + {props.children} + + + ); +}; + +export default SideBar; diff --git a/src/components/TopBar.js b/src/components/TopBar.js index 0510711..b99a2a7 100644 --- a/src/components/TopBar.js +++ b/src/components/TopBar.js @@ -1,14 +1,16 @@ // @flow import React from "react"; -import AppBar from "@material-ui/core/AppBar"; -import Toolbar from "@material-ui/core/Toolbar"; -import CircularProgress from "@material-ui/core/CircularProgress"; -import InputBase from "@material-ui/core/InputBase"; -import { fade } from "@material-ui/core/styles/colorManipulator"; -import { withStyles } from "@material-ui/core/styles"; -import Tooltip from "@material-ui/core/Tooltip"; -import IconButton from "@material-ui/core/IconButton"; +import AppBar from "@mui/material/AppBar"; +import Toolbar from "@mui/material/Toolbar"; +import CircularProgress from "@mui/material/CircularProgress"; +import InputBase from "@mui/material/InputBase"; +import { styled } from "@mui/styles"; +import { alpha } from "@mui/material/styles"; +import Tooltip from "@mui/material/Tooltip"; +import IconButton from "@mui/material/IconButton"; +import ReactIcon from "@mdi/react"; +import { mdiMap, mdiGithub, mdiMagnify } from "@mdi/js"; export type TopBarProps = { connected: boolean, @@ -21,89 +23,74 @@ export type SearchBarProps = { const renderConnectionIndicator = (connected: boolean) => { if (connected) { - return (); + return (); } return ( ); }; -const searchStyles = (theme) => ({ - search: { - position: "relative", - borderRadius: theme.shape.borderRadius, - backgroundColor: fade(theme.palette.common.white, 0.15), - "&:hover": { - backgroundColor: fade(theme.palette.common.white, 0.25) +const Search = styled('div')(({ theme }) => ({ + position: 'relative', + borderRadius: theme.shape.borderRadius, + backgroundColor: alpha(theme.palette.common.white, 0.15), + '&:hover': { + backgroundColor: alpha(theme.palette.common.white, 0.25), + }, + marginRight: theme.spacing(2), + marginLeft: 0, + width: '100%', + [theme.breakpoints.up('sm')]: { + marginLeft: theme.spacing(3), + width: 'auto', + }, +})); + +const SearchIconWrapper = styled('div')(({ theme }) => ({ + padding: theme.spacing(0, 2), + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +const StyledInputBase = styled(InputBase)(({ theme }) => ({ + color: 'inherit', + '& .MuiInputBase-input': { + padding: theme.spacing(1, 1, 1, 0), + // vertical padding + font size from searchIcon + paddingLeft: `calc(1em + ${theme.spacing(4)})`, + transition: theme.transitions.create('width'), + width: '100%', + [theme.breakpoints.up('md')]: { + width: '20ch', }, - marginRight: theme.spacing.unit * 2, - marginLeft: 0, - width: "100%", - [theme.breakpoints.up("sm")]: { - marginLeft: theme.spacing.unit * 3, - width: "auto" - } }, - searchIcon: { - width: theme.spacing.unit * 6, - height: "100%", - position: "absolute", - pointerEvents: "none", - display: "flex", - alignItems: "center", - justifyContent: "center", - fontSize: "24px" - }, - inputRoot: { - color: "inherit", - width: "100%" - }, - inputInput: { - paddingTop: theme.spacing.unit, - paddingRight: theme.spacing.unit, - paddingBottom: theme.spacing.unit, - paddingLeft: theme.spacing.unit * 6, - transition: theme.transitions.create("width"), - width: "100%", - [theme.breakpoints.up("md")]: { - width: 200 - } - } -}); - -const RawSearch = (props: SearchBarProps & Classes) => ( -
- - props.onSearch(e.target.value)} - classes={{ - root: props.classes.inputRoot, - input: props.classes.inputInput - }} /> -
-); - -const Search = withStyles(searchStyles)(RawSearch); +})); const openOnGithub = () => window.open( "https://github.com/uwap/mqtt-control-map", "_blank"); -const sendFeedback = () => window.open("mailto:mail+feedback@uwap.name"); - const TopBar = (props: TopBarProps) => ( - + {renderConnectionIndicator(props.connected)} - + + + + + props.onSearch(e.target.value)} + /> + - - - - - - - - + + + diff --git a/src/components/UiItemList.js b/src/components/UiItemList.js index dd256fd..d7c8313 100644 --- a/src/components/UiItemList.js +++ b/src/components/UiItemList.js @@ -1,6 +1,6 @@ // @flow import * as React from "react"; -import ListItem from "@material-ui/core/ListItem"; +import ListItem from "@mui/material/ListItem"; import type { ControlUI } from "config/flowtypes"; diff --git a/src/components/UiItems/DropDown.js b/src/components/UiItems/DropDown.js index 47f94e8..6f6677a 100644 --- a/src/components/UiItems/DropDown.js +++ b/src/components/UiItems/DropDown.js @@ -6,11 +6,11 @@ import { isDisabled, getValue } from "./utils"; import type { UIDropDown } from "config/flowtypes"; -import Select from "@material-ui/core/Select"; -import FormControl from "@material-ui/core/FormControl"; -import InputLabel from "@material-ui/core/InputLabel"; -import MenuItem from "@material-ui/core/MenuItem"; -import Input from "@material-ui/core/Input"; +import Select from "@mui/material/Select"; +import FormControl from "@mui/material/FormControl"; +import InputLabel from "@mui/material/InputLabel"; +import MenuItem from "@mui/material/MenuItem"; +import Input from "@mui/material/Input"; const componentId = (item: UIDropDown) => `dropdown-${item.topic}`; diff --git a/src/components/UiItems/Link.js b/src/components/UiItems/Link.js index 506f86d..05bc2c4 100644 --- a/src/components/UiItems/Link.js +++ b/src/components/UiItems/Link.js @@ -2,11 +2,10 @@ import React from "react"; import createComponent from "./base"; import { isEnabled, isDisabled } from "./utils"; -import { renderRawIcon } from "config/icon"; import type { UILink } from "config/flowtypes"; -import Button from "@material-ui/core/Button"; +import Button from "@mui/material/Button"; const followLink = (item, state) => () => { if (isEnabled(item, state)) { @@ -19,7 +18,7 @@ const Icon = ({item, state}) => { if (item.icon == null) { return false; } - return renderRawIcon(item.icon(state), "mdi-24px"); + return item.icon.render(state); }; const BaseComponent = (_h, item: UILink, state, _changeState) => ( diff --git a/src/components/UiItems/Progress.js b/src/components/UiItems/Progress.js index 6cebd81..445b922 100644 --- a/src/components/UiItems/Progress.js +++ b/src/components/UiItems/Progress.js @@ -5,7 +5,7 @@ import { getValue } from "./utils"; import type { UIProgress } from "config/flowtypes"; -import LinearProgress from "@material-ui/core/LinearProgress"; +import LinearProgress from "@mui/material/LinearProgress"; const progressVal = (item, state) => { const min = item.min || 0; diff --git a/src/components/UiItems/Section.js b/src/components/UiItems/Section.js index c03724f..b23dd76 100644 --- a/src/components/UiItems/Section.js +++ b/src/components/UiItems/Section.js @@ -4,7 +4,7 @@ import createComponent from "./base"; import type { UISection } from "config/flowtypes"; -import ListSubheader from "@material-ui/core/ListSubheader"; +import ListSubheader from "@mui/material/ListSubheader"; const BaseComponent = (_b, item: UISection, _state, _changeState) => ( {item.text} diff --git a/src/components/UiItems/Slider.js b/src/components/UiItems/Slider.js index e3fceac..4b13b75 100644 --- a/src/components/UiItems/Slider.js +++ b/src/components/UiItems/Slider.js @@ -5,7 +5,7 @@ import { isDisabled, getValue } from "./utils"; import type { UISlider } from "config/flowtypes"; -import SliderComponent from "@material-ui/lab/Slider"; +import SliderComponent from "@mui/material/Slider"; const changeSliderValue = (item: UISlider, changeState) => (_e, v) => changeState(item, v.toString()); @@ -16,10 +16,12 @@ const BaseComponent = ({Icon, Label}, item, state, changeState) => (