@constructorfleet/ultimate-govee
Version:
Library for interacting with Govee devices written in Typescript.
432 lines • 18.3 kB
JavaScript
"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