Tasmota/tasmota/include/xsns_62_esp32_mi.h
2025-12-06 15:29:06 +01:00

1135 lines
51 KiB
C++

/*
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
#pragma once
#include <NimBLEDevice.h>
/*********************************************************************************************\
* 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{
uint16_t type;
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
uint16_t objID; //0x0002
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
};
};
union BTHome_info_t{
struct{
uint8_t encrypted:1;
uint8_t reserved:1;
uint8_t triggered:1;
uint8_t reserved2:2;
uint8_t version:2;
};
char byte_value;
};
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;
uint32_t pin;
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;
uint16_t connID;
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 IRKinCfg: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 handleEveryDevice:1;
} option;
#ifdef USE_MI_EXT_GUI
uint32_t widgetSlot = 0;
#ifdef USE_ENERGY_SENSOR
uint8_t energy_history[24];
#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 payload:1;
};
uint32_t raw = 0;
} 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 payload:1;
};
uint32_t raw = 0;
} eventType;
union{
struct{
uint8_t hasWrongKey:1;
uint8_t isUnbounded:1;
};
uint8_t raw = 0;
} status;
int RSSI = 0;
uint32_t lastTime = 0;
uint32_t lux;
uint8_t lux_history[24];
float temp; //Flora, MJ_HT_V1, LYWSD0x, CGx
uint8_t temp_history[24];
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[24];
};
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)
};
struct {
uint8_t * payload = nullptr;
uint8_t payload_len;
};
};
/*********************************************************************************************\
* 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| Server client did authenticate";
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,
BLE_OP_ON_AUTHENTICATED
};
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,
MI32_SERV_CLIENT_AUTHENTICATED
};
/*********************************************************************************************\
* extended web gui
\*********************************************************************************************/
#ifdef USE_WEBSERVER
#ifdef USE_MI_EXT_GUI
const char HTTP_BTN_MENU_MI32[] PROGMEM = "<p></p><form action='m32' method='get'><button>Dashboard</button></form>";
const char HTTP_MI32_BOOTSTRAP[] PROGMEM =
"wl(_=>fetch('/m32?wi=0').then(r=>r.text()).then(t=>{"
"d=document;" // this is global now
"x=d.createElement('div');"
"x.innerHTML=t;" // was global already
"x.querySelectorAll('style').forEach(n=>d.head.appendChild(n.cloneNode(1)));"
"x.querySelectorAll('script').forEach(n=>{s=d.createElement('script');s.textContent=n.textContent;d.body.appendChild(s)});"
"eb('m').innerHTML=(x.querySelector('body')||x).innerHTML;"
"}));";
const char HTTP_MI32_PARENT_BLE_ROLE[] PROGMEM = "None|Observer|Peripheral|Central";
const char HTTP_MI32_WIDGET[] PROGMEM =
"<div class='box' id='box%u' style='opacity:%u.5;'>MAC:%s RSSI:%d %s<br>"
"<small>&zwnj;%s</small>"
"<h2 style='margin-top:0em;'>%s"
"{ci}" // circle animation
"</h2>";
#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
/*
STATIC_PAGE MI32_STATIC_PAGE
<style>
body {
display: flex;
flex-direction: column;
}
code {
word-break: break-all;
}
.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;
}
svg text {
font-size: 0.95em;
fill: currentColor;
}
.box {
padding: 10px;
border-radius: 0.8rem;
background-color: rgba(221,221,221,0.2);
overflow-y: auto;
}
.box svg {
max-width: 100%;
}
.box.w1 { grid-column: span 1; }
.box.w2 { grid-column: span 2; }
.box.w3 { grid-column: span 3; }
.box.h1 { grid-row: span 1; }
.box.h2 { grid-row: span 2; }
.box.h3 { grid-row: span 3; }
@media (width < 720px) {
.box.w2, .box.w3 { grid-column: span 1; }
.box.h3 { grid-row: span 2; }
}
</style>
<script>
let sz=[], u=0; // sizes, uptime
//global vars from tasmota header script for local reuse:
// x,lt,to,tp,pc
// d = document from the bootstrap script
// we also have:
// eb=s=>document.getElementById(s);
// qs=s=>document.querySelector(s);
// wl=f=>window.addEventListener('load',f);
function pulse(s) { // size
sz.push(s);
if (sz.length > 60) sz.shift();
lt = "(0,200,0):" + sz.join(",");
pc = `{l,250,20,${lt}}`;
eb('pulse').innerHTML = "AJAX: " + render(pc);
}
function setUp() {
setInterval(cU, 1000); // countUp
setInterval(update, 200);
}
function cU() {
x = eb('clock');
if (x) x.innerHTML = Date().slice(4,24);
}
function updateMetrics(m) {
let fk = +(m[3] / 1024).toFixed(1); // freeKB
let tk = +(m[4] / 1024).toFixed(1); // totalKB
let fp = +(m[7] / 1024).toFixed(0); // freePSRAM
let tp = +(m[6] / 1024).toFixed(0); // totalPSRAM
let fh = +(m[5]); // fragHeap
eb('r').innerHTML = 'RSSI: ' + m[0] + ' dBm';
eb('mo').innerHTML = 'Mode: ' + ['Low Rate','802.11b','802.11g','HT20 (11n)','HT40 (11n)','HE20 (11ax)'][m[2]] + ' / Channel: ' + m[1];
eb('heap_gauge').innerHTML = render(`{g,50,50,${fk},0,${tk},255,102,102,0,255,255,153,40,0,200,0,80,kB}`);
eb('psram_gauge').innerHTML = render(`{g,50,50,${fp},0,${tp},255,102,102,0,255,255,153,100,0,200,0,250,kB}`);
eb('heap').innerHTML = 'Heap free: ' + fk + '/' + tk + ' kB<br>(frag. ' + fh + '%)';
eb('psram').innerHTML = 'PSRAM free: ' + fp + '/' + tp + ' kB';
eb('ut').innerHTML = 'Uptime: ' + m[8] + ' sec';
eb('ro').innerHTML = 'BLE role: ' + ['none','observer','peripheral','central'][m[9]];
eb('sd').innerHTML = 'MI32 sensors: ' + m[10];
}
function update() {
try {
fetch('/m32?wi=1').then(r => r.text()).then(html => {
x = html.length; // number of characters ≈ bytes
pulse(x); // pass size instead of true/false
if (!html) {
return;
}
let parts = html.trim().split(/[\n\r,]+/);
if (parts.length === 11 && parts.every(p => /^-?\d+$/.test(p))) {
updateMetrics(parts.map(Number));
return;
}
let ciSVG =
"<svg height='24' width='24' >" // style='float:right;'
+ "<circle cx='11' cy='11' r='0' fill='#90ee90' opacity='0'>"
+ "<animate attributeName='r' from='11' to='0' dur='9s' repeatCount='1'/>"
+ "<animate attributeName='opacity' from='1' to='0' dur='9s' repeatCount='1'/>"
+ "</circle></svg>";
let replaced = html
.replace(/{ci}/g, ciSVG)
.replace(/\{[lLhHgG],[^}]+\}/g, match => {
try { return render(match); }
catch (e) {
console.warn("DSL render error:", e);
return "<small>graph unavailable</small>";
}
});
x = d.createElement('div');
x.innerHTML = replaced.trim();
lt = Array.from(x.getElementsByTagName('script'));
lt.forEach(el => el.remove());
tp = x.firstChild; //new element
to = eb(tp.id); // old element
if (to) to.parentNode.replaceChild(tp, to);
else eb('pr').appendChild(tp);
lt.forEach(el => {
let sc = d.createElement('script');
sc.textContent = el.textContent;
d.body.appendChild(sc);
});
});
} catch (e) {
// console.warn("update error:", e);
}
}
setTimeout(setUp, 500);
// Dispatcher for compact DSL only: {type,w,h,(r,g,b):v1,v2…[|…]}
// Uppercase ⇒ WITH grid; lowercase ⇒ WITHOUT grid
function render(payload) {
let s = String(payload).trim();
if (!s.startsWith("{") || !s.endsWith("}")) return "";
let inner = s.slice(1, -1);
// First char = type
let typeChar = inner[0];
let showGrid = typeChar === typeChar.toUpperCase();
let type = typeChar.toLowerCase();
// Handle line/histogram with series syntax
if ("lh".includes(type)) {
let m = inner.match(/^([lLhH]),(\d+),(\d+),(.+)$/);
if (!m) return "";
let [, , wStr, hStr, sStr] = m;
let w = +wStr; // width
let h = +hStr; // height
// Extract xl: parameter if present
let xl = null; // xLabels - custom X-axis labels
let seg = sStr.split("|");
for (let i = 0; i < seg.length; i++) {
if (seg[i].startsWith("xl:")) {
xl = seg[i].substring(3).split(",");
seg.splice(i, 1); // remove xl: segment from array
break;
}
}
let sl = seg.map(seg => { // sl = seriesList
let mm = seg.match(/^\((\d+),(\d+),(\d+)\):(.*)$/);
if (!mm) return null;
return {
rgb: [+mm[1], +mm[2], +mm[3]],
values: mm[4].split(",").map(v => Number(v.trim()))
};
}).filter(Boolean);
switch (type) {
case "l":
return renderLG(sl, w, h, showGrid, xl);
break;
case "h":
if (sl.length === 1)
return renderH(sl[0].values, sl[0].rgb, w, h, showGrid, xl);
break;
}
}
// Handle gauge DSL separately
// Gauge DSL: {g,w,h,val,min,max,r1,g1,b1,threshold1,r2,g2,b2,threshold2,...,unit}
// Example: {g,200,140,75,0,100,0,200,0,0,200,160,0,40,200,0,0,75,%}
// Stops: RGB triplet + threshold value, repeating. Unit string at end.
if (type === "g") {
let p = inner.split(',');
let w = +p[1], h = +p[2], val = +p[3], min = +p[4], max = +p[5];
let stops = [];
for (let i = 6; i < p.length - 1; i += 4) {
stops.push({
value: +p[i+3],
color: `rgb(${p[i]},${p[i+1]},${p[i+2]})`
});
}
let unit = p[p.length - 1];
let showValue = (typeChar === "G");
return renderG(val, min, max, w, h, stops, unit, showValue);
}
return "";
}
function buildGrid(w, h, mv, dL, yt = 4, cX = null) { // maxVal, dataLength, yTicks, customXLabels
let ln = [], lb = [], xl = []; // lines, labels, xLabels
let nm = Math.ceil(mv / 10) * 10; // niceMax
let ti = nm / yt; // tickInterval
if (ti <= 5) ti = 5;
else if (ti <= 10) ti = 10;
else if (ti <= 25) ti = 25;
else ti = Math.ceil(ti / 50) * 50;
nm = Math.ceil(mv / ti) * ti;
let at = Math.ceil(nm / ti); // actualTicks
for (let i = 0; i <= at; i++) {
let v = i * ti;
let y = h - (v * h / nm);
ln.push(`<line x1="0" y1="${y}" x2="${w}" y2="${y}" stroke="#ddd" stroke-width="1"/>`);
if (i < at) lb.push(`<text x="-5" y="${y+4}" text-anchor="end">${v}</text>`);
}
if (cX && cX.length > 0) { // X-axis labels - use custom if provided, otherwise default numeric
let xs = cX.length - 1; // xSteps
for (let i = 0; i < cX.length; i++) {
let x = xs > 0 ? i * (w / xs) : w / 2;
xl.push(`<text x="${x}" y="${h+20}" text-anchor="middle">${cX[i]}</text>`);
}
} else {
let xs = 4; // xSteps
for (let i = 0; i <= xs; i++) {
let x = i * (w / xs);
let xi = Math.round(i * (dL - 1) / xs);
xl.push(`<text x="${x}" y="${h+20}" text-anchor="middle">${xi}</text>`);
}
}
return { lines: ln.join(""), labels: lb.join(""), xl: xl.join("") };
}
function renderG(v, mn, mx, w, h, st, u = "", sv = true) { // value, min, max, stops, unit, showValue
let cl = x => Math.max(mn, Math.min(mx, x)); // clamp
let vl = cl(v); // val
let sp = mx - mn || 1; // span
let t = (vl - mn) / sp;
let cx = w / 2, cy = h / 2;
let ro = Math.min(w, h) * 0.45; // rOuter
let ri = ro * 0.75; // rInner
let so = ro * 0.06, si = ro * 0.35; // swOuter, swInner
let gd = 2; // gapDeg
let sd = -135 + gd, ed = 135 - gd, swd = ed - sd; // startDeg, endDeg, sweepDeg
function polar(cx, cy, r, d) { // deg
let rd = (d - 90) * Math.PI / 180; // rad
return { x: cx + r * Math.cos(rd), y: cy + r * Math.sin(rd) };
}
function arcPath(cx, cy, r, a0, a1) {
let la = Math.abs(a1 - a0) > 180 ? 1 : 0; // largeArc
let sw = a1 > a0 ? 1 : 0; // sweep
let p0 = polar(cx, cy, r, a0);
let p1 = polar(cx, cy, r, a1);
return `M ${p0.x},${p0.y} A ${r},${r} 0 ${la} ${sw} ${p1.x},${p1.y}`;
}
function colorFor(v) {
if (!st.length) return "rgb(120,120,120)";
let c = st[0].color;
for (let i = 0; i < st.length; i++) {
if (v >= st[i].value) c = st[i].color;
else break;
}
return c;
}
let os = ""; // outerSegs
for (let i = 0; i < st.length; i++) {
let fr = (st[i].value - mn) / sp; // from
let to = (i + 1 < st.length) ? (st[i+1].value - mn) / sp : 1;
let a0 = sd + swd * fr, a1 = sd + swd * to;
if (a1 <= a0) continue;
os += `<path d="${arcPath(cx, cy, ro, a0, a1)}" stroke="${st[i].color}" stroke-width="${so}" fill="none"/>`;
}
let bt = `<path d="${arcPath(cx, cy, ri, sd, ed)}" stroke="rgba(120,120,120,0.25)" stroke-width="${si}" fill="none"/>`; // bgTrack
let pc = colorFor(vl); // progColor
let pe = sd + swd * t; // progEnd
let ia = t > 0 ? `<path d="${arcPath(cx, cy, ri, sd, pe)}" stroke="${pc}" stroke-width="${si}" fill="none"/>` : ""; // innerArc
let tx = ""; // text
if (sv) {
let fs = Math.round(Math.min(w, h) * 0.18); // fontSize
tx = `<text x="${cx}" y="${cy}" text-anchor="middle" dominant-baseline="middle" font-size="${fs}" fill="${pc}" font-weight="600">${vl}${u}</text>`;
}
return `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">${os}${bt}${ia}${tx}</svg>`.trim();
}
function renderLG(sl, w, h, sg = true, customXL = null) { // showGrid, customXLabels
let mv = Math.max(...sl.flatMap(s => s.values), 1); // maxVal
let gd = sg ? buildGrid(w, h, mv, sl[0].values.length, 4, customXL) : { lines: "", labels: "", xl: "" }; // grid
let clipId = `clip_${Math.random().toString(36).slice(2)}`; // Clip path to hide vertical edges at x=0 and x=w
let clip = `<clipPath id="${clipId}"><rect x="0" y="0" width="${w}" height="${h}"/></clipPath>`;
let df = [], ly = []; // defs, layers
sl.forEach((s, i) => {
let gi = `g${i}_${Math.random().toString(36).slice(2)}`; // gradId
df.push(`<linearGradient id="${gi}" x1="0" y1="0" x2="0" y2="${h}" gradientUnits="userSpaceOnUse"><stop offset="0%" stop-color="rgba(${s.rgb[0]},${s.rgb[1]},${s.rgb[2]},0.35)"/><stop offset="100%" stop-color="rgba(${s.rgb[0]},${s.rgb[1]},${s.rgb[2]},0)"/></linearGradient>`);
let pt = s.values.map((v, j) => { // points
let x = s.values.length > 1 ? (j * w) / (s.values.length - 1) : 0;
let y = v <= 0 ? h : h - (v / mv) * h;
return `${x},${y}`;
}).join(" ");
ly.push(`<polyline points="-20,${h} ${pt} ${w+20},${h}" fill="url(#${gi})" stroke="rgb(${s.rgb[0]},${s.rgb[1]},${s.rgb[2]})" stroke-width="2" clip-path="url(#${clipId})"/>`);
});
return `<svg width="${w}" height="${h+30}" viewBox="-50 0 ${w+100} ${h+40}">${gd.lines}${gd.labels}${gd.xl}<defs>${clip}${df.join("")}</defs>${ly.join("")}</svg>`.trim();
}
function renderH(vs, rgb, w, h, sg = true, cX = null) { // values, showGrid, customXLabels
let mv = Math.max(...vs); // maxVal
let sc = mv > h ? h / mv : 1; // scale
let gp = 2; // gap
let tg = (vs.length - 1) * gp; // totalGaps
let bw = (w - tg) / vs.length; // barW
let gi = "hg_" + (Math.random()*1e9|0); // gradId
let df = `<linearGradient id="${gi}" x1="0%" y1="0%" x2="0%" y2="${h}" gradientUnits="userSpaceOnUse"><stop offset="0%" stop-color="rgba(${rgb[0]},${rgb[1]},${rgb[2]},1)"/><stop offset="100%" stop-color="rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.1)"/></linearGradient>`; // defs
let rs = ''; // rects
vs.forEach((v, i) => {
let bh = Math.min(v * sc, h); // barH
let y = h - bh;
let x = i * (bw + gp);
rs += `<rect x="${x}" y="${y}" width="${bw}" height="${bh}" fill="url(#${gi})"/>`;
});
if (!sg) return `<svg width="${w}" height="${h}"><defs>${df}</defs>${rs}</svg>`;
let gd = buildGrid(w, h, mv, vs.length, 4, cX); // grid
return `<svg width="${w}" height="${h+30}" viewBox="-50 0 ${w+100} ${h+40}">${gd.lines}${gd.labels}${gd.xl}<defs>${df}</defs>${rs}</svg>`.trim();
}
</script>
<div>
<div class="parent" id="pr">
<div class="box h2" id="info">
<h2 id="clock"></h2>
<p id="ut"></p>
<p id="ro"></p>
<p id="sd"></p><br>
<p id="r"></p>
<p id="mo"></p>
<p id="pulse"></p><br>
<div id="heap_gauge"></div>
<p id="heap"></p>
<div id="psram_gauge"></div>
<p id="psram"></p>
</div>
</div>
</div>
END_STATIC_PAGE
*/
const char MI32_STATIC_PAGE[] PROGMEM = {
/* AUTO-GENERATED (BEGIN) */
0x1f, 0x8b, 0x08, 0x00, 0x1a, 0x5c, 0xe1, 0x68, 0x02, 0xff, 0xc5, 0x39, 0xfd, 0x73, 0xdb, 0x36,
0xb2, 0xbf, 0xf7, 0xaf, 0x60, 0x59, 0xa7, 0x22, 0x2c, 0x4a, 0x22, 0x65, 0x2b, 0x4d, 0x24, 0x41,
0xbe, 0x24, 0xd7, 0x4b, 0x72, 0xe3, 0xf4, 0x3a, 0x4d, 0x73, 0x2f, 0x33, 0x8e, 0x5a, 0x53, 0x24,
0x44, 0xb2, 0xe6, 0xd7, 0x10, 0x90, 0x44, 0x55, 0xe1, 0xff, 0x7e, 0xbb, 0x00, 0x48, 0x51, 0x76,
0x92, 0xcb, 0x7b, 0xef, 0x66, 0x6e, 0xc6, 0x26, 0x81, 0xdd, 0xc5, 0x62, 0xb1, 0xdf, 0xa0, 0xe6,
0x5c, 0xec, 0x13, 0xb6, 0x58, 0xe5, 0xc1, 0xfe, 0x10, 0xc4, 0xbc, 0x48, 0xbc, 0xfd, 0x74, 0x9d,
0xb0, 0x6a, 0x86, 0x8f, 0x41, 0x10, 0x97, 0xcc, 0x17, 0x71, 0x9e, 0x4d, 0xfd, 0x3c, 0xd9, 0xa4,
0x59, 0xed, 0xe7, 0x01, 0x3b, 0xec, 0xf2, 0x32, 0x18, 0xac, 0x4a, 0xe6, 0xdd, 0x4d, 0xe5, 0x73,
0xe0, 0x25, 0x49, 0x3d, 0x2c, 0xbc, 0x92, 0x65, 0xa2, 0x65, 0x12, 0x96, 0x71, 0x30, 0xc3, 0xc7,
0x40, 0xb0, 0x14, 0x20, 0x82, 0x0d, 0x14, 0x0f, 0x3e, 0x2d, 0x59, 0xc1, 0x3c, 0x61, 0x79, 0x1b,
0x91, 0x0f, 0xd6, 0x71, 0x92, 0xd8, 0x17, 0x13, 0xa7, 0xa8, 0xc8, 0x3d, 0xea, 0x32, 0xdf, 0x7d,
0x82, 0x74, 0x3c, 0x3e, 0x92, 0x4a, 0xa8, 0x24, 0x93, 0xd0, 0x0e, 0xb0, 0xd9, 0x49, 0x32, 0x9e,
0x85, 0x5e, 0x31, 0x75, 0x4b, 0x96, 0xce, 0xfe, 0xd8, 0x70, 0x11, 0xaf, 0xf7, 0x80, 0xce, 0x04,
0xc8, 0x3a, 0xf5, 0xe1, 0xc1, 0xca, 0x9a, 0x6f, 0xc3, 0xc3, 0x3a, 0xc9, 0x3d, 0x31, 0x8d, 0xb3,
0x24, 0xce, 0xd8, 0x80, 0x65, 0x01, 0x02, 0x0d, 0xc1, 0x2a, 0x71, 0x58, 0x03, 0xf1, 0x80, 0xc7,
0x7f, 0xb2, 0xa9, 0x33, 0x7c, 0x3a, 0x01, 0x2e, 0x28, 0xc7, 0xd4, 0xdf, 0x94, 0x78, 0xdc, 0x17,
0x79, 0x92, 0x97, 0xf5, 0x70, 0x95, 0x57, 0x87, 0xc2, 0x0b, 0x82, 0x38, 0x0b, 0xa7, 0x2e, 0x6e,
0xb9, 0x02, 0x15, 0xb1, 0x72, 0x50, 0x7a, 0x41, 0xbc, 0xe1, 0xb0, 0xf0, 0x09, 0x6e, 0xbf, 0xf2,
0xfc, 0xbb, 0xb0, 0xcc, 0x37, 0x59, 0x80, 0x02, 0xe6, 0xe5, 0xb4, 0x0c, 0x57, 0x9e, 0x35, 0x1e,
0xbb, 0x76, 0xf3, 0xef, 0x0c, 0xc7, 0x64, 0x96, 0x6f, 0x59, 0x09, 0xe2, 0xec, 0x06, 0xfb, 0x29,
0x1e, 0x46, 0x72, 0x37, 0x50, 0xc6, 0xd4, 0xab, 0x06, 0xbb, 0x38, 0x10, 0x11, 0xec, 0xe1, 0x3c,
0x92, 0xf0, 0xe1, 0xce, 0x3d, 0xc8, 0x63, 0xab, 0x13, 0x4f, 0x79, 0xe1, 0x65, 0x86, 0xab, 0x51,
0xe3, 0x87, 0xa8, 0xb1, 0x46, 0x5d, 0x3c, 0x44, 0x5d, 0x28, 0x54, 0xa4, 0x19, 0x82, 0x5e, 0x4f,
0xb8, 0x45, 0xe3, 0x7b, 0x70, 0xcd, 0x2a, 0xba, 0xb8, 0x07, 0xbf, 0xa8, 0xff, 0x92, 0xb2, 0x20,
0xf6, 0x0c, 0x4b, 0xca, 0x6a, 0xcc, 0x8d, 0x1f, 0xa4, 0xcd, 0x0e, 0x5a, 0x28, 0xfb, 0xb3, 0x12,
0xb8, 0x9f, 0xe1, 0x38, 0xae, 0xeb, 0xf9, 0x48, 0xf9, 0xe9, 0x9c, 0xfb, 0x65, 0x5c, 0x88, 0x45,
0xc2, 0x84, 0xc1, 0xff, 0xa4, 0x37, 0x4b, 0x7b, 0x43, 0x9d, 0xd9, 0x7a, 0x93, 0x49, 0x2f, 0x35,
0x8a, 0x4d, 0xc2, 0x99, 0xc5, 0xc9, 0x81, 0xff, 0x39, 0x2c, 0x36, 0x3c, 0x82, 0xe1, 0x2c, 0x5e,
0x5b, 0x30, 0x4b, 0x58, 0x16, 0x8a, 0x68, 0xf1, 0xd8, 0x21, 0x30, 0xe1, 0x51, 0xbc, 0x16, 0x16,
0x99, 0x25, 0x82, 0x9a, 0x96, 0x63, 0x8f, 0x1d, 0xc7, 0x76, 0xc8, 0xd4, 0xec, 0x03, 0xea, 0x8f,
0x3c, 0xce, 0x2c, 0xd3, 0x36, 0xc9, 0xac, 0xf0, 0xe9, 0xed, 0x01, 0x1c, 0x6e, 0x82, 0x04, 0xf6,
0xd9, 0x21, 0x11, 0x75, 0x7d, 0x3b, 0x63, 0x2b, 0xab, 0x27, 0x77, 0xe9, 0x91, 0x61, 0x9c, 0x65,
0xac, 0x7c, 0xf5, 0xeb, 0x9b, 0x6b, 0x6a, 0x3e, 0xfb, 0xfb, 0xb3, 0xf7, 0x53, 0xc3, 0xec, 0x83,
0x4f, 0x80, 0xdd, 0xad, 0xc2, 0x27, 0xb3, 0xfa, 0x9b, 0x56, 0x2c, 0xce, 0xc4, 0xbb, 0xc2, 0x02,
0xa9, 0x98, 0x78, 0x8d, 0x1e, 0xb7, 0xf5, 0x12, 0xcb, 0x7f, 0x67, 0x83, 0x15, 0x1d, 0x32, 0xeb,
0x02, 0x37, 0x45, 0x00, 0xae, 0x8f, 0x02, 0x9d, 0xac, 0xf7, 0xdf, 0xc1, 0xe2, 0x8a, 0xe2, 0xde,
0x7e, 0x92, 0xfb, 0x77, 0x3d, 0x79, 0xaa, 0x8a, 0x54, 0x1d, 0x11, 0xfe, 0x0a, 0x0b, 0x2d, 0x32,
0xe4, 0x49, 0xec, 0x33, 0xeb, 0xd2, 0x1e, 0x5f, 0x9e, 0x70, 0x50, 0x8c, 0xdf, 0x30, 0x51, 0xc6,
0x3e, 0xb7, 0x52, 0x72, 0x40, 0x05, 0xae, 0xef, 0x68, 0xdf, 0x4a, 0x6f, 0x2e, 0x96, 0x23, 0xd7,
0x01, 0xfa, 0xa1, 0xc8, 0xff, 0x16, 0x57, 0x2c, 0xb0, 0x5c, 0xd0, 0x0c, 0xa0, 0x85, 0x42, 0x5f,
0x7e, 0x06, 0xbd, 0x2e, 0x24, 0xfa, 0x87, 0xfb, 0x68, 0x47, 0xaf, 0x56, 0xe8, 0xc7, 0x9f, 0x41,
0xaf, 0x23, 0x89, 0x9e, 0x2c, 0x89, 0x54, 0x69, 0x79, 0xa2, 0xce, 0xde, 0x2f, 0x6f, 0xdf, 0xbe,
0x9e, 0x1a, 0xbd, 0x7e, 0x7a, 0xe3, 0x2c, 0xfb, 0x3d, 0x23, 0x78, 0x9e, 0xf6, 0x24, 0x59, 0x9a,
0x9f, 0xd2, 0xbd, 0x81, 0x74, 0x84, 0x74, 0x37, 0xbd, 0xeb, 0x7c, 0x67, 0xfc, 0x02, 0x47, 0xec,
0xd9, 0xbd, 0x27, 0xce, 0x78, 0xe8, 0xba, 0xab, 0x76, 0x14, 0xc2, 0xe8, 0xd5, 0xaf, 0x63, 0xc7,
0xb0, 0x5c, 0x37, 0x23, 0x72, 0x72, 0xd9, 0x99, 0xfc, 0xa8, 0x30, 0x5e, 0x45, 0x7a, 0xcb, 0x9b,
0xf4, 0x66, 0xbc, 0xc4, 0x1d, 0x47, 0xc6, 0x8b, 0xc8, 0x83, 0x7d, 0x12, 0x25, 0x85, 0xbb, 0x94,
0xdb, 0x47, 0xcc, 0x2b, 0x7e, 0x0f, 0xbd, 0x4d, 0x78, 0x6a, 0x7d, 0x6d, 0xf4, 0xdb, 0x43, 0x68,
0x83, 0xb3, 0x4c, 0xd0, 0x59, 0xd6, 0x77, 0xb5, 0x8d, 0x6f, 0x01, 0xef, 0xf1, 0x64, 0x02, 0xa6,
0x1e, 0xcb, 0x7f, 0x47, 0xce, 0x24, 0x64, 0x72, 0x61, 0x5f, 0x3a, 0xb6, 0xf6, 0x3e, 0xfb, 0x89,
0x63, 0xdf, 0x3d, 0xaf, 0x6f, 0x95, 0x36, 0x0a, 0x5e, 0x7a, 0xe9, 0x57, 0x6e, 0x54, 0xe8, 0x8d,
0x8a, 0x2f, 0x6d, 0xe4, 0x3a, 0xc7, 0x9d, 0xd0, 0xa1, 0x8f, 0x5b, 0xe1, 0x91, 0x4e, 0x75, 0xfa,
0x0a, 0x20, 0xc6, 0xba, 0x64, 0x52, 0xb1, 0xeb, 0xbb, 0x7e, 0x6f, 0xd4, 0xeb, 0x0b, 0x78, 0x19,
0x77, 0xcf, 0xe7, 0xab, 0x72, 0x61, 0xad, 0x4b, 0x2f, 0x1c, 0x22, 0x2a, 0xea, 0xf7, 0x1e, 0x91,
0xde, 0x51, 0xe0, 0x53, 0x36, 0x3f, 0xbf, 0xfd, 0xe5, 0xd9, 0x9b, 0x23, 0x9f, 0x42, 0xf1, 0x29,
0x24, 0x1f, 0xb5, 0x68, 0x23, 0x4e, 0x57, 0xbc, 0x2b, 0x44, 0x9c, 0x32, 0xa5, 0xf0, 0x27, 0x68,
0x04, 0xce, 0x7c, 0x45, 0x59, 0xde, 0x33, 0xfb, 0xf3, 0xeb, 0x1f, 0x8d, 0x32, 0x4f, 0xb4, 0xe9,
0xb3, 0x3c, 0x43, 0xb3, 0xe7, 0x2b, 0x0e, 0x41, 0xc4, 0x4a, 0x18, 0x16, 0x0c, 0x92, 0x44, 0xc4,
0x4a, 0x2f, 0x81, 0x09, 0x26, 0x79, 0x1c, 0xa1, 0x71, 0x9f, 0x2e, 0x95, 0x21, 0x79, 0x70, 0xcf,
0x8f, 0x5e, 0x5f, 0x8c, 0x61, 0xbb, 0x8c, 0xe7, 0x25, 0xd7, 0x16, 0x77, 0x96, 0x0f, 0xe3, 0x07,
0xa2, 0x50, 0x94, 0xfb, 0xc3, 0x9a, 0x09, 0x3f, 0xb2, 0x7a, 0xa3, 0xf4, 0x62, 0x7c, 0xb5, 0x8b,
0xa9, 0x0b, 0xbc, 0x44, 0xc4, 0x32, 0xab, 0xa4, 0x8b, 0x72, 0x88, 0x35, 0xc3, 0x22, 0x1a, 0x12,
0x89, 0x34, 0xa1, 0x0b, 0x08, 0x5d, 0x1c, 0xe8, 0x1c, 0x34, 0x53, 0x49, 0xaa, 0x92, 0x31, 0xfc,
0x2d, 0x22, 0xc8, 0xa1, 0x64, 0x62, 0x53, 0x66, 0xb0, 0x21, 0x86, 0x06, 0x14, 0x52, 0xc1, 0xd5,
0x0a, 0x08, 0xd7, 0x14, 0x83, 0xba, 0x48, 0x62, 0x61, 0x8d, 0x6e, 0x3e, 0x64, 0x1f, 0x4a, 0x7b,
0xd9, 0x1f, 0xc9, 0xa5, 0x92, 0x4c, 0xf3, 0xa4, 0x94, 0xba, 0xee, 0xf7, 0xdf, 0x2b, 0x10, 0x03,
0x25, 0xec, 0xad, 0x82, 0x2e, 0x46, 0xbf, 0x0d, 0xae, 0x3e, 0x04, 0xfd, 0xb3, 0x11, 0x08, 0xc5,
0x85, 0x55, 0x10, 0x42, 0x0e, 0xa7, 0x89, 0x40, 0x2d, 0x48, 0xbd, 0xc2, 0xfa, 0x69, 0x93, 0xae,
0x58, 0x49, 0xc8, 0xec, 0x44, 0x14, 0x3f, 0x7e, 0xfb, 0xcf, 0x97, 0xd4, 0x9c, 0x63, 0x31, 0x8c,
0x58, 0x1c, 0x46, 0x82, 0xf6, 0xc6, 0x97, 0x3d, 0x43, 0x66, 0x76, 0x35, 0x5c, 0x98, 0xdf, 0xf4,
0xcd, 0xb9, 0x1f, 0x97, 0x7e, 0xc2, 0x0c, 0xbf, 0xa2, 0x3d, 0xd7, 0xed, 0x19, 0xfe, 0x5e, 0xbd,
0x4b, 0xda, 0x73, 0x7a, 0x06, 0x16, 0x4c, 0xda, 0xfb, 0xee, 0xa9, 0xc3, 0xd8, 0x53, 0x98, 0xe6,
0x85, 0xe7, 0xc7, 0x62, 0x8f, 0x28, 0xb5, 0xd8, 0xcb, 0xe2, 0x14, 0x84, 0x32, 0x3c, 0x01, 0x52,
0xad, 0x36, 0x82, 0xfd, 0xe4, 0xa5, 0x8c, 0x42, 0x4e, 0x00, 0xf7, 0xc9, 0x53, 0xc5, 0x49, 0xe4,
0x92, 0x55, 0xb0, 0x01, 0x96, 0x4f, 0x39, 0x70, 0x96, 0x8d, 0xc1, 0x0b, 0x28, 0xa6, 0x20, 0x92,
0xdb, 0x1b, 0x7d, 0x91, 0x93, 0xde, 0xb1, 0xe5, 0xf7, 0xb5, 0xec, 0x46, 0xea, 0x58, 0x0b, 0xa8,
0x3e, 0xdb, 0x70, 0x61, 0xca, 0xc4, 0x05, 0x94, 0x89, 0xe7, 0xb3, 0x40, 0x19, 0x48, 0xcf, 0xac,
0xd1, 0xc1, 0x8f, 0xeb, 0x51, 0x68, 0x4b, 0x85, 0x91, 0x23, 0xf8, 0xc3, 0xe1, 0x26, 0xb9, 0x8e,
0x5e, 0x85, 0x2f, 0x97, 0xf6, 0xcd, 0x6f, 0xf5, 0xb2, 0xff, 0x01, 0x89, 0x40, 0x44, 0x3f, 0x02,
0xb7, 0x40, 0x5f, 0x52, 0xea, 0x36, 0x74, 0x64, 0x4b, 0x0c, 0x26, 0x6e, 0x1f, 0x07, 0x16, 0x23,
0x07, 0x68, 0x55, 0x38, 0x78, 0xfa, 0x70, 0xe7, 0x95, 0x50, 0x97, 0xfe, 0xfa, 0xf6, 0x5a, 0x93,
0x1a, 0xac, 0x2c, 0xa1, 0x83, 0x30, 0x6d, 0xd6, 0x98, 0x0c, 0xac, 0x94, 0x42, 0x23, 0xb6, 0x08,
0x4b, 0xaf, 0x88, 0x8c, 0x4d, 0xe6, 0x6d, 0xbd, 0x38, 0xf1, 0x56, 0x09, 0x03, 0xe9, 0x25, 0xc2,
0x9c, 0xd5, 0x35, 0x99, 0x55, 0x34, 0x18, 0xfa, 0xd0, 0xb5, 0x09, 0xf6, 0x63, 0xc2, 0x52, 0x88,
0x0d, 0xab, 0x17, 0xc4, 0x5b, 0xa8, 0x28, 0xd5, 0x49, 0xa2, 0x51, 0x87, 0xd4, 0x0e, 0x88, 0x85,
0xf2, 0x59, 0x59, 0x7a, 0xfb, 0x21, 0x2a, 0xd0, 0xaa, 0x86, 0x21, 0x13, 0x7a, 0x35, 0x7f, 0xbe,
0xff, 0xd5, 0x0b, 0x51, 0xcd, 0x10, 0x55, 0xb2, 0x36, 0xf7, 0x08, 0xd2, 0x0f, 0xd7, 0x79, 0xf9,
0xa3, 0x87, 0x67, 0x80, 0x00, 0x60, 0xa8, 0xa8, 0x14, 0xfa, 0x1a, 0x08, 0x8c, 0x19, 0x14, 0x86,
0x6a, 0xb8, 0x8e, 0x4b, 0x2e, 0x5e, 0x44, 0x71, 0x12, 0xcc, 0xc0, 0x14, 0x10, 0x92, 0xa2, 0x18,
0xc6, 0x81, 0xf4, 0x6b, 0x91, 0x13, 0x91, 0xeb, 0x6e, 0xf2, 0x27, 0xc8, 0xef, 0x8d, 0x32, 0x25,
0x35, 0xd0, 0xd9, 0x40, 0x30, 0x63, 0x10, 0x42, 0x86, 0xcc, 0x3c, 0x58, 0x39, 0xbc, 0xa2, 0x00,
0xa5, 0x34, 0x04, 0x0f, 0xb6, 0x97, 0xc5, 0x8e, 0xfb, 0x0f, 0x0f, 0xde, 0x08, 0x3c, 0xe3, 0xbe,
0x8c, 0xdb, 0x17, 0xaa, 0x2f, 0xa4, 0x20, 0x6f, 0x67, 0x3a, 0x0b, 0x86, 0xd8, 0x1e, 0x9f, 0xec,
0xc2, 0xb1, 0xc0, 0xab, 0xbf, 0xd6, 0x54, 0x75, 0xfd, 0x0d, 0xd4, 0xf2, 0x5f, 0x21, 0x87, 0xe5,
0x1b, 0x61, 0xc9, 0x9a, 0x0f, 0x39, 0x1a, 0xea, 0x5d, 0x9b, 0x45, 0x9a, 0xee, 0xc0, 0xdb, 0x43,
0xb7, 0x19, 0xa8, 0x22, 0xcc, 0xe9, 0x5b, 0x50, 0x72, 0x16, 0xb6, 0xd0, 0x46, 0xe7, 0x98, 0x1d,
0xf8, 0x90, 0x0b, 0x0c, 0xd1, 0xff, 0x89, 0x45, 0x64, 0x99, 0x07, 0x93, 0x7c, 0xfc, 0x08, 0x30,
0xe0, 0xa2, 0x21, 0xb5, 0x49, 0x88, 0xb6, 0xbe, 0xf2, 0x4e, 0x69, 0x42, 0xca, 0x75, 0x1b, 0xe0,
0xda, 0x83, 0xa6, 0x96, 0xef, 0x0b, 0x50, 0xa0, 0x57, 0x52, 0x49, 0x00, 0x55, 0x55, 0x42, 0x79,
0x94, 0xef, 0x5e, 0x42, 0xab, 0x45, 0x5b, 0x34, 0x6d, 0x87, 0x50, 0xb0, 0xdf, 0xc1, 0x81, 0xcb,
0x17, 0x1e, 0x24, 0xab, 0x23, 0x93, 0x2e, 0x1e, 0x6a, 0x6e, 0x8b, 0x07, 0x69, 0xcd, 0x24, 0x32,
0xc1, 0x87, 0xfc, 0x64, 0x13, 0x30, 0x6e, 0x21, 0x1d, 0x51, 0x47, 0x4c, 0xd5, 0xae, 0x43, 0xe9,
0xdd, 0xd6, 0xe8, 0x37, 0x4b, 0xc6, 0xc4, 0x92, 0xd8, 0x16, 0x64, 0xa7, 0xf6, 0x39, 0xec, 0x93,
0x33, 0x95, 0xd9, 0xbe, 0x4d, 0x4f, 0x0e, 0x75, 0x63, 0xdb, 0x3b, 0xd0, 0x91, 0x1d, 0xe1, 0x83,
0xc3, 0x63, 0x49, 0x53, 0x29, 0xcf, 0x8e, 0xf6, 0x11, 0x21, 0xc7, 0xd0, 0x4e, 0x44, 0xcd, 0xb8,
0x4a, 0x68, 0xb6, 0x49, 0x12, 0x75, 0x44, 0x16, 0x52, 0x5c, 0xa3, 0x73, 0xa8, 0xf9, 0x11, 0x3a,
0x3b, 0xf0, 0x0d, 0x4b, 0x2a, 0x0b, 0x5a, 0xc7, 0x78, 0x0e, 0x14, 0x4d, 0x66, 0x8e, 0xfb, 0x7d,
0x72, 0xc0, 0x7e, 0x91, 0x85, 0x37, 0xf1, 0xf2, 0x44, 0xf7, 0x55, 0x32, 0x05, 0x5d, 0x1f, 0x80,
0x75, 0x83, 0xdc, 0xac, 0xb8, 0x32, 0xdc, 0x45, 0x93, 0xa0, 0x65, 0xdb, 0x88, 0xec, 0x70, 0x0a,
0xea, 0x8f, 0x6d, 0xd0, 0xbe, 0xbc, 0x21, 0x41, 0xe4, 0xc9, 0x7c, 0xca, 0xe5, 0x72, 0x99, 0x70,
0x51, 0x30, 0xe5, 0x99, 0x69, 0xaa, 0x81, 0x4a, 0x3b, 0x1f, 0xac, 0x13, 0xb5, 0xe0, 0xf3, 0x03,
0x99, 0x5a, 0xc3, 0xf3, 0xa3, 0x7a, 0x1a, 0xfd, 0x18, 0xf2, 0x98, 0x6a, 0x7c, 0x80, 0xcb, 0xc4,
0xf4, 0xa6, 0x9f, 0x62, 0xb3, 0x62, 0xe3, 0x6b, 0xac, 0x5e, 0x17, 0xcb, 0xa5, 0x0d, 0xad, 0xe5,
0x86, 0xf1, 0x69, 0x8a, 0xcd, 0x5c, 0x47, 0x56, 0x29, 0xc7, 0x96, 0x2e, 0x54, 0xee, 0xb7, 0xb6,
0xda, 0xf3, 0x08, 0xa9, 0xc1, 0xb1, 0x21, 0x44, 0x13, 0xe8, 0x4a, 0xad, 0xe7, 0x39, 0xa4, 0x1f,
0x2f, 0x83, 0x83, 0xed, 0x62, 0x14, 0x50, 0xda, 0xf5, 0xe0, 0x83, 0xd5, 0xcd, 0xc4, 0x9c, 0x9e,
0xa4, 0xaf, 0xeb, 0x97, 0x16, 0x4f, 0xec, 0x9d, 0x1d, 0xd9, 0x8d, 0x63, 0xd9, 0x55, 0xd2, 0x68,
0x40, 0xae, 0x88, 0xcc, 0x29, 0xaa, 0x37, 0xe9, 0x94, 0x2d, 0xf2, 0xcd, 0x09, 0x8f, 0x57, 0x80,
0x05, 0xf7, 0x1c, 0x2a, 0x91, 0x6d, 0x35, 0x81, 0xa3, 0x7d, 0x8e, 0x2d, 0x28, 0x16, 0x73, 0x06,
0x7a, 0x26, 0xa5, 0x66, 0x68, 0x2a, 0x8f, 0x2b, 0xb4, 0xc7, 0xa9, 0xb3, 0x42, 0x13, 0x40, 0x1a,
0x87, 0x29, 0x50, 0x3d, 0x11, 0xbe, 0xc7, 0x52, 0x2f, 0x38, 0xba, 0x58, 0xda, 0x69, 0x9c, 0xe1,
0xe8, 0x12, 0x46, 0x5e, 0x85, 0xa3, 0x89, 0x8e, 0x10, 0x91, 0x17, 0x1c, 0xae, 0x19, 0x1d, 0x9f,
0x79, 0x0c, 0x3e, 0x53, 0xe8, 0x03, 0x0c, 0x5c, 0xf0, 0x19, 0x7a, 0x09, 0x9d, 0x3d, 0xd2, 0xa9,
0x2b, 0xc7, 0x41, 0x8a, 0x3e, 0x05, 0x1e, 0x71, 0x1f, 0x38, 0xab, 0x9b, 0xde, 0x2d, 0x1c, 0xc1,
0x3a, 0x3b, 0x00, 0x6c, 0x59, 0xdb, 0xf2, 0xdd, 0x77, 0xdb, 0xd1, 0x78, 0x59, 0x93, 0x5b, 0x4c,
0x25, 0xd2, 0x47, 0x36, 0x59, 0x2c, 0x68, 0x71, 0x73, 0xdc, 0xe2, 0x18, 0xab, 0xff, 0x44, 0xce,
0xd4, 0xea, 0x44, 0xab, 0xf9, 0xd2, 0x6c, 0xf2, 0xbf, 0x56, 0xe0, 0x4b, 0x0b, 0xf6, 0xc7, 0xf3,
0xe0, 0x49, 0x94, 0xd6, 0x50, 0x36, 0x1b, 0xd9, 0xda, 0x2d, 0x13, 0xdc, 0xac, 0x8d, 0xb1, 0x4e,
0x9f, 0xb3, 0xda, 0x40, 0x76, 0x43, 0x15, 0x5b, 0xb8, 0x32, 0xdd, 0xda, 0xc1, 0xb5, 0xbd, 0x17,
0xf4, 0xd2, 0xf6, 0xdf, 0xcb, 0x98, 0x52, 0xea, 0x4d, 0x32, 0xbc, 0x79, 0x25, 0x2b, 0x7c, 0x42,
0x44, 0xdc, 0x28, 0x09, 0xb3, 0x94, 0xbe, 0xf1, 0x44, 0x34, 0xf4, 0x59, 0x9c, 0x58, 0xe9, 0x16,
0xfa, 0x7e, 0x72, 0xee, 0x3a, 0x2a, 0x73, 0xc4, 0x34, 0x4b, 0x47, 0x7b, 0x21, 0xf3, 0x7b, 0x3c,
0xa7, 0x13, 0x02, 0x90, 0x89, 0xca, 0xe2, 0x1a, 0x04, 0xd4, 0x00, 0x03, 0xfa, 0x2e, 0x70, 0x2c,
0x09, 0xc7, 0x9a, 0x12, 0x86, 0xc7, 0x0d, 0x44, 0x3c, 0x9a, 0xc0, 0x06, 0x13, 0x67, 0x76, 0x7f,
0x5f, 0x11, 0x93, 0x73, 0x11, 0xcb, 0x7d, 0x3d, 0xd1, 0x41, 0x81, 0x04, 0x80, 0xba, 0x17, 0xfd,
0xd4, 0x13, 0x2a, 0xec, 0x11, 0xb4, 0xa5, 0x71, 0xb3, 0x72, 0x4f, 0xa3, 0x81, 0xb5, 0x3d, 0x8f,
0x46, 0x59, 0x0a, 0xbe, 0x93, 0x29, 0xd3, 0xde, 0xce, 0xf1, 0xf3, 0x80, 0x51, 0xb9, 0xd4, 0x74,
0x4c, 0x63, 0x0f, 0xaf, 0xb3, 0xc3, 0xbe, 0x36, 0x8d, 0x6a, 0x8c, 0xa3, 0x1d, 0x8c, 0xf6, 0xe3,
0x06, 0x06, 0xc9, 0x21, 0xbf, 0x63, 0xd4, 0xfc, 0x2e, 0x08, 0x82, 0x66, 0xa6, 0x6e, 0xef, 0xd4,
0x74, 0xcd, 0xd1, 0xe2, 0x56, 0xc6, 0x72, 0x3c, 0xf7, 0x04, 0x49, 0x56, 0x0d, 0x7b, 0xac, 0x3a,
0x46, 0x45, 0xcd, 0xc1, 0x04, 0x58, 0x49, 0x4e, 0xfd, 0x4b, 0xe0, 0x85, 0xe0, 0x81, 0x97, 0xf9,
0x51, 0x5e, 0x52, 0x13, 0xac, 0x6c, 0x2e, 0xce, 0x0e, 0x5b, 0xb8, 0x13, 0x23, 0x1c, 0x19, 0xc9,
0x18, 0xf0, 0xdf, 0x7f, 0xff, 0xbd, 0xff, 0xbe, 0xb9, 0xe8, 0x3a, 0xea, 0x44, 0x15, 0xa7, 0x2d,
0x0c, 0x9c, 0xf5, 0xf4, 0xec, 0x2d, 0xe6, 0xa8, 0x81, 0x8a, 0x56, 0x7c, 0xe1, 0x5c, 0xc5, 0xe7,
0xd6, 0x6e, 0x54, 0x71, 0x32, 0xdd, 0x8d, 0xc6, 0xb3, 0x2a, 0xb9, 0x2f, 0xde, 0xd9, 0xa1, 0xaa,
0xb5, 0x80, 0x51, 0x7f, 0xec, 0xdc, 0x97, 0x30, 0x8d, 0x83, 0x20, 0x61, 0x28, 0xa4, 0xff, 0x1e,
0x1d, 0xbe, 0x23, 0x68, 0x8d, 0x86, 0x6c, 0x24, 0xbb, 0xbc, 0x6f, 0x8b, 0x8a, 0x77, 0x25, 0x69,
0x84, 0x50, 0x39, 0x5d, 0xdb, 0x5e, 0x7e, 0x3e, 0xb1, 0x00, 0x15, 0x5c, 0x43, 0x6d, 0x93, 0xe8,
0xff, 0x87, 0x7c, 0x55, 0x7c, 0x22, 0x9c, 0x0e, 0x89, 0x03, 0x5a, 0x99, 0x4f, 0xc1, 0xea, 0xea,
0x6b, 0x80, 0x49, 0x6c, 0x68, 0xa4, 0x40, 0xf2, 0x29, 0x58, 0xaa, 0x05, 0x41, 0x45, 0x80, 0x9d,
0x9b, 0x69, 0xdd, 0x8d, 0xa2, 0x36, 0x12, 0xed, 0x14, 0xc2, 0xb0, 0x89, 0x42, 0x7b, 0x43, 0x4d,
0xd3, 0xe6, 0x5b, 0x2a, 0x4a, 0x08, 0x40, 0x79, 0x48, 0x3f, 0xa1, 0x15, 0x5d, 0xc8, 0x83, 0x41,
0xb4, 0x5a, 0x40, 0xad, 0xc6, 0xc0, 0x13, 0x96, 0x55, 0x44, 0x9d, 0x7d, 0x9b, 0x50, 0x3f, 0xb1,
0xb6, 0x6a, 0xc2, 0x0b, 0x9a, 0x56, 0x83, 0x34, 0xfb, 0xf8, 0xd1, 0x55, 0xa1, 0x45, 0xad, 0x6d,
0x02, 0x73, 0x32, 0xe2, 0x85, 0x04, 0x40, 0x0f, 0x0e, 0x56, 0xb3, 0xa1, 0x05, 0x8f, 0xc0, 0x78,
0xb2, 0x65, 0xcd, 0x69, 0xcb, 0x16, 0x44, 0x21, 0xe7, 0xce, 0xf0, 0x72, 0xa2, 0x30, 0x31, 0x2d,
0x73, 0x98, 0xfe, 0xa0, 0xa6, 0x3c, 0x57, 0x53, 0xe7, 0xb1, 0xcd, 0x35, 0xe6, 0x42, 0x61, 0xc2,
0x80, 0x2a, 0x5e, 0x3c, 0xa0, 0x03, 0xf7, 0x62, 0xd2, 0x0f, 0x03, 0x1b, 0x7a, 0x60, 0x18, 0x0d,
0x60, 0xc4, 0x77, 0x01, 0x65, 0xc1, 0x80, 0x07, 0x9d, 0x2f, 0x31, 0x79, 0xe2, 0x95, 0x96, 0x5f,
0x81, 0x1c, 0x76, 0x69, 0xeb, 0x5e, 0xa7, 0x0c, 0xa8, 0x15, 0x0c, 0x9e, 0x42, 0xd8, 0x4a, 0x79,
0x7e, 0x7e, 0x3d, 0x72, 0x9f, 0x38, 0x4d, 0xfd, 0xaa, 0xa6, 0x7e, 0xd5, 0x2f, 0x15, 0xc6, 0xcf,
0xb9, 0x55, 0x06, 0xc4, 0xde, 0x4f, 0xfd, 0x7d, 0x03, 0xe3, 0x20, 0x3d, 0xc0, 0x4e, 0x14, 0xed,
0x95, 0xfe, 0xcf, 0x80, 0x6b, 0xf7, 0xf1, 0x1c, 0xdb, 0x73, 0x75, 0x8e, 0xf2, 0xd4, 0xa1, 0xbd,
0x15, 0xb7, 0x3c, 0x77, 0xe0, 0x39, 0x64, 0x01, 0x9b, 0x5d, 0xb9, 0x53, 0x95, 0x91, 0xf8, 0x8e,
0x7a, 0xee, 0xc2, 0x3b, 0x02, 0x0a, 0x87, 0x9e, 0xca, 0xec, 0xe9, 0x2f, 0x15, 0x85, 0x7b, 0x1f,
0xe1, 0x36, 0x29, 0xf7, 0xf6, 0x8d, 0x01, 0x19, 0xdc, 0x19, 0x56, 0x32, 0x93, 0x3b, 0xc3, 0x7d,
0x6d, 0x3c, 0x03, 0x48, 0x89, 0xd3, 0xb2, 0x36, 0x1c, 0x18, 0x27, 0x5e, 0x0d, 0x4f, 0xbe, 0xc3,
0x67, 0xe1, 0x6a, 0x4a, 0x17, 0x28, 0x6f, 0x4f, 0x3e, 0xf0, 0x60, 0x91, 0xf8, 0x1b, 0x04, 0xc3,
0x56, 0xf6, 0x1f, 0xdf, 0x72, 0xa1, 0x23, 0xb3, 0x69, 0x84, 0xb0, 0x7c, 0xb8, 0x63, 0xc7, 0xd6,
0xff, 0x44, 0x75, 0x7b, 0x3e, 0xe5, 0x02, 0xeb, 0xa3, 0x5c, 0x7e, 0xbf, 0xad, 0x11, 0xf7, 0xbb,
0x9a, 0xed, 0x02, 0xc9, 0x63, 0x5d, 0x5b, 0x89, 0xaf, 0x67, 0x6a, 0xb1, 0x4c, 0xb0, 0xba, 0xa4,
0x36, 0x15, 0xd9, 0xd7, 0x15, 0x29, 0xe7, 0xe0, 0xb8, 0x5f, 0x66, 0x2f, 0x3f, 0xe9, 0x94, 0xd4,
0xea, 0x6c, 0xd0, 0xf5, 0x49, 0xe8, 0xf1, 0x2d, 0xa8, 0x78, 0xc7, 0x55, 0xe4, 0x4a, 0x92, 0x42,
0x11, 0x3c, 0x21, 0x9e, 0x2a, 0x9f, 0xf6, 0x1c, 0xca, 0x83, 0x3e, 0x78, 0xd5, 0xf9, 0x1a, 0xd5,
0xdd, 0x4c, 0x44, 0x8e, 0x29, 0xd3, 0x73, 0x21, 0x69, 0x3b, 0x04, 0xbf, 0xe1, 0xc6, 0xd9, 0x86,
0xcd, 0x72, 0xde, 0xa7, 0xb7, 0xf3, 0x02, 0x6c, 0x6d, 0x04, 0x18, 0xf1, 0x1d, 0x9f, 0x80, 0x7b,
0xa8, 0x0d, 0xde, 0x6f, 0x03, 0x43, 0xf8, 0x77, 0x49, 0x27, 0x2d, 0x83, 0x4d, 0x8e, 0xa7, 0xaf,
0xef, 0x27, 0x68, 0xc0, 0xe6, 0x00, 0x94, 0x17, 0x57, 0x13, 0xbf, 0x2f, 0x60, 0xc2, 0xd6, 0xda,
0x58, 0x89, 0x2f, 0x6f, 0x17, 0xdb, 0x10, 0x24, 0xb6, 0xc1, 0x82, 0xee, 0x76, 0xf2, 0x4b, 0x6f,
0xc7, 0x80, 0xf8, 0xa5, 0x77, 0x42, 0x3e, 0xb1, 0x6d, 0xfc, 0x70, 0x5b, 0xe9, 0x84, 0x3e, 0x3d,
0xfa, 0x48, 0xa2, 0x1d, 0x93, 0xb5, 0x8a, 0x51, 0xbd, 0xbf, 0x47, 0x05, 0xe4, 0xee, 0xaf, 0x11,
0x0e, 0x5a, 0xb8, 0x13, 0x5d, 0x14, 0x7e, 0xfd, 0x55, 0xb2, 0x4c, 0xf5, 0x35, 0x43, 0x54, 0xe8,
0x11, 0xd8, 0xca, 0x6d, 0xb5, 0xed, 0x79, 0x37, 0x3d, 0xdf, 0xcf, 0x36, 0xee, 0x13, 0xb8, 0xf6,
0x55, 0xb4, 0x9b, 0xa0, 0xfd, 0x36, 0x43, 0xfb, 0xfb, 0xcf, 0xe4, 0x67, 0x23, 0xc8, 0x81, 0x87,
0x97, 0x89, 0xc1, 0x0a, 0x7a, 0x47, 0xcc, 0xc9, 0x47, 0x54, 0xfb, 0x51, 0x1e, 0x39, 0xac, 0x79,
0x2b, 0xa8, 0x3e, 0x8a, 0x44, 0xef, 0xd4, 0x07, 0x0b, 0xf3, 0xb1, 0xe3, 0xc8, 0x82, 0x99, 0xd4,
0x67, 0x87, 0x4d, 0x9b, 0xef, 0x5b, 0x37, 0xbf, 0x95, 0x5f, 0x37, 0xda, 0x53, 0x63, 0x15, 0xd7,
0x9f, 0x3a, 0xb0, 0x7c, 0xc0, 0x6c, 0x1b, 0xb3, 0xdd, 0xf3, 0x1c, 0xa4, 0x76, 0x64, 0x44, 0xcb,
0x50, 0x06, 0x38, 0xb0, 0xcc, 0x39, 0xb0, 0x5c, 0x09, 0x78, 0xc4, 0x1e, 0x3c, 0x44, 0x55, 0xab,
0x2f, 0x05, 0xb7, 0xcd, 0x1d, 0xee, 0x41, 0x4d, 0xe8, 0xb4, 0xc8, 0xa1, 0xac, 0x03, 0xb6, 0xbf,
0x81, 0xfe, 0x2c, 0x7d, 0x7f, 0xdd, 0x69, 0xaf, 0xd2, 0x2d, 0x6d, 0x4b, 0xc2, 0x70, 0x08, 0x77,
0xb9, 0xe1, 0x3a, 0xf1, 0xc4, 0x1b, 0xbc, 0x34, 0xd0, 0x05, 0xd7, 0xad, 0x31, 0xb1, 0xf5, 0xd5,
0x0e, 0x52, 0x33, 0x0f, 0xaf, 0x1e, 0x34, 0x6e, 0xdd, 0x36, 0x5a, 0x07, 0x9d, 0x7d, 0xd9, 0xee,
0x46, 0xa6, 0xba, 0xc8, 0x41, 0x45, 0xd2, 0xb5, 0x0d, 0x46, 0x78, 0xc9, 0x31, 0x6b, 0x95, 0x5a,
0x92, 0xb8, 0x78, 0x1d, 0xd0, 0x5b, 0x7c, 0xff, 0x7e, 0x76, 0x50, 0xc6, 0xf5, 0x32, 0x30, 0x89,
0x85, 0xdf, 0x72, 0xf5, 0x9d, 0xf5, 0xe2, 0x71, 0xf3, 0xc5, 0x79, 0x4c, 0xea, 0xdb, 0x76, 0x21,
0x58, 0x1a, 0x5f, 0xe8, 0x7a, 0x46, 0x2c, 0x1d, 0x51, 0xb1, 0x03, 0xa5, 0xcd, 0xf1, 0x07, 0x25,
0x74, 0x01, 0x47, 0x9a, 0x1f, 0x9e, 0x9f, 0xd7, 0xfc, 0x68, 0x31, 0x1f, 0x35, 0x7c, 0x74, 0x1c,
0x04, 0x6b, 0xd9, 0x79, 0xee, 0xb1, 0xe7, 0x44, 0xbd, 0xe8, 0x5b, 0xbe, 0xc5, 0xed, 0x98, 0xe8,
0xeb, 0x54, 0x18, 0xd3, 0xdb, 0x10, 0x2c, 0x52, 0x7f, 0xb5, 0xd8, 0xc1, 0xba, 0xdb, 0xde, 0x79,
0xe5, 0x4b, 0xfc, 0xd9, 0x06, 0x2e, 0xfe, 0x5a, 0xf6, 0x10, 0xe3, 0xa0, 0xd3, 0xf3, 0x39, 0xaa,
0xe1, 0x73, 0x9a, 0x6e, 0x0f, 0x7d, 0x24, 0xd4, 0x4b, 0xde, 0x41, 0x97, 0x0d, 0xd9, 0x72, 0xc3,
0x59, 0xf9, 0xb6, 0xf0, 0x7c, 0xf6, 0x8f, 0xec, 0x1d, 0x5c, 0x7b, 0x16, 0x73, 0xec, 0xc1, 0x8d,
0x7c, 0xbd, 0xe6, 0x0c, 0x0e, 0xe7, 0x3c, 0x32, 0xe5, 0xc5, 0x42, 0xfd, 0x0a, 0xa4, 0x93, 0x03,
0xc4, 0x1b, 0xde, 0x73, 0xc0, 0x68, 0x58, 0x22, 0xd4, 0xd8, 0xed, 0x8c, 0xe1, 0x92, 0x60, 0x63,
0x39, 0x26, 0xa8, 0x96, 0x13, 0x76, 0xf8, 0x63, 0xd0, 0xff, 0x95, 0xa1, 0xe4, 0x36, 0x3a, 0x3d,
0x36, 0xf6, 0x42, 0x32, 0xbf, 0x08, 0xda, 0x38, 0x9b, 0xbc, 0x29, 0x42, 0x3b, 0xf3, 0x47, 0xa3,
0xe4, 0xea, 0x88, 0xd2, 0x0d, 0xa7, 0x7b, 0x65, 0xfd, 0x71, 0xbe, 0x23, 0x23, 0xeb, 0x1e, 0x02,
0xba, 0x34, 0x5d, 0x61, 0xf7, 0x74, 0x3b, 0xa7, 0xce, 0x55, 0x34, 0xc5, 0xce, 0x7a, 0x94, 0x6e,
0xc9, 0x79, 0xd4, 0xd4, 0x51, 0x6c, 0xd8, 0x6c, 0xec, 0x9b, 0x6f, 0xf1, 0xe6, 0xa9, 0x7a, 0x2a,
0x03, 0x6e, 0x36, 0xc9, 0xbe, 0xb1, 0x0c, 0x94, 0xe0, 0xbd, 0x6c, 0xbe, 0x0b, 0x40, 0xa2, 0x8a,
0x07, 0xf2, 0x47, 0x99, 0x48, 0xd6, 0x55, 0x81, 0xcf, 0x1d, 0x76, 0x7a, 0xb6, 0x32, 0x87, 0x4a,
0x06, 0x9b, 0x32, 0xb1, 0xbe, 0x93, 0xf6, 0x23, 0x27, 0x99, 0xf8, 0x6b, 0x54, 0xf3, 0x20, 0x37,
0x8f, 0x4d, 0xe9, 0xda, 0x03, 0x4c, 0xae, 0x2d, 0x6b, 0xed, 0xd6, 0x44, 0xf5, 0xf3, 0x75, 0xdb,
0x17, 0x7c, 0x31, 0xa5, 0xf4, 0x2f, 0x9c, 0x6e, 0x56, 0x19, 0x4c, 0x74, 0x5e, 0xe9, 0x83, 0x21,
0x65, 0x6e, 0xe9, 0x5f, 0x3a, 0x32, 0xbd, 0x84, 0xc1, 0x50, 0x06, 0x69, 0xad, 0x86, 0x32, 0x4c,
0xd5, 0xb8, 0x4a, 0xea, 0x79, 0xc0, 0xd6, 0x7c, 0xa1, 0x44, 0x00, 0x20, 0xf8, 0x70, 0xdb, 0x8a,
0xce, 0x47, 0x1a, 0x07, 0xea, 0xeb, 0x00, 0xbf, 0x9c, 0x99, 0x5e, 0x59, 0x5b, 0x6e, 0xb7, 0x17,
0xed, 0x26, 0x39, 0xbd, 0xff, 0x7c, 0x5a, 0xda, 0xea, 0xce, 0x9c, 0xfb, 0x34, 0xdd, 0x2e, 0xa2,
0xab, 0x08, 0x4c, 0xaa, 0xeb, 0x77, 0x58, 0xe8, 0x6e, 0x51, 0x84, 0xd0, 0x9d, 0x76, 0x3c, 0xe1,
0x3c, 0x54, 0xfd, 0xc0, 0x6a, 0x47, 0xad, 0xdd, 0x40, 0x84, 0x64, 0xd4, 0x62, 0x67, 0x3a, 0x76,
0xcd, 0x28, 0xfc, 0xdd, 0xec, 0x5b, 0x27, 0x91, 0x7b, 0xee, 0xb2, 0xa7, 0x1f, 0x75, 0x3b, 0x06,
0x19, 0xe0, 0xdf, 0xc6, 0xe9, 0x23, 0x1d, 0xa8, 0x8f, 0x74, 0xa4, 0x3e, 0xfa, 0x4f, 0x87, 0xea,
0xd1, 0x79, 0x8e, 0xae, 0xd3, 0xc4, 0x94, 0xfb, 0xbf, 0x88, 0xd0, 0x2f, 0xf1, 0x81, 0xb2, 0xf9,
0xe9, 0xe8, 0x54, 0x9d, 0x3b, 0xa7, 0xbd, 0xde, 0x0c, 0xb4, 0xd7, 0xa6, 0xc0, 0xed, 0x31, 0x05,
0xae, 0xa2, 0x63, 0xc3, 0xbf, 0x3d, 0xe7, 0x3e, 0x54, 0xe1, 0xf6, 0x52, 0xbb, 0x52, 0x9a, 0x96,
0xd7, 0xab, 0xd5, 0xae, 0x1f, 0x16, 0xe0, 0xb4, 0xb2, 0x83, 0x6a, 0x12, 0x73, 0xe7, 0xf2, 0x84,
0x95, 0xb9, 0xf5, 0xe2, 0xd5, 0xa9, 0x1b, 0xaf, 0x3e, 0x1d, 0x67, 0xb2, 0x4f, 0xd2, 0x1f, 0x2f,
0x43, 0xf2, 0x75, 0x15, 0x76, 0xd1, 0xf8, 0x72, 0xb0, 0x3e, 0xfa, 0x6e, 0xc9, 0x1b, 0x97, 0x6d,
0xaa, 0xdc, 0x83, 0x12, 0xb7, 0xed, 0x16, 0xb6, 0xf7, 0xff, 0x8d, 0xe0, 0xfb, 0xb4, 0xc0, 0x6d,
0x8c, 0xc1, 0x5c, 0xfd, 0x5c, 0x3d, 0x0f, 0xe2, 0xad, 0x7c, 0x40, 0x0e, 0xf1, 0x38, 0xa7, 0xea,
0xeb, 0x36, 0xfa, 0x6d, 0x51, 0x76, 0xe1, 0x26, 0xfe, 0xd4, 0x1f, 0x41, 0xaa, 0x01, 0x4c, 0x9c,
0xad, 0xf3, 0xc5, 0x3c, 0x1a, 0xe3, 0x58, 0xfe, 0xea, 0x0b, 0xae, 0x10, 0x8d, 0x17, 0xf3, 0x02,
0x01, 0x1b, 0xe0, 0x39, 0x2a, 0xf4, 0xa4, 0xcc, 0x3b, 0x13, 0x1e, 0xa8, 0xc9, 0xaa, 0x6c, 0xb0,
0x1d, 0x64, 0xda, 0xa5, 0x94, 0xbf, 0x43, 0x1d, 0x89, 0x51, 0x0a, 0x80, 0x1e, 0x7f, 0xe3, 0x04,
0x94, 0x14, 0xbb, 0x68, 0xc0, 0x8a, 0x56, 0xd3, 0x75, 0x7e, 0xa3, 0x3c, 0x21, 0x94, 0x70, 0x45,
0xa9, 0xa0, 0x9d, 0xe7, 0xbf, 0x00, 0xe4, 0xe5, 0x5a, 0x82, 0x6d, 0x22, 0x00, 0x00
/* AUTO-GENERATED (END) */
};
constexpr unsigned int MI32_STATIC_PAGE_len = sizeof(MI32_STATIC_PAGE);
#endif //USE_MI_EXT_GUI
#endif // USE_WEBSERVER
#endif //USE_MI_ESP32