Working mqtt setup
This commit is contained in:
parent
bcb35877c1
commit
a33474d893
9 changed files with 134 additions and 53 deletions
|
|
@ -61,8 +61,8 @@ const config : Config = {
|
||||||
onkyo_connection: {
|
onkyo_connection: {
|
||||||
state: "/service/onkyo/connected",
|
state: "/service/onkyo/connected",
|
||||||
command: "",
|
command: "",
|
||||||
defaultValue: 0,
|
defaultValue: "0",
|
||||||
values: { disconnected: 0, connecting: 1, connected: 2 },
|
values: { disconnected: "0", connecting: "1", connected: "2" },
|
||||||
},
|
},
|
||||||
onkyo_power: {
|
onkyo_power: {
|
||||||
state: "/service/onkyo/status/system-power",
|
state: "/service/onkyo/status/system-power",
|
||||||
|
|
@ -279,7 +279,7 @@ const config : Config = {
|
||||||
text: "Power",
|
text: "Power",
|
||||||
icon: "power",
|
icon: "power",
|
||||||
topic: "onkyo_power",
|
topic: "onkyo_power",
|
||||||
enableCondition: (a, b, state) => state.onkyo_connection == "connected"
|
enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "section",
|
type: "section",
|
||||||
|
|
@ -292,14 +292,14 @@ const config : Config = {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 50,
|
max: 50,
|
||||||
icon: "volume-high",
|
icon: "volume-high",
|
||||||
enableCondition: (a, b, state) => state.onkyo_connection == "connected"
|
enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: "toggle",
|
||||||
text: "Mute",
|
text: "Mute",
|
||||||
topic: "onkyo_mute",
|
topic: "onkyo_mute",
|
||||||
icon: "volume-off",
|
icon: "volume-off",
|
||||||
enableCondition: (a, b, state) => state.onkyo_connection == "connected"
|
enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "section",
|
type: "section",
|
||||||
|
|
@ -316,7 +316,7 @@ const config : Config = {
|
||||||
pult: "Pult"
|
pult: "Pult"
|
||||||
},
|
},
|
||||||
icon: "usb",
|
icon: "usb",
|
||||||
enableCondition: (a, b, state) => state.onkyo_connection == "connected"
|
enableCondition: (a, b, state) => state.onkyo_connection.internal == "connected"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "dropDown",
|
type: "dropDown",
|
||||||
|
|
@ -333,7 +333,7 @@ const config : Config = {
|
||||||
somafm_lush: "Lush (SomaFM)"
|
somafm_lush: "Lush (SomaFM)"
|
||||||
},
|
},
|
||||||
icon: "radio",
|
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",
|
type: "section",
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,8 @@ body .leaflet-div-icon {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
.mdi:before {
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background: var(--before-background, transparent);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
"material-ui": "next",
|
"material-ui": "next",
|
||||||
"material-ui-old": "npm:material-ui@latest",
|
"material-ui-old": "npm:material-ui@latest",
|
||||||
"mdi": "^2.0.46",
|
"mdi": "^2.0.46",
|
||||||
"mqtt": "^2.11.0",
|
"mqtt": "^2.14.0",
|
||||||
"ramda": "^0.24.1",
|
"ramda": "^0.24.1",
|
||||||
"react": "^16.0.0",
|
"react": "^16.0.0",
|
||||||
"react-dom": "^16.0.0",
|
"react-dom": "^16.0.0",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import UiItemList from "components/UiItemList";
|
||||||
import keyOf from "utils/keyOf";
|
import keyOf from "utils/keyOf";
|
||||||
import { controlGetIcon } from "utils/parseIconName";
|
import { controlGetIcon } from "utils/parseIconName";
|
||||||
|
|
||||||
|
import connectMqtt from "../connectMqtt";
|
||||||
|
|
||||||
export type AppProps = {
|
export type AppProps = {
|
||||||
config: Config
|
config: Config
|
||||||
};
|
};
|
||||||
|
|
@ -22,7 +24,8 @@ export type AppProps = {
|
||||||
export type AppState = {
|
export type AppState = {
|
||||||
selectedControl: ?Control,
|
selectedControl: ?Control,
|
||||||
drawerOpened: boolean,
|
drawerOpened: boolean,
|
||||||
mqttState: State
|
mqttState: State,
|
||||||
|
mqttSend: (topic: string, value: any) => void
|
||||||
};
|
};
|
||||||
|
|
||||||
class App extends React.Component<AppProps & Classes, AppState> {
|
class App extends React.Component<AppProps & Classes, AppState> {
|
||||||
|
|
@ -34,7 +37,11 @@ class App extends React.Component<AppProps & Classes, AppState> {
|
||||||
mqttState: _.mapValues(props.config.topics, (topic) => ({
|
mqttState: _.mapValues(props.config.topics, (topic) => ({
|
||||||
actual: topic.defaultValue,
|
actual: topic.defaultValue,
|
||||||
internal: keyOf(topic.values, 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<AppProps & Classes, AppState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
changeControl(control: ?Control = null) {
|
||||||
this.setState({selectedControl: control, drawerOpened: control != null});
|
this.setState({selectedControl: control, drawerOpened: control != null});
|
||||||
}
|
}
|
||||||
|
|
@ -63,11 +87,14 @@ class App extends React.Component<AppProps & Classes, AppState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
changeState(topic: string, value: any) {
|
changeState(topic: string, value: any) {
|
||||||
this.setState({mqttState: _.merge(this.state.mqttState,
|
const rawTopic = this.props.config.topics[topic].command;
|
||||||
{ [topic]: {
|
if (rawTopic == null) {
|
||||||
actual: this.props.config.topics[topic].values[value],
|
return;
|
||||||
internal: value
|
}
|
||||||
}})});
|
this.state.mqttSend(
|
||||||
|
rawTopic,
|
||||||
|
String(this.props.config.topics[topic].values[value] || value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ import Select from "material-ui/Select";
|
||||||
import { MenuItem } from "material-ui/Menu";
|
import { MenuItem } from "material-ui/Menu";
|
||||||
import Button from "material-ui/Button";
|
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 = {
|
export type UiItemListProps = {
|
||||||
controls: Array<ControlUI>,
|
controls: Array<ControlUI>,
|
||||||
state: State,
|
state: State,
|
||||||
|
|
@ -61,6 +65,9 @@ export default class UiItemList extends React.Component<UiItemListProps> {
|
||||||
case "link": {
|
case "link": {
|
||||||
return this.renderLink(control);
|
return this.renderLink(control);
|
||||||
}
|
}
|
||||||
|
case "slider": {
|
||||||
|
return this.renderSlider(control);
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown UI type "${control.type}" for "${control.text}" component`
|
`Unknown UI type "${control.type}" for "${control.text}" component`
|
||||||
|
|
@ -127,7 +134,7 @@ export default class UiItemList extends React.Component<UiItemListProps> {
|
||||||
return (
|
return (
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<InputLabel htmlFor={id}>{control.text}</InputLabel>
|
<InputLabel htmlFor={id}>{control.text}</InputLabel>
|
||||||
<Select value={value.internal}
|
<Select value={value.internal || value.actual}
|
||||||
onChange={(event) => this.changeDropDown(control, event.target.value)}
|
onChange={(event) => this.changeDropDown(control, event.target.value)}
|
||||||
disabled={!this.isEnabled(control)}
|
disabled={!this.isEnabled(control)}
|
||||||
input={<Input id={id} />}
|
input={<Input id={id} />}
|
||||||
|
|
@ -159,4 +166,24 @@ export default class UiItemList extends React.Component<UiItemListProps> {
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderSlider(control: ControlUI) {
|
||||||
|
const value = this.getValue(control);
|
||||||
|
return [
|
||||||
|
<ListItemText primary={control.text} key="text" />,
|
||||||
|
<ListItemSecondaryAction key="action">
|
||||||
|
<MuiThemeProvider>
|
||||||
|
<Slider value={value.internal || value.actual}
|
||||||
|
min={control.min || 0}
|
||||||
|
max={control.max || 100}
|
||||||
|
step={control.step || 1}
|
||||||
|
onChange={
|
||||||
|
(_event, newvalue) =>
|
||||||
|
this.props.onChangeState(control.topic, newvalue)
|
||||||
|
}
|
||||||
|
style={{width: 100}}
|
||||||
|
/></MuiThemeProvider>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
57
src/connectMqtt.js
Normal file
57
src/connectMqtt.js
Normal file
|
|
@ -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<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
35
src/mqtt.js
35
src/mqtt.js
|
|
@ -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 });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -11,7 +11,7 @@ export const renderIcon = (name: string, extraClass?: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const controlGetIcon = (control: Control, state: State): string => {
|
export const controlGetIcon = (control: Control, state: State): string => {
|
||||||
return typeof control.icon !== "function" ? control.icon
|
return !_.isFunction(control.icon) ? control.icon
|
||||||
: control.icon(
|
: control.icon(
|
||||||
_.mapValues(state, (x) => x.internal || x.actual),
|
_.mapValues(state, (x) => x.internal || x.actual),
|
||||||
_.mapValues(state, (x) => x.actual),
|
_.mapValues(state, (x) => x.actual),
|
||||||
|
|
|
||||||
|
|
@ -3815,7 +3815,7 @@ mqtt-packet@^5.4.0:
|
||||||
process-nextick-args "^1.0.7"
|
process-nextick-args "^1.0.7"
|
||||||
safe-buffer "^5.1.0"
|
safe-buffer "^5.1.0"
|
||||||
|
|
||||||
mqtt@^2.11.0:
|
mqtt@^2.14.0:
|
||||||
version "2.14.0"
|
version "2.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-2.14.0.tgz#640b712e3c3c02ebe97882b109499bffc65f97a8"
|
resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-2.14.0.tgz#640b712e3c3c02ebe97882b109499bffc65f97a8"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue