У нас был один счетчик тепла с M-Bus, RaspberryPi, M-Bus to USB конвертор, Telegram-бот и возможность писать на Go
Начало
Всем привет. В этой статье я расскажу, как я упростил себе жизнь, автоматизировав подачу показаний счетчика. После переезда в новое жилье появилась возможность установить счетчик потребления тепла, который в теории (в моем случае, и на практике тоже) должен был сократить расходы на оплату услуг тепловых сетей. После установки прибора нужно каждый месяц в определенный период времени вносить показания тепла в личный кабинет на сайте тепловых сетей. Мой счетчик находится в общем коридоре в фальшстене, доступ к которому осуществляется через ревизионную рамку. В целом ― это неудобный процесс. Стоит отметить, что в странах Европы этот процесс часто автоматизирован на уровне самого поставщика тепловых услуг. Также, в наших широтах я встречал предложение от компаний, которые обслуживают ОСМД, установки такой системы на уровне подъезда или дома. Но так как это не мой случай, меня не покидала мысль, что этот процесс можно возложить на вычислительную машину. В конце концов, как звучал когда-то рекламный слоган IBM:
Machines should work; people should think.
Ближе к делу
Какого результата хочется достичь:
Что у нас есть из железа:
M-Bus master устройство
Wi-Fi реле Sonoff s20
Набор изображен на картинке. Слева блок питания, подключенный через реле. В центре сам Raspberry Pi. Справа M-Bus на USB конвертор (белый шнур идет к счетчику тепла).
Из программной части будем использовать:
Библиотеку для чтения M-Bus датаграмм написанную на языке C
Программную часть можно условно разделить на две составляющих. Первая ― это чтение данных из счетчика, вторая ― подача данных на сайт теплосетей.
M-Bus ― стандарт для удаленного считывания данных из счетчиков тепла или любых других устройств учета потребления, разработанный в Европе. Существует вариант передачи данных по кабелю и беспроводной вариант. В этой статье рассматривается только передача по кабелю.
Некоторые примечания относительно технологи:
К одному master устройству могут быть подключены несколько счетчиков (slave-устройств)
Устройствам назначается уникальный адрес
Диапазон рабочих напряжений соответствует 12-36 V: логический «0» ― 12..24 V, логическая «1» ― 36 V
Рекомендуемый тип кабеля ― стандартный телефонный (JYStY N*2*0.8 mm). Я использовал витую пару с сечением 0.51 мм, потому что она уже была протянута в подъезд.
Для того чтобы иметь возможность снять данные, счетчик должен поддерживать протокол M-Bus (выступать в роли slave-устройства). У меня счетчик этой модели.
Выход провода выглядит так.
Также нужно M-Bus master-устройство, которое в нашем случае еще и конвертирует сигнал из 36 V в 5 V.
Немного об устройстве
На самом деле оно делает немного больше, вроде защиты от короткого замыкания. Я открыл корпус устройства и сделал фото на случай, если кому-то будет интересно. Само устройство было приобретено на Aliexpress. Ссылку на товар нет смысл оставлять, так как она может быстро устареть. Поиск по ключевым словам «M-Bus USB Master» даст вам нужный результат. Важно: это должно быть именно master-устройство. Позже мне на глаза попалась плата расширения для Raspberry Pi. Я не могу дать отзыва по ее работе, но ввиду компактности решения, сейчас я обязательно рассмотрел бы этот вариант.
У нас есть счетчик, подключенный к master-устройству, которое в свою очередь подключено к Raspberry Pi и определяется как последовательный порт (у меня на Raspberry Pi OS устройство определяется как /dev/ttyUSB0). Теперь мы можем отправлять и получать датаграммы. К счастью, в открытом доступе уже есть библиотека и cli инструменты на ее основе, которые реализуют прием и отправку M-Bus датаграмм.
Нам нужен доступ к Raspberry Pi по SSH. И для начала проверим, что наше устройство определяется в сети, получает и отправляет датаграммы. Для этого воспользуемся утилитами в составе библиотеки.
Скачиваем библиотеку и распаковываем архив:
wget https://github.com/rscada/libmbus/archive/master.zip
&& unzip master.zip
&& cd libmbus-master
Если требуется, ставим инструменты для сборки:
apt-get install build-essential libtool autoconf m4
В папке libmbus-master/bin находятся нужные нам утилиты. Утилита mbus-serial-request-data запрашивает данные по умолчанию. Далее мы рассмотрим как указать в датаграмме, какие данные мы хотим запросить. На текущем этапе достаточно формата данных отправляемых по умолчанию.
Вызов утилиты выглядит следующим образом:
./mbus-serial-request-data -b 2400 /dev/ttyUSB0 12
-b 2400 ― скорость передачи данных, измеряемая в бодах. Скорости, которые поддерживает ваш счетчик, вы можете найти в его руководстве. У меня это 300 и 2400 бод. Допустимый диапазон согласно протоколу от 300 до 9600 бод.
/dev/ttyUSB0 ― путь к устройству, которое подключено к Raspberry Pi.
12 ― первичный адрес slave-устройства (счетчика).
Здесь стоит немного рассказать про адресацию slave-устройств в m-bus сети. M-Bus определяет два типа адресации: первичный (primary address) и вторичный (secondary address). Воспринимать их можно как логический и уникальный адрес, и реализованы они на канальном и сетевом уровнях соответственно. Первичный адрес принимает значения в диапазоне от 1 до 250 и может быть назначен устройству (если это поддерживается самим устройством) при помощи утилиты mbus-serial-set-address . Вторичный адрес зашит в устройство (теоретически, если производитель предоставляет такую возможность, тоже может быть изменен) и имеет вид представленный в таблице ниже.
Identification-Nr.
Manufacturer. (hex.)
Version (hex.)
Media (hex.)
Разница на практике: использование первичного адреса в датаграмме занимает всего 1 байт и всегда фиксировано в датаграмме. Но при этом количество устройств в сети ограничено до 250.
Примечание: на картинке указан адрес FDh (253) — это зарезервированное значение, которое значит, что коммуникация осуществляется через вторичный адрес. Значения 254 и 255 обозначают широковещательную рассылку. 251 и 252 ― зарезервированы на будущее. Значения же от 1 до 250 напрямую идентифицируют устройство, как первичный адрес.
Master отправляет отдельную датаграмму с CI (control information) полем со значением 52h или 56h, A (address field) полем равным FDh (253) и указанным вторичным адресом в теле датаграммы.
Slave распознает датаграмму, сравнивает вторичный адрес со своим, переходит в «selected state» и отправляет ответ E5h.
Теперь можно отправлять датаграммы на это устройство обращаясь по адресу FDh (253) в A (address field).
После того как коммуникация между master и slave устройством завершена, master должен выйти из «selected state» и отправить датаграмму с CI полем 40h.
Вторичный адрес требует больше накладных расходов, но с его помощью можно иметь в сети большее количество устройств, избежав коллизии первичных адресов. Еще он служит для задания первичного адреса для устройства. Так как в моем случае сеть состоит из одного устройства ― будет использоваться первичный адрес.
Возвращаемся к нашим счетчикам. Скорее всего ваше устройство не инициализировано и мы должны ему присвоить первичный адрес. Для этого нам нужно сначала узнать его вторичный адрес. Здесь есть два варианта: посмотреть инструкцию или найти нужные данные на корпусе устройства и сформировать адрес, как указано таблице. Или же воспользоваться одной из набора утилит.
./mbus-serial-scan-secondary -b 2400 /dev/ttyUSB0
Вывод:
Found a device on secondary address 58740397A511410C [using address mask 5FFFFFFFFFFFFFFF]
Зная вторичный адрес, устанавливаем первичный адрес 12 (или любое другое число в диапазоне):
./mbus-serial-set-address -b 2400 /dev/ttyUSB0 58740397A511410C 12
Вывод:
Set primary address of device to 12
Убедимся, что устройство доступно по установленному адресу:
./mbus-serial-scan -b 2400 /dev/ttyUSB0
Вывод:
Found a M-Bus device at address 12
И наконец-то, запрашиваем данные у счетчика:
./mbus-serial-request-data -b 2400 /dev/ttyUSB0 12
Получаем xml на выходе
Пишем код
Аппаратная часть сконфигурирована ― теперь начнем писать код.
Несколько слов на тему «почему Go»?
В коммерческой среде выбор языка и технологии диктуется множеством факторов, от таких, как наличие у вас в штате разработчиков с определенной экспертизой, и до объема легаси кода и целесообразности применения технологии в целом. В домашнем проекте нет таких ограничений. Поэтому, в академических целях, я использовал интересный мне язык за его соотношение усилия программиста/производительность. Можно писать и на чистом С или С++, если это целесообразно.
Мы будем использовать кросс-компилятор внутри docker-контейнера. Кросс-компиляция, потому что рабочая машина собирает код быстрее, чем Raspberry Pi. Docker ― чтобы изолировать среду сборки и не устанавливать инструменты для кросс-компиляции на хостовую машину.
В строке 33 идет вызов скрипта сборки RUN bash build.sh:
Теперь вызовом команды env DOCKER_BUILDKIT=1 docker build —output out . можно собрать наше приложение. Артефакт сборки будет находиться в папке out в нашей рабочей директории на хостовой машине.
Создадим пакет mbus, который будет состоять из двух файлов. Файл measurement.go содержит структуру с полями показаний и функцию для десериализации xml, который сформирует библиотека.
Файл reader.go , который содержит структуру Reader , разберем подробнее (для удобства чтения сделаем это частями). Методы структуры внутри будут вызывать C-функции. Для этого в Golang есть механизм биндинга. С ним рекомендовано ознакомиться.
В начале у нас идет содержимое в комментариях, после которого стоит преамбула import «C» . В этом месте может содержаться код на Си и директива #cgo, которая позволяет задать настройки для компилятора или линковщика.
Метод Open инициализирует дескриптор, открывает и настраивает порт. Устанавливает handshake.
Close закрывает порт и освобождает выделенную память.
И сама функция ReadData , которая получает данные и возвращает структуру mbus.Measurement .
Создадим файл main.go , и напишем код для вывода полученных значений в консоль.
Собираем это командой и получаем вывод в консоль:
Energy: 12.667, volume: 1476.014, volume flow: 0.000, power: 0, flow temperature: 22, return temperature: 23, operating days: 1330, error hours: 0
Примечание: так как сейчас у меня кран перекрыт, то значения объема и затраченной энергии равны 0. В принципе, на этом можно было бы и остановиться — показания теперь можно снять, сидя за компьютером, подключившись к Raspberry Pi по SSH. Но все-таки хочется минимального вмешательства со стороны человека.
Подача показаний на сайт теплосетей
Так как сайты локального поставщиков услуг отличаются — нет практического смысла описывать детали реализации. Все сводится к запросу и парсингу веб-страницы. Отправке POST запросов с содержимым формы для авторизации и получения сессионного токена, а также отправке POST запроса с данными показаний.
Telegram-бот
Для удобного получения уведомлений о передаче показаний или возникших ошибках было решено использовать Telegram-бота. Создадим пакет logger и в нем файл telegram.go, в котором реализуем интерфейс io.Writer для возможности установки его в качестве вывода для стандартного logger’a. Здесь нам понадобятся bot token и идентификатор чата между вами и ботом, чтобы программа могла отправлять боту сообщения. Узнать идентификатор чата можно следующим образом:
В полученном ответе result[0].messsage.chat.id — нужный нам идентификатор чата.
В начале функции main в файле main.go устанавливаем вывод для стандартного logger’а.
Теперь все сообщения, которые будут записаны в стандартный logger, также будут отправлены в чат с ботом.
Wi-Fi реле
Было решено не держать Raspberry Pi в режиме 24/7. Задача, которую она выполняет, длится не больше 1-5 минут один раз в месяц. На это также есть еще как минимум две причины:
На просторах интернета была информация о нагревании плат и нестабильной ее работе при длительном uptime’е.
В случае перебоев питания, нужно решить проблему с UPS’ом.
Для решения этой задачи будет использоваться Wi-Fi реле от производителя Sonoff, которое будет по заданному графику включать и отключать питание (настраивается через мобильное приложение).
Если бы плата поддерживала WoL, то можно было бы эту задачу возложить на роутер (в моем случае Mikrotik), сделав отправку нужных пакетов по расписанию. Возможно, в будущем эта возможность будет реализована в новых версиях.
Заметка по выбору реле
Если вы решите приобрести реле этого или другого производителя, посмотрите, чтобы оно поддерживало какой-либо из видов RPC. У Sonoff это DIY режим, который позволяет сделать REST-запрос. Таким образом реле можно включить тем же скриптом с роутера. Это даст возможность настройки более гибкого графика включения под ваши нужды. Моя модель не поддерживает это из коробки и требует сторонней прошивки.
Запускаем наше приложение как службу
Для начала скопируем собранное приложение c рабочей машины на Raspberry Pi:
scp /out/heatmeter pi@ :/home/pi
На Raspberry Pi переместим приложение:
sudo mv heatmeter /usr/local/bin
В целях безопасности, создадим отдельного пользователя для нашей службы без домашней директории и возможности зайти в систему:
sudo useradd -r -s /bin/false —no-create-home heatmeter
Изменим владельца и права для нашего приложения:
sudo chown heatmeter:heatmeter /usr/local/bin/heatmeter
sudo chmod 500 /usr/local/bin/heatmeter
Напишем файл конфигурации нашего модуля /etc/systemd/system/heatmeter.service .
Наше приложение должно автоматически запуститься при старте операционной системы, выполнить свою работу и дальше систему можно выключить.
Меняем права для файла конфигурации:
sudo chmod 644 /etc/systemd/system/heatmeter.service
Запустим команду daemon-reload, чтобы systemd подтянул наши изменения:
systemctl daemon-reload
Делаем активной нашу службу при следующем запуске системы:
systemctl enable heatmeter.service
Эпилог
На этом все. Теперь один раз в месяц Wi-Fi реле будет запускать Raspberry Pi. После будет стартовать наше приложение и снимать показания.
Спасибо, если вы дочитали эту публикацию до конца. Надеюсь, для кого-то эта информация была полезной. Полный код проекта доступен по ссылке.
Источник