/*
  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;
  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 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 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;
  uint8_t infoMsg = 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 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,
};

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
};

// 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,
};

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 ": %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=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