В предыдущей статье мы познакомились с промышленным стандартом Modbus и встроили его поддержку в Arduino, осталось состыковать устройство с платформой OpenHAB.
В этот раз мы узнаем, как настроить плагин и интерфейс OpenHAB для работы с устройством, основы адресации и отладки протокола Modbus. В работе представлен эксперимент с исходным кодом плагина, а на страничке vk.com/myremoter можно обсудить открытый контроллер умного дома, который мы будем использовать в следующих экспериментах.
Но, давайте ещё раз посмотрим, какие преимущества даст нам применение стандарта Modbus?
Современный дом — сложное инженерное сооружение, где управление освещением не самая главная функция. Кроме датчиков в доме устанавливают системы кондиционирования и вентиляции, управления отоплением, дренажными насосами и скважинами. Такие задачи можно поручить специальному или промышленному контроллеру, в этом случае протокол Modbus поможет объединить все инженерные системы в единую сеть, а простой и недорогой контроллер, работающий на одной с ними шине, добавит дополнительный контроль и функционал, сэкономив немало средств. В пользу этого низкие требования к оборудованию, открытость стандарта, массовость его применения, хотя, быть может, основной секрет популярности Modbus его гибкость при стыковке программ и оборудования.
Ну что же, пора приступать к работе.
Запустим OpenHAB, выполнив C:openhabstart.bat и откроем веб-интерфейс по ссылке localhost:8080/openhab.app?sitemap=demo
Теперь качаем и устанавливаем com0com, запускаем Setup из меню программы, смотрим, для каких последовательных портов создана виртуальная пара (у меня это COM7 + COM8). Используем их для связи OpenHAB и симулятора.
Затем установим плагин Modbus Tcp Binding который обеспечит взаимодействие OpenHAB с modbus устройствами. Откроем страницу загрузки, скачаем Addons, распакуем org.openhab.binding.modbus-1.6.2.jar в папку C:openhabaddons.
После этого настроим связь плагина с устройством, для этого запустим конфигуратор OpenHAB и откроем файл openhab_default.cfg. Найдём раздел Modbus Binding и добавим в конец этого раздела следующую информацию, затем сохраним файл.
modbus:serial.slave1.connection=COM7
modbus:serial.slave1.id=1
modbus:serial.slave1.start=0
modbus:serial.slave1.length=4
modbus:serial.slave1.type=discrete
modbus:serial.slave2.connection=COM7
modbus:serial.slave2.id=1
modbus:serial.slave2.start=16
modbus:serial.slave2.length=4
modbus:serial.slave2.type=coil
modbus:serial.slave3.connection=COM7
modbus:serial.slave3.id=1
modbus:serial.slave3.start=2
modbus:serial.slave3.length=3
modbus:serial.slave3.type=input
modbus:serial.slave4.connection=COM7
modbus:serial.slave4.id=1
modbus:serial.slave4.start=5
modbus:serial.slave4.length=3
modbus:serial.slave4.type=holding
Настройки содержат 4 группы адресов сопоставленных с регистрами устройства. В каждой группе указан номер последовательного порта, modbus адрес контроллера, modbus адрес первого в группе регистра, количество регистров в группе и тип этих регистров. Группы адресов станут связующим звеном между элементами OpenHAB и устройством.
Теперь настроим связь OpenHAB с регистрами устройства. Откроем в конфигураторе файл demo.items и добавим в конец этого файла следующий код:
Group FF_Modbus "Modbus" (All)
Contact MB_DT0 "DT0 [MAP(en.map):%s]" (FF_Modbus){modbus="slave1:0"}
Contact MB_DT1 "DT1 [MAP(en.map):%s]" (FF_Modbus){modbus="slave1:1"}
Contact MB_DT2 "DT2 [MAP(en.map):%s]" (FF_Modbus){modbus="slave1:2"}
Contact MB_BTN "BTN [MAP(en.map):%s]" (FF_Modbus){modbus="slave1:3"}
Switch MB_CL16 "CL16" (FF_Modbus){modbus="slave2:0"}
Switch MB_CL17 "CL17" (FF_Modbus){modbus="slave2:1"}
Switch MB_CL18 "CL18" (FF_Modbus){modbus="slave2:2"}
Switch MB_LED "LED" (FF_Modbus){modbus="slave2:3"}
Number MB_INPT2 "INPT2[%d]" (FF_Modbus){modbus="slave3:0"}
Number MB_INPT3 "INPT3[%d]" (FF_Modbus){modbus="slave3:1"}
Number MB_INPT4 "INPT4[%d]" (FF_Modbus){modbus="slave3:2"}
Number MB_HOLD5 "HOLD5[%d]" (FF_Modbus){modbus="slave4:0"}
Number MB_HOLD6 "HOLD6[%d]" (FF_Modbus){modbus="slave4:1"}
Number MB_HOLD7 "HOLD7[%d]" (FF_Modbus){modbus="slave4:2"}
В первой строке определена группа FF_Modbus которая объединит добавляемые элементы. Каждый элемент задан его типом, названием, форматом текстовой надписи, списком групп в которых он состоит и настройками связи с устройством. Мы использовали элементы трёх типов – Contact, Switch, Number, а в настройках связи указали тип (modbus), имя одной из групп заданных в настройках плагина (например, slave1) и порядковый номер регистра в этой группе.
Наглядно представить взаимосвязь между регистрами контроллера, обычными и логическими адресами Modbus, группами регистров в настройках плагина и элементами OpenHAB нам поможет следующая таблица.
Настало время отобразить наши элементы в интерфейсе. Откроем в конфигураторе файл demo.sitemap и добавим в него описание двух фреймов:
Frame {
Group item=FF_Modbus icon="attic"
}
Frame {
Text item= MB_DT0
Text item= MB_DT1
Text item= MB_DT2
Text item= MB_BTN
Switch item= MB_CL16
Switch item= MB_CL17
Switch item= MB_CL18
Switch item= MB_LED
Text item= MB_INPT2
Text item= MB_INPT3
Text item= MB_INPT4
Setpoint item= MB_HOLD5 minValue=0 maxValue=50 step=1
Setpoint item= MB_HOLD6 minValue=0 maxValue=500 step=10
Setpoint item= MB_HOLD7 minValue=0 maxValue=500 step=100
}
Первый фрейм содержит группу FF_Modbus, он отобразит все элементы, входящие в неё.
Во втором фрейме каждый элемент имеет индивидуальные настройки интерфейса. В настройках указан тип виджета, связь с элементом и в некоторых случаях дополнительные параметры. Ознакомиться с принципами построения интерфейса OpenHAB можно в этом описании.
Попробуем протестировать созданную конфигурацию на симуляторе.
Скачаем симулятор RSsim 8.19 со страницы загрузки и распакуем его в папку C:arduino. У программы есть интересная особенность, она бесплатна, и доступны её исходные коды, но при этом она требует регистрации. В инструкции сказано, что для регистрации нужно открыть окно About MOD_RSsim, нажать кнопку «Register», в поле registration name ввести “Completely Free”, а в поле registration key “66840713”.
Запускаем симулятор, выполняем регистрацию, в поле Port выбираем MODBUS RS-232, нажимаем кнопку , в диалоге настроек последовательного порта указываем второй порт виртуальной пары (например, COM8), скорость 9600, 1 стоповый бит, нет контроля чётности.
Запускаем OpenHAB и открываем веб-интерфейс, щёлкаем по переключателям и смотрим, как изменяются значения в таблице Coil симулятора, затем открываем в симуляторе таблицу Digital Inputs, изменяем значения в регистрах с адресами 10001-10004 и контролируем изменение состояния контактов в веб-интерфейсе. После этого открываем таблицу Analog Inputs, вводим значения в регистры 30003-30005 и контролируем изменение значений в полях INPT2- INPT4. И в завершении задаём значения в полях HOLD5-HOLD7 и проверяем их соответствие в регистрах 40006-40008 симулятора. Вы уже обратили внимание, что в симуляторе использована логическая адресация. Для того, что бы окончательно не запутаться в регистрах, адресах и элементах используйте ранее приведённую таблицу.
На первый взгляд всё получилось, но если внимательно посмотреть на консоль OpenHAB заметно, что при каждом опросе контроллера, плагин отправляет в шину событие, даже если ничего не изменилось. Похоже, что для решения этой проблемы придётся изучить и доработать плагин.
Вопросы настройки среды разработки для OpenHAB отражены в этом руководстве.
В процессе работы изменения коснулись двух файлов ModbusGenericBindingProvider.java и ModbusBinding.java.
ModbusGenericBindingProvider содержит вложенный класс ModbusBindingConfig который хранит конфигурацию элемента, создадим в нём механизм сохранения текущего состояния.
Добавим в этот класс переменную
private State mb_itemState = UnDefType.NULL;
Исправим код функции
State getItemState() {
return mb_itemState;
}
И добавим функцию
void setItemState(State state) {
mb_itemState = state;
}
Класс ModbusBinding содержит два метода:
protected void internalUpdateItem(String slaveName, InputRegister[] registers, String itemName)
Который отправляет событие обновления в шину OpenHAB для данных типа holding.
protected void internalUpdateItem(String slaveName, BitVector coils, String itemName)
Который отправляет событие обновления в шину OpenHAB для данных типа coil.
Эти методы вызываются каждый раз после опроса ведомого устройства. Поправим их код таким образом, чтобы отправлять событие только в том случае, если данные изменились.
protected void internalUpdateItem(String slaveName, InputRegister[] registers,String itemName) {
for (ModbusBindingProvider provider : providers) {
if ( !provider.providesBindingFor(itemName) ) {
continue;
}
ModbusBindingConfig config = provider.getConfig(itemName);
if ( !config.slaveName.equals(slaveName)) {
continue;
}
String slaveValueType = modbusSlaves.get(slaveName).getValueType();
double rawDataMultiplier = modbusSlaves.get(slaveName).getRawDataMultiplier();
State newState = extractStateFromRegisters(registers, config.readRegister, slaveValueType);
/* receive data manipulation */
if (config.getItem() instanceof SwitchItem) {
newState = newState.equals(DecimalType.ZERO) ? OnOffType.OFF : OnOffType.ON;
}
if (( rawDataMultiplier != 1 ) && (config.getItem() instanceof NumberItem)) {
double tmpValue = (double)((DecimalType)newState).doubleValue() * rawDataMultiplier;
newState = new DecimalType( String.valueOf(tmpValue) );
}
State currentState = config.getItemState();
if (! newState.equals(currentState)) {
eventPublisher.postUpdate(itemName, newState);
config.setItemState(newState); //!!!
}
}
}
protected void internalUpdateItem(String slaveName, BitVector coils, String itemName) {
for (ModbusBindingProvider provider : providers) {
if (provider.providesBindingFor(itemName)) {
ModbusBindingConfig config = provider.getConfig(itemName);
if (config.slaveName.equals(slaveName)) {
boolean state = coils.getBit(config.readRegister);
State currentState = provider.getConfig(itemName).getItemState();
State newState = provider.getConfig(itemName).translateBoolean2State(state);
if (!newState.equals(currentState)) {
eventPublisher.postUpdate(itemName, newState);
config.setItemState(newState); //!!!
}
}
}
}
}
Исправленный плагин вместе с исходными кодами размещён в этом репозитории.
Останавливаем OpenHAB, скачиваем и копируем org.openhab.binding.modbus-1.6.2.jar в папку C:openhabaddons. Запускаем C:openhabstart.bat, открываем веб-интерфейс и проверяем работу еще раз. Теперь всё хорошо, события появляются только тогда, когда изменяется значение соответствующего регистра.
Осталось самое интересное — проверить взаимодействие OpenHAB непосредственно с контроллером из предыдущей статьи.
Подключаем USB кабель контроллера к компьютеру, смотрим, на какой порт встал переходник (например, COM6), останавливаем OpenHAB, открываем в конфигураторе файл openhab_default.cfg, в разделе Modbus Binding в параметрах связи для каждой группы регистров исправляем номер порта (например, modbus:serial.slave1.connection=COM6). Сохраняем файл, запускаем OpenHAB и открываем веб-интерфейс. Попробуем поменять значение в полях HOLD5…HOLD7 и СL16…СL18, при этом должно поменяться значение в соответствующем поле INPT2..INPT4 и DT0..DT2, затем нажмём на кнопку макета, при этом должно поменяться значение в поле BTN, щёлкнем по полю LED, при этом должен загореться или потухнуть светодиод.
Что мы получили в результате нашей работы:
1. состыковали modbus устройство с платформой OpenHAB;
2. познакомились с принципами построения интерфейса в OpenHAB;
3. познакомились с внутренней структурой плагина, это позволило исправить неточность в его работе.
Выводы:
На основе контроллера Arduino и платформы OpenHAB не трудно создать программно-аппаратное решение для автоматизации, например в системе Умный дом. Для дальнейших практических экспериментов попробуем определить основной функционал и требования к контроллеру и системе в целом, для обсуждения этого вопроса создана страничка открытого проекта vk.com/myremoter.
Автор: Borich