dashboard settings first steps

This commit is contained in:
DeltaLima 2025-04-07 00:27:30 +02:00
parent b40380ed1e
commit bfddc51644
9 changed files with 365 additions and 16 deletions

View file

@ -52,6 +52,9 @@ const byte Max_Sensors_Read = 6;
/* how much GPIOs a Sensor can use */
const byte Max_Sensors_GPIO = 2;
/* Max Limits for Dashboard elements*/
const byte Max_Dashboard_Gauge = 6;
const byte Max_Dashboard_Chart = 6;
/* actual structure initialization for GPIO_Index is done within the header files
* for ESP32 and ESP8266
@ -254,6 +257,7 @@ struct Config_System_Sensor {
byte type[Max_Sensors];
char name[Max_Sensors][32];
byte i2c_addr[Max_Sensors];
/* sensor gpio is an array as preparation to have sensors with multiple GPIOs like HX711 ADC or other */
byte gpio[Max_Sensors][Max_Sensors_GPIO];
float offset[Max_Sensors][Max_Sensors_Read];
unsigned int low[Max_Sensors][Max_Sensors_Read];
@ -327,8 +331,18 @@ struct Config_Grow_Water {
};
struct Config_Grow_Dashboard {
bool configured[Max_Sensors][Max_Sensors_Read];
byte sensor[Max_Sensors][Max_Sensors_Read];
bool gaugeConfigured[Max_Dashboard_Gauge];
byte gaugeSensor[Max_Dashboard_Gauge];
byte gaugeRead[Max_Dashboard_Gauge];
byte gaugeMin[Max_Dashboard_Gauge];
byte gaugeMax[Max_Dashboard_Gauge];
bool chartConfigured[Max_Dashboard_Chart];
byte chartSensor[Max_Dashboard_Chart];
byte chartRead[Max_Dashboard_Chart];
bool dataConfigured[Max_Sensors];
byte dataSensor[Max_Sensors];
byte dataRead[Max_Sensors];
};
struct Config_Grow {

View file

@ -133,6 +133,62 @@ byte Give_Free_SensorId() {
return sensorId_free;
}
byte Give_Free_Dashboard_GaugeId() {
const static char LogLoc[] PROGMEM = "[Core:Give_Free_Dashboard_GaugeId]";
byte gaugeId_free;
for(byte i=0; i < Max_Dashboard_Gauge; i++) {
if(config.grow.dashboard.gaugeConfigured[i] > 0) {
// here i define that 255 stands for "no more free outputs"
gaugeId_free = 255;
} else {
gaugeId_free = i;
break;
}
}
#ifdef DEBUG
Log.verbose(F("%s next free gauge id: %d" CR), LogLoc, gaugeId_free);
#endif
return gaugeId_free;
}
byte Give_Free_Dashboard_ChartId() {
const static char LogLoc[] PROGMEM = "[Core:Give_Free_Dashboard_ChartId]";
byte chartId_free;
for(byte i=0; i < Max_Dashboard_Chart; i++) {
if(config.grow.dashboard.chartConfigured[i] > 0) {
// here i define that 255 stands for "no more free outputs"
chartId_free = 255;
} else {
chartId_free = i;
break;
}
}
#ifdef DEBUG
Log.verbose(F("%s next free chart id: %d" CR), LogLoc, chartId_free);
#endif
return chartId_free;
}
byte Give_Free_Dashboard_DataId() {
const static char LogLoc[] PROGMEM = "[Core:Give_Free_Dashboard_DataId]";
byte dataId_free;
for(byte i=0; i < Max_Sensors; i++) {
if(config.grow.dashboard.dataConfigured[i] > 0) {
// here i define that 255 stands for "no more free outputs"
dataId_free = 255;
} else {
dataId_free = i;
break;
}
}
#ifdef DEBUG
Log.verbose(F("%s next free chart id: %d" CR), LogLoc, dataId_free);
#endif
return dataId_free;
}
// checks if GPIO is already in use by output or sensor

View file

@ -412,6 +412,60 @@ bool LoadConfig() {
}
}
/*
*
* byte gaugeSensor[Max_Dashboard_Gauge];
byte gaugeRead[Max_Dashboard_Gauge];
byte chartSensor[Max_Dashboard_Chart];
byte chartRead[Max_Dashboard_Chart];
byte dataSensor[Max_Sensors];
byte dataRead[Max_Sensors];
*
*/
/* Grow Dashboard */
JsonObject objDashboard = objGrow["dashboard"][0];
/* iterate through gauges */
for(byte i = 0; i < Max_Dashboard_Gauge; i++) {
if(objDashboard.containsKey("gaugeConfigured"))
config.grow.dashboard.gaugeConfigured[i] = objDashboard["gaugeConfigured"][i];
if(config.grow.dashboard.gaugeConfigured[i] == true) {
if(objDashboard.containsKey("gaugeSensor"))
config.grow.dashboard.gaugeSensor[i] = objDashboard["gaugeSensor"][i];
if(objDashboard.containsKey("gaugeRead"))
config.grow.dashboard.gaugeRead[i] = objDashboard["gaugeRead"][i];
if(objDashboard.containsKey("gaugeMin"))
config.grow.dashboard.gaugeMin[i] = objDashboard["gaugeMin"][i];
if(objDashboard.containsKey("gaugeMax"))
config.grow.dashboard.gaugeMax[i] = objDashboard["gaugeMax"][i];
}
}
/* iterate through charts */
for(byte i = 0; i < Max_Dashboard_Chart; i++) {
if(objDashboard.containsKey("chartConfigured"))
config.grow.dashboard.chartConfigured[i] = objDashboard["chartConfigured"][i];
if(config.grow.dashboard.chartConfigured[i] == true) {
if(objDashboard.containsKey("chartSensor"))
config.grow.dashboard.chartSensor[i] = objDashboard["chartSensor"][i];
if(objDashboard.containsKey("chartRead"))
config.grow.dashboard.chartRead[i] = objDashboard["chartRead"][i];
}
}
/* iterate through data */
for(byte i = 0; i < Max_Sensors; i++) {
if(objDashboard.containsKey("dataConfigured"))
config.grow.dashboard.dataConfigured[i] = objDashboard["dataConfigured"][i];
if(config.grow.dashboard.dataConfigured[i] == true) {
if(objDashboard.containsKey("dataSensor"))
config.grow.dashboard.dataSensor[i] = objDashboard["dataSensor"][i];
if(objDashboard.containsKey("dataRead"))
config.grow.dashboard.dataRead[i] = objDashboard["dataRead"][i];
}
}
// Close the file (Curiously, File's destructor doesn't close the file)
file.close();
Log.notice(F("%s config successfully loaded" CR), LogLoc);
@ -589,6 +643,38 @@ bool SaveConfig(bool writeToSerial = false) {
}
}
/*
* Grow Dashboard
*/
JsonObject objDashboard = objGrow["dashboard"].add<JsonObject>();
/* iterate through gauge */
for(byte i = 0; i < Max_Dashboard_Gauge; i++) {
if(config.grow.dashboard.gaugeConfigured[i] == true) {
objDashboard["gaugeConfigured"][i] = config.grow.dashboard.gaugeConfigured[i];
objDashboard["gaugeSensor"][i] = config.grow.dashboard.gaugeSensor[i];
objDashboard["gaugeRead"][i] = config.grow.dashboard.gaugeRead[i];
objDashboard["gaugeMin"][i] = config.grow.dashboard.gaugeMin[i];
objDashboard["gaugeMax"][i] = config.grow.dashboard.gaugeMax[i];
}
}
/* iterate through chart */
for(byte i = 0; i < Max_Dashboard_Chart; i++) {
if(config.grow.dashboard.chartConfigured[i] == true) {
objDashboard["chartConfigured"][i] = config.grow.dashboard.chartConfigured[i];
objDashboard["chartSensor"][i] = config.grow.dashboard.chartSensor[i];
objDashboard["chartRead"][i] = config.grow.dashboard.chartRead[i];
}
}
/* iterate through data */
for(byte i = 0; i < Max_Sensors; i++) {
if(config.grow.dashboard.dataConfigured[i] == true) {
objDashboard["dataConfigured"][i] = config.grow.dashboard.dataConfigured[i];
objDashboard["dataSensor"][i] = config.grow.dashboard.dataSensor[i];
objDashboard["dataRead"][i] = config.grow.dashboard.dataRead[i];
}
}
/*
* END Building config.json here

View file

@ -83,8 +83,6 @@
/*
* Sensor Todo list:
*
* - CCS811 CO2 sensor, will have type SENSOR_TYPE_I2C_WITH_GPIO, it needs signal on pin WAK
* cheap - ~ 8 on Aliexpress
* - HX711 for weight sensor, this sensor needs two GPIOs for communication
* - SCD30/40 CO2 sensor, expensive, >70
*/

View file

@ -89,6 +89,8 @@ void Webserver_Init() {
webserver.on("/grow/water/", HTTP_POST, WebPage_grow_water);
webserver.on("/grow/dashboard/", HTTP_GET, WebPage_grow_dashboard);
webserver.on("/grow/dashboard/", HTTP_POST, WebPage_grow_dashboard);
webserver.on("/grow/dashboard/gaugeAdd", HTTP_GET, WebPage_grow_dashboard_gaugeAdd);
webserver.on("/grow/dashboard/gaugeAdd", HTTP_POST, WebPage_grow_dashboard_gaugeAdd);
/* api */
//webserver.on("/api/sensor", HTTP_GET, Api_sensor_data);
webserver.on("/api/sensor/", HTTP_GET, Api_sensor_data);

View file

@ -749,7 +749,7 @@ String Proc_WebPage_grow_water(const String& var) {
/* controledBy */
html += F("<u>Controled by</u><br><select name='controlBy' id='ctrl");
html += i;
html += F("'onChange=\"GrowSelectControlSensorRead('ctrl");
html += F("' onChange=\"GrowSelectControlSensorRead('ctrl");
html += i;
html += F("', 'controlSensor");
html += i;
@ -957,7 +957,36 @@ String Proc_WebPage_grow_dashboard(const String& var) {
return AddHeaderFooter(var, 1);
} else if(Test_WebPage_grow_SUBNAV(var)) {
return Proc_WebPage_grow_SUBNAV(var, WEB_GROW_SUBNAV_DASHBOARD);
} else if(var == "DASHBOARD") {
} else if(var == "TR_TD_GAUGE") {
String html;
for(byte i = 0; i < Max_Dashboard_Gauge; i++) {
if(config.grow.dashboard.gaugeConfigured[i] == true) {
html += F("<tr><td>");
html += i;
html += F("</td><td>");
html += config.system.sensor.name[config.grow.dashboard.gaugeSensor[i]];
html += F("</td><td>");
html += FPSTR(Sensor_Read_descr[config.grow.dashboard.gaugeRead[i]]);
html += F("</td><td>");
// delete button
html += F("<form class='linkForm' action='/grow/dashboard/' method='post'>");
html += F("<input type='hidden' name='delete_gauge' value='");
html += i;
html += F("'>");
html += F("<input type='submit' value='&#x274C;' onclick=\"return confirmDelete('");
html += F("gauge for");
html += config.system.sensor.name[config.grow.dashboard.gaugeSensor[i]];
html += F(" ");
html += FPSTR(Sensor_Read_descr[config.grow.dashboard.gaugeRead[i]]);
html += F("')\" title='Delete'></form>");
html += F("</td></tr>");
}
}
return html;
} else if(var == "TR_TD_CHART") {
String html;
return html;
} else if(var == "TR_TD_DATA") {
String html;
return html;
} else {
@ -988,9 +1017,112 @@ void WebPage_grow_dashboard(AsyncWebServerRequest *request) {
if(request->method() == HTTP_POST) {
SaveConfig();
if(request->hasParam("delete_gauge", true)) {
byte gaugeId;
Log.notice(F("%s config saved" CR), LogLoc);
const AsyncWebParameter* param = request->getParam("delete_gauge", true);
gaugeId = param->value().toInt();
// we ensure that every field is empty
config.grow.dashboard.gaugeConfigured[gaugeId] = false;
config.grow.dashboard.gaugeSensor[gaugeId] = 0;
config.grow.dashboard.gaugeRead[gaugeId] = 0;
SaveConfig();
Log.notice(F("%s config saved" CR), LogLoc);
}
//request->send_P(200, TEXT_HTML, Page_grow_dashboard_HTML, Proc_WebPage_grow_dashboard_POST);
//SaveConfig();
request->send_P(200, TEXT_HTML, Page_grow_dashboard_HTML, Proc_WebPage_grow_dashboard_POST);
} else {
request->send_P(200, TEXT_HTML, Page_grow_dashboard_HTML, Proc_WebPage_grow_dashboard);
}
}
/*******************************************************************************
* grow dashboards gaugeAdd page
*/
String Proc_WebPage_grow_dashboard_gaugeAdd(const String& var) {
const static char LogLoc[] PROGMEM = "[Webserver:grow:dashboard:gaugeAdd(Proc)]";
if(TestHeaderFooter(var)) {
return AddHeaderFooter(var, 1);
} else if(Test_WebPage_grow_SUBNAV(var)) {
return Proc_WebPage_grow_SUBNAV(var, WEB_GROW_SUBNAV_DASHBOARD);
} else if(var == "ACTION") {
return FPSTR(Common_HTML_ACTION_EDIT);
} else if(var == "GAUGE_ID") {
return String(Give_Free_Dashboard_GaugeId());
} else if(var == "SENSOR_ID") {
String html;
return html;
} else if(var == "READ_ID") {
String html;
return html;
} else {
return String();
}
}
String Proc_WebPage_grow_dashboard_gaugeAdd_POST(const String& var) {
/* This is the processor for POST
* Its exactly the same, just looking for SAVE_MSG string.
* If nothing matches, it calles the main Proc_WebPage_grow()
* processor function, so all the other stuff like header and so
* on get replaced
*/
if(var == "SAVE_MSG") {
return String(Common_HTML_SAVE_MSG);
} else {
return Proc_WebPage_grow_dashboard(var);
}
}
/* WebPage function */
void WebPage_grow_dashboard_gaugeAdd(AsyncWebServerRequest *request) {
const static char LogLoc[] PROGMEM = "[Webserver:grow:dashboard]";
/* Which kind of Request */
if(request->method() == HTTP_POST) {
if(request->hasParam("delete_gauge", true)) {
byte gaugeId;
const AsyncWebParameter* param = request->getParam("delete_gauge", true);
gaugeId = param->value().toInt();
// we ensure that every field is empty
config.grow.dashboard.gaugeConfigured[gaugeId] = false;
config.grow.dashboard.gaugeSensor[gaugeId] = 0;
config.grow.dashboard.gaugeRead[gaugeId] = 0;
SaveConfig();
Log.notice(F("%s config saved" CR), LogLoc);
}
//request->send_P(200, TEXT_HTML, Page_grow_dashboard_HTML, Proc_WebPage_grow_dashboard_POST);
//SaveConfig();
request->send_P(200, TEXT_HTML, Page_grow_dashboard_HTML, Proc_WebPage_grow_dashboard_POST);

View file

@ -91,12 +91,70 @@ const char Page_grow_dashboard_HTML[] PROGMEM = R"(%HEADER%
%SAVE_MSG%
<p>here you can set dashboard stuff<br></p>
<h3>Gauge</h3>
<a class='button %ADD_DISABLED%' href='/grow/dashboard/gaugeAdd'>&#10133; Add gauge</a>
<table class='centered'>
<tr>
<th>ID</th>
<th>Name</th>
<th>Read</th>
<th>&nbsp;</th>
</tr>
%TR_TD_GAUGE%
</table>
<hr>
%DASHBOARD%
<h3>Chart</h3>
<a class='button %ADD_DISABLED%' href='/system/sensor/add'>&#10133; Add chart</a>
<table class='centered'>
<tr>
<th>&nbsp;</th>
<th>ID</th>
<th>Name</th>
<th>Type</th>
</tr>
%TR_TD_CHART%
</table>
<hr>
<h3>Data</h3>
<a class='button %ADD_DISABLED%' href='/system/sensor/add'>&#10133; Add data</a>
<table class='centered'>
<tr>
<th>&nbsp;</th>
<th>ID</th>
<th>Name</th>
<th>Type</th>
</tr>
%TR_TD_DATA%
</table>
%FOOTER% )";
/* /grow/dashboard/gaugeAdd page */
const char Page_grow_dashboard_gaugeAdd_HTML[] PROGMEM = R"(%HEADER%
%SUBNAV%
%SAVE_MSG%
<h3>%ACTION% Gauge Id %GAUGE_ID%</h3>
<p>Select which Sensor Reading to use for the gauge<br></p>
<form method='post' action='/grow/dashboard/gaugeAdd'>
<input type='hidden' name='gaugeId' value='%GAUGE_ID%' />
<div>
<u>Sensor Reading</u>:<br>
<input type='hidden' name='gaugeSensor' value='%SENSOR_ID%' id='gaugeSensor' />
<input type='hidden' name='gaugeRead' value='%READ_ID%' id='gaugeRead' />
<select name='controlBy' id='selSensor' onChange="GrowSelectControlSensorRead('selSensor', 'gaugeSensor', 'gaugeRead');">
<option value='255:255' selected >---</option>
%SELECT_SENSOR_READ%
</select>
</div>
%FOOTER% )";

View file

@ -651,7 +651,7 @@ String Proc_WebPage_system_output_add(const String& var) {
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_OUTPUT);
} else if(var == "ACTION") {
return F("&#10133; Add");
return FPSTR(Common_HTML_ACTION_ADD);
} else if(var == "OUTPUT_ID") {
// we check which id is free. A free ID as type == 0
@ -692,7 +692,7 @@ String Proc_WebPage_system_output_addEdit(const String& var) {
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_OUTPUT);
} else if(var == "ACTION") {
return F("&#x270F;&#xFE0F; Edit");
return FPSTR(Common_HTML_ACTION_EDIT);
} else if(var == "EDIT_MODE") {
return F("editmode");
@ -1098,7 +1098,7 @@ String Proc_WebPage_system_sensor(const String& var) {
html += i;
html += F("'>");
html += F("<input type='submit' value='&#x274C;' onclick=\"return confirmDelete('");
html += config.system.sensor.name[i];;
html += config.system.sensor.name[i];
html += F("')\" title='Delete'></form>");
html += F("</td></tr>");
@ -1226,7 +1226,7 @@ String Proc_WebPage_system_sensor_add(const String& var) {
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_SENSOR);
} else if(var == "ACTION") {
return F("&#10133; Add");
return FPSTR(Common_HTML_ACTION_ADD);
} else if(var == "SENSOR_ID") {
// we check which id is free. A free ID as type == 0
@ -1262,7 +1262,7 @@ String Proc_WebPage_system_sensor_addEdit(const String& var) {
} else if(Test_WebPage_system_SUBNAV(var)) {
return Proc_WebPage_system_SUBNAV(var, WEB_SYSTEM_SUBNAV_SENSOR);
} else if(var == "ACTION") {
return F("&#x270F;&#xFE0F; Edit");
return FPSTR(Common_HTML_ACTION_EDIT);
} else if(var == "SENSOR_ID") {
// return the sensorId we got from GET .../add?edit=ID

View file

@ -24,3 +24,6 @@ const char Common_HTML_NEED_RESTART[] PROGMEM = R"EOF(
</form>
</div></div>
)EOF";
const char Common_HTML_ACTION_ADD[] PROGMEM = {"&#10133; Add"};
const char Common_HTML_ACTION_EDIT[] PROGMEM = {"&#x270F;&#xFE0F; Edit"};