diff --git a/app/script/App.tsx b/app/script/App.tsx
index aa9cc33..45325aa 100644
--- a/app/script/App.tsx
+++ b/app/script/App.tsx
@@ -2,7 +2,7 @@ import * as React from "react";
import { computed } from "mobx";
import DevTools from "mobx-react-devtools";
import { observer } from "mobx-react";
-import { SprinklersDevice, Section, Program } from "./sprinklers";
+import { SprinklersDevice, Section, Program, Duration } from "./sprinklers";
import { Item, Table, Header, Segment, Form, Input, DropdownItemProps } from "semantic-ui-react";
import FontAwesome = require("react-fontawesome");
import * as classNames from "classnames";
@@ -16,6 +16,9 @@ import "app/style/app.css";
@observer
class SectionTable extends React.PureComponent<{ sections: Section[] }, void> {
private static renderRow(section: Section, index: number) {
+ if (!section) {
+ return null;
+ }
const { name, state } = section;
return (
@@ -55,6 +58,25 @@ class SectionTable extends React.PureComponent<{ sections: Section[] }, void> {
}
}
+class DurationInput extends React.Component<{
+ duration: Duration,
+ onDurationChange?: (newDuration: Duration) => void;
+}, void> {
+ public render() {
+ const duration = this.props.duration;
+ return ;
+ }
+}
+
@observer
class RunSectionForm extends React.Component<{ sections: Section[] }, void> {
public render() {
@@ -63,26 +85,27 @@ class RunSectionForm extends React.Component<{ sections: Section[] }, void> {
-
+
;
}
-
@computed
private get sectionOptions(): DropdownItemProps[] {
- // return this.props.sections.map((s, i) => ({
- // text: s.name,
- // value: i,
- // }));
- return [];
+ return this.props.sections.map((s, i) => ({
+ text: s ? s.name : null,
+ value: i,
+ }));
}
}
@observer
class ProgramTable extends React.PureComponent<{ programs: Program[] }, void> {
private static renderRow(program: Program, i: number) {
+ if (!program) {
+ return null;
+ }
const { name, running } = program;
return (
@@ -155,8 +178,8 @@ class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void>
export default class App extends React.PureComponent<{ device: SprinklersDevice }, any> {
public render() {
return
-
-
+
+
;
}
}
diff --git a/app/script/mqtt.ts b/app/script/mqtt.ts
index 2367b96..df3a31a 100644
--- a/app/script/mqtt.ts
+++ b/app/script/mqtt.ts
@@ -4,7 +4,7 @@ import MQTT = Paho.MQTT;
import { EventEmitter } from "events";
import * as objectAssign from "object-assign";
import {
- SprinklersDevice, ISprinklersApi, Section, Program, IProgramItem, Schedule, ITimeOfDay, Weekday,
+ SprinklersDevice, ISprinklersApi, Section, Program, IProgramItem, Schedule, ITimeOfDay, Weekday, Duration,
} from "./sprinklers";
export class MqttApiClient extends EventEmitter implements ISprinklersApi {
@@ -23,6 +23,7 @@ export class MqttApiClient extends EventEmitter implements ISprinklersApi {
this.client = new MQTT.Client(location.hostname, 1884, MqttApiClient.newClientId());
this.client.onMessageArrived = (m) => this.onMessageArrived(m);
this.client.onConnectionLost = (e) => this.onConnectionLost(e);
+ // (this.client as any).trace = (m => console.log(m));
}
public start() {
@@ -65,7 +66,15 @@ export class MqttApiClient extends EventEmitter implements ISprinklersApi {
}
private onMessageArrived(m: MQTT.Message) {
- console.log("message arrived: ", m);
+ try {
+ this.processMessage(m);
+ } catch (e) {
+ console.error("error while processing mqtt message", e);
+ }
+ }
+
+ private processMessage(m: MQTT.Message) {
+ // console.log("message arrived: ", m);
const topicIdx = m.destinationName.indexOf("/"); // find the first /
const prefix = m.destinationName.substr(0, topicIdx); // assume prefix does not contain a /
const topic = m.destinationName.substr(topicIdx + 1);
@@ -197,10 +206,15 @@ function scheduleFromJSON(json: IScheduleJSON): Schedule {
return sched;
}
+interface IProgramItemJSON {
+ section: number;
+ duration: number;
+}
+
interface IProgramJSON {
name: string;
enabled: boolean;
- sequence: IProgramItem[];
+ sequence: IProgramItemJSON[];
sched: IScheduleJSON;
}
@@ -210,18 +224,26 @@ class MqttProgram extends Program {
this.running = (payload === "true");
} else if (topic == null) {
const json = JSON.parse(payload) as Partial;
- if (json.name != null) {
- this.name = json.name;
- }
- if (json.enabled != null) {
- this.enabled = json.enabled;
- }
- if (json.sequence != null) {
- this.sequence = json.sequence;
- }
- if (json.sched != null) {
- this.schedule = scheduleFromJSON(json.sched);
- }
+ this.updateFromJSON(json);
+ }
+ }
+
+ public updateFromJSON(json: Partial) {
+ if (json.name != null) {
+ this.name = json.name;
+ }
+ if (json.enabled != null) {
+ this.enabled = json.enabled;
+ }
+ if (json.sequence != null) {
+ // tslint:disable:object-literal-sort-keys
+ this.sequence = json.sequence.map((item) => ({
+ section: item.section,
+ duration: Duration.fromSeconds(item.duration),
+ }));
+ }
+ if (json.sched != null) {
+ this.schedule = scheduleFromJSON(json.sched);
}
}
}
diff --git a/app/script/sprinklers.ts b/app/script/sprinklers.ts
index e114cf7..d80fed0 100644
--- a/app/script/sprinklers.ts
+++ b/app/script/sprinklers.ts
@@ -26,11 +26,29 @@ export class Schedule {
public to?: Date = null;
}
+export class Duration {
+ public static fromSeconds(seconds: number): Duration {
+ return new Duration(Math.floor(seconds / 60), seconds % 60);
+ }
+
+ public minutes: number = 0;
+ public seconds: number = 0;
+
+ constructor(minutes: number, seconds: number) {
+ this.minutes = minutes;
+ this.seconds = seconds;
+ }
+
+ public toSeconds(): number {
+ return this.minutes * 60 + this.seconds;
+ }
+}
+
export interface IProgramItem {
// the section number
section: number;
- // duration in seconds
- duration: number;
+ // duration of the run
+ duration: Duration;
}
export class Program {
diff --git a/app/style/app.css b/app/style/app.css
index 3ce7729..b564f42 100644
--- a/app/style/app.css
+++ b/app/style/app.css
@@ -33,4 +33,13 @@
.section--state-false {
+}
+
+.durationInput--minutes,
+.durationInput--seconds {
+ width: 100px;
+}
+
+.durationInput--colon {
+ line-height: 38px;
}
\ No newline at end of file
diff --git a/package.json b/package.json
index ae1d132..e480b31 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
"source-map-loader": "^0.2.1",
"style-loader": "^0.17.0",
"ts-loader": "^2.0.3",
+ "tslint": "^5.2.0",
"typescript": "^2.3.1",
"webpack": "^2.4.1",
"webpack-dev-server": "^2.4.4"