chassis-psu: New Functions for MultiChassis App

Implemented several functions for monitoring power supplies in
multi-chassis systems. Added a new main function, made minor
modification to the PowerSupply class, and added several functions to
the Chassis and ChassisManager classes.

The following is a summary of each object class new addition or modified
functions:

ChassisManager:
  - initChassisPowerMonitoring(): Loops through all the chassis in the
    system and initializes power monitoring process for each chassis's
    PSUs.

Chassis:
 - initPowerMonitoring(): Subscribe to D-Bus power change and initialize
   power monitoring.
 - supportedConfigurationInterfaceAdded(): Handle addition of supported
   configuration and update missing PSUs.
 - psuInterfaceAdded(): Handle addition of PSUs on D-Bus.
 - validatePsuConfigAndInterfacesProcessed(): Validate the PSU
   configuration and reset validation timer if power is on, supported
   configs and have PSUs.
 - analyzeBrownout(): Analyze PSUs for a brownout failure and log error.
 - syncHistory(): Toggles the GPIO to sync power supply input history
   readings.
 - setInputVoltageRating(): Inform each PSUs to set its PSU input.
 - createError(): Create OpenBMC error.
 - hasRequiredPSUs(): TODO
 - updateMissingPSUs(): Update PSU inventory.
 - getSupportedConfiguration(): Retrieve supported configuration from
   D-BUS and matches chassis ID with the  current chassis id to update
   chassis configuration.
 - saveChassisName(): Save chassis short name in the class.
 - powerStateChanged(): Handle for power state property changes.
 - attemptToCreatePowerConfigGPIO(): Attempt to create GPIO

 PowerSupply:
  - PowerSupply(): Added additional class constructors. The constructors
    have the same parameters as the original, except that the new
    constructor include an extra parameter chassis short name.
 - The PowerSupply class functions implementation remains the same as
   the original, except for minor change in
   PowerSupply::setupInputPowerPeakSensor(), where the sensorPath was
   modified to include chassis name.

Test in simulation:
  - Verified supported configuration added, where it polpulates
    supported properties and updates PSUs changes.
  - Validated some of the brownout functionality using fault injection.
  - Validated the PowerSupply class sets the appropriate input voltage
    for the target chassis PSUs.
  - Verified that the chassis name is included in the PSU power input
    peak sensors on D-bus.

Change-Id: I75a1ab1dd004767f072e35f3ce2c83ff281eb1ca
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/phosphor-power-supply/chassis.cpp b/phosphor-power-supply/chassis.cpp
index 85e2a88..4467db5 100644
--- a/phosphor-power-supply/chassis.cpp
+++ b/phosphor-power-supply/chassis.cpp
@@ -2,7 +2,6 @@
 
 #include "chassis.hpp"
 
-#include <filesystem>
 #include <iostream>
 
 using namespace phosphor::logging;
@@ -10,6 +9,8 @@
 namespace phosphor::power::chassis
 {
 
+constexpr auto powerSystemsInputsObjPath =
+    "/xyz/openbmc_project/power/power_supplies/chassis{}/psus";
 constexpr auto IBMCFFPSInterface =
     "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
 constexpr auto chassisIdProp = "SlotNumber";
@@ -25,17 +26,23 @@
 const auto entityMgrService = "xyz.openbmc_project.EntityManager";
 const auto decoratorChassisId = "xyz.openbmc_project.Inventory.Decorator.Slot";
 
+constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
+
 Chassis::Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath,
                  const sdeventplus::Event& e) :
     bus(bus), chassisPath(chassisPath),
-    chassisPathUniqueId(getChassisPathUniqueId(chassisPath)), eventLoop(e)
+    chassisPathUniqueId(getChassisPathUniqueId(chassisPath)),
+    powerSystemInputs(
+        bus, std::format(powerSystemsInputsObjPath, chassisPathUniqueId)),
+    eventLoop(e)
 {
+    saveChassisName();
     getPSUConfiguration();
+    getSupportedConfiguration();
 }
 
 void Chassis::getPSUConfiguration()
 {
-    namespace fs = std::filesystem;
     auto depth = 0;
 
     try
@@ -49,12 +56,7 @@
         auto connectorsSubTree = getSubTree(bus, "/", IBMCFFPSInterface, depth);
         for (const auto& [path, services] : connectorsSubTree)
         {
-            uint64_t id;
-            fs::path fspath(path);
-            getProperty(decoratorChassisId, chassisIdProp, fspath.parent_path(),
-                        entityMgrService, bus, id);
-            if (id == static_cast<uint64_t>(chassisPathUniqueId))
-
+            if (chassisPathUniqueId == getParentEMUniqueId(bus, path))
             {
                 // For each object in the array of objects, I want
                 // to get properties from the service, path, and
@@ -150,12 +152,10 @@
         lg2::debug(
             "make PowerSupply bus: {I2CBUS} addr: {I2CADDR} presline: {PRESLINE}",
             "I2CBUS", *i2cbus, "I2CADDR", *i2caddr, "PRESLINE", presline);
-        // auto psu = std::make_unique<PowerSupply>(
-        //    bus, invpath, *i2cbus, *i2caddr, driverName, presline,
-        //   std::bind(&Chassis::isPowerOn, this), chassisPath);
+
         auto psu = std::make_unique<PowerSupply>(
             bus, invpath, *i2cbus, *i2caddr, driverName, presline,
-            std::bind(&Chassis::isPowerOn, this));
+            std::bind(&Chassis::isPowerOn, this), chassisShortName);
         psus.emplace_back(std::move(psu));
 
         // Subscribe to power supply presence changes
@@ -178,7 +178,38 @@
 
 void Chassis::getSupportedConfiguration()
 {
-    // TBD
+    try
+    {
+        util::DbusSubtree subtree =
+            util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
+        if (subtree.empty())
+        {
+            throw std::runtime_error("Supported Configuration Not Found");
+        }
+
+        for (const auto& [objPath, services] : subtree)
+        {
+            std::string service = services.begin()->first;
+            if (objPath.empty() || service.empty())
+            {
+                continue;
+            }
+
+            if (chassisPathUniqueId == getParentEMUniqueId(bus, objPath))
+            {
+                auto properties = util::getAllProperties(
+                    bus, objPath, supportedConfIntf, service);
+                populateSupportedConfiguration(properties);
+                break;
+            }
+        }
+    }
+    catch (const std::exception& e)
+    {
+        // Interface or property not found. Let the Interfaces Added callback
+        // process the information once the interfaces are added to D-Bus.
+        lg2::info("Interface or Property not found, error {ERROR}", "ERROR", e);
+    }
 }
 
 void Chassis::populateSupportedConfiguration(
@@ -319,5 +350,608 @@
     return invalidObjectPathUniqueId;
 }
 
-void Chassis::analyze() {}
+void Chassis::initPowerMonitoring()
+{
+    using namespace sdeventplus;
+
+    validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
+        eventLoop, std::bind(&Chassis::validateConfig, this));
+    attemptToCreatePowerConfigGPIO();
+
+    // Subscribe to power state changes
+    powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
+    powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
+        bus,
+        sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
+                                                        POWER_IFACE),
+        [this](auto& msg) { this->powerStateChanged(msg); });
+    // TODO initialize the chassis
+}
+
+void Chassis::validateConfig()
+{
+    if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
+    {
+        return;
+    }
+
+    for (const auto& psu : psus)
+    {
+        if ((psu->hasInputFault() || psu->hasVINUVFault()) && psu->isPresent())
+        {
+            // Do not try to validate if input voltage fault present.
+            validationTimer->restartOnce(validationTimeout);
+            return;
+        }
+    }
+
+    std::map<std::string, std::string> additionalData;
+    auto supported = hasRequiredPSUs(additionalData);
+    if (supported)
+    {
+        runValidateConfig = false;
+        double actualVoltage;
+        int inputVoltage;
+        int previousInputVoltage = 0;
+        bool voltageMismatch = false;
+
+        for (const auto& psu : psus)
+        {
+            if (!psu->isPresent())
+            {
+                // Only present PSUs report a valid input voltage
+                continue;
+            }
+            psu->getInputVoltage(actualVoltage, inputVoltage);
+            if (previousInputVoltage && inputVoltage &&
+                (previousInputVoltage != inputVoltage))
+            {
+                additionalData["EXPECTED_VOLTAGE"] =
+                    std::to_string(previousInputVoltage);
+                additionalData["ACTUAL_VOLTAGE"] =
+                    std::to_string(actualVoltage);
+                voltageMismatch = true;
+            }
+            if (!previousInputVoltage && inputVoltage)
+            {
+                previousInputVoltage = inputVoltage;
+            }
+        }
+        if (!voltageMismatch)
+        {
+            return;
+        }
+    }
+
+    // Validation failed, create an error log.
+    // Return without setting the runValidateConfig flag to false because
+    // it may be that an additional supported configuration interface is
+    // added and we need to validate it to see if it matches this system.
+    createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
+                additionalData);
+}
+
+void Chassis::syncHistory()
+{
+    if (driverName != ACBEL_FSG032_DD_NAME)
+    {
+        if (!syncHistoryGPIO)
+        {
+            try
+            {
+                syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
+            }
+            catch (const std::exception& e)
+            {
+                // Not an error, system just hasn't implemented the synch gpio
+                lg2::info("No synchronization GPIO found");
+                syncHistoryGPIO = nullptr;
+            }
+        }
+        if (syncHistoryGPIO)
+        {
+            const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
+            lg2::info("Synchronize INPUT_HISTORY");
+            syncHistoryGPIO->toggleLowHigh(delay);
+            lg2::info("Synchronize INPUT_HISTORY completed");
+        }
+    }
+
+    // Always clear synch history required after calling this function
+    for (auto& psu : psus)
+    {
+        psu->clearSyncHistoryRequired();
+    }
+}
+
+void Chassis::analyze()
+{
+    auto syncHistoryRequired =
+        std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
+            return psu->isSyncHistoryRequired();
+        });
+    if (syncHistoryRequired)
+    {
+        syncHistory();
+    }
+
+    for (auto& psu : psus)
+    {
+        psu->analyze();
+    }
+
+    analyzeBrownout();
+
+    // Only perform individual PSU analysis if power is on and a brownout has
+    // not already been logged
+    //
+    // Note: TODO Check the chassis state when the power sequencer publishes
+    // chassis power on and system power on
+    if (powerOn && !brownoutLogged)
+    {
+        for (auto& psu : psus)
+        {
+            std::map<std::string, std::string> additionalData;
+
+            if (!psu->isFaultLogged() && !psu->isPresent() &&
+                !validationTimer->isEnabled())
+            {
+                std::map<std::string, std::string> requiredPSUsData;
+                auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
+                // TODO check required PSU
+
+                if (!requiredPSUsPresent)
+                {
+                    additionalData.merge(requiredPSUsData);
+                    // Create error for power supply missing.
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+                    additionalData["CALLOUT_PRIORITY"] = "H";
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
+                        additionalData);
+                }
+                psu->setFaultLogged();
+            }
+            else if (!psu->isFaultLogged() && psu->isFaulted())
+            {
+                // Add STATUS_WORD and STATUS_MFR last response, in padded
+                // hexadecimal format.
+                additionalData["STATUS_WORD"] =
+                    std::format("{:#04x}", psu->getStatusWord());
+                additionalData["STATUS_MFR"] =
+                    std::format("{:#02x}", psu->getMFRFault());
+                // If there are faults being reported, they possibly could be
+                // related to a bug in the firmware version running on the power
+                // supply. Capture that data into the error as well.
+                additionalData["FW_VERSION"] = psu->getFWVersion();
+
+                if (psu->hasCommFault())
+                {
+                    additionalData["STATUS_CML"] =
+                        std::format("{:#02x}", psu->getStatusCML());
+                    /* Attempts to communicate with the power supply have
+                     * reached there limit. Create an error. */
+                    additionalData["CALLOUT_DEVICE_PATH"] =
+                        psu->getDevicePath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if ((psu->hasInputFault() || psu->hasVINUVFault()))
+                {
+                    // Include STATUS_INPUT for input faults.
+                    additionalData["STATUS_INPUT"] =
+                        std::format("{:#02x}", psu->getStatusInput());
+
+                    /* The power supply location might be needed if the input
+                     * fault is due to a problem with the power supply itself.
+                     * Include the inventory path with a call out priority of
+                     * low.
+                     */
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+                    additionalData["CALLOUT_PRIORITY"] = "L";
+                    createError("xyz.openbmc_project.Power.PowerSupply.Error."
+                                "InputFault",
+                                additionalData);
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasPSKillFault())
+                {
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
+                        additionalData);
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasVoutOVFault())
+                {
+                    // Include STATUS_VOUT for Vout faults.
+                    additionalData["STATUS_VOUT"] =
+                        std::format("{:#02x}", psu->getStatusVout());
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasIoutOCFault())
+                {
+                    // Include STATUS_IOUT for Iout faults.
+                    additionalData["STATUS_IOUT"] =
+                        std::format("{:#02x}", psu->getStatusIout());
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
+                         psu->hasPSCS12VFault())
+                {
+                    // Include STATUS_VOUT for Vout faults.
+                    additionalData["STATUS_VOUT"] =
+                        std::format("{:#02x}", psu->getStatusVout());
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                // A fan fault should have priority over a temperature fault,
+                // since a failed fan may lead to a temperature problem.
+                // Only process if not in power fault window.
+                else if (psu->hasFanFault() && !powerFaultOccurring)
+                {
+                    // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
+                    additionalData["STATUS_TEMPERATURE"] =
+                        std::format("{:#02x}", psu->getStatusTemperature());
+                    additionalData["STATUS_FANS_1_2"] =
+                        std::format("{:#02x}", psu->getStatusFans12());
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasTempFault())
+                {
+                    // Include STATUS_TEMPERATURE for temperature faults.
+                    additionalData["STATUS_TEMPERATURE"] =
+                        std::format("{:#02x}", psu->getStatusTemperature());
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasMFRFault())
+                {
+                    /* This can represent a variety of faults that result in
+                     * calling out the power supply for replacement: Output
+                     * OverCurrent, Output Under Voltage, and potentially other
+                     * faults.
+                     *
+                     * Also plan on putting specific fault in AdditionalData,
+                     * along with register names and register values
+                     * (STATUS_WORD, STATUS_MFR, etc.).*/
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                // Only process if not in power fault window.
+                else if (psu->hasPgoodFault() && !powerFaultOccurring)
+                {
+                    /* POWER_GOOD# is not low, or OFF is on */
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+            }
+        }
+    }
+}
+
+void Chassis::analyzeBrownout()
+{
+    // Count number of power supplies failing
+    size_t presentCount = 0;
+    size_t notPresentCount = 0;
+    size_t acFailedCount = 0;
+    size_t pgoodFailedCount = 0;
+    for (const auto& psu : psus)
+    {
+        if (psu->isPresent())
+        {
+            ++presentCount;
+            if (psu->hasACFault())
+            {
+                ++acFailedCount;
+            }
+            else if (psu->hasPgoodFault())
+            {
+                ++pgoodFailedCount;
+            }
+        }
+        else
+        {
+            ++notPresentCount;
+        }
+    }
+
+    // Only issue brownout failure if chassis pgood has failed, it has not
+    // already been logged, at least one PSU has seen an AC fail, and all
+    // present PSUs have an AC or pgood failure. Note an AC fail is only set if
+    // at least one PSU is present.
+    if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
+        (presentCount == (acFailedCount + pgoodFailedCount)))
+    {
+        //  Indicate that the system is in a brownout condition by creating an
+        //  error log and setting the PowerSystemInputs status property to
+        //  Fault.
+        powerSystemInputs.status(
+            sdbusplus::xyz::openbmc_project::State::Decorator::server::
+                PowerSystemInputs::Status::Fault);
+
+        std::map<std::string, std::string> additionalData;
+        additionalData.emplace("NOT_PRESENT_COUNT",
+                               std::to_string(notPresentCount));
+        additionalData.emplace("VIN_FAULT_COUNT",
+                               std::to_string(acFailedCount));
+        additionalData.emplace("PGOOD_FAULT_COUNT",
+                               std::to_string(pgoodFailedCount));
+        lg2::info(
+            "Brownout detected, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
+            "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
+            acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
+
+        createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
+                    additionalData);
+        brownoutLogged = true;
+    }
+    else
+    {
+        // If a brownout was previously logged but at least one PSU is not
+        // currently in AC fault, determine if the brownout condition can be
+        // cleared
+        if (brownoutLogged && (acFailedCount < presentCount))
+        {
+            // TODO Power State
+        }
+    }
+}
+
+void Chassis::createError(const std::string& faultName,
+                          std::map<std::string, std::string>& additionalData)
+{
+    using namespace sdbusplus::xyz::openbmc_project;
+    constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
+    constexpr auto loggingCreateInterface =
+        "xyz.openbmc_project.Logging.Create";
+
+    try
+    {
+        additionalData["_PID"] = std::to_string(getpid());
+
+        auto service =
+            util::getService(loggingObjectPath, loggingCreateInterface, bus);
+
+        if (service.empty())
+        {
+            lg2::error("Unable to get logging manager service");
+            return;
+        }
+
+        auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
+                                          loggingCreateInterface, "Create");
+
+        auto level = Logging::server::Entry::Level::Error;
+        method.append(faultName, level, additionalData);
+
+        auto reply = bus.call(method);
+        // TODO set power supply error
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Failed creating event log for fault {FAULT_NAME} due to error {ERROR}",
+            "FAULT_NAME", faultName, "ERROR", e);
+    }
+}
+
+void Chassis::attemptToCreatePowerConfigGPIO()
+{
+    try
+    {
+        powerConfigGPIO = createGPIO("power-config-full-load");
+    }
+    catch (const std::exception& e)
+    {
+        powerConfigGPIO = nullptr;
+        lg2::info("GPIO not implemented in {CHASSIS}", "CHASSIS",
+                  chassisShortName);
+    }
+}
+
+void Chassis::supportedConfigurationInterfaceAdded(
+    const util::DbusPropertyMap& properties)
+{
+    populateSupportedConfiguration(properties);
+    updateMissingPSUs();
+}
+
+void Chassis::psuInterfaceAdded(util::DbusPropertyMap& properties)
+{
+    getPSUProperties(properties);
+    updateMissingPSUs();
+}
+
+bool Chassis::hasRequiredPSUs(
+    std::map<std::string, std::string>& additionalData)
+{
+    // ignore the following loop so code will compile
+    for (const auto& pair : additionalData)
+    {
+        std::cout << "Key = " << pair.first
+                  << " additionalData value = " << pair.second << "\n";
+    }
+    return true;
+
+    // TODO validate having the required PSUs
+}
+
+void Chassis::updateMissingPSUs()
+{
+    if (supportedConfigs.empty() || psus.empty())
+    {
+        return;
+    }
+
+    // Power supplies default to missing. If the power supply is present,
+    // the PowerSupply object will update the inventory Present property to
+    // true. If we have less than the required number of power supplies, and
+    // this power supply is missing, update the inventory Present property
+    // to false to indicate required power supply is missing. Avoid
+    // indicating power supply missing if not required.
+
+    auto presentCount =
+        std::count_if(psus.begin(), psus.end(),
+                      [](const auto& psu) { return psu->isPresent(); });
+
+    for (const auto& config : supportedConfigs)
+    {
+        for (const auto& psu : psus)
+        {
+            auto psuModel = psu->getModelName();
+            auto psuShortName = psu->getShortName();
+            auto psuInventoryPath = psu->getInventoryPath();
+            auto relativeInvPath =
+                psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+            auto psuPresent = psu->isPresent();
+            auto presProperty = false;
+            auto propReadFail = false;
+
+            try
+            {
+                presProperty = getPresence(bus, psuInventoryPath);
+                propReadFail = false;
+            }
+            catch (const sdbusplus::exception_t& e)
+            {
+                propReadFail = true;
+                // Relying on property change or interface added to retry.
+                // Log an informational trace to the journal.
+                lg2::info(
+                    "D-Bus property {PSU_INVENTORY_PATH} access failure exception",
+                    "PSU_INVENTORY_PATH", psuInventoryPath);
+            }
+
+            if (psuModel.empty())
+            {
+                if (!propReadFail && (presProperty != psuPresent))
+                {
+                    // We already have this property, and it is not false
+                    // set Present to false
+                    setPresence(bus, relativeInvPath, psuPresent, psuShortName);
+                }
+                continue;
+            }
+
+            if (config.first != psuModel)
+            {
+                continue;
+            }
+
+            if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
+            {
+                setPresence(bus, relativeInvPath, psuPresent, psuShortName);
+            }
+        }
+    }
+}
+
+void Chassis::powerStateChanged(sdbusplus::message_t& msg)
+{
+    std::string msgSensor;
+    std::map<std::string, std::variant<int>> msgData;
+    msg.read(msgSensor, msgData);
+
+    // Check if it was the state property that changed.
+    auto valPropMap = msgData.find("state");
+    if (valPropMap != msgData.end())
+    {
+        int state = std::get<int>(valPropMap->second);
+        if (state)
+        {
+            // Power on requested
+            powerOn = true;
+            powerFaultOccurring = false;
+            validationTimer->restartOnce(validationTimeout);
+            // TODO clear faults
+
+            syncHistory();
+            // TODO set power config
+
+            setInputVoltageRating();
+        }
+        else
+        {
+            // Power off requested
+            powerOn = false;
+            powerFaultOccurring = false;
+            runValidateConfig = true;
+        }
+    }
+
+    // Check if it was the pgood property that changed.
+    valPropMap = msgData.find("pgood");
+    if (valPropMap != msgData.end())
+    {
+        int pgood = std::get<int>(valPropMap->second);
+        if (!pgood)
+        {
+            // Chassis power good has turned off
+            if (powerOn)
+            {
+                // pgood is off but state is on, in power fault window
+                powerFaultOccurring = true;
+            }
+        }
+    }
+    lg2::info(
+        "powerStateChanged: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
+        "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
+}
+
 } // namespace phosphor::power::chassis