Мониторинг моей морозильной камеры с помощью Raspberry Pi, Grafana/Prometheus и немного Golang.
Это перевод оригинальной статьи Monitoring my freezer with a Raspberry Pi, Grafana/Prometheus, and a bit of Golang.
Перевод сделан специально для телеграм-канала Мониторим ИТ. Подписывайтесь! Там еще больше полезных постов о мониторинге.
Мотивация — день, когда растаяло мороженое.
Однажды жена позвала меня вниз и сказала: «Мороженое в морозилке растаяло». Это плохой знак. Это значит, что у десятилетней морозилки в гараже проблема. Я умею работать руками, но нет, я не смог бы её починить. Потратить 600 долларов на новую морозилку — это само по себе достаточно неприятно, но ещё и кучу денег тратить на замену испорченных продуктов — это просто издевательство. Лучше обнаружить неисправность до того, как продукты оттают.
Итак, передо мной стоит выбор: я могу купить коммерческий продукт, неплохой от Thermoworks, примерно за сто долларов. Он будет использовать их закрытый облачный сервис. Я могу купить безымянную альтернативу на Amazon примерно за 40 долларов, также используя чей-то собственный закрытый облачный сервис. Альтернативно я могу потратить 15 долларов на Raspberry Pi Zero 2 W, 2 доллара на датчик температуры и мониторить всё сам. Преимущества самостоятельного подхода:
- Мне не нужен ничей закрытый облачный сервис без API и без прозрачности. Мои данные и доступ к ним принадлежат мне.
- Высокая детализация. Я могу логировать температуру каждые пятнадцать секунд.
- Я могу хранить эти показания вечно.
- Я могу согласовать это с потреблением энергии.
- Я могу писать интеллектуальные оповещения, которые срабатывают после того, как температура остается высокой в течение определенного времени. Это позволяет сократить количество ложных срабатываний. Также это помогает различать поломку морозильной камеры и кратковременное оставление дверцы открытой.
- Я могу отправлять оповещения по электронной почте или другими способами.
- В перспективе: можно ли объединить это с обнаружением аномалий, чтобы выявлять следующую поломку морозильной камеры до того, как она произойдет?
Железо
Для решения подобных задач я предпочитаю использовать Raspberry Pi Zero 2W. Это небольшая, энергоэффективная плата, потребляющая гораздо меньше энергии, чем Raspberry Pi 5 или аналогичные полноразмерные платы. Существует множество альтернатив, например, небольшие устройства на базе ESP8266, Raspberry Pi Pico и т.д. Я выбрал Zero 2W, потому что это полноценный Linux-компьютер, на котором легко разрабатывать, и с которым я хорошо знаком. Удобно, что при желании можно подключиться к монитору температуры по SSH. На сайте Raspberry Pi указано, где его можно купить. Примерно за 15 долларов, немного дороже, если нужен корпус, блок питания или если требуется предварительно припаянный разъем GPIO.
Следующий необходимый элемент оборудования — сам датчик температуры. Я выбрал DS18B20. Их можно недорого найти на Amazon или eBay, в варианте с герметичным корпусом и водонепроницаемостью. Морозильная камера — это зона с постоянно меняющейся температурой и влажностью, поэтому хорошо иметь герметичное устройство. Я купил упаковку из 5 штук на Amazon у продавца hitlego:
Вам также понадобится резистор на 4,7К. Упаковку из ста штук можно купить на Amazon примерно за 5 долларов.
При подключении проводов используйте следующие схемы:
- +V (красный провод) от датчика к Raspberry Pi 3,3В
- Заземление (черный провод) от датчика к заземлению Raspberry Pi.
- Данные (жёлтый провод) от датчика к GPIO4 Raspberry Pi.
- Резистор между 3,3В и GPIO4.
Также полезно поместить Raspberry Pi в корпус. Я распечатал на 3D-принтере небольшой корпус со встроенными магнитами, благодаря чему он хорошо держится на морозильной камере.
Вот фотография корпуса и различных его частей:
…а вот как это выглядит после сборки корпуса:
Если вам интересно, что это за дополнительный красно-черный кабель, то это это круглый штекер питания типа «бочка». Я не являюсь поклонником разъемов micro-USB для питания своих проектов, поэтому часто использую традиционный разъем питания.
Небольшая зеленая деталь — это напечатанный на 3D-принтере зажим для удержания зонда. Он напечатан из TPU — гибкого материала.
Софт — сбор температур и экспорт в Prometheus
Обычно для своих проектов на Raspberry Pi я использую Python, но в последнее время меня больше привлекает Golang, поэтому я написал программу на Go. Код находится в моём репозитории на GitHub.
Наша первая задача — читать данные с датчика. В операционной системе Raspberry Pi, Linux Raspbian, уже есть драйвер для протокола 1-wire. В файле INSTALL.md в моём репозитории на GitHub объясняется, как его включить. Почти досадно, так как было бы интересно управлять GPIO с помощью Golang на Raspberry Pi. В любом случае, теперь получение данных превращается в чтение файлов в /sys:
func (ds *DS18B20) MeasureDevice(name string) (float64, error) {
deviceFileName := fmt.Sprintf("%s/%s/w1_slave", DEVICE_DIR, name)
if _, err := os.Stat(deviceFileName); os.IsNotExist(err) {
return 0.0, fmt.Errorf("device file %s does not exist", deviceFileName)
}
fileContents, err := os.ReadFile(deviceFileName)
if err != nil {
return 0.0, fmt.Errorf("error reading device file %s: %v", deviceFileName, err)
}
lines := strings.Split(string(fileContents), "\n")
if len(lines) < 2 {
return 0.0, fmt.Errorf("unexpected format in device file %s", deviceFileName)
}
if !strings.HasSuffix(lines[0], "YES") {
return 0.0, fmt.Errorf("first line does not end with yes in %s: %s", deviceFileName, lines[0])
}
parts := strings.Split(lines[1], "t=")
if len(parts) != 2 {
return 0.0, fmt.Errorf("unexpected format in second line of %s: %s", deviceFileName, lines[1])
}
tempC, err := strconv.ParseFloat(parts[1], 64)
if err != nil {
return 0.0, fmt.Errorf("error parsing temperature in %s: %v", deviceFileName, err)
}
return tempC / 1000.0, nil
}Это не самый изящный мой код — это поспешный перенос из наспех написанного исходного кода на Python примерно десятилетней давности. Наша следующая задача — создать prometheus-endpoint для сбора данных:
// import "github.com/prometheus/client_golang/prometheus"
// import "github.com/prometheus/client_golang/prometheus/promhttp"
// Create the prometheus gauge
temperature := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "temperature_celsius",
Help: "Current temperature in Celsius",
})
up := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "up",
Help: "Whether the collector is healthy (1 = healthy, 0 = failed)",
})
registry := prometheus.NewRegistry()
registry.MustRegister(temperature)
registry.MustRegister(up)
// Create a goroutine to periodically measure the temperature
go func() {
// if for any reason the goroutine exits, make sure we set the up gauge to 0
defer func() {
fmt.Fprintf(os.Stderr, "Collection goroutine as exited, setting Up to 0\n")
up.Set(0)
}()
for {
tempC, err := ds.MeasureFirstDevice()
if err != nil {
fmt.Fprintf(os.Stderr, "Error measuring temperature: %v\n", err)
temperature.Set(0) // Set to 0 on error
up.Set(0)
} else {
if verbose {
log.Printf("Sampled Temperature: %.2f°C", tempC)
}
temperature.Set(tempC)
up.Set(1)
}
time.Sleep(10 * time.Second)
}
}()
// Add the metrics handler to the http server
http.Handle(
"/metrics", promhttp.HandlerFor(
registry,
promhttp.HandlerOpts{
EnableOpenMetrics: true,
}),
)
// Start the HTTP server
err = http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error starting HTTP server: %v\n", err)
os.Exit(-1)
}Вы заметите, что я создаю две метрики. Одна из них — temperature_celsius, которая сообщает текущую температуру. Другая — up, которая сообщает, работает ли сбор данных о температуре. Метрика up важна, потому что мы хотим получать сигнал тревоги не только в том случае, если морозильная камера тёплая, но и если сбор данных не происходит.
И наконец, я хочу демонизировать процесс, чтобы он корректно работал в фоне. К счастью, Сергей Ярмонов создал отличную библиотеку go-daemon:
func daemonize() {
cntxt := &daemon.Context{
PidFileName: PIDFILE,
PidFilePerm: 0644,
LogFileName: LOGFILE,
LogFilePerm: 0640,
WorkDir: "./",
Umask: 027,
}
d, err := cntxt.Reborn()
if err != nil {
log.Fatal("Unable to run: ", err)
}
if d != nil {
return
}
defer cntxt.Release() // nolint:errcheck
log.Print("- - - - - - - - - - - - - - -")
log.Print("daemon started")
run()
}Если вы хотите реализовать подобный температурный монитор, то рекомендую скачать репозиторий на github, так как я кое-что здесь в посте опустил.
Настройка Prometheus
Для сбора метрик я использую Prometheus. Обратите внимание, что моя версия Prometheus очень старая — это мой домашний проект, и я не обновлял Prometheus уже… довольно давно. Поэтому могут возникнуть проблемы с синтаксисом конфигурации, если файлы конфигурации изменились.
prometheus.yml:
scrape_configs:
# freezer monitor
- job_name: 'freezermon'
metrics_path: /metrics
static_configs:
- targets:
- <my-ip-address-here>:8080Нам также необходимо настроить оповещения:
alerts:
groups:
- name: freezer-monitoring
rules:
- alert: ScrapeDown
expr: up{instance="<my-ip-address-here>:8080",job="freezermon"}!=1
for: 5m
labels:
severity: major
annotations:
summary: "garage freezer monitor is down"
description: "garage freezer monitor is down"
- alert: FreezerWarm
expr: temperature_celsius{instance="<my-ip-address-here>:8080",job="freezermon"}>-3
for: 20m
labels:
severity: major
annotations:
summary: "garage freezer is warm"
description: "garage freezer monitor is warm"er monitor is warm"Здесь у меня настроены два алерта. Первый уведомит меня, если сбор данных (scraping) перестанет работать. Второй оповестит меня, если морозильная камера будет оставаться теплой более 20 минут. Продолжительность времени важна, поскольку после открытия дверцы морозильной камеры требуется некоторое время, чтобы она остыла. Чтобы свести к минимуму ложные срабатывания, приходится методом проб и ошибок устанавливать продолжительность сигнала.
Обратите внимание, что порог температуры «-3» задан в градусах Цельсия, потому что именно в этих единицах работает мой Prometheus-экспортер. Это важно протестировать — откройте морозильник, подержите датчик руками несколько минут (он хорошо прогреется), а затем убедитесь, что тревога срабатывает и письмо отправляется.
Визуализация в Grafana
Я не буду здесь публиковать очень подробный JSON-код дашборда Grafana. Создать дашборд в пользовательском интерфейсе Grafana и связать его с метриками довольно просто. Вот как сейчас выглядит мой дашборд:
На дашборде есть два графика. Зелёный — это монитор морозильника. Синий — мой домашний монитор энергопотребления, подключённый к гаражной линии (которая включает не только морозильник, но и другие устройства, например освещение гаража).
Два больших пика на зеленом графике — это моменты, когда я открыл дверцу морозильной камеры, чтобы достать продукты. После открытия дверцы требуется несколько часов, чтобы показания датчика вернулись к минимальной температуре.
Многочисленные мелкие колебания — это нормальный цикл охлаждения морозильной камеры. Это не компрессор с регулируемой скоростью вращения и инверторным приводом. Это простой компрессор с циклом включения/выключения, то есть у него есть пороговое значение, при котором компрессор включается, когда становится слишком тепло, и выключается, когда становится слишком холодно.
Меня удивил высокий рабочий цикл. Морозильная камера работает более 50% времени. Я купил самую дешевую морозильную камеру, которую продавал магазин. Тем не менее, она обошлась мне в 600 долларов плюс доставка. Морозильная камера потребляет около 80 ватт во время работы. Приблизительные расчеты при рабочем цикле около 50% показывают, что это примерно 1 киловатт-час в день, или 30 киловатт-часов в месяц, или 360 кВт⋅ч в год. В руководстве по энергопотреблению морозильной камеры указано 466 кВт⋅ч в год, так что мои приблизительные расчеты, по крайней мере, верны.
Подписывайтесь на телеграм-канал Мониторим ИТ, там еще больше полезной информации о мониторинге!