1212 STATE_UNAVAILABLE ,
1313 STATE_UNKNOWN ,
1414 UnitOfEnergy ,
15+ UnitOfPower ,
1516 UnitOfVolume ,
1617)
1718from homeassistant .core import HomeAssistant , callback , valid_entity_id
2324ENERGY_USAGE_UNITS : dict [str , tuple [UnitOfEnergy , ...]] = {
2425 sensor .SensorDeviceClass .ENERGY : tuple (UnitOfEnergy )
2526}
27+ POWER_USAGE_DEVICE_CLASSES = (sensor .SensorDeviceClass .POWER ,)
28+ POWER_USAGE_UNITS : dict [str , tuple [UnitOfPower , ...]] = {
29+ sensor .SensorDeviceClass .POWER : tuple (UnitOfPower )
30+ }
2631
2732ENERGY_PRICE_UNITS = tuple (
2833 f"/{ unit } " for units in ENERGY_USAGE_UNITS .values () for unit in units
2934)
3035ENERGY_UNIT_ERROR = "entity_unexpected_unit_energy"
3136ENERGY_PRICE_UNIT_ERROR = "entity_unexpected_unit_energy_price"
37+ POWER_UNIT_ERROR = "entity_unexpected_unit_power"
3238GAS_USAGE_DEVICE_CLASSES = (
3339 sensor .SensorDeviceClass .ENERGY ,
3440 sensor .SensorDeviceClass .GAS ,
@@ -82,6 +88,10 @@ def _get_placeholders(hass: HomeAssistant, issue_type: str) -> dict[str, str] |
8288 f"{ currency } { unit } " for unit in ENERGY_PRICE_UNITS
8389 ),
8490 }
91+ if issue_type == POWER_UNIT_ERROR :
92+ return {
93+ "power_units" : ", " .join (POWER_USAGE_UNITS [sensor .SensorDeviceClass .POWER ]),
94+ }
8595 if issue_type == GAS_UNIT_ERROR :
8696 return {
8797 "energy_units" : ", " .join (GAS_USAGE_UNITS [sensor .SensorDeviceClass .ENERGY ]),
@@ -159,45 +169,49 @@ def as_dict(self) -> dict:
159169
160170
161171@callback
162- def _async_validate_usage_stat (
172+ def _async_validate_stat_common (
163173 hass : HomeAssistant ,
164174 metadata : dict [str , tuple [int , recorder .models .StatisticMetaData ]],
165175 stat_id : str ,
166176 allowed_device_classes : Sequence [str ],
167177 allowed_units : Mapping [str , Sequence [str ]],
168178 unit_error : str ,
169179 issues : ValidationIssues ,
170- ) -> None :
171- """Validate a statistic."""
180+ check_negative : bool = False ,
181+ ) -> str | None :
182+ """Validate common aspects of a statistic.
183+
184+ Returns the entity_id if validation succeeds, None otherwise.
185+ """
172186 if stat_id not in metadata :
173187 issues .add_issue (hass , "statistics_not_defined" , stat_id )
174188
175189 has_entity_source = valid_entity_id (stat_id )
176190
177191 if not has_entity_source :
178- return
192+ return None
179193
180194 entity_id = stat_id
181195
182196 if not recorder .is_entity_recorded (hass , entity_id ):
183197 issues .add_issue (hass , "recorder_untracked" , entity_id )
184- return
198+ return None
185199
186200 if (state := hass .states .get (entity_id )) is None :
187201 issues .add_issue (hass , "entity_not_defined" , entity_id )
188- return
202+ return None
189203
190204 if state .state in (STATE_UNAVAILABLE , STATE_UNKNOWN ):
191205 issues .add_issue (hass , "entity_unavailable" , entity_id , state .state )
192- return
206+ return None
193207
194208 try :
195209 current_value : float | None = float (state .state )
196210 except ValueError :
197211 issues .add_issue (hass , "entity_state_non_numeric" , entity_id , state .state )
198- return
212+ return None
199213
200- if current_value is not None and current_value < 0 :
214+ if check_negative and current_value is not None and current_value < 0 :
201215 issues .add_issue (hass , "entity_negative_state" , entity_id , current_value )
202216
203217 device_class = state .attributes .get (ATTR_DEVICE_CLASS )
@@ -211,6 +225,36 @@ def _async_validate_usage_stat(
211225 if device_class and unit not in allowed_units .get (device_class , []):
212226 issues .add_issue (hass , unit_error , entity_id , unit )
213227
228+ return entity_id
229+
230+
231+ @callback
232+ def _async_validate_usage_stat (
233+ hass : HomeAssistant ,
234+ metadata : dict [str , tuple [int , recorder .models .StatisticMetaData ]],
235+ stat_id : str ,
236+ allowed_device_classes : Sequence [str ],
237+ allowed_units : Mapping [str , Sequence [str ]],
238+ unit_error : str ,
239+ issues : ValidationIssues ,
240+ ) -> None :
241+ """Validate a statistic."""
242+ entity_id = _async_validate_stat_common (
243+ hass ,
244+ metadata ,
245+ stat_id ,
246+ allowed_device_classes ,
247+ allowed_units ,
248+ unit_error ,
249+ issues ,
250+ check_negative = True ,
251+ )
252+
253+ if entity_id is None :
254+ return
255+
256+ state = hass .states .get (entity_id )
257+ assert state is not None
214258 state_class = state .attributes .get (sensor .ATTR_STATE_CLASS )
215259
216260 allowed_state_classes = [
@@ -255,6 +299,39 @@ def _async_validate_price_entity(
255299 issues .add_issue (hass , unit_error , entity_id , unit )
256300
257301
302+ @callback
303+ def _async_validate_power_stat (
304+ hass : HomeAssistant ,
305+ metadata : dict [str , tuple [int , recorder .models .StatisticMetaData ]],
306+ stat_id : str ,
307+ allowed_device_classes : Sequence [str ],
308+ allowed_units : Mapping [str , Sequence [str ]],
309+ unit_error : str ,
310+ issues : ValidationIssues ,
311+ ) -> None :
312+ """Validate a power statistic."""
313+ entity_id = _async_validate_stat_common (
314+ hass ,
315+ metadata ,
316+ stat_id ,
317+ allowed_device_classes ,
318+ allowed_units ,
319+ unit_error ,
320+ issues ,
321+ check_negative = False ,
322+ )
323+
324+ if entity_id is None :
325+ return
326+
327+ state = hass .states .get (entity_id )
328+ assert state is not None
329+ state_class = state .attributes .get (sensor .ATTR_STATE_CLASS )
330+
331+ if state_class != sensor .SensorStateClass .MEASUREMENT :
332+ issues .add_issue (hass , "entity_unexpected_state_class" , entity_id , state_class )
333+
334+
258335@callback
259336def _async_validate_cost_stat (
260337 hass : HomeAssistant ,
@@ -434,6 +511,21 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
434511 )
435512 )
436513
514+ for power_stat in source .get ("power" , []):
515+ wanted_statistics_metadata .add (power_stat ["stat_rate" ])
516+ validate_calls .append (
517+ functools .partial (
518+ _async_validate_power_stat ,
519+ hass ,
520+ statistics_metadata ,
521+ power_stat ["stat_rate" ],
522+ POWER_USAGE_DEVICE_CLASSES ,
523+ POWER_USAGE_UNITS ,
524+ POWER_UNIT_ERROR ,
525+ source_result ,
526+ )
527+ )
528+
437529 elif source ["type" ] == "gas" :
438530 wanted_statistics_metadata .add (source ["stat_energy_from" ])
439531 validate_calls .append (
0 commit comments