Ajoute la luminosite OLED reglable depuis la page web et affine le dashboard

- Slider de luminosite (0-255) sur la page web, avec correction gamma
  (2.2, configurable) cote firmware pour une progression percue
  reguliere sur toute la course plutot que concentree dans le dernier
  tiers.
- Clampe VIN/IIN/V1/I1/VOUT/I2/IOUT a 0 a la reception (bruit/offset ADC
  pouvant donner de legeres valeurs negatives, physiquement impossibles
  sur cette alim).
- Ajustements fins de layout du dashboard OLED (positions bargraphs,
  mesures, valeurs max) et suppression du port dans la ligne WIFI
  affichee (80 = port HTTP par defaut, non necessaire pour se connecter).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 11:43:56 +02:00
parent 3c03e3fbf1
commit d8f38e0b4b
4 changed files with 120 additions and 33 deletions

View File

@ -4,6 +4,7 @@
#include <U8g2lib.h> #include <U8g2lib.h>
#include <SPI.h> #include <SPI.h>
#include <WiFi.h> #include <WiFi.h>
#include <math.h>
#include "config.h" // PIN_OLED_MOSI / CLK / CS / DC / RST, WIFI_AP_SSID #include "config.h" // PIN_OLED_MOSI / CLK / CS / DC / RST, WIFI_AP_SSID
#include "tms_link.h" #include "tms_link.h"
@ -36,10 +37,26 @@ U8G2_SSD1322_NHD_256X64_F_4W_HW_SPI u8g2(
const uint32_t REFRESH_PERIOD_MS = 300; const uint32_t REFRESH_PERIOD_MS = 300;
uint32_t g_lastRefreshMs = 0; uint32_t g_lastRefreshMs = 0;
// Port du serveur web de l'IHM -- doit rester synchronise avec // Luminosite/contraste. g_brightness est la position du slider web
// `WebServer server(80)` dans web_ui.cpp (pas d'accesseur dedie pour // (0-255, valeur brute conservee telle quelle pour re-synchroniser le
// l'instant, les deux sont a changer ensemble si le port change). // slider -- voir OledUi::brightness()) ; 159 = valeur par defaut de la
const uint16_t WEB_UI_PORT = 80; // sequence d'init u8g2 pour ce controleur (u8x8_d_ssd1322.c, 0xC1).
//
// L'oeil percoit la luminosite de facon logarithmique : un mapping
// lineaire slider->registre 0xC1 concentre toute la variation
// perceptible dans le dernier tiers de la course (0-150 ne change
// quasiment rien a l'oeil, 200-255 fait toute la difference -- constate
// sur le hardware reel). GAMMA est configurable pour affiner apres test
// visuel (2.2 = valeur de depart standard, cf. courbes gamma d'affichage).
const float BRIGHTNESS_GAMMA = 2.2f;
uint8_t sliderToContrast(uint8_t slider_0_255) {
float normalized = slider_0_255 / 255.0f;
float corrected = powf(normalized, BRIGHTNESS_GAMMA);
return (uint8_t)(corrected * 255.0f + 0.5f);
}
uint8_t g_brightness = 159;
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Splash logo (voir doc/OLED-u8g2.md pour l'historique des problemes // Splash logo (voir doc/OLED-u8g2.md pour l'historique des problemes
@ -132,6 +149,15 @@ const float ALERT_THRESHOLD = 0.90f; // 90% de l'echelle -> icone attention
// des seuils/etat reels. Positions validees -> desactive. // des seuils/etat reels. Positions validees -> desactive.
const bool DEBUG_FORCE_ALL_ICONS = false; const bool DEBUG_FORCE_ALL_ICONS = false;
// DEBUG PROVISOIRE : force VIN/IIN/VOUT/IOUT a des valeurs de test
// realistes (au lieu de la telemetrie reelle) pour verifier l'affichage.
// Remettre a false une fois le test termine.
const bool DEBUG_FORCE_TEST_VALUES = false;
const float TEST_VIN = 10.5f;
const float TEST_IIN = 1.5f;
const float TEST_VOUT = 358.0f;
const float TEST_IOUT = 0.037f; // 37 mA
// Seuils temperature -- PROVISOIRES, a corriger avec les vraies specs. // Seuils temperature -- PROVISOIRES, a corriger avec les vraies specs.
const float T1_ALERT_C = 70.0f; const float T1_ALERT_C = 70.0f;
const float T2_ALERT_C = 70.0f; const float T2_ALERT_C = 70.0f;
@ -216,7 +242,7 @@ void drawVBar(int x, const char *label, float value, float vmin, float vmax,
char buf[8]; char buf[8];
u8g2.setFont(u8g2_font_4x6_tr); u8g2.setFont(u8g2_font_4x6_tr);
snprintf(buf, sizeof(buf), "%.0f", vmax * labelScale); snprintf(buf, sizeof(buf), "%.0f", vmax * labelScale);
u8g2.drawStr(x + BAR_W + 3, BAR_Y + 5 - LABEL_Y_SHIFT, buf); u8g2.drawStr(x + BAR_W + 3, BAR_Y + 3 - LABEL_Y_SHIFT, buf); // remonte de 2px
snprintf(buf, sizeof(buf), "%.0f", vmin * labelScale); snprintf(buf, sizeof(buf), "%.0f", vmin * labelScale);
u8g2.drawStr(x + BAR_W + 3, BAR_Y + BAR_H - LABEL_Y_SHIFT, buf); u8g2.drawStr(x + BAR_W + 3, BAR_Y + BAR_H - LABEL_Y_SHIFT, buf);
@ -333,7 +359,9 @@ void drawDashboard(const Telemetry &t, bool linkOk) {
// --- bandeau haut : HIVY (gauche) / SYS:OK (centre) / WIFI:... (droite) --- // --- bandeau haut : HIVY (gauche) / SYS:OK (centre) / WIFI:... (droite) ---
// SSID dynamique (config.h, maintenant "HIVY") + IP reelle (WiFi.softAPIP(), // SSID dynamique (config.h, maintenant "HIVY") + IP reelle (WiFi.softAPIP(),
// attribuee par WiFi.softAP() dans web_ui.cpp) + port (WEB_UI_PORT ci-dessus). // attribuee par WiFi.softAP() dans web_ui.cpp). Pas de port : 80 est le
// port HTTP par defaut, pas necessaire pour se connecter (http://<ip>/
// suffit, le navigateur l'ajoute lui-meme).
u8g2.setFont(u8g2_font_5x8_tr); u8g2.setFont(u8g2_font_5x8_tr);
u8g2.setFontPosTop(); u8g2.setFontPosTop();
u8g2.drawStr(0, 0, "HIVY"); u8g2.drawStr(0, 0, "HIVY");
@ -346,34 +374,42 @@ void drawDashboard(const Telemetry &t, bool linkOk) {
} }
IPAddress ip = WiFi.softAPIP(); IPAddress ip = WiFi.softAPIP();
snprintf(buf, sizeof(buf), "WIFI:%s %d.%d.%d.%d:%u", WIFI_AP_SSID, ip[0], ip[1], snprintf(buf, sizeof(buf), "WIFI:%s %d.%d.%d.%d", WIFI_AP_SSID, ip[0], ip[1], ip[2], ip[3]);
ip[2], ip[3], WEB_UI_PORT);
int wWifi = u8g2.getStrWidth(buf); int wWifi = u8g2.getStrWidth(buf);
u8g2.drawStr(SCREEN_W - wWifi, 0, buf); u8g2.drawStr(SCREEN_W - wWifi, 0, buf);
// --- colonne 1 : VIN / IIN / VOUT / IOUT --- // --- colonne 1 : VIN / IIN / VOUT / IOUT ---
// DEBUG_FORCE_TEST_VALUES : valeurs de test au lieu de la telemetrie
// reelle (t.vin/t.iin/t.vout/t.iout), pour verifier l'affichage.
float vinDisp = DEBUG_FORCE_TEST_VALUES ? TEST_VIN : t.vin;
float iinDisp = DEBUG_FORCE_TEST_VALUES ? TEST_IIN : t.iin;
float voutDisp = DEBUG_FORCE_TEST_VALUES ? TEST_VOUT : t.vout;
float ioutDisp = DEBUG_FORCE_TEST_VALUES ? TEST_IOUT : t.iout;
// Logo 4 (IOUT) : -10 encore -3px = -13. // Logo 4 (IOUT) : -10 encore -3px = -13.
drawVBar(COL1_X + 2, "VIN", t.vin, VIN_MIN, VIN_MAX, true, -14); // Bargraphs 2/3/4 (IIN/VOUT/IOUT) + valeurs + mesures decales de 2px
drawVBar(COL1_X + 29, "IIN", t.iin, IIN_MIN, IIN_MAX, true, -14); // a droite (29->31, 56->58, 83->85) ; VIN (bargraph 1) inchange.
drawVBar(COL1_X + 56, "VOUT", t.vout, VOUT_MIN, VOUT_MAX, true, -14); drawVBar(COL1_X + 2, "VIN", vinDisp, VIN_MIN, VIN_MAX, true, -14);
drawVBar(COL1_X + 32, "IIN", iinDisp, IIN_MIN, IIN_MAX, true, -14);
drawVBar(COL1_X + 58, "VOUT", voutDisp, VOUT_MIN, VOUT_MAX, true, -14);
// labelScale=1000 : bornes affichees en mA (0/50) au lieu de A (0/0.05). // labelScale=1000 : bornes affichees en mA (0/50) au lieu de A (0/0.05).
drawVBar(COL1_X + 83, "IOUT", t.iout, IOUT_MIN, IOUT_MAX, true, -13, 1000.0f); drawVBar(COL1_X + 85, "IOUT", ioutDisp, IOUT_MIN, IOUT_MAX, true, -13, 1000.0f);
u8g2.setFont(u8g2_font_6x12_tr); u8g2.setFont(u8g2_font_6x12_tr);
snprintf(buf, sizeof(buf), "%.1fV", t.vin); snprintf(buf, sizeof(buf), "%.1fV", vinDisp);
u8g2.drawStr(COL1_X, BAR_Y + BAR_H + 4, buf); u8g2.drawStr(COL1_X, BAR_Y + BAR_H + 4, buf);
snprintf(buf, sizeof(buf), "%.1fA", t.iin); // 2 digits (X.Y) snprintf(buf, sizeof(buf), "%.1fA", iinDisp); // 2 digits (X.Y)
u8g2.drawStr(COL1_X + 27, BAR_Y + BAR_H + 4, buf); u8g2.drawStr(COL1_X + 30, BAR_Y + BAR_H + 4, buf);
snprintf(buf, sizeof(buf), "%.0fV", t.vout); snprintf(buf, sizeof(buf), "%.0fV", voutDisp);
u8g2.drawStr(COL1_X + 54, BAR_Y + BAR_H + 4, buf); u8g2.drawStr(COL1_X + 56, BAR_Y + BAR_H + 4, buf);
snprintf(buf, sizeof(buf), "%.0fmA", t.iout * 1000.0f); snprintf(buf, sizeof(buf), "%.0fmA", ioutDisp * 1000.0f);
u8g2.drawStr(COL1_X + 81, BAR_Y + BAR_H + 4, buf); u8g2.drawStr(COL1_X + 83, BAR_Y + BAR_H + 4, buf);
u8g2.drawVLine(COL1_COL2_DIVIDER_X, BAR_Y - 6, BAR_H + 20); u8g2.drawVLine(COL1_COL2_DIVIDER_X, BAR_Y - 6, BAR_H + 20);
// --- colonne 2 : RENDEMENT (50-100%, pas d'alerte) / PIN / POUT --- // --- colonne 2 : RENDEMENT (50-100%, pas d'alerte) / PIN / POUT ---
float pin = t.vin * t.iin; float pin = vinDisp * iinDisp;
float pout = t.vout * t.iout; float pout = voutDisp * ioutDisp;
float eff = (pin > 0.01f) ? (pout / pin * 100.0f) : 0.0f; float eff = (pin > 0.01f) ? (pout / pin * 100.0f) : 0.0f;
if (eff > 100.0f) eff = 100.0f; if (eff > 100.0f) eff = 100.0f;
if (eff < 0.0f) eff = 0.0f; if (eff < 0.0f) eff = 0.0f;
@ -391,9 +427,9 @@ void drawDashboard(const Telemetry &t, bool linkOk) {
snprintf(buf, sizeof(buf), "%.0f%%", eff); snprintf(buf, sizeof(buf), "%.0f%%", eff);
u8g2.drawStr(COL2_X + 7, BAR_Y + BAR_H + 4, buf); u8g2.drawStr(COL2_X + 7, BAR_Y + BAR_H + 4, buf);
snprintf(buf, sizeof(buf), "%.1fW", pin); snprintf(buf, sizeof(buf), "%.1fW", pin);
u8g2.drawStr(COL2_X + 41, BAR_Y + BAR_H + 4, buf); u8g2.drawStr(COL2_X + 33, BAR_Y + BAR_H + 4, buf); // mesure PIN seule, -6px puis -2px
snprintf(buf, sizeof(buf), "%.1fW", pout); snprintf(buf, sizeof(buf), "%.1fW", pout);
u8g2.drawStr(COL2_X + 68, BAR_Y + BAR_H + 4, buf); u8g2.drawStr(COL2_X + 64, BAR_Y + BAR_H + 4, buf); // mesure POUT seule, -4px
u8g2.drawVLine(COL3_X, BAR_Y - 6, BAR_H + 20); u8g2.drawVLine(COL3_X, BAR_Y - 6, BAR_H + 20);
@ -443,7 +479,7 @@ void drawDashboard(const Telemetry &t, bool linkOk) {
// Regle reelle : HT actif (cmd.ht) ET Vout > 50V -- jamais avec DEFAUT, // Regle reelle : HT actif (cmd.ht) ET Vout > 50V -- jamais avec DEFAUT,
// meme si Vout mesure encore > 50V par inertie a ce moment-la. // meme si Vout mesure encore > 50V par inertie a ce moment-la.
// Calee tout en bas a droite de la colonne 3 (= bord ecran). // Calee tout en bas a droite de la colonne 3 (= bord ecran).
bool dangerActive = cmd.ht && t.vout > VOUT_DANGER_THRESHOLD; bool dangerActive = cmd.ht && voutDisp > VOUT_DANGER_THRESHOLD;
if ((DEBUG_FORCE_ALL_ICONS || dangerActive) && Blink1Hz()) { if ((DEBUG_FORCE_ALL_ICONS || dangerActive) && Blink1Hz()) {
drawWarningIconHalf(SCREEN_W - WARNING_ICON_WIDTH / 2, 64 - WARNING_ICON_HEIGHT / 2); drawWarningIconHalf(SCREEN_W - WARNING_ICON_WIDTH / 2, 64 - WARNING_ICON_HEIGHT / 2);
} }
@ -475,6 +511,8 @@ void begin() {
// l'hypothese initiale. // l'hypothese initiale.
u8g2.sendF("caa", 0xA0, 0x14, 0x11); u8g2.sendF("caa", 0xA0, 0x14, 0x11);
u8g2.setContrast(sliderToContrast(g_brightness));
showSplash(); showSplash();
} }
@ -507,4 +545,13 @@ void update(const Telemetry &t, bool linkOk) {
drawDashboard(t, linkOk); drawDashboard(t, linkOk);
} }
void setBrightness(uint8_t value) {
g_brightness = value;
u8g2.setContrast(sliderToContrast(g_brightness));
}
// Valeur brute du slider (0-255), pas la valeur gamma-corrigee envoyee
// au registre -- c'est ce que la page web doit reafficher.
uint8_t brightness() { return g_brightness; }
} // namespace OledUi } // namespace OledUi

View File

@ -1,7 +1,14 @@
#pragma once #pragma once
#include <Arduino.h>
#include "protocol.h" #include "protocol.h"
namespace OledUi { namespace OledUi {
void begin(); void begin();
void update(const Telemetry &t, bool linkOk); void update(const Telemetry &t, bool linkOk);
// Luminosite/contraste OLED (u8g2.setContrast(), plage 0-255, defaut 159).
// Reglable depuis le slider de la page web (voir web_ui.cpp).
void setBrightness(uint8_t value);
uint8_t brightness();
} // namespace OledUi } // namespace OledUi

View File

@ -28,20 +28,26 @@ uint8_t checksumOf(const char *s, size_t len) {
} }
// Applique une valeur "TAG=xxx" a la telemetrie courante. // Applique une valeur "TAG=xxx" a la telemetrie courante.
// Aucune tension/courant physique n'est negative sur cette alim (les
// legeres valeurs negatives observees viennent du bruit/offset ADC
// autour de zero) -- on les ramene a 0 des la reception, pour tout
// affichage en aval (OLED, page web).
float clampNonNegative(float value) { return value < 0.0f ? 0.0f : value; }
void applyField(const char *tag, float value) { void applyField(const char *tag, float value) {
if (!strcmp(tag, "FREQ1")) g_telemetry.freq1 = value; if (!strcmp(tag, "FREQ1")) g_telemetry.freq1 = value;
else if (!strcmp(tag, "FREQ2")) g_telemetry.freq2 = value; else if (!strcmp(tag, "FREQ2")) g_telemetry.freq2 = value;
else if (!strcmp(tag, "DUTY1")) g_telemetry.duty1 = value; else if (!strcmp(tag, "DUTY1")) g_telemetry.duty1 = value;
else if (!strcmp(tag, "DUTY2")) g_telemetry.duty2 = value; else if (!strcmp(tag, "DUTY2")) g_telemetry.duty2 = value;
else if (!strcmp(tag, "VIN")) g_telemetry.vin = value; else if (!strcmp(tag, "VIN")) g_telemetry.vin = clampNonNegative(value);
else if (!strcmp(tag, "IIN")) g_telemetry.iin = value; else if (!strcmp(tag, "IIN")) g_telemetry.iin = clampNonNegative(value);
else if (!strcmp(tag, "V1")) g_telemetry.v1 = value; else if (!strcmp(tag, "V1")) g_telemetry.v1 = clampNonNegative(value);
else if (!strcmp(tag, "I1")) g_telemetry.i1 = value; else if (!strcmp(tag, "I1")) g_telemetry.i1 = clampNonNegative(value);
else if (!strcmp(tag, "T1")) g_telemetry.t1 = value; else if (!strcmp(tag, "T1")) g_telemetry.t1 = value;
else if (!strcmp(tag, "VOUT")) g_telemetry.vout = value; else if (!strcmp(tag, "VOUT")) g_telemetry.vout = clampNonNegative(value);
else if (!strcmp(tag, "I2")) g_telemetry.i2 = value; else if (!strcmp(tag, "I2")) g_telemetry.i2 = clampNonNegative(value);
else if (!strcmp(tag, "T2")) g_telemetry.t2 = value; else if (!strcmp(tag, "T2")) g_telemetry.t2 = value;
else if (!strcmp(tag, "IOUT")) g_telemetry.iout = value; else if (!strcmp(tag, "IOUT")) g_telemetry.iout = clampNonNegative(value);
} }
// Parse une ligne "$T,TAG=val,TAG=val,...*XX". Retourne true si le // Parse une ligne "$T,TAG=val,TAG=val,...*XX". Retourne true si le

View File

@ -5,6 +5,7 @@
#include <WiFi.h> #include <WiFi.h>
#include "config.h" #include "config.h"
#include "oled_ui.h"
#include "tms_link.h" #include "tms_link.h"
namespace WebUi { namespace WebUi {
@ -40,6 +41,9 @@ button.on{background:var(--good);border-color:var(--good);color:#08210a}
.gauge-track{position:relative;width:16px;height:120px;border-radius:8px;background:var(--track);overflow:hidden;margin:2px 0} .gauge-track{position:relative;width:16px;height:120px;border-radius:8px;background:var(--track);overflow:hidden;margin:2px 0}
.gauge-fill{position:absolute;bottom:0;left:0;width:100%;height:0%;border-radius:8px;transition:height 0.3s} .gauge-fill{position:absolute;bottom:0;left:0;width:100%;height:0%;border-radius:8px;transition:height 0.3s}
.etages{color:var(--ink2);font-size:0.9em;margin-top:1em;line-height:1.6} .etages{color:var(--ink2);font-size:0.9em;margin-top:1em;line-height:1.6}
.brightness{margin-bottom:1em}
.brightness label{display:block;color:var(--ink2);font-size:0.85em;margin-bottom:4px}
.brightness input[type=range]{width:100%;max-width:300px}
</style></head> </style></head>
<body> <body>
<h1>AlimHT</h1> <h1>AlimHT</h1>
@ -49,6 +53,12 @@ button.on{background:var(--good);border-color:var(--good);color:#08210a}
<button id="b_pwm1" onclick="cmd('pwm1')">PWM1</button> <button id="b_pwm1" onclick="cmd('pwm1')">PWM1</button>
<button id="b_pwm2" onclick="cmd('pwm2')">PWM2</button> <button id="b_pwm2" onclick="cmd('pwm2')">PWM2</button>
</div> </div>
<div class="brightness">
<label for="brightness">Luminosite ecran OLED</label>
<input type="range" id="brightness" min="0" max="255" value="159"
onmousedown="brightnessBusy=true" ontouchstart="brightnessBusy=true"
onchange="brightnessBusy=false;setBrightness(this.value)">
</div>
<div class="gauges" id="gauges"></div> <div class="gauges" id="gauges"></div>
<div class="etages" id="etages"></div> <div class="etages" id="etages"></div>
<script> <script>
@ -100,6 +110,12 @@ function send(){
fetch('/api/command',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'}, fetch('/api/command',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'ht='+(state.ht?1:0)+'&pwm1='+(state.pwm1?1:0)+'&pwm2='+(state.pwm2?1:0)}); body:'ht='+(state.ht?1:0)+'&pwm1='+(state.pwm1?1:0)+'&pwm2='+(state.pwm2?1:0)});
} }
let brightnessBusy=false;
function setBrightness(v){
fetch('/api/brightness',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'value='+v});
}
function cmd(k){ function cmd(k){
state[k]=!state[k]; state[k]=!state[k];
document.getElementById('b_'+k).className = state[k] ? 'on' : ''; document.getElementById('b_'+k).className = state[k] ? 'on' : '';
@ -115,6 +131,7 @@ function refresh(){
updateGauges(d); updateGauges(d);
document.getElementById('etages').innerHTML = document.getElementById('etages').innerHTML =
'Etage 1 : T='+d.t1.toFixed(1)+'&deg;C &nbsp; Etage 2 : T='+d.t2.toFixed(1)+'&deg;C'; 'Etage 1 : T='+d.t1.toFixed(1)+'&deg;C &nbsp; Etage 2 : T='+d.t2.toFixed(1)+'&deg;C';
if (!brightnessBusy) document.getElementById('brightness').value = d.brightness;
}); });
} }
buildGauges(); buildGauges();
@ -138,10 +155,10 @@ void handleState() {
"{\"link\":%s,\"vin\":%.2f,\"iin\":%.2f,\"iout\":%.2f," "{\"link\":%s,\"vin\":%.2f,\"iin\":%.2f,\"iout\":%.2f,"
"\"v1\":%.2f,\"i1\":%.2f,\"t1\":%.2f," "\"v1\":%.2f,\"i1\":%.2f,\"t1\":%.2f,"
"\"vout\":%.2f,\"i2\":%.2f,\"t2\":%.2f," "\"vout\":%.2f,\"i2\":%.2f,\"t2\":%.2f,"
"\"ht\":%s,\"pwm1\":%s,\"pwm2\":%s}", "\"ht\":%s,\"pwm1\":%s,\"pwm2\":%s,\"brightness\":%u}",
TmsLink::linkOk() ? "true" : "false", t.vin, t.iin, t.iout, t.v1, t.i1, t.t1, TmsLink::linkOk() ? "true" : "false", t.vin, t.iin, t.iout, t.v1, t.i1, t.t1,
t.vout, t.i2, t.t2, cmd.ht ? "true" : "false", cmd.pwm1 ? "true" : "false", t.vout, t.i2, t.t2, cmd.ht ? "true" : "false", cmd.pwm1 ? "true" : "false",
cmd.pwm2 ? "true" : "false"); cmd.pwm2 ? "true" : "false", OledUi::brightness());
server.send(200, "application/json", buf); server.send(200, "application/json", buf);
} }
@ -155,6 +172,15 @@ void handleCommand() {
server.send(200, "text/plain", "OK"); server.send(200, "text/plain", "OK");
} }
void handleBrightness() {
markActivity();
int v = server.arg("value").toInt();
if (v < 0) v = 0;
if (v > 255) v = 255;
OledUi::setBrightness((uint8_t)v);
server.send(200, "text/plain", "OK");
}
} // namespace } // namespace
void begin() { void begin() {
@ -167,6 +193,7 @@ void begin() {
server.on("/", HTTP_GET, handleRoot); server.on("/", HTTP_GET, handleRoot);
server.on("/api/state", HTTP_GET, handleState); server.on("/api/state", HTTP_GET, handleState);
server.on("/api/command", HTTP_POST, handleCommand); server.on("/api/command", HTTP_POST, handleCommand);
server.on("/api/brightness", HTTP_POST, handleBrightness);
server.begin(); server.begin();
} }