Fixed/improved a bunch of stuff
This commit is contained in:
parent
652fe5b036
commit
d895e2e3e9
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
|
}
|
39
.vscode/tasks.json
vendored
39
.vscode/tasks.json
vendored
@ -1,27 +1,28 @@
|
|||||||
{
|
{
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
// for the documentation about the tasks.json format
|
// for the documentation about the tasks.json format
|
||||||
"version": "0.1.0",
|
"version": "2.0.0",
|
||||||
"command": "npm",
|
|
||||||
"isShellCommand": true,
|
|
||||||
"showOutput": "always",
|
|
||||||
"suppressTaskName": true,
|
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"taskName": "install",
|
"type": "npm",
|
||||||
"args": ["install"]
|
"script": "start",
|
||||||
},
|
"problemMatcher": {
|
||||||
{
|
"owner": "webpack",
|
||||||
"taskName": "update",
|
"severity": "error",
|
||||||
"args": ["update"]
|
"fileLocation": "relative",
|
||||||
},
|
"pattern": [
|
||||||
{
|
{
|
||||||
"taskName": "start",
|
"regexp": "ERROR in (.*)",
|
||||||
"args": ["run", "start"]
|
"file": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskName": "test",
|
"regexp": "\\((\\d+),(\\d+)\\):(.*)",
|
||||||
"args": ["run", "test"]
|
"line": 1,
|
||||||
|
"column": 2,
|
||||||
|
"message": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,19 +1,19 @@
|
|||||||
import * as classNames from "classnames";
|
import * as classNames from "classnames";
|
||||||
import {observer} from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {Header, Item} from "semantic-ui-react";
|
import { Header, Item } from "semantic-ui-react";
|
||||||
import {ProgramTable, RunSectionForm, SectionTable, SectionRunnerView} from ".";
|
import { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from ".";
|
||||||
|
|
||||||
import {SprinklersDevice} from "../sprinklers";
|
import { SprinklersDevice } from "../sprinklers";
|
||||||
import FontAwesome = require("react-fontawesome");
|
import FontAwesome = require("react-fontawesome");
|
||||||
|
|
||||||
const ConnectionState = ({connected}: { connected: boolean }) =>
|
const ConnectionState = ({ connected }: { connected: boolean }) =>
|
||||||
<span className={classNames({
|
<span className={classNames({
|
||||||
"device--connectionState": true,
|
"device--connectionState": true,
|
||||||
"device--connectionState-connected": connected,
|
"device--connectionState-connected": connected,
|
||||||
"device--connectionState-disconnected": !connected,
|
"device--connectionState-disconnected": !connected,
|
||||||
})}>
|
})}>
|
||||||
<FontAwesome name={connected ? "plug" : "chain-broken"}/>
|
<FontAwesome name={connected ? "plug" : "chain-broken"} />
|
||||||
|
|
||||||
{connected ? "Connected" : "Disconnected"}
|
{connected ? "Connected" : "Disconnected"}
|
||||||
</span>;
|
</span>;
|
||||||
@ -21,22 +21,22 @@ const ConnectionState = ({connected}: { connected: boolean }) =>
|
|||||||
@observer
|
@observer
|
||||||
export default class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, {}> {
|
export default class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, {}> {
|
||||||
render() {
|
render() {
|
||||||
const {id, connected, sections, programs, sectionRunner} = this.props.device;
|
const { id, connected, sections, programs, sectionRunner } = this.props.device;
|
||||||
return (
|
return (
|
||||||
<Item>
|
<Item>
|
||||||
<Item.Image src={require<string>("app/images/raspberry_pi.png")}/>
|
<Item.Image src={require<string>("app/images/raspberry_pi.png")} />
|
||||||
<Item.Content>
|
<Item.Content>
|
||||||
<Header as="h1">
|
<Header as="h1">
|
||||||
<span>Device </span><kbd>{id}</kbd>
|
<span>Device </span><kbd>{id}</kbd>
|
||||||
<ConnectionState connected={connected}/>
|
<ConnectionState connected={connected} />
|
||||||
</Header>
|
</Header>
|
||||||
<Item.Meta>
|
<Item.Meta>
|
||||||
|
|
||||||
</Item.Meta>
|
</Item.Meta>
|
||||||
<SectionRunnerView sectionRunner={sectionRunner}/>
|
<SectionRunnerView sectionRunner={sectionRunner} />
|
||||||
<SectionTable sections={sections}/>
|
<SectionTable sections={sections} />
|
||||||
<RunSectionForm sections={sections}/>
|
<RunSectionForm sections={sections} />
|
||||||
<ProgramTable programs={programs}/>
|
<ProgramTable programs={programs} />
|
||||||
</Item.Content>
|
</Item.Content>
|
||||||
</Item>
|
</Item>
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {Input} from "semantic-ui-react";
|
import { Input, InputOnChangeData } from "semantic-ui-react";
|
||||||
import {Duration} from "../sprinklers";
|
import { Duration } from "../sprinklers";
|
||||||
|
|
||||||
export default class DurationInput extends React.Component<{
|
export default class DurationInput extends React.Component<{
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
onDurationChange?: (newDuration: Duration) => void;
|
onDurationChange: (newDuration: Duration) => void;
|
||||||
}, {}> {
|
}> {
|
||||||
render() {
|
render() {
|
||||||
const duration = this.props.duration;
|
const duration = this.props.duration;
|
||||||
// const editing = this.props.onDurationChange != null;
|
// const editing = this.props.onDurationChange != null;
|
||||||
@ -13,25 +13,25 @@ export default class DurationInput extends React.Component<{
|
|||||||
<label>Duration</label>
|
<label>Duration</label>
|
||||||
<div className="fields">
|
<div className="fields">
|
||||||
<Input type="number" className="field durationInput--minutes"
|
<Input type="number" className="field durationInput--minutes"
|
||||||
value={duration.minutes} onChange={this.onMinutesChange}
|
value={duration.minutes} onChange={this.onMinutesChange}
|
||||||
label="M" labelPosition="right"/>
|
label="M" labelPosition="right" />
|
||||||
<Input type="number" className="field durationInput--seconds"
|
<Input type="number" className="field durationInput--seconds"
|
||||||
value={duration.seconds} onChange={this.onSecondsChange} max="60"
|
value={duration.seconds} onChange={this.onSecondsChange} max="60"
|
||||||
label="S" labelPosition="right"/>
|
label="S" labelPosition="right" />
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMinutesChange = (e, {value}) => {
|
private onMinutesChange = (e: React.SyntheticEvent<any>, { value }: InputOnChangeData) => {
|
||||||
if (value.length === 0 || isNaN(value)) {
|
if (value.length === 0 || isNaN(Number(value))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newMinutes = parseInt(value, 10);
|
const newMinutes = parseInt(value, 10);
|
||||||
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes));
|
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes));
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSecondsChange = (e, {value}) => {
|
private onSecondsChange = (e: React.SyntheticEvent<any>, { value }: InputOnChangeData) => {
|
||||||
if (value.length === 0 || isNaN(value)) {
|
if (value.length === 0 || isNaN(Number(value))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newSeconds = parseInt(value, 10);
|
const newSeconds = parseInt(value, 10);
|
||||||
|
@ -14,7 +14,7 @@ export class ScheduleView extends React.PureComponent<{ schedule: Schedule }, {}
|
|||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class ProgramTable extends React.PureComponent<{ programs: Program[] }, {}> {
|
export default class ProgramTable extends React.PureComponent<{ programs: Program[] }, {}> {
|
||||||
private static renderRow(program: Program, i: number): JSX.Element[] {
|
private static renderRows(program: Program, i: number): JSX.Element[] | null {
|
||||||
if (!program) {
|
if (!program) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -57,7 +57,7 @@ export default class ProgramTable extends React.PureComponent<{ programs: Progra
|
|||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
{
|
{
|
||||||
Array.prototype.concat.apply([], this.props.programs.map(ProgramTable.renderRow))
|
Array.prototype.concat.apply([], this.props.programs.map(ProgramTable.renderRows))
|
||||||
}
|
}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table>
|
</Table>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {computed} from "mobx";
|
import {computed} from "mobx";
|
||||||
import {observer} from "mobx-react";
|
import {observer} from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {SyntheticEvent} from "react";
|
|
||||||
import {DropdownItemProps, DropdownProps, Form, Header, Segment} from "semantic-ui-react";
|
import {DropdownItemProps, DropdownProps, Form, Header, Segment} from "semantic-ui-react";
|
||||||
import {DurationInput} from ".";
|
import {DurationInput} from ".";
|
||||||
import {Duration, Section} from "../sprinklers";
|
import {Duration, Section} from "../sprinklers";
|
||||||
@ -37,7 +36,7 @@ export default class RunSectionForm extends React.Component<{
|
|||||||
</Segment>;
|
</Segment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSectionChange = (e: SyntheticEvent<HTMLElement>, v: DropdownProps) => {
|
private onSectionChange = (e: React.SyntheticEvent<HTMLElement>, v: DropdownProps) => {
|
||||||
this.setState({section: v.value as number});
|
this.setState({section: v.value as number});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,10 +44,13 @@ export default class RunSectionForm extends React.Component<{
|
|||||||
this.setState({duration: newDuration});
|
this.setState({duration: newDuration});
|
||||||
}
|
}
|
||||||
|
|
||||||
private run = (e: SyntheticEvent<HTMLElement>) => {
|
private run = (e: React.SyntheticEvent<HTMLElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (typeof this.state.section !== "number") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const section: Section = this.props.sections[this.state.section];
|
const section: Section = this.props.sections[this.state.section];
|
||||||
console.log(`should run section ${section} for ${this.state.duration}`);
|
console.log(`running section ${section} for ${this.state.duration}`);
|
||||||
section.run(this.state.duration)
|
section.run(this.state.duration)
|
||||||
.then((a) => console.log("ran section", a))
|
.then((a) => console.log("ran section", a))
|
||||||
.catch((err) => console.error("error running section", err));
|
.catch((err) => console.error("error running section", err));
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import {AppContainer} from "react-hot-loader";
|
import { AppContainer } from "react-hot-loader";
|
||||||
|
|
||||||
import App from "./components/App";
|
import App from "./components/App";
|
||||||
import {MqttApiClient} from "./mqtt";
|
import { MqttApiClient } from "./mqtt";
|
||||||
import {Message, UiStore} from "./ui";
|
import { Message, UiStore } from "./ui";
|
||||||
|
|
||||||
const client = new MqttApiClient();
|
const client = new MqttApiClient();
|
||||||
client.start();
|
client.start();
|
||||||
@ -15,14 +15,14 @@ uiStore.addMessage(new Message("asdf", "boo!", Message.Type.Error));
|
|||||||
const rootElem = document.getElementById("app");
|
const rootElem = document.getElementById("app");
|
||||||
|
|
||||||
ReactDOM.render(<AppContainer>
|
ReactDOM.render(<AppContainer>
|
||||||
<App device={device} uiStore={uiStore}/>
|
<App device={device} uiStore={uiStore} />
|
||||||
</AppContainer>, rootElem);
|
</AppContainer>, rootElem);
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.accept("./components/App", () => {
|
module.hot.accept("./components/App", () => {
|
||||||
const NextApp = require<any>("./components/App").default as typeof App;
|
const NextApp = require<any>("./components/App").default as typeof App;
|
||||||
ReactDOM.render(<AppContainer>
|
ReactDOM.render(<AppContainer>
|
||||||
<NextApp device={device} uiStore={uiStore}/>
|
<NextApp device={device} uiStore={uiStore} />
|
||||||
</AppContainer>, rootElem);
|
</AppContainer>, rootElem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import {EventEmitter} from "events";
|
import { EventEmitter } from "events";
|
||||||
import "paho-mqtt";
|
import "paho-mqtt";
|
||||||
import {
|
import {
|
||||||
Duration,
|
Duration,
|
||||||
ISectionRun,
|
|
||||||
ISprinklersApi,
|
ISprinklersApi,
|
||||||
ITimeOfDay,
|
|
||||||
Program,
|
Program,
|
||||||
|
ProgramItem,
|
||||||
Schedule,
|
Schedule,
|
||||||
Section,
|
Section,
|
||||||
|
SectionRun,
|
||||||
SectionRunner,
|
SectionRunner,
|
||||||
SprinklersDevice,
|
SprinklersDevice,
|
||||||
|
TimeOfDay,
|
||||||
} from "./sprinklers";
|
} from "./sprinklers";
|
||||||
import {checkedIndexOf} from "./utils";
|
import { checkedIndexOf } from "./utils";
|
||||||
import MQTT = Paho.MQTT;
|
import MQTT = Paho.MQTT;
|
||||||
|
|
||||||
export class MqttApiClient extends EventEmitter implements ISprinklersApi {
|
export class MqttApiClient implements ISprinklersApi {
|
||||||
client: MQTT.Client;
|
client: MQTT.Client;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
devices: { [prefix: string]: MqttSprinklersDevice } = {};
|
devices: { [prefix: string]: MqttSprinklersDevice } = {};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
|
||||||
this.client = new MQTT.Client(location.hostname, 1884, MqttApiClient.newClientId());
|
this.client = new MQTT.Client(location.hostname, 1884, MqttApiClient.newClientId());
|
||||||
this.client.onMessageArrived = (m) => this.onMessageArrived(m);
|
this.client.onMessageArrived = (m) => this.onMessageArrived(m);
|
||||||
this.client.onConnectionLost = (e) => this.onConnectionLost(e);
|
this.client.onConnectionLost = (e) => this.onConnectionLost(e);
|
||||||
@ -80,6 +80,10 @@ export class MqttApiClient extends EventEmitter implements ISprinklersApi {
|
|||||||
|
|
||||||
private processMessage(m: MQTT.Message) {
|
private processMessage(m: MQTT.Message) {
|
||||||
// console.log("message arrived: ", m);
|
// console.log("message arrived: ", m);
|
||||||
|
if (m.destinationName == null) {
|
||||||
|
console.warn(`revieved invalid message: ${m}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const topicIdx = m.destinationName.indexOf("/"); // find the first /
|
const topicIdx = m.destinationName.indexOf("/"); // find the first /
|
||||||
const prefix = m.destinationName.substr(0, topicIdx); // assume prefix does not contain a /
|
const prefix = m.destinationName.substr(0, topicIdx); // assume prefix does not contain a /
|
||||||
const topic = m.destinationName.substr(topicIdx + 1);
|
const topic = m.destinationName.substr(topicIdx + 1);
|
||||||
@ -130,7 +134,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
|
|||||||
doSubscribe() {
|
doSubscribe() {
|
||||||
const c = this.apiClient.client;
|
const c = this.apiClient.client;
|
||||||
this.subscriptions
|
this.subscriptions
|
||||||
.forEach((filter) => c.subscribe(filter, {qos: 1}));
|
.forEach((filter) => c.subscribe(filter, { qos: 1 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
doUnsubscribe() {
|
doUnsubscribe() {
|
||||||
@ -186,7 +190,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
|
|||||||
}
|
}
|
||||||
matches = topic.match(/^section_runner$/);
|
matches = topic.match(/^section_runner$/);
|
||||||
if (matches != null) {
|
if (matches != null) {
|
||||||
(this.sectionRunner as MqttSectionRunner).onMessage(null, payload);
|
(this.sectionRunner as MqttSectionRunner).onMessage(payload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
matches = topic.match(/^responses\/(\d+)$/);
|
matches = topic.match(/^responses\/(\d+)$/);
|
||||||
@ -219,7 +223,15 @@ class MqttSprinklersDevice extends SprinklersDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cancelSectionRunById(id: number) {
|
cancelSectionRunById(id: number) {
|
||||||
return this.makeRequest(`section_runner/cancel_id`, {id});
|
return this.makeRequest(`section_runner/cancel_id`, { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseSectionRunner() {
|
||||||
|
return this.makeRequest(`section_runner/pause`);
|
||||||
|
}
|
||||||
|
|
||||||
|
unpauseSectionRunner() {
|
||||||
|
return this.makeRequest(`section_runner/unpause`);
|
||||||
}
|
}
|
||||||
|
|
||||||
//noinspection JSMethodCanBeStatic
|
//noinspection JSMethodCanBeStatic
|
||||||
@ -227,7 +239,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
|
|||||||
return Math.floor(Math.random() * 1000000000);
|
return Math.floor(Math.random() * 1000000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private makeRequest(topic: string, payload: object | string): Promise<IResponseData> {
|
private makeRequest(topic: string, payload: object | string = {}): Promise<IResponseData> {
|
||||||
return new Promise<IResponseData>((resolve, reject) => {
|
return new Promise<IResponseData>((resolve, reject) => {
|
||||||
const payloadStr = (typeof payload === "string") ?
|
const payloadStr = (typeof payload === "string") ?
|
||||||
payload : JSON.stringify(payload);
|
payload : JSON.stringify(payload);
|
||||||
@ -254,7 +266,7 @@ interface IResponseData {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseCallback = (IResponseData) => void;
|
type ResponseCallback = (data: IResponseData) => void;
|
||||||
|
|
||||||
interface ISectionJSON {
|
interface ISectionJSON {
|
||||||
name: string;
|
name: string;
|
||||||
@ -276,8 +288,19 @@ class MqttSection extends Section {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ITimeOfDayJSON {
|
||||||
|
hour: number;
|
||||||
|
minute: number;
|
||||||
|
second: number;
|
||||||
|
millisecond: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeOfDayFromJSON(json: ITimeOfDayJSON): TimeOfDay {
|
||||||
|
return new TimeOfDay(json.hour, json.minute, json.second, json.millisecond);
|
||||||
|
}
|
||||||
|
|
||||||
interface IScheduleJSON {
|
interface IScheduleJSON {
|
||||||
times: ITimeOfDay[];
|
times: ITimeOfDayJSON[];
|
||||||
weekdays: number[];
|
weekdays: number[];
|
||||||
from?: string;
|
from?: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
@ -285,7 +308,7 @@ interface IScheduleJSON {
|
|||||||
|
|
||||||
function scheduleFromJSON(json: IScheduleJSON): Schedule {
|
function scheduleFromJSON(json: IScheduleJSON): Schedule {
|
||||||
const sched = new Schedule();
|
const sched = new Schedule();
|
||||||
sched.times = json.times;
|
sched.times = json.times.map(timeOfDayFromJSON);
|
||||||
sched.weekdays = json.weekdays;
|
sched.weekdays = json.weekdays;
|
||||||
sched.from = json.from == null ? null : new Date(json.from);
|
sched.from = json.from == null ? null : new Date(json.from);
|
||||||
sched.to = json.to == null ? null : new Date(json.to);
|
sched.to = json.to == null ? null : new Date(json.to);
|
||||||
@ -322,11 +345,10 @@ class MqttProgram extends Program {
|
|||||||
this.enabled = json.enabled;
|
this.enabled = json.enabled;
|
||||||
}
|
}
|
||||||
if (json.sequence != null) {
|
if (json.sequence != null) {
|
||||||
// tslint:disable:object-literal-sort-keys
|
this.sequence = json.sequence.map((item) => (new ProgramItem(
|
||||||
this.sequence = json.sequence.map((item) => ({
|
item.section,
|
||||||
section: item.section,
|
Duration.fromSeconds(item.duration),
|
||||||
duration: Duration.fromSeconds(item.duration),
|
)));
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
if (json.sched != null) {
|
if (json.sched != null) {
|
||||||
this.schedule = scheduleFromJSON(json.sched);
|
this.schedule = scheduleFromJSON(json.sched);
|
||||||
@ -334,23 +356,42 @@ class MqttProgram extends Program {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISectionRunJSON {
|
||||||
|
id: number;
|
||||||
|
section: number;
|
||||||
|
duration: number;
|
||||||
|
startTime?: number;
|
||||||
|
pauseTime?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sectionRunFromJSON(json: ISectionRunJSON) {
|
||||||
|
const run = new SectionRun();
|
||||||
|
run.id = json.id;
|
||||||
|
run.section = json.section;
|
||||||
|
run.duration = Duration.fromSeconds(json.duration);
|
||||||
|
run.startTime = json.startTime == null ? null : new Date(json.startTime);
|
||||||
|
run.pauseTime = json.pauseTime == null ? null : new Date(json.pauseTime);
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
|
||||||
interface ISectionRunnerJSON {
|
interface ISectionRunnerJSON {
|
||||||
queue: ISectionRun[];
|
queue: ISectionRunJSON[];
|
||||||
current?: ISectionRun;
|
current: ISectionRunJSON | null;
|
||||||
|
paused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MqttSectionRunner extends SectionRunner {
|
class MqttSectionRunner extends SectionRunner {
|
||||||
onMessage(topic: string, payload: string) {
|
onMessage(payload: string) {
|
||||||
const json = JSON.parse(payload) as ISectionRunnerJSON;
|
const json = JSON.parse(payload) as ISectionRunnerJSON;
|
||||||
this.updateFromJSON(json);
|
this.updateFromJSON(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFromJSON(json: ISectionRunnerJSON) {
|
updateFromJSON(json: ISectionRunnerJSON) {
|
||||||
if (!json.queue) { // null means empty queue
|
if (!json.queue || !json.queue.length) { // null means empty queue
|
||||||
this.queue.clear();
|
this.queue.clear();
|
||||||
} else {
|
} else {
|
||||||
this.queue.replace(json.queue);
|
this.queue.replace(json.queue.map(sectionRunFromJSON));
|
||||||
}
|
}
|
||||||
this.current = json.current;
|
this.current = json.current == null ? null : sectionRunFromJSON(json.current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {IObservableArray, observable} from "mobx";
|
import { IObservableArray, observable } from "mobx";
|
||||||
|
|
||||||
export abstract class Section {
|
export abstract class Section {
|
||||||
device: SprinklersDevice;
|
device: SprinklersDevice;
|
||||||
@ -22,11 +22,22 @@ export abstract class Section {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITimeOfDay {
|
export class TimeOfDay {
|
||||||
hour: number;
|
hour: number;
|
||||||
minute: number;
|
minute: number;
|
||||||
second: number;
|
second: number;
|
||||||
millisecond: number;
|
millisecond: number;
|
||||||
|
|
||||||
|
constructor(hour: number, minute: number = 0, second: number = 0, millisecond: number = 0) {
|
||||||
|
this.hour = hour;
|
||||||
|
this.minute = minute;
|
||||||
|
this.second = second;
|
||||||
|
this.millisecond = millisecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromDate(date: Date): TimeOfDay {
|
||||||
|
return new TimeOfDay(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Weekday {
|
export enum Weekday {
|
||||||
@ -34,17 +45,17 @@ export enum Weekday {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Schedule {
|
export class Schedule {
|
||||||
times: ITimeOfDay[] = [];
|
times: TimeOfDay[] = [];
|
||||||
weekdays: Weekday[] = [];
|
weekdays: Weekday[] = [];
|
||||||
from?: Date = null;
|
from: Date | null = null;
|
||||||
to?: Date = null;
|
to: Date | null = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Duration {
|
export class Duration {
|
||||||
minutes: number = 0;
|
minutes: number = 0;
|
||||||
seconds: number = 0;
|
seconds: number = 0;
|
||||||
|
|
||||||
constructor(minutes: number, seconds: number) {
|
constructor(minutes: number = 0, seconds: number = 0) {
|
||||||
this.minutes = minutes;
|
this.minutes = minutes;
|
||||||
this.seconds = seconds;
|
this.seconds = seconds;
|
||||||
}
|
}
|
||||||
@ -82,11 +93,16 @@ export class Duration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProgramItem {
|
export class ProgramItem {
|
||||||
// the section number
|
// the section number
|
||||||
section: number;
|
section: number;
|
||||||
// duration of the run
|
// duration of the run
|
||||||
duration: Duration;
|
duration: Duration;
|
||||||
|
|
||||||
|
constructor(section: number, duration: Duration) {
|
||||||
|
this.section = section;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Program {
|
export class Program {
|
||||||
@ -101,7 +117,7 @@ export class Program {
|
|||||||
schedule: Schedule = new Schedule();
|
schedule: Schedule = new Schedule();
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
sequence: IProgramItem[] = [];
|
sequence: ProgramItem[] = [];
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
running: boolean = false;
|
running: boolean = false;
|
||||||
@ -120,21 +136,38 @@ export class Program {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISectionRun {
|
export class SectionRun {
|
||||||
id: number;
|
id: number;
|
||||||
section: number;
|
section: number;
|
||||||
duration: number;
|
duration: Duration;
|
||||||
startTime?: Date;
|
startTime: Date | null;
|
||||||
|
pauseTime: Date | null;
|
||||||
|
|
||||||
|
constructor(id: number = 0, section: number = 0, duration: Duration = new Duration()) {
|
||||||
|
this.id = id;
|
||||||
|
this.section = section;
|
||||||
|
this.duration = duration;
|
||||||
|
this.startTime = null;
|
||||||
|
this.pauseTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `SectionRun{id=${this.id}, section=${this.section}, duration=${this.duration},` +
|
||||||
|
` startTime=${this.startTime}, pauseTime=${this.pauseTime}}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SectionRunner {
|
export class SectionRunner {
|
||||||
device: SprinklersDevice;
|
device: SprinklersDevice;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
queue: IObservableArray<ISectionRun> = observable([]);
|
queue: IObservableArray<SectionRun> = observable([]);
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
current: ISectionRun = null;
|
current: SectionRun | null = null;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
paused: boolean = false;
|
||||||
|
|
||||||
constructor(device: SprinklersDevice) {
|
constructor(device: SprinklersDevice) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
@ -145,7 +178,7 @@ export class SectionRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `SectionRunner{queue="${this.queue}", current="${this.current}"}`;
|
return `SectionRunner{queue="${this.queue}", current="${this.current}", paused=${this.paused}}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,10 +187,10 @@ export abstract class SprinklersDevice {
|
|||||||
connected: boolean = false;
|
connected: boolean = false;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
sections: IObservableArray<Section> = [] as IObservableArray<Section>;
|
sections: IObservableArray<Section> = observable.array<Section>();
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
programs: IObservableArray<Program> = [] as IObservableArray<Program>;
|
programs: IObservableArray<Program> = observable.array<Program>();
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
sectionRunner: SectionRunner;
|
sectionRunner: SectionRunner;
|
||||||
@ -169,12 +202,16 @@ export abstract class SprinklersDevice {
|
|||||||
abstract runProgram(program: number | Program): Promise<{}>;
|
abstract runProgram(program: number | Program): Promise<{}>;
|
||||||
|
|
||||||
abstract cancelSectionRunById(id: number): Promise<{}>;
|
abstract cancelSectionRunById(id: number): Promise<{}>;
|
||||||
|
|
||||||
|
abstract pauseSectionRunner(): Promise<{}>;
|
||||||
|
|
||||||
|
abstract unpauseSectionRunner(): Promise<{}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISprinklersApi {
|
export interface ISprinklersApi {
|
||||||
start();
|
start(): void;
|
||||||
|
|
||||||
getDevice(id: string): SprinklersDevice;
|
getDevice(id: string): SprinklersDevice;
|
||||||
|
|
||||||
removeDevice(id: string);
|
removeDevice(id: string): void;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"clean": "rm -rf ./dist ./build",
|
"clean": "rm -rf ./dist ./build",
|
||||||
"build": "webpack --config ./webpack/prod.config.js",
|
"build": "webpack --config ./webpack/prod.config.js",
|
||||||
"start": "webpack-dev-server --config ./webpack/dev.config.js",
|
"start": "webpack-dev-server --config ./webpack/dev.config.js",
|
||||||
"lint": "tslint \"app/script/**/*\" --force"
|
"lint": "tslint --project . --force"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -29,6 +29,7 @@
|
|||||||
"@types/react": "^16",
|
"@types/react": "^16",
|
||||||
"@types/react-dom": "^15.5.0",
|
"@types/react-dom": "^15.5.0",
|
||||||
"@types/react-fontawesome": "^1.5.0",
|
"@types/react-fontawesome": "^1.5.0",
|
||||||
|
"@types/react-hot-loader": "^3.0.4",
|
||||||
"@types/react-transition-group": "^1.1.1",
|
"@types/react-transition-group": "^1.1.1",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": ["es6", "dom"],
|
"lib": ["es6", "dom"],
|
||||||
"typeRoots": ["node_modules/@types"]
|
"typeRoots": ["node_modules/@types"],
|
||||||
|
"strict": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"./node_modules/@types/webpack-env/index.d.ts",
|
"./node_modules/@types/webpack-env/index.d.ts",
|
||||||
|
@ -26,6 +26,13 @@
|
|||||||
{
|
{
|
||||||
"order": "fields-first"
|
"order": "fields-first"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"object-literal-sort-keys": [
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"no-submodule-imports": false,
|
||||||
|
"no-unused-variable": [
|
||||||
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"rulesDirectory": []
|
"rulesDirectory": []
|
||||||
|
@ -34,6 +34,12 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-hot-loader@^3.0.4":
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-hot-loader/-/react-hot-loader-3.0.4.tgz#7fc081509830c64218d8a99a865e2fb4a94572ad"
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-transition-group@^1.1.1":
|
"@types/react-transition-group@^1.1.1":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-1.1.2.tgz#a349e70788a6dc723a5f439217011ef926c27b4f"
|
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-1.1.2.tgz#a349e70788a6dc723a5f439217011ef926c27b4f"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user