├── .gitignore ├── README.md ├── components └── bus_t4 │ ├── __init__.py │ ├── automation.h │ ├── cover.py │ ├── nice-bust4.cpp │ └── nice-bust4.h ├── gerber └── Gerber_PCB_busT4adapter_3_2022-08-20.zip ├── img ├── 3hs.jpg ├── 924.jpg ├── IMG20230306201230.png ├── IMG20240530155927.jpg ├── IMG20240530160117.jpg ├── IMG20240530160125.jpg ├── IMG20240531091711.jpg ├── IMG_20220113_160221.jpg ├── Schematic_busT4adapter_xl.png ├── Schematic_bust4_2023-10-18.png ├── Schematic_esphome_bust4_adapter.png ├── WeMos_DC_Power_Shield_Schematic_v1.1.jpg ├── hassio-bust4.png ├── level-shifter.png ├── log.png ├── log2.png ├── mc824h.jpg ├── picture.txt ├── rba3c.jpg ├── rba4.jpg ├── spin.jpg ├── walky.jpg └── walky2.jpg ├── nice-wifi.yaml ├── uart_break.ino └── uart_stop_cmd.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Gitignore settings for ESPHome 2 | # This is an example and may include too much for your use-case. 3 | # You can modify this file to suit your needs. 4 | **/.vscode/ 5 | **/.esphome/ 6 | **/.pioenvs/ 7 | **/.piolibdeps/ 8 | **/lib/ 9 | **/src/ 10 | **/platformio.ini 11 | **/secrets.yaml 12 | **/livingroom_ac/ 13 | **/kitchen_ac/ 14 | /examples/*/*.h 15 | **/tests/test_* 16 | **/__pycache__ 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Доступен перевод на английский 2 | [English](https://github.com/xdanik/Nice_BusT4) 3 | 4 | 5 | # ESPHOME компонент для управления приводами Nice по протоколу Bus T4 6 | # Nice Bus T4 protocol 7 | 8 | Появилось желание разобраться в протоколе для управления воротами Nice. 9 | Переспектива - дешёвые устройства на базе esp8266 для управлением из умного дома. 10 | 11 | Современные блоки управления приводами имеют разъем BusT4, на который выведены GND, +V, Can-Rx, Can-Tx. Величина напряжения V может варьироваться от 24 до 35 вольт для разных блоков управления. 12 | 13 | # Текущие возможности компонента 14 | * Отправка команд: "Открыть", "Стоп", "Закрыть", "Частичное открытие", "Пошагово (SBS)" и других через кнопки. 15 | * Отправка произвольных HEX команд через службу "raw_command". Команда должна быть сформирована заранее или где-то подсмотрена. Разделителями байт могут быть точки или пробелы. Пример: 55 0c 00 03 00 81 01 05 86 01 82 01 64 e6 0c или 55.0D.00.FF.00.66.08.06.97.00.04.99.00.00.9D.0D 16 | * Формирование и отправка произвольных GET/SET запросов через службу "send_inf_command". Позволяет произвести настройку устройства или получить его статусы. 17 | * Отображение в логе пакетов от всех устройств в сети busT4. 18 | 19 | # BusT4: 20 | 21 | Это измененный UART 19200 8n1 с uart.break длительностью 519us-590us перед каждым пакетом. 22 | Можно подключать несколько устройств, для этого в физический уровень добавлены трансиверы CAN-BUS. 23 | Физическая передача чаще происходит через CAN трансиверы, но CAN-фреймов нет. 24 | 25 | # Что сделано: 26 | * Подключил FTDI232 к GND, Can-Rx, Can-Tx. Пакеты видны и поддаются расшифровке. 27 | * Логическим анализатором увидел форму сигнала и состав посылок, подобрал параметры uart. 28 | * Успешно имитировал считанный пакет через Arduino Mega, привод реагирует. 29 | * Получил команды OPEN CLOSE и тд 30 | * Получил байт статуса привода 31 | * Считал основные команды, частично расшифровал значение байт. 32 | * Собрал прототип устройства, протестировал работу. 33 | * Собрал сниффер для отлова пакетов между OVIEW и устройствами busT4 34 | * Написал компонент, который имеет возможность управлять приводами и приёмниками по протоколу BusT4 35 | * Проверил работу на Wingo5000 c блоком [MCA5](img/IMG_20220113_160221.jpg), [Robus RB500HS](img/3hs.jpg), Soona SO2000, [Rd400](img/rba4.jpg), [D-PRO924](img/924.jpg), [Walky WL1024C](img/walky.jpg), [SPIN 22BDKCE](img/spin.jpg). 36 | 37 | ![alt text](img/Schematic_esphome_bust4_adapter.png "Схема адаптера bus-t4") 38 | 39 | 40 | ESP8266 не совпадает по уровню сигнала с BUS T4, добавить преобразователь уровней 3.3В -> 5В для Tx на транзисторе. 41 | Rx ESP толерантен к 5В, но для стабильной работы нужен диод. У меня работает со случайным германиевым, возможно и кремниевый подойдёт. 42 | 43 | В дальнейшем схема была модифицирована. 44 | ![alt text](img/Schematic_busT4adapter_xl.png "Схема адаптера bus-t4 с модифицированным блоком питания") 45 | ![alt text](img/IMG20230306201230.png "Готовое устройство 2.0") 46 | 47 | ![alt text](img/hassio-bust4.png "Тест работы компонента bus-t4") 48 | 49 | 50 | Для приводов Walky нужна схема с CAN-трансивером. Разъем Bus-T4 спрятан под заглушкой. Эта же схема подойдет и к другим блокам управления с выведенным rj-11 (6p4c) разъемом, например [mc824h](img/mc824h.jpg) или [RBA3/C](img/rba3c.jpg) 51 | ![alt text](img/Schematic_bust4_2023-10-18.png "Схема CAN + bus-t4") 52 | 53 | Компонент поддерживает отправку произвольной команды на привод через службу ESPHome: nice_bust4_uart_raw_command в Home assystant. 54 | ``` 55 | SBS: 55 0c 00 03 00 81 01 05 86 01 82 01 64 e6 0c 56 | Open: 55 0c 00 03 05 81 01 05 83 01 82 03 64 e4 0c 57 | Close: 55 0c 00 03 05 81 01 05 83 01 82 04 64 e3 0c 58 | Stop: 55 0c 00 03 00 81 01 05 86 01 82 02 64 e5 0c 59 | ``` 60 | 61 | 62 | При старте и работе ESP опрашивает подключенные к шине BusT4 устройства и выводит информацию о них в лог. 63 | ![log](img/log.png "Лог") 64 | ![log](img/log2.png "Лог2") 65 | 66 | # Обновления 67 | * Добавлены службы в интерфейс компонента для более простого запуска процедуры распознавания длины створки и процедуры распознавания устройств BlueBus не разбирая корпус привода (и даже находясь удалённо). 68 | * Добавлен вывод в лог конфигурации считанные из устройства состояния L1, L2, L3 (Автоматическое 69 | закрывание, Закрыть после 70 | фотоэлемента, Всегда закрывать) 71 | * Улучшена совместимость с приводами DPRO924 72 | * Кнопка СТОП всегда доступна в User Interface объекта 73 | * Улучшена совместимость с приводами Walky WL1024C 74 | * Улучшена совместимость с приводами Spin ([@TheGoblinHero](https://github.com/TheGoblinHero)) 75 | * Добавлена функция задания произвольного положения привода ([@TheGoblinHero](https://github.com/TheGoblinHero)) 76 | 77 | Если проект заинтересовал, вы можете [купить мне пиво или кофе](https://www.tinkoff.ru/cf/12xvN3UtJkO) 78 | 79 | 80 | -------------------------------------------------------------------------------- /components/bus_t4/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/bus_t4/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include "esphome/core/automation.h" 5 | 6 | namespace esphome { 7 | namespace bus_t4 { 8 | 9 | template class RawCmdAction : public Action { 10 | 11 | 12 | 13 | void play(Ts... x) override { 14 | } 15 | 16 | protected: 17 | 18 | }; 19 | 20 | } // namespace bus_t4 21 | } // namespace esphome -------------------------------------------------------------------------------- /components/bus_t4/cover.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | import esphome.config_validation as cv 3 | from esphome.components import cover 4 | from esphome.const import CONF_ADDRESS, CONF_ID, CONF_UPDATE_INTERVAL, CONF_USE_ADDRESS 5 | 6 | 7 | 8 | bus_t4_ns = cg.esphome_ns.namespace('bus_t4') 9 | Nice = bus_t4_ns.class_('NiceBusT4', cover.Cover, cg.Component) 10 | CONFIG_SCHEMA = ( 11 | cover.cover_schema(Nice) # Changed from COVER_SCHEMA to cover_schema() 12 | .extend( 13 | { 14 | cv.Optional(CONF_ADDRESS): cv.hex_uint16_t, 15 | cv.Optional(CONF_USE_ADDRESS): cv.hex_uint16_t, 16 | # cv.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds, 17 | } 18 | ) 19 | .extend(cv.COMPONENT_SCHEMA) 20 | ) 21 | 22 | def to_code(config): 23 | var = cg.new_Pvariable(config[CONF_ID]) 24 | yield cg.register_component(var, config) 25 | 26 | yield cover.register_cover(var, config) 27 | 28 | if CONF_ADDRESS in config: 29 | address = config[CONF_ADDRESS] 30 | cg.add(var.set_to_address(address)) 31 | 32 | if CONF_USE_ADDRESS in config: 33 | use_address = config[CONF_USE_ADDRESS] 34 | cg.add(var.set_from_address(use_address)) 35 | 36 | 37 | # if CONF_UPDATE_INTERVAL in config: 38 | # update_interval = config[CONF_UPDATE_INTERVAL] 39 | # cg.add(var.set_update_interval(update_interval)) 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /components/bus_t4/nice-bust4.cpp: -------------------------------------------------------------------------------- 1 | #include "nice-bust4.h" 2 | #include "esphome/core/log.h" 3 | #include "esphome/core/helpers.h" // для использования вспомогательных функция работ со строками 4 | 5 | 6 | 7 | 8 | 9 | 10 | namespace esphome { 11 | namespace bus_t4 { 12 | 13 | static const char *TAG = "bus_t4.cover"; 14 | 15 | using namespace esphome::cover; 16 | 17 | 18 | 19 | 20 | CoverTraits NiceBusT4::get_traits() { 21 | auto traits = CoverTraits(); 22 | traits.set_supports_position(true); 23 | traits.set_supports_stop(true); 24 | return traits; 25 | } 26 | 27 | 28 | /* 29 | дампы команд OVIEW 30 | 31 | SBS 55 0c 00 ff 00 66 01 05 9D 01 82 01 64 E6 0c 32 | STOP 55 0c 00 ff 00 66 01 05 9D 01 82 02 64 E5 0c 33 | OPEN 55 0c 00 ff 00 66 01 05 9D 01 82 03 00 80 0c 34 | CLOSE 55 0c 00 ff 00 66 01 05 9D 01 82 04 64 E3 0c 35 | PARENTAL OPEN 1 55 0c 00 ff 00 66 01 05 9D 01 82 05 64 E2 0c 36 | PARENTAL OPEN 2 55 0c 00 ff 00 66 01 05 9D 01 82 06 64 E1 0c 37 | 38 | 39 | 40 | */ 41 | 42 | void NiceBusT4::control(const CoverCall &call) { 43 | position_hook_type = IGNORE; 44 | if (call.get_stop()) { 45 | send_cmd(STOP); 46 | 47 | } else if (call.get_position().has_value()) { 48 | float newpos = *call.get_position(); 49 | if (newpos != position) { 50 | if (newpos == COVER_OPEN) { 51 | if (current_operation != COVER_OPERATION_OPENING) send_cmd(OPEN); 52 | 53 | } else if (newpos == COVER_CLOSED) { 54 | if (current_operation != COVER_OPERATION_CLOSING) send_cmd(CLOSE); 55 | 56 | } else { // Произвольное положение 57 | position_hook_value = (_pos_opn - _pos_cls) * newpos + _pos_cls; 58 | ESP_LOGI(TAG, "Требуемое положение привода: %d", position_hook_value); 59 | if (position_hook_value > _pos_usl) { 60 | position_hook_type = STOP_UP; 61 | if (current_operation != COVER_OPERATION_OPENING) send_cmd(OPEN); 62 | } else { 63 | position_hook_type = STOP_DOWN; 64 | if (current_operation != COVER_OPERATION_CLOSING) send_cmd(CLOSE); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | void NiceBusT4::setup() { 72 | 73 | 74 | _uart = uart_init(_UART_NO, BAUD_WORK, SERIAL_8N1, SERIAL_FULL, TX_P, 256, false); 75 | // кто в сети? 76 | // this->tx_buffer_.push(gen_inf_cmd(0x00, 0xff, FOR_ALL, WHO, GET, 0x00)); 77 | 78 | 79 | } 80 | 81 | void NiceBusT4::loop() { 82 | 83 | if ((millis() - this->last_update_) > 10000) { // каждые 10 секунд 84 | // если привод не определился с первого раза, попробуем позже 85 | std::vector unknown = {0x55, 0x55}; 86 | if (this->init_ok == false) { 87 | this->tx_buffer_.push(gen_inf_cmd(0x00, 0xff, FOR_ALL, WHO, GET, 0x00)); 88 | this->tx_buffer_.push(gen_inf_cmd(0x00, 0xff, FOR_ALL, PRD, GET, 0x00)); //запрос продукта 89 | } 90 | 91 | else if (this->class_gate_ == 0x55) { 92 | init_device(this->addr_to[0], this->addr_to[1], 0x04); 93 | // this->tx_buffer_.push(gen_inf_cmd(0x00, 0xff, FOR_ALL, WHO, GET, 0x00)); 94 | // this->tx_buffer_.push(gen_inf_cmd(0x00, 0xff, FOR_ALL, PRD, GET, 0x00)); //запрос продукта 95 | } 96 | else if (this->manufacturer_ == unknown) { 97 | init_device(this->addr_to[0], this->addr_to[1], 0x04); 98 | // this->tx_buffer_.push(gen_inf_cmd(0x00, 0xff, FOR_ALL, WHO, GET, 0x00)); 99 | // this->tx_buffer_.push(gen_inf_cmd(0x00, 0xff, FOR_ALL, PRD, GET, 0x00)); //запрос продукта 100 | 101 | } 102 | this->last_update_ = millis(); 103 | } // if каждую минуту 104 | 105 | 106 | // разрешаем отправку каждые 100 ms 107 | uint32_t now = millis(); 108 | if (now - this->last_uart_byte_ > 100) { 109 | this->ready_to_tx_ = true; 110 | this->last_uart_byte_ = now; 111 | } 112 | 113 | 114 | while (uart_rx_available(_uart) > 0) { 115 | uint8_t c = (uint8_t)uart_read_char(_uart); // считываем байт 116 | this->handle_char_(c); // отправляем байт на обработку 117 | this->last_uart_byte_ = now; 118 | } //while 119 | 120 | if (this->ready_to_tx_) { // если можно отправлять 121 | if (!this->tx_buffer_.empty()) { // если есть что отправлять 122 | this->send_array_cmd(this->tx_buffer_.front()); // отправляем первую команду в очереди 123 | this->tx_buffer_.pop(); 124 | this->ready_to_tx_ = false; 125 | } 126 | } 127 | 128 | // Опрос текущего положения привода 129 | if (!is_robus) { 130 | 131 | now = millis(); 132 | if (init_ok && (current_operation != COVER_OPERATION_IDLE) && (now - last_position_time > POSITION_UPDATE_INTERVAL)) { 133 | last_position_time = now; 134 | request_position(); 135 | } 136 | } // not robus 137 | 138 | } //loop 139 | 140 | 141 | void NiceBusT4::handle_char_(uint8_t c) { 142 | this->rx_message_.push_back(c); // кидаем байт в конец полученного сообщения 143 | if (!this->validate_message_()) { // проверяем получившееся сообщение 144 | this->rx_message_.clear(); // если проверка не прошла, то в сообщении мусор, нужно удалить 145 | } 146 | } 147 | 148 | 149 | bool NiceBusT4::validate_message_() { // проверка получившегося сообщения 150 | uint32_t at = this->rx_message_.size() - 1; // номер последнего полученного байта 151 | uint8_t *data = &this->rx_message_[0]; // указатель на первый байт сообщения 152 | uint8_t new_byte = data[at]; // последний полученный байт 153 | 154 | // Byte 0: HEADER1 (всегда 0x00) 155 | if (at == 0) 156 | return new_byte == 0x00; 157 | // Byte 1: HEADER2 (всегда 0x55) 158 | if (at == 1) 159 | return new_byte == START_CODE; 160 | 161 | // Byte 2: packet_size - количество байт дальше + 1 162 | // Проверка не проводится 163 | 164 | if (at == 2) 165 | return true; 166 | uint8_t packet_size = data[2]; 167 | uint8_t length = (packet_size + 3); // длина ожидаемого сообщения понятна 168 | 169 | 170 | // Byte 3: Серия (ряд) кому пакет 171 | // Проверка не проводится 172 | // uint8_t command = data[3]; 173 | if (at == 3) 174 | return true; 175 | 176 | // Byte 4: Адрес кому пакет 177 | // Byte 5: Серия (ряд) от кого пакет 178 | // Byte 6: Адрес от кого пакет 179 | // Byte 7: Тип сообшения CMD или INF 180 | // Byte 8: Количество байт дальше за вычетом двух байт CRC в конце. 181 | 182 | if (at <= 8) 183 | // Проверка не проводится 184 | return true; 185 | 186 | uint8_t crc1 = (data[3] ^ data[4] ^ data[5] ^ data[6] ^ data[7] ^ data[8]); 187 | 188 | // Byte 9: crc1 = XOR (Byte 3 : Byte 8) XOR шести предыдущих байт 189 | if (at == 9) 190 | if (data[9] != crc1) { 191 | ESP_LOGW(TAG, "Received invalid message checksum 1 %02X!=%02X", data[9], crc1); 192 | return false; 193 | } 194 | // Byte 10: 195 | // ... 196 | 197 | // ждем пока поступят все данные пакета 198 | if (at < length) 199 | return true; 200 | 201 | // считаем crc2 202 | uint8_t crc2 = data[10]; 203 | for (uint8_t i = 11; i < length - 1; i++) { 204 | crc2 = (crc2 ^ data[i]); 205 | } 206 | 207 | if (data[length - 1] != crc2 ) { 208 | ESP_LOGW(TAG, "Received invalid message checksum 2 %02X!=%02X", data[length - 1], crc2); 209 | return false; 210 | } 211 | 212 | // Byte Last: packet_size 213 | // if (at == length) { 214 | if (data[length] != packet_size ) { 215 | ESP_LOGW(TAG, "Received invalid message size %02X!=%02X", data[length], packet_size); 216 | return false; 217 | } 218 | 219 | // Если сюда дошли - правильное сообщение получено и лежит в буфере rx_message_ 220 | 221 | // Удаляем 0x00 в начале сообщения 222 | rx_message_.erase(rx_message_.begin()); 223 | 224 | // для вывода пакета в лог 225 | std::string pretty_cmd = format_hex_pretty(rx_message_); 226 | ESP_LOGI(TAG, "Получен пакет: %S ", pretty_cmd.c_str() ); 227 | 228 | // здесь что-то делаем с сообщением 229 | parse_status_packet(rx_message_); 230 | 231 | 232 | 233 | // возвращаем false чтобы обнулить rx buffer 234 | return false; 235 | 236 | } 237 | 238 | 239 | // разбираем полученные пакеты 240 | void NiceBusT4::parse_status_packet (const std::vector &data) { 241 | if ((data[1] == 0x0d) && (data[13] == 0xFD)) { // ошибка 242 | ESP_LOGE(TAG, "Команда недоступна для этого устройства" ); 243 | } 244 | 245 | if (((data[11] == GET - 0x80) || (data[11] == GET - 0x81)) && (data[13] == NOERR)) { // if evt 246 | // ESP_LOGD(TAG, "Получен пакет EVT с данными. Последняя ячейка %d ", data[12]); 247 | std::vector vec_data(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 248 | std::string str(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 249 | ESP_LOGI(TAG, "Строка с данными: %S ", str.c_str() ); 250 | std::string pretty_data = format_hex_pretty(vec_data); 251 | ESP_LOGI(TAG, "Данные HEX %S ", pretty_data.c_str() ); 252 | // получили пакет с данными EVT, начинаем разбирать 253 | 254 | if ((data[6] == INF) && (data[9] == FOR_CU) && (data[11] == GET - 0x80) && (data[13] == NOERR)) { // интересуют завершенные ответы на запросы GET, пришедшие без ошибок от привода 255 | ESP_LOGI(TAG, "Получен ответ на запрос %X ", data[10] ); 256 | switch (data[10]) { // cmd_submnu 257 | case TYPE_M: 258 | // ESP_LOGI(TAG, "Тип привода %X", data[14]); 259 | switch (data[14]) { //14 260 | case SLIDING: 261 | this->class_gate_ = SLIDING; 262 | // ESP_LOGD(TAG, "Тип ворот: Откатные %#X ", data[14]); 263 | break; 264 | case SECTIONAL: 265 | this->class_gate_ = SECTIONAL; 266 | // ESP_LOGD(TAG, "Тип ворот: Секционные %#X ", data[14]); 267 | break; 268 | case SWING: 269 | this->class_gate_ = SWING; 270 | // ESP_LOGD(TAG, "Тип ворот: Распашные %#X ", data[14]); 271 | break; 272 | case BARRIER: 273 | this->class_gate_ = BARRIER; 274 | // ESP_LOGD(TAG, "Тип ворот: Шлагбаум %#X ", data[14]); 275 | break; 276 | case UPANDOVER: 277 | this->class_gate_ = UPANDOVER; 278 | // ESP_LOGD(TAG, "Тип ворот: Подъемно-поворотные %#X ", data[14]); 279 | break; 280 | } // switch 14 281 | break; // TYPE_M 282 | case INF_IO: // ответ на запрос положения концевика откатных ворот 283 | switch (data[16]) { //16 284 | case 0x00: 285 | ESP_LOGI(TAG, " Концевик не сработал "); 286 | break; // 0x00 287 | case 0x01: 288 | ESP_LOGI(TAG, " Концевик на закрытие "); 289 | this->position = COVER_CLOSED; 290 | break; // 0x01 291 | case 0x02: 292 | ESP_LOGI(TAG, " Концевик на открытие "); 293 | this->position = COVER_OPEN; 294 | break; // 0x02 295 | 296 | } // switch 16 297 | this->publish_state_if_changed(); // публикуем состояние 298 | 299 | break; // INF_IO 300 | 301 | 302 | //положение максимального открытия энкодера, открытия, закрытия 303 | 304 | case MAX_OPN: 305 | if (is_walky) { 306 | this->_max_opn = data[15]; 307 | this->_pos_opn = data[15]; 308 | } 309 | else { 310 | this->_max_opn = (data[14] << 8) + data[15]; 311 | } 312 | ESP_LOGI(TAG, "Максимальное положение энкодера: %d", this->_max_opn); 313 | break; 314 | 315 | case POS_MIN: 316 | this->_pos_cls = (data[14] << 8) + data[15]; 317 | ESP_LOGI(TAG, "Положение закрытых ворот: %d", this->_pos_cls); 318 | break; 319 | 320 | case POS_MAX: 321 | if (((data[14] << 8) + data[15])>0x00) { // если в ответе от привода есть данные о положении открытия 322 | this->_pos_opn = (data[14] << 8) + data[15];} 323 | ESP_LOGI(TAG, "Положение открытых ворот: %d", this->_pos_opn); 324 | break; 325 | 326 | case CUR_POS: 327 | if (is_walky) 328 | update_position(data[15]); 329 | else 330 | update_position((data[14] << 8) + data[15]); 331 | break; 332 | 333 | case INF_STATUS: 334 | switch (data[14]) { 335 | case OPENED: 336 | ESP_LOGI(TAG, " Ворота открыты"); 337 | this->current_operation = COVER_OPERATION_IDLE; 338 | this->position = COVER_OPEN; 339 | break; 340 | case CLOSED: 341 | ESP_LOGI(TAG, " Ворота закрыты"); 342 | this->current_operation = COVER_OPERATION_IDLE; 343 | this->position = COVER_CLOSED; 344 | break; 345 | case 0x01: 346 | ESP_LOGI(TAG, " Ворота остановлены"); 347 | this->current_operation = COVER_OPERATION_IDLE; 348 | request_position(); 349 | break; 350 | case 0x00: 351 | ESP_LOGI(TAG, " Статус ворот неизвестен"); 352 | this->current_operation = COVER_OPERATION_IDLE; 353 | request_position(); 354 | break; 355 | case 0x0b: 356 | ESP_LOGI(TAG, " Поиск положений сделан"); 357 | this->current_operation = COVER_OPERATION_IDLE; 358 | request_position(); 359 | break; 360 | case STA_OPENING: 361 | ESP_LOGI(TAG, " Идёт открывание"); 362 | this->current_operation = COVER_OPERATION_OPENING; 363 | break; 364 | case STA_CLOSING: 365 | ESP_LOGI(TAG, " Идёт закрывание"); 366 | this->current_operation = COVER_OPERATION_CLOSING; 367 | break; 368 | } // switch 369 | this->publish_state_if_changed(); // публикуем состояние 370 | break; 371 | 372 | // default: // cmd_mnu 373 | case AUTOCLS: 374 | this->autocls_flag = data[14]; 375 | ESP_LOGCONFIG(TAG, " Автозакрытие - L1: %S ", autocls_flag ? "Да" : "Нет"); 376 | break; 377 | 378 | case PH_CLS_ON: 379 | this->photocls_flag = data[14]; 380 | break; 381 | 382 | case ALW_CLS_ON: 383 | this->alwayscls_flag = data[14]; 384 | break; 385 | 386 | } // switch cmd_submnu 387 | } // if завершенные ответы на запросы GET, пришедшие без ошибок от привода 388 | 389 | if ((data[6] == INF) && (data[11] == GET - 0x81) && (data[13] == NOERR)) { // интересуют незавершенные ответы на запросы GET, пришедшие без ошибок от всех 390 | ESP_LOGI(TAG, "Получен незавершенный ответ на запрос %X, продолжение со смещением %X", data[10], data[12] ); 391 | // повторяем команду с новым смещением 392 | tx_buffer_.push(gen_inf_cmd(data[4], data[5], data[9], data[10], GET, data[12])); 393 | 394 | } // незавершенные ответы на запросы GET, пришедшие без ошибок от привода 395 | 396 | 397 | 398 | if ((data[6] == INF) && (data[9] == FOR_CU) && (data[11] == SET - 0x80) && (data[13] == NOERR)) { // интересуют ответы на запросы SET, пришедшие без ошибок от привода 399 | switch (data[10]) { // cmd_submnu 400 | case AUTOCLS: 401 | tx_buffer_.push(gen_inf_cmd(FOR_CU, AUTOCLS, GET)); // Автозакрытие 402 | break; 403 | 404 | case PH_CLS_ON: 405 | tx_buffer_.push(gen_inf_cmd(FOR_CU, PH_CLS_ON, GET)); // Закрыть после Фото 406 | break; 407 | 408 | case ALW_CLS_ON: 409 | tx_buffer_.push(gen_inf_cmd(FOR_CU, ALW_CLS_ON, GET)); // Всегда закрывать 410 | break; 411 | }// switch cmd_submnu 412 | }// if ответы на запросы SET, пришедшие без ошибок от привода 413 | 414 | if ((data[6] == INF) && (data[9] == FOR_ALL) && ((data[11] == GET - 0x80) || (data[11] == GET - 0x81)) && (data[13] == NOERR)) { // интересуют FOR_ALL ответы на запросы GET, пришедшие без ошибок 415 | 416 | switch (data[10]) { 417 | case MAN: 418 | // ESP_LOGCONFIG(TAG, " Производитель: %S ", str.c_str()); 419 | this->manufacturer_.assign(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 420 | break; 421 | case PRD: 422 | if ((this->addr_oxi[0] == data[4]) && (this->addr_oxi[1] == data[5])) { // если пакет от приемника 423 | // ESP_LOGCONFIG(TAG, " Приёмник: %S ", str.c_str()); 424 | this->oxi_product.assign(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 425 | } // если пакет от приемника 426 | else if ((this->addr_to[0] == data[4]) && (this->addr_to[1] == data[5])) { // если пакет от контроллера привода 427 | // ESP_LOGCONFIG(TAG, " Привод: %S ", str.c_str()); 428 | this->product_.assign(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 429 | std::vector wla1 = {0x57,0x4C,0x41,0x31,0x00,0x06,0x57}; // для понимания, что привод Walky 430 | std::vector ROBUSHSR10 = {0x52,0x4F,0x42,0x55,0x53,0x48,0x53,0x52,0x31,0x30,0x00}; // для понимания, что привод ROBUSHSR10 431 | if (this->product_ == wla1) { 432 | this->is_walky = true; 433 | // ESP_LOGCONFIG(TAG, " Привод WALKY!: %S ", str.c_str()); 434 | } 435 | if (this->product_ == ROBUSHSR10) { 436 | this->is_robus = true; 437 | // ESP_LOGCONFIG(TAG, " Привод ROBUS!: %S ", str.c_str()); 438 | } 439 | 440 | } 441 | break; 442 | case HWR: 443 | if ((this->addr_oxi[0] == data[4]) && (this->addr_oxi[1] == data[5])) { // если пакет от приемника 444 | this->oxi_hardware.assign(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 445 | } 446 | else if ((this->addr_to[0] == data[4]) && (this->addr_to[1] == data[5])) { // если пакет от контроллера привода 447 | this->hardware_.assign(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 448 | } //else 449 | break; 450 | case FRM: 451 | if ((this->addr_oxi[0] == data[4]) && (this->addr_oxi[1] == data[5])) { // если пакет от приемника 452 | this->oxi_firmware.assign(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 453 | } 454 | else if ((this->addr_to[0] == data[4]) && (this->addr_to[1] == data[5])) { // если пакет от контроллера привода 455 | this->firmware_.assign(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 456 | } //else 457 | break; 458 | case DSC: 459 | if ((this->addr_oxi[0] == data[4]) && (this->addr_oxi[1] == data[5])) { // если пакет от приемника 460 | this->oxi_description.assign(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 461 | } 462 | else if ((this->addr_to[0] == data[4]) && (this->addr_to[1] == data[5])) { // если пакет от контроллера привода 463 | this->description_.assign(this->rx_message_.begin() + 14, this->rx_message_.end() - 2); 464 | } //else 465 | break; 466 | case WHO: 467 | if (data[12] == 0x01) { 468 | if (data[14] == 0x04) { // привод 469 | this->addr_to[0] = data[4]; 470 | this->addr_to[1] = data[5]; 471 | this->init_ok = true; 472 | // init_device(data[4], data[5], data[14]); 473 | } 474 | else if (data[14] == 0x0A) { // приёмник 475 | this->addr_oxi[0] = data[4]; 476 | this->addr_oxi[1] = data[5]; 477 | init_device(data[4], data[5], data[14]); 478 | } 479 | } 480 | break; 481 | } // switch 482 | 483 | } // if FOR_ALL ответы на запросы GET, пришедшие без ошибок 484 | 485 | if ((data[9] == 0x0A) && (data[10] == 0x25) && (data[11] == 0x01) && (data[12] == 0x0A) && (data[13] == NOERR)) { // пакеты от приемника с информацией о списке пультов, пришедшие без ошибок 486 | ESP_LOGCONFIG(TAG, "Номер пульта: %X%X%X%X, команда: %X, кнопка: %X, режим: %X, счётчик нажатий: %d", vec_data[5], vec_data[4], vec_data[3], vec_data[2], vec_data[8] / 0x10, vec_data[5] / 0x10, vec_data[7] + 0x01, vec_data[6]); 487 | } // if 488 | 489 | if ((data[9] == 0x0A) && (data[10] == 0x26) && (data[11] == 0x41) && (data[12] == 0x08) && (data[13] == NOERR)) { // пакеты от приемника с информацией о считанной кнопке пульта 490 | ESP_LOGCONFIG(TAG, "Кнопка %X, номер пульта: %X%X%X%X", vec_data[0] / 0x10, vec_data[0] % 0x10, vec_data[1], vec_data[2], vec_data[3]); 491 | } // if 492 | 493 | } // if evt 494 | 495 | 496 | 497 | //else if ((data[14] == NOERR) && (data[1] > 0x0d)) { // иначе пакет Responce - подтверждение полученной команды 498 | else if (data[1] > 0x0d) { // иначе пакет Responce - подтверждение полученной команды 499 | ESP_LOGD(TAG, "Получен пакет RSP"); 500 | std::vector vec_data(this->rx_message_.begin() + 12, this->rx_message_.end() - 3); 501 | std::string str(this->rx_message_.begin() + 12, this->rx_message_.end() - 3); 502 | ESP_LOGI(TAG, "Строка с данными: %S ", str.c_str() ); 503 | std::string pretty_data = format_hex_pretty(vec_data); 504 | ESP_LOGI(TAG, "Данные HEX %S ", pretty_data.c_str() ); 505 | switch (data[9]) { // cmd_mnu 506 | case FOR_CU: 507 | ESP_LOGI(TAG, "Пакет контроллера привода"); 508 | switch (data[10] + 0x80) { // sub_inf_cmd 509 | case RUN: 510 | ESP_LOGI(TAG, "Подменю RUN"); 511 | if (data[11] >= 0x80) { 512 | switch (data[11] - 0x80) { // sub_run_cmd1 513 | case SBS: 514 | ESP_LOGI(TAG, "Команда: Пошагово"); 515 | break; 516 | case STOP: 517 | ESP_LOGI(TAG, "Команда: STOP"); 518 | break; 519 | case OPEN: 520 | ESP_LOGI(TAG, "Команда: OPEN"); 521 | this->current_operation = COVER_OPERATION_OPENING; 522 | break; 523 | case CLOSE: 524 | ESP_LOGI(TAG, "Команда: CLOSE"); 525 | this->current_operation = COVER_OPERATION_CLOSING; 526 | break; 527 | case P_OPN1: 528 | ESP_LOGI(TAG, "Команда: Частичное открывание 1"); 529 | break; 530 | case STOPPED: 531 | ESP_LOGI(TAG, "Команда: Остановлено"); 532 | this->current_operation = COVER_OPERATION_IDLE; 533 | request_position(); 534 | break; 535 | case ENDTIME: 536 | ESP_LOGI(TAG, "Операция завершена по таймауту"); 537 | this->current_operation = COVER_OPERATION_IDLE; 538 | request_position(); 539 | break; 540 | default: 541 | ESP_LOGI(TAG, "Неизвестная команда: %X", data[11]); 542 | } // switch sub_run_cmd1 543 | } else { 544 | switch (data[11]) { // sub_run_cmd2 545 | case STA_OPENING: 546 | ESP_LOGI(TAG, "Операция: Открывается"); 547 | this->current_operation = COVER_OPERATION_OPENING; 548 | break; 549 | case STA_CLOSING: 550 | ESP_LOGI(TAG, "Операция: Закрывается"); 551 | this->current_operation = COVER_OPERATION_CLOSING; 552 | break; 553 | case CLOSED: 554 | ESP_LOGI(TAG, "Операция: Закрыто"); 555 | this->current_operation = COVER_OPERATION_IDLE; 556 | this->position = COVER_CLOSED; 557 | break; 558 | case OPENED: 559 | ESP_LOGI(TAG, "Операция: Открыто"); 560 | this->current_operation = COVER_OPERATION_IDLE; 561 | this->position = COVER_OPEN; 562 | // calibrate opened position if the motor does not report max supported position (Road 400) 563 | if (this->_max_opn == 0) { 564 | this->_max_opn = this->_pos_opn = this->_pos_usl; 565 | ESP_LOGI(TAG, "Opened position calibrated"); 566 | } 567 | break; 568 | case STOPPED: 569 | ESP_LOGI(TAG, "Операция: Остановлено"); 570 | this->current_operation = COVER_OPERATION_IDLE; 571 | request_position(); 572 | break; 573 | case PART_OPENED: 574 | ESP_LOGI(TAG, "Операция: Частично открыто"); 575 | this->current_operation = COVER_OPERATION_IDLE; 576 | request_position(); 577 | break; 578 | default: 579 | ESP_LOGI(TAG, "Неизвестная операция: %X", data[11]); 580 | } // switch sub_run_cmd2 581 | } 582 | this->publish_state_if_changed(); // публикуем состояние 583 | break; //RUN 584 | 585 | case STA: 586 | ESP_LOGI(TAG, "Подменю Статус в движении" ); 587 | switch (data[11]) { // sub_run_cmd2 588 | case STA_OPENING: 589 | case 0x83: // Road 400 590 | ESP_LOGI(TAG, "Движение: Открывается" ); 591 | this->current_operation = COVER_OPERATION_OPENING; 592 | break; 593 | case STA_CLOSING: 594 | case 0x84: // Road 400 595 | ESP_LOGI(TAG, "Движение: Закрывается" ); 596 | this->current_operation = COVER_OPERATION_CLOSING; 597 | break; 598 | case CLOSED: 599 | ESP_LOGI(TAG, "Движение: Закрыто" ); 600 | this->current_operation = COVER_OPERATION_IDLE; 601 | this->position = COVER_CLOSED; 602 | break; 603 | case OPENED: 604 | ESP_LOGI(TAG, "Движение: Открыто"); 605 | this->current_operation = COVER_OPERATION_IDLE; 606 | this->position = COVER_OPEN; 607 | break; 608 | case STOPPED: 609 | ESP_LOGI(TAG, "Движение: Остановлено"); 610 | this->current_operation = COVER_OPERATION_IDLE; 611 | request_position(); 612 | break; 613 | default: // sub_run_cmd2 614 | ESP_LOGI(TAG, "Движение: %X", data[11] ); 615 | 616 | 617 | } // switch sub_run_cmd2 618 | 619 | update_position((data[12] << 8) + data[13]); 620 | break; //STA 621 | 622 | default: // sub_inf_cmd 623 | ESP_LOGI(TAG, "Подменю %X", data[10] ); 624 | } // switch sub_inf_cmd 625 | 626 | break; // Пакет контроллера привода 627 | case CONTROL: 628 | ESP_LOGI(TAG, "Пакет CONTROL" ); 629 | break; // CONTROL 630 | case FOR_ALL: 631 | ESP_LOGI(TAG, "Пакет для всех" ); 632 | break; // FOR_ALL 633 | case 0x0A: 634 | ESP_LOGI(TAG, "Пакет приёмника" ); 635 | break; // пакет приёмника 636 | default: // cmd_mnu 637 | ESP_LOGI(TAG, "Меню %X", data[9] ); 638 | } // switch cmd_mnu 639 | 640 | 641 | } // else 642 | 643 | if ((data[6] == CMD) && (data[9] == FOR_CU) && (data[10] == CUR_MAN) && (data[13] == NOERR)) { // интересуют FOR_CU ответы на запросы CMD, пришедшие без ошибок. Ищем статус для RO600 644 | 645 | /////////////////////////////////////////////////////////////////////////////////// 646 | 647 | 648 | // RSP ответ (ReSPonce) на простой прием команды CMD, а не ее выполнение. Также докладывает о завершении операции. 649 | /* if ((data[1] == 0x0E) && (data[6] == CMD) && (data[9] == FOR_CU) && (data[10] == CUR_MAN) && (data[12] == 0x19)) { // узнаём пакет статуса по содержимому в определённых байтах 650 | // ESP_LOGD(TAG, "Получен пакет RSP. cmd = %#x", data[11]); 651 | */ 652 | switch (data[11]) { 653 | case STA_OPENING: 654 | this->current_operation = COVER_OPERATION_OPENING; 655 | ESP_LOGD(TAG, "Статус: Открывается"); 656 | break; 657 | case STA_CLOSING: 658 | this->current_operation = COVER_OPERATION_CLOSING; 659 | ESP_LOGD(TAG, "Статус: Закрывается"); 660 | break; 661 | case OPENED: 662 | this->position = COVER_OPEN; 663 | ESP_LOGD(TAG, "Статус: Открыто"); 664 | this->current_operation = COVER_OPERATION_IDLE; 665 | break; 666 | 667 | 668 | case CLOSED: 669 | this->position = COVER_CLOSED; 670 | ESP_LOGD(TAG, "Статус: Закрыто"); 671 | this->current_operation = COVER_OPERATION_IDLE; 672 | break; 673 | case STOPPED: 674 | this->current_operation = COVER_OPERATION_IDLE; 675 | ESP_LOGD(TAG, "Статус: Остановлено"); 676 | break; 677 | 678 | } // switch 679 | 680 | this->publish_state(); // публикуем состояние 681 | 682 | } //if 683 | 684 | /* 685 | // статус после достижения концевиков 686 | if ((data[1] == 0x0E) && (data[6] == CMD) && (data[9] == FOR_CU) && (data[10] == CUR_MAN) && (data[12] == 0x00)) { // узнаём пакет статуса по содержимому в определённых байтах 687 | ESP_LOGD(TAG, "Получен пакет концевиков. Статус = %#x", data[11]); 688 | switch (data[11]) { 689 | case OPENED: 690 | this->position = COVER_OPEN; 691 | ESP_LOGD(TAG, "Статус: Открыто"); 692 | this->current_operation = COVER_OPERATION_IDLE; 693 | break; 694 | case CLOSED: 695 | this->position = COVER_CLOSED; 696 | ESP_LOGD(TAG, "Статус: Закрыто"); 697 | this->current_operation = COVER_OPERATION_IDLE; 698 | break; 699 | case OPENING: 700 | this->current_operation = COVER_OPERATION_OPENING; 701 | ESP_LOGD(TAG, "Статус: Открывается"); 702 | break; 703 | case CLOSING: 704 | this->current_operation = COVER_OPERATION_CLOSING; 705 | ESP_LOGD(TAG, "Статус: Закрывается"); 706 | break; 707 | } //switch 708 | this->publish_state(); // публикуем состояние 709 | } //if 710 | */ 711 | // STA = 0x40, // статус в движении 712 | /* 713 | if ((data[1] == 0x0E) && (data[6] == CMD) && (data[9] == FOR_CU) && (data[10] == STA) ) { // узнаём пакет статуса по содержимому в определённых байтах 714 | uint16_t ipos = (data[12] << 8) + data[13]; 715 | ESP_LOGD(TAG, "Текущий маневр: %#X Позиция: %#X %#X, ipos = %#x,", data[11], data[12], data[13], ipos); 716 | this->position = ipos / 2100.0f; // передаем позицию компоненту 717 | 718 | switch (data[11]) { 719 | case OPENING: 720 | this->current_operation = COVER_OPERATION_OPENING; 721 | ESP_LOGD(TAG, "Статус: Открывается"); 722 | break; 723 | 724 | case OPENING2: 725 | this->current_operation = COVER_OPERATION_OPENING; 726 | ESP_LOGD(TAG, "Статус: Открывается"); 727 | break; 728 | 729 | case CLOSING: 730 | this->current_operation = COVER_OPERATION_CLOSING; 731 | ESP_LOGD(TAG, "Статус: Закрывается"); 732 | break; 733 | case CLOSING2: 734 | this->current_operation = COVER_OPERATION_CLOSING; 735 | ESP_LOGD(TAG, "Статус: Закрывается"); 736 | break; 737 | case OPENED: 738 | this->position = COVER_OPEN; 739 | this->current_operation = COVER_OPERATION_IDLE; 740 | ESP_LOGD(TAG, "Статус: Открыто"); 741 | // this->current_operation = COVER_OPERATION_OPENING; 742 | // ESP_LOGD(TAG, "Статус: Открывается"); 743 | break; 744 | case CLOSED: 745 | this->position = COVER_CLOSED; 746 | this->current_operation = COVER_OPERATION_IDLE; 747 | ESP_LOGD(TAG, "Статус: Закрыто"); 748 | // this->current_operation = COVER_OPERATION_CLOSING; 749 | //ESP_LOGD(TAG, "Статус: Закрывается"); 750 | break; 751 | case STOPPED: 752 | this->current_operation = COVER_OPERATION_IDLE; 753 | ESP_LOGD(TAG, "Статус: Остановлено"); 754 | break; 755 | 756 | } // switch 757 | 758 | this->publish_state(); // публикуем состояние 759 | 760 | } //if 761 | */ 762 | 763 | 764 | //////////////////////////////////////////////////////////////////////////////////////// 765 | } // function 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | void NiceBusT4::dump_config() { // добавляем в лог информацию о подключенном контроллере 774 | ESP_LOGCONFIG(TAG, " Bus T4 Cover"); 775 | /*ESP_LOGCONFIG(TAG, " Address: 0x%02X%02X", *this->header_[1], *this->header_[2]);*/ 776 | switch (this->class_gate_) { 777 | case SLIDING: 778 | ESP_LOGCONFIG(TAG, " Тип: Откатные ворота"); 779 | break; 780 | case SECTIONAL: 781 | ESP_LOGCONFIG(TAG, " Тип: Секционные ворота"); 782 | break; 783 | case SWING: 784 | ESP_LOGCONFIG(TAG, " Тип: Распашные ворота"); 785 | break; 786 | case BARRIER: 787 | ESP_LOGCONFIG(TAG, " Тип: Шлагбаум"); 788 | break; 789 | case UPANDOVER: 790 | ESP_LOGCONFIG(TAG, " Тип: Подъёмно-поворотные ворота"); 791 | break; 792 | default: 793 | ESP_LOGCONFIG(TAG, " Тип: Неизвестные ворота, 0x%02X", this->class_gate_); 794 | } // switch 795 | 796 | 797 | ESP_LOGCONFIG(TAG, " Максимальное положение энкодера или таймера: %d", this->_max_opn); 798 | ESP_LOGCONFIG(TAG, " Положение отрытых ворот: %d", this->_pos_opn); 799 | ESP_LOGCONFIG(TAG, " Положение закрытых ворот: %d", this->_pos_cls); 800 | 801 | std::string manuf_str(this->manufacturer_.begin(), this->manufacturer_.end()); 802 | ESP_LOGCONFIG(TAG, " Производитель: %S ", manuf_str.c_str()); 803 | 804 | std::string prod_str(this->product_.begin(), this->product_.end()); 805 | ESP_LOGCONFIG(TAG, " Привод: %S ", prod_str.c_str()); 806 | 807 | std::string hard_str(this->hardware_.begin(), this->hardware_.end()); 808 | ESP_LOGCONFIG(TAG, " Железо привода: %S ", hard_str.c_str()); 809 | 810 | std::string firm_str(this->firmware_.begin(), this->firmware_.end()); 811 | ESP_LOGCONFIG(TAG, " Прошивка привода: %S ", firm_str.c_str()); 812 | 813 | std::string dsc_str(this->description_.begin(), this->description_.end()); 814 | ESP_LOGCONFIG(TAG, " Описание привода: %S ", dsc_str.c_str()); 815 | 816 | 817 | ESP_LOGCONFIG(TAG, " Адрес шлюза: 0x%02X%02X", addr_from[0], addr_from[1]); 818 | ESP_LOGCONFIG(TAG, " Адрес привода: 0x%02X%02X", addr_to[0], addr_to[1]); 819 | ESP_LOGCONFIG(TAG, " Адрес приёмника: 0x%02X%02X", addr_oxi[0], addr_oxi[1]); 820 | 821 | std::string oxi_prod_str(this->oxi_product.begin(), this->oxi_product.end()); 822 | ESP_LOGCONFIG(TAG, " Приёмник: %S ", oxi_prod_str.c_str()); 823 | 824 | std::string oxi_hard_str(this->oxi_hardware.begin(), this->oxi_hardware.end()); 825 | ESP_LOGCONFIG(TAG, " Железо приёмника: %S ", oxi_hard_str.c_str()); 826 | 827 | std::string oxi_firm_str(this->oxi_firmware.begin(), this->oxi_firmware.end()); 828 | ESP_LOGCONFIG(TAG, " Прошивка приёмника: %S ", oxi_firm_str.c_str()); 829 | 830 | std::string oxi_dsc_str(this->oxi_description.begin(), this->oxi_description.end()); 831 | ESP_LOGCONFIG(TAG, " Описание приёмника: %S ", oxi_dsc_str.c_str()); 832 | 833 | ESP_LOGCONFIG(TAG, " Автозакрытие - L1: %S ", autocls_flag ? "Да" : "Нет"); 834 | ESP_LOGCONFIG(TAG, " Закрыть после фото - L2: %S ", photocls_flag ? "Да" : "Нет"); 835 | ESP_LOGCONFIG(TAG, " Всегда закрывать - L3: %S ", alwayscls_flag ? "Да" : "Нет"); 836 | 837 | } 838 | 839 | 840 | 841 | 842 | //формирование команды управления 843 | std::vector NiceBusT4::gen_control_cmd(const uint8_t control_cmd) { 844 | std::vector frame = {this->addr_to[0], this->addr_to[1], this->addr_from[0], this->addr_from[1]}; // заголовок 845 | frame.push_back(CMD); // 0x01 846 | frame.push_back(0x05); 847 | uint8_t crc1 = (frame[0] ^ frame[1] ^ frame[2] ^ frame[3] ^ frame[4] ^ frame[5]); 848 | frame.push_back(crc1); 849 | frame.push_back(CONTROL); 850 | frame.push_back(RUN); 851 | frame.push_back(control_cmd); 852 | frame.push_back(0x64); // OFFSET CMD, DPRO924 отказался работать с 0x00, хотя остальные приводы реагировали на команды 853 | uint8_t crc2 = (frame[7] ^ frame[8] ^ frame[9] ^ frame[10]); 854 | frame.push_back(crc2); 855 | uint8_t f_size = frame.size(); 856 | frame.push_back(f_size); 857 | frame.insert(frame.begin(), f_size); 858 | frame.insert(frame.begin(), START_CODE); 859 | 860 | // для вывода команды в лог 861 | // std::string pretty_cmd = format_hex_pretty(frame); 862 | // ESP_LOGI(TAG, "Сформирована команда: %S ", pretty_cmd.c_str() ); 863 | 864 | return frame; 865 | } 866 | 867 | // формирование команды INF с данными и без 868 | std::vector NiceBusT4::gen_inf_cmd(const uint8_t to_addr1, const uint8_t to_addr2, const uint8_t whose, const uint8_t inf_cmd, const uint8_t run_cmd, const uint8_t next_data, const std::vector &data, size_t len) { 869 | std::vector frame = {to_addr1, to_addr2, this->addr_from[0], this->addr_from[1]}; // заголовок 870 | frame.push_back(INF); // 0x08 mes_type 871 | frame.push_back(0x06 + len); // mes_size 872 | uint8_t crc1 = (frame[0] ^ frame[1] ^ frame[2] ^ frame[3] ^ frame[4] ^ frame[5]); 873 | frame.push_back(crc1); 874 | frame.push_back(whose); 875 | frame.push_back(inf_cmd); 876 | frame.push_back(run_cmd); 877 | frame.push_back(next_data); // next_data 878 | frame.push_back(len); 879 | if (len > 0) { 880 | frame.insert(frame.end(), data.begin(), data.end()); // блок данных 881 | } 882 | uint8_t crc2 = frame[7]; 883 | for (size_t i = 8; i < 12 + len; i++) { 884 | crc2 = crc2 ^ frame[i]; 885 | } 886 | frame.push_back(crc2); 887 | uint8_t f_size = frame.size(); 888 | frame.push_back(f_size); 889 | frame.insert(frame.begin(), f_size); 890 | frame.insert(frame.begin(), START_CODE); 891 | 892 | // для вывода команды в лог 893 | // std::string pretty_cmd = format_hex_pretty(frame); 894 | // ESP_LOGI(TAG, "Сформирован INF пакет: %S ", pretty_cmd.c_str() ); 895 | 896 | return frame; 897 | 898 | } 899 | 900 | 901 | void NiceBusT4::send_raw_cmd(std::string data) { 902 | 903 | std::vector < uint8_t > v_cmd = raw_cmd_prepare (data); 904 | send_array_cmd (&v_cmd[0], v_cmd.size()); 905 | 906 | } 907 | 908 | 909 | // Сюда нужно добавить проверку на неправильные данные от пользователя 910 | std::vector NiceBusT4::raw_cmd_prepare (std::string data) { // подготовка введенных пользователем данных для возможности отправки 911 | // удаляем всё кроме шестнадцатеричных букв и цифр 912 | data.erase(remove_if(data.begin(), data.end(), [](const unsigned char ch) { 913 | return (!(isxdigit(ch)) ); 914 | }), data.end()); 915 | 916 | //assert (data.size () % 2 == 0); // проверяем чётность 917 | std::vector < uint8_t > frame; 918 | frame.resize(0); // обнуляем размер команды 919 | 920 | for (uint8_t i = 0; i < data.size (); i += 2 ) { // заполняем массив команды 921 | std::string sub_str(data, i, 2); // берём 2 байта из команды 922 | char hexstoi = (char)std::strtol(&sub_str[0], 0 , 16); // преобразуем в число 923 | frame.push_back(hexstoi); // записываем число в элемент строки новой команды 924 | } 925 | 926 | 927 | return frame; 928 | 929 | } 930 | 931 | 932 | 933 | void NiceBusT4::send_array_cmd (std::vector data) { // отправляет break + подготовленную ранее в массиве команду 934 | return send_array_cmd((const uint8_t *)data.data(), data.size()); 935 | } 936 | void NiceBusT4::send_array_cmd (const uint8_t *data, size_t len) { 937 | // отправка данных в uart 938 | 939 | char br_ch = 0x00; // для break 940 | uart_flush(_uart); // очищаем uart 941 | uart_set_baudrate(_uart, BAUD_BREAK); // занижаем бодрэйт 942 | uart_write(_uart, &br_ch, 1); // отправляем ноль на низкой скорости, длиинный ноль 943 | //uart_write(_uart, (char *)&dummy, 1); 944 | uart_wait_tx_empty(_uart); // ждём, пока отправка завершится. Здесь в библиотеке uart.h (esp8266 core 3.0.2) ошибка, ожидания недостаточно при дальнейшем uart_set_baudrate(). 945 | delayMicroseconds(90); // добавляем задержку к ожиданию, иначе скорость переключится раньше отправки. С задержкой на d1-mini я получил идеальный сигнал, break = 520us 946 | uart_set_baudrate(_uart, BAUD_WORK); // возвращаем рабочий бодрэйт 947 | uart_write(_uart, (char *)&data[0], len); // отправляем основную посылку 948 | //uart_write(_uart, (char *)raw_cmd_buf, sizeof(raw_cmd_buf)); 949 | uart_wait_tx_empty(_uart); // ждем завершения отправки 950 | 951 | 952 | 953 | std::string pretty_cmd = format_hex_pretty((uint8_t*)&data[0], len); // для вывода команды в лог 954 | ESP_LOGI(TAG, "Отправлено: %S ", pretty_cmd.c_str() ); 955 | 956 | } 957 | 958 | 959 | // генерация и отправка inf команд из yaml конфигурации 960 | void NiceBusT4::send_inf_cmd(std::string to_addr, std::string whose, std::string command, std::string type_command, std::string next_data, bool data_on, std::string data_command) { 961 | std::vector < uint8_t > v_to_addr = raw_cmd_prepare (to_addr); 962 | std::vector < uint8_t > v_whose = raw_cmd_prepare (whose); 963 | std::vector < uint8_t > v_command = NiceBusT4::raw_cmd_prepare (command); 964 | std::vector < uint8_t > v_type_command = raw_cmd_prepare (type_command); 965 | std::vector < uint8_t > v_next_data = raw_cmd_prepare (next_data); 966 | std::vector < uint8_t > v_data_command = raw_cmd_prepare (data_command); 967 | 968 | 969 | if (data_on) { 970 | tx_buffer_.push(gen_inf_cmd(v_to_addr[0], v_to_addr[1], v_whose[0], v_command[0], v_type_command[0], v_next_data[0], v_data_command, v_data_command.size())); 971 | } else { 972 | tx_buffer_.push(gen_inf_cmd(v_to_addr[0], v_to_addr[1], v_whose[0], v_command[0], v_type_command[0], v_next_data[0])); 973 | } // else 974 | } 975 | 976 | // генерация и отправка команд установки контроллеру привода из yaml конфигурации с минимальными параметрами 977 | void NiceBusT4::set_mcu(std::string command, std::string data_command) { 978 | std::vector < uint8_t > v_command = raw_cmd_prepare (command); 979 | std::vector < uint8_t > v_data_command = raw_cmd_prepare (data_command); 980 | tx_buffer_.push(gen_inf_cmd(0x04, v_command[0], 0xa9, 0x00, v_data_command)); 981 | } 982 | 983 | // инициализация устройства 984 | void NiceBusT4::init_device (const uint8_t addr1, const uint8_t addr2, const uint8_t device ) { 985 | if (device == FOR_CU) { 986 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, device, TYPE_M, GET, 0x00)); // запрос типа привода 987 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, FOR_ALL, MAN, GET, 0x00)); // запрос производителя 988 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, FOR_ALL, FRM, GET, 0x00)); // запрос прошивки 989 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, FOR_ALL, PRD, GET, 0x00)); //запрос продукта 990 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, FOR_ALL, HWR, GET, 0x00)); //запрос железа 991 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, device, POS_MAX, GET, 0x00)); //запрос позиции открытия 992 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, device, POS_MIN, GET, 0x00)); // запрос позиции закрытия 993 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, FOR_ALL, DSC, GET, 0x00)); //запрос описания 994 | if (is_walky) // запрос максимального значения для энкодера 995 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, device, MAX_OPN, GET, 0x00, {0x01}, 1)); 996 | else 997 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, device, MAX_OPN, GET, 0x00)); 998 | request_position(); // запрос текущего положения 999 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, device, INF_STATUS, GET, 0x00)); //Состояние ворот (Открыто/Закрыто/Остановлено) 1000 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, device, AUTOCLS, GET, 0x00)); // Автозакрытие 1001 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, device, PH_CLS_ON, GET, 0x00)); // Закрыть после Фото 1002 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, device, ALW_CLS_ON, GET, 0x00)); // Всегда закрывать 1003 | } 1004 | if (device == FOR_OXI) { 1005 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, FOR_ALL, PRD, GET, 0x00)); //запрос продукта 1006 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, FOR_ALL, HWR, GET, 0x00)); //запрос железа 1007 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, FOR_ALL, FRM, GET, 0x00)); // запрос прошивки 1008 | tx_buffer_.push(gen_inf_cmd(addr1, addr2, FOR_ALL, DSC, GET, 0x00)); //запрос описания 1009 | } 1010 | 1011 | } 1012 | 1013 | // Запрос условного текущего положения привода 1014 | void NiceBusT4::request_position(void) { 1015 | if (is_walky) 1016 | tx_buffer_.push(gen_inf_cmd(this->addr_to[0], this->addr_to[1], FOR_CU, CUR_POS, GET, 0x00, {0x01}, 1)); 1017 | else 1018 | tx_buffer_.push(gen_inf_cmd(FOR_CU, CUR_POS, GET)); 1019 | } 1020 | 1021 | // Обновление текущего положения привода 1022 | void NiceBusT4::update_position(uint16_t newpos) { 1023 | last_position_time = millis(); 1024 | _pos_usl = newpos; 1025 | position = (_pos_usl - _pos_cls) * 1.0f / (_pos_opn - _pos_cls); 1026 | ESP_LOGI(TAG, "Условное положение ворот: %d, положение в %%: %.3f", newpos, position); 1027 | if (position < CLOSED_POSITION_THRESHOLD) position = COVER_CLOSED; 1028 | publish_state_if_changed(); // публикуем состояние 1029 | 1030 | if ((position_hook_type == STOP_UP && _pos_usl >= position_hook_value) || (position_hook_type == STOP_DOWN && _pos_usl <= position_hook_value)) { 1031 | ESP_LOGI(TAG, "Достигнуто требуемое положение. Останавливаем ворота"); 1032 | send_cmd(STOP); 1033 | position_hook_type = IGNORE; 1034 | } 1035 | } 1036 | 1037 | // Публикация состояния ворот при изменении 1038 | void NiceBusT4::publish_state_if_changed(void) { 1039 | if (current_operation == COVER_OPERATION_IDLE) position_hook_type = IGNORE; 1040 | if (last_published_op != current_operation || last_published_pos != position) { 1041 | publish_state(); 1042 | last_published_op = current_operation; 1043 | last_published_pos = position; 1044 | } 1045 | } 1046 | 1047 | } // namespace bus_t4 1048 | } // namespace esphome 1049 | -------------------------------------------------------------------------------- /components/bus_t4/nice-bust4.h: -------------------------------------------------------------------------------- 1 | /* 2 | Nice BusT4 3 | Обмен данными по UART на скорости 19200 8n1 4 | Перед пакетом с данными отправляется break длительностью 519us (10 бит) 5 | Содержимое пакета, которое удалось понять, описано в структуре packet_cmd_body_t 6 | 7 | 8 | 9 | Для Oview к адресу всегда прибавляется 80. 10 | Адрес контроллера ворот без изменений. 11 | 12 | 13 | Подключение 14 | 15 | BusT4 ESP8266 16 | 17 | Стенка устройства Rx Tx GND 18 | 9 7 5 3 1 19 | 10 8 6 4 2 20 | место для кабеля 21 | 1 ---------- Rx 22 | 2 ---------- GND 23 | 4 ---------- Tx 24 | 5 ---------- +24V 25 | 26 | 27 | 28 | 29 | Из мануала nice_dmbm_integration_protocol.pdf 30 | 31 | • ADR: это адрес сети NICE, где находятся устройства, которыми вы хотите управлять. Это может быть значение от 1 до 63 (от 1 до 3F). 32 | Это значение должно быть в HEX. Если адресатом является модуль интеграции на DIN-BAR, это значение равно 0 (adr = 0), если адресат 33 | является интеллектуальным двигателем, это значение равно 1 (adr = 1). 34 | • EPT: это адрес двигателя Nice, входящего в сетевой ADR. Это может быть значение от 1 до 127. Это значение должно быть в HEX. 35 | • CMD: это команда, которую вы хотите отправить по назначению (ADR, EPT). 36 | • PRF: команда установки профиля. 37 | • FNC: это функция, которую вы хотите отправить по назначению (ADR, EPT). 38 | • EVT: это событие, которое отправляется в пункт назначения (ADR, EPT). 39 | 40 | 41 | 42 | */ 43 | 44 | 45 | #pragma once 46 | 47 | #include "esphome.h" 48 | #include "esphome/core/component.h" 49 | #include "esphome/core/automation.h" // для добавления Action 50 | #include "esphome/components/cover/cover.h" 51 | #include 52 | #include "esphome/core/helpers.h" // парсим строки встроенными инструментами 53 | #include // для работы с очередью 54 | 55 | 56 | 57 | namespace esphome { 58 | namespace bus_t4 { 59 | 60 | /* для короткого обращения к членам класса */ 61 | using namespace esphome::cover; 62 | //using esp8266::timeoutTemplate::oneShotMs; 63 | 64 | 65 | static const int _UART_NO=UART0; /* номер uart */ 66 | static const int TX_P = 1; /* пин Tx */ 67 | static const uint32_t BAUD_BREAK = 9200; /* бодрэйт для длинного импульса перед пакетом */ 68 | static const uint32_t BAUD_WORK = 19200; /* рабочий бодрэйт */ 69 | static const uint8_t START_CODE = 0x55; /*стартовый байт пакета */ 70 | 71 | static const float CLOSED_POSITION_THRESHOLD = 0.007; // Значение положения привода в процентах, ниже которого ворота считаются полностью закрытыми 72 | static const uint32_t POSITION_UPDATE_INTERVAL = 500; // Интервал обновления текущего положения привода, мс 73 | 74 | /* сетевые настройки esp 75 | Ряд может принимать значения от 0 до 63, по-умолчанию 0 76 | Адрес OVIEW начинается с 8 77 | 78 | При объединении в сеть несколько приводов с OXI необходимо для разных приводов указать разные ряды. 79 | В этом случае У OXI ряд должен быть как у привода, которым он управляет. 80 | */ 81 | 82 | 83 | 84 | 85 | 86 | 87 | /* Тип сообщения пакетов 88 | пока нас интересует только CMD и INF 89 | остальные глубоко не изучал и номера не проверял 90 | 6-й байт пакетов CMD и INF 91 | */ 92 | enum mes_type : uint8_t { 93 | CMD = 0x01, /* номер проверен, отправка команд автоматике */ 94 | // LSC = 0x02, /* работа со списками сценариев */ 95 | // LST = 0x03, /* работа со списками автоматик */ 96 | // POS = 0x04, /* запрос и изменение позиции автоматики */ 97 | // GRP = 0x05, /* отправка команд группе автоматик с указанием битовой маски мотора */ 98 | // SCN = 0x06, /* работа со сценариями */ 99 | // GRC = 0x07, /* отправка команд группе автоматик, созданных через Nice Screen Configuration Tool */ 100 | INF = 0x08, /* возвращает или устанавливает информацию об устройстве */ 101 | // LGR = 0x09, /* работа со списками групп */ 102 | // CGR = 0x0A, /* работа с категориями групп, созданных через Nice Screen Configuration Tool */ 103 | }; 104 | 105 | 106 | 107 | 108 | /* 109 | меню команды в иерархии oview 110 | 9-й байт пакетов CMD 111 | */ 112 | enum cmd_mnu : uint8_t { 113 | CONTROL = 0x01, 114 | }; 115 | 116 | 117 | /* используется в ответах STA*/ 118 | enum sub_run_cmd2 : uint8_t { 119 | STA_OPENING = 0x02, 120 | STA_CLOSING = 0x03, 121 | OPENED = 0x04, 122 | CLOSED = 0x05, 123 | ENDTIME = 0x06, // закончен маневр по таймауту 124 | STOPPED = 0x08, 125 | PART_OPENED = 0x10, // частичное открывание 126 | }; 127 | 128 | /* Ошибки */ 129 | enum errors_byte : uint8_t { 130 | NOERR = 0x00, // Нет ошибок 131 | FD = 0xFD, // Нет команды для этого устройства 132 | }; 133 | 134 | // Типы моторов 135 | enum motor_type : uint8_t { 136 | SLIDING = 0x01, 137 | SECTIONAL = 0x02, 138 | SWING = 0x03, 139 | BARRIER = 0x04, 140 | UPANDOVER = 0x05, // up-and-over подъемно-поворотные ворота 141 | }; 142 | 143 | // девятый байт 144 | enum whose_pkt : uint8_t { 145 | FOR_ALL = 0x00, /* пакет для/от всех */ 146 | FOR_CU = 0x04, /* пакет для/от блока управления */ 147 | FOR_OXI = 0x0A, /* пакет для/от приемника OXI */ 148 | }; 149 | 150 | // десятый байт GET/SET пакетов EVT, для пакетов CMD встречалось только значение RUN 151 | enum command_pkt : uint8_t { 152 | TYPE_M = 0x00, /* Запрос типа привода */ 153 | INF_STATUS = 0x01, // Состояние ворот (Открыто/Закрыто/Остановлено) 154 | WHO = 0x04, /* Кто в сети? */ 155 | MAC = 0x07, // mac address. 156 | MAN = 0x08, // manufacturer. 157 | PRD = 0x09, // product. 158 | INF_SUPPORT = 0x10, // Доступные INF команды 159 | HWR = 0x0a, // hardware version. 160 | FRM = 0x0b, // firmware version. 161 | DSC = 0x0c, // description. 162 | CUR_POS = 0x11, // текущее условное положение автоматики, DPRO924 после этого ждет выставления положений 163 | MAX_OPN = 0x12, // Максимально возможное открывание по энкодеру. 164 | POS_MAX = 0x18, // Максимальное положение (открывания) по энкодеру 165 | POS_MIN = 0x19, // Минимальное положение (закрывания) по энкодеру 166 | INF_P_OPN1 = 0x21, // Частичное открывание1 167 | INF_P_OPN2 = 0x22, // Частичное открывание2 168 | INF_P_OPN3 = 0x23, // Частичное открывание3 169 | INF_SLOW_OPN = 0x24, // Замедление в открывании 170 | INF_SLOW_CLS = 0x25, // Замедление в закрывании 171 | OPN_OFFSET = 0x28, /* Задержка открывания open offset */ 172 | CLS_OFFSET = 0x29, /* Задержка закрывания close offset */ 173 | OPN_DIS = 0x2a, /* Основные параметры - Разгрузка открытия Open discharge */ 174 | CLS_DIS = 0x2b, /* Основные параметры - Разгрузка закрытия Close discharge */ 175 | REV_TIME = 0x31, /* Основные параметры - Длительность реверса (Brief inversion value) */ 176 | OPN_PWR = 0x4A, /* Основные параметры - Управление усилием - Усилие открывания */ 177 | CLS_PWR = 0x4B, /* Основные параметры - Управление усилием - Усилие закрывания */ 178 | SPEED_OPN = 0x42, /* Основные параметры - Настройка скорости - Скорость открывания */ 179 | SPEED_CLS = 0x43, /* Основные параметры - Настройка скорости - Скорость закрывания */ 180 | SPEED_SLW_OPN = 0x45, /* Основные параметры - Настройка скорости - Скорость замедленного открывания */ 181 | SPEED_SLW_CLS = 0x46, /* Основные параметры - Настройка скорости - Скорость замедленного закрывания */ 182 | OUT1 = 0x51, /* Настройка выходов */ 183 | OUT2 = 0x52, /* Настройка выходов */ 184 | LOCK_TIME = 0x5A, /* Настройка выходов - Время работы замка */ 185 | S_CUP_TIME = 0x5C, /* Настройка выходов - Время работы присоски Suction Cup Time*/ 186 | LAMP_TIME = 0x5B, /* Настройка выходов - Время работы лампы освещения courtesy light Time*/ 187 | COMM_SBS = 0x61, /* Настройка команд - Пошагово */ 188 | COMM_POPN = 0x62, /* Настройка команд - Открыть частично */ 189 | COMM_OPN = 0x63, /* Настройка команд - Открыть */ 190 | COMM_CLS = 0x64, /* Настройка команд - Закрыть */ 191 | COMM_STP = 0x65, /* Настройка команд - СТОП */ 192 | COMM_PHOTO = 0x68, /* Настройка команд - Фото */ 193 | COMM_PHOTO2 = 0x69, /* Настройка команд - Фото2 */ 194 | COMM_PHOTO3 = 0x6A, /* Настройка команд - Фото3 */ 195 | COMM_OPN_STP = 0x6B, /* Настройка команд - Стоп при открывании */ 196 | COMM_CLS_STP = 0x6C, /* Настройка команд - Стоп при закрывании */ 197 | IN1 = 0x71, /* Настройка входов */ 198 | IN2 = 0x72, /* Настройка входов */ 199 | IN3 = 0x73, /* Настройка входов */ 200 | IN4 = 0x74, /* Настройка входов */ 201 | COMM_LET_OPN = 0x78, /* Настройка команд - Помеха открыванию */ 202 | COMM_LET_CLS = 0x79, /* Настройка команд - Помеха закрыванию */ 203 | 204 | AUTOCLS = 0x80, /* Основные параметры - Автозакрывание */ 205 | P_TIME = 0x81, /* Основные параметры - Время паузы */ 206 | PH_CLS_ON = 0x84, /* Основные параметры - Закрыть после Фото - Активно */ 207 | PH_CLS_VAR = 0x86, /* Основные параметры - Закрыть после Фото - Режим */ 208 | PH_CLS_TIME = 0x85, /* Основные параметры - Закрыть после Фото - Время ожидания */ 209 | ALW_CLS_ON = 0x88, /* Основные параметры - Всегда закрывать - Активно */ 210 | ALW_CLS_VAR = 0x8A, /* Основные параметры - Всегда закрывать - Режим */ 211 | ALW_CLS_TIME = 0x89, /* Основные параметры - Всегда закрывать - Время ожидания */ 212 | STAND_BY_ACT = 0x8c, /* Основные параметры - Режим ожидания - Активно ON / OFF */ 213 | WAIT_TIME = 0x8d, /* Основные параметры - Режим ожидания - Время ожидания */ 214 | STAND_BY_MODE = 0x8e, /* Основные параметры - Режим ожидания - Режим - safety = 0x00, bluebus=0x01, all=0x02*/ 215 | START_ON = 0x90, /* Основные параметры - Настройка пуска - Активно */ 216 | START_TIME = 0x91, /* Основные параметры - Настройка пуска - Время пуска */ 217 | SLOW_ON = 0xA2, /* Основные параметры - Замедление */ 218 | DIS_VAL = 0xA4, /* Положение - Значение недопустимо disable value */ 219 | 220 | BLINK_ON = 0x94, /* Основные параметры - Предмерцание - Активно */ 221 | BLINK_OPN_TIME = 0x95, /* Основные параметры - Предмерцание - Время при открывании */ 222 | BLINK_CLS_TIME = 0x99, /* Основные параметры - Предмерцание - Время при закрывании */ 223 | OP_BLOCK = 0x9a, /* Основные параметры - Блокирование мотора (Operator block)*/ 224 | KEY_LOCK = 0x9c, /* Основные параметры - Блокирование кнопок */ 225 | T_VAL = 0xB1, /*Alarm threshold value Порог до обслуживания в количестве маневров*/ 226 | P_COUNT = 0xB2, /* Partial count Выделенный счетчик*/ 227 | C_MAIN = 0xB4, /* Cancel maintenance Отмена обслуживания */ 228 | DIAG_BB = 0xD0, /* DIAGNOSTICS of bluebus devices */ 229 | INF_IO = 0xD1, /* состояние входов-выходов */ 230 | DIAG_PAR = 0xD2, /* DIAGNOSTICS of other parameters */ 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | CUR_MAN = 0x02, // Текущий Маневр 240 | SUBMNU = 0x04, // Подменю 241 | STA = 0xC0, // статус в движении 242 | MAIN_SET = 0x80, // Основные параметры 243 | RUN = 0x82, // Команда для выполнения 244 | 245 | }; 246 | 247 | 248 | /* run cmd 11-й байт EVT пакетов */ 249 | enum run_cmd : uint8_t { 250 | SET = 0xA9, /* запрос на изменение параметров */ 251 | GET = 0x99, /* запрос на получение параметров */ 252 | GET_SUPP_CMD = 0x89, /* получить поддерживаемые команды */ 253 | }; 254 | 255 | 256 | /* Команда, которая должна быть выполнена. 257 | 11-й байт пакета CMD 258 | Используется в запросах и ответах */ 259 | enum control_cmd : uint8_t { 260 | SBS = 0x01, /* Step by Step */ 261 | STOP = 0x02, /* Stop */ 262 | OPEN = 0x03, /* Open */ 263 | CLOSE = 0x04, /* Close */ 264 | P_OPN1 = 0x05, /* Partial opening 1 - частичное открывание, режим калитки */ 265 | P_OPN2 = 0x06, /* Partial opening 2 */ 266 | P_OPN3 = 0x07, /* Partial opening 3 */ 267 | RSP = 0x19, /* ответ интерфейса, подтверждающий получение команды */ 268 | EVT = 0x29, /* ответ интерфейса, отправляющий запрошенную информацию */ 269 | 270 | P_OPN4 = 0x0b, /* Partial opening 4 - Коллективно */ 271 | P_OPN5 = 0x0c, /* Partial opening 5 - Приоритет пошагово */ 272 | P_OPN6 = 0x0d, /* Partial opening 6 - Открыть и блокировать */ 273 | UNLK_OPN = 0x19, /* Разблокировать и открыть */ 274 | CLS_LOCK = 0x0E, /* Закрыть и блокировать */ 275 | UNLCK_CLS = 0x1A, /* Разблокировать и Закрыть */ 276 | LOCK = 0x0F, /* Блокировать*/ 277 | UNLOCK = 0x10, /* Разблокировать */ 278 | LIGHT_TIMER = 0x11, /* Таймер освещения */ 279 | LIGHT_SW = 0x12, /* Освещение вкл/выкл */ 280 | HOST_SBS = 0x13, /* Ведущий SBS */ 281 | HOST_OPN = 0x14, /* Ведущий открыть */ 282 | HOST_CLS = 0x15, /* Ведущий закрыть */ 283 | SLAVE_SBS = 0x16, /* Ведомый SBS */ 284 | SLAVE_OPN = 0x17, /* Ведомый открыть */ 285 | SLAVE_CLS = 0x18, /* Ведомый закрыть */ 286 | AUTO_ON = 0x1B, /* Автооткрывание активно */ 287 | AUTO_OFF = 0x1C, /* Автооткрывание неактивно */ 288 | 289 | }; 290 | 291 | 292 | 293 | 294 | 295 | 296 | /* Информация для лучшего понимания состава пакетов в протоколе */ 297 | // тело пакета запроса CMD 298 | // пакеты с размером тела 0x0c=12 байт 299 | /* 300 | struct packet_cmd_body_t { 301 | uint8_t byte_55; // Заголовок, всегда 0x55 302 | uint8_t pct_size1; // размер тела пакета (без заголовка и CRC. Общее количество байт минус три), для команд = 0x0c 303 | uint8_t for_series; // серия кому пакет ff = всем 304 | uint8_t for_address; // адрес кому пакет ff = всем 305 | uint8_t from_series; // серия от кого пакет 306 | uint8_t from_address; // адрес от кого пакет 307 | uint8_t mes_type; // тип сообщения, 1 = CMD, 8 = INF 308 | uint8_t mes_size; // количество байт дальше за вычетом двух байт CRC в конце, для команд = 5 309 | uint8_t crc1; // CRC1, XOR шести предыдущих байт 310 | uint8_t cmd_mnu; // Меню команды. cmd_mnu = 1 для команд управления 311 | uint8_t setup_submnu; // Подменю, в сочетании с группой команды определяет тип отправляемого сообщения 312 | uint8_t control_cmd; // Команда, которая должна быть выполнена 313 | uint8_t offset; // Смещение для ответов. Влияет на запросы вроде списка поддерживаемых комманд 314 | uint8_t crc2; // crc2, XOR четырех предыдущих байт 315 | uint8_t pct_size2; // размер тела пакета (без заголовка и CRC. Общее количество байт минус три), для команд = 0x0c 316 | 317 | }; 318 | 319 | 320 | 321 | 322 | 323 | // тело пакета ответа RSP 324 | // пакеты с размером тела 0x0e=14 байт 325 | struct packet_rsp_body_t { 326 | uint8_t byte_55; // Заголовок, всегда 0x55 327 | uint8_t pct_size1; // размер тела пакета (без заголовка и CRC. Общее количество байт минус три), >= 0x0e 328 | uint8_t to_series; // серия кому пакет ff = всем 329 | uint8_t to_address; // адрес кому пакет ff = всем 330 | uint8_t from_series; // серия от кого пакет 331 | uint8_t from_address; // адрес от кого пакет 332 | uint8_t mes_type; // тип сообщения, для этих пакетов всегда 8 = INF 333 | uint8_t mes_size; // количество байт дальше за вычетом двух байт CRC в конце, для команд = 5 334 | uint8_t crc1; // CRC1, XOR шести предыдущих байт 335 | uint8_t cmd_mnu; // Меню команды. cmd_mnu = 1 для команд управления 336 | uint8_t sub_inf_cmd; // Из какого подменю получил команду. Значение меньше на 0x80, чем первоначальное подменю 337 | uint8_t sub_run_cmd; // Какую команду получил. Значение больше на 0x80, чем полученная команда 338 | uint8_t hb_data; // данные, старший бит 339 | uint8_t lb_data; // данные, младший бит 340 | uint8_t err; // Ошибки 341 | uint8_t crc2; // crc2, XOR четырех предыдущих байт 342 | uint8_t pct_size2; // размер тела пакета (без заголовка и CRC. Общее количество байт минус три), >= 0x0e 343 | 344 | }; 345 | 346 | // тело пакета ответа с данными EVT 347 | 348 | struct packet_evt_body_t { 349 | uint8_t byte_55; // Заголовок, всегда 0x55 350 | uint8_t pct_size1; // размер тела пакета (без заголовка и CRC. Общее количество байт минус три), >= 0x0e 351 | uint8_t to_series; // серия кому пакет ff = всем 352 | uint8_t to_address; // адрес кому пакет ff = всем 353 | uint8_t from_series; // серия от кого пакет 354 | uint8_t from_address; // адрес от кого пакет 355 | uint8_t mes_type; // тип сообщения, для этих пакетов всегда 8 = INF 356 | uint8_t mes_size; // количество байт дальше за вычетом двух байт CRC в конце, для команд = 5 357 | uint8_t crc1; // CRC1, XOR шести предыдущих байт 358 | uint8_t whose; // Чей пакет. Варианты: 00 - общий, 04 - контроллера привода, 0A - приемника OXI 359 | uint8_t setup_submnu; // Из какого подменю получил команду. Значение равно первоначальному подменю 360 | uint8_t sub_run_cmd; // На какую команду отвечаем. Значение меньше на 0x80, чем отправленная ранее команда 361 | uint8_t next_data; // Следующий блок данных 362 | uint8_t err; // Ошибки 363 | uint8_t data_blk; // Блок данных, может занимать несколько байт 364 | uint8_t crc2; // crc2, XOR всех предыдущих байт до девятого (Чей пакет) 365 | uint8_t pct_size2; // размер тела пакета (без заголовка и CRC. Общее количество байт минус три), >= 0x0e 366 | 367 | }; 368 | 369 | 370 | */ 371 | 372 | enum position_hook_type : uint8_t { 373 | IGNORE = 0x00, 374 | STOP_UP = 0x01, 375 | STOP_DOWN = 0x02 376 | }; 377 | 378 | // создаю класс, наследую членов классов Component и Cover 379 | class NiceBusT4 : public Component, public Cover { 380 | public: 381 | 382 | // настройки привода 383 | bool autocls_flag; // Автозакрывание - L1 384 | bool photocls_flag; // Закрыть после фото - L2 385 | bool alwayscls_flag; // Всегда закрывать - L3 386 | bool init_ok = false; // определение привода при включении 387 | bool is_walky = false; // для walky отличается команда запроса положения 388 | bool is_robus = false; // для robus не нужно переодически запрашивать позицию 389 | bool is_ro = false; // для ro600 отличается пакет со статусом позиции и статусом движения 390 | 391 | void setup() override; 392 | void loop() override; 393 | void dump_config() override; // для вывода в лог информации об оборудовнии 394 | 395 | void send_raw_cmd(std::string data); 396 | void send_cmd(uint8_t data) {this->tx_buffer_.push(gen_control_cmd(data));} 397 | void send_inf_cmd(std::string to_addr, std::string whose, std::string command, std::string type_command, std::string next_data, bool data_on, std::string data_command); // длинная команда 398 | void set_mcu(std::string command, std::string data_command); // команда контроллеру мотора 399 | 400 | 401 | void set_class_gate(uint8_t class_gate) { class_gate_ = class_gate; } 402 | 403 | /* void set_update_interval(uint32_t update_interval) { // интервал получения статуса привода 404 | this->update_interval_ = update_interval; 405 | }*/ 406 | 407 | cover::CoverTraits get_traits() override; 408 | 409 | protected: 410 | void control(const cover::CoverCall &call) override; 411 | void send_command_(const uint8_t *data, uint8_t len); 412 | void request_position(void); // Запрос условного текущего положения привода 413 | void update_position(uint16_t newpos); // Обновление текущего положения привода 414 | 415 | uint32_t last_position_time{0}; // Время последнего обновления текущего положения 416 | uint32_t update_interval_{500}; 417 | uint32_t last_update_{0}; 418 | uint32_t last_uart_byte_{0}; 419 | 420 | CoverOperation last_published_op; // Последние опубликованные состояние и положение 421 | float last_published_pos{-1}; 422 | 423 | void publish_state_if_changed(void); 424 | 425 | uint8_t position_hook_type{IGNORE}; // Флаг и позиция установки заданного положения привода 426 | uint16_t position_hook_value; 427 | 428 | uint8_t class_gate_ = 0x55; // 0x01 sliding, 0x02 sectional, 0x03 swing, 0x04 barrier, 0x05 up-and-over 429 | // uint8_t last_init_command_; 430 | 431 | bool init_cu_flag = false; 432 | bool init_oxi_flag = false; 433 | 434 | 435 | // переменные для uart 436 | uint8_t _uart_nr; 437 | uart_t* _uart = nullptr; 438 | uint16_t _max_opn = 0; // максимальная позиция энкодера или таймера 439 | uint16_t _pos_opn = 2048; // позиция открытия энкодера или таймера, не для всех приводов. 440 | uint16_t _pos_cls = 0; // позиция закрытия энкодера или таймера, не для всех приводов 441 | uint16_t _pos_usl = 0; // условная текущая позиция энкодера или таймера, не для всех приводов 442 | // настройки заголовка формируемого пакета 443 | uint8_t addr_from[2] = {0x00, 0x66}; //от кого пакет, адрес bust4 шлюза 444 | uint8_t addr_to[2]; // = 0x00ff; // кому пакет, адрес контроллера привода, которым управляем 445 | uint8_t addr_oxi[2]; // = 0x000a; // адрес приемника 446 | 447 | std::vector raw_cmd_prepare (std::string data); // подготовка введенных пользователем данных для возможности отправки 448 | 449 | // генерация inf команд 450 | std::vector gen_inf_cmd(const uint8_t to_addr1, const uint8_t to_addr2, const uint8_t whose, const uint8_t inf_cmd, const uint8_t run_cmd, const uint8_t next_data, const std::vector &data, size_t len); // все поля 451 | std::vector gen_inf_cmd(const uint8_t whose, const uint8_t inf_cmd, const uint8_t run_cmd) {return gen_inf_cmd(this->addr_to[0], this->addr_to[1], whose, inf_cmd, run_cmd, 0x00, {0x00}, 0 );} // для команд без данных 452 | std::vector gen_inf_cmd(const uint8_t whose, const uint8_t inf_cmd, const uint8_t run_cmd, const uint8_t next_data, std::vector data){ 453 | return gen_inf_cmd(this->addr_to[0], this->addr_to[1], whose, inf_cmd, run_cmd, next_data, data, data.size());} // для команд c данными 454 | std::vector gen_inf_cmd(const uint8_t to_addr1, const uint8_t to_addr2, const uint8_t whose, const uint8_t inf_cmd, const uint8_t run_cmd, const uint8_t next_data){ 455 | return gen_inf_cmd(to_addr1, to_addr2, whose, inf_cmd, run_cmd, next_data, {0x00}, 0);} // для команд с адресом и без данных 456 | 457 | // генерация cmd команд 458 | std::vector gen_control_cmd(const uint8_t control_cmd); 459 | 460 | void init_device (const uint8_t addr1, const uint8_t addr2, const uint8_t device ); 461 | void send_array_cmd (std::vector data); 462 | void send_array_cmd (const uint8_t *data, size_t len); 463 | 464 | 465 | void parse_status_packet (const std::vector &data); // разбираем пакет статуса 466 | 467 | void handle_char_(uint8_t c); // обработчик полученного байта 468 | void handle_datapoint_(const uint8_t *buffer, size_t len); // обработчик полученных данных 469 | bool validate_message_(); // функция проверки полученного сообщения 470 | 471 | std::vector rx_message_; // здесь побайтно накапливается принятое сообщение 472 | std::queue> tx_buffer_; // очередь команд для отправки 473 | bool ready_to_tx_{true}; // флаг возможности отправлять команды 474 | 475 | std::vector manufacturer_ = {0x55, 0x55}; // при инициализации неизвестный производитель 476 | std::vector product_; 477 | std::vector hardware_; 478 | std::vector firmware_; 479 | std::vector description_; 480 | std::vector oxi_product; 481 | std::vector oxi_hardware; 482 | std::vector oxi_firmware; 483 | std::vector oxi_description; 484 | 485 | }; //класс 486 | 487 | } // namespace bus_t4 488 | } // namespace esphome 489 | -------------------------------------------------------------------------------- /gerber/Gerber_PCB_busT4adapter_3_2022-08-20.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/gerber/Gerber_PCB_busT4adapter_3_2022-08-20.zip -------------------------------------------------------------------------------- /img/3hs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/3hs.jpg -------------------------------------------------------------------------------- /img/924.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/924.jpg -------------------------------------------------------------------------------- /img/IMG20230306201230.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/IMG20230306201230.png -------------------------------------------------------------------------------- /img/IMG20240530155927.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/IMG20240530155927.jpg -------------------------------------------------------------------------------- /img/IMG20240530160117.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/IMG20240530160117.jpg -------------------------------------------------------------------------------- /img/IMG20240530160125.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/IMG20240530160125.jpg -------------------------------------------------------------------------------- /img/IMG20240531091711.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/IMG20240531091711.jpg -------------------------------------------------------------------------------- /img/IMG_20220113_160221.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/IMG_20220113_160221.jpg -------------------------------------------------------------------------------- /img/Schematic_busT4adapter_xl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/Schematic_busT4adapter_xl.png -------------------------------------------------------------------------------- /img/Schematic_bust4_2023-10-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/Schematic_bust4_2023-10-18.png -------------------------------------------------------------------------------- /img/Schematic_esphome_bust4_adapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/Schematic_esphome_bust4_adapter.png -------------------------------------------------------------------------------- /img/WeMos_DC_Power_Shield_Schematic_v1.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/WeMos_DC_Power_Shield_Schematic_v1.1.jpg -------------------------------------------------------------------------------- /img/hassio-bust4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/hassio-bust4.png -------------------------------------------------------------------------------- /img/level-shifter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/level-shifter.png -------------------------------------------------------------------------------- /img/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/log.png -------------------------------------------------------------------------------- /img/log2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/log2.png -------------------------------------------------------------------------------- /img/mc824h.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/mc824h.jpg -------------------------------------------------------------------------------- /img/picture.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/rba3c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/rba3c.jpg -------------------------------------------------------------------------------- /img/rba4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/rba4.jpg -------------------------------------------------------------------------------- /img/spin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/spin.jpg -------------------------------------------------------------------------------- /img/walky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/walky.jpg -------------------------------------------------------------------------------- /img/walky2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/img/walky2.jpg -------------------------------------------------------------------------------- /nice-wifi.yaml: -------------------------------------------------------------------------------- 1 | # Отправляет пакеты через uart в шину Nice BUS T4 2 | 3 | esphome: 4 | name: "nice-wifi" 5 | # name_add_mac_suffix: true 6 | # platform: 7 | esp8266: 8 | board: d1_mini 9 | 10 | external_components: 11 | - source: 12 | type: git 13 | url: https://github.com/pruwait/Nice_BusT4 14 | refresh: 0s 15 | # - source: my_components 16 | 17 | # Enable logging 18 | logger: 19 | level: DEBUG 20 | baud_rate: 0 21 | 22 | # Enable Home Assistant API 23 | api: 24 | reboot_timeout: 0s # иначе перезагружается при отключении от hassio 25 | 26 | services: 27 | # для отправки hex команд на привод 28 | - service: raw_command 29 | variables: 30 | raw_cmd: string 31 | then: 32 | lambda: |- 33 | nice_cover -> NiceBusT4::send_raw_cmd(raw_cmd); 34 | 35 | - service: send_inf_command 36 | variables: 37 | to_addr: string 38 | whose: string 39 | command: string 40 | type_command: string 41 | next_data: string 42 | data_on: bool 43 | data_command: string 44 | then: 45 | lambda: |- 46 | nice_cover -> NiceBusT4::send_inf_cmd(to_addr, whose, command, type_command, next_data, data_on, data_command); 47 | 48 | # распознавание длины створки 49 | - service: gate_length_recognition 50 | then: 51 | lambda: |- 52 | nice_cover -> NiceBusT4::set_mcu("0b","01"); 53 | 54 | # распознавание устройств BlueBus 55 | - service: devices_recognition 56 | then: 57 | lambda: |- 58 | nice_cover -> NiceBusT4::set_mcu("0a","01"); 59 | 60 | # усилие при закрытии 61 | - service: closing_force 62 | variables: 63 | force: string 64 | then: 65 | lambda: |- 66 | nice_cover -> NiceBusT4::set_mcu("4b", force); 67 | 68 | # усилие при открытии 69 | - service: opening_force 70 | variables: 71 | force: string 72 | then: 73 | lambda: |- 74 | nice_cover -> NiceBusT4::set_mcu("4a", force); 75 | 76 | 77 | ota: 78 | - platform: esphome 79 | # Set statul led for Wemos D1 mini 80 | status_led: 81 | pin: 82 | number: D4 83 | inverted: true 84 | 85 | wifi: 86 | ssid: !secret wifi_ssid 87 | password: !secret wifi_password 88 | 89 | 90 | # Enable fallback hotspot (captive portal) in case wifi connection fails 91 | ap: 92 | password: "" 93 | 94 | captive_portal: 95 | 96 | web_server: 97 | port: 80 98 | # js_include: "www.js" 99 | # js_url: "" 100 | # version: 2 101 | 102 | # Кнопки для отправки команд 103 | button: 104 | - platform: template 105 | name: Пошагово 106 | id: sbs 107 | on_press: 108 | lambda: |- 109 | nice_cover -> NiceBusT4::send_cmd(bus_t4::SBS); 110 | 111 | - platform: template 112 | name: Статус входов 113 | id: in_stat 114 | on_press: 115 | lambda: |- 116 | nice_cover -> NiceBusT4::send_raw_cmd("55.0D.00.03.00.66.08.06.6B.04.D0.99.00.00.4D.0D"); 117 | 118 | # nice_cover -> NiceBusT4::send_raw_cmd("55 0c 00 ff 00 66 01 05 9D 01 82 01 64 E6 0c"); 119 | # 55.0E.00.03.00.81.08.07.8D.04.0B.A9.00.01.01.A6.0E поиск положений 120 | 121 | - platform: template 122 | name: Частичное открытие 1 123 | id: p_opn1 124 | on_press: 125 | lambda: |- 126 | nice_cover -> NiceBusT4::send_cmd(bus_t4::P_OPN1); 127 | 128 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::STOP); 129 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::OPEN); 130 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::CLOSE); 131 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::P_OPN2); 132 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::P_OPN3); 133 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::P_OPN4); 134 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::P_OPN5); 135 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::P_OPN6); 136 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::UNLK_OPN); # Разблокировать и открыть 137 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::CLS_LOCK); # Закрыть и блокировать 138 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::UNLCK_CLS); # Разблокировать и Закрыть 139 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::LOCK); # Блокировать 140 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::UNLOCK); # Разблокировать 141 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::HOST_SBS); # Ведущий SBS 142 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::HOST_OPN); # Ведущий открыть 143 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::HOST_CLS); # Ведущий закрыть 144 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::SLAVE_SBS); # Ведомый SBS 145 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::SLAVE_OPN); # Ведомый открыть 146 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::SLAVE_CLS); # Ведомый закрыть 147 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::AUTO_ON); # Автооткрывание активно 148 | # nice_cover -> NiceBusT4::send_cmd(bus_t4::AUTO_OFF); # Автооткрывание неактивно 149 | 150 | cover: 151 | - platform: bus_t4 152 | name: "Nice Cover" 153 | device_class: gate 154 | id: nice_cover 155 | 156 | # address: 0x0003 # адрес привода 157 | # use_address: 0x0081 # адрес шлюза 158 | 159 | 160 | # работаем с приёмником OXI 161 | 162 | # отключаем автозакрытие ворот, если это нужно для погрузочно-разгрузочных работ 163 | switch: 164 | - platform: template 165 | name: "Автозакрытие" 166 | id: autoclose 167 | restore_mode: DISABLED 168 | # optimistic: true 169 | lambda: |- 170 | if (nice_cover -> NiceBusT4::autocls_flag) { 171 | return true; 172 | } else { 173 | return false; 174 | } 175 | turn_on_action: 176 | lambda: |- 177 | nice_cover -> NiceBusT4::send_inf_cmd("0003", "04", "80", "a9", "00", true, "01"); 178 | nice_cover -> NiceBusT4::send_inf_cmd("0003", "04", "84", "a9", "00", true, "01"); 179 | nice_cover -> NiceBusT4::send_inf_cmd("0003", "04", "80", "99", "00", true, "01"); 180 | nice_cover -> NiceBusT4::send_inf_cmd("0003", "04", "84", "99", "00", true, "01"); 181 | turn_off_action: 182 | lambda: |- 183 | nice_cover -> NiceBusT4::send_inf_cmd("0003", "04", "80", "a9", "00", true, "00"); 184 | nice_cover -> NiceBusT4::send_inf_cmd("0003", "04", "84", "a9", "00", true, "00"); 185 | nice_cover -> NiceBusT4::send_inf_cmd("0003", "04", "80", "99", "00", true, "01"); 186 | nice_cover -> NiceBusT4::send_inf_cmd("0003", "04", "84", "99", "00", true, "01"); 187 | 188 | 189 | 190 | #script: 191 | # - id: send_cmd 192 | # then: 193 | # - switch.turn_on: my_switch 194 | # - delay: 1s 195 | # - switch.turn_off: my_switch 196 | -------------------------------------------------------------------------------- /uart_break.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Отправка UART Break на Wemos D1 mini 3 | Arduino Core 3.0.2 4 | 5 | Логический анализатор помог отточить форму сигнала 6 | */ 7 | 8 | unsigned long timing; 9 | 10 | #define _UART_NO UART0 11 | #define TX_P 1 12 | #define baud_work 19200 13 | #define baud_break 9200 14 | #define LIN_BREAK_BAUDRATE(BAUD) ((BAUD * 9) / 13) 15 | 16 | // переменные для uart 17 | int _uart_nr; 18 | uart_t* _uart = nullptr; 19 | 20 | 21 | 22 | uint8_t master_tx_buf[] = {0x55, 0x00, 0x33}; 23 | uint8_t break_tx_buf[] = {0x00}; 24 | //uint8_t break_tx_buf[] = {0x00, 0x66, 0x77, 0x00}; 25 | //master_tx_buf[0] = 0x55; // sync byte 26 | //master_tx_buf[1] = 0x22; 27 | 28 | //uint8_t dummy = 0; 29 | 30 | 31 | void send_break() { 32 | 33 | /* Serial on/off Break 34 | // Работает плохо. 35 | // 1. В момент включения uart виден короткий лишний импульс 36 | // 2. Последний символ посылки низкого бодрэйта не успевает отправится до переключения, уходит в линию на высоком бодрэйте 37 | 38 | //Serial.end(); // HardwareSerial::begin consist end(); 39 | //Serial.flush(); 40 | Serial.begin(110); // Starting low baudrate uart 41 | Serial.write(0x00); // send low baud packet 42 | Serial.write(0x66); 43 | 44 | Serial.end(); // close low baud uart 45 | 46 | //Serial.flush(); 47 | Serial.begin(19200); // Starting high baudrate uart 48 | Serial.write(0x00); // send high baud packet 49 | Serial.write(0x55); 50 | */ 51 | 52 | 53 | //uart.h 54 | 55 | /* UART on/off Break 56 | //Работает плохо. 57 | // 1. В момент включения uart виден короткий лишний импульс 58 | // 2. Последний символ посылки низкого бодрэйта не успевает отправится до переключения, уходит в линию на высоком бодрэйте 59 | 60 | 61 | uart_flush(_uart); 62 | uart_uninit(_uart); 63 | _uart = uart_init(_UART_NO, baud_break, SERIAL_8N1, UART_TX_ONLY, TX_P, 2, false); 64 | uart_set_baudrate(_uart, baud_break); 65 | //uart_write(_uart, (char *)&dummy, 1); 66 | uart_write(_uart, (char *)break_tx_buf, sizeof(break_tx_buf)); 67 | uart_wait_tx_empty(_uart); 68 | //delayMicroseconds(100); 69 | uart_uninit(_uart); 70 | _uart = uart_init(_UART_NO, baud_work, SERIAL_8N1, SERIAL_FULL, TX_P, 2, false); 71 | uart_write(_uart, (char *)master_tx_buf, sizeof(master_tx_buf)); 72 | uart_wait_tx_empty(_uart); 73 | */ 74 | 75 | 76 | 77 | // UART set_baudrate Break 78 | // Работает хорошо с wemos d1 mini и Arduino Core 3.0.2 79 | 80 | // Отправка break + посылка 81 | 82 | uart_flush(_uart); // очищаем uart 83 | uart_set_baudrate(_uart, baud_break); // занижаем бодрэйт 84 | //uart_write(_uart, (char *)&dummy, 1); 85 | uart_write(_uart, (char *)break_tx_buf, sizeof(break_tx_buf)); // отправляем ноль на низкой скорости, длиинный ноль 86 | uart_wait_tx_empty(_uart); // ждём, пока отправка завершится. Здесь в библиотеке uart.h (esp8266 core 3.0.2) ошибка, ожидания недостаточно при дальнейшем uart_set_baudrate(). 87 | delayMicroseconds(90); // добавляем ожидания, иначе скорость переключится раньше отправки. С задержкой на d1-mini я получил идеальный сигнал, break = 520us? отправлял 0x00 88 | uart_set_baudrate(_uart, baud_work); // возвращаем рабочий бодрэйт 89 | uart_write(_uart, (char *)master_tx_buf, sizeof(master_tx_buf)); // отправляем основную посылку 90 | uart_wait_tx_empty(_uart); // ждем завершения отправки 91 | 92 | 93 | } 94 | 95 | void setup() { 96 | 97 | pinMode(LED_BUILTIN, OUTPUT); 98 | _uart = uart_init(_UART_NO, baud_work, SERIAL_8N1, SERIAL_FULL, TX_P, 256, false); 99 | } 100 | 101 | 102 | void loop() { 103 | 104 | 105 | if (millis() - timing > 2000) { 106 | timing = millis(); 107 | bool on_led = digitalRead(LED_BUILTIN); 108 | digitalWrite(LED_BUILTIN, !on_led); 109 | send_break(); 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /uart_stop_cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruwait/Nice_BusT4/dbdc6286dc6176449e77795b996f9621cd24c153/uart_stop_cmd.png --------------------------------------------------------------------------------