Merge remote-tracking branch 'Tasmota/development' into vl53l1x

This commit is contained in:
Jason2866 2020-08-26 14:50:28 +02:00
commit 788e468145
13 changed files with 257 additions and 89 deletions

View File

@ -53,7 +53,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
## Changelog
### Version 8.4.0.2
### Version 8.4.0.3
- Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
- Change White blend mode moved to using ``SetOption 105`` instead of ``RGBWWTable``
@ -63,6 +63,8 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
- Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046)
- Add ESP32 Analog input support for GPIO32 to GPIO39
- Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig``
- Add Zigbee web gui widget for Temp/Humidity/Pressure sensors
- Add better config corruption recovery (#9046)
- Add virtual CT for 4 channels lights, emulating a 5th channel
- Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113)
- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134)

View File

@ -1,5 +1,10 @@
## Unreleased (development)
### 8.4.0.3 20200823
- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134)
- Add Zigbee web ui widget for Lights
### 8.4.0.2 20200813
- Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
@ -9,6 +14,7 @@
- Add virtual CT for 4 channels lights, emulating a 5th channel
- Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113)
- Add Zigbee web gui widget for Temp/Humidity/Pressure sensors
- Add Zigbee battery icon
### 8.4.0.1 20200730

View File

@ -127,7 +127,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t white_blend_mode : 1; // bit 23 (v8.4.0.1) - SetOption105 - White Blend Mode - used to be `RGBWWTable` last value `0`, now deprecated in favor of this option
uint32_t virtual_ct : 1; // bit 24 (v8.4.0.1) - SetOption106 - Virtual CT - Creates a virtual White ColorTemp for RGBW lights
uint32_t virtual_ct_cw : 1; // bit 25 (v8.4.0.1) - SetOption107 - Virtual CT Channel - signals whether the hardware white is cold CW (true) or warm WW (false)
uint32_t teleinfo_rawdata : 1; // bit 21 (v8.4.0.2) - SetOption108 - enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1)
uint32_t teleinfo_rawdata : 1; // bit 21 (v8.4.0.2) - SetOption108 - enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1)
uint32_t spare27 : 1;
uint32_t spare28 : 1; // bit 28
uint32_t spare29 : 1; // bit 29
@ -557,7 +557,7 @@ struct {
uint16_t dimmer_hw_min; // E90
uint16_t dimmer_hw_max; // E92
uint32_t deepsleep; // E94
uint16_t energy_power_delta; // E98
uint16_t ex2_energy_power_delta; // E98 - Free since 8.4.0.3
uint8_t shutter_motordelay[MAX_SHUTTERS]; // E9A
int8_t temp_comp; // E9E
uint8_t weight_change; // E9F
@ -610,10 +610,14 @@ struct {
uint8_t tcp_baudrate; // F41
uint8_t fallback_module; // F42
uint8_t free_f43[113]; // F43 - Decrement if adding new Setting variables just above and below
uint8_t free_f43[1]; // F43
uint16_t energy_power_delta[3]; // F44
uint8_t free_f4e[106]; // F4A - Decrement if adding new Setting variables just above and below
// Only 32 bit boundary variables below
SysBitfield5 flag5; // EB4
SysBitfield5 flag5; // FB4
uint16_t pulse_counter_debounce_low; // FB8
uint16_t pulse_counter_debounce_high; // FBA
uint32_t keeloq_master_msb; // FBC

View File

@ -935,7 +935,9 @@ void SettingsDefaultSet2(void)
flag3.dds2382_model |= ENERGY_DDS2382_MODE;
flag3.hardware_energy_total |= ENERGY_HARDWARE_TOTALS;
Settings.param[P_MAX_POWER_RETRY] = MAX_POWER_RETRY;
// Settings.energy_power_delta = 0;
// Settings.energy_power_delta[0] = 0;
// Settings.energy_power_delta[1] = 0;
// Settings.energy_power_delta[2] = 0;
Settings.energy_power_calibration = HLW_PREF_PULSE;
Settings.energy_voltage_calibration = HLW_UREF_PULSE;
Settings.energy_current_calibration = HLW_IREF_PULSE;
@ -1346,7 +1348,7 @@ void SettingsDelta(void)
Settings.ex_sbaudrate = 0;
*/
Settings.flag3.fast_power_cycle_disable = 0;
Settings.energy_power_delta = Settings.ex_energy_power_delta;
Settings.ex2_energy_power_delta = Settings.ex_energy_power_delta;
Settings.ex_energy_power_delta = 0;
}
if (Settings.version < 0x06060015) {
@ -1503,6 +1505,11 @@ void SettingsDelta(void)
if (Settings.version < 0x08030106) {
Settings.fallback_module = FALLBACK_MODULE;
}
if (Settings.version < 0x08040003) {
Settings.energy_power_delta[0] = Settings.ex2_energy_power_delta;
Settings.energy_power_delta[1] = 0;
Settings.energy_power_delta[2] = 0;
}
Settings.version = VERSION;
SettingsSave(1);

View File

@ -538,9 +538,9 @@ void CmndStatus(void)
#if defined(USE_ENERGY_SENSOR) && defined(USE_ENERGY_MARGIN_DETECTION)
if (energy_flg) {
if ((0 == payload) || (9 == payload)) {
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERDELTA "\":%d,\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\""
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERDELTA "\":[%d,%d,%d],\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\""
D_CMND_VOLTAGELOW "\":%d,\"" D_CMND_VOLTAGEHIGH "\":%d,\"" D_CMND_CURRENTLOW "\":%d,\"" D_CMND_CURRENTHIGH "\":%d}}"),
Settings.energy_power_delta, Settings.energy_min_power, Settings.energy_max_power,
Settings.energy_power_delta[0], Settings.energy_power_delta[1], Settings.energy_power_delta[2], Settings.energy_min_power, Settings.energy_max_power,
Settings.energy_min_voltage, Settings.energy_max_voltage, Settings.energy_min_current, Settings.energy_max_current);
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "9"));
}

View File

@ -20,7 +20,7 @@
#ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_
const uint32_t VERSION = 0x08040002;
const uint32_t VERSION = 0x08040003;
// Lowest compatible version
const uint32_t VERSION_COMPATIBLE = 0x07010006;

View File

@ -620,6 +620,38 @@ const char HTTP_HEAD_STYLE2[] PROGMEM =
".r{border-radius:0.3em;padding:2px;margin:6px 2px;}";
#endif //USE_UNISHOX_COMPRESSION
#ifdef USE_ZIGBEE
// Styles used for Zigbee Web UI
// Battery icon from https://css.gg/battery
//
#ifdef USE_UNISHOX_COMPRESSION
const size_t HTTP_HEAD_STYLE_ZIGBEE_SIZE = 363;
const char HTTP_HEAD_STYLE_ZIGBEE_COMPRESSED[] PROGMEM = "\x3A\x0E\xA3\xDA\x3B\x0D\x87\x5F\xB4\xDB\xBC\x3C\x79\x8E\xCF\x88\xFE\x75\x8E\xC3"
"\x61\xE0\x66\x7B\x6B\x73\x8F\x3F\xB0\xAE\xB4\xCD\x9E\x04\xDF\x0C\x0A\xCC\x8F\x3D"
"\xE0\xB7\x99\xD6\x38\x2C\x0C\xD0\xF0\x3F\xA2\x50\xA3\xCC\xE5\x32\x18\x6C\x3C\x0A"
"\x7A\x3C\x2A\x2B\x8F\x33\x92\x88\x61\xB0\xF0\x08\x39\x29\xE6\x72\x88\x61\xB1\x7B"
"\x02\xD1\x01\x0A\x69\xD7\xFB\x13\x45\xF8\xF3\x39\x64\x30\xD8\x78\x1B\x7F\x1E\xDE"
"\x3A\xC2\x66\x28\xF3\x3A\xCE\x59\x0C\x36\x1E\xE3\xA0\xEA\x3C\xCF\x3B\x31\x4F\xE7"
"\x51\xD0\x75\x1E\x67\x98\xE6\x63\x3E\xCF\x68\x79\xD4\xFA\x8F\x33\xD8\x7B\x01\x13"
"\x5E\x04\x1D\x5C\x16\xB8\x14\xB1\xDE\xC0\x85\xD3\x04\x3D\xD0\xE7\x10\xC3\x61\xE0"
"\x75\x86\x68\x3D\xFC\x17\xC2\x1E\x61\x8B\xFF\xDF\x51\x07\x81\x67\xCF\x15\x83\x0F"
"\x33\x90\x81\x0F\x5F\x04\x2D\x53\xFA\x3C\x2A\x2B\x8F\x33\xAC\xE6\x10\x22\x70\x54"
"\x08\xFC\x0C\x82\x0F\x0A\x67\x30\x81\x23\x81\x23\xDA\x08\x34\x4C\xEF\xE7\x74\xEB"
"\x3A\xC7\x04\x75\x1C\x98\x43\x0D\x87\x78\xF0\x13\x31\x47\x99\xC8\x43\x0D\x87\xB8";
// Raw: .bt{box-sizing:border-box;position:relative;display:inline-block;width:20px;height:12px;border:2px solid;border-radius:3px;margin-left:-3px}.bt::after,.bt::before{content:"";display:block;box-sizing:border-box;position:absolute;height:6px;background:currentColor;top:1px}.bt::before{right:-4px;border-radius:3px;width:4px}.bt::after{width:var(--bl,14px);left:1px}
// Successfully compressed from 363 to 240 bytes (-33.9%)
#define HTTP_HEAD_STYLE_ZIGBEE Decompress(HTTP_HEAD_STYLE_ZIGBEE_COMPRESSED,HTTP_HEAD_STYLE_ZIGBEE_SIZE).c_str()
#else // USE_UNISHOX_COMPRESSION
const char HTTP_HEAD_STYLE_ZIGBEE[] PROGMEM =
".bt{box-sizing:border-box;position:relative;display:inline-block;width:20px;height:12px;border:2px solid;border-radius:3px;margin-left:-3px}"
".bt::after,.bt::before{content:\"\";display:block;box-sizing:border-box;position:absolute;height:6px;background:currentColor;top:1px}"
".bt::before{right:-4px;border-radius:3px;width:4px}"
".bt::after{width:var(--bl,14px);left:1px}";
#endif // USE_UNISHOX_COMPRESSION
#endif // USE_ZIGBEE
const char HTTP_HEAD_STYLE3[] PROGMEM =
"</style>"
@ -1113,6 +1145,9 @@ void WSContentSendStyle_P(const char* formatP, ...)
WSContentSend_P(HTTP_HEAD_STYLE2, WebColor(COL_BUTTON), WebColor(COL_BUTTON_TEXT), WebColor(COL_BUTTON_HOVER),
WebColor(COL_BUTTON_RESET), WebColor(COL_BUTTON_RESET_HOVER), WebColor(COL_BUTTON_SAVE), WebColor(COL_BUTTON_SAVE_HOVER),
WebColor(COL_BUTTON));
#ifdef USE_ZIGBEE
WSContentSend_P(HTTP_HEAD_STYLE_ZIGBEE);
#endif // USE_ZIGBEE
if (formatP != nullptr) {
// This uses char strings. Be aware of sending %% if % is needed
va_list arg;

View File

@ -108,7 +108,7 @@ struct ENERGY {
bool power_on = true;
#ifdef USE_ENERGY_MARGIN_DETECTION
uint16_t power_history[3] = { 0 };
uint16_t power_history[3][3] = {{ 0 }, { 0 }, { 0 }};
uint8_t power_steady_counter = 8; // Allow for power on stabilization
bool min_power_flag = false;
bool max_power_flag = false;
@ -130,6 +130,31 @@ Ticker ticker_energy;
/********************************************************************************************/
char* EnergyFormatIndex(char* result, char* input, bool json, uint32_t index, bool single = false)
{
char layout[16];
GetTextIndexed(layout, sizeof(layout), (index -1) + (3 * json), kEnergyPhases);
switch (index) {
case 2:
snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ); // Dirty
break;
case 3:
snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ, input + FLOATSZ + FLOATSZ); // Even dirtier
break;
default:
snprintf_P(result, FLOATSZ *3, input);
}
return result;
}
char* EnergyFormat(char* result, char* input, bool json, bool single = false)
{
uint8_t index = (single) ? 1 : Energy.phase_count; // 1,2,3
return EnergyFormatIndex(result, input, json, index, single);
}
/********************************************************************************************/
bool EnergyTariff1Active() // Off-Peak hours
{
uint8_t dst = 0;
@ -303,38 +328,53 @@ void EnergyMarginCheck(void)
return;
}
uint16_t energy_power_u = (uint16_t)(Energy.active_power[0]);
bool jsonflg = false;
Response_P(PSTR("{\"" D_RSLT_MARGINS "\":{"));
if (Settings.energy_power_delta) {
int16_t power_diff = energy_power_u - Energy.power_history[0];
uint16_t delta = abs(power_diff);
if (delta > 0) {
if (Settings.energy_power_delta < 101) { // 1..100 = Percentage
uint16_t min_power = (Energy.power_history[0] > energy_power_u) ? energy_power_u : Energy.power_history[0];
if (0 == min_power) { min_power++; } // Fix divide by 0 exception (#6741)
delta = (delta * 100) / min_power;
if (delta > Settings.energy_power_delta) {
jsonflg = true;
}
} else { // 101..32000 = Absolute
if (delta > (Settings.energy_power_delta -100)) {
jsonflg = true;
int16_t power_diff[3] = { 0 };
for (uint32_t phase = 0; phase < Energy.phase_count; phase++) {
uint16_t active_power = (uint16_t)(Energy.active_power[phase]);
if (Settings.energy_power_delta[phase]) {
power_diff[phase] = active_power - Energy.power_history[phase][0];
uint16_t delta = abs(power_diff[phase]);
bool threshold_met = false;
if (delta > 0) {
if (Settings.energy_power_delta[phase] < 101) { // 1..100 = Percentage
uint16_t min_power = (Energy.power_history[phase][0] > active_power) ? active_power : Energy.power_history[phase][0];
if (0 == min_power) { min_power++; } // Fix divide by 0 exception (#6741)
delta = (delta * 100) / min_power;
if (delta > Settings.energy_power_delta[phase]) {
threshold_met = true;
}
} else { // 101..32000 = Absolute
if (delta > (Settings.energy_power_delta[phase] -100)) {
threshold_met = true;
}
}
}
if (jsonflg) {
Energy.power_history[1] = Energy.active_power[0]; // We only want one report so reset history
Energy.power_history[2] = Energy.active_power[0];
ResponseAppend_P(PSTR("\"" D_CMND_POWERDELTA "\":%d"), power_diff);
if (threshold_met) {
Energy.power_history[phase][1] = active_power; // We only want one report so reset history
Energy.power_history[phase][2] = active_power;
jsonflg = true;
} else {
power_diff[phase] = 0;
}
}
Energy.power_history[phase][0] = Energy.power_history[phase][1]; // Shift in history every second allowing power changes to settle for up to three seconds
Energy.power_history[phase][1] = Energy.power_history[phase][2];
Energy.power_history[phase][2] = active_power;
}
Energy.power_history[0] = Energy.power_history[1]; // Shift in history every second allowing power changes to settle for up to three seconds
Energy.power_history[1] = Energy.power_history[2];
Energy.power_history[2] = energy_power_u;
if (jsonflg) {
char power_diff_chr[Energy.phase_count][FLOATSZ];
for (uint32_t phase = 0; phase < Energy.phase_count; phase++) {
dtostrfd(power_diff[phase], 0, power_diff_chr[phase]);
}
char value_chr[FLOATSZ *3];
ResponseAppend_P(PSTR("\"" D_CMND_POWERDELTA "\":%s"), EnergyFormat(value_chr, power_diff_chr[0], 1));
}
uint16_t energy_power_u = (uint16_t)(Energy.active_power[0]);
if (Energy.power_on && (Settings.energy_min_power || Settings.energy_max_power || Settings.energy_min_voltage || Settings.energy_max_voltage || Settings.energy_min_current || Settings.energy_max_current)) {
uint16_t energy_voltage_u = (uint16_t)(Energy.voltage[0]);
@ -725,10 +765,12 @@ void CmndModuleAddress(void)
#ifdef USE_ENERGY_MARGIN_DETECTION
void CmndPowerDelta(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32000)) {
Settings.energy_power_delta = XdrvMailbox.payload;
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32000)) {
Settings.energy_power_delta[XdrvMailbox.index -1] = XdrvMailbox.payload;
}
ResponseCmndIdxNumber(Settings.energy_power_delta[XdrvMailbox.index -1]);
}
ResponseCmndNumber(Settings.energy_power_delta);
}
void CmndPowerLow(void)
@ -881,29 +923,6 @@ const char HTTP_ENERGY_SNS3[] PROGMEM =
"{s}" D_EXPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}";
#endif // USE_WEBSERVER
char* EnergyFormatIndex(char* result, char* input, bool json, uint32_t index, bool single = false)
{
char layout[16];
GetTextIndexed(layout, sizeof(layout), (index -1) + (3 * json), kEnergyPhases);
switch (index) {
case 2:
snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ); // Dirty
break;
case 3:
snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ, input + FLOATSZ + FLOATSZ); // Even dirtier
break;
default:
snprintf_P(result, FLOATSZ *3, input);
}
return result;
}
char* EnergyFormat(char* result, char* input, bool json, bool single = false)
{
uint8_t index = (single) ? 1 : Energy.phase_count; // 1,2,3
return EnergyFormatIndex(result, input, json, index, single);
}
void EnergyShow(bool json)
{
for (uint32_t i = 0; i < Energy.phase_count; i++) {

View File

@ -146,6 +146,14 @@ public:
inline bool getReachable(void) const { return bitRead(power, 7); }
inline void setPower(bool power_on) { bitWrite(power, 0, power_on); bitWrite(power, 1, false); }
inline bool getPower(void) const { return bitRead(power, 0); }
// If light, returns the number of channels, or 0xFF if unknown
uint8_t getLightChannels(void) const {
if ((zb_profile & 0xF0) == 0x00) {
return zb_profile & 0x07;
}
return 0xFF;
}
};
/*********************************************************************************************\
@ -682,21 +690,21 @@ void Z_Devices::updateZbProfile(uint16_t shortaddr) {
{
uint32_t channels = zb_profile & 0x07;
// depending on the bulb type, the default parameters from unknown to credible defaults
if (!device.validPower()) { device.setPower(false); }
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
}
// if (!device.validPower()) { device.setPower(false); }
// 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;
}

View File

@ -136,9 +136,10 @@ enum Z_ConvOperators {
Z_ManufKeep, // copy and record Manufacturer attribute
Z_ModelKeep, // copy and record ModelId attribute
Z_AqaraSensor, // decode prioprietary Aqara Sensor message
Z_AqaraSensor2, // decode prioprietary Aqara Sensor message V2
Z_AqaraVibration, // decode Aqara vibration modes
Z_AqaraCube, // decode Aqara cube
Z_AqaraButton, // decode Aqara button
Z_AqaraButton, // decode Aqara button
Z_BatteryPercentage, // memorize Battery Percentage in RAM
};
@ -159,7 +160,8 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 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
{ Zmap8, Cx0000, 0xFF01, Z_(), 0, Z_AqaraSensor }, // Occupancy (map8)
{ Zmap8, Cx0000, 0xFF01, Z_(), 0, Z_AqaraSensor },
{ Zmap8, Cx0000, 0xFF02, Z_(), 0, Z_AqaraSensor2 },
// Power Configuration cluster
{ Zuint16, Cx0001, 0x0000, Z_(MainsVoltage), 1, Z_Nop },
@ -909,6 +911,24 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
// i += buf.get8(i) + 1;
break;
case Zstruct:
{
uint16_t struct_size = buf.get16(i);
len = 2;
if (0xFFFF != struct_size) {
if (struct_size > 16) { struct_size = 16; }
// parse inner attributes - supports only fixed length for now
for (uint32_t j = 0; j < struct_size; j++) {
uint8_t attr_type = buf.get8(i+len);
len += Z_getDatatypeLen(attr_type) + 1;
}
char hex[2*len+1];
ToHex_P(buf.buf(i), len, hex, sizeof(hex));
json[attrid_str] = hex;
}
}
break;
case Zdata8: // data8
case Zmap8: // map8
{
@ -1018,6 +1038,16 @@ void ZCLFrame::parseReportAttributes(JsonObject& json, uint8_t offset) {
}
i += parseSingleAttribute(json, key, _payload, i);
}
// Issue Philips outdoor motion sensor SML002, see https://github.com/Koenkk/zigbee2mqtt/issues/897
// The sensor expects the coordinator to send a Default Response to acknowledge the attribute reporting
if (0 == _frame_control.b.disable_def_resp) {
// the device expects a default response
SBuffer buf(2);
buf.add8(_cmd_id);
buf.add8(0x00); // Status = OK
ZigbeeZCLSend_Raw(_srcaddr, 0x0000, 0x0000 /*cluster*/, _srcendpoint, ZCL_DEFAULT_RESPONSE, false /* not cluster specific */, _manuf_code, buf.getBuffer(), buf.len(), false /* noresponse */, zigbee_devices.getNextSeqNumber(_srcaddr));
}
}
// ZCL_READ_ATTRIBUTES
@ -1378,7 +1408,6 @@ int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObj
uint32_t len = buf2.len();
char tmp[] = "tmp"; // for obscure reasons, it must be converted from const char* to char*, otherwise ArduinoJson gets confused
JsonVariant sub_value;
const char * modelId_c = zigbee_devices.getModelId(shortaddr); // null if unknown
String modelId((char*) modelId_c);
@ -1435,6 +1464,35 @@ int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObj
}
return 1; // remove original key
}
int32_t Z_AqaraSensorFunc2(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
String hex = value;
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint32_t i = 0;
uint32_t len = buf2.len();
// Look for battery value which is the first attribute of type 0x21
uint16_t struct_size = buf2.get16(0);
size_t struct_len = 2;
if (0xFFFF != struct_size) {
if (struct_size > 16) { struct_size = 16; }
for (uint32_t j = 0; (j < struct_size) && (struct_len < len); j++) {
uint8_t attr_type = buf2.get8(struct_len);
if (0x21 == attr_type) {
uint16_t val = buf2.get16(struct_len+1);
float batteryvoltage = val / 1000.0f;
json[F("BatteryVoltage")] = batteryvoltage;
uint8_t batterypercentage = toPercentageCR2032(val);
json[F("BatteryPercentage")] = batterypercentage;
zigbee_devices.setBatteryPercent(shortaddr, batterypercentage);
break;
}
struct_len += Z_getDatatypeLen(attr_type) + 1;
}
}
return 0; // remove original key
}
// ======================================================================
// apply the transformation from the converter
@ -1468,6 +1526,9 @@ int32_t Z_ApplyConverter(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje
case Z_AqaraSensor:
func = &Z_AqaraSensorFunc;
break;
case Z_AqaraSensor2:
func = &Z_AqaraSensorFunc2;
break;
case Z_AqaraVibration:
func = &Z_AqaraVibrationFunc;
break;

View File

@ -681,7 +681,7 @@ ZBM(ZBS_SET_CONCENTRATOR, EZSP_setConcentrator, 0x00 /*high*/, 0x00 /*false*/, 0
ZBM(ZBR_SET_CONCENTRATOR, EZSP_setConcentrator, 0x00 /*high*/, 0x00 /*ok*/) // 100000
// setInitialSecurityState
#define EZ_SECURITY_MODE EMBER_TRUST_CENTER_GLOBAL_LINK_KEY | EMBER_PRECONFIGURED_NETWORK_KEY_MODE | EMBER_HAVE_NETWORK_KEY | EMBER_HAVE_PRECONFIGURED_KEY
#define EZ_SECURITY_MODE EMBER_TRUST_CENTER_GLOBAL_LINK_KEY | EMBER_PRECONFIGURED_NETWORK_KEY_MODE | EMBER_HAVE_NETWORK_KEY | EMBER_HAVE_PRECONFIGURED_KEY | EMBER_NO_FRAME_COUNTER_RESET
ZBR(ZBS_SET_SECURITY, EZSP_setInitialSecurityState, 0x00 /*high*/,
Z_B0(EZ_SECURITY_MODE), Z_B1(EZ_SECURITY_MODE),
// preConfiguredKey

View File

@ -1344,10 +1344,11 @@ void ZigbeeShow(bool json)
if (zigbee_num > 255) { zigbee_num = 255; }
// Calculate fixed column width for best visual result (Theos opinion)
const uint8_t px_batt = (strlen(D_BATT) + 5 + 1) * 10; // Batt 100% = 90px + 10px column separator
const uint8_t px_batt = 30; // Battery icon is 20px, add 10px as separator
const uint8_t px_lqi = (strlen(D_LQI) + 4) * 10; // LQI 254 = 70px
WSContentSend_P(PSTR("</table>{t}")); // Terminate current two column table and open new table
WSContentSend_P(PSTR("<style>.bx{height:14px;width:14px;display:inline-block;border:1px solid currentColor;background-color:var(--cl,#fff)}</style>"));
// sort elements by name, then by id
uint8_t sorted_idx[zigbee_num];
@ -1373,10 +1374,10 @@ void ZigbeeShow(bool json)
snprintf_P(slqi, sizeof(slqi), PSTR("%d"), device.lqi);
}
char sbatt[20];
char sbatt[64];
snprintf_P(sbatt, sizeof(sbatt), PSTR("&nbsp;"));
if (device.validBatteryPercent()) {
snprintf_P(sbatt, sizeof(sbatt), PSTR(D_BATT " %d%%"), device.batterypercent);
snprintf_P(sbatt, sizeof(sbatt), PSTR("<i class=\"bt\" title=\"%d%%\" style=\"--bl:%dpx\"></i>"), device.batterypercent, changeUIntScale(device.batterypercent, 0, 100, 0, 14));
}
if (!i) { // First row needs style info
@ -1393,20 +1394,45 @@ void ZigbeeShow(bool json)
bool pressure_ok = device.validPressure();
if (temperature_ok || humidity_ok || pressure_ok) {
WSContentSend_P(PSTR("<tr><td colspan=\"3\">| "));
WSContentSend_P(PSTR("<tr><td colspan=\"3\">&#9478;"));
if (temperature_ok) {
char buf[12];
dtostrf(device.temperature / 10.0f, 3, 1, buf);
WSContentSend_PD(PSTR(" &nbsp;&#x2600;&#xFE0F;%s°C"), buf);
WSContentSend_PD(PSTR(" &#x2600;&#xFE0F;%s°C"), buf);
}
if (humidity_ok) {
WSContentSend_P(PSTR(" &nbsp;&#x1F4A7;%d%%"), device.humidity);
WSContentSend_P(PSTR(" &#x1F4A7;%d%%"), device.humidity);
}
if (pressure_ok) {
WSContentSend_P(PSTR(" &nbsp;&#x26C5; %d hPa"), device.pressure);
WSContentSend_P(PSTR(" &#x26C5; %d hPa"), device.pressure);
}
WSContentSend_P(PSTR("{e}"));
}
// Light and switches
bool power_ok = device.validPower();
if (power_ok) {
uint8_t channels = device.getLightChannels();
if (0xFF == channels) { channels = 5; } // if number of channel is unknown, display all known attributes
WSContentSend_P(PSTR("<tr><td colspan=\"3\">&#9478; %s"), device.getPower() ? PSTR(D_ON) : PSTR(D_OFF));
if (device.validDimmer() && (channels >= 1)) {
WSContentSend_P(PSTR(" &#128261;%d%%"), changeUIntScale(device.dimmer,0,254,0,100));
}
if (device.validCT() && ((channels == 2) || (channels == 5))) {
uint32_t ct_k = (((1000000 / device.ct) + 25) / 50) * 50;
WSContentSend_P(PSTR(" <span title=\"CT %d\"><small>&#9898; </small>%dK</span>"), device.ct, ct_k);
}
if (device.validHue() && device.validSat() && (channels >= 3)) {
uint8_t r,g,b;
uint8_t sat = changeUIntScale(device.sat, 0, 254, 0, 255); // scale to 0..255
LightStateClass::HsToRgb(device.hue, sat, &r, &g, &b);
WSContentSend_P(PSTR(" <i class=\"bx\" style=\"--cl:#%02X%02X%02X\"></i>#%02X%02X%02X"), r,g,b,r,g,b);
} else if (device.validX() && device.validY() && (channels >= 3)) {
uint8_t r,g,b;
LightStateClass::XyToRgb(device.x / 65535.0f, device.y / 65535.0f, &r, &g, &b);
WSContentSend_P(PSTR(" <i class=\"bx\" style=\"--cl:#%02X%02X%02X\"></i> #%02X%02X%02X"), r,g,b,r,g,b);
}
}
}
WSContentSend_P(PSTR("</table>{t}")); // Terminate current multi column table and open new table

View File

@ -205,10 +205,10 @@ void HlwEverySecond(void)
unsigned long hlw_len;
if (Hlw.energy_period_counter) {
hlw_len = 10000 / Hlw.energy_period_counter;
hlw_len = 10000 * 1000 / Hlw.energy_period_counter;
Hlw.energy_period_counter = 0;
if (hlw_len) {
Energy.kWhtoday_delta += ((Hlw.power_ratio * Settings.energy_power_calibration) / hlw_len) / 36;
Energy.kWhtoday_delta += ((Hlw.power_ratio * Settings.energy_power_calibration) * 1000 / hlw_len) / 36;
EnergyUpdateToday();
}
}