Можно сразу создавать свойства у объекта
module.exports, но как-то принят создавать отдельно объект, а потом его присваивать в
module.exports.
Создадим объект device (можно назвать и по другому).
Код: Выделить всё
const device = {
zigbeeModel: ['Mercury DTViMS'],
model: 'Mercury DTViMS',
description: 'Zigbee Mercury Counter',
vendor: 'Home DIY',
fromZigbee: [ ... ],
toZigbee: [ ... ],
configure: async (device, coordinatorEndpoint, configureDefinition) => { ... },
onEvent: async function(event) { ... }
exposes: [ ... ],
endpoint: (device) => { ... return { ... } },
options: [],
meta: {},
definitionWithoutExtend: unknown,
...
};
- `
model`: Идентификатор модели устройства.
- `
vendor`: Производитель устройства.
- `
description`: Описание устройства.
- `
exposes`: Массив объектов, описывающих доступные свойства устройства.
- `
fromZigbee`: Массив функций для конвертации данных, поступающих от устройства.
- `
toZigbee`: Массив функций для конвертации данных, отправляемых на устройство.
- `
configure`: Функция, вызываемая при подключении устройства для настройки устройства.
- `
onEvent`: Функция, вызываемая при получении события от устройства.
- `
endpoint`: Объект (Функция), описывающий конечные точки устройства.
- `
options`: Массив объектов с дополнительными опциями для устройства.
- `
meta`: Объект мета настроек устройства.
- `
definitionWithoutExtend`: Объект, содержащий определение устройства без расширения.
model и
vendor - в паре идентифицируют устройство и внешний конвертер. Именно по ним будет понятно, что именно данный конвертер надо использовать. Т.е. если значения, указанные тут совпадут со значениями, что вернет устройство.
configure - основное свойство - функция, с которого начинается наша конфигурация устройства, когда оно добавляется в систему. Также в zigbee2mqtt есть кнопка, позволяющая произвести реконфигурацию устройства, тогда будет вызван данный метод.
Параметры:
- `
device`: Объект устройства, которое конфигурируется.
- `
coordinatorEndpoint`: Объект конечной точки координатора (позволяет связать с ним наше устройство)
- `
configureDefinition`: Объект описания устройства, что-то вроде this.
Функция должна настраивать конфигурацию именно устройства, хотя там можно что угодно вызывать, но обычно тут настройка текущего оборудования. Сюда входит настройка связей с координатором, настройка отчетов и доп. параметров.
Заметим, что команды, выполняемые ниже в примере, конфигурируют именно устройство, т.е. устройству будут отправлены эти команды и именно устройство запишет себе как необходимо поступать в дальнейшем, кого слушаться и кому отправлять отчеты.
Данная информация также будет отражена в интерфейсе zigbee2mqtt.
Например (комментарии по коду):
Код: Выделить всё
configure: async (device, coordinatorEndpoint) => {
// Получение объектов конечных точек устройства
const first_endpoint = device.getEndpoint(1);
const second_endpoint = device.getEndpoint(2);
const third_endpoint = device.getEndpoint(3);
const fourth_endpoint = device.getEndpoint(4);
const fifth_endpoint = device.getEndpoint(5);
//Настройка связей с координатором (конечная точка устройства, конечная точка координатора, список кластеров, для которых устанавливается связь).
await reporting.bind(first_endpoint, coordinatorEndpoint, ['genBasic']);
await reporting.bind(second_endpoint, coordinatorEndpoint, ['haElectricalMeasurement']);
await reporting.bind(third_endpoint, coordinatorEndpoint, ['seMetering']);
await reporting.bind(fourth_endpoint, coordinatorEndpoint, ['msTemperatureMeasurement']);
await reporting.bind(fifth_endpoint, coordinatorEndpoint, ['msTemperatureMeasurement']);
//Из соответствующих свойств, можно сразу вычитать их значения (есть сомнения, что это корректно работает из данного места)
await second_endpoint.read('haElectricalMeasurement', ['acVoltageMultiplier', 'acVoltageDivisor']);
await second_endpoint.read('haElectricalMeasurement', ['acCurrentMultiplier', 'acCurrentDivisor']);
await second_endpoint.read('haElectricalMeasurement', ['acPowerMultiplier', 'acPowerDivisor']);
await third_endpoint.read('seMetering', [0xF001]); // device_address
await third_endpoint.read('seMetering', [0xF002]); // measurement_period
await fourth_endpoint.read('msTemperatureMeasurement', [0xF001]); // device_address
await fifth_endpoint.read('msTemperatureMeasurement', [0xF001]); // device_address
//Настройка стандартных отчетов. Все стандартные отчеты можно найти в библиотеке reporting
await reporting.rmsVoltage(second_endpoint);
await reporting.rmsCurrent(second_endpoint);
await reporting.activePower(second_endpoint);
// Вторым параметром можно передать объект, что позволит переопределить некоторые свойства стандартного отчета.
// В данном случае по умолчанию значение 100, а меняется на 1, у параметра reportableChange (названия свойств переопределений иные)
await reporting.temperature(fourth_endpoint,{change: 1});
await reporting.temperature(fifth_endpoint,{change: 1});
//Конфигурирование отчета без стандартных моделей
//Кластер, наименование аттрибута и его основные параметры
await third_endpoint.configureReporting('seMetering', [{attribute: 'currentSummDelivered', minimumReportInterval: 10, maximumReportInterval: 3600, reportableChange: 1}]);
await third_endpoint.configureReporting('seMetering', [{attribute: 'currentTier1SummDelivered', minimumReportInterval: 10, maximumReportInterval: 3600, reportableChange: 1}]);
await third_endpoint.configureReporting('seMetering', [{attribute: 'currentTier2SummDelivered', minimumReportInterval: 10, maximumReportInterval: 3600, reportableChange: 1}]);
await third_endpoint.configureReporting('seMetering', [{attribute: 'currentTier3SummDelivered', minimumReportInterval: 10, maximumReportInterval: 3600, reportableChange: 1}]);
await third_endpoint.configureReporting('seMetering', [{attribute: 'currentTier4SummDelivered', minimumReportInterval: 10, maximumReportInterval: 3600, reportableChange: 1}]);
},
Объект
meta - доп. настройки, варианты которых можно поискать тут: zigbee-herdsman-converters/src/lib/types.ts в interface DefinitionMeta.
Метод
onEvent используется редко. Пример описания функции:
Код: Выделить всё
async function(event) => {
// Код для обработки событий от устройства
// event.type и event.data - основные свойства
}
Вообще я нашел еще несколько вариантов описания данного метода, но не нашел реального подтверждения, что так правильно.
Эти варианты ниже НЕ работают! Возможно это устаревшие вызовы.
Код: Выделить всё
async function(type, data, device, settings, state) => {
// Код для обработки событий от устройства
}
async function(type, data, device, publish, options, meta) => {
// Код для обработки событий от устройства
}
async function(type, data, device, endpoints, logger) => {
// Код для обработки событий от устройства
}
Единственные события, которое мне удалось поймать для своего устройства - это событие, когда event.type = "
stop" и "
start", при перезапуске zigbee2mqtt, хотя событий должно быть больше.
В последствии выяснилось, что вызывается также событие "
deviceOptionsChanged" при изменении options устройства ("Настройки (Особые)" - см. далее), но при этом возможно появление события "
start" (возможно случайное совпадение).
exposes - Массив со свойствами устройства. Описывает какие свойства будут отображаться у устройства в интерфейсе zigbee2mqtt.
options - Аналогичный массив, но относится к дополнительным параметрам "Настройки (Особые)".
Например:
Код: Выделить всё
exposes: [
// Есть стандартные поля (свойства), как тут для кластера haElectricalMeasurement
e.power(),
e.current(),
e.voltage(),
// Нестандартные, которые можно создавать на свое усмотрение. Хотя Для температуры, есть стандартный, тут используется универсальный способ
e.numeric('temperature', ACCESS_STATE).withUnit('°C').withDescription('Measured temperature value'),
e.text('device_address_temp', ACCESS_STATE | ACCESS_WRITE | ACCESS_READ).withDescription('Device Address temp'),
// temperature_out - это стандартное свойство температуры, но в преобразовании ему было дано новое имя, в отличии от стандартного temperature
// По другому имени, ему можно дать свое описание и другие свойства, чтобы идентичные параметры можно было отличить в интерфейсе
// Аналогично можно сделать добавив в конце метод withEndpoint(StrNameEP)
// exposes.numeric('temperature', ACCESS_STATE).withUnit('°C').withDescription('Measured temperature value').withEndpoint('l1'),
// Тут свойство имеет имя temperature, но его полное имя будет temperature_l1 - добавится имя точки, такое и будет отличие
// Но для этого заранее необходимо завести алиасы для EP в свойстве endpoint
e.numeric('temperature_out', ACCESS_STATE).withUnit('°C').withDescription('Measured temperature out value'),
e.text('device_address_temp_out', ACCESS_STATE | ACCESS_WRITE | ACCESS_READ).withDescription('Device Address temp out'),
e.numeric('energy_t0', ACCESS_STATE).withUnit('kWh').withDescription('Energy on tariff all'),
e.numeric('energy_t1', ACCESS_STATE).withUnit('kWh').withDescription('Energy on tariff 1'),
e.numeric('energy_t2', ACCESS_STATE).withUnit('kWh').withDescription('Energy on tariff 2'),
e.text('device_address', ACCESS_STATE | ACCESS_WRITE | ACCESS_READ).withDescription('Device Address'),
e.numeric('measurement_period', ACCESS_STATE | ACCESS_WRITE | ACCESS_READ).withUnit('sec').withDescription('Measurement Period').withValueMin(10).withValueMax(600)
],
Пример для options аналогичен, но для него есть предопределенные опции в exposes.
endpoint - Тут метод возвращающий список конечных точек, вернее их алиасов
Например:
Код: Выделить всё
endpoint: (device) => {
return {
'l1': 1, 'l2': 2
};
},
Лично мне не понравилась данная возможность, хотя некоторые конфигурации выглядели бы удобнее и интуитивно понятнее, но в интерфейсе zigbee2mqtt, в разделе EndPoint они отображаются вместе со стандартными номерами конечных точек. Выбор при этом свойств работает только по стандартным номерам EP, а по их алисам выдает ошибку, что для меня вызывает лишь недоумения. Возможно, если бы алиасы работали бы в интерфейсе также корректно, то такой негативно реакции уже не было бы.
fromZigbee и
toZigbee - основные свойства описания конвертера. Они являются массивами объектов содержащих описания методы конвертеров.
fromZigbee - тут помещаются обработчики по кластерам, которые обрабатываю сообщения приходящие от устройства. Именно тут можно поместить значения в свойства которые настраивались в exposes. Например, создается свойство итогового объекта "temperature_out", где имя как константа (фиксированное имя) или для задания имени можно использовать метод "postfixWithEndpointName('temperature', msg, model, meta)", который к имени "temperature", добавит значение EP или алиас EP, если в этом есть необходимость. Кстати, аналогичное преобразование имени будет произведено в exposes, у полей, потому там необходимо именно базовые названия давать "e.numeric('temperature' ...)", а там будет дополнительно автоматом выполнено преобразование "postfixWithEndpointName". Можно использовать такой автоматический формат или фиксированный - на усмотрение разработчика (см. примеры реализованных проектов, коих много).
toZigbee - содержит объекты для описания механизмов отправки данных на устройство. Интересно, что там есть 2 метода один для записи данных в Устройств, а второй - это запрос на чтение данных из устройство. В последнем случае смысл в том что устройство само значение не пришлет и его необходимо у него запросить, вот именно этот запрос и отправляется, а ответ уже придет от устройства и будет обработан преобразованием из fromZigbee.
Тут используются названия свойств, которые также определялись в exposes. Внутри метода обработчика уже в зависимости от свойства выбирается конечная точка, кластер и свойство согласно протоколу z-stack и туда отправляется необходимое значения.
Тут есть множество нюансов, потому необходимо остановится детальнее именно на этих свойствах далее.
definitionWithoutExtend - свойство не имеющее реальных примеров. Можно предположить, что в нем можно дать описание устройству, чтобы оно определялось как поддерживаемое, но при этом использовало только стандартные кластера z-stack, что в целом возможно для стандартных устройств типа одиночного датчика температуры. Однако даже для последнего таких примеров реального использования найдено не было. Вероятно был задел на такую возможность, но оно не нашло своего клиента.