diff --git a/app/script/App.tsx b/app/script/App.tsx
deleted file mode 100644
index 3e95878..0000000
--- a/app/script/App.tsx
+++ /dev/null
@@ -1,287 +0,0 @@
-import * as React from "react";
-import {SyntheticEvent} from "react";
-import {computed} from "mobx";
-import DevTools from "mobx-react-devtools";
-import {observer} from "mobx-react";
-import {SprinklersDevice, Section, Program, Duration, Schedule} from "./sprinklers";
-import {Item, Table, Header, Segment, Form, Input, Button, DropdownItemProps, DropdownProps, Message} from "semantic-ui-react";
-import FontAwesome = require("react-fontawesome");
-import * as classNames from "classnames";
-
-import "semantic-ui-css/semantic.css";
-import "font-awesome/css/font-awesome.css";
-import "app/style/app.css";
-import {Message as UiMessage, UiStore} from "./ui";
-
-/* tslint:disable:object-literal-sort-keys */
-
-@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 (
-
- {"" + (index + 1)}
- {name}
- {state ?
- ( Irrigating)
- : "Not irrigating"}
-
-
- );
- }
-
- public render() {
- return (
-
-
- Sections
-
-
- #
- Name
- State
-
-
-
- {
- this.props.sections.map(SectionTable.renderRow)
- }
-
-
- );
- }
-}
-
-class DurationInput extends React.Component<{
- duration: Duration,
- onDurationChange?: (newDuration: Duration) => void;
-}, void> {
- public render() {
- const duration = this.props.duration;
- // const editing = this.props.onDurationChange != null;
- return ;
- }
-
- private onMinutesChange = (e, {value}) => {
- if (value.length === 0 || isNaN(value)) {
- return;
- }
- const newMinutes = parseInt(value, 10);
- this.props.onDurationChange(this.props.duration.withMinutes(newMinutes));
- }
-
- private onSecondsChange = (e, {value}) => {
- if (value.length === 0 || isNaN(value)) {
- return;
- }
- const newSeconds = parseInt(value, 10);
- this.props.onDurationChange(this.props.duration.withSeconds(newSeconds));
- }
-}
-
-@observer
-class RunSectionForm extends React.Component<{
- sections: Section[],
-}, {
- duration: Duration,
- section: number | "",
-}> {
- constructor() {
- super();
- this.state = {
- duration: new Duration(1, 1),
- section: "",
- };
- }
-
- public render() {
- const {section, duration} = this.state;
- return
-
-
-
-
- {/*Label must be to align it properly*/}
- Run
-
-
- ;
- }
-
- private onSectionChange = (e: SyntheticEvent, v: DropdownProps) => {
- this.setState({section: v.value as number});
- }
-
- private onDurationChange = (newDuration: Duration) => {
- this.setState({duration: newDuration});
- }
-
- private run = (e: SyntheticEvent) => {
- e.preventDefault();
- const section: Section = this.props.sections[this.state.section];
- console.log(`should run section ${section} for ${this.state.duration}`);
- section.run(this.state.duration)
- .then((a) => console.log("ran section", a))
- .catch((err) => console.error("error running section", err));
- }
-
- private get isValid(): boolean {
- return typeof this.state.section === "number";
- }
-
- @computed
- private get sectionOptions(): DropdownItemProps[] {
- return this.props.sections.map((s, i) => ({
- text: s ? s.name : null,
- value: i,
- }));
- }
-}
-
-@observer
-class ScheduleView extends React.PureComponent<{ schedule: Schedule }, void> {
- public render() {
- return (
- {JSON.stringify(this.props.schedule)}
- );
- }
-}
-
-@observer
-class ProgramTable extends React.PureComponent<{ programs: Program[] }, void> {
- private static renderRow(program: Program, i: number): JSX.Element[] {
- if (!program) {
- return null;
- }
- const {name, running, enabled, schedule, sequence} = program;
- return [
-
- {"" + (i + 1)}
- {name}
- {running ? "Running" : "Not running"}
- {enabled ? "Enabled" : "Not enabled"}
-
- ,
-
-
-
- {sequence.map((item) =>
- (- Section {item.section + 1 + ""} for
- {item.duration.minutes}M {item.duration.seconds}S
))}
-
-
-
-
- ,
- ];
- }
-
- public render() {
- return (
-
-
-
- Programs
-
-
- #
- Name
- Running?
- Enabled?
-
-
-
- {
- Array.prototype.concat.apply([], this.props.programs.map(ProgramTable.renderRow))
- }
-
-
- );
- }
-}
-
-const ConnectionState = ({connected}: { connected: boolean }) =>
-
-
-
- {connected ? "Connected" : "Disconnected"}
- ;
-
-@observer
-class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void> {
- public render() {
- const {id, connected, sections, programs} = this.props.device;
- return (
- -
- ("app/images/raspberry_pi.png")}/>
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-@observer
-class MessagesView extends React.PureComponent<{uiStore: UiStore}, void> {
- public render() {
- return
- {this.props.uiStore.messages.map(this.renderMessage)}
-
;
- }
-
- private renderMessage = (message: UiMessage, index: number) => {
- const {header, content, type} = message;
- return this.dismiss(index)}/>;
- }
-
- private dismiss(index: number) {
- this.props.uiStore.messages.splice(index, 1);
- }
-}
-
-@observer
-export default class App extends React.PureComponent<{ device: SprinklersDevice, uiStore: UiStore }, any> {
- public render() {
- return
-
-
-
- ;
- }
-}
diff --git a/app/script/components/App.tsx b/app/script/components/App.tsx
new file mode 100644
index 0000000..3c09f1e
--- /dev/null
+++ b/app/script/components/App.tsx
@@ -0,0 +1,22 @@
+import * as React from "react";
+import DevTools from "mobx-react-devtools";
+import {observer} from "mobx-react";
+import {SprinklersDevice} from "../sprinklers";
+import {Item} from "semantic-ui-react";
+import {UiStore} from "../ui";
+import {MessagesView, DeviceView} from ".";
+
+import "semantic-ui-css/semantic.css";
+import "font-awesome/css/font-awesome.css";
+import "app/style/app.css";
+
+@observer
+export default class App extends React.PureComponent<{ device: SprinklersDevice, uiStore: UiStore }, any> {
+ render() {
+ return
+
+
+
+ ;
+ }
+}
diff --git a/app/script/components/DeviceView.tsx b/app/script/components/DeviceView.tsx
new file mode 100644
index 0000000..a40aa15
--- /dev/null
+++ b/app/script/components/DeviceView.tsx
@@ -0,0 +1,43 @@
+import * as React from "react";
+import {observer} from "mobx-react";
+import {Item, Header} from "semantic-ui-react";
+import FontAwesome = require("react-fontawesome");
+import * as classNames from "classnames";
+
+import {SprinklersDevice} from "../sprinklers";
+import {SectionTable, RunSectionForm, ProgramTable} from ".";
+
+const ConnectionState = ({connected}: { connected: boolean }) =>
+
+
+
+ {connected ? "Connected" : "Disconnected"}
+ ;
+
+@observer
+export default class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void> {
+ render() {
+ const {id, connected, sections, programs} = this.props.device;
+ return (
+ -
+ ("app/images/raspberry_pi.png")}/>
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/app/script/components/DurationInput.tsx b/app/script/components/DurationInput.tsx
new file mode 100644
index 0000000..23f28ee
--- /dev/null
+++ b/app/script/components/DurationInput.tsx
@@ -0,0 +1,40 @@
+import * as React from "react";
+import {Duration} from "../sprinklers";
+import {Input} from "semantic-ui-react";
+
+export default class DurationInput extends React.Component<{
+ duration: Duration,
+ onDurationChange?: (newDuration: Duration) => void;
+}, void> {
+ render() {
+ const duration = this.props.duration;
+ // const editing = this.props.onDurationChange != null;
+ return ;
+ }
+
+ private onMinutesChange = (e, {value}) => {
+ if (value.length === 0 || isNaN(value)) {
+ return;
+ }
+ const newMinutes = parseInt(value, 10);
+ this.props.onDurationChange(this.props.duration.withMinutes(newMinutes));
+ }
+
+ private onSecondsChange = (e, {value}) => {
+ if (value.length === 0 || isNaN(value)) {
+ return;
+ }
+ const newSeconds = parseInt(value, 10);
+ this.props.onDurationChange(this.props.duration.withSeconds(newSeconds));
+ }
+}
diff --git a/app/script/components/MessagesView.tsx b/app/script/components/MessagesView.tsx
new file mode 100644
index 0000000..b50579a
--- /dev/null
+++ b/app/script/components/MessagesView.tsx
@@ -0,0 +1,24 @@
+import * as React from "react";
+import {observer} from "mobx-react";
+import {UiStore, Message as UiMessage} from "../ui";
+import {Message} from "semantic-ui-react";
+
+@observer
+export default class MessagesView extends React.PureComponent<{ uiStore: UiStore }, void> {
+ render() {
+ return
+ {this.props.uiStore.messages.map(this.renderMessage)}
+
;
+ }
+
+ private renderMessage = (message: UiMessage, index: number) => {
+ const {header, content, type} = message;
+ return this.dismiss(index)}/>;
+ }
+
+ private dismiss(index: number) {
+ this.props.uiStore.messages.splice(index, 1);
+ }
+}
diff --git a/app/script/components/ProgramTable.tsx b/app/script/components/ProgramTable.tsx
new file mode 100644
index 0000000..ca38080
--- /dev/null
+++ b/app/script/components/ProgramTable.tsx
@@ -0,0 +1,66 @@
+import * as React from "react";
+import {observer} from "mobx-react";
+import {Program, Schedule} from "../sprinklers";
+import {Table} from "semantic-ui-react";
+
+@observer
+export class ScheduleView extends React.PureComponent<{ schedule: Schedule }, void> {
+ render() {
+ return (
+ {JSON.stringify(this.props.schedule)}
+ );
+ }
+}
+
+@observer
+export default class ProgramTable extends React.PureComponent<{ programs: Program[] }, void> {
+ private static renderRow(program: Program, i: number): JSX.Element[] {
+ if (!program) {
+ return null;
+ }
+ const {name, running, enabled, schedule, sequence} = program;
+ return [
+
+ {"" + (i + 1)}
+ {name}
+ {running ? "Running" : "Not running"}
+ {enabled ? "Enabled" : "Not enabled"}
+
+ ,
+
+
+
+ {sequence.map((item) =>
+ (- Section {item.section + 1 + ""} for
+ {item.duration.minutes}M {item.duration.seconds}S
))}
+
+
+
+
+ ,
+ ];
+ }
+
+ render() {
+ return (
+
+
+
+ Programs
+
+
+ #
+ Name
+ Running?
+ Enabled?
+
+
+
+ {
+ Array.prototype.concat.apply([], this.props.programs.map(ProgramTable.renderRow))
+ }
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/app/script/components/RunSectionForm.tsx b/app/script/components/RunSectionForm.tsx
new file mode 100644
index 0000000..59e8a7f
--- /dev/null
+++ b/app/script/components/RunSectionForm.tsx
@@ -0,0 +1,68 @@
+import * as React from "react";
+import {SyntheticEvent} from "react";
+import {computed} from "mobx";
+import {observer} from "mobx-react";
+import {Duration, Section} from "../sprinklers";
+import {Segment, Header, Form, DropdownProps, DropdownItemProps} from "semantic-ui-react";
+import {DurationInput} from ".";
+
+@observer
+export default class RunSectionForm extends React.Component<{
+ sections: Section[],
+}, {
+ duration: Duration,
+ section: number | "",
+}> {
+ constructor() {
+ super();
+ this.state = {
+ duration: new Duration(1, 1),
+ section: "",
+ };
+ }
+
+ render() {
+ const {section, duration} = this.state;
+ return
+
+
+
+
+ {/*Label must be to align it properly*/}
+ Run
+
+
+ ;
+ }
+
+ private onSectionChange = (e: SyntheticEvent, v: DropdownProps) => {
+ this.setState({section: v.value as number});
+ }
+
+ private onDurationChange = (newDuration: Duration) => {
+ this.setState({duration: newDuration});
+ }
+
+ private run = (e: SyntheticEvent) => {
+ e.preventDefault();
+ const section: Section = this.props.sections[this.state.section];
+ console.log(`should run section ${section} for ${this.state.duration}`);
+ section.run(this.state.duration)
+ .then((a) => console.log("ran section", a))
+ .catch((err) => console.error("error running section", err));
+ }
+
+ private get isValid(): boolean {
+ return typeof this.state.section === "number";
+ }
+
+ @computed
+ private get sectionOptions(): DropdownItemProps[] {
+ return this.props.sections.map((s, i) => ({
+ text: s ? s.name : null,
+ value: i,
+ }));
+ }
+}
\ No newline at end of file
diff --git a/app/script/components/SectionTable.tsx b/app/script/components/SectionTable.tsx
new file mode 100644
index 0000000..d7d762b
--- /dev/null
+++ b/app/script/components/SectionTable.tsx
@@ -0,0 +1,54 @@
+import * as React from "react";
+import {observer} from "mobx-react";
+import * as classNames from "classnames";
+import {Table} from "semantic-ui-react";
+import FontAwesome = require("react-fontawesome");
+
+import {Section} from "../sprinklers";
+
+/* tslint:disable:object-literal-sort-keys */
+
+@observer
+export default class SectionTable extends React.PureComponent<{ sections: Section[] }, void> {
+ private static renderRow(section: Section, index: number) {
+ if (!section) {
+ return null;
+ }
+ const {name, state} = section;
+ return (
+
+ {"" + (index + 1)}
+ {name}
+ {state ?
+ ( Irrigating)
+ : "Not irrigating"}
+
+
+ );
+ }
+
+ render() {
+ return (
+
+
+ Sections
+
+
+ #
+ Name
+ State
+
+
+
+ {
+ this.props.sections.map(SectionTable.renderRow)
+ }
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/app/script/components/index.ts b/app/script/components/index.ts
new file mode 100644
index 0000000..1476b6b
--- /dev/null
+++ b/app/script/components/index.ts
@@ -0,0 +1,7 @@
+export {default as App} from "./App";
+export {default as DeviceView} from "./DeviceView";
+export {default as DurationInput} from "./DurationInput";
+export {default as MessagesView} from "./MessagesView";
+export {default as ProgramTable} from "./ProgramTable";
+export {default as RunSectionForm} from "./RunSectionForm";
+export {default as SectionTable} from "./SectionTable";
\ No newline at end of file
diff --git a/app/script/index.tsx b/app/script/index.tsx
index a9c5903..8527c91 100644
--- a/app/script/index.tsx
+++ b/app/script/index.tsx
@@ -2,7 +2,7 @@ import * as React from "react";
import * as ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader";
-import App from "./App";
+import App from "./components/App";
import { MqttApiClient } from "./mqtt";
import {Message, UiStore} from "./ui";
@@ -19,8 +19,8 @@ ReactDOM.render(
, rootElem);
if (module.hot) {
- module.hot.accept("./App", () => {
- const NextApp = require("./App").default;
+ module.hot.accept("./components/App", () => {
+ const NextApp = require("./components/App").default;
ReactDOM.render(
, rootElem);
diff --git a/app/script/mqtt.ts b/app/script/mqtt.ts
index 4c5778e..39a05c9 100644
--- a/app/script/mqtt.ts
+++ b/app/script/mqtt.ts
@@ -1,9 +1,9 @@
-import "paho-mqtt/mqttws31";
+import "paho-mqtt";
import MQTT = Paho.MQTT;
import {EventEmitter} from "events";
import {
- SprinklersDevice, ISprinklersApi, Section, Program, IProgramItem, Schedule, ITimeOfDay, Weekday, Duration,
+ SprinklersDevice, ISprinklersApi, Section, Program, Schedule, ITimeOfDay, Duration,
} from "./sprinklers";
import {checkedIndexOf} from "./utils";
import * as Promise from "bluebird";
@@ -13,11 +13,11 @@ export class MqttApiClient extends EventEmitter implements ISprinklersApi {
return "sprinklers3-MqttApiClient-" + Math.round(Math.random() * 1000);
}
- public client: MQTT.Client;
+ client: MQTT.Client;
- public connected: boolean;
+ connected: boolean;
- public devices: { [prefix: string]: MqttSprinklersDevice } = {};
+ devices: { [prefix: string]: MqttSprinklersDevice } = {};
constructor() {
super();
@@ -27,7 +27,7 @@ export class MqttApiClient extends EventEmitter implements ISprinklersApi {
// (this.client as any).trace = (m => console.log(m));
}
- public start() {
+ start() {
console.log("connecting to mqtt with client id %s", this.client.clientId);
this.client.connect({
onFailure: (e) => {
@@ -44,7 +44,7 @@ export class MqttApiClient extends EventEmitter implements ISprinklersApi {
});
}
- public getDevice(prefix: string): SprinklersDevice {
+ getDevice(prefix: string): SprinklersDevice {
if (/\//.test(prefix)) {
throw new Error("Prefix cannot contain a /");
}
@@ -57,7 +57,7 @@ export class MqttApiClient extends EventEmitter implements ISprinklersApi {
return this.devices[prefix];
}
- public removeDevice(prefix: string) {
+ removeDevice(prefix: string) {
const device = this.devices[prefix];
if (!device) {
return;
@@ -93,8 +93,8 @@ export class MqttApiClient extends EventEmitter implements ISprinklersApi {
}
class MqttSprinklersDevice extends SprinklersDevice {
- public readonly apiClient: MqttApiClient;
- public readonly prefix: string;
+ readonly apiClient: MqttApiClient;
+ readonly prefix: string;
private responseCallbacks: {
[rid: number]: ResponseCallback;
@@ -106,13 +106,13 @@ class MqttSprinklersDevice extends SprinklersDevice {
this.prefix = prefix;
}
- public doSubscribe() {
+ doSubscribe() {
const c = this.apiClient.client;
this.subscriptions
.forEach((filter) => c.subscribe(filter, {qos: 1}));
}
- public doUnsubscribe() {
+ doUnsubscribe() {
const c = this.apiClient.client;
this.subscriptions
.forEach((filter) => c.unsubscribe(filter));
@@ -123,7 +123,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
* @param topic The topic, with prefix removed
* @param payload The payload string
*/
- public onMessage(topic: string, payload: string) {
+ onMessage(topic: string, payload: string) {
if (topic === "connected") {
this.connected = (payload === "true");
// console.log(`MqttSprinklersDevice with prefix ${this.prefix}: ${this.connected}`)
@@ -131,6 +131,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
}
let matches = topic.match(/^sections(?:\/(\d+)(?:\/?(.+))?)?$/);
if (matches != null) {
+ //noinspection JSUnusedLocalSymbols
const [_topic, secStr, subTopic] = matches;
// console.log(`section: ${secStr}, topic: ${subTopic}, payload: ${payload}`);
if (!secStr) { // new number of sections
@@ -147,6 +148,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
}
matches = topic.match(/^programs(?:\/(\d+)(?:\/?(.+))?)?$/);
if (matches != null) {
+ //noinspection JSUnusedLocalSymbols
const [_topic, progStr, subTopic] = matches;
// console.log(`program: ${progStr}, topic: ${subTopic}, payload: ${payload}`);
if (!progStr) { // new number of programs
@@ -163,6 +165,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
}
matches = topic.match(/^responses\/(\d+)$/);
if (matches != null) {
+ //noinspection JSUnusedLocalSymbols
const [_topic, respIdStr] = matches;
console.log(`response: ${respIdStr}`);
const respId = parseInt(respIdStr, 10);
@@ -180,7 +183,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
return this.prefix;
}
- public runSection(section: Section | number, duration: Duration) {
+ runSection(section: Section | number, duration: Duration) {
const sectionNum = checkedIndexOf(section, this.sections, "Section");
return this.makeRequest(`sections/${sectionNum}/run`,
{
@@ -188,23 +191,20 @@ class MqttSprinklersDevice extends SprinklersDevice {
} as IRunSectionJSON);
}
- public runProgram(program: Program | number) {
+ runProgram(program: Program | number) {
const programNum = checkedIndexOf(program, this.programs, "Program");
return this.makeRequest(`programs/${programNum}/run`, {});
}
+ //noinspection JSMethodCanBeStatic
private nextRequestId(): number {
return Math.floor(Math.random() * 1000000000);
}
private makeRequest(topic: string, payload: object | string): Promise {
return new Promise((resolve, reject) => {
- let payloadStr: string;
- if (typeof payload === "string") {
- payloadStr = payload;
- } else {
- payloadStr = JSON.stringify(payload);
- }
+ const payloadStr = (typeof payload === "string") ?
+ payload : JSON.stringify(payload);
const message = new MQTT.Message(payloadStr);
message.destinationName = this.prefix + "/" + topic;
const requestId = this.nextRequestId();
@@ -250,7 +250,7 @@ interface IRunSectionJSON {
}
class MqttSection extends Section {
- public onMessage(topic: string, payload: string) {
+ onMessage(topic: string, payload: string) {
if (topic === "state") {
this.state = (payload === "true");
} else if (topic == null) {
@@ -289,7 +289,7 @@ interface IProgramJSON {
}
class MqttProgram extends Program {
- public onMessage(topic: string, payload: string) {
+ onMessage(topic: string, payload: string) {
if (topic === "running") {
this.running = (payload === "true");
} else if (topic == null) {
@@ -298,7 +298,7 @@ class MqttProgram extends Program {
}
}
- public updateFromJSON(json: Partial) {
+ updateFromJSON(json: Partial) {
if (json.name != null) {
this.name = json.name;
}
diff --git a/app/script/paho-mqtt.d.ts b/app/script/paho-mqtt.d.ts
index 0fcf695..7df5863 100644
--- a/app/script/paho-mqtt.d.ts
+++ b/app/script/paho-mqtt.d.ts
@@ -1,76 +1,444 @@
-/* tslint:disable:interface-name */
+// Type definitions for paho-mqtt 1.0
+// Project: https://github.com/eclipse/paho.mqtt.javascript#readme
+// Definitions by: Alex Mikhalev
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare namespace Paho {
+ /**
+ * Send and receive messages using web browsers.
+ *
+ * This programming interface lets a JavaScript client application use the MQTT V3.1 or
+ * V3.1.1 protocol to connect to an MQTT-supporting messaging server.
+ *
+ * The function supported includes:
+ *
+ * - Connecting to and disconnecting from a server. The server is identified by its host name and port number.
+ *
- Specifying options that relate to the communications link with the server,
+ * for example the frequency of keep-alive heartbeats, and whether SSL/TLS is required.
+ *
- Subscribing to and receiving messages from MQTT Topics.
+ *
- Publishing messages to MQTT Topics.
+ *
+ *
+ * The API consists of two main objects:
+ *
+ * - {@link Paho.MQTT.Client}
+ * - This contains methods that provide the functionality of the API,
+ * including provision of callbacks that notify the application when a message
+ * arrives from or is delivered to the messaging server,
+ * or when the status of its connection to the messaging server changes.
+ * - {@link Paho.MQTT.Message}
+ * - This encapsulates the payload of the message along with various attributes
+ * associated with its delivery, in particular the destination to which it has
+ * been (or is about to be) sent.
+ *
+ *
+ * The programming interface validates parameters passed to it, and will throw
+ * an Error containing an error message intended for developer use, if it detects
+ * an error with any parameter.
+ *
+ *
+ * @namespace Paho.MQTT
+ */
namespace MQTT {
- interface MQTTError { errorCode: string; errorMessage: string; }
- interface WithInvocationContext { invocationContext: object; }
- interface ErrorWithInvocationContext extends MQTTError, WithInvocationContext {}
- interface OnSubscribeSuccessParams extends WithInvocationContext { grantedQos: number; }
+ /**
+ * The Quality of Service used to deliver a message.
+ *
+ * - 0 Best effort (default).
+ * - 1 At least once.
+ * - 2 Exactly once.
+ *
+ */
+ type Qos = 0 | 1 | 2;
+
+ interface MQTTError {
+ /** A number indicating the nature of the error. */
+ errorCode: number;
+
+ /** Text describing the error */
+ errorMessage: string;
+ }
+ interface WithInvocationContext {
+ /**
+ * invocationContext
as passed in with the corresponding field in the connectOptions or
+ * subscribeOptions.
+ */
+ invocationContext: any;
+ }
+ interface ErrorWithInvocationContext extends MQTTError, WithInvocationContext {
+ }
+ interface OnSubscribeSuccessParams extends WithInvocationContext {
+ grantedQos: Qos;
+ }
+
+ /**
+ * Called when the connect acknowledgement has been received from the server.
+ * @param o
+ * A single response object parameter is passed to the onSuccess callback containing the following fields:
+ * invocationContext
as passed in with the corresponding field in the connectOptions.
+ */
+ type OnSuccessCallback = (o: WithInvocationContext) => void;
+
+ /**
+ * Called when the subscribe acknowledgement has been received from the server.
+ * @param o
+ * A single response object parameter is passed to the onSuccess callback containing the following fields:
+ * invocationContext
as passed in with the corresponding field in the connectOptions.
+ */
+ type OnSubscribeSuccessCallback = (o: OnSubscribeSuccessParams) => void;
+
+ /**
+ * Called when the connect request has failed or timed out.
+ * @param e
+ * A single response object parameter is passed to the onFailure callback containing the following fields:
+ * invocationContext
as passed in with the corresponding field in the connectOptions.
+ * errorCode
a number indicating the nature of the error.
+ * errorMessage
text describing the error.
+ */
+ type OnFailureCallback = (e: ErrorWithInvocationContext) => void;
+
+ /**
+ * Called when a connection has been lost.
+ * @param error A single response object parameter is passed to the onConnectionLost callback containing the
+ * following fields:
+ * errorCode
+ * errorMessage
+ */
type OnConnectionLostHandler = (error: MQTTError) => void;
+
+ /**
+ * Called when a message was delivered or has arrived.
+ * @param message The {@link Paho.MQTT.Message} that was delivered or has arrived.
+ */
type OnMessageHandler = (message: Message) => void;
+
+ /**
+ * Attributes used with a connection.
+ */
interface ConnectionOptions {
+ /**
+ * If the connect has not succeeded within this number of seconds, it is deemed to have failed.
+ * @default The default is 30 seconds.
+ */
timeout?: number;
+ /** Authentication username for this connection. */
userName?: string;
+ /** Authentication password for this connection. */
password?: string;
+ /** Sent by the server when the client disconnects abnormally. */
willMessage?: Message;
+ /**
+ * The server disconnects this client if there is no activity for this number of seconds.
+ * @default The default value of 60 seconds is assumed if not set.
+ */
keepAliveInterval?: number;
+ /**
+ * If true(default) the client and server persistent state is deleted on successful connect.
+ * @default true
+ */
cleanSession?: boolean;
+ /** If present and true, use an SSL Websocket connection. */
useSSL?: boolean;
- invocationContext?: object;
- onSuccess?: (o: WithInvocationContext) => void;
- mqttVersion?: number;
- onFailure?: (e: ErrorWithInvocationContext) => void;
+ /** Passed to the onSuccess callback or onFailure callback. */
+ invocationContext?: any;
+ /**
+ * Called when the connect acknowledgement has been received from the server.
+ */
+ onSuccess?: OnSuccessCallback;
+ /**
+ * Specifies the mqtt version to use when connecting
+ *
+ * - 3 - MQTT 3.1
+ * - 4 - MQTT 3.1.1 (default)
+ *
+ * @default 4
+ */
+ mqttVersion?: 3 | 4;
+ /**
+ * Called when the connect request has failed or timed out.
+ */
+ onFailure?: OnFailureCallback;
+ /**
+ * If present this contains either a set of hostnames or fully qualified
+ * WebSocket URIs (ws://example.com:1883/mqtt), that are tried in order in place of the host and port
+ * paramater on the construtor. The hosts are tried one at at time in order until one of then succeeds.
+ */
hosts?: string[];
+ /**
+ * If present the set of ports matching the hosts. If hosts contains URIs, this property is not used.
+ */
ports?: number[];
}
+
+ /**
+ * Used to control a subscription
+ */
interface SubscribeOptions {
- qos?: number;
- invocationContext?: object;
- onSuccess?: (o: OnSubscribeSuccessParams) => void;
- onFailure?: (e: ErrorWithInvocationContext) => void;
+ /** the maximum qos of any publications sent as a result of making this subscription. */
+ qos?: Qos;
+ /** passed to the onSuccess callback or onFailure callback. */
+ invocationContext?: any;
+ /** called when the subscribe acknowledgement has been received from the server. */
+ onSuccess?: OnSubscribeSuccessCallback;
+ /** called when the subscribe request has failed or timed out. */
+ onFailure?: OnFailureCallback;
+ /**
+ * timeout which, if present, determines the number of seconds after which the onFailure calback is called.
+ * The presence of a timeout does not prevent the onSuccess callback from being called when the subscribe
+ * completes.
+ */
timeout?: number;
}
+
interface UnsubscribeOptions {
- invocationContext?: object;
- onSuccess?: (o: WithInvocationContext) => void;
- onFailure?: (e: ErrorWithInvocationContext) => void;
+ /** passed to the onSuccess callback or onFailure callback. */
+ invocationContext?: any;
+ /** called when the unsubscribe acknowledgement has been received from the server. */
+ onSuccess?: OnSuccessCallback;
+ /** called when the unsubscribe request has failed or timed out. */
+ onFailure?: OnFailureCallback;
+ /**
+ * timeout which, if present, determines the number of seconds after which the onFailure calback is called.
+ * The presence of a timeout does not prevent the onSuccess callback from being called when the unsubscribe
+ * completes.
+ */
timeout?: number;
}
+
+ interface TraceElement {
+ severity: "Debug";
+ message: string;
+ }
+
+ type TraceFunction = (element: TraceElement) => void;
+
+ /**
+ * The JavaScript application communicates to the server using a {@link Paho.MQTT.Client} object.
+ *
+ * Most applications will create just one Client object and then call its connect() method,
+ * however applications can create more than one Client object if they wish.
+ * In this case the combination of host, port and clientId attributes must be different for each Client object.
+ *
+ * The send, subscribe and unsubscribe methods are implemented as asynchronous JavaScript methods
+ * (even though the underlying protocol exchange might be synchronous in nature).
+ * This means they signal their completion by calling back to the application,
+ * via Success or Failure callback functions provided by the application on the method in question.
+ * Such callbacks are called at most once per method invocation and do not persist beyond the lifetime
+ * of the script that made the invocation.
+ *
+ * In contrast there are some callback functions, most notably {@link onMessageArrived},
+ * that are defined on the {@link Paho.MQTT.Client} object.
+ * These may get called multiple times, and aren't directly related to specific method invocations made by the
+ * client.
+ *
+ */
class Client {
- public readonly clientId: string;
- public readonly host: string;
- public readonly path: string;
- public readonly port: number;
+ /** read only used when connecting to the server. */
+ readonly clientId: string;
+
+ /** read only the server's DNS hostname or dotted decimal IP address. */
+ readonly host: string;
+
+ /** read only the server's path. */
+ readonly path: string;
- public onConnectionLost: OnConnectionLostHandler;
- public onMessageArrived: OnMessageHandler;
- public onMessageDelivered: OnMessageHandler;
+ /** read only the server's port. */
+ readonly port: number;
- // tslint:disable unified-signatures
+ /** function called with trace information, if set */
+ trace?: TraceFunction;
+
+ /**
+ * called when a connection has been lost. after a connect() method has succeeded.
+ * Establish the call back used when a connection has been lost. The connection may be
+ * lost because the client initiates a disconnect or because the server or network
+ * cause the client to be disconnected. The disconnect call back may be called without
+ * the connectionComplete call back being invoked if, for example the client fails to
+ * connect.
+ * A single response object parameter is passed to the onConnectionLost callback containing the following
+ * fields:
+ * errorCode
+ * errorMessage
+ */
+ onConnectionLost: OnConnectionLostHandler;
+
+ /**
+ * called when a message has been delivered.
+ * All processing that this Client will ever do has been completed. So, for example,
+ * in the case of a Qos=2 message sent by this client, the PubComp flow has been received from the server
+ * and the message has been removed from persistent storage before this callback is invoked.
+ * Parameters passed to the onMessageDelivered callback are:
+ * {@link Paho.MQTT.Message} that was delivered.
+ */
+ onMessageDelivered: OnMessageHandler;
+
+ /**
+ * called when a message has arrived in this Paho.MQTT.client.
+ * Parameters passed to the onMessageArrived callback are:
+ * {@link Paho.MQTT.Message} that has arrived.
+ */
+ onMessageArrived: OnMessageHandler;
+
+ /* tslint:disable:unified-signatures */
+ /* these cannot actually be neatly unified */
+
+ /**
+ * @param host - the address of the messaging server as a DNS name or dotted decimal IP address.
+ * @param port - the port number to connect to
+ * @param path - the path on the host to connect to - only used if host is not a URI. Default: '/mqtt'.
+ * @param clientId - the Messaging client identifier, between 1 and 23 characters in length.
+ */
constructor(host: string, port: number, path: string, clientId: string);
+
+ /**
+ * @param host - the address of the messaging server as a DNS name or dotted decimal IP address.
+ * @param port - the port number to connect to
+ * @param clientId - the Messaging client identifier, between 1 and 23 characters in length.
+ */
constructor(host: string, port: number, clientId: string);
+
+ /**
+ * @param hostUri - the address of the messaging server as a fully qualified WebSocket URI
+ * @param clientId - the Messaging client identifier, between 1 and 23 characters in length.
+ */
constructor(hostUri: string, clientId: string);
- public connect(connectionOptions?: ConnectionOptions);
- public disconnect();
+ /* tslint:enable:unified-signatures */
+
+ /**
+ * Connect this Messaging client to its server.
+ * @throws {InvalidState} if the client is not in disconnected state. The client must have received
+ * connectionLost or disconnected before calling connect for a second or subsequent time.
+ */
+ connect(connectionOptions?: ConnectionOptions): void;
+
+ /**
+ * Normal disconnect of this Messaging client from its server.
+ *
+ * @throws {InvalidState} if the client is already disconnected.
+ */
+ disconnect(): void;
+
+ /**
+ * @returns True if the client is currently connected
+ */
+ isConnected(): boolean;
- public getTraceLog(): object[];
- public startTrace();
- public stopTrace();
+ /**
+ * Get the contents of the trace log.
+ *
+ * @return {Object[]} tracebuffer containing the time ordered trace records.
+ */
+ getTraceLog(): any[];
- public send(message: Message);
- public subscribe(filter: string, subcribeOptions?: SubscribeOptions);
- public unsubscribe(filter: string, unsubcribeOptions?: UnsubscribeOptions);
+ /**
+ * Start tracing.
+ */
+ startTrace(): void;
+
+ /**
+ * Stop tracing.
+ */
+ stopTrace(): void;
+
+ /**
+ * Send a message to the consumers of the destination in the Message.
+ *
+ * @param {Paho.MQTT.Message} message - mandatory The {@link Paho.MQTT.Message} object to be sent.
+ * @throws {InvalidState} if the client is not connected.
+ */
+ send(message: Message): void;
+
+ /**
+ * Send a message to the consumers of the destination in the Message.
+ *
+ * @param {string} topic - mandatory The name of the destination to which the message is to be sent.
+ * @param {string|ArrayBuffer} payload - The message data to be sent.
+ * @param {number} qos The Quality of Service used to deliver the message.
+ *
+ * - 0 Best effort (default).
+ *
- 1 At least once.
+ *
- 2 Exactly once.
+ *
+ * @param {Boolean} retained If true, the message is to be retained by the server and delivered to both
+ * current and future subscriptions. If false the server only delivers the message to current subscribers,
+ * this is the default for new Messages. A received message has the retained boolean set to true if the
+ * message was published with the retained boolean set to true and the subscrption was made after the
+ * message has been published.
+ * @throws {InvalidState} if the client is not connected.
+ */
+ send(topic: string, payload: string | ArrayBuffer, qos?: Qos, retained?: boolean): void;
+
+ /**
+ * Subscribe for messages, request receipt of a copy of messages sent to the destinations described by the
+ * filter.
+ *
+ * @param filter A filter describing the destinations to receive messages from.
+ * @param subcribeOptions Used to control the subscription
+ * @throws {InvalidState} if the client is not in connected state.
+ */
+ subscribe(filter: string, subcribeOptions?: SubscribeOptions): void;
+
+ /**
+ * Unsubscribe for messages, stop receiving messages sent to destinations described by the filter.
+ *
+ * @param filter - describing the destinations to receive messages from.
+ * @param unsubscribeOptions - used to control the subscription
+ * @throws {InvalidState} if the client is not in connected state.
+ */
+ unsubscribe(filter: string, unsubcribeOptions?: UnsubscribeOptions): void;
}
+ /**
+ * An application message, sent or received.
+ */
class Message {
- public destinationName: string;
- public readonly duplicate: boolean;
- public readonly payloadBytes: ArrayBuffer;
- public readonly payloadString: string;
- public qos: number;
- public retained: boolean;
+ /**
+ * mandatory The name of the destination to which the message is to be sent
+ * (for messages about to be sent) or the name of the destination from which the message has been received.
+ * (for messages received by the onMessage function).
+ */
+ destinationName?: string;
+
+ /**
+ * read only If true, this message might be a duplicate of one which has already been received.
+ * This is only set on messages received from the server.
+ */
+ readonly duplicate: boolean;
+
+ /** read only The payload as an ArrayBuffer. */
+ readonly payloadBytes: ArrayBuffer;
+
+ /**
+ * read only The payload as a string if the payload consists of valid UTF-8 characters.
+ * @throw {Error} if the payload is not valid UTF-8
+ */
+ readonly payloadString: string;
+
+ /**
+ * The Quality of Service used to deliver the message.
+ *
+ * - 0 Best effort (default).
+ *
- 1 At least once.
+ *
- 2 Exactly once.
+ *
+ *
+ * @default 0
+ */
+ qos: number;
+
+ /**
+ * If true, the message is to be retained by the server and delivered to both current and future
+ * subscriptions. If false the server only delivers the message to current subscribers, this is the default
+ * for new Messages. A received message has the retained boolean set to true if the message was published
+ * with the retained boolean set to true and the subscription was made after the message has been published.
+ *
+ * @default false
+ */
+ retained: boolean;
+ /**
+ * @param {String|ArrayBuffer} payload The message data to be sent.
+ */
constructor(payload: string | ArrayBuffer);
}
}
diff --git a/app/script/sprinklers.ts b/app/script/sprinklers.ts
index 5df50c4..fa04fe9 100644
--- a/app/script/sprinklers.ts
+++ b/app/script/sprinklers.ts
@@ -120,6 +120,10 @@ export class Program {
}
}
+export class SectionRunner {
+
+}
+
export abstract class SprinklersDevice {
@observable
public connected: boolean = false;
diff --git a/tslint.json b/tslint.json
index e1a4e5d..0b61947 100644
--- a/tslint.json
+++ b/tslint.json
@@ -1,25 +1,25 @@
{
- "defaultSeverity": "error",
- "extends": [
- "tslint:recommended"
+ "defaultSeverity": "error",
+ "extends": [
+ "tslint:latest"
+ ],
+ "jsRules": {},
+ "rules": {
+ "no-console": [
+ false
],
- "jsRules": {},
- "rules": {
- "no-console": [
- false
- ],
- "max-classes-per-file": [
- false
- ],
- "ordered-imports": [
- false
- ],
- "variable-name": [
- "allow-leading-underscore"
- ],
- "no-namespace": [
- "allow-declarations"
- ]
- },
- "rulesDirectory": []
+ "max-classes-per-file": [
+ false
+ ],
+ "ordered-imports": false,
+ "variable-name": [
+ "allow-leading-underscore"
+ ],
+ "no-namespace": [
+ "allow-declarations"
+ ],
+ "interface-name": false,
+ "member-access": [ true, "no-public" ]
+ },
+ "rulesDirectory": []
}
\ No newline at end of file