-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local device_object = require 'mc.mdb.device_tree.device_object'
local log = require 'mc.logging'
local utils = require 'device_tree.adapters.power_mgmt.utils'
local utils_core = require 'utils.core'
local ctx = require 'mc.context'
local debounce = require 'mc.debounce.debounce'
local base_msg = require 'messages.base'

local E_OK = nil -- 函数执行成功返回nil
local E_FAILED = '' -- 空错误信息

local psu = device_object('OnePower')

local ClassPsuSlot<const> = 'PsuSlot'
local is_utf8 = utils_core.utf8_validate

local PS_HEALTH_EVENT<const> = {
    VOUT_STATUS   = 0, -- 输出电压状态
    IOUT_STATUS   = 1, -- 输出电流状态
    VIN_STATUS    = 2, -- 输入电压状态
    TEMPER_STATUS = 3, -- 电源温度传感器的状态
    FAN_STATUS    = 4, -- 电源风扇的状态
    CML_STATUS    = 5, -- 命令的状态
    MFR_STATUS    = 6, -- 厂商自定义的状态

    PS_MONITOR_END = 7,
    DUAL_VIN_STATUS = 8,
    OTHER_STATUS = 9
}

local DEBOUNCE<const> = {
    DBD_CONBIN = {
        CONTNUM_H = 3,
        CONTNUM_L = 3
    },
    DBD_CONT = {
        CONT_NUM = 3,
        CONT_TURBO_NUM = 30 --turbo相关的告警策略需要30次
    }
}

local function snake_to_camel(name)
    local camel_str = ''
    pcall(function()
        return string.gsub(name, '([^_]+)', function(a)
            camel_str = camel_str .. a:sub(1, 1):upper() .. a:sub(2, -1)
        end)
    end)
    return camel_str
end

local function get_pbit(val)
    return 1 << val
end

function psu:ctor()
    self.protocol = false
    self.psu_slot = false
    self.last_present = false
    self.ps_id = 0
    self.frudata = {}
    self.debounce = {}
end

local onepower_properties = {
    'Protocol',
    'PowerSupplyType',
    "LineInputVoltageType", -- 电源支持的供电类型
    'Health',
    "Rate", -- 额定功率
    "FirmwareVersion", -- 电源版本
    "SerialNumber", -- 序列号
    "RatedCurrentAmps", -- 额定电流
    "ProductionDate", -- 生产日期/制造日期
    "Manufacturer", -- 厂商信息
    "Model", -- 电源型号
    'OutputPowerWatts',
    'InputCurrentAmps',
    'OutputCurrentAmps',
    'InputVoltage',
    'OutputVoltage',
    'EnvTemperatureCelsius',
    'PrimaryChipTemperatureCelsius',
    'SecondaryChipTemperatureCelsius',
    'InnerTemperatureCelsius',
    'InletTemperatureCelsius',
    'PowerSupplyChannel',  --'电源供电电路，0:主路  1:备路',
    'InputVoltageStatus',
    'InputFrequencyHz',
    'TotalRunningHours',
    'AlarmStatus',
    'PreAlarmStatus',
    'ScanStatus',
    'InputPowerWatts',
    'WorkMode',
    'NormalAndRedundancySupported',
    'CommunicationStatus',
    'DualInputVoltageStatus',
    "EquipmentMode",
    "HardwareVersion", -- 电容硬件版本号
    "PowerState", --TURBO使能标记 Off：电容使能关闭 On：电容使能打开
    "CapacityMicrofarads",   -- 电容容量
    "TPSUStatusHigh",
    "TPSUStatusLow",
    "RelaySwitchedCount",    --双输入继电器切换次数
    'Position',
    'OutputPowerLimitWatts'
}

function psu:dtor()
    self:stop_tasks()
    self.psu_slot:clear_property_fetch_task()
    self.psu_slot:psm_monitor_stop()
    self.psu_slot.init_ok = false
end

function psu:init_prop_from_slot()
    -- 设置psu的Position为关联slot对象所在的单板类型，默认为EXU
    if self.psu_slot and self.psu_slot.board_type then
        self.Position = self.psu_slot.board_type
        log:notice('PS%s set position %s successfully', self.ps_id, self.Position)
    end
end

function psu:register_psu_slot()
    local retry_times = 10
    -- 关联psu_slot
    local position = self:get_position()
    local psu_slots = self:get_dev_objects(ClassPsuSlot, position:sub(1, #position - 2)) -- 获取上一级csr的psu_slot对象
    for _ = 1, retry_times do
        for _, psu_slot in pairs(psu_slots) do
            if psu_slot.SlotNumber == self.SlotNumber then
                self.psu_slot = psu_slot
                self:init_prop_from_slot()
                return
            end
        end
        if self.psu_slot then
            return
        end
        self:sleep_ms(1000)
    end
    log:error('ps%d find psu_slot failed', self.ps_id)
end

function psu:register_power_limit_point_methods()
    self:declare_method('Power', 'bmc.dev.psu', 'SetOutputPowerLimitWatts', function (...)
        return self:set_output_limit_point_watts(...)
    end)
end

function psu:register_methods()
    -- 电源升级
    self:declare_method('Upgrade', 'bmc.dev.psu.upgrade', 'Upgrade', function (...)
        return self:power_upgrade(...)
    end)
    self:declare_method('Upgrade', 'bmc.dev.psu.upgrade', 'GetUpgradeChannel', function (...)
        return self.psu_slot:get_upgrade_channel(...)
    end)
    self:declare_method('Upgrade', 'bmc.dev.psu.upgrade', 'ChangePollingChannel', function (...)
        return self.psu_slot:change_polling_channel(...)
    end)
    self:declare_method('Upgrade', 'bmc.dev.psu.upgrade', 'RecoverPollingChannel', function (...)
        return self.psu_slot:recover_polling_channel(...)
    end)
    self:declare_method('Cooling', 'bmc.dev.psu.cooling', 'SetFanRPM', function (...)
        self.psu_slot:set_psu_fan_rpm(...)
    end)
    self:declare_method('Cooling', 'bmc.dev.psu.cooling', 'GetFanRPM', function ()
        return self.psu_slot:get_psu_fan_rpm()
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetPsuRegisterInfo', function (...)
        return self.psu_slot:get_psu_register_info(...)
    end)
    self:declare_method('BlackBox', 'bmc.dev.psu.black_box', 'GetBlackBoxData', function ()
        return self.psu_slot:get_black_box_data()
    end)
    self:declare_method('Power', 'bmc.dev.psu.power_mode', 'GetRetransferDelaySeconds', function (...)
        return self.psu_slot:get_retransfer_delay_seconds(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu.power_mode', 'SetRetransferDelaySeconds', function (...)
        return self.psu_slot:set_retransfer_delay_seconds(...)
    end)
    self:declare_method('PowerMode', 'bmc.dev.psu.power_mode', 'SetSleepMode', function (...)
        return self.psu_slot:set_sleep_mode(...)
    end)
    self:declare_method('PowerMode', 'bmc.dev.psu.power_mode', 'SetWorkMode', function (...)
        return self.psu_slot:set_work_mode(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetFruData', function (...)
        return self:update_fru_data(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'PowerAccessControl', function (...)
        return self:ps_access_control(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'UpdatePowerSupplyInfo', function (...)
        return self:update_power_supply_info(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'PowerLatch', function (...)
        return self:set_power_latch(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'Reset', function (...)
        return self:reset(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'SetAlarmLatch', function (...)
        return self:set_alarm_latch(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'SetOutputVoltage', function (...)
        return self:set_output_voltage(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'SetPowerSupplyCircuit', function (...)
        return self:set_power_supply_circuit(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'SetPowerCapacitorCalibrate', function (...)
        return self:set_power_capacitor_calibrate(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetPowerCapacitorCalibrate', function (...)
        return self:get_power_capacitor_calibrate(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetPowerCapacitorCalibrateTime', function (...)
        return self:get_power_capacitor_calibrate_time(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'SetPowerCapacitorEnable', function (...)
        return self:set_power_capacitor_enable(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetLifePredictionLevel', function (...)
        return self:get_life_prediction_level(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetLifePredictionCount', function (...)
        return self:get_life_prediction_count(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'SetLifePredictionCountIndex', function (...)
        return self:set_life_prediction_count_index(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetMainCircuitInputVoltageType', function (...)
        return self:get_main_circuit_input_voltage_type(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetBackupCircuitInputVoltageType', function (...)
        return self:get_backup_circuit_input_voltage_type(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetMainCircuitInputVoltage', function (...)
        return self:get_main_circuit_input_voltage(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'GetBackupCircuitInputVoltage', function (...)
        return self:get_backup_circuit_input_voltage(...)
    end)
end

function psu:register_capacitor_methods()
    self:declare_method('Power', 'bmc.dev.psu', 'SetVOUTDebounceMilliseconds', function (...)
        return self:set_vout_debounce_milliseconds(...)
    end)
    self:declare_method('Power', 'bmc.dev.psu', 'SetDepthOfDischargeVolts', function (...)
        return self:set_depth_of_discharge_volts(...)
    end)
end

function psu:init()
    local run_probe = function ()
        self:fetch_property()
        self.psu_slot:fetch_power_supply_info(self['PhysicalInterface'])
        self:register_methods()
        self:register_capacitor_methods()
        self:register_power_limit_point_methods()
        self.psu_slot.init_ok = true
    end
    self.ps_id = self.SlotNumber
    self:init_debounce()
    self:register_psu_slot()
    if not self.psu_slot then
        self:next_tick(function ()
            self:register_psu_slot()
            run_probe()
        end)
    else
        run_probe()
    end
end

function psu:get_main_circuit_input_voltage_type()
    if not self.psu_slot or not self.psu_slot.get_main_circuit_input_voltage_type then
        return base_msg.ActionNotSupported('getmain circuit input voltage type')
    end
    return self.psu_slot:get_main_circuit_input_voltage_type()
end

function psu:get_backup_circuit_input_voltage_type()
    if not self.psu_slot or not self.psu_slot.get_backup_circuit_input_voltage_type then
        return base_msg.ActionNotSupported('getbackup circuit input voltage type')
    end
    return self.psu_slot:get_backup_circuit_input_voltage_type()
end

function psu:get_main_circuit_input_voltage()
    if not self.psu_slot or not self.psu_slot.get_main_circuit_input_voltage then
        return base_msg.ActionNotSupported('get main circuit input voltage')
    end
    return self.psu_slot:get_main_circuit_input_voltage()
end

function psu:get_backup_circuit_input_voltage()
    if not self.psu_slot or not self.psu_slot.get_backup_circuit_input_voltage then
        return base_msg.ActionNotSupported('get backup circuit input voltage')
    end
    return self.psu_slot:get_backup_circuit_input_voltage()
end

function psu:get_life_prediction_level()
    if not self.psu_slot or not self.psu_slot.get_life_prediction_level then
        return base_msg.ActionNotSupported('life prediction')
    end
    return self.psu_slot:get_life_prediction_level()
end

function psu:get_life_prediction_count()
    if not self.psu_slot or not self.psu_slot.get_life_prediction_count then
        return base_msg.ActionNotSupported('life prediction')
    end
    return self.psu_slot:get_life_prediction_count()
end

function psu:set_life_prediction_count_index(dod_level, vout_level, current_level)
    if not self.psu_slot or not self.psu_slot.set_life_prediction_count_index then
        return base_msg.ActionNotSupported('life prediction')
    end
    return self.psu_slot:set_life_prediction_count_index(dod_level, vout_level, current_level)
end

function psu:reset(reset_type)
    if not self.psu_slot or not self.psu_slot.reset then
        return base_msg.ActionNotSupported('%ResetType')
    end
    return self.psu_slot:reset(reset_type)
end

function psu:set_output_voltage(voltage)
    if not self.psu_slot or not self.psu_slot.set_output_voltage then
        return base_msg.ActionNotSupported('voltage regulation')
    end
    return self.psu_slot:set_output_voltage(voltage)
end

function psu:set_power_supply_circuit(circuit)
    if not self.psu_slot or not self.psu_slot.set_power_supply_circuit then
        return base_msg.ActionNotSupported('%PowerSupplyChannel')
    end
    return self.psu_slot:set_power_supply_circuit(circuit)
end

function psu:set_power_capacitor_calibrate()
    if not self.psu_slot or not self.psu_slot.set_power_capacitor_calibrate then
        return base_msg.ActionNotSupported('power capatitor calibrate')
    end
    return self.psu_slot:set_power_capacitor_calibrate()
end

function psu:get_power_capacitor_calibrate_time()
    if not self.psu_slot or not self.psu_slot.get_power_capacitor_calibrate_time then
        return base_msg.ActionNotSupported('power capatitor calibrate time')
    end
    return self.psu_slot:get_power_capacitor_calibrate_time()
end

function psu:get_power_capacitor_calibrate()
    if not self.psu_slot or not self.psu_slot.get_power_capacitor_calibrate then
        return base_msg.ActionNotSupported('power capatitor calibrate')
    end
    return self.psu_slot:get_power_capacitor_calibrate()
end

function psu:set_power_capacitor_enable(enable)
    if not self.psu_slot or not self.psu_slot.set_power_capacitor_enable then
        return base_msg.ActionNotSupported('%SetPowerCapacitorEnable')
    end
    return self.psu_slot:set_power_capacitor_enable(enable)
end

function psu:set_power_latch()
    if not self.psu_slot.set_power_latch then
        log:notice('ps%s not support PowerLatch', self.ps_id)
        return E_FAILED
    end
    return self.psu_slot:set_power_latch()
end

function psu:set_alarm_latch(switch_status)
    if not self.psu_slot or not self.psu_slot.set_alarm_latch then
        return base_msg.ActionNotSupported('%SwitchStatus')
    end
    return self.psu_slot:set_alarm_latch(switch_status)
end

function psu:set_vout_debounce_milliseconds(debounce_milliseconds)
    if not self.psu_slot or not self.psu_slot.set_vout_debounce_milliseconds then
        return base_msg.ActionNotSupported('%VOUTDebounceMilliseconds')
    end
    return self.psu_slot:set_vout_debounce_milliseconds(debounce_milliseconds)
end

function psu:set_depth_of_discharge_volts(depth_of_discharge_volts)
    if not self.psu_slot or not self.psu_slot.set_depth_of_discharge_volts then
        return base_msg.ActionNotSupported('%DepthOfDischargeVolts')
    end
    return self.psu_slot:set_depth_of_discharge_volts(depth_of_discharge_volts)
end

function psu:update_power_supply_info()
    self.psu_slot:fetch_power_supply_info(self['PhysicalInterface'])
end

function psu:ps_access_control(start_flag)
    if start_flag then
        self.psu_slot:power_monitor_start()
    else
        self.psu_slot:psm_monitor_stop()
    end
end

function psu:set_output_limit_point_watts(output_power_limit_watts)
    if not self.psu_slot or not self.psu_slot.set_output_limit_point_watts then
        return base_msg.ActionNotSupported('%OutputPowerLimitWatts')
    end
    return self.psu_slot:set_output_limit_point_watts(output_power_limit_watts)
end

function psu:set_resource(prop, value)
    if prop == "DeepSleepEnabled"  and value == 0 and self.DeepSleepEnabled == 1 then
        self.debounce.health_event[PS_HEALTH_EVENT.MFR_STATUS]:clear_debounced_val()
        self.MfrSpecificStatus = 255
    end
    if type(value) == 'string' and not is_utf8(value) then
        value = 'N/A'
    end
    self[prop] = value
end

function psu:is_present()
    return self.Presence == 1
end

function psu:fetch_property()
    for _, prop in ipairs(onepower_properties) do
        -- 注册回调，支不支持属性刷新由slot-chip决定
        local snake_str = utils.camel_to_snake(prop)
        self.psu_slot:fetch_property(snake_str, function (value)
            self:set_resource(prop, value)
        end)
        self.psu_slot:fetch_property(prop, function (value)
            self:set_resource(prop, value)
        end)
    end
    self.psu_slot:fetch_property('health_event', function (...)
        self:handle_health_event(...)
    end)
    self.psu_slot:fetch_property('ps_control_data', function (value)
        if value.sleep_mode then
            self:set_resource('SleepMode', value.sleep_mode)
        end
        if value.work_mode then
            self:set_resource('WorkMode', value.work_mode)
        end
        if value.normal_and_redundancy_supported ~= nil then
            self:set_resource('NormalAndRedundancySupported', value.normal_and_redundancy_supported)
        end
    end)
    self.psu_slot:fetch_property('sleep_mode', function (value)
        self.SleepMode = value
        if value == 'DeepSleep' then
            self.WorkMode = 'StandbySpare'
            self:set_resource('DeepSleepEnabled', 1)
        else
            self:set_resource('DeepSleepEnabled', 0)
        end
    end)
    self.psu_slot:fetch_property('part_number', function(value)
        self:handle_part_number(value)
    end)
    self.psu_slot:fetch_property('PartNumber', function(value)
        self:handle_part_number(value)
    end)
    self.psu_slot:fetch_property('main_circuit_vin_status', function (value)
        self:set_resource('MainCircuitVINStatus', value)
    end)
    self.psu_slot:fetch_property('MainCircuitVINStatus', function (value)
        self:set_resource('MainCircuitVINStatus', value)
    end)
    self.psu_slot:fetch_property('backup_circuit_vin_status', function (value)
        self:set_resource('BackupCircuitVINStatus', value)
    end)
    self.psu_slot:fetch_property('BackupCircuitVINStatus', function (value)
        self:set_resource('BackupCircuitVINStatus', value)
    end)
    self.psu_slot:fetch_property('VOUTDebounceMilliseconds', function (value)
        self:set_resource('VOUTDebounceMilliseconds', value)
    end)
    self.psu_slot:fetch_property('vout_debounce_milliseconds', function (value)
        self:set_resource('VOUTDebounceMilliseconds', value)
    end)
    self.psu_slot:fetch_property('DepthOfDischargeVolts', function (value)
        self:set_resource('DepthOfDischargeVolts', value)
    end)
    self.psu_slot:fetch_property('depth_of_discharge_volts', function (value)
        self:set_resource('DepthOfDischargeVolts', value)
    end)
end

function psu:init_debounce()
    local health_event_debounce = {}
    for _, idx in pairs(PS_HEALTH_EVENT) do
        health_event_debounce[idx] = debounce['Cont'].new(DEBOUNCE.DBD_CONT.CONT_NUM, 0)
    end
    health_event_debounce[PS_HEALTH_EVENT.VOUT_STATUS] = debounce['Cont'].new(DEBOUNCE.DBD_CONT.CONT_NUM, 255)
    health_event_debounce[PS_HEALTH_EVENT.MFR_STATUS] = debounce['Cont'].new(13, 255)
    health_event_debounce[PS_HEALTH_EVENT.VIN_STATUS] =
                debounce['Cont'].new(DEBOUNCE.DBD_CONT.CONT_NUM, 32768)
    self.debounce = {
        health_event = health_event_debounce,
        health = debounce['Cont'].new(DEBOUNCE.DBD_CONT.CONT_NUM, 0),
        input_loss = debounce['Cont'].new(DEBOUNCE.DBD_CONT.CONT_NUM, 255),
        fan1 = debounce['ContBin'].new(DEBOUNCE.DBD_CONBIN.CONTNUM_H, DEBOUNCE.DBD_CONBIN.CONTNUM_L, 0),
        fan2 = debounce['ContBin'].new(DEBOUNCE.DBD_CONBIN.CONTNUM_H, DEBOUNCE.DBD_CONBIN.CONTNUM_L, 0),
        output_fail = debounce['ContBin'].new(
            DEBOUNCE.DBD_CONBIN.CONTNUM_H, DEBOUNCE.DBD_CONBIN.CONTNUM_L, 0),
        over_temp = debounce['ContBin'].new(
            DEBOUNCE.DBD_CONBIN.CONTNUM_H, DEBOUNCE.DBD_CONBIN.CONTNUM_L, 0),
        turbo_status_high = debounce['Cont'].new(DEBOUNCE.DBD_CONT.CONT_TURBO_NUM, 0),
        turbo_status_low = debounce['Cont'].new(DEBOUNCE.DBD_CONT.CONT_TURBO_NUM, 0),
        output_voltage_fault_extend = debounce['Cont'].new(15, 255)
    }
end

-- 刷新电子标签
function psu:update_fru_data(value)
    local ok, ret
    -- 获取fru信息失败时最多重试3次
    for _ = 1, 3 do
        ok, ret = pcall(function()
            return self.psu_slot:get_fru_data(value)
        end)
        if ok then
            break
        end
        self:sleep_ms(1000)
    end
    if not ok then
        return
    end
    self.frudata = ret

    local prop = {}
    local fru_value = {}
    for elabel_prop, val in pairs(self.frudata) do
        table.insert(prop, elabel_prop)
        table.insert(fru_value, val)
    end

    -- 增加失败重试，在方法调用失败之后等待3s，重新发起更新，重试次数为100次
    local RETRY_TIMES = 100
    for _ = 1, RETRY_TIMES do
        ok, ret = pcall(function()
            return self.RefFrudata:Update(ctx.new(), prop, fru_value)
        end)
        if ok then
            log:notice('PS%s set frudata successfully', self.ps_id)
            return
        end
    end
    log:error('PS%s set frudata failed, msg: %s', self.ps_id, ret)
end

function psu:handle_health_event(health_event)
    local failure = health_event.event & get_pbit(0)
    self.Failure = failure

    local input_loss = self.debounce.input_loss:get_debounced_val(health_event.event & get_pbit(4) > 0 and 1 or 0)
    self.LossOfInput = input_loss

    self:check_input_output_fault(health_event)
    self:check_internal_fault(health_event)
end

function psu:check_output_voltage_fault_extend_condition(health_event)
    if not health_event.mfr_specific_status or not health_event.output_current_fault or
        not health_event.input_voltage_fault or not health_event.temper_fault or not health_event.fan_fault or
        not health_event.output_voltage_fault then
        return 0
    end
    if self.InputState == 1 and not self.IsUpgrading and self.DeepSleepEnabled == 0 and self.OutputState == 0 and
        (health_event.mfr_specific_status & 88) == 64 and health_event.output_current_fault == 0 and
        health_event.input_voltage_fault == 0 and health_event.temper_fault == 0 and health_event.fan_fault == 0 and
        health_event.output_voltage_fault == 0 then
        return 1
    else
        return 0
    end
end

function psu:check_input_output_fault(health_event)
    if health_event.output_current_fault ~= nil then
        local output_current_fault = self.debounce.health_event[PS_HEALTH_EVENT.IOUT_STATUS]:get_debounced_val(
            health_event.output_current_fault)
        self.OutputCurrentFault = output_current_fault
    end
    if health_event.output_voltage_fault ~= nil then
        local output_voltage_fault = self.debounce.health_event[PS_HEALTH_EVENT.VOUT_STATUS]:get_debounced_val(
            health_event.output_voltage_fault)
        local output_voltage_fault_extend = self.debounce.output_voltage_fault_extend:get_debounced_val(
            self:check_output_voltage_fault_extend_condition(health_event))
        if output_voltage_fault ~= 255 and output_voltage_fault_extend ~= 255 then
            if (output_voltage_fault & get_pbit(4)) ~= 16 then
                output_voltage_fault = output_voltage_fault_extend == 1 and
                    (output_voltage_fault | get_pbit(4)) or output_voltage_fault
            end
            if output_voltage_fault ~= self.OutputVoltageFault then
                log:notice('PS%s output voltage fault status changed from %s to %s, InputState: %s, IsUpgrading: %s' ..
                ', DeepSleepEnabled: %s, mfr_specific_status: %s, OutputState: %s, output_current_fault: %s, ' ..
                'input_voltage_fault: %s, temper_fault: %s, fan_fault: %s, output_voltage_fault: %s', self.ps_id,
                self.OutputVoltageFault, output_voltage_fault, self.InputState, self.IsUpgrading,
                self.DeepSleepEnabled, health_event.mfr_specific_status, self.OutputState,
                health_event.output_current_fault, health_event.input_voltage_fault, health_event.temper_fault,
                health_event.fan_fault, health_event.output_voltage_fault)
            end
            self.OutputVoltageFault = output_voltage_fault
        end
    end
    if health_event.input_voltage_fault ~= nil then
        local input_voltage_fault = self.debounce.health_event[PS_HEALTH_EVENT.VIN_STATUS]:get_debounced_val(
            health_event.input_voltage_fault)
        self.InputVoltageFault = input_voltage_fault
    end
    if health_event.input_dual_voltage_fault ~= nil then
        local dual_input_voltage_fault = self.debounce.health_event[PS_HEALTH_EVENT.DUAL_VIN_STATUS]:get_debounced_val(
            health_event.input_dual_voltage_fault)
        self.DualInputVoltageStatus = dual_input_voltage_fault
    end
end

function psu:check_internal_fault(health_event)
    if health_event.fan_fault ~= nil then
        -- fan_fault:
        -- bit2: Fan 2 Speed Override
        -- bit4: Fan 2 Warning
        -- bit6: Fan 2 Fault
        local fan2_fault = self.debounce.fan2:get_debounced_val(health_event.fan_fault & get_pbit(6) > 0 and 1 or 0)
        self.Fan2Fault = fan2_fault
        -- bit3: Fan 1 Speed Override
        -- bit5: Fan 1 Warning
        -- bit7: Fan 1 Fault
        local fan1_fault = self.debounce.fan1:get_debounced_val(health_event.fan_fault & get_pbit(7) > 0 and 1 or 0)
        self.Fan1Fault = fan1_fault
    end
    if health_event.fan_fault ~= nil then
        local fan_fault = self.debounce.health_event[PS_HEALTH_EVENT.FAN_STATUS]:get_debounced_val(
            health_event.fan_fault)
        self.FanFault = fan_fault
    end
    if health_event.temper_fault ~= nil then
        local temper_fault = self.debounce.health_event[PS_HEALTH_EVENT.TEMPER_STATUS]:get_debounced_val(
            health_event.temper_fault)
        self.OverTemperature = temper_fault
    end
    if health_event.cml_fault ~= nil then
        local cml_fault = self.debounce.health_event[PS_HEALTH_EVENT.CML_STATUS]:get_debounced_val(
            health_event.cml_fault)
        self.CMLStatus = cml_fault
    end
    if health_event.other_fault ~= nil then
        local other_fault = self.debounce.health_event[PS_HEALTH_EVENT.OTHER_STATUS]:get_debounced_val(
            health_event.other_fault)
        self.OtherStatus = other_fault
    end
    if health_event.mfr_specific_status ~= nil then
        local mfr_specific_status = self.debounce.health_event[PS_HEALTH_EVENT.MFR_STATUS]:get_debounced_val(
            health_event.mfr_specific_status)
        self.MfrSpecificStatus = mfr_specific_status
    end
    if health_event.turbo_status_high ~= nil then
        local turbo_status_high = self.debounce.turbo_status_high:get_debounced_val(health_event.turbo_status_high)
        self.TPSUStatusHigh = turbo_status_high
    end
    if health_event.turbo_status_low ~= nil then
        local turbo_status_low = self.debounce.turbo_status_low:get_debounced_val(health_event.turbo_status_low)
        self.TPSUStatusLow = turbo_status_low
    end
end

function psu:handle_part_number(value)
    if self.SerialNumber ~= '' then
        self.PartNumber = #self.SerialNumber == 20 and self.SerialNumber:sub(3, 10) or value
    else
        local ret, resp = self.psu_slot:get_dynamic_data('serial_number', 3)
        if ret == E_OK and resp then
            self.PartNumber = #resp == 20 and resp:sub(3, 10) or value
        end
    end
end

function psu:run_probe()
    self.psu_slot:power_monitor_start()
end

function psu:power_upgrade(...)
    self:set_resource('IsUpgrading', true)
    self.debounce.health_event[PS_HEALTH_EVENT.MFR_STATUS]:clear_debounced_val()
    self.MfrSpecificStatus = 255
    local ret = self.psu_slot:power_upgrade(...)
    self:set_resource('IsUpgrading', false)
    return ret
end

function psu:start()
    self:run_probe()
    self:listen('IsUpgrading', function ()
        self.psu_slot.is_upgrading = true
    end)
end

return psu
