/* ; Project: Open Vehicle Monitor System ; Date: 14th March 2017 ; ; Changes: ; 1.0 Initial release ; ; (C) 2018 Michael Balzer ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. */ #include "ovms_log.h" // static const char *TAG = "webserver"; #include #include #include #include #include #include #include "ovms_webserver.h" #include "ovms_config.h" #include "ovms_metrics.h" #include "metrics_standard.h" #include "vehicle.h" #include "ovms_housekeeping.h" #include "ovms_peripherals.h" #define _attr(text) (c.encode_html(text).c_str()) #define _html(text) (c.encode_html(text).c_str()) /** * HandleDashboard: */ void OvmsWebServer::HandleDashboard(PageEntry_t& p, PageContext_t& c) { OvmsVehicle *vehicle = MyVehicleFactory.ActiveVehicle(); if (!vehicle) { c.head(400); c.alert("danger", "

Error: no active vehicle!

"); c.done(); return; } // get dashboard configuration: DashboardConfig cfg; vehicle->GetDashboardConfig(cfg); // output dashboard: const char* content = "" "" "
" "
Dashboard
" "
" "
" "
" "
" "
▼0 ▲0km
" "
▲0.0 ▼0.0kWh
" "
" "
" "
0
" "
0
" "
0
" "
0
" "
" "
" "
" "
" "
" "
" "" ""; c.head(200); PAGE_HOOK("body.pre"); c.printf( "" , cfg.gaugeset1.c_str()); new HttpDataSender(c.nc, (const uint8_t*)content, strlen(content)); } /** * HandleBmsCellMonitor: display cell voltages & temperatures * * Note: this is not enabled by default, as some vehicles do not provide BMS data. * To enable, include this in the vehicle init: * MyWebServer.RegisterPage("/bms/cellmon", "BMS cell monitor", OvmsWebServer::HandleBmsCellMonitor, PageMenu_Vehicle, PageAuth_Cookie); * You can change the URL path, title, menu association and authentication as you like. * For a clean shutdown, add * MyWebServer.DeregisterPage("/bms/cellmon"); * …in your vehicle cleanup. */ void OvmsWebServer::HandleBmsCellMonitor(PageEntry_t& p, PageContext_t& c) { float stemwidth_v = 0.5, stemwidth_t = 0.5; float volt_warn_def = BMS_DEFTHR_VWARN, volt_alert_def = BMS_DEFTHR_VALERT, volt_maxgrad_def = BMS_DEFTHR_VMAXGRAD, volt_maxsddev_def = BMS_DEFTHR_VMAXSDDEV, temp_warn_def = BMS_DEFTHR_TWARN, temp_alert_def = BMS_DEFTHR_TALERT; float volt_warn = 0, volt_alert = 0, volt_maxgrad = 0, volt_maxsddev = 0, temp_warn = 0, temp_alert = 0; bool alerts_enabled = true; // get vehicle BMS configuration: OvmsVehicle* vehicle = MyVehicleFactory.ActiveVehicle(); if (vehicle) { int readings_v = vehicle->BmsGetCellArangementVoltage(); if (readings_v) { stemwidth_v = 0.1 + 20.0 / readings_v; // 14 → 1.5 … 96 → 0.3 } int readings_t = vehicle->BmsGetCellArangementTemperature(); if (readings_t) { stemwidth_t = 0.1 + 10.0 / readings_t; // 7 → 1.5 … 96 → 0.4 } vehicle->BmsGetCellDefaultThresholdsVoltage(&volt_warn_def, &volt_alert_def, &volt_maxgrad_def, &volt_maxsddev_def); vehicle->BmsGetCellDefaultThresholdsTemperature(&temp_warn_def, &temp_alert_def); } if (c.method == "POST") { // process form submission: volt_warn = atof(c.getvar("volt_warn").c_str()) / 1000; if (volt_warn > 0) MyConfig.SetParamValueFloat("vehicle", "bms.dev.voltage.warn", volt_warn); else MyConfig.SetParamValue("vehicle", "bms.dev.voltage.warn", ""); volt_alert = atof(c.getvar("volt_alert").c_str()) / 1000; if (volt_alert > 0) MyConfig.SetParamValueFloat("vehicle", "bms.dev.voltage.alert", volt_alert); else MyConfig.SetParamValue("vehicle", "bms.dev.voltage.alert", ""); volt_maxgrad = atof(c.getvar("volt_maxgrad").c_str()) / 1000; if (volt_maxgrad > 0) MyConfig.SetParamValueFloat("vehicle", "bms.dev.voltage.maxgrad", volt_maxgrad); else MyConfig.SetParamValue("vehicle", "bms.dev.voltage.maxgrad", ""); volt_maxsddev = atof(c.getvar("volt_maxsddev").c_str()) / 1000; if (volt_maxsddev > 0) MyConfig.SetParamValueFloat("vehicle", "bms.dev.voltage.maxsddev", volt_maxsddev); else MyConfig.SetParamValue("vehicle", "bms.dev.voltage.maxsddev", ""); temp_warn = atof(c.getvar("temp_warn").c_str()); if (temp_warn > 0) MyConfig.SetParamValueFloat("vehicle", "bms.dev.temp.warn", temp_warn); else MyConfig.SetParamValue("vehicle", "bms.dev.temp.warn", ""); temp_alert = atof(c.getvar("temp_alert").c_str()); if (temp_alert > 0) MyConfig.SetParamValueFloat("vehicle", "bms.dev.temp.alert", temp_alert); else MyConfig.SetParamValue("vehicle", "bms.dev.temp.alert", ""); MyConfig.SetParamValueBool("vehicle", "bms.alerts.enabled", c.getvar("alerts_enabled") == "yes"); if (c.getvar("action") == "save-reset" && vehicle) vehicle->BmsResetCellStats(); c.head(200); c.alert("success", "

Saved.

"); c.print(""); c.done(); return; } // read config: volt_warn = MyConfig.GetParamValueFloat("vehicle", "bms.dev.voltage.warn"); volt_alert = MyConfig.GetParamValueFloat("vehicle", "bms.dev.voltage.alert"); volt_maxgrad = MyConfig.GetParamValueFloat("vehicle", "bms.dev.voltage.maxgrad"); volt_maxsddev = MyConfig.GetParamValueFloat("vehicle", "bms.dev.voltage.maxsddev"); temp_warn = MyConfig.GetParamValueFloat("vehicle", "bms.dev.temp.warn"); temp_alert = MyConfig.GetParamValueFloat("vehicle", "bms.dev.temp.alert"); alerts_enabled = MyConfig.GetParamValueBool("vehicle", "bms.alerts.enabled", true); c.head(200); PAGE_HOOK("body.pre"); c.print( "
\n" "
BMS Cell Monitor
\n" "
\n" "
\n" "
\n" "
\n" "
\n" "
\n" "
\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "
Voltage\n" "
Cell avgV
\n" "
Cell minV
\n" "
Cell maxV
\n" "
StdDevmV
\n" "
StdDev maxmV
\n" "
GradientmV
\n" "
Temperature\n" "
Cell avg°C
\n" "
Cell min°C
\n" "
Cell max°C
\n" "
StdDev°C
\n" "
StdDev max°C
\n" "
\n" "\n" "\n" "\n" "
\n" "
\n" "\n" "
\n" "
\n" "
\n" "
\n" "\n" "

Alert configuration

\n" "
\n" "
\n"); // Alert configuration form: std::ostringstream sval, sdef; sval << std::fixed; sdef << std::fixed; c.form_start(p.uri, "#cfg-result"); c.input_info("Thresholds", "

Deviation warnings and alerts are triggered by cell deviation from the current average.

" "

You may need to change the thresholds to adapt to the quality and age of your battery.

"); sval << std::setprecision(0); sdef << std::setprecision(0); sval.str(""); if (volt_warn > 0) sval << volt_warn * 1000; sdef.str(""); sdef << "Default: " << volt_warn_def * 1000; c.input("number", "Voltage warning", "volt_warn", sval.str().c_str(), sdef.str().c_str(), NULL, "min=\"1\" step=\"1\"", "mV"); sval.str(""); if (volt_alert > 0) sval << volt_alert * 1000; sdef.str(""); sdef << "Default: " << volt_alert_def * 1000; c.input("number", "Voltage alert", "volt_alert", sval.str().c_str(), sdef.str().c_str(), NULL, "min=\"1\" step=\"1\"", "mV"); sval << std::setprecision(1); sdef << std::setprecision(1); sval.str(""); if (volt_maxgrad > 0) sval << volt_maxgrad * 1000; sdef.str(""); sdef << "Default: " << volt_maxgrad_def * 1000; c.input("number", "Max valid gradient", "volt_maxgrad", sval.str().c_str(), sdef.str().c_str(), NULL, "min=\"0\" step=\"0.1\"", "mV"); sval.str(""); if (volt_maxsddev > 0) sval << volt_maxsddev * 1000; sdef.str(""); sdef << "Default: " << volt_maxsddev_def * 1000; c.input("number", "Max stddev deviation", "volt_maxsddev", sval.str().c_str(), sdef.str().c_str(), NULL, "min=\"0\" step=\"0.1\"", "mV"); sval << std::setprecision(1); sdef << std::setprecision(1); sval.str(""); if (temp_warn > 0) sval << temp_warn; sdef.str(""); sdef << "Default: " << temp_warn_def; c.input("number", "Temperature warning", "temp_warn", sval.str().c_str(), sdef.str().c_str(), NULL, "min=\"0.1\" step=\"0.1\"", "°C"); sval.str(""); if (temp_alert > 0) sval << temp_alert; sdef.str(""); sdef << "Default: " << temp_alert_def; c.input("number", "Temperature alert", "temp_alert", sval.str().c_str(), sdef.str().c_str(), NULL, "min=\"0.1\" step=\"0.1\"", "°C"); c.input_checkbox("Enable alert notifications", "alerts_enabled", alerts_enabled); c.print( "
" "
" " " " " "
" "
" "
" "
" "
" "
" "
"); c.form_end(); c.print( "
\n" "
\n" "\n" "
\n" "
\n" "
\n" "
\n" "\n" "\n" "\n" , stemwidth_v, stemwidth_t); c.print( "\n"); PAGE_HOOK("body.post"); c.done(); } /** * HandleCfgBrakelight: configure vehicle brake light control * * Note: this is not enabled by default, as most vehicles do not need this. * To enable, include this in the vehicle init: * MyWebServer.RegisterPage("/cfg/brakelight", "Brake light control", OvmsWebServer::HandleCfgBrakelight, PageMenu_Vehicle, PageAuth_Cookie); * You can change the URL path, title, menu association and authentication as you like. * For a clean shutdown, add * MyWebServer.DeregisterPage("/cfg/brakelight"); * …in your vehicle cleanup. */ void OvmsWebServer::HandleCfgBrakelight(PageEntry_t& p, PageContext_t& c) { std::string error, info; bool enable, ignftbrk; std::string smooth_acc, smooth_bat, port, level_on, level_off, basepwr; if (c.method == "POST") { // process form submission: enable = (c.getvar("enable") == "yes"); smooth_acc = c.getvar("smooth_acc"); smooth_bat = c.getvar("smooth_bat"); port = c.getvar("port"); level_on = c.getvar("level_on"); level_off = c.getvar("level_off"); ignftbrk = (c.getvar("ignftbrk") == "yes"); basepwr = c.getvar("basepwr"); // validate: if (smooth_acc != "") { int v = atof(smooth_acc.c_str()); if (v < 0) { error += "
  • Acceleration smoothing must be greater or equal 0.
  • "; } } if (smooth_bat != "") { int v = atof(smooth_bat.c_str()); if (v < 0) { error += "
  • Battery power smoothing must be greater or equal 0.
  • "; } } if (port != "") { int v = atoi(port.c_str()); if (v == 2 || v < 1 || v > 9) { error += "
  • Port must be one of 1, 3…9
  • "; } } if (level_on != "") { int v = atof(level_on.c_str()); if (v < 0) { error += "
  • Activation level must be greater or equal 0.
  • "; } } if (level_off != "") { int v = atof(level_off.c_str()); if (v < 0) { error += "
  • Deactivation level must be greater or equal 0.
  • "; } } if (basepwr != "") { int v = atof(basepwr.c_str()); if (v < 0) { error += "
  • Base power level must be greater or equal 0.
  • "; } } if (error == "") { // success: MyConfig.SetParamValue("vehicle", "accel.smoothing", smooth_acc); MyConfig.SetParamValue("vehicle", "batpwr.smoothing", smooth_bat); MyConfig.SetParamValueBool("vehicle", "brakelight.enable", enable); MyConfig.SetParamValue("vehicle", "brakelight.port", port); MyConfig.SetParamValue("vehicle", "brakelight.on", level_on); MyConfig.SetParamValue("vehicle", "brakelight.off", level_off); MyConfig.SetParamValueBool("vehicle", "brakelight.ignftbrk", ignftbrk); MyConfig.SetParamValue("vehicle", "brakelight.basepwr", basepwr); info = "

    Success!

      " + info + "
    "; c.head(200); c.alert("success", info.c_str()); OutputHome(p, c); c.done(); return; } // output error, return to form: error = "

    Error!

      " + error + "
    "; c.head(400); c.alert("danger", error.c_str()); } else { // read configuration: smooth_acc = MyConfig.GetParamValue("vehicle", "accel.smoothing"); smooth_bat = MyConfig.GetParamValue("vehicle", "batpwr.smoothing"); enable = MyConfig.GetParamValueBool("vehicle", "brakelight.enable", false); port = MyConfig.GetParamValue("vehicle", "brakelight.port", "1"); level_on = MyConfig.GetParamValue("vehicle", "brakelight.on"); level_off = MyConfig.GetParamValue("vehicle", "brakelight.off"); ignftbrk = MyConfig.GetParamValueBool("vehicle", "brakelight.ignftbrk", false); basepwr = MyConfig.GetParamValue("vehicle", "brakelight.basepwr"); c.head(200); } // generate form: c.panel_start("primary", "Regen Brake Light"); c.form_start(p.uri); c.input_slider("Acceleration smoothing", "smooth_acc", 3, NULL, -1, smooth_acc.empty() ? 2.0 : atof(smooth_acc.c_str()), 2.0, 0.0, 10.0, 0.1, "

    Speed/acceleration smoothing eliminates road bump and gear box backlash noise.

    " "

    Lower value = higher sensitivity. Set to zero if your vehicle data is already smoothed.

    "); c.input_slider("Battery power smoothing", "smooth_bat", 3, NULL, -1, smooth_bat.empty() ? 2.0 : atof(smooth_bat.c_str()), 2.0, 0.0, 10.0, 0.1, "

    Battery power smoothing eliminates road bump and gear box backlash noise.

    " "

    Lower value = higher sensitivity. Set to zero if your vehicle data is already smoothed.

    "); c.print("
    "); c.input_radiobtn_start("Regen brake signal", "enable"); c.input_radiobtn_option("enable", "OFF", "no", !enable); c.input_radiobtn_option("enable", "ON", "yes", enable); c.input_radiobtn_end(); c.input_select_start("… control port", "port"); c.input_select_option("SW_12V (DA26 pin 18)", "1", port == "1"); c.input_select_option("EGPIO_2", "3", port == "3"); c.input_select_option("EGPIO_3", "4", port == "4"); c.input_select_option("EGPIO_4", "5", port == "5"); c.input_select_option("EGPIO_5", "6", port == "6"); c.input_select_option("EGPIO_6", "7", port == "7"); c.input_select_option("EGPIO_7", "8", port == "8"); c.input_select_option("EGPIO_8", "9", port == "9"); c.input_select_end(); c.input_slider("… activation level", "level_on", 3, "m/s²", -1, level_on.empty() ? 1.3 : atof(level_on.c_str()), 1.3, 0.0, 3.0, 0.1, "

    Deceleration threshold to activate regen brake light.

    " "

    Under UN regulation 13H, brake light illumination is required for decelerations >1.3 m/s².

    "); c.input_slider("… deactivation level", "level_off", 3, "m/s²", -1, level_off.empty() ? 0.7 : atof(level_off.c_str()), 0.7, 0.0, 3.0, 0.1, "

    Deceleration threshold to deactivate regen brake light.

    " "

    Under UN regulation 13H, brake lights must not be illuminated for decelerations ≤0.7 m/s².

    "); c.input_slider("… base power range", "basepwr", 3, "kW", -1, basepwr.empty() ? 0 : atof(basepwr.c_str()), 0, 0.0, 5.0, 0.1, "

    Battery power range around zero (+/-) ignored for the regen signal. Raise to desensitize the signal" " for minor power levels (if the vehicle supports the battery power metric).

    "); c.input_checkbox("Ignore foot brake", "ignftbrk", ignftbrk, "

    Create regen signal independent of the foot brake status.

    "); c.print("
    "); c.input_button("default", "Save"); c.form_end(); c.panel_end( "

    The OVMS generated regenerative braking signal needs a hardware modification to drive your" " vehicle brake lights. See your OVMS vehicle manual section for details on how to do this.

    " "

    The regen brake signal is activated when the deceleration level exceeds the configured threshold," " the speed is above 1 m/s (3.6 kph / 2.2 mph) and the battery power is negative (charging).

    " "

    The signal is deactivated when deceleration drops below the deactivation level, speed drops" " below 1 m/s or battery power indicates discharging." " The signal will be held activated for at least 500 ms.

    "); c.done(); }