From a33474d8937c06f1f0073ad6dfee1cd4748a26a1 Mon Sep 17 00:00:00 2001 From: uwap Date: Sun, 12 Nov 2017 15:52:24 +0100 Subject: [PATCH] Working mqtt setup --- config/rzl.js | 14 ++++----- css/styles.css | 5 ++++ package.json | 2 +- src/components/App.js | 41 +++++++++++++++++++++----- src/components/UiItemList.js | 29 +++++++++++++++++- src/connectMqtt.js | 57 ++++++++++++++++++++++++++++++++++++ src/mqtt.js | 35 ---------------------- src/utils/parseIconName.js | 2 +- yarn.lock | 2 +- 9 files changed, 134 insertions(+), 53 deletions(-) create mode 100644 src/connectMqtt.js delete mode 100644 src/mqtt.js diff --git a/config/rzl.js b/config/rzl.js index 2205e6d..85e4ee5 100644 --- a/config/rzl.js +++ b/config/rzl.js @@ -61,8 +61,8 @@ const config : Config = { onkyo_connection: { state: "/service/onkyo/connected", command: "", - defaultValue: 0, - values: { disconnected: 0, connecting: 1, connected: 2 }, + defaultValue: "0", + values: { disconnected: "0", connecting: "1", connected: "2" }, }, onkyo_power: { state: "/service/onkyo/status/system-power", @@ -279,7 +279,7 @@ const config : Config = { text: "Power", icon: "power", topic: "onkyo_power", - enableCondition: (a, b, state) => state.onkyo_connection == "connected" + enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected" }, { type: "section", @@ -292,14 +292,14 @@ const config : Config = { min: 0, max: 50, icon: "volume-high", - enableCondition: (a, b, state) => state.onkyo_connection == "connected" + enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected" }, { type: "toggle", text: "Mute", topic: "onkyo_mute", icon: "volume-off", - enableCondition: (a, b, state) => state.onkyo_connection == "connected" + enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected" }, { type: "section", @@ -316,7 +316,7 @@ const config : Config = { pult: "Pult" }, icon: "usb", - enableCondition: (a, b, state) => state.onkyo_connection == "connected" + enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected" }, { type: "dropDown", @@ -333,7 +333,7 @@ const config : Config = { somafm_lush: "Lush (SomaFM)" }, icon: "radio", - enableCondition: (a, b, state) => state.onkyo_connection == "connected" && state.onkyo_inputs == "netzwerk" + enableCondition: (a, b, state) => state.onkyo_connection.inernal == "connected" && state.onkyo_inputs.internal == "netzwerk" }, { type: "section", diff --git a/css/styles.css b/css/styles.css index c209e4e..b6703e2 100644 --- a/css/styles.css +++ b/css/styles.css @@ -20,3 +20,8 @@ body .leaflet-div-icon { width: auto; height: auto; } +.mdi:before { + background-clip: text; + -webkit-background-clip: text; + background: var(--before-background, transparent); +} diff --git a/package.json b/package.json index 9c92663..a0a9063 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "material-ui": "next", "material-ui-old": "npm:material-ui@latest", "mdi": "^2.0.46", - "mqtt": "^2.11.0", + "mqtt": "^2.14.0", "ramda": "^0.24.1", "react": "^16.0.0", "react-dom": "^16.0.0", diff --git a/src/components/App.js b/src/components/App.js index 1c79060..d502a8f 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -15,6 +15,8 @@ import UiItemList from "components/UiItemList"; import keyOf from "utils/keyOf"; import { controlGetIcon } from "utils/parseIconName"; +import connectMqtt from "../connectMqtt"; + export type AppProps = { config: Config }; @@ -22,7 +24,8 @@ export type AppProps = { export type AppState = { selectedControl: ?Control, drawerOpened: boolean, - mqttState: State + mqttState: State, + mqttSend: (topic: string, value: any) => void }; class App extends React.Component { @@ -34,7 +37,11 @@ class App extends React.Component { mqttState: _.mapValues(props.config.topics, (topic) => ({ actual: topic.defaultValue, internal: keyOf(topic.values, topic.defaultValue) - })) + })), + mqttSend: connectMqtt(props.config.space.mqtt, { + onMessage: this.receiveMessage.bind(this), + subscribe: _.map(props.config.topics, (x) => x.state) + }) }; } @@ -54,6 +61,23 @@ class App extends React.Component { }); } + receiveMessage(rawTopic: string, message: Object) { + const topic = _.findKey( + this.props.config.topics, + (v) => v.state === rawTopic + ); + if (topic == null) { + return; + } + const parseValue = this.props.config.topics[topic].parseState; + const value = parseValue == null ? message.toString() : parseValue(message); + this.setState({mqttState: _.merge(this.state.mqttState, + { [topic]: { + actual: value, + internal: keyOf(this.props.config.topics[topic].values, value) || value + }})}); + } + changeControl(control: ?Control = null) { this.setState({selectedControl: control, drawerOpened: control != null}); } @@ -63,11 +87,14 @@ class App extends React.Component { } changeState(topic: string, value: any) { - this.setState({mqttState: _.merge(this.state.mqttState, - { [topic]: { - actual: this.props.config.topics[topic].values[value], - internal: value - }})}); + const rawTopic = this.props.config.topics[topic].command; + if (rawTopic == null) { + return; + } + this.state.mqttSend( + rawTopic, + String(this.props.config.topics[topic].values[value] || value) + ); } render() { diff --git a/src/components/UiItemList.js b/src/components/UiItemList.js index 0cde9a9..4160ce5 100644 --- a/src/components/UiItemList.js +++ b/src/components/UiItemList.js @@ -16,6 +16,10 @@ import Select from "material-ui/Select"; import { MenuItem } from "material-ui/Menu"; import Button from "material-ui/Button"; +// TODO: Use something else +import Slider from "material-ui-old/Slider"; +import MuiThemeProvider from "material-ui-old/styles/MuiThemeProvider"; + export type UiItemListProps = { controls: Array, state: State, @@ -61,6 +65,9 @@ export default class UiItemList extends React.Component { case "link": { return this.renderLink(control); } + case "slider": { + return this.renderSlider(control); + } default: { throw new Error( `Unknown UI type "${control.type}" for "${control.text}" component` @@ -127,7 +134,7 @@ export default class UiItemList extends React.Component { return ( {control.text} - } @@ -159,4 +166,24 @@ export default class UiItemList extends React.Component { ); } + + renderSlider(control: ControlUI) { + const value = this.getValue(control); + return [ + , + + + + this.props.onChangeState(control.topic, newvalue) + } + style={{width: 100}} + /> + + ]; + } } diff --git a/src/connectMqtt.js b/src/connectMqtt.js new file mode 100644 index 0000000..d7b2f93 --- /dev/null +++ b/src/connectMqtt.js @@ -0,0 +1,57 @@ +// @flow +import mqtt from "mqtt"; + +// TODO: type mqtt.js + +export type MqttSettings = { + onReconnect?: (mqtt: Object) => void, + onDisconnect?: (mqtt: Object) => void, + onMessage?: (topic: string, message: Object) => void, + onMessageSent?: (topic: string, message: any) => void, + onConnect?: (mqtt: Object) => void, + subscribe?: Array +} + +export type MessageCallback = (topic: string, message: any) => void; + +export default function connectMqtt( + url: string, + settings: MqttSettings = {} +): MessageCallback { + const client = mqtt.connect(url); + client.on("connect", () => { + if (settings.subscribe != null) { + client.subscribe(settings.subscribe); + } + if (settings.onConnect != null) { + settings.onConnect(client); + } + }); + client.on("message", (topic, message) => { + if (settings.onMessage != null) { + settings.onMessage(topic, message); + } + }); + client.on("offline", () => { + if (settings.onDisconnect != null) { + settings.onDisconnect(client); + } + }); + client.on("close", () => { + if (settings.onDisconnect != null) { + settings.onDisconnect(client); + } + }); + client.on("reconnect", () => { + if (settings.onReconnect != null) { + settings.onReconnect(client); + } + }); + return (topic: string, message: any) => { + client.publish(topic, message, null, (error) => { + if (error == null && settings.onMessageSent != null) { + settings.onMessageSent(topic, message); + } + }); + }; +} diff --git a/src/mqtt.js b/src/mqtt.js deleted file mode 100644 index 6ec04e4..0000000 --- a/src/mqtt.js +++ /dev/null @@ -1,35 +0,0 @@ -// @flow -import mqtt from "mqtt"; -import { Actions } from "./state"; -import { Store } from "redux"; -import Config from "./config"; -import R from "ramda"; - -export default function connectMqtt(url: string, store: Store<*, *>) { - const client = mqtt.connect(url); - client.on("connect", () => { - store.dispatch({ - type: Actions.MQTT_CONNECT, payload: client - }); - R.forEachObjIndexed((v) => - client.subscribe(v.state), Config.topics); - }); - client.on("message", (topic, message) => { - store.dispatch({ - type: Actions.MQTT_MESSAGE, - payload: { - message: message, - topic: topic - } - }); - }); - client.on("offline", () => { - store.dispatch({ type: null }); - }); - client.on("close", () => { - store.dispatch({ type: null }); - }); - client.on("reconnect", () => { - store.dispatch({ type: null }); - }); -} diff --git a/src/utils/parseIconName.js b/src/utils/parseIconName.js index f4d172e..265d0a4 100644 --- a/src/utils/parseIconName.js +++ b/src/utils/parseIconName.js @@ -11,7 +11,7 @@ export const renderIcon = (name: string, extraClass?: string) => { }; export const controlGetIcon = (control: Control, state: State): string => { - return typeof control.icon !== "function" ? control.icon + return !_.isFunction(control.icon) ? control.icon : control.icon( _.mapValues(state, (x) => x.internal || x.actual), _.mapValues(state, (x) => x.actual), diff --git a/yarn.lock b/yarn.lock index 632662c..2880571 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3815,7 +3815,7 @@ mqtt-packet@^5.4.0: process-nextick-args "^1.0.7" safe-buffer "^5.1.0" -mqtt@^2.11.0: +mqtt@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-2.14.0.tgz#640b712e3c3c02ebe97882b109499bffc65f97a8" dependencies: