|
|
這篇做一個效果:每個房間的顏色反映它的溫度。
20 度是藍的、25 度是綠的、30 度是紅的,而且是連續漸變。

為什麼 Picture Elements 做不到
Picture Elements 的房間顏色是一張圖片。
要做「連續變化」的話,你得準備幾十張不同顏色的 PNG ——
不切實際。
SVG 的房間是一個可以隨時改顏色的圖形,
所以只要算出「這個溫度該是什麼顏色」就好。
做法:用 style_set 算顏色
- rules:
- - entities:
- - sensor.living_room_temperature
- elements:
- - room.living_room # SVG 裡房間的 id
- state_action:
- action: call-service
- service: floorplan.style_set
- service_data: >
- ${(() => {
- const t = parseFloat(entity.state);
- if (isNaN(t)) return 'fill: #E0E0E0; transition: fill 1.5s;';
- // 把 18~32 度對應到色相 210(藍) ~ 0(紅)
- const lo = 18, hi = 32;
- const p = Math.max(0, Math.min(1, (t - lo) / (hi - lo)));
- const hue = 210 - p * 210;
- return 'fill: hsl(' + hue.toFixed(0) + ', 65%, 72%);'
- + ' transition: fill 1.5s ease-in-out;';
- })()}
複製代碼
這段在做什麼

關鍵是用 HSL 而不是 RGB。
HSL = 色相(Hue)、飽和度(Saturation)、亮度(Lightness)
- 色相是一個 0-360 的圓環:
0 = 紅、120 = 綠、210 = 藍、240 = 深藍
- 飽和度:0% 是灰的、100% 是最鮮豔
- 亮度:0% 是黑、50% 是標準、100% 是白
為什麼 HSL 比 RGB 好用:
你只要改一個數字(色相),顏色就從藍變綠變紅。
用 RGB 的話要同時算三個數字,而且中間會經過很醜的顏色。
那段算式拆開來看:
- const p = (t - 18) / (32 - 18)
- // 18 度 → p = 0
- // 25 度 → p = 0.5
- // 32 度 → p = 1
- const hue = 210 - p * 210
- // p = 0 → hue = 210(藍)
- // p = 0.5 → hue = 105(綠)
- // p = 1 → hue = 0 (紅)
複製代碼
Math.max(0, Math.min(1, ...)) 是把 p 限制在 0-1 之間 ——
溫度超出範圍的話不會算出奇怪的顏色。
transition: fill 1.5s
這一行讓顏色是「慢慢變」的。
溫度感測器可能一分鐘才回報一次,
沒有 transition 的話顏色會一格一格跳,很不自然。
調整參數的建議

範圍(lo 和 hi)
- 台灣室內常見範圍:22-30 度
- 範圍太寬 → 顏色變化不明顯
- 範圍太窄 → 整年都是紅的或藍的
我的建議:夏天用 24-32、冬天用 16-26,
或者乾脆全年用 20-30。
飽和度和亮度
- 飽和度 65% —— 再高會太刺眼
- 亮度 72% —— 淺一點,讓上面的文字和圖示看得清楚
如果房間文字看不清楚,把亮度調到 80%。
同樣的技巧可以用在
濕度:
- // 40~80% 對應 淺黃 ~ 深藍
- const h = parseFloat(entity.state);
- const p = Math.max(0, Math.min(1, (h - 40) / 40));
- const hue = 50 + p * 160; // 50(黃) → 210(藍)
- return 'fill: hsl(' + hue + ', 60%, 75%); transition: fill 1.5s;';
複製代碼
空氣品質(CO2):
- // 400~1500 ppm 對應 綠 ~ 紅
- const c = parseFloat(entity.state);
- const p = Math.max(0, Math.min(1, (c - 400) / 1100));
- const hue = 120 - p * 120; // 120(綠) → 0(紅)
- return 'fill: hsl(' + hue + ', 60%, 72%); transition: fill 2s;';
複製代碼
用電量:
- // 0~2000W 對應 淺灰 ~ 橘
- const w = parseFloat(entity.state);
- const p = Math.max(0, Math.min(1, w / 2000));
- return 'fill: hsl(30, ' + (p * 70).toFixed(0) + '%, '
- + (92 - p * 25).toFixed(0) + '%); transition: fill 1s;';
複製代碼
這個是改飽和度和亮度,色相固定在橘色 ——
用電越多,橘色越濃。

進階:用一個開關切換顯示模式
做一個 input_select 讓使用者選「現在要看什麼」:
- input_select:
- floorplan_mode:
- name: 平面圖顯示模式
- options:
- - 燈光
- - 溫度
- - 濕度
- - 空氣品質
- initial: 燈光
複製代碼
然後規則裡加判斷:
- rules:
- - entities:
- - sensor.living_room_temperature
- - input_select.floorplan_mode
- elements:
- - room.living_room
- state_action:
- action: call-service
- service: floorplan.style_set
- service_data: >
- ${(() => {
- const mode = entities['input_select.floorplan_mode'].state;
- if (mode !== '溫度') return ''; // 不是溫度模式就不動
- const t = parseFloat(entities['sensor.living_room_temperature'].state);
- if (isNaN(t)) return 'fill: #E0E0E0;';
- const p = Math.max(0, Math.min(1, (t - 20) / 12));
- return 'fill: hsl(' + (210 - p * 210).toFixed(0)
- + ', 65%, 72%); transition: fill 1.5s;';
- })()}
複製代碼
注意:
- entities 要把 input_select 也列進去,
否則切換模式時規則不會被觸發
- 用 entities['實體ID'] 存取其他實體,
不是只能用 entity(那個是「觸發這條規則的實體」)
一個很實用的加分項:把數字也顯示出來
光看顏色不知道是幾度。在房間裡放一個文字元素:
- - entities:
- - sensor.living_room_temperature
- elements:
- - text.living_room_temp # SVG 裡文字元素的 id
- state_action:
- action: call-service
- service: floorplan.text_set
- service_data: '${parseFloat(entity.state).toFixed(1) + "°"}'
複製代碼
顏色告訴你「大概怎樣」,數字告訴你「確切多少」。
效能提醒
這類規則會在每次感測器回報時執行。
如果你有十個溫度感測器、每分鐘回報一次,
那就是一分鐘十次 JavaScript 執行 + DOM 更新。
實際上完全沒問題(瀏覽器處理這種量綽綽有餘),
但如果你的感測器設定成每秒回報,那就該去改回報間隔了。
在 Z2M 裡可以設定回報門檻(變化超過 0.5 度才回報),
這對電池壽命和效能都有幫助。
---
下一篇:SVG 平面圖的完整除錯指南。
把前面提到的所有問題整理成一份速查。 |
|