Skip to content

Commit

Permalink
Vehicle: add automatic module shutdown/reboot based on 12V battery vo…
Browse files Browse the repository at this point in the history
…ltage level

  New configs:
    [vehicle] 12v.shutdown            -- Shutdown voltage level (default: disabled)
    [vehicle] 12v.wakeup              -- Reboot voltage level after shutdown (default: any)
    [vehicle] 12v.wakeup_interval     -- Reboot test interval in seconds (default: 60)

  New events:
    vehicle.alert.12v.shutdown        -- 12V shutdown threshold reached, entering deep sleep
  • Loading branch information
dexterbg committed Sep 14, 2023
1 parent 8671d03 commit fbb768b
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 19 deletions.
1 change: 1 addition & 0 deletions docs/source/userguide/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ vehicle.alarm.off Vehicle alarm has been disarmed
vehicle.alarm.on Vehicle alarm has been armed
vehicle.alert.12v.off 12V system voltage has recovered
vehicle.alert.12v.on 12V system voltage is below alert threshold
vehicle.alert.12v.shutdown 12V shutdown threshold reached, entering deep sleep
vehicle.alert.bms BMS cell/pack volts/temps exceeded thresholds
vehicle.asleep Vehicle systems are asleep
vehicle.awake Vehicle systems are awake
Expand Down
4 changes: 4 additions & 0 deletions docs/source/userguide/warnings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ so you can easily turn off the module when not needed.
If you have fixed vehicle usage/parking times, you can use the scripting system to schedule
regular sleep periods (see command ``module sleep``).

Beginning with firmware release 3.3.004, the 12V monitoring can be configured to enable
deep sleep based on a low 12V voltage level. On most vehicles, this can be used to
automatically shut down the module when parking and reboot when driving or charging.


~~~~~~~~~~~~~~~~~~
Module Version 3.3
Expand Down
8 changes: 8 additions & 0 deletions vehicle/OVMS.V3/changes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ Open Vehicle Monitor System v3 - Change log
obd2ecu.stop -- Called before the OBD2ECU process is stopped.
- Web UI: Add configuration for Valet and Flatbed geofence to the Locations config page.
- Network: New 'network ping' command to ping (ICMP) hostname or IP address. (ESP-IDFv4+ only / needs to be enabled in menuconfig - Developer Options)
- Vehicle: add automatic module shutdown/reboot based on 12V battery voltage level
New configs:
[vehicle] 12v.shutdown -- Shutdown voltage level (default: disabled)
[vehicle] 12v.wakeup -- Reboot minimum voltage level after shutdown (default: any)
[vehicle] 12v.wakeup_interval -- Reboot test interval in seconds (default: 60)
New events:
vehicle.alert.12v.shutdown -- 12V shutdown threshold reached, entering deep sleep


2022-09-01 MWJ 3.3.003 OTA release
- Toyota RAV4 EV: Initial support added. Only the Tesla bus is decoded and just listening so far.
Expand Down
28 changes: 27 additions & 1 deletion vehicle/OVMS.V3/components/ovms_webserver/src/web_cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
{
std::string error, info;
std::string vehicleid, vehicletype, vehiclename, timezone, timezone_region, pin;
std::string bat12v_factor, bat12v_ref, bat12v_alert;
std::string bat12v_factor, bat12v_ref, bat12v_alert, bat12v_shutdown, bat12v_wakeup, bat12v_wakeup_interval;

std::map<metric_group_t,std::string> units_values;
metric_group_list_t unit_groups;
Expand All @@ -629,6 +629,9 @@ void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
bat12v_factor = c.getvar("bat12v_factor");
bat12v_ref = c.getvar("bat12v_ref");
bat12v_alert = c.getvar("bat12v_alert");
bat12v_shutdown = c.getvar("bat12v_shutdown");
bat12v_wakeup = c.getvar("bat12v_wakeup");
bat12v_wakeup_interval = c.getvar("bat12v_wakeup_interval");
pin = c.getvar("pin");

if (vehicleid.length() == 0)
Expand Down Expand Up @@ -660,6 +663,9 @@ void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
MyConfig.SetParamValue("system.adc", "factor12v", bat12v_factor);
MyConfig.SetParamValue("vehicle", "12v.ref", bat12v_ref);
MyConfig.SetParamValue("vehicle", "12v.alert", bat12v_alert);
MyConfig.SetParamValue("vehicle", "12v.shutdown", bat12v_shutdown);
MyConfig.SetParamValue("vehicle", "12v.wakeup", bat12v_wakeup);
MyConfig.SetParamValue("vehicle", "12v.wakeup_interval", bat12v_wakeup_interval);
if (!pin.empty())
MyConfig.SetParamValue("password", "pin", pin);

Expand Down Expand Up @@ -690,6 +696,9 @@ void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
bat12v_factor = MyConfig.GetParamValue("system.adc", "factor12v");
bat12v_ref = MyConfig.GetParamValue("vehicle", "12v.ref");
bat12v_alert = MyConfig.GetParamValue("vehicle", "12v.alert");
bat12v_shutdown = MyConfig.GetParamValue("vehicle", "12v.shutdown");
bat12v_wakeup = MyConfig.GetParamValue("vehicle", "12v.wakeup");
bat12v_wakeup_interval = MyConfig.GetParamValue("vehicle", "12v.wakeup_interval");
c.head(200);
}

Expand Down Expand Up @@ -785,13 +794,30 @@ void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
-1, bat12v_factor.empty() ? 195.7 : atof(bat12v_factor.c_str()), 195.7, 175.0, 225.0, 0.1,
"<p>Adjust the calibration so the voltage displayed above matches your real voltage.</p>");

c.fieldset_start("Alert");
c.input("number", "12V reference", "bat12v_ref", bat12v_ref.c_str(), "Default: 12.6",
"<p>The nominal resting voltage level of your 12V battery when fully charged.</p>",
"min=\"10\" max=\"15\" step=\"0.1\"", "V");
c.input("number", "12V alert threshold", "bat12v_alert", bat12v_alert.c_str(), "Default: 1.6",
"<p>If the actual voltage drops this far below the maximum of configured and measured reference"
" level, an alert is sent.</p>",
"min=\"0\" max=\"3\" step=\"0.1\"", "V");
c.fieldset_end();

c.fieldset_start("Shutdown");
c.input("number", "12V shutdown", "bat12v_shutdown", bat12v_shutdown.c_str(), "Default: disabled",
"<p>If the voltage drops to/below this level, the module will enter deep sleep and wait for the voltage to recover to the wakeup level.</p>"
"<p>Recommended shutdown level for standard lead acid batteries: not less than 10.5 V</p>",
"min=\"10\" max=\"15\" step=\"0.1\"", "V");
c.input("number", "12V wakeup", "bat12v_wakeup", bat12v_wakeup.c_str(), "Default: any",
"<p>The minimum voltage level to allow restarting the module after a 12V shutdown.</p>"
"<p>Recommended minimum level for standard lead acid batteries: not less than 11.0 V",
"min=\"10\" max=\"15\" step=\"0.1\"", "V");
c.input("number", "Wakeup test interval", "bat12v_wakeup_interval", bat12v_wakeup_interval.c_str(), "Default: 60",
"<p>Voltage test interval after shutdown in seconds. Lowering this means faster detection of voltage recovery "
"at the cost of higher energy usage (each test needs ~3 seconds of CPU uptime).</p>",
"min=\"1\" max=\"300\" step=\"1\"", "Seconds");
c.fieldset_end();

c.print(
"</div>"
Expand Down
13 changes: 11 additions & 2 deletions vehicle/OVMS.V3/components/powermgmt/powermgmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,19 @@ void powermgmt::Ticker1(std::string event, void* data)
}
if (m_12v_shutdown_delay && m_12v_alert_timer > m_12v_shutdown_delay*60) // minutes to seconds
{
m_12v_shutdown_delay = 0;
ESP_LOGE(TAG,"Ongoing 12V battery alert time limit exceeded! Shutting down OVMS..");
MyEvents.SignalEvent("powermgmt.ovms.shutdown",NULL);
MyBoot.DeepSleep();
m_12v_shutdown_delay = 0;

// Override default boot voltage level configuration:
float dref = MyConfig.GetParamValueFloat("vehicle", "12v.ref", 12.6);
float vref = MAX(StandardMetrics.ms_v_bat_12v_voltage_ref->AsFloat(), dref);
float alert_threshold = MyConfig.GetParamValueFloat("vehicle", "12v.alert", 1.6);
MyBoot.SetMin12VLevel(vref - alert_threshold + 0.5);

// Request deep sleep shutdown:
unsigned int wakeup_interval = MyConfig.GetParamValueInt("vehicle", "12v.wakeup_interval", 60);
MyBoot.DeepSleep(wakeup_interval);
}
}
else
Expand Down
5 changes: 4 additions & 1 deletion vehicle/OVMS.V3/components/powermgmt/powermgmt_web.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ void powermgmt::WebCleanup()
c.print("<hr>");
c.input_button("default", "Save");
c.form_end();
c.panel_end();
c.panel_end(
"<p>See <a href=\"/cfg/vehicle\" target=\"#main\">vehicle configuration</a> for automatic shutdown"
" (deep sleep) when the 12V level gets too low, independent of the 12V alert state.</p>"
);
c.done();
}

Expand Down
12 changes: 12 additions & 0 deletions vehicle/OVMS.V3/components/vehicle/vehicle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ static const char *TAG = "vehicle";
#include <ovms_webserver.h>
#endif // #ifdef CONFIG_OVMS_COMP_WEBSERVER
#include <ovms_peripherals.h>
#include <ovms_boot.h>
#include <string_writer.h>
#include "vehicle.h"

Expand Down Expand Up @@ -643,6 +644,8 @@ void OvmsVehicle::VehicleTicker1(std::string event, void* data)
// be triggered if the measured ref follows a degrading battery:
float dref = MyConfig.GetParamValueFloat("vehicle", "12v.ref", 12.6);
float vref = MAX(StandardMetrics.ms_v_bat_12v_voltage_ref->AsFloat(), dref);

// Check for alert level:
bool alert_on = StandardMetrics.ms_v_bat_12v_voltage_alert->AsBool();
float alert_threshold = MyConfig.GetParamValueFloat("vehicle", "12v.alert", 1.6);
if (!alert_on && volt > 0 && vref > 0 && vref-volt > alert_threshold)
Expand All @@ -657,6 +660,15 @@ void OvmsVehicle::VehicleTicker1(std::string event, void* data)
MyEvents.SignalEvent("vehicle.alert.12v.off", NULL);
if (m_autonotifications) Notify12vRecovered();
}

// Check for shutdown level:
float shutdown_threshold = MyConfig.GetParamValueFloat("vehicle", "12v.shutdown", 0);
if (shutdown_threshold > 0 && volt > 0 && volt <= shutdown_threshold && !MyBoot.IsShuttingDown())
{
MyEvents.SignalEvent("vehicle.alert.12v.shutdown", NULL);
unsigned int wakeup_interval = MyConfig.GetParamValueInt("vehicle", "12v.wakeup_interval", 60);
MyBoot.DeepSleep(wakeup_interval);
}
}

if ((m_ticker % 10)==0)
Expand Down
75 changes: 60 additions & 15 deletions vehicle/OVMS.V3/main/ovms_boot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ Boot::Boot()
m_shutdown_deepsleep = false;
m_shutdown_deepsleep_seconds = 0;
m_shutting_down = false;
m_min_12v_level_override = false;

m_resetreason = esp_reset_reason(); // Note: necessary to link reset_reason module

Expand All @@ -311,33 +312,47 @@ Boot::Boot()
}
else if (cpu0 == DEEPSLEEP_RESET)
{
memset(&boot_data,0,sizeof(boot_data_t));
m_bootreason = BR_Wakeup;
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
ESP_LOGI(TAG, "Wakeup from deep sleep detected, wakeup cause %d", wakeup_cause);

if (boot_data.crc != boot_data.calc_crc())
{
memset(&boot_data,0,sizeof(boot_data_t));
ESP_LOGW(TAG, "Boot data corruption detected, data cleared");
}

// There is currently only one deep sleep application: saving the 12V battery
// from depletion. So we need to check if the voltage level is sufficient for
// normal operation now. MyPeripherals has not been initialized yet, so we need
// to read the ADC manually here.
#ifdef CONFIG_OVMS_COMP_ADC
// Note: RTC_MODULE nags about a lock release before aquire, this can be ignored
// (reason: RTC_MODULE needs FreeRTOS for locking, which hasn't been started yet)
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);
uint32_t adc_level = 0;
for (int i = 0; i < 5; i++)
adc_level += adc1_get_raw(ADC1_CHANNEL_0);
float level_12v = (float) adc_level / 5 / 195.7;
ESP_LOGI(TAG, "12V level: ~%.1fV", level_12v);
if (level_12v > 11.0)
ESP_LOGI(TAG, "12V level sufficient, proceeding with boot");
else if (level_12v < 1.0)
ESP_LOGI(TAG, "Assuming USB powered, proceeding with boot");
else
float min_12v_level = (boot_data.min_12v_level > 0) ? boot_data.min_12v_level : 0;
if (min_12v_level > 0)
{
ESP_LOGE(TAG, "12V level insufficient, re-entering deep sleep");
esp_deep_sleep(1000000LL * 60);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);
uint32_t adc_level = 0;
for (int i = 0; i < 5; i++)
{
ets_delay_us(2000);
adc_level += adc1_get_raw(ADC1_CHANNEL_0);
}
float adc1_factor = (boot_data.adc1_factor > 0) ? boot_data.adc1_factor : 195.7;
float level_12v = (float) adc_level / 5.0f / adc1_factor;
ESP_LOGI(TAG, "12V level: ~%.1fV", level_12v);
if (level_12v >= min_12v_level)
ESP_LOGI(TAG, "12V level sufficient, proceeding with boot");
else if (level_12v < 1.0)
ESP_LOGI(TAG, "Assuming USB powered, proceeding with boot");
else
{
ESP_LOGE(TAG, "12V level insufficient, re-entering deep sleep");
int wakeup_interval = (boot_data.wakeup_interval > 0) ? boot_data.wakeup_interval : 60;
esp_deep_sleep(1000000LL * wakeup_interval);
}
}
#else
ESP_LOGW(TAG, "ADC not available, cannot check 12V level");
Expand Down Expand Up @@ -421,6 +436,36 @@ Boot::~Boot()
{
}

void Boot::Init()
{
using std::placeholders::_1;
using std::placeholders::_2;
MyEvents.RegisterEvent(TAG, "config.changed", std::bind(&Boot::UpdateConfig, this, _1, _2));
MyEvents.RegisterEvent(TAG, "config.mounted", std::bind(&Boot::UpdateConfig, this, _1, _2));
}

void Boot::UpdateConfig(std::string event, void* data)
{
OvmsConfigParam* param = (OvmsConfigParam*) data;
if (!param || param->GetName() == "system.adc" || param->GetName() == "vehicle")
{
boot_data.adc1_factor = MyConfig.GetParamValueFloat("system.adc", "factor12v", 195.7);
boot_data.wakeup_interval = MyConfig.GetParamValueInt("vehicle", "12v.wakeup_interval", 60);
if (!m_min_12v_level_override)
boot_data.min_12v_level = MyConfig.GetParamValueFloat("vehicle", "12v.wakeup", 0);
boot_data.crc = boot_data.calc_crc();
}
}

void Boot::SetMin12VLevel(float min_12v_level)
{
boot_data.min_12v_level = min_12v_level;
boot_data.crc = boot_data.calc_crc();
// inhibit update from config:
m_min_12v_level_override = true;
ESP_LOGI(TAG, "Minimum 12V boot level set to %.1fV", min_12v_level);
}

void Boot::SetStable()
{
boot_data.stable_reached = true;
Expand Down
7 changes: 7 additions & 0 deletions vehicle/OVMS.V3/main/ovms_boot.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ typedef struct
unsigned int boot_count; // Number of times system has rebooted (not power on)
RESET_REASON bootreason_cpu0; // Reason for last boot on CPU#0
RESET_REASON bootreason_cpu1; // Reason for last boot on CPU#1
float adc1_factor; // 12V battery ADC calibration factor
float min_12v_level; // 12V battery minimum voltage level to allow boot
int wakeup_interval; // Wakeup interval in seconds for 12V restoration check
bool soft_reset; // true = user requested reset ("module reset")
bool firmware_update; // true = firmware update restart
bool stable_reached; // true = system has reached stable state (see housekeeping)
Expand All @@ -111,6 +114,7 @@ class Boot
public:
Boot();
virtual ~Boot();
void Init();

public:
bootreason_t GetBootReason() { return m_bootreason; }
Expand All @@ -125,13 +129,15 @@ class Boot
public:
void SetSoftReset();
void SetFirmwareUpdate();
void SetMin12VLevel(float min_12v_level);
void Restart(bool hard=false);
void DeepSleep(unsigned int seconds = 60);
void DeepSleep(time_t waketime);
void ShutdownPending(const char* tag);
void ShutdownReady(const char* tag);
bool IsShuttingDown();
void Ticker1(std::string event, void* data);
void UpdateConfig(std::string event, void* data);

public:
OvmsMutex m_shutdown_mutex;
Expand All @@ -141,6 +147,7 @@ class Boot
unsigned int m_shutdown_deepsleep_seconds;
time_t m_shutdown_deepsleep_waketime;
bool m_shutting_down;
bool m_min_12v_level_override;

public:
#if ESP_IDF_VERSION_MAJOR < 4
Expand Down
2 changes: 2 additions & 0 deletions vehicle/OVMS.V3/main/ovms_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ static const char *TAG = "ovms_main";
#include "ovms_events.h"
#include "ovms_config.h"
#include "ovms_module.h"
#include "ovms_boot.h"
#include <esp_task_wdt.h>

extern "C"
Expand Down Expand Up @@ -67,6 +68,7 @@ void app_main(void)

ESP_LOGI(TAG, "Executing on CPU core %d",xPortGetCoreID());
AddTaskToMap(xTaskGetCurrentTaskHandle());
MyBoot.Init();

ESP_LOGI(TAG, "Mounting CONFIG...");
MyConfig.mount();
Expand Down

0 comments on commit fbb768b

Please sign in to comment.