Files
Tasmota/tasmota/include/xsns_62_esp32_mi.h
T
Yvan JanssensandClaude Fable 5.1 d8f0d14005 MI32: add Xiaomi Mi Body Composition Scale (MIBCS/MIBFS) support (#25049)
Decode the 13-byte body composition record the scale broadcasts as
service data for UUID 0x181B (also sent as 0x2A9C notifications), and
register the scale as MI32 device type MIBCS with fake product ID 0x181b.

Per weighing session the driver publishes the weight once when the
scale's stable flag is set and the impedance once when the impedance
stable flag is set (repeating the weight so the message is self
contained). If the user steps off before a usable impedance arrives,
ImpedanceFailed:1 is published instead. A session ends on the finished
flag or after 10 s of silence, so repeated broadcasts of the final
record do not produce duplicate messages. Overload readings (0xFFF0)
and the impedance sentinels 0xFFFE/0xFFFD are ignored. The unit (kg,
lb, jin) is derived from the flag bits and published alongside.

Web UI: weight and impedance rows on the main page; with USE_MI_EXT_GUI
a dashboard tile with a 24 h weight history graph (kept in kg).

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 10:29:18 +02:00

1181 lines
53 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
#ifdef CONFIG_BT_NIMBLE_EXT_ADV
uint8_t payload[255];
#else
uint8_t payload[62]; // legacy advertisement plus scan response
#endif
};
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
};
};
// Xiaomi Mi Body Composition Scale (MIBCS/MIBFS), service data of UUID 0x181B, 13 bytes, little endian
struct MiScalePacket_t {
uint8_t flagsA; // bit0: unit lb, bit2: "part"/partial result
uint8_t flagsB; // bit1: impedance stable, bit5: weight stable, bit6: unit jin, bit7: finished (user stepped off)
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint16_t impedance; // ohms; 0xFFFE = measuring, 0xFFFD = failed
uint16_t weight; // raw; /200 for kg, /100 for lb and jin; 0xFFF0 = overload
} __attribute__((packed));
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;
uint16_t itvl_min;
uint16_t itvl_max;
uint8_t * buffer;
// Reverse-role client (peripheral side). Singleton owned by NimBLEServer
// (do NOT pass to NimBLEDevice::deleteClient). Use only for server-side
// ACNS-style queries toward the connected central. Set in
// MI32ServerCallbacks::onConnect, cleared (not freed) in onDisconnect.
NimBLEClient * serverPeer = nullptr;
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;
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 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 triggerNextJob:1;
uint32_t readyForNextJob:1;
uint32_t discoverAttributes:1;
uint32_t deleteConnectionTask:1; // request server-task teardown (renamed from deleteServerTask)
};
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;
uint8_t *beAdvBuf;
uint16_t queueDrops = 0;
uint8_t role = 0; // bitfield of MI32Role values (concurrently active roles)
} 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 weight:1; // body composition scale
uint32_t impedance:1; // body composition scale
};
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 weight:1; // stable weight available
uint32_t impedance:1; // stable impedance available
uint32_t impedanceFailed:1; // session ended without a usable impedance
};
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 { // MIBCS / MIBFS body composition scale
float weight; // in the unit the scale reports (see weightUnit)
uint16_t impedance; // ohms, 0 = not measured
uint8_t weightUnit; // 0 = kg, 1 = lb, 2 = jin
uint8_t scaleState; // session bookkeeping, see MI32_SCALE_* flags
uint32_t scaleLastSeen; // uptime of last packet, for session timeout
uint8_t weight_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 MIBCS 19
#define MI32_TYPES 19 //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
0x181b, // MIBCS/MIBFS body composition scale -> fake ID (service UUID)
};
const char kMI32DeviceType[] PROGMEM = {"Flora|MJ_HT_V1|LYWSD02|LYWSD03|CGG1|CGD1|NLIGHT|MJYD2S|YLYK01|MHOC401|MHOC303|ATC|MCCGQ02|SJWS01L|PVVX|YLKG08|YLAI003|BTHOME|MIBCS"};
const char kMI32_ScaleUnit[] PROGMEM = "kg|lb|jin";
// scaleState flags
#define MI32_SCALE_WEIGHT_SENT 0x01
#define MI32_SCALE_IMPEDANCE_SENT 0x02
#define MI32_SCALE_FAILED_SENT 0x04
#define MI32_SCALE_FINISHED 0x08
#define MI32_SCALE_SESSION_TIMEOUT 10 // seconds of silence that end a weighing session
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,
};
enum BLE_CLIENT_OP {
BLE_OP_READ = 1,
BLE_OP_WRITE,
BLE_OP_SUBSCRIBE,
BLE_OP_UNSUBSCRIBE,
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
};
// MI32.role bit layout (concurrently-active roles, not mutually exclusive)
enum MI32Role : uint8_t {
MI32_ROLE_NONE = 0x00,
MI32_ROLE_SCAN = 0x01,
MI32_ROLE_CLIENT = 0x02,
MI32_ROLE_SERVER = 0x04,
MI32_ROLE_ADVERTISER = 0x08,
};
/*********************************************************************************************\
* 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_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 ": %s " D_UNIT_VOLT "</p>"
"<p>" D_CURRENT ": %s " 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=Date.now(); // sizes, count-up timestamp
//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
update();
}
function cU() {
let n = Date.now(), s = Math.floor((n - u) / 1000); // now, elapsed seconds
if (s > 0) {
u += s * 1000;
d.querySelectorAll('.Ti').forEach(e => e.textContent = +e.textContent + s);
}
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() {
if (d.hidden) {
d.addEventListener('visibilitychange', update, {once:true});
return;
}
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());
while (tp = x.firstElementChild) {
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).remove();
});
}).catch(e => {
// console.warn("update error:", e);
}).then(_ => setTimeout(update, 200));
}
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, 1); // 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, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc5, 0x3a, 0x6b, 0x93, 0xdb, 0x36,
0x92, 0xdf, 0xf3, 0x2b, 0x18, 0xc6, 0x8e, 0x88, 0x11, 0x45, 0x91, 0x9a, 0x19, 0xc7, 0x96, 0x04,
0xcd, 0xd9, 0x8e, 0xd7, 0xf6, 0xd5, 0x38, 0x97, 0x8a, 0xed, 0x5d, 0x57, 0x8d, 0x95, 0x0c, 0x45,
0x42, 0x24, 0x63, 0xbe, 0x8e, 0x80, 0x24, 0x2a, 0x32, 0xff, 0xfb, 0x75, 0x03, 0x20, 0x45, 0x8d,
0x1f, 0xe7, 0xbb, 0xdd, 0xaa, 0xad, 0xf2, 0x90, 0x40, 0x77, 0xa3, 0xd1, 0xe8, 0x37, 0x28, 0xcf,
0xb9, 0xd8, 0xa7, 0x6c, 0xb1, 0x2a, 0xc2, 0xfd, 0x21, 0x4c, 0x78, 0x99, 0xfa, 0xfb, 0xe9, 0x3a,
0x65, 0xf5, 0x0c, 0x1f, 0xa3, 0x30, 0xa9, 0x58, 0x20, 0x92, 0x22, 0x9f, 0x06, 0x45, 0xba, 0xc9,
0xf2, 0x26, 0x28, 0x42, 0x76, 0xd8, 0x15, 0x55, 0x38, 0x5a, 0x55, 0xcc, 0xff, 0x30, 0x95, 0xcf,
0x91, 0x9f, 0xa6, 0x8d, 0x53, 0xfa, 0x15, 0xcb, 0x45, 0xc7, 0x24, 0xaa, 0x92, 0x70, 0x86, 0x8f,
0x91, 0x60, 0x19, 0x40, 0x04, 0x1b, 0x29, 0x1e, 0x7c, 0x5a, 0xb1, 0x92, 0xf9, 0xc2, 0xf2, 0x37,
0xa2, 0x18, 0xad, 0x93, 0x34, 0xb5, 0xcf, 0x2f, 0xdd, 0xb2, 0x26, 0x77, 0xa8, 0xab, 0x62, 0xf7,
0x19, 0xd2, 0xc9, 0xe4, 0x48, 0x2a, 0xa1, 0x92, 0x4c, 0x42, 0x7b, 0xc0, 0x76, 0x27, 0xc9, 0x78,
0x16, 0xf9, 0xe5, 0xd4, 0xab, 0x58, 0x36, 0xfb, 0x73, 0xc3, 0x45, 0xb2, 0xde, 0x03, 0x3a, 0x17,
0x20, 0xeb, 0x34, 0x80, 0x07, 0xab, 0x1a, 0xbe, 0x8d, 0x0e, 0xeb, 0xb4, 0xf0, 0xc5, 0x34, 0xc9,
0xd3, 0x24, 0x67, 0x23, 0x96, 0x87, 0x08, 0x34, 0x04, 0xab, 0xc5, 0x61, 0x0d, 0xc4, 0x23, 0x9e,
0xfc, 0xc5, 0xa6, 0xae, 0xf3, 0xe8, 0x12, 0xb8, 0xa0, 0x1c, 0xd3, 0x60, 0x53, 0xe1, 0x71, 0x9f,
0x16, 0x69, 0x51, 0x35, 0xce, 0xaa, 0xa8, 0x0f, 0xa5, 0x1f, 0x86, 0x49, 0x1e, 0x4d, 0x3d, 0xdc,
0x72, 0x05, 0x2a, 0x62, 0xd5, 0xa8, 0xf2, 0xc3, 0x64, 0xc3, 0x61, 0xe1, 0x43, 0xdc, 0x7e, 0xe5,
0x07, 0x1f, 0xa2, 0xaa, 0xd8, 0xe4, 0x21, 0x0a, 0x58, 0x54, 0xd3, 0x2a, 0x5a, 0xf9, 0xd6, 0x64,
0xe2, 0xd9, 0xed, 0x9f, 0xeb, 0x4c, 0xc8, 0xac, 0xd8, 0xb2, 0x0a, 0xc4, 0xd9, 0x8d, 0xf6, 0x53,
0x3c, 0x8c, 0xe4, 0x6e, 0xa0, 0x8c, 0x99, 0x5f, 0x8f, 0x76, 0x49, 0x28, 0x62, 0xd8, 0xc3, 0xbd,
0x2f, 0xe1, 0xce, 0xce, 0x3b, 0xc8, 0x63, 0xab, 0x13, 0x4f, 0x79, 0xe9, 0xe7, 0x86, 0xa7, 0x51,
0x93, 0x4f, 0x51, 0x13, 0x8d, 0x3a, 0xff, 0x14, 0x75, 0xae, 0x50, 0xb1, 0x66, 0x08, 0x7a, 0x3d,
0xe1, 0x16, 0x4f, 0xee, 0xc0, 0x35, 0xab, 0xf8, 0xfc, 0x0e, 0xfc, 0xbc, 0xf9, 0x8f, 0x8c, 0x85,
0x89, 0x6f, 0x58, 0x52, 0x56, 0x63, 0x6e, 0xfc, 0x24, 0x6d, 0x76, 0xd0, 0x42, 0xd9, 0x5f, 0x94,
0xc0, 0xfb, 0x02, 0xc7, 0x49, 0xd3, 0xcc, 0xc7, 0xca, 0x4f, 0xe7, 0x3c, 0xa8, 0x92, 0x52, 0x2c,
0x52, 0x26, 0x0c, 0xfe, 0x17, 0xbd, 0x59, 0xda, 0x1b, 0xfa, 0x33, 0x78, 0x8b, 0x93, 0x17, 0x3b,
0x8b, 0xcc, 0xd6, 0x9b, 0x5c, 0xba, 0xab, 0x51, 0x6e, 0x52, 0xce, 0x2c, 0x4e, 0x0e, 0xfc, 0x2f,
0xa7, 0xdc, 0xf0, 0x18, 0x86, 0xb3, 0x64, 0x6d, 0xc1, 0x2c, 0x65, 0x79, 0x24, 0xe2, 0xc5, 0x03,
0x97, 0xc0, 0x84, 0xc7, 0xc9, 0x5a, 0xc0, 0xba, 0x54, 0x50, 0xd3, 0x72, 0xed, 0x89, 0xeb, 0xda,
0x2e, 0x99, 0x9a, 0x43, 0x40, 0xfd, 0x59, 0x24, 0xb9, 0x65, 0xda, 0x26, 0x99, 0x95, 0x01, 0xbd,
0x3d, 0x80, 0xe7, 0x5d, 0x22, 0x81, 0x7d, 0xef, 0x90, 0x8a, 0xa6, 0xb9, 0x9d, 0xb1, 0x95, 0x35,
0x90, 0xbb, 0x0c, 0x88, 0x93, 0xe4, 0x39, 0xab, 0x5e, 0xbc, 0x79, 0x75, 0x4d, 0xcd, 0xc7, 0xff,
0xf9, 0xf8, 0xdd, 0xd4, 0x30, 0x87, 0xe0, 0x1c, 0xe0, 0x00, 0x56, 0x19, 0x90, 0x59, 0xf3, 0x5d,
0x27, 0x16, 0x67, 0xe2, 0x6d, 0x69, 0x81, 0x54, 0x4c, 0xbc, 0x44, 0xd7, 0xdb, 0xfa, 0xa9, 0x15,
0xbc, 0xb5, 0xc1, 0x9c, 0x2e, 0x99, 0x6d, 0xca, 0x10, 0x4e, 0x62, 0x9d, 0x2c, 0x08, 0xde, 0x02,
0x35, 0x1e, 0x36, 0xef, 0x1d, 0xd3, 0xe6, 0xf4, 0x95, 0x2f, 0x62, 0x07, 0x9c, 0xa4, 0xa8, 0x2c,
0x2b, 0x1f, 0x6d, 0xc8, 0x58, 0xb1, 0xc0, 0x23, 0x2e, 0x5c, 0x72, 0xd8, 0x0c, 0x29, 0x3f, 0x43,
0xd0, 0x2c, 0x74, 0xfe, 0x7b, 0xc3, 0xaa, 0xfd, 0x6b, 0x96, 0x42, 0x20, 0x17, 0xd5, 0xe3, 0x34,
0xb5, 0x06, 0xce, 0x9b, 0x04, 0x84, 0x5e, 0x17, 0xd5, 0x33, 0x3f, 0x88, 0x2d, 0x46, 0x17, 0xcc,
0x41, 0x4f, 0x7f, 0xaa, 0xa2, 0x82, 0x0e, 0x4f, 0xa6, 0x43, 0x8e, 0x02, 0xd5, 0x14, 0xcf, 0x1b,
0xa4, 0x45, 0xf0, 0x61, 0x20, 0xb7, 0xa9, 0x49, 0xdd, 0x3b, 0xf6, 0xcf, 0x52, 0x70, 0x87, 0xa7,
0x49, 0xc0, 0xac, 0x0b, 0x7b, 0x72, 0x71, 0x72, 0x08, 0x75, 0xb0, 0x57, 0x4c, 0x54, 0x49, 0xc0,
0xad, 0x4c, 0x1d, 0x68, 0xfd, 0x81, 0x0e, 0xad, 0xec, 0xe6, 0x7c, 0x09, 0xa2, 0x03, 0xbd, 0x23,
0x8a, 0xbf, 0x25, 0x35, 0x0b, 0x2d, 0x0f, 0xac, 0x01, 0x68, 0xa1, 0xd0, 0x17, 0x5f, 0x40, 0xaf,
0x4b, 0x89, 0xfe, 0xe9, 0x2e, 0xda, 0xd5, 0xab, 0x15, 0xfa, 0xc1, 0x17, 0xd0, 0xeb, 0x58, 0xa2,
0x2f, 0x97, 0x44, 0x9a, 0xb1, 0x3a, 0x31, 0xe1, 0xe0, 0xb7, 0xd7, 0xaf, 0x5f, 0x4e, 0x8d, 0xc1,
0x30, 0xbb, 0x71, 0x97, 0xc3, 0x81, 0x11, 0x3e, 0xc9, 0x06, 0x92, 0x2c, 0x2b, 0x4e, 0xe9, 0x5e,
0x41, 0x2e, 0x44, 0xba, 0x9b, 0xc1, 0x75, 0xb1, 0x33, 0x7e, 0x83, 0x23, 0x0e, 0xec, 0xc1, 0x43,
0x77, 0xe2, 0x78, 0xde, 0xaa, 0x1b, 0x45, 0x30, 0x7a, 0xf1, 0x66, 0xe2, 0x1a, 0x96, 0xe7, 0xe5,
0x44, 0x4e, 0x2e, 0x7a, 0x93, 0x67, 0x0a, 0xe3, 0xd7, 0x64, 0xb0, 0xbc, 0xc9, 0x6e, 0x26, 0x4b,
0xdc, 0x71, 0x6c, 0x3c, 0x8d, 0x7d, 0xd8, 0x27, 0x55, 0x52, 0x78, 0x4b, 0xb9, 0x7d, 0xcc, 0xfc,
0xf2, 0x8f, 0xc8, 0xdf, 0x44, 0xa7, 0x1e, 0xa7, 0x1d, 0xed, 0xf6, 0x10, 0xd9, 0xe0, 0xa0, 0x97,
0xe8, 0xa0, 0xeb, 0x0f, 0x8d, 0x8d, 0x6f, 0x01, 0xef, 0xc9, 0xe5, 0x25, 0xb8, 0xd7, 0x44, 0xfe,
0xb9, 0x72, 0x26, 0x21, 0x97, 0xe7, 0xf6, 0x85, 0x6b, 0x6b, 0x8f, 0xb7, 0x1f, 0xba, 0xf6, 0x87,
0x27, 0xcd, 0xad, 0xd2, 0x46, 0xc9, 0x2b, 0x3f, 0xfb, 0xc6, 0x8d, 0x4a, 0xbd, 0x51, 0xf9, 0xb5,
0x8d, 0x3c, 0xf7, 0xb8, 0x13, 0x06, 0xd1, 0x71, 0x2b, 0x3c, 0xd2, 0xa9, 0x4e, 0x5f, 0x00, 0xc4,
0x58, 0x57, 0x4c, 0x2a, 0x76, 0xfd, 0x61, 0x38, 0x18, 0x0f, 0x86, 0x02, 0x5e, 0xc6, 0x87, 0x27,
0xf3, 0x55, 0xb5, 0xb0, 0xd6, 0x95, 0x1f, 0x39, 0x88, 0x8a, 0x87, 0x83, 0xfb, 0x64, 0x70, 0x14,
0xf8, 0x94, 0xcd, 0xaf, 0xaf, 0x7f, 0x7b, 0xfc, 0xea, 0xc8, 0xa7, 0x54, 0x7c, 0x4a, 0xc9, 0x47,
0x2d, 0xda, 0x88, 0xd3, 0x15, 0x6f, 0x4b, 0x91, 0x64, 0x4c, 0x29, 0xfc, 0x21, 0x1a, 0x81, 0xb3,
0x40, 0x51, 0x56, 0x77, 0xcc, 0xfe, 0xe4, 0xfa, 0x99, 0x51, 0x15, 0xa9, 0x36, 0x7d, 0x5e, 0xe4,
0x68, 0xf6, 0x62, 0xc5, 0x21, 0x9a, 0x59, 0x05, 0xc3, 0x92, 0x41, 0x86, 0x8a, 0x59, 0xe5, 0xa7,
0x30, 0xc1, 0x0a, 0x83, 0x23, 0x34, 0xee, 0xa3, 0xa5, 0x32, 0x24, 0x0f, 0xef, 0xf8, 0xd1, 0xcb,
0xf3, 0x09, 0x6c, 0x97, 0xf3, 0xa2, 0xe2, 0xda, 0xe2, 0xee, 0xf2, 0xd3, 0xf8, 0x81, 0x44, 0x00,
0x71, 0x17, 0x3a, 0x71, 0x12, 0x86, 0x2c, 0x27, 0x87, 0xd0, 0x81, 0x82, 0xf3, 0x6c, 0x0b, 0x1b,
0x5c, 0x27, 0x1c, 0x22, 0x15, 0x6c, 0x33, 0xd8, 0x26, 0x3c, 0x59, 0x25, 0x69, 0x22, 0xf6, 0x01,
0x78, 0x10, 0x18, 0xd0, 0x56, 0x6b, 0xed, 0x43, 0x91, 0x07, 0x6c, 0x2a, 0xaa, 0x0d, 0x6b, 0xc8,
0xac, 0x62, 0x62, 0x53, 0xe5, 0xb8, 0x03, 0x13, 0x90, 0x00, 0x06, 0xe3, 0xec, 0x7c, 0x72, 0xb5,
0x4b, 0xa8, 0x07, 0x72, 0x89, 0x98, 0xe5, 0x56, 0x45, 0x17, 0x95, 0xcc, 0x01, 0x16, 0xd1, 0x90,
0x58, 0x64, 0x29, 0x5d, 0x1c, 0x6a, 0x8a, 0x03, 0x9d, 0x43, 0x67, 0x2a, 0xc9, 0xd6, 0x32, 0x1f,
0x7c, 0x8f, 0x08, 0x72, 0xe8, 0x58, 0x63, 0x98, 0x41, 0x47, 0x20, 0xb8, 0x5a, 0x01, 0xa1, 0x9f,
0x61, 0x82, 0x28, 0x41, 0x36, 0x6b, 0x7c, 0xf3, 0x3e, 0x7f, 0x5f, 0xd9, 0xcb, 0xe1, 0x58, 0x2e,
0x95, 0x64, 0x9a, 0x27, 0xa5, 0xd4, 0xf3, 0x7e, 0xfc, 0x51, 0x81, 0x18, 0x28, 0x74, 0x6f, 0x95,
0x74, 0x31, 0xfe, 0x7d, 0x74, 0xf5, 0x3e, 0x1c, 0xde, 0x1b, 0x83, 0x50, 0x5c, 0x58, 0x25, 0x21,
0x90, 0xe2, 0x4e, 0x92, 0x8a, 0x5a, 0x90, 0xf9, 0xa5, 0xf5, 0xcb, 0x26, 0x5b, 0xb1, 0x8a, 0xf4,
0x4e, 0x89, 0xa2, 0x04, 0xc9, 0xeb, 0xbf, 0x3f, 0xa7, 0xe6, 0x1c, 0xab, 0x7a, 0xcc, 0x92, 0x28,
0x16, 0x74, 0x30, 0xb9, 0x18, 0x18, 0xb2, 0x44, 0xa9, 0xe1, 0xc2, 0xfc, 0x6e, 0x68, 0xce, 0x83,
0xa4, 0x0a, 0x52, 0x66, 0x04, 0x35, 0x1d, 0x78, 0xde, 0xc0, 0x08, 0xf6, 0xea, 0x5d, 0xd1, 0x81,
0x3b, 0x30, 0xb0, 0xf2, 0xd3, 0xc1, 0x0f, 0x8f, 0x5c, 0xc6, 0x1e, 0xc1, 0xb4, 0x28, 0xfd, 0x00,
0x54, 0x8d, 0x28, 0xb5, 0xd8, 0xcf, 0x93, 0x0c, 0x84, 0x32, 0x7c, 0x01, 0x52, 0xad, 0x36, 0x82,
0xfd, 0xe2, 0x67, 0x8c, 0x42, 0x7e, 0x01, 0x57, 0x2c, 0x32, 0xc5, 0x49, 0x14, 0x92, 0x55, 0xb8,
0x01, 0x96, 0x8f, 0x38, 0x70, 0x96, 0x1d, 0xce, 0x53, 0xe8, 0x0a, 0x40, 0x24, 0x6f, 0x30, 0xfe,
0x2a, 0x27, 0xbd, 0x63, 0xc7, 0xef, 0x5b, 0xd9, 0x8d, 0xd5, 0xb1, 0x16, 0x50, 0x46, 0xb7, 0xd1,
0xc2, 0x94, 0x49, 0x10, 0x28, 0x53, 0x3f, 0x60, 0xa1, 0x32, 0x90, 0x9e, 0x59, 0xe3, 0x43, 0x90,
0x34, 0xe3, 0xc8, 0x96, 0x0a, 0x23, 0x47, 0xf0, 0xfb, 0xc3, 0x4d, 0x7a, 0x1d, 0xbf, 0x88, 0x9e,
0x2f, 0xed, 0x9b, 0xdf, 0x9b, 0xe5, 0xf0, 0x3d, 0x12, 0x81, 0x88, 0x41, 0x0c, 0x6e, 0x21, 0xaa,
0xbd, 0xb6, 0xbc, 0xa1, 0xb3, 0x84, 0xc4, 0x60, 0x11, 0x08, 0x70, 0x60, 0x31, 0x72, 0x80, 0x9e,
0x8b, 0x43, 0xd4, 0x38, 0x3b, 0xbf, 0x82, 0xba, 0xfa, 0xf3, 0xeb, 0x6b, 0x4d, 0x6a, 0xb0, 0xaa,
0x82, 0x56, 0xc8, 0xb4, 0x59, 0x6b, 0x32, 0xb0, 0x52, 0x06, 0x1d, 0xe5, 0x22, 0xaa, 0xfc, 0x32,
0x36, 0x36, 0xb9, 0xbf, 0xf5, 0x93, 0xd4, 0x5f, 0xa5, 0x0c, 0xa4, 0x97, 0x08, 0x73, 0xd6, 0x80,
0x17, 0xd7, 0x34, 0x74, 0x02, 0x68, 0x3f, 0x05, 0x7b, 0x96, 0xb2, 0x0c, 0xc2, 0xc0, 0x1a, 0x84,
0xc9, 0x16, 0xaa, 0x53, 0x7d, 0x92, 0xb4, 0xd4, 0x21, 0xb5, 0x03, 0x62, 0xa1, 0x7f, 0x5c, 0x55,
0xfe, 0xde, 0x41, 0x05, 0x5a, 0xb5, 0x13, 0x31, 0xa1, 0x57, 0xf3, 0x27, 0xfb, 0x37, 0x7e, 0x84,
0x6a, 0x86, 0x08, 0x95, 0x4d, 0xc6, 0x80, 0x20, 0xfd, 0xb1, 0x4e, 0x42, 0x00, 0x30, 0x54, 0x54,
0x06, 0x0d, 0x1a, 0x04, 0xc6, 0x6c, 0x17, 0x27, 0x29, 0xb3, 0xa0, 0xd4, 0xd4, 0xce, 0x3a, 0xa9,
0x78, 0xcb, 0xe8, 0x29, 0x80, 0x43, 0x72, 0x00, 0xc3, 0x40, 0xb0, 0x8b, 0xd2, 0x49, 0x42, 0xe9,
0xe5, 0xa2, 0x20, 0xa2, 0xd0, 0x4d, 0xf2, 0x2f, 0x50, 0x39, 0x5a, 0xd5, 0x4a, 0x72, 0xa0, 0xb3,
0x81, 0x60, 0xc6, 0x20, 0xa0, 0x0c, 0x99, 0xd3, 0xb0, 0x26, 0xf9, 0x65, 0x09, 0x2a, 0x6a, 0x09,
0x50, 0x9b, 0x77, 0xc4, 0x91, 0x85, 0x94, 0x07, 0x9f, 0x2a, 0xa2, 0x3d, 0xc0, 0x8c, 0x07, 0x27,
0xa5, 0x1d, 0xe4, 0xef, 0x4d, 0xa1, 0x39, 0xc0, 0xbe, 0xff, 0x64, 0x1f, 0x1e, 0x90, 0xee, 0x88,
0xb3, 0x06, 0xff, 0x39, 0xda, 0x84, 0xb0, 0x5d, 0xa3, 0xb3, 0xc1, 0x1f, 0x74, 0x01, 0x1d, 0xcc,
0x1b, 0x48, 0x98, 0xc5, 0x46, 0x58, 0x3a, 0xc5, 0x40, 0x82, 0x27, 0x28, 0x63, 0x0f, 0x23, 0xdb,
0x1d, 0x28, 0x15, 0x6e, 0xaf, 0x33, 0x6b, 0x1b, 0x23, 0x7f, 0x0f, 0x1d, 0x77, 0xa8, 0x7a, 0x01,
0x4e, 0x5f, 0x83, 0x7d, 0xf2, 0xa8, 0x83, 0xb6, 0xe6, 0xc2, 0xc4, 0xc2, 0x1d, 0x2e, 0x30, 0xba,
0xff, 0x91, 0x88, 0xd8, 0x32, 0x0f, 0x26, 0xf9, 0xf8, 0x11, 0x60, 0xc0, 0x45, 0x43, 0x1a, 0x93,
0x10, 0xed, 0x38, 0xca, 0xb1, 0xa5, 0xf5, 0x29, 0xd7, 0xdd, 0x88, 0x67, 0x8f, 0xda, 0x96, 0x62,
0x5f, 0x82, 0xb6, 0xfd, 0x8a, 0x4a, 0x02, 0x28, 0xee, 0x12, 0xca, 0xe3, 0x62, 0xf7, 0x1c, 0xda,
0x4d, 0xda, 0xa1, 0x69, 0x37, 0x84, 0xbe, 0xe1, 0x2d, 0xe8, 0xa6, 0x7a, 0xea, 0x73, 0x54, 0x47,
0xcb, 0xa4, 0x8f, 0x87, 0xd2, 0xdf, 0xe1, 0x41, 0x5a, 0x33, 0x8d, 0x4d, 0x70, 0xbf, 0x20, 0xdd,
0x84, 0x8c, 0x5b, 0x48, 0x47, 0xd4, 0x11, 0x33, 0xb5, 0xab, 0x23, 0x03, 0xc3, 0x1a, 0xff, 0x6e,
0xc9, 0x70, 0x5a, 0x12, 0xdb, 0x82, 0xc4, 0xd6, 0x3d, 0x9d, 0x21, 0xb9, 0xa7, 0x92, 0xe2, 0xf7,
0xd9, 0xc9, 0xa1, 0x6e, 0x6c, 0x7b, 0x07, 0x3a, 0xb2, 0x63, 0x7c, 0x70, 0x78, 0x2c, 0x69, 0x26,
0xe5, 0xd9, 0xd1, 0x21, 0x22, 0xe4, 0x18, 0xba, 0x9a, 0xb8, 0x1d, 0xd7, 0x29, 0xcd, 0x37, 0x69,
0xaa, 0x8e, 0xc8, 0x22, 0x8a, 0x6b, 0x74, 0xfa, 0x35, 0x3f, 0x42, 0x53, 0x0b, 0x6e, 0x64, 0x49,
0x65, 0x51, 0x77, 0x96, 0xcc, 0x81, 0xa2, 0x4d, 0xea, 0xc9, 0x70, 0x28, 0x0b, 0x0d, 0x80, 0x6e,
0x92, 0xe5, 0x89, 0xee, 0xeb, 0x74, 0x0a, 0xba, 0x3e, 0x00, 0xeb, 0x16, 0xb9, 0x59, 0x71, 0x65,
0xb8, 0xf3, 0x36, 0xb7, 0xcb, 0x8e, 0x19, 0xd9, 0xe1, 0x14, 0xd4, 0x9f, 0xd8, 0xa0, 0x7d, 0x79,
0x4b, 0x84, 0xa0, 0x95, 0xa9, 0x98, 0xcb, 0xe5, 0x32, 0x57, 0xa3, 0x60, 0xca, 0x89, 0xb3, 0x4c,
0x03, 0x95, 0x76, 0xde, 0x5b, 0x27, 0x6a, 0xc1, 0xe7, 0x7b, 0x32, 0xb5, 0x9c, 0xb3, 0xa3, 0x7a,
0x5a, 0xfd, 0x18, 0xf2, 0x98, 0x6a, 0x7c, 0x80, 0x0b, 0xd5, 0xf4, 0x66, 0x98, 0x61, 0xcf, 0x64,
0xe3, 0x6b, 0xa2, 0x5e, 0xe7, 0xcb, 0xa5, 0x0d, 0xad, 0xf6, 0x86, 0xf1, 0x69, 0x86, 0x3d, 0x65,
0x4f, 0x56, 0x29, 0xc7, 0x96, 0x2e, 0x54, 0xd9, 0xb0, 0xb6, 0xda, 0xf3, 0x08, 0x69, 0xd0, 0xf5,
0x21, 0xe1, 0x43, 0x97, 0x6e, 0x3d, 0x29, 0x20, 0x73, 0xf9, 0x39, 0x1c, 0x6c, 0x97, 0xa0, 0x80,
0xd2, 0xae, 0x87, 0x00, 0xac, 0x6e, 0xa6, 0xe6, 0xf4, 0x24, 0xf3, 0x5d, 0x3f, 0xb7, 0x78, 0x6a,
0xef, 0xec, 0xd8, 0x6e, 0x1d, 0xcb, 0xae, 0xd3, 0x56, 0x03, 0x72, 0x45, 0x6c, 0x4e, 0x51, 0xbd,
0x69, 0xaf, 0xe2, 0x91, 0xef, 0x4e, 0x78, 0xbc, 0x00, 0x2c, 0xb8, 0xa7, 0xa3, 0x44, 0xb6, 0xd5,
0x04, 0x8e, 0xf6, 0x25, 0xb6, 0xa0, 0x58, 0x4c, 0x30, 0xe8, 0x99, 0x94, 0x9a, 0x91, 0xa9, 0x3c,
0xae, 0xd4, 0x1e, 0xa7, 0xce, 0x0a, 0xbd, 0x08, 0x69, 0x1d, 0xa6, 0x44, 0xf5, 0xc4, 0xf8, 0x9e,
0x48, 0xbd, 0xe0, 0xe8, 0x7c, 0x69, 0x67, 0x49, 0x8e, 0xa3, 0x0b, 0x18, 0xf9, 0x35, 0x8e, 0x2e,
0x75, 0x84, 0x88, 0xa2, 0xe4, 0x70, 0xd5, 0xea, 0xf9, 0xcc, 0x03, 0xf0, 0x99, 0x52, 0x1f, 0x60,
0xe4, 0x81, 0xcf, 0xd0, 0x0b, 0xb8, 0xd4, 0x20, 0x9d, 0xba, 0x6d, 0x1d, 0xa4, 0xe8, 0x53, 0xe0,
0x91, 0x0c, 0x81, 0xb3, 0xba, 0xed, 0xde, 0xc2, 0x11, 0xac, 0x7b, 0x07, 0x80, 0x2d, 0x1b, 0x5b,
0xbe, 0x87, 0x5e, 0x37, 0x9a, 0x2c, 0x1b, 0x72, 0xdb, 0x10, 0x5d, 0xae, 0x37, 0x79, 0x22, 0x68,
0x79, 0x73, 0xdc, 0xe2, 0x18, 0xab, 0x7f, 0x47, 0xce, 0xd4, 0xea, 0x45, 0xab, 0xf9, 0xdc, 0x6c,
0x4b, 0x87, 0x56, 0xe0, 0x73, 0x0b, 0xf6, 0xc7, 0xf3, 0xe0, 0x49, 0x94, 0xd6, 0x50, 0x36, 0x1b,
0xd9, 0xda, 0x1d, 0x13, 0xdc, 0xac, 0x8b, 0xb1, 0x5e, 0xbb, 0xb5, 0xda, 0x40, 0x22, 0x44, 0x15,
0x5b, 0xb8, 0x32, 0xdb, 0xda, 0xe1, 0xb5, 0xbd, 0x17, 0xf4, 0xc2, 0x0e, 0xde, 0xc9, 0x98, 0x52,
0xea, 0x4d, 0x73, 0xbc, 0x7d, 0xa6, 0x2b, 0x7c, 0x42, 0x44, 0xdc, 0x28, 0x09, 0xf3, 0x4c, 0x5d,
0xcd, 0x02, 0x96, 0xa4, 0x56, 0xb6, 0x85, 0xeb, 0x07, 0x81, 0x7b, 0x98, 0xca, 0x1c, 0x09, 0xcd,
0xb3, 0xf1, 0x5e, 0xc8, 0x62, 0x90, 0xcc, 0xe9, 0x25, 0x01, 0xc8, 0xa5, 0x4a, 0xf9, 0x1a, 0x04,
0xd4, 0x00, 0x03, 0xfa, 0x3e, 0x70, 0x22, 0x09, 0x27, 0x9a, 0x12, 0x86, 0xc7, 0x0d, 0x44, 0x32,
0xbe, 0x84, 0x0d, 0x2e, 0xdd, 0xd9, 0xdd, 0x7d, 0x45, 0x42, 0xce, 0x44, 0x22, 0xf7, 0xf5, 0x45,
0x0f, 0x05, 0x12, 0x00, 0xea, 0x4e, 0xf4, 0x53, 0x5f, 0xa8, 0xb0, 0x47, 0xd0, 0x96, 0x26, 0xed,
0xca, 0x3d, 0x8d, 0x47, 0xd6, 0xf6, 0x2c, 0x1e, 0xe7, 0x19, 0xf8, 0x4e, 0xae, 0x4c, 0x7b, 0x3b,
0xc7, 0x4f, 0x24, 0x46, 0xed, 0x51, 0xd3, 0x35, 0x8d, 0x3d, 0xbc, 0xee, 0x1d, 0xf6, 0x8d, 0x69,
0xd4, 0x13, 0x1c, 0xed, 0x60, 0xb4, 0x9f, 0xb4, 0x30, 0x48, 0x0e, 0xc5, 0x07, 0x46, 0xcd, 0x1f,
0xc2, 0x30, 0x6c, 0x67, 0xea, 0x0b, 0x06, 0x35, 0x3d, 0x73, 0xbc, 0xb8, 0x95, 0xb1, 0x9c, 0xcc,
0x7d, 0x41, 0xd2, 0x55, 0xcb, 0x1e, 0x0b, 0x94, 0x51, 0x53, 0x73, 0x74, 0x09, 0xac, 0x24, 0xa7,
0xe1, 0x05, 0xf0, 0x42, 0xf0, 0xc8, 0xcf, 0x83, 0xb8, 0xa8, 0xa8, 0x09, 0x56, 0x36, 0x17, 0xf7,
0x0e, 0xdb, 0x66, 0x3e, 0x46, 0x38, 0x32, 0x92, 0x31, 0x10, 0xbc, 0xfb, 0xf1, 0xc7, 0xe0, 0x5d,
0x7b, 0xc7, 0x77, 0xd5, 0x89, 0x6a, 0x4e, 0x3b, 0x18, 0x38, 0xeb, 0xe9, 0xd9, 0x3b, 0xcc, 0x51,
0x03, 0x35, 0xad, 0xe1, 0x12, 0x7d, 0x95, 0x9c, 0x59, 0xbb, 0x71, 0xcd, 0xc9, 0x74, 0x37, 0x9e,
0xcc, 0xea, 0xf4, 0xae, 0x78, 0xf7, 0x0e, 0x75, 0xa3, 0x05, 0x8c, 0x87, 0x13, 0xf7, 0xae, 0x84,
0x19, 0x74, 0xe9, 0x29, 0x43, 0x21, 0x83, 0x77, 0xe8, 0xf0, 0x3d, 0x41, 0x1b, 0x34, 0x64, 0x2b,
0xd9, 0xc5, 0x5d, 0x5b, 0xd4, 0xbc, 0x2f, 0x49, 0x2b, 0x84, 0xca, 0xe9, 0xda, 0xf6, 0xf2, 0x13,
0x92, 0x05, 0xa8, 0xf0, 0x1a, 0x6a, 0x9b, 0x44, 0xff, 0x13, 0xf2, 0xd5, 0xc9, 0x89, 0x70, 0x3a,
0x24, 0x0e, 0x68, 0x65, 0x3e, 0x05, 0xab, 0xab, 0x0f, 0x21, 0x26, 0xb1, 0xa1, 0x07, 0x03, 0xc9,
0xa7, 0x60, 0xa9, 0x0e, 0x04, 0x15, 0x01, 0x76, 0x6e, 0xa7, 0x4d, 0x3f, 0x8a, 0xba, 0x48, 0xb4,
0x33, 0x08, 0xc3, 0x36, 0x0a, 0xed, 0x0d, 0x35, 0x4d, 0x9b, 0x6f, 0x29, 0x5e, 0x45, 0xd4, 0x21,
0x83, 0x94, 0xd6, 0x74, 0x21, 0x0f, 0x06, 0xd1, 0x6a, 0x01, 0xb5, 0x1a, 0x03, 0x4f, 0x58, 0x56,
0x13, 0x75, 0xf6, 0x6d, 0x4a, 0x83, 0xd4, 0xda, 0xaa, 0x09, 0x2f, 0x69, 0x56, 0x8f, 0xb2, 0xfc,
0xe3, 0x47, 0x4f, 0x85, 0x16, 0xb5, 0xb6, 0x29, 0xcc, 0xc9, 0x98, 0x97, 0x12, 0x00, 0xed, 0x3b,
0x58, 0xcd, 0x86, 0xee, 0x3d, 0x06, 0xe3, 0xc9, 0x6e, 0xb7, 0xa0, 0x1d, 0x5b, 0x10, 0x85, 0x9c,
0xb9, 0xce, 0xc5, 0xa5, 0xc2, 0x24, 0xb4, 0x2a, 0x60, 0xfa, 0x93, 0x9a, 0xf2, 0x42, 0x4d, 0xdd,
0x07, 0x36, 0xd7, 0x98, 0x73, 0x85, 0x89, 0x42, 0xaa, 0x78, 0xf1, 0x90, 0x8e, 0xbc, 0xf3, 0xcb,
0x61, 0x14, 0xda, 0xd0, 0x3e, 0xc3, 0x68, 0x04, 0x23, 0xbe, 0x0b, 0x29, 0x0b, 0x47, 0x3c, 0xec,
0x7d, 0x84, 0x2a, 0x52, 0xbf, 0xb2, 0x82, 0x1a, 0xe4, 0xb0, 0x2b, 0x5b, 0xf7, 0x3a, 0x55, 0x48,
0xad, 0x70, 0xf4, 0x08, 0xc2, 0x56, 0xca, 0xf3, 0xeb, 0xcb, 0xb1, 0xf7, 0xd0, 0x6d, 0xeb, 0x57,
0x3d, 0x0d, 0xea, 0x61, 0xa5, 0x30, 0x41, 0xc1, 0xad, 0x2a, 0x24, 0xf6, 0x7e, 0x1a, 0xec, 0x5b,
0x18, 0x07, 0xe9, 0x01, 0x76, 0xa2, 0x68, 0xbf, 0x0a, 0x7e, 0x05, 0x5c, 0xb7, 0x8f, 0xef, 0xda,
0xbe, 0xa7, 0x73, 0x94, 0xaf, 0x0e, 0xed, 0xaf, 0xb8, 0xe5, 0x7b, 0x23, 0xdf, 0x25, 0x0b, 0xd8,
0xec, 0xca, 0x9b, 0xaa, 0x8c, 0xc4, 0x77, 0xd4, 0xf7, 0x16, 0xfe, 0x11, 0x50, 0xba, 0xf4, 0x54,
0x66, 0x5f, 0x7f, 0x30, 0x29, 0xbd, 0xbb, 0x08, 0xaf, 0x4d, 0xb9, 0xb7, 0xaf, 0x0c, 0xc8, 0xe0,
0xae, 0x53, 0xcb, 0x4c, 0xee, 0x3a, 0xfb, 0xc6, 0x78, 0x0c, 0x90, 0x0a, 0xa7, 0x55, 0x63, 0xb8,
0x30, 0x4e, 0xfd, 0x06, 0x9e, 0x7c, 0x87, 0xcf, 0xd2, 0xd3, 0x94, 0x1e, 0x50, 0xde, 0x9e, 0x7c,
0xea, 0xc2, 0x22, 0xf1, 0x37, 0x08, 0x86, 0xad, 0xec, 0x3f, 0xbe, 0xe7, 0x42, 0x47, 0x66, 0xdb,
0x08, 0x61, 0xf9, 0xf0, 0x26, 0xae, 0xad, 0xff, 0x88, 0xea, 0xf6, 0x02, 0xca, 0x05, 0xd6, 0x47,
0xb9, 0xfc, 0x6e, 0x5b, 0x23, 0xee, 0x76, 0x35, 0xdb, 0x05, 0x92, 0x27, 0xba, 0xb6, 0x92, 0x40,
0xcf, 0xd4, 0x62, 0x99, 0x60, 0x75, 0x49, 0x6d, 0x2b, 0x72, 0xa0, 0x2b, 0x52, 0xc1, 0xc1, 0x71,
0xbf, 0xce, 0x5e, 0x7e, 0x59, 0xaa, 0xa8, 0xd5, 0xdb, 0xa0, 0xef, 0x93, 0x70, 0x21, 0xb0, 0xa0,
0xe2, 0x1d, 0x57, 0x91, 0x2b, 0x49, 0x0a, 0x45, 0xf0, 0x84, 0x78, 0xaa, 0x7c, 0xda, 0x77, 0x29,
0x0f, 0x87, 0xe0, 0x55, 0x67, 0x6b, 0x54, 0x77, 0x3b, 0x11, 0x05, 0xa6, 0x4c, 0xdf, 0x83, 0xa4,
0xed, 0x12, 0xfc, 0x8e, 0x9d, 0xe4, 0x1b, 0x36, 0x2b, 0xf8, 0x90, 0xde, 0xce, 0x4b, 0xb0, 0xb5,
0x11, 0x62, 0xc4, 0xf7, 0x7c, 0x02, 0xae, 0xb0, 0x36, 0x78, 0xbf, 0x0d, 0x0c, 0xe1, 0xcf, 0x23,
0xbd, 0xb4, 0x0c, 0x36, 0x39, 0x9e, 0xbe, 0xb9, 0x9b, 0xa0, 0x01, 0x5b, 0x00, 0x50, 0xde, 0x79,
0x4d, 0xfc, 0xcc, 0x81, 0x09, 0x5b, 0x6b, 0x63, 0x25, 0xbe, 0xbe, 0x5d, 0x62, 0x43, 0x90, 0xd8,
0x06, 0x0b, 0xfb, 0xdb, 0xc9, 0xaf, 0xdd, 0x3d, 0x03, 0xe2, 0xd7, 0xee, 0x4b, 0xf2, 0x99, 0x6d,
0x93, 0x4f, 0xb7, 0x95, 0x4e, 0x18, 0xd0, 0xa3, 0x8f, 0xa4, 0xda, 0x31, 0x59, 0xa7, 0x18, 0xd5,
0xfb, 0xfb, 0x54, 0x40, 0xee, 0xfe, 0x16, 0xe1, 0xa0, 0x85, 0x3b, 0xd1, 0x45, 0x19, 0x34, 0xdf,
0x24, 0xcb, 0x54, 0x5f, 0x33, 0x44, 0x8d, 0x1e, 0x81, 0xad, 0xdc, 0x56, 0xdb, 0x9e, 0xf7, 0xd3,
0xf3, 0xdd, 0x6c, 0xe3, 0x3d, 0x24, 0x33, 0x58, 0xd2, 0x4f, 0xd0, 0x41, 0x97, 0xa1, 0x83, 0xfd,
0x17, 0xf2, 0xb3, 0x11, 0x16, 0xc0, 0xc3, 0xcf, 0xc5, 0x68, 0x05, 0xbd, 0x23, 0xe6, 0xe4, 0x23,
0xaa, 0xfb, 0x61, 0x02, 0x39, 0xac, 0x79, 0x27, 0xa8, 0x3e, 0x8a, 0x44, 0xef, 0xd4, 0xb7, 0x0e,
0xf3, 0x81, 0xeb, 0xca, 0x82, 0x99, 0x36, 0xf7, 0x0e, 0x9b, 0x2e, 0xdf, 0x77, 0x6e, 0x7e, 0x2b,
0x3f, 0x8c, 0x74, 0xa7, 0xc6, 0x2a, 0xae, 0xbf, 0x92, 0x60, 0xf9, 0x80, 0xd9, 0x36, 0x61, 0xbb,
0x27, 0x05, 0x48, 0xed, 0xca, 0x88, 0x96, 0xa1, 0x0c, 0x70, 0x60, 0x59, 0x70, 0x60, 0xb9, 0x12,
0xf0, 0x48, 0x7c, 0x78, 0x88, 0xba, 0x51, 0x1f, 0x19, 0x6e, 0xdb, 0x3b, 0xdc, 0x27, 0x35, 0xa1,
0xd7, 0x22, 0x47, 0xb2, 0x0e, 0xd8, 0xc1, 0x06, 0xfa, 0xb3, 0xec, 0xdd, 0x75, 0xaf, 0xbd, 0xca,
0xb6, 0xb4, 0x2b, 0x09, 0x8e, 0x03, 0x77, 0x39, 0x67, 0x9d, 0xfa, 0xe2, 0x15, 0x5e, 0x1a, 0xe0,
0xf2, 0xa9, 0x5b, 0x63, 0x62, 0xeb, 0xab, 0x1d, 0xa4, 0x66, 0x1e, 0x5d, 0x7d, 0xd2, 0xb8, 0xf5,
0xdb, 0x68, 0x1d, 0x74, 0xf6, 0x45, 0xb7, 0x1b, 0x99, 0xea, 0x22, 0x07, 0x15, 0x49, 0xd7, 0x36,
0x18, 0xe1, 0x25, 0xc7, 0x6c, 0x54, 0x6a, 0x49, 0x93, 0xf2, 0x65, 0x48, 0x6f, 0xf1, 0xfd, 0xc7,
0xbd, 0x83, 0x32, 0xae, 0x9f, 0x83, 0x49, 0x2c, 0xfc, 0xa4, 0xac, 0xef, 0xac, 0xe7, 0x0f, 0xda,
0x0f, 0xdf, 0x13, 0xd2, 0xdc, 0x76, 0x0b, 0xc1, 0xd2, 0xf8, 0x42, 0xd7, 0x33, 0x12, 0xe9, 0x88,
0x8a, 0x1d, 0x28, 0x6d, 0x8e, 0x3f, 0xaa, 0xa1, 0x0b, 0xb8, 0xd2, 0xfc, 0xf0, 0xfc, 0xb2, 0xe6,
0xc7, 0x8b, 0xf9, 0xb8, 0xe5, 0xa3, 0xe3, 0x20, 0x5c, 0xcb, 0xce, 0x73, 0x8f, 0x3d, 0x27, 0xea,
0x45, 0x7f, 0x10, 0xb0, 0xb8, 0x9d, 0x10, 0x7d, 0x9d, 0x8a, 0x12, 0x7a, 0x1b, 0x81, 0x45, 0x9a,
0x6f, 0x16, 0x3b, 0x5c, 0xf7, 0xdb, 0x3b, 0xbf, 0x7a, 0x8e, 0x3f, 0x5d, 0xb1, 0x5c, 0x68, 0xd9,
0x23, 0x8c, 0x83, 0x5e, 0xcf, 0xe7, 0xaa, 0x86, 0xcf, 0x6d, 0xbb, 0x3d, 0xf4, 0x91, 0x48, 0x2f,
0x79, 0x0b, 0x5d, 0x36, 0x64, 0xcb, 0x0d, 0x67, 0xd5, 0xeb, 0xd2, 0x0f, 0xd8, 0x7f, 0xe5, 0x6f,
0xe1, 0xda, 0xb3, 0x98, 0x63, 0x0f, 0x6e, 0x14, 0xeb, 0x35, 0x67, 0x70, 0x38, 0xf7, 0xbe, 0x29,
0x2f, 0x16, 0xea, 0x97, 0x30, 0x9d, 0x1c, 0x20, 0xde, 0xf0, 0x9e, 0x03, 0x46, 0xc3, 0x12, 0xa1,
0xc6, 0x5e, 0x6f, 0x0c, 0x97, 0x04, 0x1b, 0xcb, 0x31, 0x41, 0xb5, 0x9c, 0xb0, 0xc3, 0x1f, 0xc4,
0xfe, 0xbf, 0x0c, 0x25, 0xb7, 0xf1, 0xe9, 0xb1, 0xb1, 0x17, 0x92, 0xf9, 0x45, 0xd0, 0xd6, 0xd9,
0xe4, 0x4d, 0x11, 0xda, 0x99, 0x3f, 0x5b, 0x25, 0xd7, 0x47, 0x94, 0x6e, 0x38, 0xbd, 0x2b, 0xeb,
0xcf, 0xb3, 0x1d, 0x19, 0x5b, 0x77, 0x10, 0xd0, 0xa5, 0xe9, 0x0a, 0xbb, 0xa7, 0xdb, 0x39, 0x75,
0xaf, 0xe2, 0x29, 0x76, 0xd6, 0xe3, 0x6c, 0x4b, 0xce, 0xe2, 0xb6, 0x8e, 0x62, 0xc3, 0x66, 0x63,
0xdf, 0x7c, 0x8b, 0x37, 0x4f, 0xd5, 0x53, 0x19, 0x70, 0xb3, 0x49, 0xf7, 0xad, 0x65, 0xa0, 0x04,
0xef, 0x65, 0xf3, 0x5d, 0x02, 0x12, 0x55, 0x3c, 0x92, 0xbf, 0x47, 0xc5, 0xb2, 0xae, 0x0a, 0x7c,
0xee, 0xb0, 0xd3, 0xb3, 0x95, 0x39, 0x54, 0x32, 0xd8, 0x54, 0xa9, 0xf5, 0x83, 0xb4, 0x1f, 0x39,
0xc9, 0xc4, 0xdf, 0xa2, 0x9a, 0x4f, 0x72, 0xf3, 0xc4, 0x94, 0xae, 0x3d, 0xc2, 0xe4, 0xda, 0xb1,
0xd6, 0x6e, 0x4d, 0x54, 0x3f, 0xdf, 0x7d, 0x5e, 0xfe, 0x7a, 0x4a, 0x19, 0x9e, 0xbb, 0xfd, 0xac,
0x32, 0xba, 0xd4, 0x79, 0x65, 0x08, 0x86, 0x94, 0xb9, 0x65, 0x78, 0xe1, 0xca, 0xf4, 0x12, 0x85,
0x8e, 0x0c, 0xd2, 0x46, 0x0d, 0x65, 0x98, 0xaa, 0x71, 0x9d, 0x36, 0xf3, 0x90, 0xad, 0xf9, 0x42,
0x89, 0x00, 0x40, 0xf0, 0xe1, 0xae, 0x15, 0x9d, 0x8f, 0x35, 0x0e, 0xd4, 0xd7, 0x03, 0x7e, 0x3d,
0x33, 0xbd, 0xb0, 0xb6, 0xdc, 0xee, 0x2e, 0xda, 0x6d, 0x72, 0x7a, 0xf7, 0xe5, 0xb4, 0x04, 0xe4,
0x3a, 0x03, 0xf1, 0x80, 0x66, 0xdb, 0x45, 0x7c, 0x15, 0x83, 0x51, 0x75, 0x05, 0x8f, 0x4a, 0xdd,
0x2f, 0x8a, 0x08, 0xfa, 0xd3, 0x9e, 0x2f, 0x9c, 0x45, 0xaa, 0x23, 0x58, 0xed, 0xa8, 0xb5, 0x1b,
0x89, 0x88, 0x8c, 0x3b, 0xec, 0x4c, 0x47, 0xaf, 0x19, 0x47, 0x7f, 0x98, 0x43, 0xeb, 0x24, 0x76,
0xcf, 0x3c, 0xf6, 0xe8, 0xa3, 0x6e, 0xc8, 0x20, 0x07, 0xfc, 0xaf, 0x91, 0x7a, 0x5f, 0x87, 0xea,
0x7d, 0x1d, 0xab, 0xf7, 0xff, 0xd5, 0xc1, 0x7a, 0x74, 0x9f, 0xa3, 0xf3, 0xb4, 0x51, 0xe5, 0xfd,
0x1f, 0x62, 0xf4, 0x6b, 0x7c, 0xa0, 0x70, 0x7e, 0x3e, 0x3e, 0x55, 0xef, 0xce, 0xe9, 0x60, 0x30,
0x03, 0xed, 0x75, 0x49, 0x70, 0x7b, 0x4c, 0x82, 0xab, 0xf8, 0xd8, 0xf2, 0x6f, 0xcf, 0x78, 0x00,
0x75, 0xb8, 0xbb, 0xd6, 0xae, 0x94, 0xa6, 0xe5, 0x05, 0x6b, 0xb5, 0x1b, 0x46, 0x25, 0xb8, 0xad,
0xec, 0xa1, 0xda, 0xd4, 0xdc, 0xbb, 0x3e, 0x61, 0x6d, 0xee, 0xfc, 0x78, 0x75, 0xea, 0xc8, 0xab,
0xcf, 0x47, 0x9a, 0xec, 0x94, 0xf4, 0xe7, 0xcb, 0x88, 0x7c, 0x5b, 0x8d, 0x5d, 0xb4, 0xde, 0x1c,
0xae, 0x8f, 0xde, 0x5b, 0xf1, 0xd6, 0x69, 0xdb, 0x3a, 0xf7, 0x49, 0x91, 0xdb, 0xf6, 0x4b, 0xdb,
0xbb, 0x7f, 0x47, 0xf8, 0x7d, 0x5e, 0xe0, 0x2e, 0xca, 0x60, 0xae, 0x7e, 0xb4, 0x9f, 0x87, 0xc9,
0x56, 0x3e, 0x20, 0x8b, 0xf8, 0x9c, 0x53, 0xf5, 0x31, 0x1c, 0xfd, 0xb6, 0xac, 0xfa, 0x70, 0x13,
0xff, 0xc3, 0x43, 0x0c, 0xc9, 0x06, 0x30, 0x49, 0xbe, 0x2e, 0x16, 0xf3, 0x78, 0x82, 0x63, 0xf9,
0xf3, 0x33, 0xb8, 0x42, 0x3c, 0x59, 0xcc, 0x4b, 0x04, 0x6c, 0x80, 0xe7, 0xb8, 0xd4, 0x93, 0xaa,
0xe8, 0x4d, 0x78, 0xa8, 0x26, 0xab, 0xaa, 0xc5, 0xf6, 0x90, 0x59, 0x9f, 0x52, 0xfe, 0x88, 0x75,
0x24, 0x46, 0x29, 0x00, 0x7a, 0xfc, 0xb1, 0x15, 0x50, 0x52, 0xec, 0xb2, 0x05, 0x2b, 0x5a, 0x4d,
0xd7, 0xfb, 0xb1, 0xf4, 0x84, 0x50, 0xc2, 0x15, 0xa5, 0x82, 0xf6, 0x9e, 0xff, 0x03, 0xd2, 0x55,
0x50, 0x4a, 0x73, 0x23, 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