Оставляю скрипт управления приводом AT6501, если пригодится кому-нибудь.
Добавлять экземпляры приводов нужно в самом конце по примеру:
// createBlind(NameBlind, portBlind, speedPortBlind, parityBlind, stopBitBlind, blindID, blindCh_L, blindCh_H)
createBlind("Штора кухня", "/dev/ttyRS485-2", 9600, "N", 1, 0, 0x01, 0x02);
Внимание: Идентификатор привода (BlindID) и Имя(NameBlind) должны быть уникальны.
Скрипт правила:
// хранит флаги типа операции установки слайдера позиции
var positionSetAuto = {};
// Путь к RPC
var pathRPC = "/rpc/v1/wb-mqtt-serial/port/Load/";
function crc16Modbus(buffer) {
var crc = 0xFFFF;
for (var pos = 0; pos < buffer.length; pos++) {
crc ^= buffer[pos];
for (var i = 0; i < 8; i++) {
if ((crc & 0x0001) !== 0) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
// Return as a 16-bit integer (LSB first, Modbus style)
return [crc & 0xFF, (crc >> 8) & 0xFF];
}
function getByteAt(hexStr, pos) {
// Each byte is 2 hex characters
var start = pos * 2;
if (start + 2 > hexStr.length) {
throw new Error("Position out of range");
}
return parseInt(hexStr.substr(start, 2), 16);
}
function bytesToHexString(arr) {
var result = '';
for (var i = 0; i < arr.length; i++) {
var hex = arr[i].toString(16);
if (hex.length < 2) {
hex = '0' + hex;
}
result += hex;
}
return result;
}
function hexStringToBytes(hex) {
var bytes = [];
for (var i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.substr(i, 2), 16));
}
return bytes;
}
function checkCrc16Modbus(hexStr) {
var bytes = hexStringToBytes(hexStr);
if (bytes.length < 3) {
return false; // Not enough data
}
// Split data and CRC
var dataBytes = bytes.slice(0, -2);
var crcGiven = [bytes[bytes.length - 2], bytes[bytes.length - 1]];
// Calculate CRC
var crcCalc = crc16Modbus(dataBytes);
// Compare
return (crcGiven[0] === crcCalc[0] && crcGiven[1] === crcCalc[1]);
}
function createBlind(NameBlind, portBlind, speedPortBlind, parityBlind, stopBitBlind, blindID, blindCh_L, blindCh_H) {
// Создаем виртуальное устройство
makeNewVirtualControl(NameBlind, "Position", { type: "range", value: 0, min: 0, max: 100, readonly: false });
makeNewVirtualControl(NameBlind, "Up", { type: "pushbutton", readonly: false });
makeNewVirtualControl(NameBlind, "Stop", { type: "pushbutton", readonly: false });
makeNewVirtualControl(NameBlind, "Down", { type: "pushbutton", readonly: false });
// Up
defineRule(NameBlind + "_rule_Up", {
whenChanged: NameBlind + "/Up",
then: function () {
log.info(NameBlind + "_Up");
var req = blindRequestMsg(blindID, blindCh_L, blindCh_H, 0x03, 0x01, null);
log.info("Up=", req);
requestRPC(portBlind, speedPortBlind, parityBlind, stopBitBlind, NameBlind, 1, "HEX", req, 8);
}
});
// Stop
defineRule(NameBlind + "_rule_Stop", {
whenChanged: NameBlind + "/Stop",
then: function () {
var req = blindRequestMsg(blindID, blindCh_L, blindCh_H, 0x03, 0x03, null);
log.info("req_Stop=", req);
requestRPC(portBlind, speedPortBlind, parityBlind, stopBitBlind, NameBlind, 1, "HEX", req, 8);
}
});
// Down
defineRule(NameBlind + "_rule_Down", {
whenChanged: NameBlind + "/Down",
then: function () {
var req = blindRequestMsg(blindID, blindCh_L, blindCh_H, 0x03, 0x02, null);
requestRPC(portBlind, speedPortBlind, parityBlind, stopBitBlind, NameBlind, 1, "HEX", req, 8);
}
});
// Position slider
defineRule(NameBlind + "_rule_position", {
whenChanged: NameBlind + "/Position",
then: function (value) {
var is_auto_set = positionSetAuto[blindID];
if (is_auto_set === 0) {
var val = (dev[NameBlind] && dev[NameBlind]["Position"] !== undefined) ? dev[NameBlind]["Position"] : 0;
var req = blindRequestMsg(blindID, blindCh_L, blindCh_H, 0x03, 0x04, val);
requestRPC(portBlind, speedPortBlind, parityBlind, stopBitBlind, NameBlind, 1, "HEX", req, 8);
} else {
positionSetAuto[blindID] = 0;
}
}
});
// Таймер опроса позиции (500мс)
startTicker(NameBlind + "_timer", 500);
defineRule(NameBlind + "_rule", {
when: function () { return timers[NameBlind + "_timer"] && timers[NameBlind + "_timer"].firing; },
then: function () {
var req = blindRequestMsg(blindID, blindCh_L, blindCh_H, 0x01, 0x02, 0x01);
log.info("req_timer " + NameBlind + "=", req);
requestRPC(portBlind, speedPortBlind, parityBlind, stopBitBlind, NameBlind, 1, "HEX", req, 9);
}
});
// MQTT reply tracking
trackMqtt(pathRPC + NameBlind + "/reply", function (message) {
log.info("from " + NameBlind + " name: {}, value: {}".format(message.topic, message.value));
var replyObj;
try {
replyObj = JSON.parse(message.value);
} catch (e) {
log.error("JSON parse error: " + e);
return;
}
if (replyObj && replyObj.error === null) {
var hexStr = replyObj.result.response;
log.info("from " + NameBlind + " replyObj.result.response: {}".format(hexStr));
if (checkCrc16Modbus(hexStr)) {
log.info("reply CRC is correct!");
// если получена текущая позиция
if (getByteAt(hexStr, 3) === 0x01 && getByteAt(hexStr, 4) === 0x02) {
var byte6 = getByteAt(hexStr, 6);
if (dev[NameBlind] && byte6 !== dev[NameBlind]["Position"]) {
// устанавливаем флаг авто-обновления слайдера
positionSetAuto[blindID] = 1;
dev[NameBlind]["Position"] = byte6;
}
}
} else {
log.info("reply CRC ERROR!");
}
}
});
return 0;
}
function requestRPC(modbusPort, modbusSpeed, modbusParity, reqStopbit, clientID, requestID, messageType, message, responseSize) {
// Формируем JSON запрос:
var strJson = JSON.stringify({
params: {
response_size: responseSize,
format: messageType,
path: modbusPort,
baud_rate: modbusSpeed,
parity: modbusParity,
data_bits: 8,
stop_bits: reqStopbit,
msg: message
},
id: requestID
});
publish(pathRPC + clientID, strJson, 2, false);
}
function blindRequestMsg(blindID, blindCh_L, blindCh_H, commandBlind, dataCode, dataData) {
// Сформируем набор байт команды.
var data = [0x55, blindCh_L, blindCh_H, commandBlind, dataCode];
if (dataData !== null && dataData !== undefined) {
if (Object.prototype.toString.call(dataData) === '[object Array]') {
data = data.concat(dataData);
} else {
data.push(dataData);
}
}
var crc = crc16Modbus(data);
data = data.concat(crc);
var bytesStr = bytesToHexString(data);
return bytesStr;
}
function makeNewVirtualControl(vdName, nameControl, typeControl) {
if (getDevice(vdName) === undefined) {
log.info("Define new");
defineVirtualDevice(vdName, {
title: vdName,
cells: {},
});
}
// Проверим есть ли уже контрол и если нет - создадим.
var devObj = getDevice(vdName);
if (devObj && !devObj.isControlExists(nameControl)) {
log.debug("Контрола " + nameControl + " нет, создаем.");
devObj.addControl(nameControl, typeControl);
}
}
// Создаем виртуальное устройство, с контролами для шторы
// ID делаем уникальным
// createBlind(NameBlind, portBlind, speedPortBlind, parityBlind, stopBitBlind, blindID, blindCh_L, blindCh_H)
// имя порт bod parity stopbit ID address
createBlind("Штора кухня", "/dev/ttyRS485-2", 9600, "N", 1, 0, 0x01, 0x02);