我將Home Assistant安裝VMware建構的虛擬機器上,而VMware則是安裝在一台運行Debian 12作業系統的伺服器上,為了方便監看Debain的系統資訊,我把它們整合進Home Assistant的sensor中。
過去是透過Home Assistant的automation與shell_command,經由ssh連線去獲取需要的系統資訊存成txt或json檔,以每三分鐘抓一次lm-sensors的內容為例,automation與shell_command分別為
automation:
- alias: Information:Get_Debian_Temperature
trigger:
- platform: time_pattern
minutes: "/3"
action:
- service: shell_command.get_debian_temperature
mode: queued
shell_command:
get_debian_temperature: "ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no -q user@192.168.xx.yy sensors > /config/data/DebianTemp.txt"
(缺點1:要弄ssh key)
再經由command_line sensor去處理這些檔案擷取所需要的資訊,以Core0的溫度為例,command_line sensor為
command_line:
- sensor:
name: DEBIAN_Core0_Temperature
command: "cat /config/data/DebianTemp.txt | grep 'Core 0' | cut -c17-20"
unit_of_measurement: "°C"
(缺點2:cut -c17-20實在不是一個很好的做法)
除非透過automation監聽DebianTemp.txt的最後修改時間來觸發command_line sensor的更新,automation如下
automation:
- alias: Information:Update_Debian_Temp
trigger:
- platform: state
entity_id: sensor.debiantemp_txt_last_updated
action:
- service: homeassistant.update_entity
data: {}
target:
entity_id:
- sensor.debian_core0_temperature
mode: queued
不然就只能被動地靠command_line sensor的scan_interval來更新sensor的值,但這樣子其實相當的被動,且「抓到資訊」與「更新資訊」的時間會有落差。除此之外還要維護一連串的實體,平常沒事就沒事,如果過了幾個月要修改或是新增,可能已經忘了當初是怎麼寫的,改起來就會落東落西的。
(缺點3:時間差和維護問題)
後來看到MQTT sensor,MQTT是Message Queuing Telemetry Transport的縮寫,是一種基於「發佈」和「訂閱」的通訊協議。它專為物聯網(IoT)應用而設計,具有簡單、開放、輕量級和易於實現的特點。
- 溝通模式:MQTT需要一個MQTT伺服器,連到這個伺服器的客戶端可以區分為「發佈者」和「訂閱者」。發佈者將消息發送到特定的主題(topic),而訂閱者則通過訂閱這些主題來接收訊息(message)。
- 輕量級和低頻寬消耗:MQTT協議非常輕量,具有低頻寬消耗的特點。這使得它非常適合在資源有限的設備上運行,如嵌入式系統和物聯網設備。
- 可靠的消息傳遞:MQTT支援三種消息傳送級別,從最多一次傳遞到僅一次傳遞。這使得它能夠適應不同的應用環境,並確保消息的可靠傳遞。
- 支援SSL/TLS加密:MQTT可以通過SSL/TLS協議進行加密通訊,保護資料的安全性,這使得它更適用於需要安全通訊的環境。
回到原來的題目,Home Assistant要怎麼透過MQTT sensor來擷取lm-sensors裡的資訊當sensor的值?
先要架設一個MQTT伺服器,最簡單方便的就是Home Assistant附加元件中的Mosquitto broker。
再來我是透過python將lm-sensors的資訊擷取出來並透過MQTT發佈到MQTT伺服器上,paho-mqtt是python的MQTT套件,先把它裝起來
$ pip install paho-mqtt
lm-sensors的內容如下,我要抓出Core 0到Core 5的值
coretemp-isa-0000
Adapter: ISA adapter
Package id 0: +28.0°C (high = +80.0°C, crit = +100.0°C)
Core 0: +28.0°C (high = +80.0°C, crit = +100.0°C)
Core 1: +28.0°C (high = +80.0°C, crit = +100.0°C)
Core 2: +25.0°C (high = +80.0°C, crit = +100.0°C)
Core 3: +26.0°C (high = +80.0°C, crit = +100.0°C)
Core 4: +27.0°C (high = +80.0°C, crit = +100.0°C)
Core 5: +29.0°C (high = +80.0°C, crit = +100.0°C)
acpitz-acpi-0
Adapter: ACPI interface
temp1: +27.8°C (crit = +105.0°C)
iwlwifi_1-virtual-0
Adapter: Virtual device
temp1: +28.0°C
nvme-pci-0100
Adapter: PCI adapter
Composite: +50.9°C (low = -0.1°C, high = +79.8°C)
(crit = +81.8°C)
Sensor 1: +50.9°C (low = -273.1°C, high = +65261.8°C)
撰寫一個名為sensors.py的python script,透過subprocess套件下指令,再找出所有符合Core*: +*.*°C的字串並記錄在core_temps列表裡,最後轉成json再透過MQTT以debian/core_temperature這個「主題」發佈到伺服器上。
import json
import re
import subprocess
import paho.mqtt.client as mqtt
import logging
logging.basicConfig(filename='error.log', level=logging.ERROR)
broker_address = "mqtt_server_ip"
broker_port = 1883
username = "mqtt_user"
password = "mqtt_password"
def connect_mqtt():
client = mqtt.Client()
client.username_pw_set(username, password)
client.connect(broker_address, broker_port)
return client
def publish_message(client, topic, data):
client.publish(topic, json.dumps(data), retain=True)
def sensor():
try:
core_temps = {}
sensors_output = subprocess.check_output(["sensors"]).decode("utf-8")
matches = re.findall(r'Core (\d+):\s+\+(\d+\.\d+)°C', sensors_output)
for match in matches:
core_temps[f"Core{match[0]}"] = float(match[1])
client = connect_mqtt()
publish_message(client, "debian/core_temperature", core_temps)
client.disconnect()
except Exception as e:
logging.error(f"Error in sensor: {e}")
if __name__ == "__main__":
sensor()
core_temps的結果如下
{"Core0": 28.0, "Core1": 29.0, "Core2": 25.0, "Core3": 27.0, "Core4": 27.0, "Core5": 28.0}
這時可以透過Home Assistant附加元件裡的MQTT Explorer觀看是否有成功發佈。
如果成功的話可以找到一個叫做debian的「主題」,下拉可以看到子主題為core_temperature的內容,這樣就是有成功發佈了。
再來建立MQTT sensor,以Core0為例,state_topic指定監聽debian/core_temperature就可以得到上面這一串json,再透過value_template取出key為Core0對應的值
mqtt:
sensor:
- name: DEBIAN_Core0_Temperature
state_topic: "debian/core_temperature"
value_template: "{{ value_json.Core0 }}"
unit_of_measurement: "°C"
force_update: true
最後在Debian上建立一個cronjob定時去執行sensors.py,如果要每三分鐘執行一次,寫法如下
*/3 * * * * /usr/bin/python3 /home/sensors.py
這樣就可以得到一個連續且準時更新的sensor。
MQTT sensor要做attribute也滿容易的,比如將docker stats裡,frigate的資訊整理成json如下
{"BlockIO": "336MB / 19.5GB", "CPUPerc": "378.77%", "Container": "439617391d65", "ID": "439617391d65", "MemPerc": "45.96%", "MemUsage": "2.757GiB / 6GiB", "Name": "frigate", "NetIO": "377GB / 12.1GB", "PIDs": "1318"}
我只想取出CPUPerc當sensor,其他key做為這個sensor的attribute就好,sensor和attribute的差別在於sensor會將狀態記錄到資料庫中,而attribute則只會顯示當下的值。
透過json_attributes_topic監聽docker_stats/frigate這個「主題」,再利用json_attributes_template: “{{ value_json | tojson }}”將所有key存為attribute,寫法如下
mqtt:
sensor:
- name: Frigate_DockerCPU
state_topic: "docker_stats/frigate"
value_template: "{{ value_json.CPUPerc | replace('%', '') }}"
unit_of_measurement: "%"
json_attributes_topic: "docker_stats/frigate"
json_attributes_template: "{{ value_json | tojson }}"
force_update: true
最後要特別注意的是,當一個「主題」及其訊息被發佈到MQTT伺服器上之後,MQTT伺服器並不會保留下記錄,因為從MQTT機制面上來看,訊息發佈後就會有對應該「主題」的訂閱者做接收。當Home Assistant重新啟動之後,所有實體包含MQTT sensor都會被更新,但更新MQTT sensor時MQTT伺服器上卻沒有相關記錄,所以MQTT sensor會變成unavailable。要解決這個問題,可以在發佈MQTT時加上retain的旗標,這樣在MQTT伺服器上就保留這個「主題」的最新一條訊息。
client.publish(topic, message, retain=True)