Browse Source
Added mobx, react, an mqttws. So far only displays connected status for 1 sprinklers device.update-deps
Alex Mikhalev
8 years ago
14 changed files with 436 additions and 3 deletions
@ -1,2 +1,3 @@
@@ -1,2 +1,3 @@
|
||||
/node_modules |
||||
npm-debug* |
||||
npm-debug* |
||||
/dist |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
{ |
||||
// Use IntelliSense to learn about possible Node.js debug attributes. |
||||
// Hover to view descriptions of existing attributes. |
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
||||
"version": "0.2.0", |
||||
"configurations": [ |
||||
{ |
||||
"type": "node", |
||||
"request": "launch", |
||||
"name": "Launch Program", |
||||
"program": "${workspaceRoot}/bin/www" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
{ |
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558 |
||||
// for the documentation about the tasks.json format |
||||
"version": "0.1.0", |
||||
"command": "npm", |
||||
"isShellCommand": true, |
||||
"showOutput": "always", |
||||
"suppressTaskName": true, |
||||
"tasks": [ |
||||
{ |
||||
"taskName": "install", |
||||
"args": ["install"] |
||||
}, |
||||
{ |
||||
"taskName": "update", |
||||
"args": ["update"] |
||||
}, |
||||
{ |
||||
"taskName": "start:dev", |
||||
"args": ["run", "start:dev"] |
||||
}, |
||||
{ |
||||
"taskName": "test", |
||||
"args": ["run", "test"] |
||||
} |
||||
] |
||||
} |
After Width: | Height: | Size: 216 KiB |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
import * as React from "react"; |
||||
import { observer } from "mobx-react"; |
||||
import { SprinklersDevice } from "./sprinklers"; |
||||
import "semantic-ui-css/semantic.min.css"; |
||||
import "app/style/app.css"; |
||||
import { Item } from "semantic-ui-react"; |
||||
import * as classNames from "classnames"; |
||||
|
||||
@observer |
||||
class Device extends React.Component<{ device: SprinklersDevice }, any> { |
||||
render() { |
||||
const {id, connected} = this.props.device; |
||||
return ( |
||||
<Item> |
||||
<Item.Image src={require("app/images/raspberry_pi.png")} /> |
||||
<Item.Content> |
||||
<Item.Header> |
||||
<span>Device </span><kbd>{id}</kbd> |
||||
</Item.Header> |
||||
<Item.Meta> |
||||
<span className={classNames({ |
||||
"device--connected": connected, |
||||
"device--disconnected": !connected |
||||
})}> |
||||
{connected ? "Connected" : "Disconnected"} |
||||
</span> |
||||
</Item.Meta> |
||||
</Item.Content> |
||||
</Item> |
||||
); |
||||
} |
||||
} |
||||
|
||||
@observer |
||||
export default class App extends React.Component<{ device: SprinklersDevice }, any> { |
||||
render() { |
||||
return <Item.Group divided><Device device={this.props.device} /></Item.Group> |
||||
} |
||||
} |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
import * as React from "react"; |
||||
import * as ReactDOM from "react-dom"; |
||||
|
||||
import App from "./App"; |
||||
import {MqttApiClient} from "./mqtt"; |
||||
|
||||
const client = new MqttApiClient(); |
||||
client.start(); |
||||
const device = client.getDevice("grinklers"); |
||||
|
||||
const container = document.createElement("div"); |
||||
document.body.appendChild(container); |
||||
|
||||
ReactDOM.render(<App device={device} />, container); |
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
import "paho-mqtt/mqttws31"; |
||||
/// <reference path="./paho-mqtt.d.ts" />
|
||||
import MQTT = Paho.MQTT; |
||||
|
||||
import { EventEmitter } from "events"; |
||||
import { SprinklersDevice, SprinklersApi } from "./sprinklers"; |
||||
|
||||
|
||||
export class MqttApiClient extends EventEmitter implements SprinklersApi { |
||||
client: MQTT.Client |
||||
|
||||
connected: boolean |
||||
|
||||
devices: { [prefix: string]: MqttSprinklersDevice } = {}; |
||||
|
||||
constructor() { |
||||
super(); |
||||
this.client = new Paho.MQTT.Client(location.hostname, 1884, MqttApiClient.newClientId()); |
||||
this.client.onMessageArrived = m => this.onMessageArrived(m); |
||||
this.client.onConnectionLost = e => this.onConnectionLost(e); |
||||
} |
||||
|
||||
static newClientId() { |
||||
return "sprinklers3-MqttApiClient-" + Math.round(Math.random() * 1000); |
||||
} |
||||
|
||||
start() { |
||||
console.log("connecting to mqtt with client id %s", this.client.clientId); |
||||
this.client.connect({ |
||||
onFailure: (e) => { |
||||
console.log("mqtt error: ", e.errorMessage); |
||||
}, |
||||
onSuccess: () => { |
||||
console.log("mqtt connected") |
||||
this.connected = true; |
||||
for (const prefix in this.devices) { |
||||
const device = this.devices[prefix]; |
||||
device.doSubscribe(); |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
getDevice(prefix: string): SprinklersDevice { |
||||
if (!this.devices[prefix]) { |
||||
const device = this.devices[prefix] = new MqttSprinklersDevice(this, prefix); |
||||
if (this.connected) { |
||||
device.doSubscribe(); |
||||
} |
||||
} |
||||
return this.devices[prefix]; |
||||
} |
||||
|
||||
removeDevice(prefix: string) { |
||||
const device = this.devices[prefix]; |
||||
if (!device) return; |
||||
device.doUnsubscribe(); |
||||
delete this.devices[prefix]; |
||||
} |
||||
|
||||
private onMessageArrived(m: MQTT.Message) { |
||||
// console.log("message arrived: ", m)
|
||||
for (const prefix in this.devices) { |
||||
const device = this.devices[prefix]; |
||||
device.onMessage(m); |
||||
} |
||||
} |
||||
|
||||
private onConnectionLost(e: MQTT.MQTTError) { |
||||
this.connected = false; |
||||
} |
||||
} |
||||
|
||||
class MqttSprinklersDevice extends SprinklersDevice { |
||||
readonly apiClient: MqttApiClient; |
||||
readonly prefix: string; |
||||
|
||||
constructor(apiClient: MqttApiClient, prefix: string) { |
||||
super(); |
||||
this.apiClient = apiClient; |
||||
this.prefix = prefix; |
||||
} |
||||
|
||||
private getSubscriptions() { |
||||
return [ |
||||
`${this.prefix}/connected` |
||||
]; |
||||
} |
||||
|
||||
doSubscribe() { |
||||
const c = this.apiClient.client; |
||||
this.getSubscriptions() |
||||
.forEach(filter => c.subscribe(filter, { qos: 1 })); |
||||
|
||||
} |
||||
|
||||
doUnsubscribe() { |
||||
const c = this.apiClient.client; |
||||
this.getSubscriptions() |
||||
.forEach(filter => c.unsubscribe(filter)); |
||||
} |
||||
|
||||
onMessage(m: MQTT.Message) { |
||||
const postfix = m.destinationName.replace(`${this.prefix}/`, ""); |
||||
if (postfix === m.destinationName) |
||||
return; |
||||
switch (postfix) { |
||||
case "connected": |
||||
this.connected = (m.payloadString == "true"); |
||||
console.log(`MqttSprinklersDevice with prefix ${this.prefix}: ${this.connected}`) |
||||
break; |
||||
default: |
||||
console.warn(`MqttSprinklersDevice recieved invalid message`, m) |
||||
} |
||||
} |
||||
|
||||
get id(): string { |
||||
return this.prefix; |
||||
} |
||||
} |
@ -0,0 +1,75 @@
@@ -0,0 +1,75 @@
|
||||
declare namespace Paho { |
||||
namespace MQTT { |
||||
interface MQTTError { errorCode: string, errorMessage: string } |
||||
interface WithInvocationContext { invocationContext: object } |
||||
interface ErrorWithInvocationContext extends MQTTError, WithInvocationContext {} |
||||
interface OnSubscribeSuccessParams extends WithInvocationContext { grantedQos: number } |
||||
type OnConnectionLostHandler = (error: MQTTError) => void; |
||||
type OnMessageHandler = (message: Message) => void; |
||||
interface ConnectionOptions { |
||||
timeout?: number; |
||||
userName?: string; |
||||
password?: string; |
||||
willMessage?: Message; |
||||
keepAliveInterval?: number; |
||||
cleanSession?: boolean; |
||||
useSSL?: boolean; |
||||
invocationContext?: object; |
||||
onSuccess?: (o: WithInvocationContext) => void; |
||||
mqttVersion?: number; |
||||
onFailure?: (e: ErrorWithInvocationContext) => void; |
||||
hosts?: Array<string>; |
||||
ports?: Array<number>; |
||||
} |
||||
interface SubscribeOptions { |
||||
qos?: number; |
||||
invocationContext?: object; |
||||
onSuccess?: (o: OnSubscribeSuccessParams) => void; |
||||
onFailure?: (e: ErrorWithInvocationContext) => void; |
||||
timeout?: number; |
||||
} |
||||
interface UnsubscribeOptions { |
||||
invocationContext?: object; |
||||
onSuccess?: (o: WithInvocationContext) => void; |
||||
onFailure?: (e: ErrorWithInvocationContext) => void; |
||||
timeout?: number; |
||||
} |
||||
class Client { |
||||
|
||||
constructor(host: string, port: number, path: string, clientId: string); |
||||
constructor(host: string, port: number, clientId: string); |
||||
constructor(hostUri: string, clientId: string); |
||||
|
||||
readonly clientId: string; |
||||
readonly host: string; |
||||
readonly path: string; |
||||
readonly port: number; |
||||
|
||||
onConnectionLost: OnConnectionLostHandler; |
||||
onMessageArrived: OnMessageHandler; |
||||
onMessageDelivered: OnMessageHandler; |
||||
|
||||
connect(connectionOptions?: ConnectionOptions); |
||||
disconnect(); |
||||
|
||||
getTraceLog(): Object[]; |
||||
startTrace(); |
||||
stopTrace(); |
||||
|
||||
send(message: Message); |
||||
subscribe(filter: string, subcribeOptions?: SubscribeOptions); |
||||
unsubscribe(filter: string, unsubcribeOptions?: UnsubscribeOptions); |
||||
} |
||||
|
||||
class Message { |
||||
constructor(payload: String | ArrayBuffer); |
||||
|
||||
destinationName: string; |
||||
readonly duplicate: boolean; |
||||
readonly payloadBytes: ArrayBuffer; |
||||
readonly payloadString: string; |
||||
qos: number; |
||||
retained: boolean; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
import { observable } from "mobx"; |
||||
|
||||
class Section { |
||||
@observable |
||||
name: string = "" |
||||
|
||||
@observable |
||||
state: boolean = false |
||||
} |
||||
|
||||
class TimeOfDay { |
||||
hour: number |
||||
minute: number |
||||
second: number |
||||
millisecond: number |
||||
|
||||
} |
||||
|
||||
enum Weekday { |
||||
Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday |
||||
} |
||||
|
||||
class Schedule { |
||||
times: TimeOfDay[] = []; |
||||
weekdays: Weekday[] = []; |
||||
from?: Date = null; |
||||
to?: Date = null; |
||||
} |
||||
|
||||
class ProgramItem { |
||||
section: number = -1; |
||||
// duration in milliseconds
|
||||
duration: number = 0; |
||||
} |
||||
|
||||
class Program { |
||||
@observable |
||||
name: string = "" |
||||
@observable |
||||
enabled: boolean = false |
||||
|
||||
@observable |
||||
schedule: Schedule = new Schedule() |
||||
|
||||
@observable |
||||
sequence: Array<ProgramItem> = []; |
||||
} |
||||
|
||||
export abstract class SprinklersDevice { |
||||
@observable |
||||
connected: boolean = false; |
||||
|
||||
@observable |
||||
sections: Array<Section> = []; |
||||
|
||||
@observable |
||||
programs: Array<Program> = []; |
||||
|
||||
abstract get id(): string; |
||||
} |
||||
|
||||
export interface SprinklersApi { |
||||
start(); |
||||
getDevice(id: string) : SprinklersDevice; |
||||
|
||||
removeDevice(id: string) |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
.device--connected { |
||||
color: #00FF00; |
||||
} |
||||
|
||||
.device--disconnected { |
||||
color: #FF0000; |
||||
} |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"sourceMap": true, |
||||
"jsx": "react", |
||||
"experimentalDecorators": true, |
||||
"target": "es5", |
||||
"typeRoots": ["node_modules/@types"] |
||||
} |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
const path = require("path"); |
||||
const webpack = require("webpack"); |
||||
const HtmlWebpackPlugin = require("html-webpack-plugin"); |
||||
|
||||
module.exports = { |
||||
entry: "./app/script/index.tsx", |
||||
devtool: "sourcemap", |
||||
output: { |
||||
path: path.resolve(__dirname, "dist"), |
||||
filename: "bundle.js" |
||||
}, |
||||
resolve: { |
||||
extensions: [".ts", ".tsx", ".js"], |
||||
alias: { |
||||
app: path.resolve("./app") |
||||
} |
||||
}, |
||||
module: { |
||||
rules: [ |
||||
{ test: /\.tsx?$/, loader: "awesome-typescript-loader" }, |
||||
{ test: /\.css$/, loader: "style-loader!css-loader" }, |
||||
{ test: /\.(ttf|eot|svg|woff(2)?|png|jpg)(\?[a-z0-9=&.]+)?$/, loader: "file-loader" } |
||||
] |
||||
}, |
||||
plugins: [ |
||||
new HtmlWebpackPlugin({ |
||||
title: "sprinklers3" |
||||
}) |
||||
] |
||||
}; |
Loading…
Reference in new issue