#include "config.h"

#include "chassis_state_manager.hpp"

#include "utils.hpp"
#include "xyz/openbmc_project/Common/error.hpp"
#include "xyz/openbmc_project/State/Shutdown/Power/error.hpp"

#include <cereal/archives/json.hpp>
#include <org/freedesktop/UPower/Device/client.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/lg2.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/exception.hpp>
#include <sdeventplus/event.hpp>
#include <sdeventplus/exception.hpp>
#include <xyz/openbmc_project/ObjectMapper/client.hpp>
#include <xyz/openbmc_project/State/Chassis/error.hpp>
#include <xyz/openbmc_project/State/Decorator/PowerSystemInputs/server.hpp>

#include <filesystem>
#include <format>
#include <fstream>

namespace phosphor
{
namespace state
{
namespace manager
{

PHOSPHOR_LOG2_USING;

// When you see server:: you know we're referencing our base class
namespace server = sdbusplus::server::xyz::openbmc_project::state;
namespace decoratorServer =
    sdbusplus::server::xyz::openbmc_project::state::decorator;

using ObjectMapper = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>;
using UPowerDevice = sdbusplus::client::org::freedesktop::u_power::Device<>;

using namespace phosphor::logging;
using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Blackout;
using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Regulator;
constexpr auto CHASSIS_STATE_POWEROFF_TGT_FMT =
    "obmc-chassis-poweroff@{}.target";
constexpr auto CHASSIS_STATE_HARD_POWEROFF_TGT_FMT =
    "obmc-chassis-hard-poweroff@{}.target";
constexpr auto CHASSIS_STATE_POWERON_TGT_FMT = "obmc-chassis-poweron@{}.target";
constexpr auto CHASSIS_BLACKOUT_TGT_FMT = "obmc-chassis-blackout@{}.target";
constexpr auto CHASSIS_STATE_POWERCYCLE_TGT_FMT =
    "obmc-chassis-powercycle@{}.target";
constexpr auto AUTO_POWER_RESTORE_SVC_FMT =
    "phosphor-discover-system-state@{}.service";
constexpr auto ACTIVE_STATE = "active";
constexpr auto ACTIVATING_STATE = "activating";

// Details at https://upower.freedesktop.org/docs/Device.html
constexpr uint TYPE_UPS = 3;
constexpr uint STATE_FULLY_CHARGED = 4;
constexpr uint BATTERY_LVL_FULL = 8;

constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";

constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit";

constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";

void Chassis::createSystemdTargetTable()
{
    systemdTargetTable = {
        // Use the hard off target to ensure we shutdown immediately
        {Transition::Off, std::format(CHASSIS_STATE_HARD_POWEROFF_TGT_FMT, id)},
        {Transition::On, std::format(CHASSIS_STATE_POWERON_TGT_FMT, id)},
        {Transition::PowerCycle,
         std::format(CHASSIS_STATE_POWERCYCLE_TGT_FMT, id)}};
}

// TODO - Will be rewritten once sdbusplus client bindings are in place
//        and persistent storage design is in place and sdbusplus
//        has read property function
void Chassis::determineInitialState()
{
    // Monitor for any properties changed signals on UPower device path
    uPowerPropChangeSignal = std::make_unique<sdbusplus::bus::match_t>(
        bus,
        sdbusplus::bus::match::rules::propertiesChangedNamespace(
            "/org/freedesktop/UPower", UPowerDevice::interface),
        [this](auto& msg) { this->uPowerChangeEvent(msg); });

    // Monitor for any properties changed signals on PowerSystemInputs
    powerSysInputsPropChangeSignal = std::make_unique<sdbusplus::bus::match_t>(
        bus,
        sdbusplus::bus::match::rules::propertiesChangedNamespace(
            std::format(
                "/xyz/openbmc_project/power/power_supplies/chassis{}/psus", id),
            decoratorServer::PowerSystemInputs::interface),
        [this](auto& msg) { this->powerSysInputsChangeEvent(msg); });

    determineStatusOfPower();

    std::variant<int> pgood = -1;
    auto method = this->bus.new_method_call(
        "org.openbmc.control.Power", "/org/openbmc/control/power0",
        "org.freedesktop.DBus.Properties", "Get");

    method.append("org.openbmc.control.Power", "pgood");
    try
    {
        auto reply = this->bus.call(method);
        reply.read(pgood);

        if (std::get<int>(pgood) == 1)
        {
            info("Initial Chassis State will be On");
            server::Chassis::currentPowerState(PowerState::On);
            server::Chassis::requestedPowerTransition(Transition::On);
            return;
        }
        else
        {
            // The system is off.  If we think it should be on then
            // we probably lost AC while up, so set a new state
            // change time.
            uint64_t lastTime;
            PowerState lastState;

            if (deserializeStateChangeTime(lastTime, lastState))
            {
                // If power was on before the BMC reboot and the reboot reason
                // was not a pinhole reset, log an error
                if (lastState == PowerState::On)
                {
                    info(
                        "Chassis power was on before the BMC reboot and it is off now");

                    // Reset host sensors since system is off now
                    // Ensure Power Leds are off.
                    startUnit(std::format(CHASSIS_BLACKOUT_TGT_FMT, id));

                    setStateChangeTime();
                    // Generate file indicating AC loss occurred
                    std::string chassisLostPowerFileFmt =
                        std::format(CHASSIS_LOST_POWER_FILE, id);
                    fs::create_directories(BASE_FILE_DIR);
                    fs::path chassisPowerLossFile{chassisLostPowerFileFmt};
                    std::ofstream outfile(chassisPowerLossFile);
                    outfile.close();

                    // 0 indicates pinhole reset. 1 is NOT pinhole reset
                    if (phosphor::state::manager::utils::getGpioValue(
                            "reset-cause-pinhole") != 0)
                    {
                        if (standbyVoltageRegulatorFault())
                        {
                            report<Regulator>();
                        }
                        else
                        {
                            report<Blackout>(Entry::Level::Critical);
                        }
                    }
                    else
                    {
                        info("Pinhole reset");
                    }
                }
            }
        }
    }
    catch (const sdbusplus::exception_t& e)
    {
        // It's acceptable for the pgood state service to not be available
        // since it will notify us of the pgood state when it comes up.
        if (e.name() != nullptr &&
            strcmp("org.freedesktop.DBus.Error.ServiceUnknown", e.name()) == 0)
        {
            goto fail;
        }

        // Only log for unexpected error types.
        error("Error performing call to get pgood: {ERROR}", "ERROR", e);
        goto fail;
    }

fail:
    info("Initial Chassis State will be Off");
    server::Chassis::currentPowerState(PowerState::Off);
    server::Chassis::requestedPowerTransition(Transition::Off);

    return;
}

void Chassis::determineStatusOfPower()
{
    auto initialPowerStatus = server::Chassis::currentPowerStatus();

    bool powerGood = determineStatusOfUPSPower();
    if (!powerGood)
    {
        return;
    }

    powerGood = determineStatusOfPSUPower();
    if (powerGood)
    {
        // All checks passed, set power status to good
        server::Chassis::currentPowerStatus(PowerStatus::Good);

        // If power status transitioned from bad to good and chassis power is
        // off then call Auto Power Restart to see if the system should auto
        // power on now that power status is good
        if ((initialPowerStatus != PowerStatus::Good) &&
            (server::Chassis::currentPowerState() == PowerState::Off))
        {
            info("power status transitioned from {START_PWR_STATE} to Good and "
                 "chassis power is off, calling APR",
                 "START_PWR_STATE", initialPowerStatus);
            restartUnit(std::format(AUTO_POWER_RESTORE_SVC_FMT, this->id));
        }
    }
}

bool Chassis::determineStatusOfUPSPower()
{
    // Find all implementations of the UPower interface
    auto mapper = bus.new_method_call(ObjectMapper::default_service,
                                      ObjectMapper::instance_path,
                                      ObjectMapper::interface, "GetSubTree");

    mapper.append("/", 0, std::vector<std::string>({UPowerDevice::interface}));

    std::map<std::string, std::map<std::string, std::vector<std::string>>>
        mapperResponse;

    try
    {
        auto mapperResponseMsg = bus.call(mapper);
        mapperResponseMsg.read(mapperResponse);
    }
    catch (const sdbusplus::exception_t& e)
    {
        error("Error in mapper GetSubTree call for UPS: {ERROR}", "ERROR", e);
        throw;
    }

    if (mapperResponse.empty())
    {
        debug("No UPower devices found in system");
    }

    // Iterate through all returned Upower interfaces and look for UPS's
    for (const auto& [path, services] : mapperResponse)
    {
        for (const auto& serviceIter : services)
        {
            const std::string& service = serviceIter.first;

            try
            {
                auto method = bus.new_method_call(service.c_str(), path.c_str(),
                                                  PROPERTY_INTERFACE, "GetAll");
                method.append(UPowerDevice::interface);

                auto response = bus.call(method);
                using Property = std::string;
                using Value = std::variant<bool, uint>;
                using PropertyMap = std::map<Property, Value>;
                PropertyMap properties;
                response.read(properties);

                if (std::get<uint>(properties["Type"]) != TYPE_UPS)
                {
                    info("UPower device {OBJ_PATH} is not a UPS device",
                         "OBJ_PATH", path);
                    continue;
                }

                if (!std::get<bool>(properties["IsPresent"]))
                {
                    // There is a UPS detected but it is not officially
                    // "present" yet. Monitor it for state change.
                    info("UPower device {OBJ_PATH} is not present", "OBJ_PATH",
                         path);
                    continue;
                }

                if (std::get<uint>(properties["State"]) == STATE_FULLY_CHARGED)
                {
                    info("UPS is fully charged");
                }
                else
                {
                    info("UPS is not fully charged: {UPS_STATE}", "UPS_STATE",
                         std::get<uint>(properties["State"]));
                    server::Chassis::currentPowerStatus(
                        PowerStatus::UninterruptiblePowerSupply);
                    return false;
                }

                if (std::get<uint>(properties["BatteryLevel"]) ==
                    BATTERY_LVL_FULL)
                {
                    info("UPS Battery Level is Full");
                    // Only one UPS per system, we've found it and it's all
                    // good so exit function
                    return true;
                }
                else
                {
                    info("UPS Battery Level is Low: {UPS_BAT_LEVEL}",
                         "UPS_BAT_LEVEL",
                         std::get<uint>(properties["BatteryLevel"]));
                    server::Chassis::currentPowerStatus(
                        PowerStatus::UninterruptiblePowerSupply);
                    return false;
                }
            }
            catch (const sdbusplus::exception_t& e)
            {
                error("Error reading UPS property, error: {ERROR}, "
                      "service: {SERVICE} path: {PATH}",
                      "ERROR", e, "SERVICE", service, "PATH", path);
                throw;
            }
        }
    }
    return true;
}

bool Chassis::determineStatusOfPSUPower()
{
    // Find all implementations of the PowerSystemInputs interface
    auto mapper = bus.new_method_call(ObjectMapper::default_service,
                                      ObjectMapper::instance_path,
                                      ObjectMapper::interface, "GetSubTree");

    mapper.append("/", 0,
                  std::vector<std::string>(
                      {decoratorServer::PowerSystemInputs::interface}));

    std::map<std::string, std::map<std::string, std::vector<std::string>>>
        mapperResponse;

    try
    {
        auto mapperResponseMsg = bus.call(mapper);
        mapperResponseMsg.read(mapperResponse);
    }
    catch (const sdbusplus::exception_t& e)
    {
        error("Error in mapper GetSubTree call for PowerSystemInputs: {ERROR}",
              "ERROR", e);
        throw;
    }

    for (const auto& [path, services] : mapperResponse)
    {
        for (const auto& serviceIter : services)
        {
            const std::string& service = serviceIter.first;

            try
            {
                auto method = bus.new_method_call(service.c_str(), path.c_str(),
                                                  PROPERTY_INTERFACE, "GetAll");
                method.append(decoratorServer::PowerSystemInputs::interface);

                auto response = bus.call(method);
                using Property = std::string;
                using Value = std::variant<std::string>;
                using PropertyMap = std::map<Property, Value>;
                PropertyMap properties;
                response.read(properties);

                auto statusStr = std::get<std::string>(properties["Status"]);
                auto status =
                    decoratorServer::PowerSystemInputs::convertStatusFromString(
                        statusStr);

                if (status == decoratorServer::PowerSystemInputs::Status::Fault)
                {
                    info("Power System Inputs status is in Fault state");
                    server::Chassis::currentPowerStatus(PowerStatus::BrownOut);
                    return false;
                }
            }
            catch (const sdbusplus::exception_t& e)
            {
                error(
                    "Error reading Power System Inputs property, error: {ERROR}, "
                    "service: {SERVICE} path: {PATH}",
                    "ERROR", e, "SERVICE", service, "PATH", path);
                throw;
            }
        }
    }
    return true;
}

void Chassis::uPowerChangeEvent(sdbusplus::message_t& msg)
{
    debug("UPS Property Change Event Triggered");
    std::string statusInterface;
    std::map<std::string, std::variant<uint, bool>> msgData;
    msg.read(statusInterface, msgData);

    // If the change is to any of the properties we are interested in, then call
    // determineStatusOfPower(), which looks at all the power-related
    // interfaces, to see if a power status change is needed
    auto propertyMap = msgData.find("IsPresent");
    if (propertyMap != msgData.end())
    {
        info("UPS presence changed to {UPS_PRES_INFO}", "UPS_PRES_INFO",
             std::get<bool>(propertyMap->second));
        determineStatusOfPower();
        return;
    }

    propertyMap = msgData.find("State");
    if (propertyMap != msgData.end())
    {
        info("UPS State changed to {UPS_STATE}", "UPS_STATE",
             std::get<uint>(propertyMap->second));
        determineStatusOfPower();
        return;
    }

    propertyMap = msgData.find("BatteryLevel");
    if (propertyMap != msgData.end())
    {
        info("UPS BatteryLevel changed to {UPS_BAT_LEVEL}", "UPS_BAT_LEVEL",
             std::get<uint>(propertyMap->second));
        determineStatusOfPower();
        return;
    }
    return;
}

void Chassis::powerSysInputsChangeEvent(sdbusplus::message_t& msg)
{
    debug("Power System Inputs Property Change Event Triggered");
    std::string statusInterface;
    std::map<std::string, std::variant<std::string>> msgData;
    msg.read(statusInterface, msgData);

    // If the change is to any of the properties we are interested in, then call
    // determineStatusOfPower(), which looks at all the power-related
    // interfaces, to see if a power status change is needed
    auto propertyMap = msgData.find("Status");
    if (propertyMap != msgData.end())
    {
        info("Power System Inputs status changed to {POWER_SYS_INPUT_STATUS}",
             "POWER_SYS_INPUT_STATUS",
             std::get<std::string>(propertyMap->second));
        determineStatusOfPower();
        return;
    }
    return;
}

void Chassis::startUnit(const std::string& sysdUnit)
{
    auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
                                            SYSTEMD_INTERFACE, "StartUnit");

    method.append(sysdUnit);
    method.append("replace");

    this->bus.call_noreply(method);

    return;
}

void Chassis::restartUnit(const std::string& sysdUnit)
{
    auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
                                            SYSTEMD_INTERFACE, "RestartUnit");

    method.append(sysdUnit);
    method.append("replace");

    this->bus.call_noreply(method);

    return;
}

bool Chassis::stateActive(const std::string& target)
{
    std::variant<std::string> currentState;
    sdbusplus::message::object_path unitTargetPath;

    auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
                                            SYSTEMD_INTERFACE, "GetUnit");

    method.append(target);

    try
    {
        auto result = this->bus.call(method);
        result.read(unitTargetPath);
    }
    catch (const sdbusplus::exception_t& e)
    {
        error("Error in GetUnit call: {ERROR}", "ERROR", e);
        return false;
    }

    method = this->bus.new_method_call(
        SYSTEMD_SERVICE,
        static_cast<const std::string&>(unitTargetPath).c_str(),
        SYSTEMD_PROPERTY_IFACE, "Get");

    method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState");

    try
    {
        auto result = this->bus.call(method);
        result.read(currentState);
    }
    catch (const sdbusplus::exception_t& e)
    {
        error("Error in ActiveState Get: {ERROR}", "ERROR", e);
        return false;
    }

    const auto& currentStateStr = std::get<std::string>(currentState);
    return currentStateStr == ACTIVE_STATE ||
           currentStateStr == ACTIVATING_STATE;
}

int Chassis::sysStateChange(sdbusplus::message_t& msg)
{
    sdbusplus::message::object_path newStateObjPath;
    std::string newStateUnit{};
    std::string newStateResult{};

    // Read the msg and populate each variable
    try
    {
        // newStateID is a throwaway that is needed in order to read the
        // parameters that are useful out of the dbus message
        uint32_t newStateID{};
        msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
    }
    catch (const sdbusplus::exception_t& e)
    {
        error("Error in state change - bad encoding: {ERROR} {REPLY_SIG}",
              "ERROR", e, "REPLY_SIG", msg.get_signature());
        return 0;
    }

    if ((newStateUnit == std::format(CHASSIS_STATE_POWEROFF_TGT_FMT, id)) &&
        (newStateResult == "done") &&
        (!stateActive(systemdTargetTable[Transition::On])))
    {
        info("Received signal that power OFF is complete");
        this->currentPowerState(server::Chassis::PowerState::Off);
        this->setStateChangeTime();
    }
    else if ((newStateUnit == systemdTargetTable[Transition::On]) &&
             (newStateResult == "done") &&
             (stateActive(systemdTargetTable[Transition::On])))
    {
        info("Received signal that power ON is complete");
        this->currentPowerState(server::Chassis::PowerState::On);
        this->setStateChangeTime();

        // Remove temporary file which is utilized for scenarios where the
        // BMC is rebooted while the chassis power is still on.
        // This file is used to indicate to chassis related systemd services
        // that the chassis is already on and they should skip running.
        // Once the chassis state is back to on we can clear this file.
        auto chassisFile = std::format(CHASSIS_ON_FILE, 0);
        if (std::filesystem::exists(chassisFile))
        {
            std::filesystem::remove(chassisFile);
        }
    }

    return 0;
}

Chassis::Transition Chassis::requestedPowerTransition(Transition value)
{
    info("Change to Chassis Requested Power State: {REQ_POWER_TRAN}",
         "REQ_POWER_TRAN", value);
#if ONLY_ALLOW_BOOT_WHEN_BMC_READY
    if ((value != Transition::Off) && (!utils::isBmcReady(this->bus)))
    {
        info("BMC State is not Ready so no chassis on operations allowed");
        throw sdbusplus::xyz::openbmc_project::State::Chassis::Error::
            BMCNotReady();
    }
#endif

#ifdef CHECK_FWUPDATE_BEFORE_DO_TRANSITION
    /*
     * Do not do transition when the any firmware being updated
     */
    if ((value != Transition::Off) &&
        (phosphor::state::manager::utils::isFirmwareUpdating(this->bus)))
    {
        info("Firmware being updated, reject the transition request");
        throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
    }
#endif // CHECK_FWUPDATE_BEFORE_DO_TRANSITION

    startUnit(systemdTargetTable.find(value)->second);
    return server::Chassis::requestedPowerTransition(value);
}

Chassis::PowerState Chassis::currentPowerState(PowerState value)
{
    PowerState chassisPowerState;
    info("Change to Chassis Power State: {CUR_POWER_STATE}", "CUR_POWER_STATE",
         value);

    chassisPowerState = server::Chassis::currentPowerState(value);
    if (chassisPowerState == PowerState::On)
    {
        pohTimer.resetRemaining();
    }
    return chassisPowerState;
}

uint32_t Chassis::pohCounter(uint32_t value)
{
    if (value != pohCounter())
    {
        ChassisInherit::pohCounter(value);
        serializePOH();
    }
    return pohCounter();
}

void Chassis::pohCallback()
{
    if (ChassisInherit::currentPowerState() == PowerState::On)
    {
        pohCounter(pohCounter() + 1);
    }
}

void Chassis::restorePOHCounter()
{
    uint32_t counter;
    if (!deserializePOH(counter))
    {
        // set to default value
        pohCounter(0);
    }
    else
    {
        pohCounter(counter);
    }
}

fs::path Chassis::serializePOH()
{
    fs::path path{std::format(POH_COUNTER_PERSIST_PATH, id)};
    std::ofstream os(path.c_str(), std::ios::binary);
    cereal::JSONOutputArchive oarchive(os);
    oarchive(pohCounter());
    return path;
}

bool Chassis::deserializePOH(uint32_t& pohCounter)
{
    fs::path path{std::format(POH_COUNTER_PERSIST_PATH, id)};
    try
    {
        if (fs::exists(path))
        {
            std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
            cereal::JSONInputArchive iarchive(is);
            iarchive(pohCounter);
            return true;
        }
        return false;
    }
    catch (const cereal::Exception& e)
    {
        error("deserialize exception: {ERROR}", "ERROR", e);
        fs::remove(path);
        return false;
    }
    catch (const fs::filesystem_error& e)
    {
        return false;
    }

    return false;
}

void Chassis::startPOHCounter()
{
    auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path();
    fs::create_directories(dir);

    try
    {
        auto event = sdeventplus::Event::get_default();
        bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
        event.loop();
    }
    catch (const sdeventplus::SdEventError& e)
    {
        error("Error occurred during the sdeventplus loop: {ERROR}", "ERROR",
              e);
        phosphor::logging::commit<InternalFailure>();
    }
}

void Chassis::serializeStateChangeTime()
{
    fs::path path{std::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)};
    std::ofstream os(path.c_str(), std::ios::binary);
    cereal::JSONOutputArchive oarchive(os);

    oarchive(ChassisInherit::lastStateChangeTime(),
             ChassisInherit::currentPowerState());
}

bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state)
{
    fs::path path{std::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)};

    try
    {
        if (fs::exists(path))
        {
            std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
            cereal::JSONInputArchive iarchive(is);
            iarchive(time, state);
            return true;
        }
    }
    catch (const std::exception& e)
    {
        error("deserialize exception: {ERROR}", "ERROR", e);
        fs::remove(path);
    }

    return false;
}

void Chassis::restoreChassisStateChangeTime()
{
    uint64_t time;
    PowerState state;

    if (!deserializeStateChangeTime(time, state))
    {
        ChassisInherit::lastStateChangeTime(0);
    }
    else
    {
        ChassisInherit::lastStateChangeTime(time);
    }
}

void Chassis::setStateChangeTime()
{
    using namespace std::chrono;
    uint64_t lastTime;
    PowerState lastState;

    auto now =
        duration_cast<milliseconds>(system_clock::now().time_since_epoch())
            .count();

    // If power is on when the BMC is rebooted, this function will get called
    // because sysStateChange() runs.  Since the power state didn't change
    // in this case, neither should the state change time, so check that
    // the power state actually did change here.
    if (deserializeStateChangeTime(lastTime, lastState))
    {
        if (lastState == ChassisInherit::currentPowerState())
        {
            return;
        }
    }

    ChassisInherit::lastStateChangeTime(now);
    serializeStateChangeTime();
}

bool Chassis::standbyVoltageRegulatorFault()
{
    bool regulatorFault = false;

    // find standby voltage regulator fault via gpiog

    auto gpioval = utils::getGpioValue("regulator-standby-faulted");

    if (-1 == gpioval)
    {
        error("Failed reading regulator-standby-faulted GPIO");
    }

    if (1 == gpioval)
    {
        info("Detected standby voltage regulator fault");
        regulatorFault = true;
    }

    return regulatorFault;
}

} // namespace manager
} // namespace state
} // namespace phosphor