]: T[P]};
-
-export function injectState(Component: React.ComponentType
):
- React.ComponentClass> {
- return class extends React.Component> {
- render() {
- const consumeState = (state: AppState | null) => {
- if (state == null) {
- throw new Error("Component with injectState must be mounted inside ProvideState");
- }
- return ;
- };
- return {consumeState};
+type Diff<
+ T extends string | number | symbol,
+ U extends string | number | symbol
+> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
+type Omit = { [P in Diff]: T[P] };
+
+export function injectState(
+ Component: React.ComponentType
+): React.ComponentClass> {
+ return class extends React.Component> {
+ render() {
+ const consumeState = (state: AppState | null) => {
+ if (state == null) {
+ throw new Error(
+ "Component with injectState must be mounted inside ProvideState"
+ );
}
- };
+ return ;
+ };
+ return {consumeState};
+ }
+ };
}
diff --git a/client/styles/DeviceView.scss b/client/styles/DeviceView.scss
index a04286c..4b5fc26 100644
--- a/client/styles/DeviceView.scss
+++ b/client/styles/DeviceView.scss
@@ -11,35 +11,34 @@
}
.connectionState {
@media only screen and (min-width: 768px) {
- margin-left: .75em;
+ margin-left: 0.75em;
}
- font-size: .75em;
+ font-size: 0.75em;
font-weight: lighter;
&.connected {
- color: #13D213;
+ color: #13d213;
}
&.disconnected {
- color: #D20000;
+ color: #d20000;
}
}
}
.section--number,
.program--number {
- width: 2em
+ width: 2em;
}
.section--name /*,
.program--name*/
-{
+ {
width: 10em;
white-space: nowrap;
}
.section--state {
-
}
.section-state.running {
diff --git a/client/styles/DurationView.scss b/client/styles/DurationView.scss
index b204e80..1dc7db9 100644
--- a/client/styles/DurationView.scss
+++ b/client/styles/DurationView.scss
@@ -1,4 +1,4 @@
-$durationInput-spacing: 1.0em;
+$durationInput-spacing: 1em;
$durationInput-inputWidth: 4em;
$durationInput-labelWidth: 2.5em;
@@ -10,7 +10,7 @@ $durationInput-labelWidth: 2.5em;
width: auto;
- .ui.input.durationInput>input {
+ .ui.input.durationInput > input {
width: 0 !important;
}
@@ -22,7 +22,7 @@ $durationInput-labelWidth: 2.5em;
flex-shrink: 0;
flex-grow: 1;
- >input {
+ > input {
min-width: $durationInput-inputWidth;
width: auto;
flex-basis: $durationInput-inputWidth;
@@ -30,7 +30,7 @@ $durationInput-labelWidth: 2.5em;
flex-shrink: 0;
}
- >.label {
+ > .label {
min-width: $durationInput-labelWidth;
width: $durationInput-labelWidth;
flex: $durationInput-labelWidth;
@@ -39,4 +39,4 @@ $durationInput-labelWidth: 2.5em;
text-align: center;
}
}
-}
\ No newline at end of file
+}
diff --git a/client/styles/ProgramSequenceView.scss b/client/styles/ProgramSequenceView.scss
index 8108fd2..16c8120 100644
--- a/client/styles/ProgramSequenceView.scss
+++ b/client/styles/ProgramSequenceView.scss
@@ -8,7 +8,7 @@
.programSequence-item {
list-style-type: none;
display: flex;
- margin-bottom: .5em;
+ margin-bottom: 0.5em;
&.dragging {
z-index: 1010;
}
diff --git a/client/styles/ScheduleView.scss b/client/styles/ScheduleView.scss
index 01f8e29..5600002 100644
--- a/client/styles/ScheduleView.scss
+++ b/client/styles/ScheduleView.scss
@@ -1,13 +1,14 @@
.scheduleView {
- >.field, >.fields {
- >label {
- width: 2rem !important;
- }
+ > .field,
+ > .fields {
+ > label {
+ width: 2rem !important;
}
+ }
}
.scheduleTimes {
- input {
- margin: 0 .5rem;
- }
+ input {
+ margin: 0 0.5rem;
+ }
}
diff --git a/client/styles/SectionRunnerView.scss b/client/styles/SectionRunnerView.scss
index 8b9bd4d..c84d0dc 100644
--- a/client/styles/SectionRunnerView.scss
+++ b/client/styles/SectionRunnerView.scss
@@ -5,13 +5,13 @@
}
.sectionRunner--pausedState {
- padding-left: .75em;
- font-size: .75em;
+ padding-left: 0.75em;
+ font-size: 0.75em;
font-weight: lighter;
}
.sectionRunner--pausedState > .fa {
- padding-right: .2em;
+ padding-right: 0.2em;
}
.sectionRunner--pausedState-unpaused {
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 60bc6b8..4f9d006 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -26,9 +26,7 @@
]
}
},
- "references": [
- {
- "path": "../common"
- }
- ]
+ "references": [{
+ "path": "../common"
+ }]
}
\ No newline at end of file
diff --git a/client/webpack.config.js b/client/webpack.config.js
index 033b6d1..e08abc4 100644
--- a/client/webpack.config.js
+++ b/client/webpack.config.js
@@ -11,7 +11,9 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HappyPack = require("happypack");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
-const {getClientEnvironment} = require("./env");
+const {
+ getClientEnvironment
+} = require("./env");
const paths = require("../paths");
// Webpack uses `publicPath` to determine where the app is being served from.
@@ -83,49 +85,48 @@ const rules = (env) => {
sassConfig,
],
};
- return [
- {
- // "oneOf" will traverse all following loaders until one will
- // match the requirements. when no loader matches it will fall
- // back to the "file" loader at the end of the loader list.
- oneOf: [
- // "url" loader works like "file" loader except that it embeds assets
- // smaller than specified limit in bytes as data urls to avoid requests.
- // a missing `test` is equivalent to a match.
- {
- test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
- loader: require.resolve("url-loader"),
- options: {
- limit: (env === "prod") ? 10000 : 0,
- name: "static/media/[name].[hash:8].[ext]",
- },
- },
- cssRule,
- sassRule,
- // Process TypeScript with TSC through HappyPack.
- {
- test: /\.tsx?$/, use: "happypack/loader?id=ts",
- include: [ paths.clientDir, paths.commonDir ],
+ return [{
+ // "oneOf" will traverse all following loaders until one will
+ // match the requirements. when no loader matches it will fall
+ // back to the "file" loader at the end of the loader list.
+ oneOf: [
+ // "url" loader works like "file" loader except that it embeds assets
+ // smaller than specified limit in bytes as data urls to avoid requests.
+ // a missing `test` is equivalent to a match.
+ {
+ test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
+ loader: require.resolve("url-loader"),
+ options: {
+ limit: (env === "prod") ? 10000 : 0,
+ name: "static/media/[name].[hash:8].[ext]",
},
- // "file" loader makes sure those assets get served by WebpackDevServer.
- // When you `import` an asset, you get its (virtual) filename.
- // In production, they would get copied to the `build` folder.
- // This loader doesn"t use a "test" so it will catch all modules
- // that fall through the other loaders.
- {
- // Exclude `js` files to keep "css" loader working as it injects
- // it"s runtime that would otherwise processed through "file" loader.
- // Also exclude `html` and `json` extensions so they get processed
- // by webpacks internal loaders.
- exclude: [/\.js$/, /\.html$/, /\.json$/],
- loader: require.resolve("file-loader"),
- options: {
- name: "static/media/[name].[hash:8].[ext]",
- },
+ },
+ cssRule,
+ sassRule,
+ // Process TypeScript with TSC through HappyPack.
+ {
+ test: /\.tsx?$/,
+ use: "happypack/loader?id=ts",
+ include: [paths.clientDir, paths.commonDir],
+ },
+ // "file" loader makes sure those assets get served by WebpackDevServer.
+ // When you `import` an asset, you get its (virtual) filename.
+ // In production, they would get copied to the `build` folder.
+ // This loader doesn"t use a "test" so it will catch all modules
+ // that fall through the other loaders.
+ {
+ // Exclude `js` files to keep "css" loader working as it injects
+ // it"s runtime that would otherwise processed through "file" loader.
+ // Also exclude `html` and `json` extensions so they get processed
+ // by webpacks internal loaders.
+ exclude: [/\.js$/, /\.html$/, /\.json$/],
+ loader: require.resolve("file-loader"),
+ options: {
+ name: "static/media/[name].[hash:8].[ext]",
},
- ],
- },
- ];
+ },
+ ],
+ }, ];
}
@@ -200,8 +201,7 @@ const getConfig = module.exports = (env) => {
mode: isProd ? "production" : "development",
bail: isProd,
devtool: shouldUseSourceMap ?
- isProd ? "source-map" : "inline-source-map" :
- false,
+ isProd ? "source-map" : "inline-source-map" : false,
entry: [
isDev && require.resolve("react-hot-loader/patch"),
isDev && require.resolve("react-dev-utils/webpackHotDevClient"),
@@ -212,15 +212,13 @@ const getConfig = module.exports = (env) => {
path: paths.clientBuildDir,
pathinfo: isDev,
filename: isProd ?
- 'static/js/[name].[chunkhash:8].js' :
- "static/js/bundle.js",
+ 'static/js/[name].[chunkhash:8].js' : "static/js/bundle.js",
chunkFilename: isProd ?
- 'static/js/[name].[chunkhash:8].chunk.js' :
- "static/js/[name].chunk.js",
+ 'static/js/[name].[chunkhash:8].chunk.js' : "static/js/[name].chunk.js",
publicPath: publicPath,
devtoolModuleFilenameTemplate: isDev ?
(info) =>
- "webpack://" + path.resolve(info.absoluteResourcePath).replace(/\\/g, "/") : undefined,
+ "webpack://" + path.resolve(info.absoluteResourcePath).replace(/\\/g, "/") : undefined,
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json", ".scss"],
@@ -247,4 +245,4 @@ const getConfig = module.exports = (env) => {
}],
},
}
-};
+};
\ No newline at end of file
diff --git a/common/ApiError.ts b/common/ApiError.ts
index 2803420..972ee11 100644
--- a/common/ApiError.ts
+++ b/common/ApiError.ts
@@ -1,26 +1,32 @@
import { ErrorCode, toHttpStatus } from "@common/ErrorCode";
export default class ApiError extends Error {
- name = "ApiError";
- statusCode: number;
- code: ErrorCode;
- data: any;
+ name = "ApiError";
+ statusCode: number;
+ code: ErrorCode;
+ data: any;
- constructor(message: string, code: ErrorCode = ErrorCode.BadRequest, data: any = {}) {
- super(message);
- this.statusCode = toHttpStatus(code);
- this.code = code;
- // tslint:disable-next-line:prefer-conditional-expression
- if (data instanceof Error) {
- this.data = data.toString();
- } else {
- this.data = data;
- }
+ constructor(
+ message: string,
+ code: ErrorCode = ErrorCode.BadRequest,
+ data: any = {}
+ ) {
+ super(message);
+ this.statusCode = toHttpStatus(code);
+ this.code = code;
+ // tslint:disable-next-line:prefer-conditional-expression
+ if (data instanceof Error) {
+ this.data = data.toString();
+ } else {
+ this.data = data;
}
+ }
- toJSON() {
- return {
- message: this.message, code: this.code, data: this.data,
- };
- }
+ toJSON() {
+ return {
+ message: this.message,
+ code: this.code,
+ data: this.data
+ };
+ }
}
diff --git a/common/Duration.ts b/common/Duration.ts
index 1f86fd4..ee7c460 100644
--- a/common/Duration.ts
+++ b/common/Duration.ts
@@ -1,41 +1,41 @@
export class Duration {
- static fromSeconds(seconds: number): Duration {
- return new Duration(Math.floor(seconds / 60), seconds % 60);
- }
+ static fromSeconds(seconds: number): Duration {
+ return new Duration(Math.floor(seconds / 60), seconds % 60);
+ }
- minutes: number = 0;
- seconds: number = 0;
+ minutes: number = 0;
+ seconds: number = 0;
- constructor(minutes: number = 0, seconds: number = 0) {
- this.minutes = minutes;
- this.seconds = seconds;
- }
+ constructor(minutes: number = 0, seconds: number = 0) {
+ this.minutes = minutes;
+ this.seconds = seconds;
+ }
- toSeconds(): number {
- return this.minutes * 60 + this.seconds;
- }
+ 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);
+ withSeconds(newSeconds: number): Duration {
+ let newMinutes = this.minutes;
+ if (newSeconds >= 60) {
+ newMinutes++;
+ newSeconds = 0;
}
-
- withMinutes(newMinutes: number): Duration {
- if (newMinutes < 0) {
- newMinutes = 0;
- }
- return new Duration(newMinutes, this.seconds);
+ if (newSeconds < 0) {
+ newMinutes = Math.max(0, newMinutes - 1);
+ newSeconds = 59;
}
+ return new Duration(newMinutes, newSeconds);
+ }
- toString(): string {
- return `${this.minutes}M ${this.seconds.toFixed(1)}S`;
+ withMinutes(newMinutes: number): Duration {
+ if (newMinutes < 0) {
+ newMinutes = 0;
}
+ return new Duration(newMinutes, this.seconds);
+ }
+
+ toString(): string {
+ return `${this.minutes}M ${this.seconds.toFixed(1)}S`;
+ }
}
diff --git a/common/ErrorCode.ts b/common/ErrorCode.ts
index addfb3e..a564c56 100644
--- a/common/ErrorCode.ts
+++ b/common/ErrorCode.ts
@@ -1,41 +1,41 @@
export enum ErrorCode {
- BadRequest = 100,
- NotSpecified = 101,
- Parse = 102,
- Range = 103,
- InvalidData = 104,
- BadToken = 105,
- Unauthorized = 106,
- NoPermission = 107,
- NotImplemented = 108,
- NotFound = 109,
- Internal = 200,
- Timeout = 300,
- ServerDisconnected = 301,
- BrokerDisconnected = 302,
+ BadRequest = 100,
+ NotSpecified = 101,
+ Parse = 102,
+ Range = 103,
+ InvalidData = 104,
+ BadToken = 105,
+ Unauthorized = 106,
+ NoPermission = 107,
+ NotImplemented = 108,
+ NotFound = 109,
+ Internal = 200,
+ Timeout = 300,
+ ServerDisconnected = 301,
+ BrokerDisconnected = 302
}
export function toHttpStatus(errorCode: ErrorCode): number {
- switch (errorCode) {
- case ErrorCode.BadRequest:
- case ErrorCode.NotSpecified:
- case ErrorCode.Parse:
- case ErrorCode.Range:
- case ErrorCode.InvalidData:
- return 400; // Bad request
- case ErrorCode.Unauthorized:
- case ErrorCode.BadToken:
- return 401; // Unauthorized
- case ErrorCode.NoPermission:
- return 403; // Forbidden
- case ErrorCode.NotFound:
- return 404;
- case ErrorCode.NotImplemented:
- return 501;
- case ErrorCode.Internal:
- case ErrorCode.ServerDisconnected:
- case ErrorCode.BrokerDisconnected:
- default:
- return 500;
- }
+ switch (errorCode) {
+ case ErrorCode.BadRequest:
+ case ErrorCode.NotSpecified:
+ case ErrorCode.Parse:
+ case ErrorCode.Range:
+ case ErrorCode.InvalidData:
+ return 400; // Bad request
+ case ErrorCode.Unauthorized:
+ case ErrorCode.BadToken:
+ return 401; // Unauthorized
+ case ErrorCode.NoPermission:
+ return 403; // Forbidden
+ case ErrorCode.NotFound:
+ return 404;
+ case ErrorCode.NotImplemented:
+ return 501;
+ case ErrorCode.Internal:
+ case ErrorCode.ServerDisconnected:
+ case ErrorCode.BrokerDisconnected:
+ default:
+ return 500;
+ }
}
diff --git a/common/TokenClaims.ts b/common/TokenClaims.ts
index fb805c5..d9c6995 100644
--- a/common/TokenClaims.ts
+++ b/common/TokenClaims.ts
@@ -1,34 +1,39 @@
export interface BaseClaims {
- iss: string;
- exp?: number;
+ iss: string;
+ exp?: number;
}
export interface AccessToken {
- type: "access";
- aud: number;
- name: string;
+ type: "access";
+ aud: number;
+ name: string;
}
export interface RefreshToken {
- type: "refresh";
- aud: number;
- name: string;
+ type: "refresh";
+ aud: number;
+ name: string;
}
export interface DeviceRegistrationToken {
- type: "device_reg";
+ type: "device_reg";
}
export interface DeviceToken {
- type: "device";
- aud: string;
- id: number;
+ type: "device";
+ aud: string;
+ id: number;
}
export interface SuperuserToken {
- type: "superuser";
+ type: "superuser";
}
-export type TokenClaimTypes = AccessToken | RefreshToken | DeviceRegistrationToken | DeviceToken | SuperuserToken;
+export type TokenClaimTypes =
+ | AccessToken
+ | RefreshToken
+ | DeviceRegistrationToken
+ | DeviceToken
+ | SuperuserToken;
export type TokenClaims = TokenClaimTypes & BaseClaims;
diff --git a/common/TypedEventEmitter.ts b/common/TypedEventEmitter.ts
index 9156fd9..46a49f6 100644
--- a/common/TypedEventEmitter.ts
+++ b/common/TypedEventEmitter.ts
@@ -4,61 +4,81 @@ type TEventName = string | symbol;
type AnyListener = (...args: any[]) => void;
-type Arguments = TListener extends (...args: infer TArgs) => any ? TArgs : any[];
-type Listener = TEvents[TEvent] extends (...args: infer TArgs) => any ?
- (...args: TArgs) => void : AnyListener;
+type Arguments = TListener extends (...args: infer TArgs) => any
+ ? TArgs
+ : any[];
+type Listener = TEvents[TEvent] extends (
+ ...args: infer TArgs
+) => any
+ ? (...args: TArgs) => void
+ : AnyListener;
export interface DefaultEvents {
- newListener: (event: TEventName, listener: AnyListener) => void;
- removeListener: (event: TEventName, listener: AnyListener) => void;
+ newListener: (event: TEventName, listener: AnyListener) => void;
+ removeListener: (event: TEventName, listener: AnyListener) => void;
}
-export type AnyEvents = DefaultEvents & {
- [event in TEventName]: any[];
-};
+export type AnyEvents = DefaultEvents & { [event in TEventName]: any[] };
-type IEventSubscriber =
- (event: TEvent, listener: Listener) => This;
+type IEventSubscriber = <
+ TEvent extends keyof TEvents & TEventName
+>(
+ event: TEvent,
+ listener: Listener
+) => This;
// tslint:disable:ban-types
interface ITypedEventEmitter {
- on: IEventSubscriber;
- off: IEventSubscriber;
- once: IEventSubscriber;
- addListener: IEventSubscriber;
- removeListener: IEventSubscriber;
- prependListener: IEventSubscriber;
- prependOnceListener: IEventSubscriber;
+ on: IEventSubscriber;
+ off: IEventSubscriber;
+ once: IEventSubscriber;
+ addListener: IEventSubscriber;
+ removeListener: IEventSubscriber;
+ prependListener: IEventSubscriber;
+ prependOnceListener: IEventSubscriber;
- emit(event: TEvent, ...args: Arguments): boolean;
- listeners(event: TEvent): Function[];
- rawListeners(event: TEvent): Function[];
- eventNames(): Array;
- setMaxListeners(maxListeners: number): this;
- getMaxListeners(): number;
- listenerCount(event: TEvent): number;
+ emit(
+ event: TEvent,
+ ...args: Arguments
+ ): boolean;
+ listeners(
+ event: TEvent
+ ): Function[];
+ rawListeners(
+ event: TEvent
+ ): Function[];
+ eventNames(): Array;
+ setMaxListeners(maxListeners: number): this;
+ getMaxListeners(): number;
+ listenerCount(
+ event: TEvent
+ ): number;
}
const TypedEventEmitter = EventEmitter as {
- new(): TypedEventEmitter,
+ new (): TypedEventEmitter;
};
-type TypedEventEmitter = ITypedEventEmitter;
+type TypedEventEmitter<
+ TEvents extends DefaultEvents = AnyEvents
+> = ITypedEventEmitter;
type Constructable = new (...args: any[]) => any;
-export function typedEventEmitter(Base: TBase):
- TBase & TypedEventEmitter {
- const NewClass = class extends Base {
- constructor(...args: any[]) {
- super(...args);
- EventEmitter.call(this);
- }
- };
- Object.getOwnPropertyNames(EventEmitter.prototype).forEach((name) => {
- NewClass.prototype[name] = (EventEmitter.prototype as any)[name];
- });
- return NewClass as any;
+export function typedEventEmitter<
+ TBase extends Constructable,
+ TEvents extends DefaultEvents = AnyEvents
+>(Base: TBase): TBase & TypedEventEmitter {
+ const NewClass = class extends Base {
+ constructor(...args: any[]) {
+ super(...args);
+ EventEmitter.call(this);
+ }
+ };
+ Object.getOwnPropertyNames(EventEmitter.prototype).forEach(name => {
+ NewClass.prototype[name] = (EventEmitter.prototype as any)[name];
+ });
+ return NewClass as any;
}
export { TypedEventEmitter };
diff --git a/common/httpApi/index.ts b/common/httpApi/index.ts
index 7f3803d..1f01a71 100644
--- a/common/httpApi/index.ts
+++ b/common/httpApi/index.ts
@@ -1,31 +1,33 @@
export interface TokenGrantPasswordRequest {
- grant_type: "password";
- username: string;
- password: string;
+ grant_type: "password";
+ username: string;
+ password: string;
}
export interface TokenGrantRefreshRequest {
- grant_type: "refresh";
- refresh_token: string;
+ grant_type: "refresh";
+ refresh_token: string;
}
-export type TokenGrantRequest = TokenGrantPasswordRequest | TokenGrantRefreshRequest;
+export type TokenGrantRequest =
+ | TokenGrantPasswordRequest
+ | TokenGrantRefreshRequest;
export interface TokenGrantResponse {
- access_token: string;
- refresh_token: string;
+ access_token: string;
+ refresh_token: string;
}
export interface IUser {
- id: number;
- username: string;
- name: string;
- devices: ISprinklersDevice[] | undefined;
+ id: number;
+ username: string;
+ name: string;
+ devices: ISprinklersDevice[] | undefined;
}
export interface ISprinklersDevice {
- id: number;
- deviceId: string | null;
- name: string;
- users: IUser[] | undefined;
+ id: number;
+ deviceId: string | null;
+ name: string;
+ users: IUser[] | undefined;
}
diff --git a/common/jsonRpc/index.ts b/common/jsonRpc/index.ts
index 85eee17..5dd80c5 100644
--- a/common/jsonRpc/index.ts
+++ b/common/jsonRpc/index.ts
@@ -2,9 +2,9 @@
export type DefaultRequestTypes = {};
export type DefaultResponseTypes = {};
export type DefaultErrorType = {
- code: number;
- message: string;
- data?: any;
+ code: number;
+ message: string;
+ data?: any;
};
export type DefaultNotificationTypes = {};
// tslint:enable:interface-over-type-literal
@@ -16,145 +16,196 @@ export type DefaultNotificationTypes = {};
// ErrorType: DefaultErrorType;
// }
-export interface Request {
- type: "request";
- id: number;
- method: Method;
- params: RequestTypes[Method];
+export interface Request<
+ RequestTypes = DefaultRequestTypes,
+ Method extends keyof RequestTypes = keyof RequestTypes
+> {
+ type: "request";
+ id: number;
+ method: Method;
+ params: RequestTypes[Method];
}
export interface ResponseBase {
- type: "response";
- id: number;
- method: Method;
+ type: "response";
+ id: number;
+ method: Method;
}
export interface SuccessData {
- result: "success";
- data: ResponseType;
+ result: "success";
+ data: ResponseType;
}
export interface ErrorData {
- result: "error";
- error: ErrorType;
+ result: "error";
+ error: ErrorType;
}
-export type ResponseData =
- SuccessData | ErrorData;
-
-export type Response =
- ResponseBase & ResponseData;
-
-export interface Notification {
- type: "notification";
- method: Method;
- data: NotificationTypes[Method];
+export type ResponseData<
+ ResponseTypes,
+ ErrorType,
+ Method extends keyof ResponseTypes = keyof ResponseTypes
+> = SuccessData | ErrorData;
+
+export type Response<
+ ResponseTypes,
+ ErrorType = DefaultErrorType,
+ Method extends keyof ResponseTypes = keyof ResponseTypes
+> = ResponseBase & ResponseData;
+
+export interface Notification<
+ NotificationTypes = DefaultNotificationTypes,
+ Method extends keyof NotificationTypes = keyof NotificationTypes
+> {
+ type: "notification";
+ method: Method;
+ data: NotificationTypes[Method];
}
-export type Message =
- Request |
- Response |
- Notification;
+export type Message<
+ RequestTypes = DefaultRequestTypes,
+ ResponseTypes = DefaultResponseTypes,
+ ErrorType = DefaultErrorType,
+ NotificationTypes = DefaultNotificationTypes
+> =
+ | Request
+ | Response
+ | Notification;
// export type TypesMessage =
// Message;
-export function isRequestMethod(
- message: Request, method: Method,
+export function isRequestMethod<
+ Method extends keyof RequestTypes,
+ RequestTypes
+>(
+ message: Request,
+ method: Method
): message is Request {
- return message.method === method;
+ return message.method === method;
}
-export function isResponseMethod(
- message: Response, method: Method,
+export function isResponseMethod<
+ Method extends keyof ResponseTypes,
+ ErrorType,
+ ResponseTypes
+>(
+ message: Response,
+ method: Method
): message is Response {
- return message.method === method;
+ return message.method === method;
}
-export function isNotificationMethod(
- message: Notification, method: Method,
+export function isNotificationMethod<
+ Method extends keyof NotificationTypes,
+ NotificationTypes = any
+>(
+ message: Notification,
+ method: Method
): message is Notification {
- return message.method === method;
+ return message.method === method;
}
-export type IRequestHandler =
- (request: RequestTypes[Method]) => Promise>;
-
-export type RequestHandlers = {
- [Method in keyof RequestTypes]:
- IRequestHandler;
+export type IRequestHandler<
+ RequestTypes,
+ ResponseTypes extends { [M in Method]: any },
+ ErrorType,
+ Method extends keyof RequestTypes
+> = (
+ request: RequestTypes[Method]
+) => Promise>;
+
+export type RequestHandlers<
+ RequestTypes,
+ ResponseTypes extends { [M in keyof RequestTypes]: any },
+ ErrorType
+> = {
+ [Method in keyof RequestTypes]: IRequestHandler<
+ RequestTypes,
+ ResponseTypes,
+ ErrorType,
+ Method
+ >
};
-export type IResponseHandler =
- (response: ResponseData) => void;
-
-export interface ResponseHandlers {
- [id: number]: IResponseHandler;
+export type IResponseHandler<
+ ResponseTypes,
+ ErrorType,
+ Method extends keyof ResponseTypes = keyof ResponseTypes
+> = (response: ResponseData) => void;
+
+export interface ResponseHandlers<
+ ResponseTypes = DefaultResponseTypes,
+ ErrorType = DefaultErrorType
+> {
+ [id: number]: IResponseHandler;
}
-export type NotificationHandler =
- (notification: NotificationTypes[Method]) => void;
+export type NotificationHandler<
+ NotificationTypes,
+ Method extends keyof NotificationTypes
+> = (notification: NotificationTypes[Method]) => void;
export type NotificationHandlers = {
- [Method in keyof NotificationTypes]: NotificationHandler;
+ [Method in keyof NotificationTypes]: NotificationHandler<
+ NotificationTypes,
+ Method
+ >
};
-export function listRequestHandlerMethods(
- handlers: RequestHandlers,
+export function listRequestHandlerMethods<
+ RequestTypes,
+ ResponseTypes extends { [Method in keyof RequestTypes]: any },
+ ErrorType
+>(
+ handlers: RequestHandlers
): Array {
- return Object.keys(handlers) as any;
+ return Object.keys(handlers) as any;
}
export function listNotificationHandlerMethods(
- handlers: NotificationHandlers,
+ handlers: NotificationHandlers
): Array {
- return Object.keys(handlers) as any;
+ return Object.keys(handlers) as any;
}
-export async function handleRequest(
- handlers: RequestHandlers,
- message: Request,
- thisParam?: any,
+export async function handleRequest<
+ RequestTypes,
+ ResponseTypes extends { [Method in keyof RequestTypes]: any },
+ ErrorType
+>(
+ handlers: RequestHandlers,
+ message: Request,
+ thisParam?: any
): Promise> {
- const handler = handlers[message.method];
- if (!handler) {
- throw new Error("No handler for request method " + message.method);
- }
- return handler.call(thisParam, message.params);
+ const handler = handlers[message.method];
+ if (!handler) {
+ throw new Error("No handler for request method " + message.method);
+ }
+ return handler.call(thisParam, message.params);
}
export function handleResponse(
- handlers: ResponseHandlers,
- message: Response,
- thisParam?: any,
+ handlers: ResponseHandlers,
+ message: Response,
+ thisParam?: any
) {
- const handler = handlers[message.id];
- if (!handler) {
- return;
- }
- return handler.call(thisParam, message);
+ const handler = handlers[message.id];
+ if (!handler) {
+ return;
+ }
+ return handler.call(thisParam, message);
}
export function handleNotification(
- handlers: NotificationHandlers,
- message: Notification,
- thisParam?: any,
+ handlers: NotificationHandlers,
+ message: Notification,
+ thisParam?: any
) {
- const handler = handlers[message.method];
- if (!handler) {
- throw new Error("No handler for notification method " + message.method);
- }
- return handler.call(thisParam, message.data);
+ const handler = handlers[message.method];
+ if (!handler) {
+ throw new Error("No handler for notification method " + message.method);
+ }
+ return handler.call(thisParam, message.data);
}
diff --git a/common/logger.ts b/common/logger.ts
index fe6b296..a2d7c68 100644
--- a/common/logger.ts
+++ b/common/logger.ts
@@ -4,112 +4,124 @@ import * as pino from "pino";
type Level = "default" | "60" | "50" | "40" | "30" | "20" | "10";
-const levels: {[level in Level]: string } = {
- default: "USERLVL",
- 60: "FATAL",
- 50: "ERROR",
- 40: "WARN",
- 30: "INFO",
- 20: "DEBUG",
- 10: "TRACE",
+const levels: { [level in Level]: string } = {
+ default: "USERLVL",
+ 60: "FATAL",
+ 50: "ERROR",
+ 40: "WARN",
+ 30: "INFO",
+ 20: "DEBUG",
+ 10: "TRACE"
};
-const levelColors: {[level in Level]: string } = {
- default: "text-decoration: underline; color: #000000;",
- 60: "text-decoration: underline; background-color: #FF0000;",
- 50: "text-decoration: underline; color: #FF0000;",
- 40: "text-decoration: underline; color: #FFFF00;",
- 30: "text-decoration: underline; color: #00FF00;",
- 20: "text-decoration: underline; color: #0000FF;",
- 10: "text-decoration: underline; color: #AAAAAA;",
+const levelColors: { [level in Level]: string } = {
+ default: "text-decoration: underline; color: #000000;",
+ 60: "text-decoration: underline; background-color: #FF0000;",
+ 50: "text-decoration: underline; color: #FF0000;",
+ 40: "text-decoration: underline; color: #FFFF00;",
+ 30: "text-decoration: underline; color: #00FF00;",
+ 20: "text-decoration: underline; color: #0000FF;",
+ 10: "text-decoration: underline; color: #AAAAAA;"
};
interface ColoredString {
- str: string;
- args: any[];
+ str: string;
+ args: any[];
}
function makeColored(str: string = ""): ColoredString {
- return { str, args: [] };
+ return { str, args: [] };
}
function concatColored(...coloredStrings: ColoredString[]): ColoredString {
- return coloredStrings.reduce((prev, cur) => ({
- str: prev.str + cur.str,
- args: prev.args.concat(cur.args),
- }), makeColored());
+ return coloredStrings.reduce(
+ (prev, cur) => ({
+ str: prev.str + cur.str,
+ args: prev.args.concat(cur.args)
+ }),
+ makeColored()
+ );
}
-const standardKeys = ["pid", "hostname", "name", "level", "time", "v", "source", "msg"];
+const standardKeys = [
+ "pid",
+ "hostname",
+ "name",
+ "level",
+ "time",
+ "v",
+ "source",
+ "msg"
+];
function write(value: any) {
- let line = concatColored(
- // makeColored(formatTime(value, " ")),
- formatSource(value),
- formatLevel(value),
- makeColored(": "),
- );
-
- if (value.msg) {
- line = concatColored(line, {
- str: "%c" + value.msg, args: ["color: #00FFFF"],
- });
- }
- const args = [line.str].concat(line.args)
- .concat([
- (value.type === "Error") ? value.stack : filter(value),
- ]);
- let fn;
- if (value.level >= 50) {
- fn = console.error;
- } else if (value.level >= 40) {
- fn = console.warn;
- } else {
- fn = console.log;
- }
- fn.apply(null, args);
+ let line = concatColored(
+ // makeColored(formatTime(value, " ")),
+ formatSource(value),
+ formatLevel(value),
+ makeColored(": ")
+ );
+
+ if (value.msg) {
+ line = concatColored(line, {
+ str: "%c" + value.msg,
+ args: ["color: #00FFFF"]
+ });
+ }
+ const args = [line.str]
+ .concat(line.args)
+ .concat([value.type === "Error" ? value.stack : filter(value)]);
+ let fn;
+ if (value.level >= 50) {
+ fn = console.error;
+ } else if (value.level >= 40) {
+ fn = console.warn;
+ } else {
+ fn = console.log;
+ }
+ fn.apply(null, args);
}
function filter(value: any) {
- const keys = Object.keys(value);
- const result: any = {};
+ const keys = Object.keys(value);
+ const result: any = {};
- for (const key of keys) {
- if (standardKeys.indexOf(key) < 0) {
- result[key] = value[key];
- }
+ for (const key of keys) {
+ if (standardKeys.indexOf(key) < 0) {
+ result[key] = value[key];
}
+ }
- return result;
+ return result;
}
-function formatSource(value: any): { str: string, args: any[] } {
- if (value.source) {
- return { str: "%c(" + value.source + ") ", args: ["color: #FF00FF"] };
- } else {
- return { str: "", args: [] };
- }
+function formatSource(value: any): { str: string; args: any[] } {
+ if (value.source) {
+ return { str: "%c(" + value.source + ") ", args: ["color: #FF00FF"] };
+ } else {
+ return { str: "", args: [] };
+ }
}
function formatLevel(value: any): ColoredString {
- const level = value.level as Level;
- if (levelColors.hasOwnProperty(level)) {
- return {
- str: "%c" + levels[level] + "%c",
- args: [levelColors[level], ""],
- };
- } else {
- return {
- str: levels.default,
- args: [levelColors.default],
- };
- }
+ const level = value.level as Level;
+ if (levelColors.hasOwnProperty(level)) {
+ return {
+ str: "%c" + levels[level] + "%c",
+ args: [levelColors[level], ""]
+ };
+ } else {
+ return {
+ str: levels.default,
+ args: [levelColors.default]
+ };
+ }
}
const logger: pino.Logger = pino({
- serializers: pino.stdSerializers,
- browser: { serialize: true, write },
- level: "trace",
+ serializers: pino.stdSerializers,
+ browser: { serialize: true, write },
+ level: "trace"
});
export default logger;
diff --git a/common/sprinklersRpc/ConnectionState.ts b/common/sprinklersRpc/ConnectionState.ts
index 76629b4..fb93139 100644
--- a/common/sprinklersRpc/ConnectionState.ts
+++ b/common/sprinklersRpc/ConnectionState.ts
@@ -1,73 +1,81 @@
import { computed, observable } from "mobx";
export class ConnectionState {
- /**
- * Represents if a client is connected to the sprinklers3 server (eg. via websocket)
- * Can be null if there is no client involved
- */
- @observable clientToServer: boolean | null = null;
+ /**
+ * Represents if a client is connected to the sprinklers3 server (eg. via websocket)
+ * Can be null if there is no client involved
+ */
+ @observable
+ clientToServer: boolean | null = null;
- /**
- * Represents if the sprinklers3 server is connected to the broker (eg. via mqtt)
- * Can be null if there is no broker involved
- */
- @observable serverToBroker: boolean | null = null;
+ /**
+ * Represents if the sprinklers3 server is connected to the broker (eg. via mqtt)
+ * Can be null if there is no broker involved
+ */
+ @observable
+ serverToBroker: boolean | null = null;
- /**
- * Represents if the device is connected to the broker and we can communicate with it (eg. via mqtt)
- * Can be null if there is no device involved
- */
- @observable brokerToDevice: boolean | null = null;
+ /**
+ * Represents if the device is connected to the broker and we can communicate with it (eg. via mqtt)
+ * Can be null if there is no device involved
+ */
+ @observable
+ brokerToDevice: boolean | null = null;
- /**
- * Represents if whoever is trying to access this device has permission to access it.
- * Is null if there is no concept of access involved.
- */
- @observable hasPermission: boolean | null = null;
+ /**
+ * Represents if whoever is trying to access this device has permission to access it.
+ * Is null if there is no concept of access involved.
+ */
+ @observable
+ hasPermission: boolean | null = null;
- @computed get noPermission() {
- return this.hasPermission === false;
- }
+ @computed
+ get noPermission() {
+ return this.hasPermission === false;
+ }
- @computed get isAvailable(): boolean {
- if (this.hasPermission === false) {
- return false;
- }
- if (this.brokerToDevice != null) {
- return true;
- }
- if (this.serverToBroker != null) {
- return this.serverToBroker;
- }
- if (this.clientToServer != null) {
- return this.clientToServer;
- }
- return false;
+ @computed
+ get isAvailable(): boolean {
+ if (this.hasPermission === false) {
+ return false;
+ }
+ if (this.brokerToDevice != null) {
+ return true;
+ }
+ if (this.serverToBroker != null) {
+ return this.serverToBroker;
+ }
+ if (this.clientToServer != null) {
+ return this.clientToServer;
}
+ return false;
+ }
- @computed get isDeviceConnected(): boolean | null {
- if (this.hasPermission === false) {
- return false;
- }
- if (this.serverToBroker === false || this.clientToServer === false) {
- return null;
- }
- if (this.brokerToDevice != null) {
- return this.brokerToDevice;
- }
- return null;
+ @computed
+ get isDeviceConnected(): boolean | null {
+ if (this.hasPermission === false) {
+ return false;
}
+ if (this.serverToBroker === false || this.clientToServer === false) {
+ return null;
+ }
+ if (this.brokerToDevice != null) {
+ return this.brokerToDevice;
+ }
+ return null;
+ }
- @computed get isServerConnected(): boolean | null {
- if (this.hasPermission === false) {
- return false;
- }
- if (this.serverToBroker != null) {
- return this.serverToBroker;
- }
- if (this.clientToServer != null) {
- return this.brokerToDevice;
- }
- return null;
+ @computed
+ get isServerConnected(): boolean | null {
+ if (this.hasPermission === false) {
+ return false;
+ }
+ if (this.serverToBroker != null) {
+ return this.serverToBroker;
+ }
+ if (this.clientToServer != null) {
+ return this.brokerToDevice;
}
+ return null;
+ }
}
diff --git a/common/sprinklersRpc/Program.ts b/common/sprinklersRpc/Program.ts
index 1b32b69..7ab916d 100644
--- a/common/sprinklersRpc/Program.ts
+++ b/common/sprinklersRpc/Program.ts
@@ -6,59 +6,69 @@ import * as schema from "./schema";
import { SprinklersDevice } from "./SprinklersDevice";
export class ProgramItem {
- // the section number
- readonly section!: number;
- // duration of the run, in seconds
- readonly duration!: number;
+ // the section number
+ readonly section!: number;
+ // duration of the run, in seconds
+ readonly duration!: number;
- constructor(data?: Partial) {
- if (data) {
- Object.assign(this, data);
- }
+ constructor(data?: Partial) {
+ if (data) {
+ Object.assign(this, data);
}
+ }
}
export class Program {
- readonly device: SprinklersDevice;
- readonly id: number;
+ readonly device: SprinklersDevice;
+ readonly id: number;
- @observable name: string = "";
- @observable enabled: boolean = false;
- @observable schedule: Schedule = new Schedule();
- @observable.shallow sequence: ProgramItem[] = [];
- @observable running: boolean = false;
+ @observable
+ name: string = "";
+ @observable
+ enabled: boolean = false;
+ @observable
+ schedule: Schedule = new Schedule();
+ @observable.shallow
+ sequence: ProgramItem[] = [];
+ @observable
+ running: boolean = false;
- constructor(device: SprinklersDevice, id: number, data?: Partial) {
- this.device = device;
- this.id = id;
- if (data) {
- Object.assign(this, data);
- }
+ constructor(device: SprinklersDevice, id: number, data?: Partial) {
+ this.device = device;
+ this.id = id;
+ if (data) {
+ Object.assign(this, data);
}
+ }
- run() {
- return this.device.runProgram({ programId: this.id });
- }
+ run() {
+ return this.device.runProgram({ programId: this.id });
+ }
- cancel() {
- return this.device.cancelProgram({ programId: this.id });
- }
+ cancel() {
+ return this.device.cancelProgram({ programId: this.id });
+ }
- update() {
- const data = serialize(schema.program, this);
- return this.device.updateProgram({ programId: this.id, data });
- }
+ update() {
+ const data = serialize(schema.program, this);
+ return this.device.updateProgram({ programId: this.id, data });
+ }
- clone(): Program {
- return new Program(this.device, this.id, {
- name: this.name, enabled: this.enabled, running: this.running,
- schedule: this.schedule.clone(),
- sequence: this.sequence.slice(),
- });
- }
+ clone(): Program {
+ return new Program(this.device, this.id, {
+ name: this.name,
+ enabled: this.enabled,
+ running: this.running,
+ schedule: this.schedule.clone(),
+ sequence: this.sequence.slice()
+ });
+ }
- toString(): string {
- return `Program{name="${this.name}", enabled=${this.enabled}, schedule=${this.schedule}, ` +
- `sequence=${this.sequence}, running=${this.running}}`;
- }
+ toString(): string {
+ return (
+ `Program{name="${this.name}", enabled=${this.enabled}, schedule=${
+ this.schedule
+ }, ` + `sequence=${this.sequence}, running=${this.running}}`
+ );
+ }
}
diff --git a/common/sprinklersRpc/RpcError.ts b/common/sprinklersRpc/RpcError.ts
index e2a15c4..6eb6e89 100644
--- a/common/sprinklersRpc/RpcError.ts
+++ b/common/sprinklersRpc/RpcError.ts
@@ -2,20 +2,24 @@ import { ErrorCode } from "@common/ErrorCode";
import { IError } from "./websocketData";
export class RpcError extends Error implements IError {
- name = "RpcError";
- code: number;
- data: any;
+ name = "RpcError";
+ code: number;
+ data: any;
- constructor(message: string, code: number = ErrorCode.BadRequest, data: any = {}) {
- super(message);
- this.code = code;
- if (data instanceof Error) {
- this.data = data.toString();
- }
- this.data = data;
+ constructor(
+ message: string,
+ code: number = ErrorCode.BadRequest,
+ data: any = {}
+ ) {
+ super(message);
+ this.code = code;
+ if (data instanceof Error) {
+ this.data = data.toString();
}
+ this.data = data;
+ }
- toJSON(): IError {
- return { code: this.code, message: this.message, data: this.data };
- }
+ toJSON(): IError {
+ return { code: this.code, message: this.message, data: this.data };
+ }
}
diff --git a/common/sprinklersRpc/Section.ts b/common/sprinklersRpc/Section.ts
index 37bc699..f93ba2d 100644
--- a/common/sprinklersRpc/Section.ts
+++ b/common/sprinklersRpc/Section.ts
@@ -2,27 +2,29 @@ import { observable } from "mobx";
import { SprinklersDevice } from "./SprinklersDevice";
export class Section {
- readonly device: SprinklersDevice;
- readonly id: number;
+ readonly device: SprinklersDevice;
+ readonly id: number;
- @observable name: string = "";
- @observable state: boolean = false;
+ @observable
+ name: string = "";
+ @observable
+ state: boolean = false;
- constructor(device: SprinklersDevice, id: number) {
- this.device = device;
- this.id = id;
- }
+ constructor(device: SprinklersDevice, id: number) {
+ this.device = device;
+ this.id = id;
+ }
- /** duration is in seconds */
- run(duration: number) {
- return this.device.runSection({ sectionId: this.id, duration });
- }
+ /** duration is in seconds */
+ run(duration: number) {
+ return this.device.runSection({ sectionId: this.id, duration });
+ }
- cancel() {
- return this.device.cancelSection({ sectionId: this.id });
- }
+ cancel() {
+ return this.device.cancelSection({ sectionId: this.id });
+ }
- toString(): string {
- return `Section ${this.id}: '${this.name}'`;
- }
+ toString(): string {
+ return `Section ${this.id}: '${this.name}'`;
+ }
}
diff --git a/common/sprinklersRpc/SectionRunner.ts b/common/sprinklersRpc/SectionRunner.ts
index 6f875ed..4154fac 100644
--- a/common/sprinklersRpc/SectionRunner.ts
+++ b/common/sprinklersRpc/SectionRunner.ts
@@ -2,57 +2,69 @@ import { observable } from "mobx";
import { SprinklersDevice } from "./SprinklersDevice";
export class SectionRun {
- readonly sectionRunner: SectionRunner;
- readonly id: number;
- section: number;
- totalDuration: number = 0;
- duration: number = 0;
- startTime: Date | null = null;
- pauseTime: Date | null = null;
- unpauseTime: Date | null = null;
-
- constructor(sectionRunner: SectionRunner, id: number = 0, section: number = 0) {
- this.sectionRunner = sectionRunner;
- this.id = id;
- this.section = section;
- }
-
- cancel = () => this.sectionRunner.cancelRunById(this.id);
-
- toString() {
- return `SectionRun{id=${this.id}, section=${this.section}, duration=${this.duration},` +
- ` startTime=${this.startTime}, pauseTime=${this.pauseTime}}`;
- }
+ readonly sectionRunner: SectionRunner;
+ readonly id: number;
+ section: number;
+ totalDuration: number = 0;
+ duration: number = 0;
+ startTime: Date | null = null;
+ pauseTime: Date | null = null;
+ unpauseTime: Date | null = null;
+
+ constructor(
+ sectionRunner: SectionRunner,
+ id: number = 0,
+ section: number = 0
+ ) {
+ this.sectionRunner = sectionRunner;
+ this.id = id;
+ this.section = section;
+ }
+
+ cancel = () => this.sectionRunner.cancelRunById(this.id);
+
+ toString() {
+ return (
+ `SectionRun{id=${this.id}, section=${this.section}, duration=${
+ this.duration
+ },` + ` startTime=${this.startTime}, pauseTime=${this.pauseTime}}`
+ );
+ }
}
export class SectionRunner {
- readonly device: SprinklersDevice;
+ readonly device: SprinklersDevice;
- @observable queue: SectionRun[] = [];
- @observable current: SectionRun | null = null;
- @observable paused: boolean = false;
+ @observable
+ queue: SectionRun[] = [];
+ @observable
+ current: SectionRun | null = null;
+ @observable
+ paused: boolean = false;
- constructor(device: SprinklersDevice) {
- this.device = device;
- }
+ constructor(device: SprinklersDevice) {
+ this.device = device;
+ }
- cancelRunById(runId: number) {
- return this.device.cancelSectionRunId({ runId });
- }
+ cancelRunById(runId: number) {
+ return this.device.cancelSectionRunId({ runId });
+ }
- setPaused(paused: boolean) {
- return this.device.pauseSectionRunner({ paused });
- }
+ setPaused(paused: boolean) {
+ return this.device.pauseSectionRunner({ paused });
+ }
- pause() {
- return this.setPaused(true);
- }
+ pause() {
+ return this.setPaused(true);
+ }
- unpause() {
- return this.setPaused(false);
- }
+ unpause() {
+ return this.setPaused(false);
+ }
- toString(): string {
- return `SectionRunner{queue="${this.queue}", current="${this.current}", paused=${this.paused}}`;
- }
+ toString(): string {
+ return `SectionRunner{queue="${this.queue}", current="${
+ this.current
+ }", paused=${this.paused}}`;
+ }
}
diff --git a/common/sprinklersRpc/SprinklersDevice.ts b/common/sprinklersRpc/SprinklersDevice.ts
index c2436f9..190d160 100644
--- a/common/sprinklersRpc/SprinklersDevice.ts
+++ b/common/sprinklersRpc/SprinklersDevice.ts
@@ -7,85 +7,94 @@ import { SectionRunner } from "./SectionRunner";
import { SprinklersRPC } from "./SprinklersRPC";
export abstract class SprinklersDevice {
- readonly rpc: SprinklersRPC;
- readonly id: string;
-
- @observable connectionState: ConnectionState = new ConnectionState();
- @observable sections: Section[] = [];
- @observable programs: Program[] = [];
- @observable sectionRunner: SectionRunner;
-
- @computed get connected(): boolean {
- return this.connectionState.isDeviceConnected || false;
- }
-
- sectionConstructor: typeof Section = Section;
- sectionRunnerConstructor: typeof SectionRunner = SectionRunner;
- programConstructor: typeof Program = Program;
-
- private references: number = 0;
-
- protected constructor(rpc: SprinklersRPC, id: string) {
- this.rpc = rpc;
- this.id = id;
- this.sectionRunner = new (this.sectionRunnerConstructor)(this);
- }
-
- abstract makeRequest(request: req.Request): Promise;
-
- /**
- * Increase the reference count for this sprinklers device
- * @returns The new reference count
- */
- acquire(): number {
- return ++this.references;
- }
-
- /**
- * Releases one reference to this device. When the reference count reaches 0, the device
- * will be released and no longer updated.
- * @returns The reference count after being updated
- */
- release(): number {
- this.references--;
- if (this.references <= 0) {
- this.rpc.releaseDevice(this.id);
- }
- return this.references;
- }
-
- runProgram(opts: req.WithProgram) {
- return this.makeRequest({ ...opts, type: "runProgram" });
- }
-
- cancelProgram(opts: req.WithProgram) {
- return this.makeRequest({ ...opts, type: "cancelProgram" });
- }
-
- updateProgram(opts: req.UpdateProgramData): Promise {
- return this.makeRequest({ ...opts, type: "updateProgram" }) as Promise;
- }
-
- runSection(opts: req.RunSectionData): Promise {
- return this.makeRequest({ ...opts, type: "runSection" }) as Promise;
- }
-
- cancelSection(opts: req.WithSection) {
- return this.makeRequest({ ...opts, type: "cancelSection" });
- }
-
- cancelSectionRunId(opts: req.CancelSectionRunIdData) {
- return this.makeRequest({ ...opts, type: "cancelSectionRunId" });
- }
-
- pauseSectionRunner(opts: req.PauseSectionRunnerData) {
- return this.makeRequest({ ...opts, type: "pauseSectionRunner" });
- }
-
- toString(): string {
- return `SprinklersDevice{id="${this.id}", connected=${this.connected}, ` +
- `sections=[${this.sections}], ` +
- `programs=[${this.programs}], ` +
- `sectionRunner=${this.sectionRunner} }`;
+ readonly rpc: SprinklersRPC;
+ readonly id: string;
+
+ @observable
+ connectionState: ConnectionState = new ConnectionState();
+ @observable
+ sections: Section[] = [];
+ @observable
+ programs: Program[] = [];
+ @observable
+ sectionRunner: SectionRunner;
+
+ @computed
+ get connected(): boolean {
+ return this.connectionState.isDeviceConnected || false;
+ }
+
+ sectionConstructor: typeof Section = Section;
+ sectionRunnerConstructor: typeof SectionRunner = SectionRunner;
+ programConstructor: typeof Program = Program;
+
+ private references: number = 0;
+
+ protected constructor(rpc: SprinklersRPC, id: string) {
+ this.rpc = rpc;
+ this.id = id;
+ this.sectionRunner = new this.sectionRunnerConstructor(this);
+ }
+
+ abstract makeRequest(request: req.Request): Promise;
+
+ /**
+ * Increase the reference count for this sprinklers device
+ * @returns The new reference count
+ */
+ acquire(): number {
+ return ++this.references;
+ }
+
+ /**
+ * Releases one reference to this device. When the reference count reaches 0, the device
+ * will be released and no longer updated.
+ * @returns The reference count after being updated
+ */
+ release(): number {
+ this.references--;
+ if (this.references <= 0) {
+ this.rpc.releaseDevice(this.id);
}
+ return this.references;
+ }
+
+ runProgram(opts: req.WithProgram) {
+ return this.makeRequest({ ...opts, type: "runProgram" });
+ }
+
+ cancelProgram(opts: req.WithProgram) {
+ return this.makeRequest({ ...opts, type: "cancelProgram" });
+ }
+
+ updateProgram(
+ opts: req.UpdateProgramData
+ ): Promise {
+ return this.makeRequest({ ...opts, type: "updateProgram" }) as Promise;
+ }
+
+ runSection(opts: req.RunSectionData): Promise {
+ return this.makeRequest({ ...opts, type: "runSection" }) as Promise;
+ }
+
+ cancelSection(opts: req.WithSection) {
+ return this.makeRequest({ ...opts, type: "cancelSection" });
+ }
+
+ cancelSectionRunId(opts: req.CancelSectionRunIdData) {
+ return this.makeRequest({ ...opts, type: "cancelSectionRunId" });
+ }
+
+ pauseSectionRunner(opts: req.PauseSectionRunnerData) {
+ return this.makeRequest({ ...opts, type: "pauseSectionRunner" });
+ }
+
+ toString(): string {
+ return (
+ `SprinklersDevice{id="${this.id}", connected=${this.connected}, ` +
+ `sections=[${this.sections}], ` +
+ `programs=[${this.programs}], ` +
+ `sectionRunner=${this.sectionRunner} }`
+ );
+ }
}
diff --git a/common/sprinklersRpc/SprinklersRPC.ts b/common/sprinklersRpc/SprinklersRPC.ts
index fc17115..e383a2d 100644
--- a/common/sprinklersRpc/SprinklersRPC.ts
+++ b/common/sprinklersRpc/SprinklersRPC.ts
@@ -2,30 +2,30 @@ import { ConnectionState } from "./ConnectionState";
import { SprinklersDevice } from "./SprinklersDevice";
export abstract class SprinklersRPC {
- abstract readonly connectionState: ConnectionState;
- abstract readonly connected: boolean;
+ abstract readonly connectionState: ConnectionState;
+ abstract readonly connected: boolean;
- abstract start(): void;
+ abstract start(): void;
- /**
- * Acquires a reference to a device. This reference must be released by calling
- * SprinklersDevice#release for every time this method was called
- * @param id The id of the device
- */
- acquireDevice(id: string): SprinklersDevice {
- const device = this.getDevice(id);
- device.acquire();
- return device;
- }
+ /**
+ * Acquires a reference to a device. This reference must be released by calling
+ * SprinklersDevice#release for every time this method was called
+ * @param id The id of the device
+ */
+ acquireDevice(id: string): SprinklersDevice {
+ const device = this.getDevice(id);
+ device.acquire();
+ return device;
+ }
- /**
- * Forces a device to be released. The device will no longer be updated.
- *
- * This should not be used normally, instead SprinklersDevice#release should be called to manage
- * each reference to a device.
- * @param id The id of the device to remove
- */
- abstract releaseDevice(id: string): void;
+ /**
+ * Forces a device to be released. The device will no longer be updated.
+ *
+ * This should not be used normally, instead SprinklersDevice#release should be called to manage
+ * each reference to a device.
+ * @param id The id of the device to remove
+ */
+ abstract releaseDevice(id: string): void;
- protected abstract getDevice(id: string): SprinklersDevice;
+ protected abstract getDevice(id: string): SprinklersDevice;
}
diff --git a/common/sprinklersRpc/deviceRequests.ts b/common/sprinklersRpc/deviceRequests.ts
index 143b159..389ee4e 100644
--- a/common/sprinklersRpc/deviceRequests.ts
+++ b/common/sprinklersRpc/deviceRequests.ts
@@ -1,17 +1,22 @@
export interface WithType {
- type: Type;
+ type: Type;
}
-export interface WithProgram { programId: number; }
+export interface WithProgram {
+ programId: number;
+}
export type RunProgramRequest = WithProgram & WithType<"runProgram">;
export type CancelProgramRequest = WithProgram & WithType<"cancelProgram">;
export type UpdateProgramData = WithProgram & { data: any };
-export type UpdateProgramRequest = UpdateProgramData & WithType<"updateProgram">;
+export type UpdateProgramRequest = UpdateProgramData &
+ WithType<"updateProgram">;
export type UpdateProgramResponse = Response<"updateProgram", { data: any }>;
-export interface WithSection { sectionId: number; }
+export interface WithSection {
+ sectionId: number;
+}
export type RunSectionData = WithSection & { duration: number };
export type RunSectionRequest = RunSectionData & WithType<"runSection">;
@@ -19,30 +24,44 @@ export type RunSectionResponse = Response<"runSection", { runId: number }>;
export type CancelSectionRequest = WithSection & WithType<"cancelSection">;
-export interface CancelSectionRunIdData { runId: number; }
-export type CancelSectionRunIdRequest = CancelSectionRunIdData & WithType<"cancelSectionRunId">;
+export interface CancelSectionRunIdData {
+ runId: number;
+}
+export type CancelSectionRunIdRequest = CancelSectionRunIdData &
+ WithType<"cancelSectionRunId">;
-export interface PauseSectionRunnerData { paused: boolean; }
-export type PauseSectionRunnerRequest = PauseSectionRunnerData & WithType<"pauseSectionRunner">;
+export interface PauseSectionRunnerData {
+ paused: boolean;
+}
+export type PauseSectionRunnerRequest = PauseSectionRunnerData &
+ WithType<"pauseSectionRunner">;
-export type Request = RunProgramRequest | CancelProgramRequest | UpdateProgramRequest |
- RunSectionRequest | CancelSectionRequest | CancelSectionRunIdRequest | PauseSectionRunnerRequest;
+export type Request =
+ | RunProgramRequest
+ | CancelProgramRequest
+ | UpdateProgramRequest
+ | RunSectionRequest
+ | CancelSectionRequest
+ | CancelSectionRunIdRequest
+ | PauseSectionRunnerRequest;
export type RequestType = Request["type"];
-export interface SuccessResponseData extends WithType {
- result: "success";
- message: string;
+export interface SuccessResponseData
+ extends WithType {
+ result: "success";
+ message: string;
}
-export interface ErrorResponseData extends WithType {
- result: "error";
- message: string;
- code: number;
- name?: string;
- cause?: any;
+export interface ErrorResponseData
+ extends WithType {
+ result: "error";
+ message: string;
+ code: number;
+ name?: string;
+ cause?: any;
}
export type Response =
- (SuccessResponseData & Res) |
- (ErrorResponseData);
+ | (SuccessResponseData & Res)
+ | (ErrorResponseData);
diff --git a/common/sprinklersRpc/mqtt/MqttProgram.ts b/common/sprinklersRpc/mqtt/MqttProgram.ts
index 759254d..330f34e 100644
--- a/common/sprinklersRpc/mqtt/MqttProgram.ts
+++ b/common/sprinklersRpc/mqtt/MqttProgram.ts
@@ -4,15 +4,15 @@ import * as s from "@common/sprinklersRpc";
import * as schema from "@common/sprinklersRpc/schema";
export class MqttProgram extends s.Program {
- onMessage(payload: string, topic: string | undefined) {
- if (topic === "running") {
- this.running = (payload === "true");
- } else if (topic == null) {
- this.updateFromJSON(JSON.parse(payload));
- }
+ onMessage(payload: string, topic: string | undefined) {
+ if (topic === "running") {
+ this.running = payload === "true";
+ } else if (topic == null) {
+ this.updateFromJSON(JSON.parse(payload));
}
+ }
- updateFromJSON(json: any) {
- update(schema.program, this, json);
- }
+ updateFromJSON(json: any) {
+ update(schema.program, this, json);
+ }
}
diff --git a/common/sprinklersRpc/mqtt/MqttSection.ts b/common/sprinklersRpc/mqtt/MqttSection.ts
index a8eb071..42bf66f 100644
--- a/common/sprinklersRpc/mqtt/MqttSection.ts
+++ b/common/sprinklersRpc/mqtt/MqttSection.ts
@@ -4,15 +4,15 @@ import * as s from "@common/sprinklersRpc";
import * as schema from "@common/sprinklersRpc/schema";
export class MqttSection extends s.Section {
- onMessage(payload: string, topic: string | undefined) {
- if (topic === "state") {
- this.state = (payload === "true");
- } else if (topic == null) {
- this.updateFromJSON(JSON.parse(payload));
- }
+ onMessage(payload: string, topic: string | undefined) {
+ if (topic === "state") {
+ this.state = payload === "true";
+ } else if (topic == null) {
+ this.updateFromJSON(JSON.parse(payload));
}
+ }
- updateFromJSON(json: any) {
- update(schema.section, this, json);
- }
+ updateFromJSON(json: any) {
+ update(schema.section, this, json);
+ }
}
diff --git a/common/sprinklersRpc/mqtt/MqttSectionRunner.ts b/common/sprinklersRpc/mqtt/MqttSectionRunner.ts
index e6e2dd6..b5156d7 100644
--- a/common/sprinklersRpc/mqtt/MqttSectionRunner.ts
+++ b/common/sprinklersRpc/mqtt/MqttSectionRunner.ts
@@ -4,11 +4,11 @@ import * as s from "@common/sprinklersRpc";
import * as schema from "@common/sprinklersRpc/schema";
export class MqttSectionRunner extends s.SectionRunner {
- onMessage(payload: string) {
- this.updateFromJSON(JSON.parse(payload));
- }
+ onMessage(payload: string) {
+ this.updateFromJSON(JSON.parse(payload));
+ }
- updateFromJSON(json: any) {
- update(schema.sectionRunner, this, json);
- }
+ updateFromJSON(json: any) {
+ update(schema.sectionRunner, this, json);
+ }
}
diff --git a/common/sprinklersRpc/mqtt/index.ts b/common/sprinklersRpc/mqtt/index.ts
index b28f606..18dc534 100644
--- a/common/sprinklersRpc/mqtt/index.ts
+++ b/common/sprinklersRpc/mqtt/index.ts
@@ -16,296 +16,337 @@ import { MqttSectionRunner } from "./MqttSectionRunner";
const log = logger.child({ source: "mqtt" });
interface WithRid {
- rid: number;
+ rid: number;
}
export const DEVICE_PREFIX = "devices";
const REQUEST_TIMEOUT = 5000;
export interface MqttRpcClientOptions {
- mqttUri: string;
- username?: string;
- password?: string;
+ mqttUri: string;
+ username?: string;
+ password?: string;
}
-export class MqttRpcClient extends s.SprinklersRPC implements MqttRpcClientOptions {
- get connected(): boolean {
- return this.connectionState.isServerConnected || false;
+export class MqttRpcClient extends s.SprinklersRPC
+ implements MqttRpcClientOptions {
+ get connected(): boolean {
+ return this.connectionState.isServerConnected || false;
+ }
+
+ private static newClientId() {
+ return "sprinklers3-MqttApiClient-" + getRandomId();
+ }
+
+ mqttUri!: string;
+ username?: string;
+ password?: string;
+
+ client!: mqtt.Client;
+ @observable
+ connectionState: s.ConnectionState = new s.ConnectionState();
+ devices: Map = new Map();
+
+ constructor(opts: MqttRpcClientOptions) {
+ super();
+ Object.assign(this, opts);
+ this.connectionState.serverToBroker = false;
+ }
+
+ start() {
+ const clientId = MqttRpcClient.newClientId();
+ const mqttUri = this.mqttUri;
+ log.info({ mqttUri, clientId }, "connecting to mqtt broker with client id");
+ this.client = mqtt.connect(
+ mqttUri,
+ {
+ clientId,
+ connectTimeout: 5000,
+ reconnectPeriod: 5000,
+ username: this.username,
+ password: this.password
+ }
+ );
+ this.client.on("message", this.onMessageArrived.bind(this));
+ this.client.on("close", () => {
+ logger.warn("mqtt disconnected");
+ this.connectionState.serverToBroker = false;
+ });
+ this.client.on("error", err => {
+ log.error({ err }, "mqtt error");
+ });
+ this.client.on("connect", () => {
+ log.info("mqtt connected");
+ this.connectionState.serverToBroker = true;
+ });
+ }
+
+ releaseDevice(id: string) {
+ const device = this.devices.get(id);
+ if (!device) {
+ return;
}
+ device.doUnsubscribe();
+ this.devices.delete(id);
+ }
- private static newClientId() {
- return "sprinklers3-MqttApiClient-" + getRandomId();
+ protected getDevice(id: string): s.SprinklersDevice {
+ if (/\//.test(id)) {
+ throw new Error("Device id cannot contain a /");
}
-
- mqttUri!: string;
- username?: string;
- password?: string;
-
- client!: mqtt.Client;
- @observable connectionState: s.ConnectionState = new s.ConnectionState();
- devices: Map = new Map();
-
- constructor(opts: MqttRpcClientOptions) {
- super();
- Object.assign(this, opts);
- this.connectionState.serverToBroker = false;
- }
-
- start() {
- const clientId = MqttRpcClient.newClientId();
- const mqttUri = this.mqttUri;
- log.info({ mqttUri, clientId }, "connecting to mqtt broker with client id");
- this.client = mqtt.connect(mqttUri, {
- clientId, connectTimeout: 5000, reconnectPeriod: 5000,
- username: this.username, password: this.password,
- });
- this.client.on("message", this.onMessageArrived.bind(this));
- this.client.on("close", () => {
- logger.warn("mqtt disconnected");
- this.connectionState.serverToBroker = false;
- });
- this.client.on("error", (err) => {
- log.error({ err }, "mqtt error");
- });
- this.client.on("connect", () => {
- log.info("mqtt connected");
- this.connectionState.serverToBroker = true;
- });
+ let device = this.devices.get(id);
+ if (!device) {
+ this.devices.set(id, (device = new MqttSprinklersDevice(this, id)));
+ if (this.connected) {
+ device.doSubscribe();
+ }
}
-
- releaseDevice(id: string) {
- const device = this.devices.get(id);
- if (!device) {
- return;
- }
- device.doUnsubscribe();
- this.devices.delete(id);
- }
-
- protected getDevice(id: string): s.SprinklersDevice {
- if (/\//.test(id)) {
- throw new Error("Device id cannot contain a /");
- }
- let device = this.devices.get(id);
- if (!device) {
- this.devices.set(id, device = new MqttSprinklersDevice(this, id));
- if (this.connected) {
- device.doSubscribe();
- }
- }
- return device;
+ return device;
+ }
+
+ private onMessageArrived(
+ topic: string,
+ payload: Buffer,
+ packet: mqtt.Packet
+ ) {
+ try {
+ this.processMessage(topic, payload, packet);
+ } catch (err) {
+ log.error({ err }, "error while processing mqtt message");
}
-
- private onMessageArrived(topic: string, payload: Buffer, packet: mqtt.Packet) {
- try {
- this.processMessage(topic, payload, packet);
- } catch (err) {
- log.error({ err }, "error while processing mqtt message");
- }
+ }
+
+ private processMessage(
+ topic: string,
+ payloadBuf: Buffer,
+ packet: mqtt.Packet
+ ) {
+ const payload = payloadBuf.toString("utf8");
+ log.trace({ topic, payload }, "message arrived: ");
+ const regexp = new RegExp(`^${DEVICE_PREFIX}\\/([^\\/]+)\\/?(.*)$`);
+ const matches = regexp.exec(topic);
+ if (!matches) {
+ return log.warn({ topic }, "received message on invalid topic");
}
-
- private processMessage(topic: string, payloadBuf: Buffer, packet: mqtt.Packet) {
- const payload = payloadBuf.toString("utf8");
- log.trace({ topic, payload }, "message arrived: ");
- const regexp = new RegExp(`^${DEVICE_PREFIX}\\/([^\\/]+)\\/?(.*)$`);
- const matches = regexp.exec(topic);
- if (!matches) {
- return log.warn({ topic }, "received message on invalid topic");
- }
- const id = matches[1];
- const topicSuffix = matches[2];
- const device = this.devices.get(id);
- if (!device) {
- log.debug({ id }, "received message for unknown device");
- return;
- }
- device.onMessage(topicSuffix, payload);
+ const id = matches[1];
+ const topicSuffix = matches[2];
+ const device = this.devices.get(id);
+ if (!device) {
+ log.debug({ id }, "received message for unknown device");
+ return;
}
+ device.onMessage(topicSuffix, payload);
+ }
}
type ResponseCallback = (response: requests.Response) => void;
const subscriptions = [
- "/connected",
- "/sections",
- "/sections/+/#",
- "/programs",
- "/programs/+/#",
- "/responses",
- "/section_runner",
+ "/connected",
+ "/sections",
+ "/sections/+/#",
+ "/programs",
+ "/programs/+/#",
+ "/responses",
+ "/section_runner"
];
type IHandler = (payload: any, ...matches: string[]) => void;
interface IHandlerEntry {
- test: RegExp;
- handler: IHandler;
+ test: RegExp;
+ handler: IHandler;
}
-const handler = (test: RegExp) =>
- (target: MqttSprinklersDevice, propertyKey: string, descriptor: TypedPropertyDescriptor) => {
- if (typeof descriptor.value === "function") {
- const entry = {
- test, handler: descriptor.value,
- };
- (target.handlers || (target.handlers = [])).push(entry);
- }
+const handler = (test: RegExp) => (
+ target: MqttSprinklersDevice,
+ propertyKey: string,
+ descriptor: TypedPropertyDescriptor
+) => {
+ if (typeof descriptor.value === "function") {
+ const entry = {
+ test,
+ handler: descriptor.value
};
+ (target.handlers || (target.handlers = [])).push(entry);
+ }
+};
class MqttSprinklersDevice extends s.SprinklersDevice {
- readonly apiClient: MqttRpcClient;
-
- handlers!: IHandlerEntry[];
- private subscriptions: string[];
- private nextRequestId: number = Math.floor(Math.random() * 1000000000);
- private responseCallbacks: Map = new Map();
-
- constructor(apiClient: MqttRpcClient, id: string) {
- super(apiClient, id);
- this.sectionConstructor = MqttSection;
- this.sectionRunnerConstructor = MqttSectionRunner;
- this.programConstructor = MqttProgram;
- this.apiClient = apiClient;
- this.sectionRunner = new MqttSectionRunner(this);
- this.subscriptions = subscriptions.map((filter) => this.prefix + filter);
-
- autorun(() => {
- const brokerConnected = apiClient.connected;
- this.connectionState.serverToBroker = brokerConnected;
- if (brokerConnected) {
- if (this.connectionState.brokerToDevice == null) {
- this.connectionState.brokerToDevice = false;
- }
- this.doSubscribe();
- } else {
- this.connectionState.brokerToDevice = false;
- }
- });
- }
-
- get prefix(): string {
- return DEVICE_PREFIX + "/" + this.id;
- }
-
- doSubscribe() {
- this.apiClient.client.subscribe(this.subscriptions, { qos: 1 }, (err) => {
- if (err) {
- log.error({ err, id: this.id }, "error subscribing to device");
- } else {
- log.debug({ id: this.id }, "subscribed to device");
- }
- });
- }
-
- doUnsubscribe() {
- this.apiClient.client.unsubscribe(this.subscriptions, (err) => {
- if (err) {
- log.error({ err, id: this.id }, "error unsubscribing to device");
- } else {
- log.debug({ id: this.id }, "unsubscribed to device");
- }
- });
- }
-
- onMessage(topic: string, payload: string) {
- for (const { test, handler: hndlr } of this.handlers) {
- const matches = topic.match(test);
- if (!matches) {
- continue;
- }
- matches.shift();
- hndlr.call(this, payload, ...matches);
- return;
+ readonly apiClient: MqttRpcClient;
+
+ handlers!: IHandlerEntry[];
+ private subscriptions: string[];
+ private nextRequestId: number = Math.floor(Math.random() * 1000000000);
+ private responseCallbacks: Map = new Map();
+
+ constructor(apiClient: MqttRpcClient, id: string) {
+ super(apiClient, id);
+ this.sectionConstructor = MqttSection;
+ this.sectionRunnerConstructor = MqttSectionRunner;
+ this.programConstructor = MqttProgram;
+ this.apiClient = apiClient;
+ this.sectionRunner = new MqttSectionRunner(this);
+ this.subscriptions = subscriptions.map(filter => this.prefix + filter);
+
+ autorun(() => {
+ const brokerConnected = apiClient.connected;
+ this.connectionState.serverToBroker = brokerConnected;
+ if (brokerConnected) {
+ if (this.connectionState.brokerToDevice == null) {
+ this.connectionState.brokerToDevice = false;
}
- log.warn({ topic }, "MqttSprinklersDevice recieved message on invalid topic");
+ this.doSubscribe();
+ } else {
+ this.connectionState.brokerToDevice = false;
+ }
+ });
+ }
+
+ get prefix(): string {
+ return DEVICE_PREFIX + "/" + this.id;
+ }
+
+ doSubscribe() {
+ this.apiClient.client.subscribe(this.subscriptions, { qos: 1 }, err => {
+ if (err) {
+ log.error({ err, id: this.id }, "error subscribing to device");
+ } else {
+ log.debug({ id: this.id }, "subscribed to device");
+ }
+ });
+ }
+
+ doUnsubscribe() {
+ this.apiClient.client.unsubscribe(this.subscriptions, err => {
+ if (err) {
+ log.error({ err, id: this.id }, "error unsubscribing to device");
+ } else {
+ log.debug({ id: this.id }, "unsubscribed to device");
+ }
+ });
+ }
+
+ onMessage(topic: string, payload: string) {
+ for (const { test, handler: hndlr } of this.handlers) {
+ const matches = topic.match(test);
+ if (!matches) {
+ continue;
+ }
+ matches.shift();
+ hndlr.call(this, payload, ...matches);
+ return;
}
-
- makeRequest(request: requests.Request): Promise {
- return new Promise((resolve, reject) => {
- const topic = this.prefix + "/requests";
- const json = seralizeRequest(request);
- const requestId = json.rid = this.getRequestId();
- const payloadStr = JSON.stringify(json);
-
- let timeoutHandle: any;
- const callback: ResponseCallback = (data) => {
- if (data.result === "error") {
- reject(new RpcError(data.message, data.code, data));
- } else {
- resolve(data);
- }
- this.responseCallbacks.delete(requestId);
- clearTimeout(timeoutHandle);
- };
-
- timeoutHandle = setTimeout(() => {
- reject(new RpcError("the request has timed out", ErrorCode.Timeout));
- this.responseCallbacks.delete(requestId);
- clearTimeout(timeoutHandle);
- }, REQUEST_TIMEOUT);
-
- this.responseCallbacks.set(requestId, callback);
- this.apiClient.client.publish(topic, payloadStr, { qos: 1 });
- });
- }
-
- private getRequestId(): number {
- return this.nextRequestId++;
- }
-
- /* tslint:disable:no-unused-variable */
- @handler(/^connected$/)
- private handleConnected(payload: string) {
- this.connectionState.brokerToDevice = (payload === "true");
- log.trace(`MqttSprinklersDevice with prefix ${this.prefix}: ${this.connected}`);
- return;
- }
-
- @handler(/^sections(?:\/(\d+)(?:\/?(.+))?)?$/)
- private handleSectionsUpdate(payload: string, secNumStr?: string, subTopic?: string) {
- log.trace({ section: secNumStr, topic: subTopic, payload }, "handling section update");
- if (!secNumStr) { // new number of sections
- this.sections.length = Number(payload);
+ log.warn(
+ { topic },
+ "MqttSprinklersDevice recieved message on invalid topic"
+ );
+ }
+
+ makeRequest(request: requests.Request): Promise {
+ return new Promise((resolve, reject) => {
+ const topic = this.prefix + "/requests";
+ const json = seralizeRequest(request);
+ const requestId = (json.rid = this.getRequestId());
+ const payloadStr = JSON.stringify(json);
+
+ let timeoutHandle: any;
+ const callback: ResponseCallback = data => {
+ if (data.result === "error") {
+ reject(new RpcError(data.message, data.code, data));
} else {
- const secNum = Number(secNumStr);
- let section = this.sections[secNum];
- if (!section) {
- this.sections[secNum] = section = new MqttSection(this, secNum);
- }
- (section as MqttSection).onMessage(payload, subTopic);
+ resolve(data);
}
+ this.responseCallbacks.delete(requestId);
+ clearTimeout(timeoutHandle);
+ };
+
+ timeoutHandle = setTimeout(() => {
+ reject(new RpcError("the request has timed out", ErrorCode.Timeout));
+ this.responseCallbacks.delete(requestId);
+ clearTimeout(timeoutHandle);
+ }, REQUEST_TIMEOUT);
+
+ this.responseCallbacks.set(requestId, callback);
+ this.apiClient.client.publish(topic, payloadStr, { qos: 1 });
+ });
+ }
+
+ private getRequestId(): number {
+ return this.nextRequestId++;
+ }
+
+ /* tslint:disable:no-unused-variable */
+ @handler(/^connected$/)
+ private handleConnected(payload: string) {
+ this.connectionState.brokerToDevice = payload === "true";
+ log.trace(
+ `MqttSprinklersDevice with prefix ${this.prefix}: ${this.connected}`
+ );
+ return;
+ }
+
+ @handler(/^sections(?:\/(\d+)(?:\/?(.+))?)?$/)
+ private handleSectionsUpdate(
+ payload: string,
+ secNumStr?: string,
+ subTopic?: string
+ ) {
+ log.trace(
+ { section: secNumStr, topic: subTopic, payload },
+ "handling section update"
+ );
+ if (!secNumStr) {
+ // new number of sections
+ this.sections.length = Number(payload);
+ } else {
+ const secNum = Number(secNumStr);
+ let section = this.sections[secNum];
+ if (!section) {
+ this.sections[secNum] = section = new MqttSection(this, secNum);
+ }
+ (section as MqttSection).onMessage(payload, subTopic);
}
-
- @handler(/^programs(?:\/(\d+)(?:\/?(.+))?)?$/)
- private handleProgramsUpdate(payload: string, progNumStr?: string, subTopic?: string) {
- log.trace({ program: progNumStr, topic: subTopic, payload }, "handling program update");
- if (!progNumStr) { // new number of programs
- this.programs.length = Number(payload);
- } else {
- const progNum = Number(progNumStr);
- let program = this.programs[progNum];
- if (!program) {
- this.programs[progNum] = program = new MqttProgram(this, progNum);
- }
- (program as MqttProgram).onMessage(payload, subTopic);
- }
+ }
+
+ @handler(/^programs(?:\/(\d+)(?:\/?(.+))?)?$/)
+ private handleProgramsUpdate(
+ payload: string,
+ progNumStr?: string,
+ subTopic?: string
+ ) {
+ log.trace(
+ { program: progNumStr, topic: subTopic, payload },
+ "handling program update"
+ );
+ if (!progNumStr) {
+ // new number of programs
+ this.programs.length = Number(payload);
+ } else {
+ const progNum = Number(progNumStr);
+ let program = this.programs[progNum];
+ if (!program) {
+ this.programs[progNum] = program = new MqttProgram(this, progNum);
+ }
+ (program as MqttProgram).onMessage(payload, subTopic);
}
-
- @handler(/^section_runner$/)
- private handleSectionRunnerUpdate(payload: string) {
- (this.sectionRunner as MqttSectionRunner).onMessage(payload);
- }
-
- @handler(/^responses$/)
- private handleResponse(payload: string) {
- const data = JSON.parse(payload) as requests.Response & WithRid;
- log.trace({ rid: data.rid }, "handling request response");
- const cb = this.responseCallbacks.get(data.rid);
- if (typeof cb === "function") {
- delete data.rid;
- cb(data);
- }
+ }
+
+ @handler(/^section_runner$/)
+ private handleSectionRunnerUpdate(payload: string) {
+ (this.sectionRunner as MqttSectionRunner).onMessage(payload);
+ }
+
+ @handler(/^responses$/)
+ private handleResponse(payload: string) {
+ const data = JSON.parse(payload) as requests.Response & WithRid;
+ log.trace({ rid: data.rid }, "handling request response");
+ const cb = this.responseCallbacks.get(data.rid);
+ if (typeof cb === "function") {
+ delete data.rid;
+ cb(data);
}
+ }
- /* tslint:enable:no-unused-variable */
+ /* tslint:enable:no-unused-variable */
}
diff --git a/common/sprinklersRpc/schedule.ts b/common/sprinklersRpc/schedule.ts
index 1bdc1a6..50e06e3 100644
--- a/common/sprinklersRpc/schedule.ts
+++ b/common/sprinklersRpc/schedule.ts
@@ -2,101 +2,140 @@ import { observable } from "mobx";
import { Moment } from "moment";
export class TimeOfDay {
- static fromMoment(m: Moment): TimeOfDay {
- return new TimeOfDay(m.hour(), m.minute(), m.second(), m.millisecond());
- }
-
- static fromDate(date: Date): TimeOfDay {
- return new TimeOfDay(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
- }
-
- static equals(a: TimeOfDay | null | undefined, b: TimeOfDay | null | undefined): boolean {
- return (a === b) || ((a != null && b != null) && a.hour === b.hour &&
- a.minute === b.minute &&
- a.second === b.second &&
- a.millisecond === b.millisecond);
- }
-
- readonly hour: number;
- readonly minute: number;
- readonly second: number;
- readonly millisecond: number;
-
- constructor(hour: number = 0, minute: number = 0, second: number = 0, millisecond: number = 0) {
- this.hour = hour;
- this.minute = minute;
- this.second = second;
- this.millisecond = millisecond;
- }
+ static fromMoment(m: Moment): TimeOfDay {
+ return new TimeOfDay(m.hour(), m.minute(), m.second(), m.millisecond());
+ }
+
+ static fromDate(date: Date): TimeOfDay {
+ return new TimeOfDay(
+ date.getHours(),
+ date.getMinutes(),
+ date.getSeconds(),
+ date.getMilliseconds()
+ );
+ }
+
+ static equals(
+ a: TimeOfDay | null | undefined,
+ b: TimeOfDay | null | undefined
+ ): boolean {
+ return (
+ a === b ||
+ (a != null &&
+ b != null &&
+ a.hour === b.hour &&
+ a.minute === b.minute &&
+ a.second === b.second &&
+ a.millisecond === b.millisecond)
+ );
+ }
+
+ readonly hour: number;
+ readonly minute: number;
+ readonly second: number;
+ readonly millisecond: number;
+
+ constructor(
+ hour: number = 0,
+ minute: number = 0,
+ second: number = 0,
+ millisecond: number = 0
+ ) {
+ this.hour = hour;
+ this.minute = minute;
+ this.second = second;
+ this.millisecond = millisecond;
+ }
}
export enum Weekday {
- Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday,
+ Sunday,
+ Monday,
+ Tuesday,
+ Wednesday,
+ Thursday,
+ Friday,
+ Saturday
}
export const WEEKDAYS: Weekday[] = Object.keys(Weekday)
- .map((weekday) => Number(weekday))
- .filter((weekday) => !isNaN(weekday));
+ .map(weekday => Number(weekday))
+ .filter(weekday => !isNaN(weekday));
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,
+ 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 {
- static readonly DEFAULT = new DateOfYear({ day: 1, month: Month.January, year: 0 });
-
- static equals(a: DateOfYear | null | undefined, b: DateOfYear | null | undefined): boolean {
- return (a === b) || ((a instanceof DateOfYear && b instanceof DateOfYear) &&
- a.day === b.day &&
- a.month === b.month &&
- a.year === b.year);
- }
-
- static fromMoment(m: Moment): DateOfYear {
- return new DateOfYear({ day: m.date(), month: m.month(), year: m.year() });
- }
-
- readonly day!: number;
- readonly month!: Month;
- readonly year!: number;
-
- constructor(data?: Partial) {
- Object.assign(this, DateOfYear.DEFAULT, data);
- }
-
- with(data: Partial): DateOfYear {
- return new DateOfYear(Object.assign({}, this, data));
- }
-
- toString() {
- return `${Month[this.month]} ${this.day}, ${this.year}`;
- }
+ static readonly DEFAULT = new DateOfYear({
+ day: 1,
+ month: Month.January,
+ year: 0
+ });
+
+ static equals(
+ a: DateOfYear | null | undefined,
+ b: DateOfYear | null | undefined
+ ): boolean {
+ return (
+ a === b ||
+ (a instanceof DateOfYear &&
+ b instanceof DateOfYear &&
+ a.day === b.day &&
+ a.month === b.month &&
+ a.year === b.year)
+ );
+ }
+
+ static fromMoment(m: Moment): DateOfYear {
+ return new DateOfYear({ day: m.date(), month: m.month(), year: m.year() });
+ }
+
+ readonly day!: number;
+ readonly month!: Month;
+ readonly year!: number;
+
+ constructor(data?: Partial) {
+ Object.assign(this, DateOfYear.DEFAULT, data);
+ }
+
+ with(data: Partial): DateOfYear {
+ return new DateOfYear(Object.assign({}, this, data));
+ }
+
+ toString() {
+ return `${Month[this.month]} ${this.day}, ${this.year}`;
+ }
}
export class Schedule {
- @observable times: TimeOfDay[] = [];
- @observable weekdays: Weekday[] = [];
- @observable from: DateOfYear | null = null;
- @observable to: DateOfYear | null = null;
-
- constructor(data?: Partial) {
- if (typeof data === "object") {
- Object.assign(this, data);
- }
+ @observable
+ times: TimeOfDay[] = [];
+ @observable
+ weekdays: Weekday[] = [];
+ @observable
+ from: DateOfYear | null = null;
+ @observable
+ to: DateOfYear | null = null;
+
+ constructor(data?: Partial) {
+ if (typeof data === "object") {
+ Object.assign(this, data);
}
+ }
- clone(): Schedule {
- return new Schedule(this);
- }
+ clone(): Schedule {
+ return new Schedule(this);
+ }
}
diff --git a/common/sprinklersRpc/schema/common.ts b/common/sprinklersRpc/schema/common.ts
index 82ca2cb..8851dec 100644
--- a/common/sprinklersRpc/schema/common.ts
+++ b/common/sprinklersRpc/schema/common.ts
@@ -1,40 +1,38 @@
-import {
- ModelSchema, primitive, PropSchema,
-} from "serializr";
+import { ModelSchema, primitive, PropSchema } from "serializr";
import * as s from "..";
export const duration: PropSchema = primitive();
export const date: PropSchema = {
- serializer: (jsDate: Date | null) => jsDate != null ?
- jsDate.toISOString() : null,
- deserializer: (json: any, done) => {
- if (json === null) {
- return done(null, null);
- }
- try {
- done(null, new Date(json));
- } catch (e) {
- done(e, undefined);
- }
- },
+ serializer: (jsDate: Date | null) =>
+ jsDate != null ? jsDate.toISOString() : null,
+ deserializer: (json: any, done) => {
+ if (json === null) {
+ return done(null, null);
+ }
+ try {
+ done(null, new Date(json));
+ } catch (e) {
+ done(e, undefined);
+ }
+ }
};
export const dateOfYear: ModelSchema = {
- factory: () => new s.DateOfYear(),
- props: {
- year: primitive(),
- month: primitive(), // this only works if it is represented as a # from 0-12
- day: primitive(),
- },
+ factory: () => new s.DateOfYear(),
+ props: {
+ year: primitive(),
+ month: primitive(), // this only works if it is represented as a # from 0-12
+ day: primitive()
+ }
};
export const timeOfDay: ModelSchema = {
- factory: () => new s.TimeOfDay(),
- props: {
- hour: primitive(),
- minute: primitive(),
- second: primitive(),
- millisecond: primitive(),
- },
+ factory: () => new s.TimeOfDay(),
+ props: {
+ hour: primitive(),
+ minute: primitive(),
+ second: primitive(),
+ millisecond: primitive()
+ }
};
diff --git a/common/sprinklersRpc/schema/index.ts b/common/sprinklersRpc/schema/index.ts
index 32a1a40..86fd9c3 100644
--- a/common/sprinklersRpc/schema/index.ts
+++ b/common/sprinklersRpc/schema/index.ts
@@ -1,6 +1,4 @@
-import {
- createSimpleSchema, ModelSchema, object, primitive,
-} from "serializr";
+import { createSimpleSchema, ModelSchema, object, primitive } from "serializr";
import * as s from "..";
import list from "./list";
@@ -11,81 +9,89 @@ import * as common from "./common";
export * from "./common";
export const connectionState: ModelSchema = {
- factory: (c) => new s.ConnectionState(),
- props: {
- clientToServer: primitive(),
- serverToBroker: primitive(),
- brokerToDevice: primitive(),
- },
+ factory: c => new s.ConnectionState(),
+ props: {
+ clientToServer: primitive(),
+ serverToBroker: primitive(),
+ brokerToDevice: primitive()
+ }
};
export const section: ModelSchema = {
- factory: (c) => new (c.parentContext.target as s.SprinklersDevice).sectionConstructor(
- c.parentContext.target, c.json.id),
- props: {
- id: primitive(),
- name: primitive(),
- state: primitive(),
- },
+ factory: c =>
+ new (c.parentContext.target as s.SprinklersDevice).sectionConstructor(
+ c.parentContext.target,
+ c.json.id
+ ),
+ props: {
+ id: primitive(),
+ name: primitive(),
+ state: primitive()
+ }
};
export const sectionRun: ModelSchema = {
- factory: (c) => new s.SectionRun(c.parentContext.target, c.json.id),
- props: {
- id: primitive(),
- section: primitive(),
- totalDuration: common.duration,
- duration: common.duration,
- startTime: common.date,
- pauseTime: common.date,
- unpauseTime: common.date,
- },
+ factory: c => new s.SectionRun(c.parentContext.target, c.json.id),
+ props: {
+ id: primitive(),
+ section: primitive(),
+ totalDuration: common.duration,
+ duration: common.duration,
+ startTime: common.date,
+ pauseTime: common.date,
+ unpauseTime: common.date
+ }
};
export const sectionRunner: ModelSchema = {
- factory: (c) => new (c.parentContext.target as s.SprinklersDevice).sectionRunnerConstructor(
- c.parentContext.target),
- props: {
- queue: list(object(sectionRun)),
- current: object(sectionRun),
- paused: primitive(),
- },
+ factory: c =>
+ new (c.parentContext.target as s.SprinklersDevice).sectionRunnerConstructor(
+ c.parentContext.target
+ ),
+ props: {
+ queue: list(object(sectionRun)),
+ current: object(sectionRun),
+ paused: primitive()
+ }
};
export const schedule: ModelSchema = {
- factory: () => new s.Schedule(),
- props: {
- times: list(object(common.timeOfDay)),
- weekdays: list(primitive()),
- from: object(common.dateOfYear),
- to: object(common.dateOfYear),
- },
+ factory: () => new s.Schedule(),
+ props: {
+ times: list(object(common.timeOfDay)),
+ weekdays: list(primitive()),
+ from: object(common.dateOfYear),
+ to: object(common.dateOfYear)
+ }
};
export const programItem: ModelSchema = {
- factory: () => new s.ProgramItem(),
- props: {
- section: primitive(),
- duration: common.duration,
- },
+ factory: () => new s.ProgramItem(),
+ props: {
+ section: primitive(),
+ duration: common.duration
+ }
};
export const program: ModelSchema = {
- factory: (c) => new (c.parentContext.target as s.SprinklersDevice).programConstructor(
- c.parentContext.target, c.json.id),
- props: {
- id: primitive(),
- name: primitive(),
- enabled: primitive(),
- schedule: object(schedule),
- sequence: list(object(programItem)),
- running: primitive(),
- },
+ factory: c =>
+ new (c.parentContext.target as s.SprinklersDevice).programConstructor(
+ c.parentContext.target,
+ c.json.id
+ ),
+ props: {
+ id: primitive(),
+ name: primitive(),
+ enabled: primitive(),
+ schedule: object(schedule),
+ sequence: list(object(programItem)),
+ running: primitive()
+ }
};
export const sprinklersDevice = createSimpleSchema({
- connectionState: object(connectionState),
- sections: list(object(section)),
- sectionRunner: object(sectionRunner),
- programs: list(object(program)),
+ connectionState: object(connectionState),
+ sections: list(object(section)),
+ sectionRunner: object(sectionRunner),
+ programs: list(object(program))
});
diff --git a/common/sprinklersRpc/schema/list.ts b/common/sprinklersRpc/schema/list.ts
index 6fde8a9..079dec5 100644
--- a/common/sprinklersRpc/schema/list.ts
+++ b/common/sprinklersRpc/schema/list.ts
@@ -1,65 +1,75 @@
import { primitive, PropSchema } from "serializr";
function invariant(cond: boolean, message?: string) {
- if (!cond) {
- throw new Error("[serializr] " + (message || "Illegal ServerState"));
- }
+ if (!cond) {
+ throw new Error("[serializr] " + (message || "Illegal ServerState"));
+ }
}
function isPropSchema(thing: any) {
- return thing && thing.serializer && thing.deserializer;
+ return thing && thing.serializer && thing.deserializer;
}
function isAliasedPropSchema(propSchema: any) {
- return typeof propSchema === "object" && !!propSchema.jsonname;
+ 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, []);
+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);
+ }
}
- 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)));
+ };
+ 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,
- );
- },
- };
+ 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
+ );
+ }
+ };
}
diff --git a/common/sprinklersRpc/schema/requests.ts b/common/sprinklersRpc/schema/requests.ts
index 42bcc56..f8d7d73 100644
--- a/common/sprinklersRpc/schema/requests.ts
+++ b/common/sprinklersRpc/schema/requests.ts
@@ -1,69 +1,89 @@
-import { createSimpleSchema, deserialize, ModelSchema, primitive, serialize } from "serializr";
+import {
+ createSimpleSchema,
+ deserialize,
+ ModelSchema,
+ primitive,
+ serialize
+} from "serializr";
import * as requests from "@common/sprinklersRpc/deviceRequests";
import * as common from "./common";
export const withType: ModelSchema = createSimpleSchema({
- type: primitive(),
+ type: primitive()
});
-export const withProgram: ModelSchema = createSimpleSchema({
- ...withType.props,
- programId: primitive(),
+export const withProgram: ModelSchema<
+ requests.WithProgram
+> = createSimpleSchema({
+ ...withType.props,
+ programId: primitive()
});
-export const withSection: ModelSchema = createSimpleSchema({
- ...withType.props,
- sectionId: primitive(),
+export const withSection: ModelSchema<
+ requests.WithSection
+> = createSimpleSchema({
+ ...withType.props,
+ sectionId: primitive()
});
-export const updateProgram: ModelSchema = createSimpleSchema({
- ...withProgram.props,
- data: {
- serializer: (data) => data,
- deserializer: (json, done) => { done(null, json); },
- },
+export const updateProgram: ModelSchema<
+ requests.UpdateProgramData
+> = createSimpleSchema({
+ ...withProgram.props,
+ data: {
+ serializer: data => data,
+ deserializer: (json, done) => {
+ done(null, json);
+ }
+ }
});
-export const runSection: ModelSchema = createSimpleSchema({
- ...withSection.props,
- duration: common.duration,
+export const runSection: ModelSchema<
+ requests.RunSectionData
+> = createSimpleSchema({
+ ...withSection.props,
+ duration: common.duration
});
-export const cancelSectionRunId: ModelSchema = createSimpleSchema({
- ...withType.props,
- runId: primitive(),
+export const cancelSectionRunId: ModelSchema<
+ requests.CancelSectionRunIdData
+> = createSimpleSchema({
+ ...withType.props,
+ runId: primitive()
});
-export const pauseSectionRunner: ModelSchema = createSimpleSchema({
- ...withType.props,
- paused: primitive(),
+export const pauseSectionRunner: ModelSchema<
+ requests.PauseSectionRunnerData
+> = createSimpleSchema({
+ ...withType.props,
+ paused: primitive()
});
export function getRequestSchema(request: requests.WithType): ModelSchema {
- switch (request.type as requests.RequestType) {
- case "runProgram":
- case "cancelProgram":
- return withProgram;
- case "updateProgram":
- return updateProgram;
- case "runSection":
- return runSection;
- case "cancelSection":
- return withSection;
- case "cancelSectionRunId":
- return cancelSectionRunId;
- case "pauseSectionRunner":
- return pauseSectionRunner;
- default:
- throw new Error(`Cannot serialize request with type "${request.type}"`);
- }
+ switch (request.type as requests.RequestType) {
+ case "runProgram":
+ case "cancelProgram":
+ return withProgram;
+ case "updateProgram":
+ return updateProgram;
+ case "runSection":
+ return runSection;
+ case "cancelSection":
+ return withSection;
+ case "cancelSectionRunId":
+ return cancelSectionRunId;
+ case "pauseSectionRunner":
+ return pauseSectionRunner;
+ default:
+ throw new Error(`Cannot serialize request with type "${request.type}"`);
+ }
}
export function seralizeRequest(request: requests.Request): any {
- return serialize(getRequestSchema(request), request);
+ return serialize(getRequestSchema(request), request);
}
export function deserializeRequest(json: any): requests.Request {
- return deserialize(getRequestSchema(json), json);
+ return deserialize(getRequestSchema(json), json);
}
diff --git a/common/sprinklersRpc/websocketData.ts b/common/sprinklersRpc/websocketData.ts
index 80cc9d6..73b5437 100644
--- a/common/sprinklersRpc/websocketData.ts
+++ b/common/sprinklersRpc/websocketData.ts
@@ -3,76 +3,92 @@ import * as rpc from "@common/jsonRpc/index";
import { Response as ResponseData } from "@common/sprinklersRpc/deviceRequests";
export interface IAuthenticateRequest {
- accessToken: string;
+ accessToken: string;
}
export interface IDeviceSubscribeRequest {
- deviceId: string;
+ deviceId: string;
}
export interface IDeviceCallRequest {
- deviceId: string;
- data: any;
+ deviceId: string;
+ data: any;
}
export interface IClientRequestTypes {
- "authenticate": IAuthenticateRequest;
- "deviceSubscribe": IDeviceSubscribeRequest;
- "deviceUnsubscribe": IDeviceSubscribeRequest;
- "deviceCall": IDeviceCallRequest;
+ authenticate: IAuthenticateRequest;
+ deviceSubscribe: IDeviceSubscribeRequest;
+ deviceUnsubscribe: IDeviceSubscribeRequest;
+ deviceCall: IDeviceCallRequest;
}
export interface IAuthenticateResponse {
- authenticated: boolean;
- message: string;
- user: IUser;
+ authenticated: boolean;
+ message: string;
+ user: IUser;
}
export interface IDeviceSubscribeResponse {
- deviceId: string;
+ deviceId: string;
}
export interface IDeviceCallResponse {
- data: ResponseData;
+ data: ResponseData;
}
export interface IServerResponseTypes {
- "authenticate": IAuthenticateResponse;
- "deviceSubscribe": IDeviceSubscribeResponse;
- "deviceUnsubscribe": IDeviceSubscribeResponse;
- "deviceCall": IDeviceCallResponse;
+ authenticate: IAuthenticateResponse;
+ deviceSubscribe: IDeviceSubscribeResponse;
+ deviceUnsubscribe: IDeviceSubscribeResponse;
+ deviceCall: IDeviceCallResponse;
}
export type ClientRequestMethods = keyof IClientRequestTypes;
export interface IBrokerConnectionUpdate {
- brokerConnected: boolean;
+ brokerConnected: boolean;
}
export interface IDeviceUpdate {
- deviceId: string;
- data: any;
+ deviceId: string;
+ data: any;
}
export interface IServerNotificationTypes {
- "brokerConnectionUpdate": IBrokerConnectionUpdate;
- "deviceUpdate": IDeviceUpdate;
- "error": IError;
+ brokerConnectionUpdate: IBrokerConnectionUpdate;
+ deviceUpdate: IDeviceUpdate;
+ error: IError;
}
export type ServerNotificationMethod = keyof IServerNotificationTypes;
export type IError = rpc.DefaultErrorType;
export type ErrorData = rpc.ErrorData;
-export type ServerMessage = rpc.Message<{}, IServerResponseTypes, IError, IServerNotificationTypes>;
+export type ServerMessage = rpc.Message<
+ {},
+ IServerResponseTypes,
+ IError,
+ IServerNotificationTypes
+>;
export type ServerNotification = rpc.Notification;
export type ServerResponse = rpc.Response;
-export type ServerResponseData =
- rpc.ResponseData;
-export type ServerResponseHandlers = rpc.ResponseHandlers