2019-10-06 11:40:58 +01:00
|
|
|
/*
|
2019-10-27 10:13:24 +00:00
|
|
|
xdrv_23_zigbee.ino - zigbee support for Tasmota
|
2019-10-06 11:40:58 +01:00
|
|
|
|
2019-12-31 13:23:34 +00:00
|
|
|
Copyright (C) 2020 Theo Arends and Stephan Hadinger
|
2019-10-06 11:40:58 +01:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef USE_ZIGBEE
|
|
|
|
|
2020-01-17 23:02:01 +00:00
|
|
|
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
|
2020-08-20 07:25:53 +01:00
|
|
|
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
|
2020-01-17 23:02:01 +00:00
|
|
|
#endif
|
|
|
|
const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds
|
2019-12-15 15:02:41 +00:00
|
|
|
|
2020-10-07 19:04:33 +01:00
|
|
|
enum class Z_Data_Type : uint8_t {
|
|
|
|
Z_Unknown = 0x00,
|
|
|
|
Z_Light = 1, // Lights 1-5 channels
|
|
|
|
Z_Plug = 2, // Plug power consumption
|
|
|
|
Z_PIR = 3,
|
|
|
|
Z_Alarm = 4,
|
|
|
|
Z_Thermo = 5, // Thermostat and sensor for home environment (temp, himudity, pressure)
|
|
|
|
Z_OnOff = 6, // OnOff, Buttons and Relays (always complements Lights and Plugs)
|
|
|
|
Z_Ext = 0xF, // extended for other values
|
|
|
|
Z_Device = 0xFF // special value when parsing Device level attributes
|
|
|
|
};
|
|
|
|
|
|
|
|
class Z_Data_Set;
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Device specific data, sensors...
|
|
|
|
\*********************************************************************************************/
|
|
|
|
class Z_Data {
|
|
|
|
public:
|
|
|
|
Z_Data(Z_Data_Type type = Z_Data_Type::Z_Unknown, uint8_t endpoint = 0) : _type(type), _endpoint(endpoint), _config(-1), _power(0) {}
|
|
|
|
inline Z_Data_Type getType(void) const { return _type; }
|
|
|
|
inline int8_t getConfig(void) const { return _config; }
|
|
|
|
inline void setConfig(int8_t config) { _config = config; }
|
|
|
|
|
|
|
|
inline uint8_t getEndpoint(void) const { return _endpoint; }
|
|
|
|
|
|
|
|
static const Z_Data_Type type = Z_Data_Type::Z_Unknown;
|
|
|
|
|
|
|
|
friend class Z_Data_Set;
|
|
|
|
protected:
|
|
|
|
Z_Data_Type _type; // encoded on 4 bits, type of the device
|
|
|
|
uint8_t _endpoint; // source endpoint, or 0x00 if any endpoint
|
|
|
|
int8_t _config; // encoded on 4 bits, customize behavior
|
|
|
|
uint8_t _power; // power state if the type supports it
|
|
|
|
};
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Device specific: On/Off, power up to 8 relays
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
// _config contains the number of valid OnOff relays: 0..7
|
|
|
|
// _power contains a bitfield of relays values
|
|
|
|
class Z_Data_OnOff : public Z_Data {
|
|
|
|
public:
|
|
|
|
Z_Data_OnOff(uint8_t endpoint = 0) :
|
|
|
|
Z_Data(Z_Data_Type::Z_OnOff, endpoint)
|
|
|
|
{
|
|
|
|
_config = 1; // at least 1 OnOff
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline bool validPower(uint32_t relay = 0) const { return (_config > relay); } // power is declared
|
|
|
|
inline bool getPower(uint32_t relay = 0) const { return bitRead(_power, relay); }
|
|
|
|
void setPower(bool val, uint32_t relay = 0);
|
|
|
|
|
|
|
|
static void toAttributes(Z_attribute_list & attr_list, const Z_Data_OnOff & light);
|
|
|
|
|
|
|
|
static const Z_Data_Type type = Z_Data_Type::Z_OnOff;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void Z_Data_OnOff::setPower(bool val, uint32_t relay) {
|
|
|
|
if (relay < 8) {
|
|
|
|
if (_config < relay) { _config = relay; } // we update the number of valid relays
|
|
|
|
bitWrite(_power, relay, val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Device specific: Light device
|
|
|
|
\*********************************************************************************************/
|
|
|
|
class Z_Data_Plug : public Z_Data {
|
|
|
|
public:
|
|
|
|
Z_Data_Plug(uint8_t endpoint = 0) :
|
|
|
|
Z_Data(Z_Data_Type::Z_Plug, endpoint),
|
|
|
|
mains_voltage(0xFFFF),
|
|
|
|
mains_power(-0x8000)
|
|
|
|
{}
|
|
|
|
|
|
|
|
inline bool validMainsVoltage(void) const { return 0xFFFF != mains_voltage; }
|
|
|
|
inline bool validMainsPower(void) const { return -0x8000 != mains_power; }
|
|
|
|
|
|
|
|
inline uint16_t getMainsVoltage(void) const { return mains_voltage; }
|
|
|
|
inline int16_t getMainsPower(void) const { return mains_power; }
|
|
|
|
|
|
|
|
inline void setMainsVoltage(uint16_t _mains_voltage) { mains_voltage = _mains_voltage; }
|
|
|
|
inline void setMainsPower(int16_t _mains_power) { mains_power = _mains_power; }
|
|
|
|
|
|
|
|
static void toAttributes(Z_attribute_list & attr_list, const Z_Data_Plug & light);
|
|
|
|
|
|
|
|
static const Z_Data_Type type = Z_Data_Type::Z_Plug;
|
|
|
|
// 4 bytes
|
|
|
|
uint16_t mains_voltage; // AC voltage
|
|
|
|
int16_t mains_power; // Active power
|
|
|
|
};
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Device specific: Light device
|
|
|
|
\*********************************************************************************************/
|
|
|
|
class Z_Data_Light : public Z_Data {
|
|
|
|
public:
|
|
|
|
Z_Data_Light(uint8_t endpoint = 0) :
|
|
|
|
Z_Data(Z_Data_Type::Z_Light, endpoint),
|
|
|
|
colormode(0xFF),
|
|
|
|
dimmer(0xFF),
|
|
|
|
sat(0xFF),
|
|
|
|
hue(0xFF),
|
|
|
|
ct(0xFFFF),
|
|
|
|
x(0xFFFF),
|
|
|
|
y(0xFFFF)
|
|
|
|
{}
|
|
|
|
|
|
|
|
inline bool validColormode(void) const { return 0xFF != colormode; }
|
|
|
|
inline bool validDimmer(void) const { return 0xFF != dimmer; }
|
|
|
|
inline bool validSat(void) const { return 0xFF != sat; }
|
|
|
|
inline bool validHue(void) const { return 0xFFFF != hue; }
|
|
|
|
inline bool validCT(void) const { return 0xFFFF != ct; }
|
|
|
|
inline bool validX(void) const { return 0xFFFF != x; }
|
|
|
|
inline bool validY(void) const { return 0xFFFF != y; }
|
|
|
|
|
|
|
|
inline uint8_t getColorMode(void) const { return colormode; }
|
|
|
|
inline uint8_t getDimmer(void) const { return dimmer; }
|
|
|
|
inline uint8_t getSat(void) const { return sat; }
|
|
|
|
inline uint16_t getHue(void) const { return changeUIntScale(hue, 0, 254, 0, 360); }
|
|
|
|
inline uint16_t getCT(void) const { return ct; }
|
|
|
|
inline uint16_t getX(void) const { return x; }
|
|
|
|
inline uint16_t getY(void) const { return y; }
|
|
|
|
|
|
|
|
inline void setColorMode(uint8_t _colormode) { colormode = _colormode; }
|
|
|
|
inline void setDimmer(uint8_t _dimmer) { dimmer = _dimmer; }
|
|
|
|
inline void setSat(uint8_t _sat) { sat = _sat; }
|
|
|
|
inline void setHue(uint16_t _hue) { hue = changeUIntScale(_hue, 0, 360, 0, 254);; }
|
|
|
|
inline void setCT(uint16_t _ct) { ct = _ct; }
|
|
|
|
inline void setX(uint16_t _x) { x = _x; }
|
|
|
|
inline void setY(uint16_t _y) { y = _y; }
|
|
|
|
|
|
|
|
static void toAttributes(Z_attribute_list & attr_list, const Z_Data_Light & light);
|
|
|
|
|
|
|
|
static const Z_Data_Type type = Z_Data_Type::Z_Light;
|
|
|
|
// 12 bytes
|
|
|
|
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
|
|
|
|
uint8_t hue; // last Hue: 0..359 | 0xFFFF not set, default 0
|
|
|
|
uint16_t ct; // last CT: 153-500 | 0xFFFF not set, default 200
|
|
|
|
uint16_t x, y; // last color [x,y] | 0xFFFF not set, default 0
|
|
|
|
};
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Device specific: Sensors: temp, humidity, pressure...
|
|
|
|
\*********************************************************************************************/
|
|
|
|
class Z_Data_Thermo : public Z_Data {
|
|
|
|
public:
|
|
|
|
Z_Data_Thermo(uint8_t endpoint = 0) :
|
|
|
|
Z_Data(Z_Data_Type::Z_Thermo, endpoint),
|
|
|
|
temperature(-0x8000),
|
|
|
|
pressure(0xFFFF),
|
|
|
|
humidity(0xFFFF),
|
|
|
|
th_setpoint(0xFF),
|
|
|
|
temperature_target(-0x8000)
|
|
|
|
{}
|
|
|
|
|
|
|
|
inline bool validTemperature(void) const { return -0x8000 != temperature; }
|
|
|
|
inline bool validPressure(void) const { return 0xFFFF != pressure; }
|
|
|
|
inline bool validHumidity(void) const { return 0xFFFF != humidity; }
|
|
|
|
inline bool validThSetpoint(void) const { return 0xFF != th_setpoint; }
|
|
|
|
inline bool validTempTarget(void) const { return -0x8000 != temperature_target; }
|
|
|
|
|
|
|
|
inline int16_t getTemperature(void) const { return temperature; }
|
|
|
|
inline uint16_t getPressure(void) const { return pressure; }
|
|
|
|
inline uint16_t getHumidity(void) const { return humidity; }
|
|
|
|
inline uint8_t getThSetpoint(void) const { return th_setpoint; }
|
|
|
|
inline int16_t getTempTarget(void) const { return temperature_target; }
|
|
|
|
|
|
|
|
inline void setTemperature(int16_t _temperature) { temperature = _temperature; }
|
|
|
|
inline void setPressure(uint16_t _pressure) { pressure = _pressure; }
|
|
|
|
inline void setHumidity(uint16_t _humidity) { humidity = _humidity; }
|
|
|
|
inline void setThSetpoint(uint8_t _th_setpoint) { th_setpoint = _th_setpoint; }
|
|
|
|
inline void setTempTarget(int16_t _temperature_target){ temperature_target = _temperature_target; }
|
|
|
|
|
|
|
|
static void toAttributes(Z_attribute_list & attr_list, const Z_Data_Thermo & thermo);
|
|
|
|
|
|
|
|
static const Z_Data_Type type = Z_Data_Type::Z_Thermo;
|
|
|
|
// 8 bytes
|
|
|
|
// sensor data
|
|
|
|
int16_t temperature; // temperature in 1/10th of Celsius, 0x8000 if unknown
|
|
|
|
uint16_t pressure; // air pressure in hPa, 0xFFFF if unknown
|
|
|
|
uint16_t humidity; // humidity in percent, 0..100, 0xFF if unknown
|
|
|
|
// thermostat
|
|
|
|
uint8_t th_setpoint; // percentage of heat/cool in percent
|
|
|
|
int16_t temperature_target; // settings for the temparature
|
|
|
|
};
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Device specific: Alarm
|
|
|
|
\*********************************************************************************************/
|
|
|
|
class Z_Data_Alarm : public Z_Data {
|
|
|
|
public:
|
|
|
|
Z_Data_Alarm(uint8_t endpoint = 0) :
|
|
|
|
Z_Data(Z_Data_Type::Z_Alarm, endpoint),
|
|
|
|
zone_type(0xFFFF)
|
|
|
|
{}
|
|
|
|
|
|
|
|
static const Z_Data_Type type = Z_Data_Type::Z_Alarm;
|
|
|
|
|
|
|
|
inline bool validZoneType(void) const { return 0xFFFF != zone_type; }
|
|
|
|
|
|
|
|
inline uint16_t getZoneType(void) const { return zone_type; }
|
|
|
|
|
|
|
|
inline void setZoneType(uint16_t _zone_type) { zone_type = _zone_type; }
|
|
|
|
|
|
|
|
static void toAttributes(Z_attribute_list & attr_list, const Z_Data_Alarm & alarm);
|
|
|
|
|
|
|
|
// 4 bytes
|
|
|
|
uint16_t zone_type; // mapped to the Zigbee standard
|
|
|
|
// 0x0000 Standard CIE
|
|
|
|
// 0x000d Motion sensor
|
|
|
|
// 0x0015 Contact switch
|
|
|
|
// 0x0028 Fire sensor
|
|
|
|
// 0x002a Water sensor
|
|
|
|
// 0x002b Carbon Monoxide (CO) sensor
|
|
|
|
// 0x002c Personal emergency device
|
|
|
|
// 0x002d Vibration/Movement sensor
|
|
|
|
// 0x010f Remote Control
|
|
|
|
// 0x0115 Key fob
|
|
|
|
// 0x021d Keypad
|
|
|
|
// 0x0225 Standard Warning Device (see [N1] part 4)
|
|
|
|
// 0x0226 Glass break sensor
|
|
|
|
// 0x0229 Security repeater*
|
|
|
|
};
|
|
|
|
/*********************************************************************************************\
|
|
|
|
*
|
|
|
|
* Device specific Linked List
|
|
|
|
*
|
|
|
|
\*********************************************************************************************/
|
|
|
|
class Z_Data_Set : public LList<Z_Data> {
|
|
|
|
public:
|
|
|
|
// List<Z_Data>() : List<Z_Data>() {}
|
|
|
|
Z_Data & getByType(Z_Data_Type type, uint8_t ep = 0); // creates if non-existent
|
|
|
|
const Z_Data & find(Z_Data_Type type, uint8_t ep = 0) const;
|
|
|
|
|
|
|
|
// getX() always returns a valid object, and creates the object if there is none
|
|
|
|
// find() does not create an object if it does not exist, and returns *(X*)nullptr
|
|
|
|
|
|
|
|
|
|
|
|
template <class M>
|
|
|
|
M & get(uint8_t ep = 0);
|
|
|
|
|
|
|
|
template <class M>
|
|
|
|
const M & find(uint8_t ep = 0) const;
|
|
|
|
|
|
|
|
// check if the point is null, if so create a new object with the right sub-class
|
|
|
|
template <class M>
|
|
|
|
M & addIfNull(M & cur, uint8_t ep = 0);
|
|
|
|
};
|
|
|
|
|
|
|
|
Z_Data & Z_Data_Set::getByType(Z_Data_Type type, uint8_t ep) {
|
|
|
|
switch (type) {
|
|
|
|
case Z_Data_Type::Z_Light:
|
|
|
|
return get<Z_Data_Light>(ep);
|
|
|
|
case Z_Data_Type::Z_Plug:
|
|
|
|
return get<Z_Data_Plug>(ep);
|
|
|
|
case Z_Data_Type::Z_Alarm:
|
|
|
|
return get<Z_Data_Alarm>(ep);
|
|
|
|
case Z_Data_Type::Z_Thermo:
|
|
|
|
return get<Z_Data_Thermo>(ep);
|
|
|
|
case Z_Data_Type::Z_OnOff:
|
|
|
|
return get<Z_Data_OnOff>(ep);
|
|
|
|
default:
|
|
|
|
return *(Z_Data*)nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class M>
|
|
|
|
M & Z_Data_Set::get(uint8_t ep) {
|
|
|
|
M & m = (M&) find(M::type, ep);
|
|
|
|
return addIfNull<M>(m, ep);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class M>
|
|
|
|
const M & Z_Data_Set::find(uint8_t ep) const {
|
|
|
|
return (M&) find(M::type, ep);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Input: reference to object
|
|
|
|
// Output: if reference is null, instantiate object and add it at the end of the list
|
|
|
|
template <class M>
|
|
|
|
M & Z_Data_Set::addIfNull(M & cur, uint8_t ep) {
|
|
|
|
if (nullptr == &cur) {
|
|
|
|
LList_elt<M> * elt = new LList_elt<M>();
|
|
|
|
elt->val()._endpoint = ep;
|
|
|
|
this->addToLast((LList_elt<Z_Data>*)elt);
|
|
|
|
return elt->val();
|
|
|
|
} else {
|
|
|
|
if (cur._endpoint == 0) { cur._endpoint = ep; } // be more specific on endpoint
|
|
|
|
return cur;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const Z_Data & Z_Data_Set::find(Z_Data_Type type, uint8_t ep) const {
|
|
|
|
for (auto & elt : *this) {
|
|
|
|
if (elt._type == type) {
|
|
|
|
// type matches, check if ep matches.
|
|
|
|
// 0 matches all endpoints
|
|
|
|
if ((ep == 0) || (elt._endpoint == 0) || (ep == elt._endpoint)) {
|
|
|
|
return elt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return *(Z_Data*)nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Low-level
|
|
|
|
// Add light attributes, used by dumpLightState and by SbData
|
|
|
|
//
|
|
|
|
void Z_Data_Plug::toAttributes(Z_attribute_list & attr_list, const Z_Data_Plug & plug) {
|
|
|
|
if (&plug == nullptr) { return; }
|
|
|
|
// dump all known values
|
|
|
|
if (plug.validMainsVoltage()) { attr_list.addAttribute(PSTR("RMSVoltage")).setUInt(plug.getMainsVoltage()); }
|
|
|
|
if (plug.validMainsPower()) { attr_list.addAttribute(PSTR("ActivePower")).setInt(plug.getMainsPower()); }
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z_Data_Light::toAttributes(Z_attribute_list & attr_list, const Z_Data_Light & light) {
|
|
|
|
if (&light == nullptr) { return; }
|
|
|
|
// expose the last known status of the bulb, for Hue integration
|
|
|
|
attr_list.addAttribute(PSTR(D_JSON_ZIGBEE_LIGHT)).setInt(light.getConfig()); // special case, since type is 0x00 we can assume getConfig() is good
|
|
|
|
// dump all known values
|
|
|
|
if (light.validDimmer()) { attr_list.addAttribute(PSTR("Dimmer")).setUInt(light.getDimmer()); }
|
|
|
|
if (light.validColormode()) { attr_list.addAttribute(PSTR("Colormode")).setUInt(light.getColorMode()); }
|
|
|
|
if (light.validCT()) { attr_list.addAttribute(PSTR("CT")).setUInt(light.getCT()); }
|
|
|
|
if (light.validSat()) { attr_list.addAttribute(PSTR("Sat")).setUInt(light.getSat()); }
|
|
|
|
if (light.validHue()) { attr_list.addAttribute(PSTR("Hue")).setUInt(light.getHue()); }
|
|
|
|
if (light.validX()) { attr_list.addAttribute(PSTR("X")).setUInt(light.getX()); }
|
|
|
|
if (light.validY()) { attr_list.addAttribute(PSTR("Y")).setUInt(light.getY()); }
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z_Data_OnOff::toAttributes(Z_attribute_list & attr_list, const Z_Data_OnOff & onoff) {
|
|
|
|
if (&onoff == nullptr) { return; }
|
|
|
|
if (onoff.validPower()) { attr_list.addAttribute(PSTR("Power")).setUInt(onoff.getPower() ? 1 : 0); }
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z_Data_Thermo::toAttributes(Z_attribute_list & attr_list, const Z_Data_Thermo & thermo) {
|
|
|
|
if (&thermo == nullptr) { return; }
|
|
|
|
if (thermo.validTemperature()) { attr_list.addAttribute(PSTR("Temperature")).setInt(thermo.getTemperature()); }
|
|
|
|
if (thermo.validPressure()) { attr_list.addAttribute(PSTR("Pressure")).setUInt(thermo.getPressure()); }
|
|
|
|
if (thermo.validHumidity()) { attr_list.addAttribute(PSTR("Humidity")).setUInt(thermo.getHumidity()); }
|
|
|
|
if (thermo.validThSetpoint()) { attr_list.addAttribute(PSTR("ThSetpoint")).setUInt(thermo.getThSetpoint()); }
|
|
|
|
if (thermo.validTempTarget()) { attr_list.addAttribute(PSTR("TempTarget")).setInt(thermo.getTempTarget()); }
|
|
|
|
}
|
|
|
|
|
|
|
|
void Z_Data_Alarm::toAttributes(Z_attribute_list & attr_list, const Z_Data_Alarm & alarm) {
|
|
|
|
if (&alarm == nullptr) { return; }
|
|
|
|
if (alarm.validZoneType()) { attr_list.addAttribute(PSTR("ZoneType")).setUInt(alarm.getZoneType()); }
|
|
|
|
}
|
|
|
|
|
2020-05-29 21:52:45 +01:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Structures for Rules variables related to the last received message
|
|
|
|
\*********************************************************************************************/
|
2020-03-17 17:46:05 +00:00
|
|
|
const size_t endpoints_max = 8; // we limit to 8 endpoints
|
|
|
|
|
2020-08-22 17:40:44 +01:00
|
|
|
class Z_Device {
|
|
|
|
public:
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
uint64_t longaddr; // 0x00 means unspecified
|
2020-03-14 13:17:30 +00:00
|
|
|
char * manufacturerId;
|
|
|
|
char * modelId;
|
|
|
|
char * friendlyName;
|
2020-09-12 09:57:54 +01:00
|
|
|
// _defer_last_time : what was the last time an outgoing message is scheduled
|
|
|
|
// this is designed for flow control and avoid messages to be lost or unanswered
|
|
|
|
uint32_t defer_last_message_sent;
|
|
|
|
|
2020-03-17 17:46:05 +00:00
|
|
|
uint8_t endpoints[endpoints_max]; // static array to limit memory consumption, list of endpoints until 0x00 or end of array
|
2020-09-05 13:44:31 +01:00
|
|
|
// Used for attribute reporting
|
|
|
|
Z_attribute_list attr_list;
|
2020-02-22 19:53:55 +00:00
|
|
|
// sequence number for Zigbee frames
|
2020-03-14 13:17:30 +00:00
|
|
|
uint16_t shortaddr; // unique key if not null, or unspecified if null
|
|
|
|
uint8_t seqNumber;
|
2020-10-07 19:04:33 +01:00
|
|
|
bool hidden;
|
|
|
|
bool reachable;
|
2020-03-14 13:17:30 +00:00
|
|
|
// Light information for Hue integration integration, last known values
|
2020-10-07 19:04:33 +01:00
|
|
|
|
|
|
|
// New version of device data handling
|
|
|
|
Z_Data_Set data; // Linkedlist of device data per endpoint
|
2020-08-20 07:25:53 +01:00
|
|
|
// other status
|
2020-08-22 17:40:44 +01:00
|
|
|
uint8_t lqi; // lqi from last message, 0xFF means unknown
|
2020-06-28 17:04:36 +01:00
|
|
|
uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon
|
2020-10-07 19:04:33 +01:00
|
|
|
// power plug data-
|
2020-09-03 03:16:13 +01:00
|
|
|
uint32_t last_seen; // Last seen time (epoch)
|
2020-08-22 17:40:44 +01:00
|
|
|
|
|
|
|
// Constructor with all defaults
|
2020-09-05 13:44:31 +01:00
|
|
|
Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00):
|
2020-08-22 17:40:44 +01:00
|
|
|
longaddr(_longaddr),
|
|
|
|
manufacturerId(nullptr),
|
|
|
|
modelId(nullptr),
|
|
|
|
friendlyName(nullptr),
|
2020-09-12 09:57:54 +01:00
|
|
|
defer_last_message_sent(0),
|
2020-08-22 17:40:44 +01:00
|
|
|
endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
2020-09-05 13:44:31 +01:00
|
|
|
attr_list(),
|
2020-08-22 17:40:44 +01:00
|
|
|
shortaddr(_shortaddr),
|
|
|
|
seqNumber(0),
|
2020-10-07 19:04:33 +01:00
|
|
|
hidden(false),
|
|
|
|
reachable(false),
|
2020-08-22 17:40:44 +01:00
|
|
|
// Hue support
|
|
|
|
lqi(0xFF),
|
|
|
|
batterypercent(0xFF),
|
2020-10-07 19:04:33 +01:00
|
|
|
last_seen(0)
|
2020-08-22 17:40:44 +01:00
|
|
|
{ };
|
|
|
|
|
|
|
|
inline bool valid(void) const { return BAD_SHORTADDR != shortaddr; } // is the device known, valid and found?
|
|
|
|
|
|
|
|
inline bool validLongaddr(void) const { return 0x0000 != longaddr; }
|
|
|
|
inline bool validManufacturerId(void) const { return nullptr != manufacturerId; }
|
|
|
|
inline bool validModelId(void) const { return nullptr != modelId; }
|
|
|
|
inline bool validFriendlyName(void) const { return nullptr != friendlyName; }
|
|
|
|
|
2020-10-07 19:04:33 +01:00
|
|
|
inline bool validPower(uint8_t ep =0) const;
|
2020-08-22 17:40:44 +01:00
|
|
|
|
|
|
|
inline bool validLqi(void) const { return 0xFF != lqi; }
|
|
|
|
inline bool validBatteryPercent(void) const { return 0xFF != batterypercent; }
|
2020-09-03 03:16:13 +01:00
|
|
|
inline bool validLastSeen(void) const { return 0x0 != last_seen; }
|
2020-08-22 17:40:44 +01:00
|
|
|
|
2020-10-07 19:04:33 +01:00
|
|
|
inline void setReachable(bool _reachable) { reachable = _reachable; }
|
|
|
|
inline bool getReachable(void) const { return reachable; }
|
|
|
|
inline bool getPower(uint8_t ep =0) const;
|
2020-09-27 17:42:19 +01:00
|
|
|
|
2020-10-07 19:04:33 +01:00
|
|
|
// dump device attributes to ZbData
|
|
|
|
void toAttributes(Z_attribute_list & attr_list) const;
|
2020-08-28 21:53:34 +01:00
|
|
|
|
2020-10-07 19:04:33 +01:00
|
|
|
// Device data specific
|
|
|
|
void setPower(bool power_on, uint8_t ep = 0);
|
2020-08-26 07:56:13 +01:00
|
|
|
|
|
|
|
// If light, returns the number of channels, or 0xFF if unknown
|
2020-10-07 19:04:33 +01:00
|
|
|
int8_t getLightChannels(void) const {
|
|
|
|
const Z_Data_Light & light = data.find<Z_Data_Light>(0);
|
|
|
|
if (&light != nullptr) {
|
|
|
|
return light.getConfig();
|
|
|
|
} else {
|
|
|
|
return -1;
|
2020-08-26 07:56:13 +01:00
|
|
|
}
|
2020-10-07 19:04:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// returns: dirty flag, did we change the value of the object
|
|
|
|
bool setLightChannels(int8_t channels) {
|
|
|
|
bool dirty = false;
|
|
|
|
if (channels >= 0) {
|
|
|
|
// retrieve of create light object
|
|
|
|
Z_Data_Light & light = data.get<Z_Data_Light>(0);
|
|
|
|
if (channels != light.getConfig()) {
|
|
|
|
light.setConfig(channels);
|
|
|
|
dirty = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// remove light object if any
|
|
|
|
for (auto & data_elt : data) {
|
|
|
|
if (data_elt.getType() == Z_Data_Type::Z_Light) {
|
|
|
|
// remove light object
|
|
|
|
data.remove(&data_elt);
|
|
|
|
dirty = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dirty;
|
2020-08-26 07:56:13 +01:00
|
|
|
}
|
2020-08-22 17:40:44 +01:00
|
|
|
};
|
2019-10-06 11:40:58 +01:00
|
|
|
|
2020-03-23 21:46:26 +00:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Structures for deferred callbacks
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2020-09-12 09:57:54 +01:00
|
|
|
typedef void (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value);
|
2020-03-23 21:46:26 +00:00
|
|
|
|
2020-03-14 13:17:30 +00:00
|
|
|
// Category for Deferred actions, this allows to selectively remove active deferred or update them
|
|
|
|
typedef enum Z_Def_Category {
|
2020-09-12 09:57:54 +01:00
|
|
|
Z_CAT_ALWAYS = 0, // no category, it will happen whatever new timers
|
|
|
|
// Below will clear any event in the same category for the same address (shortaddr / groupaddr)
|
|
|
|
Z_CLEAR_DEVICE = 0x01,
|
2020-03-14 13:17:30 +00:00
|
|
|
Z_CAT_READ_ATTR, // Attribute reporting, either READ_ATTRIBUTE or REPORT_ATTRIBUTE, we coalesce all attributes reported if we can
|
2020-04-17 11:27:36 +01:00
|
|
|
Z_CAT_VIRTUAL_OCCUPANCY, // Creation of a virtual attribute, typically after a time-out. Ex: Aqara presence sensor
|
2020-03-26 18:34:59 +00:00
|
|
|
Z_CAT_REACHABILITY, // timer set to measure reachability of device, i.e. if we don't get an answer after 1s, it is marked as unreachable (for Alexa)
|
2020-09-24 18:15:07 +01:00
|
|
|
Z_CAT_PERMIT_JOIN, // timer to signal the end of the PermitJoin period
|
2020-09-12 09:57:54 +01:00
|
|
|
// Below will clear based on device + cluster pair.
|
|
|
|
Z_CLEAR_DEVICE_CLUSTER,
|
|
|
|
Z_CAT_READ_CLUSTER,
|
|
|
|
// Below will clear based on device + cluster + endpoint
|
|
|
|
Z_CLEAR_DEVICE_CLUSTER_ENDPOINT,
|
|
|
|
Z_CAT_EP_DESC, // read endpoint descriptor to gather clusters
|
|
|
|
Z_CAT_BIND, // send auto-binding to coordinator
|
|
|
|
Z_CAT_CONFIG_ATTR, // send a config attribute reporting request
|
|
|
|
Z_CAT_READ_ATTRIBUTE, // read a single attribute
|
2020-03-14 13:17:30 +00:00
|
|
|
} Z_Def_Category;
|
|
|
|
|
2020-09-12 09:57:54 +01:00
|
|
|
const uint32_t Z_CAT_REACHABILITY_TIMEOUT = 2000; // 1000 ms or 1s
|
2020-03-26 18:34:59 +00:00
|
|
|
|
2020-03-14 13:17:30 +00:00
|
|
|
typedef struct Z_Deferred {
|
|
|
|
// below are per device timers, used for example to query the new state of the device
|
|
|
|
uint32_t timer; // millis() when to fire the timer, 0 if no timer
|
|
|
|
uint16_t shortaddr; // identifier of the device
|
|
|
|
uint16_t groupaddr; // group address (if needed)
|
|
|
|
uint16_t cluster; // cluster to use for the timer
|
|
|
|
uint8_t endpoint; // endpoint to use for timer
|
2020-07-19 14:57:37 +01:00
|
|
|
uint8_t category; // which category of deferred is it
|
2020-03-14 13:17:30 +00:00
|
|
|
uint32_t value; // any raw value to use for the timer
|
|
|
|
Z_DeviceTimer func; // function to call when timer occurs
|
|
|
|
} Z_Deferred;
|
|
|
|
|
2020-03-23 21:46:26 +00:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Singleton for device configuration
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
// All devices are stored in a Vector
|
|
|
|
// Invariants:
|
|
|
|
// - shortaddr is unique if not null
|
|
|
|
// - longaddr is unique if not null
|
|
|
|
// - shortaddr and longaddr cannot be both null
|
|
|
|
class Z_Devices {
|
|
|
|
public:
|
2020-09-05 13:44:31 +01:00
|
|
|
Z_Devices() : _deferred() {};
|
2019-10-06 11:40:58 +01:00
|
|
|
|
2020-01-17 23:02:01 +00:00
|
|
|
// Probe the existence of device keys
|
|
|
|
// Results:
|
|
|
|
// - 0x0000 = not found
|
2020-05-17 17:33:42 +01:00
|
|
|
// - BAD_SHORTADDR = bad parameter
|
2020-01-17 23:02:01 +00:00
|
|
|
// - 0x<shortaddr> = the device's short address
|
|
|
|
uint16_t isKnownLongAddr(uint64_t longaddr) const;
|
|
|
|
uint16_t isKnownIndex(uint32_t index) const;
|
|
|
|
uint16_t isKnownFriendlyName(const char * name) const;
|
2020-08-22 17:40:44 +01:00
|
|
|
|
2020-09-05 13:44:31 +01:00
|
|
|
Z_Device & findShortAddr(uint16_t shortaddr);
|
2020-08-22 17:40:44 +01:00
|
|
|
const Z_Device & findShortAddr(uint16_t shortaddr) const;
|
2020-09-05 13:44:31 +01:00
|
|
|
Z_Device & findLongAddr(uint64_t longaddr);
|
|
|
|
const Z_Device & findLongAddr(uint64_t longaddr) const;
|
2020-08-22 17:40:44 +01:00
|
|
|
Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist
|
|
|
|
Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist
|
2020-09-05 13:44:31 +01:00
|
|
|
// check if a device was found or if it's the fallback device
|
|
|
|
inline bool foundDevice(const Z_Device & device) const {
|
|
|
|
return (&device != &device_unk);
|
|
|
|
}
|
2020-01-17 23:02:01 +00:00
|
|
|
|
2020-08-22 17:40:44 +01:00
|
|
|
int32_t findFriendlyName(const char * name) const;
|
2020-02-02 19:53:49 +00:00
|
|
|
uint64_t getDeviceLongAddr(uint16_t shortaddr) const;
|
|
|
|
|
2020-03-17 17:46:05 +00:00
|
|
|
uint8_t findFirstEndpoint(uint16_t shortaddr) const;
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
// Add new device, provide ShortAddr and optional longAddr
|
|
|
|
// If it is already registered, update information, otherwise create the entry
|
|
|
|
void updateDevice(uint16_t shortaddr, uint64_t longaddr = 0);
|
|
|
|
|
|
|
|
// Add an endpoint to a device
|
2020-03-17 17:46:05 +00:00
|
|
|
void addEndpoint(uint16_t shortaddr, uint8_t endpoint);
|
2020-03-22 15:11:01 +00:00
|
|
|
void clearEndpoints(uint16_t shortaddr);
|
2020-07-20 18:30:32 +01:00
|
|
|
uint32_t countEndpoints(uint16_t shortaddr) const; // return the number of known endpoints (0 if unknown)
|
2019-10-06 11:40:58 +01:00
|
|
|
|
|
|
|
void setManufId(uint16_t shortaddr, const char * str);
|
|
|
|
void setModelId(uint16_t shortaddr, const char * str);
|
2020-01-17 23:02:01 +00:00
|
|
|
void setFriendlyName(uint16_t shortaddr, const char * str);
|
2020-08-22 17:40:44 +01:00
|
|
|
inline const char * getFriendlyName(uint16_t shortaddr) const {
|
|
|
|
return findShortAddr(shortaddr).friendlyName;
|
|
|
|
}
|
|
|
|
inline const char * getModelId(uint16_t shortaddr) const {
|
|
|
|
return findShortAddr(shortaddr).modelId;
|
|
|
|
}
|
|
|
|
inline const char * getManufacturerId(uint16_t shortaddr) const{
|
|
|
|
return findShortAddr(shortaddr).manufacturerId;
|
|
|
|
}
|
|
|
|
|
2020-03-26 18:34:59 +00:00
|
|
|
void setReachable(uint16_t shortaddr, bool reachable);
|
2020-06-28 16:53:59 +01:00
|
|
|
void setLQI(uint16_t shortaddr, uint8_t lqi);
|
2020-09-03 03:16:13 +01:00
|
|
|
void setLastSeenNow(uint16_t shortaddr);
|
2020-08-22 17:40:44 +01:00
|
|
|
// uint8_t getLQI(uint16_t shortaddr) const;
|
2020-06-28 17:04:36 +01:00
|
|
|
void setBatteryPercent(uint16_t shortaddr, uint8_t bp);
|
|
|
|
uint8_t getBatteryPercent(uint16_t shortaddr) const;
|
2019-10-13 11:56:52 +01:00
|
|
|
|
2020-02-22 19:53:55 +00:00
|
|
|
// get next sequence number for (increment at each all)
|
|
|
|
uint8_t getNextSeqNumber(uint16_t shortaddr);
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
// Dump json
|
2020-10-07 19:04:33 +01:00
|
|
|
static void addLightState(Z_attribute_list & attr_list, const Z_Data_Light & light);
|
2020-03-14 13:17:30 +00:00
|
|
|
String dumpLightState(uint16_t shortaddr) const;
|
2020-01-17 23:02:01 +00:00
|
|
|
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
|
2020-09-21 20:49:32 +01:00
|
|
|
int32_t deviceRestore(JsonParserObject json);
|
2019-10-06 11:40:58 +01:00
|
|
|
|
2020-08-20 07:25:53 +01:00
|
|
|
// General Zigbee device profile support
|
2020-10-07 19:04:33 +01:00
|
|
|
void setLightProfile(uint16_t shortaddr, uint8_t light_profile);
|
|
|
|
uint8_t getLightProfile(uint16_t shortaddr) const ;
|
2020-08-20 07:25:53 +01:00
|
|
|
|
2020-03-14 13:17:30 +00:00
|
|
|
// Hue support
|
|
|
|
int8_t getHueBulbtype(uint16_t shortaddr) const ;
|
2020-08-20 07:25:53 +01:00
|
|
|
void hideHueBulb(uint16_t shortaddr, bool hidden);
|
|
|
|
bool isHueBulbHidden(uint16_t shortaddr) const ;
|
2020-10-07 19:04:33 +01:00
|
|
|
Z_Data_Light & getLight(uint16_t shortaddr);
|
2020-03-14 13:17:30 +00:00
|
|
|
|
2019-12-15 15:02:41 +00:00
|
|
|
// Timers
|
2020-09-12 09:57:54 +01:00
|
|
|
void resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category, uint16_t cluster = 0xFFFF, uint8_t endpoint = 0xFF);
|
2020-03-14 13:17:30 +00:00
|
|
|
void setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func);
|
2020-09-12 09:57:54 +01:00
|
|
|
void queueTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func);
|
2019-12-15 15:02:41 +00:00
|
|
|
void runTimer(void);
|
|
|
|
|
2019-12-22 16:47:45 +00:00
|
|
|
// Append or clear attributes Json structure
|
2020-09-05 13:44:31 +01:00
|
|
|
void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list);
|
2020-01-17 23:02:01 +00:00
|
|
|
void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer
|
2020-09-05 13:44:31 +01:00
|
|
|
bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const;
|
|
|
|
void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list);
|
2020-01-17 23:02:01 +00:00
|
|
|
|
|
|
|
// Iterator
|
|
|
|
size_t devicesSize(void) const {
|
2020-09-05 13:44:31 +01:00
|
|
|
return _devices.length();
|
2020-01-17 23:02:01 +00:00
|
|
|
}
|
2020-09-05 13:44:31 +01:00
|
|
|
const Z_Device & devicesAt(size_t i) const {
|
|
|
|
const Z_Device * devp = _devices.at(i);
|
|
|
|
if (devp) {
|
|
|
|
return *devp;
|
|
|
|
} else {
|
|
|
|
return device_unk;
|
|
|
|
}
|
2020-01-17 23:02:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Remove device from list
|
|
|
|
bool removeDevice(uint16_t shortaddr);
|
|
|
|
|
|
|
|
// Mark data as 'dirty' and requiring to save in Flash
|
|
|
|
void dirty(void);
|
2020-01-22 21:40:28 +00:00
|
|
|
void clean(void); // avoid writing to flash the last changes
|
2020-03-14 13:17:30 +00:00
|
|
|
void shrinkToFit(uint16_t shortaddr);
|
2020-01-17 23:02:01 +00:00
|
|
|
|
|
|
|
// Find device by name, can be short_addr, long_addr, number_in_array or name
|
|
|
|
uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const;
|
2019-12-22 16:47:45 +00:00
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
private:
|
2020-09-05 13:44:31 +01:00
|
|
|
LList<Z_Device> _devices; // list of devices
|
|
|
|
LList<Z_Deferred> _deferred; // list of deferred calls
|
2020-07-19 14:57:37 +01:00
|
|
|
uint32_t _saveTimer = 0;
|
2020-03-14 13:17:30 +00:00
|
|
|
uint8_t _seqNumber = 0; // global seqNumber if device is unknown
|
2019-10-06 11:40:58 +01:00
|
|
|
|
2020-08-22 17:40:44 +01:00
|
|
|
// Following device is used represent the unknown device, with all defaults
|
|
|
|
// Any find() function will not return Null, instead it will return this instance
|
|
|
|
const Z_Device device_unk = Z_Device(BAD_SHORTADDR);
|
2019-10-06 11:40:58 +01:00
|
|
|
|
2020-09-05 13:44:31 +01:00
|
|
|
//int32_t findShortAddrIdx(uint16_t shortaddr) const;
|
2019-10-06 11:40:58 +01:00
|
|
|
// Create a new entry in the devices list - must be called if it is sure it does not already exist
|
|
|
|
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
|
2020-03-14 13:17:30 +00:00
|
|
|
void freeDeviceEntry(Z_Device *device);
|
2020-03-23 21:46:26 +00:00
|
|
|
|
|
|
|
void setStringAttribute(char*& attr, const char * str);
|
2019-10-06 11:40:58 +01:00
|
|
|
};
|
|
|
|
|
2020-03-23 21:46:26 +00:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Singleton variable
|
|
|
|
\*********************************************************************************************/
|
2019-10-06 11:40:58 +01:00
|
|
|
Z_Devices zigbee_devices = Z_Devices();
|
|
|
|
|
2020-02-02 19:53:49 +00:00
|
|
|
// Local coordinator information
|
|
|
|
uint64_t localIEEEAddr = 0;
|
2020-06-29 21:21:32 +01:00
|
|
|
uint16_t localShortAddr = 0;
|
2020-02-02 19:53:49 +00:00
|
|
|
|
2020-03-22 15:11:01 +00:00
|
|
|
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
#endif // USE_ZIGBEE
|