Bobaos — доступ к шине KNX TP/UART c Raspberry Pi
- вторник, 16 января 2018 г. в 03:15:22
Если вы не знакомы с системами автоматизации и стандартом KNX, то нужную информацию можете получить в гугле или с официальных сайтов. Если же вы работаете с данным стандартом — то многие вещи вам будут понятны, и, возможно, вас, как и меня, давно интересует вопрос каким же образом можно получить доступ к физической шине KNX, минуя IP роутеры.
Далее я покажу каким образом я решил для себя эту задачу, используя Raspberry Pi и модуль KNX BAOS 838 kBerry от Weinzierl.
Стандарт KNX подразумевает собой децентрализованную систему, работоспособную в отсутствии центрального контроллера ПЛК. Децентрализация обеспечивает надежность, но отсутствие ПЛК в системе лишает нас необходимой нам гибкости. Можно использовать различные IP шлюзы, и общаться по UPD с сервера, но это решение не рассматривал, поскольку доступ напрямую к шине выглядит надежнее.
На рынке можно найти решения для данной задачи, со следующими из них я плотно работал:
Некоторые решения хороши, но отталкивает цена, плюс хотелось бы использовать больше гибкости в программировании.
Задача решилась с использованием 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 | Максимальное количество элементов
...
И далее в зависимости от сервиса. Эти четыре поля присутствуют во всех сообщениях.
Схема коммуникации:
Модуль подключается к GPIO контактам платы Raspberry Pi, более подробно можно узнать по ссылкам в конце статьи.
Как и любое KNX устройство, BAOS 838 module конфигурируется через ETS. Аппликация в данном случае используется "KNX BAOS 830", ее можно скачать с официального сайта производителя. Устройство поддерживает до 1000 групповых адресов.
Первым делом устанавливаем 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
Подготовка закончена, переходим к нашему проекту.
Для тестирования можно использовать простой интерфейс для командной строки. Устанавливаем:
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: <Buffer 0c bf> },
{ id: 2, state: 0, length: 1, value: <Buffer 00> },
{ id: 3, state: 0, length: 1, value: <Buffer 00> },
{ id: 4, state: 0, length: 1, value: <Buffer 00> },
{ id: 5, state: 0, length: 1, value: <Buffer 00> },
{ id: 6, state: 0, length: 1, value: <Buffer 00> },
{ id: 7, state: 0, length: 1, value: <Buffer 00> },
{ id: 8, state: 0, length: 1, value: <Buffer 00> },
{ id: 9, state: 0, length: 1, value: <Buffer 00> },
{ id: 10,
state: 0,
length: 14,
value: <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00> } ] }
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: <Buffer 80> } ] }
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: <Buffer 01 03 05 07 09 0b 0a 00 00 00> }
{ 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: <Buffer 0c fb> } ] }
....
....
Теперь мы вправе экспериментировать как нам угодно, создавать любые скрипты, использовать возможности nodejs, npm пакетов. Впереди еще много работы, но начало положено хорошее, что радует и мотивирует продолжать дальше.