mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	[sensor] Optimize sliding window filters to eliminate heap fragmentation (#11282)
This commit is contained in:
		
							
								
								
									
										58
									
								
								tests/integration/fixtures/sensor_filters_batch_window.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								tests/integration/fixtures/sensor_filters_batch_window.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| esphome: | ||||
|   name: test-batch-window-filters | ||||
|  | ||||
| host: | ||||
| api: | ||||
|   batch_delay: 0ms  # Disable batching to receive all state updates | ||||
| logger: | ||||
|   level: DEBUG | ||||
|  | ||||
| # Template sensor that we'll use to publish values | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: "Source Sensor" | ||||
|     id: source_sensor | ||||
|     accuracy_decimals: 2 | ||||
|  | ||||
|   # Batch window filters (window_size == send_every) - use streaming filters | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Min Sensor" | ||||
|     id: min_sensor | ||||
|     filters: | ||||
|       - min: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|  | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Max Sensor" | ||||
|     id: max_sensor | ||||
|     filters: | ||||
|       - max: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|  | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Moving Avg Sensor" | ||||
|     id: moving_avg_sensor | ||||
|     filters: | ||||
|       - sliding_window_moving_average: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|  | ||||
| # Button to trigger publishing test values | ||||
| button: | ||||
|   - platform: template | ||||
|     name: "Publish Values Button" | ||||
|     id: publish_button | ||||
|     on_press: | ||||
|       - lambda: |- | ||||
|           // Publish 10 values: 1.0, 2.0, ..., 10.0 | ||||
|           for (int i = 1; i <= 10; i++) { | ||||
|             id(source_sensor).publish_state(float(i)); | ||||
|           } | ||||
							
								
								
									
										84
									
								
								tests/integration/fixtures/sensor_filters_nan_handling.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								tests/integration/fixtures/sensor_filters_nan_handling.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| esphome: | ||||
|   name: test-nan-handling | ||||
|  | ||||
| host: | ||||
| api: | ||||
|   batch_delay: 0ms  # Disable batching to receive all state updates | ||||
| logger: | ||||
|   level: DEBUG | ||||
|  | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: "Source NaN Sensor" | ||||
|     id: source_nan_sensor | ||||
|     accuracy_decimals: 2 | ||||
|  | ||||
|   - platform: copy | ||||
|     source_id: source_nan_sensor | ||||
|     name: "Min NaN Sensor" | ||||
|     id: min_nan_sensor | ||||
|     filters: | ||||
|       - min: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|  | ||||
|   - platform: copy | ||||
|     source_id: source_nan_sensor | ||||
|     name: "Max NaN Sensor" | ||||
|     id: max_nan_sensor | ||||
|     filters: | ||||
|       - max: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|  | ||||
| script: | ||||
|   - id: publish_nan_values_script | ||||
|     then: | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: 10.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: !lambda 'return NAN;' | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: 5.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: !lambda 'return NAN;' | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: 15.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: 8.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: !lambda 'return NAN;' | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: 12.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: 3.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_nan_sensor | ||||
|           state: !lambda 'return NAN;' | ||||
|  | ||||
| button: | ||||
|   - platform: template | ||||
|     name: "Publish NaN Values Button" | ||||
|     id: publish_nan_button | ||||
|     on_press: | ||||
|       - script.execute: publish_nan_values_script | ||||
							
								
								
									
										115
									
								
								tests/integration/fixtures/sensor_filters_ring_buffer.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								tests/integration/fixtures/sensor_filters_ring_buffer.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | ||||
| esphome: | ||||
|   name: test-sliding-window-filters | ||||
|  | ||||
| host: | ||||
| api: | ||||
|   batch_delay: 0ms  # Disable batching to receive all state updates | ||||
| logger: | ||||
|   level: DEBUG | ||||
|  | ||||
| # Template sensor that we'll use to publish values | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: "Source Sensor" | ||||
|     id: source_sensor | ||||
|     accuracy_decimals: 2 | ||||
|  | ||||
|   # ACTUAL sliding window filters (window_size != send_every) - use ring buffers | ||||
|   # Window of 5, send every 2 values | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Sliding Min Sensor" | ||||
|     id: sliding_min_sensor | ||||
|     filters: | ||||
|       - min: | ||||
|           window_size: 5 | ||||
|           send_every: 2 | ||||
|           send_first_at: 1 | ||||
|  | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Sliding Max Sensor" | ||||
|     id: sliding_max_sensor | ||||
|     filters: | ||||
|       - max: | ||||
|           window_size: 5 | ||||
|           send_every: 2 | ||||
|           send_first_at: 1 | ||||
|  | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Sliding Median Sensor" | ||||
|     id: sliding_median_sensor | ||||
|     filters: | ||||
|       - median: | ||||
|           window_size: 5 | ||||
|           send_every: 2 | ||||
|           send_first_at: 1 | ||||
|  | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Sliding Moving Avg Sensor" | ||||
|     id: sliding_moving_avg_sensor | ||||
|     filters: | ||||
|       - sliding_window_moving_average: | ||||
|           window_size: 5 | ||||
|           send_every: 2 | ||||
|           send_first_at: 1 | ||||
|  | ||||
| # Button to trigger publishing test values | ||||
| script: | ||||
|   - id: publish_values_script | ||||
|     then: | ||||
|       # Publish 10 values: 1.0, 2.0, ..., 10.0 | ||||
|       # With window_size=5, send_every=2, send_first_at=1: | ||||
|       # - Output at position 1: window=[1], min=1, max=1, median=1, avg=1 | ||||
|       # - Output at position 3: window=[1,2,3], min=1, max=3, median=2, avg=2 | ||||
|       # - Output at position 5: window=[1,2,3,4,5], min=1, max=5, median=3, avg=3 | ||||
|       # - Output at position 7: window=[3,4,5,6,7], min=3, max=7, median=5, avg=5 | ||||
|       # - Output at position 9: window=[5,6,7,8,9], min=5, max=9, median=7, avg=7 | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 1.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 2.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 3.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 4.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 5.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 6.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 7.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 8.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 9.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 10.0 | ||||
|  | ||||
| button: | ||||
|   - platform: template | ||||
|     name: "Publish Values Button" | ||||
|     id: publish_button | ||||
|     on_press: | ||||
|       - script.execute: publish_values_script | ||||
| @@ -0,0 +1,72 @@ | ||||
| esphome: | ||||
|   name: test-ring-buffer-wraparound | ||||
|  | ||||
| host: | ||||
| api: | ||||
|   batch_delay: 0ms  # Disable batching to receive all state updates | ||||
| logger: | ||||
|   level: DEBUG | ||||
|  | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: "Source Wraparound Sensor" | ||||
|     id: source_wraparound | ||||
|     accuracy_decimals: 2 | ||||
|  | ||||
|   - platform: copy | ||||
|     source_id: source_wraparound | ||||
|     name: "Wraparound Min Sensor" | ||||
|     id: wraparound_min_sensor | ||||
|     filters: | ||||
|       - min: | ||||
|           window_size: 3 | ||||
|           send_every: 3 | ||||
|           send_first_at: 1 | ||||
|  | ||||
| script: | ||||
|   - id: publish_wraparound_script | ||||
|     then: | ||||
|       # Publish 9 values to test ring buffer wraparound | ||||
|       # Values: 10, 20, 30, 5, 25, 15, 40, 35, 20 | ||||
|       - sensor.template.publish: | ||||
|           id: source_wraparound | ||||
|           state: 10.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_wraparound | ||||
|           state: 20.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_wraparound | ||||
|           state: 30.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_wraparound | ||||
|           state: 5.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_wraparound | ||||
|           state: 25.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_wraparound | ||||
|           state: 15.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_wraparound | ||||
|           state: 40.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_wraparound | ||||
|           state: 35.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_wraparound | ||||
|           state: 20.0 | ||||
|  | ||||
| button: | ||||
|   - platform: template | ||||
|     name: "Publish Wraparound Button" | ||||
|     id: publish_wraparound_button | ||||
|     on_press: | ||||
|       - script.execute: publish_wraparound_script | ||||
							
								
								
									
										123
									
								
								tests/integration/fixtures/sensor_filters_sliding_window.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								tests/integration/fixtures/sensor_filters_sliding_window.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| esphome: | ||||
|   name: test-sliding-window-filters | ||||
|  | ||||
| host: | ||||
| api: | ||||
|   batch_delay: 0ms  # Disable batching to receive all state updates | ||||
| logger: | ||||
|   level: DEBUG | ||||
|  | ||||
| # Template sensor that we'll use to publish values | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: "Source Sensor" | ||||
|     id: source_sensor | ||||
|     accuracy_decimals: 2 | ||||
|  | ||||
|   # Min filter sensor | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Min Sensor" | ||||
|     id: min_sensor | ||||
|     filters: | ||||
|       - min: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|  | ||||
|   # Max filter sensor | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Max Sensor" | ||||
|     id: max_sensor | ||||
|     filters: | ||||
|       - max: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|  | ||||
|   # Median filter sensor | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Median Sensor" | ||||
|     id: median_sensor | ||||
|     filters: | ||||
|       - median: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|  | ||||
|   # Quantile filter sensor (90th percentile) | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Quantile Sensor" | ||||
|     id: quantile_sensor | ||||
|     filters: | ||||
|       - quantile: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|           quantile: 0.9 | ||||
|  | ||||
|   # Moving average filter sensor | ||||
|   - platform: copy | ||||
|     source_id: source_sensor | ||||
|     name: "Moving Avg Sensor" | ||||
|     id: moving_avg_sensor | ||||
|     filters: | ||||
|       - sliding_window_moving_average: | ||||
|           window_size: 5 | ||||
|           send_every: 5 | ||||
|           send_first_at: 1 | ||||
|  | ||||
| # Script to publish values with delays | ||||
| script: | ||||
|   - id: publish_values_script | ||||
|     then: | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 1.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 2.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 3.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 4.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 5.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 6.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 7.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 8.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 9.0 | ||||
|       - delay: 20ms | ||||
|       - sensor.template.publish: | ||||
|           id: source_sensor | ||||
|           state: 10.0 | ||||
|  | ||||
| # Button to trigger publishing test values | ||||
| button: | ||||
|   - platform: template | ||||
|     name: "Publish Values Button" | ||||
|     id: publish_button | ||||
|     on_press: | ||||
|       - script.execute: publish_values_script | ||||
		Reference in New Issue
	
	Block a user