UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

163 lines (145 loc) 5.19 kB
import type { JSONObject } from "@zwave-js/shared"; import { clamp } from "alcalzone-shared/math"; import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError"; export type DurationUnit = "seconds" | "minutes" | "unknown" | "default"; const durationStringRegex = /^(?:(?<hoursStr>\d+)h)?(?:(?<minutesStr>\d+)m)?(?:(?<secondsStr>\d+)s)?$/i; /** Represents a duration that is used by some command classes */ export class Duration { public constructor(value: number, public unit: DurationUnit) { switch (unit) { case "minutes": // Don't allow 0 minutes as a duration if (value === 0) this.unit = "seconds"; break; case "unknown": case "default": value = 0; break; } this.value = value; } private _value!: number; public get value(): number { return this._value; } public set value(v: number) { this._value = clamp(v, 0, 127); } /** Parses a duration as represented in Report commands */ public static parseReport(payload?: number): Duration | undefined { if (payload == undefined) return undefined; if (payload === 0xff) return undefined; // reserved value if (payload === 0xfe) return new Duration(0, "unknown"); const isMinutes = !!(payload & 0b1000_0000); const value = (payload & 0b0111_1111) + (isMinutes ? 1 : 0); // minutes start at 1 return new Duration(value, isMinutes ? "minutes" : "seconds"); } /** Parses a duration as represented in Set commands */ public static parseSet(payload?: number): Duration | undefined { if (payload == undefined) return undefined; if (payload === 0xff) return new Duration(0, "default"); const isMinutes = !!(payload & 0b1000_0000); const value = (payload & 0b0111_1111) + (isMinutes ? 1 : 0); // minutes start at 1 return new Duration(value, isMinutes ? "minutes" : "seconds"); } /** * Parses a user-friendly duration string in the format "Xs", "Xm", "XhYm" or "XmYs", for example "10m20s". * If that cannot be exactly represented as a Z-Wave duration, the nearest possible representation will be used. */ public static parseString(text: string): Duration | undefined { if (!text.length) return undefined; if (text === "default") return new Duration(0, "default"); // unknown durations shouldn't be parsed from strings because they are only ever reported // Try to parse the numeric parts from a duration const match = durationStringRegex.exec(text); if (!match) return undefined; const { hoursStr, minutesStr, secondsStr } = match.groups!; const hours = hoursStr ? parseInt(hoursStr) : 0; const minutes = minutesStr ? parseInt(minutesStr) : 0; const seconds = secondsStr ? parseInt(secondsStr) : 0; if (hours) { // Up to 2h7m can be represented as a duration if (hours * 60 + minutes <= 127) { return new Duration(60 * hours + minutes, "minutes"); } } else if (minutes * 60 + seconds > 127) { // Up to 2m7s can be represented with seconds // anything higher has to be minutes - we round to the nearest minute return new Duration(minutes + Math.round(seconds / 60), "minutes"); } else { return new Duration(minutes * 60 + seconds, "seconds"); } } /** * Takes a user-friendly duration string or a Duration instance and returns a Duration instance (if one was given) */ public static from(input: "default"): Duration; public static from(input?: Duration | string): Duration | undefined; public static from(input?: Duration | string): Duration | undefined { if (input instanceof Duration) { return input; } else if (input) { return Duration.parseString(input); } else { return undefined; } } /** Serializes a duration for a Set command */ public serializeSet(): number { if (this.unit === "default") return 0xff; if (this.unit === "unknown") throw new ZWaveError( "Set commands don't support unknown durations", ZWaveErrorCodes.CC_Invalid, ); const isMinutes = this.unit === "minutes"; let payload = isMinutes ? 0b1000_0000 : 0; payload += (this._value - (isMinutes ? 1 : 0)) & 0b0111_1111; return payload; } /** Serializes a duration for a Report command */ public serializeReport(): number { if (this.unit === "unknown") return 0xfe; const isMinutes = this.unit === "minutes"; let payload = isMinutes ? 0b1000_0000 : 0; payload += (this._value - (isMinutes ? 1 : 0)) & 0b0111_1111; return payload; } public toJSON(): string | JSONObject { if (this.unit === "default" || this.unit === "unknown") return this.unit; return { value: this.value, unit: this.unit, }; } public toMilliseconds(): number | undefined { switch (this.unit) { case "minutes": return this._value * 60000; case "seconds": return this._value * 1000; } // The other values have no ms representation } public toString(): string { let ret = ""; switch (this.unit) { case "minutes": if (this._value > 60) { ret += `${Math.floor(this._value / 60)}h`; } ret += `${this._value % 60}m`; return ret; case "seconds": if (this._value > 60) { ret += `${Math.floor(this._value / 60)}m`; } ret += `${this._value % 60}s`; return ret; default: return this.unit; } } }