Приводы штор dooya

добрый день.
4 года назад делал у себя дома УД на WB.
Единственная проблема, которая возникла это работа с этими приводами.
они вроде в системе есть, ВУ создаются и даже работают, если ими управлять, но с них постоянно сыплются ошибки чтения. В первый год я приложил максимум усилий, что бы победить эту проблему, но безрезультатно. Потом плюнул, так в моих сценариях чтение положения штор не сильно было нужно, а запись нормально работала.

первое чудо случилось после обновления WB до 2602. Из 5 штор 2 стали проявлять признаки полноценной жизни. Ошибки чтения с них пропали. Остальные продолжили работать в том же режиме (запись работает, чтение идет с ошибками)
Вчера я обновился до 2606… и опять произошло чудо.
те две “живые” шторы так и работают нормально, а остальные вылетели с концами. Они глухо красные в WD и ни на что не реагируют.

Все мои сценарии открытия штор, понятно,…терпят фиаско…Жена ругается.
Подскажите…что делать?

Здравствуйте!

  1. Для диагностики проблемы пришлите, пожалуйста, архив с диагностической информацией контроллера. Создание архива описано в документации.
  2. Какие приводы используете? Приведите точную модель и каким образом их подключили.
  3. Опишите топологию шины, если подключение проводное.
  4. Как организовано питание приводов?
  1. приложил.
  2. Движки штор вот такие Onviz MR2234 c modbus на борту
    два на кухне 3 с в спальне
    проадресованы так:
    0x0101 Dooya 82 (Мотор раздвижных штор) - работает

0x0102 Dooya 82 (Мотор раздвижных штор)

0x0201 Dooya 82 (Мотор раздвижных штор)

0x0202 Dooya 82 (Мотор раздвижных штор) - работает

0x0203 Dooya 82 (Мотор раздвижных штор)

  1. подключены физически к MOD1 общим кабелем (utp медный многожильный) из щита по периметру квартиры. максимально удаленный (0x0203) привод метрах в 15 по длине провода.
    но предпоследний привод на линии нормально определяется и полностью работает.
    Напомню что до 2602 они все вели себя одинаково: писалось в них но не читалось!
    на 2602 2 движка стали работать вообще без ошибок, остальные только писались, но не читались
    2606 2 работают… остальные вообще отлетели.
    продолжу…
    Около каждого двигателя розетка с разъемом типа телефонного на 4 контакта и короткий патчкорд до движка.
    гарантий чистоты физ линии у меня нет, конечно…
    но я раньше выдергивал по очереди двигатели на коротком проводе к разным портам… И там был ровно такой же результат (до установки 2602).
  2. Питание приводов 220 в подключено от щита висят на отдельном автомате.

приложен диагностический архив, доступен только сотрудникам поддержки
(335,2 КБ)

Не нахожу информации о том, что этот привод поддерживает протокол modbus. Может быть у вас специальное исполнение. Пришлите, пожалуйста, свой шаблон, который составляли для данных приводов и карту регистров.

В числе поддерживаемых Wiren Board приводов вашей модели не нахожу - это значит, что мы не тестировали их.

В логах фигурирует “Bad response” и нет ошибок по таймауту - это говорит о том, что проблема не в физичкая.

Jun 18 17:24:23 wirenboard-AO2T7LJW wb-mqtt-serial[15440]: WARNING: [serial device] TSerialDevice::ReadRegister(): Serial protocol error: Bad response [slave_id is dooya:0x0102]
Jun 18 17:24:23 wirenboard-AO2T7LJW wb-mqtt-serial[15440]: WARNING: [serial device] TSerialDevice::ReadRegister(): Serial protocol error: Bad response [slave_id is dooya:0x0203]

двигатель похоже вот такой

шаблон с самого начала был использован для dooya82.

я не исключаю…
возможно действительно вы не тестировали эти движки,
но на момент покупки их в 2022 году…у меня почему то сомнений не было, что движки один в один Dooya DT82
Возможно продавец меня убедил
да…и все работало же почти
:)))))
вот нашел в поддржке

такой же движок

кстати все инструкции по подключению приводов
от сюда Использование электрокарниза Dooya DT82 с контроллером Wiren Board — Wiren Board
сработали в точности…
Поэтому я ни разу не усомнился, что это подходящий шаблон

А есть возможность каким то методом изучить регистры моих движков и написать подобие шаблона или изменить существующий?
дайте методичку … я бы в промежутках между основной работой с удовольствием

ну и корневой вопрос…почему два то идеально стали работать вдруг…
а три вообще перестали работать
а раньше все 5 работали только на запись… чтение было с ошибками

Я не составлял шаблон … использовал с самого начала шаблон Dooya 82 (мотор раздвижных штор)

Полагаю, что отправкой команд электромотору с помощью serial_tool, как в примере, возможно добится корректной работы. Добавлю в список предложений “поддержать Onviz MR-2234F”

Ну раз на мою тему сослались - рекомендую обратиться к онвиз поддержке, они есть на этом портале @Onviz

Ребята реально помогают

Там не dooya протокол. Точнее - “почти” но с отличиями.
Проще скриптом написать.

Хорошее предложение…
я, кстати, по этому пути тоже двигаться пытался…но это было 4 года назад. я тогда только начинал JS постигать… получилось не очень, да и шаблоны тогда удовлетворили вполне.
Сейчас, надеюсь, будет попроще.
Есть какие то ссылочки на тексты или прототипы …подглядеть, что бы понять куда двигаться?

а я пока пойду свои скрипты старые подниму.
что то в этом духе?

return

function addCrc16MODBUS(string) {
var CrcTable = [
0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040
];

var crc = 0xFFFF;
//  log(string.length)
for (var i = 0, l = string.length; i < l; i++) {
crc = ((crc >> 8) ^ CrcTable[(crc ^ string[i]) & 0xFF]);
};
crc = swap16(crc);
string.push(crc >> 8);
string.push(crc & 0xFF);
var hexString = string.map(function (x) { //(“0”+(Number(d).toString(16))).slice(-2).toUpperCase()
x = x + 0xFF + 1;  // twos complement
x = x.toString(16).toUpperCase(); // to hex
x = (“00” + x).substr(-2); // zero-pad to 8-digits
return x
}).join(’ ');
// var result = hexString + " " +(“00” + (crc>>8)).toString(16).substr(-2) + " " + (“00” + (crc&0xFF)).toString(16).substr(-2);
var result = hexString;
//log(“string + crc44 = {}”, result);
return result;
}

function swap16(val) { // меняем местами байты в слове
return ((val & 0xFF) << 8)
| ((val >> 8) & 0xFF);
}

var hexString = (100).toString(16);
if (hexString.length % 2) {
hexString = ‘0’ + hexString;
}
log(hexString)

log(“0x55 “+ 0x55);
//log(addCrc16MODBUS([0x55,0x00,0x00,0x02,0x00,0x02,0x01,0x01]))
//log(addCrc16MODBUS([0x55,0x01,0x01,0x03,0x04,30])) //55 01 01 03 04 1E
//s = addCrc16MODBUS([0x55,0x01,0x01,0x03,0x04,0x1E]);
//log(”/usr/bin/printf '” + (’ ’ + s).replace(/ /g, “\\x”) + “' > /dev/ttyMOD1”);
//“/usr/bin/printf ‘\x55\x01\x01\x03\x04\x1E\x42\xEA’ > /dev/ttyMOD1”

defineVirtualDevice(“Dooya_0x0101”, {
title: “Dooya_0x0101”,
cells: {
position: {
type: “range”,
value: 0,
max: 100,
},
enabled: {
type: “pushbutton”,
value: false
}
}
});

posChanging = false;

defineRule(“Dooya_position”, {
whenChanged: “Dooya_0x0101/position”,
then: function(newValue) {
if (posChanging){
return;
}
posChanging = true;
s = addCrc16MODBUS([0x55,0x01,0x01,0x03,0x04, newValue]);
//     s = addCrc16MODBUS([0x55,0x01,0x01,0x01,0x02]);
//    s = “/usr/bin/printf '” + (’ ’ + s).replace(/ /g, “\x”) + “' > /dev/ttyMOD1”; //“/usr/bin/printf ‘\x55\x01\x01\x03\x04\x1E\x42\xEA’ > /dev/ttyMOD1”
log(s)
s = “'” + s + “'”
log(s)
// //  runShellCommand(“serial_tool -b 9600 -s1 -pN /dev/ttyMOD1 --batch ‘55 01 01 01 02 01 A1 42’ -t 1.0”, {
//    runShellCommand(“serial_tool -b 9600 -pN -d8 -s1 -t0.2 /dev/ttyMOD1 --batch ‘55 01 01 01 02 01 A1 42’”, {
runShellCommand("serial_tool -b 9600 -pN -d8 -s1 -t0.2 /dev/ttyMOD1 --batch " + s, {
captureOutput: true,
captureErrorOutput: true,
exitCallback: function(exitCode, capturedOutput, capturedErrorOutput ){
log("exitCode: " + exitCode)
log("capturedOutput: " + capturedOutput)
log("capturedErrorOutput: " + capturedErrorOutput)
dev[“Dooya_0x0101”][“enabled”] = true;
//return;
}
});
}
});

var _interval = null;

defineRule({
whenChanged: “Dooya_0x0101/enabled”,
then: function (newValue, devName, cellName) {
var n = 0;
var P = 0;
var prevP = -1;
if (dev[“Dooya_0x0101”][“enabled”]){
log(“Старт”)
_interval = setInterval(function () {
n += 1;
runShellCommand(“serial_tool -b 9600 -pN -d8 -s1 -t0.2 /dev/ttyMOD1 --batch ‘55 01 01 01 02 01 A1 42’”, {
captureOutput: true,
exitCallback: function(exitCode, capturedOutput){
var s = ‘0x’ + capturedOutput.substring(15,17);
P = parseInt(s, 16);
if (P > -1 ) {
dev[“Dooya_0x0101”][“position”] = P;
}
}
});
if ((n >= 20) || (P == prevP)){
clearInterval(_interval);
posChanging = false;
}
prevP = P;
}, 500);
}
}
});

Вот пример от пользователя.

Нет, без таблиц, расчет функцией сделан.

Спасибо.
Погружусь!

Спасибо. Там действительно на портале много всего. Даже шаблоны есть для WB.
сегодня попробую.

Провел я лабораторную работу со своими приводами…
Судя по результатам у меня приводы 2х разных типов
2 dooya подобные
а 3 вообще не понятно что…
теперь понятно почему 2 полноценно работать стали, а 3 вообще выпали
На чтение эти 3 привода выдают ответ на 1 байт больше

root@wirenboard-AO2T7LJW:~# systemctl stop wb-mqtt-serial
root@wirenboard-AO2T7LJW:~#  serial_tool -b 9600 -p N -d 8 -s 1 -t 1 /dev/ttyMOD1
serial_tool on /dev/ttyMOD1: 9600 8N1.0
Enter your commands below in HEX form.
All characters but 0-9,a-f including spaces are ignored.
Press Control-D or Control-C to leave the application.
Press [Enter] to print received data

Position

55 01 01 01 02 01 A1 42
<< 55 01 01 01 01 01 A1 B2
55 02 01 01 02 01 E5 42
<< 55 02 01 01 02 01 03 C3 8A
55 01 02 01 02 01 A1 06
<< 55 01 02 01 02 01 00 C7 B8
55 02 02 01 02 01 E5 06
<< 55 02 02 01 01 00 24 36
55 03 02 01 02 01 D8 C6
<< 55 03 02 01 02 01 00 C6 5A

Direction

55 01 01 01 03 01 A0 D2
<< 55 01 01 01 01 00 60 72
55 02 01 01 03 01 E4 D2
<< 55 02 01 01 03 01 00 D2 4B
55 01 02 01 03 01 A0 96
<< 55 01 02 01 03 01 00 96 78
55 02 02 01 03 01 E4 96
<< 55 02 02 01 01 00 24 36
55 03 02 01 03 01 D9 56
<< 55 03 02 01 03 01 00 97 9A

Open

55 01 01 03 01 B9 00
<< 55 01 01 03 01 B9 00
55 02 01 03 01 B9 44
<< 55 02 01 03 01 B9 44
55 01 02 03 01 49 00
<< 55 01 02 03 01 49 00
55 02 02 03 01 49 44
<< 55 02 02 03 01 49 44
55 03 02 03 01 48 B8
<< 55 03 02 03 01 48 B8

Close

55 01 01 03 02 F9 01
<< 55 01 01 03 02 F9 01
55 02 01 03 02 F9 45
<< 55 02 01 03 02 F9 45
55 01 02 03 02 09 01
<< 55 01 02 03 02 09 01
55 02 02 03 02 09 45
<< 55 02 02 03 02 09 45
55 03 02 03 02 08 B9
<< 55 03 02 03 02 08 B9

STOP

55 01 01 03 03 38 C1
<< 55 01 01 03 03 38 C1
55 02 01 03 03 38 85
<< 55 02 01 03 03 38 85
55 01 02 03 03 C8 C1
<< 55 01 02 03 03 C8 C1
55 02 02 03 03 C8 85
<< 55 02 02 03 03 C8 85
55 03 02 03 03 C9 79
<< 55 03 02 03 03 C9 79

50% откр

55 01 01 03 04 32 43 37
<< 55 01 01 03 04 32 43 37
55 02 01 03 04 32 07 37
<< 55 02 01 03 04 32 07 37
55 01 02 03 04 32 43 73
<< 55 01 02 03 04 32 43 73
55 02 02 03 04 32 07 73
<< 55 02 02 03 04 32 07 73
55 03 02 03 04 32 3A B3
<< 55 03 02 03 04 32 3A B3

04

55 01 01 01 04 01 A2 E2
<< 55 01 01 01 04 01 A2 E2
55 02 01 01 04 01 E6 E2
<< 55 02 01 01 04 01 00 63 8A
55 01 02 01 04 01 A2 A6
<< 55 01 02 01 04 01 00 27 B9
55 02 02 01 04 01 E6 A6
<< 55 02 02 01 01 00 24 36
55 03 02 01 04 01 DB 66
<< 55 03 02 01 04 01 00 26 5B

05

55 01 01 01 05 01 A3 72
<< 55 01 01 01 01 00 60 72
55 02 01 01 05 01 E7 72
<< 55 02 01 01 05 01 00 32 4A
55 01 02 01 05 01 A3 36
<< 55 01 02 01 05 01 00 76 79
55 02 02 01 05 01 E7 36
<< 55 02 02 01 01 00 24 36
55 03 02 01 05 01 DA F6
<< 55 03 02 01 05 01 00 77 9B 


забавно мне движки продали…
попробую теперь в Onviz постучаться.

  1. Поправил для себя рекомендованный код
  2. Удалил свои апоплексические приводы штор (в данном контексте красные) из устройств.
  3. Вызвал это в моем основном скрипте createCurtain(“Штора кухня 1”, 0x01, 0x01)…для всех приводов
  4. Все устройства подхватились на лету, даже менять больше ничего не пришлось в сценариях, благодаря var curtainVdPrefix = “dooya_”
  5. Все шторы полноценно работают…
  6. 4 года этого счастья ждал, а оно было рядом
  7. Всем спасибо!
// универсальный скрипт подходит для привода AT6501 и Dooya DT82

// Хранит флаги автоматической установки слайдера позиции
var positionSetAuto = {};
// Путь к RPC
var pathRPC = "/rpc/v1/wb-mqtt-serial/port/Load/";

var portCurtain = "/dev/ttyMOD1";
var speedPortCurtain = 9600;
var parityCurtain = "N";
var stopBitCurtain = 1;
var pollIntervalMs = 1500;
var curtainVdPrefix = "dooya_";
var writeResponseSize = 8;

// Список приводов: CurtainCh хранится как 0xHHLL, в протокол отправляется L,H.
var curtainDrivers = [
  { title: "Штора кухня 1", CurtainCh: 0x0101, readResponseSize: 8 },
  { title: "Штора кухня 2", CurtainCh: 0x0102, readResponseSize: 9 },
  { title: "Штора спальня 1", CurtainCh: 0x0201, readResponseSize: 9 },
  { title: "Штора спальня 2", CurtainCh: 0x0202, readResponseSize: 8 },
  { title: "Штора спальня 3", CurtainCh: 0x0203, readResponseSize: 9 }
];

function crc16Modbus(buffer) {
  var crc = 0xFFFF;
  for (var pos = 0; pos < buffer.length; pos++) {
    crc ^= buffer[pos];
    for (var i = 0; i < 8; i++) {
      crc = (crc & 0x0001) !== 0 ? (crc >> 1) ^ 0xA001 : crc >> 1;
    }
  }
  return [crc & 0xFF, (crc >> 8) & 0xFF];
}

// Переводит массив байтов в HEX-строку для RPC format="HEX".
function bytesToHexString(arr) {
  return arr.map(function (byte) {
    return ("0" + (byte & 0xFF).toString(16)).slice(-2);
  }).join("");
}

// Разбирает HEX-строку ответа в массив байтов.
function hexStringToBytes(hex) {
  var bytes = [];
  for (var i = 0; i < hex.length; i += 2) {
    bytes.push(parseInt(hex.substr(i, 2), 16));
  }
  return bytes;
}

// Проверяет два последних байта ответа как CRC16 Modbus.
function checkCrc16Modbus(hexStr) {
  var bytes = hexStringToBytes(hexStr);
  if (bytes.length < 3) return false;

  var crcCalc = crc16Modbus(bytes.slice(0, -2));
  return bytes[bytes.length - 2] === crcCalc[0] && bytes[bytes.length - 1] === crcCalc[1];
}

function requestRPC(clientID, requestID, messageType, message, responseSize) {
  // Формируем JSON запрос:
  var strJson = JSON.stringify({
    params: {
      response_size: responseSize,
      format: messageType,
      path: portCurtain,
      baud_rate: speedPortCurtain,
      parity: parityCurtain,
      data_bits: 8,
      stop_bits: stopBitCurtain,
      msg: message
    },
    id: requestID
  });
  publish(pathRPC + clientID, strJson, 2, false);
}

// Собирает команду привода и добавляет CRC.
function CurtainRequestMsg(CurtainCh, commandCurtain, dataCode, dataData) {
  // Сформируем набор байт команды.
  var data = [0x55, CurtainCh & 0xFF, (CurtainCh >> 8) & 0xFF, commandCurtain, dataCode];

  if (dataData !== null && dataData !== undefined) {
    data = data.concat(Array.isArray(dataData) ? dataData : [dataData]);
  }
  return bytesToHexString(data.concat(crc16Modbus(data)));
}

function makeNewVirtualControl(vdName, vdTitle, nameControl, typeControl) {
  if (getDevice(vdName) === undefined) {
    log.info("Define new device " + vdName);
    defineVirtualDevice(vdName, { title: vdTitle, cells: {} });
  }
  // Проверим есть ли уже контрол и если нет - создадим.
  var devObj = getDevice(vdName);
  if (devObj && !devObj.isControlExists(nameControl)) {
    log.debug("Контрола " + nameControl + " нет, создаем.");
    devObj.addControl(nameControl, typeControl);
  }
}

function createCurtain(driver) {
  var name = driver.title;
  var ch = driver.CurtainCh;
  var resSize = driver.readResponseSize || 9;
  // Адрес VD и ключ runtime-флагов: 0xHHLL.
  var addr = "0x" + ("0000" + (ch & 0xFFFF).toString(16).toUpperCase()).slice(-4);
  var vd = curtainVdPrefix + addr;
  var cmds = [
    { id: "Position", t: "Позиция", cfg: { type: "range", value: 0, min: 0, max: 100, readonly: false }, cmd: 0x03, code: 0x04 },
    { id: "Open", t: "Открыть", cfg: { type: "pushbutton", readonly: false }, cmd: 0x03, code: 0x01 },
    { id: "Stop", t: "Стоп", cfg: { type: "pushbutton", readonly: false }, cmd: 0x03, code: 0x03 },
    { id: "Close", t: "Закрыть", cfg: { type: "pushbutton", readonly: false }, cmd: 0x03, code: 0x02 }
  ];
  cmds.forEach(function (c) {
    c.cfg.title = c.t;
    makeNewVirtualControl(vd, name, c.id, c.cfg);
    defineRule(vd + "_rule_" + c.id, {
      whenChanged: vd + "/" + c.id,
      then: function () {
        var data = null;
        var req;
        if (c.id === "Position") {
          // Авто-обновление слайдера из ответа привода не должно отправлять команду обратно.
          if (positionSetAuto[addr]) {
            positionSetAuto[addr] = 0;
            return;
          }
          data = (dev[vd] && dev[vd]["Position"] !== undefined) ? dev[vd]["Position"] : 0;
        }
        req = CurtainRequestMsg(ch, c.cmd, c.code, data);
        log.info(name + " " + c.t + "=", req);
        requestRPC(vd, 1, "HEX", req, writeResponseSize);
      }
    });
  });

  // Таймер опроса позиции.
  var timerName = vd + "_timer";
  if (typeof killTicker === "function") killTicker(timerName);
  startTicker(timerName, pollIntervalMs);
  defineRule(vd + "_rule", {
    when: function () { return timers[timerName] && timers[timerName].firing; },
    then: function () {
      requestRPC(vd, 1, "HEX", CurtainRequestMsg(ch, 0x01, 0x02, 0x01), resSize);
    }
  });
  // MQTT reply tracking
  trackMqtt(pathRPC + vd + "/reply", function (message) {
    var obj;
    try {
      obj = JSON.parse(message.value);
    } catch (e) {
      log.error("JSON parse error: " + e);
      return;
    }
    if (!obj || obj.error !== null) return;
    var hex = obj.result.response;
    if (!checkCrc16Modbus(hex)) {
      log.info("reply CRC ERROR!");
      return;
    }
    var b = hexStringToBytes(hex);
    // Ответ текущей позиции бывает в двух форматах: 8 байт и 9 байт.
    var pos = (b.length === 8 && b[3] === 0x01 && b[4] === 0x01) ? b[5] :
      (b.length === 9 && b[3] === 0x01 && b[4] === 0x02) ? b[6] : null;
    if (pos !== null && dev[vd] && pos !== dev[vd]["Position"]) {
      positionSetAuto[addr] = 1;
      dev[vd]["Position"] = pos;
    }
  });

  return 0;
}

function createAllCurtain() {
  curtainDrivers.forEach(createCurtain);
}

exports.createCurtain = createCurtain;
exports.createAllCurtain = createAllCurtain;

Ага, благодарю - вот это прямо полезное наблюдение. Как-то уже видел что в одной внешне идентичной партии был один привод с другой платой, соответственно с другим протоколом. Но там решилось заменой привода продавцом.