[Из песочницы] Bobaos — доступ к шине KNX TP/UART c Raspberry Pi

Если вы не знакомы с системами автоматизации и стандартом KNX, то нужную информацию можете получить в гугле или с официальных сайтов. Если же вы работаете с данным стандартом — то многие вещи вам будут понятны, и, возможно, вас, как и меня, давно интересует вопрос каким же образом можно получить доступ к физической шине KNX, минуя IP роутеры.


Далее я покажу каким образом я решил для себя эту задачу, используя Raspberry Pi и модуль KNX BAOS 838 kBerry от Weinzierl.


suwrisllhke1azam7kq_jirkfqu.jpeg


Мотивация


Стандарт 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       |  Максимальное количество элементов
...          
И далее в зависимости от сервиса. Эти четыре поля присутствуют во всех сообщениях.                                                                                                                                                                             

Схема коммуникации:


  1. Наше приложение (клиент) отправляет reset request.
  2. BAOS 838 (сервер) отправляет acknowledge.
  3. Клиент отправляет первый запрос (для примера GetDatapointDescription.Req). Нечетный фрейм.
  4. Сервер получает запрос, отправляет acknowledge.
  5. Сервер отправляет ответ на запрос.
  6. Клиент отправляет подтверждение 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 пакетов. Впереди еще много работы, но начало положено хорошее, что радует и мотивирует продолжать дальше.


Ссылки


  1. Репозиторий на github
  2. KNX BAOS Module 838 kBerry. По ссылке можно посмотреть информацию, скачать аппликацию для ETS.
  3. Описание протокола
  4. Официальная документация по установке на Raspberry Pi

© Habrahabr.ru