Skip to content

Conversation

@JamesDCowley
Copy link
Member

@JamesDCowley JamesDCowley commented Apr 17, 2025

Summary

Closes #159 (finally). Created a new State of Health class to hold information from a bunch of sensors such as temperatures, voltage, current, etc. Will be used for upcoming Mode Manager to manage power states.

How was this tested

  • Added new unit tests
  • Ran code on hardware (screenshots are helpful)
    image
    (Power monitor stuff is null but does work with a working battery board, but ours blew up)
    Works on v4 flight boards with v2 battery boards. Make sure to import and add the INA219Manager and StateOfHealth into main.py on whatever version repo.

main.py For v4 flight + v2 battery (will be added in upcoming PR to v4 and v5 boards):

from lib.pysquared.hardware.power_monitor.manager.ina219 import INA219Manager
...
from lib.pysquared.state_of_health import StateOfHealth
...
# v4 only -- use i2c1 for v5
i2c0 = initialize_i2c_bus(
        logger,
        board.I2C0_SCL,
        board.I2C0_SDA,
        100000,
)
...
battery_power_monitor = INA219Manager(logger, i2c0, 0x40) # address and bus may be different depending on your board
soh = StateOfHealth(
        logger,
        battery_power_monitor,
        solar_power_monitor,
        radio,
        imu,
        Counter(index=register.BOOTCNT, datastore=microcontroller.nvm), # boot count
        Flag(index=register.FLAG, bit_index=6, datastore=microcontroller.nvm), # burned
        Flag(index=register.FLAG, bit_index=3, datastore=microcontroller.nvm), # brownout
        Flag(index=register.FLAG, bit_index=7, datastore=microcontroller.nvm), # fsk
)
# you can put None in place of the power monitors if you don't have a battery board and are using a v4 flight board
...
def main():
...
        state_of_health.update()
...
  • Other (Please describe)

@JamesDCowley JamesDCowley marked this pull request as ready for review April 18, 2025 23:05
@Mikefly123 Mikefly123 requested a review from Copilot April 19, 2025 00:56

This comment was marked as outdated.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@sonarqubecloud
Copy link

Copy link
Member

@nateinaction nateinaction left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty great refactor from the old state of health. Thank you for extracting this functionality from functions.py. This feels like the start of a new "services" layer in pysquared.

While you were still building out the ina219 power monitor manager I remember us talking about how useful the protos could be in creating a generic state of health service. While not as customized as this implementation, in #240 I took a swing at spinning up a generic state of health service just to see what it could look like. How do you think of the generic model compares to what you have here?

Copy link
Member

@Mikefly123 Mikefly123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed based on Nate's comments that there should be a slight change to the implementation of the system_voltage() and battery_voltage() calls!

nateinaction and others added 2 commits June 10, 2025 19:23
Copy link
Member

@nateinaction nateinaction left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is on a good track. Talking with Michael, since there is nothing we can do on the satellite to fix an overheating condition we should slim this down so that we only care about battery charge.

@Mikefly123
Copy link
Member

@nateinaction I think it could go either way with keeping the temperature monitoring in here for flavor or splitting it off into a dedicated thermal manager / interface. It is true that right now there is not actually much we can do in response to our of range temperatures and it is mostly just a nice FYI for the ground operators.

If you'd like I can see if someone on my team has some time to address the test and typecheck errors so we can more forward with this.

@yudataguy
Copy link
Collaborator

@Mikefly123 Just added new PowerHealth, and removed StateOfHealth. Please take a look at the update

This appeases the typecheck CI

Signed-off-by: Michael Pham <phamlongmichael@gmail.com>
@Mikefly123
Copy link
Member

Hey @yudataguy I think the updates look good, a few things to take a look at though:

  1. It looks like two of your tests are failing, one because of the float =/= int comparison thing and the other due to a __name__ attribute error. If you can straighten these out that would be great!

  2. Rather than using an absolute value threshold based on self.config.normal_battery_voltage - self.config.critical_battery_voltage I think it would be more clear if we just added another line to config for degraded_battery_voltage = 7.0V or something like that. So if it goes below that number then we are in the degraded range.

@yudataguy yudataguy requested a review from Mikefly123 June 28, 2025 01:38
Copy link
Member

@Mikefly123 Mikefly123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @yudataguy! This update looks really great. Just one last comment before we go ahead and merge it in.

I was reading through and thinking about error handling / fault tolerance. I think if the power monitors start returning us None values rather than actual numbers we will probably be expecting errors to be thrown because the sensors are not actually reading anything or talking to us. As a result, we should be prepared to have to catch those errors in here and safely return a value that lets the rest of the system know the state of health is unknown.

Let me know what you think about my comments. If you agree with making a few tweaks, we can get this merged in right after they are done!

Copy link
Member

@nateinaction nateinaction left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good Sam! I had a few small comments about naming and one about removing a logic block that is no longer necessary with your refactor. Great job!

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jul 2, 2025

@Mikefly123 Mikefly123 requested a review from Copilot July 2, 2025 23:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds a new PowerHealth class to aggregate sensor readings and determine battery health states, alongside introducing a degraded_battery_voltage configuration parameter.

  • Implements PowerHealth with states (NOMINAL, DEGRADED, CRITICAL, UNKNOWN) and extensive unit tests
  • Adds degraded_battery_voltage to config loader, schema validation, docs, and test JSON
  • Updates existing config tests to verify the new parameter

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/test_power_health.py New unit tests covering PowerHealth.get() and _avg_reading()
tests/unit/test_config.py Added assertion for degraded_battery_voltage in config tests
tests/unit/files/config.test.json Included degraded_battery_voltage entry in test JSON data
pysquared/config/config.py Loads degraded_battery_voltage and adds schema validation entry
pysquared/docs/config.md Documents the new degraded_battery_voltage config option
pysquared/power_health.py Implements PowerHealth logic and averaging helper
Comments suppressed due to low confidence (1)

pysquared/config/config.py:40

  • Add a check to ensure degraded_battery_voltage falls between critical_battery_voltage and normal_battery_voltage to prevent misconfiguration of thresholds.
        self.degraded_battery_voltage: float = json_data["degraded_battery_voltage"]

if reading is None:
func_name = getattr(func, "__name__", "unknown_function")
self.logger.warning(f"Couldn't get reading from {func_name}")
return
Copy link

Copilot AI Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Explicitly return None instead of relying on an implicit return for clarity and to match the documented return type.

Suggested change
return
return None

Copilot uses AI. Check for mistakes.
return NOMINAL()

def _avg_reading(
self, func: Callable[..., float | None], num_readings: int = 50
Copy link

Copilot AI Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider making the default num_readings configurable or reducing it to improve performance in time-sensitive contexts, as 50 sensor calls per measurement may be high.

Copilot uses AI. Check for mistakes.
Copy link
Member

@Mikefly123 Mikefly123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Sam!

These updates look great to me. Let's get this merged in!

It would be a little tricky to implement this on V4 boards, since not all of them will be connected to power monitors, but if you wanted to create a usage example for the v5x boards it would be pretty cool to see! We can tag up about this at the next software meeting or on Discord.

@yudataguy yudataguy merged commit 5635c23 into main Jul 3, 2025
5 checks passed
@yudataguy yudataguy deleted the new-state-of-health branch July 3, 2025 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TASK] Create a state_of_health Interface to Monitor Power Modes

5 participants