Merge pull request #9689 from s-hadinger/zigbee_zboccupancy

Zigbee added ``ZbOccupancy`` command to configure the time-out for PIR
This commit is contained in:
s-hadinger 2020-10-31 18:13:23 +01:00 committed by GitHub
commit cfe52e6bd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 117 additions and 15 deletions

View File

@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
- Zigbee reduce battery drain (#9642)
- Zigbee added ``ZbMap`` command to describe Zigbee topology (#9651)
- Command ``Gpios 255`` to show all possible GPIO configurations
- Zigbee added ``ZbOccupancy`` command to configure the time-out for PIR
### Changed
- PlatformIO library structure redesigned for compilation speed by Jason2866

View File

@ -577,6 +577,8 @@
#define D_JSON_ZIGBEE_STATUS_MSG "StatusMessage"
#define D_CMND_ZIGBEE_LIGHT "Light"
#define D_JSON_ZIGBEE_LIGHT "Light"
#define D_CMND_ZIGBEE_OCCUPANCY "Occupancy"
#define D_JSON_ZIGBEE_OCCUPANCY "Occupancy"
#define D_CMND_ZIGBEE_RESTORE "Restore"
#define D_CMND_ZIGBEE_CONFIG "Config"
#define D_JSON_ZIGBEE_CONFIG "Config"

View File

@ -172,7 +172,7 @@ void Z_Data_OnOff::setPower(bool val, uint32_t relay) {
}
/*********************************************************************************************\
* Device specific: Light device
* Device specific: Plug device
\*********************************************************************************************/
class Z_Data_Plug : public Z_Data {
public:
@ -249,6 +249,18 @@ public:
/*********************************************************************************************\
* Device specific: PIR
*
// List of occupancy time-outs:
// 0xF = default (90 s)
// 0x0 = no time-out
// 0x1 = 15 s
// 0x2 = 30 s
// 0x3 = 45 s
// 0x4 = 60 s
// 0x5 = 75 s
// 0x6 = 90 s -- default
// 0x7 = 105 s
// 0x8 = 120 s
\*********************************************************************************************/
class Z_Data_PIR : public Z_Data {
public:
@ -265,7 +277,10 @@ public:
inline uint16_t getIlluminance(void) const { return illuminance; }
inline void setOccupancy(uint8_t _occupancy) { occupancy = _occupancy; }
inline void setilluminance(uint16_t _illuminance) { illuminance = _illuminance; }
inline void setIlluminance(uint16_t _illuminance) { illuminance = _illuminance; }
uint32_t getTimeoutSeconds(void) const;
void setTimeoutSeconds(int32_t value);
static const Z_Data_Type type = Z_Data_Type::Z_PIR;
// PIR
@ -273,6 +288,24 @@ public:
uint16_t illuminance; // illuminance
};
uint32_t Z_Data_PIR::getTimeoutSeconds(void) const {
if (_config != 0xF) {
return _config * 15;
} else {
return 90;
}
}
void Z_Data_PIR::setTimeoutSeconds(int32_t value) {
if (value < 0) {
_config = 0xF;
} else {
uint32_t val_15 = (value + 14)/ 15; // always round up
if (val_15 > 8) { val_15 = 8; }
_config = val_15;
}
}
/*********************************************************************************************\
* Device specific: Sensors: temp, humidity, pressure...
\*********************************************************************************************/
@ -510,6 +543,7 @@ public:
inline bool getReachable(void) const { return reachable; }
inline bool getPower(uint8_t ep =0) const;
bool addEndpoint(uint8_t endpoint);
// dump device attributes to ZbData
void toAttributes(Z_attribute_list & attr_list) const;

View File

@ -228,21 +228,27 @@ void Z_Devices::clearEndpoints(uint16_t shortaddr) {
//
// Add an endpoint to a shortaddr
// return true if a change was made
//
void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) {
if ((0x00 == endpoint) || (endpoint > 240)) { return; }
Z_Device &device = getShortAddr(shortaddr);
bool Z_Device::addEndpoint(uint8_t endpoint) {
if ((0x00 == endpoint) || (endpoint > 240)) { return false; }
for (uint32_t i = 0; i < endpoints_max; i++) {
if (endpoint == device.endpoints[i]) {
return; // endpoint already there
if (endpoint == endpoints[i]) {
return false; // endpoint already there
}
if (0 == device.endpoints[i]) {
device.endpoints[i] = endpoint;
dirty();
return;
if (0 == endpoints[i]) {
endpoints[i] = endpoint;
return true;
}
}
return false;
}
void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) {
if (getShortAddr(shortaddr).addEndpoint(endpoint)) {
dirty();
}
}
//

View File

@ -265,7 +265,7 @@ void hydrateSingleDevice(const SBuffer & buf_d, uint32_t version) {
uint8_t ep = buf_d.get8(d++);
if (0xFF == ep) { break; } // ep 0xFF marks the end of the endpoints
if (ep > 240) { ep = 0xFF; } // ep == 0xFF means ignore
if ((ep > 0) && (ep != 0xFF)) { zigbee_devices.addEndpoint(shortaddr, ep); } // don't add endpoint if it is 0x00
device.addEndpoint(ep); // it will ignore invalid endpoints
while (d < buf_len) {
uint8_t config_type = buf_d.get8(d++);
if (0xFF == config_type) { break; } // 0xFF marks the end of congiguration

View File

@ -1285,7 +1285,12 @@ void ZCLFrame::generateCallBacks(Z_attribute_list& attr_list) {
case 0x04060000: // Occupancy
uint32_t occupancy = attr.getUInt();
if (occupancy) {
zigbee_devices.setTimer(_srcaddr, 0 /* groupaddr */, OCCUPANCY_TIMEOUT, _cluster_id, _srcendpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
uint32_t pir_timer = OCCUPANCY_TIMEOUT;
const Z_Data_PIR & pir_found = (const Z_Data_PIR&) zigbee_devices.getShortAddr(_srcaddr).data.find(Z_Data_Type::Z_PIR, _srcendpoint);
if (&pir_found != nullptr) {
pir_timer = pir_found.getTimeoutSeconds() * 1000;
}
zigbee_devices.setTimer(_srcaddr, 0 /* groupaddr */, pir_timer, _cluster_id, _srcendpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
} else {
zigbee_devices.resetTimersForDevice(_srcaddr, 0 /* groupaddr */, Z_CAT_VIRTUAL_OCCUPANCY);
}

View File

@ -32,7 +32,8 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|"
D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|"
D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|"
D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_MAP "|"
D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_OCCUPANCY "|"
D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_MAP "|"
D_CMND_ZIGBEE_CONFIG "|" D_CMND_ZIGBEE_DATA
;
@ -47,7 +48,8 @@ void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe,
&CmndZbForget, &CmndZbSave, &CmndZbName,
&CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId,
&CmndZbLight, &CmndZbRestore, &CmndZbBindState, &CmndZbMap,
&CmndZbLight, &CmndZbOccupancy,
&CmndZbRestore, &CmndZbBindState, &CmndZbMap,
&CmndZbConfig, CmndZbData,
};
@ -1111,6 +1113,56 @@ void CmndZbLight(void) {
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_LIGHT));
ResponseCmndDone();
}
//
// Command `ZbOccupancy`
// Specify, read or erase the Occupancy detector configuration
void CmndZbOccupancy(void) {
// Syntax is:
// ZbOccupancy <device_id>,<x> - set the occupancy time-out
// ZbOccupancy <device_id> - display the configuration
//
// List of occupancy time-outs:
// 0xF = default (90 s)
// 0x0 = no time-out
// 0x1 = 15 s
// 0x2 = 30 s
// 0x3 = 45 s
// 0x4 = 60 s
// 0x5 = 75 s
// 0x6 = 90 s -- default
// 0x7 = 105 s
// 0x8 = 120 s
// Where <device_id> can be: short_addr, long_addr, device_index, friendly_name
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
// check if parameters contain a comma ','
char *p;
char *str = strtok_r(XdrvMailbox.data, ", ", &p);
// parse first part, <device_id>
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
Z_Device & device = zigbee_devices.getShortAddr(shortaddr);
int8_t occupancy_time = -1;
if (p) {
Z_Data_PIR & pir = (Z_Data_PIR&) device.data.getByType(Z_Data_Type::Z_PIR);
occupancy_time = strtol(p, nullptr, 10);
pir.setTimeoutSeconds(occupancy_time);
zigbee_devices.dirty();
} else {
const Z_Data_PIR & pir_found = (const Z_Data_PIR&) device.data.find(Z_Data_Type::Z_PIR);
if (&pir_found != nullptr) {
occupancy_time = pir_found.getTimeoutSeconds();
}
}
Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_OCCUPANCY "\":%d}"), occupancy_time);
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_LIGHT));
ResponseCmndDone();
}
//
// Command `ZbForget`
@ -1357,6 +1409,7 @@ bool parseDeviceInnerData(class Z_Device & device, JsonParserObject root) {
if (val = data_values[PSTR("LastSeen")]) { device.last_seen = val.getUInt(); }
} else {
// Import generic attributes first
device.addEndpoint(endpoint);
Z_Data & data = device.data.getByType(data_type, endpoint);
// scan through attributes
@ -1459,6 +1512,7 @@ void CmndZbData(void) {
}
}
}
zigbee_devices.dirty(); // save to flash
ResponseCmndDone();
} else {
// non-JSON, export current data