每次我收到水電費時,我都會感到驚訝——我的家人真的消耗這麼多嗎? 嗯,是的,浴室有加熱地板和鍋爐,但它們不會一直生火。 我們似乎也在節約用水(儘管我們也喜歡在浴室裡潑水)。 幾年前我已經
我最近改用家庭助理作為我的智慧家庭系統。 原因之一正是有機會組織大量資料的收集,並且能夠輕鬆地建立各種類型的圖表。
本文所描述的資訊並不新鮮;所有這些不同醬料下的東西都已經在網路上描述過。 但每篇文章通常只描述一種方法或面向。 我必須比較所有這些方法並自己選擇最合適的方法。 這篇文章仍然沒有提供有關數據收集的全面信息,而是對我如何做到這一點的一種總結。 因此,歡迎提出建設性批評和改進建議。
制定問題
因此,今天練習的目標是獲得漂亮的水和電消耗圖表:
- 每小時一次,持續 2 天
- 每日一次,持續 2 週
- (可選)每周和每月
這樣做有一些困難:
- 標準圖表組件通常很差。 充其量,您可以逐點建立折線圖。
如果您夠仔細,您可以找到擴展標準圖表功能的第三方組件。 對於家庭助理來說,原則上來說,這是一個又好又漂亮的組件
迷你圖卡 ,但也有一定的限制:- 很難在大間隔內設置條形圖的參數(條形的寬度以小時的小數形式設置,這意味著超過一小時的間隔將以小數形式設置)
- 您無法將不同的實體新增至一張圖表中(例如,溫度和濕度,或將長條圖與線條組合)
- 家庭助理不僅預設使用最原始的SQLite資料庫(而我這個勤雜工,無法安裝MySQL或Postgres),而且資料也不是以最優化的方式儲存的。 因此,例如,每次更改參數中的最小數字參數時,都會將大約 KB 大小的巨大 json 寫入資料庫
{"entity_id": "sensor.water_cold_hourly", "old_state": {"entity_id": "sensor.water_cold_hourly", "state": "3", "attributes": {"source": "sensor.water_meter_cold", "status": "collecting", "last_period": "29", "last_reset": "2020-02-23T21:00:00.022246+02:00", "meter_period": "hourly", "unit_of_measurement": "l", "friendly_name": "water_cold_hourly", "icon": "mdi:counter"}, "last_changed": "2020-02-23T19:05:06.897604+00:00", "last_updated": "2020-02-23T19:05:06.897604+00:00", "context": {"id": "aafc8ca305ba4e49ad4c97f0eddd8893", "parent_id": null, "user_id": null}}, "new_state": {"entity_id": "sensor.water_cold_hourly", "state": "4", "attributes": {"source": "sensor.water_meter_cold", "status": "collecting", "last_period": "29", "last_reset": "2020-02-23T21:00:00.022246+02:00", "meter_period": "hourly", "unit_of_measurement": "l", "friendly_name": "water_cold_hourly", "icon": "mdi:counter"}, "last_changed": "2020-02-23T19:11:11.251545+00:00", "last_updated": "2020-02-23T19:11:11.251545+00:00", "context": {"id": "0de64b8af6f14bb9a419dcf3b200ef56", "parent_id": null, "user_id": null}}}
我有相當多的感測器(每個房間的溫度感測器、水錶和電錶),有些還產生相當多的數據。 例如,光是SDM220電錶每10-15秒就會產生大約十幾個值,我想安裝大約8個這樣的電錶,還有一大堆參數是根據其他感測器計算出來的。 那。 所有這些值每天都很容易使資料庫膨脹 100-200 MB。 一週後系統幾乎不會移動,一個月後快閃磁碟機就會失效(在 Raspberry PI 上安裝典型家庭助理的情況下),並且儲存一整年的資料是不可能的。
- 如果幸運的話,您的電錶可以自行計算消耗量。 您可以隨時查看計價器,詢問累計消費值是幾點。 通常,所有具有數位介面(RS232/RS485/Modbus/Zigbee)的電錶都提供這種機會。
如果設備可以簡單地測量某些瞬時參數(例如瞬時功率或電流),或者只是每 X 瓦時或升產生脈衝,情況會更糟。 然後你需要考慮如何整合、與什麼整合以及在哪裡累積價值。 無論出於何種原因,都存在錯過下一份報告的風險,而且整個系統的準確性也受到質疑。 當然,您可以將所有這些委託給像家庭助理這樣的智慧家庭系統,但是沒有人取消關於資料庫中記錄數量的觀點,並且每秒輪詢感測器的次數不可能超過一次(家庭助理架構的限制)。
方法一
首先,讓我們看看家庭助理提供了哪些開箱即用的功能。 測量一段時間內的消耗量是一項非常受歡迎的功能。 當然,它很早以前就以專門的元件 - utility_meter 的形式實現了。
這個元件的本質是它在內部創建一個變數 current_accumulated_value 並在指定時間段(小時/週/月)後重設它。 元件本身會監控輸入變數(某些感測器的值),訂閱值的變化 - 您只需得到最終結果。 這個東西在設定檔裡只用幾行就描述了
utility_meter:
water_cold_hour_um:
source: sensor.water_meter_cold
cycle: hourly
water_cold_day_um:
source: sensor.water_meter_cold
cycle: daily
這裡的sensor.water_meter_cold是我收到的當前儀表值(以升為單位)
lovelace-UI 的每小時和每日圖表的代碼如下所示:
- type: history-graph
title: 'Hourly water consumption using vars'
hours_to_show: 48
entities:
- sensor.water_hour
- type: history-graph
title: 'Daily water consumption using vars'
hours_to_show: 360
entities:
- sensor.water_day
其實這個方法的問題就出在這個演算法上。 正如我已經提到的,對於每個輸入值(下一公升的當前儀表讀數),資料庫中都會產生 1kb 的記錄。 每個公用事業儀表也會產生一個新值,該值也會新增到基礎中。 如果我想收集每小時/每天/每週/每月的讀數,以及幾個立管的讀數,並添加一組電錶,這將是大量數據。 嗯,更準確地說,數據並不多,但是由於家庭助理將一堆不必要的資訊寫入資料庫,因此資料庫的大小會突飛猛進。 我什至不敢估計每周和每月圖表的底部大小。
此外,電錶本身並不能解決問題。 電錶產生的值的圖形是一個單調遞增函數,每小時重設為 0。 我們需要一個使用者可以理解的消耗圖表,顯示在此期間消耗了多少公升。 標準歷史圖組件無法做到這一點,但迷你圖卡外部組件可以幫助我們。
這是lovelace-UI 的卡片代碼:
- aggregate_func: max
entities:
- color: var(--primary-color)
entity: sensor.water_cold_hour_um
group_by: hour
hours_to_show: 48
name: "Hourly water consumption aggregated by utility meter"
points_per_hour: 1
show:
graph: bar
type: 'custom:mini-graph-card'
除了感測器名稱、圖形類型、顏色(我不喜歡標準橙色)等標準設定外,還需要注意 3 個設定:
- group_by:hour — 產生的圖表中的長條與小時的開始對齊
- point_per_hour: 1 - 每小時一柱
- 最重要的是,aggregate_func: max - 取每小時內的最大值。 正是這個參數將鋸齒圖變成了長條圖
不要注意左側的行列 - 這是沒有資料時元件的標準行為。 但沒有數據 - 我只是為了本文而在幾個小時前打開了公用事業儀表數據收集(我將在下面描述我當前的方法)。
在這張圖片中,我想表明有時數據顯示甚至可以工作,並且條形實際上反映了正確的值。 但這還不是全部。 由於某種原因,所選的上午 11 點到 12 點期間的列顯示為 19 公升,但在同一感測器的同一時間段的齒狀圖表上,我們看到消耗量為 62 公升。 要嘛是有bug,要嘛是手歪了。 但我還是不明白為什麼右邊的數據會中斷——那裡的消費是正常的,這也可以從牙齒圖上看出。
總的來說,我無法實現這種方法的合理性——圖表幾乎總是顯示出某種異端。
日間感測器的類似代碼。
- aggregate_func: max
entities:
- color: var(--primary-color)
entity: sensor.water_cold_day_um
group_by: interval
hours_to_show: 360
name: "Daily water consumption aggregated by utility meter"
points_per_hour: 0.0416666666
show:
graph: bar
type: 'custom:mini-graph-card'
請注意,group_by 參數設定為間隔,points_per_hour 參數決定一切。 該組件存在另一個問題 - point_per_hour 在一小時或更短的圖表上運行良好,但在較大的時間間隔上卻很糟糕。 因此,為了在一天內獲得一列,我必須輸入值 1/24=0.04166666。 我什至沒有談論每周和每月的圖表。
方法一
當我還在了解家庭助理時,我看到了這個影片:
一位朋友收集了幾種小米插座的消費數據。 他的任務稍微簡單一些 - 只需顯示今天、昨天和本月的消費值。 無需時間表。
我們先把關於手動積分瞬時功率值的討論放在一邊——我上面已經寫過關於這種方法的「準確性」的內容。 目前尚不清楚他為何不使用累積消費值,這些消費值已經由同一家商店收集。 我認為,硬體內部的整合效果會更好。
從影片中,我們將採用手動計算一段時間內的消耗量的想法。 這傢伙只計算了今天和昨天的值,但我們會更進一步並嘗試繪製圖表。 在我的案例中,所提出的方法的本質如下。
讓我們建立一個變數 value_at_the_beginning_of_hour,我們將在其中記錄當前的儀表讀數
使用計時器,在一小時結束時(或下一小時開始時),我們計算當前讀數與一小時開始時儲存的讀數之間的差異。 這個差異將是當前小時的消耗量 - 我們將把該值保存到感測器中,將來我們將根據該值建立一個圖表。
您還需要透過寫入目前計數器值來「重設」 value_at_beginning_of_hour 變數。
所有這些都可以透過家庭助理本身來完成。
與之前的方法相比,您將需要編寫更多的程式碼。 首先,讓我們建立這些相同的「變數」。 我們沒有開箱即用的「變數」實體,但我們可以使用 mqtt 代理的服務。 我們將使用保留 = true 標誌向那裡發送值 - 這會將值保存在代理內,並且可以隨時將其從那裡取出,即使家庭助理重新啟動也是如此。 我同時製作了每小時和每日計數器。
- platform: mqtt
state_topic: "test/water/hour"
name: water_hour
unit_of_measurement: l
- platform: mqtt
state_topic: "test/water/hour_begin"
name: water_hour_begin
unit_of_measurement: l
- platform: mqtt
state_topic: "test/water/day"
name: water_day
unit_of_measurement: l
- platform: mqtt
state_topic: "test/water/day_begin"
name: water_day_begin
unit_of_measurement: l
所有的魔力都發生在自動化中,它分別每小時和每晚運行。
- id: water_new_hour
alias: water_new_hour
initial_state: true
trigger:
- platform: time_pattern
minutes: 0
action:
- service: mqtt.publish
data:
topic: "test/water/hour"
payload_template: >
{{ (states.sensor.water_meter_cold.state|int) - (states.sensor.water_hour_begin.state|int) }}
retain: true
- service: mqtt.publish
data:
topic: "test/water/hour_begin"
payload_template: >
{{ states.sensor.water_meter_cold.state }}
retain: true
- id: water_new_day
alias: water_new_day
initial_state: true
trigger:
- platform: time
at: "00:00:00"
action:
- service: mqtt.publish
data:
topic: "test/water/day"
payload_template: >
{{ (states.sensor.water_meter_cold.state|int) - (states.sensor.water_day_begin.state|int) }}
retain: true
- service: mqtt.publish
data:
topic: "test/water/day_begin"
payload_template: >
{{ states.sensor.water_meter_cold.state }}
retain: true
兩個自動化執行 2 個操作:
- 計算間隔的值作為開始值和結束值之間的差值
- 更新下一個間隔的基底值
這種情況下圖的構造是透過通常的歷史圖來解決的:
- type: history-graph
title: 'Hourly water consumption using vars'
hours_to_show: 48
entities:
- sensor.water_hour
- type: history-graph
title: 'Daily water consumption using vars'
hours_to_show: 360
entities:
- sensor.water_day
它看起來像這樣:
原則上,這已經是所需要的了。 這種方法的優點是每個時間間隔產生一次資料。 那些。 每小時圖表每天只有 24 筆記錄。
不幸的是,這仍然不能解決基數不斷增長的普遍問題。 如果我想要每月的消費圖表,我將必須儲存至少一年的資料。 而且由於家庭助理只為整個資料庫提供一個儲存期限設置,這意味著系統中的所有資料都必須儲存一整年。 例如,我一年消耗 200 立方公尺的水,這意味著資料庫中有 200000 個條目。 如果考慮到其他感測器,那麼這個數字通常會變得不雅。
方法一
幸運的是,聰明人已經透過編寫 InfluxDB 資料庫解決了這個問題。 該資料庫專門針對儲存基於時間的資料進行了最佳化,非常適合儲存不同感測器的值。 系統還提供了類似SQL的查詢語言,讓您可以從資料庫中提取值,然後以各種方式聚合它們。 最後,不同的資料可以儲存不同的時間。 例如,溫度或濕度等經常變化的讀數只能儲存幾週,而每日耗水量讀數則可以儲存一整年。
除了InfluxDB之外,聰明人還發明了Grafana,一個基於InfluxDB資料繪製圖表的系統。 Grafana 可以繪製不同類型的圖表,對其進行詳細定制,最重要的是,這些圖表可以「插入」到 lovelace-UI 家庭助手上。
得到啟發
因此,首先,讓我們開始在 influxDB 中加入計數器值。 家庭助理配置的一部分(在這個例子中,我不僅可以使用冷水,還可以使用熱水):
influxdb:
host: localhost
max_retries: 3
default_measurement: state
database: homeassistant
include:
entities:
- sensor.water_meter_hot
- sensor.water_meter_cold
讓我們禁止將相同的資料保存到內部家庭助理資料庫中,以免再次使其膨脹:
recorder:
purge_keep_days: 10
purge_interval: 1
exclude:
entities:
- sensor.water_meter_hot
- sensor.water_meter_cold
現在讓我們轉到 InfluxDB 控制台並配置我們的資料庫。 特別是,您需要配置某些資料的儲存時間。 這是由所謂的規定的。 保留策略 - 這類似於主資料庫中的資料庫,每個內部資料庫都有自己的設定。 預設情況下,所有資料都儲存在名為 autogen 的保留策略中;該資料將儲存一週。 我希望每小時資料保留一個月,每週資料保留一年,每月資料永遠不會被刪除。 讓我們建立適當的保留策略
CREATE RETENTION POLICY "month" ON "homeassistant" DURATION 30d REPLICATION 1
CREATE RETENTION POLICY "year" ON "homeassistant" DURATION 52w REPLICATION 1
CREATE RETENTION POLICY "infinite" ON "homeassistant" DURATION INF REPLICATION 1
事實上,現在的主要技巧是使用連續查詢進行資料聚合。 這是一種按指定時間間隔自動執行查詢、聚合該查詢的資料並將結果新增至新值的機制。 讓我們看一個例子(為了方便閱讀,我寫在一列中,但實際上我必須在一行中輸入此命令)
CREATE CONTINUOUS QUERY cq_water_hourly ON homeassistant
BEGIN
SELECT max(value) AS value
INTO homeassistant.month.water_meter_hour
FROM homeassistant.autogen.l
GROUP BY time(1h), entity_id fill(previous)
END
這個命令:
- 在 homeassistant 資料庫中建立名為 cq_water_cold_hourly 的連續查詢
- 該請求將每小時執行一次 (time(1h))
- 該請求將從測量'homeassistant.autogen.l(公升)中抓取所有數據,包括冷水和熱水讀數
- 聚合資料將按entity_id分組,這將為我們提供冷水和熱水的單獨值
- 由於升計數器是每小時內單調遞增的序列,因此需要取最大值,因此將透過函數 max(value) 進行聚合
- 新值將寫入 homeassistant.month.water_meter_hour,其中 Month 是保留期為 XNUMX 個月的保留策略的名稱。 此外,冷水和熱水的資料將分散到單獨的記錄中,並在value欄位中具有相應的entity_id和value
在夜間或無人在家時,沒有水消耗,因此 homeassistant.autogen.l 中沒有新條目。 為了避免常規查詢中缺失值,可以使用 fill(previous)。 這將強制 InfluxDB 使用最後一小時的值。
不幸的是,連續查詢有一個特點:填充(前一個)技巧不起作用,並且根本不會創建記錄。 而且,這是一個無法克服的問題
讓我們檢查一下發生了什麼(當然,你需要等待幾個小時):
> select * from homeassistant.month.water_meter_hour group by entity_id
...
name: water_meter_hour
tags: entity_id=water_meter_cold
time value
---- -----
...
2020-03-08T01:00:00Z 370511
2020-03-08T02:00:00Z 370513
2020-03-08T05:00:00Z 370527
2020-03-08T06:00:00Z 370605
2020-03-08T07:00:00Z 370635
2020-03-08T08:00:00Z 370699
2020-03-08T09:00:00Z 370761
2020-03-08T10:00:00Z 370767
2020-03-08T11:00:00Z 370810
2020-03-08T12:00:00Z 370818
2020-03-08T13:00:00Z 370827
2020-03-08T14:00:00Z 370849
2020-03-08T15:00:00Z 370921
請注意,資料庫中的值以 UTC 存儲,因此此列表相差 3 小時 - InfluxDB 輸出中的上午 7 點值對應於上圖中上午 10 點的值。 另請注意,凌晨 2 點到 5 點之間根本沒有記錄 - 這與連續查詢的特徵相同。
如您所看到的,聚合值也是單調遞增的序列,只是條目出現的頻率較低 - 每小時一次。 但這不是問題 - 我們可以編寫另一個查詢來檢索圖表的正確資料。
SELECT difference(max(value))
FROM homeassistant.month.water_meter_hour
WHERE entity_id='water_meter_cold' and time >= now() -24h
GROUP BY time(1h), entity_id
fill(previous)
我來破解:
- 我們將從 homeassistant.month.water_meter_hour 資料庫中提取實體_id='water_meter_cold' 最後一天(時間 >= now() -24h)的資料。
- 正如我已經提到的,homeassistant.month.water_meter_hour 序列中可能缺少一些條目。 我們將透過使用 GROUP BY time(1h) 執行查詢來重新產生此資料。 這次 fill(previous) 將如預期般運作,產生遺失的資料(該函數將採用先前的值)
- 這個請求中最重要的是差異函數,它將計算小時標記之間的差異。 它本身不能工作,需要聚合函數。 讓它成為之前使用的 max() 。
執行結果是這樣的
name: water_meter_hour
tags: entity_id=water_meter_cold
time difference
---- ----------
...
2020-03-08T02:00:00Z 2
2020-03-08T03:00:00Z 0
2020-03-08T04:00:00Z 0
2020-03-08T05:00:00Z 14
2020-03-08T06:00:00Z 78
2020-03-08T07:00:00Z 30
2020-03-08T08:00:00Z 64
2020-03-08T09:00:00Z 62
2020-03-08T10:00:00Z 6
2020-03-08T11:00:00Z 43
2020-03-08T12:00:00Z 8
2020-03-08T13:00:00Z 9
2020-03-08T14:00:00Z 22
2020-03-08T15:00:00Z 72
凌晨 2 點至 5 點(UTC)沒有任何消費。 儘管如此,由於 fill(previous),查詢將傳回相同的消耗值,而差異函數將從自身中減去該值,並且輸出將為 0,這正是所需要的。
剩下的就是建立一個圖表。 為此,請開啟 Grafana,開啟一些現有(或建立新的)儀表板,然後建立新面板。 圖表設定將是這樣的。
我將在同一張圖表上顯示冷水和熱水數據。 該請求與我上面描述的完全相同。
顯示參數設定如下。 對我來說,這將是一個帶有線條的圖表,呈階梯狀(樓梯)。 我將在下面解釋 Stack 參數。 下面還有幾個顯示選項,但它們並不是那麼有趣。
要將產生的圖表新增至家庭助理,您需要:
- 退出圖表編輯模式。 由於某種原因,僅從儀表板頁面提供正確的圖表共享設置
- 點擊圖表名稱旁邊的三角形,然後從選單中選擇共享
- 在打開的視窗中,轉到嵌入選項卡
- 取消選取目前時間範圍 - 我們將透過 URL 設定時間範圍
- 選擇所需的主題。 就我而言,它很輕
- 將產生的 URL 複製到 lovelace-UI 設定卡
- type: iframe
id: graf_water_hourly
url: "http://192.168.10.200:3000/d-solo/rZARemQWk/water?orgId=1&panelId=2&from=now-2d&to=now&theme=light"
請注意,時間範圍(最近 2 天)是在此處設定的,而不是在儀表板設定中設定的。
該圖看起來像這樣。 最近兩天沒有使用熱水,所以只畫了冷水圖。
我還沒有自己決定我比較喜歡哪種圖表,是線步圖還是真實長條圖。 因此,我將簡單地給出一個每日消耗圖表的例子,只是這次是在長條圖上。 查詢的建構與上述類似。 顯示選項有:
該圖如下所示:
關於 Stack 參數。 在此圖中,冷水柱繪製在熱水柱的頂部。 總高度對應於該時段的冷水和熱水的總消耗量。
顯示的所有圖表都是動態的。 您可以將滑鼠懸停在感興趣的點上,查看特定點的詳細資訊和價值。
不幸的是,有一些美中不足。 在長條圖上(與有階梯線的圖表不同),長條圖的中間不是在一天的中間,而是在 00:00。 那些。 該欄的左半部是在前一天的位置繪製的。 因此,週六和週日的圖表稍微繪製在藍色區域的左側。 直到我弄清楚如何打敗它。
另一個問題是無法按月正常工作。 事實上,小時/天/週的長度是固定的,但月份的長度每次都不同。 InfluxDB只能等間隔工作。 到目前為止我的腦子已經足夠設定30天的固定間隔了。 是的,圖表在一年中會稍微浮動,且長條圖不會與月份完全對應。 但由於我對這個東西只是作為一個顯示儀表感興趣,所以我同意它。
我看到至少有兩種解決方案:
- 放棄月度圖表並限制自己只專注於週度圖表。 今年 52 條週線看起來相當不錯
- 將每月消費本身視為方法 2,並且僅使用 grafana 來獲得漂亮的圖表。 這將是一個非常準確的解決方案。 您甚至可以疊加過去一年的圖表進行比較 - grafana 也可以做到這一點。
結論
我不知道為什麼,但我對這類圖表著迷。 它們顯示生活如火如荼,一切都在變化。 昨天很多,今天很少,明天又會是別的。 剩下的就是與家庭成員就消費話題合作。 但即使按照目前的胃口,只是付款單上一個大而難以理解的數字已經變成了相當可以理解的消費圖景。
儘管我有近 20 年的程式設計師職業生涯,但我幾乎沒有接觸過資料庫。 因此,安裝外部資料庫似乎是一件深奧難懂的事。 改變了一切
在標題中我提到了電力消耗。 不幸的是,目前我無法提供任何圖表。 一台 SDM120 儀表對我來說已經死了,另一台在透過 Modbus 存取時出現故障。 然而,這不會以任何方式影響本文的主題 - 圖表將以與水相同的方式構建。
在這篇文章中,我介紹了我自己嘗試過的方法。 當然還有一些我不知道的其他組織資料收集和視覺化的方法。 在評論中告訴我,我會很感興趣。 我很樂意接受建設性的批評和新想法。 我希望所提供的材料也能對某人有所幫助。
來源: www.habr.com