mirror of https://github.com/arendst/Tasmota.git
3471 lines
109 KiB
C++
3471 lines
109 KiB
C++
/*
|
|
xsns_62_esp32_mi_ble.ino - MI-BLE-sensors via ESP32 support for Tasmota
|
|
enabled by ESP32 && USE_BLE_ESP32
|
|
if (ESP32 && !USE_BLE_ESP32) then xsns_62_esp32_mi.ino is used - the older driver
|
|
|
|
|
|
Copyright (C) 2020 Christian Baars and Theo Arends
|
|
Also Simon Hailes and Robert Klauco
|
|
|
|
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/>.
|
|
|
|
|
|
--------------------------------------------------------------------------------------------
|
|
Version yyyymmdd Action Description
|
|
--------------------------------------------------------------------------------------------
|
|
0.9.2.1 20210217 changed - make features alos depend on received data - i.e. 'unknown' devices will show what they send.
|
|
Add MI32Option6 1 to switch to tele/tasmota_ble/<somename> style MQTT independent of HASS discovery.
|
|
-------
|
|
0.9.2.0 20210127 changed - Officially includes as the mi driver when using USE_BLE_ESP32.
|
|
-------
|
|
0.9.1.9 20201226 changed - All change now.
|
|
-------
|
|
0.9.1.7 20201116 changed - small bugfixes, add BLOCK and OPTION command, send BLE scan via MQTT
|
|
-------
|
|
0.9.1.6 20201022 changed - Beacon support, RSSI at TELEPERIOD, refactoring
|
|
-------
|
|
0.9.1.5 20201021 changed - HASS related ('null', hold back discovery), number of found sensors for RULES
|
|
-------
|
|
0.9.1.4 20201020 changed - use BearSSL for decryption, revert to old TELEPERIOD-cycle as default
|
|
-------
|
|
0.9.1.3 20200926 changed - Improve HA discovery, make key+MAC case insensitive
|
|
-------
|
|
0.9.1.3 20200916 changed - add ATC (custom FW for LYWSD03MMC), API adaption for NimBLE-Arduino 1.0.2
|
|
-------
|
|
0.9.1.2 20200802 changed - add MHO-C303
|
|
-------
|
|
0.9.1.1 20200715 changed - add MHO-C401, refactoring
|
|
-------
|
|
0.9.1.0 20200712 changed - add lights and yeerc, add pure passive mode with decryption,
|
|
lots of refactoring
|
|
-------
|
|
0.9.0.1 20200706 changed - adapt to new NimBLE-API, tweak scan process
|
|
-------
|
|
0.9.0.0 20200413 started - initial development by Christian Baars
|
|
forked - from arendst/tasmota - https://github.com/arendst/Tasmota
|
|
|
|
*/
|
|
//#define VSCODE_DEV
|
|
|
|
/*
|
|
#ifdef VSCODE_DEV
|
|
#define ESP32
|
|
#define USE_BLE_ESP32
|
|
#define USE_MI_ESP32
|
|
#endif
|
|
*/
|
|
//#undef USE_MI_ESP32
|
|
|
|
// for testing of BLE_ESP32, we remove xsns_62_MI_ESP32.ino completely, and instead add this modified xsns_52_ibeacon_BLE_ESP32.ino
|
|
#ifdef USE_BLE_ESP32
|
|
|
|
#ifdef ESP32 // ESP32 family only. Use define USE_HM10 for ESP8266 support
|
|
#if defined CONFIG_IDF_TARGET_ESP32 || defined CONFIG_IDF_TARGET_ESP32C3
|
|
|
|
#ifdef USE_MI_ESP32
|
|
|
|
#define XSNS_62 62
|
|
#define USE_MI_DECRYPTION
|
|
|
|
#include <vector>
|
|
#ifdef USE_MI_DECRYPTION
|
|
#include <t_bearssl.h>
|
|
#endif //USE_MI_DECRYPTION
|
|
|
|
void MI32scanEndedCB(NimBLEScanResults results);
|
|
void MI32notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify);
|
|
|
|
struct {
|
|
uint16_t perPage = 4;
|
|
uint8_t mqttCurrentSlot = 0;
|
|
uint8_t mqttCurrentSingleSlot = 0;
|
|
uint32_t period; // set manually in addition to TELE-period, is set to TELE-period after start
|
|
int secondsCounter = 0; // counts up in MI32EverySecond to period
|
|
int secondsCounter2 = 0; // counts up in MI32EverySecond to period
|
|
union {
|
|
struct {
|
|
uint32_t init:1;
|
|
uint32_t shallClearResults:1; // BLE scan results
|
|
uint32_t shallShowStatusInfo:1; // react to amount of found sensors via RULES
|
|
uint32_t firstAutodiscoveryDone:1;
|
|
uint32_t shallTriggerTele:1;
|
|
uint32_t triggeredTele:1;
|
|
};
|
|
uint32_t all = 0;
|
|
} mode;
|
|
|
|
struct {
|
|
// the slot currently having it's battery read
|
|
// set to 0 to start a battery read cycle
|
|
uint8_t slot = 255;
|
|
uint8_t active = 0;
|
|
} batteryreader;
|
|
|
|
struct {
|
|
// the slot currently having it's battery read
|
|
// set to 0 to start a battery read cycle
|
|
uint8_t slot = 255;
|
|
uint8_t active = 0;
|
|
} sensorreader;
|
|
|
|
struct {
|
|
uint32_t allwaysAggregate:1; // always show all known values of one sensor in brdigemode
|
|
uint32_t noSummary:1; // no sensor values at TELE-period
|
|
uint32_t directBridgeMode:1; // send every received BLE-packet as a MQTT-message in real-time
|
|
uint32_t holdBackFirstAutodiscovery:1; // allows to trigger it later
|
|
uint32_t showRSSI:1;
|
|
uint32_t ignoreBogusBattery:1;
|
|
uint32_t minimalSummary:1; // DEPRECATED!!
|
|
uint32_t onlyAliased:1; // only include sensors that are aliased
|
|
uint32_t MQTTType:1;
|
|
} option;
|
|
} MI32;
|
|
|
|
#pragma pack(1) // byte-aligned structures to read the sensor data
|
|
|
|
struct {
|
|
int16_t temp;
|
|
uint8_t hum;
|
|
uint16_t volt; // LYWSD03 only
|
|
} LYWSD0x_HT;
|
|
struct {
|
|
uint8_t spare;
|
|
int16_t temp;
|
|
uint16_t hum;
|
|
} CGD1_HT;
|
|
struct {
|
|
int16_t temp;
|
|
uint8_t spare;
|
|
uint32_t lux;
|
|
uint8_t moist;
|
|
uint16_t fert;
|
|
} Flora_TLMF; // temperature, lux, moisture, fertility
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// from https://github.com/Magalex2x14/LYWSD03MMC-info
|
|
struct mi_beacon_frame_data_t{
|
|
// data from byte 0 - e.g. 30
|
|
uint8_t meshflag; //Byte 0: x.......
|
|
uint8_t dataflag; //Byte 0: .x......
|
|
uint8_t compatibilityflag; //Byte 0: ..x..... - indicates compatibility data present
|
|
uint8_t MACFlag; //Byte 0: ...x....
|
|
uint8_t isencrypted; //Byte 0: ....x...
|
|
uint8_t reserved; //Byte 0: .....xxx
|
|
|
|
// data from byte 1 - e.g. 58
|
|
uint8_t version; //Byte 0: xxxx....
|
|
uint8_t authMode; //Byte 0: ....xx.. // e.g. 2
|
|
uint8_t bindingvalidreq; //Byte 0: ......x.
|
|
uint8_t registeredflag; //Byte 0: .......x
|
|
};
|
|
|
|
struct mi_beacon_compatibility_data_t{ // e.g. 28/08
|
|
uint8_t reserved; //Byte 0: xx......
|
|
uint8_t IOcap; //Byte 0: ..x.....
|
|
uint8_t bondability; //Byte 0: ...xx...
|
|
uint8_t unused; //Byte 0: .....xxx
|
|
uint16_t IOCapability; // bytes 1-2, e.g. 01 00 -> 0001
|
|
};
|
|
struct mi_beacon_mac_data_t{ // e.g. 28/08
|
|
uint8_t mac[6];
|
|
};
|
|
struct mi_beacon_payload_data_t{ //
|
|
uint16_t type;
|
|
uint8_t size;
|
|
uint8_t data[16];
|
|
};
|
|
|
|
struct mi_beacon_data_t { //
|
|
mi_beacon_frame_data_t framedata;
|
|
uint16_t devicetype;
|
|
uint8_t framecnt;
|
|
mi_beacon_mac_data_t macdata;
|
|
mi_beacon_compatibility_data_t compatibility;
|
|
uint8_t payloadpresent;
|
|
uint8_t needkey; // we need a (new) encryption key?
|
|
|
|
mi_beacon_payload_data_t payload;
|
|
};
|
|
|
|
struct mi_beacon_data_payload_data_t { //
|
|
union {
|
|
struct{ //01
|
|
uint16_t num;
|
|
uint8_t longPress;
|
|
} Btn;
|
|
|
|
int16_t temp; //04
|
|
uint16_t hum; //06
|
|
uint32_t lux; //07
|
|
uint8_t moist; //08
|
|
uint16_t fert; //09
|
|
uint8_t bat; //0a
|
|
struct{ //0d
|
|
int16_t temp;
|
|
uint16_t hum;
|
|
} HT;
|
|
uint32_t NMT; //17
|
|
};
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
union mi_bindKey_t{
|
|
struct{
|
|
uint8_t key[16];
|
|
uint8_t MAC[6];
|
|
};
|
|
uint8_t buf[22];
|
|
};
|
|
|
|
struct ATCPacket_t{
|
|
//uint8_t size; // = 16?
|
|
//uint8_t uid; // = 0x16, 16-bit UUID
|
|
//uint16_t UUID; // = 0x181A, GATT Service 0x181A Environmental Sensing
|
|
uint8_t MAC[6]; // [0] - hi, .. [6] - lo digits
|
|
uint16_t temp; //sadly this is in wrong endianess
|
|
uint8_t hum;
|
|
uint8_t batPer;
|
|
uint16_t batMV;
|
|
uint8_t frameCnt;
|
|
};
|
|
|
|
// GATT Service 0x181A Environmental Sensing
|
|
// All data little-endian
|
|
struct PVVXPacket_t {
|
|
//uint8_t size; // = 19
|
|
//uint8_t uid; // = 0x16, 16-bit UUID
|
|
//uint16_t UUID; // = 0x181A, GATT Service 0x181A Environmental Sensing
|
|
uint8_t MAC[6]; // [0] - lo, .. [6] - hi digits
|
|
int16_t temperature; // x 0.1 degree
|
|
uint16_t humidity; // x 0.01 %
|
|
uint16_t battery_mv; // mV
|
|
uint8_t battery_level; // 0..100 %
|
|
uint8_t counter; // measurement count
|
|
uint8_t flags;
|
|
};
|
|
|
|
struct MiScaleV1Packet_t {
|
|
//uint8_t size; // = 14
|
|
//uint8_t uid; // = 0x16, 16-bit UUID
|
|
//uint16_t UUID; // = 0x181D
|
|
uint8_t status; // bit 0 lbs, 4 jin, 5, stabilized, 7, weight removed
|
|
uint16_t weight;
|
|
uint16_t year;
|
|
uint8_t month;
|
|
uint8_t day;
|
|
uint8_t hour;
|
|
uint8_t minute;
|
|
uint8_t second;
|
|
};
|
|
|
|
struct MiScaleV2Packet_t {
|
|
//uint8_t size; // = 17
|
|
//uint8_t uid; // = 0x16, 16-bit UUID
|
|
//uint16_t UUID; // = 0x181B
|
|
uint8_t weight_unit;
|
|
uint8_t status; // bit 14 impedance stabilized
|
|
uint16_t year;
|
|
uint8_t month;
|
|
uint8_t day;
|
|
uint8_t hour;
|
|
uint8_t minute;
|
|
uint8_t second;
|
|
uint16_t impedance;
|
|
uint16_t weight;
|
|
};
|
|
|
|
#pragma pack(0)
|
|
|
|
struct mi_sensor_t{
|
|
uint8_t type; //MI_Flora = 1; MI_MI-HT_V1=2; MI_LYWSD02=3; MI_LYWSD03=4; MI_CGG1=5; MI_CGD1=6
|
|
uint8_t needkey; // tells http to display needkey message with link
|
|
uint8_t lastCnt; //device generated counter of the packet
|
|
uint8_t nextDiscoveryData; // used to lkimit discovery to one MQTT per sec
|
|
|
|
uint8_t shallSendMQTT;
|
|
uint8_t MAC[6];
|
|
union {
|
|
struct {
|
|
uint32_t temp:1;
|
|
uint32_t hum:1;
|
|
uint32_t tempHum:1; //every hum sensor has temp too, easier to use Tasmota dew point functions
|
|
uint32_t lux:1;
|
|
uint32_t moist:1;
|
|
uint32_t fert:1;
|
|
uint32_t bat:1;
|
|
uint32_t NMT:1;
|
|
uint32_t PIR:1;
|
|
uint32_t Btn:1;
|
|
uint32_t events:1;
|
|
uint32_t pairing:1;
|
|
uint32_t light:1; // binary light sensor
|
|
uint32_t scale:1;
|
|
uint32_t impedance:1;
|
|
};
|
|
uint32_t raw;
|
|
} feature;
|
|
union {
|
|
struct {
|
|
uint32_t temp:1;
|
|
uint32_t hum:1;
|
|
uint32_t tempHum:1; //can be combined from the sensor
|
|
uint32_t lux:1;
|
|
uint32_t moist:1;
|
|
uint32_t fert:1;
|
|
uint32_t bat:1;
|
|
uint32_t NMT:1;
|
|
uint32_t motion:1;
|
|
uint32_t noMotion:1;
|
|
uint32_t Btn:1;
|
|
uint32_t PairBtn:1;
|
|
uint32_t light:1; // binary light sensor
|
|
uint32_t scale:1;
|
|
};
|
|
uint32_t raw;
|
|
} eventType;
|
|
|
|
int RSSI;
|
|
uint8_t pairing;
|
|
int8_t light; // binary light sensor - initialise to -1
|
|
int16_t Btn; // moved so we can initialise to -1
|
|
|
|
uint32_t lastTime;
|
|
uint32_t lux;
|
|
float temp; //Flora, MJ_HT_V1, LYWSD0x, CGx
|
|
union {
|
|
struct {
|
|
uint8_t moisture;
|
|
uint16_t fertility;
|
|
char firmware[6]; // actually only for FLORA but hopefully we can add for more devices
|
|
}; // Flora
|
|
struct {
|
|
float hum;
|
|
}; // MJ_HT_V1, LYWSD0x
|
|
struct {
|
|
uint16_t events; //"alarms" since boot
|
|
uint32_t NMT; // no motion time in seconds for the MJYD2S
|
|
};
|
|
};
|
|
union {
|
|
uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1)
|
|
};
|
|
union {
|
|
struct {
|
|
uint8_t weight_stabilized;
|
|
uint8_t weight_removed;
|
|
char weight_unit[4]; // kg, lbs, jin or empty when unknown
|
|
float weight;
|
|
uint8_t impedance_stabilized;
|
|
uint16_t impedance;
|
|
};
|
|
};
|
|
};
|
|
|
|
struct MAC_t {
|
|
uint8_t buf[7];
|
|
};
|
|
|
|
std::vector<mi_sensor_t> MIBLEsensors;
|
|
std::vector<mi_bindKey_t> MIBLEbindKeys;
|
|
std::vector<MAC_t> MIBLEBlockList;
|
|
|
|
SemaphoreHandle_t slotmutex = (SemaphoreHandle_t) nullptr;
|
|
|
|
/*********************************************************************************************\
|
|
* constants
|
|
\*********************************************************************************************/
|
|
|
|
#define D_CMND_MI32 "MI32"
|
|
|
|
const char kMI32_Commands[] PROGMEM = D_CMND_MI32 "|"
|
|
#ifdef USE_MI_DECRYPTION
|
|
"Key|"
|
|
"Keys|"
|
|
#endif // USE_MI_DECRYPTION
|
|
"Period|Time|Page|Battery|Unit|Block|Option";
|
|
|
|
void (*const MI32_Commands[])(void) PROGMEM = {
|
|
#ifdef USE_MI_DECRYPTION
|
|
&CmndMi32Key,
|
|
&CmndMi32Keys,
|
|
#endif // USE_MI_DECRYPTION
|
|
&CmndMi32Period, &CmndMi32Time, &CmndMi32Page, &CmndMi32Battery, &CmndMi32Unit, &CmndMi32Block, &CmndMi32Option };
|
|
|
|
|
|
#define MI_UNKOWN 1
|
|
#define MI_FLORA 2
|
|
#define MI_MJ_HT_V1 3
|
|
#define MI_LYWSD02 4
|
|
#define MI_LYWSD03MMC 5
|
|
#define MI_CGG1 6
|
|
#define MI_CGD1 7
|
|
#define MI_NLIGHT 8
|
|
#define MI_MJYD2S 9
|
|
#define MI_YEERC 10
|
|
#define MI_MHOC401 11
|
|
#define MI_MHOC303 12
|
|
#define MI_ATC 13
|
|
#define MI_DOOR 14
|
|
#define MI_SCALE_V1 15
|
|
#define MI_SCALE_V2 16
|
|
|
|
#define MI_MI32_TYPES 16 //count this manually
|
|
|
|
const uint16_t kMI32DeviceID[MI_MI32_TYPES]={
|
|
0x0000, // Unkown
|
|
0x0098, // Flora
|
|
0x01aa, // MJ_HT_V1
|
|
0x045b, // LYWSD02
|
|
0x055b, // LYWSD03
|
|
0x0347, // CGG1
|
|
0x0576, // CGD1
|
|
0x03dd, // NLIGHT
|
|
0x07f6, // MJYD2S
|
|
0x0153, // yee-rc
|
|
0x0387, // MHO-C401
|
|
0x06d3, // MHO-C303
|
|
0x0a1c, // ATC -> this is a fake ID
|
|
0x098b, // door/window sensor
|
|
0x181d, // Mi Scale V1
|
|
0x181b // Mi Scale V2
|
|
};
|
|
|
|
const char kMI32DeviceType0[] PROGMEM = "Unknown";
|
|
const char kMI32DeviceType1[] PROGMEM = "Flora";
|
|
const char kMI32DeviceType2[] PROGMEM = "MJ_HT_V1";
|
|
const char kMI32DeviceType3[] PROGMEM = "LYWSD02";
|
|
const char kMI32DeviceType4[] PROGMEM = "LYWSD03";
|
|
const char kMI32DeviceType5[] PROGMEM = "CGG1";
|
|
const char kMI32DeviceType6[] PROGMEM = "CGD1";
|
|
const char kMI32DeviceType7[] PROGMEM = "NLIGHT";
|
|
const char kMI32DeviceType8[] PROGMEM = "MJYD2S";
|
|
const char kMI32DeviceType9[] PROGMEM = "YEERC";
|
|
const char kMI32DeviceType10[] PROGMEM ="MHOC401";
|
|
const char kMI32DeviceType11[] PROGMEM ="MHOC303";
|
|
const char kMI32DeviceType12[] PROGMEM ="ATC";
|
|
const char kMI32DeviceType13[] PROGMEM ="DOOR";
|
|
const char kMI32DeviceType14[] PROGMEM ="MISCALEV1";
|
|
const char kMI32DeviceType15[] PROGMEM ="MISCALEV2";
|
|
const char * kMI32DeviceType[] PROGMEM = {kMI32DeviceType0,kMI32DeviceType1,kMI32DeviceType2,kMI32DeviceType3,kMI32DeviceType4,kMI32DeviceType5,kMI32DeviceType6,kMI32DeviceType7,kMI32DeviceType8,kMI32DeviceType9,kMI32DeviceType10,kMI32DeviceType11,kMI32DeviceType12,kMI32DeviceType13,kMI32DeviceType14,kMI32DeviceType15};
|
|
|
|
typedef int BATREAD_FUNCTION(int slot);
|
|
typedef int UNITWRITE_FUNCTION(int slot, int unit);
|
|
typedef int TIMEWRITE_FUNCTION(int slot);
|
|
|
|
int genericOpCompleteFn(BLE_ESP32::generic_sensor_t *pStruct);
|
|
int genericBatReadFn(int slot);
|
|
int genericUnitWriteFn(int slot, int unit);
|
|
int genericTimeWriteFn(int slot);
|
|
int MI32scanCompleteCallback(NimBLEScanResults results);
|
|
|
|
const char LYWSD02_Svc[] PROGMEM = "EBE0CCB0-7A0A-4B0C-8A1A-6FF2997DA3A6";
|
|
const char LYWSD02_BattChar[] PROGMEM = "EBE0CCC4-7A0A-4B0C-8A1A-6FF2997DA3A6";
|
|
const char LYWSD02_UnitChar[] PROGMEM = "EBE0CCBE-7A0A-4B0C-8A1A-6FF2997DA3A6";
|
|
const char LYWSD02_TimeChar[] PROGMEM = "EBE0CCB7-7A0A-4B0C-8A1A-6FF2997DA3A6";
|
|
const char LYWSD02_BattNotifyChar[] PROGMEM = "EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6";
|
|
|
|
const char *LYWSD03_Svc = LYWSD02_Svc;
|
|
const char *LYWSD03_BattNotifyChar = LYWSD02_BattNotifyChar;
|
|
|
|
const char *MHOC303_Svc = LYWSD02_Svc;
|
|
const char *MHOC303_UnitChar = LYWSD02_UnitChar;
|
|
const char *MHOC303_TimeChar = LYWSD02_TimeChar;
|
|
|
|
const char *MHOC401_Svc = LYWSD02_Svc;
|
|
const char *MHOC401_BattNotifyChar = LYWSD02_BattNotifyChar;
|
|
|
|
const char CGD1_Svc[] PROGMEM = "180F";
|
|
const char CGD1_BattChar[] PROGMEM = "2A19";
|
|
|
|
const char FLORA_Svc[] PROGMEM = "00001204-0000-1000-8000-00805F9B34FB";
|
|
const char FLORA_BattChar[] PROGMEM = "00001A02-0000-1000-8000-00805F9B34FB";
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
* enumerations
|
|
\*********************************************************************************************/
|
|
|
|
// types of operation performed, included in context
|
|
enum MI32_MI_OP_TYPES {
|
|
OP_TIME_WRITE = 0,
|
|
OP_BATT_READ = 1,
|
|
OP_UNIT_WRITE = 2,
|
|
OP_UNIT_READ = 3,
|
|
OP_UNIT_TOGGLE = 4,
|
|
OP_READ_HT_LY = 5,
|
|
};
|
|
|
|
|
|
enum MI32_MI_KEY_REQ {
|
|
KEY_REQUIREMENT_UNKNOWN = 0, // we don't know if a key is needed
|
|
KEY_NOT_REQUIRED = 1, // we got an unencrypted payload
|
|
KEY_REQUIRED_BUT_NOT_FOUND = 2, // we got an encrypted packet, but had not key
|
|
KEY_REQUIRED_AND_FOUND = 3, // we got an encrypted packet, and could decrypt
|
|
KEY_REQUIRED_AND_INVALID = 4, // we got an encrypted packet, and could not decrypt
|
|
};
|
|
|
|
/*********************************************************************************************\
|
|
* Classes
|
|
\*********************************************************************************************/
|
|
|
|
|
|
// fn type READ_CALLBACK
|
|
// NOTE!!!: this callback is called DIRECTLY from the operation task, so be careful about cross-thread access of data
|
|
// if is called after read, so that you can do a read/modify/write operation on a characteristic.
|
|
int toggleUnit(BLE_ESP32::generic_sensor_t *op){
|
|
uint32_t context = (uint32_t) op->context;
|
|
int opType = context >> 24;
|
|
// we only need to op type
|
|
int devType = (context >> 16) & 0xff;
|
|
int slot = (context) & 0xff;
|
|
switch (opType){
|
|
case OP_UNIT_TOGGLE:{
|
|
uint8_t curUnit = 0;
|
|
if( op->dataRead[0] != 0 && op->dataRead[0] < 101 ){
|
|
curUnit = op->dataRead[0];
|
|
}
|
|
|
|
curUnit = curUnit == 0x01?0xFF:0x01; // C/F
|
|
// copy in ALL of the data, because we don't know how long this is from the existing src code.
|
|
memcpy(op->dataToWrite, op->dataRead, op->readlen);
|
|
op->writelen = op->readlen;
|
|
op->dataToWrite[0] = curUnit;
|
|
} break;
|
|
case OP_UNIT_WRITE:{
|
|
uint8_t curUnit = op->dataToWrite[0];
|
|
// copy in ALL of the data, because we don't know how long this is from the existing src code.
|
|
memcpy(op->dataToWrite, op->dataRead, op->readlen);
|
|
op->writelen = op->readlen;
|
|
op->dataToWrite[0] = curUnit;
|
|
} break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool MI32Operation(int slot, int optype, const char *svc, const char *charactistic, const char *notifychar = nullptr, const uint8_t *data = nullptr, int datalen = 0, uint8_t *addr = nullptr ) {
|
|
if (!svc || !svc[0]){
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: Op inv svc"));
|
|
return 0;
|
|
}
|
|
|
|
BLE_ESP32::generic_sensor_t *op = nullptr;
|
|
|
|
// ALWAYS use this function to create a new one.
|
|
int res = BLE_ESP32::newOperation(&op);
|
|
if (!res){
|
|
AddLog(LOG_LEVEL_ERROR,PSTR("M32: Can't get a newOperation"));
|
|
return 0;
|
|
} else {
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: Got a newOperation"));
|
|
}
|
|
|
|
if (slot >= 0){
|
|
op->addr = NimBLEAddress(MIBLEsensors[slot].MAC);
|
|
} else {
|
|
if (!addr){
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: No addr"));
|
|
BLE_ESP32::freeOperation(&op);
|
|
return 0;
|
|
}
|
|
op->addr = NimBLEAddress(addr);
|
|
}
|
|
|
|
bool havechar = false;
|
|
op->serviceUUID = NimBLEUUID(svc);
|
|
|
|
if (!op->serviceUUID.bitSize()){
|
|
BLE_ESP32::freeOperation(&op);
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: MI Bad service string %s"), svc);
|
|
return 0;
|
|
}
|
|
|
|
|
|
if (charactistic && charactistic[0]){
|
|
havechar = true;
|
|
op->characteristicUUID = NimBLEUUID(charactistic);
|
|
if (!op->characteristicUUID.bitSize()){
|
|
BLE_ESP32::freeOperation(&op);
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: MI Bad characteristic string %s"), charactistic);
|
|
return 0;
|
|
}
|
|
}
|
|
if (notifychar && notifychar[0]){
|
|
op->notificationCharacteristicUUID = NimBLEUUID(notifychar);
|
|
if (!op->notificationCharacteristicUUID.bitSize()){
|
|
BLE_ESP32::freeOperation(&op);
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: MI Bad notifycharacteristic string %s"), notifychar);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (data && datalen) {
|
|
op->writelen = datalen;
|
|
memcpy(op->dataToWrite, data, datalen);
|
|
} else {
|
|
if (!datalen && havechar){
|
|
op->readlen = 1; // if we don't set readlen, then it won't read
|
|
}
|
|
}
|
|
|
|
// the only times we intercept between read abnd write
|
|
if ((optype == OP_UNIT_WRITE) || (optype == OP_UNIT_TOGGLE)){
|
|
op->readlen = 1; // if we don't set readlen, then it won't read
|
|
op->readmodifywritecallback = (void *)toggleUnit;
|
|
}
|
|
|
|
// this op will call us back on complete or failure.
|
|
op->completecallback = (void *)genericOpCompleteFn;
|
|
uint32_t context = (optype << 24) | (MIBLEsensors[slot].type << 16) | slot;
|
|
op->context = (void *)context;
|
|
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: MI s:%d op:%s"), slot, BLE_ESP32::BLETriggerResponse(op).c_str());
|
|
|
|
res = BLE_ESP32::extQueueOperation(&op);
|
|
if (!res){
|
|
// if it fails to add to the queue, do please delete it
|
|
BLE_ESP32::freeOperation(&op);
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: Failed to queue new operation - deleted"));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
int genericBatReadFn(int slot){
|
|
int res = 0;
|
|
|
|
switch(MIBLEsensors[slot].type) {
|
|
// these use notify for battery read, and it comes in the temp packet
|
|
case MI_LYWSD03MMC:
|
|
res = MI32Operation(slot, OP_BATT_READ, LYWSD03_Svc, nullptr, LYWSD03_BattNotifyChar);
|
|
break;
|
|
case MI_MHOC401:
|
|
res = MI32Operation(slot, OP_BATT_READ, MHOC401_Svc, nullptr, MHOC401_BattNotifyChar);
|
|
break;
|
|
|
|
// these read a characteristic
|
|
case MI_FLORA:
|
|
res = MI32Operation(slot, OP_BATT_READ, FLORA_Svc, FLORA_BattChar);
|
|
break;
|
|
case MI_LYWSD02:
|
|
res = MI32Operation(slot, OP_BATT_READ, LYWSD02_Svc, LYWSD02_BattChar);
|
|
break;
|
|
case MI_CGD1:
|
|
res = MI32Operation(slot, OP_BATT_READ, CGD1_Svc, CGD1_BattChar);
|
|
break;
|
|
|
|
// this was for testing only - it does work, but no need to read as we get good bat in advert
|
|
// case MI_MJ_HT_V1:
|
|
// res = MI32Operation(slot, OP_BATT_READ, CGD1_Svc, CGD1_BattChar);
|
|
// break;
|
|
|
|
default:
|
|
res = -10; // no need to read
|
|
break;
|
|
}
|
|
if (res > 0){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_INFO, PSTR("M32: Req batt read slot %d type %d queued"), slot, MIBLEsensors[slot].type);
|
|
} else {
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_INFO, PSTR("M32: Req batt read slot %d type %d non-queued res %d"), slot, MIBLEsensors[slot].type, res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
int genericSensorReadFn(int slot, int force){
|
|
int res = 0;
|
|
switch(MIBLEsensors[slot].type) {
|
|
/* seen notify timeout consistently with MI_LYWSD02,
|
|
so although the characteristic seems to exist, it does not work?
|
|
further dev required with sensor to hand.
|
|
case MI_LYWSD02:
|
|
// don't read if key present and we've decoded at least one advert
|
|
if (MIBLEsensors[slot].needkey == KEY_REQUIRED_AND_FOUND) return -2;
|
|
res = MI32Operation(slot, OP_READ_HT_LY, LYWSD02_Svc, nullptr, LYWSD02_BattNotifyChar);
|
|
break;*/
|
|
case MI_LYWSD03MMC:
|
|
// don't read if key present and we've decoded at least one advert
|
|
if (MIBLEsensors[slot].needkey == KEY_REQUIRED_AND_FOUND && !force) return -2;
|
|
res = MI32Operation(slot, OP_READ_HT_LY, LYWSD03_Svc, nullptr, LYWSD03_BattNotifyChar);
|
|
break;
|
|
case MI_MHOC401:
|
|
// don't read if key present and we've decoded at least one advert
|
|
if (MIBLEsensors[slot].needkey == KEY_REQUIRED_AND_FOUND && !force) return -2;
|
|
res = MI32Operation(slot, OP_READ_HT_LY, MHOC401_Svc, nullptr, MHOC401_BattNotifyChar);
|
|
break;
|
|
|
|
default:
|
|
res = -1;
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
// called once per second
|
|
int readOneSensor(){
|
|
if (MI32.sensorreader.active){
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: readOneSensor - already active reading %d"), MI32.sensorreader.slot-1);
|
|
return 0;
|
|
}
|
|
|
|
// loop if the sensor at the slot does not need to be read
|
|
// i.e. drop out of loop when we start a read, or hit the end
|
|
int res = -1;
|
|
do {
|
|
// MI32.sensorreader.slot is reset to zero to trigger a read sequence
|
|
if (MI32.sensorreader.slot >= MIBLEsensors.size()){
|
|
//AddLog(LOG_LEVEL_DEBUG, PSTR("BLE: readOneSensor past end of slots - %d > %d"), MI32.sensorreader.slot, MIBLEsensors.size());
|
|
return 0;
|
|
}
|
|
|
|
res = genericSensorReadFn(MI32.sensorreader.slot, 0);
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: genericSensorReadFn slot %d res %d"), MI32.sensorreader.slot, res);
|
|
|
|
// if this sensor in this slot does not need to be read via notify, just move on top the next one
|
|
if (res < 0){
|
|
MI32.sensorreader.slot++;
|
|
} else {
|
|
break;
|
|
}
|
|
} while (1);
|
|
|
|
if (res == 0){
|
|
// can't read at the moment (no operations available?)
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: readOneSensor no ops available slot %d res %d"), MI32.sensorreader.slot, res);
|
|
return 0;
|
|
}
|
|
|
|
// setup next slot to read
|
|
MI32.sensorreader.slot++;
|
|
// and make it wait until the read/notify is complete
|
|
// this is cleared in the response callback.
|
|
MI32.sensorreader.active = 1;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: readOneSensor reading for slot %d res %d"), MI32.sensorreader.slot-1, res);
|
|
|
|
// started one
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
// called once per second
|
|
int readOneBat(){
|
|
if (MI32.batteryreader.active){
|
|
return 0;
|
|
}
|
|
|
|
//MI32.batteryreader.slot is rest to zero to trigger a read...
|
|
if (MI32.batteryreader.slot >= MIBLEsensors.size()){
|
|
return 0;
|
|
}
|
|
|
|
int res = genericBatReadFn(MI32.batteryreader.slot);
|
|
|
|
// if this sensor in this slot does not support battery read, just move on top the next one
|
|
if (res < 0){
|
|
MI32.batteryreader.slot++;
|
|
if (MI32.batteryreader.slot >= MIBLEsensors.size()){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_INFO, PSTR("M32: Batt loop complete at %d"), MI32.batteryreader.slot);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (res == 0){
|
|
// can't read at the moment (no operations available?)
|
|
return 0;
|
|
}
|
|
|
|
// setup next slot to read
|
|
MI32.batteryreader.slot++;
|
|
// and make it wait until the read/notify is complete
|
|
// this is cleared in the response callback.
|
|
MI32.batteryreader.active = 1;
|
|
if (MI32.batteryreader.slot >= MIBLEsensors.size()){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_INFO, PSTR("M32: Batt loop will complete at %d"), MI32.batteryreader.slot);
|
|
}
|
|
// started one
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
// change the unit of measurement?
|
|
// call with unit == -1 to cause the unit to be toggled.
|
|
int genericUnitWriteFn(int slot, int unit){
|
|
int res = 0;
|
|
int op = OP_UNIT_WRITE;
|
|
if (unit == -1){
|
|
op = OP_UNIT_TOGGLE;
|
|
}
|
|
uint8_t writeData[1];
|
|
writeData[0] = unit;
|
|
switch (MIBLEsensors[slot].type){
|
|
case MI_LYWSD02:
|
|
res = MI32Operation(slot, op, LYWSD02_Svc, LYWSD02_UnitChar, nullptr, writeData, 1);
|
|
break;
|
|
case MI_MHOC303: // actually, EXACTLY the same as above, including the sevice and characteristic...
|
|
res = MI32Operation(slot, op, MHOC303_Svc, MHOC303_UnitChar, nullptr, writeData, 1);
|
|
break;
|
|
default:
|
|
res = -1;
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
// read the unit of measurement. genericOpCompleteFn
|
|
int genericUnitReadFn(int slot){
|
|
int res = 0;
|
|
switch (MIBLEsensors[slot].type){
|
|
case MI_LYWSD02:
|
|
res = MI32Operation(slot, OP_UNIT_READ, LYWSD02_Svc, LYWSD02_UnitChar);
|
|
break;
|
|
case MI_MHOC303: // actually, EXACTLY the same as above, including the sevice and characteristic...
|
|
res = MI32Operation(slot, OP_UNIT_READ, MHOC303_Svc, MHOC303_UnitChar);
|
|
break;
|
|
default:
|
|
res = -1;
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
// write time to a device. genericOpCompleteFn
|
|
int genericTimeWriteFn(int slot){
|
|
int res = 0;
|
|
switch (MIBLEsensors[slot].type){
|
|
case MI_LYWSD02: {
|
|
union {
|
|
uint8_t buf[5];
|
|
uint32_t time;
|
|
} _utc;
|
|
_utc.time = Rtc.utc_time;
|
|
_utc.buf[4] = Rtc.time_timezone / 60;
|
|
res = MI32Operation(slot, OP_TIME_WRITE, LYWSD02_Svc, LYWSD02_TimeChar, nullptr, _utc.buf, sizeof(_utc.buf));
|
|
} break;
|
|
case MI_MHOC303: // actually, EXACTLY the same as above, including the sevice and characteristic...
|
|
union {
|
|
uint8_t buf[5];
|
|
uint32_t time;
|
|
} _utc;
|
|
_utc.time = Rtc.utc_time;
|
|
_utc.buf[4] = Rtc.time_timezone / 60;
|
|
res = MI32Operation(slot, OP_TIME_WRITE, MHOC303_Svc, MHOC303_TimeChar, nullptr, _utc.buf, sizeof(_utc.buf));
|
|
break;
|
|
default:
|
|
res = -1;
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
int genericOpCompleteFn(BLE_ESP32::generic_sensor_t *op){
|
|
uint32_t context = (uint32_t) op->context;
|
|
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: MI op complete context %x"), context);
|
|
|
|
int opType = context >> 24;
|
|
int devType = (context >> 16) & 0xff;
|
|
int slot = (context) & 0xff;
|
|
|
|
char slotMAC[13];
|
|
BLE_ESP32::dump(slotMAC, sizeof(slotMAC), MIBLEsensors[slot].MAC, 6) ;
|
|
uint8_t addrrev[6];
|
|
memcpy(addrrev, MIBLEsensors[slot].MAC, 6);
|
|
//BLE_ESP32::ReverseMAC(addrrev);
|
|
NimBLEAddress addr(addrrev);
|
|
|
|
bool fail = false;
|
|
if (op->addr != addr){
|
|
// slot changed during operation?
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: Slot mac changed during an operation"));
|
|
fail = true;
|
|
}
|
|
|
|
if (op->state <= GEN_STATE_FAILED){
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: Operation failed %d for %s"), op->state, slotMAC);
|
|
fail = true;
|
|
}
|
|
|
|
if (fail){
|
|
switch(opType){
|
|
case OP_BATT_READ:{
|
|
// allow another...
|
|
MI32.batteryreader.active = 0;
|
|
} break;
|
|
case OP_READ_HT_LY: {
|
|
// allow another...
|
|
MI32.sensorreader.active = 0;
|
|
} break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
switch(opType){
|
|
case OP_TIME_WRITE:
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: Time write for %s complete"), slotMAC);
|
|
return 0; // nothing to do
|
|
case OP_BATT_READ:{
|
|
uint8_t *data = nullptr;
|
|
int len = 0;
|
|
if (op->notifylen){
|
|
data = op->dataNotify;
|
|
len = op->notifylen;
|
|
// note: the only thingas that have battery in notify FOR THE MOMENT read it like this.
|
|
MI32notifyHT_LY(slot, (char*)op->dataNotify, op->notifylen);
|
|
}
|
|
if (op->readlen){
|
|
data = op->dataRead;
|
|
len = op->readlen;
|
|
MIParseBatt(slot, data, len);
|
|
}
|
|
|
|
// allow another...
|
|
MI32.batteryreader.active = 0;
|
|
AddLog(LOG_LEVEL_INFO, PSTR("M32: Batt read slot %d done state %x"), slot, op->state);
|
|
|
|
} return 0;
|
|
|
|
case OP_UNIT_WRITE: // nothing more to do?
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: Unit write for %s complete"), slotMAC);
|
|
return 0;
|
|
|
|
case OP_UNIT_READ: {
|
|
uint8_t currUnit = op->dataRead[0];
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: Unit read for %s complete %d"), slotMAC, currUnit);
|
|
} return 0;
|
|
|
|
case OP_UNIT_TOGGLE: {
|
|
uint8_t currUnit = op->dataToWrite[0];
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: Unit toggle for %s complete %d->%d; datasize was %d"), slotMAC, op->dataRead[0], op->dataToWrite[0], op->readlen);
|
|
} return 0;
|
|
|
|
case OP_READ_HT_LY: {
|
|
// allow another...
|
|
MI32.sensorreader.active = 0;
|
|
MI32notifyHT_LY(slot, (char*)op->dataNotify, op->notifylen);
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: HT_LY notify for %s complete"), slotMAC);
|
|
} return 0;
|
|
|
|
default:
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: OpType %d not recognised?"), opType);
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int MI32advertismentCallback(BLE_ESP32::ble_advertisment_t *pStruct)
|
|
{
|
|
// we will try not to use this...
|
|
BLEAdvertisedDevice *advertisedDevice = pStruct->advertisedDevice;
|
|
|
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("M32: Advertised Device: %s Buffer: %u"),advertisedDevice->getAddress().toString().c_str(),advertisedDevice->getServiceData(0).length());
|
|
int RSSI = pStruct->RSSI;
|
|
const uint8_t *addr = pStruct->addr;
|
|
if(MI32isInBlockList(addr) == true) return 0;
|
|
if (MI32.option.onlyAliased){
|
|
const char *alias = BLE_ESP32::getAlias(addr);
|
|
if (!alias || !(*alias)){
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int svcdataCount = advertisedDevice->getServiceDataCount();
|
|
|
|
if (svcdataCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
NimBLEUUID UUIDBig = advertisedDevice->getServiceDataUUID(0);//.getNative()->u16.value;
|
|
|
|
const ble_uuid_any_t* native = UUIDBig.getNative();
|
|
if (native->u.type != 16){
|
|
//not interested in 128 bit;
|
|
return 0;
|
|
}
|
|
uint16_t UUID = native->u16.value;
|
|
|
|
char temp[60];
|
|
BLE_ESP32::dump(temp, 13, addr, 6);
|
|
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("M32: MI:%s svc[0] UUID (%x)"), temp, UUID);
|
|
std::string ServiceDataStr = advertisedDevice->getServiceData(0);
|
|
|
|
uint32_t ServiceDataLength = ServiceDataStr.length();
|
|
const uint8_t *ServiceData = (const uint8_t *)ServiceDataStr.data();
|
|
BLE_ESP32::dump(temp, 60, ServiceData, ServiceDataLength);
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("M32: MI:%s"), temp);
|
|
|
|
|
|
if (UUID){
|
|
// this will take and keep the mutex until the function is over
|
|
TasAutoMutex localmutex(&slotmutex, "Mi32AdCB2");
|
|
switch(UUID){
|
|
case 0xfe95: // std MI?
|
|
case 0xfdcd: // CGD1?
|
|
{
|
|
MI32ParseResponse(ServiceData, ServiceDataLength, addr, RSSI);
|
|
} break;
|
|
case 0x181a: { //ATC
|
|
MI32ParseATCPacket(ServiceData, ServiceDataLength, addr, RSSI);
|
|
} break;
|
|
case 0x181d: // Mi Scale V1
|
|
case 0x181b: // Mi Scale V2
|
|
{
|
|
MI32ParseMiScalePacket(ServiceData, ServiceDataLength, addr, RSSI, UUID);
|
|
} break;
|
|
|
|
default:{
|
|
} break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
* Helper functions
|
|
\*********************************************************************************************/
|
|
|
|
/**
|
|
* @brief Remove all colons from null terminated char array
|
|
*
|
|
* @param _string Typically representing a MAC-address like AA:BB:CC:DD:EE:FF
|
|
*/
|
|
void MI32stripColon(char* _string){
|
|
uint32_t _length = strlen(_string);
|
|
uint32_t _index = 0;
|
|
while (_index < _length) {
|
|
char c = _string[_index];
|
|
if(c==':'){
|
|
memmove(_string+_index,_string+_index+1,_length-_index);
|
|
}
|
|
_index++;
|
|
}
|
|
_string[_index] = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Convert string that repesents a hexadecimal number to a byte array
|
|
*
|
|
* @param _string input string in format: AABBCCDDEEFF or AA:BB:CC:DD:EE:FF, caseinsensitive
|
|
* @param _mac target byte array must match the correct size (i.e. AA:BB -> uint8_t bytes[2])
|
|
*/
|
|
|
|
void MI32HexStringToBytes(char* _string, uint8_t* _byteArray) {
|
|
MI32stripColon(_string);
|
|
UpperCase(_string,_string);
|
|
uint32_t index = 0;
|
|
uint32_t _end = strlen(_string);
|
|
memset(_byteArray,0,_end/2);
|
|
while (index < _end) {
|
|
char c = _string[index];
|
|
uint8_t value = 0;
|
|
if(c >= '0' && c <= '9')
|
|
value = (c - '0');
|
|
else if (c >= 'A' && c <= 'F')
|
|
value = (10 + (c - 'A'));
|
|
_byteArray[(index/2)] += value << (((index + 1) % 2) * 4);
|
|
index++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Reverse an array of 6 bytes
|
|
*
|
|
* @param _mac a byte array of size 6 (typicalliy representing a MAC address)
|
|
*/
|
|
void MI32_ReverseMAC(uint8_t _mac[]){
|
|
uint8_t _reversedMAC[6];
|
|
for (uint8_t i=0; i<6; i++){
|
|
_reversedMAC[5-i] = _mac[i];
|
|
}
|
|
memcpy(_mac,_reversedMAC, sizeof(_reversedMAC));
|
|
}
|
|
|
|
#ifdef USE_MI_DECRYPTION
|
|
int MI32AddKey(char* payload, char* key = nullptr){
|
|
mi_bindKey_t keyMAC;
|
|
|
|
if (!key){
|
|
MI32HexStringToBytes(payload,keyMAC.buf);
|
|
} else {
|
|
MI32HexStringToBytes(payload,keyMAC.MAC);
|
|
MI32HexStringToBytes(key,keyMAC.key);
|
|
}
|
|
|
|
bool unknownKey = true;
|
|
for(uint32_t i=0; i<MIBLEbindKeys.size(); i++){
|
|
if(memcmp(keyMAC.MAC,MIBLEbindKeys[i].MAC,sizeof(keyMAC.MAC))==0){
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: Known key"));
|
|
memcpy(MIBLEbindKeys[i].key, keyMAC.key, 16);
|
|
unknownKey=false;
|
|
return 1;
|
|
}
|
|
}
|
|
if(unknownKey){
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: New key"));
|
|
MIBLEbindKeys.push_back(keyMAC);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
int MIDecryptPayload(const uint8_t *macin, const uint8_t *nonce, uint32_t tag, uint8_t *data, int len){
|
|
uint8_t payload[32];
|
|
uint8_t mac[6];
|
|
memcpy(mac, macin, 6);
|
|
MI32_ReverseMAC(mac);
|
|
uint8_t _bindkey[32] = {0x0};
|
|
const unsigned char authData[16] = {0x11};
|
|
bool foundNoKey = true;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: Search key for MAC: %02x%02x%02x%02x%02x%02x"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
for(uint32_t i=0; i<MIBLEbindKeys.size(); i++){
|
|
if(memcmp(mac, MIBLEbindKeys[i].MAC, 6)==0){
|
|
memcpy(_bindkey, MIBLEbindKeys[i].key, 16);
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Decryption Key found"));
|
|
foundNoKey = false;
|
|
break;
|
|
}
|
|
}
|
|
if(foundNoKey){
|
|
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: No Key found"));
|
|
return -2; // indicates needs key
|
|
}
|
|
|
|
br_aes_small_ctrcbc_keys keyCtx;
|
|
br_aes_small_ctrcbc_init(&keyCtx, _bindkey, 16);
|
|
|
|
br_ccm_context ctx;
|
|
br_ccm_init(&ctx, &keyCtx.vtable);
|
|
br_ccm_reset(&ctx, nonce, 12, 1, len, 4);
|
|
br_ccm_aad_inject(&ctx, authData, 1);
|
|
br_ccm_flip(&ctx);
|
|
|
|
memcpy(payload, data, len); //we want to be sure about 4-byte alignement
|
|
br_ccm_run(&ctx, 0, payload, len);
|
|
memcpy(data, payload, len); //back to the packet
|
|
|
|
|
|
// crashed in here - why?, so give it more space to work with?
|
|
// returns 1 if matched, else 0
|
|
int ret = br_ccm_check_tag(&ctx, &tag);
|
|
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Err:%i, Decrypted : %02x %02x %02x %02x %02x"), ret, payload[1],payload[2],payload[3],payload[4],payload[5]);
|
|
return ret-1; // -> -1=fail, 0=success
|
|
}
|
|
|
|
#endif // USE_MI_DECRYPTION
|
|
|
|
|
|
// packet examples:
|
|
// MJ_HT_V1
|
|
// 5020 AA01 41 3AF4DAA8654C 0A100109
|
|
// 5020 AA01 43 3AF4DAA8654C 061002E901
|
|
// 5020 AA01 48 3AF4DAA8654C 041002BF00
|
|
// 5020 AA01 4A 3AF4DAA8654C 0D1004BF00E901
|
|
// 7122 AA01 15 3AF4DAA8654C 0D 0200020D10
|
|
|
|
// LYWSD03 encrypted data:
|
|
// 5858 5B05 2F B3E30838C1A4 [69A9FBDF67] ,060000 0791C39A - 23bytes
|
|
// 23-9 = 14
|
|
// -> nonce B3E30838C1A4|5B02|2F|060000
|
|
// 23-6 = 17
|
|
// -> tag 0791C39A
|
|
// datalen = 23 - 9 - 4 - 3 - 1 - 1 = 5
|
|
|
|
// CGD1 reconstructed from src: (svcdata on fdcd)
|
|
// xxyy FFEEDDCCBBAA MMMM TTTTHHHH|BB
|
|
// xxyy FFEEDDCCBBAA 0104 TTTTHHHH
|
|
// xxyy FFEEDDCCBBAA 0201 BB
|
|
|
|
const char *MIaddrStr(const uint8_t *addr, int useAlias = 0){
|
|
static char addrstr[32];
|
|
|
|
const char *id = nullptr;
|
|
if (useAlias){
|
|
id = BLE_ESP32::getAlias(addr);
|
|
}
|
|
if (!id || !(*id)){
|
|
id = addrstr;
|
|
BLE_ESP32::dump(addrstr, 13, addr, 6);
|
|
} else {
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
int MIParsePacket(const uint8_t* slotmac, struct mi_beacon_data_t *parsed, const uint8_t *datain, int len){
|
|
uint8_t data[32];
|
|
memcpy(data, datain, len);
|
|
if (!parsed){
|
|
return 0;
|
|
}
|
|
if (len < 5){
|
|
return 0;
|
|
}
|
|
|
|
int byteindex = 0;
|
|
|
|
// 58 58 = 0x5858 = data|comp|mac|enc, v5|auth2
|
|
// 30 58 = 0x5830 = comp|mac, v5|auth2
|
|
// 30 50 = 0x5030 = comp|mac, v5|auth0
|
|
// 48 59 = 0x5948 = data|enc, v5|auth2|registered
|
|
// 10 59 = 0x5910 = mac, v5|auth2|registered
|
|
// 71 22 = 0x2271 = data|comp|mac v2|bind
|
|
// 50 20 = 0x2050 = data|mac v2 - MJ_HT_V1 data
|
|
// 71 22 = 0x2271 = data|comp|mac|reserved1 v2|bind - MJ_HT_V1 pair
|
|
|
|
// data from byte 0 - e.g. 30
|
|
parsed->framedata.meshflag = (data[byteindex] & 0x80)>>7; //Byte 0: x.......
|
|
parsed->framedata.dataflag = (data[byteindex] & 0x40)>>6; //Byte 0: .x......
|
|
parsed->framedata.compatibilityflag = (data[byteindex] & 0x20)>>5; //Byte 0: ..x..... - indicates compatibility data present
|
|
parsed->framedata.MACFlag = (data[byteindex] & 0x10)>>4; //Byte 0: ...x....
|
|
parsed->framedata.isencrypted = (data[byteindex] & 0x08)>>3; //Byte 0: ....x...
|
|
parsed->framedata.reserved = (data[byteindex] & 0x03)>>6; //Byte 0: .....xxx
|
|
|
|
// data from byte 1 - e.g. 58
|
|
byteindex++;
|
|
parsed->framedata.version = (data[byteindex] & 0xf0)>>4; //Byte 0: xxxx....
|
|
parsed->framedata.authMode = (data[byteindex] & 0x0C)>>6; //Byte 0: ....xx.. // e.g. 2
|
|
parsed->framedata.bindingvalidreq = (data[byteindex] & 0x02)>>1; //Byte 0: ......x.
|
|
parsed->framedata.registeredflag = (data[byteindex] & 0x01); //Byte 0: .......x
|
|
|
|
// note:
|
|
// if bindingvalidreq, we should connect and establish a key.
|
|
// However, how do we determine WHICH TAS should do this?
|
|
|
|
byteindex++;
|
|
|
|
parsed->devicetype = *((uint16_t *)(data + byteindex));
|
|
byteindex += 2;
|
|
parsed->framecnt = data[byteindex];
|
|
//if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: MI frame %d"), parsed->framecnt);
|
|
byteindex++;
|
|
|
|
|
|
if (parsed->framedata.version <= 3){
|
|
// e.g. MJ_HT_V1
|
|
}
|
|
|
|
if (parsed->framedata.MACFlag){
|
|
if (len < byteindex + 6){
|
|
return 0;
|
|
}
|
|
memcpy(parsed->macdata.mac, &data[byteindex], 6);
|
|
byteindex += 6;
|
|
}
|
|
|
|
int decres = 1;
|
|
// everything after MAC is encrypted if specified?
|
|
if (parsed->framedata.isencrypted){
|
|
if (len < byteindex + 3+4+1){
|
|
return 0;
|
|
}
|
|
const uint8_t* mac = slotmac;
|
|
if (parsed->framedata.MACFlag){
|
|
mac = parsed->macdata.mac;
|
|
}
|
|
uint8_t nonce[12];
|
|
uint8_t *p = nonce;
|
|
memcpy(p, mac, 6);
|
|
p += 6;
|
|
memcpy(p, &parsed->devicetype, 2);
|
|
p += 2;
|
|
*(p++) = parsed->framecnt;
|
|
uint8_t *extCnt = data +(len-7);
|
|
memcpy(p, extCnt, 3);
|
|
p += 3;
|
|
uint32_t tag = *(uint32_t *)(data + (len-4));
|
|
|
|
// decrypt the data in place
|
|
decres = MIDecryptPayload(mac, nonce, tag, data + byteindex, len - byteindex - 7);
|
|
// no longer need the nonce data.
|
|
len -= 7;
|
|
}
|
|
|
|
switch(decres){
|
|
case 1: // decrypt not requested
|
|
break;
|
|
case 0: // suceeded
|
|
parsed->needkey = KEY_REQUIRED_AND_FOUND;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32 %s: Payload decrypted"), MIaddrStr(slotmac));
|
|
break;
|
|
case -1: // key failed to work
|
|
parsed->needkey = KEY_REQUIRED_AND_INVALID;
|
|
AddLog(LOG_LEVEL_ERROR,PSTR("M32 %s: Payload decrypt failed"), MIaddrStr(slotmac));
|
|
parsed->payloadpresent = 0;
|
|
return 0;
|
|
break;
|
|
case -2: // key not present
|
|
parsed->needkey = KEY_REQUIRED_BUT_NOT_FOUND;
|
|
AddLog(LOG_LEVEL_ERROR,PSTR("M32 %s: Payload encrypted but no key"), MIaddrStr(slotmac));
|
|
parsed->payloadpresent = 0;
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
// if set, there could be 1 or 3 bytes here.
|
|
if (parsed->framedata.compatibilityflag) {
|
|
if (len < byteindex + 1){
|
|
return 0;
|
|
}
|
|
// e.g. in pair: 7122 AA01 15 3AF4DAA8654C [0D] 0200020D10 -> bond|unused2
|
|
parsed->compatibility.reserved = (data[byteindex] & 0xc0) >> 6; //Byte 0: xx......
|
|
parsed->compatibility.IOcap = (data[byteindex] & 0x20) >> 5; //Byte 0: ..x.....
|
|
parsed->compatibility.bondability = (data[byteindex] & 0x18) >> 3; //Byte 0: ...xx...
|
|
parsed->compatibility.unused = (data[byteindex] & 0x07) >> 0; //Byte 0: .....xxx
|
|
byteindex ++;
|
|
|
|
if (parsed->compatibility.IOcap) {
|
|
if (len < byteindex + 2){
|
|
return 0;
|
|
}
|
|
parsed->compatibility.IOCapability = *((uint16_t *)(data + byteindex)); // bytes 1-2, e.g. 01 00 -> 0001
|
|
byteindex += 2;
|
|
}
|
|
}
|
|
|
|
// rest is payload
|
|
int rem = (len - byteindex);
|
|
if (rem > sizeof(parsed->payload)){
|
|
rem = sizeof(parsed->payload);
|
|
return 0;
|
|
}
|
|
|
|
if ((len - byteindex) == 0){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32 %s: No payload"), MIaddrStr(slotmac));
|
|
parsed->payload.size = 0;
|
|
parsed->payloadpresent = 0;
|
|
return 0;
|
|
}
|
|
|
|
// we have payload which did not need decrypt.
|
|
if (decres == 1){
|
|
parsed->needkey = KEY_NOT_REQUIRED;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32 %s: Payload unencrypted"), MIaddrStr(slotmac));
|
|
}
|
|
|
|
// already decrypted if required
|
|
parsed->payloadpresent = 1;
|
|
memcpy(&parsed->payload, (data + byteindex), (len - byteindex));
|
|
if (parsed->payload.size != (len - byteindex) - 3){
|
|
AddLog(LOG_LEVEL_DEBUG,PSTR("M32 %s: Payload length mismatch"), MIaddrStr(slotmac));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* common functions
|
|
\*********************************************************************************************/
|
|
|
|
|
|
/**
|
|
* @brief Return the slot number of a known sensor or return create new sensor slot
|
|
*
|
|
* @param _MAC BLE address of the sensor
|
|
* @param _type Type number of the sensor
|
|
* @return uint32_t Known or new slot in the sensors-vector
|
|
*/
|
|
uint32_t MIBLEgetSensorSlot(const uint8_t *mac, uint16_t _type, uint8_t counter){
|
|
|
|
//AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: will test ID-type: %x"),D_CMND_MI32, _type);
|
|
bool _success = false;
|
|
for (uint32_t i=0; i < MI_MI32_TYPES; i++){ // i < sizeof(kMI32DeviceID) gives compiler warning
|
|
if(_type == kMI32DeviceID[i]){
|
|
_type = i+1;
|
|
_success = true;
|
|
break;
|
|
}
|
|
else {
|
|
//AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: ID-type is not: %x"),D_CMND_MI32,kMI32DeviceID[i]);
|
|
}
|
|
}
|
|
if(!_success) {
|
|
_type = 1; // unknown
|
|
}
|
|
|
|
//AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: vector size %u"),D_CMND_MI32, MIBLEsensors.size());
|
|
for(uint32_t i=0; i<MIBLEsensors.size(); i++){
|
|
if(memcmp(mac, MIBLEsensors[i].MAC, 6)==0){
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Counters: %x %x"),MIBLEsensors[i].lastCnt, counter);
|
|
if(MIBLEsensors[i].lastCnt==counter) {
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("Old packet"));
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: slot: %u/%u - ign repeat"),D_CMND_MI32, i, MIBLEsensors.size());
|
|
//return 0xff; // packet received before, stop here
|
|
}
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Frame %d, last %d"), counter, MIBLEsensors[i].lastCnt);
|
|
MIBLEsensors[i].lastCnt = counter;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: slot: %u/%u"),D_CMND_MI32, i, MIBLEsensors.size());
|
|
|
|
if (MIBLEsensors[i].type != _type){
|
|
// this happens on incorrectly configured pvvx ATC firmware
|
|
AddLog(LOG_LEVEL_ERROR,PSTR("M32: %s: slot: %u - device type 0x%04x(%s) -> 0x%04x(%s) - check device is only sending one type of advert."),D_CMND_MI32, i,
|
|
kMI32DeviceID[MIBLEsensors[i].type-1], kMI32DeviceType[MIBLEsensors[i].type-1], kMI32DeviceID[_type-1], kMI32DeviceType[_type-1]);
|
|
MIBLEsensors[i].type = _type;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
//AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: i: %x %x %x %x %x %x"),D_CMND_MI32, MIBLEsensors[i].MAC[5], MIBLEsensors[i].MAC[4],MIBLEsensors[i].MAC[3],MIBLEsensors[i].MAC[2],MIBLEsensors[i].MAC[1],MIBLEsensors[i].MAC[0]);
|
|
//AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: n: %x %x %x %x %x %x"),D_CMND_MI32, mac[5], mac[4], mac[3],mac[2],mac[1],mac[0]);
|
|
}
|
|
//AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: new sensor -> slot: %u"),D_CMND_MI32, MIBLEsensors.size());
|
|
//AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: found new sensor"),D_CMND_MI32);
|
|
mi_sensor_t _newSensor;
|
|
memset(&_newSensor, 0 , sizeof(_newSensor));
|
|
memcpy(_newSensor.MAC, mac, 6);
|
|
_newSensor.type = _type;
|
|
_newSensor.eventType.raw = 0;
|
|
_newSensor.feature.raw = 0;
|
|
_newSensor.temp = NAN;
|
|
_newSensor.needkey = KEY_REQUIREMENT_UNKNOWN;
|
|
_newSensor.bat = 0x00;
|
|
_newSensor.RSSI = 0xffff;
|
|
_newSensor.lux = 0x00ffffff;
|
|
_newSensor.light = -1;
|
|
_newSensor.Btn = -1;
|
|
|
|
switch (_type)
|
|
{
|
|
case MI_FLORA:
|
|
_newSensor.moisture =0xff;
|
|
_newSensor.fertility =0xffff;
|
|
_newSensor.firmware[0]='\0';
|
|
_newSensor.feature.temp=1;
|
|
_newSensor.feature.moist=1;
|
|
_newSensor.feature.fert=1;
|
|
_newSensor.feature.lux=1;
|
|
_newSensor.feature.bat=1;
|
|
break;
|
|
case MI_NLIGHT:
|
|
_newSensor.events=0x00;
|
|
_newSensor.feature.PIR=1;
|
|
_newSensor.feature.NMT=1;
|
|
_newSensor.feature.events=1;
|
|
break;
|
|
case MI_MJYD2S:
|
|
_newSensor.NMT=0;
|
|
_newSensor.events=0x00;
|
|
_newSensor.feature.PIR=1;
|
|
_newSensor.feature.NMT=1;
|
|
_newSensor.feature.lux=1;
|
|
_newSensor.feature.bat=1;
|
|
_newSensor.feature.events=1;
|
|
break;
|
|
case MI_YEERC:
|
|
_newSensor.feature.Btn=1;
|
|
break;
|
|
case MI_DOOR: // MCCGQ02HL
|
|
_newSensor.feature.Btn=1;
|
|
_newSensor.feature.light=1;
|
|
_newSensor.feature.bat=1;
|
|
break;
|
|
case MI_SCALE_V1:
|
|
_newSensor.feature.scale=1;
|
|
break;
|
|
case MI_SCALE_V2:
|
|
_newSensor.feature.scale=1;
|
|
_newSensor.feature.impedance=1;
|
|
break;
|
|
default:
|
|
_newSensor.hum=NAN;
|
|
_newSensor.feature.temp=1;
|
|
_newSensor.feature.hum=1;
|
|
_newSensor.feature.tempHum=1;
|
|
_newSensor.feature.bat=1;
|
|
break;
|
|
}
|
|
MIBLEsensors.push_back(_newSensor);
|
|
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s: new %s at slot: %u"),D_CMND_MI32, kMI32DeviceType[_type-1],MIBLEsensors.size()-1);
|
|
MI32.mode.shallShowStatusInfo = 1;
|
|
return MIBLEsensors.size()-1;
|
|
};
|
|
|
|
/**
|
|
* @brief trigger real-time message for PIR or RC
|
|
*
|
|
*/
|
|
void MI32triggerTele(void){
|
|
MI32.mode.triggeredTele = 1;
|
|
MI32ShowTriggeredSensors();
|
|
MI32.mode.triggeredTele = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Is called after every finding of new BLE sensor
|
|
*
|
|
*/
|
|
void MI32StatusInfo() {
|
|
MI32.mode.shallShowStatusInfo = 0;
|
|
Response_P(PSTR("{\"%s\":{\"found\":%u}}"), D_CMND_MI32, MIBLEsensors.size());
|
|
XdrvRulesProcess(0);
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* BLE callbacks section
|
|
* These are called from main thread only.
|
|
\*********************************************************************************************/
|
|
|
|
|
|
int MI32scanCompleteCallback(NimBLEScanResults results){
|
|
// we actually don't need to do anything here....
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Scan complete"));
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
* init BLE_32
|
|
\*********************************************************************************************/
|
|
|
|
|
|
void MI32Init(void) {
|
|
MIBLEsensors.reserve(10);
|
|
MIBLEbindKeys.reserve(10);
|
|
MI32.mode.init = false;
|
|
|
|
//test section for options
|
|
MI32.option.allwaysAggregate = 1;
|
|
MI32.option.noSummary = 0;
|
|
MI32.option.minimalSummary = 0;
|
|
MI32.option.directBridgeMode = 0;
|
|
MI32.option.showRSSI = 1;
|
|
MI32.option.ignoreBogusBattery = 1; // from advertisements
|
|
MI32.option.holdBackFirstAutodiscovery = 1;
|
|
|
|
BLE_ESP32::registerForAdvertismentCallbacks((const char *)"MI32", MI32advertismentCallback);
|
|
BLE_ESP32::registerForScanCallbacks((const char *)"MI32", MI32scanCompleteCallback);
|
|
// note: for operations, we will set individual callbacks in the operations we request
|
|
//void registerForOpCallbacks(const char *tag, BLE_ESP32::OPCOMPLETE_CALLBACK* pFn);
|
|
|
|
AddLog(LOG_LEVEL_INFO,PSTR("M32: init: request callbacks"));
|
|
MI32.period = Settings->tele_period;
|
|
MI32.mode.init = 1;
|
|
return;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
* Task section
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
|
|
int MIParseBatt(int slot, uint8_t *data, int len){
|
|
int value = data[0];
|
|
char slotMAC[13];
|
|
BLE_ESP32::dump(slotMAC, sizeof(slotMAC), MIBLEsensors[slot].MAC, 6) ;
|
|
|
|
if ((value != 0) && (value < 101)){
|
|
MIBLEsensors[slot].bat = value;
|
|
if(MIBLEsensors[slot].type==MI_FLORA){
|
|
if (len < 7){
|
|
AddLog(LOG_LEVEL_ERROR,PSTR("M32: FLORA: not enough bytes read for firmware?"));
|
|
} else {
|
|
memcpy(MIBLEsensors[slot].firmware, data+2, 5);
|
|
MIBLEsensors[slot].firmware[5] = '\0';
|
|
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s: FLORA Firmware: %s"),D_CMND_MI32,MIBLEsensors[slot].firmware);
|
|
}
|
|
}
|
|
MIBLEsensors[slot].eventType.bat = 1;
|
|
MIBLEsensors[slot].shallSendMQTT = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Batt read for %s complete %d"), slotMAC, value);
|
|
} else {
|
|
AddLog(LOG_LEVEL_ERROR,PSTR("M32: Batt read for %s complete but out of range 1-101 (%d)"), slotMAC, value);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* parse the response from advertisements
|
|
\*********************************************************************************************/
|
|
|
|
|
|
void MI32ParseATCPacket(const uint8_t * _buf, uint32_t length, const uint8_t *addr, int RSSI){
|
|
ATCPacket_t *_packet = (ATCPacket_t*)_buf;
|
|
PVVXPacket_t *ppv_packet = (PVVXPacket_t*)_buf;
|
|
|
|
|
|
if (length == 15){ // 19-1-1-2
|
|
uint8_t addrrev[6];
|
|
memcpy(addrrev, addr, 6);
|
|
MI32_ReverseMAC(addrrev);
|
|
if (!memcmp(addrrev, ppv_packet->MAC, 6)){
|
|
//int16_t temperature; // x 0.1 degree
|
|
//uint16_t humidity; // x 0.01 %
|
|
//uint16_t battery_mv; // mV
|
|
//uint8_t battery_level; // 0..100 %
|
|
//uint8_t counter; // measurement count
|
|
//uint8_t flags;
|
|
|
|
uint32_t _slot = MIBLEgetSensorSlot(addr, 0x0a1c, ppv_packet->counter); // This must be a hard-coded fake ID
|
|
if(_slot==0xff) return;
|
|
|
|
if ((_slot >= 0) && (_slot < MIBLEsensors.size())){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s:pvvx at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot);
|
|
MIBLEsensors[_slot].RSSI=RSSI;
|
|
MIBLEsensors[_slot].needkey=KEY_NOT_REQUIRED;
|
|
|
|
MIBLEsensors[_slot].temp = (float)(ppv_packet->temperature)/100.0f;
|
|
MIBLEsensors[_slot].hum = (float)(ppv_packet->humidity)/100.0f;
|
|
MIBLEsensors[_slot].eventType.tempHum = 1;
|
|
MIBLEsensors[_slot].bat = ppv_packet->battery_level;
|
|
MIBLEsensors[_slot].eventType.bat = 1;
|
|
|
|
MIBLEsensors[_slot].Btn = (ppv_packet->flags) & 0x1; // First bit is reed switch status
|
|
MIBLEsensors[_slot].eventType.Btn = 1;
|
|
MIBLEsensors[_slot].feature.Btn = 1;
|
|
|
|
if(MI32.option.directBridgeMode) {
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
}
|
|
}
|
|
return;
|
|
} else {
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: PVVX packet mac mismatch - ignored?"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
uint8_t addrrev[6];
|
|
memcpy(addrrev, addr, 6);
|
|
//MI32_ReverseMAC(addrrev);
|
|
|
|
// if packet tell a different address to origin, use the different address
|
|
if (memcmp(addrrev, _packet->MAC, 6)){
|
|
MI32_ReverseMAC(_packet->MAC);
|
|
if (!memcmp(addrrev, _packet->MAC, 6)){
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: ATC packet with reversed MAC addr?"));
|
|
} else {
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: ATC packet with MAC addr mismatch - is this mesh?"));
|
|
memcpy(addrrev, _packet->MAC, 6);
|
|
}
|
|
addr = addrrev;
|
|
}
|
|
|
|
uint32_t _slot = MIBLEgetSensorSlot(addr, 0x0a1c, _packet->frameCnt); // This must be a hard-coded fake ID
|
|
|
|
if(_slot==0xff) return;
|
|
|
|
if ((_slot >= 0) && (_slot < MIBLEsensors.size())){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot);
|
|
MIBLEsensors[_slot].RSSI=RSSI;
|
|
MIBLEsensors[_slot].needkey=KEY_NOT_REQUIRED;
|
|
|
|
MIBLEsensors[_slot].temp = (float)(int16_t(__builtin_bswap16(_packet->temp)))/10.0f;
|
|
MIBLEsensors[_slot].hum = (float)_packet->hum;
|
|
MIBLEsensors[_slot].eventType.tempHum = 1;
|
|
MIBLEsensors[_slot].bat = _packet->batPer;
|
|
MIBLEsensors[_slot].eventType.bat = 1;
|
|
|
|
if(MI32.option.directBridgeMode) {
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
}
|
|
} else {
|
|
|
|
}
|
|
}
|
|
|
|
void MI32ParseMiScalePacket(const uint8_t * _buf, uint32_t length, const uint8_t *addr, int RSSI, int UUID) {
|
|
MiScaleV1Packet_t *_packetV1 = (MiScaleV1Packet_t*)_buf;
|
|
MiScaleV2Packet_t *_packetV2 = (MiScaleV2Packet_t*)_buf;
|
|
uint8_t weight_stabilized = 0;
|
|
uint8_t weight_removed = 0;
|
|
uint8_t impedance_stabilized = 0;
|
|
|
|
// Mi Scale V1
|
|
if (length == 10 && UUID == 0x181d) { // 14-1-1-2
|
|
weight_stabilized = (_packetV1->status & (1 << 5)) ? 1 : 0;
|
|
weight_removed = (_packetV1->status & (1 << 7)) ? 1 : 0;
|
|
if (!MI32.option.directBridgeMode && (!weight_stabilized || weight_removed))
|
|
return;
|
|
|
|
uint32_t _slot = MIBLEgetSensorSlot(addr, UUID, 0);
|
|
if (_slot==0xff) return;
|
|
|
|
if ((_slot >= 0) && (_slot < MIBLEsensors.size())) {
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s: at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot);
|
|
MIBLEsensors[_slot].RSSI = RSSI;
|
|
MIBLEsensors[_slot].needkey = KEY_NOT_REQUIRED;
|
|
MIBLEsensors[_slot].eventType.scale = 1;
|
|
|
|
MIBLEsensors[_slot].weight_stabilized = weight_stabilized;
|
|
MIBLEsensors[_slot].weight_removed = weight_removed;
|
|
|
|
if (_packetV1->status & (1 << 0)) {
|
|
strcpy(MIBLEsensors[_slot].weight_unit, PSTR("lbs"));
|
|
MIBLEsensors[_slot].weight = (float)_packetV1->weight / 100.0f;
|
|
} else if(_packetV1->status & (1 << 4)) {
|
|
strcpy(MIBLEsensors[_slot].weight_unit, PSTR("jin"));
|
|
MIBLEsensors[_slot].weight = (float)_packetV1->weight / 100.0f;
|
|
} else {
|
|
strcpy(MIBLEsensors[_slot].weight_unit, PSTR("kg"));
|
|
MIBLEsensors[_slot].weight = (float)_packetV1->weight / 200.0f;
|
|
}
|
|
|
|
if (MIBLEsensors[_slot].weight_removed) {
|
|
MIBLEsensors[_slot].weight = 0.0f;
|
|
}
|
|
|
|
if(MI32.option.directBridgeMode) {
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mi Scale V2
|
|
else if (length == 13 && UUID == 0x181b) { // 17-1-1-2
|
|
weight_stabilized = (_packetV2->status & (1 << 5)) ? 1 : 0;
|
|
weight_removed = (_packetV2->status & (1 << 7)) ? 1 : 0;
|
|
impedance_stabilized = (_packetV2->status & (1 << 1)) ? 1 : 0;
|
|
if (!MI32.option.directBridgeMode && (!weight_stabilized || weight_removed /* || !impedance_stabilized */))
|
|
return;
|
|
|
|
uint32_t _slot = MIBLEgetSensorSlot(addr, UUID, 0);
|
|
if (_slot==0xff) return;
|
|
|
|
if ((_slot >= 0) && (_slot < MIBLEsensors.size())) {
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s: at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot);
|
|
MIBLEsensors[_slot].RSSI = RSSI;
|
|
MIBLEsensors[_slot].needkey = KEY_NOT_REQUIRED;
|
|
MIBLEsensors[_slot].eventType.scale = 1;
|
|
|
|
MIBLEsensors[_slot].weight_stabilized = weight_stabilized;
|
|
MIBLEsensors[_slot].weight_removed = weight_removed;
|
|
MIBLEsensors[_slot].impedance_stabilized = impedance_stabilized;
|
|
MIBLEsensors[_slot].impedance = _packetV2->impedance;
|
|
|
|
if (_packetV2->weight_unit & (1 << 4)) {
|
|
strcpy(MIBLEsensors[_slot].weight_unit, PSTR("jin"));
|
|
MIBLEsensors[_slot].weight = (float)_packetV2->weight / 100.0f;
|
|
} else if(_packetV2->weight_unit == 3) {
|
|
strcpy(MIBLEsensors[_slot].weight_unit, PSTR("lbs"));
|
|
MIBLEsensors[_slot].weight = (float)_packetV2->weight / 100.0f;
|
|
} else if(_packetV2->weight_unit == 2) {
|
|
strcpy(MIBLEsensors[_slot].weight_unit, PSTR("kg"));
|
|
MIBLEsensors[_slot].weight = (float)_packetV2->weight / 200.0f;
|
|
} else {
|
|
strcpy(MIBLEsensors[_slot].weight_unit, PSTR(""));
|
|
MIBLEsensors[_slot].weight = (float)_packetV2->weight / 100.0f;
|
|
}
|
|
|
|
if (MIBLEsensors[_slot].weight_removed) {
|
|
MIBLEsensors[_slot].weight = 0.0f;
|
|
MIBLEsensors[_slot].impedance = 0;
|
|
}
|
|
|
|
if(MI32.option.directBridgeMode) {
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// this SHOULD parse any MI payload.
|
|
int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){
|
|
struct mi_beacon_data_payload_data_t *pld =
|
|
(struct mi_beacon_data_payload_data_t *) &parsed->payload.data;
|
|
int res = 1;
|
|
|
|
if (!parsed->payloadpresent){
|
|
return 0;
|
|
}
|
|
|
|
char tmp[20];
|
|
BLE_ESP32::dump(tmp, 20, (uint8_t*)&(parsed->payload), parsed->payload.size+3);
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: MI%d payload %s"), _slot, tmp);
|
|
|
|
// clear this for every payload
|
|
MIBLEsensors[_slot].pairing = 0;
|
|
MIBLEsensors[_slot].eventType.PairBtn = 0;
|
|
|
|
//https://iot.mi.com/new/doc/embedded-development/ble/object-definition
|
|
|
|
switch(parsed->payload.type){
|
|
case 0x0002: // related to pair button? 'easypairing'
|
|
MIBLEsensors[_slot].pairing = 1;
|
|
MIBLEsensors[_slot].eventType.PairBtn = 1;
|
|
MIBLEsensors[_slot].feature.pairing = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
break;
|
|
case 0x0003: {// motion? 1 byte 'near'
|
|
uint8_t motion = parsed->payload.data[0];
|
|
res = 0;
|
|
}break;
|
|
case 0x000f: // 'Someone is moving (with light)'
|
|
MIBLEsensors[_slot].eventType.motion = 1;
|
|
MIBLEsensors[_slot].lastTime = millis();
|
|
MIBLEsensors[_slot].events++;
|
|
MIBLEsensors[_slot].lux = pld->lux;
|
|
MIBLEsensors[_slot].eventType.lux = 1;
|
|
MIBLEsensors[_slot].NMT = 0;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MIBLEsensors[_slot].feature.lux = 1;
|
|
MIBLEsensors[_slot].feature.NMT = 1;
|
|
MIBLEsensors[_slot].feature.events=1;
|
|
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: PIR: primary"),MIBLEsensors[_slot].lux );
|
|
break;
|
|
|
|
|
|
|
|
case 0x1001: // button press
|
|
MIBLEsensors[_slot].Btn = pld->Btn.num + (pld->Btn.longPress/2)*6;
|
|
MIBLEsensors[_slot].feature.Btn = 1;
|
|
MIBLEsensors[_slot].eventType.Btn = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
break;
|
|
//case 0x1002: // 'sleep'
|
|
//case 0x1003: // 'RSSI'
|
|
|
|
case 0x1004:{ // 'temperature'
|
|
float _tempFloat=(float)(pld->temp)/10.0f;
|
|
if(_tempFloat<60){
|
|
MIBLEsensors[_slot].temp=_tempFloat;
|
|
MIBLEsensors[_slot].feature.temp = 1;
|
|
MIBLEsensors[_slot].eventType.temp = 1;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode 4: temp updated"));
|
|
} else {
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode 4: temp ignored > 60 (%f)"), _tempFloat);
|
|
}
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 4: U16: %u Temp"), _beacon.temp );
|
|
} break;
|
|
// 0x1005 - not documented
|
|
case 0x1006: { // 'humidity'
|
|
float _tempFloat=(float)(pld->hum)/10.0f;
|
|
if(_tempFloat<101){
|
|
MIBLEsensors[_slot].hum=_tempFloat;
|
|
MIBLEsensors[_slot].feature.hum = 1;
|
|
MIBLEsensors[_slot].eventType.hum = 1;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode 6: hum updated"));
|
|
} else {
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode 6: hum ignored > 101 (%f)"), _tempFloat);
|
|
}
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 6: U16: %u Hum"), _beacon.hum);
|
|
} break;
|
|
case 0x1007: // 'Light illuminance'
|
|
MIBLEsensors[_slot].lux=pld->lux & 0x00ffffff;
|
|
if(MIBLEsensors[_slot].type==MI_MJYD2S){
|
|
MIBLEsensors[_slot].eventType.noMotion = 1;
|
|
}
|
|
MIBLEsensors[_slot].eventType.lux = 1;
|
|
MIBLEsensors[_slot].feature.lux = 1;
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 7: U24: %u Lux"), _beacon.lux & 0x00ffffff);
|
|
break;
|
|
case 0x1008: //'Soil moisture'
|
|
MIBLEsensors[_slot].moisture=pld->moist;
|
|
MIBLEsensors[_slot].eventType.moist = 1;
|
|
MIBLEsensors[_slot].feature.moist = 1;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode 8: moisture updated"));
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 8: U8: %u Moisture"), _beacon.moist);
|
|
break;
|
|
case 0x1009: // 'conductivity' / 'Soil EC value'
|
|
MIBLEsensors[_slot].fertility=pld->fert;
|
|
MIBLEsensors[_slot].eventType.fert = 1;
|
|
MIBLEsensors[_slot].feature.fert = 1;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode 9: fertility updated"));
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 9: U16: %u Fertility"), _beacon.fert);
|
|
break;
|
|
case 0x100a:// 'Electricity'
|
|
if(MI32.option.ignoreBogusBattery){
|
|
if(MIBLEsensors[_slot].type==MI_LYWSD03MMC || MIBLEsensors[_slot].type==MI_MHOC401){
|
|
res = 0;
|
|
break;
|
|
}
|
|
}
|
|
MIBLEsensors[_slot].feature.bat = 1;
|
|
if(pld->bat<101){
|
|
MIBLEsensors[_slot].bat = pld->bat;
|
|
MIBLEsensors[_slot].eventType.bat = 1;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode a: bat updated"));
|
|
} else {
|
|
MIBLEsensors[_slot].bat = 100;
|
|
MIBLEsensors[_slot].eventType.bat = 1;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode a: bat > 100 (%d)"), pld->bat);
|
|
}
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode a: U8: %u %%"), _beacon.bat);
|
|
break;
|
|
// 100b-100d -> undefioend in docs.
|
|
case 0x100d:{ // is this right????
|
|
MIBLEsensors[_slot].feature.tempHum = 1;
|
|
float _tempFloat=(float)(pld->HT.temp)/10.0f;
|
|
if(_tempFloat < 60){
|
|
MIBLEsensors[_slot].temp = _tempFloat;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode d: temp updated"));
|
|
} else {
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode d: temp ignored > 60 (%f)"), _tempFloat);
|
|
}
|
|
_tempFloat=(float)(pld->HT.hum)/10.0f;
|
|
if(_tempFloat < 100){
|
|
MIBLEsensors[_slot].hum = _tempFloat;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode d: hum updated"));
|
|
} else {
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Mode d: hum ignored > 100 (%f)"), _tempFloat);
|
|
}
|
|
MIBLEsensors[_slot].eventType.tempHum = 1;
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode d: U16: %x Temp U16: %x Hum"), _beacon.HT.temp, _beacon.HT.hum);
|
|
} break;
|
|
// 100e = 'lock'
|
|
// 100f = 'door'
|
|
case 0x1010:{ // 'formaldehide'
|
|
const uint16_t f = uint16_t(parsed->payload.data[0]) | (uint16_t(parsed->payload.data[1]) << 8);
|
|
float formaldehyde = (float)f / 100.0f;
|
|
res = 0;
|
|
} break;
|
|
// 1011 = 'bind'
|
|
case 0x1012:{ // 'switch'
|
|
int active = parsed->payload.data[0];
|
|
res = 0;
|
|
} break;
|
|
case 0x1013:{ // 'Remaining amount of consumables' - mosquito tablet
|
|
int tablet = parsed->payload.data[0];
|
|
res = 0;
|
|
} break;
|
|
//Flooding 0x1014 1 1
|
|
//smoke 0x1015 1 1
|
|
//Gas 0x1016
|
|
case 0x1017:{ // 'No one moves'
|
|
const uint32_t idle_time =
|
|
uint32_t(parsed->payload.data[0]) | (uint32_t(parsed->payload.data[1]) << 8) | (uint32_t(parsed->payload.data[2]) << 16) | (uint32_t(parsed->payload.data[2]) << 24);
|
|
float idlemins = (float)idle_time / 60.0f;
|
|
int has_motion = (idle_time) ? 0 : 0;
|
|
|
|
MIBLEsensors[_slot].NMT = pld->NMT;
|
|
MIBLEsensors[_slot].eventType.NMT = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MIBLEsensors[_slot].feature.NMT = 1;
|
|
|
|
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Mode 17: NMT: %u seconds"), _beacon.NMT);
|
|
} break;
|
|
//Light intensity 0x1018
|
|
case 0x1018:{ //'Light intensity' - 0=dark, 1=light? - MCCGQ02HL
|
|
MIBLEsensors[_slot].light = parsed->payload.data[0];
|
|
MIBLEsensors[_slot].eventType.light = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MIBLEsensors[_slot].feature.light = 1;
|
|
} break;
|
|
case 0x1019:{ //'Door sensor' - 0=open, 1=closed, 2=timeout? - MCCGQ02HL
|
|
MIBLEsensors[_slot].Btn = (uint8_t) parsed->payload.data[0]; // just an 8 bit value in a union.
|
|
MIBLEsensors[_slot].eventType.Btn = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MIBLEsensors[_slot].feature.Btn = 1;
|
|
} break;
|
|
|
|
//Weight attributes 0x101A 600 0
|
|
//No one moves over time 0x101B 1 1
|
|
//Smart pillow 0x101C 60 1
|
|
//Formaldehyde (new) 0x101D
|
|
|
|
default: {
|
|
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Unknown MI pld type %x %s"), parsed->payload.type, tmp);
|
|
res = 0;
|
|
} break;
|
|
}
|
|
|
|
if(res && MI32.option.directBridgeMode) {
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// this SHOULD parse any MI packet, including encrypted.
|
|
void MI32ParseResponse(const uint8_t *buf, uint16_t bufsize, const uint8_t* addr, int RSSI) {
|
|
struct mi_beacon_data_t parsed;
|
|
memset(&parsed, 0, sizeof(parsed));
|
|
int res = MIParsePacket(addr, &parsed, buf, bufsize);
|
|
|
|
uint8_t addrrev[6];
|
|
memcpy(addrrev, addr, 6);
|
|
MI32_ReverseMAC(addrrev);
|
|
|
|
if (memcmp(addrrev, parsed.macdata.mac, 6)){
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: MI packet with MAC addr mismatch - is this mesh?"));
|
|
memcpy(addrrev, parsed.macdata.mac, 6);
|
|
MI32_ReverseMAC(addrrev);
|
|
addr = addrrev;
|
|
}
|
|
|
|
uint16_t _slot = MIBLEgetSensorSlot( addr, parsed.devicetype, parsed.framecnt );
|
|
if(_slot==0xff) return;
|
|
if ((_slot >= 0) && (_slot < MIBLEsensors.size())){
|
|
if (parsed.needkey != KEY_REQUIREMENT_UNKNOWN){
|
|
MIBLEsensors[_slot].needkey = parsed.needkey;
|
|
}
|
|
MIBLEsensors[_slot].RSSI=RSSI;
|
|
if (!res){ // - if the payload is not valid
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: MIParsePacket returned %d"), res);
|
|
return;
|
|
} else {
|
|
}
|
|
MI32parseMiPayload(_slot, &parsed);
|
|
}
|
|
}
|
|
|
|
bool MI32isInBlockList(const uint8_t* MAC){
|
|
bool isBlocked = false;
|
|
for(auto &_blockedMAC : MIBLEBlockList){
|
|
if(memcmp(_blockedMAC.buf,MAC,6) == 0) isBlocked = true;
|
|
}
|
|
return isBlocked;
|
|
}
|
|
|
|
void MI32removeMIBLEsensor(uint8_t* MAC){
|
|
// this will take and keep the mutex until the function is over
|
|
TasAutoMutex localmutex(&slotmutex, "Mi32Rem");
|
|
|
|
MIBLEsensors.erase( std::remove_if( MIBLEsensors.begin() , MIBLEsensors.end(), [MAC]( mi_sensor_t _sensor )->bool
|
|
{ return (memcmp(_sensor.MAC,MAC,6) == 0); }
|
|
), end( MIBLEsensors ) );
|
|
}
|
|
/***********************************************************************\
|
|
* Read data from connections
|
|
\***********************************************************************/
|
|
|
|
void MI32notifyHT_LY(int slot, char *_buf, int len){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: %s: raw data: %x%x%x%x%x%x%x"),D_CMND_MI32,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]);
|
|
// the value 0b00 is 28.16 C?
|
|
if(_buf[0] != 0 || _buf[1] != 0){
|
|
memcpy(&LYWSD0x_HT,(void *)_buf,sizeof(LYWSD0x_HT));
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: %s: T * 100: %u, H: %u, V: %u"),D_CMND_MI32,LYWSD0x_HT.temp,LYWSD0x_HT.hum, LYWSD0x_HT.volt);
|
|
uint32_t _slot = slot;
|
|
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: MIBLE: Sensor slot: %u"), _slot);
|
|
static float _tempFloat;
|
|
_tempFloat=(float)(LYWSD0x_HT.temp)/100.0f;
|
|
if(_tempFloat<60){
|
|
MIBLEsensors[_slot].temp=_tempFloat;
|
|
// MIBLEsensors[_slot].showedUp=255; // this sensor is real
|
|
}
|
|
_tempFloat=(float)LYWSD0x_HT.hum;
|
|
if(_tempFloat<100){
|
|
MIBLEsensors[_slot].hum = _tempFloat;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: LYWSD0x: hum updated"));
|
|
}
|
|
MIBLEsensors[_slot].eventType.tempHum = 1;
|
|
if (MIBLEsensors[_slot].type == MI_LYWSD03MMC || MIBLEsensors[_slot].type == MI_MHOC401){
|
|
// ok, so CR2032 is 3.0v, but drops immediately to ~2.9.
|
|
// so we'll go with the 2.1 min, 2.95 max.
|
|
float minVolts = 2100.0;
|
|
//float maxVolts = 2950.0;
|
|
//float range = maxVolts - minVolts;
|
|
//float divisor = range/100; // = 8.5
|
|
float percent = (((float)LYWSD0x_HT.volt) - minVolts)/ 8.5; //divisor;
|
|
if (percent > 100) percent = 100;
|
|
|
|
MIBLEsensors[_slot].bat = (int) percent;
|
|
MIBLEsensors[_slot].eventType.bat = 1;
|
|
}
|
|
if(MI32.option.directBridgeMode) {
|
|
MIBLEsensors[_slot].shallSendMQTT = 1;
|
|
MI32.mode.shallTriggerTele = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Launch functions from Core 1 to make race conditions less likely
|
|
*
|
|
*/
|
|
|
|
void MI32Every50mSecond(){
|
|
|
|
if(MI32.mode.shallTriggerTele){
|
|
MI32.mode.shallTriggerTele = 0;
|
|
MI32triggerTele();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Main loop of the driver, "high level"-loop
|
|
*
|
|
*/
|
|
|
|
void MI32EverySecond(bool restart){
|
|
|
|
// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("M32: onesec"));
|
|
MI32TimeoutSensors();
|
|
|
|
if (MI32.option.MQTTType == 0){
|
|
// show tas style sensor MQTT
|
|
MI32ShowSomeSensors();
|
|
}
|
|
|
|
if (MI32.option.MQTTType == 1
|
|
#ifdef USE_HOME_ASSISTANT
|
|
||
|
|
Settings->flag.hass_discovery
|
|
#endif
|
|
) {
|
|
// these two share a counter
|
|
// discovery only sent if hass_discovery
|
|
MI32DiscoveryOneMISensor();
|
|
// show independent style sensor MQTT
|
|
// note - if !MQTTType, then this is IN ADDITION to 'normal'
|
|
MI32ShowOneMISensor();
|
|
}
|
|
|
|
// read a battery if
|
|
// MI32.batteryreader.slot < filled and !MI32.batteryreader.active
|
|
readOneBat();
|
|
|
|
|
|
// read a sensor if
|
|
// MI32.sensorreader.slot < filled and !MI32.sensorreader.active
|
|
// for sensors which need to get data through notify...
|
|
readOneSensor();
|
|
|
|
if (MI32.secondsCounter >= MI32.period){
|
|
// only if we finished the last read
|
|
if (MI32.sensorreader.slot >= MIBLEsensors.size()){
|
|
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Kick off readOneSensor"));
|
|
// kick off notification sensor reading every period.
|
|
MI32.sensorreader.slot = 0;
|
|
MI32.secondsCounter = 0;
|
|
}
|
|
}
|
|
MI32.secondsCounter ++;
|
|
|
|
if (MI32.secondsCounter2 >= MI32.period){
|
|
if (MI32.mqttCurrentSlot >= MIBLEsensors.size()){
|
|
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Kick off tele sending"));
|
|
MI32.mqttCurrentSlot = 0;
|
|
MI32.secondsCounter2 = 0;
|
|
MI32.mqttCurrentSingleSlot = 0;
|
|
} else {
|
|
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Hit tele time, restarted but not finished last - lost from slot %d")+MI32.mqttCurrentSlot);
|
|
MI32.mqttCurrentSlot = 0;
|
|
MI32.secondsCounter2 = 0;
|
|
MI32.mqttCurrentSingleSlot = 0;
|
|
}
|
|
}
|
|
MI32.secondsCounter2++;
|
|
|
|
static uint32_t _counter = MI32.period - 15;
|
|
static uint32_t _nextSensorSlot = 0;
|
|
uint32_t _idx = 0;
|
|
|
|
int numsensors = MIBLEsensors.size();
|
|
for (uint32_t i = 0; i < numsensors; i++) {
|
|
if(MIBLEsensors[i].type==MI_NLIGHT || MIBLEsensors[i].type==MI_MJYD2S){
|
|
MIBLEsensors[i].NMT++;
|
|
}
|
|
}
|
|
|
|
if(MI32.mode.shallShowStatusInfo == 1){
|
|
MI32StatusInfo();
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* Commands
|
|
\*********************************************************************************************/
|
|
|
|
void CmndMi32Period(void) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (1 == XdrvMailbox.payload) {
|
|
MI32EverySecond(true);
|
|
} else {
|
|
MI32.period = XdrvMailbox.payload;
|
|
}
|
|
}
|
|
ResponseCmndNumber(MI32.period);
|
|
}
|
|
|
|
int findSlot(char *addrOrAlias){
|
|
uint8_t mac[7];
|
|
int res = BLE_ESP32::getAddr(mac, addrOrAlias);
|
|
if (!res) return -1;
|
|
|
|
for (int i = MIBLEsensors.size()-1; i >= 0 ; i--) {
|
|
if (!memcmp(MIBLEsensors[i].MAC, mac, 6)){
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
void CmndMi32Time(void) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
int slot = findSlot(XdrvMailbox.data);
|
|
if (slot < 0) {
|
|
slot = XdrvMailbox.payload;
|
|
}
|
|
if (MIBLEsensors.size() > slot) {
|
|
int res = genericTimeWriteFn(slot);
|
|
if (res > 0){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: will set Time"));
|
|
ResponseCmndNumber(slot);
|
|
return;
|
|
}
|
|
if (res < 0) {
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: cannot set Time on sensor type"));
|
|
}
|
|
if (res == 0) {
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: cannot set Time right now"));
|
|
}
|
|
}
|
|
}
|
|
ResponseCmndChar_P("fail");
|
|
}
|
|
|
|
void CmndMi32Page(void) {
|
|
if (XdrvMailbox.payload > 0) {
|
|
MI32.perPage = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(MI32.perPage);
|
|
}
|
|
|
|
// read ALL battery values where we can?
|
|
void CmndMi32Battery(void) {
|
|
// trigger a read cycle
|
|
MI32.batteryreader.slot = 0;
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
|
|
void CmndMi32Unit(void) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
int slot = findSlot(XdrvMailbox.data);
|
|
if (slot < 0) {
|
|
slot = XdrvMailbox.payload;
|
|
}
|
|
|
|
if (MIBLEsensors.size() > slot) {
|
|
// TOGGLE unit?
|
|
int res = genericUnitWriteFn(slot, -1);
|
|
if (res > 0){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG, PSTR("M32: will toggle Unit"));
|
|
ResponseCmndNumber(slot);
|
|
return;
|
|
}
|
|
if (res < 0) {
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: cannot toggle Unit on sensor type"));
|
|
}
|
|
if (res == 0) {
|
|
AddLog(LOG_LEVEL_ERROR, PSTR("M32: cannot toggle Unit right now"));
|
|
}
|
|
}
|
|
}
|
|
ResponseCmndIdxChar(PSTR("Invalid"));
|
|
}
|
|
|
|
#ifdef USE_MI_DECRYPTION
|
|
void CmndMi32Key(void) {
|
|
if (44 == XdrvMailbox.data_len) { // a KEY-MAC-string
|
|
MI32AddKey(XdrvMailbox.data, nullptr);
|
|
MI32KeyListResp();
|
|
} else {
|
|
ResponseCmndIdxChar(PSTR("Invalid"));
|
|
}
|
|
}
|
|
#endif // USE_MI_DECRYPTION
|
|
|
|
void MI32BlockListResp(){
|
|
Response_P(PSTR("{\"MI32Block\":{"));
|
|
for (int i = 0; i < MIBLEBlockList.size(); i++){
|
|
if (i){
|
|
ResponseAppend_P(PSTR(","));
|
|
}
|
|
char tmp[20];
|
|
ToHex_P(MIBLEBlockList[i].buf,6,tmp,20,0);
|
|
ResponseAppend_P(PSTR("\"%s\":1"), tmp);
|
|
}
|
|
ResponseAppend_P(PSTR("}}"));
|
|
}
|
|
|
|
|
|
void CmndMi32Block(void){
|
|
if (XdrvMailbox.data_len == 0) {
|
|
switch (XdrvMailbox.index) {
|
|
case 0: {
|
|
//TasAutoMutex localmutex(&slotmutex, "Mi32Block1");
|
|
MIBLEBlockList.clear();
|
|
} break;
|
|
default:
|
|
case 1:
|
|
break;
|
|
}
|
|
MI32BlockListResp();
|
|
return;
|
|
}
|
|
|
|
MAC_t _MACasBytes;
|
|
int res = BLE_ESP32::getAddr(_MACasBytes.buf, XdrvMailbox.data);
|
|
if (!res){
|
|
ResponseCmndIdxChar(PSTR("Addr invalid"));
|
|
return;
|
|
}
|
|
|
|
//MI32HexStringToBytes(XdrvMailbox.data,_MACasBytes.buf);
|
|
switch (XdrvMailbox.index) {
|
|
case 0: {
|
|
//TasAutoMutex localmutex(&slotmutex, "Mi32Block2");
|
|
MIBLEBlockList.erase( std::remove_if( begin( MIBLEBlockList ), end( MIBLEBlockList ), [_MACasBytes]( MAC_t& _entry )->bool
|
|
{ return (memcmp(_entry.buf,_MACasBytes.buf,6) == 0); }
|
|
), end( MIBLEBlockList ) );
|
|
} break;
|
|
case 1: {
|
|
//TasAutoMutex localmutex(&slotmutex, "Mi32Block3");
|
|
bool _notYetInList = true;
|
|
for (auto &_entry : MIBLEBlockList) {
|
|
if (memcmp(_entry.buf,_MACasBytes.buf,6) == 0){
|
|
_notYetInList = false;
|
|
}
|
|
}
|
|
if (_notYetInList) {
|
|
MIBLEBlockList.push_back(_MACasBytes);
|
|
MI32removeMIBLEsensor(_MACasBytes.buf);
|
|
}
|
|
// AddLog(LOG_LEVEL_INFO,PSTR("M32: size of ilist: %u"), MIBLEBlockList.size());
|
|
} break;
|
|
}
|
|
MI32BlockListResp();
|
|
}
|
|
|
|
void CmndMi32Option(void){
|
|
bool set = false;
|
|
if (strlen(XdrvMailbox.data)){
|
|
set = true;
|
|
}
|
|
int onOff = atoi(XdrvMailbox.data);
|
|
switch(XdrvMailbox.index) {
|
|
case 0:
|
|
if (set){
|
|
MI32.option.allwaysAggregate = onOff;
|
|
} else {
|
|
onOff = MI32.option.allwaysAggregate;
|
|
}
|
|
break;
|
|
case 1:
|
|
if (set){
|
|
MI32.option.noSummary = onOff;
|
|
} else {
|
|
onOff = MI32.option.noSummary;
|
|
}
|
|
break;
|
|
case 2:
|
|
if (set){
|
|
MI32.option.directBridgeMode = onOff;
|
|
} else {
|
|
onOff = MI32.option.directBridgeMode;
|
|
}
|
|
break;
|
|
case 4:{
|
|
if (set){
|
|
MI32.option.ignoreBogusBattery = onOff;
|
|
} else {
|
|
onOff = MI32.option.ignoreBogusBattery;
|
|
}
|
|
} break;
|
|
case 5:{
|
|
if (set){
|
|
MI32.option.onlyAliased = onOff;
|
|
if (MI32.option.onlyAliased){
|
|
// discard all sensors for a restart
|
|
MIBLEsensors.clear();
|
|
}
|
|
} else {
|
|
onOff = MI32.option.onlyAliased;
|
|
}
|
|
} break;
|
|
case 6:{
|
|
if (set){
|
|
MI32.option.MQTTType = onOff;
|
|
} else {
|
|
onOff = MI32.option.MQTTType;
|
|
}
|
|
} break;
|
|
default:{
|
|
ResponseCmndIdxError();
|
|
return;
|
|
} break;
|
|
}
|
|
ResponseCmndIdxNumber(onOff);
|
|
return;
|
|
}
|
|
|
|
void MI32KeyListResp(){
|
|
Response_P(PSTR("{\"MIKeys\":{"));
|
|
for (int i = 0; i < MIBLEbindKeys.size(); i++){
|
|
if (i){
|
|
ResponseAppend_P(PSTR(","));
|
|
}
|
|
char tmp[20];
|
|
ToHex_P(MIBLEbindKeys[i].MAC,6,tmp,20,0);
|
|
char key[16*2+1];
|
|
ToHex_P(MIBLEbindKeys[i].key,16,key,33,0);
|
|
|
|
ResponseAppend_P(PSTR("\"%s\":\"%s\""), tmp, key);
|
|
}
|
|
ResponseAppend_P(PSTR("}}"));
|
|
}
|
|
|
|
|
|
void CmndMi32Keys(void){
|
|
#ifdef BLE_ESP32_ALIASES
|
|
int op = XdrvMailbox.index;
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Key %d %s"), op, XdrvMailbox.data);
|
|
|
|
int res = -1;
|
|
switch(op){
|
|
case 0:
|
|
case 1:{
|
|
char *p = strtok(XdrvMailbox.data, " ,=");
|
|
bool trigger = false;
|
|
int added = 0;
|
|
|
|
do {
|
|
if (!p || !(*p)){
|
|
break;
|
|
}
|
|
|
|
uint8_t addr[7];
|
|
char *mac = p;
|
|
int addrres = BLE_ESP32::getAddr(addr, p);
|
|
if (!addrres){
|
|
ResponseCmndChar("invalidmac");
|
|
return;
|
|
}
|
|
|
|
p = strtok(nullptr, " ,=");
|
|
char *key = p;
|
|
if (!p || !(*p)){
|
|
int i = 0;
|
|
for (i = 0; i < MIBLEbindKeys.size(); i++){
|
|
mi_bindKey_t *key = &MIBLEbindKeys[i];
|
|
if (!memcmp(key->MAC, addr, 6)){
|
|
MIBLEbindKeys.erase(MIBLEbindKeys.begin() + i);
|
|
MI32KeyListResp();
|
|
return;
|
|
}
|
|
}
|
|
ResponseCmndChar("invalidmac");
|
|
return;
|
|
}
|
|
|
|
AddLog(LOG_LEVEL_ERROR,PSTR("M32: Add key mac %s = key %s"), mac, key);
|
|
char tmp[20];
|
|
// convert mac back to string
|
|
ToHex_P(addr,6,tmp,20,0);
|
|
if (MI32AddKey(tmp, key)){
|
|
added++;
|
|
}
|
|
p = strtok(nullptr, " ,=");
|
|
} while (p);
|
|
|
|
if (added){
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Added %d Keys"), added);
|
|
MI32KeyListResp();
|
|
} else {
|
|
MI32KeyListResp();
|
|
}
|
|
return;
|
|
} break;
|
|
case 2:{ // clear
|
|
if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Keys clearing %d"), MIBLEbindKeys.size());
|
|
for (int i = MIBLEbindKeys.size()-1; i >= 0; i--){
|
|
MIBLEbindKeys.pop_back();
|
|
}
|
|
MI32KeyListResp();
|
|
return;
|
|
} break;
|
|
}
|
|
ResponseCmndChar("invalididx");
|
|
#endif
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
* Presentation
|
|
\*********************************************************************************************/
|
|
|
|
const char HTTP_MI32[] PROGMEM = "{s}MI ESP32 v0921{m}%u%s / %u{e}";
|
|
const char HTTP_MI32_ALIAS[] PROGMEM = "{s}%s Alias {m}%s{e}";
|
|
const char HTTP_MI32_MAC[] PROGMEM = "{s}%s %s{m}%s{e}";
|
|
const char HTTP_RSSI[] PROGMEM = "{s}%s " D_RSSI "{m}%d dBm{e}";
|
|
const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u %%{e}";
|
|
const char HTTP_LASTBUTTON[] PROGMEM = "{s}%s Last Button{m}%u {e}";
|
|
const char HTTP_EVENTS[] PROGMEM = "{s}%s Events{m}%u {e}";
|
|
const char HTTP_NMT[] PROGMEM = "{s}%s No motion{m}> %u seconds{e}";
|
|
const char HTTP_MI32_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%u us/cm{e}";
|
|
const char HTTP_MI32_HL[] PROGMEM = "{s}<hr>{m}<hr>{e}";
|
|
const char HTTP_MI32_LIGHT[] PROGMEM = "{s}%s" " Light" "{m}%d{e}";
|
|
const char HTTP_MISCALE_WEIGHT[] PROGMEM = "{s}%s" " Weight" "{m}%*_f %s{e}";
|
|
const char HTTP_MISCALE_WEIGHT_REMOVED[] PROGMEM = "{s}%s" " Weight removed" "{m}%s{e}";
|
|
const char HTTP_MISCALE_WEIGHT_STABILIZED[] PROGMEM = "{s}%s" " Weight stabilized" "{m}%s{e}";
|
|
const char HTTP_MISCALE_IMPEDANCE[] PROGMEM = "{s}%s" " Impedance" "{m}%u{e}";
|
|
const char HTTP_MISCALE_IMPEDANCE_STABILIZED[] PROGMEM = "{s}%s" " Impedance stabilized" "{m}%s{e}";
|
|
|
|
//const char HTTP_NEEDKEY[] PROGMEM = "{s}%s <a target=\"_blank\" href=\""
|
|
// "https://atc1441.github.io/TelinkFlasher.html?mac=%s&cb=http%%3A%%2F%%2F%s%%2Fmikey"
|
|
// "\">%s</a>{m} {e}";
|
|
|
|
//const char HTTP_NEEDKEY[] PROGMEM = "{s}%s <a target=\"_blank\" href=\""
|
|
// "http://127.0.0.1:8887/keys/TelinkFlasher.html?mac=%s&cb=http%%3A%%2F%%2F%s%%2Fmikey"
|
|
// "\">%s</a>{m} {e}";
|
|
const char HTTP_NEEDKEY[] PROGMEM = "{s}%s <a target=\"_blank\" href=\""
|
|
"https://tasmota.github.io/ble_key_extractor?mac=%s&cb=http%%3A%%2F%%2F%s%%2Fmikey"
|
|
"\">%s</a>{m} {e}";
|
|
|
|
|
|
const char HTTP_PAIRING[] PROGMEM = "{s}%s Pair Button Pressed{m} {e}";
|
|
|
|
|
|
const char HTTP_KEY_ERROR[] PROGMEM = "Key error %s";
|
|
const char HTTP_MAC_ERROR[] PROGMEM = "MAC error %s";
|
|
const char HTTP_KEY_ADDED[] PROGMEM = "Cmnd: MI32Keys %s=%s";
|
|
const char HTTP_MI_KEY_STYLE[] PROGMEM = "";
|
|
|
|
|
|
#define D_MI32_KEY "MI32 Set Key"
|
|
|
|
void HandleMI32Key(){
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: HandleMI32Key hit"));
|
|
if (!HttpCheckPriviledgedAccess()) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: !HttpCheckPriviledgedAccess()"));
|
|
return;
|
|
}
|
|
WSContentStart_P(PSTR(D_MI32_KEY));
|
|
WSContentSendStyle_P(HTTP_MI_KEY_STYLE);
|
|
|
|
char key[64] = {0};
|
|
WebGetArg("key", key, sizeof(key));
|
|
|
|
if (strlen(key) != 16*2){
|
|
WSContentSend_P(HTTP_KEY_ERROR, key);
|
|
WSContentStop();
|
|
return;
|
|
}
|
|
|
|
char mac[13] = {0};
|
|
WebGetArg("mac", mac, sizeof(mac));
|
|
if (strlen(mac) != 12){
|
|
WSContentSend_P(HTTP_MAC_ERROR, mac);
|
|
WSContentStop();
|
|
return;
|
|
}
|
|
|
|
WSContentSend_P(HTTP_KEY_ADDED, mac, key);
|
|
|
|
strncat(key, mac, sizeof(key));
|
|
MI32AddKey(key, nullptr);
|
|
|
|
// WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
|
|
void MI32TimeoutSensors(){
|
|
// whatever, this function access all the arrays....
|
|
// so block for as long as it takes.
|
|
|
|
// PROBLEM: when we take this, it hangs the BLE loop.
|
|
// BUT, devicePresent uses the
|
|
// remove devices for which the adverts have timed out
|
|
for (int i = MIBLEsensors.size()-1; i >= 0 ; i--) {
|
|
//if (MIBLEsensors[i].MAC[2] || MIBLEsensors[i].MAC[3] || MIBLEsensors[i].MAC[4] || MIBLEsensors[i].MAC[5]){
|
|
if (!BLE_ESP32::devicePresent(MIBLEsensors[i].MAC)){
|
|
uint8_t *mac = MIBLEsensors[i].MAC;
|
|
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Dev no longer present MAC: %02x%02x%02x%02x%02x%02x"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
TasAutoMutex localmutex(&slotmutex, "Mi32Timeout");
|
|
MIBLEsensors.erase(MIBLEsensors.begin() + i);
|
|
}
|
|
//}
|
|
}
|
|
}
|
|
|
|
|
|
// this assumes that we're adding to a ResponseTime_P
|
|
void MI32GetOneSensorJson(int slot, int hidename){
|
|
mi_sensor_t *p;
|
|
p = &MIBLEsensors[slot];
|
|
|
|
// remove hyphen - make it difficult to configure HASS
|
|
if (!hidename) {
|
|
ResponseAppend_P(PSTR("\"%s%02x%02x%02x\":{"),
|
|
kMI32DeviceType[p->type-1],
|
|
p->MAC[3], p->MAC[4], p->MAC[5]);
|
|
}
|
|
|
|
const char *alias = BLE_ESP32::getAlias(p->MAC);
|
|
if (alias && alias[0]){
|
|
ResponseAppend_P(PSTR("\"alias\":\"%s\","),
|
|
alias);
|
|
}
|
|
|
|
ResponseAppend_P(PSTR("\"mac\":\"%02x%02x%02x%02x%02x%02x\""),
|
|
p->MAC[0], p->MAC[1], p->MAC[2],
|
|
p->MAC[3], p->MAC[4], p->MAC[5]);
|
|
|
|
if((!MI32.mode.triggeredTele && !MI32.option.minimalSummary)||MI32.mode.triggeredTele){
|
|
bool tempHumSended = false;
|
|
if(p->feature.tempHum){
|
|
if(p->eventType.tempHum || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
|
|
if (!isnan(p->hum) && !isnan(p->temp)
|
|
#ifdef USE_HOME_ASSISTANT
|
|
||(hass_mode!=-1)
|
|
#endif //USE_HOME_ASSISTANT
|
|
) {
|
|
ResponseAppend_P(PSTR(","));
|
|
ResponseAppendTHD(p->temp, p->hum);
|
|
tempHumSended = true;
|
|
}
|
|
}
|
|
}
|
|
if(p->feature.temp && !tempHumSended){
|
|
if(p->eventType.temp || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate) {
|
|
if (!isnan(p->temp)
|
|
#ifdef USE_HOME_ASSISTANT
|
|
||(hass_mode!=-1)
|
|
#endif //USE_HOME_ASSISTANT
|
|
) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%*_f"),
|
|
Settings->flag2.temperature_resolution, &p->temp);
|
|
}
|
|
}
|
|
}
|
|
if(p->feature.hum && !tempHumSended){
|
|
if(p->eventType.hum || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate) {
|
|
if (!isnan(p->hum)
|
|
#ifdef USE_HOME_ASSISTANT
|
|
||(hass_mode!=-1)
|
|
#endif //USE_HOME_ASSISTANT
|
|
) {
|
|
char hum[FLOATSZ];
|
|
dtostrfd(p->hum, Settings->flag2.humidity_resolution, hum);
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_HUMIDITY "\":%s"), hum);
|
|
}
|
|
}
|
|
}
|
|
if (p->feature.lux){
|
|
if(p->eventType.lux || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
|
|
#ifdef USE_HOME_ASSISTANT
|
|
if ((hass_mode != -1) && (p->lux == 0x0ffffff)) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_ILLUMINANCE "\":null"));
|
|
} else
|
|
#endif //USE_HOME_ASSISTANT
|
|
if ((p->lux != 0x0ffffff)
|
|
#ifdef USE_HOME_ASSISTANT
|
|
|| (hass_mode != -1)
|
|
#endif //USE_HOME_ASSISTANT
|
|
) { // this is the error code -> no lux
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_ILLUMINANCE "\":%u"), p->lux);
|
|
}
|
|
}
|
|
}
|
|
if (p->feature.light){
|
|
if(p->eventType.light || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate
|
|
#ifdef USE_HOME_ASSISTANT
|
|
||(hass_mode==2)
|
|
#endif //USE_HOME_ASSISTANT
|
|
){
|
|
ResponseAppend_P(PSTR(",\"Light\":%d"), p->light);
|
|
}
|
|
}
|
|
if (p->feature.moist){
|
|
if(p->eventType.moist || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
|
|
#ifdef USE_HOME_ASSISTANT
|
|
if ((hass_mode != -1) && (p->moisture == 0xff)) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_MOISTURE "\":null"));
|
|
} else
|
|
#endif //USE_HOME_ASSISTANT
|
|
if ((p->moisture != 0xff)
|
|
#ifdef USE_HOME_ASSISTANT
|
|
|| (hass_mode != -1)
|
|
#endif //USE_HOME_ASSISTANT
|
|
) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_MOISTURE "\":%u"), p->moisture);
|
|
}
|
|
}
|
|
}
|
|
if (p->feature.fert){
|
|
if(p->eventType.fert || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
|
|
#ifdef USE_HOME_ASSISTANT
|
|
if ((hass_mode != -1) && (p->fertility == 0xffff)) {
|
|
ResponseAppend_P(PSTR(",\"Fertility\":null"));
|
|
} else
|
|
#endif //USE_HOME_ASSISTANT
|
|
if ((p->fertility != 0xffff)
|
|
#ifdef USE_HOME_ASSISTANT
|
|
|| (hass_mode != -1)
|
|
#endif //USE_HOME_ASSISTANT
|
|
) {
|
|
ResponseAppend_P(PSTR(",\"Fertility\":%u"), p->fertility);
|
|
}
|
|
}
|
|
}
|
|
if (p->feature.scale){
|
|
if(p->eventType.scale || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate
|
|
#ifdef USE_HOME_ASSISTANT
|
|
||(hass_mode==2)
|
|
#endif //USE_HOME_ASSISTANT
|
|
){
|
|
if (MI32.option.directBridgeMode) {
|
|
ResponseAppend_P(PSTR(",\"WeightRemoved\":%u"), p->weight_removed);
|
|
ResponseAppend_P(PSTR(",\"WeightStabilized\":%u"), p->weight_stabilized);
|
|
}
|
|
ResponseAppend_P(PSTR(",\"WeightUnit\":\"%s\""), p->weight_unit);
|
|
ResponseAppend_P(PSTR(",\"Weight\":%*_f"),
|
|
Settings->flag2.weight_resolution, &p->weight);
|
|
if (p->feature.impedance) {
|
|
if (MI32.option.directBridgeMode) {
|
|
ResponseAppend_P(PSTR(",\"ImpedanceStabilized\":%u"), p->impedance_stabilized);
|
|
}
|
|
ResponseAppend_P(PSTR(",\"Impedance\":%u"), p->impedance);
|
|
}
|
|
}
|
|
}
|
|
if (p->feature.Btn){
|
|
if(p->eventType.Btn || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate
|
|
#ifdef USE_HOME_ASSISTANT
|
|
||(hass_mode==2)
|
|
#endif //USE_HOME_ASSISTANT
|
|
){
|
|
ResponseAppend_P(PSTR(",\"Btn\":%d"),p->Btn);
|
|
}
|
|
}
|
|
if(p->eventType.PairBtn && p->pairing){
|
|
ResponseAppend_P(PSTR(",\"Pair\":%u"),p->pairing);
|
|
}
|
|
} // minimal summary
|
|
|
|
|
|
if (p->feature.PIR){
|
|
if(p->eventType.motion || !MI32.mode.triggeredTele){
|
|
if(MI32.mode.triggeredTele) ResponseAppend_P(PSTR(",\"PIR\":1")); // only real-time
|
|
ResponseAppend_P(PSTR(",\"Events\":%u"),p->events);
|
|
}
|
|
else if(p->eventType.noMotion && MI32.mode.triggeredTele){
|
|
ResponseAppend_P(PSTR(",\"PIR\":0"));
|
|
}
|
|
}
|
|
|
|
if (p->type == MI_FLORA && !MI32.mode.triggeredTele) {
|
|
if (p->firmware[0] != '\0') { // this is the error code -> no firmware
|
|
ResponseAppend_P(PSTR(",\"Firmware\":\"%s\""), p->firmware);
|
|
}
|
|
}
|
|
|
|
if (p->feature.NMT || !MI32.mode.triggeredTele){
|
|
if(p->eventType.NMT){
|
|
ResponseAppend_P(PSTR(",\"NMT\":%u"), p->NMT);
|
|
}
|
|
}
|
|
if (p->feature.bat){
|
|
if(p->eventType.bat || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){
|
|
#ifdef USE_HOME_ASSISTANT
|
|
if ((hass_mode != -1) && (p->bat == 0x00)) {
|
|
ResponseAppend_P(PSTR(",\"Battery\":null"));
|
|
} else
|
|
#endif //USE_HOME_ASSISTANT
|
|
if ((p->bat != 0x00)
|
|
#ifdef USE_HOME_ASSISTANT
|
|
|| (hass_mode != -1)
|
|
#endif //USE_HOME_ASSISTANT
|
|
) {
|
|
ResponseAppend_P(PSTR(",\"Battery\":%u"), p->bat);
|
|
}
|
|
}
|
|
}
|
|
if (MI32.option.showRSSI) ResponseAppend_P(PSTR(",\"RSSI\":%d"), p->RSSI);
|
|
|
|
if (!hidename) {
|
|
ResponseAppend_P(PSTR("}"));
|
|
}
|
|
p->eventType.raw = 0;
|
|
p->shallSendMQTT = 0;
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
// starts a completely fresh MQTT message.
|
|
// sends up to 4 sensors
|
|
// triggered by setting MI32.mqttCurrentSlot = 0
|
|
void MI32ShowSomeSensors(){
|
|
// don't detect half-added ones here
|
|
int numsensors = MIBLEsensors.size();
|
|
if (MI32.mqttCurrentSlot >= numsensors){
|
|
// if we got to the end of the sensors, then don't send more
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_HOME_ASSISTANT
|
|
bool _noSummarySave = MI32.option.noSummary;
|
|
bool _minimalSummarySave = MI32.option.minimalSummary;
|
|
if(hass_mode==2){
|
|
if(MI32.option.holdBackFirstAutodiscovery){
|
|
if(!MI32.mode.firstAutodiscoveryDone){
|
|
MI32.mode.firstAutodiscoveryDone = 1;
|
|
return;
|
|
}
|
|
}
|
|
MI32.option.noSummary = false;
|
|
MI32.option.minimalSummary = false;
|
|
}
|
|
#endif //USE_HOME_ASSISTANT
|
|
|
|
ResponseTime_P(PSTR(""));
|
|
int cnt = 0;
|
|
int maxcnt = 4;
|
|
mi_sensor_t *p;
|
|
for (; (MI32.mqttCurrentSlot < numsensors) && (cnt < maxcnt); MI32.mqttCurrentSlot++, cnt++) {
|
|
ResponseAppend_P(PSTR(","));
|
|
p = &MIBLEsensors[MI32.mqttCurrentSlot];
|
|
|
|
MI32GetOneSensorJson(MI32.mqttCurrentSlot, (maxcnt == 1));
|
|
int mlen = ResponseLength();
|
|
|
|
// if we ran out of room, leave here.
|
|
if (ResponseSize() - mlen < 100){
|
|
MI32.mqttCurrentSlot++;
|
|
break;
|
|
}
|
|
cnt++;
|
|
}
|
|
ResponseAppend_P(PSTR("}"));
|
|
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR), Settings->flag.mqtt_sensor_retain);
|
|
//AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s: show some %d %s"),D_CMND_MI32, MI32.mqttCurrentSlot, ResponseData());
|
|
|
|
#ifdef USE_HOME_ASSISTANT
|
|
if(hass_mode==2){
|
|
MI32.option.noSummary = _noSummarySave;
|
|
MI32.option.minimalSummary = _minimalSummarySave;
|
|
}
|
|
#endif //USE_HOME_ASSISTANT
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
// starts a completely fresh MQTT message.
|
|
// sends ONE sensor on a dedicated topic NOT related to this TAS
|
|
// triggered by setting MI32.mqttCurrentSingleSlot = 0
|
|
void MI32ShowOneMISensor(){
|
|
// don't detect half-added ones here
|
|
int numsensors = MIBLEsensors.size();
|
|
if (MI32.mqttCurrentSingleSlot >= numsensors){
|
|
// if we got to the end of the sensors, then don't send more
|
|
return;
|
|
}
|
|
|
|
if(
|
|
#ifdef USE_HOME_ASSISTANT
|
|
Settings->flag.hass_discovery
|
|
||
|
|
#endif //USE_HOME_ASSISTANT
|
|
MI32.option.MQTTType == 1
|
|
){
|
|
|
|
ResponseTime_P(PSTR(","));
|
|
MI32GetOneSensorJson(MI32.mqttCurrentSingleSlot, 1);
|
|
mi_sensor_t *p;
|
|
p = &MIBLEsensors[MI32.mqttCurrentSingleSlot];
|
|
|
|
ResponseAppend_P(PSTR("}"));
|
|
|
|
char idstr[32];
|
|
const char *alias = BLE_ESP32::getAlias(p->MAC);
|
|
const char *id = idstr;
|
|
if (alias && *alias){
|
|
id = alias;
|
|
} else {
|
|
sprintf(idstr, PSTR("%s%02x%02x%02x"),
|
|
kMI32DeviceType[p->type-1],
|
|
p->MAC[3], p->MAC[4], p->MAC[5]);
|
|
}
|
|
char SensorTopic[TOPSZ];
|
|
GetTopic_P(SensorTopic, TELE, (char*)"tasmota_ble", id);
|
|
|
|
MqttPublish(SensorTopic, Settings->flag.mqtt_sensor_retain);
|
|
//AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s: show some %d %s"),D_CMND_MI32, MI32.mqttCurrentSlot, ResponseData());
|
|
}
|
|
MI32.mqttCurrentSingleSlot++;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
// starts a completely fresh MQTT message.
|
|
// sends ONE sensor's worth of HA discovery msg
|
|
const char MI_HA_DISCOVERY_TEMPLATE[] PROGMEM =
|
|
"{\"availability\":[],\"device\":"
|
|
"{\"identifiers\":[\"BLE%s\"],"
|
|
"\"name\":\"%s\","
|
|
"\"manufacturer\":\"tas\","
|
|
"\"model\":\"%s\","
|
|
"\"via_device\":\"%s\""
|
|
"},"
|
|
"%s%s%s"
|
|
"\"expire_after\":600,"
|
|
"\"json_attr_t\":\"%s\","
|
|
"\"name\":\"%s_%s\","
|
|
"\"state_topic\":\"%s\","
|
|
"\"uniq_id\":\"%s_%s\","
|
|
"%s%s%s"
|
|
"\"val_tpl\":\"{{ %s%s%s }}\"}";
|
|
|
|
// careful - a missing comma causes a crash!!!!
|
|
// because of the way we loop?
|
|
const char *classes[] = {
|
|
// 0
|
|
"temperature",
|
|
"Temperature",
|
|
"°C",
|
|
|
|
// 1
|
|
"humidity",
|
|
"Humidity",
|
|
"%",
|
|
|
|
// 2
|
|
"temperature",
|
|
"DewPoint",
|
|
"°C",
|
|
|
|
// 3
|
|
"battery",
|
|
"Battery",
|
|
"%",
|
|
|
|
// 4
|
|
"signal_strength",
|
|
"RSSI",
|
|
"dB",
|
|
|
|
// 5
|
|
"",//- empty device class
|
|
"Btn",
|
|
"",
|
|
|
|
// 6
|
|
"", //- empty device class
|
|
"Light",
|
|
"",
|
|
|
|
// 7
|
|
"", //- empty device class
|
|
"Moisture",
|
|
"%",
|
|
|
|
// 8
|
|
"", //- empty device class
|
|
"Illuminance",
|
|
"lx",
|
|
|
|
// 9
|
|
"", //- empty device class
|
|
"Fertility",
|
|
"µS/cm",
|
|
|
|
// 10
|
|
"", //- empty device class
|
|
"Firmware",
|
|
"",
|
|
};
|
|
|
|
|
|
void MI32DiscoveryOneMISensor(){
|
|
// don't detect half-added ones here
|
|
int numsensors = MIBLEsensors.size();
|
|
if (MI32.mqttCurrentSingleSlot >= numsensors){
|
|
// if we got to the end of the sensors, then don't send more
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_HOME_ASSISTANT
|
|
if(Settings->flag.hass_discovery){
|
|
mi_sensor_t *p;
|
|
p = &MIBLEsensors[MI32.mqttCurrentSingleSlot];
|
|
|
|
|
|
|
|
|
|
int datacount = (sizeof(classes)/sizeof(*classes))/3;
|
|
|
|
if (p->nextDiscoveryData >= datacount){
|
|
p->nextDiscoveryData = 0;
|
|
}
|
|
|
|
char DiscoveryTopic[80];
|
|
const char *host = NetworkHostname();
|
|
const char *devtype = kMI32DeviceType[p->type-1];
|
|
char idstr[32];
|
|
const char *alias = BLE_ESP32::getAlias(p->MAC);
|
|
const char *id = idstr;
|
|
if (alias && *alias){
|
|
id = alias;
|
|
} else {
|
|
sprintf(idstr, PSTR("%s%02x%02x%02x"),
|
|
devtype,
|
|
p->MAC[3], p->MAC[4], p->MAC[5]);
|
|
}
|
|
|
|
char SensorTopic[TOPSZ];
|
|
GetTopic_P(SensorTopic, TELE, (char*)"tasmota_ble", id);
|
|
|
|
//int i = p->nextDiscoveryData*3;
|
|
for (int i = 0; i < datacount*3; i += 3){
|
|
if (!classes[i] || !classes[i+1] || !classes[i+2]){
|
|
return;
|
|
}
|
|
uint8_t isBinary = 0;
|
|
|
|
ResponseClear();
|
|
|
|
switch(i/3){
|
|
case 0: // temp
|
|
if (!p->feature.temp && !p->feature.tempHum){
|
|
continue;
|
|
}
|
|
break;
|
|
case 1:// hum
|
|
if (!p->feature.hum && !p->feature.tempHum){
|
|
continue;
|
|
}
|
|
break;
|
|
case 2: //dew
|
|
if (!p->feature.tempHum && !(p->feature.temp && p->feature.hum)){
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 3: //bat
|
|
if (!p->feature.bat){
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 4: //rssi - all
|
|
break;
|
|
|
|
case 5: // button
|
|
if (!p->feature.Btn){
|
|
continue;
|
|
}
|
|
//isBinary = 2; // invert payload
|
|
break;
|
|
|
|
case 6: // binary light sense
|
|
if (!p->feature.light){
|
|
continue;
|
|
}
|
|
//isBinary = 1;
|
|
break;
|
|
case 7: // moisture
|
|
if (!p->feature.moist){
|
|
continue;
|
|
}
|
|
//isBinary = 1;
|
|
break;
|
|
case 8: // lux
|
|
if (!p->feature.lux){
|
|
continue;
|
|
}
|
|
break;
|
|
case 9: // fertility
|
|
if (!p->feature.fert){
|
|
continue;
|
|
}
|
|
break;
|
|
case 10: // firmware
|
|
if (!p->feature.fert){ // Flora only
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
{"availability":[],"device":{"identifiers":["TasmotaBLEa4c1387fc1e1"],"manufacturer":"simon","model":"someBLEsensor","name":"TASBLEa4c1387fc1e1","sw_version":"0.0.0"},"dev_cla":"temperature","json_attr_t":"tele/tasmota_esp32/SENSOR","name":"TASLYWSD037fc1e1Temp","state_topic":"tele/tasmota_esp32/SENSOR","uniq_id":"Tasmotaa4c1387fc1e1temp","unit_of_meas":"°C","val_tpl":"{{ value_json.LYWSD037fc1e1.Temperature }}"}
|
|
{"availability":[],"device":{"identifiers":["TasmotaBLEa4c1387fc1e1"],
|
|
"name":"TASBLEa4c1387fc1e1"},"dev_cla":"temperature",
|
|
"json_attr_t":"tele/tasmota_esp32/SENSOR",
|
|
"name":"TASLYWSD037fc1e1Temp","state_topic": "tele/tasmota_esp32/SENSOR",
|
|
"uniq_id":"Tasmotaa4c1387fc1e1temp","unit_of_meas":"°C",
|
|
"val_tpl":"{{ value_json.LYWSD037fc1e1.Temperature }}"}
|
|
*/
|
|
|
|
ResponseAppend_P(MI_HA_DISCOVERY_TEMPLATE,
|
|
//"{\"identifiers\":[\"BLE%s\"],"
|
|
id,
|
|
//"\"name\":\"%s\"},"
|
|
id,
|
|
//\"model\":\"%s\",
|
|
devtype,
|
|
//\"via_device\":\"%s\"
|
|
host,
|
|
//"\"dev_cla\":\"%s\","
|
|
(classes[i][0]?"\"dev_cla\":\"":""),
|
|
classes[i],
|
|
(classes[i][0]?"\",":""),
|
|
//"\"json_attr_t\":\"%s\"," - the topic the sensor publishes on
|
|
SensorTopic,
|
|
//"\"name\":\"%s_%s\"," - the name of this DATA
|
|
id, classes[i+1],
|
|
//"\"state_topic\":\"%s\"," - the topic the sensor publishes on?
|
|
SensorTopic,
|
|
//"\"uniq_id\":\"%s_%s\"," - unique for this data,
|
|
id, classes[i+1],
|
|
//"\"unit_of_meas\":\"%s\"," - the measure of this type of data
|
|
(classes[i+2][0]?"\"unit_of_meas\":\"":""),
|
|
classes[i+2],
|
|
(classes[i+2][0]?"\",":""),
|
|
//"\"val_tpl\":\"{{ %s%s }}") // e.g. Temperature
|
|
// inverted binary - {{ 'off' if value_json.posn else 'on' }}
|
|
// binary - {{ 'on' if value_json.posn else 'off' }}
|
|
|
|
((isBinary < 1)?"value_json.":
|
|
((isBinary < 2)?"value_json.":"'off' if value_json.")
|
|
),
|
|
classes[i+1],
|
|
((isBinary < 1)?"":
|
|
((isBinary < 2)?"":" else 'on'")
|
|
)
|
|
|
|
//
|
|
);
|
|
|
|
sprintf(DiscoveryTopic, "homeassistant/%ssensor/%s/%s/config",
|
|
(isBinary? "binary_":""), id, classes[i+1]);
|
|
|
|
MqttPublish(DiscoveryTopic);
|
|
p->nextDiscoveryData++;
|
|
//vTaskDelay(100/ portTICK_PERIOD_MS);
|
|
}
|
|
} // end if hass discovery
|
|
//AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s: show some %d %s"),D_CMND_MI32, MI32.mqttCurrentSlot, ResponseData());
|
|
#endif //USE_HOME_ASSISTANT
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
// starts a completely fresh MQTT message.
|
|
// sends up to 4 sensors pe5r msg
|
|
// sends only those which are raw and triggered.
|
|
// triggered by setting MI32.mode.triggeredTele = 1
|
|
void MI32ShowTriggeredSensors(){
|
|
if (!MI32.mode.triggeredTele) return; // none to show
|
|
MI32.mode.triggeredTele = 0;
|
|
|
|
// don't detect half-added ones here
|
|
int numsensors = MIBLEsensors.size();
|
|
|
|
int sensor = 0;
|
|
|
|
int maxcnt = 4;
|
|
if(
|
|
#ifdef USE_HOME_ASSISTANT
|
|
Settings->flag.hass_discovery
|
|
||
|
|
#endif //USE_HOME_ASSISTANT
|
|
MI32.option.MQTTType == 1
|
|
){
|
|
maxcnt = 1;
|
|
}
|
|
|
|
|
|
do {
|
|
ResponseTime_P(PSTR(""));
|
|
int cnt = 0;
|
|
mi_sensor_t *p;
|
|
for (; (sensor < numsensors) && (cnt < maxcnt); sensor++) {
|
|
p = &MIBLEsensors[sensor];
|
|
if(p->eventType.raw == 0) continue;
|
|
if(p->shallSendMQTT==0) continue;
|
|
|
|
cnt++;
|
|
ResponseAppend_P(PSTR(","));
|
|
// hide sensor name if HASS or option6
|
|
MI32GetOneSensorJson(sensor, (maxcnt == 1));
|
|
int mlen = ResponseLength();
|
|
|
|
// if we ran out of room, leave here.
|
|
if (ResponseSize() - mlen < 100){
|
|
sensor++;
|
|
break;
|
|
}
|
|
}
|
|
if (cnt){ // if we got one, then publish
|
|
ResponseAppend_P(PSTR("}"));
|
|
if(
|
|
#ifdef USE_HOME_ASSISTANT
|
|
Settings->flag.hass_discovery
|
|
||
|
|
#endif //USE_HOME_ASSISTANT
|
|
MI32.option.MQTTType == 1
|
|
){
|
|
char SensorTopic[TOPSZ];
|
|
char idstr[32];
|
|
const char *alias = BLE_ESP32::getAlias(p->MAC);
|
|
const char *id = idstr;
|
|
if (alias && *alias){
|
|
id = alias;
|
|
} else {
|
|
snprintf_P(idstr, sizeof(idstr), PSTR("%s%02x%02x%02x"),
|
|
kMI32DeviceType[p->type-1],
|
|
p->MAC[3], p->MAC[4], p->MAC[5]);
|
|
}
|
|
GetTopic_P(SensorTopic, TELE, (char*)"tasmota_ble", id);
|
|
MqttPublish(SensorTopic, Settings->flag.mqtt_sensor_retain);
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("M32: triggered %d %s"), sensor, ResponseData());
|
|
XdrvRulesProcess(0);
|
|
} else {
|
|
MqttPublishPrefixTopicRulesProcess_P(STAT, PSTR(D_RSLT_SENSOR), Settings->flag.mqtt_sensor_retain);
|
|
}
|
|
|
|
} else { // else don't and clear
|
|
ResponseClear();
|
|
}
|
|
} while (sensor < numsensors);
|
|
}
|
|
|
|
|
|
void MI32Show(bool json)
|
|
{
|
|
// don't detect half-added ones here
|
|
int numsensors = MIBLEsensors.size();
|
|
|
|
if (json) {
|
|
// TELE JSON messages now do nothing here, apart from set MI32.mqttCurrentSlot
|
|
// which will trigger send next second of up to 4 sensors, then the next four in the next second, etc.
|
|
//MI32.mqttCurrentSlot = 0;
|
|
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
static uint16_t _page = 0;
|
|
static uint16_t _counter = 0;
|
|
int32_t i = _page * MI32.perPage;
|
|
uint32_t j = i + MI32.perPage;
|
|
|
|
if (j+1 > numsensors){
|
|
j = numsensors;
|
|
}
|
|
char stemp[5] ={0};
|
|
if (numsensors-(_page*MI32.perPage)>1 && MI32.perPage!=1) {
|
|
sprintf_P(stemp,"-%u",j);
|
|
}
|
|
if (numsensors==0) i=-1; // only for the GUI
|
|
|
|
WSContentSend_PD(HTTP_MI32, i+1,stemp,numsensors);
|
|
for (i; i<j; i++) {
|
|
WSContentSend_PD(HTTP_MI32_HL);
|
|
mi_sensor_t *p;
|
|
p = &MIBLEsensors[i];
|
|
|
|
const char *typeName = kMI32DeviceType[p->type-1];
|
|
const char *alias = BLE_ESP32::getAlias(p->MAC);
|
|
if (alias && *alias){
|
|
WSContentSend_PD(HTTP_MI32_ALIAS, typeName, alias);
|
|
}
|
|
char _MAC[18];
|
|
ToHex_P(p->MAC,6,_MAC,18);//,':');
|
|
WSContentSend_PD(HTTP_MI32_MAC, typeName, D_MAC_ADDRESS, _MAC);
|
|
WSContentSend_PD(HTTP_RSSI, typeName, p->RSSI);
|
|
|
|
|
|
// for some reason, display flora differently
|
|
switch(p->type){
|
|
case MI_FLORA:{
|
|
if (!isnan(p->temp)) {
|
|
WSContentSend_Temp(typeName, p->temp);
|
|
}
|
|
if (p->moisture!=0xff) {
|
|
WSContentSend_PD(HTTP_SNS_MOISTURE, typeName, p->moisture);
|
|
}
|
|
if (p->fertility!=0xffff) {
|
|
WSContentSend_PD(HTTP_MI32_FLORA_DATA, typeName, p->fertility);
|
|
}
|
|
} break;
|
|
default:{
|
|
if (!isnan(p->hum) && !isnan(p->temp)) {
|
|
WSContentSend_THD(typeName, p->temp, p->hum);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_MI_DECRYPTION
|
|
bool showkey = false;
|
|
char tmp[40];
|
|
strcpy(tmp, PSTR("KeyRqd"));
|
|
switch(p->needkey) {
|
|
default:{
|
|
snprintf(tmp, 39, PSTR("?%d?"), p->needkey );
|
|
showkey = true;
|
|
} break;
|
|
case KEY_REQUIREMENT_UNKNOWN: {
|
|
strcpy(tmp, PSTR("WAIT"));
|
|
showkey = true;
|
|
} break;
|
|
case KEY_NOT_REQUIRED: {
|
|
strcpy(tmp, PSTR("NOTKEY"));
|
|
//showkey = true;
|
|
} break;
|
|
case KEY_REQUIRED_BUT_NOT_FOUND: {
|
|
strcpy(tmp, PSTR("NoKey"));
|
|
showkey = true;
|
|
} break;
|
|
case KEY_REQUIRED_AND_FOUND: {
|
|
strcpy(tmp, PSTR("KeyOk"));
|
|
showkey = true;
|
|
} break;
|
|
case KEY_REQUIRED_AND_INVALID: {
|
|
strcpy(tmp, PSTR("KeyInv"));
|
|
showkey = true;
|
|
} break;
|
|
}
|
|
|
|
// adds the link to get the key.
|
|
// provides mac and callback address to receive the key, if we had a website which did this
|
|
// (future work)
|
|
if (showkey){
|
|
BLE_ESP32::dump(_MAC, 13, p->MAC,6);
|
|
WSContentSend_PD(HTTP_NEEDKEY, typeName, _MAC, Webserver->client().localIP().toString().c_str(), tmp );
|
|
}
|
|
|
|
#endif //USE_MI_DECRYPTION
|
|
|
|
if (p->feature.events){
|
|
WSContentSend_PD(HTTP_EVENTS, typeName, p->events);
|
|
}
|
|
if (p->feature.NMT){
|
|
// no motion time
|
|
if(p->NMT>0) WSContentSend_PD(HTTP_NMT, typeName, p->NMT);
|
|
}
|
|
|
|
if (p->feature.lux){
|
|
if (p->lux!=0x00ffffff) { // this is the error code -> no valid value
|
|
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, typeName, p->lux);
|
|
}
|
|
}
|
|
if (p->feature.light){
|
|
WSContentSend_PD(HTTP_MI32_LIGHT, typeName, p->light);
|
|
}
|
|
if (p->feature.scale){
|
|
WSContentSend_PD(HTTP_MISCALE_WEIGHT, typeName, Settings->flag2.weight_resolution, &p->weight, p->weight_unit);
|
|
if (MI32.option.directBridgeMode) {
|
|
WSContentSend_PD(HTTP_MISCALE_WEIGHT_REMOVED, typeName, p->weight_removed? PSTR("yes") : PSTR("no"));
|
|
WSContentSend_PD(HTTP_MISCALE_WEIGHT_STABILIZED, typeName, p->weight_stabilized ? PSTR("yes") : PSTR("no"));
|
|
}
|
|
if (p->feature.impedance) {
|
|
WSContentSend_PD(HTTP_MISCALE_IMPEDANCE, typeName, p->impedance);
|
|
if (MI32.option.directBridgeMode) {
|
|
WSContentSend_PD(HTTP_MISCALE_IMPEDANCE_STABILIZED, typeName, p->impedance_stabilized? PSTR("yes") : PSTR("no"));
|
|
}
|
|
}
|
|
}
|
|
if(p->bat!=0x00){
|
|
WSContentSend_PD(HTTP_BATTERY, typeName, p->bat);
|
|
}
|
|
if (p->feature.Btn){
|
|
WSContentSend_PD(HTTP_LASTBUTTON, typeName, p->Btn);
|
|
}
|
|
if (p->pairing){
|
|
WSContentSend_PD(HTTP_PAIRING, typeName);
|
|
}
|
|
}
|
|
_counter++;
|
|
if(_counter>3) {
|
|
_page++;
|
|
_counter=0;
|
|
}
|
|
if (MIBLEsensors.size()%MI32.perPage==0 && _page==MIBLEsensors.size()/MI32.perPage) { _page = 0; }
|
|
if (_page>MIBLEsensors.size()/MI32.perPage) { _page = 0; }
|
|
#endif // USE_WEBSERVER
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* Interface
|
|
\*********************************************************************************************/
|
|
#define WEB_HANDLE_MI32 "mikey"
|
|
|
|
bool Xsns62(uint8_t function)
|
|
{
|
|
// if (!Settings->flag5.mi32_enable) { return false; } // SetOption115 - Enable ESP32 MI32 BLE
|
|
// return false;
|
|
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
MI32Init();
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
MI32Every50mSecond();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
MI32EverySecond(false);
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kMI32_Commands, MI32_Commands);
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
// we are not in control of when this is called...
|
|
//MI32Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer_on(PSTR("/" WEB_HANDLE_MI32), HandleMI32Key);
|
|
break;
|
|
case FUNC_WEB_SENSOR:
|
|
MI32Show(0);
|
|
break;
|
|
#endif // USE_WEBSERVER
|
|
}
|
|
return result;
|
|
}
|
|
#endif // USE_MI_ESP32
|
|
#endif // CONFIG_IDF_TARGET_ESP32 or CONFIG_IDF_TARGET_ESP32C3
|
|
#endif // ESP32
|
|
|
|
#endif |