@yunlefun-home/air-conditioner-core 0.3.0 → 0.4.1
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/dist/index.cjs +513 -46
- package/dist/index.d.cts +301 -19
- package/dist/index.d.ts +301 -19
- package/dist/index.js +486 -47
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -33,7 +33,13 @@ const defaultAcState = {
|
|
|
33
33
|
mode: "cold",
|
|
34
34
|
status: false,
|
|
35
35
|
temperature: 26,
|
|
36
|
-
|
|
36
|
+
temperatures: {
|
|
37
|
+
cold: 26,
|
|
38
|
+
hot: 26
|
|
39
|
+
},
|
|
40
|
+
sound: "normal",
|
|
41
|
+
swing: false,
|
|
42
|
+
fan: "auto"
|
|
37
43
|
};
|
|
38
44
|
/**
|
|
39
45
|
* 遥控器「声音」键依次切换的顺序。
|
|
@@ -43,6 +49,13 @@ const acSoundModes = [
|
|
|
43
49
|
"sleep",
|
|
44
50
|
"mute"
|
|
45
51
|
];
|
|
52
|
+
/**
|
|
53
|
+
* 各模式的中文名称(遥控器小屏与按键提示共用)。
|
|
54
|
+
*/
|
|
55
|
+
const acModeLabels = {
|
|
56
|
+
cold: "制冷",
|
|
57
|
+
hot: "制热"
|
|
58
|
+
};
|
|
46
59
|
const acSoundModeLabels = {
|
|
47
60
|
normal: "标准",
|
|
48
61
|
sleep: "睡眠",
|
|
@@ -56,6 +69,48 @@ const acSoundVolumes = {
|
|
|
56
69
|
sleep: .3,
|
|
57
70
|
mute: 0
|
|
58
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* 遥控器「风速」键依次切换的顺序。
|
|
74
|
+
*/
|
|
75
|
+
const acFanSpeeds = [
|
|
76
|
+
"auto",
|
|
77
|
+
"low",
|
|
78
|
+
"medium",
|
|
79
|
+
"high"
|
|
80
|
+
];
|
|
81
|
+
const acFanSpeedLabels = {
|
|
82
|
+
auto: "自动",
|
|
83
|
+
low: "低风",
|
|
84
|
+
medium: "中风",
|
|
85
|
+
high: "高风"
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* 小屏上点亮的风速格数(1–3)。自动风速由空调自己决定,显示为 0 并另加「自动」标记。
|
|
89
|
+
*/
|
|
90
|
+
const acFanBars = {
|
|
91
|
+
auto: 0,
|
|
92
|
+
low: 1,
|
|
93
|
+
medium: 2,
|
|
94
|
+
high: 3
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* 固定风速下风声的相对音量(0–1);自动风速沿用开机后的「达标 / 发力」循环。
|
|
98
|
+
*/
|
|
99
|
+
const acFanSoundLevels = {
|
|
100
|
+
low: .45,
|
|
101
|
+
medium: .7,
|
|
102
|
+
high: 1
|
|
103
|
+
};
|
|
104
|
+
function isAcFanSpeed(value) {
|
|
105
|
+
return acFanSpeeds.includes(value);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 风速键的下一档:自动 → 低 → 中 → 高 → 自动。
|
|
109
|
+
*/
|
|
110
|
+
function nextAcFanSpeed(fan) {
|
|
111
|
+
const index = Math.max(acFanSpeeds.indexOf(fan), 0);
|
|
112
|
+
return acFanSpeeds[(index + 1) % acFanSpeeds.length];
|
|
113
|
+
}
|
|
59
114
|
function isAcSoundMode(value) {
|
|
60
115
|
return acSoundModes.includes(value);
|
|
61
116
|
}
|
|
@@ -127,6 +182,25 @@ function createDefaultAudioContext() {
|
|
|
127
182
|
return AudioContextConstructor ? new AudioContextConstructor() : null;
|
|
128
183
|
}
|
|
129
184
|
/**
|
|
185
|
+
* 合成一声很轻的「嗒」:音高快速下滑的正弦波,短促起音、指数衰减,像水珠落在窗台上。
|
|
186
|
+
*/
|
|
187
|
+
function playDrip(context, output) {
|
|
188
|
+
const start = context.currentTime;
|
|
189
|
+
const oscillator = context.createOscillator();
|
|
190
|
+
const gain = context.createGain();
|
|
191
|
+
oscillator.type = "sine";
|
|
192
|
+
oscillator.frequency.setValueAtTime(1500, start);
|
|
193
|
+
oscillator.frequency.exponentialRampToValueAtTime(520, start + .07);
|
|
194
|
+
gain.gain.setValueAtTime(1e-4, start);
|
|
195
|
+
gain.gain.exponentialRampToValueAtTime(.12, start + .004);
|
|
196
|
+
gain.gain.exponentialRampToValueAtTime(1e-4, start + .16);
|
|
197
|
+
oscillator.connect(gain);
|
|
198
|
+
gain.connect(output);
|
|
199
|
+
oscillator.start(start);
|
|
200
|
+
oscillator.stop(start + .18);
|
|
201
|
+
oscillator.onended = () => gain.disconnect();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
130
204
|
* 等功率淡入 / 淡出曲线,噪声类声音交叉淡化时中段不会变小。
|
|
131
205
|
*/
|
|
132
206
|
function fadeCurve(direction) {
|
|
@@ -165,6 +239,11 @@ function stopElement(element) {
|
|
|
165
239
|
function createAcAudioController(elements, options = {}) {
|
|
166
240
|
const random = options.random ?? Math.random;
|
|
167
241
|
let soundMode = options.soundMode ?? "normal";
|
|
242
|
+
let fanSpeed = options.fanSpeed ?? "auto";
|
|
243
|
+
/** 开机后风声循环已经接上(启动声结束) */
|
|
244
|
+
let fanRunning = false;
|
|
245
|
+
/** 每次重启「达标 / 发力」循环自增,丢弃旧循环的回调 */
|
|
246
|
+
let fanCycle = 0;
|
|
168
247
|
/** undefined:尚未尝试;null:不可用 */
|
|
169
248
|
let engine;
|
|
170
249
|
let run;
|
|
@@ -299,20 +378,32 @@ function createAcAudioController(elements, options = {}) {
|
|
|
299
378
|
gain.linearRampToValueAtTime(level, now + rampMs / 1e3);
|
|
300
379
|
} else tweenFallbackFan(level, rampMs);
|
|
301
380
|
}
|
|
381
|
+
/** 风声循环刚接上时的音量:自动风速从满音量开始,固定风速保持各自的音量。 */
|
|
382
|
+
function initialFanLevel() {
|
|
383
|
+
return fanSpeed === "auto" ? 1 : acFanSoundLevels[fanSpeed];
|
|
384
|
+
}
|
|
302
385
|
/**
|
|
303
|
-
*
|
|
386
|
+
* 自动风速:室温达标后风声变小;之后不定时随压缩机发力变大,再回落。
|
|
387
|
+
* 固定风速不走这个循环。
|
|
304
388
|
*/
|
|
305
389
|
function startFanCycle() {
|
|
390
|
+
fanRunning = true;
|
|
391
|
+
const cycle = ++fanCycle;
|
|
392
|
+
if (fanSpeed !== "auto") return;
|
|
393
|
+
const current = () => cycle === fanCycle && fanSpeed === "auto";
|
|
306
394
|
const quiet = () => {
|
|
307
395
|
later(() => {
|
|
396
|
+
if (!current()) return;
|
|
308
397
|
setFanLevel(soundTimings.surgeLevel, soundTimings.surgeRampMs);
|
|
309
398
|
later(() => {
|
|
399
|
+
if (!current()) return;
|
|
310
400
|
setFanLevel(soundTimings.settleLevel, soundTimings.surgeRampMs);
|
|
311
401
|
quiet();
|
|
312
402
|
}, randomBetween(soundTimings.surgeDurationMs, random));
|
|
313
403
|
}, randomBetween(soundTimings.quietDurationMs, random));
|
|
314
404
|
};
|
|
315
405
|
later(() => {
|
|
406
|
+
if (!current()) return;
|
|
316
407
|
setFanLevel(soundTimings.settleLevel, soundTimings.settleRampMs);
|
|
317
408
|
quiet();
|
|
318
409
|
}, soundTimings.settleAfterMs);
|
|
@@ -321,6 +412,7 @@ function createAcAudioController(elements, options = {}) {
|
|
|
321
412
|
const output = web.context.createGain();
|
|
322
413
|
output.connect(web.master);
|
|
323
414
|
const fan = web.context.createGain();
|
|
415
|
+
fan.gain.value = initialFanLevel();
|
|
324
416
|
fan.connect(output);
|
|
325
417
|
const target = {
|
|
326
418
|
output,
|
|
@@ -361,7 +453,7 @@ function createAcAudioController(elements, options = {}) {
|
|
|
361
453
|
applyElementVolume(work, 0);
|
|
362
454
|
if (work.loop !== void 0) work.loop = true;
|
|
363
455
|
playElement(work);
|
|
364
|
-
tweenFallbackFan(
|
|
456
|
+
tweenFallbackFan(initialFanLevel(), soundTimings.loopCrossfadeMs);
|
|
365
457
|
startFanCycle();
|
|
366
458
|
}, soundTimings.startDelayMs);
|
|
367
459
|
}
|
|
@@ -372,6 +464,8 @@ function createAcAudioController(elements, options = {}) {
|
|
|
372
464
|
}
|
|
373
465
|
function powerOn() {
|
|
374
466
|
const id = ++session;
|
|
467
|
+
fanRunning = false;
|
|
468
|
+
fanCycle++;
|
|
375
469
|
clearTimers();
|
|
376
470
|
cancelFallbackTween();
|
|
377
471
|
stopWeb(0);
|
|
@@ -390,6 +484,8 @@ function createAcAudioController(elements, options = {}) {
|
|
|
390
484
|
}
|
|
391
485
|
function powerOff() {
|
|
392
486
|
session++;
|
|
487
|
+
fanRunning = false;
|
|
488
|
+
fanCycle++;
|
|
393
489
|
clearTimers();
|
|
394
490
|
stopWeb(soundTimings.stopFadeMs);
|
|
395
491
|
stopFallback(soundTimings.stopFadeMs);
|
|
@@ -408,6 +504,13 @@ function createAcAudioController(elements, options = {}) {
|
|
|
408
504
|
applyElementVolume(elements.beep);
|
|
409
505
|
playElement(elements.beep);
|
|
410
506
|
},
|
|
507
|
+
drip() {
|
|
508
|
+
if (soundMode !== "normal") return;
|
|
509
|
+
const web = ensureEngine();
|
|
510
|
+
if (!web || typeof web.context.createOscillator !== "function") return;
|
|
511
|
+
resume(web);
|
|
512
|
+
playDrip(web.context, web.master);
|
|
513
|
+
},
|
|
411
514
|
toggleStatusAudio(previousStatus) {
|
|
412
515
|
if (previousStatus) powerOff();
|
|
413
516
|
else powerOn();
|
|
@@ -419,6 +522,21 @@ function createAcAudioController(elements, options = {}) {
|
|
|
419
522
|
applyElementVolume(elements.start);
|
|
420
523
|
applyElementVolume(elements.work, fallbackFanLevel);
|
|
421
524
|
},
|
|
525
|
+
setFanSpeed(fan) {
|
|
526
|
+
if (fan === fanSpeed) return;
|
|
527
|
+
fanSpeed = fan;
|
|
528
|
+
if (!fanRunning) {
|
|
529
|
+
if (run) run.fan.gain.value = initialFanLevel();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (fan === "auto") {
|
|
533
|
+
setFanLevel(1, soundTimings.loopCrossfadeMs);
|
|
534
|
+
startFanCycle();
|
|
535
|
+
} else {
|
|
536
|
+
fanCycle++;
|
|
537
|
+
setFanLevel(acFanSoundLevels[fan], soundTimings.loopCrossfadeMs);
|
|
538
|
+
}
|
|
539
|
+
},
|
|
422
540
|
dispose() {
|
|
423
541
|
session++;
|
|
424
542
|
clearTimers();
|
|
@@ -431,6 +549,54 @@ function createAcAudioController(elements, options = {}) {
|
|
|
431
549
|
};
|
|
432
550
|
}
|
|
433
551
|
//#endregion
|
|
552
|
+
//#region src/condensation.ts
|
|
553
|
+
/** 风速越大,雾气越浓、冷凝水越快 */
|
|
554
|
+
const fanFactor = {
|
|
555
|
+
auto: .8,
|
|
556
|
+
low: .55,
|
|
557
|
+
medium: .8,
|
|
558
|
+
high: 1
|
|
559
|
+
};
|
|
560
|
+
/** 高于这个设定温度不滴水:冷凝水不该常见,只在开得很冷时偶尔出现 */
|
|
561
|
+
const acDripMaxTemperature = 22;
|
|
562
|
+
/** 最冷(16 °C)与 {@link acDripMaxTemperature} 时,两滴水之间的间隔(ms) */
|
|
563
|
+
const acDripIntervalRange = [9e3, 18e3];
|
|
564
|
+
/**
|
|
565
|
+
* 出风口雾气的浓度(#70),0–1:只在制冷开机时出现,设定温度越低、风越大越浓。
|
|
566
|
+
*
|
|
567
|
+
* 视图把它写进 CSS 变量 `--acw-mist`,3D 等其他渲染器也可以按它控制粒子数量。
|
|
568
|
+
*/
|
|
569
|
+
function getAcMist(state) {
|
|
570
|
+
if (!state.status || state.mode !== "cold") return 0;
|
|
571
|
+
const cold = .2 + .8 * (31 - state.temperature) / 15;
|
|
572
|
+
const mist = Math.min(1, Math.max(.2, cold)) * fanFactor[state.fan ?? "auto"];
|
|
573
|
+
return Math.round(mist * 100) / 100;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* 冷凝水滴落的间隔(#21,ms):只在制冷开机、设定温度不高于 {@link acDripMaxTemperature} 时滴水,
|
|
577
|
+
* 越冷越频繁,高风再快一些;不滴水时返回 `undefined`。
|
|
578
|
+
*
|
|
579
|
+
* 视图把它写进 CSS 变量 `--acw-drip-cycle`,水珠在出风口边缘慢慢聚起再落下,开机后要等一整个间隔才滴第一滴。
|
|
580
|
+
*/
|
|
581
|
+
function getAcDripInterval(state) {
|
|
582
|
+
if (!state.status || state.mode !== "cold" || state.temperature > 22) return void 0;
|
|
583
|
+
const [fastest, slowest] = acDripIntervalRange;
|
|
584
|
+
const progress = (state.temperature - 16) / 6;
|
|
585
|
+
const interval = fastest + (slowest - fastest) * Math.min(1, Math.max(0, progress));
|
|
586
|
+
return Math.round(interval * (state.fan === "high" ? .8 : 1));
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* 视图要写到空调根元素上的雾气 / 滴水样式变量与属性。
|
|
590
|
+
*/
|
|
591
|
+
function getAcCondensationStyle(state) {
|
|
592
|
+
const interval = getAcDripInterval(state);
|
|
593
|
+
return {
|
|
594
|
+
mist: getAcMist(state),
|
|
595
|
+
dripCycle: interval === void 0 ? void 0 : `${interval}ms`,
|
|
596
|
+
drip: interval === void 0 ? "off" : "on"
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
434
600
|
//#region src/motion.ts
|
|
435
601
|
/**
|
|
436
602
|
* 动效 token:各框架视图层共用的时长与缓动曲线。
|
|
@@ -449,7 +615,9 @@ const acMotion = {
|
|
|
449
615
|
/** 导风板、出风淡入淡出 */
|
|
450
616
|
slow: 420,
|
|
451
617
|
/** 出风气流单次循环 */
|
|
452
|
-
flow: 1800
|
|
618
|
+
flow: 1800,
|
|
619
|
+
/** 扫风:左右摆动一个来回 */
|
|
620
|
+
swing: 5200
|
|
453
621
|
},
|
|
454
622
|
easing: {
|
|
455
623
|
/** 常规状态切换 */
|
|
@@ -471,7 +639,8 @@ const acReducedMotionDuration = {
|
|
|
471
639
|
fast: 80,
|
|
472
640
|
base: 120,
|
|
473
641
|
slow: 120,
|
|
474
|
-
flow: 0
|
|
642
|
+
flow: 0,
|
|
643
|
+
swing: 0
|
|
475
644
|
};
|
|
476
645
|
const acReducedMotionQuery = "(prefers-reduced-motion: reduce)";
|
|
477
646
|
/**
|
|
@@ -593,6 +762,124 @@ function observeAcTilt(target, onTilt, maxTilt = 4) {
|
|
|
593
762
|
};
|
|
594
763
|
}
|
|
595
764
|
//#endregion
|
|
765
|
+
//#region src/model.ts
|
|
766
|
+
const acForms = ["wall", "tower"];
|
|
767
|
+
const acFormLabels = {
|
|
768
|
+
wall: "挂机",
|
|
769
|
+
tower: "柜机"
|
|
770
|
+
};
|
|
771
|
+
/** 网页用的模型文件名(站点的 `public/models/` 下) */
|
|
772
|
+
const acModelFiles = {
|
|
773
|
+
wall: "CuteAC.glb",
|
|
774
|
+
tower: "CuteACTower.glb"
|
|
775
|
+
};
|
|
776
|
+
/** 渲染器要控制的命名节点,两种形态相同 */
|
|
777
|
+
const acModelParts = {
|
|
778
|
+
/** 导风板:挂机是一整块,柜机是一组百叶(子节点) */
|
|
779
|
+
louver: "Louver",
|
|
780
|
+
/** 0.25 × 0.1、UV 为 0–1 的平面,贴温度画面 */
|
|
781
|
+
display: "Display",
|
|
782
|
+
/** 指示灯 */
|
|
783
|
+
led: "Led"
|
|
784
|
+
};
|
|
785
|
+
const acLouverMotion = {
|
|
786
|
+
wall: {
|
|
787
|
+
pivot: "node",
|
|
788
|
+
openAngle: 40
|
|
789
|
+
},
|
|
790
|
+
tower: {
|
|
791
|
+
pivot: "children",
|
|
792
|
+
openAngle: 30
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
const acAirflowAnchors = {
|
|
796
|
+
wall: {
|
|
797
|
+
offset: [
|
|
798
|
+
0,
|
|
799
|
+
-.1,
|
|
800
|
+
.05
|
|
801
|
+
],
|
|
802
|
+
extent: 1.5,
|
|
803
|
+
direction: "down"
|
|
804
|
+
},
|
|
805
|
+
tower: {
|
|
806
|
+
offset: [
|
|
807
|
+
.24,
|
|
808
|
+
0,
|
|
809
|
+
.08
|
|
810
|
+
],
|
|
811
|
+
extent: .5,
|
|
812
|
+
direction: "side"
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
/** 指示灯与显示窗的颜色:开机时是模式色,关机是灰色(与 `ACPalette` 一致) */
|
|
816
|
+
const acModelColors = {
|
|
817
|
+
cold: "#5aa9ff",
|
|
818
|
+
hot: "#ff9f5a",
|
|
819
|
+
off: "#8e97aa"
|
|
820
|
+
};
|
|
821
|
+
function getAcLedColor(state) {
|
|
822
|
+
return state.status ? acModelColors[state.mode] : acModelColors.off;
|
|
823
|
+
}
|
|
824
|
+
function isAcForm(value) {
|
|
825
|
+
return value === "wall" || value === "tower";
|
|
826
|
+
}
|
|
827
|
+
//#endregion
|
|
828
|
+
//#region src/press.ts
|
|
829
|
+
/**
|
|
830
|
+
* 让按键支持长按连续触发,和真的遥控器一样:按下立即触发一次,按住一会儿后连续触发并逐渐加速,
|
|
831
|
+
* 松开、移出或取消时停止。键盘的 Enter / 空格仍走 `click`,每按一次触发一次。
|
|
832
|
+
*
|
|
833
|
+
* @param element 按键
|
|
834
|
+
* @param fire 每次触发时调用
|
|
835
|
+
* @param options 节奏与停止条件
|
|
836
|
+
* @returns 解除绑定的清理函数
|
|
837
|
+
*/
|
|
838
|
+
function bindAcPressRepeat(element, fire, options = {}) {
|
|
839
|
+
const { delay = 500, interval = 200, minInterval = 100, value } = options;
|
|
840
|
+
let timer;
|
|
841
|
+
let wait = interval;
|
|
842
|
+
let last;
|
|
843
|
+
const stop = () => {
|
|
844
|
+
clearTimeout(timer);
|
|
845
|
+
timer = void 0;
|
|
846
|
+
};
|
|
847
|
+
const tick = () => {
|
|
848
|
+
if (value && value() === last) return stop();
|
|
849
|
+
last = value?.();
|
|
850
|
+
fire();
|
|
851
|
+
wait = Math.max(minInterval, wait * .85);
|
|
852
|
+
timer = setTimeout(tick, wait);
|
|
853
|
+
};
|
|
854
|
+
const onPointerDown = (event) => {
|
|
855
|
+
if (event.button !== 0) return;
|
|
856
|
+
stop();
|
|
857
|
+
last = value?.();
|
|
858
|
+
fire();
|
|
859
|
+
wait = interval;
|
|
860
|
+
timer = setTimeout(tick, delay);
|
|
861
|
+
};
|
|
862
|
+
const onClick = (event) => {
|
|
863
|
+
if (event.detail === 0) fire();
|
|
864
|
+
};
|
|
865
|
+
const onContextMenu = (event) => event.preventDefault();
|
|
866
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
867
|
+
element.addEventListener("pointerup", stop);
|
|
868
|
+
element.addEventListener("pointerleave", stop);
|
|
869
|
+
element.addEventListener("pointercancel", stop);
|
|
870
|
+
element.addEventListener("click", onClick);
|
|
871
|
+
element.addEventListener("contextmenu", onContextMenu);
|
|
872
|
+
return () => {
|
|
873
|
+
stop();
|
|
874
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
875
|
+
element.removeEventListener("pointerup", stop);
|
|
876
|
+
element.removeEventListener("pointerleave", stop);
|
|
877
|
+
element.removeEventListener("pointercancel", stop);
|
|
878
|
+
element.removeEventListener("click", onClick);
|
|
879
|
+
element.removeEventListener("contextmenu", onContextMenu);
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
//#endregion
|
|
596
883
|
//#region src/reducer.ts
|
|
597
884
|
/**
|
|
598
885
|
* Merge a partial state over the defaults and clamp its temperature.
|
|
@@ -601,11 +888,38 @@ function observeAcTilt(target, onTilt, maxTilt = 4) {
|
|
|
601
888
|
* @param limits - Device limits; omit to keep the default 16–31 °C range
|
|
602
889
|
*/
|
|
603
890
|
function normalizeAcState(state, limits) {
|
|
891
|
+
const mode = state?.mode === "hot" ? "hot" : "cold";
|
|
892
|
+
const temperature = clampTemperature(state?.temperature ?? defaultAcState.temperature, limits);
|
|
893
|
+
const remembered = (other) => {
|
|
894
|
+
const value = state?.temperatures?.[other];
|
|
895
|
+
return typeof value === "number" && Number.isFinite(value) ? clampTemperature(value, limits) : temperature;
|
|
896
|
+
};
|
|
604
897
|
return {
|
|
605
898
|
...defaultAcState,
|
|
606
899
|
...state,
|
|
607
|
-
|
|
608
|
-
|
|
900
|
+
mode,
|
|
901
|
+
temperature,
|
|
902
|
+
temperatures: {
|
|
903
|
+
cold: mode === "cold" ? temperature : remembered("cold"),
|
|
904
|
+
hot: mode === "hot" ? temperature : remembered("hot")
|
|
905
|
+
},
|
|
906
|
+
sound: isAcSoundMode(state?.sound) ? state.sound : defaultAcState.sound,
|
|
907
|
+
swing: typeof state?.swing === "boolean" ? state.swing : defaultAcState.swing,
|
|
908
|
+
fan: isAcFanSpeed(state?.fan) ? state.fan : "auto"
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
/** Set the current mode's temperature, keeping `temperatures` in step. */
|
|
912
|
+
function withTemperature(state, temperature) {
|
|
913
|
+
const temperatures = {
|
|
914
|
+
cold: temperature,
|
|
915
|
+
hot: temperature,
|
|
916
|
+
...state.temperatures
|
|
917
|
+
};
|
|
918
|
+
temperatures[state.mode] = temperature;
|
|
919
|
+
return {
|
|
920
|
+
...state,
|
|
921
|
+
temperature,
|
|
922
|
+
temperatures
|
|
609
923
|
};
|
|
610
924
|
}
|
|
611
925
|
/**
|
|
@@ -631,14 +945,8 @@ function clampTemperature(temperature, limits) {
|
|
|
631
945
|
function acReducer(state, action, limits) {
|
|
632
946
|
const step = limits?.step ?? defaultAcLimits.step;
|
|
633
947
|
switch (action.type) {
|
|
634
|
-
case "increment": return
|
|
635
|
-
|
|
636
|
-
temperature: clampTemperature(state.temperature + step, limits)
|
|
637
|
-
};
|
|
638
|
-
case "decrement": return {
|
|
639
|
-
...state,
|
|
640
|
-
temperature: clampTemperature(state.temperature - step, limits)
|
|
641
|
-
};
|
|
948
|
+
case "increment": return withTemperature(state, clampTemperature(state.temperature + step, limits));
|
|
949
|
+
case "decrement": return withTemperature(state, clampTemperature(state.temperature - step, limits));
|
|
642
950
|
case "toggleStatus": return {
|
|
643
951
|
...state,
|
|
644
952
|
status: !state.status
|
|
@@ -647,10 +955,26 @@ function acReducer(state, action, limits) {
|
|
|
647
955
|
...state,
|
|
648
956
|
status: action.status
|
|
649
957
|
};
|
|
650
|
-
case "mode":
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
958
|
+
case "mode": {
|
|
959
|
+
const status = state.status || action.powerOn === true;
|
|
960
|
+
if (action.mode === state.mode) return status === state.status ? state : {
|
|
961
|
+
...state,
|
|
962
|
+
status
|
|
963
|
+
};
|
|
964
|
+
const temperatures = {
|
|
965
|
+
cold: state.temperature,
|
|
966
|
+
hot: state.temperature,
|
|
967
|
+
...state.temperatures,
|
|
968
|
+
[state.mode]: state.temperature
|
|
969
|
+
};
|
|
970
|
+
return {
|
|
971
|
+
...state,
|
|
972
|
+
status,
|
|
973
|
+
mode: action.mode,
|
|
974
|
+
temperature: clampTemperature(temperatures[action.mode] ?? state.temperature, limits),
|
|
975
|
+
temperatures
|
|
976
|
+
};
|
|
977
|
+
}
|
|
654
978
|
case "sound": return {
|
|
655
979
|
...state,
|
|
656
980
|
sound: isAcSoundMode(action.sound) ? action.sound : state.sound
|
|
@@ -659,6 +983,22 @@ function acReducer(state, action, limits) {
|
|
|
659
983
|
...state,
|
|
660
984
|
sound: nextAcSoundMode(state.sound)
|
|
661
985
|
};
|
|
986
|
+
case "toggleSwing": return {
|
|
987
|
+
...state,
|
|
988
|
+
swing: !state.swing
|
|
989
|
+
};
|
|
990
|
+
case "swing": return {
|
|
991
|
+
...state,
|
|
992
|
+
swing: action.swing === true
|
|
993
|
+
};
|
|
994
|
+
case "fan": return {
|
|
995
|
+
...state,
|
|
996
|
+
fan: isAcFanSpeed(action.fan) ? action.fan : state.fan
|
|
997
|
+
};
|
|
998
|
+
case "cycleFan": return {
|
|
999
|
+
...state,
|
|
1000
|
+
fan: nextAcFanSpeed(state.fan)
|
|
1001
|
+
};
|
|
662
1002
|
case "update": return normalizeAcState({
|
|
663
1003
|
...state,
|
|
664
1004
|
...action.payload
|
|
@@ -666,6 +1006,32 @@ function acReducer(state, action, limits) {
|
|
|
666
1006
|
default: throw new Error("Unexpected Ac Action");
|
|
667
1007
|
}
|
|
668
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* 节能建议的短文案(显示在小纸条上)与补充说明。
|
|
1011
|
+
*/
|
|
1012
|
+
function getEnergyTip(mode, temperature) {
|
|
1013
|
+
return mode === "cold" ? {
|
|
1014
|
+
message: `制冷调到 ${temperature}°C 以上更省电`,
|
|
1015
|
+
detail: "少开一度,为节能减排出一份力"
|
|
1016
|
+
} : {
|
|
1017
|
+
message: `制热调到 ${temperature}°C 以下更省电`,
|
|
1018
|
+
detail: "少开一度,为节能减排出一份力"
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* 创建一个提示「呈现器」:每个视图实例一个,记住提示过的节能建议,避免重复打扰。
|
|
1023
|
+
*/
|
|
1024
|
+
function createAcNotificationPresenter() {
|
|
1025
|
+
let lastTip;
|
|
1026
|
+
return (notification, state) => {
|
|
1027
|
+
if (notification.kind === "limit") return "shake";
|
|
1028
|
+
if (notification.kind === "setting") return "announce";
|
|
1029
|
+
const key = `${state.mode}:${state.temperature}`;
|
|
1030
|
+
if (key === lastTip) return "skip";
|
|
1031
|
+
lastTip = key;
|
|
1032
|
+
return "note";
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
669
1035
|
function getRecommendedTemperatureMessage(mode, temperature) {
|
|
670
1036
|
return `建议将空调的制${mode === "cold" ? "冷" : "热"}温度调至 ${temperature} 度以${mode === "cold" ? "上" : "下"},为节能减排贡献一份力量!`;
|
|
671
1037
|
}
|
|
@@ -676,6 +1042,12 @@ function getSoundModeMessage(sound) {
|
|
|
676
1042
|
mute: "空调不再发出任何声音"
|
|
677
1043
|
}[sound]}`;
|
|
678
1044
|
}
|
|
1045
|
+
function getFanSpeedMessage(fan) {
|
|
1046
|
+
return fan === "auto" ? "风速:自动,由空调根据室温调节" : `风速:${acFanSpeedLabels[fan]}`;
|
|
1047
|
+
}
|
|
1048
|
+
function getSwingMessage(swing) {
|
|
1049
|
+
return swing ? "扫风已开启:导风板上下摆动,出风左右扫" : "扫风已关闭:导风板停在当前位置";
|
|
1050
|
+
}
|
|
679
1051
|
/**
|
|
680
1052
|
* Visual tone for a state: `idle` when off, otherwise `cool` or `heat`.
|
|
681
1053
|
*
|
|
@@ -688,23 +1060,38 @@ function acTone(state) {
|
|
|
688
1060
|
function getAcActionNotification(previousState, nextState, action, limits = defaultAcLimits) {
|
|
689
1061
|
if (action.type === "increment" && previousState.temperature >= limits.max) return {
|
|
690
1062
|
message: "已经是最大温度啦!",
|
|
691
|
-
severity: "error"
|
|
1063
|
+
severity: "error",
|
|
1064
|
+
kind: "limit"
|
|
692
1065
|
};
|
|
693
1066
|
if (action.type === "decrement" && previousState.temperature <= limits.min) return {
|
|
694
1067
|
message: "已经是最小温度啦!",
|
|
695
|
-
severity: "error"
|
|
1068
|
+
severity: "error",
|
|
1069
|
+
kind: "limit"
|
|
696
1070
|
};
|
|
697
1071
|
if ((action.type === "sound" || action.type === "cycleSound") && nextState.sound !== previousState.sound) return {
|
|
698
1072
|
message: getSoundModeMessage(nextState.sound),
|
|
699
|
-
severity: "info"
|
|
1073
|
+
severity: "info",
|
|
1074
|
+
kind: "setting"
|
|
700
1075
|
};
|
|
701
|
-
if (action.type === "
|
|
702
|
-
message:
|
|
703
|
-
severity: "
|
|
1076
|
+
if ((action.type === "fan" || action.type === "cycleFan") && nextState.fan && nextState.fan !== previousState.fan) return {
|
|
1077
|
+
message: getFanSpeedMessage(nextState.fan),
|
|
1078
|
+
severity: "info",
|
|
1079
|
+
kind: "setting"
|
|
704
1080
|
};
|
|
705
|
-
if (action.type === "
|
|
706
|
-
message:
|
|
707
|
-
severity: "
|
|
1081
|
+
if ((action.type === "swing" || action.type === "toggleSwing") && nextState.swing !== previousState.swing) return {
|
|
1082
|
+
message: getSwingMessage(nextState.swing),
|
|
1083
|
+
severity: "info",
|
|
1084
|
+
kind: "setting"
|
|
1085
|
+
};
|
|
1086
|
+
if (action.type === "mode" && nextState.mode === "cold" && nextState.temperature < 26) return {
|
|
1087
|
+
...getEnergyTip("cold", 26),
|
|
1088
|
+
severity: "success",
|
|
1089
|
+
kind: "tip"
|
|
1090
|
+
};
|
|
1091
|
+
if (action.type === "mode" && nextState.mode === "hot" && nextState.temperature > 20) return {
|
|
1092
|
+
...getEnergyTip("hot", 20),
|
|
1093
|
+
severity: "success",
|
|
1094
|
+
kind: "tip"
|
|
708
1095
|
};
|
|
709
1096
|
}
|
|
710
1097
|
//#endregion
|
|
@@ -929,24 +1316,41 @@ function createComputePressureSource(options = {}) {
|
|
|
929
1316
|
};
|
|
930
1317
|
}
|
|
931
1318
|
//#endregion
|
|
932
|
-
//#region src/
|
|
1319
|
+
//#region src/icons.ts
|
|
933
1320
|
/**
|
|
934
|
-
*
|
|
935
|
-
*
|
|
936
|
-
*/
|
|
937
|
-
/**
|
|
938
|
-
* 遥控器按钮图标(24 × 24 viewBox 的 SVG path)。
|
|
1321
|
+
* 遥控器按钮与提示图标(24 × 24 viewBox 的 SVG path),全部来自 Material Design
|
|
1322
|
+
* Icons,和站点通过 UnoCSS presetIcons 使用的 i-mdi-* 是同一套。
|
|
939
1323
|
*/
|
|
940
1324
|
const acIconPaths = {
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1325
|
+
/** mdi:snowflake */
|
|
1326
|
+
cold: "m20.79 13.95l-2.33.62l-2-1.13v-2.88l2-1.13l2.33.62l.52-1.93l-1.77-.47l.46-1.77l-1.93-.52l-.62 2.33l-2 1.13L13 7.38V5.12l1.71-1.71L13.29 2L12 3.29L10.71 2L9.29 3.41L11 5.12v2.26L8.5 8.82l-2-1.13l-.58-2.33L4 5.88l.47 1.77l-1.77.47l.52 1.93l2.33-.62l2 1.13v2.89l-2 1.13l-2.33-.62l-.52 1.93l1.77.47L4 18.12l1.93.52l.62-2.33l2-1.13L11 16.62v2.26l-1.71 1.71L10.71 22L12 20.71L13.29 22l1.41-1.41l-1.7-1.71v-2.26l2.5-1.45l2 1.13l.62 2.33l1.88-.51l-.47-1.77l1.77-.47zM9.5 10.56L12 9.11l2.5 1.45v2.88L12 14.89l-2.5-1.45z",
|
|
1327
|
+
/** mdi:white-balance-sunny */
|
|
1328
|
+
hot: "m3.55 19.09l1.41 1.41l1.8-1.79l-1.42-1.42M12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6s6-2.69 6-6c0-3.32-2.69-6-6-6m8 7h3v-2h-3m-2.76 7.71l1.8 1.79l1.41-1.41l-1.79-1.8M20.45 5l-1.41-1.4l-1.8 1.79l1.42 1.42M13 1h-2v3h2M6.76 5.39L4.96 3.6L3.55 5l1.79 1.81zM1 13h3v-2H1m12 9h-2v3h2",
|
|
1329
|
+
/** mdi:power */
|
|
1330
|
+
power: "m16.56 5.44l-1.45 1.45A5.97 5.97 0 0 1 18 12a6 6 0 0 1-6 6a6 6 0 0 1-6-6c0-2.17 1.16-4.06 2.88-5.12L7.44 5.44A7.96 7.96 0 0 0 4 12a8 8 0 0 0 8 8a8 8 0 0 0 8-8c0-2.72-1.36-5.12-3.44-6.56M13 3h-2v10h2",
|
|
1331
|
+
/** mdi:plus */
|
|
1332
|
+
plus: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6z",
|
|
1333
|
+
/** mdi:minus */
|
|
1334
|
+
minus: "M19 13H5v-2h14z",
|
|
1335
|
+
/** mdi:menu-up */
|
|
1336
|
+
increase: "m7 15l5-5l5 5z",
|
|
1337
|
+
/** mdi:menu-down */
|
|
1338
|
+
decrease: "m7 10l5 5l5-5z",
|
|
1339
|
+
/** mdi:fan */
|
|
1340
|
+
fan: "M12 11a1 1 0 0 0-1 1a1 1 0 0 0 1 1a1 1 0 0 0 1-1a1 1 0 0 0-1-1m.5-9c4.5 0 4.61 3.57 2.25 4.75c-.99.49-1.43 1.54-1.62 2.47c.48.2.9.51 1.22.91c3.7-2 7.68-1.21 7.68 2.37c0 4.5-3.57 4.6-4.75 2.23c-.5-.99-1.56-1.43-2.49-1.62c-.2.48-.51.89-.91 1.23c1.99 3.69 1.2 7.66-2.38 7.66c-4.5 0-4.59-3.58-2.23-4.76c.98-.49 1.42-1.53 1.62-2.45c-.49-.2-.92-.52-1.24-.92C5.96 15.85 2 15.07 2 11.5C2 7 5.56 6.89 6.74 9.26c.5.99 1.55 1.42 2.48 1.61c.19-.48.51-.9.92-1.22C8.15 5.96 8.94 2 12.5 2",
|
|
1341
|
+
/** mdi:arrow-oscillating */
|
|
1342
|
+
swing: "M6 14h3l-4 4l-4-4h3c0-2.7 1.7-7.4 7-7.9v2c-3.4.5-5 3.8-5 5.9m14 0c0-2.7-1.7-7.4-7-7.9v2c3.4.6 5 3.8 5 5.9h-3l4 4l4-4z",
|
|
1343
|
+
/** mdi:volume-high */
|
|
1344
|
+
soundNormal: "M14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.84-5 6.7v2.07c4-.91 7-4.49 7-8.77s-3-7.86-7-8.77M16.5 12c0-1.77-1-3.29-2.5-4.03V16c1.5-.71 2.5-2.24 2.5-4M3 9v6h4l5 5V4L7 9z",
|
|
1345
|
+
/** mdi:weather-night */
|
|
1346
|
+
soundSleep: "m17.75 4.09l-2.53 1.94l.91 3.06l-2.63-1.81l-2.63 1.81l.91-3.06l-2.53-1.94L12.44 4l1.06-3l1.06 3zm3.5 6.91l-1.64 1.25l.59 1.98l-1.7-1.17l-1.7 1.17l.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85c-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14c.4-.4.82-.76 1.27-1.08c.75-.53 1.93.36 1.85 1.19c-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82c-2.81 3.14-2.7 7.96.31 10.98c3.02 3.01 7.84 3.12 10.98.31",
|
|
1347
|
+
/** mdi:volume-off */
|
|
1348
|
+
soundMute: "M12 4L9.91 6.09L12 8.18M4.27 3L3 4.27L7.73 9H3v6h4l5 5v-6.73l4.25 4.26c-.67.51-1.42.93-2.25 1.17v2.07c1.38-.32 2.63-.95 3.68-1.81L19.73 21L21 19.73l-9-9M19 12c0 .94-.2 1.82-.54 2.64l1.51 1.51A8.9 8.9 0 0 0 21 12c0-4.28-3-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71m-2.5 0c0-1.77-1-3.29-2.5-4.03v2.21l2.45 2.45c.05-.2.05-.42.05-.63",
|
|
1349
|
+
/** mdi:leaf */
|
|
1350
|
+
leaf: "M17 8C8 10 5.9 16.17 3.82 21.34l1.89.66l.95-2.3c.48.17.98.3 1.34.3C19 20 22 3 22 3c-1 2-8 2.25-13 3.25S2 11.5 2 13.5s1.75 3.75 1.75 3.75C7 8 17 8 17 8"
|
|
949
1351
|
};
|
|
1352
|
+
//#endregion
|
|
1353
|
+
//#region src/ui.ts
|
|
950
1354
|
/**
|
|
951
1355
|
* 声音键在各声音模式下显示的图标。
|
|
952
1356
|
*/
|
|
@@ -956,13 +1360,42 @@ const acSoundIcons = {
|
|
|
956
1360
|
mute: "soundMute"
|
|
957
1361
|
};
|
|
958
1362
|
/**
|
|
959
|
-
*
|
|
1363
|
+
* 遥控器小屏的读屏文字与各按键的悬停提示,保证各框架文案一致。
|
|
1364
|
+
*/
|
|
1365
|
+
function getAcRemoteText(state) {
|
|
1366
|
+
const swing = `扫风:${state.swing ? "开" : "关"}`;
|
|
1367
|
+
const sound = `声音:${acSoundModeLabels[state.sound]}`;
|
|
1368
|
+
const fan = `风速:${acFanSpeedLabels[state.fan ?? "auto"]}`;
|
|
1369
|
+
return {
|
|
1370
|
+
display: `${state.status ? "运行" : "待机"},${acModeLabels[state.mode]} ${state.temperature}°C,${fan},${swing},${sound}`,
|
|
1371
|
+
power: `电源:${state.status ? "开" : "关"}`,
|
|
1372
|
+
mode: acModeLabels,
|
|
1373
|
+
fan,
|
|
1374
|
+
swing,
|
|
1375
|
+
sound
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* 能效标签的点阵与等级条布局:蓝白背景的「中国能效标识」,带能效信息码
|
|
1380
|
+
* (二维码),样式依据《房间空气调节器能源效率标识实施规则》(CEL 010—2019)。
|
|
1381
|
+
*
|
|
1382
|
+
* 等级按现行 GB 21455-2019 分 5 级,1 级最高。GB 21455 的修订版征求意见稿
|
|
1383
|
+
* (2026 年 5 月)拟改为 3 级、以全年动态能源消耗效率(DAPF)分级;正式发布并
|
|
1384
|
+
* 实施后,再更新 `grades`、`standard` 与 `levels`。
|
|
1385
|
+
*
|
|
1386
|
+
* - `top`:标识名称(白点),右侧是能效信息码
|
|
1387
|
+
* - 等级区:首行是 1 级绿色条和指示箭头,`levels` 是 2–5 级
|
|
1388
|
+
* - `detail`:生产者、型号、APF 等参数(黑点)
|
|
1389
|
+
* - `footer`:依据的国家标准编号(白点)
|
|
960
1390
|
*/
|
|
961
1391
|
const acEnergyLabel = {
|
|
1392
|
+
grade: 1,
|
|
1393
|
+
grades: 5,
|
|
1394
|
+
standard: "GB 21455-2019",
|
|
962
1395
|
top: [{
|
|
963
|
-
num:
|
|
1396
|
+
num: 4,
|
|
964
1397
|
size: 4,
|
|
965
|
-
mx:
|
|
1398
|
+
mx: 1.5,
|
|
966
1399
|
my: 0,
|
|
967
1400
|
color: "white"
|
|
968
1401
|
}],
|
|
@@ -1002,7 +1435,7 @@ const acEnergyLabel = {
|
|
|
1002
1435
|
width: "50%"
|
|
1003
1436
|
},
|
|
1004
1437
|
{
|
|
1005
|
-
background: "#
|
|
1438
|
+
background: "#fdd835",
|
|
1006
1439
|
width: "60%"
|
|
1007
1440
|
},
|
|
1008
1441
|
{
|
|
@@ -1015,12 +1448,32 @@ const acEnergyLabel = {
|
|
|
1015
1448
|
}
|
|
1016
1449
|
]
|
|
1017
1450
|
};
|
|
1451
|
+
/**
|
|
1452
|
+
* 能效标签的读屏文字。
|
|
1453
|
+
*/
|
|
1454
|
+
function getAcEnergyLabelText() {
|
|
1455
|
+
return `中国能效标识:能效等级 ${acEnergyLabel.grade} 级(共 ${acEnergyLabel.grades} 级),依据 ${acEnergyLabel.standard}`;
|
|
1456
|
+
}
|
|
1018
1457
|
//#endregion
|
|
1458
|
+
exports.acAirflowAnchors = acAirflowAnchors;
|
|
1019
1459
|
exports.acAudioFileNames = acAudioFileNames;
|
|
1460
|
+
exports.acDripIntervalRange = acDripIntervalRange;
|
|
1461
|
+
exports.acDripMaxTemperature = acDripMaxTemperature;
|
|
1020
1462
|
exports.acEnergyLabel = acEnergyLabel;
|
|
1463
|
+
exports.acFanBars = acFanBars;
|
|
1464
|
+
exports.acFanSoundLevels = acFanSoundLevels;
|
|
1465
|
+
exports.acFanSpeedLabels = acFanSpeedLabels;
|
|
1466
|
+
exports.acFanSpeeds = acFanSpeeds;
|
|
1467
|
+
exports.acFormLabels = acFormLabels;
|
|
1468
|
+
exports.acForms = acForms;
|
|
1021
1469
|
exports.acIconPaths = acIconPaths;
|
|
1470
|
+
exports.acLouverMotion = acLouverMotion;
|
|
1022
1471
|
exports.acMaxTilt = acMaxTilt;
|
|
1023
1472
|
exports.acModeIcons = acModeIcons;
|
|
1473
|
+
exports.acModeLabels = acModeLabels;
|
|
1474
|
+
exports.acModelColors = acModelColors;
|
|
1475
|
+
exports.acModelFiles = acModelFiles;
|
|
1476
|
+
exports.acModelParts = acModelParts;
|
|
1024
1477
|
exports.acMotion = acMotion;
|
|
1025
1478
|
exports.acReducedMotionDuration = acReducedMotionDuration;
|
|
1026
1479
|
exports.acReducedMotionQuery = acReducedMotionQuery;
|
|
@@ -1032,9 +1485,11 @@ exports.acSoundModes = acSoundModes;
|
|
|
1032
1485
|
exports.acSoundVolumes = acSoundVolumes;
|
|
1033
1486
|
exports.acStorageKey = acStorageKey;
|
|
1034
1487
|
exports.acTone = acTone;
|
|
1488
|
+
exports.bindAcPressRepeat = bindAcPressRepeat;
|
|
1035
1489
|
exports.celsiusToHeatLevel = celsiusToHeatLevel;
|
|
1036
1490
|
exports.clampTemperature = clampTemperature;
|
|
1037
1491
|
exports.createAcAudioController = createAcAudioController;
|
|
1492
|
+
exports.createAcNotificationPresenter = createAcNotificationPresenter;
|
|
1038
1493
|
exports.createAirConditionerStore = createAirConditionerStore;
|
|
1039
1494
|
exports.createComputePressureSource = createComputePressureSource;
|
|
1040
1495
|
exports.createHeatGovernor = createHeatGovernor;
|
|
@@ -1043,16 +1498,28 @@ exports.defaultAcLimits = defaultAcLimits;
|
|
|
1043
1498
|
exports.defaultAcState = defaultAcState;
|
|
1044
1499
|
exports.defaultHeatThresholds = defaultHeatThresholds;
|
|
1045
1500
|
exports.getAcActionNotification = getAcActionNotification;
|
|
1501
|
+
exports.getAcCondensationStyle = getAcCondensationStyle;
|
|
1502
|
+
exports.getAcDripInterval = getAcDripInterval;
|
|
1503
|
+
exports.getAcEnergyLabelText = getAcEnergyLabelText;
|
|
1504
|
+
exports.getAcLedColor = getAcLedColor;
|
|
1505
|
+
exports.getAcMist = getAcMist;
|
|
1506
|
+
exports.getAcRemoteText = getAcRemoteText;
|
|
1046
1507
|
exports.getAcScale = getAcScale;
|
|
1047
1508
|
exports.getBrowserStorage = getBrowserStorage;
|
|
1509
|
+
exports.getEnergyTip = getEnergyTip;
|
|
1510
|
+
exports.getFanSpeedMessage = getFanSpeedMessage;
|
|
1048
1511
|
exports.getRecommendedTemperatureMessage = getRecommendedTemperatureMessage;
|
|
1049
1512
|
exports.getSoundModeMessage = getSoundModeMessage;
|
|
1513
|
+
exports.getSwingMessage = getSwingMessage;
|
|
1050
1514
|
exports.heatLevelRank = heatLevelRank;
|
|
1051
1515
|
exports.heatLevels = heatLevels;
|
|
1516
|
+
exports.isAcFanSpeed = isAcFanSpeed;
|
|
1517
|
+
exports.isAcForm = isAcForm;
|
|
1052
1518
|
exports.isAcSoundMode = isAcSoundMode;
|
|
1053
1519
|
exports.isHeatLevel = isHeatLevel;
|
|
1054
1520
|
exports.maxTemperature = maxTemperature;
|
|
1055
1521
|
exports.minTemperature = minTemperature;
|
|
1522
|
+
exports.nextAcFanSpeed = nextAcFanSpeed;
|
|
1056
1523
|
exports.nextAcSoundMode = nextAcSoundMode;
|
|
1057
1524
|
exports.normalizeAcState = normalizeAcState;
|
|
1058
1525
|
exports.observeAcScale = observeAcScale;
|