-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Integrate um_data structure to battery usermod #5521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,10 +16,14 @@ class UsermodBattery : public Usermod | |
| private: | ||
| // battery pin can be defined in my_config.h | ||
| int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN; | ||
|
|
||
| UMBattery* bat = new UnkownUMBattery(); | ||
| batteryConfig cfg; | ||
|
|
||
| int8_t batteryLevel = 0; // current battery level in % | ||
| float batteryVoltage = 0.0f; // current battery voltage in V | ||
|
|
||
|
|
||
| // Initial delay before first reading to allow voltage stabilization | ||
| unsigned long initialDelay = USERMOD_BATTERY_INITIAL_DELAY; | ||
| bool initialDelayComplete = false; | ||
|
|
@@ -80,6 +84,12 @@ class UsermodBattery : public Usermod | |
| return str; | ||
| } | ||
|
|
||
| void refreshExportedBatteryData() | ||
| { | ||
| batteryVoltage = bat->getVoltage(); | ||
| batteryLevel = bat->getLevel(); | ||
| } | ||
|
|
||
| /** | ||
| * Turn off all leds | ||
| */ | ||
|
|
@@ -213,6 +223,21 @@ class UsermodBattery : public Usermod | |
| pinMode(batteryPin, INPUT); | ||
| #endif | ||
|
|
||
| // Export snapshot for um_data is allocated only once. | ||
| // setup() may be called again after a pin change via readFromConfig(), | ||
| // but the exported pointers remain valid because they still point to | ||
| // the same member variables of this usermod instance. | ||
| if (!this->um_data) { | ||
| this->um_data = new um_data_t; | ||
| this->um_data->u_size = 2; | ||
| this->um_data->u_type = new um_types_t[this->um_data->u_size]; | ||
| this->um_data->u_data = new void*[this->um_data->u_size]; | ||
| this->um_data->u_data[0] = &batteryVoltage; | ||
| this->um_data->u_type[0] = UMT_FLOAT; | ||
| this->um_data->u_data[1] = &batteryLevel; | ||
| this->um_data->u_type[1] = UMT_BYTE; | ||
| } | ||
|
|
||
| // First voltage reading is delayed to allow voltage stabilization after powering up | ||
| nextReadTime = millis() + initialDelay; | ||
| lastReadTime = millis(); | ||
|
|
@@ -256,7 +281,10 @@ class UsermodBattery : public Usermod | |
| // Make the first voltage reading after the initial delay has elapsed | ||
| if (isFirstVoltageReading) | ||
| { | ||
| bat->setVoltage(readVoltage()); | ||
| float firstVoltage = readVoltage(); | ||
| bat->setVoltage(firstVoltage); | ||
| bat->calculateAndSetLevel(firstVoltage); | ||
| refreshExportedBatteryData(); | ||
| isFirstVoltageReading = false; | ||
| } | ||
|
|
||
|
|
@@ -277,13 +305,14 @@ class UsermodBattery : public Usermod | |
| bat->setVoltage(filteredVoltage); | ||
| // translate battery voltage into percentage | ||
| bat->calculateAndSetLevel(filteredVoltage); | ||
| refreshExportedBatteryData(); | ||
|
|
||
| // Auto off -- Master power off | ||
| if (autoOffEnabled && (autoOffThreshold >= bat->getLevel())) | ||
| turnOff(); | ||
|
|
||
| #ifndef WLED_DISABLE_MQTT | ||
| publishMqtt("battery", String(bat->getLevel(), 0).c_str()); | ||
| publishMqtt("battery", String(static_cast<int>(bat->getLevel())).c_str()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why? this is worse.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change is based on a note from coderabbitai on my first proposed version of the code. The original The
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not too deep into this usermod. I suspect that a float was once changed to an int (if it is an int now) but doing a static cast of an int into an int only makes sense if String class requires that (I dont think it does) so its more confusing than helping here.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with you. But the old notation with 0 as numeric base is not right either. Which of the two proposed fixes should I commit? String(bat->getLevel()), or alternatively String(bat->getLevel(), DEC)?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would expect String(bat->getLevel()) to use base 10 by default? the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely. To my knowledge you are right about the base 10 default. Ok, then lets use the simplest form of the code: |
||
| publishMqtt("voltage", String(bat->getVoltage()).c_str()); | ||
| #endif | ||
|
|
||
|
|
@@ -313,7 +342,7 @@ class UsermodBattery : public Usermod | |
|
|
||
| infoNextUpdate.add((nextReadTime - millis()) / 1000); | ||
| infoNextUpdate.add(F(" sec")); | ||
|
|
||
| if (initializing) { | ||
| infoPercentage.add(FPSTR(_init)); | ||
| infoVoltage.add(FPSTR(_init)); | ||
|
|
@@ -376,7 +405,7 @@ class UsermodBattery : public Usermod | |
| setLowPowerIndicatorThreshold(lp[FPSTR(_threshold)] | lowPowerIndicatorThreshold); | ||
| lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10; | ||
| setLowPowerIndicatorDuration(lp[FPSTR(_duration)] | lowPowerIndicatorDuration); | ||
|
|
||
| if(initDone) | ||
| bat->update(cfg); | ||
| } | ||
|
|
@@ -391,9 +420,9 @@ class UsermodBattery : public Usermod | |
|
|
||
| if (battery.isNull()) | ||
| battery = root.createNestedObject(FPSTR(_name)); | ||
|
|
||
| addBatteryToJsonObject(battery, true); | ||
|
|
||
| DEBUG_PRINTLN(F("Battery state exposed in JSON API.")); | ||
| } | ||
|
|
||
|
|
@@ -411,7 +440,7 @@ class UsermodBattery : public Usermod | |
|
|
||
| if (!battery.isNull()) { | ||
| getUsermodConfigFromJsonObject(battery); | ||
|
|
||
| DEBUG_PRINTLN(F("Battery state read from JSON API.")); | ||
| } | ||
| } | ||
|
|
@@ -456,19 +485,22 @@ class UsermodBattery : public Usermod | |
| void addToConfig(JsonObject& root) | ||
| { | ||
| JsonObject battery = root.createNestedObject(FPSTR(_name)); | ||
|
|
||
| if (battery.isNull()) { | ||
| battery = root.createNestedObject(FPSTR(_name)); | ||
| } | ||
|
|
||
| #ifdef ARDUINO_ARCH_ESP32 | ||
| battery[F("pin")] = batteryPin; | ||
| #endif | ||
|
|
||
| addBatteryToJsonObject(battery, false); | ||
|
|
||
| // read voltage in case calibration or voltage multiplier changed to see immediate effect | ||
| bat->setVoltage(readVoltage()); | ||
| float configVoltage = readVoltage(); | ||
| bat->setVoltage(configVoltage); | ||
| bat->calculateAndSetLevel(configVoltage); | ||
| refreshExportedBatteryData(); | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| DEBUG_PRINTLN(F("Battery config saved.")); | ||
| } | ||
|
|
@@ -488,7 +520,7 @@ class UsermodBattery : public Usermod | |
| oappend(F("addInfo('Battery:auto-off:threshold',1,'%');")); // 45 Bytes | ||
| oappend(F("addInfo('Battery:indicator:threshold',1,'%');")); // 46 Bytes | ||
| oappend(F("addInfo('Battery:indicator:duration',1,'s');")); // 45 Bytes | ||
|
|
||
| // this option list would exeed the oappend() buffer | ||
| // a list of all presets to select one from | ||
| // oappend(F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); | ||
|
|
@@ -523,7 +555,7 @@ class UsermodBattery : public Usermod | |
| #ifdef ARDUINO_ARCH_ESP32 | ||
| int8_t newBatteryPin = batteryPin; | ||
| #endif | ||
|
|
||
| JsonObject battery = root[FPSTR(_name)]; | ||
| if (battery.isNull()) | ||
| { | ||
|
|
@@ -652,7 +684,7 @@ class UsermodBattery : public Usermod | |
| { | ||
| return bat->getMaxVoltage(); | ||
| } | ||
|
|
||
| /** | ||
| * Set highest battery voltage | ||
| * can't be below minBatteryVoltage | ||
|
|
@@ -733,7 +765,7 @@ class UsermodBattery : public Usermod | |
| { | ||
| autoOffEnabled = enabled; | ||
| } | ||
|
|
||
| /** | ||
| * Get auto-off threshold in percent (0-100) | ||
| */ | ||
|
|
@@ -844,6 +876,13 @@ class UsermodBattery : public Usermod | |
| { | ||
| return HomeAssistantDiscovery; | ||
| } | ||
|
|
||
| bool getUMData(um_data_t **data) override | ||
| { | ||
| if (!data || !this->um_data) return false; // no pointer provided or no data available | ||
| *data = this->um_data; | ||
| return true; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| }; | ||
|
|
||
| // strings to reduce flash memory usage (used more than twice) | ||
|
|
@@ -858,4 +897,4 @@ const char UsermodBattery::_haDiscovery[] PROGMEM = "HA-discovery"; | |
|
|
||
|
|
||
| static UsermodBattery battery; | ||
| REGISTER_USERMOD(battery); | ||
| REGISTER_USERMOD(battery); | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Signed/unsigned mismatch:
batteryLevel(int8_t) exposed asUMT_BYTE.batteryLevelcan be negative (the code emits"invalid"whenbat->getLevel() < 0at line 348), but it is exported withUMT_BYTE, which consumers will dereference asuint8_t(see e.g. the AudioReactive pattern inusermods/audioreactive/audio_reactive.cpp:1342-1370). A -1 sentinel will appear as 255 to consumers, which is indistinguishable from an almost-full battery. Consider storingbatteryLevelasuint8_tand using a distinct sentinel (e.g.0xFFonly when explicitly documented), or adding a new typed entry, so the exported type matches the exported payload.Also applies to: 233-234
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In UMBattery.h the battery level is also defined as int8_t and its range is 0 to 100. Please confirm if it should be changed to uint8_t as suggested by coderabbit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Length of output: 10410
@PSi86, after checking the full source ofUMBattery.hand all concrete battery types, here is a precise answer:What the code actually does today
UMBattery.hint8_t level = 100setLevel()callsconstrain(level, 0.0f, 110.0f)→ always 0–110, never negativeUMBattery::getLevel()floatBattery.cppint8_t batteryLevelgetLevel()(float→int8_t truncation), so 0–110UMT_BYTEuint8_tThe check
bat->getLevel() < 0inaddToJsonInfois dead code — the base classsetLevel()usesconstrain(..., 0.0f, 110.0f), so a negative level cannot be produced through the normal API.Should you change to
uint8_t?Yes, it is safe and semantically correct, because:
uint8_t(max 255) comfortably holds 110.UMT_BYTEexport type so consumers aren't misled.For full consistency, both places should change together:
UMBattery.h— change the stored field:Battery.cpp— change the cached export variable:Battery.cpp— remove the now-confirmed dead< 0guard (or convert it to a range check):Battery.cpp— update the getter signature to match:🧠 Learnings used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good analysis. what about the change of "String(bat->getLevel(), 0).c_str()" to "String(static_cast(bat->getLevel())).c_str()", what are the advantages and disadvantages?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only users with a collaborator, contributor, member, or owner role can interact with CodeRabbit.