-- 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 skynet = require 'skynet'
local queue = require 'skynet.queue'
local c_object = require 'mc.orm.object'
local c_tasks = require 'mc.orm.tasks'
local log = require 'mc.logging'
local context = require 'mc.context'
local c_object_manage = require 'mc.orm.object_manage'
local bs = require 'mc.bitstring'
local utils = require 'mc.utils'
local vos = require 'utils.vos'
local ipmi = require 'ipmi'
local enums = require 'ipmi.enums'
local c_om_event_handler = require 'infrastructure.om_event_handler'
local c_optical_channel = require 'device.class.optical_channel'
local om_config = require 'hardware_config.OpticalModuleConfig'
local npu_om_event_debounce_config = require 'event.debounce_config.npu_om'
local npu_imu_cmd = require 'npu.hdk_cmd'
local dfx_defs = require 'dfx_collect.dfx_defs'
local dfx_collector = require 'dfx_collect.dfx_collect_mgnt'
local fructl = require 'infrastructure.fructl'
local prbs = require 'prbs_test.optical_module_prbs_test'
local dirty = require 'dirty_test.optical_module_dirty_test'
local client = require 'network_adapter.client'
local common_func = require 'common.common_func'

local channel_type = enums.ChannelType
local comp_code = ipmi.types.Cc

local c_optical_module = c_object('OpticalModule')
local ETH_PATH<const> = '/bmc/kepler/Systems/1/PCIeDevices/EthDevices/EthDevice'

local IDENTIFIER_DEFAULT<const> = 0x00
local IDENTIFIER_TYPE_SFP<const> = 0x03
local IDENTIFIER_TYPE_QSFP28<const> = 0x11
local INVALID_READING<const> = 0xFFFF
local ALARM_VALUE<const> = 0x8000
local UNIT_CONVERSION1<const> = 10000 -- 06 8e接口电源、功率精度转换
local UNIT_CONVERSION2<const> = 1000 -- 06 8e接口电流精度转换
local LANE_PER_OP<const> = 8
local default_optical_module_value<const> = {
    Presence = 0,
    PartNumber = '',
    Manufacturer = '',
    SerialNumber = '',
    RevisionNumber = '',
    PowerClass = 0xFF,
    ModuleState = 0xFF,
    PowerCapacityWatts = 0,
    MediumType = '',
    ConnectorType = '',
    TransmissionDistance = tostring(0xFFFF),
    RxLossState = false,
    TxFaultState = false,
    TXBiasCurrentMilliAmps = {0xFFFF},
    TXOutputPowerMilliWatts = {0xFFFF},
    RXInputPowerMilliWatts = {0xFFFF},
    SupplyVoltage = 0xFFFF,
    Vol_UpperThresholdCritical = 0xFFFF,
    Vol_LowerThresholdCritical = 0xFFFF,
    VoltageLowerThresholdCritical = 0xFFFF,
    VoltageUpperThresholdCritical = 0xFFFF,
    Power_TXUpperThresholdCritical = 0xFFFF,
    Power_TXLowerThresholdCritical = 0xFFFF,
    RXLowerThresholdCritical = 0xFFFF,
    RXUpperThresholdCritical = 0xFFFF,
    WaveLengthNanometer = '',
    Identifier = '',
    Temp_LowerThresholdCritical = 0xFFFF,
    Temp_UpperThresholdCritical = 0xFFFF,
    TemperatureLowerThresholdCritical = 0xFFFF,
    TemperatureUpperThresholdCritical =0xFFFF,
    BC_TXUpperThresholdCritical = 0xFFFF,
    BC_TXLowerThresholdCritical = 0xFFFF,
    FiberDirtyDetected = false,
    FiberLoosenessDetected = false
}

local default_npu_optical_module_value<const> = {
    Presence = 0,
    ReadingCelsius = 0xFFFF,
    RxLossState = false,
    TxFaultState = false,
    TXBiasCurrentMilliAmps = {0xFFFF},
    TXOutputPowerMilliWatts = {0xFFFF},
    RXInputPowerMilliWatts = {0xFFFF},
    SupplyVoltage = 0xFFFF,
    Vol_UpperThresholdCritical = 0xFFFF,
    Vol_LowerThresholdCritical = 0xFFFF,
    VoltageLowerThresholdCritical = 0xFFFF,
    VoltageUpperThresholdCritical = 0xFFFF,
    Power_TXUpperThresholdCritical = 0xFFFF,
    Power_TXLowerThresholdCritical = 0xFFFF,
    RXLowerThresholdCritical = 0xFFFF,
    RXUpperThresholdCritical = 0xFFFF,
    Temp_LowerThresholdCritical = 0xFFFF,
    Temp_UpperThresholdCritical = 0xFFFF,
    TemperatureLowerThresholdCritical = 0xFFFF,
    TemperatureUpperThresholdCritical =0xFFFF,
    BC_TXUpperThresholdCritical = 0xFFFF,
    BC_TXLowerThresholdCritical = 0xFFFF,
    RXInputPowerStatus = 0,

    TXOutputPowerStatus = 0,
    TXBiasCurrentStatus = 0,
    TxLoS = 0,
    RxLoS = 0,
    TxFault = 0,
    TxLossOfLock = 0,
    RxLossOfLock = 0,
    TemperatureCelsius = 255,
    Accessible = 0,
    SelfTestStatus = -1,
    MediaInterfaceFaultStatus = 0xFFFF,
    HostInterfaceFaultStatus = 0xFFFF
}

local presence_max_retry = 3
local NCSI_FAILED_THRESHOLD = 3

--该函数只有Hi182x系列网卡走smbus获取温度时调用
function c_optical_module:set_temp_hi182x(temp)
    self:update_182x_optical_temp(temp)
end

function c_optical_module:set_temp(temp)
    self.ReadingCelsius = temp
    self:update_reading_celsius(nil, temp)
end

function c_optical_module:set_presence(presence)
    self.Presence = presence
end

function c_optical_module:get_channel_num()
    return self.channel_num
end

function c_optical_module:update_health(health)
    self.Health = health
end

function c_optical_module:check_port_workload_type()
    local port = self:get_parent()
    return (port and port.WorkloadType and port.WorkloadType == 1)
end

function c_optical_module:reset_npu_om()
    for k, v in pairs(default_npu_optical_module_value) do
        self[k] = v
    end
    self.last_record_time = 0
    self.last_get_elec_info_time = 0
    self.last_record_elec_info_time = 0
end

--光模块资产清单更新
function c_optical_module:update_asset_data_info()
    self.AssetType = 'OpticalModule'
    self.AssetName = self.Name
    self.InventorySerialNumber = self.SerialNumber
    self.InventoryFirmwareVersion = 'N/A'
    self.PCBVersion = 'N/A'
    self.InventoryManufacturer = self.Manufacturer
    self.AssetTag = 'N/A'
    self.InventoryPartNumber = self.PartNumber
    self.ManufactureDate = self.ProductionDate
    self.Slot = 'N/A'
    self.Model = 'N/A'
end

function c_optical_module:reset()
    -- 如果iBMA获取到了信息，则不回复默认值
    if self.get_diagnostic_by_ibma_success then
        return
    end
    log:debug('optical module info reset')
    for k, v in pairs(default_optical_module_value) do
        self:set_prop(k, v)
    end
    self.presence_retry_count = 0
end

local function get_optical_temp(self)

    local port = self and type( self.get_parent ) == "function" and self:get_parent() or nil
    local card = port and type( port.get_parent ) == "function" and port:get_parent() or nil 

    local opticalTemp
    if card and card.Model == "E810" then 
        opticalTemp = self.ncsi_config_obj:OpticalTemp({
            channel_id = self.PortID,
            package_id = self.package_id
        })
    else 
        opticalTemp = self.ncsi_config_obj:OpticalTemp({
            channel_id = self.PortID,
            extra_cmd = self.PortID,
            package_id = self.package_id
        })
    end
    return opticalTemp
end

function c_optical_module:get_optical_temp_scheduler()
    if not self.ncsi_config_obj.OpticalGetIdentifier then
        log:notice('%s(Port %s) optical can not get identifier', self.NetworkAdapterId, self.PortID)
        return get_optical_temp(self)
    end
    local identifier = self.ncsi_config_obj:OpticalGetIdentifier({
        channel_id = self.PortID,
        package_id = self.package_id}):value()
    if identifier then -- 部分厂商需要先获取光模块的identifier，再决定使用哪种协议获取光模块温度，如Mellanox
        log:notice('%s(Port %s) optical identifier is %s, need to use specific protocol', self.NetworkAdapterId,
            self.PortID, identifier)
        return self.ncsi_config_obj[identifier](self.ncsi_config_obj, {
            channel_id = self.PortID,
            extra_cmd = self.PortID,
            package_id = self.package_id
        })
    end
    return get_optical_temp(self)
end

function c_optical_module:update_182x_optical_temp(temp)
    if temp == 0x7ffe or temp ==0x7ffd then
        -- 不在位或无效数据等场景，恢复默认值
        self.ReadingCelsius = 0xffff
    elseif temp == 0x7fff then
        -- 获取温度失败，置位告警，告警值为0x8000
        self.ReadingCelsius = 0xffff
        self.TemperatureCelsius = 0x8000
    else
        self.ReadingCelsius = temp    -- 正常获取到温度，更新ReadingCelsius
    end
end

function c_optical_module:update_182x_optical_temp_on_error()
    self.ReadingCelsius = 0xffff
    -- 光模块在位且bios正常时，命令报错需要置位告警，其余情况不告警
    if self.Presence == 1 and self.smbios_status == 3 then
        self.TemperatureCelsius = 0x8000
    end
end

-- 光模块有2个温度属性，ReadingCelsius用于北向，TemperatureCelsius用于调速和告警
function c_optical_module:update_optical_temp_by_ncsi()
    local ncsi_failed_count = 0

    local s = self:get_optical_temp_scheduler()
    self:connect_signal(s.on_data_change, function(data)
        local card = self:get_parent():get_parent()
        if string.find(card.Model, 'Hi182') then
            self:update_182x_optical_temp(data)
            self.get_properties_res.TempFromNcsi = true
            ncsi_failed_count = 0 -- 成功时重置计数
            -- NCSI恢复正常，停止备选SMBUS任务
            if self.smbus_schedulers.optical_temp_task and not self.smbus_schedulers.optical_temp_task.is_paused then
                self.smbus_schedulers.optical_temp_task:pause()
                self.smbus_schedulers.optical_temp_task.data = nil
                log:notice('%s optical_module_%s OpticalTemp: NCSI recovered, stopped SMBUS fallback',
                    self.NetworkAdapterId, self.PortID)
            end
            return
        end
        log:debug('update_optical_temp_by_ncsi on_data_change')
        log:debug(data)
        if data ~= nil then
            self.ReadingCelsius = data
            self.Presence = 1
            self.presence_retry_count = 0
            self.get_properties_res.TempFromNcsi = true
        end
    end)

    self:connect_signal(s.on_error, function()
        local card = self:get_parent():get_parent()
        
        ncsi_failed_count = ncsi_failed_count + 1
        if string.find(card.Model, 'Hi182') then
            if self.get_properties_res.TempFromNcsi then
                log:error('%s optical_module_%s update optical temp by ncsi on_error', self.NetworkAdapterId, self.PortID)
            end
            self:update_182x_optical_temp_on_error()
            if ncsi_failed_count >= NCSI_FAILED_THRESHOLD and self.smbus_schedulers.optical_temp_task and
                self.smbus_schedulers.optical_temp_task.is_paused then
                self.smbus_schedulers.optical_temp_task:resume()
                log:notice('%s optical_module_%s OpticalTemp: SMBUS fallback activated successfully',
                    self.NetworkAdapterId, self.PortID)
            end
            return
        end
        
        if self.get_properties_res.TempFromNcsi then
            log:error('%s optical_module_%s update optical temp by ncsi on_error', self.NetworkAdapterId, self.PortID)
            self.get_properties_res.TempFromNcsi = false
        end

        self.presence_retry_count = self.presence_retry_count + 1
        if self.Presence ~= 0 and self.presence_retry_count >= presence_max_retry then
            log:debug('unable to get op info after %s times, set Presence to 0',
                self.presence_retry_count)
            self:reset()
            self.ReadingCelsius = 0xFFFF
        end
    end)

    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical temp by ncsi start', self.NetworkAdapterId, self.PortID)
end

-- ncsi获取光模块协议
function c_optical_module:get_optical_identifier()
    if not self.ncsi_config_obj.OpticalIdentifier then
        log:notice('%s(Port %s) optical can not get identifier', self.NetworkAdapterId, self.PortID)
        return ''
    end
    local identifier = self.ncsi_config_obj:OpticalIdentifier({
        channel_id = self.PortID,
        package_id = self.package_id}):value()
    if not identifier then
        log:notice('%s(Port %s) optical identifier return is null', self.NetworkAdapterId,
            self.PortID, identifier)
        return ''
    end
    -- 部分厂商需要先获取光模块的identifier，再决定使用哪种协议来获取光模块信息，如Mellanox
    log:notice('%s(Port %s) optical identifier is %s, need to use specific protocol', self.NetworkAdapterId,
        self.PortID, identifier)
    self.Identifier = identifier
    if identifier == 'DSFP' or identifier == 'QSFP+ or later with CMIS' then
        return 'CMIS50'
    end
    return 'SFF8636'
end

-- ncsi更新光模块电压
function c_optical_module:update_optical_voltage_by_ncsi(id)
    local identifier = 'OpticalVoltage' .. id
    local s = self.ncsi_config_obj[identifier](self.ncsi_config_obj, {
        channel_id = self.PortID,
        extra_cmd = self.PortID,
        package_id = self.package_id
    })
    self:connect_signal(s.on_data_change, function(data)
        self.SupplyVoltage = data
        self.get_properties_res.VoltageFromNcsi = true
        log:debug('%s optical_module_%s update optical voltage to %s by ncsi', self.NetworkAdapterId, self.PortID, data)
    end)
    self:connect_signal(s.on_error, function()
        if self.get_properties_res.VoltageFromNcsi then
            log:error('%s optical_module_%s update optical voltage by ncsi on_error', 
                self.NetworkAdapterId, self.PortID)
            self.get_properties_res.VoltageFromNcsi = false
        end
        s.data = nil -- 重置scheduler里的data，否则连接恢复后不会进入on_data_change
        self.SupplyVoltage = 0xFFFF
    end)
    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical voltage by ncsi start', self.NetworkAdapterId, self.PortID)
end

-- ncsi更新光模块偏置电流
function c_optical_module:update_optical_bias_by_ncsi(id)
    local identifier = 'OpticalBias' .. id
    local s = self.ncsi_config_obj[identifier](self.ncsi_config_obj, {
        channel_id  = self.PortID,
        extra_cmd   = self.PortID,
        package_id  = self.package_id
    })
    self:connect_signal(s.on_data_change, function(data)
        local prop_value = self.TXBiasCurrentMilliAmps
        for i, v in pairs(data) do
            prop_value[i] = v
            log:debug('%s optical_module_%s channel %s update optical bias to %s by ncsi', 
                self.NetworkAdapterId, self.PortID, i, v)
        end
        -- 修改后需要显式地设置属性值，修改才会生效
        self.TXBiasCurrentMilliAmps           = prop_value
        self.get_properties_res.BiasFromNcsi  = true
    end)
    self:connect_signal(s.on_error, function()
        if self.get_properties_res.BiasFromNcsi then
            log:error('%s optical_module_%s update optical bias by ncsi on_error', self.NetworkAdapterId, self.PortID)
            self.get_properties_res.BiasFromNcsi = false
        end
        s.data = nil -- 重置scheduler里的data，否则连接恢复后不会进入on_data_change
        self.TXBiasCurrentMilliAmps = {0xFFFF}
    end)
    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical bias by ncsi start', self.NetworkAdapterId,
               self.PortID)
end

-- ncsi更新光模块接收功率
function c_optical_module:update_optical_input_power_by_ncsi(id)
    local identifier = 'OpticalInputPower' .. id
    local s = self.ncsi_config_obj[identifier](self.ncsi_config_obj, {
        channel_id  = self.PortID,
        extra_cmd   = self.PortID,
        package_id  = self.package_id
    })
    self:connect_signal(s.on_data_change, function(data)
        local prop_value = self.RXInputPowerMilliWatts
        for i, v in pairs(data) do
            prop_value[i] = v
            log:debug('%s optical_module_%s channel %s update optical input power to %s by ncsi', 
                self.NetworkAdapterId, self.PortID, i, v)
        end
        -- 修改后需要显式地设置属性值，修改才会生效
        self.RXInputPowerMilliWatts              = prop_value
        self.get_properties_res.RxPowerFromNcsi  = true
    end)
    self:connect_signal(s.on_error, function()
        if self.get_properties_res.RxPowerFromNcsi then
            log:error('%s optical_module_%s update optical input power by ncsi on_error', 
                self.NetworkAdapterId, self.PortID)
            self.get_properties_res.RxPowerFromNcsi = false
        end
        s.data = nil -- 重置scheduler里的data，否则连接恢复后不会进入on_data_change
        self.RXInputPowerMilliWatts = {0xFFFF}
    end)
    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical input power by ncsi start', self.NetworkAdapterId,
               self.PortID)
end

-- ncsi更新光模块发射功率
function c_optical_module:update_optical_output_power_by_ncsi(id)
    local identifier = 'OpticalOutputPower' .. id
    local s = self.ncsi_config_obj[identifier](self.ncsi_config_obj, {
        channel_id = self.PortID,
        extra_cmd = self.PortID,
        package_id = self.package_id
    })
    self:connect_signal(s.on_data_change, function(data)
        local prop_value = self.TXOutputPowerMilliWatts
        for i, v in pairs(data) do
            prop_value[i] = v
            log:debug('%s optical_module_%s channel %s update optical output power to %s by ncsi', 
                self.NetworkAdapterId, self.PortID, i, v)
        end
        -- 修改后需要显式地设置属性值，修改才会生效
        self.TXOutputPowerMilliWatts = prop_value
        self.get_properties_res.TxPowerFromNcsi = true
    end)
    self:connect_signal(s.on_error, function()
        if self.get_properties_res.TxPowerFromNcsi then
            log:error('%s optical_module_%s update optical output power by ncsi on_error', 
                self.NetworkAdapterId, self.PortID)
            self.get_properties_res.TxPowerFromNcsi = false
        end
        s.data = nil -- 重置scheduler里的data，否则连接恢复后不会进入on_data_change
        self.TXOutputPowerMilliWatts = {0xFFFF}
    end)
    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical output power by ncsi start', self.NetworkAdapterId, self.PortID)
end

-- ncsi获取光模块厂商及版本等信息
function c_optical_module:update_optical_module_static_info_by_ncsi(id)
    local identifier = 'OpticalStaticInfo' .. id
    local s = self.ncsi_config_obj[identifier](self.ncsi_config_obj, {
        channel_id = self.PortID,
        extra_cmd = self.PortID,
        package_id = self.package_id
    })
    if s.is_running then
        log:debug('update_optical_module_static_info_by_ncsi is_running')
        return
    end
    self:connect_signal(s.on_data_change, function(data)
        for k, v in pairs(data) do
            self:set_prop(k, v)
            log:notice('%s optical_module_%s update %s to %s by ncsi', self.NetworkAdapterId, self.PortID, k, v)
        end
        self.get_properties_res.StaticInfoFromNcsi = true
        self:update_asset_data_info()
    end)
    self:connect_signal(s.on_error, function()
        if self.get_properties_res.StaticInfoFromNcsi then
            log:error('%s optical_module_%s update optical module static info by ncsi on_error', 
                self.NetworkAdapterId, self.PortID)
            self.get_properties_res.StaticInfoFromNcsi = false
        end
        s.data = nil -- 重置scheduler里的data，否则连接恢复后不会进入on_data_change
        self:reset()
    end)
    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical module static info by ncsi start', 
        self.NetworkAdapterId, self.PortID)
end

-- ncsi获取光模块连接器类型
function c_optical_module:update_optical_connector_type_by_ncsi(id)
    local identifier = 'OpticalConnectorType' .. id
    local s = self.ncsi_config_obj[identifier](self.ncsi_config_obj, {
        channel_id = self.PortID,
        extra_cmd = self.PortID,
        package_id = self.package_id
    })
    self:connect_signal(s.on_data_change, function(data)
        self.ConnectorType = data
        self.get_properties_res.ConnectorTypeFromNcsi = true
        log:notice('%s optical_module_%s update connector type to %s by ncsi', self.NetworkAdapterId, self.PortID, data)
    end)
    self:connect_signal(s.on_error, function()
        if self.get_properties_res.ConnectorTypeFromNcsi then
            log:error('%s optical_module_%s update optical connector type by ncsi on_error', 
                self.NetworkAdapterId, self.PortID)
            self.get_properties_res.ConnectorTypeFromNcsi = false
        end
        s.data = nil -- 重置scheduler里的data，否则连接恢复后不会进入on_data_change
        self.ConnectorType = ''
    end)
    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical connector type by ncsi start', self.NetworkAdapterId, self.PortID)
end

-- ncsi获取光模块功耗信息
function c_optical_module:update_optical_power_info_by_ncsi(id)
    local identifier = 'OpticalPowerInfo' .. id
    local s = self.ncsi_config_obj[identifier](self.ncsi_config_obj, {
        channel_id = self.PortID,
        extra_cmd = self.PortID,
        package_id = self.package_id
    })
    self:connect_signal(s.on_data_change, function(data)
        for k, v in pairs(data) do
            self:set_prop(k, v)
            log:debug('%s optical_module_%s update %s to %s by ncsi', self.NetworkAdapterId, self.PortID, k, v)
        end
        self.get_properties_res.PowerInfoFromNcsi = true
    end)
    self:connect_signal(s.on_error, function()
        if self.get_properties_res.PowerInfoFromNcsi then
            log:error('%s optical_module_%s update optical power info by ncsi on_error', 
                self.NetworkAdapterId, self.PortID)
            self.get_properties_res.PowerInfoFromNcsi = false
        end
        s.data = nil -- 重置scheduler里的data，否则连接恢复后不会进入on_data_change
        self.PowerClass = 0xFF
        self.PowerCapacityWatts = 0
    end)
    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical power info by ncsi start', self.NetworkAdapterId, self.PortID)
end

-- ncsi获取光模块状态信息
function c_optical_module:update_optical_module_state_by_ncsi(id)
    local identifier = 'OpticalModuleState' .. id
    local s = self.ncsi_config_obj[identifier](self.ncsi_config_obj, {
        channel_id = self.PortID,
        extra_cmd = self.PortID,
        package_id = self.package_id
    })
    self:connect_signal(s.on_data_change, function(data)
        self.ModuleState = data
        log:debug('%s optical_module_%s update module state to %s by ncsi', self.NetworkAdapterId, self.PortID, data)
    end)
    self:connect_signal(s.on_error, function()
        if self.get_properties_res.ModuleStateFromNcsi then
            log:error('%s optical_module_%s update optical module state by ncsi on_error', 
                self.NetworkAdapterId, self.PortID)
            self.get_properties_res.ModuleStateFromNcsi = false
        end
        s.data = nil -- 重置scheduler里的data，否则连接恢复后不会进入on_data_change
        self.ModuleState = 0xFF
    end)
    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical module state by ncsi start', self.NetworkAdapterId, self.PortID)
end

function c_optical_module:is_prop_from_bma(key)
    -- 如果获取的数组长度大于1一定是bma获取的数据
    if self[key] and #self[key] > 1 then
        return true
    end
    return false
end

function c_optical_module:reset_prop_without_bma()
    for key, value in pairs(self.t_prop_without_bma) do
        self[key] = value
    end
end

local prop_name_with_bma = {
    ['TXBiasCurrentMilliAmps'] = 1,
    ['RXInputPowerMilliWatts'] = 2,
    ['TXOutputPowerMilliWatts'] = 3
}

function c_optical_module:set_prop(key, value)
    if not prop_name_with_bma[key] then
        self[key] = value
        return
    end
    -- prop_name_with_bma表中的属性更新前先判断当前数据是否为bma获取的，优先以bma获取的值为准
    if not self:is_prop_from_bma(key) then
        self[key] = value
    end
    self.t_prop_without_bma[key] = value -- 用于重置bma时恢复为其他途径获取的值
end

function c_optical_module:update_NSCI_optical_module_info()
    local s = self.ncsi_config_obj:OpticalModuleInfo({channel_id = self.PortID, package_id = self.package_id})
    if s.is_running then
        log:debug('update_NSCI_optical_module_info is_running')
        return
    end
    self:connect_signal(s.on_data_change, function(data)
        -- 0x8008表示不在位，目前该命令只适用Hi182x网卡
        if data == 0x8008 then
            self:reset()
            return
        end
        for k, v in pairs(data) do
            self:set_prop(k, v)
            log:debug('%s optical_module_%s update %s to %s by ncsi', self.NetworkAdapterId, self.PortID, k, v)
        end
        self.get_properties_res.ModuleInfoFromNcsi = true
        self.Presence = 1
        self.presence_retry_count = 0
        self:update_asset_data_info()
    end)
    self:connect_signal(s.on_error, function()
        if self.get_properties_res.ModuleInfoFromNcsi then
            log:error('%s optical_module_%s update optical module info by ncsi on_error', 
                self.NetworkAdapterId, self.PortID)
            self.get_properties_res.ModuleInfoFromNcsi = false
        end
        s.data = nil -- 重置scheduler里的data，否则连接恢复后不会进入on_data_change
    end)
    table.insert(self.schedulers, s)
    s.data = nil
    s:start()
    log:notice('%s optical_module_%s update optical module info by ncsi start', self.NetworkAdapterId, self.PortID)
end

function c_optical_module:start_schedulers()
    local identifier = self:get_optical_identifier()
    pcall(self.update_optical_temp_by_ncsi, self)
    pcall(self.update_optical_voltage_by_ncsi, self, identifier)
    pcall(self.update_optical_module_static_info_by_ncsi, self, identifier)
    pcall(self.update_optical_bias_by_ncsi, self, identifier)
    pcall(self.update_optical_input_power_by_ncsi, self, identifier)
    pcall(self.update_optical_output_power_by_ncsi, self, identifier)
    pcall(self.update_optical_connector_type_by_ncsi, self, identifier)
    pcall(self.update_optical_power_info_by_ncsi, self, identifier)
    pcall(self.update_optical_module_state_by_ncsi, self, identifier)
    pcall(self.update_NSCI_optical_module_info, self)
end

function c_optical_module:restart_schedulers()
    self.exec_queue(function()
        self:stop()
        self:start_schedulers()
    end)
end

function c_optical_module:update_ncsi_properties(mctp_obj, package_id)
    self.ncsi_config_obj = mctp_obj
    self.package_id = package_id
    self.exec_queue(function()
        self:start_schedulers()
    end)
end

-- MPU繁忙时暂停所有的轮询操作
function c_optical_module:pause()
    log:notice("pause optical module schedulers ,size of opticalmodule schedulers is %s", #self.schedulers)
    for _, s in ipairs(self.schedulers) do
        s:pause()
    end
end

-- MPU空闲时恢复所有的轮询操作
function c_optical_module:resume()
    log:notice("resume optical module schedulers ,size of opticalmodule schedulers is %s", #self.schedulers)
    for _, s in ipairs(self.schedulers) do
        s:resume()
    end
end

-- 卸载时停止所有的轮询操作
function c_optical_module:stop()
    for _, s in ipairs(self.schedulers) do
        s:deconstruct()
    end
    self.schedulers = {}
end

function c_optical_module:get_fault_state()
    return self.FaultState
end

function c_optical_module:set_power_state(power_state_value)
    self.PowerState = power_state_value
end

function c_optical_module:set_link_status(link_status_value)
    self.link_status = link_status_value
    log:notice('OpticalModule(%sPort%s) LinkStatus change to %s',
            self.NetworkAdapterId, self.PortID, link_status_value)
    if link_status_value == 'Connected' or link_status_value == 'LinkUp' then
        self.Presence = 1
    end
end

function c_optical_module:update_supported_type(name, _)
    if name == 'SpeedMatch' or name == 'TypeMatch' then
        local supported_type_flag = ((self.SpeedMatch == false) or (self.TypeMatch == false))
        self.IsSupportedType = (supported_type_flag and 1 or 0)
    end
end

function c_optical_module:update_fault_state(_, fault_state)
    local link_status = self.link_status
    log:debug('update_fault_state, link_status=%s', link_status)
    local power_state_flag = (((fault_state & 771) ~= 0) and (link_status ~= 'LinkDown') and
                                 (link_status ~= 'N/A'))
    self.PowerState = (power_state_flag and 1 or 0)
end

function c_optical_module:update_reading_celsius(_, value)
    -- 0x8000为告警专用值，不能随ReadingCelsius变化
    if value >= 255 and self.TemperatureCelsius == 0x8000 then
        return
    else
        self.TemperatureCelsius = value
    end
end

function c_optical_module:reset_bma_info()
    self.TransceiverType = ''
    self.ProductionDate = ''
    self.FiberConnectionType = ''
    self.SupportedSpeedsMbps = {}
    self.Type = ''
    self.TypeMatch = true
    self.SpeedMatch = true
    if not next(self.ncsi_config_obj) or not self.ncsi_config_obj.OpticalModuleInfo then
        self.Manufacturer = ''
        self.PartNumber = ''
        self.SerialNumber = ''
        self.WaveLengthNanometer = ''
    else
        self:next_tick(self.update_NSCI_optical_module_info, self)
    end
    log:debug('OpticalModule(%sPort%s) stats reset from bma complete', self.NetworkAdapterId,
        self.PortID)
end

function c_optical_module:reset_bma_stats()
    self.get_diagnostic_by_ibma_success = false
    self:reset_prop_without_bma()
    if not next(self.ncsi_config_obj) or not self.ncsi_config_obj.OpticalModuleInfo then
        self.ReadingCelsius = 0xFFFF
        self.Temp_UpperThresholdCritical = 0xFFFF
        self.Temp_LowerThresholdCritical = 0xFFFF
        self.TemperatureLowerThresholdCritical = 0xFFFF
        self.TemperatureUpperThresholdCritical = 0xFFFF
        self.SupplyVoltage = 0xFFFF
        self.Vol_UpperThresholdCritical = 0xFFFF
        self.Vol_LowerThresholdCritical = 0xFFFF
        self.VoltageLowerThresholdCritical = 0xFFFF
        self.VoltageUpperThresholdCritical = 0xFFFF
        self.TXBiasCurrentMilliAmps = {0xFFFF}
        self.BC_TXUpperThresholdCritical = 0xFFFF
        self.BC_TXLowerThresholdCritical = 0xFFFF
        self.TXOutputPowerMilliWatts = {0xFFFF}
        self.Power_TXUpperThresholdCritical = 0xFFFF
        self.Power_TXLowerThresholdCritical = 0xFFFF
        self.RXInputPowerMilliWatts = {0xFFFF}
        self.RXUpperThresholdCritical = 0xFFFF
        self.RXLowerThresholdCritical = 0xFFFF
    else
        self:next_tick(self.update_NSCI_optical_module_info, self)
    end

    log:debug('OpticalModule(%sPort%s) stats reset from bma complete', self.NetworkAdapterId,
        self.PortID)
end

function c_optical_module:restart_update_NSCI_optical_module_info(_, Presence)
    if Presence ~= 1 then
        log:notice('OpticalModule(%sPort%s) presence change to 0', self.NetworkAdapterId, self.PortID)
        return
    end
    self:restart_schedulers()
    log:notice('OpticalModule(%sPort%s) presence change to 1, restart schedulers', self.NetworkAdapterId, self.PortID)
end

-- 注册对应监听信号的回调函数
function c_optical_module:register_property_changed_callback()
    local switch = {
        FaultState = c_optical_module.update_fault_state,
        ReadingCelsius = c_optical_module.update_reading_celsius,
        SpeedMatch = c_optical_module.update_supported_type,
        TypeMatch = c_optical_module.update_supported_type,
        Presence = c_optical_module.restart_update_NSCI_optical_module_info
    }
    self:connect_signal(self.on_property_changed, function(name, value)
        if switch[name] then
            switch[name](self, name, value)
        end
    end)
end

function c_optical_module.create_mdb_object(value, port_orm_obj)
    local app = c_object_manage.get_instance().app
    if value.CreatedByDeviceObject then
        return app:CreateOpticalModule(1, value.CardObjectName, value.PortObjectName, function (obj)
            log:notice("create optical module mdb object, NetworkAdapterId(%s), PortID(%s)",
                value.NetworkAdapterId, value.PortID)
            obj.NetworkAdapterId = value.NetworkAdapterId
            obj.PortID = value.PortID
            obj.ObjectName = value.ObjectName
            obj.ObjectIdentifier = value.ObjectIdentifier
            obj.CreatedByDeviceObject = true

            rawset(obj, 'get_parent', function()
                return port_orm_obj
            end)
        end)
    end
end

function c_optical_module:ctor()
    self.schedulers = {}
    self.link_status = 'N/A'
    self.channel_num = 0
    self.presence_retry_count = 0
    self.CpuId = 0
    self.ncsi_config_obj = {}
    self.smbus_schedulers = {}
    self.package_id = 0
    self.t_prop_without_bma = {}
    self.tasks = c_tasks.new()
    self.is_npu_heartbeat_loss = false
    self.is_dmp_status_ready = false
    self.om_event_handler = {}
    self.last_record_time = 0
    self.last_get_elec_info_time = 0
    self.last_record_elec_info_time = 0
    self.electric_info = {
        npu_id = 0,
        pcs_err_cnt = 0,
        cw_before_correct_cnt = 0,
        cw_correct_cnt = 0,
        cw_uncorrect_cnt = 0,
        npu_rx_snr_max = {0, 0, 0, 0, 0, 0, 0, 0},
        npu_rx_snr_min = {0, 0, 0, 0, 0, 0, 0, 0},
        cdr_host_snr_max = {0, 0, 0, 0, 0, 0, 0, 0},
        cdr_host_snr_min = {0, 0, 0, 0, 0, 0, 0, 0},
        cdr_media_snr_max = {0, 0, 0, 0, 0, 0, 0, 0},
        cdr_media_snr_min = {0, 0, 0, 0, 0, 0, 0, 0}
    }
    self.CorrectableFECErrors = 0
    self.UnCorrectableFECErrors = 0
    self.npu_optical_module_info_abnormal = false
    self.npu_om_not_present = true -- npu光模块信息获取返回错误码9，表示npu光模块不在位
    self.smbios_status = 0
    self.children = {}
    self.parent = {}
    self.device_path = ''
    self.get_diagnostic_by_ibma_success = false
    self.get_properties_res = {}
    self.extreme_value_log_interval = 60 * 60
    self.cache_related_port_info = {}
    self.cache_location = ''
    self.RxTotalPktNum = 0
    self.RxBadPktNum = 0
    self.exec_queue = queue()
end

function c_optical_module.before_add_object(object)
    local parent = object:get_parent()
    -- 光模块必须依赖其父对象网口上树
    while not parent or parent.NetworkAdapterId == '' do
        skynet.sleep(100)
        parent = object:get_parent()
    end

    object.NetworkAdapterId = parent.NetworkAdapterId
    object.PortID = parent.PortID
    return false
end

local first_statistics = {}
local optical_max_and_min_info = {}

-- 处理每周期内 第一帧消息作为参考数据
local function process_first_optical_info_in_period(optical_max_and_min_info, state_info, runtime_info)
    optical_max_and_min_info.vcc_max = state_info.SupplyVoltage
    optical_max_and_min_info.vcc_min = state_info.SupplyVoltage
    optical_max_and_min_info.tx_power_max = utils.table_copy(state_info.TXOutputPowerMilliWatts)
    optical_max_and_min_info.tx_power_min = utils.table_copy(state_info.TXOutputPowerMilliWatts)
    optical_max_and_min_info.rx_power_max = utils.table_copy(state_info.RXInputPowerMilliWatts)
    optical_max_and_min_info.rx_power_min = utils.table_copy(state_info.RXInputPowerMilliWatts)
    optical_max_and_min_info.tx_bias_max = utils.table_copy(state_info.TXBiasCurrentMilliAmps)
    optical_max_and_min_info.tx_bias_min = utils.table_copy(state_info.TXBiasCurrentMilliAmps)
    optical_max_and_min_info.tx_los = state_info.TxLoss
    optical_max_and_min_info.rx_los = state_info.RxLoss
    optical_max_and_min_info.tx_lol = state_info.TxLossOfLock
    optical_max_and_min_info.rx_lol = state_info.RxLossOfLock
    optical_max_and_min_info.temperature_max = state_info.TemperatureCelsius
    optical_max_and_min_info.temperature_min = state_info.TemperatureCelsius
    optical_max_and_min_info.odsp_die_temp_max = runtime_info.OdspDieTemperatureCelsius
    optical_max_and_min_info.odsp_die_temp_min = runtime_info.OdspDieTemperatureCelsius
    optical_max_and_min_info.odsp_high_temp_runtime = runtime_info.OdspHighTempRuntimeSeconds
    optical_max_and_min_info.host_snr_max = utils.table_copy(state_info.HostSNR)
    optical_max_and_min_info.host_snr_min = utils.table_copy(state_info.HostSNR)
    optical_max_and_min_info.media_snr_max = utils.table_copy(state_info.MediaSNR)
    optical_max_and_min_info.media_snr_min = utils.table_copy(state_info.MediaSNR)
    optical_max_and_min_info.op_physical_id = state_info.op_physical_id
    optical_max_and_min_info.location = state_info.location
    optical_max_and_min_info.vpp_max = utils.table_copy(runtime_info.VppVoltage)
    optical_max_and_min_info.vpp_min = utils.table_copy(runtime_info.VppVoltage)
end

-- 处理每周期内最值
local function process_max_min_optical_info_in_period(optical_max_and_min_info, state_info, runtime_info)
    optical_max_and_min_info.vcc_max = math.max(optical_max_and_min_info.vcc_max,
        state_info.SupplyVoltage)
    optical_max_and_min_info.vcc_min = math.min(optical_max_and_min_info.vcc_min,
        state_info.SupplyVoltage)
    for i = 1, #state_info.TXOutputPowerMilliWatts do
        optical_max_and_min_info.tx_power_max[i] =
            math.max(optical_max_and_min_info.tx_power_max[i],
            state_info.TXOutputPowerMilliWatts[i])
        optical_max_and_min_info.tx_power_min[i] =
            math.min(optical_max_and_min_info.tx_power_min[i],
            state_info.TXOutputPowerMilliWatts[i])
        optical_max_and_min_info.rx_power_max[i] =
            math.max(optical_max_and_min_info.rx_power_max[i],
            state_info.RXInputPowerMilliWatts[i])
        optical_max_and_min_info.rx_power_min[i] =
            math.min(optical_max_and_min_info.rx_power_min[i],
            state_info.RXInputPowerMilliWatts[i])
        optical_max_and_min_info.tx_bias_max[i] =
            math.max(optical_max_and_min_info.tx_bias_max[i],
            state_info.TXBiasCurrentMilliAmps[i])
        optical_max_and_min_info.tx_bias_min[i] =
            math.min(optical_max_and_min_info.tx_bias_min[i],
            state_info.TXBiasCurrentMilliAmps[i])
    end
    for i = 1, #state_info.HostSNR do
        optical_max_and_min_info.host_snr_max[i] =
            math.max(optical_max_and_min_info.host_snr_max[i],
            state_info.HostSNR[i])
        optical_max_and_min_info.host_snr_min[i] =
            math.min(optical_max_and_min_info.host_snr_min[i],
            state_info.HostSNR[i])
        optical_max_and_min_info.media_snr_max[i] =
            math.max(optical_max_and_min_info.media_snr_max[i],
            state_info.MediaSNR[i])
        optical_max_and_min_info.media_snr_min[i] =
            math.min(optical_max_and_min_info.media_snr_min[i],
            state_info.MediaSNR[i])
    end
    for i = 1, #runtime_info.VppVoltage do
        optical_max_and_min_info.vpp_max[i] = math.max(optical_max_and_min_info.vpp_max[i], runtime_info.VppVoltage[i])
        optical_max_and_min_info.vpp_min[i] = math.min(optical_max_and_min_info.vpp_min[i], runtime_info.VppVoltage[i])
    end
    optical_max_and_min_info.tx_los = optical_max_and_min_info.tx_los | state_info.TxLoss
    optical_max_and_min_info.rx_los = optical_max_and_min_info.rx_los | state_info.RxLoss
    optical_max_and_min_info.tx_lol = optical_max_and_min_info.tx_lol | state_info.TxLossOfLock
    optical_max_and_min_info.rx_lol = optical_max_and_min_info.rx_lol | state_info.RxLossOfLock
    optical_max_and_min_info.temperature_max = math.max(optical_max_and_min_info.temperature_max, state_info.TemperatureCelsius)
    optical_max_and_min_info.temperature_min = math.min(optical_max_and_min_info.temperature_min, state_info.TemperatureCelsius)
    optical_max_and_min_info.odsp_die_temp_max = math.max(optical_max_and_min_info.odsp_die_temp_max, runtime_info.OdspDieTemperatureCelsius)
    optical_max_and_min_info.odsp_die_temp_min = math.min(optical_max_and_min_info.odsp_die_temp_min, runtime_info.OdspDieTemperatureCelsius)
    optical_max_and_min_info.odsp_high_temp_runtime = runtime_info.OdspHighTempRuntimeSeconds
    optical_max_and_min_info.op_physical_id = state_info.op_physical_id
    optical_max_and_min_info.location = state_info.location
end

-- 获取某NPU获取到的光模块最值信息
local function get_max_and_min_optical_info(npu_id, state_info, runtime_info, op_logical_id)
    local op_num = (not op_logical_id or op_logical_id == 255) and 1 or (op_logical_id + 1)
    if first_statistics[npu_id] == nil then
        first_statistics[npu_id] = {}
    end
    if first_statistics[npu_id][op_num] == nil then
        first_statistics[npu_id][op_num] = true
    end
    if not optical_max_and_min_info[npu_id] then
        optical_max_and_min_info[npu_id] = {}
    end
    -- 周期内第一帧消息处理
    if first_statistics[npu_id][op_num] then
        optical_max_and_min_info[npu_id][op_num] = {
            log_time = vos.vos_tick_get() // 1000,
            npu_id = npu_id,
            component = dfx_defs.component.OPTICAL_MODULE,
            msg_type = dfx_defs.optical_module.PERIOD,
        }
        process_first_optical_info_in_period(optical_max_and_min_info[npu_id][op_num], state_info, runtime_info)
        first_statistics[npu_id][op_num] = false
        return nil
    end
    -- 周期内每帧最值比较
    process_max_min_optical_info_in_period(optical_max_and_min_info[npu_id][op_num], state_info, runtime_info)
    return optical_max_and_min_info[npu_id][op_num]
end

-- 资源树上光模块电压、tx\rx功率、偏置电流与日志记录的单位不一致，这里做转换
local function unit_conversion(dfx_msg)
    dfx_msg.vcc_max = math.floor(dfx_msg.vcc_max * UNIT_CONVERSION1)
    dfx_msg.vcc_min = math.floor(dfx_msg.vcc_min * UNIT_CONVERSION1)
    for i = 1, #dfx_msg.tx_power_max do
        dfx_msg.tx_power_max[i] = math.floor(dfx_msg.tx_power_max[i] * UNIT_CONVERSION1)
        dfx_msg.tx_power_min[i] = math.floor(dfx_msg.tx_power_min[i] * UNIT_CONVERSION1)
        dfx_msg.rx_power_max[i] = math.floor(dfx_msg.rx_power_max[i] * UNIT_CONVERSION1)
        dfx_msg.rx_power_min[i] = math.floor(dfx_msg.rx_power_min[i] * UNIT_CONVERSION1)
        dfx_msg.tx_bias_max[i] = math.floor(dfx_msg.tx_bias_max[i] * UNIT_CONVERSION2)
        dfx_msg.tx_bias_min[i] = math.floor(dfx_msg.tx_bias_min[i] * UNIT_CONVERSION2)
    end
    return dfx_msg
end

local function process_period_dfx_msg(id, state_info, runtime_info, extreme_value_log_interval, op_logical_id)
    -- 部分空表，表示属性捞取异常，这里不做处理
    if #state_info.TXOutputPowerMilliWatts == 0 or
        #state_info.RXInputPowerMilliWatts == 0 or
        #state_info.TXBiasCurrentMilliAmps == 0 then
        return
    end
    local cur_time = vos.vos_tick_get() // 1000
    local dfx_msg = get_max_and_min_optical_info(id, state_info, runtime_info, op_logical_id)
    local op_num = (not op_logical_id or op_logical_id == 255) and 1 or (op_logical_id + 1)
    -- 每45min记录一条极值dfx记录
    if dfx_msg and (cur_time - optical_max_and_min_info[id][op_num].log_time) > extreme_value_log_interval then
        dfx_msg = unit_conversion(dfx_msg)
        first_statistics[id][op_num] = true
        dfx_collector.get_instance():msg_productor(dfx_msg)
    end
end

function c_optical_module:filled_resource_tree_runtime_attributes(runtime_info)
    if runtime_info then
        self.PowerOnCount = runtime_info.PowerOnCount
        self.UptimeSeconds = runtime_info.UptimeSeconds
        self.PowerStatus = runtime_info.PowerStatus
        self.OdspDieTemperatureCelsius = runtime_info.OdspDieTemperatureCelsius
        self.OdspHighTempRuntimeSeconds = runtime_info.OdspHighTempRuntimeSeconds
        self.LaserRuntimeSeconds = runtime_info.LaserRuntimeSeconds
        self.LaserTemperatureCelsius = runtime_info.LaserTemperatureCelsius
    end
end

function c_optical_module:filled_resource_tree_alarm_attributes(alarm_info)
    if alarm_info then
        self.SelfTestStatus = alarm_info.HardwareFaultStatus
        self.HostInterfaceFaultStatus = alarm_info.HostInterfaceFaultStatus
        self.MediaInterfaceFaultStatus = alarm_info.MediaInterfaceFaultStatus
    end
end

function c_optical_module:filled_resource_tree_base_attributes(base_info_table)
    self.Presence = 1
    self.Manufacturer = base_info_table.vendor_name
    self.SerialNumber = base_info_table.sn
    self.TransceiverType = base_info_table.transceiver_type
    self.ProductionDate = base_info_table.manufacture_date
    self.PartNumber = base_info_table.part_number
    self.ChannelNum = base_info_table.channel_num
    self.Type = base_info_table.type
end

function c_optical_module:filled_resource_tree_presence_attributes()
    self.Presence = 0
    self.FiberDirtyDetected = false
    self.FiberLoosenessDetected = false
    self.Accessible = 0
end

function c_optical_module:filled_resource_tree_state_attributes(state_info)
    self.Presence = 1
    self.SupplyVoltage = state_info.SupplyVoltage
    self.TXOutputPowerMilliWatts = state_info.TXOutputPowerMilliWatts
    self.RXInputPowerMilliWatts = state_info.RXInputPowerMilliWatts
    self.TXBiasCurrentMilliAmps = state_info.TXBiasCurrentMilliAmps
    self.TxLoS = state_info.TxLoS
    self.RxLoS = state_info.RxLoS
    self.TxFault = state_info.TxFault
    self.TxLossOfLock = state_info.TxLossOfLock
    self.RxLossOfLock = state_info.RxLossOfLock
    self.TemperatureCelsius = state_info.TemperatureCelsius
    self.ReadingCelsius = state_info.TemperatureCelsius
    self.Accessible = state_info.Accessible
    self.HostSNR = state_info.HostSNR
    self.MediaSNR = state_info.MediaSNR
    self.RxSNR = state_info.RxSNR
end

function c_optical_module:set_npu_heartbeat_loss_status(status)
    self.is_npu_heartbeat_loss = status
end

function c_optical_module:set_dmp_ready_status(status)
    self.is_dmp_status_ready = status
end

function c_optical_module:filled_resource_tree_statistics_info(statistics_info)
    if not statistics_info then
        return
    end
    self.CorrectableFECErrors = statistics_info.CorrectableFECErrors
    self.UnCorrectableFECErrors = statistics_info.UnCorrectableFECErrors
    self.RxTotalPktNum = statistics_info.RxTotalPktNum
    self.RxBadPktNum = statistics_info.RxBadPktNum
end

function c_optical_module:get_optical_info_from_imu_retry(id)
    local ok, result, state_info, op_not_present
    local physical_id = tonumber(string.match(self.SilkText, '(%d+)$'))  --匹配倒数的数字
    for i = 1, 3 do -- 最多尝试3次
        ok, result, state_info, op_not_present = pcall(npu_imu_cmd.get_optical_info_from_imu, id, self.ChannelNum,
                    self.LogicalId, self:get_parent():get_parent().Model, self.cache_location, physical_id)
        if ok and (result or op_not_present) then
            return ok, result, state_info, op_not_present
        end
        skynet.sleep(10) -- 延时100ms
    end
    return ok, result, state_info, op_not_present
end

function c_optical_module:init_npu_optical_module_info(id)
    local model = self:get_parent():get_parent().Model
    local runtime_info = npu_imu_cmd.get_op_runtime_info(id, self.LogicalId, model)
    self:filled_resource_tree_runtime_attributes(runtime_info)
    local alarm_info = npu_imu_cmd.get_op_alarm_info(id, self.LogicalId, model)
    self:filled_resource_tree_alarm_attributes(alarm_info)
    local statistics_info = npu_imu_cmd.get_op_statistics_info(id, self.LogicalId, model)
    self:filled_resource_tree_statistics_info(statistics_info)
    -- 首次获取，或距离上次获取超过600s才更新op base info, 否则沿用之前的数据
    if (self.last_record_time == 0) or 
        (skynet.now() - self.last_record_time > 60000)then
        log:debug('npu op_base_info is time to update, id:%s', id)
        -- 光模块门限更新
        self:check_update_thresholds(id)
        local err1, result, base_info_table = pcall(npu_imu_cmd.get_op_base_info, id, self.LogicalId, model)
        self.last_record_time = skynet.now()
        -- 获取信息不成功时不刷新静态信息
        if result ==comp_code.Success then
            self:filled_resource_tree_base_attributes(base_info_table)
        else
            log:debug('failed to call get npu%s op base info:%s ,error:%s', id, result, err1)
        end
    end
    local err2, result, state_info, op_not_present = self:get_optical_info_from_imu_retry(id)
    if not err2 then
        log:debug('failed to get npu%s op state info', id)
        return
    end
    self.npu_om_not_present = op_not_present -- npu光模块信息获取返回错误码9，表示npu光模块不在位
    if result then
        -- 返回成功 npu光模块信息获取置为正常
        self.npu_optical_module_info_abnormal = false
        self:filled_resource_tree_state_attributes(state_info)
    else
        -- 返回错误码 npu光模块信息获取置为异常
        self.npu_optical_module_info_abnormal = true
        self:filled_resource_tree_presence_attributes()
    end

    if not self.is_dmp_status_ready then
        self.npu_optical_module_info_abnormal = false
    end

    -- 光模块日志记录
    if runtime_info and runtime_info.ValidFlag and state_info then
        process_period_dfx_msg(id, state_info, runtime_info, self.extreme_value_log_interval, self.LogicalId)
    end
    self:get_op_log_info(id)
    -- 光模块告警状态值更新
    self:update_bias_current()
    -- 更新prbs测试信息
    self:update_prbs_test_info(id)
    -- 更新脏污测试信息
    self:update_dirty_test_info(id)
end

function c_optical_module:get_op_log_info(id)
    local model = self:get_parent():get_parent().Model
    local physical_id = tonumber(string.match(self.SilkText, '(%d+)$'))
    -- 光模块闪断信息日志记录
    npu_imu_cmd.get_linkdown_optical_info(id, self.LogicalId, model, self.cache_location, physical_id)
    -- 光模块闪断电链路信息日志记录
    npu_imu_cmd.get_linkdown_electric_info(id, self.LogicalId, model, self.cache_location, physical_id)
    -- 首次获取，或距离上次获取超过30min才更新日志信息
    if (self.last_get_elec_info_time == 0) or 
        (skynet.now() - self.last_get_elec_info_time > 30 * 60 * 100)then
        -- 光模块周期电链路信息日志记录
        self:get_period_electric_info(id)
        self.last_get_elec_info_time = skynet.now()
    end
end

function c_optical_module:get_period_electric_info(id)
    local model = self:get_parent():get_parent().Model
    local physical_id = tonumber(string.match(self.SilkText, '(%d+)$'))
    local ok, result, info = pcall(npu_imu_cmd.get_period_electric_info, id, self.LogicalId, model, self.cache_location, physical_id)
    if not ok or result ~= comp_code.Success or not info then
        return
    end
    self.electric_info.op_physical_id = info.op_physical_id
    self.electric_info.location = info.location
    self.electric_info.npu_id = info.npu_id
    self.electric_info.component = info.component
    self.electric_info.msg_type = info.msg_type
    self.electric_info.pcs_err_cnt = info.pcs_err_cnt
    self.electric_info.cw_before_correct_cnt = info.cw_before_correct_cnt
    self.electric_info.cw_correct_cnt = info.cw_correct_cnt
    self.electric_info.cw_uncorrect_cnt = info.cw_uncorrect_cnt
    for i = 1, LANE_PER_OP, 1 do
        if info.npu_rx_snr[i] > self.electric_info.npu_rx_snr_max[i] then
            self.electric_info.npu_rx_snr_max[i] = info.npu_rx_snr[i]
        end
        if info.npu_rx_snr[i] < self.electric_info.npu_rx_snr_min[i] or
            self.electric_info.npu_rx_snr_min[i] == 0 then
            self.electric_info.npu_rx_snr_min[i] = info.npu_rx_snr[i]
        end
        if info.cdr_host_snr[i] > self.electric_info.cdr_host_snr_max[i] then
            self.electric_info.cdr_host_snr_max[i] = info.cdr_host_snr[i]
        end
        if info.cdr_host_snr[i] < self.electric_info.cdr_host_snr_min[i] or
            self.electric_info.cdr_host_snr_min[i] == 0 then
            self.electric_info.cdr_host_snr_min[i] = info.cdr_host_snr[i]
        end
        if info.cdr_media_snr[i] > self.electric_info.cdr_media_snr_max[i] then
            self.electric_info.cdr_media_snr_max[i] = info.cdr_media_snr[i]
        end
        if info.cdr_media_snr[i] < self.electric_info.cdr_media_snr_min[i] or
            self.electric_info.cdr_media_snr_min[i] == 0 then
            self.electric_info.cdr_media_snr_min[i] = info.cdr_media_snr[i]
        end
    end
    if self.last_record_elec_info_time == 0 then
        self.last_record_elec_info_time = skynet.now()
    -- 超过180min才发送记录到Dfx消息队列
    elseif skynet.now() - self.last_record_elec_info_time > 180 * 60 * 100 then
        dfx_collector.get_instance():msg_productor(self.electric_info)
        self.last_record_elec_info_time = skynet.now()
    end
end

function c_optical_module:check_info_from_imu(id, parent)
    if self.is_first_get_info_from_imu then
        if self.is_npu_heartbeat_loss or fructl.get_power_status() ~= 'ON' or parent.PowerOn ~= 1 then
            self.Presence = 0
            self.FiberDirtyDetected = false
            self.FiberLoosenessDetected = false
        end
        self.is_first_get_info_from_imu = false
        return
    end

    if fructl.get_power_status() ~= 'ON' or parent.PowerOn ~= 1 then
        if log:getLevel() >= log.DEBUG then
            log:debug('[NPU] npu is power off')
        end
        self:reset_npu_om()
        self.npu_optical_module_info_abnormal = false -- 业务下电或驱动不在位，不认为获取异常。
        return
    end
    if self.is_npu_heartbeat_loss then
        log:debug('[NPU] npu %s heartbeat loss, pause get npu op info', id)
        self.npu_optical_module_info_abnormal = true
        return
    end

    local ok, err = pcall(function()
        return self:init_npu_optical_module_info(id)
    end)
    if not ok then
        if log:getLevel() >= log.DEBUG then
            log:debug('[NPU] init npu optical module info fail, err: %s', err)
        end
    end
end

function c_optical_module:get_info_from_imu_task(id)
    local parent = self:get_parent()
    while true do
        -- 每15s更新从npu获取的光模块信息
        skynet.sleep(1500)
        self:check_info_from_imu(id, parent)
        -- 更新npu om告警事件
        self.om_event_handler:update_om_event()
    end
end

function c_optical_module:get_info_from_imu(id)
    log:notice('[NPU] get_info_from_imu task start, id = %s', id)
    skynet.fork_loop({count = 0}, function()
        skynet.sleep(((id + 1) // 2) * 50)
        self:get_info_from_imu_task(id)
    end)
end

-- 更新光模块门限值
function c_optical_module:check_update_thresholds(id)
    local err, result, state_info = pcall(npu_imu_cmd.get_optical_thresholds, id, self.LogicalId,
                        self:get_parent():get_parent().Model)
    if not err then
        if log:getLevel() >= log.DEBUG then
            log:debug(state_info)
        end
        return
    end
    self:set_thresholds(state_info, result)
end

-- 更新光模块门限值
function c_optical_module:set_thresholds(state_info, result)
    if state_info and result then
        self.TemperatureLowerThresholdCritical = state_info.TemperatureLowerThresholdCritical
        self.TemperatureUpperThresholdCritical = state_info.TemperatureUpperThresholdCritical
        self.VoltageLowerThresholdCritical = state_info.VoltageLowerThresholdCritical
        self.VoltageUpperThresholdCritical = state_info.VoltageUpperThresholdCritical
        self.Power_TXLowerThresholdCritical = state_info.Power_TXLowerThresholdCritical
        self.Power_TXUpperThresholdCritical = state_info.Power_TXUpperThresholdCritical
        self.RXLowerThresholdCritical = state_info.RXLowerThresholdCritical
        self.RXUpperThresholdCritical = state_info.RXUpperThresholdCritical
        self.BC_TXLowerThresholdCritical = state_info.BC_TXLowerThresholdCritical
        self.BC_TXUpperThresholdCritical = state_info.BC_TXUpperThresholdCritical
        self.PowerRXLowerThresholdWarning = state_info.PowerRXLowerThresholdWarning
    end
end

-- 更新各通道偏置电流读数
function c_optical_module:update_bias_current()
    local prop_name
    local ok, err = pcall(function()
        for k, v in ipairs(self.TXBiasCurrentMilliAmps) do
            prop_name = "TXBiasCurrent" .. k .. "MilliAmps"
            self[prop_name] = v
        end
    end)
    if not ok then
        if log:getLevel() >= log.DEBUG then
            log:debug("update_bias_current failed, err=%s", err)
        end
    end
end

function c_optical_module:register_npu_property_changed_callback()
    local switch = {
        FiberDirtyDetected = 1,
        FiberLoosenessDetected = 2
    }
    self:connect_signal(self.on_property_changed, function(name, value)
        if switch[name] then
            local ctx = context.get_context()
            if ctx and common_func:has_initiator(ctx) then
                log:notice('%s update %sPort%sOpticalModule %s to %s', ctx.Requestor,
                    self.NetworkAdapterId, self.PortID + 1, name, value)
            end
        end
    end)
end

-- NPU光模块监听os下电或复位信号后恢复脏污、松动告警
function c_optical_module:npu_register_listen_os_status()
    client:OnFruCtrlPropertiesChanged(function(values, _, _)
        local sys_reset_detected, power_state
        if values.SysResetDetected then
            sys_reset_detected = values.SysResetDetected:value()
        elseif values.PowerState then
            power_state = values.PowerState:value()
        end
        if sys_reset_detected == 1 or power_state == 'OFF' then -- 为1代表OS复位
            self.FiberDirtyDetected = false
            self.FiberLoosenessDetected = false
        end
    end)
end

function c_optical_module:init()
    if self.CreatedByDeviceObject then
        log:notice('c_optical_module init start, NetworkAdapterId(%s), PortID(%s)', self.NetworkAdapterId, self.PortID)
        c_optical_module.super.init(self)
        return
    end
    local parent = self:get_parent()
    self.channel_num = parent.PortID + 1
    self.PortID = parent.PortID
    self.NetworkAdapterId = parent.NetworkAdapterId
    c_optical_module.super.init(self)
    local card = parent:get_parent()

    if card.Model and string.match(card.Model, 'NPU') then
        self:register_npu_property_changed_callback()
        self:npu_register_listen_os_status()
        self:register_mdb_objects()
        log:notice('[NPU] get_info_from_imu start, slot = %s, npuid = %s',
            card.SlotNumber, parent.NpuID)
        self.om_event_handler = c_om_event_handler.new(self, npu_om_event_debounce_config)
        self:next_tick(function ()
            self:get_info_from_imu(parent.NpuID)
        end)
    else
        self:register_mdb_objects()
        self:register_property_changed_callback()
        self:start_get_optical_info_task()
    end
end

local function get_pfid(op_name)
    local startPos = string.find(op_name, '_')
    if not startPos then
        return 255
    end
    local result = string.sub(op_name, startPos)
    local objs = client:GetEthDeviceObjects()
    local obj = objs[ETH_PATH..result]
    if obj then
        return obj.PortFunctionId
    end
    return 255
end

function c_optical_module:start_get_optical_info_task()
    local card = self:get_parent():get_parent()
    -- 仅CPU直出光模块通过IMU获取
    if not card or card.Type ~= 1 then
        return
    end
    local retry_times = 0
    while not card or card.SocketId == '' and retry_times < 10 do
        retry_times = retry_times + 1
        self.tasks:sleep_ms(1000)
    end
    self.tasks:new_task(
        'get optical module asset task ' .. tostring(self.NetworkAdapterId) .. '-' ..
        tostring(self.PortID)):loop(function(task)
        self.CpuId = card.SocketId
        -- 兼容老版本PfID为默认时采用PortID
        local pfid = get_pfid(self.name)
        self.PfID = pfid ~= 255 and pfid or (self.PfID ~= 255 and self.PfID or self.PortID)
        local ok, identifier = self:get_optical_module_identifier()
        if  ok then
            self:get_optical_module_static_info(identifier)
            self:get_optical_module_diagnostic_info(identifier)
        else
            self:reset_diagnostic_info()
        end
    -- 每5s更新光模块信息
    end):set_timeout_ms(5000)
end

function c_optical_module:get_optical_module_identifier()
    local identifier = self:get_optical_module_diagnostic({offset = 0, length = 1})
    if identifier then
        return true, identifier
    end
    return nil
end

function c_optical_module:get_optical_module_static_info(identifier)
    if identifier == IDENTIFIER_DEFAULT or not om_config.static_info_list[identifier] then
        return
    end
    for k, v in pairs(om_config.static_info_list[identifier]) do
        skynet.sleep(150)  -- 等待1.5s减少cpu占用
        local set_prop = self:get_optical_module_asset(v)
        self[k] = set_prop or self[k]
    end
end

-- 下电或IMU异常获取不到信息，要恢复默认值
function c_optical_module:reset_diagnostic_info()
    for prop_name, v in pairs(om_config.diagnostic_info_list[IDENTIFIER_DEFAULT]) do
        self[prop_name] = v.default
    end
    if not c_optical_channel.collection.objects then
        return
    end
    for _, channel in ipairs(c_optical_channel.collection.objects) do
        local optical_module = channel:get_parent()
        if optical_module and optical_module.name == self.name then
            channel:reset_diagnostic_info()
        end
    end
end

function c_optical_module:get_optical_module_diagnostic_info(identifier)
    if identifier == IDENTIFIER_DEFAULT or not om_config.diagnostic_info_list[identifier] then
        return
    end
    local muti_props = {}
    for k, v in pairs(om_config.diagnostic_info_list[identifier]) do
        skynet.sleep(150)  -- 等待1.5s减少cpu占用
        muti_props[k] = {}
        for channel, vv in pairs(v) do
            if type(vv) == 'table' then
                local channel_num = string.match(channel,'Channel(%d)') or ''
                local set_prop = self:update_channel_prop(v, tonumber(channel_num), vv)
                muti_props[k][tonumber(channel_num)] = set_prop
            else
                self:update_diag_prop_single(k, v)
            end
        end
    end
    self:update_diag_prop_muti(muti_props)
end

local function check_validity(input)
    return input ~= INVALID_READING and input ~= 0
end

function c_optical_module:update_channel_prop(v, channel_num, vv)
    local prop_name = vv.alias
    local set_prop = self:get_optical_module_diagnostic(vv)
    local upper = self[v.UpperThreshold] or 0
    local lower = self[v.LowerThreshold] or 0
    local alarm_val = 0
    if check_validity(upper) and check_validity(set_prop) and set_prop > upper then
        alarm_val = ALARM_VALUE
    elseif check_validity(lower) and check_validity(set_prop) and set_prop < lower then
        alarm_val = ALARM_VALUE
    end
    if not c_optical_channel.collection.objects then
        return
    end
    for _, channel in ipairs(c_optical_channel.collection.objects) do
        local om_obj = channel:get_parent()
        local channel_id = channel.Id
        if channel_id == channel_num and om_obj and om_obj.name == self.name then
            channel:update_prop_single(prop_name, set_prop, alarm_val)
        end
    end
    return set_prop
end

function c_optical_module:update_diag_prop_single(prop_name, v)
    local set_prop = self:get_optical_module_diagnostic(v)
    if set_prop ~= nil then
        self[prop_name] = set_prop
    end
end

function c_optical_module:update_diag_prop_muti(muti_props)
    for prop_name, set_prop in pairs(muti_props) do
        if set_prop ~= nil and next(set_prop) then
            self[prop_name] = set_prop
        end
    end
end

-- 获取光模块静态资源，返回字符串
function c_optical_module:get_optical_module_asset(v)
    local offset = v.offset
    local length = v.length
    if not offset or not length then
        return
    end
    local ok, rsp = self:sfp_read_eeprom_offset(offset, length)
    if not ok then
        if log:getLevel() >= log.DEBUG then
            log:debug('get optical module asset failed, offset: %d, length: %d, error: %s', offset, length, rsp)
        end
        return
    end

    if rsp == '' then
        return
    end
    local ret_val = string.gsub(rsp, "[ ]+$", "")
    if not ret_val then
        return
    end
    if v.handle_func then
        return v.handle_func(ret_val)
    end

    return ret_val
end

-- 获取光模块动态资源，返回数字，可以用掩码
function c_optical_module:get_optical_module_diagnostic(v)
    local offset = v.offset
    local length = v.length
    if not offset or not length then
        return
    end
    local ok, rsp = self:sfp_read_eeprom_offset(offset, length)
    if not ok then
        if log:getLevel() >= log.DEBUG then
            log:debug('get optical module asset failed, offset: %d, length: %d, error: %s', offset, length, rsp)
        end
        return
    end
    local ret_val = length > 1 and string.unpack('>H', rsp) or string.byte(rsp)
    local mask = v.mask
    if not ret_val then
        return
    end
    if mask then
        ret_val = ret_val & mask
    end
    if v.handle_func then
        return v.handle_func(ret_val)
    end

    return ret_val
end

function c_optical_module:sfp_read_eeprom_offset(offset, length)
    return pcall(function()
        if length == 0 then
            error('sfp_read_eeprom_offset size is 0, offset: %d', offset)
        end

        local sfp_format = bs.new('<<0xDB0700:3/unit:8, 0x22, chip_id, pfid, offset:2/unit:8, length>>')
        local req_data = sfp_format:pack({chip_id = self.CpuId, pfid = self.PfID, offset = offset, length = length})
        local cc, payload
        for _ = 1, 3 do
            cc, payload = self:send_ipmi_request(req_data)
            if cc == comp_code.Success then
                break
            end
            self.tasks:sleep_ms(1000)
        end
        if cc ~= comp_code.Success then
            error("ipmi get eeprom data failed, complete code: %s", cc)
        end

        local head = string.sub(payload, 1, 5)
        local rsp_format = bs.new('<<0xDB0700:3/unit:8, Status:1/unit:8, 0x5a>>')
        local rsp = rsp_format:unpack(head)
        local data = string.sub(payload, 6, -1)

        if rsp and rsp.Status == 0x00 then
            self.Presence = 1
        else
            log:info("optical module not supported")
        end

        return data
    end)
end

function c_optical_module:send_ipmi_request(req_data)
    local err, rsp = ipmi.request(self:get_bus(), channel_type.CT_ME:value(),
        {DestNetFn = 0x30, Cmd = 0x98, Payload = req_data})

    if err ~= comp_code.Success then
        return err, nil
    end
    return err, rsp
end

-- 更新脏污测试相关信息
function c_optical_module:update_dirty_test_info(id)
    -- 1、更新光模块列表信息：位置校正 和 不在位光模块位置信息清除
    dirty.get_instance():update_test_object(id, self.SerialNumber, self.Presence, self.LogicalId, self.Type)
    if self.Presence == 0 then
        self.ContaminationDetectionSupported = false
        return
    end
    -- 2、获取第一步更新后dirty_test对象
    local dirty_test_object = dirty.get_instance():get_test_object_by_object_id(self.SerialNumber, false)
    -- 说明光模块索引在第一步函数就已经清除了
    if dirty_test_object == nil then
        self.ContaminationDetectionSupported = false
        return
    end
    log:debug('npu id %s logical id %s, dirty test object id %s, support dirty test %s, presence:%s',
        dirty_test_object.npu_id, dirty_test_object.logical_id, dirty_test_object.test_object_id,
        dirty_test_object.support_dirty_test, dirty_test_object.presence)
    -- 光模块在位且dirty Test属性未初始化，需要查询
    if dirty_test_object.support_dirty_test == nil then
        dirty_test_object:update_dirty_test_supported()
        if not dirty_test_object.support_dirty_test then
            self.ContaminationDetectionSupported = false
            return
        end
    end
    self.ContaminationDetectionSupported = dirty_test_object.support_dirty_test
end

-- 更新PRBS测试相关信息
function c_optical_module:update_prbs_test_info(id)
    -- 1、更新光模块列表信息：位置校正 和 不在位光模块位置信息清除
    prbs.get_instance():update_test_object(id, self.SerialNumber, self.Presence, self.LogicalId)
    if self.Presence == 0 then
        self.PRBSTestSupported = false
        return
    end
    -- 2、获取第一步更新后prbs_test对象
    local prbs_test_object = prbs.get_instance():get_test_object_by_object_id(self.SerialNumber, false)
    -- 说明光模块索引在第一步函数就已经清除了
    if prbs_test_object == nil then
        self.PRBSTestSupported = false
        return
    end
    log:debug('npu id %s logical id %s, prbs test object id %s, support prbs test %s, presence:%s',
        prbs_test_object.npu_id, prbs_test_object.logical_id, prbs_test_object.test_object_id,
        prbs_test_object.support_prbs_test, prbs_test_object.presence)
    -- 光模块在位且PRBS Test属性未初始化，需要查询
    if prbs_test_object.support_prbs_test == nil then
        local prbs_test_result = prbs_test_object:get_prbs_test_supported()
        if prbs_test_result == nil or prbs_test_result == false then
            self.PRBSTestSupported = false
            return
        end
    end
    self.PRBSTestSupported = prbs_test_object.support_prbs_test
end

function c_optical_module:update_smbios_status(value)
    self.smbios_status = value
end

c_optical_module.mock_get_max_and_min_optical_info = get_max_and_min_optical_info

return c_optical_module
