-- 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.

-- Description: The procedure of sensor instance.
local skynet = require 'skynet'
local log = require 'mc.logging'
local signal = require 'mc.signal'
local common = require 'sensor.sensor_common'
local cc = require 'sensor.sensor_const'
local err = require 'sensor_error'
local utils = require 'sensor_utils'
local oper = require 'sensor_operation'
local bs = require 'mc.bitstring'
local cls_mgnt = require 'mc.class_mgnt'
local json = require 'cjson'
local shm_data_mgmt = require 'shm_data_management'
local custom_messages = require 'messages.custom'
local queue = require 'skynet.queue'
local sf = string.format
local rton = common.convert_raw_to_normal
local us = common.get_sensor_unit_str
local INTERFACE_NAME, PROPERTY_NAME = "bmc.kepler.Systems.ThresholdSensor", "Reading"


local sensor_instance = {}
sensor_instance.__index = sensor_instance

function sensor_instance.new(number, clz, sel_sigs, entity_sigs, sdr_sigs, sensor_sigs, global_sigs)
    local disp_intf = 'bmc.kepler.Systems.ThresholdSensorDisplay'
    if clz == 'DiscreteSensor' then
        disp_intf = 'bmc.kepler.Systems.DiscreteSensorDisplay'
    end
    return setmetatable({
        sensor_number = number,
        sensor_clz = clz,
        sel_sigs = sel_sigs,
        entity_sigs = entity_sigs,
        sdr_sigs = sdr_sigs,
        sensor_sigs = sensor_sigs,
        global_sigs = global_sigs,
        assert_state = clz == 'DiscreteSensor' and 0x8000 or 0,
        deassert_state = 0,
        is_mock_mode = false,
        mock_reading = 0,
        mock_ori_reading = 0,
        mock_sig = signal.new(),
        disp_intf = disp_intf,
        last_status = cc.SCAN_NORMAL,
        last_health = utils.HEALTH_NORMAL,
        cur_health = utils.HEALTH_NORMAL,
        minor_count = 0,
        major_count = 0,
        critical_count = 0,
        cur_reading = 0,
        cur_reading_status = cc.SCAN_NORMAL,
        discrete_events = {},
        log_filters = {},
        event_handlers = {},
        exec_queue = queue()
    }, sensor_instance)
end

function sensor_instance:add_dynamic_data_to_shm()
    local data = {
        sdr_type = self.sensor_clz == 'ThresholdSensor' and 1 or 2,
        reading = self.mdb_obj.Reading,
        scan_status = self.sensor_status,
        assert_status = self.disp_obj.AssertStatus
    }
    shm_data_mgmt.add_dynamic_data(self.sensor_id, self.sensor_number, data)
end

local threshold_to_mask = {
    ['LowerNonrecoverable'] = 'readable_lower_nonrecoverable',
    ['LowerCritical'] = 'readable_lower_critical',
    ['LowerNoncritical'] = 'readable_lower_noncritical',
    ['UpperNoncritical'] = 'readable_upper_noncritical',
    ['UpperCritical'] = 'readable_upper_critical',
    ['UpperNonrecoverable'] = 'readable_upper_nonrecoverable'
}

local threshold_flag_value_map = {
    UpperNonrecoverable = { flag = 'UNRFlag', value = 'UNRValue' },
    UpperCritical = { flag = 'UCRFlag', value = 'UCRValue' },
    UpperNoncritical = { flag = 'UNCFlag', value = 'UNCValue' },
    LowerNonrecoverable = { flag = 'LNRFlag', value = 'LNRValue' },
    LowerCritical = { flag = 'LCRFlag', value = 'LCRValue' },
    LowerNoncritical = { flag = 'LNCFlag', value = 'LNCValue' }
}

local function discrete_ori_value_update(self, id, prop)
    local event = self.discrete_events[id]
    local event_dir
    local data1

    if prop == 'EventDir' and event.ListenType == cc.SINGLE_MODE then
        event_dir = event.EventDir ~= 0 and utils.EVENT_DIR_ASSERT or utils.EVENT_DIR_DEASSERT
        data1 = event.EventData1
    elseif prop == 'Property' and event.ListenType == cc.COMBO_MODE then
        event_dir = (event.Property >> 24) ~= 0 and utils.EVENT_DIR_ASSERT or utils.EVENT_DIR_DEASSERT
        data1 = event.Property & 0xFF
    else
        return
    end

    -- 若为数字离散，考虑互斥，需要先将 mock_ori_reading 清为 0x8000
    if self.mdb_obj.DiscreteType == cc.DISCRETE_DIGITAL then
        self.mock_ori_reading = 0x8000
    end

    if event.Conversion & 0x0F == 1 then
        -- Conversion 属性的低4bit是翻转位，翻转 SEL 的 dir
        event_dir = event_dir == utils.EVENT_DIR_ASSERT and utils.EVENT_DIR_DEASSERT or utils.EVENT_DIR_ASSERT
    end

    local cur_state = (1 << (data1 & 0x0F)) & 0xFFFF
    if event_dir == utils.EVENT_DIR_ASSERT then
        self.mock_ori_reading = self.mock_ori_reading | cur_state
    else
        self.mock_ori_reading = self.mock_ori_reading & ((~cur_state) & 0xFFFF)
    end
end

function sensor_instance:add_discrete_event(id, mdb_obj)
    if mdb_obj.ListenType ~= cc.SINGLE_MODE and mdb_obj.ListenType ~= cc.COMBO_MODE then
        log:error('discrete event [%s] listen mode [%d] is invalid.', id, mdb_obj.ListenType)
        return
    end
    self.discrete_events[id] = mdb_obj
    local invalid_mode = mdb_obj.InvalidReadingIgnore
    local invalid_value = mdb_obj.InvalidReading

    -- 开始监听属性的变化以及处理SEL
    self.discrete_events[id].property_changed:on(function(prop, value, sender)
        if self.is_mock_mode then
            -- 模拟过程中忽略处理，只更新原始状态值
            discrete_ori_value_update(self, id, prop)
            log:info('sensor [%s] is in mock mode and temporarily ignored.', self.sensor_id)
            return
        end
        if not common.get_scan_enabled(self.sensor_status) then
            log:info('sensor [%s] has been disabled and ignored', self.sensor_id)
            return
        end
        self:discrete_event_update(id, prop, value, invalid_mode, invalid_value)
    end)

    -- 对初始值先进行一次SEL处理
    local p = mdb_obj.ListenType == cc.SINGLE_MODE and 'EventDir' or 'Property'
    local v = mdb_obj.ListenType == cc.SINGLE_MODE and mdb_obj.EventDir or mdb_obj.Property
    if common.get_scan_enabled(self.sensor_status) then
        self:discrete_event_update(id, p, v, invalid_mode, invalid_value)
        self:bmc_boot_deassert(id, mdb_obj)
    end
end

function sensor_instance:bmc_boot_deassert(id, mdb_obj)
    local prefix = 'DiscreteEvent_BMCBoot'
    if string.sub(id, 1, #prefix) == prefix and self.bmc_boot_sensor ~= nil and mdb_obj.EventDir == 1 then
        -- BMCBoot需要先deassert
        mdb_obj.EventDir = 0
        skynet.sleep(200)
        mdb_obj.EventDir = 1
        skynet.sleep(200)
    end
end

function sensor_instance:remove_discrete_event(id)
    self.discrete_events[id] = nil
end

function sensor_instance:discrete_event_update(id, prop, value, invalid_mode, invalid_value)
    local event = self.discrete_events[id]
    -- 第一步，拼接对应的 SEL 信息
    local sel = {}
    sel.SensorId = self.sensor_id
    sel.EventMsgVersion = utils.EVENT_MSG_VERSION
    sel.SensorType = self.mdb_obj.SensorType
    sel.SensorNumber = self.mdb_obj.SensorNumber
    sel.EventType = self.mdb_obj.ReadingType & 0x7F
    local dir_value
    if event.ListenType == cc.SINGLE_MODE then
        if prop ~= 'EventDir' then
            return
        end
        log:info('discrete event [%s] dir has been changed to 0x%02X', id, value)
        if invalid_mode == 1 and invalid_value == value then
            return
        end
        dir_value = event.EventDir
        sel.EventDir = dir_value ~= 0 and utils.EVENT_DIR_ASSERT or utils.EVENT_DIR_DEASSERT
        sel.EventData1 = event.EventData1
        sel.EventData2 = event.EventData2
        sel.EventData3 = event.EventData3
    else
        if prop ~= 'Property' then
            return
        end
        log:info('discrete event [%s] property has been changed to 0x%08X', id, value)
        dir_value = event.Property >> 24
        if invalid_mode == 1 and invalid_value == dir_value then
            return
        end
        sel.EventDir = dir_value ~= 0 and utils.EVENT_DIR_ASSERT or utils.EVENT_DIR_DEASSERT
        sel.EventData1 = (event.Property & 0xFF) | (event.Conversion & 0xF0)
        sel.EventData2 = (event.Property >> 8) & 0xFF
        sel.EventData3 = (event.Property >> 16) & 0xFF
    end
    if event.Conversion & 0x0F == 1 then
        -- Conversion 属性的低4bit是翻转位，翻转 SEL 的 dir
        sel.EventDir = sel.EventDir == utils.EVENT_DIR_ASSERT and utils.EVENT_DIR_DEASSERT or utils.EVENT_DIR_ASSERT
    end

    if event.Severity and (event.Severity >= 0 and event.Severity <= 3) then
        sel.Level = event.Severity
    end

    -- 第二步，开始处理 SEL
    self:add_sensor_sel('discrete event', id, sel, dir_value)
end

function sensor_instance:add_sensor_sel(from, id, sel, dir_value)
    if sel.EventDir == utils.EVENT_DIR_ASSERT then
        log:info('%s [%s] will assert a SEL', from, id)
        self:assert_discrete_event(sel, dir_value)
    else
        log:info('%s [%s] will deassert a SEL', from, id)
        self:deassert_discrete_event(sel, dir_value)
    end
end

function sensor_instance:deassert_discrete_event(sel, dir_value)
    log:info('deassert SEL [0x%04X, 0x%04X, 0x%02X, 0x%02X]',
        self.assert_state, self.deassert_state, sel.EventData1, sel.SensorNumber)
    local asserted = self.assert_state & (1 << (sel.EventData1 & 0x0F))
    local deasserted = self.deassert_state & (1 << (sel.EventData1 & 0x0F))
    if asserted ~= 0 and deasserted == 0 then
        if self.mdb_obj.DeassertMask & (1 << (sel.EventData1 & 0x0F)) ~= 0 then
            -- 允许上报deassert事件，则上报事件
            log:info('discrete event deassert SEL by sensor [%s].', self.sensor_id)
            self:process_event(sel)
        else
            -- 不允许上报deassert事件，则更新健康状态
            log:info('discrete event deassert is masked by sensor [%s].', self.sensor_id)
            self:update_all(sel)
        end
        log:notice('[%s] deassert an event [event dir: %s]', self.sensor_id, dir_value or 'mock')
    end
end

function sensor_instance:assert_discrete_event(sel, dir_value)
    log:info('assert SEL [0x%04X, 0x%04X, 0x%02X, 0x%02X]',
        self.assert_state, self.deassert_state, sel.EventData1, sel.SensorNumber)
    if self.mdb_obj.AssertMask & (1 << (sel.EventData1 & 0x0F)) == 0 then
        log:info('discrete event assert is masked by sensor [%s].', self.sensor_id)
        return
    end
    local records = self.sel_sigs.find:emit(utils.SEL_MSG_MODE, sel)
    if records then
        local msg = records[1].__datas
        log:info('assert already has sel: %s', require 'cjson'.encode(msg))
        return
    end
    if self.mdb_obj.DiscreteType == cc.DISCRETE_DIGITAL then
        -- 数字离散是互斥的，需要删除当前传感器下的已经产生的SEL
        self:remove_mutex_event()
    end
    self:process_event(sel)
    log:notice('[%s] assert an event [event dir: %s]', self.sensor_id, dir_value or 'mock')
end

function sensor_instance:remove_mutex_event()
    -- 由于数字离散一个传感器仅能有一个SEL，因此直接查询之后获取
    local sel = {SensorId = self.sensor_id}
    local records = self.sel_sigs.find:emit(utils.SENSOR_ID_MODE, sel)
    if not records then
        -- 没有SEL则证明当前没产生过SEL事件
        return
    end

    sel = records[1].__datas
    sel.EventDir = utils.EVENT_DIR_DEASSERT
    self:update_all(sel)
end

function sensor_instance:update_assert_state(sel)
    local cur_state = (1 << (sel.EventData1 & 0x0F)) & 0xFFFF
    local cap = utils.capability:unpack(string.pack('I1', self.mdb_obj.Capabilities))
    if sel.EventDir == utils.EVENT_DIR_ASSERT then
        self.assert_state = self.assert_state | cur_state
        if cap.auto_rearm_support == 1 then
            self.deassert_state = self.deassert_state & ((~cur_state) & 0xFFFF)
        end
    else
        -- 同一个SEL的事件全部更新完毕之后才能deassert
        local s = utils.copy(sel)
        s.EventData2 = 0xFF
        s.EventData3 = 0xFF
        if self.sel_sigs.find:emit(utils.SEL_MSG_MODE, s) then
            log:info('current sel [no: %x] has other sel and cannot deassert.', sel.SensorNumber)
            return
        end
        self.deassert_state = self.deassert_state | cur_state
        if cap.auto_rearm_support == 1 then
            self.assert_state = self.assert_state & ((~cur_state) & 0xFFFF)
        end
    end
    local ori_as = self.disp_obj.AssertStatus
    self.disp_obj.AssertStatus = self.assert_state
    shm_data_mgmt.update_sensor_prop(self.sensor_number, 'assert_status', self.disp_obj.AssertStatus)
    log:info('sensor [%s] assert status changed from 0x%04X to 0x%04X', self.sensor_id, ori_as, self.assert_state)
end

function sensor_instance:update_sel_list(sel)
    if sel.EventDir == utils.EVENT_DIR_ASSERT then
        -- 将当前的 SEL 加入到当前的 SEL list 中
        return self.sel_sigs.add:emit(sel)
    else
        local ret
        -- 若为连续传感器事件，应模糊EventData2(Reading)和EventData3(Threshold)以匹配之前的assert事件数据
        if sel.EventType == 0x01 and (sel.EventData1 & 0xF0) == 0x50 then
            local reading = sel.EventData2
            sel.EventData2 = 0xFF
            local threshold = sel.EventData3
            sel.EventData3 = 0xFF
            ret = self.sel_sigs.remove:emit(utils.SEL_MSG_MODE, sel)
            -- 恢复原值，保证事件记录中的数据为真实值
            sel.EventData2 = reading
            sel.EventData3 = threshold
        else
            ret =  self.sel_sigs.remove:emit(utils.SEL_MSG_MODE, sel)
        end
        return ret
    end
end

function sensor_instance:update_sensor_health_count(dir, level)
    if level == utils.HEALTH_MINOR then
        if dir == utils.EVENT_DIR_ASSERT then
            self.minor_count = self.minor_count + 1
        end
        if dir == utils.EVENT_DIR_DEASSERT and self.minor_count > 0 then
            self.minor_count = self.minor_count - 1
        end
    elseif level == utils.HEALTH_MAJOR then
        if dir == utils.EVENT_DIR_ASSERT then
            self.major_count = self.major_count + 1
        end
        if dir == utils.EVENT_DIR_DEASSERT and self.major_count > 0 then
            self.major_count = self.major_count - 1
        end
    elseif level == utils.HEALTH_CRITICAL then
        if dir == utils.EVENT_DIR_ASSERT then
            self.critical_count = self.critical_count + 1
        end
        if dir == utils.EVENT_DIR_DEASSERT and self.critical_count > 0 then
            self.critical_count = self.critical_count - 1
        end
    end
end

function sensor_instance:update_sensor_health(dir, level)
    self:update_sensor_health_count(dir, level)
    self.last_health = self.cur_health
    if self.critical_count > 0 then
        self.cur_health = utils.HEALTH_CRITICAL
    elseif self.major_count > 0 then
        self.cur_health = utils.HEALTH_MAJOR
    elseif self.minor_count > 0 then
        self.cur_health = utils.HEALTH_MINOR
    else
        self.cur_health = utils.HEALTH_NORMAL
    end
    self.disp_obj.Health = common.convert_health(self.cur_health)
    log:info('sensor [%s] health has been changed from %d to %d', self.sensor_id, self.last_health, self.cur_health)
end

function sensor_instance:update_entity_health()
    local data = {
        id = self.mdb_obj.EntityId,
        instance = self.mdb_obj.EntityInstance,
        host_id = self.host_id,
        last_health = self.last_health,
        cur_health = self.cur_health
    }
    self.entity_sigs.update:emit(data)
end

function sensor_instance:update_global_health()
    local data = {
        last_health = self.last_health,
        cur_health = self.cur_health
    }

    self.global_sigs.update:emit(data)
end

function sensor_instance:udpate_scan_status_enabled()
    local ori_status = self.sensor_status
    local s = utils.reading_state:unpack(string.pack('I1', self.sensor_status))
    if not s then
        log:error('sensor [%s] status is invalid.', self.sensor_id)
        return
    end

    s.disable_scanning_local = utils.SCAN_ENABLED
    self.sensor_status = string.unpack('I1', utils.reading_state:pack(s))
    shm_data_mgmt.update_sensor_prop(self.sensor_number, 'scan_status', self.sensor_status)
    local disp_status = common.get_scan_enabled(self.sensor_status) and 'Enabled' or 'Disabled'
    self.disp_obj.Status = common.convert_sensor_status(self.disp_obj.Status, disp_status)
    log:info('sensor [%s] status is changed from 0x%02X to 0x%02X', self.sensor_id, ori_status, self.sensor_status)
end

function sensor_instance:update_after_scan_local_enabled()
    -- 检查扫描状态
    if not common.get_scan_enabled(self.sensor_status) then
        log:info('sensor [%s] has been disabled and ignored', self.sensor_id)
        return
    end

    if self.sensor_clz == 'DiscreteSensor' then -- 离散传感器需要主动重新触发事件
        for id, obj in pairs(self.discrete_events) do
            local p = obj.ListenType == cc.SINGLE_MODE and 'EventDir' or 'Property'
            local v = obj.ListenType == cc.SINGLE_MODE and obj.EventDir or obj.Property
            self:discrete_event_update(id, p, v, obj.InvalidReadingIgnore, obj.InvalidReading)
        end
    else
        -- 更新读值状态
        self.cur_reading_status = self.mdb_obj.ReadingStatus
        self:update_sensor_reading_status(self.mdb_obj.ReadingStatus)
        -- 更新传感器读值
        self.cur_reading = self.mdb_obj.Reading
        if not self.is_mock_mode then
            shm_data_mgmt.update_sensor_prop(self.sensor_number, 'reading', self.cur_reading)
            self.disp_obj.ReadingDisplay = rton(self.mdb_obj, self.cur_reading)
            self:update_sensor_event(self.mdb_obj.Reading)
        end
    end
end

function sensor_instance:remove_all_sel()
    local sel_finder = {SensorId = self.sensor_id}
    local records = self.sel_sigs.find:emit(utils.SENSOR_ID_MODE, sel_finder)
    if not records then
        return
    end

    local sel
    for _, record in pairs(records) do
        sel = record.__datas
        sel.EventDir = utils.EVENT_DIR_DEASSERT
        self:process_event(sel)
    end
end

function sensor_instance:udpate_scan_status_disabled()
    local cap = utils.capability:unpack(string.pack('I1', self.mdb_obj.Capabilities))
    if (not cap or cap.ignore_support ~= 1) then
        if not self.log_filters['capability'] then
            log:notice('sensor [%s] capability is invalid or ignored.', self.sensor_id)
            self.log_filters['capability'] = true
        end
        return
    end

    -- 恢复当前传感器的混合状态
    if self.last_status == cc.SCAN_FAILURE then
        self:update_management_health(0x00, self.mdb_obj.OwnerLun, self.mdb_obj.SensorNumber, 0x00)
    end

    local ori_status = self.sensor_status
    local s = utils.reading_state:unpack(string.pack('I1', self.sensor_status))
    if not s or s.disable_scanning_local == utils.SCAN_DISABLED then
        log:error('sensor [%s] status is invalid or in local disabled state.', self.sensor_id)
        return
    end
    s.disable_scanning_local = utils.SCAN_DISABLED
    self.sensor_status = string.unpack('I1', utils.reading_state:pack(s))
    shm_data_mgmt.update_sensor_prop(self.sensor_number, 'scan_status', self.sensor_status)
    local disp_status = common.get_scan_enabled(self.sensor_status) and 'Enabled' or 'Disabled'
    self.disp_obj.Status = common.convert_sensor_status(self.disp_obj.Status, disp_status)
    log:info('sensor [%s] status is changed from 0x%02X to 0x%02X', self.sensor_id, ori_status, self.sensor_status)

    -- 删除传感器下的所有的的SEL
    self:remove_all_sel()
end

local udpate_scan_status_handler = {
    [utils.SCAN_ENABLED] = function (self)
        self:udpate_scan_status_enabled()
        -- Entity更新扫描状态在ReadingStatus更新之后，因此需要做补偿更新
        self:update_after_scan_local_enabled()
    end,
    [utils.SCAN_DISABLED] = function (self)
        self:udpate_scan_status_disabled()
    end
}


function sensor_instance:udpate_scan_status(status)
    local handler = udpate_scan_status_handler[status]
    if not handler then
        log:warn('scan status [%s] has no handler to update', status)
        return
    end
    handler(self)
end

function sensor_instance:update_status_and_health(sel)
    -- 更新传感器的 Health
    self:update_sensor_health(sel.EventDir, sel.Level)

    -- 更新传感器的 Assert State
    self:update_assert_state(sel)

    -- 更新 Entity 的Health
    self:update_entity_health()

    -- 更新全局健康状态
    self:update_global_health()
end

function sensor_instance:update_all(sel)
    -- 更新当前的 SEL list
    local sel_ret = self:update_sel_list(sel)
    if not sel_ret then
        log:info('current sel [%s] has no action and omit.', require('cjson').encode(sel))
        return
    end

    self:update_status_and_health(sel)
end

function sensor_instance:process_event(sel)
    -- 更新全部的状态信息和SEL列表
    self:update_all(sel)

    local s = utils.copy(sel)
    s.SensorName = self.mdb_obj.SensorName
    s.SubjectName = self.entity_sigs.getName:emit(self.mdb_obj.EntityId, self.mdb_obj.EntityInstance, self.host_id)
    if self.sensor_clz == 'ThresholdSensor' then
       local alia = self.mdb_obj:get_alias(INTERFACE_NAME, PROPERTY_NAME)
       local source_info = self.mdb_obj:get_property_source_detail(alia)
       local ok, source_info_s = pcall(json.encode, source_info)
       if not ok then
           log:error("canot get %s related info, error is %s", s.SensorName, source_info_s)
       else
	       log:notice(s.SensorName .. '|' .. source_info_s)
       end
    end
    -- 处理当前的传感器SEL
    self.sel_sigs.post:emit({
        msg = s,
        generator_id_h = self.mdb_obj.OwnerLun,
        generator_id_l = self.mdb_obj.OwnerId
    })
end

local thresholds_offset_maps = {
    [cc.OFFSET_NONCRITICAL_UGH] = 'UpperNoncritical',
    [cc.OFFSET_NONCRITICAL_LGL] = 'LowerNoncritical',
    [cc.OFFSET_CRITICAL_UGH] = 'UpperCritical',
    [cc.OFFSET_CRITICAL_LGL] = 'LowerCritical',
    [cc.OFFSET_NONRECOVERABLE_UGH] = 'UpperNonrecoverable',
    [cc.OFFSET_NONRECOVERABLE_LGL] = 'LowerNonrecoverable'
}
function sensor_instance:process_threshold_event(reading, dir, offset)
    local sel = {}
    sel.SensorId = self.sensor_id
    sel.EventMsgVersion = utils.EVENT_MSG_VERSION
    sel.SensorNumber = self.mdb_obj.SensorNumber
    sel.SensorType = self.mdb_obj.SensorType
    sel.EventType = self.mdb_obj.ReadingType & 0x7F
    sel.EventData1 = (offset & 0x0F) | 0x50
    sel.EventData2 = reading
    local threshold = self.mdb_obj[thresholds_offset_maps[offset]]
    sel.EventData3 = threshold

    if dir == utils.EVENT_DIR_ASSERT then
        -- 如果新产生事件，则先恢复以前对应的事件
        sel.EventDir = utils.EVENT_DIR_DEASSERT
        self:update_all(sel)
    end
    sel.EventDir = dir

    self:process_event(sel)
    local action = dir == utils.EVENT_DIR_ASSERT and 'assert' or 'deassert'
    log:notice('[%s] %s an event [reading: %d, %s threshold: %d]', self.sensor_id, action, reading,
        thresholds_offset_maps[offset], threshold)
end

function sensor_instance:update_sensor_event(reading)
    -- 对协程切换场景进行临界区保护
    self.exec_queue(function()
        if #self.event_handlers == 0 then
            return
        end

        local as = common.assert_state:unpack(string.pack('I2', self.assert_state))
        local ds = common.deassert_state:unpack(string.pack('I2', self.deassert_state))
        if not as or not ds then
            log:error('assert state or assert mask or deassert state is invalid.')
            return
        end

        for _, handler in ipairs(self.event_handlers) do
            handler(self, reading, as, ds)
        end
    end)
end

function sensor_instance:update_management_health(dir_value, data3, data2, data1)
    local objs = cls_mgnt('DiscreteSensor'):get_all()
    local obj
    for _, v in pairs(objs) do
        if v['SensorType'] == 0x28 and v['ReadingType'] == 0x6F then
            obj = v
            break
        end
    end

    if not obj then
        log:error("get sensor[management_health] obj failed")
        return
    end

    local sel = {}
    sel.SensorId = obj:get_object_name()
    sel.EventMsgVersion = utils.EVENT_MSG_VERSION
    sel.SensorType = obj.SensorType
    sel.SensorNumber = obj.SensorNumber
    sel.EventType = obj.ReadingType & 0x7F
    sel.EventDir = dir_value ~= 0 and utils.EVENT_DIR_ASSERT or utils.EVENT_DIR_DEASSERT
    sel.EventData1 = data1
    sel.EventData2 = data2
    sel.EventData3 = data3

    self.sensor_sigs.addsel:emit(sel.SensorId, sel, dir_value)
end

function sensor_instance:update_sensor_reading_status(status)
    -- sensor 资源中的 reading status 用来指示 sensor 数据源的扫描状态，需要引用 scanner 的扫描值
    -- sensor 资源中的 status 用来标记当前 sensor 的禁用状态
    local mdb_status = utils.reading_state:unpack(string.pack('I1', self.sensor_status))
    if status == cc.SCAN_PRE_FAILURE then
        -- 传感器状态轻度异常，更新传感器状态即可
        mdb_status.disable_access_error = utils.SCAN_DISABLED
        mdb_status.initial_update_progress = cc.SENSOR_UPDATE_END
    end
    if status == cc.SCAN_FAILURE then
        mdb_status.disable_access_error = utils.SCAN_DISABLED
        self:update_management_health(self.sensor_status, self.mdb_obj.OwnerLun, self.mdb_obj.SensorNumber, 0x00)
        log:info('sensor current status will generate a management health SEL')
    end
    if status == cc.SCAN_NORMAL or status == cc.SCAN_NOT_ACCESSIBLE or status == cc.SCAN_NOT_SCANNED then
        -- 传感器非异常状态，需要恢复正常，并且置更新中状态
        mdb_status.disable_access_error = utils.SCAN_ENABLED
        if status == cc.SCAN_NOT_ACCESSIBLE or status == cc.SCAN_NOT_SCANNED then
            mdb_status.initial_update_progress = cc.SENSOR_UPDATE_IN_PROGRESS
        end
        if status == cc.SCAN_NORMAL then
            skynet.fork_once(function ()
                -- 无法保证ReadingStatus和Reading的同步时序，因此当ReadingStatus恢复正常时要重新更新读值、检测事件上报
                self:linear_sensor_update(self.mdb_obj.OriginalReading)
                self:update_sensor_reading()
            end)
        end
        if (self.last_status == cc.SCAN_NOT_ACCESSIBLE or self.last_status == cc.SCAN_NOT_SCANNED) and status == cc.SCAN_NORMAL then
            mdb_status.initial_update_progress = cc.SENSOR_UPDATE_END
        end
        -- 如果上次产生过异常SEL，则本次需要恢复SEL
        if self.last_status == cc.SCAN_FAILURE then
            self:update_management_health(0x00, self.mdb_obj.OwnerLun, self.mdb_obj.SensorNumber, 0x00)
            log:info('sensor current status will resume a management health SEL')
        end
    end

    self.last_status = status
    self.sensor_status = string.unpack('I1', utils.reading_state:pack(mdb_status))
    shm_data_mgmt.update_sensor_prop(self.sensor_number, 'scan_status', self.sensor_status)
    local disp_status = common.get_scan_enabled(self.sensor_status) and 'Enabled' or 'Disabled'
    self.disp_obj.Status = common.convert_sensor_status(self.disp_obj.Status, disp_status)
end

local function calculate_sensor_factors(obj)
    local linearization = obj.Linearization
    if linearization < 0x70 or linearization > 0x7F then
        -- 线性传感器的系数因子直接在配置中带有，因此不需要计算
        return false
    end

    -- 计算M值，原始值是double类型，因此计算结果有可能是负数
    local id = obj:get_object_name()
    local ori_reading = obj.OriginalReading
    local ori_m = ori_reading // 170 -- 按照 50% 裕量留给高低门限，则Reading值取值只能到170
    if ori_m < -512 or ori_m > 511 then
        log:error('sensor [%s] original reading [%s] M [%s] is out of range', id, ori_reading, ori_m)
        return false
    end
    if ori_m == 0 then
        -- M值为0时无法用于计算，补齐为1，其余量通过B值体现
        ori_m = 1
    end

    -- 计算B值:[-170, 170) 取整避免用浮点数进行位运算
    local ori_b = math.ceil(ori_reading - (ori_m * 170))
    if ori_b < -170 or ori_b >= 170 then
        log:error('sensor [%s] original reading [%s] B [%s] is out of range.', id, ori_reading, ori_b)
        return false
    end

    -- 更新当前对象的参数值
    obj.M = ori_m & 0xFF
    obj.MT = ((ori_m & 0x300) >> 2) | (obj.MT & 0x3F)
    obj.B = ori_b & 0xFF
    obj.BA = ((ori_b & 0x300) >> 2) | (obj.BA & 0x3F)
    obj.RBExp = 0 -- 非线性传感器，RExp和BExp写死为0
    log:info('sensor [%s][%s] nonlinear factors[M: %s, B: %s].', id, ori_reading, ori_m, ori_b)

    obj.Reading = 170 -- 非线性传感器，Reading写死为170
    return true
end

function sensor_instance:init_disp_status()
    local data = { id = self.mdb_obj.EntityId, instance = self.mdb_obj.EntityInstance, host_id = self.host_id }
    local status = self.entity_sigs.getStatus:emit(data)
    if status == utils.SCAN_DISABLED then -- 若为禁用状态，还需确定传感器是否支持禁用
        local cap = utils.capability:unpack(string.pack('I1', self.mdb_obj.Capabilities))
        if (not cap or cap.ignore_support ~= 1) then
            status = utils.SCAN_ENABLED
        end
    end

    local s = utils.reading_state:unpack(string.pack('I1', self.sensor_status))
    s.disable_scanning_local = status
    self.sensor_status = string.unpack('I1', utils.reading_state:pack(s))
    shm_data_mgmt.update_sensor_prop(self.sensor_number, 'scan_status', self.sensor_status)
    local disp_status = common.get_scan_enabled(self.sensor_status) and 'Enabled' or 'Disabled'
    return common.convert_sensor_status(self.disp_obj.Status, disp_status)
end

local assert_event_handlers = {
    ['assert_noncritical_lgl'] = function(self, reading, as, _)
        local cur_meet = common.compare(self.mdb_obj.Unit, self.mdb_obj.LowerNoncritical, reading)
        if cur_meet and as.assert_noncritical_lgl == 0 then
            -- 没有产生过 noncritical lower going low 事件，则产生该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_ASSERT, cc.OFFSET_NONCRITICAL_LGL)
        end
    end,
    ['assert_critical_lgl'] = function(self, reading, as, _)
        local cur_meet = common.compare(self.mdb_obj.Unit, self.mdb_obj.LowerCritical, reading)
        if cur_meet and as.assert_critical_lgl == 0 then
            -- 没有产生过 critical lower going low 事件，则产生该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_ASSERT, cc.OFFSET_CRITICAL_LGL)
        end
    end,
    ['assert_nonrecoverable_lgl'] = function(self, reading, as, _)
        local cur_meet = common.compare(self.mdb_obj.Unit, self.mdb_obj.LowerNonrecoverable, reading)
        if cur_meet and as.assert_nonrecoverable_lgl == 0 then
            -- 没有产生过 nonrecoverable lower going low 事件
            self:process_threshold_event(reading, utils.EVENT_DIR_ASSERT, cc.OFFSET_NONRECOVERABLE_LGL)
        end
    end,
    ['assert_noncritical_ugh'] = function(self, reading, as, _)
        local cur_meet = common.compare(self.mdb_obj.Unit, reading, self.mdb_obj.UpperNoncritical)
        if cur_meet and as.assert_noncritical_ugh == 0 then
            -- 没有产生过 noncritical upper going high 事件，则产生该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_ASSERT, cc.OFFSET_NONCRITICAL_UGH)
        end
    end,
    ['assert_critical_ugh'] = function(self, reading, as, _)
        local cur_meet = common.compare(self.mdb_obj.Unit, reading, self.mdb_obj.UpperCritical)
        if cur_meet and as.assert_critical_ugh == 0 then
            -- 没有产生过 critical upper going high 事件，则产生该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_ASSERT, cc.OFFSET_CRITICAL_UGH)
        end
    end,
    ['assert_nonrecoverable_ugh'] = function(self, reading, as, _)
        local cur_meet = common.compare(self.mdb_obj.Unit, reading, self.mdb_obj.UpperNonrecoverable)
        if cur_meet and as.assert_nonrecoverable_ugh == 0 then
            -- 没有产生过 nonrecoverable upper going high 事件，则产生该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_ASSERT, cc.OFFSET_NONRECOVERABLE_UGH)
        end
    end
}

local deassert_event_handlers = {
    ['deassert_noncritical_lgl'] = function(self, reading, as, ds)
        local threshold = (self.mdb_obj.LowerNonrecoverable + self.mdb_obj.NegativeHysteresis + 1)
        local cur_meet = common.compare(self.mdb_obj.Unit, reading, threshold)
        if cur_meet and ds.deassert_nonrecoverable_lgl == 0 and as.assert_nonrecoverable_lgl == 1 then
            -- 已产生 nonrecoverable lower going low 事件，则恢复该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_DEASSERT, cc.OFFSET_NONRECOVERABLE_LGL)
        end
    end,
    ['deassert_critical_lgl'] = function(self, reading, as, ds)
        local threshold = (self.mdb_obj.LowerCritical + self.mdb_obj.NegativeHysteresis + 1)
        local cur_meet = common.compare(self.mdb_obj.Unit, reading, threshold)
        if cur_meet and ds.deassert_critical_lgl == 0 and as.assert_critical_lgl == 1 then
            -- 已产生 critical lower going low 事件，则恢复该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_DEASSERT, cc.OFFSET_CRITICAL_LGL)
        end
    end,
    ['deassert_nonrecoverable_lgl'] = function(self, reading, as, ds)
        local threshold = (self.mdb_obj.LowerNonrecoverable + self.mdb_obj.NegativeHysteresis + 1)
        local cur_meet = common.compare(self.mdb_obj.Unit, reading, threshold)
        if cur_meet and ds.deassert_nonrecoverable_lgl == 0 and as.assert_nonrecoverable_lgl == 1 then
            -- 已产生 nonrecoverable lower going low 事件，则恢复该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_DEASSERT, cc.OFFSET_NONRECOVERABLE_LGL)
        end
    end,
    ['deassert_noncritical_ugh'] = function(self, reading, as, ds)
        local threshold = (self.mdb_obj.UpperNoncritical - self.mdb_obj.PositiveHysteresis - 1)
        local cur_meet = common.compare(self.mdb_obj.Unit, threshold, reading)
        if cur_meet and ds.deassert_noncritical_ugh == 0 and as.assert_noncritical_ugh == 1 then
            -- 已产生 noncritical upper going high 事件，则恢复该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_DEASSERT, cc.OFFSET_NONCRITICAL_UGH)
        end
    end,
    ['deassert_critical_ugh'] = function(self, reading, as, ds)
        local threshold = (self.mdb_obj.UpperCritical - self.mdb_obj.PositiveHysteresis - 1)
        local cur_meet = common.compare(self.mdb_obj.Unit, threshold, reading)
        if cur_meet and ds.deassert_critical_ugh == 0 and as.assert_critical_ugh == 1 then
            -- 已产生 critical upper going high 事件，则恢复该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_DEASSERT, cc.OFFSET_CRITICAL_UGH)
        end
    end,
    ['deassert_nonrecoverable_ugh'] = function(self, reading, as, ds)
        local threshold = (self.mdb_obj.UpperNonrecoverable - self.mdb_obj.PositiveHysteresis - 1)
        local cur_meet = common.compare(self.mdb_obj.Unit, threshold, reading)
        if cur_meet and ds.deassert_nonrecoverable_ugh == 0 and as.assert_nonrecoverable_ugh == 1 then
            -- 已产生 nonrecoverable upper going high 事件，则恢复该事件
            self:process_threshold_event(reading, utils.EVENT_DIR_DEASSERT, cc.OFFSET_NONRECOVERABLE_UGH)
        end
    end
}

function sensor_instance:register_event_handler()
    local am = common.assert_mask:unpack(string.pack('I2', self.mdb_obj.AssertMask))
    for k, handler in pairs(assert_event_handlers) do
        if am[k] == 1 then
            table.insert(self.event_handlers, handler)
        end
    end
    local dm = common.deassert_mask:unpack(string.pack('I2', self.mdb_obj.DeassertMask))
    for k, handler in pairs(deassert_event_handlers) do
        if dm[k] == 1 then
            table.insert(self.event_handlers, handler)
        end
    end
end

function sensor_instance:init_mdb_obj(obj)
    -- 初始化传感器的 ipmi 属性，由于需要监听该属性，因此初始化为成员变量
    self.mdb_obj = obj
    self.cur_reading = self.mdb_obj.Reading
    self.sensor_status = string.unpack('I1', utils.reading_state:pack({
        disable_scanning = utils.SCAN_ENABLED,
        disable_all = cc.SENSOR_ENABLED,
        disable_scanning_local = utils.SCAN_ENABLED,
        disable_override = 0,
        disable_access_error = cc.SENSOR_ENABLED,
        initial_update_progress = cc.SENSOR_UPDATE_END,
        auto_re_arm = 0,
        ignore_if_disable = 0
    }))

    -- 初始化传感器的描述属性
    self.disp_obj = obj:get_mdb_object(self.disp_intf)
    self.disp_obj.Health = common.convert_health(utils.HEALTH_NORMAL)
    self.disp_obj.AssertStatus = self.assert_state
    self.disp_obj.Status = self:init_disp_status()
    if self.sensor_clz == 'ThresholdSensor' then
        -- 根据配置的AssertMask及DeassertMask注册事件处理回调
        self:register_event_handler()
        -- 初始化ReadingMask(默认为0)
        local rm = common.reading_mask:unpack(string.pack('I2', obj.ReadingMask))
        if rm then
            self.rm = rm
        else
            self.rm = common.reading_mask:unpack(string.pack('I2', 0))
            log:error('reading mask is invalid.')
        end
        -- 门限传感器的值需要转换为可读值
        self.disp_obj.UnitDisplay = us(self.mdb_obj.Unit, self.mdb_obj.BaseUnit, self.mdb_obj.ModifierUnit)
        self.disp_obj.UpperNonrecoverableDisplay = self.rm.readable_upper_nonrecoverable == 1 and
        rton(self.mdb_obj, self.mdb_obj.UpperNonrecoverable) or '0.000'
        self.disp_obj.UpperCriticalDisplay = self.rm.readable_upper_critical == 1 and
        rton(self.mdb_obj, self.mdb_obj.UpperCritical) or '0.000'
        self.disp_obj.UpperNoncriticalDisplay = self.rm.readable_upper_noncritical == 1 and
        rton(self.mdb_obj, self.mdb_obj.UpperNoncritical) or '0.000'
        self.disp_obj.LowerNonrecoverableDisplay = self.rm.readable_lower_nonrecoverable == 1 and
        rton(self.mdb_obj, self.mdb_obj.LowerNonrecoverable) or '0.000'
        self.disp_obj.LowerCriticalDisplay = self.rm.readable_lower_critical == 1 and
        rton(self.mdb_obj, self.mdb_obj.LowerCritical) or '0.000'
        self.disp_obj.LowerNoncriticalDisplay = self.rm.readable_lower_noncritical == 1 and
        rton(self.mdb_obj, self.mdb_obj.LowerNoncritical) or '0.000'
        self.disp_obj.PositiveHysteresisDisplay = rton(self.mdb_obj, self.mdb_obj.PositiveHysteresis)
        self.disp_obj.NegativeHysteresisDisplay = rton(self.mdb_obj, self.mdb_obj.NegativeHysteresis)
        -- 计算传感器系数
        calculate_sensor_factors(self.mdb_obj)
        self.disp_obj.ReadingDisplay = rton(self.mdb_obj, self.mdb_obj.Reading)
    end
end

local update_threshold_hysteresis_hander = {
    ['UpperNonrecoverable'] = function(self, val)
        self.disp_obj.UpperNonrecoverableDisplay = rton(self.mdb_obj, val)
    end,
    ['UpperCritical'] = function(self, val)
        self.disp_obj.UpperCriticalDisplay = rton(self.mdb_obj, val)
    end,
    ['UpperNoncritical'] = function(self, val)
        self.disp_obj.UpperNoncriticalDisplay = rton(self.mdb_obj, val)
    end,
    ['LowerNonrecoverable'] = function(self, val)
        self.disp_obj.LowerNonrecoverableDisplay = rton(self.mdb_obj, val)
    end,
    ['LowerCritical'] = function(self, val)
        self.disp_obj.LowerCriticalDisplay = rton(self.mdb_obj, val)
    end,
    ['LowerNoncritical'] = function (self, val)
        self.disp_obj.LowerNoncriticalDisplay = rton(self.mdb_obj, val)
    end,
    ['PositiveHysteresis'] = function (self, val)
        self.disp_obj.PositiveHysteresisDisplay = rton(self.mdb_obj, val)
    end,
    ['NegativeHysteresis'] = function (self, val)
        self.disp_obj.NegativeHysteresisDisplay = rton(self.mdb_obj, val)
    end
}

function sensor_instance:update_threshold_hysteresis(prop, value)
    local handler = update_threshold_hysteresis_hander[prop]
    if not handler then
        log:info('sensor prop [%s] has no handler to update', prop)
        return
    end
    if self.rm[threshold_to_mask[prop]] and self.rm[threshold_to_mask[prop]] ~= 1 then
        log:notice('sensor prop [%s] not change by mask', prop)
        return
    end
    handler(self, value)
end

function sensor_instance:linear_sensor_update(value)
    if not calculate_sensor_factors(self.mdb_obj) then
        -- 对于不需要计算传感器系数的，SDR不会发生变化，因此不需要处理
        return false
    end

    -- 对于需要重新计算传感器系数的，Reading是固定的170（0xAA），因此需要重新计算读数，门限保持不变
    log:info('sensor [%s] orignal reading is changed to [%s].', self.sensor_id, value)
    self.sdr_sigs.update:emit(self.sensor_clz, self.mdb_obj, self.host_id)
    return true
end

-- 传感器读值发生变化
function sensor_instance:update_sensor_reading()
    self.cur_reading = self.mdb_obj.Reading
    if not self.is_mock_mode then
        shm_data_mgmt.update_sensor_prop(self.sensor_number, 'reading', self.cur_reading)
        self.disp_obj.ReadingDisplay = rton(self.mdb_obj, self.cur_reading)
        self:update_sensor_event(self.cur_reading)
    end
end

local sdr_static_props = {'EntityId', 'EntityInstance', 'SensorName'}
function sensor_instance:discrete_listen()
    self.mdb_obj['bmc.kepler.Systems.DiscreteSensor'].property_changed:on(function(name, value, sender)
        if not common.check_updation(name) then
            log:info('sensor %s property [%s] has been changed and ignored', self.sensor_id, name)
            return
        end

        -- 静态属性更新，需要更新SDR数据
        if utils.is_in(name, sdr_static_props) then
            self.sdr_sigs.update:emit(self.sensor_clz, self.mdb_obj, self.host_id)
            return
        end
    end)
end

function sensor_instance:threshold_listen()
    self.mdb_obj['bmc.kepler.Systems.ThresholdSensor'].property_changed:on(function(name, value, sender)
        if not common.check_updation(name) then
            log:info('sensor %s property [%s] has been changed and ignored', self.sensor_id, name)
            return
        end

        -- 静态属性更新，需要更新SDR数据
        if utils.is_in(name, sdr_static_props) then
            self.sdr_sigs.update:emit(self.sensor_clz, self.mdb_obj, self.host_id)
            return
        end

        if name == 'ReadingStatus' then
            -- 读值状态发生变化
            self.cur_reading_status = value
            self:update_sensor_reading_status(value)
            return
        end

        -- 如果传感器是禁止扫描状态，则下述处理不再进行
        if not common.get_scan_enabled(self.sensor_status, true) then
            log:info('sensor [%s] has been disabled and ignored', self.sensor_id)
            return
        end

        if name ~= 'OriginalReading' and name ~= 'Reading' then
            -- 传感器其他属性（门限，迟滞量）发生变化
            self:update_threshold_hysteresis(name, value)
            self.sdr_sigs.update:emit(self.sensor_clz, self.mdb_obj, self.host_id)
            self:update_sensor_event(self.mdb_obj.Reading)
            return
        end

        if name == 'OriginalReading' and not self:linear_sensor_update(value) then
            return
        end

        self:update_sensor_reading()
    end)
end

function sensor_instance:start_listen()
    if self.sensor_clz == 'DiscreteSensor' then
        self:discrete_listen()
    else
        self:threshold_listen()
    end
end

function sensor_instance:init_mdb_listen()
    -- 开始监听属性的变化以及处理sensor数据（包括SDR更新以及SEL生成）
    self:start_listen()

    if self.sensor_clz == 'DiscreteSensor' then
        return
    end

    -- 初始更新门限传感器扫描状态
    self.cur_reading_status = self.mdb_obj.ReadingStatus
    self:update_sensor_reading_status(self.cur_reading_status)

    -- 对门限传感器初始值先进行一次SEL处理
    if common.get_scan_enabled(self.sensor_status, true) then
        self.cur_reading = self.mdb_obj.Reading
        self:update_sensor_event(self.cur_reading)
    end
end

function sensor_instance:recover_sensor_status()
    local sels = self.sel_sigs.get:emit(self.sensor_id)
    if not sels then
        return
    end

    -- 若为Mngmnt Health传感器，则将所有事件恢复
    if self.mdb_obj.SensorType == 0x28 and self.mdb_obj.ReadingType == 0x6F then
        local subject_name = self.entity_sigs.getName:emit(self.mdb_obj.EntityId, self.mdb_obj.EntityInstance,
            self.host_id)
        for _, sel in ipairs(sels) do
            self.sel_sigs.remove:emit(utils.SEL_MSG_MODE, sel)

            sel.EventDir = utils.EVENT_DIR_DEASSERT
            sel.SensorNumber = self.mdb_obj.SensorNumber
            sel.SensorName = self.mdb_obj.SensorName
            sel.SubjectName = subject_name
            self.sel_sigs.post:emit({
                msg = sel,
                generator_id_h = self.mdb_obj.OwnerLun,
                generator_id_l = self.mdb_obj.OwnerId
            })
        end
        return
    end

    for _, sel in ipairs(sels) do
        self:update_status_and_health(sel)
    end
end

function sensor_instance:temp_store_bmc_boot(sensor_id, obj)
    local prefix = 'DiscreteSensor_BMCBoot'
    if string.sub(sensor_id, 1, #prefix) == prefix and obj.AssertStatus ~= 32768 then
        self.bmc_boot_sensor = obj
    end
end

function sensor_instance:register(o, host_id)
    self.sensor_id = o:get_object_name()
    self.host_id = host_id

    -- 初始化资源树对象属性
    self:init_mdb_obj(o)
    self:add_dynamic_data_to_shm()

    -- 根据复位持久化的sel信息恢复传感器状态及健康信息
    self:recover_sensor_status()

    -- 监听传感器的读值和属性变化
    self:init_mdb_listen()

    -- 建立传感器测试的信号回调
    self.mock_sig:on(function()
        if self.is_mock_mode then
            -- 开启测试，触发更新传感器状态以及对应的 SEL 生成
            log:info('sensor [%s] enter mock mode, mock reading: %d ...', self.sensor_id, self.mock_reading)
            shm_data_mgmt.update_sensor_prop(self.sensor_number, 'reading', self.mock_reading)
            self.disp_obj.ReadingDisplay = rton(self.mdb_obj, self.mock_reading)
            self:update_sensor_reading_status(cc.SCAN_NORMAL)
            self:update_sensor_event(self.mock_reading)
        else
            -- 关闭测试，根据上次的传感器状态更新状态以及对应的 SEL 生成
            shm_data_mgmt.update_sensor_prop(self.sensor_number, 'reading', self.cur_reading)
            self.disp_obj.ReadingDisplay = rton(self.mdb_obj, self.cur_reading)
            self:update_sensor_reading_status(self.cur_reading_status)
            self:update_sensor_event(self.cur_reading)
            log:info('sensor [%s] exit mock mode ...', self.sensor_id)
        end
    end)

    self:temp_store_bmc_boot(self.sensor_id, o)
end

function sensor_instance:match_sensor_obj(sensor_id, host_id)
    return sensor_id == self.sensor_id and host_id == self.host_id
end

function sensor_instance:get_ts_sensor_status()
    local as = common.assert_state:unpack(string.pack('I2', self.assert_state))
    if not as then
        log:error('assert state is invalid.')
        return
    end

    if not common.get_scan_enabled(self.sensor_status, true) then
        return 'na'
    end

    if (as.assert_nonrecoverable_lgl ~= 0) or (as.assert_nonrecoverable_ugh ~= 0) then
        return 'nr'
    elseif (as.assert_critical_lgl ~= 0) or (as.assert_critical_ugh ~= 0) then
        return 'cr'
    elseif (as.assert_noncritical_lgl ~= 0) or (as.assert_noncritical_ugh ~= 0) then
        return 'nc'
    else
        return 'ok'
    end
end

function sensor_instance:get_ts_sensor_threshold()
    local cap = utils.capability:unpack(string.pack('I1', self.mdb_obj.Capabilities))
    local thres_accessible = true
    local hys_accessible = true
    if not cap then
        log:error('capability is invalid.')
        thres_accessible = false
        hys_accessible = false
    else
        if cap.threshold_access_support ~= 1 and cap.threshold_access_support ~= 2 then
            log:notice('sensor [%s] threshold capability can not be supported.', self.sensor_id)
            thres_accessible = false
        end
        if cap.hysteresis_support ~= 1 and cap.hysteresis_support ~= 2 then
            log:notice('sensor [%s] hysteresis capability can not be supported.', self.sensor_id)
            hys_accessible = false
        end
    end

    local thres = {}
    thres.LNR = (thres_accessible and self.rm.readable_lower_nonrecoverable ~= 0) and
        self.disp_obj.LowerNonrecoverableDisplay or 'null'
    thres.LCR = (thres_accessible and self.rm.readable_lower_critical ~= 0) and
        self.disp_obj.LowerCriticalDisplay or 'null'
    thres.LNC = (thres_accessible and self.rm.readable_lower_noncritical ~= 0) and
        self.disp_obj.LowerNoncriticalDisplay or 'null'
    thres.UNC = (thres_accessible and self.rm.readable_upper_noncritical ~= 0) and
        self.disp_obj.UpperNoncriticalDisplay or 'null'
    thres.UCR = (thres_accessible and self.rm.readable_upper_critical ~= 0) and
        self.disp_obj.UpperCriticalDisplay or 'null'
    thres.UNR = (thres_accessible and self.rm.readable_upper_nonrecoverable ~= 0) and
        self.disp_obj.UpperNonrecoverableDisplay or 'null'
    thres.PH = hys_accessible and self.disp_obj.PositiveHysteresisDisplay or 'null'
    thres.NH = hys_accessible and self.disp_obj.NegativeHysteresisDisplay or 'null'
    return thres
end

function sensor_instance:add_all_sensor_to_list(list, type)
    if self.sensor_clz == 'ThresholdSensor' then
        self:add_threshold_sensor(list, type)
    else
        self:add_discrete_sensor(list, type)
    end
end

function sensor_instance:add_threshold_sensor(list, type)
    local status = self:get_ts_sensor_status()
    local threshold = self:get_ts_sensor_threshold()
    if type == cc.GET_THRESHOLD_SENSOR_LIST then
        list[#list + 1] = {
            SensorNumber = self.sensor_number,
            SensorName = self.mdb_obj.SensorName,
            SensorIdentifier = self.mdb_obj.SensorIdentifier,
            SystemId = self.host_id,
            SensorUnit = self.disp_obj.UnitDisplay,
            SensorStatus = status,
            SensorReading = status ~= 'na' and self.disp_obj.ReadingDisplay or 'na',
            LowerThresholdFatal = threshold.LNR,
            LowerThresholdCritical = threshold.LCR,
            LowerThresholdNonCritical = threshold.LNC,
            UpperThresholdNonCritical = threshold.UNC,
            UpperThresholdCritical = threshold.UCR,
            UpperThresholdFatal = threshold.UNR,
            PosHysteresis = threshold.PH,
            NegHysteresis = threshold.NH,
            SensorType = self.mdb_obj.SensorType,
            Status = self.mdb_obj.Status,
            Health = self.mdb_obj.Health
        }
    else
        list[#list + 1] = {
            SensorNumber = self.sensor_number,
            SensorName = self.mdb_obj.SensorName,
            SystemId = self.host_id,
            SensorUnit = self.disp_obj.UnitDisplay,
            SensorStatus = status,
            SensorReading = status ~= 'na' and self.disp_obj.ReadingDisplay or 'na',
            LowerFatal = threshold.LNR,
            LowerCritical = threshold.LCR,
            LowerNonCritical = threshold.LNC,
            UpperNonCritical = threshold.UNC,
            UpperCritical = threshold.UCR,
            UpperFatal = threshold.UNR,
            PosHysteresis = threshold.PH,
            NegHysteresis = threshold.NH,
            SensorType = self.mdb_obj.SensorType,
            BaseUnit = self.mdb_obj.BaseUnit,
            ReadingType = self.mdb_obj.ReadingType
        }
    end
end

function sensor_instance:add_discrete_sensor(list, type)
    local reading_str = self:get_ds_reading()
    local status = reading_str and sf('0x%x', self.assert_state) or 'na'
    reading_str = reading_str and sf('0x%x', reading_str) or 'na'
    if type == cc.GET_DISCRETE_SENSOR_LIST then
        list[#list + 1] = {
            SensorNumber = self.sensor_number,
            SensorName = self.mdb_obj.SensorName,
            SensorIdentifier = self.mdb_obj.SensorIdentifier,
            SystemId = self.host_id,
            SensorStatus = status
        }
    else
        list[#list + 1] = {
            SensorNumber = self.sensor_number,
            SensorName = self.mdb_obj.SensorName,
            SystemId = self.host_id,
            SensorUnit = 'discrete',
            SensorStatus = status,
            SensorReading = reading_str,
            LowerFatal = 'null',
            LowerCritical = 'null',
            LowerNonCritical = 'null',
            UpperNonCritical = 'null',
            UpperCritical = 'null',
            UpperFatal = 'null',
            PosHysteresis = 'null',
            NegHysteresis = 'null',
            SensorType = self.mdb_obj.SensorType,
            BaseUnit = self.mdb_obj.BaseUnit,
            ReadingType = self.mdb_obj.ReadingType
        }
    end
end

function sensor_instance:set_sensor_hysteresis(pos, neg)
    local cap = utils.capability:unpack(string.pack('I1', self.mdb_obj.Capabilities))
    if not cap then
        log:error('sensor [%s] capability is invalid.', self.sensor_id)
        return err.ERR_CANNOT_RESPONSE
    end

    if cap.hysteresis_support ~= cc.SENSOR_CAP_READABLE_SETTABLE then
        log:error('sensor [%s] capability can not be supported.', self.sensor_id)
        return err.ERR_CANNOT_RESPONSE
    end

    self.mdb_obj.PositiveHysteresis = pos
    self.mdb_obj.NegativeHysteresis = neg
    self.disp_obj.PositiveHysteresisDisplay = rton(self.mdb_obj, pos)
    self.disp_obj.NegativeHysteresisDisplay = rton(self.mdb_obj, neg)
    return err.SUCCESS
end

function sensor_instance:get_sensor_hysteresis()
    local cap = utils.capability:unpack(string.pack('I1', self.mdb_obj.Capabilities))
    if not cap then
        log:error('capability is invalid.')
        return err.ERR_CANNOT_RESPONSE
    end

    if cap.hysteresis_support ~= cc.SENSOR_CAP_READABLE and cap.hysteresis_support ~=
        cc.SENSOR_CAP_READABLE_SETTABLE then
        log:error('capability can not be supported.')
        return err.ERR_CANNOT_RESPONSE
    end

    local data = {}
    data.PHysteresis = self.mdb_obj.PositiveHysteresis
    data.NHysteresis = self.mdb_obj.NegativeHysteresis
    return err.SUCCESS, data
end

function sensor_instance:set_ipmi_sensor_threshold_unr(rm, data, ctx)
    if data.UNRFlag == 0 then
        return err.SUCCESS
    end
    if rm.settable_upper_nonrecoverable == 0 then
        log:error('upper nonrecoverable threshold cannot be set.')
        return err.ERR_CANNOT_RESPONSE
    end

    -- 严重告警上门限+正向迟滞量+1<=紧急告警上门限<=有符号(0x7F)/无符号(0xFF)
    local min = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x80) or 0x00
    local max = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x7F) or 0xFF
    if rm.settable_upper_critical == 1 then
        min = (data.UCRValue + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    elseif rm.settable_upper_noncritical == 1 then
        min = (data.UNCValue + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    elseif rm.settable_lower_nonrecoverable == 1 then
        min = (data.LNRValue + self.mdb_obj.NegativeHysteresis + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    elseif rm.settable_lower_critical == 1 then
        min = (data.LCRValue + self.mdb_obj.NegativeHysteresis + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    elseif rm.settable_lower_noncritical == 1 then
        min = (data.LNCValue + self.mdb_obj.NegativeHysteresis + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    end
    if data.UNRValue < min or data.UNRValue > max then
        log:error(
            'upper nonrecoverable threshold [set: 0x%02X, min: 0x%02X, max: 0x%02X] is out of range.',
            data.UNRValue, min, max)
        return err.ERR_CANNOT_RESPONSE
    end
    self.mdb_obj.UpperNonrecoverable = data.UNRValue
    self.disp_obj.UpperNonrecoverableDisplay = rton(self.mdb_obj, data.UNRValue)
    oper.log(ctx, oper.SENSOR_THRES, oper.SUCCESS, self.mdb_obj.SensorName,
        'upper non-recoverable', self.disp_obj.UpperNonrecoverableDisplay)
    return err.SUCCESS
end

function sensor_instance:set_ipmi_sensor_threshold_ucr(rm, data, ctx)
    if data.UCRFlag == 0 then
        return err.SUCCESS
    end
    if rm.settable_upper_critical == 0 then
        log:error('upper critical threshold cannot be set.')
        return err.ERR_CANNOT_RESPONSE
    end

    -- 一般告警上门限+正向迟滞量+1<=严重告警上门限<=紧急告警上门限-正向迟滞量-1
    local min = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x80) or 0x00
    local max = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x7F) or 0xFF
    if rm.settable_upper_noncritical == 1 then
        min = (data.UNCValue + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    elseif rm.settable_lower_nonrecoverable == 1 then
        min = (data.LNRValue + self.mdb_obj.NegativeHysteresis + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    elseif rm.settable_lower_critical == 1 then
        min = (data.LCRValue + self.mdb_obj.NegativeHysteresis + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    elseif rm.settable_lower_noncritical == 1 then
        min = (data.LNCValue + self.mdb_obj.NegativeHysteresis + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    end
    if rm.settable_upper_nonrecoverable == 1 then
        max = (data.UNRValue - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    end
    if data.UCRValue < min or data.UCRValue > max then
        log:error(
            'upper critical threshold [set: 0x%02X, min: 0x%02X, max: 0x%02X] is out of range.',
            data.UCRValue, min, max)
        return err.ERR_CANNOT_RESPONSE
    end
    self.mdb_obj.UpperCritical = data.UCRValue
    self.disp_obj.UpperCriticalDisplay = rton(self.mdb_obj, data.UCRValue)
    oper.log(ctx, oper.SENSOR_THRES, oper.SUCCESS, self.mdb_obj.SensorName,
        'upper critical', self.disp_obj.UpperCriticalDisplay)
    return err.SUCCESS
end

function sensor_instance:set_ipmi_sensor_threshold_unc(rm, data, ctx)
    if data.UNCFlag == 0 then
        return err.SUCCESS
    end
    if rm.settable_upper_noncritical == 0 then
        log:error('upper noncritical threshold cannot be set.')
        return err.ERR_CANNOT_RESPONSE
    end

    -- 一般告警下门限+负向迟滞量+正向迟滞量+1<=一般告警上门限<=严重告警上门限-正向迟滞量-1
    local min = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x80) or 0x00
    local max = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x7F) or 0xFF
    if rm.settable_lower_nonrecoverable == 1 then
        min = (data.LNRValue + self.mdb_obj.NegativeHysteresis + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    elseif rm.settable_lower_critical == 1 then
        min = (data.LCRValue + self.mdb_obj.NegativeHysteresis + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    elseif rm.settable_lower_noncritical == 1 then
        min = (data.LNCValue + self.mdb_obj.NegativeHysteresis + self.mdb_obj.PositiveHysteresis + 1) & 0xFF
    end
    if rm.settable_upper_nonrecoverable == 1 then
        max = (data.UNRValue - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    elseif rm.settable_upper_critical == 1 then
        max = (data.UCRValue - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    end
    if data.UNCValue < min or data.UNCValue > max then
        log:error(
            'upper noncritical threshold [set: 0x%02X, min: 0x%02X, max: 0x%02X] is out of range.',
            data.UNCValue, min, max)
        return err.ERR_CANNOT_RESPONSE
    end
    self.mdb_obj.UpperNoncritical = data.UNCValue
    self.disp_obj.UpperNoncriticalDisplay = rton(self.mdb_obj, data.UNCValue)
    oper.log(ctx, oper.SENSOR_THRES, oper.SUCCESS, self.mdb_obj.SensorName,
        'upper non-critical', self.disp_obj.UpperNoncriticalDisplay)
    return err.SUCCESS
end

function sensor_instance:set_ipmi_sensor_threshold_lnr(rm, data, ctx)
    if data.LNRFlag == 0 then
        return err.SUCCESS
    end
    if rm.settable_lower_nonrecoverable == 0 then
        log:error('lower nonrecoverable threshold cannot be set.')
        return err.ERR_CANNOT_RESPONSE
    end

    -- 有符号(0x80)/无符号(0x00)<=紧急告警下门限<=严重告警下门限-负向迟滞量-1
    local min = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x80) or 0x00
    local max = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x7F) or 0xFF
    if rm.settable_upper_nonrecoverable == 1 then
        max = (data.UNRValue - self.mdb_obj.NegativeHysteresis - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    elseif rm.settable_upper_critical == 1 then
        max = (data.UCRValue - self.mdb_obj.NegativeHysteresis - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    elseif rm.settable_upper_noncritical == 1 then
        max = (data.UNCValue - self.mdb_obj.NegativeHysteresis - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    elseif rm.settable_lower_critical == 1 then
        max = (data.LCRValue - self.mdb_obj.NegativeHysteresis - 1) & 0xFF
    elseif rm.settable_lower_noncritical == 1 then
        max = (data.LNCValue - self.mdb_obj.NegativeHysteresis - 1) & 0xFF
    end
    if data.LNRValue < min or data.LNRValue > max then
        log:error(
            'lower nonrecoverable threshold [set: 0x%02X, min: 0x%02X, max: 0x%02X] is out of range.',
            data.LNRValue, min, max)
        return err.ERR_CANNOT_RESPONSE
    end
    self.mdb_obj.LowerNonrecoverable = data.LNRValue
    self.disp_obj.LowerNonrecoverableDisplay = rton(self.mdb_obj, data.LNRValue)
    oper.log(ctx, oper.SENSOR_THRES, oper.SUCCESS, self.mdb_obj.SensorName,
        'lower non-recoverable', self.disp_obj.LowerNonrecoverableDisplay)
    return err.SUCCESS
end

function sensor_instance:set_ipmi_sensor_threshold_lcr(rm, data, ctx)
    if data.LCRFlag == 0 then
        return err.SUCCESS
    end
    if rm.settable_lower_critical == 0 then
        log:error('lower critical threshold cannot be set.')
        return err.ERR_CANNOT_RESPONSE
    end

    -- 紧急告警下门限+负向迟滞量+1<=严重告警下门限<=一般告警下门限-负向迟滞量-1
    local min = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x80) or 0x00
    local max = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x7F) or 0xFF
    if rm.settable_lower_nonrecoverable == 1 then
        min = (data.LNRValue + self.mdb_obj.NegativeHysteresis + 1) & 0xFF
    end
    if rm.settable_upper_nonrecoverable == 1 then
        max = (data.UNRValue - self.mdb_obj.NegativeHysteresis - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    elseif rm.settable_upper_critical == 1 then
        max = (data.UCRValue - self.mdb_obj.NegativeHysteresis - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    elseif rm.settable_upper_noncritical == 1 then
        max = (data.UNCValue - self.mdb_obj.NegativeHysteresis - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    elseif rm.settable_lower_noncritical == 1 then
        max = (data.LNCValue - self.mdb_obj.NegativeHysteresis - 1) & 0xFF
    end
    if data.LCRValue < min or data.LCRValue > max then
        log:error(
            'lower critical threshold [set: 0x%02X, min: 0x%02X, max: 0x%02X] is out of range.',
            data.LCRValue, min, max)
        return err.ERR_CANNOT_RESPONSE
    end
    self.mdb_obj.LowerCritical = data.LCRValue
    self.disp_obj.LowerCriticalDisplay = rton(self.mdb_obj, data.LCRValue)
    oper.log(ctx, oper.SENSOR_THRES, oper.SUCCESS, self.mdb_obj.SensorName,
        'lower critical', self.disp_obj.LowerCriticalDisplay)
    return err.SUCCESS
end

function sensor_instance:set_ipmi_sensor_threshold_lnc(rm, data, ctx)
    if data.LNCFlag == 0 then
        return err.SUCCESS
    end
    if rm.settable_lower_noncritical == 0 then
        log:error('lower noncritical threshold cannot be set.')
        return err.ERR_CANNOT_RESPONSE
    end

    -- 严重告警下门限+负向迟滞量+1<=一般告警下门限<=一般告警上门限-负向迟滞量-正向迟滞量-1
    local min = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x80) or 0x00
    local max = (self.mdb_obj.Unit & 0xC0) ~= 0 and utils.utos_byte(0x7F) or 0xFF
    if rm.settable_lower_nonrecoverable == 1 then
        min = (data.LNRValue + self.mdb_obj.NegativeHysteresis + 1) & 0xFF
    elseif rm.settable_lower_critical == 1 then
        min = (data.LCRValue + self.mdb_obj.NegativeHysteresis + 1) & 0xFF
    end
    if rm.settable_upper_nonrecoverable == 1 then
        max = (data.UNRValue - self.mdb_obj.NegativeHysteresis - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    elseif rm.settable_upper_critical == 1 then
        max = (data.UCRValue - self.mdb_obj.NegativeHysteresis - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    elseif rm.settable_upper_noncritical == 1 then
        max = (data.UNCValue - self.mdb_obj.NegativeHysteresis - self.mdb_obj.PositiveHysteresis - 1) & 0xFF
    end
    if data.LNCValue < min or data.LNCValue > max then
        log:error(
            'lower noncritical threshold [set: 0x%02X, min: 0x%02X, max: 0x%02X] is out of range.',
            data.LNCValue, min, max)
        return err.ERR_CANNOT_RESPONSE
    end
    self.mdb_obj.LowerNoncritical = data.LNCValue
    self.disp_obj.LowerNoncriticalDisplay = rton(self.mdb_obj, data.LNCValue)
    oper.log(ctx, oper.SENSOR_THRES, oper.SUCCESS, self.mdb_obj.SensorName,
        'lower non-critical', self.disp_obj.LowerNoncriticalDisplay)
    return err.SUCCESS
end

function sensor_instance:set_sensor_threshold(data, ctx)
    if self.sensor_clz == 'DiscreteSensor' then
        log:error('discrete sensor cannot set the threshold.')
        return err.ERR_CANNOT_RESPONSE
    end

    local cap = utils.capability:unpack(string.pack('I1', self.mdb_obj.Capabilities))
    if not cap then
        log:error('capability is invalid.')
        return err.ERR_CANNOT_RESPONSE
    end
    if cap.threshold_access_support ~= cc.SENSOR_CAP_READABLE_SETTABLE then
        log:error('capability can not be supported.')
        return err.ERR_CANNOT_RESPONSE
    end

    local threshold = data
    threshold.UNRValue = data.UNRFlag == 1 and data.UNRValue or self.mdb_obj.UpperNonrecoverable
    threshold.UCRValue = data.UCRFlag == 1 and data.UCRValue or self.mdb_obj.UpperCritical
    threshold.UNCValue = data.UNCFlag == 1 and data.UNCValue or self.mdb_obj.UpperNoncritical
    threshold.LNRValue = data.LNRFlag == 1 and data.LNRValue or self.mdb_obj.LowerNonrecoverable
    threshold.LCRValue = data.LCRFlag == 1 and data.LCRValue or self.mdb_obj.LowerCritical
    threshold.LNCValue = data.LNCFlag == 1 and data.LNCValue or self.mdb_obj.LowerNoncritical

    local ret = self:set_ipmi_sensor_threshold_unr(self.rm, threshold, ctx)
    if ret ~= err.SUCCESS then
        return ret
    end

    ret = self:set_ipmi_sensor_threshold_ucr(self.rm, threshold, ctx)
    if ret ~= err.SUCCESS then
        return ret
    end

    ret = self:set_ipmi_sensor_threshold_unc(self.rm, threshold, ctx)
    if ret ~= err.SUCCESS then
        return ret
    end

    ret = self:set_ipmi_sensor_threshold_lnr(self.rm, threshold, ctx)
    if ret ~= err.SUCCESS then
        return ret
    end

    ret = self:set_ipmi_sensor_threshold_lcr(self.rm, threshold, ctx)
    if ret ~= err.SUCCESS then
        return ret
    end

    ret = self:set_ipmi_sensor_threshold_lnc(self.rm, threshold, ctx)
    if ret ~= err.SUCCESS then
        return ret
    end
    return err.SUCCESS
end

function sensor_instance:get_sensor_threshold()
    if self.sensor_clz == 'DiscreteSensor' then
        log:error('discrete sensor cannot get the threshold.')
        return err.ERR_CANNOT_RESPONSE
    end

    local cap = utils.capability:unpack(string.pack('I1', self.mdb_obj.Capabilities))
    if not cap then
        log:error('capability is invalid.')
        return err.ERR_CANNOT_RESPONSE
    end

    if cap.threshold_access_support ~= cc.SENSOR_CAP_READABLE_SETTABLE and
        cap.threshold_access_support ~= cc.SENSOR_CAP_READABLE then
        log:error('capability can not be supported.')
        return err.ERR_CANNOT_RESPONSE
    end

    local data = {}
    data.Reserved = 0
    data.UNRFlag = self.rm.readable_upper_nonrecoverable
    data.UCRFlag = self.rm.readable_upper_critical
    data.UNCFlag = self.rm.readable_upper_noncritical
    data.LNRFlag = self.rm.readable_lower_nonrecoverable
    data.LCRFlag = self.rm.readable_lower_critical
    data.LNCFlag = self.rm.readable_lower_noncritical
    data.UNRValue = self.mdb_obj.UpperNonrecoverable
    data.UCRValue = self.mdb_obj.UpperCritical
    data.UNCValue = self.mdb_obj.UpperNoncritical
    data.LNRValue = self.mdb_obj.LowerNonrecoverable
    data.LCRValue = self.mdb_obj.LowerCritical
    data.LNCValue = self.mdb_obj.LowerNoncritical
    return err.SUCCESS, data
end

function sensor_instance:set_sensor_status(disabled, initiator)
    -- 设置传感器读数状态
    local rs = utils.reading_state:unpack(string.pack('I1', self.sensor_status))
    if not rs then
        log:error('sensor status is invalid.')
        return err.ERR_INVALID_ENABLE_FAILED
    end

    rs.disable_all = disabled
    rs.disable_scanning = disabled
    self.sensor_status = string.unpack('I1', utils.reading_state:pack(rs))
    shm_data_mgmt.update_sensor_prop(self.sensor_number, 'scan_status', self.sensor_status)
    local disp_status = common.get_scan_enabled(self.sensor_status) and 'Enabled' or 'Disabled'
    self.disp_obj.Status = common.convert_sensor_status(self.disp_obj.Status, disp_status)

    if disabled == cc.SENSOR_ENABLED then
        common.op(initiator, 'Enable sensor (%s) successfully', self.mdb_obj.SensorName)
    else
        common.op(initiator, 'Disable sensor (%s) successfully', self.mdb_obj.SensorName)
    end
    return err.SUCCESS
end

function sensor_instance:set_sensor_enable(req)
    local cap = utils.capability:unpack(string.pack('I1', self.mdb_obj.Capabilities))
    if not cap then
        log:error('capability is invalid.')
        return err.ERR_CANNOT_RESPONSE
    end

    if cap.event_msg_support == cc.SENSOR_CAP_EVENT_NONE then
        log:error('capability can not support to set enable.')
        return err.ERR_ILLEGAL
    end
    if cap.event_msg_support == cc.SENSOR_CAP_EVENT_GLOBAL_DISABLE and req.all_disabled == 0 then
        log:error('capability is disabled to set enable.')
        return err.ERR_ILLEGAL
    end

    -- 设置传感器读数状态
    local rs = utils.reading_state:unpack(string.pack('I1', self.sensor_status))
    if not rs then
        log:error('sensor status is invalid.')
        return err.ERR_CANNOT_RESPONSE
    end
    rs.disable_override = 1
    rs.disable_all = req.all_disabled
    rs.disable_scanning = req.scan_disabled
    self.sensor_status = string.unpack('I1', utils.reading_state:pack(rs))
    shm_data_mgmt.update_sensor_prop(self.sensor_number, 'scan_status', self.sensor_status)
    local disp_status = common.get_scan_enabled(self.sensor_status) and 'Enabled' or 'Disabled'
    self.disp_obj.Status = common.convert_sensor_status(self.disp_obj.Status, disp_status)

    -- 设置传感器assert/deassert mask
    local am = (req.all_disabled == 0 and req.len < 3) and 0 or req.assert_mask
    local dm = (req.all_disabled == 0 and req.len < 3) and 0 or req.deassert_mask
    if req.change_mode == cc.SENSOR_EVENT_ENABLE_SELECTED then
        self.mdb_obj.AssertMask = self.mdb_obj.AssertMask | am
        self.mdb_obj.DeassertMask = self.mdb_obj.DeassertMask | dm
    elseif req.change_mode == cc.SENSOR_EVENT_DISABLE_SELECTED then
        self.mdb_obj.AssertMask = self.mdb_obj.AssertMask & ((~(am & 0xFFFF)) & 0xFFFF)
        self.mdb_obj.DeassertMask = self.mdb_obj.DeassertMask | ((~(dm & 0xFFFF)) & 0xFFFF)
    end
    return err.SUCCESS
end

function sensor_instance:get_sensor_enable()
    local rs = utils.reading_state:unpack(string.pack('I1', self.sensor_status))
    if not rs then
        log:error('sensor status is invalid.')
        return err.ERR_CANNOT_RESPONSE
    end

    local rsp = {}
    -- AllDisabled和ScanDisabled返回值为0表示未使能，为disabled状态
    rsp.AllDisabled = rs.disable_all
    rsp.ScanDisabled = (rs.disable_scanning == cc.SENSOR_ENABLED and
        rs.disable_scanning_local == cc.SENSOR_ENABLED) and 1 or 0
    rsp.Reserved = 0
    rsp.AssertMask = self.mdb_obj.AssertMask
    rsp.DeassertMask = self.mdb_obj.DeassertMask
    if self.sensor_clz == 'ThresholdSensor' then
        -- 门限传感器的 AssertMask 高 4bits 不使用，直接清零
        rsp.AssertMask = rsp.AssertMask & 0x0FFF
        rsp.DeassertMask = rsp.DeassertMask & 0x0FFF
    end
    return err.SUCCESS, rsp
end

function sensor_instance:get_sensor_status()
    local rs = utils.reading_state:unpack(string.pack('I1', self.sensor_status))
    if not rs then
        log:error('sensor status is invalid.')
        return err.ERR_CANNOT_RESPONSE
    end

    local rsp = {}
    -- AllDisabled和ScanDisabled返回值为0表示未使能，为disabled状态
    rsp.AllDisabled = rs.disable_all
    rsp.ScanDisabled = (rs.disable_scanning == cc.SENSOR_ENABLED and
        rs.disable_scanning_local == cc.SENSOR_ENABLED) and 1 or 0
    -- ReadingUnavailable返回值为1表示读值异常，为unavailable状态
    rsp.ReadingUnavailable = (rs.disable_access_error == cc.SENSOR_ENABLED and
        rs.initial_update_progress == cc.SENSOR_UPDATE_END) and 0 or 1
    rsp.Reserved = 0
    rsp.AssertStatusL = self.assert_state & 0xFF
    rsp.AssertStatusH = (self.assert_state >> 8) & 0xFF
    rsp.DeassertStatusL = self.deassert_state & 0xFF
    rsp.DeassertStatusH = (self.deassert_state >> 8) & 0xFF
    return err.SUCCESS, rsp
end

local as_pattern = bs.new([[<<l_nc:1,l_c:1,l_nr:1,u_nc:1,u_c:1,u_nr:1,wk:2,wb:8>>]])
function sensor_instance:get_sensor_reading()
    local rs = utils.reading_state:unpack(string.pack('I1', self.sensor_status))
    if not rs then
        log:error('sensor status is invalid.')
        return err.ERR_CANNOT_RESPONSE
    end

    local rsp = {}
    -- AllDisabled和ScanDisabled返回值为0表示未使能，为disabled状态
    rsp.AllDisabled = rs.disable_all
    rsp.ScanDisabled = (rs.disable_scanning == cc.SENSOR_ENABLED and
        rs.disable_scanning_local == cc.SENSOR_ENABLED) and 1 or 0
    rsp.Reserved = 0
    -- InitialProgress返回值为1表示读值异常，为unavailable状态
    rsp.InitialProgress = (rs.disable_access_error == cc.SENSOR_ENABLED and
        rs.initial_update_progress == cc.SENSOR_UPDATE_END) and 0 or 1

    if self.sensor_clz == 'ThresholdSensor' then
        local assert_status = {}
        local as = common.assert_state:unpack(string.pack('I2', self.assert_state))
        assert_status.wk = 0x03
        assert_status.wb = 0x00
        assert_status.u_nr = as.assert_nonrecoverable_ugh
        assert_status.u_c = as.assert_critical_ugh
        assert_status.u_nc = as.assert_noncritical_ugh
        assert_status.l_nr = as.assert_nonrecoverable_lgl
        assert_status.l_c = as.assert_critical_lgl
        assert_status.l_nc = as.assert_noncritical_lgl
        rsp.AssertStatus = string.unpack('I2', as_pattern:pack(assert_status))
    else
        rsp.AssertStatus = rs.disable_all == cc.SENSOR_DISALBED and 0x8000 or self.assert_state
    end
    local reading = self.is_mock_mode and self.mock_reading or self.mdb_obj.Reading
    rsp.Reading = reading & 0xFF
    return err.SUCCESS, rsp
end

function sensor_instance:get_dcmi_temperature_reading()
    local raw_reading = self.is_mock_mode and self.mock_reading or self.mdb_obj.Reading
    raw_reading = raw_reading & 0xFF
    local val_signed = (raw_reading > 127) and (raw_reading - 256) or raw_reading
    local dcmi_byte
    if val_signed < 0 then
        dcmi_byte = 0x80 | (math.abs(val_signed) & 0x7F)
    else
        dcmi_byte = val_signed & 0x7F
    end
    return dcmi_byte, self.mdb_obj.EntityInstance
end

function sensor_instance:get_sensor_type()
    local rsp = {}
    rsp.Reserved = 0
    rsp.Type = self.mdb_obj.SensorType
    rsp.TypeCode = self.mdb_obj.ReadingType & 0x7F
    return err.SUCCESS, rsp
end

function sensor_instance:mock_threshold_start(enable, value)
    if not self.is_mock_mode and enable == 1 then
        self.mock_ori_reading = self.disp_obj.ReadingDisplay
    end
    local v, e = common.convert_normal_to_raw(self.mdb_obj, value)
    if not v then
        return e
    end
    self.is_mock_mode = enable == 1 and true or false
    self.mock_reading = v & 0xFF
    self.mock_sig:emit()
    log:notice('sensor [%s] mock start [%s:%s] successfully', self.sensor_id, value, self.mock_ori_reading)
end

function sensor_instance:mock_discrete_start(enable, value)
    -- 第一次测试，需要保存离散传感器的 assert state
    if not self.is_mock_mode and enable == 1 then
        self.mock_ori_reading = self.assert_state
    end

    self.mock_reading = value
    self:discrete_test(self.mock_reading)
    self.is_mock_mode = true
    log:notice('sensor [%s] mock start [%d:%d] successfully', self.sensor_id, value, self.mock_ori_reading)
end

function sensor_instance:mock_threshold_stop()
    self.is_mock_mode = false
    self.mock_reading = 0
    self.mock_sig:emit()
    log:notice('sensor [%s] mock stop [%s] successfully', self.sensor_id, self.mock_ori_reading)
end

function sensor_instance:mock_discrete_stop()
    self:discrete_test(self.mock_ori_reading)
    self.is_mock_mode = false
    self.mock_reading = 0
    self.mock_ori_reading = 0
    log:notice('sensor [%s] mock stop [%d] successfully', self.sensor_id, self.mock_ori_reading)
end

function sensor_instance:discrete_test(v)
    if not common.get_scan_enabled(self.sensor_status) then
        log:notice('sensor scanning is disabled and cannot be mocked.')
        return
    end

    local sel = {}
    sel.SensorId = self.sensor_id
    sel.EventMsgVersion = utils.EVENT_MSG_VERSION
    sel.SensorNumber = self.mdb_obj.SensorNumber
    sel.SensorType = self.mdb_obj.SensorType
    sel.EventType = self.mdb_obj.ReadingType & 0x7F
    local records = self.sel_sigs.find:emit(utils.SENSOR_ID_MODE, sel)
    if not records then
        -- 没有SEL则证明当前没产生过SEL事件
        sel.EventData2 = 0xFF
        sel.EventData3 = 0xFF
        log:notice('current sel [no: %d] has no SEL.', sel.SensorNumber)
    else
        local msg = records[1].__datas
        sel.EventData2 = msg.EventData2
        sel.EventData3 = msg.EventData3
        log:notice('current sel [no: %d] has SEL [0x%02X:0x%02X].', sel.SensorNumber, sel.EventData2, sel.EventData3)
    end

    -- 根据输入的 v 的 16bit 逐位进行 assert 和 deassert 判断
    local mask
    for i = 1, 16 do
        sel.EventData1 = i - 1
        mask = 1 << (i - 1)
        if self.mdb_obj.AssertMask & mask ~= 0 and v & mask ~= 0 then
            sel.EventDir = utils.EVENT_DIR_ASSERT
            self:assert_discrete_event(sel)
        end
        if self.mdb_obj.DeassertMask & mask ~= 0 and v & mask == 0 then
            sel.EventDir = utils.EVENT_DIR_DEASSERT
            self:deassert_discrete_event(sel)
        end
    end
end

function sensor_instance:mock(initiator, enable, value)
    if not self.is_mock_mode and enable == 0 then
        log:info('sensor %s is already out of mock state.', self.mdb_obj.SensorName)
        return
    end

    local op_fmt
    local fail_flag
    if enable == 1 then
        local v = tonumber(value)
        if not v then
            log:error('sensor mock value %s is invalid.', value)
            common.op(initiator, 'Mock sensor failed: mock value is invalid')
            error(err.sensor_error_map(err.ERR_INVALID_MOCK_VALUE))
            return
        end

        local ret
        if self.sensor_clz == 'ThresholdSensor' then
            ret = self:mock_threshold_start(enable, value)
        else
            ret = self:mock_discrete_start(enable, value)
        end
        if not ret then
            op_fmt = 'Start mock (sensor=%s, enable=%d, value=%s) success'
        else
            op_fmt = 'Mock sensor failed: ' .. ret
            fail_flag = true
        end
    else
        if self.sensor_clz == 'ThresholdSensor' then
            self:mock_threshold_stop()
        else
            self:mock_discrete_stop()
        end
        op_fmt = 'Stop mock (sensor=%s, enable=%d, value=%s) success'
    end
    common.op(initiator, op_fmt, self.mdb_obj.SensorName, enable, value)
    if fail_flag then
        error(err.sensor_error_map(err.ERR_INVALID_MOCK_FAILED))
    end
end

function sensor_instance:set_rearm_assert_state(as, ds)
    local ori = self.assert_state
    local fmt = 'sensor [%s] assert state is changed from 0x%04X to 0x%04X by rearm 0x%04X'
    self.assert_state = self.assert_state & ((~as) & 0xFFFF)
    self.disp_obj.AssertStatus = self.assert_state
    shm_data_mgmt.update_sensor_prop(self.sensor_number, 'assert_status', self.disp_obj.AssertStatus)
    log:info(fmt, self.sensor_id, ori, self.assert_state, as)

    ori = self.deassert_state
    fmt = 'sensor [%s] deassert state is changed from 0x%04X to 0x%04X by rearm 0x%04X'
    self.deassert_state = self.deassert_state & ((~ds) & 0xFFFF)
    log:info(fmt, self.sensor_id, ori, self.deassert_state, ds)
end

function sensor_instance:dump_threshold_sensor()
    local thres = self:get_ts_sensor_threshold()
    thres.LNR = thres.LNR == 'null' and 'na' or thres.LNR
    thres.LCR = thres.LCR == 'null' and 'na' or thres.LCR
    thres.LNC = thres.LNC == 'null' and 'na' or thres.LNC
    thres.UNC = thres.UNC == 'null' and 'na' or thres.UNC
    thres.UCR = thres.UCR == 'null' and 'na' or thres.UCR
    thres.UNR = thres.UNR == 'null' and 'na' or thres.UNR
    thres.PH = thres.PH == 'null' and 'na' or thres.PH
    thres.NH = thres.NH == 'null' and 'na' or thres.NH

    local dump = {}
    dump[#dump + 1] = self.sensor_number -- sensor number
    dump[#dump + 1] = self.mdb_obj.SensorName -- sensor name
    local status = self:get_ts_sensor_status()
    dump[#dump + 1] = status ~= 'na' and self.disp_obj.ReadingDisplay or 'na' -- sensor reading
    dump[#dump + 1] = self.disp_obj.UnitDisplay -- unit
    dump[#dump + 1] = status -- status
    dump[#dump + 1] = thres.LNR -- lower nonrecoverable
    dump[#dump + 1] = thres.LCR -- lower critical
    dump[#dump + 1] = thres.LNC -- lower noncritical
    dump[#dump + 1] = thres.UNC -- upper noncritical
    dump[#dump + 1] = thres.UCR -- upper critical
    dump[#dump + 1] = thres.UNR -- upper nonrecoverable
    dump[#dump + 1] = thres.PH -- positive hysteresis
    dump[#dump + 1] = thres.NH -- negative hysteresis
    return dump
end

function sensor_instance:get_ds_reading()
    if not common.get_scan_enabled(self.sensor_status, true) then
        return nil
    end

    if self.mdb_obj.Reading & 0xFF00 ~= 0 then
        return nil
    end

    return self.mdb_obj.Reading & 0xFF
end

function sensor_instance:dump_discrete_sensor()
    local reading = self:get_ds_reading()
    local status = reading and sf('0x%x', self.assert_state) or 'na'
    local dump = {}
    dump[#dump + 1] = self.sensor_number -- sensor number
    dump[#dump + 1] = self.mdb_obj.SensorName -- sensor name
    dump[#dump + 1] = reading or 'na' -- sensor reading
    dump[#dump + 1] = 'discrete' -- sensor unit
    dump[#dump + 1] = status -- sensor status
    dump[#dump + 1] = 'na' -- lower nonrecoverable
    dump[#dump + 1] = 'na' -- lower critical
    dump[#dump + 1] = 'na' -- lower noncritical
    dump[#dump + 1] = 'na' -- upper noncritical
    dump[#dump + 1] = 'na' -- upper critical
    dump[#dump + 1] = 'na' -- upper nonrecoverable
    dump[#dump + 1] = 'na' -- positive hysteresis
    dump[#dump + 1] = 'na' -- negative hysteresis
    return dump
end

local function initialize_threshold_data(self)
    return {
        UNRFlag = 0,
        UCRFlag = 0,
        UNCFlag = 0,
        LNRFlag = 0,
        LCRFlag = 0,
        LNCFlag = 0,
        UNRValue = self.mdb_obj.UpperNonrecoverable,
        UCRValue = self.mdb_obj.UpperCritical,
        UNCValue = self.mdb_obj.UpperNoncritical,
        LNRValue = self.mdb_obj.LowerNonrecoverable,
        LCRValue = self.mdb_obj.LowerCritical,
        LNCValue = self.mdb_obj.LowerNoncritical
    }
end

local function process_threshold_property(self, property_name, value, threshold_data)
    local map = threshold_flag_value_map[property_name]
    if not map then
        log:error('Invalid threshold property name: %s', tostring(property_name))
        error(custom_messages.PropertyValueError(tostring(property_name)))
    end
    local raw, err_msg = common.convert_normal_to_raw(self.mdb_obj, value)
    if not raw then
        log:error('Sensor object [%s] threshold property [%s] value [%s] is invalid: %s',
            tostring(self.sensor_id), tostring(property_name), tostring(value), err_msg or 'conversion failed')
        error(custom_messages.PropertyValueOutOfRange(tostring(value), tostring(property_name)))
    end
    threshold_data[map.flag] = 1
    threshold_data[map.value] = raw & 0xFF
end

local function process_properties(self, properties_to_process, threshold_data)
    local threshold_changed = false

    for property_name, property_val in pairs(properties_to_process) do
        process_threshold_property(self, property_name, property_val, threshold_data)
        threshold_changed = true
    end

    return threshold_changed
end

function sensor_instance:set_threshold_sensor_property(initiator, property_value)
    if self.sensor_clz ~= 'ThresholdSensor' then
        error(string.format('Sensor [%d] is not a ThresholdSensor, cannot set threshold property.', self.sensor_number))
    end

    local threshold_data = initialize_threshold_data(self)
    local threshold_changed = process_properties(self, property_value, threshold_data)

    if threshold_changed then
        local ctx = { _initiator = initiator or { Interface = 'Internal' } }
        function ctx:get_initiator()
            return ctx._initiator
        end
        local ret = self:set_sensor_threshold(threshold_data, ctx)
        if ret ~= err.SUCCESS then
            error(string.format('Sensor [%d] set threshold failed, error code: %s', self.sensor_number, tostring(ret)))
        end
    end
end

return sensor_instance
