Working mqtt setup

This commit is contained in:
uwap 2017-11-12 15:52:24 +01:00
parent bcb35877c1
commit a33474d893
9 changed files with 134 additions and 53 deletions

View file

@ -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<AppProps & Classes, AppState> {
@ -34,7 +37,11 @@ class App extends React.Component<AppProps & Classes, AppState> {
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<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) {
this.setState({selectedControl: control, drawerOpened: control != null});
}
@ -63,11 +87,14 @@ class App extends React.Component<AppProps & Classes, AppState> {
}
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() {

View file

@ -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<ControlUI>,
state: State,
@ -61,6 +65,9 @@ export default class UiItemList extends React.Component<UiItemListProps> {
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<UiItemListProps> {
return (
<FormControl>
<InputLabel htmlFor={id}>{control.text}</InputLabel>
<Select value={value.internal}
<Select value={value.internal || value.actual}
onChange={(event) => this.changeDropDown(control, event.target.value)}
disabled={!this.isEnabled(control)}
input={<Input id={id} />}
@ -159,4 +166,24 @@ export default class UiItemList extends React.Component<UiItemListProps> {
</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
View 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);
}
});
};
}

View file

@ -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 });
});
}

View file

@ -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),