-- 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 log = require 'mc.logging'
local crc8 = require 'mc.crc8'
local ctx = require 'mc.context'
local bs = require 'mc.bitstring'
local utils = require 'power_mgmt_utils'
local enums = require 'macros.power_mgmt_enums'
local class = require 'mc.class'
local upgrade = require 'device_tree.adapters.power_mgmt.protocol.upgrade.pmbus_upgrade'
local fw_def = require 'device_tree.adapters.power_mgmt.protocol.upgrade.fw_def'
local skynet = require 'skynet'
local utils_vos = require 'utils.vos'
local custom_msg = require 'messages.custom'
local os = require 'os'

local pmbus = class()

local MAX_BLACK_BOX_NUMBER<const> = 11
local BLACK_BOX_MAX_LEN_PMBUS<const> = 512
local PS_DEV_ID_START <const> = 0
local DEV_ID_INVALID <const> = 0xFF
local PS_I_SCALING_FACTOR <const> = 100

local ADDR_WR_LEN <const> = 1
local CMD_LEN <const> = 1
local ADDR_RD_LEN <const> = 1
local BYTE_CNT_LEN <const> = 1
local CS_LEN <const> = 1
local BUFFER_MAX_LEN <const> = 32
local ADDR_WR_OFF <const> = 0
local ONE_BLACK_BOX_LEN <const> = 40

local CMD_OFF <const> = ADDR_WR_OFF + ADDR_WR_LEN
local REQ_DATA_OFF <const> = CMD_OFF + CMD_LEN
local ADDR_RD_OFF <const> = CMD_OFF + CMD_LEN
local RESP_DATA_OFF <const> = ADDR_RD_OFF + ADDR_RD_LEN
local BYTE_CNT_OFF <const> = RESP_DATA_OFF
local LOAD_INFO_RESP_LEN <const> = 36

local E_ERR = 1
local E_OK  = 0
pmbus.E_ERR = E_ERR
pmbus.E_OK  = E_OK

local INPUT_TYPE_NONE<const> = 0xff
local INPUT_TYPE_DC <const> = 0
local INPUT_TYPE_AC <const> = 1
local INPUT_TYPE_ACORDC <const> = 2
local VOLTAGE_TYPE_AC_WIDE_RANGE <const> = "ACWideRange"
local VOLTAGE_TYPE_AC_AND_DC_WIDE_RANGE <const> = "ACandDCWideRange"
local VOLTAGE_TYPE_DC_NEG_48V <const> = "DCNeg48V"
local VOLTAGE_TYPE_HVDC <const> = "HVDC"
local VOLTAGE_TYPE_UNKNOWN <const> = "Unknown"
local POWER_ACTIVE_MODE <const> = 0 -- 主用模式
local POWER_STANDBY_MODE <const> = 1 -- 备用模式
local POWER_ACTIVE_MODE_STR <const> = 'Enabled' -- 主用模式
local POWER_STANDBY_MODE_STR <const> = 'StandbySpare' -- 备用模式
local POWER_DEEP_SLEEP_MODE <const> = 'DeepSleep'
local POWER_NORMAL_MODE <const> = 'Normal'
local POWER_SUPPORT_NAR <const> = true
local POWER_NOT_SUPPORT_NAR <const> = false
local POWER_CFG_ACTIVE_POWER <const> = 0
local PMBUS_MODE_ACTIVE_VOUT <const> = 12.3
local PMBUS_MODE_STANDBY_VOUT <const> = 12.05
local FRU_DATA_LEN <const> = 256
local CHAR_TYPE <const> = 0x03
local DEFAULT_AREA_VER <const> = 0x01
local RETRY_TIMES <const> = 3
local SOFT_INFO_SIZE <const> = 8
local MAX_MDU_NUM <const> = 2
local VOUT_EXPONENT<const> = 5
local TWO_BYTE<const> = 16
local ONE_BYTE<const> = 8

-- 电源寄存器地址
pmbus.cmd = {
    VOUT_MODE           = 0x20,
    VOUT_COMMOND        = 0x21,
    PS_RATE             = 0x31,
    FAN_COMMAND         = 0x3B,
    MFR_QB_SOFT_VER     = 0x6A,
    STATUS_BYTE         = 0x78,
    STATUS_WORD         = 0x79,
    STATUS_VOUT         = 0x7A,
    STATUS_IOUT         = 0x7B,
    STATUS_INPUT        = 0x7C,
    STATUS_TEMPERATURE  = 0x7D,
    STATUS_CML          = 0x7E,
    STATUS_OTHER        = 0x7F,
    STATUS_MFR_SPECIFIC = 0x80,
    STATUS_FANS_1_2     = 0x81,
    READ_VIN            = 0x88,
    READ_IIN            = 0x89,
    READ_12VOUT         = 0x8B,
    READ_12VIOUT        = 0x8C,
    READ_TEMPERATURE_1  = 0x8D,
    READ_TEMPERATURE_2  = 0x8E,
    READ_TEMPERATURE_3  = 0x8F,
    READ_FAN_SPEED      = 0x90,
    READ_POUT           = 0x96,
    READ_PIN            = 0x97,
    REVISON             = 0x98,
    MFR_ID              = 0x99,
    MFR_MODEL           = 0x9A,
    MFR_REVISION        = 0x9B,
    MFR_DATE            = 0x9D,
    MFR_SERIAL          = 0x9E,
    POWER_TYPE          = 0xCF,
    EVENT               = 0xD0,
    PART_NUMBER         = 0xDE,
    INPUT_TYPE          = 0xDF,
    CONTROL_CMD         = 0xCE,
    DC_VERSION          = 0xe4,
    PFC_VERSION         = 0xe7,
    EQUIPMENT_ENABLE    = 0xE8,
    READ_EVENT_LOG      = 0xE0,
    MFR_SPEC_00         = 0xF0,
    MFR_SPEC_01         = 0xF1,
    MFR_SPEC_02         = 0xF2,
    MFR_SPEC_03         = 0xF3,
    MFR_SPEC_04         = 0xF4,
    TIME_SERVICE        = 0xFA,
    LOAD_INFO_INQUIRY   = 0xFB,
    LOAD_CTRL           = 0xFC,
    LOAD_DATA           = 0xFD
}

-- 读/写、只读电源寄存器地址
pmbus.read_cmd = {
    pmbus.cmd.VOUT_MODE, pmbus.cmd.VOUT_COMMOND, pmbus.cmd.PS_RATE, pmbus.cmd.FAN_COMMAND, pmbus.cmd.STATUS_BYTE,
    pmbus.cmd.STATUS_WORD, pmbus.cmd.STATUS_VOUT, pmbus.cmd.STATUS_IOUT, pmbus.cmd.STATUS_INPUT,
    pmbus.cmd.STATUS_TEMPERATURE, pmbus.cmd.STATUS_CML, pmbus.cmd.STATUS_OTHER, pmbus.cmd.STATUS_MFR_SPECIFIC,
    pmbus.cmd.STATUS_FANS_1_2, pmbus.cmd.READ_VIN, pmbus.cmd.READ_IIN, pmbus.cmd.READ_12VOUT, pmbus.cmd.READ_12VIOUT,
    pmbus.cmd.READ_TEMPERATURE_1, pmbus.cmd.READ_TEMPERATURE_2, pmbus.cmd.READ_TEMPERATURE_3, pmbus.cmd.READ_FAN_SPEED,
    pmbus.cmd.READ_POUT, pmbus.cmd.READ_PIN, pmbus.cmd.REVISON, pmbus.cmd.MFR_ID, pmbus.cmd.MFR_MODEL,
    pmbus.cmd.MFR_REVISION, pmbus.cmd.MFR_DATE, pmbus.cmd.MFR_SERIAL, pmbus.cmd.POWER_TYPE, pmbus.cmd.EVENT,
    pmbus.cmd.PART_NUMBER, pmbus.cmd.INPUT_TYPE, pmbus.cmd.CONTROL_CMD, pmbus.cmd.DC_VERSION, pmbus.cmd.PFC_VERSION,
    pmbus.cmd.READ_EVENT_LOG, pmbus.cmd.MFR_SPEC_00, pmbus.cmd.MFR_SPEC_04, pmbus.cmd.TIME_SERVICE,
    pmbus.cmd.LOAD_INFO_INQUIRY, pmbus.cmd.LOAD_CTRL, pmbus.cmd.LOAD_DATA
}

pmbus.firmware = {
    DCDC_SOFT = 0x80,
    PFC_SOFT  = 0x40,
    QB_SOFT   = 0x20
}

local PS_STANDARD_READ = {
    DC_VERSION_OFFSET  = 0xe4,
    PFC_VERSION_OFFSET = 0xe7,
    QB_VERSION_OFFSET = 0xf3
}

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

function pmbus:ctor(psu_slot, protocol_name)
    self.ps_id = psu_slot['SlotNumber']
    self.psu_chip = psu_slot['PsuChip'][enums.PSU_CHIP_OBJECT.BLOCKIO]
    self.slot_i2c_addr = psu_slot['SlotI2cAddr']
    self.fru_chip = psu_slot['FruChip']
    self.fru_i2c_addr = psu_slot['FruI2cAddr']
    self.health_event = {}
    self.utils = utils.get_instance()
    self.protocol_name = protocol_name
    self.bus = psu_slot.bus
end

-- 封装crc8校验，与read_data比较
local function check_data(check_sum, data)
    local crc = crc8(data)
    if crc ~= check_sum then
        log:debug('get crc: %d, real crc: %d', check_sum, crc)
        return E_ERR
    end
    return E_OK
end

local function get_addr_wr(val)
    return val & 0xfffffffe
end

local function get_addr_rd(val)
    return val | 1
end

-- chip_write 写入指定长的数据
function pmbus:chip_write(cmd, data)
    local check_buf = string.format('%s%s%s', string.char(self.slot_i2c_addr), string.char(cmd), data)
    local crc = crc8(check_buf)
    self.psu_chip:Write(ctx.new(), cmd, data .. string.pack('B', crc))
end

-- chip_bytewrite 从Chip对象中写一个Byte的信息
function pmbus:chip_bytewrite(cmd, data)
    self:chip_write(cmd, string.pack('B', data))
end

-- chip_wordwrite 从Chip对象中写一个Word的信息
function pmbus:chip_wordwrite(cmd, data)
    self:chip_write(cmd, string.pack('H', data))
end

-- chip_blkwrite 从Chip对象中写一个块的信息，第一位为数据长度
function pmbus:chip_blkwrite(cmd, data)
    self:chip_write(cmd, string.pack('B', #data) .. data)
end

-- chip_read 读取指定长的数据
function pmbus:chip_read(cmd, len)
    local err_str = ''
    local value
    local check_buf
    for _ = 1, RETRY_TIMES, 1 do
        value = self.psu_chip:Read(ctx.get_context_or_default(), cmd, len)
        check_buf = string.format('%s%s%s%s',
            string.char(self.slot_i2c_addr),
            string.char(cmd),
            string.char(self.slot_i2c_addr | 0x01),
            value:sub(1, #value - 1))
        if value and check_data(value:sub(#value, #value):byte(), check_buf) == E_OK then
            return value
        end
        err_str = string.format('[power_mgmt][pmbus]read commad(0x%02x) failed', cmd)
    end
    error(err_str)
end

-- chip_wordread 读取一个16位的数据
function pmbus:chip_wordread(cmd)
    return string.unpack('H', self:chip_read(cmd, 3):sub(1, 2))
end

-- chip_byteread 读取一个8位的数据
function pmbus:chip_byteread(cmd)
    return string.unpack('B', self:chip_read(cmd, 2):sub(1, 1))
end

-- chip_blkread 读取一个块的信息
function pmbus:chip_blkread(cmd, len)
    local value = self:chip_read(cmd, len + 2)
    return value:sub(2, #value - 1)
end

-- 根据数据的实际长度，读取pmbus块数据
function pmbus:block_read(cmd)
    local val = self.psu_chip:Read(ctx.get_context_or_default(), cmd, BYTE_CNT_LEN):byte()
    if val == nil then
        error('[power_mgmt][pmbus] invalid read_len(nil)')
    end
    local read_len = val + BYTE_CNT_LEN + CS_LEN
    -- 读len至checksum的数据
    if read_len > BUFFER_MAX_LEN - RESP_DATA_OFF then
        error(string.format('invalid read len: %d, buffer len: %d', read_len, BUFFER_MAX_LEN - RESP_DATA_OFF))
    end
    return self.psu_chip:Read(ctx.get_context_or_default(), cmd, read_len)
end

function pmbus:is_pmbus_cmd_read_i(cmd)
    return cmd == self.cmd.READ_IIN or cmd == self.cmd.READ_12VIOUT
end

-- get_dynamic_block 读取不定长的数据
function pmbus:get_dynamic_block(cmd)
    local value = self:block_read(cmd)
    local check_buf = string.format('%s%s%s%s', string.char(get_addr_wr(self.slot_i2c_addr)),
        string.char(cmd),
        string.char(get_addr_rd(self.slot_i2c_addr)),
        value:sub(1, #value - 1))
    -- value返回类型由pmbus.block_read保证
    if check_data(value:sub(-1, -1):byte(), check_buf) == E_OK then
        return value:sub(2, #value - 1)
    end
    error(string.format('[power_mgmt][pmbus]read commad(0x%02x) failed, data:[%s]', cmd, utils.to_hex(value)))
end

function pmbus:get_linear_11(cmd)
    local raw_word = self:chip_wordread(cmd)
    local value = raw_word & 0x7FF
    local bit = ((raw_word >> 11) & 0x1f)
    if raw_word & 0x8000 > 0 then
        bit = (255 - (bit | 0xE0)) + 1
        --  电流较小（0~1 A）时，以A为单位传值会导致精度丢失；
        --  计算电流时，将读数先放大，以10mA为单位传值以保留精度；
        return self:is_pmbus_cmd_read_i(cmd) and ((value * PS_I_SCALING_FACTOR) >> bit) or (value >> bit)
    end
    return value << bit
end

function pmbus:get_linear_16(cmd, vout_mode)
    local exponent = (1 << VOUT_EXPONENT) - vout_mode
    -- bit0-4为exponent，需要判断正负数
    if (vout_mode & 0x10) ~= 0 then
        return self:chip_wordread(cmd) / (2 ^ exponent)
    end

    return self:chip_wordread(cmd) << exponent
end

-- get_input_current 输入电流
function pmbus:get_input_current_amps()
    return self:get_linear_11(self.cmd.READ_IIN) / 100
end

-- get_output_current 输出电流
function pmbus:get_output_current_amps()
    return self:get_linear_11(self.cmd.READ_12VIOUT) / 100
end

-- get_input_voltage 输入电压
function pmbus:get_input_voltage()
    return self:get_linear_11(self.cmd.READ_VIN)
end

-- get_equipment_mode 获取电源装备模式
function pmbus:get_equipment_mode()
    return self:chip_wordread(self.cmd.EQUIPMENT_ENABLE) == 0x5A and 0 or 1
end

-- 输入补码和位数，转换为原码
function pmbus:twos_complement_to_original(complement, bits)
    if (complement & (1 << (bits - 1))) == 0  then
        return complement
    end

    return (-((1 << bits) - complement))
end

-- 获取Direct模式下的数据, m, r, b为二进制补码数据
function pmbus:get_direct(m, r, b, cmd) 
    m = self:twos_complement_to_original(m, TWO_BYTE)
    r = self:twos_complement_to_original(r, ONE_BYTE)
    b = self:twos_complement_to_original(b, TWO_BYTE)
    local y = self:chip_wordread(cmd)
    if m == 0 then
        return nil
    end
    return (y * (10 ^ (-r)) -b) / m
end

-- get_output_voltage 输出电压
function pmbus:get_output_voltage()
    local ok, vout_mode = pcall(function ()
        return self:chip_byteread(self.cmd.VOUT_MODE)
    end)

    -- 不支持获取vout_mode时，与原先逻辑保持一致
    if not ok then
        return tonumber(string.format("%.2f", self:chip_wordread(self.cmd.READ_12VOUT) / 4096))
    end

    -- bit 7:5 为mode
    local mode = ((vout_mode >> VOUT_EXPONENT) & 0x07)
    -- 000 linear 001 VID 010 Direct
    if mode == 0 then
        return tonumber(string.format("%.2f", self:get_linear_16(self.cmd.READ_12VOUT, vout_mode)))
    end

    return nil
end

-- get_input_power_watts 单电源输入功率
function pmbus:get_input_power_watts()
    return self:get_linear_11(self.cmd.READ_PIN)
end

-- get_output_power_watts 单电源输出功率
function pmbus:get_output_power_watts()
    return self:get_linear_11(self.cmd.READ_POUT)
end

-- get_power_supply_type 输入类型 交直流模式
function pmbus:get_power_supply_type()
    local ok, input_type = pcall(function()
        return self:chip_wordread(self.cmd.INPUT_TYPE)
    end)
    if not ok then
        return INPUT_TYPE_NONE
    end
    local power_supply_type = ({
        [0] = INPUT_TYPE_NONE,
        [1] = INPUT_TYPE_AC,
        [2] = INPUT_TYPE_DC,
        [3] = INPUT_TYPE_DC
    })[input_type]
    return power_supply_type
end

-- get_line_input_voltage_type 电源类型 交流电源或直流电源
--电源上报的电压类型：0：AC; 1:48VDC; 2:HVDC; 3: AC&240HVDC; 4: AC&240HVDC&380HVDC
function pmbus:get_line_input_voltage_type()
    local ok, power_type = pcall(function()
        return self:chip_wordread(self.cmd.POWER_TYPE)
    end)
    if not ok then
        return VOLTAGE_TYPE_UNKNOWN
    end
    local line_input_voltage_type = ({
        [0] = VOLTAGE_TYPE_AC_WIDE_RANGE,
        [1] = VOLTAGE_TYPE_DC_NEG_48V,
        [2] = VOLTAGE_TYPE_HVDC,
        [3] = VOLTAGE_TYPE_AC_AND_DC_WIDE_RANGE,
        [4] = VOLTAGE_TYPE_AC_AND_DC_WIDE_RANGE
    })[power_type]
    return line_input_voltage_type
end

-- get_rate 额定功率
function pmbus:get_rate()
    return self:get_linear_11(self.cmd.PS_RATE)
end

-- get_manufacturer 厂商
function pmbus:get_manufacturer()
    return self:get_dynamic_block(self.cmd.MFR_ID)
end

-- get_production_date 制造日期/生产日期
function pmbus:get_production_date()
    return self:get_dynamic_block(self.cmd.MFR_DATE)
end

-- get_model 电源型号
function pmbus:get_model()
    return self:get_dynamic_block(self.cmd.MFR_MODEL)
end

-- get_product_version 电源版本
function pmbus:get_product_version()
    return self:get_dynamic_block(self.cmd.MFR_REVISION)
end

function pmbus:get_soft_version(soft_id)
    local cmd = ({
        [self.firmware.DCDC_SOFT] = self.cmd.DC_VERSION,
        [self.firmware.PFC_SOFT] = self.cmd.PFC_VERSION,
        [self.firmware.QB_SOFT] = self.cmd.PFC_VERSION
    })[soft_id]
    for _ = 1, RETRY_TIMES, 1 do
        local ok, ver = pcall(function()
            return self:chip_wordread(cmd)
        end)
        if ok then
            return ver
        else
            self.utils:frequency_limit_log(enums.LOG_LEVEL.ERROR, 60,
                'get_soft_version failed, ps id: %d, soft id: %s, error info: %s', self.ps_id, soft_id, ver)
        end
    end
    return nil
end

-- get_firmware_version 电源版本
function pmbus:get_firmware_version()
    local soft_load_info = self:get_soft_load_info()
    local fw_version = { 0, 0 }
    local app_count = 1
    for i = 1, soft_load_info.soft_cnt, 1 do
        local soft_info = soft_load_info.soft_info[i]
        log:info('soft_load_info.soft_cnt: %d, soft_info.module_num :%d', soft_load_info.soft_cnt,
            soft_info.module_num)
        local module_num = soft_info.module_num > MAX_MDU_NUM and 1 or soft_info.module_num
        for j = 1, module_num, 1 do
            if j < module_num then
                -- 下发FC切换通道
                self:load_app_ctrl_cmd(self.cmd.MFR_QB_SOFT_VER, j)
                -- 查询切换是否成功
                self:load_app_ctrl_cmd_read(self.cmd.MFR_QB_SOFT_VER)
            end
            fw_version[app_count] = self:get_soft_version(soft_info.soft_id)
            if not fw_version[app_count] then
                return 'N/A'
            end
            app_count = app_count + 1
        end
    end
    local input_type = self:get_power_supply_type()
    return string.format('DC:%x%02x PFC:%s%x%02x', ((fw_version[1] >> 8) & 0xff), fw_version[1] & 0xff,
        input_type == 0 and '(QB)' or '', ((fw_version[2] >> 8) & 0xff), fw_version[2] & 0xff)
end

-- get_part_number 电源部件号
function pmbus:get_part_number()
    return self:get_dynamic_block(self.cmd.PART_NUMBER)
end

-- get_serial_number 电源序列号
function pmbus:get_serial_number()
    return self:get_dynamic_block(self.cmd.MFR_SERIAL)
end

-- get_status_input 输入状态信息
function pmbus:get_status_input()
    return self:chip_byteread(self.cmd.STATUS_INPUT)
end

-- get_status_temperature 温度状态信息
function pmbus:get_status_temperature()
    return self:chip_byteread(self.cmd.STATUS_TEMPERATURE)
end

-- get_status_vout 输出电压状态
function pmbus:get_status_vout()
    return self:chip_byteread(self.cmd.STATUS_VOUT)
end

-- get_status_iout 输出电流状态
function pmbus:get_status_iout()
    return self:chip_byteread(self.cmd.STATUS_IOUT)
end

-- get_status_cml 逻辑、通信、存储器状态信息
function pmbus:get_status_cml()
    return self:chip_byteread(self.cmd.STATUS_CML)
end

-- get_status_vin 输入电压状态
function pmbus.get_status_vin()
    return pmbus:get_status_input()
end

function pmbus:update_status_input(event, failed_flag)
    local ok, vin_status = pcall(function()
        return self:get_status_input()
    end)
    if not ok then
        failed_flag = true
    else
        self.health_event.input_voltage_fault = vin_status
        if vin_status & 0x90 > 0 then
            event = event | get_pbit(4)
        end
    end
    return event, failed_flag
end

function pmbus:update_temper_input(event, failed_flag)
    local ok, temper_status = pcall(function()
        return self:get_status_temperature()
    end)
    if not ok then
        failed_flag = true
    else
        self.health_event.temper_fault = temper_status
        if temper_status & 0xf0 > 0 then
            event = event | get_pbit(3)
        end
    end
    return event, failed_flag
end

function pmbus:update_status_vout(event, failed_flag)
    local ok, vout_status = pcall(function()
        return self:get_status_vout()
    end)
    if not ok then
        failed_flag = true
    else
        self.health_event.output_voltage_fault = vout_status
        if vout_status & 0x90 > 0 then
            event = event | get_pbit(1)
        end
    end
    return event, failed_flag
end

function pmbus:update_status_iout(event, failed_flag)
    local ok, iout_status = pcall(function()
        return self:get_status_iout()
    end)
    if not ok then
        failed_flag = true
    else
        self.health_event.output_current_fault = iout_status
        if iout_status & 0x88 > 0 then
            event = event | get_pbit(2)
        end
    end
    return event, failed_flag
end

-- get_health_event 获取健康状态
function pmbus:get_health_event()
    local event = 0
    local failed_flag = false

    -- 输入状态信息 7C
    event, failed_flag = self:update_status_input(event, failed_flag)
    -- 温度状态信息 7D
    event, failed_flag = self:update_temper_input(event, failed_flag)
    -- 输出电压状态 7A
    event, failed_flag = self:update_status_vout(event, failed_flag)
    -- 输出电流状态 7B
    event, failed_flag = self:update_status_iout(event, failed_flag)
    self.health_event.event = event
    return self.health_event, failed_flag
end

-- check_input_loss 检查输入状态
function pmbus:check_input_loss(event)
    if event & get_pbit(4) > 0 then
        return 1
    else
        return 0
    end
end

-- get_specific 厂商自定义状态
function pmbus:get_specific()
    return self:chip_byteread(self.cmd.STATUS_MFR_SPECIFIC)
end

-- get_env_temperature_celsius 环境温度
function pmbus:get_env_temperature_celsius()
    return self:get_linear_11(self.cmd.READ_TEMPERATURE_1)
end

-- get_primary_chip_temperature_celsius 电源内部器件温度（原边）
function pmbus:get_primary_chip_temperature_celsius()
    return self:get_linear_11(self.cmd.READ_TEMPERATURE_2)
end

-- get_secondary_chip_temperature_celsius 电源内部器件温度（副边）
function pmbus:get_secondary_chip_temperature_celsius()
    return self:get_linear_11(self.cmd.READ_TEMPERATURE_3)
end

-- val_to_linear16 协议linear16 格式数据构造
function pmbus.val_to_linear16(mode, value)
    if mode & 0x10 > 0 then
        return math.ceil(value * (1 << (0x1F - (mode & 0x1F) + 1))) & 0xFFFF
    end
    return math.ceil(value / (1 << (mode & 0x1F))) & 0xFFFF
end

function pmbus.reg_vout_by_mode(mode)
    if mode == POWER_CFG_ACTIVE_POWER then
        return PMBUS_MODE_ACTIVE_VOUT
    end
    return PMBUS_MODE_STANDBY_VOUT
end

-- get_soft_load_info 获取固件参数
function pmbus:get_soft_load_info()
    local soft_load_info =  bs.new([[<<
        power_type:8/unit:8,
        soft_cnt:8,
        protocol_ver:8,
        frame_len:8,
        resv:8,
        soft_info/string
    >>]]):unpack(self:chip_blkread(self.cmd.LOAD_INFO_INQUIRY, LOAD_INFO_RESP_LEN), true)

    local soft_info = {}
    for i = 1, 3 do
        -- 软件信息大小为8字节
        table.insert(
            soft_info,
            bs.new([[<<
                soft_id:8,
                erase_time:16,
                write_waiting_time:16,
                restart_time:8,
                module_num:8,
            resv:8>>]]):unpack(soft_load_info.soft_info:sub((i - 1) * SOFT_INFO_SIZE + 1, i * SOFT_INFO_SIZE), true)
        )
    end
    return {
        power_type = soft_load_info.power_type,
        soft_cnt = soft_load_info.soft_cnt,
        protocol_ver = soft_load_info.protocol_ver,
        frame_len = soft_load_info.frame_len,
        recv = soft_load_info.recv,
        soft_info = soft_info,
    }
end

-- load_app_ctrl_cmd 下发控制命令
function pmbus:load_app_ctrl_cmd(cmd, data)
    local check_buf = string.format('%s%s%s%s', string.char((self.slot_i2c_addr)),
        string.char(self.cmd.LOAD_CTRL),
        string.char(data & 0xff),
        string.char(data >> 8))
    local crc = crc8(check_buf)
    check_buf = check_buf .. string.char(crc)
    self.psu_chip:Write(ctx.new(), cmd, check_buf:sub(3, -1))
end

function pmbus:load_app_ctrl_cmd_read(cmd)
    return self:chip_wordread(cmd)
end

function pmbus:write_read_event_log(data)
    return self:chip_bytewrite(self.cmd.READ_EVENT_LOG, data)
end

function pmbus:get_read_event_log()
    return self:chip_blkread(self.cmd.READ_EVENT_LOG, ONE_BLACK_BOX_LEN)
end

local function date_to_timestamp(date)
    local base_time = os.time({year = 1996, month = 1, day = 1, hour = 0, min = 0, sec = 0})
    
    local y, m, d = date:match("(%d+)-(%d+)-(%d+)")
    if not y then
        log:error("Invalid date format, expected YYYY-MM-DD")
        return ""
    end

    local target_time = os.time({
        year = tonumber(y), month = tonumber(m), day = tonumber(d), hour = 0, min = 0, sec = 0})
    
    local diff_seconds = os.difftime(target_time, base_time)
    if diff_seconds < 0 then
        log:error("Date cannot be earlier than 1996-01-01")
        return ""
    end

    local minutes = math.floor(diff_seconds / 60)
    if minutes > 0xFFFFFF then
        log:error("Date exceeds maximum supported range")
        return ""
    end

    return string.format("%02X%02X%02X", minutes % 256, (minutes // 256) % 256, minutes // 65536):lower()
end

function pmbus:get_fru_data(fru_config)
    local version_func
    if fru_config == 1 then
        version_func = self:get_firmware_version()
    else
        version_func = self:get_product_version()
    end
    return {
        ManufacturerName = self:get_manufacturer(),
        ProductName = self:get_model(),
        ProductVersion = version_func,
        ProductDate = self:get_production_date(),
        ProductSerialNumber = self:get_serial_number(),
        ProductPartNumber = self:get_part_number(),
        MfgDate = date_to_timestamp(self:get_production_date())
    }
end

function pmbus:get_psu_register_info(cmd, length)
    if length <= 0 then
        log:error('length is out of range', length)
        error(custom_msg.PropertyValueOutOfRange(length, "%Length"))
    end
    local is_exist = is_read_cmd(self, cmd)
    if not is_exist then
        log:error('%s cmd is out of range.', cmd)
        error(custom_msg.PropertyValueOutOfRange(cmd, "%Cmd"))
    end
    return self.psu_chip:Read(ctx.get_context_or_default(), cmd, length)
end

function pmbus:get_event_log()
    local black_box_data_table = {}
    local ok, data = pcall(function ()
        for i = 0, MAX_BLACK_BOX_NUMBER - 1 do
            self:write_read_event_log(i)
            skynet.sleep(10)
            table.insert(black_box_data_table, self:get_read_event_log())
        end
    end)
    if not ok then
        log:error('[pmbus]ps%d get event log failed, msg: %s', self.ps_id, data)
    end
    return BLACK_BOX_MAX_LEN_PMBUS, table.concat(black_box_data_table)
end

function pmbus:upgrade(upgrade_path, upgrade_process, para_tab)
    local obj = upgrade(self).new(upgrade_process, para_tab)
    local upgrade_ret = obj:process(upgrade_path)

    if upgrade_ret ~= fw_def.SUPPLY_0 then
        log:error('upgrade ps%u(protocol_name: %s) failed', self.ps_id, self.protocol_name)
    else
        log:notice('upgrade ps%u successfully', self.ps_id)
    end
    return upgrade_ret
end

-- 查询软件版本
function pmbus:inquiry_psu_version(soft_info)
    local cmd = ({
        [fw_def.FIRMWARE.DCDC_SOFT] = PS_STANDARD_READ.DC_VERSION_OFFSET,
        [fw_def.FIRMWARE.PFC_SOFT] = PS_STANDARD_READ.PFC_VERSION_OFFSET,
        [fw_def.FIRMWARE.QB_SOFT] = PS_STANDARD_READ.PFC_VERSION_OFFSET
    })[soft_info.soft_id]
    if not cmd then
        log:error("inquiry_psu_version is error, soft_id %s", soft_info.soft_id)
        return ''
    end
    for _ = 1, fw_def.GET_RESULT_RETRY do
        local ok, rsp = pcall(function()
            return self:chip_wordread(cmd)
        end)
        if ok then
            log:notice("qurey softid:%s version ok, version is %s", soft_info.soft_id, rsp)
            return nil
        end
        c_tasks.get_instance():sleep_ms(100)
    end
    return ''
end

function pmbus:time_service()
    local sys_time_second = utils_vos.vos_get_cur_time_stamp()
    if sys_time_second >= 0xffffffff then
        self.utils:frequency_limit_log(enums.LOG_LEVEL.ERROR, 60, 'sys_time_second is error, psid %d', self.ps_id)
        return
    end
    -- 这里使用小端序
    local current_time = string.pack('<I4', sys_time_second)
    local ok, resp = pcall(self.chip_blkwrite, self, self.cmd.TIME_SERVICE, current_time)
    if not ok then
        self.utils:frequency_limit_log(enums.LOG_LEVEL.ERROR, 60,
            'time service failed, psid: %d, error info: %s', self.ps_id, resp)
    end
end

return pmbus
