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

local log = require 'mc.logging'
local c_object = require 'mc.orm.object'
local c_object_manage = require 'mc.orm.object_manage'
local c_factory = require 'mc.orm.factory'
local c_fan_type = require 'fan_type_object'
local mdb = require 'mc.mdb'
local thermal_mgmt_utils = require 'thermal_mgmt_utils'
local skynet = require 'skynet'
local client = require 'thermal_mgmt.client'
local abnormal_fan =  require "basic_cooling.cooling_abnormal_device.abnormal_fan"
local signal = require 'mc.signal'
local enums = require 'basic_cooling.define.cooling_enums'
local air_config =  require "basic_cooling.cooling_config.air_config"

local PWM_MAX<const> = 1000
local FAN_STATUS_ERROR_COUNT<const> = 5
local FAN_STATUS_NORMAL<const> = 0
local FAN_STATUS_MAJOR<const> = 2
local PWM_5_PERCENT<const> = 30
local STATUS_CHECK_THRESHOLD<const> = 5
local IDENTIFY_CNT_MAX<const> = 5
local STOP_ROTATION_ERROR_COUNT<const> = 5

local identify_status<const> = {
    UNIDENTIFIED = 0,
    IDENTIFYING = 1,
    IDENTIFIED = 2,
    IDENTIFY_FAILED = 3
}

local fan_status<const> = {
    STATUS_NORMAL = 0,
    STATUS_ERROR = 1,
    STATUS_TYPE_MISMATCH = 2,
    STATUS_TYPE_MISMATCH_PCIE = 4,
    STATUS_MULTI_TYPE = 8, -- 混插告警
    STATUS_ACCESS_FAILED = 16, -- 智能风扇MCU访问失败
    STATUS_STOP_ROTATION = 32 -- 停转告警
}

local model_source<const> = {NONE = 0, PERSISTENT = 1, DEFAULT_MODEL = 2, IDENTIFY = 3}

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

local fan_obj_manager = c_object('Fan')

function fan_obj_manager:ctor()
    self.db = c_factory.get_instance().db
    self.model_pool = {}
    self.status_record = { front_error_count = 0, rear_error_count = 0, front_stop_rotation_count = 0,
        rear_stop_rotation_count = 0 }
    self.status_error = { front_error = 0, rear_error = 0 }
    self.error_speed = { front_error_speed = {}, rear_error_speed = {} }       -- 转子告警转速表
    self.error_hardpwm = { front_error_hardpwm = {}, rear_error_hardpwm = {} } -- 转子告警占空比表
    self.change_times = 0
    self.last_pwm = 0
    self.default_model = false
    self.last_present = -1
    self.model_source = model_source.NONE
    self.fan_model = false
    self.listener = {}
    self.mcu_upgrading = false
    self.start_flag = false
    self.persist_fan_id = -1
    self.fan_board = false
    self.standbyfan = false
    self.dual_connector = false
    self.single_connector = false
    self.fan_status_changed = signal.new()
    self.abnormal_fan = abnormal_fan.get_instance()
    self.air_config = air_config.get_instance()
end

function fan_obj_manager:set_status_error(fan_location, error_record)
    if self.status_error[fan_location .. '_error'] == error_record then
        -- 防止频繁重复设置
        return
    end
    self.status_error[fan_location .. '_error'] = error_record
    self[snake_to_camel(fan_location .. '_status')] =
        error_record > 0 and FAN_STATUS_MAJOR or FAN_STATUS_NORMAL
    self.fan_status_changed:emit(snake_to_camel(fan_location .. '_status'), self:get_fan_status())
end

function fan_obj_manager:get_status_error(fan_location)
    return self.status_error[fan_location .. '_error']
end

function fan_obj_manager:is_independence()
    if self.RearPresence ~= 255 and self.FrontPresence == self.RearPresence then
        return false
    end
    return true
end

function fan_obj_manager:get_present()
    return self.RearPresence
end

function fan_obj_manager:set_pwm(pwm_percent)
    self.Fan.methods.SetFanHardwarePWM(pwm_percent)
end

function fan_obj_manager:set_expected_pwm(pwm)
    self.ExpectedPWM = math.floor(pwm)
end

function fan_obj_manager:set_fan_expected_pwm(pwm_value)
    self.Fan.methods.SetFanExpectedPWM(pwm_value)
end

function fan_obj_manager:set_fan_hardware_pwm(pwm_percent)
    return self.Fan.methods.SetFanPWM(pwm_percent)
end

function fan_obj_manager:configurable()
    return self.Fan.methods.Configurable()
end

function fan_obj_manager:get_manage_fans()
    if self.Fan.methods.GetManageFans then
        return self.Fan.methods.GetManageFans()
    end
    return nil
end

function fan_obj_manager:get_max_supported_pwm()
    if self.MaxSupportedPWM == nil or self.MaxSupportedPWM == 0 then
        return PWM_MAX
    end
    return self.MaxSupportedPWM
end

function fan_obj_manager:get_error_count(fan_location)
    return self.status_record[fan_location .. '_error_count']
end

function fan_obj_manager:set_error_count(fan_location, error_count)
    self.status_record[fan_location .. '_error_count'] = error_count
end


-- 转子告警时的占空比和转速表插入
function fan_obj_manager:insert_error_hardpwm_speed(fan_location, hardpwm, speed)
    table.insert(self.error_hardpwm[fan_location .. '_error_hardpwm'], hardpwm)
    table.insert(self.error_speed[fan_location .. '_error_speed'], speed)
    -- 避免告警持续产生不消除场景内存增长
    if #self.error_hardpwm[fan_location .. '_error_hardpwm'] > FAN_STATUS_ERROR_COUNT + 1 then
        table.remove(self.error_hardpwm[fan_location .. '_error_hardpwm'], 1)
        table.remove(self.error_speed[fan_location .. '_error_speed'], 1)
    end
end

-- 转子告警占空比转速表清空
function fan_obj_manager:clear_error_hardpwm_speed(fan_location)
    if next(self.error_hardpwm[fan_location .. '_error_hardpwm']) then
        self.error_hardpwm[fan_location .. '_error_hardpwm'] = {}
    end
    if next(self.error_speed[fan_location .. '_error_speed']) then
        self.error_speed[fan_location .. '_error_speed'] = {}
    end
end

-- 转子转速告警日志打印
function fan_obj_manager:print_error_hardpwm_speed_log(fan_id, fan_location)
    local hardpwm_table = self.error_hardpwm[fan_location .. '_error_hardpwm']
    local speed_table = self.error_speed[fan_location .. '_error_speed']
    if type(hardpwm_table) == 'table' then
        log:error('fan%s %s error_hardpwm = [%s]', fan_id, fan_location, table.concat(hardpwm_table, ','))
    end
    if type(speed_table) == 'table' then
        log:error('fan%s %s error_speed = [%s]', fan_id, fan_location, table.concat(speed_table, ','))
    end
end

function fan_obj_manager:change_times_add_once()
    self.change_times = self.change_times + 1
end

function fan_obj_manager:is_identified()
    return self.IdentifyStatus == identify_status.IDENTIFIED
end

local function calculate_fan_power(speed_range, power_range, pwm_percent)
    local len_speed_range = #speed_range
    local len_power_range = #power_range
    -- SpeedRange和PowerRange长度必须相等，且长度至少大于等于2
    -- 原因：
    -- 例如：SpeedRange: 0,    20,  30,  40,  50,  60,  70,    80,   90,  100
    --      PowerRange: 192, 192, 276, 396, 564, 828, 1176, 1632, 2160, 2700
    --      若数组长度为1，则无法计算对应功耗值，长度大于等于2时通过斜率计算
    if len_speed_range ~= len_power_range or len_speed_range < 2 then
        log:info('Fan speed range or power range is incorrectly configured!')
        return 0
    end
    if pwm_percent < speed_range[1] or pwm_percent > speed_range[len_speed_range] then
        log:info('Parse fan speed range array failed!')
        return 0
    end
    local index = 2
    for i = 2, len_speed_range do
        if pwm_percent <= speed_range[i] then
            index = i
            break
        end
    end
    local slope = (power_range[index] - power_range[index - 1]) / (speed_range[index] - speed_range[index - 1])
    local intercept = power_range[index] - slope * speed_range[index]
    return slope * pwm_percent + intercept
end

function fan_obj_manager:get_fan_power()
    if self:get_present() == 0 then
        log:info('Fan%d is not present!', self.FanId)
        return 0
    end
    local fan_type_obj = c_fan_type.collection:find({Name = self.Model})
    if not fan_type_obj then
        log:info('Get fan%d type failed!', self.FanId)
        return 0
    end
    if self.PowerGood == 0 and not self.standbyfan then
        return 0
    end
    local speed_range = fan_type_obj:get_speed_range()
    local power_range = fan_type_obj:get_power_range()
    if not speed_range or not next(speed_range) then
        log:info('Get speed_range failed!')
        return 0
    end
    if not power_range or not next(power_range) then
        log:info('Get power_range failed!')
        return 0
    end
    local pwm_percent = 100 * self.HardwarePWM / self.MaxSupportedPWM
    local power = calculate_fan_power(speed_range, power_range, pwm_percent)
    return math.floor(power + 0.5)
end

function fan_obj_manager:update_fan_info(fan_type)
    if fan_type then
        self.Model = fan_type:get_fan_type_name()
        self.PartNumber = fan_type:get_type_part_num()
        self.BOM = fan_type:get_type_bom_id()
        self.IsTwins = fan_type:get_is_twins()
        self.FrontMaxSpeed = fan_type:get_front_max_speed()
        self.RearMaxSpeed = fan_type:get_rear_max_speed()
    end
end

function fan_obj_manager:set_default_fan_model(model)
    if not model or model == self.default_model then
        return
    end
    self.default_model = model
    if self.model_source == model_source.DEFAULT_MODEL then
        -- 风扇加载顺序会影响default_model
        local fan_type = c_fan_type.collection:find(function (obj) 
            return obj.Name == model and obj:get_position() == self:get_position()
        end)
        if fan_type then
            self:update_fan_info(fan_type)
            -- 类型刷新 需要刷新持久化数据
            self:persist_fan_info()
        end
    elseif self:is_identified() then
        if self.Model ~= model then
            self:set_mismatch()
        else
            self:clear_mismatch()
        end
    end
end

-- set_mismatch 设置混插告警
function fan_obj_manager:set_mismatch()
    if self:is_identified() then
        self:set_status_error('front', self:get_status_error('front') | fan_status.STATUS_MULTI_TYPE)
        self:set_status_error('rear', self:get_status_error('rear') | fan_status.STATUS_MULTI_TYPE)
    end
end

-- clear_mismatch 清除混插告警
function fan_obj_manager:clear_mismatch()
    if self:is_identified() then
        self:set_status_error('front',
            self:get_status_error('front') & (~fan_status.STATUS_MULTI_TYPE))
        self:set_status_error('rear',
            self:get_status_error('rear') & (~fan_status.STATUS_MULTI_TYPE))
    end
end

function fan_obj_manager:recover_info_from_db(db_obj)
    self.Type = db_obj.Type
    self.Model = db_obj.Model
    self.BOM = db_obj.BOM
    self.PartNumber = db_obj.PartNumber
    self.FrontMaxSpeed = db_obj.FrontMaxSpeed
    self.RearMaxSpeed = db_obj.RearMaxSpeed
    self.IsTwins = db_obj.IsTwins
    self.model_source = model_source.PERSISTENT
end

function fan_obj_manager:recover_fan_info()
    local db = self.db
    local tbl = db.FanInfo
    local record = db:select(tbl)
        :where(tbl.SystemId:eq(self.SystemId), tbl.FanId:eq(self.persist_fan_id), tbl.FanPosition:eq(self:get_position()))
        :first()
    if record ~= nil then
        log:notice('found persistent info of %s, recovered', self.ObjectName)
        self:recover_info_from_db(record)
    end
end

function fan_obj_manager:fan_model_saved()
    local model = c_fan_type.collection:find({Name = self.Model})
    if model then
        if self.default_model and self.default_model ~= self.Model then
            self:set_mismatch()
        end
        return model
    end
    return nil
end

-- 去掉最大值和最小值，取平均值
local function get_average_val(array, len)
    local max = 0
    local min = 0xffffff
    local sum = 0
    if len <= 2 then
        return 0
    end
    for i = 1, len do
        if array[i] > max then
            max = array[i]
        end
        if array[i] < min then
            min = array[i]
        end
        sum = sum + array[i]
    end
    return (sum - min - max) / (len - 2)
end

-- 根据调速区间来筛选掉一部分选项
function fan_obj_manager:filter_type_by_speed()
    local record = {}
    local record_pwm = {}
    local READ_TIMES = 5
    self:sleep_ms(15000) -- 等待转速稳定
    local time = 1
    -- 取稳定后的转速平均值
    while time <= READ_TIMES do
        record[time] = self.RearSpeed
        record_pwm[time] = self.HardwarePWM
        time = time + 1
        self:sleep_ms(3100)
    end
    local average_speed = get_average_val(record, READ_TIMES)
    local i = 1
    while i <= #self.model_pool do
        if average_speed < self.model_pool[i]:get_identify_low_speed() or average_speed >
            self.model_pool[i]:get_identify_high_speed() then
            table.remove(self.model_pool, i)
        else
            i = i + 1
        end
    end
    return record, record_pwm
end

-- 根据单双转子来筛选掉一部分选项
function fan_obj_manager:filter_type_by_twins()
    local is_twins = (self.FrontSpeed ~= 0)
    local i = 1
    while i <= #self.model_pool do
        if self.model_pool[i]:get_is_twins() ~= is_twins then
            table.remove(self.model_pool, i)
        else
            i = i + 1
        end
    end
end

-- 根据筛选完的结果，刷新风扇对象内部属性
function fan_obj_manager:refresh_fans_model()
    local model
    if #self.model_pool == 1 then
        model = self.model_pool[1]
        model.fan_num = model.fan_num + 1
        self.model_source = model_source.IDENTIFY
    else
        if self.default_model then
            model = c_fan_type.collection:find({ Name = self.default_model })
            self.model_source = model_source.DEFAULT_MODEL
            log:warn('identify fan%d model with default_model', self.FanId)
        end
    end
    self:update_fan_info(model)
end

-- 判断当前电源状态是否是上电
local function check_power_status(system_id)
    local fru_path = '/bmc/kepler/Systems/' .. system_id .. '/FruCtrl'
    local power_state = nil
    local software_type = thermal_mgmt_utils:get_chassis_type()
    if software_type == enums.chassis_type.RACK_MGMT then
        log:notice('chassis_type %s power_state ON',software_type)
        power_state = 'ON'
        return power_state == 'ON'
    end
    client:ForeachFruCtrlObjects(function (obj)
        if obj and string.find(obj.path, fru_path) then
            power_state = obj.PowerState
            return
        end
    end)
    return power_state == 'ON'
end

function fan_obj_manager:fan_identify(identify_cnt)
    -- 1.把所有可能的型号加入备选池
    self.model_pool = c_fan_type.collection:fetch_by_position(self:get_position())
    -- 防止转速设置失败后task抛出异常退出
    local ok, err_msg = pcall(self.set_pwm, self, self.IdentifySpeedLevel)
    if not ok then
        log:error('Set the pwm of %s failed, err_msg:%s', self.ObjectName, err_msg)
        return false
    end
    -- 2.通过特定百分比下的风扇转速所处的区间进行判断，去掉不符合项
    local record_speed, record_pwm = self:filter_type_by_speed()
    -- 3.通过单双转子特征进行判断，去掉不符合项
    self:filter_type_by_twins()
    -- 非standby供电风扇 判断是否发生过掉电
    if self.IdentifyStatus ~= identify_status.IDENTIFYING and not self.standbyfan then
        log:error('break fantype identifing because of power-off')
        return false
    end
    -- 4.判断剩余的可能项是否唯一，是则匹配成功，否则重试若干次
    self:refresh_fans_model()
    local saved_model = self:fan_model_saved()
    if saved_model then
        log:notice('identify fan%d model successfully. identify_cnt = %d. record_pwm = [%s], record_speed = [%s]',
            self.FanId, identify_cnt, table.concat(record_pwm, ','), table.concat(record_speed, ','))
        self.IdentifyStatus = identify_status.IDENTIFIED
        return true
    end
    return false
end

function fan_obj_manager:identify_fans_type()
    local saved_model = self:fan_model_saved()
    if self.Model ~= nil and self.Model ~= '' and saved_model then
        log:notice('The %s model already identified', self.ObjectName)
        self.IdentifyStatus = identify_status.IDENTIFIED
        saved_model.fan_num = saved_model.fan_num + 1
        return
    end
    local identify_cnt, identify_result, record_speed, record_pwm = 0, false, {}, {}
    -- 风扇识别

    while (self:get_present() == 1 or self:get_present() == 255) and not self:is_identified() do
        while not check_power_status(self.SystemId) and not self.standbyfan do
            -- 下电状态，且风扇是非standby供电
            self:sleep_ms(1000)
        end
        self.IdentifyStatus = identify_status.IDENTIFYING
        identify_result = self:fan_identify(identify_cnt)
        identify_cnt = identify_cnt + 1
        -- 识别成功则退出任务
        if identify_result then
            break
        end
        -- 识别达到5此还没成功则告警
        if identify_cnt == IDENTIFY_CNT_MAX then
            self:set_status_error('front',
                self:get_status_error('front') | fan_status.STATUS_TYPE_MISMATCH)
            self:set_status_error('rear',
                self:get_status_error('rear') | fan_status.STATUS_TYPE_MISMATCH)
            log:error('identify fan%d model failed. identify_cnt = %d. record_pwm = [%s], record_speed = [%s]',
                self.FanId, identify_cnt, table.concat(record_pwm, ','), table.concat(record_speed, ','))
        end
        -- 识别失败则等待10s进行下一次识别
        self:sleep_ms(10000) -- 等待1000ms
    end
end

function fan_obj_manager:refresh_single_fan_record(db_obj)
    db_obj.FanId = self.persist_fan_id
    db_obj.SystemId = self.SystemId
    db_obj.FanPosition = self:get_position()
    db_obj.Type = self.Type
    db_obj.Model = self.Model
    db_obj.BOM = self.BOM
    db_obj.PartNumber = self.PartNumber
    db_obj.FrontMaxSpeed = self.FrontMaxSpeed
    db_obj.RearMaxSpeed = self.RearMaxSpeed
    db_obj.IsTwins = self.IsTwins
end

function fan_obj_manager:persist_fan_info()
    if self.db == nil or self.db.FanInfo == nil then
        return
    end
    local tbl = self.db.FanInfo
    local fan_id = self.persist_fan_id
    -- 根据最新风扇信息更新数据库
    local system_id = self.SystemId
    local record = self.db:select(tbl):
        where(tbl.SystemId:eq(system_id), tbl.FanId:eq(fan_id), tbl.FanPosition:eq(self:get_position()))
        :first()
    if record ~= nil then
        log:notice('refresh persistent info of %s', self.ObjectName)
        self:refresh_single_fan_record(record)
        record:save()
    else
        log:notice('add persistent info of %s', self.ObjectName)
        local new_record = tbl({
            FanId = fan_id,
            SystemId = system_id,
            FanPosition = self:get_position(),
            Type = self.Type,
            Model = self.Model,
            BOM = self.BOM,
            PartNumber = self.PartNumber,
            FrontMaxSpeed = self.FrontMaxSpeed,
            RearMaxSpeed = self.RearMaxSpeed,
            IsTwins = self.IsTwins
        })
        new_record:save()
    end
end

function fan_obj_manager:clear_fan_info()
    self:clear_fans_status_change_count()
    self.FrontRPM = 0
    self.RearRPM = 0
    self.FrontStatus = 0
    self.RearStatus = 0
    local model = c_fan_type.collection:find({Name = self.Model})
    if model and self:is_identified() and (self.model_source == model_source.PERSISTENT or
        self.model_source == model_source.IDENTIFY) then
        model.fan_num = model.fan_num - 1
    end
    self.Model = ''
    self.PartNumber = ''
    self.SpeedStable = false
    self.default_model = false
    self.last_present = self:get_present()
    self.IdentifyStatus = identify_status.UNIDENTIFIED
    self.status_error = {front_error = 0, rear_error = 0}
    self.model_source = model_source.NONE
end

function fan_obj_manager:clear_fans_status_change_count()
    self:set_error_count('front', 0)
    self:set_error_count('rear', 0)
    self.change_times = 0
end

function fan_obj_manager:clear_fan_status()
    local front_status = self:get_status_error('front')
    if (front_status & fan_status.STATUS_ERROR) > 0 then
        front_status = front_status & (~fan_status.STATUS_ERROR)
        self.status_error.front_error = front_status
    end

    local rear_status = self:get_status_error('rear')
    if (rear_status & fan_status.STATUS_ERROR) > 0 then
        rear_status = rear_status & (~fan_status.STATUS_ERROR)
        self.status_error.rear_error = rear_status
    end
end

function fan_obj_manager:update_fan_status(expected_pwm, target, fan_location)
    local fan_id = self.FanId
    local max_speed = self[snake_to_camel(fan_location .. '_max_speed')]
    local speed_tolerance = self.air_config:get_deviation_threshold_percent() * 0.01 --转为百分比值
    local low_speed, high_speed = math.floor((target - speed_tolerance) * max_speed),
        math.floor((target + speed_tolerance) * max_speed)
    local real_speed = self[snake_to_camel(fan_location .. '_speed')]
    local error_count = self:get_error_count(fan_location)
    local old_status = self:get_status_error(fan_location)
    local new_status = old_status & (~fan_status.STATUS_TYPE_MISMATCH)

    if real_speed < low_speed or real_speed > high_speed then -- 在非预期浮动区间
        error_count = error_count + 1
        -- 每次识别到非预期浮动区间的转速，插入到对应转子的错误转速表中
        self:insert_error_hardpwm_speed(fan_location, self.HardwarePWM, real_speed)
        if error_count <= FAN_STATUS_ERROR_COUNT then
            log:notice(
                '[%s]update_fan_status:fan%s is not steady, expect PWM %s, %s_rpm current %s',
                self.ObjectName, self.FanId, expected_pwm, fan_location, real_speed)
        end
        if error_count > FAN_STATUS_ERROR_COUNT then
            new_status = new_status | fan_status.STATUS_ERROR
        end
    else
        new_status = new_status & (~fan_status.STATUS_ERROR)
        error_count = 0
        self:clear_error_hardpwm_speed(fan_location)
    end
    self:set_error_count(fan_location, error_count)
    self:set_status_error(fan_location, new_status) -- 更新风扇对象的状态内存数据
    if new_status == 0 or old_status == new_status then
        return
    end
    log:error('scan_fan_status: Scan fan%u %s speed error, real %d%%(%d), expect %d%% +- %d%%; expected_pwm %d',
                fan_id, fan_location,  math.floor(real_speed / max_speed * 100), real_speed, math.floor(target * 100),
                math.floor(speed_tolerance * 100), math.floor(self.HardwarePWM))
    self:print_error_hardpwm_speed_log(fan_id, fan_location)
end

function fan_obj_manager:scan_fan_status()
    local expected_pwm = self.ExpectedPWM
    local target = expected_pwm / self:get_max_supported_pwm()
    self:update_fan_status(expected_pwm, target, 'rear') -- 后风扇转子健康状态更新
    if not self.IsTwins and not self:is_independence() then
        local front_status_error = self:get_status_error('front')
        if front_status_error ~= self:get_status_error('rear') and front_status_error ~=
            fan_status.STATUS_NORMAL then
            log:error('[thermal_mgmt]scan_fan_status:update front fan status from rear fan.')
        end
        self.FrontStatus = self.RearStatus
    elseif self.IsTwins then
        self:update_fan_status(expected_pwm, target, 'front') -- 前风扇转子健康状态更新
    end
end

function fan_obj_manager:check_speed_change_for_status()
    -- 连续n次PWM变化小于5%的情况下，才进行故障判断
    local current_pwm = self.ExpectedPWM
    if math.abs(current_pwm - self.last_pwm) < PWM_5_PERCENT * self:get_max_supported_pwm() / 255 then
        self:change_times_add_once()
        if self.change_times < STATUS_CHECK_THRESHOLD then
            return
        end
        self:scan_fan_status()
    else
        self.change_times = 0
    end
    self.last_pwm = current_pwm
end

-- 刷新风扇转速是否稳定
function fan_obj_manager:refresh_speed_is_steady()
    local target = self.ExpectedPWM / self:get_max_supported_pwm()
    local max_speed = self.RearMaxSpeed
    local speed_tolerance = self.air_config:get_deviation_threshold_percent() * 0.01 --转为百分比值
    local low_speed, high_speed = math.floor((target - speed_tolerance) * max_speed),
        math.floor((target + speed_tolerance) * max_speed)
    local real_speed = self.RearSpeed
    if real_speed < low_speed or real_speed > high_speed then
        log:debug(
            '[%s]refresh_speed_is_steady:fan%d is not steady, expect PWM %d, r_rpm current %d',
            self.ObjectName, self.FanId, self.ExpectedPWM, real_speed)
        self.SpeedStable = false
        return
    end
    if self.IsTwins then
        max_speed = self.FrontMaxSpeed
        low_speed, high_speed = math.floor((target - speed_tolerance) * max_speed),
            math.floor((target + speed_tolerance) * max_speed)
        real_speed = self.FrontSpeed
        if real_speed < low_speed or real_speed > high_speed then
            log:debug('[%s]fan%d is not steady, expect PWM %d, f_rpm current %d', self.FanId,
                self.ObjectName, self.ExpectedPWM, real_speed)
            self.SpeedStable = false
            return
        end
    end
    self.SpeedStable = true
end

function fan_obj_manager:get_stop_rotation_count(fan_location)
    return self.status_record[fan_location .. '_stop_rotation_count']
end

function fan_obj_manager:set_stop_rotation_count(fan_location, stop_rotation_count)
    self.status_record[fan_location .. '_stop_rotation_count'] = stop_rotation_count
end

function fan_obj_manager:update_stop_rotation_status(fan_location)
    local real_speed = self[snake_to_camel(fan_location .. '_speed')]
    local stop_rotation_count = self:get_stop_rotation_count(fan_location)
    local is_stop_rotation = real_speed == 0
    if is_stop_rotation then
        stop_rotation_count = stop_rotation_count + 1
    else
        stop_rotation_count = 0
    end
    self:set_stop_rotation_count(fan_location, stop_rotation_count)
    local new_status = self:get_status_error(fan_location) & (~fan_status.STATUS_STOP_ROTATION)
    if is_stop_rotation then
        if stop_rotation_count > STOP_ROTATION_ERROR_COUNT then
            new_status = new_status | fan_status.STATUS_STOP_ROTATION
        else
            log:notice('[%s]update_stop_rotation_status:fan%s %s is stop rotation, stop rotation count: %s',
                self.ObjectName, self.FanId, fan_location, stop_rotation_count)
        end
    end
    if new_status == self:get_status_error(fan_location) then
        return
    end
    log:notice('update_stop_rotation_status: Scan fan%u %s stop rotation, check result: %s', self.FanId, fan_location,
        new_status)
    self:set_status_error(fan_location, new_status)
end

function fan_obj_manager:check_stop_rotation()

    self:update_stop_rotation_status('rear')
    if not self.IsTwins and not self:is_independence() then
        local front_status_error = self:get_status_error('front')
        if front_status_error ~= self:get_status_error('rear') and front_status_error ~=
            fan_status.STATUS_NORMAL then
            log:error('[thermal_mgmt]check_stop_rotation:update front fan status from rear fan.')
        end
        self.FrontStatus = self.RearStatus
    elseif self.IsTwins then
        self:update_stop_rotation_status('front') -- 前风扇转子健康状态更新
    end
end

function fan_obj_manager:clear_fans_stop_rotation_count()
    self:set_stop_rotation_count('rear', 0)
    self:set_stop_rotation_count('front', 0)
end

function fan_obj_manager:monitor_fan_status()
    if self.mcu_upgrading or (self.PowerGood == 0 and not self.standbyfan) then -- MCU升级或下电状态，且风扇是非standby供电
        self:clear_fans_status_change_count()
        self:clear_fans_stop_rotation_count()
        return
    end
    -- 风扇停转不进行转速偏差判断
    self:check_stop_rotation()
    if self:get_stop_rotation_count('rear') ~= 0 or self:get_stop_rotation_count('front') ~= 0 then
        self:clear_fan_status()
        self:clear_error_hardpwm_speed('rear')
        self:clear_error_hardpwm_speed('front')
        self:clear_fans_status_change_count()
        return
    end
    if self.ExpectedPWM == 0 then
        self:clear_fan_status()
        return
    end
    self:check_speed_change_for_status() -- 根基转速变化更新风扇健康状态
end

function fan_obj_manager:start()
    self.last_present = self:get_present()
    -- 1. 在ExpectedPWM为0时（为使风扇稳定），或者不在位的风扇重新加载后（不在位期间pwm可能发生变化），需要刷新一遍ExpectedPWM
    self.ExpectedPWM = self.HardwarePWM
    self:recover_fan_info()
    self:identify_fans_type()
    self:persist_fan_info()

    self:next_tick(function(task)
        while (self:get_present() == 1 or self:get_present() == 255) do
            self:sleep_ms(2000) -- 2秒扫描一次
            if task.is_exit then
                break
            end
            self:monitor_fan_status()
            if not self.SpeedStable then
                self:refresh_speed_is_steady() -- 刷新风扇SpeedStable属性值
            end
        end
    end)
end

function fan_obj_manager:update_pwm_percentage(hardware_pwm)
    if (self.PowerGood == 0 and not self.standbyfan) or not self.MaxSupportedPWM or
        self.MaxSupportedPWM == 0 then
        self.PWMPercentage = 0
        return
    end
    if hardware_pwm then
        self.PWMPercentage = math.floor((hardware_pwm / self.MaxSupportedPWM) * 100)
    else
        self.PWMPercentage = math.floor((self.HardwarePWM / self.MaxSupportedPWM) * 100)
    end
end

function fan_obj_manager:clear_persist_fan_info()
    if self.db == nil or self.db.FanInfo == nil then
        return
    end
    local tbl = self.db.FanInfo
    local fan_id = self.persist_fan_id
    local system_id = self.SystemId
    local record = self.db:select(tbl)
        :where(tbl.SystemId:eq(system_id), tbl.FanId:eq(fan_id), tbl.FanPosition:eq(self:get_position()))
        :first()
    if record ~= nil then
        log:notice('[%s]delete persistent info of fan%d', self.ObjectName, fan_id)
        record:delete()
    end
end

function fan_obj_manager:fructl_changed_handle(interface, dics)
    local fructrl_interface = 'bmc.kepler.Systems.FruCtrl'
    if interface ~= fructrl_interface or not dics['PowerState'] then
        return
    end
    -- 风扇识别过程中下电
    if dics['PowerState']:value() == 'OFF' and self.IdentifyStatus == identify_status.IDENTIFYING then
        self.IdentifyStatus = identify_status.IDENTIFY_FAILED
        log:error('detect power off when fan identifing')
    end

    log:debug('PowerState changed to %s', tostring(dics['PowerState']:value()))
end

function fan_obj_manager:fructl_listenning()
    self.listener[#self.listener + 1] = client:OnFruCtrlPropertiesChanged(function(prop_value, path, interface)
        self:fructl_changed_handle(interface, prop_value)
    end)
end

function fan_obj_manager:fan_board_changed_handle(interface, dics)
    local unit_interface = 'bmc.kepler.Systems.Board.Unit'
    if interface ~= unit_interface or not dics['CurrentUpgradeStatus'] then
        return
    end
    -- MCU/CPLD升级
    if dics['CurrentUpgradeStatus']:value() ~= 0 then
        self.mcu_upgrading = true
    else
        self.mcu_upgrading = false
        -- MCU升级过程中setPWM无法生效，升级完成后需要重新设置一遍
        self:next_tick(self.set_fan_hardware_pwm, self, self.ExpectedPWM / self.MaxSupportedPWM * 100)
    end

    log:notice('[%s]update fan%d mcu_upgrading = %s', self.ObjectName, self.FanId, tostring(self.mcu_upgrading))
end

local function get_connector_object(self)
    -- 对引用对象设置属性值不会生效，这里需要通过mdb重新获取资源树对象
    local ret
    if self.DualConnector and self.DualConnector.path and not self.dual_connector then
        ret = client:GetConnectorObjects()[self.DualConnector.path]
        if not ret then
            log:error('get object failed, error: %s', ret)
        else
            self.dual_connector = ret
        end
    end
    if self.SingleConnector and self.SingleConnector.path and not self.single_connector then
        ret = client:GetConnectorObjects()[self.SingleConnector.path]
        if not ret then
            log:error('get object failed, error: %s', ret)
        else
            self.single_connector = ret
        end
    end
end

function fan_obj_manager:load_connector()
    get_connector_object(self)
    if self.dual_connector then
        self.dual_connector.Presence = self.IsTwins and 1 or 0
        log:notice('[%s]set dual_connector present %s', self.ObjectName, self.dual_connector.Presence)
    end
    if self.single_connector then
        self.single_connector.Presence = self.IsTwins and 0 or 1
        log:notice('[%s]set single_connector present %s', self.ObjectName, self.single_connector.Presence)
    end
end

function fan_obj_manager:reload_connector()
    -- 传感器刷新需要先卸载再加载
    get_connector_object(self)
    if self.dual_connector then
        pcall(function()
            self.dual_connector.Presence = 0
        end)
    end
    if self.single_connector then
        pcall(function()
            self.single_connector.Presence = 0
        end)
    end
    -- 卸载5s后 重新加载
    self:timeout_ms(5000, self.load_connector, self)
end

local function fan_start(self)
    if self.start_flag then
        return
    end
    self.start_flag = true
    self:start()
end

function fan_obj_manager:get_fan_status()
    return {
        FanId = self.FanId,
        FrontPresence = self.FrontPresence,
        RearPresence = self.RearPresence,
        FrontStatus = self.status_error.front_error,
        RearStatus = self.status_error.rear_error
    }
end

function fan_obj_manager:create_fan_listenning()
    self:listen('RearPresence', function()
        local is_presence = self:get_present()
        log:notice('Fan(id: %s) rear presence changed from %s to %s', self.FanId, self.last_present, is_presence)
        if is_presence == 1 and (self.last_present == 0 or self.last_present == -1) then
            log:notice('[%s]detect fan%d plugin.', self.ObjectName, self.FanId)
            thermal_mgmt_utils.operation_log(nil, 'Fan %d insertion detected', self.FanId)
            self:next_tick(fan_start, self)
        elseif is_presence == 0 and self.last_present == 1 then
            log:notice('[%s]detect fan%d plugout.', self.ObjectName, self.FanId)
            thermal_mgmt_utils.operation_log(nil, 'Fan %d extraction detected', self.FanId)
            self:next_tick(function()
                self:stop_tasks()
                self.start_flag = false
                self:clear_fan_info()
                self:clear_persist_fan_info()
            end)
        end
        log:notice('Detect fan(%s) RearPresence:%s', self.FanId, self.RearPresence)
        self.fan_status_changed:emit("RearPresence", self:get_fan_status())
    end)
    self:listen('Slot', function()
        -- 槽位号发生变化，传感器需要同步变化
        self:reload_connector()
    end)
    self:listen('IsTwins', function()
        log:debug('[%s]IsTwins is Change to %s', self.ObjectName, self.IsTwins)
        self:load_connector()
    end)
    self:listen('FrontPresence', function()
        log:notice('Detect fan(%s) FrontPresence:%s', self.FanId, self.FrontPresence)
        self.fan_status_changed:emit("FrontPresence", self:get_fan_status())
    end)
    self:listen('RearStatus', function()
        log:notice('Detect fan(%s) RearStatus:%s', self.FanId, self.RearStatus)
    end)
    self:listen('FrontStatus', function()
        log:notice('Detect fan(%s) FrontStatus:%s', self.FanId, self.FrontStatus)
    end)
    self:listen('HardwarePWM', function()
        log:debug('Detect fan(%s) HardwarePWM:%s', self.FanId, self.HardwarePWM)
        self:update_pwm_percentage()
    end)
    self:fructl_listenning()
    if self.RearPresence == 0 then
        self.fan_status_changed:emit("RearPresence", self:get_fan_status())
    end
    if self.FrontPresence == 0 then
        self.fan_status_changed:emit("FrontPresence", self:get_fan_status())
    end
end

function fan_obj_manager:init()
    self.persist_fan_id = self.FanId
    self.IsTwins = true
    self.ExpectedPWM = self.HardwarePWM
    self:update_pwm_percentage()
    self:connect_signal(self.on_add_object_complete, function()
        log:notice('add fan_object complete %s, ExpectedPWM:%s, HardwarePWM:%s, FSpeed:%s,RSpeed:%s, persist_fan_id:%s',
            self.ObjectName, self.ExpectedPWM, self.HardwarePWM, self.FrontSpeed, self.RearSpeed, self.persist_fan_id)
        if self:get_present() == 1 or self:get_present() == 255 then
            self:next_tick(fan_start, self)
        end
        self.abnormal_fan:fan_status_signal_subscribed(self)
        self:create_fan_listenning()
        self:next_tick(self.load_connector, self)
    end)
    fan_obj_manager.super.init(self)
end

return fan_obj_manager
