Решил менять электрический счетчик и конечно на такой, с которого можно снимать показания по RS485, оптимально некий Меркурий.
На него нашлось 2 проекта DIY одного автора, один для ESP8266, а второй для zigbee на cc2530. Наверное желание доразобраться с последним и склонило именно к выбору второго.
https://github.com/Bacchus777/Mercury - сам проект.
Проект с самого начало был в концепте Шредингера, в суперпозиции: одновременно не работал и работал.
Как заметили в комментариях к описанию проекта
https://habr.com/ru/articles/803389/: как написать прошивку? Также как нарисовать сову!
К схемотехнике вопросов не нашлось, но как и бывает в таких проектах, автор больше по электронике, чем по программированию, ну или ему совсем было лень довести проект до ума и описать проблемы, которые надо решить для повторения. Пользуясь случаем, разберемся в проекте, и решим все или почти все проблемы проекта и даже zstack полечим, ну или почти. Всё далее...
Итак.Код: Выделить всё
CONST zclAttrRec_t zclApp_Attrs_FirstEP[] = {
... Пропущено описание кластера BASIC
{ELECTRICAL, {ATTRID_ELECTRICAL_MEASUREMENT_RMS_VOLTAGE, ZCL_UINT16, RR, (void *)&zclApp_CurrentValues.Voltage}},
{ELECTRICAL, {ATTRID_ELECTRICAL_MEASUREMENT_RMS_CURRENT, ZCL_UINT16, RR, (void *)&zclApp_CurrentValues.Current}},
{ELECTRICAL, {ATTRID_ELECTRICAL_MEASUREMENT_ACTIVE_POWER, ZCL_INT16, RR, (void *)&zclApp_CurrentValues.Power}},
{ELECTRICAL, {ATTRID_ELECTRICAL_MEASUREMENT_AC_VOLTAGE_DIVISOR, ZCL_UINT16, R, (void *)&zclApp_Config.VoltageDivisor}},
{ELECTRICAL, {ATTRID_ELECTRICAL_MEASUREMENT_AC_CURRENT_DIVISOR, ZCL_UINT16, R, (void *)&zclApp_Config.CurrentDivisor}},
{ELECTRICAL, {ATTRID_ELECTRICAL_MEASUREMENT_AC_POWER_DIVISOR, ZCL_UINT16, R, (void *)&zclApp_Config.PowerDivisor}},
{ELECTRICAL, {ATTRID_ELECTRICAL_MEASUREMENT_AC_VOLTAGE_MULTIPLIER, ZCL_UINT16, R, (void *)&zclApp_Config.VoltageMultiplier}},
{ELECTRICAL, {ATTRID_ELECTRICAL_MEASUREMENT_AC_CURRENT_MULTIPLIER, ZCL_UINT16, R, (void *)&zclApp_Config.CurrentMultiplier}},
{ELECTRICAL, {ATTRID_ELECTRICAL_MEASUREMENT_AC_POWER_MULTIPLIER, ZCL_UINT16, R, (void *)&zclApp_Config.PowerMultiplier}},
{TEMP, {ATTRID_MS_TEMPERATURE_MEASURED_VALUE, ZCL_INT16, RR, (void *)&zclApp_Temperature}},
};
Вот описание первой конечной точки.
Для понимания, что мы конечной точкой объединяем несколько типов данных, т.е. кластеров, а именно Basic, Electrical и Temp. Названия кластеров тут сокращены, а в итоге использованы не все, что заготовлены. Возможно автор экспериментировал как и я далее.
Код: Выделить всё
#define BASIC ZCL_CLUSTER_ID_GEN_BASIC
#define GEN_ON_OFF ZCL_CLUSTER_ID_GEN_ON_OFF
#define POWER_CFG ZCL_CLUSTER_ID_GEN_ON
#define TEMP ZCL_CLUSTER_ID_MS_TEMPERATURE_MEASUREMENT
#define HUMIDITY ZCL_CLUSTER_ID_MS_RELATIVE_HUMIDITY
#define PRESSURE ZCL_CLUSTER_ID_MS_PRESSURE_MEASUREMENT
#define ELECTRICAL ZCL_CLUSTER_ID_HA_ELECTRICAL_MEASUREMENT
#define SE_METERING ZCL_CLUSTER_ID_SE_METERING
у каждого кластера могут быть, а могут не быть атрибуты, например ATTRID_ELECTRICAL_MEASUREMENT_AC_VOLTAGE_DIVISOR - это название атрибута или свойства, которое может менять свое значение. Вообще, если подумать, zstack не фиксирует атрибуты как константы и они могут меняться в любой момент, но есть правила, согласно которым, что-то можно менять, а что-то нет и "ZigBee Cluster Library Specification" в деталях описывает это, но не так, что у вас это не получится обойти, а просто как стандарт, который надо соблюдать, если хотите, чтобы вас поняли. Сам стандарт нацелен на устройства умного дома, т.е. как универсальный протокол по обмену состояниями между умными устройствами, где есть атрибуты и виды устройств (кластеров) на все случаи жизни, а если не на все, то есть универсальные, о чем немного позже. На основе этого, видимо, и получился такой проект как PTVO, в котором можно просто быстро построить простое устройство (интересно а там все проблемы решены?). Разработчики PTVO не делятся секретами, а мы попытаемся разобраться в том, что еще не очень понятно из ранее прочтенного.
Так вот, добавление нового свойства для нашего устройства, если совсем примитивно будет выглядеть так:
1. Придумываем код кластера для которого будем накидывать атрибуты
2. Придумываем коды атрибутов
3. Для каждого атрибута задаем тип данных
4. Для каждого атрибута задаем его свойства доступа (может это и не обязательно, если мы вот так абстрагируемся)
5. Указываем адрес переменной, откуда будут браться данные и куда они будут помещаться.
Записываем, что напридумывали в виде:
Код: Выделить всё
{МойКластер, {Атрибут1, ZCL_UINT16, БитовоеСвойствоПравДоступа, (void *)&ПеременнаяСДанными1}},
{МойКластер, {Атрибут2, ZCL_UINT16, БитовоеСвойствоПравДоступа, (void *)&ПеременнаяСДанными2}},
Это абстрагирование, чтобы понимать как это заполняется, т.е. просто заполняем такие комбинации, главное, что в одной конечной точке может быть только одна комбинация МойКластер-Атрибут1!
Если у меня несколько одинаковых чего-то в одном, то надо заводить 2-ю конечную точку. Тут конечная точка как одно базовое устройства и если мы несколько одинаковых устройств объединяем на одно железо, то необходимо завести столько конечных точек, сколько простейших устройств мы определяем.
Если мы делаем сложное устройство, то как нам его описать? Разбить на более простые. Если у нас метеостанция и есть два датчика температуры (на улице и дома), то один датчик мы разместим в первой конечной точке, а второй во второй. Аналогично, если у нас многоклавишный выключатель, то мы размещаем кластер OnOff на каждую кнопку в отдельной конечной точке.
Теперь взглянем на "ZigBee Cluster Library Specification"!
Мы хотим сделать передачу показаний электросчетчика. Ищем, что-то подходящее в спецификации и находим "10.4 Metering", где написано, что это как раз измерители потребления электричества, топлива, воды...
"10.4.1.3 Cluster Identifiers" - раздел описывает код кластера.
0x0702 - Metering (Smart Energy)На основании этого кода мы и создаем константу или ищем готовую в библиотеке (почти все есть в библиотеке, но иногда надо добавить свои).
Код: Выделить всё
#define SE_METERING ZCL_CLUSTER_ID_SE_METERING
Данная строка в нашем проект, просто задает короткое имя для константы ZCL_CLUSTER_ID_SE_METERING, определение которой есть где-то там в библиотеке и будет иметь значение
0x0702.
Далее смотрим атрибуты.
Вообще кластер интересный с точки зрения того, что там какое-то огромное количество атрибутов, для чего задействованы 2 байта кодирования, для удобства. Один байт определяет подгруппу атрибутов, а второй сам атрибут. Но это сугубо для удобства восприятия нумерования.
Первый байт "Attribute Set Identifier" он имеет 9 значений и описаны они в общей табличке (домашнее задание найти это в доке и сопоставить с написанным).
Нам интересен пункт "
0x01 - TOU Information Set".
Переходим в раздел "10.4.2.2.2 Summation TOU Information Set", где табличка с атрибутами.
Код: Выделить всё
0x0000 CurrentTier1SummationDelivered uint48
0x0001 CurrentTier1SummationReceived uint48
0x0002 CurrentTier2SummationDelivered uint48
Мы будем читать их так, что они относятся к группе "
0x01 - TOU Information Set", а значит их коды должны выглядеть так:
Код: Выделить всё
0x0100 CurrentTier1SummationDelivered uint48
0x0101 CurrentTier1SummationReceived uint48
0x0102 CurrentTier2SummationDelivered uint48
Для примера из самой первой группы "
0x00 Reading Information Set"
Код: Выделить всё
0x0000 CurrentSummationDelivered uint48
0x0001 CurrentSummationReceived uint48
Их коды такие и останутся. В коде просто ставим значение первого байта (если читать слева на право) как значение группы.
ВНИМАНИЕ!!! Обычно такой группировки нет и код атрибута - это сразу код атрибута, тут просто их так пронумеровали по группам с "простым" понимаем (кто-то может с этим не согласиться).Код: Выделить всё
#define ATTRID_SE_METERING_CURR_SUMM_DLVD 0x0000
#define ATTRID_SE_METERING_CURR_TIER1_SUMM_DLVD 0x0100
#define ATTRID_SE_METERING_CURR_TIER2_SUMM_DLVD 0x0102
#define ATTRID_SE_METERING_CURR_TIER3_SUMM_DLVD 0x0104
#define ATTRID_SE_METERING_CURR_TIER4_SUMM_DLVD 0x0106
Вот мы задали атрибуты в константы, которые будем использовать.
Первый атрибут для суммы показаний по всем тарифам и далее 4 разных тарифа. Мне известны 1 тарифные 2 и 3 тарифные счетчики, но там реально можно настроить и 4, а наш кластер позволяет их вообще 15 штук задать, но нам не надо, а мы и не будем.
Заметим, что там на каждый тариф по два значения "Delivered" и "Received", ну это для не только умных домов, а для очень умных, которые, например, солнечными панелями питаются и нехватку электроэнергии берут из сети, а излишек (если сами не использовали, что получили) отдают обратно в сеть и вот они 2 параметра: получил и отдал.
Вот так мы себе отобрали 5 параметров.
Также там описан тип данных
uint48, т.е. 6 байт. Необходимо использовать именно такой тип, чтобы нас поняли.
Открою очевидную вещь (Спасибо КЭП): по коду атрибута мы понимаем, что его значение занимает 6 байт, значит следующие 6 байт - это данные. Именно так, наше устройство сможет понять кто-то другой. Если нет соответствия стандарта, то никто понять не сможет, ну по крайней мере интуитивно. Некоторые производители, чтобы использовали только их комплекс устройств, могут изменить стандарту, специально чтобы их не поняли. Есть, конечно, еще конфигурация, которой устройство может делиться и само рассказывать о себе, что поддерживает, но видимо это не достаточно хорошо работает.
Далее в табличке описания атрибутов значения:
Range - диапазон допустимых значений (может быть не задан)
Acc - доступ к переменной, где R только чтение, т.е. само устройство его задает, а изменить из вне его нельзя. W - запись, что можно присылать значение из вне.
Def - значение по умолчанию
M/O - Mandatory or Optional - Обязательные или опциональный соответственно.
Вот так мы добавим опции:
Код: Выделить всё
CONST zclAttrRec_t zclApp_Attrs_SecondEP[] = {
{SE_METERING, {ATTRID_SE_METERING_CURR_SUMM_DLVD, ZCL_UINT48, RR, (void *)&zclApp_Energies.Energy_T0}},
{SE_METERING, {ATTRID_SE_METERING_CURR_TIER1_SUMM_DLVD, ZCL_UINT48, RR, (void *)&zclApp_Energies.Energy_T1}},
{SE_METERING, {ATTRID_SE_METERING_CURR_TIER2_SUMM_DLVD, ZCL_UINT48, RR, (void *)&zclApp_Energies.Energy_T2}},
{SE_METERING, {ATTRID_SE_METERING_CURR_TIER3_SUMM_DLVD, ZCL_UINT48, RR, (void *)&zclApp_Energies.Energy_T3}},
{SE_METERING, {ATTRID_SE_METERING_CURR_TIER4_SUMM_DLVD, ZCL_UINT48, RR, (void *)&zclApp_Energies.Energy_T4}},
{SE_METERING, {ZCL_ATTRID_CUSTOM_DEVICE_ADDRESS, ZCL_UINT32, RW, (void *)&zclApp_Config.DeviceAddress}},
{SE_METERING, {ZCL_ATTRID_CUSTOM_MEASUREMENT_PERIOD, ZCL_UINT16, RW, (void *)&zclApp_Config.MeasurementPeriod}},
};
Тут это описание второй конечной точки.
Не забудем ее описать в конфигурации, а также перечислить все кластера которые используются в данной EP
Код: Выделить всё
const cId_t zclApp_InClusterList_SecondEP[] = {SE_METERING};
По хорошему это все что надо чтобы можно было работать с данными параметрами. Я к тому, что достаточно их описать и остальное сделает zlib за нас, не все конечно, но работать что-то уже будет. Например, после добавления устройства в сеть мы сможем увидеть что у него есть кластер SeMetering, а (если смотреть в zigbee2mqtt) в разделе для разработчика, можно выбрать 2-ю конечную точку, кластер и свойство, и, нажав Read, прочитать значение из нашего стройства, какое установлено по адресу, например (void *)&zclApp_Energies.Energy_T0.