Каждый раз получая платежку за электричество и воду я удивляюсь — неужели моя семья стооооолько потребляет? Ну да, в ванной установлен теплый пол и бойлер, но ведь они же не кочегарят постоянно. Воду тоже вроде экономим (хотя поплескаться в ванной тоже любим). Несколько лет назад я уже подключил счетчики воды и электричества к умному дому, но на этом дело так и застряло. До анализа потребления руки дошли только сейчас, о чем, собственно, вот эта статья.
Недавно я перешел на Home Assistant в качестве системы умного дома. Одной из причин была как раз возможность организовать сбор большого количества данных с возможностью удобного построения различного рода графиков.
Информация описанная в этой статье не нова, все эти штуки под разными соусами уже были описаны в Интернетах. Но каждая статья, как правило, описывает только один подход или аспект. Сравнивать все эти подходы и выбирать наиболее подходящий пришлось самому. Статья все равно не дает исчерпывающей информации по сбору данных, но является своего рода конспектом того как сделал я. Так что конструктивная критика и предложения по улучшению приветствуются.
Постановка задачи
Итак, цель сегодняшнего упражнения в том, чтобы получить красивые графики потребления воды и электричества:
- Почасовой за 2 дня
- Посуточный за 2 недели
- (опционально) понедельный и помесячный
В этом нас подстерегают некоторые сложности:
- Стандартные компоненты графиков, как правило, весьма убоги. В лучшем случае можно построить линейный график по точкам.
Если хорошо поискать, то можно найти сторонние компоненты, которые расширяют возможности стандартного графика. Для home assistant, в принципе, неплох и красив компонент mini-graph-card, но и он несколько ограничен:
- Сложно задавать параметры столбикового графика на больших промежутках (ширина столбика задается в долях часа, а значит промежутки длиннее часа будут задаваться дробными числами)
- Нельзя на один график добавлять различные сущности (например температуру и влажность, или столбиковый график совместить с линией)
- Мало того, что home assistant по умолчанию использует самую примитивную базу данных SQLite
(а я, рукожоп, не осилил установку MySQL или Postgres), так и данные хранятся не самым оптимальным образом. Так, например, при каждом изменении каждого даже самого мелкого цифрового параметра параметра в базу записывается огромный 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 Мб ежедневно. Через неделю система будет еле ворочаться, а через месяц сдохнет флешка (в случае типичной установки home assistant на raspberry PI), а уж о хранении данных целый год и речи быть не может. - Если вам повезло, ваш счетчик сам умеет считать потребление. Вы в любой момент можете обратиться к счетчику и спросить который час накопленное значение потребления. Как правило все счетчики электричества у которых есть цифровой интерфейс (RS232/RS485/Modbus/Zigbee) предоставляют такую возможность.
Хуже если устройство может просто измерять некий мгновенный параметр (например мгновенную мощность или ток), или просто генерировать импульсы каждые X ватт-часов или литров. Тогда нужно думать как и чем это интегрировать и где накапливать значение. Есть риск пропустить очередной отчет по какой либо причине, да и точность системы в целом вызывает вопросы. Можно, конечно, это все поручить системе умного дома вроде home assistant, но пункт про количество записей в базе никто не отменял, да и опрос датчиков чаще чем раз в секунду устроить не получится (ограничение архитектуры home assistant).
Подход 1
Сначала посмотрим что предоставляется home assistant из коробки. Измерение потребления за период — весьма востребованная функциональность. Разумеется, ее давным давно реализовали в виде специализированного компонента — utility_meter.
Суть компонента в том, что он внутри заводит переменную текущее_накопленное_значение, и сбрасывает ее по истечении заданного периода (час/неделя/месяц). Компонент сам мониторит входящую переменную (значение какого нибудь сенсора), сам подписывается на изменения значения — вы просто получаете готовый результат. Описывается эта штука всего несколькими строчками в конфигурационном файле
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 это текущее значение счетчика в литрах, которые я получаю непосредственно от железяки по mqtt. Конструкция создает 2 новых сенсора water_cold_hour_um и water_cold_day_um, которые накапливают часовые и дневные показания, обнуляя их по истечении периода. Вот график часового аккумулятора за полдня
Код часового и дневного графиков для 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
Собственно, в этом алгоритме и кроется проблема такого подхода. Как я уже упоминал, на каждое входящее значение (текущее показание счетчика на каждый следующий литр) генерируется по 1кб записи в базе. Каждый utility meter также генерирует по новому значению, которое также складывается в базу. Если я хочу собирать часовые/дневные/недельные/месячные показания, да по нескольким стоякам воды, да еще пачку электросчетчиков добавить — это будет очень много данных. Ну точнее данных то не много, но поскольку home assistant в базу пишет кучу лишней информации размер базы будет расти как на дрожжах. Боюсь даже прикидывать размер базы для понедельных и помесячных графиков.
Помимо этого utility meter сам по себе не решает поставленную задачу. График значений, которые выдает utility meter это монотонно возрастающая функция, которая сбрасывается в 0 каждый час. Нам же нужен понятный для пользователя график потребления, сколько же литров было съедено за период. Стандартный компонент history-graph такого не умеет, но нам может помочь внешний компонент mini-graph-card.
Это код карточки для 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 — график будет генерироваться с выравниванием столбиков по началу часа
- points_per_hour: 1 — один столбик на каждый час
- И самое важное, aggregate_func: max — брать максимальное значение в пределах каждого часа. Именно этот параметр и превращает пилообразный график в столбики
На ряд столбиков слева не обращайте внимания — это стандартное поведение компонента если нет данных. А данных и не было — я только пару часов назад включил сбор данных по utility meter только ради этой статьи (свой текущий подход я расскажу чуть ниже).
На этой картинке я хотел показать, что иногда отображение данных даже работает, и столбики действительно отражают правильные значения. Только вот далеко не все. Выделенный столбик за промежуток с 11 до 12 утра почему то отображает 19 литров, хотя на зубастом графике чуть выше за этот же самый период с того же самого сенсора видим потребление 62 литра. Либо баг, либо руки кривые. А вот почему отломались данные справа я пока не понял — потребление там было в норме, что также видно по зубастому графику.
В общем, правдоподобности это этого подхода мне добиться не удалось — график почти всегда показывает какую-то ересь.
Аналогичный код для дневного сенсора.
- 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 установлен в значение interval, и рулит всем параметр points_per_hour. И в этом заключается другая проблема этого компонента — points_per_hour хорошо работает на графиках за час или менее, но отвратительно на бОльших промежутках. Так чтобы получить один столбик за один день пришлось вписать значение 1/24=0.04166666. Я уже не говорю про недельные и месячные графики.
Подход 2
Еще только разбираясь с home assistant я наткнулся на вот это видео
Товарищ собирает данные потребления с нескольких видов розеток Xiaomi. Задача у него чуть проще — просто выводить значение потребления за сегодня, вчера и за месяц. Никаких графиков не требуется.
Оставим в стороне рассуждения о ручном интегрировании мгновенных значений мощности — о “точности” такого подхода я уже писал выше. Не ясно почему он не стал использовать накопленные значения потребления, которые уже собираются той же розеткой. На мой взгляд интегрирование внутри железяки будет работать лучше.
От видео мы возьмем идею ручного подсчета потребления за период. У мужика считаются только значения за сегодня и за вчера, но мы пойдем дальше и попробуем нарисовать график. Суть предложенного метода в моем случае заключается в следующем.
Заведем переменную значение_в_начале_часа, в которую запишем текущие показания счетчика
По таймеру в конце часа (или в начале следующего) посчитаем разницу между текущим показанием и запомненным в начале часа. Эта разница и будет потреблением за текущий час — сохраним значение в сенсор, и в будущем будем по этому значению строить график.
Также нужно “обнулить” переменную значение_в_начале_часа записав туда текущее значение счетчика.
Все это можно сделать
Кода придется написать несколько больше чем в предыдущем подходе. Для начала заведем эти самые “переменные”. Из коробки у нас нет сущности “переменная”, но можно воспользоваться услугами mqtt брокера. Будем отправлять туда значения с флагом retain=true — это сохранит значение внутри брокера, и его в любой момент можно оттуда выдернуть, даже при перезагрузке home assistant. Я сделал сразу часовые и дневные счетчики.
- 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 действия:
- Вычисляют значение за интервал как разницу между начальным и конечным значением
- Обновляют базовое значение для следующего интервала
Построение графиков в данном случае решается обычным history-graph
- 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 записи за сутки для часового графика.
К сожалению, общую проблему растущей базы это все равно не решает. Если я захочу график помесячного потребления, то придется хранить данные как минимум за год. А поскольку home assistant предоставляет только одну настройку длительности хранения на всю базу, то это означает что ВСЕ данные в системе придется хранить целый год. Например за год я потребляю 200 кубов воды, а значит это 200000 записей в базу. А если учесть еще и другие сенсоры, то цифра вообще неприличной становится.
Подход 3
К счастью, умные люди уже решили эту проблему написав базу данных InfluxDB. Эта база специальным образом оптимизирована под хранение time-based данных и идеально подходит для хранения значений разных сенсоров. Система также предоставляет SQL-подобный язык запросов, который позволяет выковыривать из базы значения, и потом их агрегировать различными способами. Наконец, разные данные можно хранить разное время. Например, часто меняющиеся показания вроде температуры или влажности можно хранить всего пару недель, тогда как дневные показания потребления воды можно хранить целый год.
Помимо InfluxDB умные люди также изобрели Grafana — систему рисования графиков по данным из InfluxDB. Графана умеет рисовать разные виды графиков, детально их кастомизировать, и, что самое главное, эти графики можно “воткнуть” на lovelace-UI home assistant’а.
Вдохновляться тут и тут. В статьях подробно описан процесс установки и подключения InfluxDB и Grafana к home assistant. Я же сосредоточусь на решении своей конкретной задачи.
Итак, первым делом начнем складывать значение счетчика в influxDB. Кусок конфигурации home assistant (в этом примере я буду развлекаться не только холодной, но и горячей водой):
influxdb:
host: localhost
max_retries: 3
default_measurement: state
database: homeassistant
include:
entities:
- sensor.water_meter_hot
- sensor.water_meter_cold
Отключим сохранение этих же самых данных внутреннюю базу home assistant, чтобы не раздувать ее лишний раз
recorder:
purge_keep_days: 10
purge_interval: 1
exclude:
entities:
- sensor.water_meter_hot
- sensor.water_meter_cold
Перейдем теперь в консоль InfluxDB и настроим нашу базу. В частности нужно настроить сколько времени будут хранится те или иные данные. Это регулируется т.н. retention policy — это похоже на базы данных внутри основной базы данных, причем каждая внутренняя база имеет свои настройки. По умолчанию все данные складываются в retention policy под названием autogen, эти данные будут хранится неделю. Я бы хотел, чтобы часовые данные хранились месяц, недельные — год, а месячные вообще никогда не удалялись. Создадим соответствующие retention policy
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
Теперь, собственно, главный трюк — агрегация данных с помощью continuous query. Это механизм, который автоматически запускает запрос через заданные промежутки времени, агрегирует данные по этому запросу, а результат складывает в новое значение. Разберем на примере (я пишу в столбик для удобочитаемости, но на деле мне пришлось вводить эту команду одной строкой)
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
Эта команда
- Создает continuous query по имени cq_water_cold_hourly в базе homeassistant
- Запрос будет выполняться каждый час (time(1h))
- Запрос будет выгребать все данные из measurement’а homeassistant.autogen.l (литры), включая показания холодной и горячей воды
- Агрегированные данные будут группироваться по entity_id, что создаст нам отдельные значения по холодной и горячей воде
- Поскольку счетчик литров это монотонно возрастающая последовательность в рамках каждого часа нужно будет брать максимальное значение, поэтому агрегация будет проводится функцией max(value)
- Новое значение будет записано в homeassistant.month.water_meter_hour, где month это имя retention policy со сроком хранения в месяц. Причем данные по холодной и горячей воде будут разбросаны в отдельные записи с соответствующим entity_id и значением в поле value
Ночью или когда никого нет дома потребления воды нет, а соответственно новых записей в homeassistant.autogen.l тоже нет. Чтобы не было пропусков значений в обычных запросах можно использовать fill(previous). Это заставит InfluxDB использовать значение прошлого часа.
К сожалению, у continuous query есть особенность: трюк fill(previous) не работает и записи просто не создаются. Причем это какая-то непреодолимая проблема, которая обсуждается уже не первый год. С этой проблемой мы разберемся позже, а fill(previous) в continuous query пусть будет — оно не мешает.
Проверим что получилось (разумеется, нужно выждать пару часиков):
> 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 часа — значения за 7 утра в выводе InfluxDB соответствуют значениям за 10 утра на графиках выше. Также обратите внимание, что между 2 и 5 утра записей просто нету — это та самая особенность continuous query.
Как видите, агрегированное значение также является монотонно возрастающей последовательностью, только записи идут реже — раз в час. Но это не проблема — мы можем написать еще один запрос, который будет добывать правильные данные для графика.
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 вытянем данные для entity_id='water_meter_cold' за последние сутки (time >= now() -24h).
- Как я уже упоминал в последовательности homeassistant.month.water_meter_hour могут отсутствовать некоторые записи. Эти данные мы сгенерируем заново, запустив запрос с GROUP BY time(1h). На этот раз fill(previous) сработает как нужно, сгенерировав недостающие данные (функция возьмет предыдущее значение)
- Самое главное в этом запросе это функция difference, которая и посчитает разницу между часовыми отметками. Сама по себе она не работает и требует агрегирующую функцию. Пускай это будет 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), а функция difference это значение вычтет само из себя и на выходе получим 0, что собственно и требуется.
Осталось дело за малым — построить график. Для этого откроем Grafana, откроем какой нибудь существующий (или создадим новый) дашборд, создадим новую панель. Настройки графиков будут такими.
Я буду отображать данные по холодной и горячей воде на одном графике. Запрос точно такой же как я описал выше.
Параметры отображения задаются так. У меня это будет график линиями (lines), который идет ступеньками (stairs). Параметр Stack я объясню чуть ниже. Там ниже еще пара параметров отображения, но они не так интересны.
Чтобы добавить полученный график в home assistant нужно:
- выйти из режима редактирования графика. Почему-то правильные настройки шаринга графиков предлагаются только со страницы дашборда
- Нажать на треугольник возле имени графика, в меню выбрать share
- В открывшемся окне перейти на вкладку embed
- Убрать галочку current time range — временной диапазон мы будем задавать через URL
- Выбрать необходимую тему. В моем случае это light
- Скопировать получившийся 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 дня) задается именно тут, а не в настройках дашборда.
Выглядит график вот так. Горячую воду я не за последние 2 дня не использовал, поэтому рисуется только график холодной воды.
Я так для себя и не решил какой график мне больше нравится, линией-ступенькой, или реальными столбиками. Поэтому просто приведу пример дневного графика потребления, только на этот раз столбиками. Запросы строятся аналогично вышеописанным. Параметры отображения такие:
Выглядит этот график так:
Так вот про параметр Stack. В этом графике столбик холодной воды рисуется поверх столбика горячей. Общая высота соответствует суммарному потреблению по холодной и горячей воде за период.
Все показанные графики — динамические. Можно навести мышкой на интересующую точку и посмотреть детали и значение в конкретной точке.
К сожалению без пары ложек дегтя не обошлось. На столбиковом графике (в отличии от графика линиями-ступеньками) середина столбика находится не в середине суток, а в 00:00. Т.е. левая половина столбика нарисована на месте предыдущего дня. Так вот графики за субботу и воскресенье нарисованы чуть левее чем синеватая зона. Пока я не придумал как это победить.
Другая проблема заключается в невозможности правильно работать с интервалами в месяц. Дело в том, что длина часа/дня/недели фиксирована, а вот длина месяца каждый раз разная. InfluxDB умеет работать только с одинаковыми интервалами. Пока моих мозгов хватило чтобы задать фиксированный интервал в 30 дней. Да, график в течении года немного поплывет и столбики не совсем точно будут соответствовать месяцам. Но поскольку мне это штука интересна просто в качестве показометра, то я с этим ок.
Решений вижу как минимум два:
- Забить на помесячные графики и ограничиться недельными. 52 недельных столбика за год вполне неплохо смотрятся
- Само помесячное потребление считать способом №2, а графану только для красивых графиков использовать. Вполне себе точное решение получится. Можно даже наложить графики за прошлый год для сравнения — графана и такое умеет.
Заключение
Не знаю почему, но я тащусь от такого рода графиков. Они показывают что жизнь кипит и все меняется. Вчера было много, сегодня мало, завтра будет как нибудь еще. Осталось поработать с домочадцами на тему потребления. Но даже при текущих аппетитах просто большая и непонятная цифра в платежке уже превращается в достаточно понятную картину потребления.
Несмотря на почти 20-летнюю карьеру программиста, с базами данных я практически не пересекался. Поэтому установка внешней базы данных казалось чем-то таким заумным и непонятным. Все изменила вышеупомянутая статья — оказалось что прикручивание подходящего инструмента делается в пару кликов, а со специализированным инструментом задача построения графиков становится немного проще.
В заголовке я упомянул потребление электричества. К сожалению в данный момент я не могу привести ни одного графика. Один счетчик SDM120 у меня сдох, а другой глючит при обращении по Modbus. Впрочем, на тему данной статьи это никак не влияет — графики будут строится тем же самым способом, как и для воды.
В этой статье я привел те подходы, которые испробовал сам. Наверняка есть еще какие-то способы организации сбора и визуализации данных, о которых я не знаю. Расскажите мне об этом в комментариях, мне будет очень интересно. Буду рад конструктивной критике и новым идеям. Надеюсь изложенный материал также кому-то поможет.