-- 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 ipmi = require 'ipmi'
local bs = require 'mc.bitstring'
local log = require 'mc.logging'
local enums = require 'ipmi.enums'
local c_object_manage = require 'mc.orm.object_manage'
local dfx_defs = require 'dfx_collect.dfx_defs'
local dfx_collector = require 'dfx_collect.dfx_collect_mgnt'
local fructl = require 'infrastructure.fructl'
local skynet = require 'skynet'

local channel_type = enums.ChannelType
local comp_code = ipmi.types.Cc
local NPU_PROP_INVALID_DWORD<const> = 0xFFFFFFFF
local GET_ARM_NPU_CMD<const> = 0x17
local DMP_LAST_REQ<const> = 0x80
local HEARTBEAT_LOSS<const> = 0xc3
local MAX_CHANNEL_NUM<const> = 8 -- 当前hdk支持的最大通道数是8通道
local MIN_SPEED<const> = 1000
local OP_BASE_INFO_LEN<const> = 130
local OP_NOT_PRESENT<const> = 9 -- hdk返回错误码为9，表示光模块不在位。
local MACRO_MAX<const> = 7
local imu_cmd = {}
local self = imu_cmd


local npu_dmp_req = bs.new([[<<
    0xDB0700:3/unit:8,
    cmd:1/unit:8,
    lun:1/unit:8,
    para:1/unit:8,
    op_cmd:1/unit:8,
    op_fun:1/unit:8,
    offset:4/unit:8,
    data_length:4/unit:8
    >>]])
-- 获取NPU网口的收发总包数、丢包数
local rspNpuEthStat = bs.new([[<<
    head:3/unit:8,
    total_length:4/little-unit:8,
    respond_length:4/little-unit:8,
    received:4/little-unit:8,
    transmitted:4/little-unit:8,
    dropped:4/little-unit:8
    >>]])
local function get_npu_eth_statistics(bus, id)
    -- ReceivedPackets的RESPONSE_DATA_INDEX为12~15，TransmittedPackets的RESPONSE_DATA_INDEX为16~19,
    -- DroppedPackets的RESPONSE_DATA_INDEX为20~23
    local received, transmitted, dropped =
        NPU_PROP_INVALID_DWORD, NPU_PROP_INVALID_DWORD, NPU_PROP_INVALID_DWORD
    local req_data = npu_dmp_req:pack({
        cmd = GET_ARM_NPU_CMD,
        lun = DMP_LAST_REQ,
        para = 0,
        op_cmd = 0x89, -- 0x0089 查询网口收发信息
        op_fun = 0,
        offset = 0,
        data_length = 12
    })
    local result, payload = ipmi.request(
        bus, {channel_type.CT_ME:value(), id},
        {DestNetFn = 0x30, Cmd = 0x98, Payload = req_data}
    )
    if result ~= comp_code.Success then
        return result, received, transmitted, dropped
    end
    local rsp = rspNpuEthStat:unpack(payload)
    if rsp and rsp.received and rsp.transmitted and rsp.dropped then
        received = rsp.received
        transmitted = rsp.transmitted
        dropped = rsp.dropped
    end
    log:debug('network_port%s received = %s, transmitted = %s, dropped = %s',
        id, received, transmitted, dropped)
    return result, received, transmitted, dropped
end

-- 获取NPU网口的mac地址
local rspNpuEthMac = bs.new([[<<
    head:3/unit:8,
    total_length:4/little-unit:8,
    respond_length:4/little-unit:8,
    mac_addr_a:1/little-unit:8,
    mac_addr_b:1/little-unit:8,
    mac_addr_c:1/little-unit:8,
    mac_addr_d:1/little-unit:8,
    mac_addr_e:1/little-unit:8,
    mac_addr_f:1/little-unit:8
    >>]])
local function get_npu_eth_mac(bus, id)
    log:debug('start to get npu eth mac, id: %s', id)
    local mac_addr = 'N/A'
    local req_data = npu_dmp_req:pack({
        cmd = GET_ARM_NPU_CMD,
        lun = DMP_LAST_REQ,
        para = 0,
        op_cmd = 0x86, -- 0x0089 查询网口mac地址
        op_fun = 0,
        offset = 0,
        data_length = 6
    })
    local result, payload = ipmi.request(
        bus, {channel_type.CT_ME:value(), id},
        {DestNetFn = 0x30, Cmd = 0x98, Payload = req_data}
    )
    if result ~= comp_code.Success then
        return result, mac_addr
    end
    local rsp = rspNpuEthMac:unpack(payload)
    if rsp and rsp.mac_addr_a and rsp.mac_addr_b and rsp.mac_addr_c and 
        rsp.mac_addr_d and rsp.mac_addr_e and rsp.mac_addr_f then
        mac_addr = string.format('%02X:%02X:%02X:%02X:%02X:%02X', rsp.mac_addr_a, rsp.mac_addr_b,
            rsp.mac_addr_c, rsp.mac_addr_d, rsp.mac_addr_e, rsp.mac_addr_f)
    end
    log:debug('network_port%s mac_addr = %s', id, mac_addr)
    return result, mac_addr
end

-- 获取NPU网口的ipv4、掩码、网关
local function get_npu_eth_ipv4(bus, id, op_cmd, offset)
    log:debug('start to get npu eth ipv4, id: %s, op_cmd: %s, offset: %d', id, op_cmd, offset)
    local data = 'N/A'
    local req_data = npu_dmp_req:pack({
        cmd = GET_ARM_NPU_CMD,
        lun = DMP_LAST_REQ,
        para = 0,
        op_cmd = op_cmd,
        op_fun = 0,
        offset = offset,
        data_length = 16
    })
    local retry = 5
    local result, payload
    while retry >= 0 do
        result, payload = ipmi.request(bus, {channel_type.CT_ME:value(), id},
            {DestNetFn = 0x30, Cmd = 0x98, Payload = req_data})
        if result == comp_code.Success then
            break
        end
        retry = retry - 1
        skynet.sleep(50)
    end

    if result ~= comp_code.Success then
        log:info('network_port%s op_cmd = %s, offset = %s, get data failed', id, op_cmd, offset)
        return result, data
    end
    local rsp = {}
    for i = 1, #payload, 1 do
        rsp[#rsp + 1] = string.byte(payload, i, i)
    end
    -- ipv4、掩码、网关均取第12-15位拼接
    if rsp and rsp[12] and rsp[13] and rsp[14] and rsp[15] then
        data = string.format('%s.%s.%s.%s', rsp[12], rsp[13], rsp[14], rsp[15])
    end
    log:info('network_port%s op_cmd = %s, offset = %s, data = %s', id, op_cmd, offset, data)
    return result, data
end

local op_static_req = bs.new([[<<
    0xDB0700:3/unit:8,
    cmd:1/unit:8,
    lun:1/unit:8,
    para:1/unit:8,
    op_cmd:1/unit:8,
    op_fun:1/unit:8,
    offset:4/unit:8,
    data_length:4/unit:8
    >>]])

local get_port_statistics_req = op_static_req:pack({
    cmd = GET_ARM_NPU_CMD,
    lun = DMP_LAST_REQ,
    para = 0,
    op_cmd = 0x8f,
    op_fun = 0x06, -- 0x068f:端口统计状态信息获取
    offset = 0,
    data_length = 0
})

local get_port_statistics_rsp = bs.new([[<<
    head:3/unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    rx_total_pkt_num:4/little-unit:8,
    rx_fcs_err_pkt_num:4/little-unit:8,
    tail/binary
    >>]])

local get_port_statistics_extra_rsp = bs.new([[<<
    rx_bad_pkt_num:4/little-unit:8,
    tx_bad_pkt_num:4/little-unit:8,
    link_up_cnt:8/little-unit:8,
    link_down_cnt:8/little-unit:8,
    link_up_timestamp1:4/little-unit:8,
    link_up_timestamp2:4/little-unit:8,
    link_up_timestamp3:4/little-unit:8,
    link_up_timestamp4:4/little-unit:8,
    link_up_timestamp5:4/little-unit:8,
    link_up_timestamp6:4/little-unit:8,
    link_up_timestamp7:4/little-unit:8,
    link_up_timestamp8:4/little-unit:8,
    link_up_timestamp9:4/little-unit:8,
    link_up_timestamp10:4/little-unit:8,
    link_down_timestamp1:4/little-unit:8,
    link_down_timestamp2:4/little-unit:8,
    link_down_timestamp3:4/little-unit:8,
    link_down_timestamp4:4/little-unit:8,
    link_down_timestamp5:4/little-unit:8,
    link_down_timestamp6:4/little-unit:8,
    link_down_timestamp7:4/little-unit:8,
    link_down_timestamp8:4/little-unit:8,
    link_down_timestamp9:4/little-unit:8,
    link_down_timestamp10:4/little-unit:8,
    tail/string
    >>]])

local function get_port_statistics(bus, id)
    local rx_fcs_err_pkt_num = NPU_PROP_INVALID_DWORD
    local link_up_cnt, link_down_cnt = 0, 0
    local link_up_timestamp, link_down_timestamp = {}, {}
    local result, payload = ipmi.request(
        bus, {channel_type.CT_ME:value(), id},
        {DestNetFn = 0x30, Cmd = 0x98, Payload = get_port_statistics_req}
    )
    if result ~= comp_code.Success then
        return result, rx_fcs_err_pkt_num, link_up_cnt, link_down_cnt, link_up_timestamp,
            link_down_timestamp
    end
    local rsp = get_port_statistics_rsp:unpack(payload, true)
    if rsp and rsp.rx_fcs_err_pkt_num then
        rx_fcs_err_pkt_num = rsp.rx_fcs_err_pkt_num
    end
    log:debug('network_port%s rx_fcs_err_pkt_num = %s', id, rx_fcs_err_pkt_num)
    local ok, rsp_extra = pcall(function ()
        return get_port_statistics_extra_rsp:unpack(rsp.tail, true)
    end)
    if not ok then
        return result, rx_fcs_err_pkt_num, link_up_cnt, link_down_cnt, link_up_timestamp,
            link_down_timestamp
    end
    if rsp_extra and rsp_extra.link_up_cnt then
        link_up_cnt = rsp_extra.link_up_cnt
    end
    if rsp_extra and rsp_extra.link_down_cnt then
        link_down_cnt = rsp_extra.link_down_cnt
    end
    log:debug('network_port%s link_up_cnt = %s, link_down_cnt = %s', id, link_up_cnt, link_down_cnt)
    if rsp_extra and rsp_extra.link_up_timestamp10 then
        link_up_timestamp = {rsp_extra.link_up_timestamp1, rsp_extra.link_up_timestamp2,
            rsp_extra.link_up_timestamp3, rsp_extra.link_up_timestamp4, rsp_extra.link_up_timestamp5,
            rsp_extra.link_up_timestamp6, rsp_extra.link_up_timestamp7, rsp_extra.link_up_timestamp8,
            rsp_extra.link_up_timestamp9, rsp_extra.link_up_timestamp10}
    end
    if rsp_extra and rsp_extra.link_down_timestamp10 then
        link_down_timestamp = {rsp_extra.link_down_timestamp1, rsp_extra.link_down_timestamp2,
            rsp_extra.link_down_timestamp3, rsp_extra.link_down_timestamp4, rsp_extra.link_down_timestamp5,
            rsp_extra.link_down_timestamp6, rsp_extra.link_down_timestamp7, rsp_extra.link_down_timestamp8,
            rsp_extra.link_down_timestamp9, rsp_extra.link_down_timestamp10}
    end
    return result, rx_fcs_err_pkt_num, link_up_cnt, link_down_cnt, link_up_timestamp, link_down_timestamp
end

function imu_cmd.get_info_from_imu(id, power_on)
    local rx_fcs_err_pkt_num, received, transmitted, dropped, mac_addr, gateway, ip_addr, subnet_mask,
        link_up_cnt, link_down_cnt, link_up_timestamp, link_down_timestamp
        = NPU_PROP_INVALID_DWORD, NPU_PROP_INVALID_DWORD, NPU_PROP_INVALID_DWORD, NPU_PROP_INVALID_DWORD,
        'N/A', 'N/A', 'N/A', 'N/A', 0, 0, {}, {}
    local bus = c_object_manage.get_instance().bus
    log:debug('npu%s power_on is %s', id, power_on)
    -- power_on值为1代表NPU驱动正常运行，通过smc命令字获取
    if fructl.get_power_status() == 'ON' and power_on == 1 then
        log:debug('network_port get_info_from_imu')
        _, received, transmitted, dropped = get_npu_eth_statistics(bus, id)
        skynet.sleep(10)
        _, mac_addr = get_npu_eth_mac(bus, id)
        skynet.sleep(10)
        _, gateway = get_npu_eth_ipv4(bus, id, 0x87, 0) -- gateway的op_cmd为0x87，offset为0
        skynet.sleep(10)
        _, ip_addr = get_npu_eth_ipv4(bus, id, 0x88, 0) -- ip_addr的op_cmd为0x88，offset为0
        skynet.sleep(10)
        _, subnet_mask = get_npu_eth_ipv4(bus, id, 0x88, 16) -- subnet_mask的op_cmd为0x88，offset为16
        skynet.sleep(10)
        _, rx_fcs_err_pkt_num, link_up_cnt, link_down_cnt, link_up_timestamp, link_down_timestamp =
            get_port_statistics(bus, id)
    end
    return {
        rx_fcs_err_pkt_num = tostring(rx_fcs_err_pkt_num),
        link_up_cnt = link_up_cnt,
        link_down_cnt = link_down_cnt,
        link_up_timestamp = link_up_timestamp,
        link_down_timestamp = link_down_timestamp,
        received = tostring(received),
        transmitted = tostring(transmitted),
        dropped = tostring(dropped),
        mac_addr = mac_addr,
        gateway = gateway,
        ip_addr = ip_addr,
        subnet_mask = subnet_mask
    }
end

local function update_hccs_info(macro_id, rsp)
    local health_status = rsp.health_status_support == 1 and rsp.health_status or 0
    local lane_mode = rsp.lane_mode_support == 1 and rsp.lane_mode or 0
    local link_lane_list = rsp.link_lane_list_support == 1 and rsp.link_lane_list or 0
    local link_speed = rsp.link_speed_support == 1 and rsp.link_speed or 0
    local tx_packets = rsp.tx_packets_support == 1 and rsp.tx_packets or 0
    local tx_bytes = rsp.tx_bytes_support == 1 and rsp.tx_bytes or 0
    local rx_packets = rsp.rx_packets_support == 1 and rsp.rx_packets or 0
    local rx_bytes = rsp.rx_bytes_support == 1 and rsp.rx_bytes or 0
    local retry_count = rsp.retry_count_support == 1 and rsp.retry_count or 0
    local error_count = rsp.error_count_support == 1 and rsp.error_count or 0
    local first_error_lane = rsp.first_error_lane_support == 1 and rsp.first_error_lane or 0
    local snr = rsp.snr_support == 1 and {rsp.snr1, rsp.snr2, rsp.snr3, rsp.snr4} or
        {0, 0, 0, 0}
    local half_height = rsp.half_height_support == 1 and
        {rsp.half_height1, rsp.half_height2, rsp.half_height3, rsp.half_height4} or
        {0, 0, 0, 0}
    return {
        macro_id = macro_id,
        health_status = health_status,
        lane_mode = lane_mode,
        link_lane_list = link_lane_list,
        link_speed = link_speed,
        tx_packets = tx_packets,
        tx_bytes = tx_bytes,
        rx_packets = rx_packets,
        rx_bytes = rx_bytes,
        retry_count = retry_count,
        error_count = error_count,
        first_error_lane = first_error_lane,
        snr = snr,
        half_height = half_height
    }
end

local hccs_info_req = bs.new([[<<
    0xDB0700:3/unit:8,
    cmd:1/unit:8,
    lun:1/unit:8,
    para:1/unit:8,
    op_cmd:1/unit:8,
    op_fun:1/unit:8,
    offset:4/unit:8,
    data_length:4/unit:8,
    macro_id:1/unit:8
>>]])

local hccs_info_rsp = bs.new([[<<
    error_code:1/unit:8,
    opcode:2/little-unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    cmd_version:1/unit:8,

    # support_info 有7个字节,每个bit代表接下来结构体中对应顺序的属性是否支持,小端字节序
    reserved:43,
    half_height_support:1,
    snr_support:1,
    first_error_lane_support:1,
    error_count_support:1,
    retry_count_support:1,
    rx_bytes_support:1,
    rx_packets_support:1,
    tx_bytes_support:1,
    tx_packets_support:1,
    link_speed_support:1,
    link_lane_list_support:1,
    lane_mode_support:1,
    health_status_support:1,

    health_status:4/little-unit:8,
    lane_mode:4/little-unit:8,
    link_lane_list:4/little-unit:8,
    link_speed:4/little-unit:8,
    tx_packets:8/little-unit:8,
    tx_bytes:8/little-unit:8,
    rx_packets:8/little-unit:8,
    rx_bytes:8/little-unit:8,
    retry_count:8/little-unit:8,
    error_count:8/little-unit:8,
    first_error_lane:4/little-unit:8,
    snr1:4/little-unit:8,
    snr2:4/little-unit:8,
    snr3:4/little-unit:8,
    snr4:4/little-unit:8,
    half_height1:4/little-unit:8,
    half_height2:4/little-unit:8,
    half_height3:4/little-unit:8,
    half_height4:4/little-unit:8,
    tail/string
>>]])

local function get_npu_hccs(id, macro_id)
    log:debug('start get hccs info from npu%s macro%s', id, macro_id)
    local bus = c_object_manage.get_instance().bus
    local get_hccs_info_req = hccs_info_req:pack({
        cmd = GET_ARM_NPU_CMD,
        lun = DMP_LAST_REQ,
        para = 0,
        op_cmd = 0xb5,
        op_fun = 0x06, -- 0x06b5:hccs信息获取
        offset = 0,
        data_length = 1,
        macro_id = macro_id
    })
    local result, payload = ipmi.request(bus, {channel_type.CT_ME:value(), id},
        { DestNetFn = 0x30, Cmd = 0x98, Payload = get_hccs_info_req }
    )
    if result ~= comp_code.Success then
        log:debug('get_npu_hccs failed, npuid%s macro%s, result = %s', id, macro_id, result)
        return nil
    end
    local rsp = hccs_info_rsp:unpack(payload)
    if not rsp then
        log:debug('get_npu_hccs unpack failed')
        return nil
    end
    return true, update_hccs_info(macro_id, rsp)
end

function imu_cmd.get_hccs_info_from_imu(id, power_on)
    local rsp, macro_info
    local hccs_info = {}
    if fructl.get_power_status() == 'ON' and power_on == 1 then
        for i = 1, MACRO_MAX, 1 do -- 查询macro_id从1到7的数据
            rsp, macro_info = get_npu_hccs(id, i)
            if rsp then
                hccs_info[#hccs_info + 1] = macro_info
            end
        end
    end
    return hccs_info
end

local op_smart_info_req = bs.new([[<<
    0xDB0700:3/unit:8,
    cmd:1/unit:8,
    lun:1/unit:8,
    request_parameter:1/unit:8,
    op_cmd:1/unit:8,
    op_fun:1/unit:8,
    offset:4/unit:8,
    data_length:4/unit:8,
    request_type:1/unit:8
>>]])

local op_runtime_info_rsp = bs.new([[<<
    error_code:1/unit:8,
    opcode:2/little-unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    cmd_version:1/unit:8,

    # support_info 有7个字节,每个bit代表接下来结构体中对应顺序的属性是否支持,小端字节序
    reserved:47,
    laser_core_temp_supported:1,
    laser_temp_supported:1,
    laser_run_time_supported:1,
    odsp_high_heat_time_supported:1,
    odsp_temp_supported:1,
    power_status_supported:1,
    poweron_count_supported:1,
    poweron_time_supported:1,
    runtime_supported:1,

    runtime:4/little-unit:8,
    poweron_time:4/little-unit:8,
    poweron_count:2/little-unit:8,
    power_status:2/little-unit:8,
    # odsp的温度,单位为1/256度
    odsp_temp:2/little-unit:8,
    odsp_high_heat_time:4/little-unit:8,
    laser_run_time:4/little-unit:8,
    # laser的温度,单位为1/256度
    laser_temp:2/little-unit:8,
    # laser核温,单位为1/256度
    laser_core_temp:2/little-unit:8,
    tail/string
>>]])

local op_alarm_info_rsp = bs.new([[<<
    error_code:1/unit:8,
    opcode:2/little-unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    cmd_version:1/unit:8,

    # support_info 有7个字节,每个bit代表接下来结构体中对应顺序的属性是否支持,小端字节序
    reserved:52,
    # 光链路检测
    media_interface_fault_supported:1,
    # 电链路检测
    host_interface_fault_supported:1,
    # 模块本体诊断一般隐患,bmc不用于告警
    hardware_minor_fault_supported:1,
    # 模块本体诊断严重故障,用于bmc告警
    hardware_major_fault_supported:1,

    hardware_fault:8/little-unit:8,
    host_interface_fault:2/little-unit:8,
    media_interface_fault:2/little-unit:8,
    tail/string
>>]])

local get_op_runtime_info_req = op_smart_info_req:pack({
    cmd = GET_ARM_NPU_CMD,
    lun = DMP_LAST_REQ,
    request_parameter = 0,
    op_cmd = 0xa2,
    op_fun = 0x06, -- 0x06a2:智能光模块信息获取
    offset = 0,
    data_length = 1,
    request_type = 0  -- 0:运行时数据,1:告警诊断数据
})

function imu_cmd.get_op_runtime_info(id)
    local bus = c_object_manage.get_instance().bus
    if log:getLevel() >= log.DEBUG then
        log:debug('start get op runtime info from npu')
    end
    local result, payload = ipmi.request(
        bus, {channel_type.CT_ME:value(), id},
        { DestNetFn = 0x30, Cmd = 0x98, Payload = get_op_runtime_info_req }
    )
    if result ~= comp_code.Success then
        if log:getLevel() >= log.DEBUG then
            log:debug('get_op_runtime_info failed, npuid = %s, result = %s', id, result)
        end
        return nil
    end
    local rsp = op_runtime_info_rsp:unpack(payload)
    if not rsp then
        if log:getLevel() >= log.DEBUG then
            log:debug('get_op_runtime_info unpack failed')
        end
        return nil
    end

    if log:getLevel() >= log.DEBUG then
        log:debug('optical%s poweron_count = [%s] %s, poweron_time = [%s] %s, power_status = [%s] %s',
            id, rsp.poweron_count_supported, rsp.poweron_count,
            rsp.poweron_time_supported, rsp.poweron_time, rsp.power_status_supported, rsp.power_status)
    end
    if log:getLevel() >= log.DEBUG then
        log:debug('odsp_temp = [%s] %s, odsp_high_heat_time = [%s] %s, ' ..
            'laser_run_time = [%s] %s,laser_core_temp = [%s] %s',
            rsp.odsp_temp_supported, rsp.odsp_temp, rsp.odsp_high_heat_time_supported, rsp.odsp_high_heat_time,
            rsp.laser_run_time_supported, rsp.laser_run_time, rsp.laser_core_temp_supported, rsp.laser_core_temp)
    end
    local poweron_count = rsp.poweron_count_supported == 1 and rsp.poweron_count or 0xffff
    local poweron_time = rsp.poweron_time_supported == 1 and rsp.poweron_time or 0xffffffff
    local power_status = rsp.power_status_supported == 1 and rsp.power_status or 0xffff
    local odsp_temp = rsp.odsp_temp_supported == 1 and (rsp.odsp_temp >> 8) or 0xffff
    local odsp_high_heat_time = rsp.odsp_high_heat_time_supported == 1 and rsp.odsp_high_heat_time or 0xffffffff
    local laser_run_time = rsp.laser_run_time_supported == 1 and rsp.laser_run_time or 0xffffffff
    local laser_core_temp = rsp.laser_core_temp_supported == 1 and (rsp.laser_core_temp >> 8) or 0xffff
    return {
        PowerOnCount = poweron_count,
        UptimeSeconds = poweron_time,
        PowerStatus = power_status,
        OdspDieTemperatureCelsius = odsp_temp,
        OdspHighTempRuntimeSeconds = odsp_high_heat_time,
        LaserRuntimeSeconds = laser_run_time,
        LaserTemperatureCelsius = laser_core_temp
    }
end

local get_op_alarm_info_req = op_smart_info_req:pack({
    cmd = GET_ARM_NPU_CMD,
    lun = DMP_LAST_REQ,
    request_parameter = 0,
    op_cmd = 0xa2,
    op_fun = 0x06, -- 0x06a2:智能光模块信息获取
    offset = 0,
    data_length = 1,
    request_type = 1  -- 0:运行时数据,1:告警诊断数据
})

function imu_cmd.get_op_alarm_info(id)
    local bus = c_object_manage.get_instance().bus
    if log:getLevel() >= log.DEBUG then
        log:debug('start get op alarm info from npu')
    end
    local result, payload = ipmi.request(
        bus, {channel_type.CT_ME:value(), id},
        { DestNetFn = 0x30, Cmd = 0x98, Payload = get_op_alarm_info_req }
    )
    if result ~= comp_code.Success then
        if log:getLevel() >= log.DEBUG then
            log:debug('get_op_alarm_info failed, result = %s', result)
        end
        return nil
    end
    local rsp = op_alarm_info_rsp:unpack(payload)
    if not rsp then
        if log:getLevel() >= log.DEBUG then
            log:debug('get_op_alarm_info unpack failed')
        end
        return nil
    end
    if log:getLevel() >= log.INFO then
        log:info('optical%s hardware_fault = [%s %s] %s, host_interface_fault = [%s] %s, ' ..
            'media_interface_fault = [%s] %s',
            id, rsp.hardware_minor_fault_supported, rsp.hardware_major_fault_supported, rsp.hardware_fault,
            rsp.host_interface_fault_supported, rsp.host_interface_fault,
            rsp.media_interface_fault_supported, rsp.media_interface_fault)
    end
    local hardware_fault = rsp.hardware_major_fault_supported == 1 and
        rsp.hardware_fault or -1
    local host_interface_fault = rsp.host_interface_fault_supported == 1 and rsp.host_interface_fault or 0xffff
    local media_interface_fault = rsp.media_interface_fault_supported == 1 and rsp.media_interface_fault or 0xffff
    return {
        HardwareFaultStatus = hardware_fault,
        HostInterfaceFaultStatus = host_interface_fault,
        MediaInterfaceFaultStatus = media_interface_fault
    }
end

local op_statistics_info_req = bs.new([[<<
    0xDB0700:3/unit:8,
    cmd:1/unit:8,
    lun:1/unit:8,
    request_parameter:1/unit:8,
    op_cmd:1/unit:8,
    op_fun:1/unit:8,
    offset:4/unit:8,
    data_length:4/unit:8
>>]])

local op_statistics_info_rsp = bs.new([[<<
    error_code:1/unit:8,
    opcode:2/little-unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    cmd_version:1/unit:8,

    # support_info 有7个字节,每个bit代表接下来结构体中对应顺序的属性是否支持,小端字节序
    reserved:45,
    cdr_media_snr_support:1,
    cdr_host_snr_support:1,
    npu_rx_snr_support:1,
    fec_fail_support:1,
    fec_success_support:1,
    fec_pre_support:1,
    pcs_err_cnt_support:1,
    tx_bad_pkt_num_support:1,
    rx_bad_pkt_num_support:1,
    rx_fec_err_pkt_num_support:1,
    rx_total_pkt_num_support:1,

    rx_total_pkt_num:8/little-unit:8,
    rx_fec_err_pkt_num:8/little-unit:8,
    rx_bad_pkt_num:8/little-unit:8,
    tx_bad_pkt_num:8/little-unit:8,
    pcs_err_cnt:8/little-unit:8,
    fec_pre:8/little-unit:8,
    fec_success:8/little-unit:8,
    fec_fail:8/little-unit:8,
    tail/string
>>]])

local get_op_statistics_info_req = op_statistics_info_req:pack({
    cmd = GET_ARM_NPU_CMD,
    lun = DMP_LAST_REQ,
    request_parameter = 0,
    op_cmd = 0xa3,
    op_fun = 0x06, -- 0x06a3:智能光模块信息获取
    offset = 0,
    data_length = 0
})

function imu_cmd.get_op_statistics_info(id)
    local bus = c_object_manage.get_instance().bus
    if log:getLevel() >= log.DEBUG then
        log:debug('start get op statistics info from npu')
    end
    local result, payload = ipmi.request(
        bus, {channel_type.CT_ME:value(), id},
        { DestNetFn = 0x30, Cmd = 0x98, Payload = get_op_statistics_info_req }
    )
    if result ~= comp_code.Success then
        if log:getLevel() >= log.DEBUG then
            log:debug('get_op_statistics_info failed, npuid = %s, result = %s', id, result)
        end
        return nil
    end
    local rsp = op_statistics_info_rsp:unpack(payload)
    if not rsp then
        if log:getLevel() >= log.DEBUG then
            log:debug('get_op_statistics_info unpack failed')
        end
        return nil
    end

    if log:getLevel() >= log.DEBUG then
        log:debug('optical%s fec_pre = [support:%s]%s, fec_success = [support:%s]%s, fec_fail = [support:%s]%s',
            id, rsp.fec_pre_support, rsp.fec_pre, rsp.fec_success_support,
            rsp.fec_success, rsp.fec_fail_support, rsp.fec_fail)
    end

    local fec_success = rsp.fec_success_support == 1 and rsp.fec_success or 0
    local fec_fail = rsp.fec_fail_support == 1 and rsp.fec_fail or 0

    return {
        CorrectableFECErrors = fec_success,
        UnCorrectableFECErrors = fec_fail
    }
end

local function u32_to_signed(num)
    if num < 2 ^ 31 then
        return num
    end
    return num - 2 ^ 32
end

local function remove_array_default(channel_num, tx_power, rx_power, tx_bias, host_snr, media_snr)

    while channel_num < MAX_CHANNEL_NUM do
        table.remove(tx_power)
        table.remove(rx_power)
        table.remove(tx_bias)
        table.remove(host_snr)
        table.remove(media_snr)
        channel_num = channel_num + 1
    end
    return tx_power, rx_power, tx_bias, host_snr, media_snr
end

local op_static_req = bs.new([[<<
    0xDB0700:3/unit:8,
    cmd:1/unit:8,
    lun:1/unit:8,
    para:1/unit:8,
    op_cmd:1/unit:8,
    op_fun:1/unit:8,
    offset:4/unit:8,
    data_length:4/unit:8
    >>]])

local op_static_rsp = bs.new([[<<
    head:3/unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    voltage:4/little-unit:8,
    tx_power1:2/big-unit:8,
    tx_power2:2/big-unit:8,
    tx_power3:2/big-unit:8,
    tx_power4:2/big-unit:8,
    tx_power5:2/big-unit:8,
    tx_power6:2/big-unit:8,
    tx_power7:2/big-unit:8,
    tx_power8:2/big-unit:8,
    rx_power1:2/big-unit:8,
    rx_power2:2/big-unit:8,
    rx_power3:2/big-unit:8,
    rx_power4:2/big-unit:8,
    rx_power5:2/big-unit:8,
    rx_power6:2/big-unit:8,
    rx_power7:2/big-unit:8,
    rx_power8:2/big-unit:8,
    tx_bias1:2/big-unit:8,
    tx_bias2:2/big-unit:8,
    tx_bias3:2/big-unit:8,
    tx_bias4:2/big-unit:8,
    tx_bias5:2/big-unit:8,
    tx_bias6:2/big-unit:8,
    tx_bias7:2/big-unit:8,
    tx_bias8:2/big-unit:8,
    tx_los:4/little-unit:8,
    rx_los:4/little-unit:8,
    tx_lol:4/little-unit:8,
    rx_lol:4/little-unit:8,
    temperature:4/little-unit:8,
    tx_fault:4/little-unit:8,
    host_snr1:32/little-float,
    host_snr2:32/little-float,
    host_snr3:32/little-float,
    host_snr4:32/little-float,
    host_snr5:32/little-float,
    host_snr6:32/little-float,
    host_snr7:32/little-float,
    host_snr8:32/little-float,
    media_snr1:32/little-float,
    media_snr2:32/little-float,
    media_snr3:32/little-float,
    media_snr4:32/little-float,
    media_snr5:32/little-float,
    media_snr6:32/little-float,
    media_snr7:32/little-float,
    media_snr8:32/little-float,
    access_failed:1/little-unit:8,
    tail/string
    >>]])
local UNIT_CONVERSION1<const> = 0.0001
local UNIT_CONVERSION2<const> = 0.002
local get_op_status_info_req = op_static_req:pack({
    cmd = GET_ARM_NPU_CMD,
    lun = DMP_LAST_REQ,
    para = 0,
    op_cmd = 0x8e,
    op_fun = 0x06, -- 0x068e:光模块状态信息获取
    offset = 0,
    data_length = 0
})
local function get_op_status_info(bus, id, channel_num)
    local voltage, temperature = 0xFFFF, 255
    local tx_los, rx_los, tx_fault, access_failed, tx_lol, rx_lol = 0, 0, 0, 0, 0, 0
    local tx_power, rx_power, tx_bias, host_snr, media_snr = {}, {}, {}, {}, {}
    local result, payload = ipmi.request(bus,{channel_type.CT_ME:value(), id},
        {DestNetFn = 0x30, Cmd = 0x98, Payload = get_op_status_info_req}
    )
    if result ~= comp_code.Success then
        if log:getLevel() >= log.DEBUG then
            log:debug('get op_status result is %s', result)
        end
        return result, voltage, tx_power, rx_power, tx_bias, tx_los, rx_los, tx_lol, rx_lol,
            temperature, tx_fault, host_snr, media_snr, access_failed
    end
    local rsp = op_static_rsp:unpack(payload)
    if not rsp then
        if log:getLevel() >= log.DEBUG then
            log:debug('unpack fail get_op_status_info')
        end
        return result, voltage, tx_power, rx_power, tx_bias, tx_los, rx_los, tx_lol, rx_lol,
            temperature, tx_fault, host_snr, media_snr, access_failed
    end
    voltage = (rsp.voltage * UNIT_CONVERSION1) or 255
    tx_los = rsp.tx_los or 0
    rx_los = rsp.rx_los or 0
    tx_lol = rsp.tx_lol or 0
    rx_lol = rsp.rx_lol or 0
    temperature = rsp.temperature or 255
    tx_power = {rsp.tx_power1 * UNIT_CONVERSION1, rsp.tx_power2 * UNIT_CONVERSION1, rsp.tx_power3 * UNIT_CONVERSION1,
        rsp.tx_power4 * UNIT_CONVERSION1, rsp.tx_power5 * UNIT_CONVERSION1, rsp.tx_power6 * UNIT_CONVERSION1,
        rsp.tx_power7 * UNIT_CONVERSION1, rsp.tx_power8 * UNIT_CONVERSION1}
    rx_power = {rsp.rx_power1 * UNIT_CONVERSION1, rsp.rx_power2 * UNIT_CONVERSION1, rsp.rx_power3 * UNIT_CONVERSION1,
        rsp.rx_power4 * UNIT_CONVERSION1, rsp.rx_power5 * UNIT_CONVERSION1, rsp.rx_power6 * UNIT_CONVERSION1,
        rsp.rx_power7 * UNIT_CONVERSION1, rsp.rx_power8 * UNIT_CONVERSION1}
    tx_bias = {rsp.tx_bias1 * UNIT_CONVERSION2, rsp.tx_bias2 * UNIT_CONVERSION2, rsp.tx_bias3 * UNIT_CONVERSION2,
        rsp.tx_bias4 * UNIT_CONVERSION2, rsp.tx_bias5 * UNIT_CONVERSION2, rsp.tx_bias6 * UNIT_CONVERSION2,
        rsp.tx_bias7 * UNIT_CONVERSION2, rsp.tx_bias8 * UNIT_CONVERSION2}
    host_snr = {rsp.host_snr1, rsp.host_snr2, rsp.host_snr3, rsp.host_snr4, rsp.host_snr5,
        rsp.host_snr6, rsp.host_snr7, rsp.host_snr8}
    media_snr = {rsp.media_snr1, rsp.media_snr2, rsp.media_snr3, rsp.media_snr4, rsp.media_snr5,
        rsp.media_snr6, rsp.media_snr7, rsp.media_snr8}
    -- tx_power rx_power tx_bias host_snr media_snr 需要根据光模块通道数进行裁减
    -- 当光模块是四通道，上述参数返回值后4个是0 无效值，上树会影响web和redfish展示
    remove_array_default(channel_num, tx_power, rx_power, tx_bias, host_snr, media_snr)
    access_failed = rsp.access_failed or 0
    return result, voltage, tx_power, rx_power, tx_bias, tx_los, rx_los, tx_lol, rx_lol,
        temperature, tx_fault, host_snr, media_snr, access_failed
end

function imu_cmd.heartbeat_check(id)
    local bus = c_object_manage.get_instance().bus
    if log:getLevel() >= log.DEBUG then
        log:debug('start check heartbeat from npu')
    end
    local result = ipmi.request(
        bus, {channel_type.CT_ME:value(), id},
        { DestNetFn = 0x30, Cmd = 0x98, Payload = get_port_statistics_req }
    )
    if result == HEARTBEAT_LOSS then
        if log:getLevel() >= log.DEBUG then
            log:debug('heartbeat_check failed, set heartbeat loss status, npu id = %s', id)
        end
        return true
    end
    return false
end

-- 获取光模块门限值
local op_static_threshold_rsp = bs.new([[<<
    head:3/unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    vcc_high_warn_threshold:4/little-unit:8,
    vcc_low_warn_threshold:4/little-unit:8,
    temp_high_warn_threshold:4/little-unit:8,
    temp_low_warn_threshold:4/little-unit:8,
    tx_power_high_warn_threshold:4/little-unit:8,
    tx_power_low_warn_threshold:4/little-unit:8,
    rx_power_high_warn_threshold:4/little-unit:8,
    rx_power_low_warn_threshold:4/little-unit:8,
    bias_high_warn_threshold:4/little-unit:8,
    bias_low_warn_threshold:4/little-unit:8,
    vcc_high_critical_threshold:4/little-unit:8,
    vcc_low_critical_threshold:4/little-unit:8,
    temp_high_critical_threshold:4/little-unit:8,
    temp_low_critical_threshold:4/little-unit:8,
    tx_power_high_critical_threshold:4/little-unit:8,
    tx_power_low_critical_threshold:4/little-unit:8,
    rx_power_high_critical_threshold:4/little-unit:8,
    rx_power_low_critical_threshold:4/little-unit:8,
    bias_high_critical_threshold:4/little-unit:8,
    bias_low_critical_threshold:4/little-unit:8,
    tail/string
    >>]])

local get_op_threshold_req = op_static_req:pack({
    cmd = GET_ARM_NPU_CMD,
    lun = DMP_LAST_REQ,
    para = 0,
    op_cmd = 0x8d,
    op_fun = 0x06, -- 0x068d:光模块告警门限获取
    offset = 0,
    data_length = 0
})


local function get_op_threshold(bus, id)
    local TemperatureLowerThresholdCritical, TemperatureUpperThresholdCritical,
        VoltageLowerThresholdCritical, VoltageUpperThresholdCritical,
        Power_TXLowerThresholdCritical, Power_TXUpperThresholdCritical,
        RXLowerThresholdCritical, RXUpperThresholdCritical,
        BC_TXLowerThresholdCritical, BC_TXUpperThresholdCritical,
        PowerRXLowerThresholdWarning = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

    local result, payload = ipmi.request(
        bus,{channel_type.CT_ME:value(), id},{DestNetFn = 0x30, Cmd = 0x98, Payload = get_op_threshold_req})
    if result ~= comp_code.Success then
        log:debug('get op threshold result is %s', result)
        return result, TemperatureLowerThresholdCritical, TemperatureUpperThresholdCritical,
            VoltageLowerThresholdCritical, VoltageUpperThresholdCritical,
            Power_TXLowerThresholdCritical, Power_TXUpperThresholdCritical,
            RXLowerThresholdCritical, RXUpperThresholdCritical,
            BC_TXLowerThresholdCritical, BC_TXUpperThresholdCritical, PowerRXLowerThresholdWarning
    end
    local rsp = op_static_threshold_rsp:unpack(payload)
    if not rsp then
        log:debug('unpack fail get_op_threshold')
        return result, TemperatureLowerThresholdCritical, TemperatureUpperThresholdCritical,
            VoltageLowerThresholdCritical, VoltageUpperThresholdCritical,
            Power_TXLowerThresholdCritical, Power_TXUpperThresholdCritical,
            RXLowerThresholdCritical, RXUpperThresholdCritical,
            BC_TXLowerThresholdCritical, BC_TXUpperThresholdCritical, PowerRXLowerThresholdWarning
    end
    TemperatureLowerThresholdCritical = u32_to_signed(rsp.temp_low_critical_threshold)
    TemperatureUpperThresholdCritical = u32_to_signed(rsp.temp_high_critical_threshold)
    VoltageLowerThresholdCritical =
        rsp.vcc_low_critical_threshold and (rsp.vcc_low_critical_threshold * UNIT_CONVERSION1) or 255
    VoltageUpperThresholdCritical =
        rsp.vcc_high_critical_threshold and (rsp.vcc_high_critical_threshold * UNIT_CONVERSION1) or 255
    Power_TXLowerThresholdCritical =
        rsp.tx_power_low_critical_threshold and rsp.tx_power_low_critical_threshold * UNIT_CONVERSION1
    Power_TXUpperThresholdCritical =
        rsp.tx_power_high_critical_threshold and rsp.tx_power_high_critical_threshold * UNIT_CONVERSION1
    RXLowerThresholdCritical =
        rsp.rx_power_low_critical_threshold and rsp.rx_power_low_critical_threshold * UNIT_CONVERSION1
    PowerRXLowerThresholdWarning =
        rsp.rx_power_low_warn_threshold and rsp.rx_power_low_warn_threshold * UNIT_CONVERSION1
    RXUpperThresholdCritical =
        rsp.rx_power_high_critical_threshold and rsp.rx_power_high_critical_threshold * UNIT_CONVERSION1
    BC_TXLowerThresholdCritical = rsp.bias_low_critical_threshold and rsp.bias_low_critical_threshold * UNIT_CONVERSION2
    BC_TXUpperThresholdCritical =
        rsp.bias_high_critical_threshold and rsp.bias_high_critical_threshold * UNIT_CONVERSION2

    return result, TemperatureLowerThresholdCritical, TemperatureUpperThresholdCritical,
        VoltageLowerThresholdCritical, VoltageUpperThresholdCritical,
        Power_TXLowerThresholdCritical, Power_TXUpperThresholdCritical,
        RXLowerThresholdCritical, RXUpperThresholdCritical,
        BC_TXLowerThresholdCritical, BC_TXUpperThresholdCritical, PowerRXLowerThresholdWarning
end

local npu_cdr_req = bs.new([[<<
    0xDB0700:3/unit:8,
    cmd:1/unit:8,
    lun:1/unit:8,
    para:1/unit:8,
    op_cmd:1/unit:8,
    op_fun:1/unit:8,
    offset:4/unit:8,
    data_length:4/unit:8
    >>]])

local rspNpuCdrTemp = bs.new([[<<
    head:3/unit:8,
    total_length:4/little-unit:8,
    respond_length:4/little-unit:8,
    device_id:2/little-unit:8,
    cdr_temp:2/little-unit:8,
    cdr_version:5/little-unit:8,
    retry_flag:1/little-unit:8,
    port_id:1/little-unit:8,
    cdr_manuf:1/little-unit:8,
    tail/string
    >>]])

local cdr_manufacturer_table = {
    [0] = "",
    [1] = "Huawei",
    [2] = "HUYANG024",
}

function imu_cmd.get_npu_cdr_temp_from_imu(id, power_on)
    local npu_cdr_temp = 0
    local rsp = {}
    local npu_cdr_manuf
    local req_data = npu_cdr_req:pack({
        cmd = GET_ARM_NPU_CMD,
        lun = DMP_LAST_REQ,
        para = 0,
        op_cmd = 0x30,
        op_fun = 0x06,
        offset = 0,
        data_length = 13
    })
    local bus = c_object_manage.get_instance().bus
    local result, payload =ipmi.request(
        bus,{channel_type.CT_ME:value(), id},
        {DestNetFn = 0x30, Cmd = 0x98, Payload = req_data}
    )
    if result == comp_code.Success then
        rsp = rspNpuCdrTemp:unpack(payload)
        if not rsp or not rsp.cdr_temp or not rsp.cdr_manuf then
            return result == comp_code.Success, npu_cdr_temp, cdr_manufacturer_table[0]
        end
        npu_cdr_temp = rsp.cdr_temp
        npu_cdr_manuf = cdr_manufacturer_table[rsp.cdr_manuf] or cdr_manufacturer_table[0]
    end
    log:debug('npu%s cdr_temp is %s, cdr_manuf = %s', id, npu_cdr_temp, rsp.cdr_manuf)
    return result == comp_code.Success, npu_cdr_temp, npu_cdr_manuf
end

function imu_cmd.get_optical_thresholds(npuid)
    local optical_info_table = {}
    local bus = c_object_manage.get_instance().bus
    local cc1, TemperatureLowerThresholdCritical, TemperatureUpperThresholdCritical,
        VoltageLowerThresholdCritical, VoltageUpperThresholdCritical,
        Power_TXLowerThresholdCritical, Power_TXUpperThresholdCritical,
        RXLowerThresholdCritical, RXUpperThresholdCritical,
        BC_TXLowerThresholdCritical, BC_TXUpperThresholdCritical,
        PowerRXLowerThresholdWarning = get_op_threshold(bus, npuid)

    optical_info_table.TemperatureLowerThresholdCritical = TemperatureLowerThresholdCritical
    optical_info_table.TemperatureUpperThresholdCritical = TemperatureUpperThresholdCritical
    optical_info_table.VoltageLowerThresholdCritical = VoltageLowerThresholdCritical
    optical_info_table.VoltageUpperThresholdCritical = VoltageUpperThresholdCritical
    optical_info_table.Power_TXLowerThresholdCritical = Power_TXLowerThresholdCritical
    optical_info_table.Power_TXUpperThresholdCritical = Power_TXUpperThresholdCritical
    optical_info_table.RXLowerThresholdCritical = RXLowerThresholdCritical
    optical_info_table.RXUpperThresholdCritical = RXUpperThresholdCritical
    optical_info_table.BC_TXLowerThresholdCritical = BC_TXLowerThresholdCritical
    optical_info_table.BC_TXUpperThresholdCritical = BC_TXUpperThresholdCritical
    optical_info_table.PowerRXLowerThresholdWarning = PowerRXLowerThresholdWarning
    
    return cc1 == comp_code.Success, optical_info_table
end

local function trim(s)
    return (s:gsub("^%s*(.-)%s*$", "%1"))
end

-- 参考光模块标准协议Table 4-6 MMF media interface ID
local mmf_media_type_map<const> = {
    [0] = "Undefined",
    [1] = "10G BASE-SW",
    [2] = "10G BASE-SR",
    [3] = "25G BASE-SR",
    [4] = "40G BASE-SR4",
    [5] = "40GE SWDM4 MSA Spec",
    [6] = "40GE BiDi",
    [7] = "50G BASE-SR",
    [8] = "100G BASE-SR10",
    [9] = "100G BASE-SR4",
    [10] = "100GE SWDM4 MSA Spec",
    [11] = "100GE BiDi",
    [12] = "100G BASE-SR2",
    [13] = "100G BASE-SR1",
    [14] = "200G BASE-SR4",
    [15] = "400G BASE-SR16",
    [16] = "400G BASE-SR8",
    [17] = "400G BASE-SR4",
    [18] = "800G BASE-SR8",
    [26] = "400G BASE-SR4.2",
    [27] = "200G BASE-SR2",
    [29] = "100G BASE-VR1",
    [31] = "400G BASE-VR4",
    [32] = "800G BASE-VR8"
}

-- 参考光模块标准协议Table 4-7 SMF media interface IDs
local smf_media_type_map<const> = {
    [20] = "100G BASE-DR",
    [23] = "200G BASE-DR4",
    [28] = "400G BASE-DR4"
}

local optical_type<const> = {
    OPTICAL_TYPE_UNKOWN = 0,
    OPTICAL_SINGLE_MODE = 1,
    OPTICAL_MULTI_MODE = 2,
    OPTICAL_AOC_MODE = 3,
    OPTICAL_COPPER_MODE = 4
}

local media_type_protocol = {
    [optical_type.OPTICAL_SINGLE_MODE] = smf_media_type_map,
    [optical_type.OPTICAL_MULTI_MODE] = mmf_media_type_map
}

local media_type_suffix = {
    [optical_type.OPTICAL_AOC_MODE] = "AOC",
    [optical_type.OPTICAL_COPPER_MODE] = "COPPER"
}

-- hdk结构体定义转发，因transceiver_type、optical_type、channel_num定义的是1字节char类型,
-- speed定义的是4字节。C语言会自动补齐，定义useless接收无用的数据
local op_speed_rsp = bs.new([[<<
    useless:1/little-unit:8,
    speed:4/little-unit:8,
    tail/binary
>>]])


-- 适配SR4光模块，只有4个通道，HDK在0x068C命令扩充，optical_type老版本已支持获取，未解析
local op_channel_num_rsp = bs.new([[<<
    optical_type:1/little-unit:8,
    channel_num:1/little-unit:8,
    tail/binary
>>]])

local op_optical_type_rsp = bs.new([[<<
    optical_type:1/little-unit:8,
    tail/binary
>>]])

local op_base_rsp = bs.new([[<<
    head:3/unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    vendor_name:32/string,
    serial_number:32/string,
    part_number:32/string,
    manufacture_date:32/string,
    transceiver_type:1/little-unit:8,
    tail/binary
    >>]])

local get_op_base_info_req = op_static_req:pack({
    cmd = GET_ARM_NPU_CMD,
    lun = DMP_LAST_REQ,
    para = 0,
    op_cmd = 0x8c,
    op_fun = 0x06, -- 0x068c:光模块状态信息获取
    offset = 0,
    data_length = 0
})

local function get_extend_info(id, tail, total_length)
    local extend_info_table = {
        OpticalType = 0,
        ChannelNum = MAX_CHANNEL_NUM,
        Speed = 0
    }
    if not tail then
        log:debug('The parameter is incorrect.')
        return extend_info_table
    end
    -- HDK版本在V3上支持OpticalType, 但bmc未解析
    if total_length <= OP_BASE_INFO_LEN then
        local optical_type_rsp = op_optical_type_rsp:unpack(tail, true)
        extend_info_table.OpticalType = optical_type_rsp.optical_type
        log:debug("The HDK version is not supported. optical id : %s, OpticalType : %s",
            id, extend_info_table.OpticalType)
        return extend_info_table
    end
    local rsp = op_channel_num_rsp:unpack(tail, true)
    if not rsp or not rsp.channel_num then
        return extend_info_table
    end
    extend_info_table.ChannelNum = rsp.channel_num
    extend_info_table.OpticalType = rsp.optical_type
    local ok, res = pcall(function (...)
        return op_speed_rsp:unpack(rsp.tail, true)
    end)

    if not ok then
        log:debug("optical%s get speed failed. res : %s", res)
        return extend_info_table
    end

    extend_info_table.Speed = res.speed
    log:debug('optical%s speed = %s', id, extend_info_table.Speed)
    return extend_info_table
end

local function get_transceiver_type(transceiver_type_id, extend_info_table)
    if media_type_protocol[extend_info_table.OpticalType] then
        return media_type_protocol[extend_info_table.OpticalType][transceiver_type_id] or ''
    end
    -- AOC 和 COPPER标准协议不支持解析，按对齐纪要'速率 类型'显示， 如400G AOC、200G COPPER
    if not media_type_suffix[extend_info_table.OpticalType] then
        log:debug('optical%s optical type illegal, value : %s', extend_info_table.OpticalType)
        return ''
    end
    if extend_info_table.Speed < MIN_SPEED then
        log:debug('optical%s Speed illegal, value : %s', extend_info_table.Speed)
        return ''
    end
    return string.format('%sG %s', (extend_info_table.Speed // 1000), media_type_suffix[extend_info_table.OpticalType])
end

function imu_cmd.get_op_base_info(id)
    log:debug('start to get op base info id :%s', id)
    local base_info_table = {
        vendor_name = 'N/A',
        sn = 'N/A',
        transceiver_type = 'N/A',
        manufacture_date = 'N/A',
        part_number = 'N/A',
        channel_num = 8
    }
    local bus = c_object_manage.get_instance().bus
    local vendor_name, sn, transceiver_type, manufacture_date, part_number
    local result, payload = ipmi.request(
        bus,{channel_type.CT_ME:value(), id},
        {DestNetFn = 0x30, Cmd = 0x98, Payload = get_op_base_info_req}
    )
    if result ~= comp_code.Success then
        return result, base_info_table
    end
    if log:getLevel() >= log.DEBUG then
        log:debug('get base_info result is %s', result)
    end
    local rsp = op_base_rsp:unpack(payload, true)
    if not rsp then
        return comp_code.ResponseError, base_info_table
    end
    if log:getLevel() >= log.DEBUG then
        log:debug('optical%s vendor_name = %s, serial_number = %s, manufacture_date = %s, transceiver_type = %s',
            id, rsp.vendor_name, rsp.serial_number, rsp.manufacture_date, rsp.transceiver_type)
    end
    vendor_name = trim(rsp.vendor_name:gsub("%z", "")) or 'N/A'
    sn = trim(rsp.serial_number:gsub("%z", "")) or 'N/A'
    part_number = trim(rsp.part_number:gsub("%z", "")) or 'N/A'
    manufacture_date = trim(rsp.manufacture_date:gsub("%z", "")) or 'N/A'
    local extend_info_table = get_extend_info(id, rsp.tail, rsp.total_length)
    transceiver_type = get_transceiver_type(rsp.transceiver_type, extend_info_table)
    base_info_table.vendor_name = vendor_name
    base_info_table.sn = sn
    base_info_table.transceiver_type = transceiver_type
    base_info_table.manufacture_date = manufacture_date
    base_info_table.part_number = part_number
    base_info_table.channel_num = extend_info_table.ChannelNum
    return comp_code.Success, base_info_table

end

imu_cmd.mock_get_op_base_info = imu_cmd.get_op_base_info
imu_cmd.mock_get_op_status_info = get_op_status_info

function imu_cmd.get_optical_info_from_imu(npuid, channel_num)
    log:debug('start to get op state info id :%s', npuid)
    local optical_info_table = {
        SupplyVoltage = 255,
        TXOutputPowerMilliWatts = {},
        RXInputPowerMilliWatts = {},
        TXBiasCurrentMilliAmps = {},
        TxLoS = 0,
        RxLoS = 0,
        TxLoss = 0,
        RxLoss = 0,
        TxLossOfLock = 0,
        RxLossOfLock = 0,
        TemperatureCelsius = 255,
        Accessible = 0,
        HostSNR = {0, 0, 0, 0, 0, 0, 0, 0},
        MediaSNR = {0, 0, 0, 0, 0, 0, 0, 0},
        RxSNR = {0, 0, 0, 0, 0, 0, 0, 0},
        Manufacturer = 'N/A',
        SerialNumber = 'N/A',
        TransceiverType = 'N/A',
        ProductionDate = 'N/A',
        PartNumber = 'N/A',
        ChannelNum = 8
    }
    local bus = c_object_manage.get_instance().bus
    local cc1, voltage, tx_power, rx_power, tx_bias, tx_los, rx_los, tx_lol, rx_lol,
        temperature, tx_fault, host_snr, media_snr, access_failed = get_op_status_info(bus, npuid, channel_num)

    optical_info_table.SupplyVoltage = voltage
    optical_info_table.TXOutputPowerMilliWatts = tx_power
    optical_info_table.RXInputPowerMilliWatts = rx_power
    optical_info_table.TXBiasCurrentMilliAmps = tx_bias
    -- 光模块TxLoss\RxLoss不能继承之前属性TxFaultState、RxLossState(因为为BOOL型，实际表示8个通道Bit位状态)
    optical_info_table.TxLoS = tx_los
    optical_info_table.RxLoS = rx_los
    optical_info_table.TxFault = tx_fault
    optical_info_table.TxLossOfLock = tx_lol
    optical_info_table.RxLossOfLock = rx_lol
    optical_info_table.TemperatureCelsius = temperature
    optical_info_table.Accessible = access_failed
    optical_info_table.HostSNR = host_snr
    optical_info_table.MediaSNR = media_snr
    return cc1 == comp_code.Success, optical_info_table, cc1 == OP_NOT_PRESENT
end


local op_linkdown_info_req = bs.new([[<<
    0xDB0700:3/unit:8,
    cmd:1/unit:8,
    lun:1/unit:8,
    request_parameter:1/unit:8,
    op_cmd:1/unit:8,
    op_fun:1/unit:8,
    offset:4/unit:8,
    data_length:4/unit:8
>>]])

-- error_code接口表定义是2个字节，实际bs.new 中1个字节+1个返回值构成两个字节
local op_linkdown_info_rsp = bs.new([[<<
    error_code:1/unit:8,
    opcode:2/little-unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    packet_type:1/little-unit:8,
    packet_all:1/little-unit:8,
    packet_num:1/little-unit:8,
    packet_len:1/little-unit:8,
    timestamp:4/little-unit:8,
    reserve:4/little-unit:8,
    voltage:4/little-unit:8,
    tx_power1:4/little-unit:8,
    tx_power2:4/little-unit:8,
    tx_power3:4/little-unit:8,
    tx_power4:4/little-unit:8,
    tx_power5:4/little-unit:8,
    tx_power6:4/little-unit:8,
    tx_power7:4/little-unit:8,
    tx_power8:4/little-unit:8,
    rx_power1:4/little-unit:8,
    rx_power2:4/little-unit:8,
    rx_power3:4/little-unit:8,
    rx_power4:4/little-unit:8,
    rx_power5:4/little-unit:8,
    rx_power6:4/little-unit:8,
    rx_power7:4/little-unit:8,
    rx_power8:4/little-unit:8,
    tx_bias1:4/little-unit:8,
    tx_bias2:4/little-unit:8,
    tx_bias3:4/little-unit:8,
    tx_bias4:4/little-unit:8,
    tx_bias5:4/little-unit:8,
    tx_bias6:4/little-unit:8,
    tx_bias7:4/little-unit:8,
    tx_bias8:4/little-unit:8,
    tx_los:4/little-unit:8,
    rx_los:4/little-unit:8,
    tx_lol:4/little-unit:8,
    rx_lol:4/little-unit:8,
    temperature:4/little-unit:8,
    tx_fault:4/little-unit:8,
    host_snr1:32/little-float,
    host_snr2:32/little-float,
    host_snr3:32/little-float,
    host_snr4:32/little-float,
    host_snr5:32/little-float,
    host_snr6:32/little-float,
    host_snr7:32/little-float,
    host_snr8:32/little-float,
    media_snr1:32/little-float,
    media_snr2:32/little-float,
    media_snr3:32/little-float,
    media_snr4:32/little-float,
    media_snr5:32/little-float,
    media_snr6:32/little-float,
    media_snr7:32/little-float,
    media_snr8:32/little-float,
    tail/binary
>>]])

local op_linkdown_info_extra_rsp = bs.new([[<<
    device_id:4/little-unit:8,
    tail/string
>>]])


local get_op_linkdown_info_req = op_linkdown_info_req:pack({
    cmd = GET_ARM_NPU_CMD,
    lun = DMP_LAST_REQ,
    request_parameter = 0,
    op_cmd = 0xa0,
    op_fun = 0x06, -- 0x06a0:闪断场景光模块信息获取
    offset = 0,
    data_length = 0
})

local function get_linkdown_optical_info_cmd(id)
    local bus = c_object_manage.get_instance().bus
    log:debug('start get op linkdown info from npu')
    local result, payload = ipmi.request(
        bus, {channel_type.CT_ME:value(), id},
        { DestNetFn = 0x30, Cmd = 0x98, Payload = get_op_linkdown_info_req }
    )
    if result ~= comp_code.Success then
        log:debug('get_op_linkdown_info failed, npuid = %s, result = %s', id, result)
        return 0
    end
    local rsp = op_linkdown_info_rsp:unpack(payload)
    if not rsp then
        log:debug('get_op_linkdown_info unpack failed')
        return 0
    end
    if rsp.packet_type ~= 0 then
        log:debug('packet type (%d) does not meet the expectation.', rsp.packet_type)
        return 0
    end
    if rsp.packet_num == 0 then
        return rsp.packet_all
    end
    local linkdown_msg = {
        log_time = os.time(),
        npu_id = id,
        component = dfx_defs.component.OPTICAL_MODULE,
        msg_type = dfx_defs.optical_module.LINKDOWN,
        timestamp = rsp.timestamp,
        vcc_current = rsp.voltage,
        tx_power = {rsp.tx_power1, rsp.tx_power2, rsp.tx_power3, rsp.tx_power4,
            rsp.tx_power5, rsp.tx_power6, rsp.tx_power7, rsp.tx_power8},
        rx_power = {rsp.rx_power1, rsp.rx_power2, rsp.rx_power3, rsp.rx_power4,
            rsp.rx_power5, rsp.rx_power6, rsp.rx_power7, rsp.rx_power8},
        bias = {rsp.tx_bias1, rsp.tx_bias2, rsp.tx_bias3, rsp.tx_bias4, rsp.tx_bias5,
            rsp.tx_bias6, rsp.tx_bias7, rsp.tx_bias8},
        tx_los = rsp.tx_los,
        rx_los = rsp.rx_los,
        tx_lol = rsp.tx_lol,
        rx_lol = rsp.rx_lol,
        temperature = rsp.temperature,
        tx_fault = rsp.tx_fault,
        host_snr = {rsp.host_snr1, rsp.host_snr2, rsp.host_snr3, rsp.host_snr4, rsp.host_snr5,
            rsp.host_snr6, rsp.host_snr7, rsp.host_snr8},
        media_snr = {rsp.media_snr1, rsp.media_snr2, rsp.media_snr3, rsp.media_snr4, rsp.media_snr5,
            rsp.media_snr6, rsp.media_snr7, rsp.media_snr8}
    }
    local ok, rsp_extra = pcall(function ()
        return op_linkdown_info_extra_rsp:unpack(rsp.tail, true)
    end)
    if ok and rsp_extra and rsp_extra.device_id then
        linkdown_msg.device_id = rsp_extra.device_id
    end
    dfx_collector.get_instance():msg_productor(linkdown_msg)
    return rsp.packet_all
end

function imu_cmd.get_linkdown_optical_info(id)
    local packet_all = get_linkdown_optical_info_cmd(id)
    if packet_all == 0 then
        return
    end
    if packet_all > 5 then
        if log:getLevel() >= log.DEBUG then
            log:debug('Packet_all is invilds')
        end
        return
    end
    for _ = 0, packet_all - 1 do
        get_linkdown_optical_info_cmd(id)
    end
end

local op_linkdown_electric_info_rsp = bs.new([[<<
    error_code:1/unit:8,
    opcode:2/little-unit:8,
    total_length:4/little-unit:8,
    length:4/little-unit:8,
    cmd_version:1/little-unit:8,
    support_info:7/little-unit:8,
    packet_type:1/little-unit:8,
    packet_all:1/little-unit:8,
    packet_num:1/little-unit:8,
    packet_len:1/little-unit:8,
    timestamp:4/little-unit:8,
    npu_rx_snr1:32/little-float,
    npu_rx_snr2:32/little-float,
    npu_rx_snr3:32/little-float,
    npu_rx_snr4:32/little-float,
    npu_rx_snr5:32/little-float,
    npu_rx_snr6:32/little-float,
    npu_rx_snr7:32/little-float,
    npu_rx_snr8:32/little-float,
    pcs_err_cnt:8/little-unit:8,
    cw_before_correct_cnt:8/little-unit:8,
    cw_correct_cnt:8/little-unit:8,
    cw_uncorrect_cnt:8/little-unit:8,
    cdr_host_snr1:32/little-float,
    cdr_host_snr2:32/little-float,
    cdr_host_snr3:32/little-float,
    cdr_host_snr4:32/little-float,
    cdr_host_snr5:32/little-float,
    cdr_host_snr6:32/little-float,
    cdr_host_snr7:32/little-float,
    cdr_host_snr8:32/little-float,
    cdr_media_snr1:32/little-float,
    cdr_media_snr2:32/little-float,
    cdr_media_snr3:32/little-float,
    cdr_media_snr4:32/little-float,
    cdr_media_snr5:32/little-float,
    cdr_media_snr6:32/little-float,
    cdr_media_snr7:32/little-float,
    cdr_media_snr8:32/little-float,
    device_id:4/little-unit:8,
    tail/string
>>]])

local get_op_linkdown_electric_info_req = op_linkdown_info_req:pack({
    cmd = GET_ARM_NPU_CMD,
    lun = DMP_LAST_REQ,
    request_parameter = 0x02,
    op_cmd = 0xa5,
    op_fun = 0x06, -- 0x06a5:闪断场景端口电链路信息获取
    offset = 0,
    data_length = 0
})

local function get_linkdown_electric_info_cmd(id)
    local bus = c_object_manage.get_instance().bus
    log:debug('start get op linkdown electric info from npu')
    local result, payload = ipmi.request(
        bus, {channel_type.CT_ME:value(), id},
        { DestNetFn = 0x30, Cmd = 0x98, Payload = get_op_linkdown_electric_info_req }
    )
    if result ~= comp_code.Success then
        log:debug('get_op_linkdown_electric_info failed, npuid = %s, result = %s',
            id, result)
        return 0
    end
    local rsp = op_linkdown_electric_info_rsp:unpack(payload)
    if not rsp then
        log:debug('get_op_linkdown_electric_info unpack failed')
        return 0
    end
    if rsp.packet_type ~= 0 then
        log:debug('linkdown electric packet type (%d) does not meet the expectation.',
            rsp.packet_type)
        return 0
    end
    if rsp.packet_num == 0 then
        return rsp.packet_all
    end
    local linkdown_msg = {
        log_time = os.time(),
        npu_id = id,
        component = dfx_defs.component.OPTICAL_MODULE,
        msg_type = dfx_defs.optical_module.ELECTRIC_LINKDOWN,
        timestamp = rsp.timestamp,
        npu_rx_snr = {rsp.npu_rx_snr1, rsp.npu_rx_snr2, rsp.npu_rx_snr3, rsp.npu_rx_snr4,
            rsp.npu_rx_snr5, rsp.npu_rx_snr6, rsp.npu_rx_snr7, rsp.npu_rx_snr8},
        pcs_err_cnt = rsp.pcs_err_cnt,
        cw_before_correct_cnt = rsp.cw_before_correct_cnt,
        cw_correct_cnt = rsp.cw_correct_cnt,
        cw_uncorrect_cnt = rsp.cw_uncorrect_cnt,
        cdr_host_snr = {rsp.cdr_host_snr1, rsp.cdr_host_snr2, rsp.cdr_host_snr3, rsp.cdr_host_snr4,
            rsp.cdr_host_snr5, rsp.cdr_host_snr6, rsp.cdr_host_snr7, rsp.cdr_host_snr8},
        cdr_media_snr = {rsp.cdr_media_snr1, rsp.cdr_media_snr2, rsp.cdr_media_snr3, rsp.cdr_media_snr4,
            rsp.cdr_media_snr5, rsp.cdr_media_snr6, rsp.cdr_media_snr7, rsp.cdr_media_snr8},
        device_id = rsp.device_id
    }
    dfx_collector.get_instance():msg_productor(linkdown_msg)
    return rsp.packet_all
end

function imu_cmd.get_linkdown_electric_info(id)
    local packet_all = get_linkdown_electric_info_cmd(id)
    if packet_all == 0 then
        return
    end
    if packet_all > 5 then
        if log:getLevel() >= log.DEBUG then
            log:debug('Linkdown electric packet_all is invilds')
        end
        return
    end
    for _ = 0, packet_all - 1 do
        get_linkdown_electric_info_cmd(id)
        skynet.sleep(20)
    end
end

imu_cmd.mock_get_linkdown_optical_info_cmd = get_linkdown_optical_info_cmd
imu_cmd.mock_get_linkdown_electric_info_cmd = get_linkdown_electric_info_cmd
imu_cmd.mock_get_npu_hccs = get_npu_hccs

return imu_cmd
