@@ -4013,5 +4013,195 @@ test.register_coroutine_test(
40134013 }
40144014)
40154015
4016+ -- Battery / batteryLevel / chargingState profile and handler tests. These use a fake device
4017+ -- (like the volume range tests above) so that PowerSource combinations can be varied freely.
4018+ local function build_fake_power_source_device (opts )
4019+ local fields_store = {}
4020+ if opts .percent ~= nil or opts .level ~= nil then
4021+ fields_store [camera_fields .BATTERY_SUPPORT_BY_ENDPOINT ] = { [" 0" ] = { percent = opts .percent , level = opts .level } }
4022+ end
4023+ return {
4024+ profile = { components = {} },
4025+ endpoints = {},
4026+ emitted_events = {},
4027+ get_field = function (_ , field ) return fields_store [field ] end ,
4028+ set_field = function (_ , field , value ) fields_store [field ] = value end ,
4029+ get_endpoints = function (_ , cluster_id , filter )
4030+ if cluster_id == clusters .PowerSource .ID and filter and filter .feature_bitmap ==
4031+ clusters .PowerSource .types .PowerSourceFeature .RECHARGEABLE and opts .rechargeable then
4032+ return { 0 }
4033+ end
4034+ return {}
4035+ end ,
4036+ supports_capability = function () return false end ,
4037+ try_update_metadata = function (self , metadata ) self .updated_metadata = metadata end ,
4038+ emit_event_for_endpoint = function (self , _ , event ) table.insert (self .emitted_events , event ) end ,
4039+ }
4040+ end
4041+
4042+ local function power_source_attribute_list_ib (attribute_ids , endpoint_id )
4043+ local elements = {}
4044+ for _ , id in ipairs (attribute_ids ) do table.insert (elements , { value = id }) end
4045+ return { endpoint_id = endpoint_id or 0 , data = { elements = elements } }
4046+ end
4047+
4048+ local BAT_PERCENT_ID = clusters .PowerSource .attributes .BatPercentRemaining .ID
4049+ local BAT_LEVEL_ID = clusters .PowerSource .attributes .BatChargeLevel .ID
4050+ local BAT_STATE_ID = clusters .PowerSource .attributes .BatChargeState .ID
4051+
4052+ local power_source_profile_cases = {
4053+ { name = " neither battery attribute nor RECHG" , opts = {}, battery = false , batteryLevel = false , chargingState = false },
4054+ { name = " battery percentage only" , opts = { percent = true }, battery = true , batteryLevel = false , chargingState = false },
4055+ { name = " battery level only" , opts = { level = true }, battery = false , batteryLevel = true , chargingState = false },
4056+ { name = " percentage and level (percentage preferred)" , opts = { percent = true , level = true }, battery = true , batteryLevel = false , chargingState = false },
4057+ { name = " RECHG only" , opts = { rechargeable = true }, battery = false , batteryLevel = false , chargingState = true },
4058+ { name = " percentage and RECHG" , opts = { percent = true , rechargeable = true }, battery = true , batteryLevel = false , chargingState = true },
4059+ { name = " level and RECHG" , opts = { level = true , rechargeable = true }, battery = false , batteryLevel = true , chargingState = true },
4060+ { name = " percentage, level and RECHG" , opts = { percent = true , level = true , rechargeable = true }, battery = true , batteryLevel = false , chargingState = true },
4061+ }
4062+
4063+ for _ , case in ipairs (power_source_profile_cases ) do
4064+ test .register_coroutine_test (
4065+ " match_profile should set battery capabilities correctly for: " .. case .name ,
4066+ function ()
4067+ local camera_cfg = require " sub_drivers.camera.camera_utils.device_configuration"
4068+ local fake_device = build_fake_power_source_device (case .opts )
4069+
4070+ camera_cfg .match_profile (fake_device )
4071+
4072+ assert (fake_device .updated_metadata ~= nil , " profile update should be requested" )
4073+ local main_capabilities = component_capabilities (fake_device .updated_metadata , " main" )
4074+ assert (list_contains (main_capabilities , capabilities .battery .ID ) == case .battery , " unexpected battery presence" )
4075+ assert (list_contains (main_capabilities , capabilities .batteryLevel .ID ) == case .batteryLevel , " unexpected batteryLevel presence" )
4076+ assert (list_contains (main_capabilities , capabilities .chargingState .ID ) == case .chargingState , " unexpected chargingState presence" )
4077+ end ,
4078+ {
4079+ min_api_version = 14
4080+ }
4081+ )
4082+ end
4083+
4084+ local attribute_list_cases = {
4085+ { name = " percentage then level" , ids = { BAT_PERCENT_ID , BAT_LEVEL_ID }, percent = true , level = true },
4086+ { name = " level then percentage (out of order)" , ids = { 0x00 , BAT_LEVEL_ID , 0x01 , BAT_PERCENT_ID }, percent = true , level = true },
4087+ { name = " percentage only" , ids = { 0x00 , BAT_PERCENT_ID , BAT_STATE_ID }, percent = true , level = false },
4088+ { name = " level only" , ids = { 0x00 , BAT_LEVEL_ID , BAT_STATE_ID }, percent = false , level = true },
4089+ { name = " neither" , ids = { 0x00 , 0x01 , BAT_STATE_ID }, percent = false , level = false },
4090+ }
4091+
4092+ for _ , case in ipairs (attribute_list_cases ) do
4093+ test .register_coroutine_test (
4094+ " PowerSource AttributeList handler should record battery attribute support for: " .. case .name ,
4095+ function ()
4096+ local attribute_handlers = require " sub_drivers.camera.camera_handlers.attribute_handlers"
4097+ local fake_device = build_fake_power_source_device ({})
4098+
4099+ attribute_handlers .power_source_attribute_list_handler ({}, fake_device , power_source_attribute_list_ib (case .ids ))
4100+
4101+ local stored = (fake_device :get_field (camera_fields .BATTERY_SUPPORT_BY_ENDPOINT ) or {})[" 0" ] or {}
4102+ assert ((stored .percent or false ) == case .percent , " unexpected percentage support" )
4103+ assert ((stored .level or false ) == case .level , " unexpected level support" )
4104+ if case .percent or case .level then
4105+ local main_capabilities = component_capabilities (fake_device .updated_metadata , " main" )
4106+ assert (list_contains (main_capabilities , capabilities .battery .ID ) == case .percent , " battery should follow percentage support" )
4107+ assert (list_contains (main_capabilities , capabilities .batteryLevel .ID ) == (case .level and not case .percent ), " batteryLevel only used without percentage" )
4108+ end
4109+ end ,
4110+ {
4111+ min_api_version = 14
4112+ }
4113+ )
4114+ end
4115+
4116+ test .register_coroutine_test (
4117+ " PowerSource AttributeList handler should not request a profile update when support is unchanged" ,
4118+ function ()
4119+ local attribute_handlers = require " sub_drivers.camera.camera_handlers.attribute_handlers"
4120+ local fake_device = build_fake_power_source_device ({ percent = true , level = false })
4121+
4122+ attribute_handlers .power_source_attribute_list_handler ({}, fake_device , power_source_attribute_list_ib ({ BAT_PERCENT_ID }))
4123+
4124+ assert (fake_device .updated_metadata == nil , " no profile update expected when support is unchanged" )
4125+ end ,
4126+ {
4127+ min_api_version = 14
4128+ }
4129+ )
4130+
4131+ local multi_endpoint_cases = {
4132+ { name = " battery endpoint reports before wired endpoint" , order = { 2 , 1 } },
4133+ { name = " wired endpoint reports before battery endpoint" , order = { 1 , 2 } },
4134+ }
4135+
4136+ for _ , case in ipairs (multi_endpoint_cases ) do
4137+ test .register_coroutine_test (
4138+ " PowerSource AttributeList reports from multiple endpoints should not overwrite each other: " .. case .name ,
4139+ function ()
4140+ local attribute_handlers = require " sub_drivers.camera.camera_handlers.attribute_handlers"
4141+ local fake_device = build_fake_power_source_device ({})
4142+ local reports = {
4143+ [1 ] = power_source_attribute_list_ib ({ 0x00 , 0x01 }, 1 ), -- wired: no battery attributes
4144+ [2 ] = power_source_attribute_list_ib ({ 0x00 , BAT_LEVEL_ID , BAT_PERCENT_ID }, 2 ), -- battery
4145+ }
4146+
4147+ for _ , ep in ipairs (case .order ) do
4148+ attribute_handlers .power_source_attribute_list_handler ({}, fake_device , reports [ep ])
4149+ end
4150+
4151+ -- re-evaluate the profile from the stored state, as any later reconcile would
4152+ fake_device .updated_metadata = nil
4153+ require (" sub_drivers.camera.camera_utils.device_configuration" ).match_profile (fake_device )
4154+ local main_capabilities = component_capabilities (fake_device .updated_metadata , " main" )
4155+ assert (list_contains (main_capabilities , capabilities .battery .ID ), " battery should be supported regardless of report order" )
4156+ assert (not list_contains (main_capabilities , capabilities .batteryLevel .ID ), " batteryLevel should not be used when percentage is supported" )
4157+ end ,
4158+ {
4159+ min_api_version = 14
4160+ }
4161+ )
4162+ end
4163+
4164+ test .register_coroutine_test (
4165+ " PowerSource AttributeList should clear support when the same endpoint later drops the attributes" ,
4166+ function ()
4167+ local attribute_handlers = require " sub_drivers.camera.camera_handlers.attribute_handlers"
4168+ local fake_device = build_fake_power_source_device ({})
4169+
4170+ attribute_handlers .power_source_attribute_list_handler ({}, fake_device , power_source_attribute_list_ib ({ BAT_PERCENT_ID }, 2 ))
4171+ attribute_handlers .power_source_attribute_list_handler ({}, fake_device , power_source_attribute_list_ib ({ 0x00 }, 2 ))
4172+
4173+ local stored = fake_device :get_field (camera_fields .BATTERY_SUPPORT_BY_ENDPOINT )[" 2" ]
4174+ assert (stored .percent == false and stored .level == false , " support should be cleared for that endpoint" )
4175+ end ,
4176+ {
4177+ min_api_version = 14
4178+ }
4179+ )
4180+
4181+ local charge_state_cases = {
4182+ { value = clusters .PowerSource .types .BatChargeStateEnum .IS_CHARGING , expected = " charging" },
4183+ { value = clusters .PowerSource .types .BatChargeStateEnum .IS_AT_FULL_CHARGE , expected = " fullyCharged" },
4184+ { value = clusters .PowerSource .types .BatChargeStateEnum .IS_NOT_CHARGING , expected = " stopped" },
4185+ { value = clusters .PowerSource .types .BatChargeStateEnum .UNKNOWN , expected = " error" },
4186+ }
4187+
4188+ for _ , case in ipairs (charge_state_cases ) do
4189+ test .register_coroutine_test (
4190+ " BatChargeState " .. case .expected .. " should emit chargingState " .. case .expected ,
4191+ function ()
4192+ local attribute_handlers = require " sub_drivers.camera.camera_handlers.attribute_handlers"
4193+ local fake_device = build_fake_power_source_device ({ rechargeable = true })
4194+
4195+ attribute_handlers .bat_charge_state_handler ({}, fake_device , { endpoint_id = 0 , data = { value = case .value } })
4196+
4197+ assert (# fake_device .emitted_events == 1 , " exactly one event expected" )
4198+ assert (fake_device .emitted_events [1 ].value .value == case .expected , " unexpected chargingState value" )
4199+ end ,
4200+ {
4201+ min_api_version = 14
4202+ }
4203+ )
4204+ end
4205+
40164206-- run the tests
40174207test .run_registered_tests ()
0 commit comments