inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
254 lines (233 loc) • 6.95 kB
text/typescript
import { JSONObject, num2hex } from "@zwave-js/shared/safe";
import { isArray, isObject } from "alcalzone-shared/typeguards";
import { hexKeyRegexNDigits, throwInvalidConfig } from "./utils_safe";
interface NotificationStateDefinition {
type: "state";
variableName: string;
value: number;
idle: boolean;
}
interface NotificationEventDefinition {
type: "event";
}
export type NotificationValueDefinition = (
| NotificationStateDefinition
| NotificationEventDefinition
) & {
description?: string;
label: string;
parameter?: NotificationParameter;
};
export type NotificationMap = ReadonlyMap<number, Notification>;
export class Notification {
public constructor(id: number, definition: JSONObject) {
this.id = id;
this.name = definition.name;
this.variables = isArray(definition.variables)
? definition.variables.map((v: any) => new NotificationVariable(v))
: [];
const events = new Map<number, NotificationEvent>();
if (isObject(definition.events)) {
for (const [eventId, eventDefinition] of Object.entries(
definition.events,
)) {
if (!hexKeyRegexNDigits.test(eventId)) {
throwInvalidConfig(
"notifications",
`found invalid key "${eventId}" in notification ${num2hex(
id,
)}. Notification events must have lowercase hexadecimal IDs.`,
);
}
const eventIdNum = parseInt(eventId.slice(2), 16);
events.set(
eventIdNum,
new NotificationEvent(eventIdNum, eventDefinition as any),
);
}
}
this.events = events;
}
public readonly id: number;
public readonly name: string;
public readonly variables: readonly NotificationVariable[];
public readonly events: ReadonlyMap<number, NotificationEvent>;
public lookupValue(value: number): NotificationValueDefinition | undefined {
// Events are easier to look up, do that first
if (this.events.has(value)) {
const { id, ...event } = this.events.get(value)!;
return {
type: "event",
...event,
};
}
// Then try to find a variable with a matching state
const variable = this.variables.find((v) => v.states.has(value));
if (variable) {
const state = variable.states.get(value)!;
return {
type: "state",
value,
idle: variable.idle,
label: state.label,
description: state.description,
variableName: variable.name,
parameter: state.parameter,
};
}
}
}
export class NotificationVariable {
public constructor(definition: JSONObject) {
this.name = definition.name;
// By default all notification variables may return to idle
// Otherwise it must be specified explicitly using `idle: false`
this.idle = definition.idle !== false;
if (!isObject(definition.states)) {
throwInvalidConfig(
"notifications",
`the variable definition for ${this.name} is not an object`,
);
}
const states = new Map<number, NotificationState>();
for (const [stateId, stateDefinition] of Object.entries(
definition.states,
)) {
if (!hexKeyRegexNDigits.test(stateId)) {
throwInvalidConfig(
"notifications",
`found invalid key "${stateId}" in notification variable ${this.name}. Notification states must have lowercase hexadecimal IDs.`,
);
}
const stateIdNum = parseInt(stateId.slice(2), 16);
states.set(
stateIdNum,
new NotificationState(stateIdNum, stateDefinition as any),
);
}
this.states = states;
}
public readonly name: string;
/** Whether the variable may be reset to idle */
public readonly idle: boolean;
public readonly states: ReadonlyMap<number, NotificationState>;
}
export class NotificationState {
public constructor(id: number, definition: JSONObject) {
this.id = id;
if (typeof definition.label !== "string") {
throwInvalidConfig(
"notifications",
`The label of notification state ${num2hex(
id,
)} has a non-string label`,
);
}
this.label = definition.label;
if (
definition.description != undefined &&
typeof definition.description !== "string"
) {
throwInvalidConfig(
"notifications",
`The label of notification state ${num2hex(
id,
)} has a non-string description`,
);
}
this.description = definition.description;
if (definition.params != undefined) {
if (!isObject(definition.params)) {
throwInvalidConfig(
"notifications",
`The parameter definition of notification state ${num2hex(
id,
)} must be an object`,
);
} else if (typeof definition.params.type !== "string") {
throwInvalidConfig(
"notifications",
`The parameter type of notification state ${num2hex(
id,
)} must be a string`,
);
}
this.parameter = new NotificationParameter(definition.params);
}
}
public readonly id: number;
public readonly label: string;
public readonly description?: string;
public readonly parameter?: NotificationParameter;
}
export class NotificationEvent {
public constructor(id: number, definition: JSONObject) {
this.id = id;
this.label = definition.label;
this.description = definition.description;
if (definition.params != undefined) {
if (!isObject(definition.params)) {
throwInvalidConfig(
"notifications",
`The parameter definition of notification event ${num2hex(
id,
)} must be an object`,
);
} else if (typeof definition.params.type !== "string") {
throwInvalidConfig(
"notifications",
`The parameter type of notification event ${num2hex(
id,
)} must be a string`,
);
}
this.parameter = new NotificationParameter(definition.params);
}
}
public readonly id: number;
public readonly label: string;
public readonly description?: string;
public readonly parameter?: NotificationParameter;
}
export class NotificationParameter {
public constructor(definition: JSONObject) {
// Allow subclassing
if (new.target !== NotificationParameter) return;
// Return the correct subclass
switch (definition.type) {
case "duration":
return new NotificationParameterWithDuration(definition);
case "commandclass":
return new NotificationParameterWithCommandClass(definition);
case "value":
return new NotificationParameterWithValue(definition);
case "enum":
// TODO
break;
}
}
}
/** Marks a notification that contains a duration */
export class NotificationParameterWithDuration {
public constructor(_definition: JSONObject) {
// nothing to do
}
}
/** Marks a notification that contains a CC */
export class NotificationParameterWithCommandClass {
public constructor(_definition: JSONObject) {
// nothing to do
}
}
export class NotificationParameterWithValue {
public constructor(definition: JSONObject) {
if (typeof definition.name !== "string") {
throwInvalidConfig(
"notifications",
`Missing property name definition for Notification parameter with type: "value"!`,
);
}
this.propertyName = definition.name;
}
public readonly propertyName: string;
}