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:
@ -4,6 +4,7 @@
|
||||
#include <U8g2lib.h>
|
||||
#include <SPI.h>
|
||||
#include <WiFi.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "config.h" // PIN_OLED_MOSI / CLK / CS / DC / RST, WIFI_AP_SSID
|
||||
#include "tms_link.h"
|
||||
@ -36,10 +37,26 @@ U8G2_SSD1322_NHD_256X64_F_4W_HW_SPI u8g2(
|
||||
const uint32_t REFRESH_PERIOD_MS = 300;
|
||||
uint32_t g_lastRefreshMs = 0;
|
||||
|
||||
// Port du serveur web de l'IHM -- doit rester synchronise avec
|
||||
// `WebServer server(80)` dans web_ui.cpp (pas d'accesseur dedie pour
|
||||
// l'instant, les deux sont a changer ensemble si le port change).
|
||||
const uint16_t WEB_UI_PORT = 80;
|
||||
// Luminosite/contraste. g_brightness est la position du slider web
|
||||
// (0-255, valeur brute conservee telle quelle pour re-synchroniser le
|
||||
// slider -- voir OledUi::brightness()) ; 159 = valeur par defaut de la
|
||||
// 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
|
||||
@ -132,6 +149,15 @@ const float ALERT_THRESHOLD = 0.90f; // 90% de l'echelle -> icone attention
|
||||
// des seuils/etat reels. Positions validees -> desactive.
|
||||
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.
|
||||
const float T1_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];
|
||||
u8g2.setFont(u8g2_font_4x6_tr);
|
||||
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);
|
||||
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) ---
|
||||
// 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.setFontPosTop();
|
||||
u8g2.drawStr(0, 0, "HIVY");
|
||||
@ -346,34 +374,42 @@ void drawDashboard(const Telemetry &t, bool linkOk) {
|
||||
}
|
||||
|
||||
IPAddress ip = WiFi.softAPIP();
|
||||
snprintf(buf, sizeof(buf), "WIFI:%s %d.%d.%d.%d:%u", WIFI_AP_SSID, ip[0], ip[1],
|
||||
ip[2], ip[3], WEB_UI_PORT);
|
||||
snprintf(buf, sizeof(buf), "WIFI:%s %d.%d.%d.%d", WIFI_AP_SSID, ip[0], ip[1], ip[2], ip[3]);
|
||||
int wWifi = u8g2.getStrWidth(buf);
|
||||
u8g2.drawStr(SCREEN_W - wWifi, 0, buf);
|
||||
|
||||
// --- 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.
|
||||
drawVBar(COL1_X + 2, "VIN", t.vin, VIN_MIN, VIN_MAX, true, -14);
|
||||
drawVBar(COL1_X + 29, "IIN", t.iin, IIN_MIN, IIN_MAX, true, -14);
|
||||
drawVBar(COL1_X + 56, "VOUT", t.vout, VOUT_MIN, VOUT_MAX, true, -14);
|
||||
// Bargraphs 2/3/4 (IIN/VOUT/IOUT) + valeurs + mesures decales de 2px
|
||||
// a droite (29->31, 56->58, 83->85) ; VIN (bargraph 1) inchange.
|
||||
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).
|
||||
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);
|
||||
snprintf(buf, sizeof(buf), "%.1fV", t.vin);
|
||||
snprintf(buf, sizeof(buf), "%.1fV", vinDisp);
|
||||
u8g2.drawStr(COL1_X, BAR_Y + BAR_H + 4, buf);
|
||||
snprintf(buf, sizeof(buf), "%.1fA", t.iin); // 2 digits (X.Y)
|
||||
u8g2.drawStr(COL1_X + 27, BAR_Y + BAR_H + 4, buf);
|
||||
snprintf(buf, sizeof(buf), "%.0fV", t.vout);
|
||||
u8g2.drawStr(COL1_X + 54, BAR_Y + BAR_H + 4, buf);
|
||||
snprintf(buf, sizeof(buf), "%.0fmA", t.iout * 1000.0f);
|
||||
u8g2.drawStr(COL1_X + 81, BAR_Y + BAR_H + 4, buf);
|
||||
snprintf(buf, sizeof(buf), "%.1fA", iinDisp); // 2 digits (X.Y)
|
||||
u8g2.drawStr(COL1_X + 30, BAR_Y + BAR_H + 4, buf);
|
||||
snprintf(buf, sizeof(buf), "%.0fV", voutDisp);
|
||||
u8g2.drawStr(COL1_X + 56, BAR_Y + BAR_H + 4, buf);
|
||||
snprintf(buf, sizeof(buf), "%.0fmA", ioutDisp * 1000.0f);
|
||||
u8g2.drawStr(COL1_X + 83, BAR_Y + BAR_H + 4, buf);
|
||||
|
||||
u8g2.drawVLine(COL1_COL2_DIVIDER_X, BAR_Y - 6, BAR_H + 20);
|
||||
|
||||
// --- colonne 2 : RENDEMENT (50-100%, pas d'alerte) / PIN / POUT ---
|
||||
float pin = t.vin * t.iin;
|
||||
float pout = t.vout * t.iout;
|
||||
float pin = vinDisp * iinDisp;
|
||||
float pout = voutDisp * ioutDisp;
|
||||
float eff = (pin > 0.01f) ? (pout / pin * 100.0f) : 0.0f;
|
||||
if (eff > 100.0f) eff = 100.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);
|
||||
u8g2.drawStr(COL2_X + 7, BAR_Y + BAR_H + 4, buf);
|
||||
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);
|
||||
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);
|
||||
|
||||
@ -443,7 +479,7 @@ void drawDashboard(const Telemetry &t, bool linkOk) {
|
||||
// Regle reelle : HT actif (cmd.ht) ET Vout > 50V -- jamais avec DEFAUT,
|
||||
// meme si Vout mesure encore > 50V par inertie a ce moment-la.
|
||||
// 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()) {
|
||||
drawWarningIconHalf(SCREEN_W - WARNING_ICON_WIDTH / 2, 64 - WARNING_ICON_HEIGHT / 2);
|
||||
}
|
||||
@ -475,6 +511,8 @@ void begin() {
|
||||
// l'hypothese initiale.
|
||||
u8g2.sendF("caa", 0xA0, 0x14, 0x11);
|
||||
|
||||
u8g2.setContrast(sliderToContrast(g_brightness));
|
||||
|
||||
showSplash();
|
||||
}
|
||||
|
||||
@ -507,4 +545,13 @@ void update(const Telemetry &t, bool 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
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "protocol.h"
|
||||
|
||||
namespace OledUi {
|
||||
void begin();
|
||||
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
|
||||
|
||||
@ -28,20 +28,26 @@ uint8_t checksumOf(const char *s, size_t len) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (!strcmp(tag, "FREQ1")) g_telemetry.freq1 = value;
|
||||
else if (!strcmp(tag, "FREQ2")) g_telemetry.freq2 = value;
|
||||
else if (!strcmp(tag, "DUTY1")) g_telemetry.duty1 = value;
|
||||
else if (!strcmp(tag, "DUTY2")) g_telemetry.duty2 = value;
|
||||
else if (!strcmp(tag, "VIN")) g_telemetry.vin = value;
|
||||
else if (!strcmp(tag, "IIN")) g_telemetry.iin = value;
|
||||
else if (!strcmp(tag, "V1")) g_telemetry.v1 = value;
|
||||
else if (!strcmp(tag, "I1")) g_telemetry.i1 = value;
|
||||
else if (!strcmp(tag, "VIN")) g_telemetry.vin = clampNonNegative(value);
|
||||
else if (!strcmp(tag, "IIN")) g_telemetry.iin = clampNonNegative(value);
|
||||
else if (!strcmp(tag, "V1")) g_telemetry.v1 = clampNonNegative(value);
|
||||
else if (!strcmp(tag, "I1")) g_telemetry.i1 = clampNonNegative(value);
|
||||
else if (!strcmp(tag, "T1")) g_telemetry.t1 = value;
|
||||
else if (!strcmp(tag, "VOUT")) g_telemetry.vout = value;
|
||||
else if (!strcmp(tag, "I2")) g_telemetry.i2 = value;
|
||||
else if (!strcmp(tag, "VOUT")) g_telemetry.vout = clampNonNegative(value);
|
||||
else if (!strcmp(tag, "I2")) g_telemetry.i2 = clampNonNegative(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
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "oled_ui.h"
|
||||
#include "tms_link.h"
|
||||
|
||||
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-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}
|
||||
.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>
|
||||
<body>
|
||||
<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_pwm2" onclick="cmd('pwm2')">PWM2</button>
|
||||
</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="etages" id="etages"></div>
|
||||
<script>
|
||||
@ -100,6 +110,12 @@ function send(){
|
||||
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)});
|
||||
}
|
||||
|
||||
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){
|
||||
state[k]=!state[k];
|
||||
document.getElementById('b_'+k).className = state[k] ? 'on' : '';
|
||||
@ -115,6 +131,7 @@ function refresh(){
|
||||
updateGauges(d);
|
||||
document.getElementById('etages').innerHTML =
|
||||
'Etage 1 : T='+d.t1.toFixed(1)+'°C Etage 2 : T='+d.t2.toFixed(1)+'°C';
|
||||
if (!brightnessBusy) document.getElementById('brightness').value = d.brightness;
|
||||
});
|
||||
}
|
||||
buildGauges();
|
||||
@ -138,10 +155,10 @@ void handleState() {
|
||||
"{\"link\":%s,\"vin\":%.2f,\"iin\":%.2f,\"iout\":%.2f,"
|
||||
"\"v1\":%.2f,\"i1\":%.2f,\"t1\":%.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,
|
||||
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);
|
||||
}
|
||||
|
||||
@ -155,6 +172,15 @@ void handleCommand() {
|
||||
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
|
||||
|
||||
void begin() {
|
||||
@ -167,6 +193,7 @@ void begin() {
|
||||
server.on("/", HTTP_GET, handleRoot);
|
||||
server.on("/api/state", HTTP_GET, handleState);
|
||||
server.on("/api/command", HTTP_POST, handleCommand);
|
||||
server.on("/api/brightness", HTTP_POST, handleBrightness);
|
||||
server.begin();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user