Беспроводной контроллер бытового кондиционера в OpenHAB по Modbus через RF24Network

1d48edb4b2b24079a0e2a7e7d5d7e584.png
После первой моей статьи про управление кондиционером с помощью контроллера прошло чуть больше 2х лет. За это время идея управлять кондиционером удалённо меня не оставляла и имела несколько перерождений. Главным условием было отсутствие каких-либо проводов до кондиционера.

То есть управление контроллером должно быть беспроводным.

Предыстория


Первым прототипом была Arduino UNO. Команды она принимала по UART и умела включать и выключать кондиционер. Т.к. практического смысла от подключенной к рабочему компьютеру ардуинки было мало, голова все время искала возможность подключить последнюю к домашнему серверу. Прямой видимости от сервера до виновника всех головоломок не было. Максимум это розетка с локалкой все у того же рабочего компа — благо он стоит почти напротив кондиционера. Ethernet-шилда в наличии не было. Но вспомнив что где-то в загашнике валяется не используемый уже давно dsl-модем D-link DSL-2500U как раз с одним портом на борту. Желание дать вторую жизнь железке подтолкнуло к гуглению, которое, в свою очередь, чудесным образом вывело на статью Превращаем ADSL-модем в Ethernet-шилд для Arduino/CraftDuino.

Забегая вперед и пропуская интереснейший процесс создания кастомной прошивки мне-таки удалось заставить модем слушать на нужном порту и «пробросить» через него UART. Таким образом я мог на домашнем сервере отправить команду на включение/выключение в порт на локальный адрес модема, который отправится на подключенную к нему ардуинку.

Но эта статья не об этом. Конечное решение использует протокол Modbus и беспроводную сеть RF24Network. А управляется все в OpenHAB.

За эти два года я успел заказать с поднебесной много всяких ништяков и большинство из них ждало своего часа. В этом списке был модуль NRF24L01+, купленный горстью для экспериментов. И лежал бы он дальше где-то в шкафу без дела, если бы однажды я не наткнулся на серию статей:


от Borich, за что ему спасибо!

Благодаря им я познакомился с протоколом Modbus. Но самое главное я открыл для себя OpenHAB — то, что я давно искал. Это меня и подтолкнуло приступить к реализации давней идей.

Поигравшись с примерами из статей я решил попробовать разнести контроллер и сервер используя вышеописанный ethernet-shield из модема. Такое решение позволяло достичь послтавленной задачи — шилд и контроллер располагались на рабочем столе, при этом не используя рабочий компьютер, в то же время шилд всегда подключен к локальной сети и доступен с домашнего сервера.

Но это решение было обречено на провал.
Для такой связки требовалась поддержка ModbusRtu over TCP для управляющей программы. К счастью программа modpoll, упомянутая в Arduino & Modbus поддерживала таковой. Радости не было предела — опрос контроллера в такой связке по локальному адресу модема 192.168.1.110 на порту 3000 успешно работал!

Нужно настроить подключение в OpenHAB. Но, к сожалению, Modbus Binding не умеет «RTU over TCP». На тот момент меня это не остановило — решено было допилить библиотеку ModbusRtu для ардуино c целью изменить формат пакета на TCP. Отличия оказались совсем небольшие и вот контроллер уже работает в связке OpenHAB-(ethernet)-модем-(UART)-контроллер.

Казалось бы, что еще нужно? Но проблема крылась в количестве Item в конфигурации. При тестировании я настроил только 1 Item и его опрос осуществлялся успешно. Но стоило добавить еще несколько — сразу же появлялись ошибки при передаче. Проблема, как мне казалось, была в ассинхронности TCP коннектора со стороны Modbus Binding. Т.е. мой шилд из модема не был рассчитан на несколько одновременных подключений и данные смешивались.

Мои попытки доработать Modbus Binding для возможности синхронизировать опросы Item'ов с одинаковым host-port-slaveID не привели к улучшению ситуации.

Решающим фактором поставить крест на таком решении стало то, что оно не было масштабируемым. При желании добавить в общую систему еще один контроллер потребует еще один такой шилд из модема. Да и беспроводностью здесь и не пахло, хотя удовлетворяло условиям задачи.

Идея


И вот тогда я окончательно утвердился в том, что нужно делать контроллер, доступный по воздуху. Не пропадать же модулям NRF24L01+!.. Правда это требовало как минимум два контроллера — один выполняет непосредственную роль, второй — роль маршрутизатора. Второй должен быть подключен к серверу и именно через него осуществяется беспроводная связь с остальными. Подключаясь к нему мы указываем ID подчиненного, которому предназначен пакет. Получается на одном последовательном порту доступно множество подчиненных устройств. Да — это беспроводная сеть на базе Modbus.

Modbus как нельзя кстати позволял строить сеть один мастер — много подчиненных. А библиотека RF24Network — сделать все беспроводным, да еще и с автоматической маршрутизацией между узлами сети.

Разработка библиотеки для Arduino


Для реализации такого решения потребовалось совсем немного доработать библиотеку Modbus-Master-Slave-for-Arduino для возможности пронаследоваться от нее и перегрузить пару методов. Моя реализация также обновлена(добавлен library.properties, файл библиотеки разбит на заголовок и тело) для последней на текущий момент Arduino IDE 1.6.5.

Библиотека Modbus-over-RF24Network-for-Arduino позволяет реализовать два возможных поведения — Proxy и Slave. Пример ModbusRF24Proxy фактически является реализацией «маршрутизатора» и не требует никаких доработок, кроме настроек нужных пинов.

Пример ModbusRF24Proxy
#include <RF24Network.h>
#include <RF24.h>
#include <SPI.h>
#include <ModbusRtu.h>
#include <ModbusRtuRF24.h>

#define stlPin  13  // номер выхода индикатора работы (расположен на плате Arduino)

// nRF24L01(+) radio attached using Getting Started board 
RF24 radio(9, 10);

// Network uses that radio
RF24Network network(radio);

// Address of our node
const uint16_t this_node = 0;

//Задаём последовательный порт, выход управления TX
ModbusRF24 proxy(network, 0, 0);
int8_t state = 0;
unsigned long tempus;

void setup() {
    // настраиваем входы и выходы
    io_setup();
    // настраиваем последовательный порт ведомого

    proxy.begin(57600);

    SPI.begin();
    radio.begin();
    network.begin(/*channel*/ 90, /*node address*/ this_node);

    // зажигаем светодиод на 100 мс
    tempus = millis() + 100;
    digitalWrite(stlPin, HIGH);
}

void io_setup() {
    digitalWrite(stlPin, HIGH);
    pinMode(stlPin, OUTPUT);
}

void loop() {

    // Pump the network regularly
    network.update();

    // обработка сообщений
    state = proxy.proxy();

    // если получили пакет без ошибок - зажигаем светодиод на 50 мс 
    if (state > 4) {
        tempus = millis() + 50;
        digitalWrite(stlPin, HIGH);
    }
    if (millis() > tempus) digitalWrite(stlPin, LOW);
}



Маршрутизатор или прокси, использует особый формат конструктора:

//Задаём последовательный порт, выход управления TX
ModbusRF24 proxy(network, 0, 0);


и функцию

proxy.proxy();


для обработки входящих пакетов по последовательному порту, отправки их в сеть RF24Network, получения ответа из сети, отправки результата обратно в последовательный порт.

В данной реализации прокси имеет RF24Network-адрес равный нулю:

// Address of our node
const uint16_t this_node = 0;


— т.е. это корневое устройство сети. При необходимости, положение в топологии контроллера-прокси можно изменить.

Пример ModbusRF24Slave
реализует пример из Arduino & Modbus:
#include <RF24Network.h>
#include <RF24.h>
#include <SPI.h>
#include <ModbusRtu.h>
#include <ModbusRtuRF24.h>

#define ID   1      // адрес ведомого
#define btnPin  2   // номер входа, подключенный к кнопке
#define ledPin  7  // номер выхода светодиода

// nRF24L01(+) radio attached using Getting Started board 
RF24 radio(9, 10);

// Network uses that radio
RF24Network network(radio);

// Address of our node
const uint16_t this_node = ID;

//Задаём ведомому адрес
ModbusRF24 slave(network, ID);

// массив данных modbus
uint16_t au16data[11];

void io_setup() {
    digitalWrite(ledPin, LOW);
    pinMode(ledPin, OUTPUT);
    pinMode(btnPin, INPUT);
}

void io_poll() {
    //Копируем Coil[1] в Discrete[0]
    au16data[0] = au16data[1];
    //Выводим значение регистра 1.3 на светодиод 
    digitalWrite(ledPin, bitRead(au16data[1], 3));
    //Сохраняем состояние кнопки в регистр 0.3
    bitWrite(au16data[0], 3, digitalRead(btnPin));
    //Копируем Holding[5,6,7] в Input[2,3,4]
    au16data[2] = au16data[5];
    au16data[3] = au16data[6];
    au16data[4] = au16data[7];
    //Сохраняем в регистры отладочную информацию
    au16data[8] = slave.getInCnt();
    au16data[9] = slave.getOutCnt();
    au16data[10] = slave.getErrCnt();
}

void setup() {
    // настраиваем входы и выходы
    io_setup();

    Serial.begin(57600);
    Serial.println("RF24Network/examples/modbus_slave/");

    SPI.begin();
    radio.begin();
    network.begin(/*channel*/ 90, /*node address*/ this_node);
}

void loop() {

    // Pump the network regularly
    network.update();

    if (network.available()) {
        slave.poll(au16data, 11);
    }
    //обновляем данные в регистрах Modbus и в пользовательской программе
    io_poll();
}


Конструктор в этом случае уже другой:

//Задаём ведомому адрес
ModbusRF24 slave(network, ID);

Структура регистров Modbus


После первых удачных попыток управления кондиционером с возможностью только включить и выключить мой аппетит только увеличивался. Теперь уже этого было мало и раз в руках у меня функционал OpenHAB в мобильном приложении, то делать он должен как миминум весь функционал родного пульта управления.
Это значит вот такой список возможностей как минимум:

  • индикация текущего состояния
  • включение и выключение кондиционера, отдельных режимов(O2, ионизация, тихий режим);
  • выбор текущего режима(авто, обогрев, охлаждение, осушение, вентилятор);
  • указание температуры и скорости вентилятора для каждого режима. Для режима вентилятора только скорость;
  • настройка вертикальной шторки(авто, 0°, 15°, 30°, 45°, 60°);
  • настройка горизонтальной шторки(авто, "| |", "/ /", "/ |", "| \", "\ \" );
  • настройка времени(час, минута);
  • настройка таймера включения;
  • настройка таймера выключения;
  • настройка времени включения(час, минута);
  • настройка времени выключения(час, минута);


В процессе разработки структура регистров менялась у меня много раз. Текуща версия ниже под спойлером.

Регистры Modbus
Type Byte Bit Name Описание
Bit RO 0 0 CFG_OXYGEN 1 — Есть кислородный режим
Bit RO 1 CFG_ION 1 — Есть режим ионизации
Bit RO 2 CFG_QUIET 1 — Есть тихий режим
Bit RO 3 CFG_TIMER 1 — Есть вкл/выкл по расписанию времени
Bit RO 4 CFG_DELAY 1 — Есть отложенный вкл/выкл
Bit RO 5 CFG_SWING 1 — Есть управление шторкой
Bit RO 6 CFG_SWINGH 1 — Есть управление горизонтальной шторкой
Bit RO 7 CFG_SWINGV 1 — Есть управление вертикальной шторкой
Bit RO 8 CFG_CLOCK 1 — Есть часы
Bit RO 9
Bit RO 10
Bit RO 11 CFG_AUTO 1 — Есть режим AUTO
Bit RO 12 CFG_COOL 1 — Есть режим COOL
Bit RO 13 CFG_HEAT 1 — Есть режим HEAT
Bit RO 14 CFG_DRY 1 — Есть режим DRY
Bit RO 15 CFG_FAN 1 — Есть режим FAN
Integer RO 1 CFG_TEMP Мин. и макс. температуры: Min + Max*256
Integer RO 2 CFG_FAN_SPEED Макс скорость FAN
Bit RO 3 0 STATE_POWER Кондиционер:0 — выкл, 1 — вкл
Bit RO 1 STATE_OXYGEN Кислород:0 — выкл, 1 — вкл
Bit RO 2 STATE_ION Ионизация:0 — выкл, 1 — вкл
Bit RO 3 STATE_QUIET Тихий:0 — выкл, 1 — вкл
Bit RO 4 STATE_TIMER Таймер:0 — выкл, 1 — вкл
Bit RW 8 CONTROL_POWER
Bit RW 9 CONTROL_OXYGEN
Bit RW 10 CONTROL_ION
Bit RW 11 CONTROL_QUIET
Integer RO 4 RTC_HR_MI 0x1308
Integer RW 5 RTCW_HR_MI 0x1308
Integer RO 6 TEMPERATURE1 Температура окружающая. INT16, сотые доли градуса
Integer RO 7 TEMPERATURE2 Температура сопла. INT16, сотые доли градуса
Bit RW 8 0 MODE_AUTO
Bit RW 1 MODE_COOL
Bit RW 2 MODE_HEAT
Bit RW 3 MODE_DRY
Bit RW 4 MODE_FAN
Integer RW 9 TEMP_AUTO Температура AUTO
Integer RW 10 TEMP_COOL Температура COOL
Integer RW 11 TEMP_HEAT Температура HEAT
Integer RW 12 TEMP_DRY Температура DRY
Integer RW 13 FAN_AUTO Скорость AUTO. 0: Auto
Integer RW 14 FAN_COOL Скорость COOL. 0: Auto
Integer RW 15 FAN_HEAT Скорость HEAT. 0: Auto
Integer RW 16 FAN_DRY Скорость DRY. 0: Auto
Integer RW 17 FAN_SPEED Скорость FAN. 0: Auto
Bit RW 18 0 SWING_AUTO Автовращение вертикальное
Bit RW 1 SWINGV_0 Вертикальная шторка угол 0°
Bit RW 2 SWINGV_15 Вертикальная шторка угол 15°
Bit RW 3 SWINGV_30 Вертикальная шторка угол 30°
Bit RW 4 SWINGV_45 Вертикальная шторка угол 45°
Bit RW 5 SWINGV_60 Вертикальная шторка угол 60°
Bit RW 19 0 SWINGH_AUTO Автовращение горизонтальное
Bit RW 1 SWINGH_VV Горизонтальная шторка |  |
Bit RW 2 SWINGH_LL Горизонтальная шторка /  /
Bit RW 3 SWINGH_LV Горизонтальная шторка /  |
Bit RW 4 SWINGH_VR Горизонтальная шторка |  \
Bit RW 5 SWINGH_RR Горизонтальная шторка \  \
Bit RW 20 0 TIMER_ON
Bit RW 1 TIMER_OFF
Integer RW 21 TIME_ON_HOUR Час включения
Integer RW 22 TIME_ON_MINUTE Минута включения
Integer RW 23 TIME_OFF_HOUR Час выключения
Integer RW 24 TIME_OFF_MINUTE Минута выключения
Integer RW 25 DS18B20_ENV Адрес. Окружение
Integer RW 26 DS18B20_NOZ Адрес. Сопло


Регистры организованы таким образом, что весь массив данных инициализируется из EEPROM при старте контроллера.
Первые 3 регистра(CFG_*) сожержат конфигурацию возможностей, никогда не меняются и инициализируются прошивкой EEPROM.
Регистры 3-7 всегда отображают текущее состояние контроллера. Старшие биты регистра 3 используются для смены состояния. Их изменения инициируют включение/выключение кондиционера и специальных режимов. После исполнения команды, значения младших бит этого регистра копируются в старшие.
Регистр 4 содержит текущее время контроллера, которое читается из RTC. Значение сохраняется в BCD формате, чтобы при отображении регистра в шестнадцатеричном исчислении время читалось как есть — 12:34 это 0x1234.
Регистр 5 используется для смены времени RTC.
Регистры 6-7 содержат температуру с датчиков DS18B20. Значение содержит знаковое целое и равно T*100, т.е. 25.67°С=2567. Максимум предусмотрено 2 датчика, но кол-во можно легко изменить расширив таблицу регистров для хранения адресов датчиков и их температур.
В регистрах 25-26 хранятся последние 2 байта адреса датчиков. При смене датчиков нужно обнулить соответствующий регистр адреса. При обнаружении нового датчика его адрес проверяется на наличие в регистрах 25-26. Если адрес присутствует в таблице, значение температуры датчика заносится в соотв регистр 6-7. Если адреса нет в таблице и в таблице есть нулевые ячейки, текущий адрес датчика прописывается в свободной ячейке.
Регистры 8-26 при изменении пользователем сохраняются в EEPROM.

Железки


Аппаратная часть состоит из следующих компонент:

  1. Arduino Pro Mini.
    Китайский вариант
    Arduino Pro Mini NEW
  2. NRF24L01+ — беспроводной модуль 2.4ГГц
  3. LM1117-3.3 — стабилизатор 3.3В для NRF24L01+
  4. DS1302 — RTC
  5. кварц 32768кГц для RTC
  6. DS18B20 — датчик температуры. 2шт
  7. Мелочевка — оптопары, резисторы, конденсатор

b4f6f2357e044c78a3472d4d6b25ad07.jpg
0256de902fed444682fc2e16f3e9be88.jpg
e456044aabe0499c9b8016d908411434.jpg
3f2818d9af814ffba67ed9e271c1450d.jpg
f76385124e4e4c2ca573b268365f5e40.jpg
be6dedb3c93a40fe9841a17dead1b723.jpg
80a50c1b7e154c87ae07296e704d6674.jpg
a5bf6196ee764f90bf535df517477363.jpg

Обратная связь с кондиционером реализована подключением к соответсвующим светодиодам оптопар. Таким образом обеспечена гальваническая развязка с электросхемой кондиционера. Ток входов для оптопар был экспериментально подобран с помощью сопротивлений таким образом, чтобы и выход оптопары открывался и яркость основного светодиода на кондиционере не падала.

ИК-светодиод был установлен рядом с ИК-приемником кондиционера.

Питание контроллера осуществляется от mini-USB зарядки от какого-то китайского чуда. Из корпуса зарядки были демонтированы китайские контакты сетевого напряжения, выведены провода. Сама зарядка поселилась в недрах корпуса кондиционера, подключена на вход 220 параллельно с ним. Контроллер подключается к зарядке обычным кабелем USB A-B.

Контроллер «маршрутизатора» построен на базе Arduino Mega2560 и NRF24L01+ с отдельным LM1117-3.3. Кроме отдельного питания 3.3 к беспроводному модулю подключен электролит(у меня нашелся на 470мкф*16в) на ноги питания. Как известно внутренняя шина питания меги2560 на 3.3в очень шумная и модуль отказывался передавать данные, хотя и отвечал корректно. Но даже с отдельным питанием без конденсатора связь была очень нестабильной.
Чтобы мега2560 не сбрасывалась при открытии порта через USB на пин сброса подключен 10мкф электролит.

OpenHAB


В статье Arduino & OpenHAB описана особенность плагина Modbus Binding, что при каждом опросе контроллера, плагин отправляет в шину событие, даже если ничего не изменилось. Я последовал примеру и доработал плагин.

Настройка плагина Modbus Binding
#Состояние
modbus:serial.ac_hall_state.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_state.id=1
modbus:serial.ac_hall_state.start=48
modbus:serial.ac_hall_state.length=5
modbus:serial.ac_hall_state.type=discrete

#Управление
modbus:serial.ac_hall_power.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_power.id=1
modbus:serial.ac_hall_power.start=56
modbus:serial.ac_hall_power.length=4
modbus:serial.ac_hall_power.type=coil

#Часы
modbus:serial.ac_hall_rtc.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_rtc.id=1
modbus:serial.ac_hall_rtc.start=4
modbus:serial.ac_hall_rtc.length=1
modbus:serial.ac_hall_rtc.type=holding

#Температура датчики
modbus:serial.ac_hall_temperature.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_temperature.id=1
modbus:serial.ac_hall_temperature.start=6
modbus:serial.ac_hall_temperature.length=2
modbus:serial.ac_hall_temperature.type=holding
modbus:serial.ac_hall_temperature.valuetype=int16

#Режим
modbus:serial.ac_hall_mode.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_mode.id=1
modbus:serial.ac_hall_mode.start=8
modbus:serial.ac_hall_mode.length=1
modbus:serial.ac_hall_mode.type=holding

#температура режима
modbus:serial.ac_hall_temp.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_temp.id=1
modbus:serial.ac_hall_temp.start=9
modbus:serial.ac_hall_temp.length=4
modbus:serial.ac_hall_temp.type=holding

#Скорость режима
modbus:serial.ac_hall_fan.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_fan.id=1
modbus:serial.ac_hall_fan.start=13
modbus:serial.ac_hall_fan.length=5
modbus:serial.ac_hall_fan.type=holding

#Шторки
modbus:serial.ac_hall_swing.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_swing.id=1
modbus:serial.ac_hall_swing.start=18
modbus:serial.ac_hall_swing.length=2
modbus:serial.ac_hall_swing.type=holding

#Таймеры
modbus:serial.ac_hall_timer.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_timer.id=1
modbus:serial.ac_hall_timer.start=320
modbus:serial.ac_hall_timer.length=2
modbus:serial.ac_hall_timer.type=coil

#Время таймеров
modbus:serial.ac_hall_timer_time.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_timer_time.id=1
modbus:serial.ac_hall_timer_time.start=21
modbus:serial.ac_hall_timer_time.length=4
modbus:serial.ac_hall_timer_time.type=holding

#Адреса датчиков DS18B20
modbus:serial.ac_hall_ds18b20.connection=/dev/ttyACM0:57600:8:none:1:rtu
modbus:serial.ac_hall_ds18b20.id=1
modbus:serial.ac_hall_ds18b20.start=25
modbus:serial.ac_hall_ds18b20.length=2
modbus:serial.ac_hall_ds18b20.type=holding


Настройки items
Contact AC_HALL_STATE_POWER             "AC_HALL_STATE_POWER [MAP(air_cond.map):%s]"    (){modbus="ac_hall_state:0"}
Contact AC_HALL_STATE_OXYGEN            "AC_HALL_STATE_OXYGEN [MAP(air_cond.map):%s]"   (){modbus="ac_hall_state:1"}
Contact AC_HALL_STATE_ION               "AC_HALL_STATE_ION [MAP(air_cond.map):%s]"      (){modbus="ac_hall_state:2"}
Contact AC_HALL_STATE_QUIET             "AC_HALL_STATE_QUIET [MAP(air_cond.map):%s]"    (){modbus="ac_hall_state:3"}
Contact AC_HALL_STATE_TIMER             "Таймер[MAP(air_cond.map):%s]"                  (){modbus="ac_hall_state:4"}

Switch  AC_HALL_CONTROL_POWER           "Кондиционер"                   <climate>       (){modbus="ac_hall_power:0"}
Switch  AC_HALL_CONTROL_OXYGEN          "Генератор O2"                                  (){modbus="ac_hall_power:1"}
Switch  AC_HALL_CONTROL_ION             "Ионизация"                                     (){modbus="ac_hall_power:2"}
Switch  AC_HALL_CONTROL_QUIET           "Тихий режим"                                   (){modbus="ac_hall_power:3"}

Number  AC_HALL_RTC                     "RTC[%x]"                                       (){modbus="ac_hall_rtc:0"}
String  AC_HALL_RTC_S                   "Время контроллера[%s]"         <clock>         ()

Group   gAC_HALL_TEMPERATURE            "Living Room temp"

Number  AC_HALL_TEMPERATURE_ENV         "Комната[%d]"                                   (){modbus="ac_hall_temperature:0"}
Number  AC_HALL_TEMPERATURE_NOZ         "Поток[%d]"                                     (){modbus="ac_hall_temperature:1"}
Number  AC_HALL_TEMPERATURE_ENVF        "Комната [%.2f °C]"     <temperature>           (gAC_HALL_TEMPERATURE)
Number  AC_HALL_TEMPERATURE_NOZF        "Поток [%.2f °C]"       <temperature>           (gAC_HALL_TEMPERATURE)

Number  AC_HALL_DS18B20_ENV             "ENV[%x]"                                       (){modbus="ac_hall_ds18b20:0"}
Number  AC_HALL_DS18B20_NOZ             "NOZZLES[%x]"                                   (){modbus="ac_hall_ds18b20:1"}

Number  AC_HALL_MODE                    ""                                              (){modbus="ac_hall_mode:0"}

Number  AC_HALL_TEMP_AUTO               "Температура[%d °C]"    <temperature>           (){modbus="ac_hall_temp:0"}
Number  AC_HALL_TEMP_COOL               "Температура[%d °C]"    <temperature>           (){modbus="ac_hall_temp:1"}
Number  AC_HALL_TEMP_HEAT               "Температура[%d °C]"    <temperature>           (){modbus="ac_hall_temp:2"}
Number  AC_HALL_TEMP_DRY                "Температура[%d °C]"    <temperature>           (){modbus="ac_hall_temp:3"}

Number  AC_HALL_FAN_AUTO                "Скорость[%d]"                                  (){modbus="ac_hall_fan:0"}
Number  AC_HALL_FAN_COOL                "Скорость[%d]"                                  (){modbus="ac_hall_fan:1"}
Number  AC_HALL_FAN_HEAT                "Скорость[%d]"                                  (){modbus="ac_hall_fan:2"}
Number  AC_HALL_FAN_DRY                 "Скорость[%d]"                                  (){modbus="ac_hall_fan:3"}
Number  AC_HALL_FAN_SPEED               "Скорость[%d]"                                  (){modbus="ac_hall_fan:4"}

Number  AC_HALL_SWINGV                  ""                                              (){modbus="ac_hall_swing:0"}
Number  AC_HALL_SWINGH                  ""                                              (){modbus="ac_hall_swing:1"}

Switch  AC_HALL_TIMER_ON                "Таймер включения"              <clock>         (){modbus="ac_hall_timer:0"}
Switch  AC_HALL_TIMER_OFF               "Таймер выключения"             <clock>         (){modbus="ac_hall_timer:1"}

Number  AC_HALL_TIME_ON_HR              "Час[%02d]"                                     (){modbus="ac_hall_timer_time:0"}
Number  AC_HALL_TIME_ON_MI              "Минута[%02d]"                                  (){modbus="ac_hall_timer_time:1"}
Number  AC_HALL_TIME_OFF_HR             "Час[%02d]"                                     (){modbus="ac_hall_timer_time:2"}
Number  AC_HALL_TIME_OFF_MI             "Минута[%02d]"                                  (){modbus="ac_hall_timer_time:3"}


Правила используются для преобразования целочисленных температур в вещественные, а также для форматирования времени контроллера к виду HH:MM.

Настройки rules
import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import java.io.File

rule "Update AC_HALL ENV temp"
        when
                Item AC_HALL_TEMPERATURE_ENV received update
        then
                var Number T = AC_HALL_TEMPERATURE_ENV.state as DecimalType
                var Number H = T/100
                postUpdate(AC_HALL_TEMPERATURE_ENVF, H)
end
rule "Update AC_HALL NOZZLES temp"
        when
                Item AC_HALL_TEMPERATURE_NOZ received update
        then
                var Number T = AC_HALL_TEMPERATURE_NOZ.state as DecimalType
                var Number H = T/100
                postUpdate(AC_HALL_TEMPERATURE_NOZF, H)
end
rule "Update AC_HALL_RTC clock"
        when
                Item AC_HALL_RTC received update
        then
                var Number T = AC_HALL_RTC.state as DecimalType
                var H = T.intValue / 256
                var M = T.intValue % 256
                var S = String::format("%02x:%02x",H,M)
                postUpdate(AC_HALL_RTC_S, S)
end


Настроки sitemaps
sitemap demo label="Demo House"{
        Frame label="HOME"{
                Text label="Кондиционер зал" icon="ac_cond"{
                        Frame label="Управление" {
                                Switch item= AC_HALL_CONTROL_POWER labelcolor=[AC_HALL_STATE_POWER==OPEN="blue"]
                                Switch item= AC_HALL_CONTROL_OXYGEN labelcolor=[AC_HALL_STATE_OXYGEN==OPEN="blue"]
                                Switch item= AC_HALL_CONTROL_ION labelcolor=[AC_HALL_STATE_ION==OPEN="blue"]
                                Switch item= AC_HALL_CONTROL_QUIET labelcolor=[AC_HALL_STATE_QUIET==OPEN="blue"]
                                Text item=AC_HALL_STATE_TIMER labelcolor=[AC_HALL_STATE_TIMER==OPEN="blue"] icon="clock-on"
                                Text item=AC_HALL_RTC_S
                                Text item=AC_HALL_TEMPERATURE_ENVF
                                Text item=AC_HALL_TEMPERATURE_NOZF
                        }

                        Frame label="Режим"{
                                Selection item=AC_HALL_MODE label="Режим" mappings=[1=AUTO, 2=COOL, 4=HEAT, 8=DRY, 16=FAN]
                                Text item=AC_HALL_TEMP_AUTO visibility=[AC_HALL_MODE==1]
                                Text item=AC_HALL_TEMP_COOL visibility=[AC_HALL_MODE==2]
                                Text item=AC_HALL_TEMP_HEAT visibility=[AC_HALL_MODE==4]
                                Text item=AC_HALL_TEMP_DRY visibility=[AC_HALL_MODE==8]

                                Text item=AC_HALL_FAN_AUTO visibility=[AC_HALL_MODE==1]
                                Text item=AC_HALL_FAN_COOL visibility=[AC_HALL_MODE==2]
                                Text item=AC_HALL_FAN_HEAT visibility=[AC_HALL_MODE==4]
                                Text item=AC_HALL_FAN_DRY visibility=[AC_HALL_MODE==8]
                                Text item=AC_HALL_FAN_SPEED visibility=[AC_HALL_MODE==16]

                                Selection item=AC_HALL_SWINGV label="Вертикальное" mappings=[1=AUTO, 2="0°", 4="15°", 8="30°", 16="45°", 32="60°"]
                                Selection item=AC_HALL_SWINGH label="Горизонтальное" mappings=[1=AUTO, 4="/   /", 8="/   |", 2="|   |", 16="|   \\", 32="\\   \\"]

                                Text label="Настройки" icon="settings"{
                                        Frame label="AUTO"{
                                                Setpoint item=AC_HALL_TEMP_AUTO minValue=16 maxValue=30 step=1
                                                Switch   item=AC_HALL_FAN_AUTO mappings=[0=AUTO, 1="1", 2="2", 3="3", 4="4", 5="5"]
                                        }
                                        Frame label="COOL"{
                                                Setpoint item=AC_HALL_TEMP_COOL minValue=16 maxValue=30 step=1
                                                Switch   item=AC_HALL_FAN_COOL mappings=[0=AUTO, 1="1", 2="2", 3="3", 4="4", 5="5"]
                                        }
                                        Frame label="HEAT"{
                                                Setpoint item=AC_HALL_TEMP_HEAT minValue=16 maxValue=30 step=1
                                                Switch   item=AC_HALL_FAN_HEAT mappings=[0=AUTO, 1="1", 2="2", 3="3", 4="4", 5="5"]
                                        }
                                        Frame label="DRY"{
                                                Setpoint item=AC_HALL_TEMP_DRY minValue=16 maxValue=30 step=1
                                                Switch   item=AC_HALL_FAN_DRY mappings=[0=AUTO, 1="1", 2="2", 3="3", 4="4", 5="5"]
                                        }
                                        Frame label="FAN"{
                                                Switch item=AC_HALL_FAN_SPEED mappings=[0=AUTO, 1="1", 2="2", 3="3", 4="4", 5="5"]
                                        }
                                        Frame label="Прочее"{
                                                Text item=AC_HALL_DS18B20_ENV
                                                Text item=AC_HALL_DS18B20_NOZ
                                        }
                                }
                        }
                        Frame label="Таймер" {
                                Switch item= AC_HALL_TIMER_ON labelcolor=[AC_HALL_STATE_TIMER==OPEN="blue"]
                                Setpoint item=AC_HALL_TIME_ON_HR minValue=0 maxValue=23 step=1
                                Setpoint item=AC_HALL_TIME_ON_MI minValue=0 maxValue=50 step=5

                                Switch item= AC_HALL_TIMER_OFF labelcolor=[AC_HALL_STATE_TIMER==OPEN="blue"]
                                Setpoint item=AC_HALL_TIME_OFF_HR minValue=0 maxValue=23 step=1
                                Setpoint item=AC_HALL_TIME_OFF_MI minValue=0 maxValue=50 step=5
                        }
                }
                Text item=AC_HALL_RTC_S
                Text item=AC_HALL_TEMPERATURE_ENVF{
                        Frame label="Последний час"{
                                Chart item=gAC_HALL_TEMPERATURE period=h refresh=60000
                        }
                        Frame label="Последние 4 часа" {
                                Chart item=gAC_HALL_TEMPERATURE period=4h refresh=600000
                        }
                }
        }
}

Результат выглядит примерно так через браузер:
5c01290f365b47399e9b16123c21f6fa.png63445b94b4ab47349482b85667313e10.png09c8cb8ffb8240349ce57e8c7ccc6629.png

Заключение


Полученным результатом я доволен как слон. Управление кондиционером сейчас доступно мне везде где есть мобильный интернет или WiFI. Используя VPN-клиент на смарфоне мне становится доступен OpenHAB на домашнем сервере как через браузер, так и через мобильное приложение.
Беспроводное решение позволило тесно интегрировать контроллер с кондиционером.
Наличие обратной связи дает уверенность, что отправленная команда принята кондиционером, а датчики температуры наглядно это демонстрируют — через несколько секунд можно наблюдать изменение показаний температуры на выходе из кондиционера. Ну а через несколько минут — и окружающей температуры.
Интересно было наблюдать за профилем работы кондиционера при приближении к заданным условиям.

Несомненно это будет не единственный беспроводной контроллер который я планирую использовать.

В планах есть задействование ИК-приемника самого кондиционера, чтобы читать команды родного пульта для актуализации настроек в контроллере. Тем более, что сам ИК-приемник уже подключен через оптопару к контроллеру.

Дочитавшим до конца отдельное спасибо!

Ссылки


  1. Arduino library ModbusRtu Modbus-Master-Slave-for-Arduino
  2. Arduino library ModbusRtuRF24 Modbus-over-RF24Network-for-Arduino

© Geektimes