First commit
This commit is contained in:
commit
9d69521b26
14 changed files with 3636 additions and 0 deletions
5
README
Normal file
5
README
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# RZL Map
|
||||||
|
|
||||||
|
Die Config ist in src/config.js erreichbar.
|
||||||
|
|
||||||
|
Doku folgt. Bald™
|
||||||
27
index.html
Normal file
27
index.html
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.2.0/dist/leaflet.css" integrity="sha512-M2wvCLH6DSRazYeZRIm1JnYyh22purTM+FDB5CsyxtQJYeKq83arPe5wgbNmcFXGqiSH2XR8dT/fJISVA1r/zQ==" crossorigin=""/>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<style>
|
||||||
|
#content {
|
||||||
|
height: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.leaflet-container {
|
||||||
|
height: calc(100vh - 64px);
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#drawer_uiComponents {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script src="public/dist/main.js"></script>
|
||||||
|
</body>
|
||||||
32
package.json
Normal file
32
package.json
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"name": "reacttest",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "uwap <me@uwap.name>",
|
||||||
|
"description": "react",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --bail"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"leaflet": "^1.2.0",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"material-ui": "^0.18.7",
|
||||||
|
"mqtt": "^2.11.0",
|
||||||
|
"ramda": "^0.24.1",
|
||||||
|
"react": "^15.6.1",
|
||||||
|
"react-dom": "^15.6.1",
|
||||||
|
"react-leaflet": "^1.5.0",
|
||||||
|
"react-tap-event-plugin": "^2.0.1",
|
||||||
|
"redux": "^3.7.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.24.1",
|
||||||
|
"babel-core": "^6.25.0",
|
||||||
|
"babel-loader": "^7.1.1",
|
||||||
|
"babel-preset-es2015": "^6.24.1",
|
||||||
|
"babel-preset-react": "^6.24.1",
|
||||||
|
"flow": "^0.2.3",
|
||||||
|
"flow-bin": "^0.50.0",
|
||||||
|
"webpack": "^3.1.0"
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
2
rzl.svg
Normal file
2
rzl.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="571px" height="341px" version="1.1" content="<mxfile userAgent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36" version="7.1.6" editor="www.draw.io"><diagram id="99136bbf-b1d7-fc77-4446-920ab02a96c1" name="Page-1">3ZdNj5swEIZ/jY8rYRsIHBeatlK1p1Tq2YsdsGJw5DgL6a+vCSZ8RuLASnRzieed8dczYzAAx3n1Q5Fz9iYpEwA5tAL4G0AIutA1f7Vya5RdbdVCqji1QZ1w4H+ZFR2rXjlll0GgllJofh6KiSwKluiBRpSS5TDsKMVw1jNJ2UQ4JERM1T+c6qxRA7Tr9J+Mp1k7M/TDxvNOklOq5LWw8wGEj/df485JO5bd6CUjVJY9Ce8BjpWUumnlVcxEzbbF1vT7/sT7WLdihV7Uweblg4gra5d8X5i+tTDu22F1BwfgqMy4ZoczSWpvadJvtEznwljQNO1wTGlWPV0TfOzUVBCTOdPqZkKqYRG0tWPNskuE12pZLwnYtSKxyU8fI3cATMMyeMID/488kDPDAwZr8PC2x2MM5HGYekQgnKuQNYD42wcyUyEw/Cweu+3zgM4UiDt3YtAaQILtAcH+CAheCASuASTcHhA3HAIJF/JY4xXTrnZTPIY48NyBCWaA+GvwcCc8fgPkCzNFRPmHaaZ1k7SaGa8nz0SqxZF0cSRfHHl5FjlKsUmWHubxopU8sVgKqYxSyMJERkcuxEgigqeFMROTXGb0qE49N5fXV+vIOaX1NLOF8wmnB3reouOzxuMVTS8kv0CMQRQnGftKkFEwhrzsGbUK5OklZ/+yT05fCvACvt46fI3ZfdHdfb3PZrz/Bw==</diagram></mxfile>" style="background-color: rgb(255, 255, 255);"><defs/><g transform="translate(0.5,0.5)"><rect x="0" y="0" width="570" height="340" fill="#ffffff" stroke="#000000" pointer-events="none"/><rect x="0" y="0" width="200" height="180" fill="#ffffff" stroke="#000000" pointer-events="none"/><rect x="200" y="150" width="110" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"/><rect x="200" y="0" width="190" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"/><rect x="200" y="30" width="40" height="120" fill="#ffffff" stroke="#000000" pointer-events="none"/><rect x="350" y="60" width="40" height="110" fill="#ffffff" stroke="#000000" pointer-events="none"/><rect x="480" y="20" width="40" height="140" fill="#ffffff" stroke="#000000" pointer-events="none"/><rect x="30" y="230" width="480" height="60" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(495.5,53.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="8" height="82" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 8px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">T<div>a</div><div>r</div><div>d</div><div>i</div><div>s</div></div></div></foreignObject><text x="4" y="47" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text></switch></g><g transform="translate(272.5,83.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="34" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 36px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Küche</div></div></foreignObject><text x="17" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Küche</text></switch></g><g transform="translate(75.5,83.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="38" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 40px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">E-Ecke</div></div></foreignObject><text x="19" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">E-Ecke</text></switch></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
20
src/UiItems.js
Normal file
20
src/UiItems.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import Toggle from "material-ui/Toggle";
|
||||||
|
import Config from "./config";
|
||||||
|
import R from "ramda";
|
||||||
|
|
||||||
|
const onToggle = (topic, props, state) => (x, toggled) =>
|
||||||
|
state.mqtt.publish(Config.topics[topic].command,
|
||||||
|
toggled ? Config.topics[topic].values[R.propOr("on", "on", props)]
|
||||||
|
: Config.topics[topic].values[R.propOr("off", "off", props)]);
|
||||||
|
|
||||||
|
export const toggle = (props: Object) => (
|
||||||
|
<Toggle label={props.text}
|
||||||
|
toggled={
|
||||||
|
props.state.values[props.topic] ===
|
||||||
|
Config.topics[props.topic].values[R.propOr("on", "on", props)]
|
||||||
|
}
|
||||||
|
onToggle={onToggle(props.topic, props, props.state)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
37
src/appbar.js
Normal file
37
src/appbar.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import AppBar from "material-ui/AppBar";
|
||||||
|
import CircularProgress from "material-ui/CircularProgress";
|
||||||
|
import DoneIcon from "material-ui/svg-icons/action/done";
|
||||||
|
import IconMenu from "material-ui/IconMenu";
|
||||||
|
import IconButton from "material-ui/IconButton";
|
||||||
|
import MenuItem from "material-ui/MenuItem";
|
||||||
|
import { orange400, grey50 } from "material-ui/styles/colors";
|
||||||
|
|
||||||
|
const TopBarIndicatorMenu = (props: Object) => (
|
||||||
|
<IconMenu
|
||||||
|
iconButtonElement={
|
||||||
|
<IconButton style={{width:48, height:48, padding: 0}}
|
||||||
|
iconStyle={{width:48, height: 48}}
|
||||||
|
tooltip="Connected!">
|
||||||
|
<DoneIcon color={grey50} />
|
||||||
|
</IconButton>}
|
||||||
|
style={{width:48, height:48}}>
|
||||||
|
<MenuItem primaryText="Reconnect" />
|
||||||
|
</IconMenu>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const TopBarIndicator = (props: Object) => (
|
||||||
|
props.mqtt == null ? <CircularProgress size={48} color={grey50} />
|
||||||
|
: <TopBarIndicatorMenu {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const TopBar = (props: Object) => (
|
||||||
|
<AppBar title={props.title}
|
||||||
|
style={{background:orange400}}
|
||||||
|
iconElementLeft={<TopBarIndicator {...props} />}
|
||||||
|
className="nav"
|
||||||
|
/>);
|
||||||
|
|
||||||
|
export default TopBar;
|
||||||
126
src/config.js
Normal file
126
src/config.js
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
// @flow
|
||||||
|
const config : Config = {
|
||||||
|
topics: {
|
||||||
|
led_stahltraeger: {
|
||||||
|
state: "/service/openhab/out/pca301_ledstrips/state",
|
||||||
|
command: "/service/openhab/in/pca301_ledstrips/command",
|
||||||
|
value: "OFF", # defaultValue
|
||||||
|
values: { on: "ON", off: "OFF" }
|
||||||
|
},
|
||||||
|
snackbar: {
|
||||||
|
state: "/service/openhab/out/pca301_snackbar/state",
|
||||||
|
command: "/service/openhab/in/pca301_snackbar/command",
|
||||||
|
value: "OFF",
|
||||||
|
values: { on: "ON", off: "OFF" }
|
||||||
|
},
|
||||||
|
twinkle: {
|
||||||
|
state: "/service/openhab/out/pca301_twinkle/state",
|
||||||
|
command: "/service/openhab/in/pca301_twinkle/command",
|
||||||
|
value: "OFF",
|
||||||
|
values: { on: "ON", off: "OFF" }
|
||||||
|
},
|
||||||
|
flyfry: {
|
||||||
|
state: "/service/openhab/out/wifi_flyfry/state",
|
||||||
|
command: "/service/openhab/in/wifi_flyfry/command",
|
||||||
|
value: "OFF",
|
||||||
|
values: { on: "ON", off: "OFF" }
|
||||||
|
},
|
||||||
|
artnet: {
|
||||||
|
state: "/artnet/state",
|
||||||
|
command: "/artnet/push",
|
||||||
|
value: "blackout",
|
||||||
|
values: { off: "blackout", yellow: "yellow", purple: "purple",
|
||||||
|
blue: "blue", green: "green", red: "red", random: "random",
|
||||||
|
cycle: "cycle-random" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
controls: {
|
||||||
|
led_stahltrager: {
|
||||||
|
name: "LED Stahlträger",
|
||||||
|
position: [360, 80],
|
||||||
|
icon: "",
|
||||||
|
ui: [
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
text: "Stahlträger LED",
|
||||||
|
topic: "led_stahltraeger"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
snackbar: {
|
||||||
|
name: "Snackbar",
|
||||||
|
position: [550, 200],
|
||||||
|
icon: "",
|
||||||
|
ui: [
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
text: "Snackbar",
|
||||||
|
topic: "snackbar"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
twinkle: {
|
||||||
|
name: "Twinkle",
|
||||||
|
position: [500, 280],
|
||||||
|
icon: "",
|
||||||
|
ui: [
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
text: "Twinkle",
|
||||||
|
topic: "twinkle"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
flyfry: {
|
||||||
|
name: "Fliegenbratgerät",
|
||||||
|
position: [450, 320],
|
||||||
|
icon: "",
|
||||||
|
ui: [
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
text: "Fliegenbratgerät",
|
||||||
|
topic: "flyfry"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
artnet: {
|
||||||
|
name: "Artnet",
|
||||||
|
position: [550,150],
|
||||||
|
icon: "",
|
||||||
|
ui: [
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
text: "Gelb",
|
||||||
|
topic: "artnet",
|
||||||
|
on: "yellow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
text: "Rot",
|
||||||
|
topic: "artnet",
|
||||||
|
on: "red"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
text: "Pink",
|
||||||
|
topic: "artnet",
|
||||||
|
on: "purple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
text: "Grün",
|
||||||
|
topic: "artnet",
|
||||||
|
on: "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
text: "Cycle Random",
|
||||||
|
topic: "artnet",
|
||||||
|
on: "cycle"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
92
src/index.jsx
Normal file
92
src/index.jsx
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||||
|
import Drawer from 'material-ui/Drawer';
|
||||||
|
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||||
|
import { createStore } from "redux";
|
||||||
|
import { MQTT_CONNECT } from "./stateActions";
|
||||||
|
import connectMqtt from "./mqtt";
|
||||||
|
import AppBar from "./appbar";
|
||||||
|
import Toggle from "material-ui/Toggle";
|
||||||
|
import * as UiItems from "./UiItems.js";
|
||||||
|
import SpaceMap from "./map.js";
|
||||||
|
import R from "ramda";
|
||||||
|
import Config from "./config";
|
||||||
|
import { Toolbar, ToolbarGroup, ToolbarTitle } from "material-ui/Toolbar";
|
||||||
|
|
||||||
|
injectTapEventPlugin();
|
||||||
|
|
||||||
|
const appState : State = {
|
||||||
|
mqtt: null,
|
||||||
|
ui: null,
|
||||||
|
values: R.map(R.prop("value"), Config.topics)
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(appState.values);
|
||||||
|
|
||||||
|
const handleEvent = (state = appState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "CONNECT":
|
||||||
|
return R.merge(state, { mqtt: action.mqtt });
|
||||||
|
case "uiopen":
|
||||||
|
return R.merge(state, { ui: action.ui });
|
||||||
|
case "uiclose":
|
||||||
|
return R.merge(state, { ui: null });
|
||||||
|
case "mqtt_message":
|
||||||
|
console.log(action.topic + ": " + action.message.toString());
|
||||||
|
const val = (topic: string) =>
|
||||||
|
Config.topics[topic].parseState == null ?
|
||||||
|
action.message.toString() :
|
||||||
|
Config.topics[topic].parseState(action.message);
|
||||||
|
const keysToUpdate = R.keys(R.pickBy(val => val.state == action.topic,
|
||||||
|
Config.topics));
|
||||||
|
return R.mergeDeepRight(state, R.objOf("values", R.mergeAll(R.map(
|
||||||
|
k => R.objOf(k, val(k)), keysToUpdate))));
|
||||||
|
/*
|
||||||
|
return R.merge(state, R.objOf("topics", R.merge(state.topics,
|
||||||
|
R.map(R.merge(R.__, { value: val }),
|
||||||
|
R.pickBy(val => val.state == action.topic, Config.topics)))));
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = createStore(handleEvent);
|
||||||
|
|
||||||
|
const UiItem = (state) => (props) =>
|
||||||
|
UiItems[props.type](R.merge(props, {state:state}));
|
||||||
|
|
||||||
|
const renderUi = (state, key) => key != null && Config.controls[key] != null ?
|
||||||
|
R.map(UiItem(state), Config.controls[key].ui) : null;
|
||||||
|
|
||||||
|
const App = (state: State) => (
|
||||||
|
<div>
|
||||||
|
<MuiThemeProvider>
|
||||||
|
<div>
|
||||||
|
<AppBar title="RZL Map" {...state} />
|
||||||
|
<Drawer open={state.ui != null} openSecondary={true} disableSwipeToOpen={true}>
|
||||||
|
<Toolbar>
|
||||||
|
<ToolbarGroup firstChild={true}>
|
||||||
|
<ToolbarTitle text={
|
||||||
|
state.ui == null ? "" : Config.controls[state.ui].name}
|
||||||
|
style={{"marginLeft": 10}} />
|
||||||
|
</ToolbarGroup>
|
||||||
|
</Toolbar>
|
||||||
|
<div id="drawer_uiComponents">
|
||||||
|
{renderUi(state, state.ui)}
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
</MuiThemeProvider>
|
||||||
|
<SpaceMap width={640} height={400} image="rzl.svg" zoom={0.1}
|
||||||
|
store={store} state={state} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
store.subscribe(() => ReactDOM.render(<App {...store.getState()} />, document.getElementById("content")));
|
||||||
|
|
||||||
|
store.dispatch({type: null});
|
||||||
|
store.dispatch({type: "mqtt_message", topic: "/service/openhab/out/pca301_ledstrips/state", message: "ON"});
|
||||||
|
|
||||||
|
connectMqtt("ws://172.22.36.207:1884", store); // wss://mqtt.starletp9.de/mqtt", store);
|
||||||
29
src/map.js
Normal file
29
src/map.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { Map, ImageOverlay, Marker, Popup } from "react-leaflet";
|
||||||
|
import Leaflet from "leaflet";
|
||||||
|
import R from "ramda";
|
||||||
|
import Config from "./config";
|
||||||
|
|
||||||
|
// convert width/height coordinates to -height/width coordinates
|
||||||
|
const c = (p) => [-p[1], p[0]]
|
||||||
|
|
||||||
|
const Markers = (props) => R.values(R.mapObjIndexed((el,key) => (
|
||||||
|
<Marker position={c(el.position)} key={el.name}>
|
||||||
|
<Popup onOpen={() => props.store.dispatch({type: "uiopen", ui: key})}
|
||||||
|
onClose={() => props.store.dispatch({type: "uiclose"})}>
|
||||||
|
<span>{el.name}</span>
|
||||||
|
</Popup>
|
||||||
|
</Marker>
|
||||||
|
), R.propOr({}, "controls", Config)));
|
||||||
|
|
||||||
|
const SpaceMap = (props: Object) => (
|
||||||
|
<Map center={c([props.width / 2, props.height / 2])} zoom={props.zoom}
|
||||||
|
crs={Leaflet.CRS.Simple}>
|
||||||
|
<ImageOverlay url={props.image}
|
||||||
|
bounds={[c([0,0]), c([props.width, props.height])]} />
|
||||||
|
{Markers(props)}
|
||||||
|
</Map>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SpaceMap;
|
||||||
22
src/mqtt.js
Normal file
22
src/mqtt.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
// @flow
|
||||||
|
import mqtt from "mqtt";
|
||||||
|
import { MQTT_MESSAGE, MQTT_CONNECT, MQTT_DISCONNECT } from "./stateActions";
|
||||||
|
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: MQTT_CONNECT, mqtt: client
|
||||||
|
});
|
||||||
|
R.forEachObjIndexed(v =>
|
||||||
|
client.subscribe(v.state), Config.topics);
|
||||||
|
});
|
||||||
|
client.on("message", (topic, message) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: "mqtt_message", message: message, topic: topic
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
5
src/stateActions.js
Normal file
5
src/stateActions.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export const MQTT_DISCONNECT = "DISCONNECT";
|
||||||
|
export const MQTT_CONNECT = "CONNECT";
|
||||||
|
export const MQTT_MESSAGE = "MESSAGE";
|
||||||
35
types/types.js
Normal file
35
types/types.js
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
declare type Map<K,V> = { [K]: V };
|
||||||
|
|
||||||
|
declare type Topic = {
|
||||||
|
state: string,
|
||||||
|
command: string,
|
||||||
|
value: any,
|
||||||
|
values: Map<string,any>,
|
||||||
|
parseState?: (msg: Object) => any
|
||||||
|
};
|
||||||
|
declare type Topics = Map<string,Topic>;
|
||||||
|
|
||||||
|
declare type ControlUI = {
|
||||||
|
type: "toggle",
|
||||||
|
text: string,
|
||||||
|
topic: string
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type Control = {
|
||||||
|
name: string,
|
||||||
|
position: Array<number>,
|
||||||
|
icon: string,
|
||||||
|
ui: Array<ControlUI>
|
||||||
|
};
|
||||||
|
declare type Controls = Map<string,Control>;
|
||||||
|
|
||||||
|
declare type Config = {
|
||||||
|
topics: Topics,
|
||||||
|
controls: Controls
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type State = {
|
||||||
|
mqtt: ?any,
|
||||||
|
ui: ?string,
|
||||||
|
values: Map<string,any>
|
||||||
|
};
|
||||||
25
webpack.config.js
Normal file
25
webpack.config.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// webpack.config.js:
|
||||||
|
|
||||||
|
var path = require('path');
|
||||||
|
var webpack = require('webpack');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.jsx']
|
||||||
|
},
|
||||||
|
entry: [
|
||||||
|
path.resolve(__dirname, 'src/index.jsx')
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'public/dist'),
|
||||||
|
filename: 'main.js',
|
||||||
|
publicPath: 'dist/'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{ test: /\.css$/, loader: "style!css" },
|
||||||
|
{ test: /\.js(x)?$/, exclude: /node_modules/, loader: "babel-loader" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
devtool: 'source-map'
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue