mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Reduce entity memory usage by eliminating field shadowing and bit-packing (#9076)
This commit is contained in:
		
							
								
								
									
										108
									
								
								tests/integration/fixtures/host_mode_entity_fields.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								tests/integration/fixtures/host_mode_entity_fields.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| esphome: | ||||
|   name: host-test | ||||
|  | ||||
| host: | ||||
|  | ||||
| api: | ||||
|  | ||||
| logger: | ||||
|  | ||||
| # Test various entity types with different flag combinations | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: "Test Normal Sensor" | ||||
|     id: normal_sensor | ||||
|     update_interval: 1s | ||||
|     lambda: |- | ||||
|       return 42.0; | ||||
|  | ||||
|   - platform: template | ||||
|     name: "Test Internal Sensor" | ||||
|     id: internal_sensor | ||||
|     internal: true | ||||
|     update_interval: 1s | ||||
|     lambda: |- | ||||
|       return 43.0; | ||||
|  | ||||
|   - platform: template | ||||
|     name: "Test Disabled Sensor" | ||||
|     id: disabled_sensor | ||||
|     disabled_by_default: true | ||||
|     update_interval: 1s | ||||
|     lambda: |- | ||||
|       return 44.0; | ||||
|  | ||||
|   - platform: template | ||||
|     name: "Test Mixed Flags Sensor" | ||||
|     id: mixed_flags_sensor | ||||
|     internal: true | ||||
|     entity_category: diagnostic | ||||
|     update_interval: 1s | ||||
|     lambda: |- | ||||
|       return 45.0; | ||||
|  | ||||
|   - platform: template | ||||
|     name: "Test Diagnostic Sensor" | ||||
|     id: diagnostic_sensor | ||||
|     entity_category: diagnostic | ||||
|     update_interval: 1s | ||||
|     lambda: |- | ||||
|       return 46.0; | ||||
|  | ||||
|   - platform: template | ||||
|     name: "Test All Flags Sensor" | ||||
|     id: all_flags_sensor | ||||
|     internal: true | ||||
|     disabled_by_default: true | ||||
|     entity_category: diagnostic | ||||
|     update_interval: 1s | ||||
|     lambda: |- | ||||
|       return 47.0; | ||||
|  | ||||
| # Also test other entity types to ensure bit-packing works across all | ||||
| binary_sensor: | ||||
|   - platform: template | ||||
|     name: "Test Binary Sensor" | ||||
|     entity_category: config | ||||
|     lambda: |- | ||||
|       return true; | ||||
|  | ||||
| text_sensor: | ||||
|   - platform: template | ||||
|     name: "Test Text Sensor" | ||||
|     disabled_by_default: true | ||||
|     lambda: |- | ||||
|       return {"Hello"}; | ||||
|  | ||||
| number: | ||||
|   - platform: template | ||||
|     name: "Test Number" | ||||
|     initial_value: 50 | ||||
|     min_value: 0 | ||||
|     max_value: 100 | ||||
|     step: 1 | ||||
|     optimistic: true | ||||
|     entity_category: diagnostic | ||||
|  | ||||
| select: | ||||
|   - platform: template | ||||
|     name: "Test Select" | ||||
|     options: | ||||
|       - "Option 1" | ||||
|       - "Option 2" | ||||
|     initial_option: "Option 1" | ||||
|     optimistic: true | ||||
|     internal: true | ||||
|  | ||||
| switch: | ||||
|   - platform: template | ||||
|     name: "Test Switch" | ||||
|     optimistic: true | ||||
|     disabled_by_default: true | ||||
|     entity_category: config | ||||
|  | ||||
| button: | ||||
|   - platform: template | ||||
|     name: "Test Button" | ||||
|     on_press: | ||||
|       - logger.log: "Button pressed" | ||||
							
								
								
									
										93
									
								
								tests/integration/test_host_mode_entity_fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								tests/integration/test_host_mode_entity_fields.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| """Integration test for entity bit-packed fields.""" | ||||
|  | ||||
| from __future__ import annotations | ||||
|  | ||||
| import asyncio | ||||
|  | ||||
| from aioesphomeapi import EntityCategory, EntityState | ||||
| import pytest | ||||
|  | ||||
| from .types import APIClientConnectedFactory, RunCompiledFunction | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_host_mode_entity_fields( | ||||
|     yaml_config: str, | ||||
|     run_compiled: RunCompiledFunction, | ||||
|     api_client_connected: APIClientConnectedFactory, | ||||
| ) -> None: | ||||
|     """Test entity bit-packed fields work correctly with all possible values.""" | ||||
|     # Write, compile and run the ESPHome device, then connect to API | ||||
|     async with run_compiled(yaml_config), api_client_connected() as client: | ||||
|         # Get all entities | ||||
|         entities = await client.list_entities_services() | ||||
|  | ||||
|         # Create a map of entity names to entity info | ||||
|         entity_map = {} | ||||
|         for entity in entities[0]: | ||||
|             if hasattr(entity, "name"): | ||||
|                 entity_map[entity.name] = entity | ||||
|  | ||||
|         # Test entities that should be visible via API (non-internal) | ||||
|         visible_test_cases = [ | ||||
|             # (entity_name, expected_disabled_by_default, expected_entity_category) | ||||
|             ("Test Normal Sensor", False, EntityCategory.NONE), | ||||
|             ("Test Disabled Sensor", True, EntityCategory.NONE), | ||||
|             ("Test Diagnostic Sensor", False, EntityCategory.DIAGNOSTIC), | ||||
|             ("Test Switch", True, EntityCategory.CONFIG), | ||||
|             ("Test Binary Sensor", False, EntityCategory.CONFIG), | ||||
|             ("Test Number", False, EntityCategory.DIAGNOSTIC), | ||||
|         ] | ||||
|  | ||||
|         # Test entities that should NOT be visible via API (internal) | ||||
|         internal_entities = [ | ||||
|             "Test Internal Sensor", | ||||
|             "Test Mixed Flags Sensor", | ||||
|             "Test All Flags Sensor", | ||||
|             "Test Select", | ||||
|         ] | ||||
|  | ||||
|         # Verify visible entities | ||||
|         for entity_name, expected_disabled, expected_category in visible_test_cases: | ||||
|             assert entity_name in entity_map, ( | ||||
|                 f"Entity '{entity_name}' not found - it should be visible via API" | ||||
|             ) | ||||
|             entity = entity_map[entity_name] | ||||
|  | ||||
|             # Check disabled_by_default flag | ||||
|             assert entity.disabled_by_default == expected_disabled, ( | ||||
|                 f"{entity_name}: disabled_by_default flag mismatch - " | ||||
|                 f"expected {expected_disabled}, got {entity.disabled_by_default}" | ||||
|             ) | ||||
|  | ||||
|             # Check entity_category | ||||
|             assert entity.entity_category == expected_category, ( | ||||
|                 f"{entity_name}: entity_category mismatch - " | ||||
|                 f"expected {expected_category}, got {entity.entity_category}" | ||||
|             ) | ||||
|  | ||||
|         # Verify internal entities are NOT visible | ||||
|         for entity_name in internal_entities: | ||||
|             assert entity_name not in entity_map, ( | ||||
|                 f"Entity '{entity_name}' found in API response - " | ||||
|                 f"internal entities should not be exposed via API" | ||||
|             ) | ||||
|  | ||||
|         # Subscribe to states to verify has_state flag works | ||||
|         states: dict[int, EntityState] = {} | ||||
|         state_received = asyncio.Event() | ||||
|  | ||||
|         def on_state(state: EntityState) -> None: | ||||
|             states[state.key] = state | ||||
|             state_received.set() | ||||
|  | ||||
|         client.subscribe_states(on_state) | ||||
|  | ||||
|         # Wait for at least one state | ||||
|         try: | ||||
|             await asyncio.wait_for(state_received.wait(), timeout=5.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail("No states received within 5 seconds") | ||||
|  | ||||
|         # Verify we received states (which means has_state flag is working) | ||||
|         assert len(states) > 0, "No states received - has_state flag may not be working" | ||||
		Reference in New Issue
	
	Block a user