UNPKG

@constructorfleet/ultimate-govee

Version:

Library for interacting with Govee devices written in Typescript.

432 lines 18.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RGBICActiveState = exports.ColorModeState = exports.WholeColorModeStateName = exports.SegmentColorModeState = exports.SegmentColorModeStateName = exports.ColorTemperatureModeState = exports.DiyModeState = exports.DiyModeStateName = exports.MicModeState = exports.MicModeStateName = exports.SceneModeState = exports.RGBICModes = void 0; const _ultimate_govee_common_1 = require("../../../../../common"); const states_1 = require("../../../states"); const rxjs_1 = require("rxjs"); const op_code_1 = require("../../../../../data/api/diy/models/op-code"); var RGBICModes; (function (RGBICModes) { RGBICModes[RGBICModes["SCENE"] = 4] = "SCENE"; RGBICModes[RGBICModes["MIC"] = 19] = "MIC"; RGBICModes[RGBICModes["DIY"] = 10] = "DIY"; RGBICModes[RGBICModes["SEGMENT_COLOR"] = 21] = "SEGMENT_COLOR"; RGBICModes[RGBICModes["WHOLE_COLOR"] = 2] = "WHOLE_COLOR"; })(RGBICModes || (exports.RGBICModes = RGBICModes = {})); class SceneModeState extends states_1.LightEffectState { constructor(device, opType = _ultimate_govee_common_1.OpType.REPORT, identifier = [0x05, RGBICModes.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?.opCode === undefined) { this.logger.warn(`Unable to locate effect with code ${nextState.code} or ${nextState.name}`); return undefined; } return { command: { data: { command: effect.opCode, }, }, 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.MicModeStateName = 'micMode'; class MicModeState extends states_1.DeviceOpState { constructor(device, opType = _ultimate_govee_common_1.OpType.REPORT, identifier = [0x05, RGBICModes.MIC]) { super({ opType, identifier }, device, exports.MicModeStateName, {}); this.stateToCommand = (nextState) => { const next = { micScene: nextState.micScene ?? this.value.micScene ?? 0, sensitivity: nextState.sensitivity ?? this.value.sensitivity ?? 50, 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, }, }; return { command: { data: { command: [ (0, _ultimate_govee_common_1.asOpCode)(_ultimate_govee_common_1.OpType.COMMAND, this.identifier, next.micScene, next.sensitivity, next.calm, next.autoColor, next.color.red, next.color.green, next.color.blue), ], }, }, status: { op: { command: [ [ next.micScene, next.sensitivity, next.calm, next.autoColor, next.color.red, next.color.green, next.color.blue, ], ], }, }, }; }; } parseOpCommand(opCommand) { const [micScene, sensitivity, calm, autoColor, red, green, blue] = opCommand.slice(1); this.stateValue.next({ micScene, sensitivity, calm: calm === 0x01, autoColor: autoColor === 0x01, color: { red, green, blue, }, }); } } exports.MicModeState = MicModeState; exports.DiyModeStateName = 'diyEffect'; class DiyModeState extends states_1.DeviceOpState { constructor(device) { super({ opType: _ultimate_govee_common_1.OpType.REPORT, identifier: [5, 10] }, device, exports.DiyModeStateName, {}); this.effects = new _ultimate_govee_common_1.DeltaMap(); this.activeEffectCode = new rxjs_1.BehaviorSubject(undefined); this.stateToCommand = (state) => { let newEffect; if (state?.name === undefined) { if (state?.code === undefined) { this.logger.warn('Missing name and code, ignoring command'); return; } newEffect = Array.from(this.effects.entries()).find(([_, effect]) => effect.code === state.code)?.[1]; } if (newEffect === undefined) { const effect = Array.from(this.effects.values()).find((effect) => effect.name === state.name); if (newEffect === undefined && effect === undefined) { this.logger.warn('Unable to determing DIY effect from provided name or code, skipping command'); return; } else if (newEffect === undefined && effect !== undefined) { newEffect = effect; } } if (newEffect === undefined) { this.logger.warn('Unable to determine effect from name or code, ignoring command'); return; } const commands = (0, op_code_1.rebuildDiyOpCode)(newEffect.code, newEffect.diyOpCodeBase64)(this.identifier); return { command: { type: 1, cmdVersion: 0, data: { command: commands, }, }, status: { op: { command: [[newEffect.code % 255, newEffect.code >> 8]], }, }, }; }; this.activeEffectCode.subscribe((effectCode) => { if (effectCode !== undefined) { this.stateValue.next(this.effects.get(effectCode) ?? {}); } }); this.effects.delta$.subscribe(() => { this.stateValue.next(this.stateValue.getValue()); }); } parseOpCommand(opCommand) { const effectCode = (0, _ultimate_govee_common_1.total)(opCommand.slice(0, 2), true); this.activeEffectCode.next(effectCode); } } exports.DiyModeState = DiyModeState; class ColorTemperatureModeState extends states_1.ColorTempState { constructor(device) { super(device, _ultimate_govee_common_1.OpType.REPORT, 5, 21, 1); this.stateToCommand = (nextState) => { if (nextState.current === undefined) { this.logger.warn('color temperature not supplied, skipping command'); return; } return { command: [ // { // data: { // command: [ // asOpCode( // OpType.COMMAND, // ...this.identifier!, // nextState.current >> 8, // nextState.current % 256, // ), // ], // }, // }, { command: 'colorTem', data: { colorTemInKelvin: nextState.current, }, }, ], status: [ // { // op: { // command: [[nextState.current >> 8, nextState.current % 256]], // }, // }, { state: { colorTemperature: { min: 2000, max: 9000, current: nextState.current, }, }, }, ], }; }; } parseOpCommand(opCommand) { this.stateValue.next({ range: { min: 2000, max: 9000, }, current: (0, _ultimate_govee_common_1.total)(opCommand.slice(0, 2)), }); } } exports.ColorTemperatureModeState = ColorTemperatureModeState; 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 states_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, RGBICModes.SEGMENT_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, RGBICModes.SEGMENT_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; exports.WholeColorModeStateName = 'wholeColorMode'; class ColorModeState extends states_1.DeviceOpState { constructor(device, opType = _ultimate_govee_common_1.OpType.REPORT, identifier = [0x05, RGBICModes.WHOLE_COLOR]) { super({ opType, identifier }, device, exports.WholeColorModeStateName, {}, states_1.ParseOption.opCode.or(states_1.ParseOption.state)); this.stateToCommand = (state) => { return { command: [ { command: 'colorwc', data: { color: { r: state.red ?? 0, g: state.green ?? 0, b: state.blue ?? 0, }, colorTemInKelvin: 0, }, }, { data: { command: [ (0, _ultimate_govee_common_1.asOpCode)(_ultimate_govee_common_1.OpType.COMMAND, this.identifier, state.red ?? 0, state.green ?? 0, state.blue ?? 0), ], }, }, ], status: [ { state: { color: { red: state.red ?? 0, green: state.green ?? 0, blue: state.blue ?? 0, }, mode: RGBICModes.WHOLE_COLOR, }, }, { op: { command: [[state.red ?? 0, state.green ?? 0, state.blue ?? 0]], }, }, ], }; }; } parseState(data) { if (data?.state?.color !== undefined) { this.stateValue.next(data.state.color); } } parseOpCommand(opCommand) { this.stateValue.next({ red: opCommand[1], green: opCommand[2], blue: opCommand[3], }); } } exports.ColorModeState = ColorModeState; class RGBICActiveState extends states_1.ModeState { constructor(device, states) { super(device, states.filter((state) => state !== undefined), _ultimate_govee_common_1.OpType.REPORT, [0x05], true); this.stateToCommand = () => { return undefined; }; this.activeIdentifier?.subscribe((identifier) => { if (identifier === undefined) { return; } switch (identifier[0]) { case RGBICModes.MIC: this.stateValue.next(this.modes.find((mode) => mode.name === exports.MicModeStateName)); break; case RGBICModes.SEGMENT_COLOR: if (identifier.length > 10) { this.modes.find((mode) => mode.name === states_1.ColorTempStateName); } else { this.stateValue.next(this.modes.find((mode) => mode.name === exports.SegmentColorModeStateName)); } break; case RGBICModes.DIY: this.stateValue.next(this.modes.find((mode) => mode.name === exports.DiyModeStateName)); break; case RGBICModes.WHOLE_COLOR: this.stateValue.next(this.modes.find((mode) => mode.name === exports.WholeColorModeStateName)); break; case RGBICModes.SCENE: this.stateValue.next(this.modes.find((mode) => mode.name === states_1.LightEffectStateName)); break; default: break; } }); } setState(nextState) { if (nextState === undefined) { this.logger.warn('Next state not specified, ignoring command'); return []; } return nextState.setState(nextState.value); } } exports.RGBICActiveState = RGBICActiveState; //# sourceMappingURL=rgbic-light.modes.js.map