mi32 refactor web UI (#23979)

This commit is contained in:
Christian Baars 2025-10-05 10:59:14 +02:00 committed by GitHub
parent a245696d7c
commit 20566dd1dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 784 additions and 185 deletions

View File

@ -26,7 +26,7 @@ extern void be_MI32_set_temp(int slot, int temp_val);
BE_FUNC_CTYPE_DECLARE(be_MI32_set_temp, "", "ii");
extern bbool be_MI32_widget(const char *sbuf, void* function);
BE_FUNC_CTYPE_DECLARE(be_MI32_widget, "b", "s[c]");
BE_FUNC_CTYPE_DECLARE(be_MI32_widget, "b", "[sc]");
#include "be_fixed_MI32.h"

View File

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

View File

@ -80,6 +80,13 @@ extern "C" {
}
bool be_MI32_widget(const char* sbuf, void* function){
if (!sbuf && !function){
if(be_MI32Widget.size == 0){
return true; // we can safely overwrite sbuf
} else {
return false; // sbuf is notsent yet, keep it
}
}
if (function){
be_MI32Widget.callback = function;
}

View File

@ -565,45 +565,55 @@ void MI32triggerTele(void){
#ifdef USE_MI_EXT_GUI
/**
* @brief Saves a sensor value mapped to the graph range of 0-20 pixel, this function automatically reads the actual hour from system time
* @brief Saves a sensor value mapped to the graph range of 0127.
* This function automatically reads the actual hour from system time.
*
* @param history - pointer to uint8_t[23]
* @param value - value as float, this
* @param type - internal type. for BLE: 0 - temperature, 1 - humidity, 2 - illuminance, for internal sensors: 100 - wattage
* @param history - pointer to uint8_t[24]
* @param value - sensor value as int
* @param type - internal type. for BLE: 0 - temperature, 1 - humidity,
* 2 - illuminance, 3 - BLE sightings,
* for internal sensors: 100 - wattage
*/
void MI32addHistory(uint8_t history[24], float value, const uint32_t type){
const uint32_t _hour = (LocalTime()%SECS_PER_DAY)/SECS_PER_HOUR;
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: history hour: %u"),_hour);
switch(type){
case 0: //temperature
history[_hour] = ((((value + 5.0f)/4) + 1) + 0b10000000); //temp
void MI32addHistory(uint8_t history[24], int value, const uint32_t type) {
const uint32_t _hour = (LocalTime() % SECS_PER_DAY) / SECS_PER_HOUR;
uint8_t scaled = 0;
switch (type) {
case 0: { // temperature, -20..60 °C
scaled = changeIntScale((int16_t)value, -20, 60, 1, 127);
break;
case 1: //humidity
history[_hour] = (((value/5.0f) + 1) + 0b10000000) ; //hum
}
case 1: { // humidity, 0..100 %
scaled = changeIntScale((int16_t)value, 0, 100, 1, 127);
break;
case 2: //light
if(value>100.0f) value=100.0f; //clamp it for now
history[_hour] = (((value/5.0f) + 1) + 0b10000000); //lux
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: history lux: %u in hour:%u"),history[_hour], _hour);
}
case 2: { // light, 0..1000 lux
scaled = changeIntScale((int16_t)value, 0, 1000, 1, 127);
break;
case 3: //BLE device sighting
uint16_t sightings = history[_hour] & 0b01111111;
if(sightings<20){
history[_hour] = (sightings | 0b10000000) + 1;
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: history sighting: %u in hour:%u"),history[_hour], _hour);
}
case 3: { // BLE sightings, count up to 127
uint8_t sightings = history[_hour] & 0x7F;
if (sightings < 127) {
scaled = sightings + 1;
} else {
scaled = 127;
}
break;
}
#ifdef USE_MI_ESP32_ENERGY
case 100: // energy
if(value == 0.0f) value = 1.0f;
const uint8_t _watt = ((MI32ln(value)*2) + 0b10000000); //watt
history[_hour] = _watt;
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: history energy: %u for value:%u"),history[_hour], value); //still playing with the mapping
case 100: { // energy/wattage, logarithmic mapping
if (value <= 0) value = 1;
int16_t lnval = MI32ln(value);
scaled = changeIntScale(lnval, 0, 64, 1, 127);
break;
#endif //USE_MI_ESP32_ENERGY
}
#endif
}
// Set MSB to mark “valid datapoint”
history[_hour] = (scaled & 0x7F) | 0x80;
}
/**
* @brief Returns a value between 0-21 for use as a data point in the history graph of the extended web UI
*
@ -611,11 +621,13 @@ void MI32addHistory(uint8_t history[24], float value, const uint32_t type){
* @param hour - hour of datapoint
* @return uint8_t - value for the y-axis, should be between 0-21
*/
uint8_t MI32fetchHistory(uint8_t history[24], uint32_t hour){
if((hour>23 || bitRead(history[hour],7)) == 0) {
return 0; //invalidated data
}
return (history[hour]) - 0b10000000;
uint8_t MI32fetchHistory(uint8_t history[24], uint32_t hour) {
if (hour > 23) return 0;
uint8_t v = history[hour];
if ((v & 0x80) == 0) {
return 0; // no data received
}
return v & 0x7F; // strip MSB, return 1..127
}
/**
@ -957,10 +969,33 @@ extern "C" {
}
void MI32sendBerryWidget() {
static uint32_t lastMetricsTime = UINT32_MAX; //we want an overlow in the first run
if(be_MI32Widget.size != 0) {
WSContentSend(be_MI32Widget.data, be_MI32Widget.size);
be_MI32Widget.data = nullptr;
be_MI32Widget.size = 0;
} else {
uint32_t now = millis();
if (now - lastMetricsTime >= 10000) {
lastMetricsTime = now;
char metricsBuf[64];
// Fields: RSSI (dBm), Channel, PHY Mode, Free Heap (bytes), Total Heap (bytes), Heap Fragmentation (%), PSRAM Total (bytes), PSRAM Free (bytes), Uptime (sec), MI32.role, BLE Sensors Count
snprintf(metricsBuf, sizeof(metricsBuf),
"%i,%u,%u,%d,%d,%d,%d,%d,%d,%d,%d",
WiFi.RSSI(),
WiFi.channel(),
WiFiHelper::getPhyMode(),
ESP.getFreeHeap(),
ESP.getHeapSize(),
ESP_getHeapFragmentation(),
ESP.getPsramSize(),
ESP.getFreePsram(),
UpTime(),
MI32.role,
MIBLEsensors.size()
);
WSContentSend(metricsBuf, 64);
}
}
}
@ -1821,7 +1856,7 @@ if(decryptRet!=0){
}
MIBLEsensors[_slot].eventType.lux = 1;
#ifdef USE_MI_EXT_GUI
MI32addHistory(MIBLEsensors[_slot].lux_history, (float)MIBLEsensors[_slot].lux, 2);
MI32addHistory(MIBLEsensors[_slot].lux_history, MIBLEsensors[_slot].lux, 2);
#endif //USE_MI_EXT_GUI
// AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 7: U24: %u Lux"), _payload.lux & 0x00ffffff);
break;
@ -1879,7 +1914,7 @@ if(decryptRet!=0){
MIBLEsensors[_slot].NMT = 0;
MI32.mode.shallTriggerTele = 1;
#ifdef USE_MI_EXT_GUI
MI32addHistory(MIBLEsensors[_slot].lux_history, (float)MIBLEsensors[_slot].lux, 2);
MI32addHistory(MIBLEsensors[_slot].lux_history, MIBLEsensors[_slot].lux, 2);
#endif //USE_MI_EXT_GUI
// AddLog(LOG_LEVEL_DEBUG,PSTR("motion: primary"),MIBLEsensors[_slot].lux );
break;
@ -2050,8 +2085,8 @@ void MI32ParseATCPacket(char * _buf, uint32_t length, uint8_t addr[6], int RSSI)
MIBLEsensors[_slot].eventType.bat = 1;
#ifdef USE_MI_EXT_GUI
bitSet(MI32.widgetSlot,_slot);
MI32addHistory(MIBLEsensors[_slot].temp_history, (float)MIBLEsensors[_slot].temp, 0);
MI32addHistory(MIBLEsensors[_slot].hum_history, (float)MIBLEsensors[_slot].hum, 1);
MI32addHistory(MIBLEsensors[_slot].temp_history, MIBLEsensors[_slot].temp, 0);
MI32addHistory(MIBLEsensors[_slot].hum_history, MIBLEsensors[_slot].hum, 1);
#endif //USE_MI_EXT_GUI
MIBLEsensors[_slot].shallSendMQTT = 1;
if(MI32.option.directBridgeMode == 1) MI32.mode.shallTriggerTele = 1;
@ -2074,7 +2109,7 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI
MIBLEsensors[_slot].eventType.temp = 1;
DEBUG_SENSOR_LOG(PSTR("CGD1: temp updated"));
#ifdef USE_MI_EXT_GUI
MI32addHistory(MIBLEsensors[_slot].temp_history, (float)MIBLEsensors[_slot].temp, 0);
MI32addHistory(MIBLEsensors[_slot].temp_history, MIBLEsensors[_slot].temp, 0);
#endif //USE_MI_EXT_GUI
}
_tempFloat=(float)(_packet.hum)/10.0f;
@ -2083,7 +2118,7 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI
MIBLEsensors[_slot].eventType.hum = 1;
DEBUG_SENSOR_LOG(PSTR("CGD1: hum updated"));
#ifdef USE_MI_EXT_GUI
MI32addHistory(MIBLEsensors[_slot].hum_history, (float)MIBLEsensors[_slot].hum, 1);
MI32addHistory(MIBLEsensors[_slot].hum_history, MIBLEsensors[_slot].hum, 1);
#endif //USE_MI_EXT_GUI
}
DEBUG_SENSOR_LOG(PSTR("CGD1: U16: %x Temp U16: %x Hum"), _packet.temp, _packet.hum);
@ -2164,7 +2199,7 @@ void MI32HandleEveryDevice(const NimBLEAdvertisedDevice* advertisedDevice, uint8
memcpy(_sensor.payload, advertisedDevice->getPayload().data(), advertisedDevice->getPayload().size());
_sensor.payload_len = advertisedDevice->getPayload().size();
bitSet(MI32.widgetSlot,_slot);
MI32addHistory(_sensor.temp_history, 0.0f, 3); // reuse temp_history as sighting history
MI32addHistory(_sensor.temp_history, 0, 3); // reuse temp_history as sighting history
_sensor.RSSI=RSSI;
_sensor.feature.payload = 1;
_sensor.eventType.payload = 1;
@ -2417,57 +2452,72 @@ bool MI32HandleWebGUIResponse(void){
}
char tmp[16];
WebGetArg(PSTR("wi"), tmp, sizeof(tmp));
if (!tmp[0]) return false;
WSContentBegin(200, CT_PLAIN);
uint32_t slot = MI32.widgetSlot;
if (slot) {
uint32_t i = __builtin_ctz(slot); // index of first set bit
MI32sendWidget(i);
MI32.widgetSlot &= ~(1UL << i); // clear that bit
} else {
MI32sendBerryWidget();
}
WSContentEnd();
if (!tmp[0]) {
return false;
}
if (atoi(tmp) == 0) {
MI32ServeStaticPage();
return true;
}
WSContentBegin(200, CT_PLAIN);
uint32_t slot = MI32.widgetSlot;
if (slot) {
uint32_t i = __builtin_ctz(slot); // index of first set bit
MI32sendWidget(i);
MI32.widgetSlot &= ~(1UL << i); // clear that bit
} else {
MI32sendBerryWidget();
}
WSContentEnd();
return true;
}
#ifdef USE_MI_ESP32_ENERGY
//https://gist.github.com/LingDong-/7e4c4cae5cbbc44400a05fba65f06f23
// used for logarithmic mapping of 0 - 3600 watts to 0-20 pixel - TaylorLog did not work as expected
float MI32ln(float x) {
unsigned int bx = * (unsigned int *) (&x);
unsigned int ex = bx >> 23;
signed int t = (signed int)ex-(signed int)127;
unsigned int s = (t < 0) ? (-t) : t;
bx = 1065353216 | (bx & 8388607);
x = * (float *) (&bx);
return -1.49278+(2.11263+(-0.729104+0.10969*x)*x)*x+0.6931471806*t;
// ultra simple integer log2 using builtin CLZ
int MI32ln(uint32_t x) {
return 31 - __builtin_clz(v);
}
#endif //USE_MI_ESP32_ENERGY
void MI32createPolyline(char *polyline, uint8_t *history){
uint32_t _pos = 0;
uint32_t _inc = 0;
for (uint32_t i = 0; i<24;i++){
uint32_t y = 21-MI32fetchHistory(history,i);
if (y>20){
y = 150; //create a big gap in the graph to represent invalidated data
}
_inc = snprintf_P(polyline+_pos,10,PSTR("%u,%u "),i*6,y);
_pos+=_inc;
void MI32createGraph(char *buffer, uint8_t *history, uint8_t r, uint8_t g, uint8_t b) {
constexpr size_t bufferSize = 256; // total size of buffer
uint32_t pos = 0;
uint16_t w = 150;
uint16_t h = 20;
// Start compact DSL for a single-series histogram: "{h,width,height,(r,g,b):"
if (pos < bufferSize - 20) {
pos += snprintf_P(buffer + pos, bufferSize - pos,
PSTR("{h,%u,%u,(%u,%u,%u):"),
w, h, r, g, b);
}
// Emit 24 history values separated by commas
for (uint8_t i = 0; i < 24 && pos < bufferSize - 10; i++) {
uint8_t value = MI32fetchHistory(history, i);
if (i > 0 && pos < bufferSize - 1) {
buffer[pos++] = ','; // add comma
buffer[pos] = '\0';
}
pos += snprintf_P(buffer + pos, bufferSize - pos,
PSTR("%u"),
value);
}
// Close the DSL block "}"
if (pos < bufferSize - 2) {
pos += snprintf_P(buffer + pos, bufferSize - pos,
PSTR("}"));
}
// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: polyline: %s"),polyline);
}
#ifdef USE_MI_ESP32_ENERGY
void MI32sendEnergyWidget(){
if (Energy->current_available && Energy->voltage_available) {
WSContentSend_P(HTTP_MI32_POWER_WIDGET,MIBLEsensors.size()+1, Energy->voltage,Energy->current[1]);
char _polyline[176];
MI32createPolyline(_polyline,MI32.energy_history);
WSContentSend_P(PSTR("<p>" D_POWERUSAGE ": %.1f " D_UNIT_WATT ""),Energy->active_power);
WSContentSend_P(HTTP_MI32_GRAPH,_polyline,185,124,124,_polyline,1);
WSContentSend_P(PSTR("</p></div>"));
char _graph[256];
MI32createGraph(_graph, MI32.energy_history, 185, 124, 124);
WSContentSend_P(PSTR("<p>" D_POWERUSAGE ": %.1f " D_UNIT_WATT "%s</p></div>"), Energy->active_power, _graph);
}
}
#endif //USE_MI_ESP32_ENERGY
@ -2524,18 +2574,14 @@ void MI32sendWidget(uint32_t slot){
if(_sensor.feature.temp == 1 && _sensor.feature.hum == 1){
if(!isnan(_sensor.temp)){
char _polyline[176];
MI32createPolyline(_polyline,_sensor.temp_history);
WSContentSend_P(PSTR("<p>" D_JSON_TEMPERATURE ": %.1f °C"),_sensor.temp);
WSContentSend_P(HTTP_MI32_GRAPH,_polyline,185,124,124,_polyline,1);
WSContentSend_P(PSTR("</p>"));
char _graph[256];
MI32createGraph(_graph, _sensor.temp_history, 185, 124, 124);
WSContentSend_P(PSTR("<p>" D_JSON_TEMPERATURE ": %.1f °C%s</p>"), _sensor.temp, _graph);
}
if(!isnan(_sensor.hum)){
char _polyline[176];
MI32createPolyline(_polyline,_sensor.hum_history);
WSContentSend_P(PSTR("<p>" D_JSON_HUMIDITY ": %.1f %%"),_sensor.hum);
WSContentSend_P(HTTP_MI32_GRAPH,_polyline,151,190,216,_polyline,2);
WSContentSend_P(PSTR("</p>"));
char _graph[256];
MI32createGraph(_graph, _sensor.hum_history, 151, 190, 216);
WSContentSend_P(PSTR("<p>" D_JSON_HUMIDITY ": %.1f %%%s</p>"), _sensor.hum, _graph);
}
if(!isnan(_sensor.temp) && !isnan(_sensor.hum)){
WSContentSend_P(PSTR("" D_JSON_DEWPOINT ": %.1f °C"),CalcTempHumToDew(_sensor.temp,_sensor.hum));
@ -2543,20 +2589,16 @@ void MI32sendWidget(uint32_t slot){
}
else if(_sensor.feature.temp == 1){
if(!isnan(_sensor.temp)){
char _polyline[176];
MI32createPolyline(_polyline,_sensor.temp_history);
WSContentSend_P(PSTR("<p>" D_JSON_TEMPERATURE ": %.1f °C"),_sensor.temp);
WSContentSend_P(HTTP_MI32_GRAPH,_polyline,185,124,124,_polyline,1);
WSContentSend_P(PSTR("</p>"));
char _graph[256];
MI32createGraph(_graph, _sensor.temp_history, 185, 124, 124);
WSContentSend_P(PSTR("<p>" D_JSON_TEMPERATURE ": %.1f °C%s</p>"), _sensor.temp, _graph);
}
}
if(_sensor.feature.lux == 1){
if(_sensor.lux!=0x00ffffff){
char _polyline[176];
MI32createPolyline(_polyline,_sensor.lux_history);
WSContentSend_P(PSTR("<p>" D_JSON_ILLUMINANCE ": %d Lux"),_sensor.lux);
WSContentSend_P(HTTP_MI32_GRAPH,_polyline,242,240,176,_polyline,3);
WSContentSend_P(PSTR("</p>"));
char _graph[256];
MI32createGraph(_graph, _sensor.lux_history, 242, 240, 176);
WSContentSend_P(PSTR("<p>" D_JSON_ILLUMINANCE ": %d Lux%s</p>"), _sensor.lux, _graph);
}
}
if(_sensor.feature.knob == 1){
@ -2599,12 +2641,10 @@ void MI32sendWidget(uint32_t slot){
if(_sensor.feature.payload == 1){
if(_sensor.payload != nullptr){
char _payload[128];
char _polyline[176];
char _graph[256];
ToHex_P((const unsigned char*)_sensor.payload,_sensor.payload_len,_payload, (_sensor.payload_len * 2) + 1);
MI32createPolyline(_polyline,_sensor.temp_history);
WSContentSend_P(PSTR("<p>Payload:"));
WSContentSend_P(HTTP_MI32_GRAPH,_polyline,60,240,176,_polyline,4);
WSContentSend_P(PSTR("</p><code style='word-break: break-all;'>%s</code>"),_payload);
MI32createGraph(_graph, _sensor.temp_history, 60, 240, 176);
WSContentSend_P(PSTR("<p>Payload:%s</p><code>%s</code>"),_graph,_payload);
}
}
WSContentSend_P(PSTR("<p>Timestamp: %s</p>"),GetDT(_sensor.lastTime).c_str());
@ -2614,38 +2654,25 @@ void MI32sendWidget(uint32_t slot){
void MI32InitGUI(void){
MI32.widgetSlot=0;
WSContentStart_P("m32");
WSContentSend_P(HTTP_MI32_SCRIPT_1);
WSContentSend_P(HTTP_MI32_BOOTSTRAP);
WSContentSendStyle();
WSContentSend_P(HTTP_MI32_STYLE);
WSContentSend_P(HTTP_MI32_STYLE_SVG,1,185,124,124,185,124,124);
WSContentSend_P(HTTP_MI32_STYLE_SVG,2,151,190,216,151,190,216);
WSContentSend_P(HTTP_MI32_STYLE_SVG,3,242,240,176,242,240,176);
WSContentSend_P(HTTP_MI32_STYLE_SVG,4,60,240,176,60,240,176);
char _role[16];
GetTextIndexed(_role, sizeof(_role), MI32.role, HTTP_MI32_PARENT_BLE_ROLE);
WSContentSend_P((HTTP_MI32_PARENT_START),MIBLEsensors.size(),UpTime(),ESP.getFreeHeap()/1024,_role);
uint32_t _slot;
for(_slot = 0;_slot<MIBLEsensors.size();_slot++){
MI32sendWidget(_slot);
}
#ifdef USE_MI_ESP32_ENERGY
MI32sendEnergyWidget();
#endif //USE_MI_ESP32_ENERGY
#ifdef USE_WEBCAM
MI32sendCamWidget();
#endif //USE_WEBCAM
WSContentSend_P(PSTR("</div>"));
WSContentSend_P("<div id='m'></div>");
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
}
void MI32ServeStaticPage(void) {
Webserver->sendHeader(F("Content-Encoding"), F("gzip"));
Webserver->sendHeader(F("Cache-Control"), F("no-store, no-cache, must-revalidate, max-age=0"));
Webserver->send_P(200, PSTR("text/html"), MI32_STATIC_PAGE, MI32_STATIC_PAGE_len);
}
void MI32HandleWebGUI(void){
if (!HttpCheckPriviledgedAccess()) { return; }
if (MI32HandleWebGUIResponse()) { return; }
MI32InitGUI();
size_t n = MIBLEsensors.size();
MI32.widgetSlot = ((1u << n) - 1);
}
#endif //USE_MI_EXT_GUI