UNPKG

@constructorfleet/ultimate-govee

Version:

Library for interacting with Govee devices written in Typescript.

555 lines 20.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SyncBoxActiveState = exports.SegmentColorModeState = exports.SegmentColorModeStateName = exports.DIYModeState = exports.DIYModeStateName = exports.VideoModeState = exports.VideoModeStateName = exports.SceneModeState = exports.MicModeState = exports.MicModeStateName = exports.SyncBoxModes = exports.AmbiantState = exports.BrightnessMode = exports.AmbiantStateName = void 0; const device_state_1 = require("../../../states/device.state"); const _ultimate_govee_common_1 = require("../../../../../common"); const states_1 = require("../../../states"); // 35 -> ParserTimers // 18 => WakeupInfo // AbsController (bool, byte[]) // emab;ed // end birghtness // wake hour // wake min // repeat // waketime // default light // color // 17 => SLeepInfo // AbsController (bool, byte[]) // enabled // start brightness (0 -> 100) // close time (0 -> 240) // current time (0 -> 240) // vice code // voice volume // default light // color // 7, 7 => parseint by 32 bytes // 7, 8 => // brightness // // ambiant on/off // consistent = 0, segment = 1 // Ambiant brightness // Sync section # // Pars Other Info // OtherDeviceController 8, [2,5,3, 1] // 6 => reset // 163 => ParserGradual bleutil.r[0] // 169, 1 => EventVideoSensitivityResult ([2]) // 238, 48 => HDMI and light info notify // 169 3 2 => HDMI selected // 169,4, 7 => // __ // AI Ident: on // Game model # exports.AmbiantStateName = 'ambiant'; var BrightnessMode; (function (BrightnessMode) { BrightnessMode["CONSISTENT"] = "CONSISTENT"; BrightnessMode["SEGMENT"] = "SEGMENT"; })(BrightnessMode || (exports.BrightnessMode = BrightnessMode = {})); class AmbiantState extends device_state_1.DeviceOpState { constructor(device) { super({ opType: _ultimate_govee_common_1.OpType.REPORT, identifier: [7, 8] }, device, exports.AmbiantStateName, {}); } parseOpCommand(opCommand) { this.stateValue.next({ on: opCommand[2] === 0x01, brightnessMode: opCommand[2] === 0x00 ? BrightnessMode.CONSISTENT : BrightnessMode.SEGMENT, brigness: opCommand[3], }); } } exports.AmbiantState = AmbiantState; // export class TimerState extends DeviceOpState<TimerStateName, unknown> { // constructor(device: DeviceModel) { // super( // { opType: OpType.REPORT, identifier: [35] }, // device, // TimerStateName, // {}, // ); // } // parseOpCommand(opCommand: number[]): void { // const [enabledType, hour, minute, repeat] = opCommand; // } // } // export const WakeUpStateName: 'wakeUp' = 'wakeUp' as const; // export type WakeUpStatename = typeof WakeUpStateName; // export type WakeUpData = { // enabled?: boolean; // endBrightness?: number; // wakeHour?: number; // wakeMinute?: number; // repeat?: boolean; // wakeTime?: number; // defaultLight?: boolean; // color?: { // red: number; // green: number; // blue: number; // }; // }; // export class WakeupState extends DeviceOpState<WakeUpStatename, WakeUpData> { // constructor(deviceModel: DeviceModel) { // super( // { opType: OpType.Report, identifier: [18] }, // deviceModel, // WakeUpStateName, // {}, // ); // } // protected parseOpCommand(opCommand: number[]) { // const [ // enabled, // endBrtghtness, // wakeHour, // wakeMinute, // repeat, // wakeTime, // defaultLight, // colorRed, // colorBlue, // colorGreen, // ] = opCommand; // this.stateValue.next({ // enabled, // endBrtghtness, // wakeHour, // wakeMinute, // repeat, // wakeTime, // defaultLight, // color: { // red: colorRed, // green: colorGreen, // blue: colorBlue, // }, // }); // } // } // export const SleepStateName: 'wakeUp' = 'wakeUp' as const; // export type SleepStatename = typeof SleepStateName; // export type SleepData = { // enabled?: boolean; // endBrightness?: number; // wakeHour?: number; // wakeMinute?: number; // repeat?: boolean; // wakeTime?: number; // defaultLight?: boolean; // color?: { // red: number; // green: number; // blue: number; // }; // }; // export class WakeupState extends DeviceOpState<WakeUpStatename, WakeUpData> { // constructor(devicModel: DeviceModel) { // super( // { opType: OpType.Report, identifier: [18] }, // WakeUpStateName, // device, // {}, // ); // } // protected parseOpCommand(opCommand: number) { // const [ // enabled, // endBrtghtness, // wakeHour, // wakeMinute, // repeat, // wakeTime, // defaultLight, // colorRed, // colorBlue, // colorGreen, // ] = opCommand; // this.stateValue.next({ // enabled, // endBrtghtness, // wakeHour, // wakeMinute, // repeat, // wakeTime, // defaultLight, // color: { // red: colorRed, // green: colorGreen, // blue: colorBlue, // }, // }); // } // } var SyncBoxModes; (function (SyncBoxModes) { SyncBoxModes[SyncBoxModes["VIDEO"] = 0] = "VIDEO"; SyncBoxModes[SyncBoxModes["MUSIC"] = 19] = "MUSIC"; SyncBoxModes[SyncBoxModes["SCENE"] = 1] = "SCENE"; SyncBoxModes[SyncBoxModes["COLOR"] = 21] = "COLOR"; SyncBoxModes[SyncBoxModes["DIY"] = 10] = "DIY"; })(SyncBoxModes || (exports.SyncBoxModes = SyncBoxModes = {})); exports.MicModeStateName = 'micMode'; const MicScenes = { Energetic: 5, Rhythm: 3, Spectrum: 4, Rolling: 6, }; const MicSceneIds = Object.fromEntries(Object.entries(MicScenes).map(([k, v]) => [v, k])); class MicModeState extends device_state_1.DeviceOpState { constructor(device, opType = _ultimate_govee_common_1.OpType.REPORT, identifier = [0x05, SyncBoxModes.MUSIC]) { super({ opType, identifier }, device, exports.MicModeStateName, {}); this.stateToCommand = (nextState) => { const next = { micScene: nextState.micScene, micSceneId: nextState.micSceneId, sensitivity: nextState.sensitivity ?? this.value.sensitivity, calm: (nextState.calm ?? this.value.calm) === true ? 0x01 : 0x00, autoColor: (nextState.autoColor ?? this.value?.autoColor) === true ? 0x01 : 0x00, color: { red: nextState.color?.red ?? this.value?.color?.red ?? 0, green: nextState.color?.green ?? this.value?.color?.green ?? 0, blue: nextState.color?.blue ?? this.value?.color?.blue ?? 0, }, }; if (next.micScene === undefined) { if (next.micSceneId !== undefined) { next.micScene = MicSceneIds[next.micSceneId]; } else if (this.value.micSceneId !== undefined) { next.micScene = MicSceneIds[this.value.micSceneId]; } else if (this.value.micScene !== undefined) { next.micScene = this.value.micScene; } } else if (next.micSceneId === undefined) { if (this.value.micSceneId !== undefined) { next.micSceneId = this.value.micSceneId; } else if (this.value.micScene !== undefined) { next.micSceneId = MicScenes[this.value.micScene]; } } if (next.micScene === undefined || next.micSceneId === undefined) { this.logger.warn('micScene and micSceneId are not supplied, ignoring command.'); return; } if (next.sensitivity === undefined) { this.logger.warn('sensitivity is not supplied, ignoring command.'); return; } return { command: { data: { command: [ (0, _ultimate_govee_common_1.asOpCode)(_ultimate_govee_common_1.OpType.COMMAND, this.identifier, next.micSceneId, next.sensitivity, next.calm, 0, next.autoColor, next.color.red, next.color.green, next.color.blue), ], }, }, status: { op: { command: [ [ next.micSceneId, next.sensitivity, next.calm, 0, next.autoColor, next.color.red, next.color.green, next.color.blue, ], ], }, }, }; }; } parseOpCommand(opCommand) { /* trunk-ignore(eslint/no-unused-vars) */ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars const [micSceneId, sensitivity, calm, _, autoColor, red, green, blue] = opCommand; this.stateValue.next({ micScene: MicSceneIds[micSceneId], micSceneId, sensitivity, calm: calm === 0x01, autoColor: autoColor === 0x01, color: { red, green, blue, }, }); } } exports.MicModeState = MicModeState; class SceneModeState extends states_1.LightEffectState { constructor(device, opType = _ultimate_govee_common_1.OpType.REPORT, identifier = [0x05, SyncBoxModes.SCENE]) { super(device, opType, identifier); this.stateToCommand = (nextState) => { if (nextState.code === undefined && nextState.name === undefined) { this.logger.warn(`Scene code or name is required to issue commands to ${this.constructor.name}`); return undefined; } let effect; if (nextState.code !== undefined) { effect = this.effects.get(nextState.code); } if (effect === undefined && nextState.name !== undefined) { effect = Array.from(this.effects.keys()) .map((k) => { const r = this.effects.get(k); return r; }) .find((e) => e?.name === nextState.name); } if (effect === undefined) { this.logger.warn(`Unable to locate effect with code ${nextState.code} or ${nextState.name}`); return undefined; } return { command: { data: { command: effect.opCode, }, cmdVersion: effect.cmdVersion, }, status: { op: { command: [[(effect.code ?? 0) >> 8, (effect.code ?? 0) % 256]], }, }, }; }; } parseOpCommand(opCommand) { this.activeEffectCode.next((0, _ultimate_govee_common_1.total)(opCommand.slice(0, 2), true)); } } exports.SceneModeState = SceneModeState; exports.VideoModeStateName = 'videoMode'; class VideoModeState extends device_state_1.DeviceOpState { constructor(device, opType = _ultimate_govee_common_1.OpType.REPORT, identifier = [0x05, SyncBoxModes.VIDEO]) { super({ opType, identifier }, device, exports.VideoModeStateName, {}); this.stateToCommand = (nextState) => { this.logger.log(nextState); return undefined; // if (nextState.code === undefined && nextState.name === undefined) { // this.logger.warn( // `Scene code or name is required to issue commands to ${this.constructor.name}`, // ); // return undefined; // } // let effect: Partial<Effect> | undefined; // if (nextState.code !== undefined) { // effect = this.effects.get(nextState.code); // } // if (effect === undefined && nextState.name !== undefined) { // effect = Array.from(this.effects.keys()) // .map((k) => { // const r = this.effects.get(k); // return r; // }) // .find((e) => e?.name === nextState.name); // } // if (effect === undefined) { // this.logger.warn( // `Unable to locate effect with code ${nextState.code} or ${nextState.name}`, // ); // return undefined; // } // return { // command: { // data: { // command: effect.opCode, // }, // cmdVersion: effect.cmdVersion, // }, // status: { // op: { // command: [[(effect.code ?? 0) >> 8, (effect.code ?? 0) % 256]], // }, // }, // }; }; } parseOpCommand(opCommand) { this.logger.log(opCommand); // this.activeEffectCode.next(total(opCommand.slice(0, 2), true)); } } exports.VideoModeState = VideoModeState; exports.DIYModeStateName = 'diyMode'; class DIYModeState extends device_state_1.DeviceOpState { constructor(device) { super({ opType: _ultimate_govee_common_1.OpType.REPORT, identifier: [5, SyncBoxModes.DIY] }, device, exports.DIYModeStateName, {}); this.stateToCommand = (state) => { if (state.code === undefined) { this.logger.warn('diy code not supplied, ignoring command.'); return; } return { command: [ { data: { command: [ (0, _ultimate_govee_common_1.asOpCode)(_ultimate_govee_common_1.OpType.COMMAND, this.identifier, state.code % 255, state.code >> 8), ], }, }, ], status: [ { op: { command: [[state.code % 255, state.code >> 8]], }, }, ], }; }; } parseOpCommand(opCommand) { const effectCode = (0, _ultimate_govee_common_1.total)(opCommand.slice(0, 2)); this.stateValue.next({ code: effectCode, }); } } exports.DIYModeState = DIYModeState; exports.SegmentColorModeStateName = 'segmentColorMode'; const indexToSegmentBits = (indices) => Number(indices) .toString(16) .split(/(.{2})/g) ?.filter((i) => i.length > 0) .map((i) => parseInt(`0x${i}`, 16)); class SegmentColorModeState extends device_state_1.DeviceOpState { constructor(device, opType = _ultimate_govee_common_1.OpType.REPORT, identifier = [0xa5]) { super({ opType, identifier }, device, exports.SegmentColorModeStateName, []); this.segments = []; this.stateToCommand = (nextState) => { const pad = (val) => `000${val}`.slice(-3); const groups = nextState.reduce((group, segment) => { if (segment.id === undefined) { return group; } if (segment.color !== undefined) { const key = `${pad(segment.color.red)}${pad(segment.color.green)}${pad(segment.color.blue)}`; group.colorGroups[key] = (group.colorGroups[key] || 0) + (1 << segment.id); } if (segment.brightness !== undefined) { group.brightnessGroups[`${segment.brightness}`] = (group.brightnessGroups[`${segment.brightness}`] || 0) + (1 << segment.id); } return group; }, { colorGroups: {}, brightnessGroups: {}, }); const colorCommands = Object.entries(groups.colorGroups).map(([key, indicies]) => { const colorKeys = key .split(/(.{3})/g) .filter((i) => i.length > 0) .map((i) => Number.parseInt(i, 10)); const color = { red: colorKeys[0], green: colorKeys[1], blue: colorKeys[2], }; const indexBytes = indexToSegmentBits(indicies); return (0, _ultimate_govee_common_1.asOpCode)(_ultimate_govee_common_1.OpType.COMMAND, 0x05, SyncBoxModes.COLOR, 1, color.red, color.green, color.blue, 0, 0, 0, 0, 0, ...indexBytes); }); const brightnessCommands = Object.entries(groups.brightnessGroups).map(([key, indicies]) => { const indexBytes = indexToSegmentBits(indicies); return (0, _ultimate_govee_common_1.asOpCode)(_ultimate_govee_common_1.OpType.COMMAND, 0x05, SyncBoxModes.COLOR, 2, parseInt(key, 10), ...indexBytes); }); return { command: { data: { command: [...colorCommands, ...brightnessCommands], }, }, status: { // TODO }, }; }; } parseMultiOpCommand(opCommands) { if (this.segments.length < opCommands.length * 3) { new Array(opCommands.length * 3 - this.segments.length) .fill({}) .forEach((segment) => this.segments.push(segment)); } else { this.segments = this.segments.slice(0, opCommands.length * 3); } const parseOpCommand = (opCommand) => { const messageNumber = opCommand[0] - 1; const segmentCodes = (0, _ultimate_govee_common_1.chunk)(opCommand.slice(1), 4).slice(0, 3); segmentCodes.forEach((segmentCode, index) => { const id = messageNumber * 3 + index; const [brightness, red, green, blue] = segmentCode; this.segments[id] = { id: messageNumber * 3 + index, brightness, color: { red, green, blue, }, }; }); }; opCommands.forEach((opCommand) => parseOpCommand(opCommand)); this.stateValue.next(this.segments); } } exports.SegmentColorModeState = SegmentColorModeState; class SyncBoxActiveState extends states_1.ModeState { constructor(device, states) { super(device, states.filter((state) => state !== undefined), 0xaa, [0x05], true); this.stateToCommand = () => { return undefined; }; this.activeIdentifier?.subscribe((identifier) => { if (identifier === undefined) { return; } switch (identifier[0]) { case SyncBoxModes.MUSIC: this.stateValue.next(this.modes.find((mode) => mode.name === exports.MicModeStateName)); break; case SyncBoxModes.COLOR: this.stateValue.next(this.modes.find((mode) => mode.name === exports.SegmentColorModeStateName)); break; case SyncBoxModes.VIDEO: this.stateValue.next(this.modes.find((mode) => mode.name === exports.VideoModeStateName)); break; case SyncBoxModes.SCENE: this.stateValue.next(this.modes.find((mode) => mode.name === states_1.LightEffectStateName)); break; case SyncBoxModes.DIY: this.stateValue.next(this.modes.find((mode) => mode.name === exports.DIYModeStateName)); break; default: break; } }); } setState(nextState) { if (nextState === undefined) { this.logger.warn('Next state not specified, ignoring command'); return []; } return nextState.setState(nextState.value); } } exports.SyncBoxActiveState = SyncBoxActiveState; //# sourceMappingURL=sync-box.states.js.map