forked from arendst/Tasmota
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxsns_62_esp32_mi.h
546 lines (489 loc) · 16 KB
/
xsns_62_esp32_mi.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
/*
xsns_62_esp32_mi.h - MI-BLE-sensors via ESP32 support for Tasmota
Copyright (C) 2021 Christian Baars and Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_MI_ESP32
/*********************************************************************************************\
* structs and types
\*********************************************************************************************/
#pragma pack(1) // byte-aligned structures to read the sensor data
struct frame_crtl_t{
uint16_t reserved1:1;
uint16_t reserved2:1;
uint16_t reserved3:1;
uint16_t isEncrypted:1;
uint16_t includesMAC:1;
uint16_t includesCapability:1;
uint16_t includesObj:1;
uint16_t MESH:1;
uint16_t registered:1;
uint16_t solicited:1;
uint16_t AuthMode:2;
uint16_t version:4;
};
struct mi_payload_t{
uint8_t type;
uint8_t ten;
uint8_t size;
union {
struct{ //0d
int16_t temp;
uint16_t hum;
}HT;
uint8_t bat; //0a
int16_t temp; //04
uint16_t hum; //06
uint32_t lux; //07
uint8_t moist; //08
uint16_t fert; //09
uint8_t leak; //14
uint32_t NMT; //17
uint8_t door; //19
struct{ //01
uint8_t num;
uint8_t value;
uint8_t type;
}Btn;
};
uint8_t padding[12]; //for decryption
};
struct mi_beacon_t{
frame_crtl_t frame;
uint16_t productID;
uint8_t counter;
uint8_t MAC[6];
uint8_t capability;
mi_payload_t payload;
};
struct cg_packet_t {
uint16_t frameID;
uint8_t MAC[6];
uint16_t mode;
union {
struct {
int16_t temp; // -9 - 59 °C
uint16_t hum;
};
uint8_t bat;
};
};
struct encPacket_t{
// the packet is longer, but this part is enough to decrypt
uint16_t PID;
uint8_t frameCnt;
uint8_t MAC[6];
uint8_t payload[16]; // only a pointer to the address, size is variable
};
struct berryAdvPacket_t{
uint8_t MAC[6];
uint8_t addressType;
uint8_t RSSI;
uint8_t length; // length of payload
uint8_t payload[32]; // only a pointer to the address, size is 0-31 bytes
};
union mi_bindKey_t{
struct{
uint8_t key[16];
uint8_t MAC[6];
};
uint8_t buf[22];
};
struct ATCPacket_t{ //and PVVX
uint8_t MAC[6];
union {
struct{
uint16_t temp; //sadly this is in wrong endianess
uint8_t hum;
uint8_t batPer;
uint16_t batMV;
uint8_t frameCnt;
} A; //ATC
struct{
int16_t temp;
uint16_t hum; // x 0.01 %
uint16_t batMV;
uint8_t batPer;
uint8_t frameCnt;
struct {
uint8_t reed:1;
uint8_t TRGval:1;
uint8_t TRGcrtl:1;
uint8_t tempTrig:1;
uint8_t humTrig:1;
uint8_t spare:3;
};
}P; //PVVX
};
};
struct BTHome_info_t{
uint8_t encrypted:1;
uint8_t reserved:1;
uint8_t triggered:1;
uint8_t reserved2:2;
uint8_t version:2;
};
struct BLERingBufferItem_t{
uint16_t returnCharUUID;
uint16_t handle;
uint32_t type;
uint8_t length;
};
#pragma pack(0)
struct MI32connectionContextBerry_t{
NimBLEUUID serviceUUID;
NimBLEUUID charUUID;
uint16_t returnCharUUID;
uint16_t handle;
uint8_t * buffer;
uint8_t MAC[6];
uint8_t operation;
uint8_t addrType;
int error;
int32_t arg1;
bool hasArg1;
bool oneOp;
bool response;
};
struct {
// uint32_t period; // set manually in addition to TELE-period, is set to TELE-period after start
TaskHandle_t ScanTask = nullptr;
TaskHandle_t ConnTask = nullptr;
TaskHandle_t ServerTask = nullptr;
MI32connectionContextBerry_t *conCtx = nullptr;
union {
struct {
uint32_t init:1;
uint32_t connected:1;
uint32_t autoScan:1;
// uint32_t canScan:1;
uint32_t runningScan:1;
uint32_t updateScan:1;
uint32_t deleteScanTask:1;
uint32_t canConnect:1;
uint32_t willConnect:1;
uint32_t readingDone:1;
uint32_t shallTriggerTele:1;
uint32_t triggeredTele:1;
uint32_t shallShowStatusInfo:1; // react to amount of found sensors via RULES
uint32_t didGetConfig:1;
uint32_t triggerBerryAdvCB:1;
uint32_t triggerBerryConnCB:1;
uint32_t triggerNextConnJob:1;
uint32_t readyForNextConnJob:1;
uint32_t discoverAttributes:1;
uint32_t triggerNextServerJob:1;
uint32_t readyForNextServerJob:1;
uint32_t triggerBerryServerCB:1;
uint32_t deleteServerTask:1;
};
uint32_t all = 0;
} mode;
struct {
uint8_t sensor; // points to to the number 0...255
} state;
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 activeScan:1;
uint32_t ignoreBogusBattery:1;
uint32_t minimalSummary:1; // DEPRECATED!!
} option;
#ifdef USE_MI_EXT_GUI
uint32_t widgetSlot;
#ifdef USE_ENERGY_SENSOR
uint8_t *energy_history;
#endif //USE_ENERGY_SENSOR
#endif //USE_MI_EXT_GUI
void *beConnCB;
void *beAdvCB;
void *beServerCB;
uint8_t *beAdvBuf;
uint8_t infoMsg = 0;
uint8_t role = 0;
} MI32;
struct mi_sensor_t{
uint8_t type; //Flora = 1; MI-HT_V1=2; LYWSD02=3; LYWSD03=4; CGG1=5; CGD1=6
uint8_t lastCnt; //device generated counter of the packet
uint8_t shallSendMQTT;
uint8_t MAC[6];
uint16_t PID;
uint8_t *key = nullptr;
char *name = nullptr;
union {
struct {
uint32_t needsKey:1;
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 motion:1;
uint32_t Btn:1;
uint32_t knob:1;
uint32_t door:1;
uint32_t leak: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 knob:1;
uint32_t longpress:1; //needs no extra feature bit, because knob is sufficient
uint32_t door:1;
uint32_t leak:1;
};
uint32_t raw;
} eventType;
union{
struct{
uint8_t hasWrongKey:1;
uint8_t isUnbounded:1;
};
uint8_t raw;
} status;
int RSSI;
uint32_t lastTime;
uint32_t lux;
uint8_t *lux_history;
float temp; //Flora, MJ_HT_V1, LYWSD0x, CGx
uint8_t *temp_history;
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;
uint8_t *hum_history;
}; // MJ_HT_V1, LYWSD0x
struct {
uint16_t events; //"alarms" since boot
uint32_t NMT; // no motion time in seconds for the MJYD2S and NLIGHT
};
struct {
uint8_t Btn; // number starting with 0
uint8_t BtnType; // 0 -single, 1 - double, 2 - hold
uint8_t leak; // the leak sensor is the only non-RC device so far with a button fuctionality, so we handle it here
int8_t dimmer;
uint8_t pressed; // dimmer knob pressed while rotating
uint8_t longpress; // dimmer knob pressed without rotating
};
uint8_t door;
};
union {
uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1)
};
};
/*********************************************************************************************\
* constants
\*********************************************************************************************/
#define D_CMND_MI32 "MI32"
const char kMI32_Commands[] PROGMEM = D_CMND_MI32 "|Key|Name|Cfg|Option";
void (*const MI32_Commands[])(void) PROGMEM = {&CmndMi32Key, &CmndMi32Name,&CmndMi32Cfg, &CmndMi32Option };
#define UNKNOWN_MI 0
#define FLORA 1
#define MJ_HT_V1 2
#define LYWSD02 3
#define LYWSD03MMC 4
#define CGG1 5
#define CGD1 6
#define NLIGHT 7
#define MJYD2S 8
#define YLYK01 9
#define MHOC401 10
#define MHOC303 11
#define ATC 12
#define MCCGQ02 13
#define SJWS01L 14
#define PVVX 15
#define YLKG08 16
#define YLAI003 17
#define BTHOME 18
#define MI32_TYPES 18 //count this manually
const uint16_t kMI32DeviceID[MI32_TYPES]={ 0x0098, // Flora
0x01aa, // MJ_HT_V1
0x045b, // LYWSD02
0x055b, // LYWSD03
0x0347, // CGG1
0x0576, // CGD1
0x03dd, // NLIGHT
0x07f6, // MJYD2S
0x0153, // YLYK01, old name yee-rc
0x0387, // MHO-C401
0x06d3, // MHO-C303
0x0a1c, // ATC -> this is a fake ID
0x098b, // MCCGQ02
0x0863, // SJWS01L
0x944a, // PVVX -> this is a fake ID
0x03b6, // YLKG08 and YLKG07 - version w/wo mains
0x07bf, // YLAI003
0xb770, // BTHome -> fake ID
};
const char kMI32DeviceType[] PROGMEM = {"Flora|MJ_HT_V1|LYWSD02|LYWSD03|CGG1|CGD1|NLIGHT|MJYD2S|YLYK01|MHOC401|MHOC303|ATC|MCCGQ02|SJWS01L|PVVX|YLKG08|YLAI003|BTHOME"};
const char kMI32_ConnErrorMsg[] PROGMEM = "no Error|could not connect|did disconnect|got no service|got no characteristic|can not read|can not notify|can not write|did not write|notify time out";
const char kMI32_BLEInfoMsg[] PROGMEM = "Scan ended|Got Notification|Did connect|Did disconnect|Still connected|Start passive scanning|Start active scanning|Server characteristic set|Server advertisement set|Server scan response set|Server client did connect|Server client did disconnect";
const char kMI32_ButtonMsg[] PROGMEM = "Single|Double|Hold"; //mapping: in Tasmota: 1,2,3 ; for HomeKit and Xiaomi 0,1,2
/*********************************************************************************************\
* enumerations
\*********************************************************************************************/
enum MI32_Commands { // commands useable in console or rules
CMND_MI32_KEY, // add bind key to a mac for packet decryption
CMND_MI32_CFG, // save config file as JSON with all sensors w/o keys to mi32cfg
CMND_MI32_OPTION // change driver options at runtime
};
enum MI32_TASK {
MI32_TASK_SCAN = 0,
MI32_TASK_CONN = 1,
MI32_TASK_SERV = 2,
};
enum BLE_CLIENT_OP {
BLE_OP_READ = 1,
BLE_OP_WRITE,
BLE_OP_SUBSCRIBE,
BLE_OP_UNSUBSCRIBE, //maybe used later
BLE_OP_DISCONNECT,
BLE_OP_GET_NOTIFICATION = 103,
};
enum BLE_SERVER_OP {
//commands
BLE_OP_SET_ADV = 201,
BLE_OP_SET_SCAN_RESP,
BLE_OP_SET_CHARACTERISTIC = 211,
//response
BLE_OP_ON_READ = 221,
BLE_OP_ON_WRITE,
BLE_OP_ON_UNSUBSCRIBE,
BLE_OP_ON_SUBSCRIBE_TO_NOTIFICATIONS,
BLE_OP_ON_SUBSCRIBE_TO_INDICATIONS,
BLE_OP_ON_SUBSCRIBE_TO_NOTIFICATIONS_AND_INDICATIONS,
BLE_OP_ON_CONNECT,
BLE_OP_ON_DISCONNECT,
BLE_OP_ON_STATUS,
};
enum MI32_ConnErrorMsg {
MI32_CONN_NO_ERROR = 0,
MI32_CONN_NO_CONNECT,
MI32_CONN_DID_DISCCONNECT,
MI32_CONN_NO_SERVICE,
MI32_CONN_NO_CHARACTERISTIC,
MI32_CONN_CAN_NOT_READ,
MI32_CONN_CAN_NOT_NOTIFY,
MI32_CONN_CAN_NOT_WRITE,
MI32_CONN_DID_NOT_WRITE,
MI32_CONN_NOTIFY_TIMEOUT
};
enum MI32_BLEInfoMsg {
MI32_SCAN_ENDED = 1,
MI32_GOT_NOTIFICATION,
MI32_DID_CONNECT,
MI32_DID_DISCONNECT,
MI32_STILL_CONNECTED,
MI32_START_SCANNING_PASSIVE,
MI32_START_SCANNING_ACTIVE,
MI32_SERV_CHARACTERISTIC_ADDED,
MI32_SERV_ADVERTISEMENT_ADDED,
MI32_SERV_SCANRESPONSE_ADDED,
MI32_SERV_CLIENT_CONNECTED,
MI32_SERV_CLIENT_DISCONNECTED
};
/*********************************************************************************************\
* extended web gui
\*********************************************************************************************/
#ifdef USE_WEBSERVER
#ifdef USE_MI_EXT_GUI
const char HTTP_BTN_MENU_MI32[] PROGMEM = "<p><form action='m32' method='get'><button>Mi Dashboard</button></form></p>";
const char HTTP_MI32_SCRIPT_1[] PROGMEM =
"function setUp(){setInterval(countUp,1000); setInterval(update,1000);}"
"function countUp(){let ti=document.querySelectorAll('.Ti');"
"for(const el of ti){var t=parseInt(el.innerText);el.innerText=t+1;}}"
"function update(){"
"fetch('/m32?wi=1').then(r=>r.text())"
".then((r)=>{"
// console.log(r); // optional
"if(r.length>0){"
"var d=document.createElement('div');"
"d.innerHTML=r.trim();"
"var old=eb(d.firstChild.id);"
"old.parentNode.replaceChild(d.firstChild, old);"
"}"
"})"
//".catch((e) => {console.error(e);});" //optional
"};"
;
const char HTTP_MI32_STYLE[] PROGMEM =
"<style onload=setTimeout(setUp,500)>body{display:flex;flex-direction:column;}"
".parent{display:grid;grid-template-columns:repeat(auto-fill,350px);grid-template-rows:repeat(auto-fill,220px);"
"grid-auto-rows:220px;grid-auto-columns:350px;gap:1rem;justify-content:center;}"
"svg{float:inline-end;}"
".box{padding:10px;border-radius:0.8rem;background-color:rgba(221, 221, 221, 0.2);}"
"@media screen and (min-width: 720px){.wide{grid-column:span 2;grid-row:span 1;}.big {grid-column:span 2;grid-row:span 2;}}"
".tall {grid-column:span 1;grid-row:span 2;}"
"</style>";
const char HTTP_MI32_STYLE_SVG[] PROGMEM =
"<svg height='0'><defs><linearGradient id='grd%u' x1='0%%' y1='0%%' x2='0%%' y2='15%%'>"
"<stop offset='0' stop-color='rgba(%u, %u, %u, 0.5)'/>"
"<stop offset='1' stop-color='rgba(%u, %u, %u, 0)'/></linearGradient></defs></svg>"
;
const char HTTP_MI32_PARENT_BLE_ROLE[] PROGMEM = "None|Observer|Peripheral|Central";
const char HTTP_MI32_PARENT_START[] PROGMEM =
"<div class='parent'>"
"<div class='box tall'><h2>MI32 Bridge</h2>"
"Observing <span id='numDev'>%u</span> devices<br><br>"
"Uptime: <span class='Ti'>%u</span> seconds<br><br>"
"Free Heap: %u kB<br><br>"
"BLE Role: %s"
"</div>";
const char HTTP_MI32_WIDGET[] PROGMEM =
"<div class='box' id='box%u' style='opacity:%u.5;'>MAC:%s RSSI:%d %s<br>"
"<small>‌%s</small>"
"<h2 style='margin-top:0em;'>%s"
"<svg height='24' width='24' style='float:inline-end;'>"
"<circle cx='11' cy='11' r='0' fill='#90ee90' opacity='0'>"
"<animate attributeName='r' from='11' to='0' dur='9s' repeatCount='1'></animate>"
"<animate attributeName='opacity' from='1' to='0' dur='9s' repeatCount='1'></animate></circle></svg>"
"</h2>";
const char HTTP_MI32_GRAPH[] PROGMEM =
"<svg height='20' width='150'>"
"<polyline points='%s'"
"style='stroke:rgb(%u, %u, %u);fill:none;'></polyline>"
"<polyline points='%s138,20 0,20'"
"style='stroke:none;fill:url(#grd%u);'></polyline>"
"</svg>";
//rgb(185, 124, 124) - red, rgb(185, 124, 124) - blue, rgb(242, 240, 176) - yellow
#ifdef USE_MI_ESP32_ENERGY
const char HTTP_MI32_POWER_WIDGET[] PROGMEM =
"<div class='box' id='box%u'>"
"<h2 style='margin-top:0em;'>Energy"
"</h2>"
"<p>" D_VOLTAGE ": %.1f " D_UNIT_VOLT "</p>"
"<p>" D_CURRENT ": %.3f " D_UNIT_AMPERE "</p>";
#endif //USE_MI_ESP32_ENERGY
#endif //USE_MI_EXT_GUI
#endif // USE_WEBSERVER
#endif //USE_MI_ESP32