More work on server; lots of refactoring and good stuff
This commit is contained in:
parent
4b85395302
commit
f23ad08f92
5
.vscode/launch.json
vendored
5
.vscode/launch.json
vendored
@ -8,7 +8,10 @@
|
|||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Launch Program",
|
"name": "Launch Program",
|
||||||
"program": "${workspaceRoot}/bin/www"
|
"env": {
|
||||||
|
"NODE_ENV": "development"
|
||||||
|
},
|
||||||
|
"program": "${workspaceRoot}/dist/server/index.js"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
10
.vscode/tasks.json
vendored
10
.vscode/tasks.json
vendored
@ -33,6 +33,16 @@
|
|||||||
"type": "npm",
|
"type": "npm",
|
||||||
"script": "start:pretty",
|
"script": "start:pretty",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "start:dev",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "start:watch",
|
||||||
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { MqttApiClient } from "@common/mqtt";
|
|
||||||
import { ISprinklersApi } from "@common/sprinklers";
|
import { ISprinklersApi } from "@common/sprinklers";
|
||||||
|
import { MqttApiClient } from "@common/sprinklers/mqtt";
|
||||||
|
|
||||||
import { UiMessage, UiStore } from "./ui";
|
import { UiMessage, UiStore } from "./ui";
|
||||||
export { UiMessage, UiStore };
|
export { UiMessage, UiStore };
|
||||||
|
@ -1,224 +0,0 @@
|
|||||||
import { IObservableArray, observable } from "mobx";
|
|
||||||
|
|
||||||
export abstract class Section {
|
|
||||||
device: SprinklersDevice;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
name: string = "";
|
|
||||||
|
|
||||||
@observable
|
|
||||||
state: boolean = false;
|
|
||||||
|
|
||||||
constructor(device: SprinklersDevice) {
|
|
||||||
this.device = device;
|
|
||||||
}
|
|
||||||
|
|
||||||
run(duration: Duration) {
|
|
||||||
return this.device.runSection(this, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return `Section{name="${this.name}", state=${this.state}}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TimeOfDay {
|
|
||||||
hour: number;
|
|
||||||
minute: number;
|
|
||||||
second: 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 {
|
|
||||||
Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Schedule {
|
|
||||||
times: TimeOfDay[] = [];
|
|
||||||
weekdays: Weekday[] = [];
|
|
||||||
from: Date | null = null;
|
|
||||||
to: Date | null = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Duration {
|
|
||||||
minutes: number = 0;
|
|
||||||
seconds: number = 0;
|
|
||||||
|
|
||||||
constructor(minutes: number = 0, seconds: number = 0) {
|
|
||||||
this.minutes = minutes;
|
|
||||||
this.seconds = seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromSeconds(seconds: number): Duration {
|
|
||||||
return new Duration(Math.floor(seconds / 60), seconds % 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
toSeconds(): number {
|
|
||||||
return this.minutes * 60 + this.seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
withSeconds(newSeconds: number): Duration {
|
|
||||||
let newMinutes = this.minutes;
|
|
||||||
if (newSeconds >= 60) {
|
|
||||||
newMinutes++;
|
|
||||||
newSeconds = 0;
|
|
||||||
}
|
|
||||||
if (newSeconds < 0) {
|
|
||||||
newMinutes = Math.max(0, newMinutes - 1);
|
|
||||||
newSeconds = 59;
|
|
||||||
}
|
|
||||||
return new Duration(newMinutes, newSeconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
withMinutes(newMinutes: number): Duration {
|
|
||||||
if (newMinutes < 0) {
|
|
||||||
newMinutes = 0;
|
|
||||||
}
|
|
||||||
return new Duration(newMinutes, this.seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return `${this.minutes}M ${this.seconds}S`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ProgramItem {
|
|
||||||
// the section number
|
|
||||||
section: number;
|
|
||||||
// duration of the run
|
|
||||||
duration: Duration;
|
|
||||||
|
|
||||||
constructor(section: number, duration: Duration) {
|
|
||||||
this.section = section;
|
|
||||||
this.duration = duration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Program {
|
|
||||||
device: SprinklersDevice;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
name: string = "";
|
|
||||||
@observable
|
|
||||||
enabled: boolean = false;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
schedule: Schedule = new Schedule();
|
|
||||||
|
|
||||||
@observable
|
|
||||||
sequence: ProgramItem[] = [];
|
|
||||||
|
|
||||||
@observable
|
|
||||||
running: boolean = false;
|
|
||||||
|
|
||||||
constructor(device: SprinklersDevice) {
|
|
||||||
this.device = device;
|
|
||||||
}
|
|
||||||
|
|
||||||
run() {
|
|
||||||
return this.device.runProgram(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return `Program{name="${this.name}", enabled=${this.enabled}, schedule=${this.schedule},
|
|
||||||
sequence=${this.sequence}, running=${this.running}}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SectionRun {
|
|
||||||
id: number;
|
|
||||||
section: number;
|
|
||||||
duration: Duration;
|
|
||||||
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 {
|
|
||||||
device: SprinklersDevice;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
queue: IObservableArray<SectionRun> = observable([]);
|
|
||||||
|
|
||||||
@observable
|
|
||||||
current: SectionRun | null = null;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
paused: boolean = false;
|
|
||||||
|
|
||||||
constructor(device: SprinklersDevice) {
|
|
||||||
this.device = device;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelRunById(id: number): Promise<{}> {
|
|
||||||
return this.device.cancelSectionRunById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return `SectionRunner{queue="${this.queue}", current="${this.current}", paused=${this.paused}}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class SprinklersDevice {
|
|
||||||
@observable
|
|
||||||
connected: boolean = false;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
sections: IObservableArray<Section> = observable.array<Section>();
|
|
||||||
|
|
||||||
@observable
|
|
||||||
programs: IObservableArray<Program> = observable.array<Program>();
|
|
||||||
|
|
||||||
@observable
|
|
||||||
sectionRunner: SectionRunner;
|
|
||||||
|
|
||||||
abstract get id(): string;
|
|
||||||
|
|
||||||
abstract runSection(section: number | Section, duration: Duration): Promise<{}>;
|
|
||||||
|
|
||||||
abstract runProgram(program: number | Program): Promise<{}>;
|
|
||||||
|
|
||||||
abstract cancelSectionRunById(id: number): Promise<{}>;
|
|
||||||
|
|
||||||
abstract pauseSectionRunner(): Promise<{}>;
|
|
||||||
|
|
||||||
abstract unpauseSectionRunner(): Promise<{}>;
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return `SprinklersDevice{id="${this.id}", connected=${this.connected},
|
|
||||||
sections=${this.sections},
|
|
||||||
programs=${this.programs},
|
|
||||||
sectionRunner=${this.sectionRunner} }`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISprinklersApi {
|
|
||||||
start(): void;
|
|
||||||
|
|
||||||
getDevice(id: string): SprinklersDevice;
|
|
||||||
|
|
||||||
removeDevice(id: string): void;
|
|
||||||
}
|
|
41
common/sprinklers/Duration.ts
Normal file
41
common/sprinklers/Duration.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
export class Duration {
|
||||||
|
minutes: number = 0;
|
||||||
|
seconds: number = 0;
|
||||||
|
|
||||||
|
constructor(minutes: number = 0, seconds: number = 0) {
|
||||||
|
this.minutes = minutes;
|
||||||
|
this.seconds = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromSeconds(seconds: number): Duration {
|
||||||
|
return new Duration(Math.floor(seconds / 60), seconds % 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
toSeconds(): number {
|
||||||
|
return this.minutes * 60 + this.seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
withSeconds(newSeconds: number): Duration {
|
||||||
|
let newMinutes = this.minutes;
|
||||||
|
if (newSeconds >= 60) {
|
||||||
|
newMinutes++;
|
||||||
|
newSeconds = 0;
|
||||||
|
}
|
||||||
|
if (newSeconds < 0) {
|
||||||
|
newMinutes = Math.max(0, newMinutes - 1);
|
||||||
|
newSeconds = 59;
|
||||||
|
}
|
||||||
|
return new Duration(newMinutes, newSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
withMinutes(newMinutes: number): Duration {
|
||||||
|
if (newMinutes < 0) {
|
||||||
|
newMinutes = 0;
|
||||||
|
}
|
||||||
|
return new Duration(newMinutes, this.seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `${this.minutes}M ${this.seconds}S`;
|
||||||
|
}
|
||||||
|
}
|
9
common/sprinklers/ISprinklersApi.ts
Normal file
9
common/sprinklers/ISprinklersApi.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { SprinklersDevice } from "./SprinklersDevice";
|
||||||
|
|
||||||
|
export interface ISprinklersApi {
|
||||||
|
start(): void;
|
||||||
|
|
||||||
|
getDevice(id: string): SprinklersDevice;
|
||||||
|
|
||||||
|
removeDevice(id: string): void;
|
||||||
|
}
|
47
common/sprinklers/Program.ts
Normal file
47
common/sprinklers/Program.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { observable } from "mobx";
|
||||||
|
import { Duration } from "./Duration";
|
||||||
|
import { Schedule } from "./schedule";
|
||||||
|
import { SprinklersDevice } from "./SprinklersDevice";
|
||||||
|
|
||||||
|
export class ProgramItem {
|
||||||
|
// the section number
|
||||||
|
section: number;
|
||||||
|
// duration of the run
|
||||||
|
duration: Duration;
|
||||||
|
|
||||||
|
constructor(section: number, duration: Duration) {
|
||||||
|
this.section = section;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Program {
|
||||||
|
device: SprinklersDevice;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
name: string = "";
|
||||||
|
@observable
|
||||||
|
enabled: boolean = false;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
schedule: Schedule = new Schedule();
|
||||||
|
|
||||||
|
@observable
|
||||||
|
sequence: ProgramItem[] = [];
|
||||||
|
|
||||||
|
@observable
|
||||||
|
running: boolean = false;
|
||||||
|
|
||||||
|
constructor(device: SprinklersDevice) {
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
return this.device.runProgram(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `Program{name="${this.name}", enabled=${this.enabled}, schedule=${this.schedule},
|
||||||
|
sequence=${this.sequence}, running=${this.running}}`;
|
||||||
|
}
|
||||||
|
}
|
25
common/sprinklers/Section.ts
Normal file
25
common/sprinklers/Section.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { observable } from "mobx";
|
||||||
|
import { Duration } from "./Duration";
|
||||||
|
import { SprinklersDevice } from "./SprinklersDevice";
|
||||||
|
|
||||||
|
export class Section {
|
||||||
|
device: SprinklersDevice;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
name: string = "";
|
||||||
|
|
||||||
|
@observable
|
||||||
|
state: boolean = false;
|
||||||
|
|
||||||
|
constructor(device: SprinklersDevice) {
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
run(duration: Duration) {
|
||||||
|
return this.device.runSection(this, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `Section{name="${this.name}", state=${this.state}}`;
|
||||||
|
}
|
||||||
|
}
|
49
common/sprinklers/SectionRunner.ts
Normal file
49
common/sprinklers/SectionRunner.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { IObservableArray, observable } from "mobx";
|
||||||
|
import { Duration } from "./Duration";
|
||||||
|
import { SprinklersDevice } from "./SprinklersDevice";
|
||||||
|
|
||||||
|
export class SectionRun {
|
||||||
|
id: number;
|
||||||
|
section: number;
|
||||||
|
duration: Duration;
|
||||||
|
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 {
|
||||||
|
device: SprinklersDevice;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
queue: IObservableArray<SectionRun> = observable([]);
|
||||||
|
|
||||||
|
@observable
|
||||||
|
current: SectionRun | null = null;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
paused: boolean = false;
|
||||||
|
|
||||||
|
constructor(device: SprinklersDevice) {
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelRunById(id: number): Promise<{}> {
|
||||||
|
return this.device.cancelSectionRunById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `SectionRunner{queue="${this.queue}", current="${this.current}", paused=${this.paused}}`;
|
||||||
|
}
|
||||||
|
}
|
38
common/sprinklers/SprinklersDevice.ts
Normal file
38
common/sprinklers/SprinklersDevice.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { IObservableArray, observable } from "mobx";
|
||||||
|
import { Duration } from "./Duration";
|
||||||
|
import { Program } from "./Program";
|
||||||
|
import { Section } from "./Section";
|
||||||
|
import { SectionRunner } from "./SectionRunner";
|
||||||
|
|
||||||
|
export abstract class SprinklersDevice {
|
||||||
|
@observable
|
||||||
|
connected: boolean = false;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
sections: IObservableArray<Section> = observable.array<Section>();
|
||||||
|
|
||||||
|
@observable
|
||||||
|
programs: IObservableArray<Program> = observable.array<Program>();
|
||||||
|
|
||||||
|
@observable
|
||||||
|
sectionRunner: SectionRunner;
|
||||||
|
|
||||||
|
abstract get id(): string;
|
||||||
|
|
||||||
|
abstract runSection(section: number | Section, duration: Duration): Promise<{}>;
|
||||||
|
|
||||||
|
abstract runProgram(program: number | Program): Promise<{}>;
|
||||||
|
|
||||||
|
abstract cancelSectionRunById(id: number): Promise<{}>;
|
||||||
|
|
||||||
|
abstract pauseSectionRunner(): Promise<{}>;
|
||||||
|
|
||||||
|
abstract unpauseSectionRunner(): Promise<{}>;
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `SprinklersDevice{id="${this.id}", connected=${this.connected},
|
||||||
|
sections=${this.sections},
|
||||||
|
programs=${this.programs},
|
||||||
|
sectionRunner=${this.sectionRunner} }`;
|
||||||
|
}
|
||||||
|
}
|
8
common/sprinklers/index.ts
Normal file
8
common/sprinklers/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { IObservableArray, observable } from "mobx";
|
||||||
|
export * from "./Duration";
|
||||||
|
export * from "./ISprinklersApi";
|
||||||
|
export * from "./Program";
|
||||||
|
export * from "./schedule";
|
||||||
|
export * from "./Section";
|
||||||
|
export * from "./SectionRunner";
|
||||||
|
export * from "./SprinklersDevice";
|
172
common/sprinklers/json/index.ts
Normal file
172
common/sprinklers/json/index.ts
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import { assign, pick } from "lodash";
|
||||||
|
import * as s from "..";
|
||||||
|
|
||||||
|
export interface ISectionJSON {
|
||||||
|
name: string;
|
||||||
|
state: boolean;
|
||||||
|
}
|
||||||
|
const sectionProps = ["name", "state"];
|
||||||
|
|
||||||
|
export function sectionToJSON(sec: s.Section): ISectionJSON {
|
||||||
|
return pick(sec, sectionProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sectionFromJSON(sec: s.Section, json: ISectionJSON) {
|
||||||
|
assign(sec, pick(json, sectionProps));
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITimeOfDayJSON {
|
||||||
|
hour: number;
|
||||||
|
minute: number;
|
||||||
|
second: number;
|
||||||
|
millisecond: number;
|
||||||
|
}
|
||||||
|
const timeOfDayProps = ["hour", "minute", "second", "millisecond"];
|
||||||
|
|
||||||
|
export function timeOfDayToJSON(timeOfDay: s.TimeOfDay): ITimeOfDayJSON {
|
||||||
|
return pick(timeOfDay, timeOfDayProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function timeOfDayFromJSON(timeOfDay: s.TimeOfDay, json: ITimeOfDayJSON) {
|
||||||
|
assign(timeOfDay, pick(json, timeOfDayProps));
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IScheduleJSON {
|
||||||
|
times: ITimeOfDayJSON[];
|
||||||
|
weekdays: number[];
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
}
|
||||||
|
const scheduleProps = ["weekdays", "from", "to"];
|
||||||
|
|
||||||
|
export function scheduleToJSON(schedule: s.Schedule): IScheduleJSON {
|
||||||
|
return {
|
||||||
|
...pick(schedule, scheduleProps),
|
||||||
|
times: schedule.times.map(timeOfDayToJSON),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scheduleFromJSON(schedule: s.Schedule, json: IScheduleJSON) {
|
||||||
|
assign(schedule, pick(json, scheduleProps));
|
||||||
|
schedule.times.length = json.times.length;
|
||||||
|
schedule.times.forEach((timeOfDay, i) =>
|
||||||
|
timeOfDayFromJSON(timeOfDay, json.times[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProgramItemJSON {
|
||||||
|
section: number;
|
||||||
|
duration: number;
|
||||||
|
}
|
||||||
|
const programItemProps = ["section"];
|
||||||
|
|
||||||
|
export function programItemToJSON(programItem: s.ProgramItem): IProgramItemJSON {
|
||||||
|
return {
|
||||||
|
...pick(programItem, programItemProps),
|
||||||
|
duration: programItem.duration.toSeconds(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function programItemFromJSON(programItem: s.ProgramItem, json: IProgramItemJSON) {
|
||||||
|
assign(programItem, pick(json, programItemProps));
|
||||||
|
programItem.duration = s.Duration.fromSeconds(json.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProgramJSON {
|
||||||
|
name: string;
|
||||||
|
enabled: boolean;
|
||||||
|
sequence: IProgramItemJSON[];
|
||||||
|
schedule: IScheduleJSON;
|
||||||
|
running: boolean;
|
||||||
|
}
|
||||||
|
const programProps = ["name", "enabled", "running"];
|
||||||
|
|
||||||
|
export function programToJSON(program: s.Program): IProgramJSON {
|
||||||
|
return {
|
||||||
|
...pick(program, programProps),
|
||||||
|
sequence: program.sequence.map(programItemToJSON),
|
||||||
|
schedule: scheduleToJSON(program.schedule),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function programFromJSON(program: s.Program, json: IProgramJSON) {
|
||||||
|
assign(program, pick(json, programProps));
|
||||||
|
program.sequence.length = json.sequence.length;
|
||||||
|
program.sequence.forEach((programItem, i) =>
|
||||||
|
programItemFromJSON(programItem, json.sequence[i]));
|
||||||
|
scheduleFromJSON(program.schedule, json.schedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISectionRunJSON {
|
||||||
|
id: number;
|
||||||
|
section: number;
|
||||||
|
duration: number;
|
||||||
|
startTime?: number;
|
||||||
|
pauseTime?: number;
|
||||||
|
}
|
||||||
|
const sectionRunProps = ["id", "section", "duration", "startTime", "pauseTime"];
|
||||||
|
|
||||||
|
export function sectionRunToJSON(sectionRun: s.SectionRun): ISectionRunJSON {
|
||||||
|
return pick(sectionRun, sectionRunProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sectionRunFromJSON(sectionRun: s.SectionRun, json: ISectionRunJSON) {
|
||||||
|
assign(sectionRun, pick(json, sectionRunProps));
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISectionRunnerJSON {
|
||||||
|
queue: ISectionRunJSON[];
|
||||||
|
current: ISectionRunJSON | null;
|
||||||
|
paused: boolean;
|
||||||
|
}
|
||||||
|
const sectionRunnerProps = ["paused"];
|
||||||
|
|
||||||
|
export function sectionRunnerToJSON(sectionRunner: s.SectionRunner): ISectionRunnerJSON {
|
||||||
|
return {
|
||||||
|
...pick(sectionRunner, sectionRunnerProps),
|
||||||
|
queue: sectionRunner.queue.map(sectionRunToJSON),
|
||||||
|
current: sectionRunner.current ? sectionRunToJSON(sectionRunner.current) : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sectionRunnerFromJSON(sectionRunner: s.SectionRunner, json: ISectionRunnerJSON) {
|
||||||
|
assign(sectionRunner, pick(json, sectionRunnerProps));
|
||||||
|
sectionRunner.queue.length = json.queue.length;
|
||||||
|
sectionRunner.queue.forEach((sectionRun, i) =>
|
||||||
|
sectionRunFromJSON(sectionRun, json.queue[i]));
|
||||||
|
if (json.current == null) {
|
||||||
|
sectionRunner.current = null;
|
||||||
|
} else {
|
||||||
|
if (sectionRunner.current == null) {
|
||||||
|
sectionRunner.current = new s.SectionRun();
|
||||||
|
}
|
||||||
|
sectionRunFromJSON(sectionRunner.current, json.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISprinklersDeviceJSON {
|
||||||
|
connected: boolean;
|
||||||
|
sections: ISectionJSON[];
|
||||||
|
sectionRunner: ISectionRunnerJSON;
|
||||||
|
programs: IProgramJSON[];
|
||||||
|
}
|
||||||
|
const sprinklersDeviceProps = ["connected"];
|
||||||
|
|
||||||
|
export function sprinklersDeviceToJSON(sprinklersDevice: s.SprinklersDevice): ISprinklersDeviceJSON {
|
||||||
|
return {
|
||||||
|
...pick(sprinklersDevice, sprinklersDeviceProps),
|
||||||
|
sections: sprinklersDevice.sections.map(sectionToJSON),
|
||||||
|
sectionRunner: sectionRunnerToJSON(sprinklersDevice.sectionRunner),
|
||||||
|
programs: sprinklersDevice.programs.map(programToJSON),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sprinklersDeviceFromJSON(sprinklersDevice: s.SprinklersDevice, json: ISprinklersDeviceJSON) {
|
||||||
|
assign(sprinklersDevice, pick(json, sprinklersDeviceProps));
|
||||||
|
sprinklersDevice.sections.length = json.sections.length;
|
||||||
|
sprinklersDevice.sections.forEach((section, i) =>
|
||||||
|
sectionFromJSON(section, json.sections[i]));
|
||||||
|
sectionRunnerFromJSON(sprinklersDevice.sectionRunner, json.sectionRunner);
|
||||||
|
sprinklersDevice.programs.length = json.programs.length;
|
||||||
|
sprinklersDevice.programs.forEach((program, i) =>
|
||||||
|
programFromJSON(program, json.programs[i]));
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import * as mqtt from "mqtt";
|
import * as mqtt from "mqtt";
|
||||||
|
|
||||||
import logger from "./logger";
|
import logger from "@common/logger";
|
||||||
import {
|
import {
|
||||||
Duration,
|
Duration,
|
||||||
ISprinklersApi,
|
ISprinklersApi,
|
||||||
@ -12,8 +12,8 @@ import {
|
|||||||
SectionRunner,
|
SectionRunner,
|
||||||
SprinklersDevice,
|
SprinklersDevice,
|
||||||
TimeOfDay,
|
TimeOfDay,
|
||||||
} from "./sprinklers";
|
} from "@common/sprinklers";
|
||||||
import { checkedIndexOf } from "./utils";
|
import { checkedIndexOf } from "@common/utils";
|
||||||
|
|
||||||
const log = logger.child({ source: "mqtt" });
|
const log = logger.child({ source: "mqtt" });
|
||||||
|
|
28
common/sprinklers/schedule.ts
Normal file
28
common/sprinklers/schedule.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export class TimeOfDay {
|
||||||
|
hour: number;
|
||||||
|
minute: number;
|
||||||
|
second: 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 {
|
||||||
|
Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Schedule {
|
||||||
|
times: TimeOfDay[] = [];
|
||||||
|
weekdays: Weekday[] = [];
|
||||||
|
from: Date | null = null;
|
||||||
|
to: Date | null = null;
|
||||||
|
}
|
@ -35,6 +35,7 @@
|
|||||||
"@types/classnames": "^2.2.0",
|
"@types/classnames": "^2.2.0",
|
||||||
"@types/core-js": "^0.9.43",
|
"@types/core-js": "^0.9.43",
|
||||||
"@types/express": "^4.0.37",
|
"@types/express": "^4.0.37",
|
||||||
|
"@types/lodash": "^4.14.77",
|
||||||
"@types/mqtt": "^0.0.34",
|
"@types/mqtt": "^0.0.34",
|
||||||
"@types/node": "^8.0.6",
|
"@types/node": "^8.0.6",
|
||||||
"@types/object-assign": "^4.0.30",
|
"@types/object-assign": "^4.0.30",
|
||||||
@ -52,6 +53,7 @@
|
|||||||
"express-pino-logger": "^2.0.0",
|
"express-pino-logger": "^2.0.0",
|
||||||
"extract-text-webpack-plugin": "^3.0.0",
|
"extract-text-webpack-plugin": "^3.0.0",
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
"mobx": "^3.1.11",
|
"mobx": "^3.1.11",
|
||||||
"mobx-react": "^4.2.1",
|
"mobx-react": "^4.2.1",
|
||||||
"module-alias": "^2.0.1",
|
"module-alias": "^2.0.1",
|
||||||
|
@ -3,11 +3,11 @@ import "./configureAlias";
|
|||||||
import "env";
|
import "env";
|
||||||
|
|
||||||
import log from "@common/logger";
|
import log from "@common/logger";
|
||||||
import * as mqtt from "@common/mqtt";
|
import * as mqtt from "@common/sprinklers/mqtt";
|
||||||
import { Server } from "http";
|
import { Server } from "http";
|
||||||
import app from "./app";
|
import app from "./app";
|
||||||
|
|
||||||
const mqttClient = new mqtt.MqttApiClient("mqtt://localhost:1882");
|
const mqttClient = new mqtt.MqttApiClient("mqtt://localhost:1883");
|
||||||
|
|
||||||
mqttClient.start();
|
mqttClient.start();
|
||||||
|
|
||||||
@ -15,6 +15,14 @@ import { autorun } from "mobx";
|
|||||||
const device = mqttClient.getDevice("grinklers");
|
const device = mqttClient.getDevice("grinklers");
|
||||||
autorun(() => log.info("device: ", device.toString()));
|
autorun(() => log.info("device: ", device.toString()));
|
||||||
|
|
||||||
|
import * as json from "@common/sprinklers/json";
|
||||||
|
|
||||||
|
app.get("/grinklers", (req, res) => {
|
||||||
|
const j = json.sprinklersDeviceToJSON(device);
|
||||||
|
console.dir(device);
|
||||||
|
res.send(j);
|
||||||
|
});
|
||||||
|
|
||||||
const server = new Server(app);
|
const server = new Server(app);
|
||||||
|
|
||||||
const port = +(process.env.PORT || 8080);
|
const port = +(process.env.PORT || 8080);
|
||||||
|
@ -27,6 +27,10 @@
|
|||||||
"@types/express-serve-static-core" "*"
|
"@types/express-serve-static-core" "*"
|
||||||
"@types/serve-static" "*"
|
"@types/serve-static" "*"
|
||||||
|
|
||||||
|
"@types/lodash@^4.14.77":
|
||||||
|
version "4.14.77"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.77.tgz#0bc699413e84d6ed5d927ca30ea0f0a890b42d75"
|
||||||
|
|
||||||
"@types/mime@*":
|
"@types/mime@*":
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.1.tgz#2cf42972d0931c1060c7d5fa6627fce6bd876f2f"
|
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.1.tgz#2cf42972d0931c1060c7d5fa6627fce6bd876f2f"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user