
После первой моей статьи про управление кондиционером с помощью контроллера прошло чуть больше 2х лет. За это время идея управлять кондиционером удалённо меня не оставляла и имела несколько перерождений. Главным условием было отсутствие каких-либо проводов до кондиционера.
То есть управление контроллером должно быть беспроводным.
Предыстория
Первым прототипом была Arduino UNO. Команды она принимала по UART и умела включать и выключать кондиционер. Т.к. практического смысла от подключенной к рабочему компьютеру ардуинки было мало, голова все время искала возможность подключить последнюю к домашнему серверу. Прямой видимости от сервера до виновника всех головоломок не было. Максимум это розетка с локалкой все у того же рабочего компа — благо он стоит почти напротив кондиционера. Ethernet-шилда в наличии не было. Но вспомнив что где-то в загашнике валяется не используемый уже давно dsl-модем D-link DSL-2500U как раз с одним портом на борту. Желание дать вторую жизнь железке подтолкнуло к гуглению, которое, в свою очередь, чудесным образом вывело на статью Превращаем ADSL-модем в Ethernet-шилд для Arduino/CraftDuino.
Забегая вперед и пропуская интереснейший процесс создания кастомной прошивки мне-таки удалось заставить модем слушать на нужном порту и «пробросить» через него UART. Таким образом я мог на домашнем сервере отправить команду на включение/выключение в порт на локальный адрес модема, который отправится на подключенную к нему ардуинку.
Но эта статья не об этом. Конечное решение использует протокол Modbus и беспроводную сеть RF24Network. А управляется все в OpenHAB.
За эти два года я успел заказать с поднебесной много всяких ништяков и большинство из них ждало своего часа. В этом списке был модуль NRF24L01+, купленный горстью для экспериментов. И лежал бы он дальше где-то в шкафу без дела, если бы однажды я не наткнулся на серию статей:
от Borich, за что ему спасибо!
Благодаря им я познакомился с протоколом Modbus. Но самое главное я открыл для себя OpenHAB — то, что я давно искал. Это меня и подтолкнуло приступить к реализации давней идей.
Поигравшись с примерами из статей я решил попробовать разнести контроллер и сервер используя вышеописанный ethernet-shield из модема. Такое решение позволяло достичь послтавленной задачи — шилд и контроллер располагались на рабочем столе, при этом не используя рабочий компьютер, в то же время шилд всегда подключен к локальной сети и доступен с домашнего сервера.
Нужно настроить подключение в 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 фактически является реализацией «маршрутизатора» и не требует никаких доработок, кроме настроек нужных пинов.
#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;
— т.е. это корневое устройство сети. При необходимости, положение в топологии контроллера-прокси можно изменить.
#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°);
- настройка горизонтальной шторки(авто, "| |", "/ /", "/ |", "| \", "\ \" );
- настройка времени(час, минута);
- настройка таймера включения;
- настройка таймера выключения;
- настройка времени включения(час, минута);
- настройка времени выключения(час, минута);
В процессе разработки структура регистров менялась у меня много раз. Текуща версия ниже под спойлером.
| 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.
Железки
Аппаратная часть состоит из следующих компонент:
- Arduino Pro Mini. Китайский вариант 
- NRF24L01+ — беспроводной модуль 2.4ГГц
- LM1117-3.3 — стабилизатор 3.3В для NRF24L01+
- DS1302 — RTC
- кварц 32768кГц для RTC
- DS18B20 — датчик температуры. 2шт
- Мелочевка — оптопары, резисторы, конденсатор








Обратная связь с кондиционером реализована подключением к соответсвующим светодиодам оптопар. Таким образом обеспечена гальваническая развязка с электросхемой кондиционера. Ток входов для оптопар был экспериментально подобран с помощью сопротивлений таким образом, чтобы и выход оптопары открывался и яркость основного светодиода на кондиционере не падала.
ИК-светодиод был установлен рядом с ИК-приемником кондиционера.
Питание контроллера осуществляется от 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: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
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.
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
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
                        }
                }
        }
}
Результат выглядит примерно так через браузер:



Заключение
Полученным результатом я доволен как слон. Управление кондиционером сейчас доступно мне везде где есть мобильный интернет или WiFI. Используя VPN-клиент на смарфоне мне становится доступен OpenHAB на домашнем сервере как через браузер, так и через мобильное приложение.
Беспроводное решение позволило тесно интегрировать контроллер с кондиционером.
Наличие обратной связи дает уверенность, что отправленная команда принята кондиционером, а датчики температуры наглядно это демонстрируют — через несколько секунд можно наблюдать изменение показаний температуры на выходе из кондиционера. Ну а через несколько минут — и окружающей температуры.
Интересно было наблюдать за профилем работы кондиционера при приближении к заданным условиям.
Несомненно это будет не единственный беспроводной контроллер который я планирую использовать.
В планах есть задействование ИК-приемника самого кондиционера, чтобы читать команды родного пульта для актуализации настроек в контроллере. Тем более, что сам ИК-приемник уже подключен через оптопару к контроллеру.
Дочитавшим до конца отдельное спасибо!
Ссылки
- Arduino library ModbusRtu Modbus-Master-Slave-for-Arduino
- Arduino library ModbusRtuRF24 Modbus-over-RF24Network-for-Arduino
Комментарии (11)
 - Frimen318.09.2015 11:55- Интересно! Спасибо за труды и описание. Недавно писал статью на эту же тему по своей разработке: geektimes.ru/post/259662/ 
 У меня плюс в том что система может работать абсолютно со всеми кондиционерами что есть на рынке (у вас она будет работать только с тем протоколом который вы разобрали), но у вас зато есть возможность полностью имитировать родной пульт управления (у меня же записываются только 3 команды и все). - karakum2220.09.2015 11:34- Вот интересно… Мой пульт не имеет отдельных кнопок на включение и выключение. Для этого есть одна кнопка «ON/OFF» и шлет она всегда одно и то же. 
 Получается у вас в некоторых ситуациях возможно непредвиденное поведение.
 1 Режим охлаждение — включение на охлаждение.
 2 Режим вентиляция — команда приведет к выключению кондиционера. - Frimen320.09.2015 12:02- Вы уверены что у вашего кондиционера пульт при включении и выключении шлет абсолютно идентичные ИК команды? Такие кондиционеры есть, но их очень мало и как правило включение на холод, включение на вентиляцию и выключение это разные команды! 
 Хотя если у вас не так то с такими кондиционерами тоже можно работать, но чтобы включить или выключить такой кондиционер придется последовательно подавать сразу 3 команды (вкл-выкл-вкл) чтобы быть точно уверенным что кондиционер перейдет в нужный режим.
 Если память не изменяет, был такой кондиционер у которого вкл и выкл команды совпадали — mcquay назывался.
 
 
 - kacang18.09.2015 17:58- Хорошо получилось! Не совсем понял как вы считываете внутренний статус кондиционера. 
 
 А свои байндинги писать не пробовали? - artyfarty19.09.2015 11:45- Внутренний статус не нужно считывать, достаточно знать последнее установленное значение. Пульты от кондиционера каждый раз посылают все настройки разом, стремясь привести кондиционер к тому режиму, который у пульта на экране. 
 
 P.S. Плюс возможно автор еще и считывает сигнал, приходящий на кондей от внешнего пульта.
  - karakum2220.09.2015 11:25- Считывается только состояние ВКЛЮЧЕН-ВЫКЛЮЧЕН для режимов «Питание»,«Кислород»,«Ионизация»,«Тихий режим»,«Таймер» подключением к соответствующим светодиодам на панели внутреннего блока кондиционера: 
  
 На J1.1 всегда питание, на J1.4-J1.8 появлялась земля когда соответствующий светодиод загорался.
 Все остальные настройки(температура, скорость и пр.) хранятся в памяти контроллера.
 Считывание сигнала с пульта пока не реализовано.
 
 Биндинг не стал «городить», т.к. меня вполне устроил существующий биндинг для Modbus.
 
 
           
 
av0000
А забавно!
Мои игры с RF24network приостановились после портирования её на «голый» С, изготовления пробного метео-датчика и написания «тупого» драйвера ядра для Cubietruck SPI.
Буквально вчера набрёл на весьма много-обещающую альтернативу — Souliss (сайт: souliss.github.io) — чем-то напоминает более навороченный вариант RF24network с готовыми реализациями под 100500 контроллеров, биндингами в OpenHAB (1.7.0+) и готовым механизмом работы со стандарнтыми значениями (измерение, управление и т.п.) — не надо городить велосипед с форматом передаваемых данных на большинство DIY датчиков.
mlu
Посмотрите в сторону MySensors — www.mysensors.org
Довольно зрелый проект. Поддерживает много контроллеров, в т.ч. и OpenHAB. Есть поддержка MQTT. Работает практически со всеми сенсорами, доступными ардуине. Умеет обновлять прошивки ардуин прямо по воздуху без необходимости подключать к программатору/компьютеру. Есть поддержка авторизации (SHA256). Поддерживает разные радиоинтерфейсы: NRF24 (даже есть тестовые прошивки под NRF24LE1), RFM69, на форуме у них не так давно выкладывали поддержку проводного RS485
av0000
Благодарю!
Как-то натыкался и мне показалось, что они тоже хотят отправлять всё в своё облако. Сейчас присмотрелся – нет, кажись, можно и локальную систему строить… Про MQTT там как-то мутно форуме, то локальный брокер не работает, то ардуине памяти не хватает… Надо пробовать, короче ))
А вот под LE1 интересно — их есть у меня, даже немного поправил компилятор для аппаратных вычислений, но доделать порт RF24Network ещё не собрался — esp8266 приехали…
mlu
Нет, в облако ничего не отправляется, сам по себе проект вообще в интернет не лезет, это могут делать лишь контроллеры.
По поводу MQTT — у них оригинальная прошивка работает с MQTT как очень урезанный брокер, не как MQTT-клиент, отсюда и все проблемы :) Но есть как обычные клиенты (https://github.com/mysensors/Arduino/tree/development/libraries/MySensors/examples/MQTTClientGateway), так и посредники на перле (https://github.com/Yveaux/MySensors_MQTTGateway).
Вот топик по LE1: forum.mysensors.org/topic/1774/introducing-mysensors-on-nrf24le1
ESP8266 в качестве гейтвея тоже поддерживается, кстати :)
Еще из полезных вещей — их relay-ноды умеют пересылать сообщения гейтвею, если напрямую связь невозможна, это актуально для страдающих качеством связи китайских поддельных nrf24. А еще там довольно неплохие результаты работы нод от батареек.