@ray-js/lamp-schedule-core 1.0.4-beta-15 → 1.0.5-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/config/dpCodes.d.ts +5 -1
- package/lib/config/dpCodes.js +5 -1
- package/lib/dpParser/__test__/rhythms.test.js +7 -2
- package/lib/dpParser/__test__/rhythmsSigmesh.test.d.ts +1 -0
- package/lib/dpParser/__test__/rhythmsSigmesh.test.js +263 -0
- package/lib/dpParser/__test__/sleepSigmesh.test.d.ts +1 -0
- package/lib/dpParser/__test__/sleepSigmesh.test.js +183 -0
- package/lib/dpParser/brightAdjustData.d.ts +10 -0
- package/lib/dpParser/brightAdjustData.js +43 -0
- package/lib/dpParser/colourDataRaw.d.ts +14 -0
- package/lib/dpParser/colourDataRaw.js +56 -0
- package/lib/dpParser/index.d.ts +4 -1
- package/lib/dpParser/index.js +14 -1
- package/lib/dpParser/rhythms/index.d.ts +9 -0
- package/lib/dpParser/rhythms/index.js +27 -0
- package/lib/dpParser/{rhythms.d.ts → rhythms/rhythmsCommon.d.ts} +2 -3
- package/lib/dpParser/{rhythms.js → rhythms/rhythmsCommon.js} +6 -9
- package/lib/dpParser/rhythms/rhythmsSigmesh.d.ts +17 -0
- package/lib/dpParser/rhythms/rhythmsSigmesh.js +143 -0
- package/lib/dpParser/sleep/index.d.ts +1 -2
- package/lib/dpParser/sleep/index.js +13 -12
- package/lib/dpParser/sleep/sleepSigmesh.d.ts +20 -0
- package/lib/dpParser/sleep/sleepSigmesh.js +86 -98
- package/lib/dpParser/stripLocalTimer.d.ts +10 -0
- package/lib/dpParser/stripLocalTimer.js +107 -0
- package/lib/dpParser/wakeup/index.d.ts +0 -2
- package/lib/dpParser/wakeup/index.js +12 -16
- package/lib/dpParser/wakeup/wakeupSigmesh.js +9 -1
- package/lib/hooks/useBaseLightDp.d.ts +1 -1
- package/lib/hooks/useBaseLightDp.js +1 -0
- package/lib/hooks/useCommonSupport.js +19 -0
- package/lib/hooks/useCycleDp.js +2 -0
- package/lib/hooks/useDPByProtocol.d.ts +2 -0
- package/lib/hooks/useRandomDp.js +2 -0
- package/lib/hooks/useRhythmsDp.d.ts +3 -3
- package/lib/hooks/useRhythmsDp.js +2 -0
- package/lib/hooks/useSleepDp.js +2 -0
- package/lib/hooks/useWakeUpDp.js +2 -0
- package/lib/hooks/useWakeupDp.js +2 -0
- package/lib/types/rhythms.d.ts +16 -0
- package/lib/types/rhythms.js +3 -1
- package/lib/types/timer.d.ts +42 -0
- package/lib/utils/ScheduleSupport.d.ts +4 -0
- package/lib/utils/ScheduleSupport.js +9 -0
- package/lib/utils/getDPByProtocol.d.ts +4 -0
- package/lib/utils/matterDeviceUtils.d.ts +4 -0
- package/package.json +1 -1
package/lib/config/dpCodes.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export declare const scheduleDpCodes: {
|
|
|
19
19
|
readonly CYCLE_TIMING: "cycle_timing";
|
|
20
20
|
/** 通用本地定时 */
|
|
21
21
|
readonly LOCAL_TIMER: "local_timer";
|
|
22
|
-
/** 灯带本地定时 */
|
|
22
|
+
/** 灯带本地定时 Beacon */
|
|
23
23
|
readonly STRIP_LOCAL_TIMER: "strip_local_timer";
|
|
24
24
|
/** 定时同步 一般用于 Beacon */
|
|
25
25
|
readonly TIMER_SYNC: "timer_sync";
|
|
@@ -33,6 +33,10 @@ export declare const scheduleDpCodes: {
|
|
|
33
33
|
readonly BRIGHTNESS: "bright_value";
|
|
34
34
|
/** 彩光 */
|
|
35
35
|
readonly COLOUR_DATA: "colour_data";
|
|
36
|
+
/** 彩光原始数据 Beacon */
|
|
37
|
+
readonly COLOUR_DATA_RAW: "colour_data_raw";
|
|
38
|
+
/** 亮度调节 Beacon */
|
|
39
|
+
readonly BRIGHT_ADJUST_DATA: "bright_adjust_data";
|
|
36
40
|
/** 模式 */
|
|
37
41
|
readonly WORK_MODE: "work_mode";
|
|
38
42
|
};
|
package/lib/config/dpCodes.js
CHANGED
|
@@ -22,7 +22,7 @@ export const scheduleDpCodes = {
|
|
|
22
22
|
[EScheduleType.CYCLE_TIMING]: 'cycle_timing',
|
|
23
23
|
/** 通用本地定时 */
|
|
24
24
|
[EScheduleType.LOCAL_TIMER]: 'local_timer',
|
|
25
|
-
/** 灯带本地定时 */
|
|
25
|
+
/** 灯带本地定时 Beacon */
|
|
26
26
|
[EScheduleType.STRIP_LOCAL_TIMER]: 'strip_local_timer',
|
|
27
27
|
/** 定时同步 一般用于 Beacon */
|
|
28
28
|
[EScheduleType.TIMER_SYNC]: 'timer_sync',
|
|
@@ -36,6 +36,10 @@ export const scheduleDpCodes = {
|
|
|
36
36
|
BRIGHTNESS: 'bright_value',
|
|
37
37
|
/** 彩光 */
|
|
38
38
|
COLOUR_DATA: 'colour_data',
|
|
39
|
+
/** 彩光原始数据 Beacon */
|
|
40
|
+
COLOUR_DATA_RAW: 'colour_data_raw',
|
|
41
|
+
/** 亮度调节 Beacon */
|
|
42
|
+
BRIGHT_ADJUST_DATA: 'bright_adjust_data',
|
|
39
43
|
/** 模式 */
|
|
40
44
|
WORK_MODE: 'work_mode'
|
|
41
45
|
};
|
|
@@ -71,7 +71,9 @@ describe('RhythmParser', () => {
|
|
|
71
71
|
expect(result).toBe('');
|
|
72
72
|
});
|
|
73
73
|
it('should return empty string when support instance is missing', () => {
|
|
74
|
-
getSupportIns
|
|
74
|
+
// getSupportIns is called in both the dispatcher and the common formatter,
|
|
75
|
+
// so we need to mock it for all calls within this test
|
|
76
|
+
getSupportIns.mockReturnValue(null);
|
|
75
77
|
const result = rhythmParser.formatter({
|
|
76
78
|
version: 1,
|
|
77
79
|
power: true,
|
|
@@ -91,9 +93,12 @@ describe('RhythmParser', () => {
|
|
|
91
93
|
});
|
|
92
94
|
expect(result).toBe('');
|
|
93
95
|
expect(scheduleLogger.error).toHaveBeenCalledWith('协议解析 rhythms formatter =====', '数据为空');
|
|
96
|
+
// Restore default mock
|
|
97
|
+
getSupportIns.mockReturnValue(createSupport(true));
|
|
94
98
|
});
|
|
95
99
|
it('should force temperature to 100 when device does not support temperature DP', () => {
|
|
96
|
-
getSupportIns
|
|
100
|
+
// getSupportIns is called in both the dispatcher and the common formatter
|
|
101
|
+
getSupportIns.mockReturnValue(createSupport(false));
|
|
97
102
|
const result = rhythmParser.formatter({
|
|
98
103
|
version: 0,
|
|
99
104
|
power: true,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import "core-js/modules/esnext.iterator.constructor.js";
|
|
2
|
+
import "core-js/modules/esnext.iterator.map.js";
|
|
3
|
+
jest.mock('@ray-js/panel-sdk/lib/utils', () => ({
|
|
4
|
+
generateDpStrStep: jest.fn(str => {
|
|
5
|
+
let index = 0;
|
|
6
|
+
return len => {
|
|
7
|
+
const segment = str.slice(index, index + len);
|
|
8
|
+
index += len;
|
|
9
|
+
return {
|
|
10
|
+
value: parseInt(segment, 16)
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
}),
|
|
14
|
+
numToHexString: jest.fn(num => num.toString(16).padStart(2, '0'))
|
|
15
|
+
}));
|
|
16
|
+
jest.mock('../../utils/ScheduleLogger', () => {
|
|
17
|
+
const logger = {
|
|
18
|
+
debug: jest.fn(),
|
|
19
|
+
warn: jest.fn(),
|
|
20
|
+
error: jest.fn(),
|
|
21
|
+
info: jest.fn()
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
scheduleLogger: logger
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
jest.mock('../../hooks/useCommonSupport', () => ({
|
|
28
|
+
getSupportIns: jest.fn()
|
|
29
|
+
}));
|
|
30
|
+
jest.mock('../../utils/getDPByProtocol', () => ({
|
|
31
|
+
getDPByProtocol: jest.fn().mockReturnValue({
|
|
32
|
+
TEMPERATURE: 'temp_value'
|
|
33
|
+
})
|
|
34
|
+
}));
|
|
35
|
+
import { rhythmParserSigmesh, RhythmSigmesh } from '../rhythms/rhythmsSigmesh';
|
|
36
|
+
const {
|
|
37
|
+
getSupportIns
|
|
38
|
+
} = require('../../hooks/useCommonSupport');
|
|
39
|
+
const createSupport = function () {
|
|
40
|
+
let isSupportTemp = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
|
41
|
+
return {
|
|
42
|
+
isSigMeshDevice: jest.fn().mockReturnValue(true),
|
|
43
|
+
isSupportDp: jest.fn().mockImplementation(dp => {
|
|
44
|
+
if (dp === 'temp_value') {
|
|
45
|
+
return isSupportTemp;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
})
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 构建 sigmesh rhythms DP 字符串
|
|
54
|
+
* 格式: version(2) + power(2) + mode(2) + weeks(2) + paramMode(2) + number(2)
|
|
55
|
+
* + nodes[valid(2) + hour(2) + minute(2) + brightness(2) + temperature(2)]
|
|
56
|
+
*/
|
|
57
|
+
const buildDpStr = function (nodes) {
|
|
58
|
+
let version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
|
59
|
+
let power = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
|
|
60
|
+
let mode = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
|
|
61
|
+
let weeks = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0x7f;
|
|
62
|
+
let paramMode = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1;
|
|
63
|
+
const header = [version, power, mode, weeks, paramMode, nodes.length].map(v => v.toString(16).padStart(2, '0')).join('');
|
|
64
|
+
const body = nodes.map(node => [node.valid, node.hour, node.minute, node.brightness, node.temperature].map(v => v.toString(16).padStart(2, '0')).join('')).join('');
|
|
65
|
+
return `${header}${body}`;
|
|
66
|
+
};
|
|
67
|
+
describe('RhythmSigmesh parser & formatter', () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
jest.clearAllMocks();
|
|
70
|
+
getSupportIns.mockReturnValue(createSupport(true));
|
|
71
|
+
});
|
|
72
|
+
describe('parser', () => {
|
|
73
|
+
it('returns default value when dp string is empty', () => {
|
|
74
|
+
const result = rhythmParserSigmesh.parser('');
|
|
75
|
+
expect(result).toEqual(new RhythmSigmesh().defaultValue);
|
|
76
|
+
});
|
|
77
|
+
it('returns default value when dp string length is invalid', () => {
|
|
78
|
+
// Too short
|
|
79
|
+
const result = rhythmParserSigmesh.parser('010100');
|
|
80
|
+
expect(result).toEqual(new RhythmSigmesh().defaultValue);
|
|
81
|
+
});
|
|
82
|
+
it('returns default value when node data is not aligned', () => {
|
|
83
|
+
// Header (12 chars) + partial node data (not multiple of 10)
|
|
84
|
+
// 14 chars header + 2 chars body (not 10)
|
|
85
|
+
const result = rhythmParserSigmesh.parser('01010000007f0104' + '01');
|
|
86
|
+
expect(result).toEqual(new RhythmSigmesh().defaultValue);
|
|
87
|
+
});
|
|
88
|
+
it('parses valid dp string with 4 nodes', () => {
|
|
89
|
+
const dpStr = buildDpStr([{
|
|
90
|
+
valid: 1,
|
|
91
|
+
hour: 6,
|
|
92
|
+
minute: 30,
|
|
93
|
+
brightness: 100,
|
|
94
|
+
temperature: 50
|
|
95
|
+
}, {
|
|
96
|
+
valid: 1,
|
|
97
|
+
hour: 11,
|
|
98
|
+
minute: 30,
|
|
99
|
+
brightness: 100,
|
|
100
|
+
temperature: 100
|
|
101
|
+
}, {
|
|
102
|
+
valid: 1,
|
|
103
|
+
hour: 17,
|
|
104
|
+
minute: 0,
|
|
105
|
+
brightness: 70,
|
|
106
|
+
temperature: 50
|
|
107
|
+
}, {
|
|
108
|
+
valid: 1,
|
|
109
|
+
hour: 20,
|
|
110
|
+
minute: 30,
|
|
111
|
+
brightness: 70,
|
|
112
|
+
temperature: 50
|
|
113
|
+
}]);
|
|
114
|
+
const result = rhythmParserSigmesh.parser(dpStr);
|
|
115
|
+
expect(result.version).toBe(1);
|
|
116
|
+
expect(result.power).toBe(true);
|
|
117
|
+
expect(result.mode).toBe(0);
|
|
118
|
+
expect(result.paramMode).toBe(1);
|
|
119
|
+
expect(result.number).toBe(4);
|
|
120
|
+
expect(result.rhythms).toHaveLength(4);
|
|
121
|
+
expect(result.rhythms[0]).toMatchObject({
|
|
122
|
+
power: true,
|
|
123
|
+
hour: 6,
|
|
124
|
+
minute: 30,
|
|
125
|
+
brightness: 100,
|
|
126
|
+
temperature: 50
|
|
127
|
+
});
|
|
128
|
+
expect(result.rhythms[3]).toMatchObject({
|
|
129
|
+
power: true,
|
|
130
|
+
hour: 20,
|
|
131
|
+
minute: 30,
|
|
132
|
+
brightness: 70,
|
|
133
|
+
temperature: 50
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
it('parses power=false correctly', () => {
|
|
137
|
+
const dpStr = buildDpStr([{
|
|
138
|
+
valid: 0,
|
|
139
|
+
hour: 8,
|
|
140
|
+
minute: 0,
|
|
141
|
+
brightness: 50,
|
|
142
|
+
temperature: 30
|
|
143
|
+
}], 1, 0);
|
|
144
|
+
const result = rhythmParserSigmesh.parser(dpStr);
|
|
145
|
+
expect(result.power).toBe(false);
|
|
146
|
+
expect(result.rhythms[0].power).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('formatter', () => {
|
|
150
|
+
it('returns empty string when data is null', () => {
|
|
151
|
+
const result = rhythmParserSigmesh.formatter(null);
|
|
152
|
+
expect(result).toBe('');
|
|
153
|
+
});
|
|
154
|
+
it('returns empty string when support is null', () => {
|
|
155
|
+
getSupportIns.mockReturnValueOnce(null);
|
|
156
|
+
const result = rhythmParserSigmesh.formatter({
|
|
157
|
+
version: 1,
|
|
158
|
+
power: true,
|
|
159
|
+
mode: 0,
|
|
160
|
+
weeks: [1, 1, 1, 1, 1, 1, 1, 0],
|
|
161
|
+
paramMode: 1,
|
|
162
|
+
number: 1,
|
|
163
|
+
rhythms: [{
|
|
164
|
+
power: true,
|
|
165
|
+
hour: 6,
|
|
166
|
+
minute: 30,
|
|
167
|
+
brightness: 100,
|
|
168
|
+
temperature: 50
|
|
169
|
+
}]
|
|
170
|
+
});
|
|
171
|
+
expect(result).toBe('');
|
|
172
|
+
});
|
|
173
|
+
it('formats valid data to dp string', () => {
|
|
174
|
+
const result = rhythmParserSigmesh.formatter({
|
|
175
|
+
version: 1,
|
|
176
|
+
power: true,
|
|
177
|
+
mode: 0,
|
|
178
|
+
weeks: [1, 1, 1, 1, 1, 1, 1, 0],
|
|
179
|
+
paramMode: 1,
|
|
180
|
+
number: 2,
|
|
181
|
+
rhythms: [{
|
|
182
|
+
power: true,
|
|
183
|
+
hour: 6,
|
|
184
|
+
minute: 30,
|
|
185
|
+
brightness: 100,
|
|
186
|
+
temperature: 50
|
|
187
|
+
}, {
|
|
188
|
+
power: false,
|
|
189
|
+
hour: 20,
|
|
190
|
+
minute: 0,
|
|
191
|
+
brightness: 70,
|
|
192
|
+
temperature: 30
|
|
193
|
+
}]
|
|
194
|
+
});
|
|
195
|
+
const expected = buildDpStr([{
|
|
196
|
+
valid: 1,
|
|
197
|
+
hour: 6,
|
|
198
|
+
minute: 30,
|
|
199
|
+
brightness: 100,
|
|
200
|
+
temperature: 50
|
|
201
|
+
}, {
|
|
202
|
+
valid: 0,
|
|
203
|
+
hour: 20,
|
|
204
|
+
minute: 0,
|
|
205
|
+
brightness: 70,
|
|
206
|
+
temperature: 30
|
|
207
|
+
}], 1, 1, 0, 0x7f, 1);
|
|
208
|
+
expect(result).toBe(expected);
|
|
209
|
+
});
|
|
210
|
+
it('forces temperature to 100 when device does not support temperature DP', () => {
|
|
211
|
+
getSupportIns.mockReturnValueOnce(createSupport(false));
|
|
212
|
+
const result = rhythmParserSigmesh.formatter({
|
|
213
|
+
version: 1,
|
|
214
|
+
power: true,
|
|
215
|
+
mode: 0,
|
|
216
|
+
weeks: [1, 1, 1, 1, 1, 1, 1, 0],
|
|
217
|
+
paramMode: 1,
|
|
218
|
+
number: 1,
|
|
219
|
+
rhythms: [{
|
|
220
|
+
power: true,
|
|
221
|
+
hour: 6,
|
|
222
|
+
minute: 0,
|
|
223
|
+
brightness: 50,
|
|
224
|
+
temperature: 30
|
|
225
|
+
}]
|
|
226
|
+
});
|
|
227
|
+
// temperature should be 100 (0x64), not 30 (0x1e)
|
|
228
|
+
expect(result.slice(-2)).toBe('64');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
describe('round-trip', () => {
|
|
232
|
+
it('round-trips dp string through parser and formatter', () => {
|
|
233
|
+
const dpStr = buildDpStr([{
|
|
234
|
+
valid: 1,
|
|
235
|
+
hour: 6,
|
|
236
|
+
minute: 30,
|
|
237
|
+
brightness: 100,
|
|
238
|
+
temperature: 50
|
|
239
|
+
}, {
|
|
240
|
+
valid: 1,
|
|
241
|
+
hour: 11,
|
|
242
|
+
minute: 30,
|
|
243
|
+
brightness: 100,
|
|
244
|
+
temperature: 100
|
|
245
|
+
}, {
|
|
246
|
+
valid: 1,
|
|
247
|
+
hour: 17,
|
|
248
|
+
minute: 0,
|
|
249
|
+
brightness: 70,
|
|
250
|
+
temperature: 50
|
|
251
|
+
}, {
|
|
252
|
+
valid: 1,
|
|
253
|
+
hour: 20,
|
|
254
|
+
minute: 30,
|
|
255
|
+
brightness: 70,
|
|
256
|
+
temperature: 50
|
|
257
|
+
}]);
|
|
258
|
+
const parsed = rhythmParserSigmesh.parser(dpStr);
|
|
259
|
+
const formatted = rhythmParserSigmesh.formatter(parsed);
|
|
260
|
+
expect(formatted).toBe(dpStr);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import "core-js/modules/esnext.iterator.constructor.js";
|
|
2
|
+
import "core-js/modules/esnext.iterator.map.js";
|
|
3
|
+
jest.mock('@ray-js/panel-sdk/lib/utils', () => ({
|
|
4
|
+
generateDpStrStep: jest.fn(str => {
|
|
5
|
+
let index = 0;
|
|
6
|
+
return len => {
|
|
7
|
+
const segment = str.slice(index, index + len);
|
|
8
|
+
index += len;
|
|
9
|
+
return {
|
|
10
|
+
value: parseInt(segment, 16)
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
}),
|
|
14
|
+
numToHexString: jest.fn(num => num.toString(16).padStart(2, '0'))
|
|
15
|
+
}));
|
|
16
|
+
import { sleepParserSigmesh, SleepSigmesh } from '../sleep/sleepSigmesh';
|
|
17
|
+
const loopsToHex = loops => {
|
|
18
|
+
const binary = loops.split('').reverse().join('');
|
|
19
|
+
return parseInt(binary, 2);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 构建 sigmesh sleep DP 字符串
|
|
24
|
+
* 格式: version(2) + dataMode(2) + length(2) + nodes[onOff(2) + loops(2) + step(2) + hour(2) + minute(2) + brightness(2) + temperature(2)]
|
|
25
|
+
* 注意: sleep 模式不含 duration 字段(与 wakeup 的区别)
|
|
26
|
+
*/
|
|
27
|
+
const buildDpStr = function (nodes) {
|
|
28
|
+
let version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
|
29
|
+
let dataMode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
|
|
30
|
+
const header = [version, dataMode, nodes.length].map(v => v.toString(16).padStart(2, '0')).join('');
|
|
31
|
+
const body = nodes.map(node => [node.onOff, node.loops, node.step, node.hour, node.minute, node.brightness, node.temperature].map(v => v.toString(16).padStart(2, '0')).join('')).join('');
|
|
32
|
+
return `${header}${body}`;
|
|
33
|
+
};
|
|
34
|
+
describe('SleepSigmesh parser & formatter', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
it('returns default value when dp string is empty', () => {
|
|
39
|
+
expect(sleepParserSigmesh.parser('')).toEqual(new SleepSigmesh().defaultValue);
|
|
40
|
+
});
|
|
41
|
+
it('returns default value when dp string length invalid', () => {
|
|
42
|
+
// header + incomplete node data (not multiple of 14 after header)
|
|
43
|
+
|
|
44
|
+
expect(sleepParserSigmesh.parser('01020112345')).toEqual(new SleepSigmesh().defaultValue);
|
|
45
|
+
});
|
|
46
|
+
it('parses valid dp string into nodes', () => {
|
|
47
|
+
const nodes = [{
|
|
48
|
+
onOff: 1,
|
|
49
|
+
loops: loopsToHex('1000000'),
|
|
50
|
+
step: 3,
|
|
51
|
+
hour: 6,
|
|
52
|
+
minute: 30,
|
|
53
|
+
brightness: 80,
|
|
54
|
+
temperature: 90
|
|
55
|
+
}, {
|
|
56
|
+
onOff: 0,
|
|
57
|
+
loops: loopsToHex('0100000'),
|
|
58
|
+
step: 5,
|
|
59
|
+
hour: 7,
|
|
60
|
+
minute: 45,
|
|
61
|
+
brightness: 60,
|
|
62
|
+
temperature: 70
|
|
63
|
+
}];
|
|
64
|
+
const dpStr = buildDpStr(nodes, 1, 2);
|
|
65
|
+
const result = sleepParserSigmesh.parser(dpStr);
|
|
66
|
+
expect(result.version).toBe(1);
|
|
67
|
+
expect(result.dataMode).toBe(2);
|
|
68
|
+
expect(result.length).toBe(2);
|
|
69
|
+
expect(result.nodes).toHaveLength(2);
|
|
70
|
+
expect(result.nodes[0]).toMatchObject({
|
|
71
|
+
onOff: true,
|
|
72
|
+
loops: '1000000',
|
|
73
|
+
step: 3,
|
|
74
|
+
hour: 6,
|
|
75
|
+
minute: 30,
|
|
76
|
+
brightness: 80,
|
|
77
|
+
temperature: 90,
|
|
78
|
+
index: 0
|
|
79
|
+
});
|
|
80
|
+
expect(result.nodes[1]).toMatchObject({
|
|
81
|
+
onOff: false,
|
|
82
|
+
loops: '0100000',
|
|
83
|
+
step: 5,
|
|
84
|
+
hour: 7,
|
|
85
|
+
minute: 45,
|
|
86
|
+
brightness: 60,
|
|
87
|
+
temperature: 70,
|
|
88
|
+
index: 1
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
it('does NOT parse duration field (sleep has no duration)', () => {
|
|
92
|
+
const nodes = [{
|
|
93
|
+
onOff: 1,
|
|
94
|
+
loops: loopsToHex('1111111'),
|
|
95
|
+
step: 2,
|
|
96
|
+
hour: 22,
|
|
97
|
+
minute: 0,
|
|
98
|
+
brightness: 50,
|
|
99
|
+
temperature: 30
|
|
100
|
+
}];
|
|
101
|
+
const dpStr = buildDpStr(nodes, 1, 1);
|
|
102
|
+
const result = sleepParserSigmesh.parser(dpStr);
|
|
103
|
+
|
|
104
|
+
// The parsed node should NOT have a duration field
|
|
105
|
+
expect(result.nodes[0]).not.toHaveProperty('duration');
|
|
106
|
+
expect(result.nodes).toHaveLength(1);
|
|
107
|
+
});
|
|
108
|
+
it('formats nodes back to dp string', () => {
|
|
109
|
+
const result = sleepParserSigmesh.formatter({
|
|
110
|
+
version: 1,
|
|
111
|
+
dataMode: '03',
|
|
112
|
+
length: 1,
|
|
113
|
+
nodes: [{
|
|
114
|
+
onOff: true,
|
|
115
|
+
loops: '1000000',
|
|
116
|
+
step: 10,
|
|
117
|
+
hour: 5,
|
|
118
|
+
minute: 15,
|
|
119
|
+
brightness: 90,
|
|
120
|
+
temperature: 50,
|
|
121
|
+
index: 0
|
|
122
|
+
}]
|
|
123
|
+
});
|
|
124
|
+
expect(result).toBe(buildDpStr([{
|
|
125
|
+
onOff: 1,
|
|
126
|
+
loops: loopsToHex('1000000'),
|
|
127
|
+
step: 10,
|
|
128
|
+
hour: 5,
|
|
129
|
+
minute: 15,
|
|
130
|
+
brightness: 90,
|
|
131
|
+
temperature: 50
|
|
132
|
+
}], 1, 3));
|
|
133
|
+
});
|
|
134
|
+
it('round-trips dp string through parser and formatter', () => {
|
|
135
|
+
const nodes = [{
|
|
136
|
+
onOff: 1,
|
|
137
|
+
loops: loopsToHex('1111111'),
|
|
138
|
+
step: 2,
|
|
139
|
+
hour: 12,
|
|
140
|
+
minute: 0,
|
|
141
|
+
brightness: 100,
|
|
142
|
+
temperature: 40
|
|
143
|
+
}];
|
|
144
|
+
const dpStr = buildDpStr(nodes, 2, 3);
|
|
145
|
+
const parsed = sleepParserSigmesh.parser(dpStr);
|
|
146
|
+
const formatted = sleepParserSigmesh.formatter(parsed);
|
|
147
|
+
expect(formatted).toBe(dpStr);
|
|
148
|
+
});
|
|
149
|
+
it('handles multiple nodes correctly', () => {
|
|
150
|
+
const nodes = [{
|
|
151
|
+
onOff: 1,
|
|
152
|
+
loops: loopsToHex('1010101'),
|
|
153
|
+
step: 1,
|
|
154
|
+
hour: 21,
|
|
155
|
+
minute: 30,
|
|
156
|
+
brightness: 80,
|
|
157
|
+
temperature: 60
|
|
158
|
+
}, {
|
|
159
|
+
onOff: 1,
|
|
160
|
+
loops: loopsToHex('0101010'),
|
|
161
|
+
step: 4,
|
|
162
|
+
hour: 22,
|
|
163
|
+
minute: 0,
|
|
164
|
+
brightness: 40,
|
|
165
|
+
temperature: 20
|
|
166
|
+
}, {
|
|
167
|
+
onOff: 0,
|
|
168
|
+
loops: loopsToHex('0000000'),
|
|
169
|
+
step: 6,
|
|
170
|
+
hour: 23,
|
|
171
|
+
minute: 15,
|
|
172
|
+
brightness: 10,
|
|
173
|
+
temperature: 100
|
|
174
|
+
}];
|
|
175
|
+
const dpStr = buildDpStr(nodes, 1, 1);
|
|
176
|
+
const parsed = sleepParserSigmesh.parser(dpStr);
|
|
177
|
+
expect(parsed.nodes).toHaveLength(3);
|
|
178
|
+
expect(parsed.nodes[2].onOff).toBe(false);
|
|
179
|
+
expect(parsed.nodes[2].brightness).toBe(10);
|
|
180
|
+
const formatted = sleepParserSigmesh.formatter(parsed);
|
|
181
|
+
expect(formatted).toBe(dpStr);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TBrightAdjustData } from '../types';
|
|
2
|
+
export declare class BrightAdjustDataFormatter {
|
|
3
|
+
parser(dpValue: string): TBrightAdjustData;
|
|
4
|
+
formatter(data: TBrightAdjustData): string;
|
|
5
|
+
}
|
|
6
|
+
export declare const brightAdjustDataParser: {
|
|
7
|
+
parser: (dpValue: string) => TBrightAdjustData;
|
|
8
|
+
formatter: (data: TBrightAdjustData) => string;
|
|
9
|
+
};
|
|
10
|
+
export declare const getBrightAdjustDataParser: () => BrightAdjustDataFormatter;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import { scheduleLogger as ScheduleLogger } from '../utils/ScheduleLogger';
|
|
4
|
+
export class BrightAdjustDataFormatter {
|
|
5
|
+
parser(dpValue) {
|
|
6
|
+
try {
|
|
7
|
+
ScheduleLogger.debug('dpParser ===> BrightAdjustDataFormatter parser dpValue:', dpValue);
|
|
8
|
+
if (!dpValue || dpValue.length < 6) {
|
|
9
|
+
return {
|
|
10
|
+
mode: 0,
|
|
11
|
+
brightness: 0
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const mode = parseInt(dpValue.substring(0, 2), 16);
|
|
15
|
+
const brightness = parseInt(dpValue.substring(2, 6), 16);
|
|
16
|
+
return {
|
|
17
|
+
mode,
|
|
18
|
+
brightness
|
|
19
|
+
};
|
|
20
|
+
} catch (error) {
|
|
21
|
+
ScheduleLogger.error('dpParser ===> BrightAdjustDataFormatter parser error:', error);
|
|
22
|
+
return {
|
|
23
|
+
mode: 0,
|
|
24
|
+
brightness: 0
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
formatter(data) {
|
|
29
|
+
try {
|
|
30
|
+
ScheduleLogger.debug('dpParser ===> BrightAdjustDataFormatter formatter data:', data);
|
|
31
|
+
const modeStr = data.mode.toString(16).padStart(2, '0');
|
|
32
|
+
const brightnessStr = data.brightness.toString(16).padStart(4, '0');
|
|
33
|
+
return `${modeStr}${brightnessStr}`;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
ScheduleLogger.error('dpParser ===> BrightAdjustDataFormatter formatter error:', error);
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export const brightAdjustDataParser = new BrightAdjustDataFormatter();
|
|
41
|
+
export const getBrightAdjustDataParser = () => {
|
|
42
|
+
return brightAdjustDataParser;
|
|
43
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TColourDataRaw } from '../types';
|
|
2
|
+
export declare class ColourDataRawFormatter {
|
|
3
|
+
parser(dpValue: string): TColourDataRaw;
|
|
4
|
+
formatter(data: {
|
|
5
|
+
hue: number;
|
|
6
|
+
saturation: number;
|
|
7
|
+
value: number;
|
|
8
|
+
} | TColourDataRaw): string;
|
|
9
|
+
}
|
|
10
|
+
export declare const colourDataRawParser: {
|
|
11
|
+
parser: (dpValue: string) => TColourDataRaw;
|
|
12
|
+
formatter: (data: any) => string;
|
|
13
|
+
};
|
|
14
|
+
export declare const getColourDataRawParser: () => ColourDataRawFormatter;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { numToHexString } from '@ray-js/panel-sdk/lib/utils';
|
|
3
|
+
import { scheduleLogger as ScheduleLogger } from '../utils/ScheduleLogger';
|
|
4
|
+
const toHex = function (value) {
|
|
5
|
+
let len = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
|
|
6
|
+
return numToHexString(value).padStart(len, '0');
|
|
7
|
+
};
|
|
8
|
+
export class ColourDataRawFormatter {
|
|
9
|
+
parser(dpValue) {
|
|
10
|
+
try {
|
|
11
|
+
ScheduleLogger.debug('dpParser ===> ColourDataRawFormatter parser dpValue:', dpValue);
|
|
12
|
+
if (!dpValue || dpValue.length < 8) {
|
|
13
|
+
return {
|
|
14
|
+
hue: 0,
|
|
15
|
+
saturation: 1000,
|
|
16
|
+
value: 1000
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const hue = parseInt(dpValue.substring(0, 4), 16);
|
|
20
|
+
const saturation = parseInt(dpValue.substring(4, 6), 16) * 10;
|
|
21
|
+
const value = parseInt(dpValue.substring(6, 8), 16) * 10;
|
|
22
|
+
return {
|
|
23
|
+
hue,
|
|
24
|
+
saturation,
|
|
25
|
+
value
|
|
26
|
+
};
|
|
27
|
+
} catch (error) {
|
|
28
|
+
ScheduleLogger.error('dpParser ===> ColourDataRawFormatter parser error:', error);
|
|
29
|
+
return {
|
|
30
|
+
hue: 0,
|
|
31
|
+
saturation: 1000,
|
|
32
|
+
value: 1000
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
formatter(data) {
|
|
37
|
+
try {
|
|
38
|
+
ScheduleLogger.debug('dpParser ===> ColourDataRawFormatter formatter data:', data);
|
|
39
|
+
// 兼容两种数据格式: { hue, saturation, value } 或 { h, s, v }
|
|
40
|
+
const h = 'hue' in data ? data.hue : data.h;
|
|
41
|
+
const s = 'saturation' in data ? data.saturation : data.s;
|
|
42
|
+
const v = 'value' in data ? data.value : data.v;
|
|
43
|
+
const hStr = toHex(Math.floor(h), 4);
|
|
44
|
+
const sStr = toHex(Math.floor(s / 10), 2);
|
|
45
|
+
const vStr = toHex(Math.floor(v / 10), 2);
|
|
46
|
+
return `${hStr}${sStr}${vStr}`;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
ScheduleLogger.error('dpParser ===> ColourDataRawFormatter formatter error:', error);
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export const colourDataRawParser = new ColourDataRawFormatter();
|
|
54
|
+
export const getColourDataRawParser = () => {
|
|
55
|
+
return colourDataRawParser;
|
|
56
|
+
};
|
package/lib/dpParser/index.d.ts
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
import { scheduleDpCodes } from '../config/dpCodes';
|
|
3
3
|
export { randomParser, getRandomParser } from './random';
|
|
4
4
|
export { rhythmParser, getRhythmParser } from './rhythms';
|
|
5
|
-
export { sleepParserCommon as sleepParser, getSleepParser, SleepParser } from './sleep';
|
|
5
|
+
export { sleepParserCommon as sleepParser, getSleepParser, SleepParser, sleepParserSigmesh, } from './sleep';
|
|
6
6
|
export { rtcTimerParser, getRtcParser } from './rtcTimer';
|
|
7
7
|
export { cycleParser, getCycleParser } from './cycle';
|
|
8
8
|
export { wakeupParserCommon as wakeupParser, getWakeUpParser, WakeUpParser } from './wakeup';
|
|
9
9
|
export { timerReportParser } from './timerReport';
|
|
10
|
+
export { stripLocalTimerParser, getStripLocalTimerParser } from './stripLocalTimer';
|
|
11
|
+
export { brightAdjustDataParser, getBrightAdjustDataParser } from './brightAdjustData';
|
|
12
|
+
export { colourDataRawParser, getColourDataRawParser } from './colourDataRaw';
|
|
10
13
|
type DpCode = keyof typeof scheduleDpCodes;
|
|
11
14
|
/**
|
|
12
15
|
* 自动匹配 dp 解析函数
|