From 6e3794efd36f9100171ede2e22672457b526541b Mon Sep 17 00:00:00 2001 From: acheronfail Date: Fri, 8 May 2026 22:06:57 +0930 Subject: [PATCH] Added motor and current saturation to the led status bar Feature: Show motor and battery current saturation (together with the duty cycle called motor utilization) on the status bar > Motor saturation is pink, battery saturation is teal and duty cycle is amber in color. --- src/conf/datatypes.h | 2 +- src/conf/settings.xml | 22 ++++++++------- src/leds.c | 63 ++++++++++++++++++++++++++++++++----------- src/leds.h | 9 ++++--- src/main.c | 2 +- src/motor_data.c | 26 +++++++++++++----- src/motor_data.h | 2 ++ ui.qml.in | 12 +++++++++ 8 files changed, 100 insertions(+), 38 deletions(-) diff --git a/src/conf/datatypes.h b/src/conf/datatypes.h index 75607f5e..992a4e29 100644 --- a/src/conf/datatypes.h +++ b/src/conf/datatypes.h @@ -130,7 +130,7 @@ typedef struct { typedef struct { uint16_t idle_timeout; - float duty_threshold; + float motor_utilization_threshold; float red_bar_percentage; bool show_sensors_while_running; float brightness_headlights_on; diff --git a/src/conf/settings.xml b/src/conf/settings.xml index 706b9361..b5395425 100644 --- a/src/conf/settings.xml +++ b/src/conf/settings.xml @@ -3294,32 +3294,34 @@ p, li { white-space: pre-wrap; } CFG_DFLT_LEDS_STATUS_SHOW_SENSORS_WHILE_RUNNING 1 - - Status Duty Threshold + + Motor Utilization Threshold 1 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Roboto'; ; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Duty threshold above which the duty is shown on the status bar (in amber yellow color) instead of the battery. Note there is a 10% hysteresis, meaning if you duty shows at 20%, it will be shown until it drops below 10% (to avoid constant blinking). Values below 15% set the threshold to 15%.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Threshold above which motor utilization is shown on the status bar (instead of the battery). Motor utilization is the maximum of duty cycle, motor current saturation, and battery current saturation. Note there is a 10% hysteresis, meaning if motor utilization shows at 60%, it will keep showing until it drops below 50% (to avoid constant blinking).</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Set to 0 to not show the duty bar.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The colors are as follows:<br />Duty cycle: amber</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Motor current: pink</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Battery current: teal</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Note: Since duty above 95% is not achievable and you're unlikely to ever benefit from values above 90% being shown on your status bar, duty level of 90% is scaled to 100% on the status bar to make full use of its real estate.</p></body></html> - CFG_DFLT_HARDWARE_LEDS_STATUS_DUTY_THRESHOLD + CFG_DFLT_HARDWARE_LEDS_STATUS_MOTOR_UTILIZATION_THRESHOLD 0 100 0 1 - 0 + 0.15 0 5 - 0.5 + 0.6 10000 % 7 - + Red Color Bar Percentage 1 @@ -4007,7 +4009,7 @@ p, li { white-space: pre-wrap; } leds.status.brightness_headlights_off leds.status.brightness_headlights_on leds.status.show_sensors_while_running - leds.status.duty_threshold + leds.status.motor_utilization_threshold leds.status.red_bar_percentage leds.status.idle_timeout leds.status_idle.mode @@ -4259,7 +4261,7 @@ p, li { white-space: pre-wrap; } leds.status.brightness_headlights_off leds.status.brightness_headlights_on leds.status.show_sensors_while_running - leds.status.duty_threshold + leds.status.motor_utilization_threshold leds.status.red_bar_percentage leds.status.idle_timeout ::sep::Status Idle diff --git a/src/leds.c b/src/leds.c index 2b19f4b9..a998edac 100644 --- a/src/leds.c +++ b/src/leds.c @@ -78,6 +78,8 @@ static const uint32_t colors[] = { #define BATTERY_COLOR 0x00909090 #define DUTY_COLOR 0x00FFB030 +#define MOTOR_CURRENT_COLOR 0x00FF5090 +#define BATTERY_CURRENT_COLOR 0x0000FF80 #define FOOTPAD_SENSOR_COLOR 0x0000C0FF #define RED_BAR_COLOR 0x00FF3828 #define BATTERY10_BAR_COLOR 0x00FF5038 @@ -535,21 +537,32 @@ static void anim_confirm(Leds *leds, const LedStrip *strip, float time) { } static void status_animate( - Leds *leds, const LedStrip *strip, float current_time, float blend, float idle_blend + Leds *leds, + const LedStrip *strip, + const MotorData *motor, + float current_time, + float blend, + float idle_blend ) { if (fabsf(VESC_IF->mc_get_rpm()) > ERPM_MOVING_THRESHOLD) { leds->status_idle_time = current_time; } - float duty = 0; + float duty = 0.0f; + float motor_current = 0.0f; + float battery_current = 0.0f; if (leds->state.state == STATE_RUNNING && leds->state.mode != MODE_FLYWHEEL) { - duty = fminf(fabsf(VESC_IF->mc_get_duty_cycle_now() * 10.0f / 9.0f), 1.0f); + duty = clampf(motor->duty_cycle.value * 10.0f / 9.0f, 0.0f, 1.0f); + motor_current = clampf(motor->motor_current_saturation, 0.0f, 1.0f); + battery_current = clampf(motor->battery_current_saturation, 0.0f, 1.0f); } - if (duty > leds->duty_threshold) { - rate_limitf(&leds->status_duty_blend, 1.0f, SB_RATE); - } else if (duty < leds->duty_threshold - 0.1f) { // 10 percent hysteresis - rate_limitf(&leds->status_duty_blend, 0.0f, SB_RATE); + float motor_utilization = fmaxf(duty, fmaxf(motor_current, battery_current)); + // 10 percent hysteresis + if (motor_utilization > leds->motor_utilization_threshold) { + rate_limitf(&leds->status_utilization_blend, 1.0f, SB_RATE); + } else if (motor_utilization < leds->motor_utilization_threshold - 0.1f) { + rate_limitf(&leds->status_utilization_blend, 0.0f, SB_RATE); } if (idle_blend > 0.0f) { @@ -567,16 +580,28 @@ static void status_animate( if (idle_blend < 1.0f) { const bool reverse = strip == &leds->front_strip; - if (leds->status_duty_blend < 1.0f) { + if (leds->status_utilization_blend < 1.0f) { const float battery = VESC_IF->mc_get_battery_level(NULL); anim_battery_bar( leds, strip, battery, reverse, fminf(blend, 1.0f - idle_blend), current_time ); } - if (leds->status_duty_blend > 0.0f) { + if (leds->status_utilization_blend > 0.0f) { + float max_sat = duty; + uint32_t max_color = DUTY_COLOR; + + if (motor_current > max_sat) { + max_sat = motor_current; + max_color = MOTOR_CURRENT_COLOR; + } + if (battery_current > max_sat) { + max_sat = battery_current; + max_color = BATTERY_CURRENT_COLOR; + } + anim_progress_bar( - leds, strip, duty, DUTY_COLOR, true, reverse, leds->status_duty_blend + leds, strip, max_sat, max_color, true, reverse, leds->status_utilization_blend ); } @@ -770,8 +795,8 @@ void leds_init(Leds *leds) { leds->on_off_fade = 0.0f; - leds->duty_threshold = 0.0f; - leds->status_duty_blend = 0.0f; + leds->motor_utilization_threshold = 0.0f; + leds->status_utilization_blend = 0.0f; leds->status_idle_blend = 0.0f; leds->status_idle_time = 0.0f; leds->status_animation_start = 0.0f; @@ -886,7 +911,7 @@ void leds_setup(Leds *leds, CfgHwLeds *hw_cfg, const CfgLeds *cfg) { } void leds_configure(Leds *leds, const CfgLeds *cfg) { - leds->duty_threshold = fmaxf(cfg->status.duty_threshold, 0.15); + leds->motor_utilization_threshold = fmaxf(cfg->status.motor_utilization_threshold, 0.15); leds->headlights_trans.transition = cfg->headlights_transition; leds->dir_trans.transition = cfg->direction_transition; @@ -917,7 +942,9 @@ void leds_set_headlights_enabled(Leds *leds, bool value) { leds->runtime_status_overriden.headlights_enabled = true; } -void leds_update(Leds *leds, const State *state, FootpadSensorState fs_state) { +void leds_update( + Leds *leds, const State *state, const MotorData *motor, FootpadSensorState fs_state +) { if (!leds->led_data) { return; } @@ -1200,10 +1227,13 @@ void leds_update(Leds *leds, const State *state, FootpadSensorState fs_state) { rate_limitf(&leds->status_idle_blend, 0.0f, BR_RATE); } - status_animate(leds, &leds->status_strip, current_time, 1.0f, leds->status_idle_blend); + status_animate( + leds, &leds->status_strip, motor, current_time, 1.0f, leds->status_idle_blend + ); } - if (leds->cfg->status_on_front_when_lifted && leds->status_on_front_blend > 0.0f) { + if (leds->cfg->status_on_front_when_lifted && leds->status_on_front_blend > 0.0f && + leds->front_strip.length > 0) { if (leds->cfg->lights_off_when_lifted && current_time - leds->status_on_front_idle_time > 3.0f) { rate_limitf(&leds->status_on_front_idle_blend, 1.0f, BR_RATE); @@ -1214,6 +1244,7 @@ void leds_update(Leds *leds, const State *state, FootpadSensorState fs_state) { status_animate( leds, &leds->front_strip, + motor, current_time, leds->status_on_front_blend, leds->status_on_front_idle_blend diff --git a/src/leds.h b/src/leds.h index 0afb46c3..3b07bb4c 100644 --- a/src/leds.h +++ b/src/leds.h @@ -21,6 +21,7 @@ #include "footpad_sensor.h" #include "led_driver.h" #include "led_strip.h" +#include "motor_data.h" #include "state.h" #define LEDS_REFRESH_RATE 30 @@ -51,8 +52,8 @@ typedef struct { float on_off_fade; - float duty_threshold; - float status_duty_blend; + float motor_utilization_threshold; + float status_utilization_blend; float status_idle_blend; float status_idle_time; float status_animation_start; @@ -101,7 +102,9 @@ void leds_set_enabled(Leds *leds, bool value); void leds_set_headlights_enabled(Leds *leds, bool value); -void leds_update(Leds *leds, const State *state, FootpadSensorState fs_state); +void leds_update( + Leds *leds, const State *state, const MotorData *motor, FootpadSensorState fs_state +); void leds_status_confirm(Leds *leds); diff --git a/src/main.c b/src/main.c index 5d6ebb00..b4a4efa7 100644 --- a/src/main.c +++ b/src/main.c @@ -1154,7 +1154,7 @@ static void aux_thd(void *arg) { &d->imu_freq_tracker, running, &d->time, &imu_freq_update_reconfigure ); - leds_update(&d->leds, &d->state, d->footpad.state); + leds_update(&d->leds, &d->state, &d->motor, d->footpad.state); // store odometer if we've gone more than 200m if (!running && VESC_IF->mc_get_odometer() > d->odometer + 200) { diff --git a/src/motor_data.c b/src/motor_data.c index 2c03dc6b..60a542de 100644 --- a/src/motor_data.c +++ b/src/motor_data.c @@ -45,6 +45,8 @@ void motor_data_init(MotorData *m) { ema_init(&m->batt_current); m->batt_voltage = 0.0f; + m->motor_current_saturation = 0.0f; + m->battery_current_saturation = 0.0f; m->mosfet_temp = 0.0f; m->motor_temp = 0.0f; @@ -89,7 +91,7 @@ void motor_data_refresh_motor_config(MotorData *m, float lv_threshold, float hv_ // min motor current is a positive value here! m->current_min = fabsf(VESC_IF->get_cfg_float(CFG_PARAM_l_current_min)); m->current_max = VESC_IF->get_cfg_float(CFG_PARAM_l_current_max); - m->battery_current_min = VESC_IF->get_cfg_float(CFG_PARAM_l_in_current_min); + m->battery_current_min = fabsf(VESC_IF->get_cfg_float(CFG_PARAM_l_in_current_min)); m->battery_current_max = VESC_IF->get_cfg_float(CFG_PARAM_l_in_current_max); m->mosfet_temp_max = VESC_IF->get_cfg_float(CFG_PARAM_l_temp_fet_start) - 3; m->motor_temp_max = VESC_IF->get_cfg_float(CFG_PARAM_l_temp_motor_start) - 3; @@ -144,6 +146,21 @@ void motor_data_update(MotorData *m, float dt) { ema_update(&m->batt_current, VESC_IF->mc_get_tot_current_in_filtered()); m->batt_voltage = VESC_IF->mc_get_input_voltage_filtered(); + float motor_current_limit = m->braking ? m->current_min : m->current_max; + if (motor_current_limit > 0.0f) { + m->motor_current_saturation = fabsf(m->filt_current.value) / motor_current_limit; + } else { + m->motor_current_saturation = 0.0f; + } + + float battery_current_limit = + m->batt_current.value < 0 ? m->battery_current_min : m->battery_current_max; + if (battery_current_limit > 0.0f) { + m->battery_current_saturation = fabsf(m->batt_current.value) / battery_current_limit; + } else { + m->battery_current_saturation = 0.0f; + } + m->mosfet_temp = VESC_IF->mc_temp_fet_filtered(); m->motor_temp = VESC_IF->mc_temp_motor_filtered(); } @@ -158,10 +175,5 @@ void motor_data_evaluate_alerts(const MotorData *m, AlertTracker *at, const Time } float motor_data_get_current_saturation(const MotorData *m) { - float motor_saturation = - fabsf(m->filt_current.value) / (m->braking ? m->current_min : m->current_max); - float battery_saturation = m->batt_current.value / - (m->batt_current.value < 0 ? m->battery_current_min : m->battery_current_max); - - return max(motor_saturation, battery_saturation); + return max(m->motor_current_saturation, m->battery_current_saturation); } diff --git a/src/motor_data.h b/src/motor_data.h index d31c369b..5111da3c 100644 --- a/src/motor_data.h +++ b/src/motor_data.h @@ -50,6 +50,8 @@ typedef struct { EMA batt_current; float batt_voltage; + float motor_current_saturation; + float battery_current_saturation; float mosfet_temp; float motor_temp; diff --git a/ui.qml.in b/ui.qml.in index 83f9c772..c5f4d895 100644 --- a/ui.qml.in +++ b/ui.qml.in @@ -1360,6 +1360,13 @@ Item { return tunes; } + function replaceProperty(tune, oldName, newName) { + if (tune.settings.hasOwnProperty(oldName)) { + tune.settings[newName] = tune.settings[oldName]; + delete tune.settings[oldName]; + } + } + // Major package versions require a migration, minor don't. // Migration functions should return: [tuneObject, [newMajor, newMinor]] property var migrations: { @@ -1376,6 +1383,11 @@ Item { return [tune, [1, 2]]; }, + "1.2": function(tune) { + replaceProperty(tune, "leds.status.duty_threshold", "leds.status.motor_utilization_threshold"); + + return [tune, [1, 3]]; + }, } function onUpdate() {