Browse Source

Much better serialization/deserialization for sprinklers

update-deps
Alex Mikhalev 7 years ago
parent
commit
e5cc6b86e7
  1. 1
      .vscode/launch.json
  2. 2
      common/logger.ts
  3. 2
      common/sprinklers/Program.ts
  4. 4
      common/sprinklers/SprinklersDevice.ts
  5. 277
      common/sprinklers/json/index.ts
  6. 65
      common/sprinklers/json/list.ts
  7. 144
      common/sprinklers/mqtt/index.ts
  8. 37
      common/sprinklers/schedule.ts
  9. 3
      package.json
  10. 3
      server/configureLogger.ts
  11. 8
      server/index.ts
  12. 10
      yarn.lock

1
.vscode/launch.json vendored

@ -11,6 +11,7 @@
"env": { "env": {
"NODE_ENV": "development" "NODE_ENV": "development"
}, },
"console": "integratedTerminal",
"program": "${workspaceRoot}/dist/server/index.js" "program": "${workspaceRoot}/dist/server/index.js"
} }
] ]

2
common/logger.ts

@ -135,7 +135,7 @@ let logger: pino.Logger = pino({
}); });
export function setLogger(newLogger: pino.Logger) { export function setLogger(newLogger: pino.Logger) {
logger = newLogger; exports.default = logger = newLogger;
} }
export default logger; export default logger;

2
common/sprinklers/Program.ts

@ -9,7 +9,7 @@ export class ProgramItem {
// duration of the run // duration of the run
readonly duration: Duration; readonly duration: Duration;
constructor(section: number, duration: Duration) { constructor(section: number = 0, duration: Duration = new Duration()) {
this.section = section; this.section = section;
this.duration = duration; this.duration = duration;
} }

4
common/sprinklers/SprinklersDevice.ts

@ -17,6 +17,10 @@ export abstract class SprinklersDevice {
abstract pauseSectionRunner(): Promise<{}>; abstract pauseSectionRunner(): Promise<{}>;
abstract unpauseSectionRunner(): Promise<{}>; abstract unpauseSectionRunner(): Promise<{}>;
abstract get sectionConstructor(): typeof Section;
abstract get sectionRunnerConstructor(): typeof SectionRunner;
abstract get programConstructor(): typeof Program;
toString(): string { toString(): string {
return `SprinklersDevice{id="${this.id}", connected=${this.connected}, ` + return `SprinklersDevice{id="${this.id}", connected=${this.connected}, ` +
`sections=[${this.sections}], ` + `sections=[${this.sections}], ` +

277
common/sprinklers/json/index.ts

@ -1,175 +1,108 @@
/* tslint:disable:ordered-imports */
import { assign, pick } from "lodash"; import { assign, pick } from "lodash";
import {
createSimpleSchema, createModelSchema, primitive, object, date, custom,
ModelSchema, PropSchema,
} from "serializr";
import list from "./list";
import * as s from ".."; import * as s from "..";
export interface ISectionJSON { export const durationSchema: PropSchema = {
name: string; serializer: (duration: s.Duration | null) =>
state: boolean; duration != null ? duration.toSeconds() : null,
} deserializer: (json: any) =>
const sectionProps = ["name", "state"]; typeof json === "number" ? s.Duration.fromSeconds(json) : null,
};
export function sectionToJSON(sec: s.Section): ISectionJSON {
return pick(sec, sectionProps); export const dateSchema: PropSchema = {
} serializer: (jsDate: Date | null) => jsDate != null ?
jsDate.toISOString() : null,
export function sectionFromJSON(sec: s.Section, json: ISectionJSON) { deserializer: (json: any) => typeof json === "string" ?
assign(sec, pick(json, sectionProps)); new Date(json) : null,
} };
export interface ITimeOfDayJSON { export const dateOfYearSchema: ModelSchema<s.DateOfYear> = {
hour: number; factory: () => new s.DateOfYear(),
minute: number; props: {
second: number; year: primitive(),
millisecond: number; month: primitive(), // this only works if it is represented as a # from 0-12
} day: primitive(),
const timeOfDayProps = ["hour", "minute", "second", "millisecond"]; },
};
export function timeOfDayToJSON(timeOfDay: s.TimeOfDay): ITimeOfDayJSON {
return pick(timeOfDay, timeOfDayProps); export const timeOfDaySchema: ModelSchema<s.TimeOfDay> = {
} factory: () => new s.TimeOfDay(),
props: {
export function timeOfDayFromJSON(timeOfDay: s.TimeOfDay, json: ITimeOfDayJSON) { hour: primitive(),
assign(timeOfDay, pick(json, timeOfDayProps)); minute: primitive(),
} second: primitive(),
millisecond: primitive(),
export interface IScheduleJSON { },
times: ITimeOfDayJSON[]; };
weekdays: number[];
from?: string; export const sectionSchema: ModelSchema<s.Section> = {
to?: string; factory: (c) => new (c.parentContext.target as s.SprinklersDevice).sectionConstructor(
} c.parentContext.target, c.json.id),
const scheduleProps = ["weekdays", "from", "to"]; props: {
name: primitive(),
export function scheduleToJSON(schedule: s.Schedule): IScheduleJSON { state: primitive(),
return { },
...pick(schedule, scheduleProps), };
times: schedule.times.map(timeOfDayToJSON),
}; export const sectionRunSchema: ModelSchema<s.SectionRun> = {
} factory: (c) => new s.SectionRun(c.json.id),
props: {
export function scheduleFromJSON(schedule: s.Schedule, json: IScheduleJSON) { id: primitive(),
assign(schedule, pick(json, scheduleProps)); section: primitive(),
schedule.times.length = json.times.length; duration: durationSchema,
schedule.times.forEach((timeOfDay, i) => startTime: dateSchema,
timeOfDayFromJSON(timeOfDay, json.times[i])); endTime: dateSchema,
} },
};
export interface IProgramItemJSON {
section: number; export const sectionRunnerSchema: ModelSchema<s.SectionRunner> = {
duration: number; factory: (c) => new (c.parentContext.target as s.SprinklersDevice).sectionRunnerConstructor(
} c.parentContext.target),
const programItemProps = ["section"]; props: {
queue: list(object(sectionRunSchema)),
export function programItemToJSON(programItem: s.ProgramItem): IProgramItemJSON { current: object(sectionRunSchema),
return { paused: primitive(),
...pick(programItem, programItemProps), },
duration: programItem.duration.toSeconds(), };
};
} export const scheduleSchema: ModelSchema<s.Schedule> = {
factory: () => new s.Schedule(),
export function programItemFromJSON(json: IProgramItemJSON): s.ProgramItem { props: {
const duration = s.Duration.fromSeconds(json.duration); times: list(object(timeOfDaySchema)),
return new s.ProgramItem(json.section, duration); weekdays: list(primitive()),
} from: object(dateOfYearSchema),
to: object(dateOfYearSchema),
export interface IProgramJSON { },
name: string; };
enabled: boolean;
sequence: IProgramItemJSON[]; export const programItemSchema: ModelSchema<s.ProgramItem> = {
schedule: IScheduleJSON; factory: () => new s.ProgramItem(),
running: boolean; props: {
} section: primitive(),
const programProps = ["name", "enabled", "running"]; duration: durationSchema,
},
export function programToJSON(program: s.Program): IProgramJSON { };
return {
...pick(program, programProps), export const programSchema: ModelSchema<s.Program> = {
sequence: program.sequence.map(programItemToJSON), factory: (c) => new (c.parentContext.target as s.SprinklersDevice).programConstructor(
schedule: scheduleToJSON(program.schedule), c.parentContext.target, c.json.id),
}; props: {
} name: primitive(),
enabled: primitive(),
export function programFromJSON(program: s.Program, json: IProgramJSON) { schedule: object(scheduleSchema),
assign(program, pick(json, programProps)); sequence: list(object(programItemSchema)),
program.sequence = json.sequence.map((programItemJson) => running: primitive(),
programItemFromJSON(programItemJson)); },
scheduleFromJSON(program.schedule, json.schedule); };
}
export const sprinklersDeviceSchema = createSimpleSchema({
export interface ISectionRunJSON { connected: primitive(),
id: number; sections: list(object(sectionSchema)),
section: number; sectionRunner: object(sectionRunnerSchema),
duration: number; programs: list(object(programSchema)),
startTime?: number; });
pauseTime?: number;
}
const sectionRunProps = ["id", "section", "duration", "startTime", "pauseTime"];
export function sectionRunToJSON(sectionRun: s.SectionRun): ISectionRunJSON {
return {
...pick(sectionRun, sectionRunProps),
duration: sectionRun.duration.toSeconds(),
};
}
export function sectionRunFromJSON(sectionRun: s.SectionRun, json: ISectionRunJSON) {
assign(sectionRun, pick(json, sectionRunProps));
sectionRun.duration = s.Duration.fromSeconds(json.duration);
}
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]));
}

65
common/sprinklers/json/list.ts

@ -0,0 +1,65 @@
import { primitive, PropSchema } from "serializr";
function invariant(cond: boolean, message?: string) {
if (!cond) {
throw new Error("[serializr] " + (message || "Illegal State"));
}
}
function isPropSchema(thing: any) {
return thing && thing.serializer && thing.deserializer;
}
function isAliasedPropSchema(propSchema: any) {
return typeof propSchema === "object" && !!propSchema.jsonname;
}
function parallel(ar: any[], processor: (item: any, done: any) => void, cb: any) {
if (ar.length === 0) {
return void cb(null, []);
}
let left = ar.length;
const resultArray: any[] = [];
let failed = false;
const processorCb = (idx: number, err: any, result: any) => {
if (err) {
if (!failed) {
failed = true;
cb(err);
}
} else if (!failed) {
resultArray[idx] = result;
if (--left === 0) {
cb(null, resultArray);
}
}
};
ar.forEach((value, idx) => processor(value, processorCb.bind(null, idx)));
}
export default function list(propSchema: PropSchema): PropSchema {
propSchema = propSchema || primitive();
invariant(isPropSchema(propSchema), "expected prop schema as first argument");
invariant(!isAliasedPropSchema(propSchema), "provided prop is aliased, please put aliases first");
return {
serializer(ar) {
invariant(ar && typeof ar.length === "number" && typeof ar.map === "function",
"expected array (like) object");
return ar.map(propSchema.serializer);
},
deserializer(jsonArray, done, context) {
if (jsonArray === null) { // sometimes go will return null in place of empty array
return void done(null, []);
}
if (!Array.isArray(jsonArray)) {
return void done("[serializr] expected JSON array", null);
}
parallel(
jsonArray,
(item: any, itemDone: (err: any, targetPropertyValue: any) => void) =>
propSchema.deserializer(item, itemDone, context, undefined),
done,
);
},
};
}

144
common/sprinklers/mqtt/index.ts

@ -1,23 +1,15 @@
import { cloneDeep } from "lodash";
import * as mqtt from "mqtt"; import * as mqtt from "mqtt";
import { deserialize, update } from "serializr";
import logger from "@common/logger"; import logger from "@common/logger";
import { import * as s from "@common/sprinklers";
Duration, import * as schema from "@common/sprinklers/json";
ISprinklersApi,
Program,
ProgramItem,
Schedule,
Section,
SectionRun,
SectionRunner,
SprinklersDevice,
TimeOfDay,
} from "@common/sprinklers";
import { checkedIndexOf } from "@common/utils"; import { checkedIndexOf } from "@common/utils";
const log = logger.child({ source: "mqtt" }); const log = logger.child({ source: "mqtt" });
export class MqttApiClient implements ISprinklersApi { export class MqttApiClient implements s.ISprinklersApi {
readonly mqttUri: string; readonly mqttUri: string;
client: mqtt.Client; client: mqtt.Client;
connected: boolean; connected: boolean;
@ -54,7 +46,7 @@ export class MqttApiClient implements ISprinklersApi {
}); });
} }
getDevice(prefix: string): SprinklersDevice { getDevice(prefix: string): s.SprinklersDevice {
if (/\//.test(prefix)) { if (/\//.test(prefix)) {
throw new Error("Prefix cannot contain a /"); throw new Error("Prefix cannot contain a /");
} }
@ -84,7 +76,8 @@ export class MqttApiClient implements ISprinklersApi {
} }
} }
private processMessage(topic: string, payload: Buffer, packet: mqtt.Packet) { private processMessage(topic: string, payloadBuf: Buffer, packet: mqtt.Packet) {
const payload = payloadBuf.toString("utf8");
log.trace({ topic, payload }, "message arrived: "); log.trace({ topic, payload }, "message arrived: ");
const topicIdx = topic.indexOf("/"); // find the first / const topicIdx = topic.indexOf("/"); // find the first /
const prefix = topic.substr(0, topicIdx); // assume prefix does not contain a / const prefix = topic.substr(0, topicIdx); // assume prefix does not contain a /
@ -108,7 +101,7 @@ const subscriptions = [
"/section_runner", "/section_runner",
]; ];
class MqttSprinklersDevice extends SprinklersDevice { class MqttSprinklersDevice extends s.SprinklersDevice {
readonly apiClient: MqttApiClient; readonly apiClient: MqttApiClient;
readonly prefix: string; readonly prefix: string;
@ -127,6 +120,10 @@ class MqttSprinklersDevice extends SprinklersDevice {
return this.prefix; return this.prefix;
} }
get sectionConstructor() { return MqttSection; }
get sectionRunnerConstructor() { return MqttSectionRunner; }
get programConstructor() { return MqttProgram; }
doSubscribe() { doSubscribe() {
const topics = subscriptions.map((filter) => this.prefix + filter); const topics = subscriptions.map((filter) => this.prefix + filter);
this.apiClient.client.subscribe(topics, { qos: 1 }); this.apiClient.client.subscribe(topics, { qos: 1 });
@ -142,8 +139,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
* @param topic The topic, with prefix removed * @param topic The topic, with prefix removed
* @param payload The payload buffer * @param payload The payload buffer
*/ */
onMessage(topic: string, payloadBuf: Buffer) { onMessage(topic: string, payload: string) {
const payload = payloadBuf.toString("utf8");
if (topic === "connected") { if (topic === "connected") {
this.connected = (payload === "true"); this.connected = (payload === "true");
log.trace(`MqttSprinklersDevice with prefix ${this.prefix}: ${this.connected}`); log.trace(`MqttSprinklersDevice with prefix ${this.prefix}: ${this.connected}`);
@ -207,7 +203,7 @@ class MqttSprinklersDevice extends SprinklersDevice {
log.warn({ topic }, "MqttSprinklersDevice recieved invalid message"); log.warn({ topic }, "MqttSprinklersDevice recieved invalid message");
} }
runSection(section: Section | number, duration: Duration) { runSection(section: s.Section | number, duration: s.Duration) {
const sectionNum = checkedIndexOf(section, this.sections, "Section"); const sectionNum = checkedIndexOf(section, this.sections, "Section");
const payload: IRunSectionJSON = { const payload: IRunSectionJSON = {
duration: duration.toSeconds(), duration: duration.toSeconds(),
@ -215,9 +211,9 @@ class MqttSprinklersDevice extends SprinklersDevice {
return this.makeRequest(`sections/${sectionNum}/run`, payload); return this.makeRequest(`sections/${sectionNum}/run`, payload);
} }
runProgram(program: Program | number) { runProgram(program: s.Program | number) {
const programNum = checkedIndexOf(program, this.programs, "Program"); const programNum = checkedIndexOf(program, this.programs, "Program");
return this.makeRequest(`programs/${programNum}/run`, {}); return this.makeRequest(`programs/${programNum}/run`);
} }
cancelSectionRunById(id: number) { cancelSectionRunById(id: number) {
@ -273,120 +269,40 @@ interface IRunSectionJSON {
duration: number; duration: number;
} }
class MqttSection extends Section { class MqttSection extends s.Section {
onMessage(topic: string, payload: string) { onMessage(topic: string, payload: string) {
if (topic === "state") { if (topic === "state") {
this.state = (payload === "true"); this.state = (payload === "true");
} else if (topic == null) { } else if (topic == null) {
const json = JSON.parse(payload) as ISectionJSON; this.updateFromJSON(JSON.parse(payload));
this.name = json.name;
} }
} }
}
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 {
times: ITimeOfDayJSON[];
weekdays: number[];
from?: string;
to?: string;
}
function scheduleFromJSON(json: IScheduleJSON): Schedule {
const sched = new Schedule();
sched.times = json.times.map(timeOfDayFromJSON);
sched.weekdays = json.weekdays;
sched.from = json.from == null ? null : new Date(json.from);
sched.to = json.to == null ? null : new Date(json.to);
return sched;
}
interface IProgramItemJSON {
section: number;
duration: number;
}
interface IProgramJSON { updateFromJSON(json: any) {
name: string; update(schema.sectionSchema, this, json);
enabled: boolean; }
sequence: IProgramItemJSON[];
sched: IScheduleJSON;
} }
class MqttProgram extends Program { class MqttProgram extends s.Program {
onMessage(topic: string, payload: string) { onMessage(topic: string, payload: string) {
if (topic === "running") { if (topic === "running") {
this.running = (payload === "true"); this.running = (payload === "true");
} else if (topic == null) { } else if (topic == null) {
const json = JSON.parse(payload) as Partial<IProgramJSON>; this.updateFromJSON(JSON.parse(payload));
this.updateFromJSON(json);
} }
} }
updateFromJSON(json: Partial<IProgramJSON>) { updateFromJSON(json: any) {
if (json.name != null) { update(schema.programSchema, this, json);
this.name = json.name;
}
if (json.enabled != null) {
this.enabled = json.enabled;
}
if (json.sequence != null) {
this.sequence = json.sequence.map((item) => (new ProgramItem(
item.section,
Duration.fromSeconds(item.duration),
)));
}
if (json.sched != null) {
this.schedule = scheduleFromJSON(json.sched);
}
} }
} }
export interface ISectionRunJSON { class MqttSectionRunner extends s.SectionRunner {
id: number;
section: number;
duration: number;
startTime?: number;
pauseTime?: number;
}
function sectionRunFromJSON(json: ISectionRunJSON) {
const run = new SectionRun(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 {
queue: ISectionRunJSON[];
current: ISectionRunJSON | null;
paused: boolean;
}
class MqttSectionRunner extends SectionRunner {
onMessage(payload: string) { onMessage(payload: string) {
const json = JSON.parse(payload) as ISectionRunnerJSON; this.updateFromJSON(JSON.parse(payload));
this.updateFromJSON(json);
} }
updateFromJSON(json: ISectionRunnerJSON) { updateFromJSON(json: any) {
this.queue.length = 0; update(schema.sectionRunnerSchema, this, json);
if (json.queue && json.queue.length) { // null means empty queue
this.queue.push.apply(this.queue, json.queue.map(sectionRunFromJSON));
}
this.current = json.current == null ? null : sectionRunFromJSON(json.current);
this.paused = json.paused;
} }
} }

37
common/sprinklers/schedule.ts

@ -6,7 +6,7 @@ export class TimeOfDay {
readonly second: number; readonly second: number;
readonly millisecond: number; readonly millisecond: number;
constructor(hour: number, minute: number = 0, second: number = 0, millisecond: number = 0) { constructor(hour: number = 0, minute: number = 0, second: number = 0, millisecond: number = 0) {
this.hour = hour; this.hour = hour;
this.minute = minute; this.minute = minute;
this.second = second; this.second = second;
@ -22,9 +22,40 @@ export enum Weekday {
Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday,
} }
export enum Month {
January = 1,
February = 2,
March = 3,
April = 4,
May = 5,
June = 6,
July = 7,
August = 8,
September = 9,
October = 10,
November = 11,
December = 12,
}
export class DateOfYear {
readonly day: number;
readonly month: Month;
readonly year: number;
constructor(day: number = 0, month: Month = Month.January, year: number = 0) {
this.day = day;
this.month = month;
this.year = year;
}
toString() {
return `${Month[this.month]} ${this.day}, ${this.year}`;
}
}
export class Schedule { export class Schedule {
@observable times: TimeOfDay[] = []; @observable times: TimeOfDay[] = [];
@observable weekdays: Weekday[] = []; @observable weekdays: Weekday[] = [];
@observable from: Date | null = null; @observable from: DateOfYear | null = null;
@observable to: Date | null = null; @observable to: DateOfYear | null = null;
} }

3
package.json

@ -31,6 +31,7 @@
}, },
"homepage": "https://github.com/amikhalev/sprinklers3#readme", "homepage": "https://github.com/amikhalev/sprinklers3#readme",
"dependencies": { "dependencies": {
"@types/async": "^2.0.43",
"@types/chalk": "^0.4.31", "@types/chalk": "^0.4.31",
"@types/classnames": "^2.2.0", "@types/classnames": "^2.2.0",
"@types/core-js": "^0.9.43", "@types/core-js": "^0.9.43",
@ -45,6 +46,7 @@
"@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-hot-loader": "^3.0.4",
"async": "^2.5.0",
"autoprefixer": "^7.1.4", "autoprefixer": "^7.1.4",
"case-sensitive-paths-webpack-plugin": "^2.1.1", "case-sensitive-paths-webpack-plugin": "^2.1.1",
"classnames": "^2.2.5", "classnames": "^2.2.5",
@ -70,6 +72,7 @@
"react-fontawesome": "^1.6.1", "react-fontawesome": "^1.6.1",
"semantic-ui-css": "^2.2.10", "semantic-ui-css": "^2.2.10",
"semantic-ui-react": "^0.73.0", "semantic-ui-react": "^0.73.0",
"serializr": "^1.1.13",
"tslint-loader": "^3.5.3", "tslint-loader": "^3.5.3",
"uglifyjs-webpack-plugin": "^0.4.6", "uglifyjs-webpack-plugin": "^0.4.6",
"url-loader": "^0.5.9" "url-loader": "^0.5.9"

3
server/configureLogger.ts

@ -1,6 +1,5 @@
import log, { setLogger } from "@common/logger"; import log, { setLogger } from "@common/logger";
setLogger(log.child({ setLogger(log.child({
name: "sprinklers3/server", name: "sprinklers3/server",
level: "trace", level: "debug",
})); }));

8
server/index.ts

@ -1,6 +1,7 @@
/* tslint:disable:ordered-imports */
import "./configureAlias"; import "./configureAlias";
import "env"; import "env";
import "./configureLogger";
import log from "@common/logger"; import log from "@common/logger";
import * as mqtt from "@common/sprinklers/mqtt"; import * as mqtt from "@common/sprinklers/mqtt";
@ -11,12 +12,13 @@ const mqttClient = new mqtt.MqttApiClient("mqtt://localhost:1883");
mqttClient.start(); mqttClient.start();
import * as sjson from "@common/sprinklers/json"; import { sprinklersDeviceSchema } from "@common/sprinklers/json";
import { autorun } from "mobx"; import { autorun } from "mobx";
import { serialize } from "serializr";
const device = mqttClient.getDevice("grinklers"); const device = mqttClient.getDevice("grinklers");
app.get("/api/grinklers", (req, res) => { app.get("/api/grinklers", (req, res) => {
const j = sjson.sprinklersDeviceToJSON(device); const j = serialize(sprinklersDeviceSchema, device);
log.trace(j); log.trace(j);
res.send(j); res.send(j);
}); });

10
yarn.lock

@ -2,6 +2,10 @@
# yarn lockfile v1 # yarn lockfile v1
"@types/async@^2.0.43":
version "2.0.43"
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.43.tgz#0d6fce7e11a582b4251a4bf5439399428c79e387"
"@types/chalk@^0.4.31": "@types/chalk@^0.4.31":
version "0.4.31" version "0.4.31"
resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9" resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9"
@ -334,7 +338,7 @@ async@^1.5.2:
version "1.5.2" version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.1.2, async@^2.4.1: async@^2.1.2, async@^2.4.1, async@^2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
dependencies: dependencies:
@ -4706,6 +4710,10 @@ send@0.15.4:
range-parser "~1.2.0" range-parser "~1.2.0"
statuses "~1.3.1" statuses "~1.3.1"
serializr@^1.1.13:
version "1.1.13"
resolved "https://registry.yarnpkg.com/serializr/-/serializr-1.1.13.tgz#21b20f220fcf94caaecd86eb09e438cde6532b25"
serve-index@^1.7.2: serve-index@^1.7.2:
version "1.9.0" version "1.9.0"
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.0.tgz#d2b280fc560d616ee81b48bf0fa82abed2485ce7" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.0.tgz#d2b280fc560d616ee81b48bf0fa82abed2485ce7"

Loading…
Cancel
Save