[Из песочницы] Bobaos — доступ к шине KNX TP/UART c Raspberry Pi
Если вы не знакомы с системами автоматизации и стандартом KNX, то нужную информацию можете получить в гугле или с официальных сайтов. Если же вы работаете с данным стандартом — то многие вещи вам будут понятны, и, возможно, вас, как и меня, давно интересует вопрос каким же образом можно получить доступ к физической шине KNX, минуя IP роутеры.
Далее я покажу каким образом я решил для себя эту задачу, используя Raspberry Pi и модуль KNX BAOS 838 kBerry от Weinzierl.
Мотивация
Стандарт KNX подразумевает собой децентрализованную систему, работоспособную в отсутствии центрального контроллера ПЛК. Децентрализация обеспечивает надежность, но отсутствие ПЛК в системе лишает нас необходимой нам гибкости. Можно использовать различные IP шлюзы, и общаться по UPD с сервера, но это решение не рассматривал, поскольку доступ напрямую к шине выглядит надежнее.
Существующие решения
На рынке можно найти решения для данной задачи, со следующими из них я плотно работал:
Loxone Miniserver
- Цена: ~600 евро
- Плюс: своя шина, большой ассортимент устройств.
- Плюс: простой конфигуратор, программирование логики блоками, хорошая документация.
- Минус: нету возможности программировать на языках высокого уровня. Есть встроенный интерпретатор PicoC, но на нем проблематично реализовать, для примера, доступ к облаку.
iRidium Server для UMC
- Цена: рекомендуемая розничная 119000р, или ~1700 евро.
- Плюс: поддержка большого количества систем автоматизации, помимо KNX.
- Плюс: поддержка JavaScript.
- Минус: файл проекта — zip архив, что усложняет его редактирование. Т.е. для того чтобы отредактировать скрипт, вам надо либо использовать iRidium Studio с встроенным редактором скриптов, в котором нету прелестей привычного вам IDE/редактора, либо придумать способ распаковки/сборки проекта.
- Минус: JavaScript движок. Для примера, нету привычного setTimeout/setInterval, вместо них IR.SetTimeout/IR.SetInterval. Нету совместимости с nodejs.
- Минус: стабильность работы, частые перезагрузки.
EVIKA LogicMachine
- Цена: ~3000 евро.
- Плюс: помимо KNX TP/UART на плате имеются интерфейсы RS-485, RS-232 и прочие, в зависимости от конфигурации.
- Плюс: язык lua, cron расписания, и прочее.
Некоторые решения хороши, но отталкивает цена, плюс хотелось бы использовать больше гибкости в программировании.
Решение
Задача решилась с использованием Raspberry Pi и модуль Weinzierl KNX BAOS Module 838 kBerry.
Стоимость модуля ~70 евро, в сумме с Raspberry Pi + блок питания, корпус на DIN рейку выходит около 150 евро.
В качестве среды выполнения используется nodejs, из зависимостей модуль «serialport» для общения с UART.
Наше приложение подключается по серийному порту к модулю BAOS 838 и общается посредством ObjectServer protocol, описание которого можно найти по ссылке в конце статьи.
Фрейм с данными заключается в протокол FT1.2 следующим образом:
|0x68|L|L|0x68|CR|data|C|0x16|
где L - длина данных data +1 для контрольного байта
CR - контрольный байт, равен 0x73 для нечетных, 0x53 для четных фреймов,
C - чексумма = (сумма байт данных + контрольного байта) mod 256
Также есть фреймы с постоянной длиной, такие как reset request, reset indication, acknowledge frame.
Reset request отправляется при открытие соединения, reset indication отправляется с модуля baos 838 к Raspberry Pi при сбросе модуля, acknowledge отправляет каждая сторона при приеме данных.
Данные data состоят из следующих полей:
| Поле | Размер | Описание
| MainService | 1 | Главный сервис. Везде 0xF0
| SubService | 1 | Сервис. Их три вида: запросы, ответы, индикация.
| StartItem | 2 | ID первого элемента
| NumberOfItems | 2 | Максимальное количество элементов
...
И далее в зависимости от сервиса. Эти четыре поля присутствуют во всех сообщениях.
Схема коммуникации:
- Наше приложение (клиент) отправляет reset request.
- BAOS 838 (сервер) отправляет acknowledge.
- Клиент отправляет первый запрос (для примера GetDatapointDescription.Req). Нечетный фрейм.
- Сервер получает запрос, отправляет acknowledge.
- Сервер отправляет ответ на запрос.
- Клиент отправляет подтверждение acknowledge.
…
Подключение
Модуль подключается к GPIO контактам платы Raspberry Pi, более подробно можно узнать по ссылкам в конце статьи.
ETS
Как и любое KNX устройство, BAOS 838 module конфигурируется через ETS. Аппликация в данном случае используется «KNX BAOS 830», ее можно скачать с официального сайта производителя. Устройство поддерживает до 1000 групповых адресов.
Настройка Raspberry Pi
Первым делом устанавливаем raspbian-stretch-lite, настраиваем доступ по ssh.
Далее, настраиваем UART интерфейс. Для Raspberry Pi 3:
sudo sh -c "echo dtoverlay=pi3-miniuart-bt >>/boot/config.txt"
Из /boot/cmdline.txt убираем запись console=serial0,115200
Добавляем пользователя в группу dialout:
sudo usermod -a -G dialout YOURUSERNAME
Перезагружаемся
reboot
Устанавливаем nodejs, git:
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs git
Подготовка закончена, переходим к нашему проекту.
bobaos-cli
Для тестирования можно использовать простой интерфейс для командной строки. Устанавливаем:
sudo npm install -g bobaos-cli
Запускаем:
% bobaos-cli
bobaos> help
Commands:
help [command...] Provides help for a given command.
exit Exits application.
open [options] Open serial port
getDatapointDescription [options] GetDatapointDescription.Req service
setDatapointValue [options] SetDatapointValue.Req service with command "set and send to bus"
readDatapointFromBus [options] SetDatapointValue.Req service with command "read via bus"
getDatapointValue [options] GetDatapointValue.Req service
getParameterByte [options] GetParameterByte.Req service
Далее мы открываем порт командой open. По умолчанию открывается соединение с устройством /dev/ttyAMA0, если в системе другой интерфейс, используем опцию -p, --port.
Далее, мы можем получить информацию о сконфигурированных датапоинтах:
bobaos> getDatapointDescription -s 1 -n 10
{ service: 'GetDatapointDescription.Res',
direction: 'response',
error: false,
start: 1,
number: 10,
payload:
[ { id: 1, valueType: 8, configFlags: 95, dpt: 'dpt9' },
{ id: 2, valueType: 7, configFlags: 87, dpt: 'dpt5' },
{ id: 3, valueType: 7, configFlags: 87, dpt: 'dpt5' },
{ id: 4, valueType: 7, configFlags: 87, dpt: 'dpt5' },
{ id: 5, valueType: 7, configFlags: 87, dpt: 'dpt5' },
{ id: 6, valueType: 0, configFlags: 95, dpt: 'dpt1' },
{ id: 7, valueType: 0, configFlags: 95, dpt: 'dpt1' },
{ id: 8, valueType: 0, configFlags: 87, dpt: 'dpt1' },
{ id: 9, valueType: 0, configFlags: 87, dpt: 'dpt1' },
{ id: 10, valueType: 14, configFlags: 87, dpt: 'dpt16' } ] }
bobaos>
В моем случае первый — датчик температуры, далее идет несколько однобайтных значения, несколько однобитных, и строковое значение dpt16.
Получаем значения:
bobaos> getDatapointValue -s 1 -n 10
{ service: 'GetDatapointValue.Res',
direction: 'response',
error: false,
start: 1,
number: 10,
payload:
[ { id: 1, state: 16, length: 2, value: },
{ id: 2, state: 0, length: 1, value: },
{ id: 3, state: 0, length: 1, value: },
{ id: 4, state: 0, length: 1, value: },
{ id: 5, state: 0, length: 1, value: },
{ id: 6, state: 0, length: 1, value: },
{ id: 7, state: 0, length: 1, value: },
{ id: 8, state: 0, length: 1, value: },
{ id: 9, state: 0, length: 1, value: },
{ id: 10,
state: 0,
length: 14,
value: } ] }
bobaos>
Значения возвращаются в непреобразованном виде, для преобразования можно использовать библиотеку knx-dpts-baos, поддерживающую типы от dpt1 до dpt18.
Устанавливаем значение:
bobaos> setDatapointValue -s 2 -v 128 -t dpt5
{ service: 'SetDatapointValue.Res',
direction: 'response',
error: false,
start: 2,
number: 0,
payload: null }
bobaos> getDatapointValue -s 2
{ service: 'GetDatapointValue.Res',
direction: 'response',
error: false,
start: 2,
number: 1,
payload: [ { id: 2, state: 16, length: 1, value: } ] }
bobaos>
bobaos-cli можно использовать для быстрого тестирования, установки, чтения данных с шины.
Использование в приложениях
Устанавливаем npm пакет:
npm install --save bobaos
Добавляем в наш скрипт:
const Baos = require('bobaos');
const app = new Baos({serialPort: {device: '/dev/ttyAMA0'}, debug: false});
// send requests after successful initial reset
app.on('open', () => {
app
.getDatapointDescription(1, 10)
.getParameterByte(1, 10)
.readDatapointFromBus(1, 2) // good
.readDatapointFromBus(1, 10) // error!
.getDatapointValue(1, 10)
.setDatapointValue(2, Buffer.alloc(1, 0xc0))
.getDatapointValue(2);
});
// listen to incoming events and responses
app.on('service', console.log);
Вывод должен выглядить примерно так:
{ service: 'GetParameterByte.Res',
error: false,
start: 1,
number: 10,
payload: }
{ service: 'SetDatapointValue.Res',
error: false,
start: 1,
number: 0,
payload: null }
{ service: 'GetDatapointValue.Res',
error: false,
start: 1,
number: 1,
payload: [ { id: 1, state: 4, length: 2, value: } ] }
....
....
Теперь мы вправе экспериментировать как нам угодно, создавать любые скрипты, использовать возможности nodejs, npm пакетов. Впереди еще много работы, но начало положено хорошее, что радует и мотивирует продолжать дальше.
Ссылки
- Репозиторий на github
- KNX BAOS Module 838 kBerry. По ссылке можно посмотреть информацию, скачать аппликацию для ETS.
- Описание протокола
- Официальная документация по установке на Raspberry Pi