Merge pull request #9133 from s-hadinger/zigbee_aug_20

Zigbee device profile phase 1
This commit is contained in:
Theo Arends 2020-08-20 09:04:16 +02:00 committed by GitHub
commit dd4f185ba8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 166 additions and 70 deletions

View File

@ -22,7 +22,7 @@
#include <vector>
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
#define ZIGBEE_SAVE_DELAY_SECONDS 2; // wait for 2s before saving Zigbee info
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
#endif
const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds
@ -63,14 +63,25 @@ typedef struct Z_Device {
uint16_t shortaddr; // unique key if not null, or unspecified if null
uint8_t seqNumber;
// Light information for Hue integration integration, last known values
int8_t bulbtype; // number of channel for the bulb: 0-5, or 0xFF if no Hue integration
uint8_t zb_profile; // profile of the device
// high 4 bits is device type:
// 0x0. = bulb
// 0x1. = switch
// 0x2. = motion sensor
// 0x3. = other alarms
// 0xE. = reserved for extension
// 0xF. = unknown
// For Bulb (0x0.)
// 0x0N = number of channel for the bulb: 0-5
// 0x08 = the device is hidden from Alexa
// other status
uint8_t power; // power state (boolean), MSB (0x80) stands for reachable
uint8_t colormode; // 0x00: Hue/Sat, 0x01: XY, 0x02: CT
uint8_t dimmer; // last Dimmer value: 0-254
uint8_t sat; // last Sat: 0..254
uint16_t ct; // last CT: 153-500
uint16_t hue; // last Hue: 0..359
uint16_t x, y; // last color [x,y]
uint8_t colormode; // 0x00: Hue/Sat, 0x01: XY, 0x02: CT | 0xFF not set, default 0x01
uint8_t dimmer; // last Dimmer value: 0-254 | 0xFF not set, default 0x00
uint8_t sat; // last Sat: 0..254 | 0xFF not set, default 0x00
uint16_t ct; // last CT: 153-500 | 0xFFFF not set, default 200
uint16_t hue; // last Hue: 0..359 | 0xFFFF not set, default 0
uint16_t x, y; // last color [x,y] | 0xFFFF not set, default 0
uint8_t linkquality; // lqi from last message, 0xFF means unknown
uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon
} Z_Device;
@ -163,9 +174,15 @@ public:
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
int32_t deviceRestore(const JsonObject &json);
// General Zigbee device profile support
void setZbProfile(uint16_t shortaddr, uint8_t zb_profile);
uint8_t getZbProfile(uint16_t shortaddr) const ;
// Hue support
void setHueBulbtype(uint16_t shortaddr, int8_t bulbtype);
int8_t getHueBulbtype(uint16_t shortaddr) const ;
void hideHueBulb(uint16_t shortaddr, bool hidden);
bool isHueBulbHidden(uint16_t shortaddr) const ;
void updateHueState(uint16_t shortaddr,
const bool *power, const uint8_t *colormode,
const uint8_t *dimmer, const uint8_t *sat,
@ -236,6 +253,8 @@ private:
void freeDeviceEntry(Z_Device *device);
void setStringAttribute(char*& attr, const char * str);
void updateZbProfile(uint16_t shortaddr);
};
/*********************************************************************************************\
@ -294,14 +313,14 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
shortaddr,
0, // seqNumber
// Hue support
-1, // no Hue support
0xFF, // no Hue support
0x80, // power off + reachable
0, // colormode
0, // dimmer
0, // sat
200, // ct
0, // hue
0, 0, // x, y
0xFF, // colormode
0xFF, // dimmer
0xFF, // sat
0xFFFF, // ct
0xFFFF, // hue
0xFFFF, 0xFFFF, // x, y
0xFF, // lqi, 0xFF = unknown
0xFF // battery percentage x 2, 0xFF means unknown
};
@ -691,24 +710,99 @@ uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) {
}
}
// Hue support
void Z_Devices::setHueBulbtype(uint16_t shortaddr, int8_t bulbtype) {
// General Zigbee device profile support
void Z_Devices::setZbProfile(uint16_t shortaddr, uint8_t zb_profile) {
Z_Device &device = getShortAddr(shortaddr);
if (bulbtype != device.bulbtype) {
device.bulbtype = bulbtype;
if (zb_profile != device.zb_profile) {
device.zb_profile = zb_profile;
updateZbProfile(shortaddr);
dirty();
}
}
int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr) const {
// Do all the required action when a profile is changed
void Z_Devices::updateZbProfile(uint16_t shortaddr) {
Z_Device &device = getShortAddr(shortaddr);
uint8_t zb_profile = device.zb_profile;
if (0xFF == zb_profile) { return; }
switch (zb_profile & 0xF0) {
case 0x00: // bulb profile
{
uint32_t channels = zb_profile & 0x07;
// depending on the bulb type, the default parameters from unknown to credible defaults
if (0xFF == device.power) { device.power = 0; }
if (1 <= channels) {
if (0xFF == device.dimmer) { device.dimmer = 0; }
}
if (3 <= channels) {
if (0xFF == device.sat) { device.sat = 0; }
if (0xFFFF == device.hue) { device.hue = 0; }
if (0xFFFF == device.x) { device.x = 0; }
if (0xFFFF == device.y) { device.y = 0; }
if (0xFF == device.colormode) { device.colormode = 0; } // HueSat mode
}
if ((2 == channels) || (5 == channels)) {
if (0xFFFF == device.ct) { device.ct = 200; }
if (0xFF == device.colormode) { device.colormode = 2; } // CT mode
}
}
break;
}
}
// Returns the device profile or 0xFF if the device or profile is unknown
uint8_t Z_Devices::getZbProfile(uint16_t shortaddr) const {
int32_t found = findShortAddr(shortaddr);
if (found >= 0) {
return _devices[found]->bulbtype;
return _devices[found]->zb_profile;
} else {
return -1; // Hue not activated
return 0xFF; // Hue not activated
}
}
// Hue support
void Z_Devices::setHueBulbtype(uint16_t shortaddr, int8_t bulbtype) {
uint8_t zb_profile = (0 > bulbtype) ? 0xFF : (bulbtype & 0x07);
setZbProfile(shortaddr, zb_profile);
}
int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr) const {
uint8_t zb_profile = getZbProfile(shortaddr);
if (0x00 == (zb_profile & 0xF0)) {
return (zb_profile & 0x07);
} else {
// not a bulb
return -1;
}
}
void Z_Devices::hideHueBulb(uint16_t shortaddr, bool hidden) {
uint8_t hue_hidden_flag = hidden ? 0x08 : 0x00;
Z_Device &device = getShortAddr(shortaddr);
if (0x00 == (device.zb_profile & 0xF0)) {
// bulb type
// set bit 3 accordingly
if (hue_hidden_flag != (device.zb_profile & 0x08)) {
device.zb_profile = (device.zb_profile & 0xF7) | hue_hidden_flag;
dirty();
}
}
}
// true if device is not knwon or not a bulb - it wouldn't make sense to publish a non-bulb
bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
int32_t found = findShortAddr(shortaddr);
if (found >= 0) {
uint8_t zb_profile = _devices[found]->zb_profile;
if (0x00 == (zb_profile & 0xF0)) {
// bulb type
return (zb_profile & 0x08) ? true : false;
}
}
return true; // Fallback - Device is considered as hidden
}
// Hue support
void Z_Devices::updateHueState(uint16_t shortaddr,
const bool *power, const uint8_t *colormode,
@ -1056,27 +1150,17 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const {
}
// expose the last known status of the bulb, for Hue integration
dev[F(D_JSON_ZIGBEE_LIGHT)] = device.bulbtype; // sign extend, 0xFF changed as -1
if (0 <= device.bulbtype) {
// bulbtype is defined
dev[F("Power")] = bitRead(device.power, 0);
dev[F("Reachable")] = bitRead(device.power, 7);
if (1 <= device.bulbtype) {
dev[F("Dimmer")] = device.dimmer;
}
if (2 <= device.bulbtype) {
dev[F("Colormode")] = device.colormode;
}
if ((2 == device.bulbtype) || (5 == device.bulbtype)) {
dev[F("CT")] = device.ct;
}
if (3 <= device.bulbtype) {
dev[F("Sat")] = device.sat;
dev[F("Hue")] = device.hue;
dev[F("X")] = device.x;
dev[F("Y")] = device.y;
}
}
dev[F(D_JSON_ZIGBEE_LIGHT)] = getHueBulbtype(shortaddr); // sign extend, 0xFF changed as -1
// dump all known values
dev[F("Reachable")] = bitRead(device.power, 7); // TODO TODO
if (0xFF != device.power) { dev[F("Power")] = bitRead(device.power, 0); }
if (0xFF != device.dimmer) { dev[F("Dimmer")] = device.dimmer; }
if (0xFF != device.colormode) { dev[F("Colormode")] = device.colormode; }
if (0xFFFF != device.ct) { dev[F("CT")] = device.ct; }
if (0xFF != device.sat) { dev[F("Sat")] = device.sat; }
if (0xFFFF != device.hue) { dev[F("Hue")] = device.hue; }
if (0xFFFF != device.x) { dev[F("X")] = device.x; }
if (0xFFFF != device.y) { dev[F("Y")] = device.y; }
}
String payload = "";
@ -1119,8 +1203,9 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
if (device.modelId) {
dev[F(D_JSON_MODEL D_JSON_ID)] = device.modelId;
}
if (device.bulbtype >= 0) {
dev[F(D_JSON_ZIGBEE_LIGHT)] = device.bulbtype; // sign extend, 0xFF changed as -1
int8_t bulbtype = getHueBulbtype(shortaddr);
if (bulbtype >= 0) {
dev[F(D_JSON_ZIGBEE_LIGHT)] = bulbtype; // sign extend, 0xFF changed as -1
}
if (device.manufacturerId) {
dev[F("Manufacturer")] = device.manufacturerId;

View File

@ -103,10 +103,10 @@ void ZigbeeHueStatus(String * response, uint16_t shortaddr) {
void ZigbeeCheckHue(String * response, bool &appending) {
uint32_t zigbee_num = zigbee_devices.devicesSize();
for (uint32_t i = 0; i < zigbee_num; i++) {
int8_t bulbtype = zigbee_devices.devicesAt(i).bulbtype;
uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr;
int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr);
if (bulbtype >= 0) {
uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr;
// this bulb is advertized
if (appending) { *response += ","; }
*response += "\"";
@ -122,7 +122,8 @@ void ZigbeeCheckHue(String * response, bool &appending) {
void ZigbeeHueGroups(String * lights) {
uint32_t zigbee_num = zigbee_devices.devicesSize();
for (uint32_t i = 0; i < zigbee_num; i++) {
int8_t bulbtype = zigbee_devices.devicesAt(i).bulbtype;
uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr;
int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr);
if (bulbtype >= 0) {
*lights += ",\"";

View File

@ -46,7 +46,7 @@
// str - FriendlyName (null terminated C string, 32 chars max)
// reserved for extensions
// -- V2 --
// int8_t - bulbtype
// int8_t - zigbee profile of the device
// Memory footprint
#ifdef ESP8266
@ -126,8 +126,8 @@ class SBuffer hibernateDevice(const struct Z_Device &device) {
}
buf.add8(0x00); // end of string marker
// Hue Bulbtype
buf.add8(device.bulbtype);
// Zigbee Profile
buf.add8(device.zb_profile);
// update overall length
buf.set8(0, buf.len());
@ -225,7 +225,7 @@ void hydrateDevices(const SBuffer &buf) {
// Hue bulbtype - if present
if (d < dev_record_len) {
zigbee_devices.setHueBulbtype(shortaddr, buf_d.get8(d));
zigbee_devices.setZbProfile(shortaddr, buf_d.get8(d));
d++;
}

View File

@ -99,7 +99,7 @@ typedef struct Z_AttributeConverter {
uint8_t cluster_short;
uint16_t attribute;
const char * name;
int16_t multiplier; // multiplier for numerical value, (if > 0 multiply by x, if <0 device by x)
int8_t multiplier; // multiplier for numerical value, (if > 0 multiply by x, if <0 device by x)
uint8_t cb; // callback func from Z_ConvOperators
// Z_AttrConverter func;
} Z_AttributeConverter;
@ -142,10 +142,12 @@ enum Z_ConvOperators {
};
ZF(ZCLVersion) ZF(AppVersion) ZF(StackVersion) ZF(HWVersion) ZF(Manufacturer) ZF(ModelId)
ZF(DateCode) ZF(PowerSource) ZF(SWBuildID) ZF(Power) ZF(SwitchType) ZF(Dimmer)
ZF(GenericDeviceClass) ZF(GenericDeviceType) ZF(ProductCode) ZF(ProductURL)
ZF(DateCode) ZF(PowerSource) ZF(SWBuildID) ZF(Power) ZF(SwitchType) ZF(Dimmer) ZF(DimmerOptions)
ZF(DimmerRemainingTime) ZF(OnOffTransitionTime) ZF(StartUpOnOff)
ZF(MainsVoltage) ZF(MainsFrequency) ZF(BatteryVoltage) ZF(BatteryPercentage)
ZF(CurrentTemperature) ZF(MinTempExperienced) ZF(MaxTempExperienced) ZF(OverTempTotalDwell)
ZF(IdentifyTime)
ZF(IdentifyTime) ZF(GroupNameSupport)
ZF(SceneCount) ZF(CurrentScene) ZF(CurrentGroup) ZF(SceneValid)
ZF(AlarmCount) ZF(Time) ZF(TimeStatus) ZF(TimeZone) ZF(DstStart) ZF(DstEnd)
ZF(DstShift) ZF(StandardTime) ZF(LocalTime) ZF(LastSetTime) ZF(ValidUntilTime) ZF(TimeEpoch)
@ -243,6 +245,10 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ Zstring, Cx0000, 0x0005, Z(ModelId), 1, Z_ModelKeep }, // record Model
{ Zstring, Cx0000, 0x0006, Z(DateCode), 1, Z_Nop },
{ Zenum8, Cx0000, 0x0007, Z(PowerSource), 1, Z_Nop },
{ Zenum8, Cx0000, 0x0008, Z(GenericDeviceClass), 1, Z_Nop },
{ Zenum8, Cx0000, 0x0009, Z(GenericDeviceType), 1, Z_Nop },
{ Zoctstr, Cx0000, 0x000A, Z(ProductCode), 1, Z_Nop },
{ Zstring, Cx0000, 0x000B, Z(ProductURL), 1, Z_Nop },
{ Zstring, Cx0000, 0x4000, Z(SWBuildID), 1, Z_Nop },
// { Zunk, Cx0000, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values
// Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
@ -263,6 +269,9 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// Identify cluster
{ Zuint16, Cx0003, 0x0000, Z(IdentifyTime), 1, Z_Nop },
// Groups cluster
{ Zmap8, Cx0004, 0x0000, Z(GroupNameSupport), 1, Z_Nop },
// Scenes cluster
{ Zuint8, Cx0005, 0x0000, Z(SceneCount), 1, Z_Nop },
{ Zuint8, Cx0005, 0x0001, Z(CurrentScene), 1, Z_Nop },
@ -272,6 +281,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// On/off cluster
{ Zbool, Cx0006, 0x0000, Z(Power), 1, Z_Nop },
{ Zenum8, Cx0006, 0x4003, Z(StartUpOnOff), 1, Z_Nop },
{ Zbool, Cx0006, 0x8000, Z(Power), 1, Z_Nop }, // See 7280
// On/Off Switch Configuration cluster
@ -279,12 +289,13 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// Level Control cluster
{ Zuint8, Cx0008, 0x0000, Z(Dimmer), 1, Z_Nop },
// { Zuint16, Cx0008, 0x0001, Z(RemainingTime", 1, Z_Nop },
// { Zuint16, Cx0008, 0x0010, Z(OnOffTransitionTime", 1, Z_Nop },
// { Zuint8, Cx0008, 0x0011, Z(OnLevel", 1, Z_Nop },
// { Zuint16, Cx0008, 0x0012, Z(OnTransitionTime", 1, Z_Nop },
// { Zuint16, Cx0008, 0x0013, Z(OffTransitionTime", 1, Z_Nop },
// { Zuint16, Cx0008, 0x0014, Z(DefaultMoveRate", 1, Z_Nop },
{ Zmap8, Cx0008, 0x000F, Z(DimmerOptions), 1, Z_Nop },
{ Zuint16, Cx0008, 0x0001, Z(DimmerRemainingTime), 1, Z_Nop },
{ Zuint16, Cx0008, 0x0010, Z(OnOffTransitionTime), 1, Z_Nop },
// { Zuint8, Cx0008, 0x0011, Z(OnLevel), 1, Z_Nop },
// { Zuint16, Cx0008, 0x0012, Z(OnTransitionTime), 1, Z_Nop },
// { Zuint16, Cx0008, 0x0013, Z(OffTransitionTime), 1, Z_Nop },
// { Zuint16, Cx0008, 0x0014, Z(DefaultMoveRate), 1, Z_Nop },
// Alarms cluster
{ Zuint16, Cx0009, 0x0000, Z(AlarmCount), 1, Z_Nop },
@ -615,7 +626,7 @@ typedef union ZCLHeaderFrameControl_t {
// If not found:
// - returns nullptr
const __FlashStringHelper* zigbeeFindAttributeByName(const char *command,
uint16_t *cluster, uint16_t *attribute, int16_t *multiplier,
uint16_t *cluster, uint16_t *attribute, int8_t *multiplier,
uint8_t *cb) {
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
@ -623,7 +634,7 @@ const __FlashStringHelper* zigbeeFindAttributeByName(const char *command,
if (0 == strcasecmp_P(command, converter->name)) {
if (cluster) { *cluster = CxToCluster(pgm_read_byte(&converter->cluster_short)); }
if (attribute) { *attribute = pgm_read_word(&converter->attribute); }
if (multiplier) { *multiplier = pgm_read_word(&converter->multiplier); }
if (multiplier) { *multiplier = pgm_read_byte(&converter->multiplier); }
if (cb) { *cb = pgm_read_byte(&converter->cb); }
return (const __FlashStringHelper*) converter->name;
}
@ -1363,7 +1374,6 @@ int32_t Z_AqaraCubeFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObjec
// presentValue = x + 128 = 180º flip to side x on top
// presentValue = x + 256 = push/slide cube while side x is on top
// presentValue = x + 512 = double tap while side x is on top
return 0;
}
@ -1487,7 +1497,7 @@ int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObj
// apply the transformation from the converter
int32_t Z_ApplyConverter(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name,
uint16_t cluster, uint16_t attr, int16_t multiplier, uint8_t cb) {
uint16_t cluster, uint16_t attr, int8_t multiplier, uint8_t cb) {
// apply multiplier if needed
if (1 == multiplier) { // copy unchanged
json[new_name] = value;
@ -1596,7 +1606,7 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
uint16_t conv_attribute = pgm_read_word(&converter->attribute);
int16_t conv_multiplier = pgm_read_word(&converter->multiplier);
int8_t conv_multiplier = pgm_read_byte(&converter->multiplier);
uint8_t conv_cb = pgm_read_byte(&converter->cb); // callback id
if ((conv_cluster == cluster) &&

View File

@ -190,7 +190,7 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
// multiplier == 1: ignore
// multiplier > 0: divide by the multiplier
// multiplier < 0: multiply by the -multiplier (positive)
void ZbApplyMultiplier(double &val_d, int16_t multiplier) {
void ZbApplyMultiplier(double &val_d, int8_t multiplier) {
if ((0 != multiplier) && (1 != multiplier)) {
if (multiplier > 0) { // inverse of decoding
val_d = val_d / multiplier;
@ -217,7 +217,7 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
uint16_t attr_id = 0xFFFF;
uint16_t cluster_id = 0xFFFF;
uint8_t type_id = Znodata;
int16_t multiplier = 1; // multiplier to adjust the key value
int8_t multiplier = 1; // multiplier to adjust the key value
double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss
const char* val_str = ""; // variant as string
@ -245,7 +245,7 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
uint16_t local_attr_id = pgm_read_word(&converter->attribute);
uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
uint8_t local_type_id = pgm_read_byte(&converter->type);
int16_t local_multiplier = pgm_read_word(&converter->multiplier);
int8_t local_multiplier = pgm_read_byte(&converter->multiplier);
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id);
if (delimiter) {