From c0117fa7d653dd364c585632a8af5ef7341ce2bb Mon Sep 17 00:00:00 2001 From: uwap Date: Thu, 28 Jun 2018 21:25:06 +0200 Subject: [PATCH] Do not use internal and actual values anymore (Fixes #42) --- config/entropia.js | 19 +- config/rzl.js | 436 ++++++++++++++++------------ config/utils.js | 95 +++--- src/components/App.js | 45 ++- src/components/ControlMap.js | 8 +- src/components/SideBar.js | 4 +- src/components/UiItemList/UiItem.js | 39 +-- src/components/UiItemList/index.js | 4 +- src/config/flowtypes.js | 46 ++- src/config/icon.js | 70 ++--- src/config/types.js | 19 +- src/utils/state.js | 8 - types/types.js | 21 +- 13 files changed, 421 insertions(+), 393 deletions(-) delete mode 100644 src/utils/state.js diff --git a/config/entropia.js b/config/entropia.js index fc2f6ca..ea6939a 100644 --- a/config/entropia.js +++ b/config/entropia.js @@ -1,6 +1,7 @@ // @flow import type { Config } from "config/flowtypes"; import { hex, rgb, rgba, rainbow } from "config/colors"; +import * as types from "config/types"; import { mdi } from "config/icon"; import { esper_topics, esper_statistics } from "./utils"; @@ -13,16 +14,18 @@ const config : Config = { topics: [ { hauptraum_table_light: { - command: "/public/sensoren/TPH/leinwand/control", - state: "test", - defaultValue: "A1 ON", - values: { on: "A1 ON", off: "A1 OFF" } + command: { + name: "/public/sensoren/TPH/leinwand/control", + type: types.option({ "A1 ON": "on", "A1 OFF": "off" }) + }, + defaultValue: "off" }, hauptraum_table_light_on_hack: { - command: "/public/sensoren/TPH/leinwand/control", - state: "test", - defaultValue: "A1 OFF", - values: { on: "A1 ON", off: "A1 OFF" } + command: { + name: "/public/sensoren/TPH/leinwand/control", + type: types.option({ "A1 ON": "on", "A1 OFF": "off" }) + }, + defaultValue: "on" } } ], diff --git a/config/rzl.js b/config/rzl.js index aec1a33..e606527 100644 --- a/config/rzl.js +++ b/config/rzl.js @@ -14,187 +14,299 @@ const config : Config = { topics: [ { led_stahltraeger: { - state: "/service/openhab/out/pca301_ledstrips/state", - command: "/service/openhab/in/pca301_ledstrips/command", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } + state: { + name: "/service/openhab/out/pca301_ledstrips/state", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "/service/openhab/in/pca301_ledstrips/command", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off" }, snackbar: { - state: "/service/openhab/out/pca301_snackbar/state", - command: "/service/openhab/in/pca301_snackbar/command", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } + state: { + name: "/service/openhab/out/pca301_snackbar/state", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "/service/openhab/in/pca301_snackbar/command", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off", }, twinkle: { - state: "/service/openhab/out/pca301_twinkle/state", - command: "/service/openhab/in/pca301_twinkle/command", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } + state: { + name: "/service/openhab/out/pca301_twinkle/state", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "/service/openhab/in/pca301_twinkle/command", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off" }, fan: { - state: "/service/openhab/out/pca301_fan/state", - command: "/service/openhab/in/pca301_fan/command", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } + state: { + name: "/service/openhab/out/pca301_fan/state", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "/service/openhab/in/pca301_fan/command", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off" }, videogames: { - state: "/service/openhab/out/pca301_videogames/state", - command: "/service/openhab/in/pca301_videogames/command", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } + state: { + name: "/service/openhab/out/pca301_videogames/state", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "/service/openhab/in/pca301_videogames/command", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off" }, olymp_pc: { - state: "/service/openhab/out/pca301_olymp_pc/state", - command: "/service/openhab/in/pca301_olymp_pc/command", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } + state: { + name: "/service/openhab/out/pca301_olymp_pc/state", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "/service/openhab/in/pca301_olymp_pc/command", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off" }, olymp_printer: { - state: "stat/sonoff2/POWER", - command: "cmnd/sonoff2/power", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } + state: { + name: "stat/sonoff2/POWER", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "cmnd/sonoff2/power", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off" }, flyfry: { - state: "/service/openhab/out/wifi_flyfry/state", - command: "/service/openhab/in/wifi_flyfry/command", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } - }, - artnet: { - state: "/artnet/state", - command: "/artnet/push", - defaultValue: "blackout", - values: { off: "blackout", yellow: "yellow", purple: "purple", - blue: "blue", green: "green", red: "red", random: "random", - cycle: "cycle-random" } + state: { + name: "/service/openhab/out/wifi_flyfry/state", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "/service/openhab/in/wifi_flyfry/command", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off" }, onkyo_connection: { - state: "/service/onkyo/connected", - command: "", - defaultValue: "0", - values: { disconnected: "0", connecting: "1", connected: "2" }, + state: { + name: "/service/onkyo/connected", + type: types.option({ + "0": "disconnected", + "1": "connecting", + "2": "connected" + }) + }, + defaultValue: "disconnected" }, onkyo_power: { - state: "/service/onkyo/status/system-power", - command: "/service/onkyo/command", - defaultValue: "PWR00", - values: { off: "PWR00", on: "PWR01" }, - type: types.json("onkyo_raw") + state: { + name: "/service/onkyo/status/system-power", + type: types.json("onkyo_raw", types.option({ + PWR00: "off", + PWR01: "on" + })) + }, + command: { + name: "/service/onkyo/command", + type: types.option({ off: "PWR00", on: "PWR01" }) + }, + defaultValue: "off" }, onkyo_mute: { - state: "/service/onkyo/status/audio-muting", - command: "/service/onkyo/command", - defaultValue: "AMT00", - values: { off: "AMT00", on: "AMT01" }, - type: types.json("onkyo_raw") + state: { + name: "/service/onkyo/status/audio-muting", + type: types.json("onkyo_raw", types.option({ + AMT00: "off", + AMT01: "on" + })) + }, + command: { + name: "/service/onkyo/command", + type: types.option({ off: "AMT00", on: "AMT01" }) + }, + defaultValue: "off" }, onkyo_volume: { - state: "/service/onkyo/status/volume", - command: "/service/onkyo/set/volume", - defaultValue: 0, - values: {}, - type: types.json("val") + state: { + name: "/service/onkyo/status/volume", + type: types.json("val") + }, + command: { + name: "/service/onkyo/set/volume", + type: types.string + }, + defaultValue: "0" }, onkyo_inputs: { - state: "/service/onkyo/status/input-selector", - command: "/service/onkyo/command", - defaultValue: "SLI00", - values: { tisch: "SLI11", chromecast: "SLI01", pult: "SLI10", netzwerk: "SLI2B", front: "SLI03" }, - type: types.json("onkyo_raw") + state: { + name: "/service/onkyo/status/input-selector", + type: types.json("onkyo_raw", types.option({ + SLI11: "tisch", + SLI01: "chromecast", + SLI10: "pult", + SLI2B: "netzwerk", + SLI03: "front", + otherwise: "unknown" + })) + }, + command: { + name: "/service/onkyo/command", + type: types.option({ + tisch: "SLI11", + chromecast: "SLI01", + pult: "SLI10", + netzwerk: "SLI2B", + front: "SLI03", + unknown: "SLI00" + }) + }, + defaultValue: "unknown", }, onkyo_radios: { - state: "/service/onkyo/status/latest-NPR", - command: "/service/onkyo/command", - defaultValue: "", - values: { mpd: "NPR01", kohina: "NPR02", somafm_dronezone: "NPR03", somafm_thetrip: "NPR04", - querfunk: "NPR05", somafm_defconradio: "NPR06", somafm_secretagent: "NPR07", somafm_lush: "NPR08", - somafm_beatblender: "NPR09", ponyville: "NPR0a"} + state: { + name: "/service/onkyo/status/latest-NPR", + type: types.option({ + NPR01: "mpd", + NPR02: "kohina", + NPR03: "somafm_dronezone", + NPR04: "somafm_thetrip", + NPR05: "querfunk", + NPR06: "somafm_defconradio", + NPR07: "somafm_secretagent", + NPR08: "somafm_lush", + NPR09: "somafm_beatblender", + NPR0a: "ponyville", + otherwise: "unknown" + }) + }, + command: { + name: "/service/onkyo/command", + type: types.option({ + mpd: "NPR01", + kohina: "NPR02", + somafm_dronezone: "NPR03", + somafm_thetrip: "NPR04", + querfunk: "NPR05", + somafm_defconradio: "NPR06", + somafm_secretagent: "NPR07", + somafm_lush: "NPR08", + somafm_beatblender: "NPR09", + ponyville: "NPR0a", + otherwise: "NPR00" + }) + }, + defaultValue: "unknown" }, rundumleuchte: { - state: "/service/openhab/out/pca301_rundumleuchte/state", - command: "/service/openhab/in/pca301_rundumleuchte/command", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } + state: { + name: "/service/openhab/out/pca301_rundumleuchte/state", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "/service/openhab/in/pca301_rundumleuchte/command", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off" }, door_status: { - state: "/service/status", - command: "", - defaultValue: "\"closed\"", - values: { on: "\"open\"", off: "\"closed\"" } + state: { + name: "/service/status", + type: types.option({ "\"open\"": "on", "\"closed\"": "off" }) + }, + defaultValue: "off" }, presence_status: { - state: "service/status/presence", - command: "", - defaultValue: "", - values: {}, - type: msg => JSON.parse(msg.toString()).join(", ") + state: { + name: "service/status/presence", + type: types.jsonArray + }, + defaultValue: "" }, devices_status: { - state: "/service/status/devices", - command: "", - defaultValue: "", - values: {}, - type: types.string + state: { + name: "/service/status/devices", + type: types.string + }, + defaultValue: "" }, infoscreen: { - state: "/service/openhab/out/pca301_infoscreen/state", - command: "/service/openhab/in/pca301_infoscreen/command", - defaultValue: "OFF", - values: { on: "ON", off: "OFF" } + state: { + name: "/service/openhab/out/pca301_infoscreen/state", + type: types.option({ ON: "on", OFF: "off" }) + }, + command: { + name: "/service/openhab/in/pca301_infoscreen/command", + type: types.option({ on: "ON", off: "OFF" }) + }, + defaultValue: "off" }, printer_3d_status: { - state: "/service/ultimaker/state", - command: "", + state: { + name: "/service/ultimaker/state", + type: types.option({ + unreachable: "unavailable", + booting: "unavailable", + pre_print: "printing", + post_print: "printing", + printing: "printing", + otherwise: "awaiting_interaction" + }) + }, defaultValue: "unavailable", - values: {}, - type: msg => { - switch (msg.toString()) { - case "unreachable": - case "booting": - return "unavailable" - - case "pausing": - case "paused": - case "resuming": - case "wait_cleanup": - case "maintenance": - return "awaiting_interaction" - - case "pre_print": - case "post_print": - case "printing": - return "printing" - - default: - return msg.toString() - } - } }, printer_3d_progress: { - state: "/service/ultimaker/job", - command: "", - defaultValue: "", - values: {}, - type: msg => JSON.parse(msg.toString()).progress || 0 + state: { + name: "/service/ultimaker/job", + type: msg => JSON.parse(msg.toString()).progress || "0" + }, + defaultValue: "0" }, kitchen_light_color: { - state: "/service/openhab/out/kitchen_light_all_color_temperature/state", - command: "/service/openhab/in/kitchen_light_all_color_temperature/command", - defaultValue: "0", - values: {} + state: { + name: "/service/openhab/out/kitchen_light_all_color_temperature/state", + type: types.string + }, + command: { + name: "/service/openhab/in/kitchen_light_all_color_temperature/command", + type: types.string + }, + defaultValue: "0" }, kitchen_light_brightness: { - state: "/service/openhab/out/kitchen_light_all_brightness/state", - command: "/service/openhab/in/kitchen_light_all_brightness/command", - defaultValue: "0", - values: {} + state: { + name: "/service/openhab/out/kitchen_light_all_brightness/state", + type: types.string + }, + command: { + name: "/service/openhab/in/kitchen_light_all_brightness/command", + type: types.string + }, + defaultValue: "0" }, kitchen_sink_light_brightness: { - state: "/service/openhab/out/tradfri_0100_gwb8d7af2b448f_65545_brightness/state", - command: "/service/openhab/in/tradfri_0100_gwb8d7af2b448f_65545_brightness/command", - defaultValue: "0", - values: {} + state: { + name: "/service/openhab/out/tradfri_0100_gwb8d7af2b448f_65545_brightness/state", + type: types.string + }, + command: { + name: "/service/openhab/in/tradfri_0100_gwb8d7af2b448f_65545_brightness/command", + type: types.string + }, + defaultValue: "0" } }, //Kuechen-Floalts @@ -345,44 +457,6 @@ const config : Config = { } ]) }, - artnet: { - name: "Artnet", - position: [535,480], - icon: mdi("spotlight"), - iconColor: ({artnet}) => - ({ - off: hex("#000000"), - yellow: hex("#F0DF10"), - red: hex("#FF0000"), - purple: hex("#FF00FF"), - green: hex("#00FF00"), - cycle: rainbow - })[artnet], - ui: [ - { - type: "toggle", - text: "An/Aus", - topic: "artnet", - on: "cycle", - toggled: val => val != "off", - icon: mdi("power") - }, - { - type: "dropDown", - text: "Farbe", - topic: "artnet", - options: { - yellow: "Gelb", - red: "Rot", - purple: "Pink", - green: "GrĂ¼n", - cycle: "Farbwechsel" - }, - enableCondition: val => val != "off", - icon: mdi("palette") - } - ] - }, onkyo: { name: "Onkyo", position: [350, 650], @@ -395,7 +469,7 @@ const config : Config = { text: "Power", icon: mdi("power"), topic: "onkyo_power", - enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected" + enableCondition: ({ onkyo_connection }) => onkyo_connection == "connected" }, { type: "section", @@ -408,14 +482,14 @@ const config : Config = { min: 0, max: 50, icon: mdi("volume-high"), - enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected" + enableCondition: ({ onkyo_connection }) => onkyo_connection == "connected" }, { type: "toggle", text: "Mute", topic: "onkyo_mute", icon: mdi("volume-off"), - enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected" + enableCondition: ({ onkyo_connection }) => onkyo_connection == "connected" }, { type: "section", @@ -433,7 +507,7 @@ const config : Config = { front: "Front HDMI" }, icon: mdi("usb"), - enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected" + enableCondition: ({ onkyo_connection }) => onkyo_connection == "connected" }, { type: "dropDown", @@ -452,7 +526,7 @@ const config : Config = { ponyville: "Ponyville FM" }, icon: mdi("radio"), - enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected" && state.onkyo_inputs.internal == "netzwerk" + enableCondition: (state) => state.onkyo_connection == "connected" && state.onkyo_inputs == "netzwerk" }, { type: "section", @@ -582,8 +656,8 @@ const config : Config = { ui: [ { type: "toggle", - on: 50, - off: 0, + on: "50", + off: "0", toggled: n => parseInt(n) > 0, topic: "kitchen_light_brightness", text: "Ein/Ausschalten", @@ -704,8 +778,8 @@ const config : Config = { ui: [ { type: "toggle", - on: 50, - off: 0, + on: "50", + off: "0", toggled: n => parseInt(n) > 0, topic: "kitchen_sink_light_brightness", text: "Ein/Ausschalten", diff --git a/config/utils.js b/config/utils.js index e3d4169..5ebd526 100644 --- a/config/utils.js +++ b/config/utils.js @@ -1,43 +1,44 @@ // @flow import type { ControlUI } from "config/flowtypes"; import { mdi } from "config/icon"; +import * as types from "config/types"; export const esper_topics = (chip_id: string, name: string) => ({ [ `esper_${name}_version` ]: { - state: `/service/esper/${chip_id}/info`, - command: "", - defaultValue: "UNKNOWN", - values: {}, - type: msg => JSON.parse(msg.toString()).version.esper + state: { + name: `/service/esper/${chip_id}/info`, + type: types.json("version.esper") + }, + defaultValue: "UNKNOWN" }, [ `esper_${name}_ip` ]: { - state: `/service/esper/${chip_id}/info`, - command: "", - defaultValue: "UNKNOWN", - values: {}, - type: msg => JSON.parse(msg.toString()).network.ip + state: { + name: `/service/esper/${chip_id}/info`, + type: types.json("network.ip") + }, + defaultValue: "UNKNOWN" }, [ `esper_${name}_rssi` ]: { - state: `/service/esper/${chip_id}/info`, - command: "", - defaultValue: "UNKNOWN", - values: {}, - type: msg => JSON.parse(msg.toString()).wifi.rssi + state: { + name: `/service/esper/${chip_id}/info`, + type: types.json("wifi.rssi") + }, + defaultValue: "UNKNOWN" }, [ `esper_${name}_uptime` ]: { - state: `/service/esper/${chip_id}/info`, - command: "", - defaultValue: "UNKNOWN", - values: {}, - type: msg => new Date(JSON.parse(msg.toString()).time.startup * 1000) + state: { + name: `/service/esper/${chip_id}/info`, + type: msg => new Date(JSON.parse(msg.toString()).time.startup * 1000) .toLocaleString() + }, + defaultValue: "UNKNOWN", }, [ `esper_${name}_device` ]: { - state: `/service/esper/${chip_id}/info`, - command: "", - defaultValue: "UNKNOWN", - values: {}, - type: msg => JSON.parse(msg.toString()).device + state: { + name: `/service/esper/${chip_id}/info`, + type: types.json("device") + }, + defaultValue: "UNKNOWN" } }); @@ -46,16 +47,26 @@ export const floalt = { brightness: (light_id: string) => `floalt_${light_id}_brightness`, topics: (light_id: string) => ({ [ `floalt_${light_id}_color` ]: { - state: `/service/openhab/out/tradfri_0220_gwb8d7af2b448f_${light_id}_color_temperature/state`, - command: `/service/openhab/in/tradfri_0220_gwb8d7af2b448f_${light_id}_color_temperature/command`, - defaultValue: "0", - values: {} + state: { + name: `/service/openhab/out/tradfri_0220_gwb8d7af2b448f_${light_id}_color_temperature/state`, + type: types.string + }, + command: { + name: `/service/openhab/in/tradfri_0220_gwb8d7af2b448f_${light_id}_color_temperature/command`, + type: types.string + }, + defaultValue: "0" }, [ `floalt_${light_id}_brightness` ]: { - state: `/service/openhab/out/tradfri_0220_gwb8d7af2b448f_${light_id}_brightness/state`, - command: `/service/openhab/in/tradfri_0220_gwb8d7af2b448f_${light_id}_brightness/command`, - defaultValue: "0", - values: {} + state: { + name: `/service/openhab/out/tradfri_0220_gwb8d7af2b448f_${light_id}_brightness/state`, + type: types.string + }, + command: { + name: `/service/openhab/in/tradfri_0220_gwb8d7af2b448f_${light_id}_brightness/command`, + type: types.string + }, + defaultValue: "0" } }) } @@ -65,16 +76,18 @@ export const tradfri_remote = { low: (remote_id: string) => `tradfri_remote_${remote_id}_low`, topics: (remote_id: string) => ({ [ `tradfri_remote_${remote_id}_level` ]: { - state: `/service/openhab/out/tradfri_0830_gwb8d7af2b448f_${remote_id}_battery_level/state`, - command: "", - defaultValue: "0", - values: {} + state: { + name: `/service/openhab/out/tradfri_0830_gwb8d7af2b448f_${remote_id}_battery_level/state`, + type: types.string + }, + defaultValue: "0" }, [ `tradfri_remote_${remote_id}_low` ]: { - state: `/service/openhab/out/tradfri_0830_gwb8d7af2b448f_${remote_id}_battery_low/state`, - command: "", - defaultValue: "OFF", - values: { true: "ON", false: "OFF" } + state: { + name: `/service/openhab/out/tradfri_0830_gwb8d7af2b448f_${remote_id}_battery_low/state`, + type: types.option({ ON: "true", OFF: "false" }) + }, + defaultValue: "false", } }) } diff --git a/src/components/App.js b/src/components/App.js index 7d6fb2f..d11a56b 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -18,9 +18,6 @@ import ControlMap from "components/ControlMap"; import TopBar from "components/TopBar"; import UiItemList from "components/UiItemList"; -import keyOf from "utils/keyOf"; -import { toRawIcon } from "config/icon"; - import connectMqtt from "../connectMqtt"; export type AppProps = { @@ -31,7 +28,7 @@ export type AppState = { selectedControl: ?Control, drawerOpened: boolean, mqttState: State, - mqttSend: (topic: string, value: Actual) => void, + mqttSend: (topic: string, value: Buffer) => void, mqttConnected: boolean, }; @@ -41,16 +38,15 @@ class App extends React.PureComponent { this.state = { selectedControl: null, drawerOpened: false, - mqttState: mapValues(this.topics, (topic) => ({ - actual: topic.defaultValue, - internal: keyOf(topic.values, topic.defaultValue) - })), + mqttState: mapValues(this.topics, (topic) => topic.defaultValue), mqttSend: connectMqtt(props.config.space.mqtt, { onMessage: this.receiveMessage.bind(this), onConnect: () => this.setState({ mqttConnected: true }), onReconnect: () => this.setState({ mqttConnected: false }), onDisconnect: () => this.setState({ mqttConnected: false }), - subscribe: map(this.topics, (x) => x.state) + subscribe: map( + filter(keys(this.topics), (x) => this.topics[x].state != null), + (x) => this.topics[x].state.name) }), mqttConnected: false }; @@ -77,23 +73,23 @@ class App extends React.PureComponent { }); } - receiveMessage(rawTopic: string, message: Object) { + receiveMessage(rawTopic: string, message: Buffer) { const topics = filter( keys(this.topics), - (k) => this.topics[k].state === rawTopic + (k) => this.topics[k].state != null && + this.topics[k].state.name === rawTopic ); if (topics.length === 0) { return; } for (let i in topics) { + // TODO: Remove FlowFixMe const topic = topics[i]; - const parseValue = this.topics[topic].type; + // $FlowFixMe + const parseValue = this.topics[topic].state.type; const val = parseValue == null ? message.toString() : parseValue(message); this.setState({mqttState: Object.assign({}, merge(this.state.mqttState, - { [topic]: { - actual: val, - internal: keyOf(this.topics[topic].values, val) || val - }}))}); + { [topic]: val}))}); } } @@ -105,15 +101,15 @@ class App extends React.PureComponent { this.setState({drawerOpened: false}); } - changeState(topic: string, value: Actual) { - const rawTopic = this.topics[topic].command; - if (rawTopic == null) { + changeState(topic: string, value: string) { + if (this.topics[topic].command == null) { return; } - this.state.mqttSend( - rawTopic, - String(this.topics[topic].values[value] || value) - ); + 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)); } render() { @@ -127,8 +123,7 @@ class App extends React.PureComponent { control={this.state.selectedControl} onCloseRequest={this.closeDrawer.bind(this)} icon={this.state.selectedControl == null ? null : - toRawIcon(this.state.selectedControl.icon, - this.state.mqttState)} + this.state.selectedControl.icon(this.state.mqttState)} > {this.state.selectedControl == null || { } createLeafletIcon(control: Control) { - const icon = toRawIcon(control.icon, this.props.state); + const icon = control.icon(this.props.state); const iconClass = `${icon} mdi-36px`; return divIcon({ iconSize: point(36, 36), @@ -61,10 +59,8 @@ export default class ControlMap extends React.PureComponent { } iconColor(control: Control): string { - const ints = mapValues(this.props.state, (x) => x.internal || x.actual); - const acts = mapValues(this.props.state, (x) => x.actual); if (control.iconColor != null) { - return control.iconColor(ints, acts, this.props.state); + return control.iconColor(this.props.state); } return "#000"; } diff --git a/src/components/SideBar.js b/src/components/SideBar.js index 1f30932..eff2c34 100644 --- a/src/components/SideBar.js +++ b/src/components/SideBar.js @@ -8,7 +8,7 @@ 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 { renderIcon } from "config/icon"; import type { RawIcon } from "config/icon"; import type { Control } from "config/flowtypes"; @@ -57,7 +57,7 @@ class SideBar extends React.PureComponent { {this.props.icon == null - || renderRawIcon(this.props.icon, "mdi-36px")} + || renderIcon(this.props.icon, "mdi-36px")} {this.props.control == null || this.props.control.name} diff --git a/src/components/UiItemList/UiItem.js b/src/components/UiItemList/UiItem.js index de70edc..aadfd83 100644 --- a/src/components/UiItemList/UiItem.js +++ b/src/components/UiItemList/UiItem.js @@ -21,12 +21,11 @@ import type { } from "config/flowtypes"; import keyOf from "utils/keyOf"; -import { getInternals, getActuals } from "utils/state"; type UiItemProps = { item: I, state: State, - onChangeState: (topic: string, nextState: Actual) => void + onChangeState: (topic: string, nextState: string) => void }; // eslint-disable-next-line flowtype/no-weak-types @@ -54,9 +53,7 @@ export default class UiItem typeof this.props.item.enableCondition == "function") { const enableCondition = this.props.item.enableCondition; const state = this.props.state; - const internals = getInternals(state); - const actuals = getActuals(state); - return enableCondition(internals, actuals, state); + return enableCondition(state); } else { return true; } @@ -68,7 +65,7 @@ export class UiControl extends UiItem { super(props); } - changeState(next: Actual) { + changeState(next: string) { if (this.props.item.topic == null) { throw new Error( `Missing topic in ${this.props.item.type} "${this.props.item.text}"` @@ -93,19 +90,6 @@ export class UiControl extends UiItem { } return value; } - - isEnabled() { - if (Object.keys(this.props.item).includes("enableCondition") && - // $FlowFixMe - typeof this.props.item.enableCondition == "function") { - const enableCondition = this.props.item.enableCondition; - const value = this.getValue(); - return enableCondition( - value.internal || value.actual, value.actual, this.props.state); - } else { - return true; - } - } } export class Toggle extends UiControl { @@ -113,9 +97,8 @@ export class Toggle extends UiControl { const value = this.getValue(); const control = this.props.item; const isChecked = control.toggled || - ((i, _a, _s) => i === (control.on || "on")); - const checked = isChecked( - value.internal || value.actual, value.actual, this.props.state); + ((i, _s) => i === (control.on || "on")); + const checked = isChecked(value, this.props.state); return checked; } @@ -145,7 +128,7 @@ export class Toggle extends UiControl { } export class DropDown extends UiControl { - runPrimaryAction = (next?: Actual) => { + runPrimaryAction = (next?: string) => { if (this.isEnabled()) { const control = this.props.item; const optionKeys = keys(control.options); @@ -172,7 +155,7 @@ export class DropDown extends UiControl { return ( {control.text} - } @@ -187,7 +170,7 @@ export class DropDown extends UiControl { export class Slider extends UiControl { runPrimaryAction = (e: ?Event, v: ?number) => { if (v != null) { - this.changeState(v); + this.changeState(v.toString()); } } @@ -195,7 +178,7 @@ export class Slider extends UiControl { return [ , @@ -245,7 +228,7 @@ export class Text extends UiControl { render() { return [ , - + ]; } } @@ -254,7 +237,7 @@ export class Progress extends UiControl { render() { const min = this.props.item.min || 0; const max = this.props.item.max || 100; - const val = parseFloat(this.getValue().internal || this.getValue().actual); + const val = parseFloat(this.getValue()); const value = val * 100 / max - min; return [ , diff --git a/src/components/UiItemList/index.js b/src/components/UiItemList/index.js index b0c3f16..a627377 100644 --- a/src/components/UiItemList/index.js +++ b/src/components/UiItemList/index.js @@ -12,7 +12,7 @@ import { Toggle, DropDown, Link, export type UiItemListProps = { controls: Array, state: State, - onChangeState: (topic: string, nextState: Actual) => void + onChangeState: (topic: string, nextState: string) => void }; export default class UiItemList extends React.PureComponent { @@ -34,7 +34,7 @@ export default class UiItemList extends React.PureComponent { {control.icon == null || - {renderIcon(control.icon, this.props.state, "mdi-24px")} + {renderIcon(control.icon(this.props.state), "mdi-24px")} } {this.renderControl(control)} diff --git a/src/config/flowtypes.js b/src/config/flowtypes.js index 6e58f3b..6cccf3f 100644 --- a/src/config/flowtypes.js +++ b/src/config/flowtypes.js @@ -2,24 +2,20 @@ import type { Color } from "config/colors"; import type { Icon } from "config/icon"; -export type TopicType = (msg: Buffer) => any; +export type TopicType = (msg: Buffer) => string; + +export type StateCommand = { + name: string, + type: TopicType +} export type Topic = { - state: string, - command: string, - defaultValue: Actual, - values: Map, - type?: TopicType + state?: StateCommand, + command?: StateCommand, + defaultValue: string }; export type Topics = Map; -export type TopicDependentOption = ( - internal: Internal, actual: Actual, state: State - ) => T; -export type StateDependentOption = ( - internals: Map, actuals: Map, state: State - ) => T; - export interface UIControl { +type: string, +text: string, @@ -27,7 +23,7 @@ export interface UIControl { } export interface Enableable { - enableCondition?: TopicDependentOption + enableCondition?: (s: State) => boolean } export type UIToggle = $ReadOnly<{| @@ -35,10 +31,10 @@ export type UIToggle = $ReadOnly<{| text: string, topic: string, icon?: Icon, - enableCondition?: TopicDependentOption, - on?: Actual, - off?: Actual, - toggled?: TopicDependentOption + enableCondition?: (s: State) => boolean, + on?: string, + off?: string, + toggled?: (v: string, s: State) => boolean |}>; export type UIDropDown = $ReadOnly<{| @@ -46,8 +42,8 @@ export type UIDropDown = $ReadOnly<{| text: string, topic: string, icon?: Icon, - enableCondition?: TopicDependentOption, - options: Map, + enableCondition?: (s: State) => boolean, + options: Map, renderValue?: (value: string) => string |}>; @@ -56,7 +52,7 @@ export type UISlider = $ReadOnly<{| text: string, topic: string, icon?: Icon, - enableCondition?: TopicDependentOption, + enableCondition?: (s: State) => boolean, min?: number, max?: number, step?: number, @@ -72,7 +68,7 @@ export type UILink = $ReadOnly<{| type: "link", text: string, link: string, - enableCondition?: StateDependentOption, + enableCondition?: (s: State) => boolean, // TODO: check if both the following options are implemented icon?: Icon @@ -107,11 +103,7 @@ export type Control = { name: string, position: [number, number], icon: Icon, - iconColor?: ( - internals: Map, - actuals: Map, - state: State - ) => Color, + iconColor?: (state: State) => Color, ui: Array }; export type Controls = Map; diff --git a/src/config/icon.js b/src/config/icon.js index 524395e..e1ae9f8 100644 --- a/src/config/icon.js +++ b/src/config/icon.js @@ -1,11 +1,9 @@ // @flow import * as React from "react"; -import { getInternals, getActuals } from "utils/state"; export opaque type RawIcon: string = string; -export type Icon = (Map, Map, State) => - RawIcon; +export type Icon = (State) => RawIcon; export const raw_mdi = (name: string): RawIcon => { return `mdi ${name.split(" ").map((icon) => "mdi-".concat(icon)).join(" ")}`; @@ -13,47 +11,35 @@ export const raw_mdi = (name: string): RawIcon => { export const mdi = (icon: string) => () => raw_mdi(icon); -export const mdi_battery = (topic: string) => - (state: Map) => { - const rawval = state[topic]; - const val = parseInt(rawval); - if (isNaN(val)) { - return raw_mdi("battery-unknown"); - } else if (val > 95) { - return raw_mdi("battery"); - } else if (val > 85) { - return raw_mdi("battery-90"); - } else if (val > 75) { - return raw_mdi("battery-80"); - } else if (val > 65) { - return raw_mdi("battery-70"); - } else if (val > 55) { - return raw_mdi("battery-60"); - } else if (val > 45) { - return raw_mdi("battery-50"); - } else if (val > 35) { - return raw_mdi("battery-40"); - } else if (val > 25) { - return raw_mdi("battery-30"); - } else if (val > 15) { - return raw_mdi("battery-20"); - } else { - return raw_mdi("battery-10"); - } - }; - -export const toRawIcon = (icon: Icon, state: State): RawIcon => { - const internals: Map = getInternals(state); - const actuals: Map = getActuals(state); - return icon(internals, actuals, state); +export const mdi_battery = (topic: string) => (state: State) => { + const rawval = state[topic]; + const val = parseInt(rawval); + if (isNaN(val)) { + return raw_mdi("battery-unknown"); + } else if (val > 95) { + return raw_mdi("battery"); + } else if (val > 85) { + return raw_mdi("battery-90"); + } else if (val > 75) { + return raw_mdi("battery-80"); + } else if (val > 65) { + return raw_mdi("battery-70"); + } else if (val > 55) { + return raw_mdi("battery-60"); + } else if (val > 45) { + return raw_mdi("battery-50"); + } else if (val > 35) { + return raw_mdi("battery-40"); + } else if (val > 25) { + return raw_mdi("battery-30"); + } else if (val > 15) { + return raw_mdi("battery-20"); + } else { + return raw_mdi("battery-10"); + } }; -export const renderRawIcon = +export const renderIcon = (icon: RawIcon, extraClass?: string): React.Node => { return ; }; - -export const renderIcon = - (icon: Icon, state: State, extraClass?: string): React.Node => { - return renderRawIcon(toRawIcon(icon, state), extraClass); - }; diff --git a/src/config/types.js b/src/config/types.js index 8fe7c58..91bb28b 100644 --- a/src/config/types.js +++ b/src/config/types.js @@ -1,8 +1,21 @@ // @flow import type { TopicType } from "config/flowtypes"; +import at from "lodash/at"; + +export const string: TopicType = (msg: Buffer) => msg.toString(); -export const string: TopicType = (msg) => msg.toString(); export const json = (path: string, innerType?: TopicType): TopicType => { - const parseAgain = innerType == null ? (x) => x : innerType; - return (msg) => parseAgain(JSON.parse(msg.toString())[path]); + const parseAgain = innerType == null ? (x) => x.toString() : innerType; + return (msg) => parseAgain(Buffer.from( + at(JSON.parse(msg.toString()), path)[0].toString())); }; + +export type TypeOptionParam = { otherwise?: string, [string]: string }; +export const option = (values: TypeOptionParam): TopicType => { + // TODO: error + const defaultValue = values.otherwise != null ? values.otherwise : ""; + const mapVal = (x) => (values[x] != null ? values[x] : defaultValue); + return (x) => mapVal(x.toString()); +}; + +export const jsonArray = (msg: Buffer) => JSON.parse(msg.toString()).join(", "); diff --git a/src/utils/state.js b/src/utils/state.js deleted file mode 100644 index c68269c..0000000 --- a/src/utils/state.js +++ /dev/null @@ -1,8 +0,0 @@ -// @flow -import mapValues from "lodash/mapValues"; - -export const getInternals = (state: State): Map => - mapValues(state, (x) => x.internal || x.actual); - -export const getActuals = (state: State): Map => - mapValues(state, (x) => x.actual); diff --git a/types/types.js b/types/types.js index d0f09ba..5503e1a 100644 --- a/types/types.js +++ b/types/types.js @@ -7,26 +7,7 @@ declare type Classes = { classes: Map }; -declare type Internal = string; -declare type Actual = any; -declare type StateValue = { - internal: string, - actual: any -}; -declare type State = Map; - -//declare type State = { -// mqtt: ?any, -// uiOpened: ?string, - // A map of the actual state values for each topic. - // internal is the internal term for the value, - // that is equal to the key in the values section of that - // topic, for example given by: - // values: { off: "OFF", on: "ON" } - // and actual is the value of that or whatever is given by mqtt. -// values: Map, -// visibleLayers: Array -//}; +declare type State = Map; declare type Point = [number, number];