Compare commits

..

3 Commits

Author SHA1 Message Date
Bram Kragten
5f6b60cb78 use mode instead 2026-03-25 15:59:13 +01:00
Bram Kragten
6b61984342 Update selector.py 2026-03-25 15:32:10 +01:00
Bram Kragten
bad52f0824 Add automation behavior selector 2026-03-25 14:48:44 +01:00
342 changed files with 4737 additions and 13801 deletions

View File

@@ -12,7 +12,6 @@ from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import LIGHT_LUX, PERCENTAGE, UnitOfTemperature
@@ -41,7 +40,6 @@ SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = (
AbodeSensorDescription(
key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda device: ABODE_TEMPERATURE_UNIT_HA_UNIT[
device.temp_unit
],
@@ -50,14 +48,12 @@ SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = (
AbodeSensorDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda _: PERCENTAGE,
value_fn=lambda device: cast(float, device.humidity),
),
AbodeSensorDescription(
key="lux",
device_class=SensorDeviceClass.ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda _: LIGHT_LUX,
value_fn=lambda device: cast(float, device.lux),
),

View File

@@ -4,161 +4,370 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
mode: condition
# --- Number or entity selectors ---
.number_or_entity_co: &number_or_entity_co
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "ppm"
- "mg/m³"
- "μg/m³"
- domain: sensor
device_class: carbon_monoxide
- domain: number
device_class: carbon_monoxide
translation_key: number_or_entity
.number_or_entity_co2: &number_or_entity_co2
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "ppm"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "ppm"
- domain: sensor
device_class: carbon_dioxide
- domain: number
device_class: carbon_dioxide
translation_key: number_or_entity
.number_or_entity_pm1: &number_or_entity_pm1
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm1
- domain: number
device_class: pm1
translation_key: number_or_entity
.number_or_entity_pm25: &number_or_entity_pm25
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm25
- domain: number
device_class: pm25
translation_key: number_or_entity
.number_or_entity_pm4: &number_or_entity_pm4
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm4
- domain: number
device_class: pm4
translation_key: number_or_entity
.number_or_entity_pm10: &number_or_entity_pm10
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm10
- domain: number
device_class: pm10
translation_key: number_or_entity
.number_or_entity_ozone: &number_or_entity_ozone
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "ppm"
- "μg/m³"
- domain: sensor
device_class: ozone
- domain: number
device_class: ozone
translation_key: number_or_entity
.number_or_entity_voc: &number_or_entity_voc
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "μg/m³"
- "mg/m³"
- domain: sensor
device_class: volatile_organic_compounds
- domain: number
device_class: volatile_organic_compounds
translation_key: number_or_entity
.number_or_entity_voc_ratio: &number_or_entity_voc_ratio
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "ppm"
- domain: sensor
device_class: volatile_organic_compounds_parts
- domain: number
device_class: volatile_organic_compounds_parts
translation_key: number_or_entity
.number_or_entity_no: &number_or_entity_no
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "μg/m³"
- domain: sensor
device_class: nitrogen_monoxide
- domain: number
device_class: nitrogen_monoxide
translation_key: number_or_entity
.number_or_entity_no2: &number_or_entity_no2
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "ppm"
- "μg/m³"
- domain: sensor
device_class: nitrogen_dioxide
- domain: number
device_class: nitrogen_dioxide
translation_key: number_or_entity
.number_or_entity_n2o: &number_or_entity_n2o
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: nitrous_oxide
- domain: number
device_class: nitrous_oxide
translation_key: number_or_entity
.number_or_entity_so2: &number_or_entity_so2
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "μg/m³"
- domain: sensor
device_class: sulphur_dioxide
- domain: number
device_class: sulphur_dioxide
translation_key: number_or_entity
# --- Unit selectors ---
.unit_co: &unit_co
required: false
selector:
select:
options:
- all
- any
- "ppb"
- "ppm"
- "mg/m³"
- "μg/m³"
# --- Unit lists for multi-unit pollutants ---
.unit_ozone: &unit_ozone
required: false
selector:
select:
options:
- "ppb"
- "ppm"
- "μg/m³"
.co_units: &co_units
- "ppb"
- "ppm"
- "mg/m³"
- "μg/m³"
.unit_no2: &unit_no2
required: false
selector:
select:
options:
- "ppb"
- "ppm"
- "μg/m³"
.ozone_units: &ozone_units
- "ppb"
- "ppm"
- "μg/m³"
.unit_no: &unit_no
required: false
selector:
select:
options:
- "ppb"
- "μg/m³"
.voc_units: &voc_units
- "μg/m³"
- "mg/m³"
.unit_so2: &unit_so2
required: false
selector:
select:
options:
- "ppb"
- "μg/m³"
.voc_ratio_units: &voc_ratio_units
- "ppb"
- "ppm"
.unit_voc: &unit_voc
required: false
selector:
select:
options:
- "μg/m³"
- "mg/m³"
.no_units: &no_units
- "ppb"
- "μg/m³"
.no2_units: &no2_units
- "ppb"
- "ppm"
- "μg/m³"
.so2_units: &so2_units
- "ppb"
- "μg/m³"
# --- Entity filter anchors ---
.co_threshold_entity: &co_threshold_entity
- domain: input_number
unit_of_measurement: *co_units
- domain: sensor
device_class: carbon_monoxide
- domain: number
device_class: carbon_monoxide
.co2_threshold_entity: &co2_threshold_entity
- domain: input_number
unit_of_measurement: "ppm"
- domain: sensor
device_class: carbon_dioxide
- domain: number
device_class: carbon_dioxide
.pm1_threshold_entity: &pm1_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm1
- domain: number
device_class: pm1
.pm25_threshold_entity: &pm25_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm25
- domain: number
device_class: pm25
.pm4_threshold_entity: &pm4_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm4
- domain: number
device_class: pm4
.pm10_threshold_entity: &pm10_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm10
- domain: number
device_class: pm10
.ozone_threshold_entity: &ozone_threshold_entity
- domain: input_number
unit_of_measurement: *ozone_units
- domain: sensor
device_class: ozone
- domain: number
device_class: ozone
.voc_threshold_entity: &voc_threshold_entity
- domain: input_number
unit_of_measurement: *voc_units
- domain: sensor
device_class: volatile_organic_compounds
- domain: number
device_class: volatile_organic_compounds
.voc_ratio_threshold_entity: &voc_ratio_threshold_entity
- domain: input_number
unit_of_measurement: *voc_ratio_units
- domain: sensor
device_class: volatile_organic_compounds_parts
- domain: number
device_class: volatile_organic_compounds_parts
.no_threshold_entity: &no_threshold_entity
- domain: input_number
unit_of_measurement: *no_units
- domain: sensor
device_class: nitrogen_monoxide
- domain: number
device_class: nitrogen_monoxide
.no2_threshold_entity: &no2_threshold_entity
- domain: input_number
unit_of_measurement: *no2_units
- domain: sensor
device_class: nitrogen_dioxide
- domain: number
device_class: nitrogen_dioxide
.n2o_threshold_entity: &n2o_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: nitrous_oxide
- domain: number
device_class: nitrous_oxide
.so2_threshold_entity: &so2_threshold_entity
- domain: input_number
unit_of_measurement: *so2_units
- domain: sensor
device_class: sulphur_dioxide
- domain: number
device_class: sulphur_dioxide
# --- Number anchors for single-unit pollutants ---
.co2_threshold_number: &co2_threshold_number
mode: box
unit_of_measurement: "ppm"
.ugm3_threshold_number: &ugm3_threshold_number
mode: box
unit_of_measurement: "μg/m³"
.unit_voc_ratio: &unit_voc_ratio
required: false
selector:
select:
options:
- "ppb"
- "ppm"
# --- Binary sensor targets ---
@@ -280,99 +489,57 @@ is_co_value:
target: *target_co_sensor
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *co_threshold_entity
mode: is
number:
mode: box
unit_of_measurement: *co_units
above: *number_or_entity_co
below: *number_or_entity_co
unit: *unit_co
is_ozone_value:
target: *target_ozone
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *ozone_threshold_entity
mode: is
number:
mode: box
unit_of_measurement: *ozone_units
above: *number_or_entity_ozone
below: *number_or_entity_ozone
unit: *unit_ozone
is_voc_value:
target: *target_voc
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *voc_threshold_entity
mode: is
number:
mode: box
unit_of_measurement: *voc_units
above: *number_or_entity_voc
below: *number_or_entity_voc
unit: *unit_voc
is_voc_ratio_value:
target: *target_voc_ratio
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *voc_ratio_threshold_entity
mode: is
number:
mode: box
unit_of_measurement: *voc_ratio_units
above: *number_or_entity_voc_ratio
below: *number_or_entity_voc_ratio
unit: *unit_voc_ratio
is_no_value:
target: *target_no
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *no_threshold_entity
mode: is
number:
mode: box
unit_of_measurement: *no_units
above: *number_or_entity_no
below: *number_or_entity_no
unit: *unit_no
is_no2_value:
target: *target_no2
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *no2_threshold_entity
mode: is
number:
mode: box
unit_of_measurement: *no2_units
above: *number_or_entity_no2
below: *number_or_entity_no2
unit: *unit_no2
is_so2_value:
target: *target_so2
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *so2_threshold_entity
mode: is
number:
mode: box
unit_of_measurement: *so2_units
above: *number_or_entity_so2
below: *number_or_entity_so2
unit: *unit_so2
# --- Numerical sensor conditions without unit conversion ---
@@ -380,70 +547,40 @@ is_co2_value:
target: *target_co2
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *co2_threshold_entity
mode: is
number: *co2_threshold_number
above: *number_or_entity_co2
below: *number_or_entity_co2
is_pm1_value:
target: *target_pm1
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *pm1_threshold_entity
mode: is
number: *ugm3_threshold_number
above: *number_or_entity_pm1
below: *number_or_entity_pm1
is_pm25_value:
target: *target_pm25
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *pm25_threshold_entity
mode: is
number: *ugm3_threshold_number
above: *number_or_entity_pm25
below: *number_or_entity_pm25
is_pm4_value:
target: *target_pm4
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *pm4_threshold_entity
mode: is
number: *ugm3_threshold_number
above: *number_or_entity_pm4
below: *number_or_entity_pm4
is_pm10_value:
target: *target_pm10
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *pm10_threshold_entity
mode: is
number: *ugm3_threshold_number
above: *number_or_entity_pm10
below: *number_or_entity_pm10
is_n2o_value:
target: *target_n2o
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *n2o_threshold_entity
mode: is
number: *ugm3_threshold_number
above: *number_or_entity_n2o
below: *number_or_entity_n2o

View File

@@ -1,26 +1,41 @@
{
"common": {
"condition_above_description": "Require the value to be above this value.",
"condition_above_name": "Above",
"condition_behavior_description": "How the value should match on the targeted entities.",
"condition_behavior_name": "Behavior",
"condition_threshold_description": "What to test for and threshold values.",
"condition_threshold_name": "Threshold configuration",
"condition_below_description": "Require the value to be below this value.",
"condition_below_name": "Below",
"condition_unit_description": "All values will be converted to this unit when evaluating the condition.",
"condition_unit_name": "Unit of measurement",
"trigger_behavior_description": "The behavior of the targeted entities to trigger on.",
"trigger_behavior_name": "Behavior",
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
"trigger_threshold_name": "Threshold configuration"
"trigger_changed_above_name": "Above",
"trigger_changed_below_name": "Below",
"trigger_threshold_lower_limit_description": "The lower limit of the threshold.",
"trigger_threshold_lower_limit_name": "Lower limit",
"trigger_threshold_type_description": "The type of threshold to use.",
"trigger_threshold_type_name": "Threshold type",
"trigger_threshold_upper_limit_description": "The upper limit of the threshold.",
"trigger_threshold_upper_limit_name": "Upper limit",
"trigger_unit_description": "All values will be converted to this unit when evaluating the trigger.",
"trigger_unit_name": "Unit of measurement"
},
"conditions": {
"is_co2_value": {
"description": "Tests the carbon dioxide level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
}
},
"name": "Carbon dioxide value"
@@ -48,13 +63,21 @@
"is_co_value": {
"description": "Tests the carbon monoxide level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::condition_unit_description%]",
"name": "[%key:component::air_quality::common::condition_unit_name%]"
}
},
"name": "Carbon monoxide value"
@@ -82,13 +105,17 @@
"is_n2o_value": {
"description": "Tests the nitrous oxide level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
}
},
"name": "Nitrous oxide value"
@@ -96,13 +123,21 @@
"is_no2_value": {
"description": "Tests the nitrogen dioxide level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::condition_unit_description%]",
"name": "[%key:component::air_quality::common::condition_unit_name%]"
}
},
"name": "Nitrogen dioxide value"
@@ -110,13 +145,21 @@
"is_no_value": {
"description": "Tests the nitrogen monoxide level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::condition_unit_description%]",
"name": "[%key:component::air_quality::common::condition_unit_name%]"
}
},
"name": "Nitrogen monoxide value"
@@ -124,13 +167,21 @@
"is_ozone_value": {
"description": "Tests the ozone level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::condition_unit_description%]",
"name": "[%key:component::air_quality::common::condition_unit_name%]"
}
},
"name": "Ozone value"
@@ -138,13 +189,17 @@
"is_pm10_value": {
"description": "Tests the PM10 level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
}
},
"name": "PM10 value"
@@ -152,13 +207,17 @@
"is_pm1_value": {
"description": "Tests the PM1 level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
}
},
"name": "PM1 value"
@@ -166,13 +225,17 @@
"is_pm25_value": {
"description": "Tests the PM2.5 level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
}
},
"name": "PM2.5 value"
@@ -180,13 +243,17 @@
"is_pm4_value": {
"description": "Tests the PM4 level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
}
},
"name": "PM4 value"
@@ -214,13 +281,21 @@
"is_so2_value": {
"description": "Tests the sulphur dioxide level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::condition_unit_description%]",
"name": "[%key:component::air_quality::common::condition_unit_name%]"
}
},
"name": "Sulphur dioxide value"
@@ -228,13 +303,21 @@
"is_voc_ratio_value": {
"description": "Tests the volatile organic compounds ratio of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::condition_unit_description%]",
"name": "[%key:component::air_quality::common::condition_unit_name%]"
}
},
"name": "Volatile organic compounds ratio value"
@@ -242,13 +325,21 @@
"is_voc_value": {
"description": "Tests the volatile organic compounds level of one or more entities.",
"fields": {
"above": {
"description": "[%key:component::air_quality::common::condition_above_description%]",
"name": "[%key:component::air_quality::common::condition_above_name%]"
},
"behavior": {
"description": "[%key:component::air_quality::common::condition_behavior_description%]",
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::condition_threshold_description%]",
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
"below": {
"description": "[%key:component::air_quality::common::condition_below_description%]",
"name": "[%key:component::air_quality::common::condition_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::condition_unit_description%]",
"name": "[%key:component::air_quality::common::condition_unit_name%]"
}
},
"name": "Volatile organic compounds value"
@@ -261,12 +352,26 @@
"any": "Any"
}
},
"number_or_entity": {
"choices": {
"entity": "Entity",
"number": "Number"
}
},
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
},
"trigger_threshold_type": {
"options": {
"above": "Above",
"below": "Below",
"between": "Between",
"outside": "Outside"
}
}
},
"title": "Air Quality",
@@ -274,9 +379,13 @@
"co2_changed": {
"description": "Triggers after one or more carbon dioxide levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when carbon dioxide level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when carbon dioxide level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
}
},
"name": "Carbon dioxide level changed"
@@ -288,9 +397,17 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "Carbon dioxide level crossed threshold"
@@ -298,9 +415,17 @@
"co_changed": {
"description": "Triggers after one or more carbon monoxide levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when carbon monoxide level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when carbon monoxide level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
}
},
"name": "Carbon monoxide level changed"
@@ -322,9 +447,21 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "Carbon monoxide level crossed threshold"
@@ -362,9 +499,13 @@
"n2o_changed": {
"description": "Triggers after one or more nitrous oxide levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when nitrous oxide level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when nitrous oxide level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
}
},
"name": "Nitrous oxide level changed"
@@ -376,9 +517,17 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "Nitrous oxide level crossed threshold"
@@ -386,9 +535,17 @@
"no2_changed": {
"description": "Triggers after one or more nitrogen dioxide levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when nitrogen dioxide level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when nitrogen dioxide level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
}
},
"name": "Nitrogen dioxide level changed"
@@ -400,9 +557,21 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "Nitrogen dioxide level crossed threshold"
@@ -410,9 +579,17 @@
"no_changed": {
"description": "Triggers after one or more nitrogen monoxide levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when nitrogen monoxide level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when nitrogen monoxide level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
}
},
"name": "Nitrogen monoxide level changed"
@@ -424,9 +601,21 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "Nitrogen monoxide level crossed threshold"
@@ -434,9 +623,17 @@
"ozone_changed": {
"description": "Triggers after one or more ozone levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when ozone level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when ozone level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
}
},
"name": "Ozone level changed"
@@ -448,9 +645,21 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "Ozone level crossed threshold"
@@ -458,9 +667,13 @@
"pm10_changed": {
"description": "Triggers after one or more PM10 levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when PM10 level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when PM10 level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
}
},
"name": "PM10 level changed"
@@ -472,9 +685,17 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "PM10 level crossed threshold"
@@ -482,9 +703,13 @@
"pm1_changed": {
"description": "Triggers after one or more PM1 levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when PM1 level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when PM1 level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
}
},
"name": "PM1 level changed"
@@ -496,9 +721,17 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "PM1 level crossed threshold"
@@ -506,9 +739,13 @@
"pm25_changed": {
"description": "Triggers after one or more PM2.5 levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when PM2.5 level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when PM2.5 level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
}
},
"name": "PM2.5 level changed"
@@ -520,9 +757,17 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "PM2.5 level crossed threshold"
@@ -530,9 +775,13 @@
"pm4_changed": {
"description": "Triggers after one or more PM4 levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when PM4 level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when PM4 level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
}
},
"name": "PM4 level changed"
@@ -544,9 +793,17 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "PM4 level crossed threshold"
@@ -574,9 +831,17 @@
"so2_changed": {
"description": "Triggers after one or more sulphur dioxide levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when sulphur dioxide level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when sulphur dioxide level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
}
},
"name": "Sulphur dioxide level changed"
@@ -588,9 +853,21 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "Sulphur dioxide level crossed threshold"
@@ -598,9 +875,17 @@
"voc_changed": {
"description": "Triggers after one or more volatile organic compound levels change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when volatile organic compounds level is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when volatile organic compounds level is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
}
},
"name": "Volatile organic compounds level changed"
@@ -612,9 +897,21 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "Volatile organic compounds level crossed threshold"
@@ -622,9 +919,17 @@
"voc_ratio_changed": {
"description": "Triggers after one or more volatile organic compound ratios change.",
"fields": {
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_changed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when volatile organic compounds ratio is above this value.",
"name": "[%key:component::air_quality::common::trigger_changed_above_name%]"
},
"below": {
"description": "Only trigger when volatile organic compounds ratio is below this value.",
"name": "[%key:component::air_quality::common::trigger_changed_below_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
}
},
"name": "Volatile organic compounds ratio changed"
@@ -636,9 +941,21 @@
"description": "[%key:component::air_quality::common::trigger_behavior_description%]",
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::air_quality::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
"lower_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_lower_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_lower_limit_name%]"
},
"threshold_type": {
"description": "[%key:component::air_quality::common::trigger_threshold_type_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_type_name%]"
},
"unit": {
"description": "[%key:component::air_quality::common::trigger_unit_description%]",
"name": "[%key:component::air_quality::common::trigger_unit_name%]"
},
"upper_limit": {
"description": "[%key:component::air_quality::common::trigger_threshold_upper_limit_description%]",
"name": "[%key:component::air_quality::common::trigger_threshold_upper_limit_name%]"
}
},
"name": "Volatile organic compounds ratio crossed threshold"

View File

@@ -3,162 +3,378 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger
# --- Unit lists for multi-unit pollutants ---
.number_or_entity_co: &number_or_entity_co
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "ppm"
- "mg/m³"
- "μg/m³"
- domain: sensor
device_class: carbon_monoxide
- domain: number
device_class: carbon_monoxide
translation_key: number_or_entity
.co_units: &co_units
- "ppb"
- "ppm"
- "mg/m³"
- "μg/m³"
.number_or_entity_co2: &number_or_entity_co2
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "ppm"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "ppm"
- domain: sensor
device_class: carbon_dioxide
- domain: number
device_class: carbon_dioxide
translation_key: number_or_entity
.ozone_units: &ozone_units
- "ppb"
- "ppm"
- "μg/m³"
.number_or_entity_pm1: &number_or_entity_pm1
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm1
- domain: number
device_class: pm1
translation_key: number_or_entity
.voc_units: &voc_units
- "μg/m³"
- "mg/m³"
.number_or_entity_pm25: &number_or_entity_pm25
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm25
- domain: number
device_class: pm25
translation_key: number_or_entity
.voc_ratio_units: &voc_ratio_units
- "ppb"
- "ppm"
.number_or_entity_pm4: &number_or_entity_pm4
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm4
- domain: number
device_class: pm4
translation_key: number_or_entity
.no_units: &no_units
- "ppb"
- "μg/m³"
.number_or_entity_pm10: &number_or_entity_pm10
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm10
- domain: number
device_class: pm10
translation_key: number_or_entity
.no2_units: &no2_units
- "ppb"
- "ppm"
- "μg/m³"
.number_or_entity_ozone: &number_or_entity_ozone
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "ppm"
- "μg/m³"
- domain: sensor
device_class: ozone
- domain: number
device_class: ozone
translation_key: number_or_entity
.so2_units: &so2_units
- "ppb"
- "μg/m³"
.number_or_entity_voc: &number_or_entity_voc
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "μg/m³"
- "mg/m³"
- domain: sensor
device_class: volatile_organic_compounds
- domain: number
device_class: volatile_organic_compounds
translation_key: number_or_entity
# --- Entity filter anchors ---
.number_or_entity_voc_ratio: &number_or_entity_voc_ratio
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "ppm"
- domain: sensor
device_class: volatile_organic_compounds_parts
- domain: number
device_class: volatile_organic_compounds_parts
translation_key: number_or_entity
.co_threshold_entity: &co_threshold_entity
- domain: input_number
unit_of_measurement: *co_units
- domain: sensor
device_class: carbon_monoxide
- domain: number
device_class: carbon_monoxide
.number_or_entity_no: &number_or_entity_no
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "μg/m³"
- domain: sensor
device_class: nitrogen_monoxide
- domain: number
device_class: nitrogen_monoxide
translation_key: number_or_entity
.co2_threshold_entity: &co2_threshold_entity
- domain: input_number
unit_of_measurement: "ppm"
- domain: sensor
device_class: carbon_dioxide
- domain: number
device_class: carbon_dioxide
.number_or_entity_no2: &number_or_entity_no2
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "ppm"
- "μg/m³"
- domain: sensor
device_class: nitrogen_dioxide
- domain: number
device_class: nitrogen_dioxide
translation_key: number_or_entity
.pm1_threshold_entity: &pm1_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm1
- domain: number
device_class: pm1
.number_or_entity_n2o: &number_or_entity_n2o
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "μg/m³"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: nitrous_oxide
- domain: number
device_class: nitrous_oxide
translation_key: number_or_entity
.pm25_threshold_entity: &pm25_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm25
- domain: number
device_class: pm25
.number_or_entity_so2: &number_or_entity_so2
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "ppb"
- "μg/m³"
- domain: sensor
device_class: sulphur_dioxide
- domain: number
device_class: sulphur_dioxide
translation_key: number_or_entity
.pm4_threshold_entity: &pm4_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm4
- domain: number
device_class: pm4
.unit_co: &unit_co
required: false
selector:
select:
options:
- "ppb"
- "ppm"
- "mg/m³"
- "μg/m³"
.pm10_threshold_entity: &pm10_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: pm10
- domain: number
device_class: pm10
.unit_ozone: &unit_ozone
required: false
selector:
select:
options:
- "ppb"
- "ppm"
- "μg/m³"
.ozone_threshold_entity: &ozone_threshold_entity
- domain: input_number
unit_of_measurement: *ozone_units
- domain: sensor
device_class: ozone
- domain: number
device_class: ozone
.unit_no2: &unit_no2
required: false
selector:
select:
options:
- "ppb"
- "ppm"
- "μg/m³"
.voc_threshold_entity: &voc_threshold_entity
- domain: input_number
unit_of_measurement: *voc_units
- domain: sensor
device_class: volatile_organic_compounds
- domain: number
device_class: volatile_organic_compounds
.unit_no: &unit_no
required: false
selector:
select:
options:
- "ppb"
- "μg/m³"
.voc_ratio_threshold_entity: &voc_ratio_threshold_entity
- domain: input_number
unit_of_measurement: *voc_ratio_units
- domain: sensor
device_class: volatile_organic_compounds_parts
- domain: number
device_class: volatile_organic_compounds_parts
.unit_so2: &unit_so2
required: false
selector:
select:
options:
- "ppb"
- "μg/m³"
.no_threshold_entity: &no_threshold_entity
- domain: input_number
unit_of_measurement: *no_units
- domain: sensor
device_class: nitrogen_monoxide
- domain: number
device_class: nitrogen_monoxide
.unit_voc: &unit_voc
required: false
selector:
select:
options:
- "μg/m³"
- "mg/m³"
.no2_threshold_entity: &no2_threshold_entity
- domain: input_number
unit_of_measurement: *no2_units
- domain: sensor
device_class: nitrogen_dioxide
- domain: number
device_class: nitrogen_dioxide
.unit_voc_ratio: &unit_voc_ratio
required: false
selector:
select:
options:
- "ppb"
- "ppm"
.n2o_threshold_entity: &n2o_threshold_entity
- domain: input_number
unit_of_measurement: "μg/m³"
- domain: sensor
device_class: nitrous_oxide
- domain: number
device_class: nitrous_oxide
.so2_threshold_entity: &so2_threshold_entity
- domain: input_number
unit_of_measurement: *so2_units
- domain: sensor
device_class: sulphur_dioxide
- domain: number
device_class: sulphur_dioxide
# --- Number anchors for single-unit pollutants ---
.co2_threshold_number: &co2_threshold_number
mode: box
unit_of_measurement: "ppm"
.ugm3_threshold_number: &ugm3_threshold_number
mode: box
unit_of_measurement: "μg/m³"
.trigger_threshold_type: &trigger_threshold_type
required: true
default: above
selector:
select:
options:
- above
- below
- between
- outside
translation_key: trigger_threshold_type
# Binary sensor detected/cleared trigger fields
.trigger_binary_fields: &trigger_binary_fields
@@ -276,342 +492,198 @@ smoke_cleared:
# --- Numerical sensor triggers ---
# CO (multi-unit)
co_changed:
target: *target_co_sensor
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *co_threshold_entity
mode: changed
number:
mode: box
unit_of_measurement: *co_units
above: *number_or_entity_co
below: *number_or_entity_co
unit: *unit_co
co_crossed_threshold:
target: *target_co_sensor
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *co_threshold_entity
mode: crossed
number:
mode: box
unit_of_measurement: *co_units
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_co
upper_limit: *number_or_entity_co
unit: *unit_co
# CO2 (single-unit: ppm)
co2_changed:
target: *target_co2
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *co2_threshold_entity
mode: changed
number: *co2_threshold_number
above: *number_or_entity_co2
below: *number_or_entity_co2
co2_crossed_threshold:
target: *target_co2
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *co2_threshold_entity
mode: crossed
number: *co2_threshold_number
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_co2
upper_limit: *number_or_entity_co2
# PM1 (single-unit: μg/m³)
pm1_changed:
target: *target_pm1
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *pm1_threshold_entity
mode: changed
number: *ugm3_threshold_number
above: *number_or_entity_pm1
below: *number_or_entity_pm1
pm1_crossed_threshold:
target: *target_pm1
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *pm1_threshold_entity
mode: crossed
number: *ugm3_threshold_number
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_pm1
upper_limit: *number_or_entity_pm1
# PM2.5 (single-unit: μg/m³)
pm25_changed:
target: *target_pm25
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *pm25_threshold_entity
mode: changed
number: *ugm3_threshold_number
above: *number_or_entity_pm25
below: *number_or_entity_pm25
pm25_crossed_threshold:
target: *target_pm25
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *pm25_threshold_entity
mode: crossed
number: *ugm3_threshold_number
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_pm25
upper_limit: *number_or_entity_pm25
# PM4 (single-unit: μg/m³)
pm4_changed:
target: *target_pm4
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *pm4_threshold_entity
mode: changed
number: *ugm3_threshold_number
above: *number_or_entity_pm4
below: *number_or_entity_pm4
pm4_crossed_threshold:
target: *target_pm4
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *pm4_threshold_entity
mode: crossed
number: *ugm3_threshold_number
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_pm4
upper_limit: *number_or_entity_pm4
# PM10 (single-unit: μg/m³)
pm10_changed:
target: *target_pm10
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *pm10_threshold_entity
mode: changed
number: *ugm3_threshold_number
above: *number_or_entity_pm10
below: *number_or_entity_pm10
pm10_crossed_threshold:
target: *target_pm10
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *pm10_threshold_entity
mode: crossed
number: *ugm3_threshold_number
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_pm10
upper_limit: *number_or_entity_pm10
# Ozone (multi-unit)
ozone_changed:
target: *target_ozone
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *ozone_threshold_entity
mode: changed
number:
mode: box
unit_of_measurement: *ozone_units
above: *number_or_entity_ozone
below: *number_or_entity_ozone
unit: *unit_ozone
ozone_crossed_threshold:
target: *target_ozone
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *ozone_threshold_entity
mode: crossed
number:
mode: box
unit_of_measurement: *ozone_units
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_ozone
upper_limit: *number_or_entity_ozone
unit: *unit_ozone
# VOC (multi-unit)
voc_changed:
target: *target_voc
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *voc_threshold_entity
mode: changed
number:
mode: box
unit_of_measurement: *voc_units
above: *number_or_entity_voc
below: *number_or_entity_voc
unit: *unit_voc
voc_crossed_threshold:
target: *target_voc
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *voc_threshold_entity
mode: crossed
number:
mode: box
unit_of_measurement: *voc_units
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_voc
upper_limit: *number_or_entity_voc
unit: *unit_voc
# VOC ratio (multi-unit)
voc_ratio_changed:
target: *target_voc_ratio
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *voc_ratio_threshold_entity
mode: changed
number:
mode: box
unit_of_measurement: *voc_ratio_units
above: *number_or_entity_voc_ratio
below: *number_or_entity_voc_ratio
unit: *unit_voc_ratio
voc_ratio_crossed_threshold:
target: *target_voc_ratio
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *voc_ratio_threshold_entity
mode: crossed
number:
mode: box
unit_of_measurement: *voc_ratio_units
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_voc_ratio
upper_limit: *number_or_entity_voc_ratio
unit: *unit_voc_ratio
# NO (multi-unit)
no_changed:
target: *target_no
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *no_threshold_entity
mode: changed
number:
mode: box
unit_of_measurement: *no_units
above: *number_or_entity_no
below: *number_or_entity_no
unit: *unit_no
no_crossed_threshold:
target: *target_no
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *no_threshold_entity
mode: crossed
number:
mode: box
unit_of_measurement: *no_units
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_no
upper_limit: *number_or_entity_no
unit: *unit_no
# NO2 (multi-unit)
no2_changed:
target: *target_no2
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *no2_threshold_entity
mode: changed
number:
mode: box
unit_of_measurement: *no2_units
above: *number_or_entity_no2
below: *number_or_entity_no2
unit: *unit_no2
no2_crossed_threshold:
target: *target_no2
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *no2_threshold_entity
mode: crossed
number:
mode: box
unit_of_measurement: *no2_units
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_no2
upper_limit: *number_or_entity_no2
unit: *unit_no2
# N2O (single-unit: μg/m³)
n2o_changed:
target: *target_n2o
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *n2o_threshold_entity
mode: changed
number: *ugm3_threshold_number
above: *number_or_entity_n2o
below: *number_or_entity_n2o
n2o_crossed_threshold:
target: *target_n2o
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *n2o_threshold_entity
mode: crossed
number: *ugm3_threshold_number
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_n2o
upper_limit: *number_or_entity_n2o
# SO2 (multi-unit)
so2_changed:
target: *target_so2
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *so2_threshold_entity
mode: changed
number:
mode: box
unit_of_measurement: *so2_units
above: *number_or_entity_so2
below: *number_or_entity_so2
unit: *unit_so2
so2_crossed_threshold:
target: *target_so2
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *so2_threshold_entity
mode: crossed
number:
mode: box
unit_of_measurement: *so2_units
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_so2
upper_limit: *number_or_entity_so2
unit: *unit_so2

View File

@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"incomplete_discovery": "The discovered air-Q device did not provide a device ID. Ensure the firmware is up to date."
},
"error": {

View File

@@ -7,11 +7,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_armed: *condition_common

View File

@@ -7,12 +7,9 @@
required: true
default: any
selector:
select:
options:
- first
- last
- any
automation_behavior:
translation_key: trigger_behavior
mode: trigger
armed: *trigger_common

View File

@@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"loggers": ["pyanglianwater"],
"quality_scale": "bronze",
"requirements": ["pyanglianwater==3.1.2"]
"requirements": ["pyanglianwater==3.1.1"]
}

View File

@@ -7,11 +7,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_idle: *condition_common
is_listening: *condition_common

View File

@@ -7,12 +7,9 @@
required: true
default: any
selector:
select:
options:
- first
- last
- any
automation_behavior:
translation_key: trigger_behavior
mode: trigger
idle: *trigger_common
listening: *trigger_common

View File

@@ -136,7 +136,6 @@ _EXPERIMENTAL_CONDITION_PLATFORMS = {
"light",
"lock",
"media_player",
"moisture",
"motion",
"occupancy",
"person",
@@ -144,7 +143,6 @@ _EXPERIMENTAL_CONDITION_PLATFORMS = {
"schedule",
"siren",
"switch",
"temperature",
"text",
"vacuum",
"water_heater",
@@ -157,7 +155,6 @@ _EXPERIMENTAL_TRIGGER_PLATFORMS = {
"assist_satellite",
"button",
"climate",
"counter",
"cover",
"device_tracker",
"door",

View File

@@ -78,11 +78,11 @@
"services": {
"reload": {
"description": "Reloads the automation configuration.",
"name": "Reload automations"
"name": "[%key:common::action::reload%]"
},
"toggle": {
"description": "Toggles (enable / disable) an automation.",
"name": "Toggle automation"
"name": "[%key:common::action::toggle%]"
},
"trigger": {
"description": "Triggers the actions of an automation.",
@@ -92,7 +92,7 @@
"name": "Skip conditions"
}
},
"name": "Trigger automation"
"name": "Trigger"
},
"turn_off": {
"description": "Disables an automation.",
@@ -102,11 +102,11 @@
"name": "Stop actions"
}
},
"name": "Turn off automation"
"name": "[%key:common::action::turn_off%]"
},
"turn_on": {
"description": "Enables an automation.",
"name": "Turn on automation"
"name": "[%key:common::action::turn_on%]"
}
},
"title": "Automation"

View File

@@ -34,4 +34,4 @@ EXCLUDE_DATABASE_FROM_BACKUP = [
"home-assistant_v2.db-wal",
]
SECURETAR_CREATE_VERSION = 3
SECURETAR_CREATE_VERSION = 2

View File

@@ -8,25 +8,28 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
.battery_threshold_entity: &battery_threshold_entity
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: battery
- domain: number
device_class: battery
.battery_threshold_number: &battery_threshold_number
min: 0
max: 100
mode: box
unit_of_measurement: "%"
.number_or_entity: &number_or_entity
required: false
selector:
choose:
choices:
number:
selector:
number:
unit_of_measurement: "%"
entity:
selector:
entity:
filter:
domain:
- input_number
- number
- sensor
translation_key: number_or_entity
is_low: *condition_common
@@ -57,10 +60,5 @@ is_level:
device_class: battery
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *battery_threshold_entity
mode: is
number: *battery_threshold_number
above: *number_or_entity
below: *number_or_entity

View File

@@ -1,9 +1,7 @@
{
"common": {
"condition_behavior_description": "How the state should match on the targeted batteries.",
"condition_behavior_name": "Behavior",
"condition_threshold_description": "What to test for and threshold values.",
"condition_threshold_name": "Threshold configuration"
"condition_behavior_name": "Behavior"
},
"conditions": {
"is_charging": {
@@ -19,13 +17,17 @@
"is_level": {
"description": "Tests the battery level of one or more batteries.",
"fields": {
"above": {
"description": "Require the battery percentage to be above this value.",
"name": "Above"
},
"behavior": {
"description": "[%key:component::battery::common::condition_behavior_description%]",
"name": "[%key:component::battery::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::battery::common::condition_threshold_description%]",
"name": "[%key:component::battery::common::condition_threshold_name%]"
"below": {
"description": "Require the battery percentage to be below this value.",
"name": "Below"
}
},
"name": "Battery level"
@@ -67,6 +69,12 @@
"all": "All",
"any": "Any"
}
},
"number_or_entity": {
"choices": {
"entity": "Entity",
"number": "Number"
}
}
},
"title": "Battery"

View File

@@ -8,7 +8,7 @@
"iot_class": "local_polling",
"loggers": ["bsblan"],
"quality_scale": "silver",
"requirements": ["python-bsblan==5.1.3"],
"requirements": ["python-bsblan==5.1.2"],
"zeroconf": [
{
"name": "bsb-lan*",

View File

@@ -11,7 +11,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from .coordinator import CasperGlowConfigEntry, CasperGlowCoordinator
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.LIGHT]
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.LIGHT]
async def async_setup_entry(hass: HomeAssistant, entry: CasperGlowConfigEntry) -> bool:

View File

@@ -1,73 +0,0 @@
"""Casper Glow integration button platform."""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from pycasperglow import CasperGlow
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import CasperGlowConfigEntry, CasperGlowCoordinator
from .entity import CasperGlowEntity
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class CasperGlowButtonEntityDescription(ButtonEntityDescription):
"""Describe a Casper Glow button entity."""
press_fn: Callable[[CasperGlow], Awaitable[None]]
BUTTON_DESCRIPTIONS: tuple[CasperGlowButtonEntityDescription, ...] = (
CasperGlowButtonEntityDescription(
key="pause",
translation_key="pause",
press_fn=lambda device: device.pause(),
),
CasperGlowButtonEntityDescription(
key="resume",
translation_key="resume",
press_fn=lambda device: device.resume(),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: CasperGlowConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the button platform for Casper Glow."""
async_add_entities(
CasperGlowButton(entry.runtime_data, description)
for description in BUTTON_DESCRIPTIONS
)
class CasperGlowButton(CasperGlowEntity, ButtonEntity):
"""A Casper Glow button entity."""
entity_description: CasperGlowButtonEntityDescription
def __init__(
self,
coordinator: CasperGlowCoordinator,
description: CasperGlowButtonEntityDescription,
) -> None:
"""Initialize a Casper Glow button."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = (
f"{format_mac(coordinator.device.address)}_{description.key}"
)
async def async_press(self) -> None:
"""Press the button."""
await self._async_command(self.entity_description.press_fn(self._device))

View File

@@ -4,14 +4,6 @@
"paused": {
"default": "mdi:timer-pause"
}
},
"button": {
"pause": {
"default": "mdi:pause"
},
"resume": {
"default": "mdi:play"
}
}
}
}

View File

@@ -14,6 +14,6 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["pycasperglow"],
"quality_scale": "silver",
"quality_scale": "bronze",
"requirements": ["pycasperglow==1.1.0"]
}

View File

@@ -32,9 +32,7 @@ rules:
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow:
status: exempt
comment: Bluetooth device with no authentication credentials.
reauthentication-flow: todo
test-coverage: done
# Gold
@@ -55,9 +53,15 @@ rules:
entity-category: todo
entity-device-class: todo
entity-disabled-by-default: todo
entity-translations: done
exception-translations: done
icon-translations: done
entity-translations:
status: exempt
comment: No entity translations needed.
exception-translations:
status: exempt
comment: No custom services that raise exceptions.
icon-translations:
status: exempt
comment: No icon translations needed.
reconfiguration-flow: todo
repair-issues: todo
stale-devices: todo

View File

@@ -31,14 +31,6 @@
"paused": {
"name": "Dimming paused"
}
},
"button": {
"pause": {
"name": "Pause dimming"
},
"resume": {
"name": "Resume dimming"
}
}
},
"exceptions": {

View File

@@ -1,68 +1,20 @@
{
"entity": {
"sensor": {
"chess960_daily_draw": {
"default": "mdi:chess-pawn"
},
"chess960_daily_lost": {
"default": "mdi:chess-pawn"
},
"chess960_daily_rating": {
"default": "mdi:chart-line"
},
"chess960_daily_won": {
"default": "mdi:chess-pawn"
},
"chess_blitz_draw": {
"default": "mdi:chess-pawn"
},
"chess_blitz_lost": {
"default": "mdi:chess-pawn"
},
"chess_blitz_rating": {
"default": "mdi:chart-line"
},
"chess_blitz_won": {
"default": "mdi:chess-pawn"
},
"chess_bullet_draw": {
"default": "mdi:chess-pawn"
},
"chess_bullet_lost": {
"default": "mdi:chess-pawn"
},
"chess_bullet_rating": {
"default": "mdi:chart-line"
},
"chess_bullet_won": {
"default": "mdi:chess-pawn"
},
"chess_daily_draw": {
"default": "mdi:chess-pawn"
},
"chess_daily_lost": {
"default": "mdi:chess-pawn"
},
"chess_daily_rating": {
"default": "mdi:chart-line"
},
"chess_daily_won": {
"default": "mdi:chess-pawn"
},
"chess_rapid_draw": {
"default": "mdi:chess-pawn"
},
"chess_rapid_lost": {
"default": "mdi:chess-pawn"
},
"chess_rapid_rating": {
"default": "mdi:chart-line"
},
"chess_rapid_won": {
"default": "mdi:chess-pawn"
},
"followers": {
"default": "mdi:account-multiple"
},
"total_daily_draw": {
"default": "mdi:chess-pawn"
},
"total_daily_lost": {
"default": "mdi:chess-pawn"
},
"total_daily_won": {
"default": "mdi:chess-pawn"
}
}
}

View File

@@ -2,9 +2,6 @@
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
from chess_com_api import PlayerStats
from homeassistant.components.sensor import (
SensorEntity,
@@ -27,14 +24,7 @@ class ChessEntityDescription(SensorEntityDescription):
value_fn: Callable[[ChessData], float]
@dataclass(kw_only=True, frozen=True)
class ChessModeEntityDescription(SensorEntityDescription):
"""Sensor description for a Chess.com game mode."""
value_fn: Callable[[dict[str, Any]], float]
PLAYER_SENSORS: tuple[ChessEntityDescription, ...] = (
SENSORS: tuple[ChessEntityDescription, ...] = (
ChessEntityDescription(
key="followers",
translation_key="followers",
@@ -43,46 +33,35 @@ PLAYER_SENSORS: tuple[ChessEntityDescription, ...] = (
value_fn=lambda state: state.player.followers,
entity_registry_enabled_default=False,
),
)
GAME_MODE_SENSORS: tuple[ChessModeEntityDescription, ...] = (
ChessModeEntityDescription(
key="rating",
translation_key="rating",
ChessEntityDescription(
key="chess_daily_rating",
translation_key="chess_daily_rating",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda mode: mode["last"]["rating"],
value_fn=lambda state: state.stats.chess_daily["last"]["rating"],
),
ChessModeEntityDescription(
key="won",
translation_key="won",
ChessEntityDescription(
key="total_daily_won",
translation_key="total_daily_won",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda mode: mode["record"]["win"],
value_fn=lambda state: state.stats.chess_daily["record"]["win"],
),
ChessModeEntityDescription(
key="lost",
translation_key="lost",
ChessEntityDescription(
key="total_daily_lost",
translation_key="total_daily_lost",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda mode: mode["record"]["loss"],
value_fn=lambda state: state.stats.chess_daily["record"]["loss"],
),
ChessModeEntityDescription(
key="draw",
translation_key="draw",
ChessEntityDescription(
key="total_daily_draw",
translation_key="total_daily_draw",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda mode: mode["record"]["draw"],
value_fn=lambda state: state.stats.chess_daily["record"]["draw"],
),
)
GAME_MODES: dict[str, Callable[[PlayerStats], dict[str, Any] | None]] = {
"chess_daily": lambda stats: stats.chess_daily,
"chess_rapid": lambda stats: stats.chess_rapid,
"chess_bullet": lambda stats: stats.chess_bullet,
"chess_blitz": lambda stats: stats.chess_blitz,
"chess960_daily": lambda stats: stats.chess960_daily,
}
async def async_setup_entry(
hass: HomeAssistant,
@@ -92,22 +71,13 @@ async def async_setup_entry(
"""Initialize the entries."""
coordinator = entry.runtime_data
entities: list[SensorEntity] = [
ChessPlayerSensor(coordinator, description) for description in PLAYER_SENSORS
]
for game_mode, stats_fn in GAME_MODES.items():
if stats_fn(coordinator.data.stats) is not None:
entities.extend(
ChessGameModeSensor(coordinator, description, game_mode, stats_fn)
for description in GAME_MODE_SENSORS
)
async_add_entities(entities)
async_add_entities(
ChessPlayerSensor(coordinator, description) for description in SENSORS
)
class ChessPlayerSensor(ChessEntity, SensorEntity):
"""Chess.com player sensor."""
"""Chess.com sensor."""
entity_description: ChessEntityDescription
@@ -125,33 +95,3 @@ class ChessPlayerSensor(ChessEntity, SensorEntity):
def native_value(self) -> float:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator.data)
class ChessGameModeSensor(ChessEntity, SensorEntity):
"""Chess.com game mode sensor."""
entity_description: ChessModeEntityDescription
def __init__(
self,
coordinator: ChessCoordinator,
description: ChessModeEntityDescription,
game_mode: str,
stats_fn: Callable[[PlayerStats], dict[str, Any] | None],
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = description
self._stats_fn = stats_fn
self._attr_unique_id = (
f"{coordinator.config_entry.unique_id}.{game_mode}.{description.key}"
)
self._attr_translation_key = f"{game_mode}_{description.translation_key}"
@property
def native_value(self) -> float:
"""Return the state of the sensor."""
mode_data = self._stats_fn(self.coordinator.data.stats)
if TYPE_CHECKING:
assert mode_data is not None
return self.entity_description.value_fn(mode_data)

View File

@@ -23,84 +23,24 @@
},
"entity": {
"sensor": {
"chess960_daily_draw": {
"name": "Total daily Chess960 games drawn",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess960_daily_lost": {
"name": "Total daily Chess960 games lost",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess960_daily_rating": {
"name": "Daily Chess960 rating"
},
"chess960_daily_won": {
"name": "Total daily Chess960 games won",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_blitz_draw": {
"name": "Total blitz chess games drawn",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_blitz_lost": {
"name": "Total blitz chess games lost",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_blitz_rating": {
"name": "Blitz chess rating"
},
"chess_blitz_won": {
"name": "Total blitz chess games won",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_bullet_draw": {
"name": "Total bullet chess games drawn",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_bullet_lost": {
"name": "Total bullet chess games lost",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_bullet_rating": {
"name": "Bullet chess rating"
},
"chess_bullet_won": {
"name": "Total bullet chess games won",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_daily_draw": {
"name": "Total daily chess games drawn",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_daily_lost": {
"name": "Total daily chess games lost",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_daily_rating": {
"name": "Daily chess rating"
},
"chess_daily_won": {
"name": "Total daily chess games won",
"unit_of_measurement": "games"
},
"chess_rapid_draw": {
"name": "Total rapid chess games drawn",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_rapid_lost": {
"name": "Total rapid chess games lost",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"chess_rapid_rating": {
"name": "Rapid chess rating"
},
"chess_rapid_won": {
"name": "Total rapid chess games won",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
},
"followers": {
"name": "Followers",
"unit_of_measurement": "followers"
},
"total_daily_draw": {
"name": "Total chess games drawn",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::total_daily_won::unit_of_measurement%]"
},
"total_daily_lost": {
"name": "Total chess games lost",
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::total_daily_won::unit_of_measurement%]"
},
"total_daily_won": {
"name": "Total chess games won",
"unit_of_measurement": "games"
}
}
}

View File

@@ -7,37 +7,62 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
.humidity_threshold_entity: &humidity_threshold_entity
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: humidity
- domain: number
device_class: humidity
.number_or_entity_humidity: &number_or_entity_humidity
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "%"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: humidity
- domain: number
device_class: humidity
translation_key: number_or_entity
.humidity_threshold_number: &humidity_threshold_number
min: 0
max: 100
mode: box
unit_of_measurement: "%"
.number_or_entity_temperature: &number_or_entity_temperature
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "°C"
- "°F"
- domain: sensor
device_class: temperature
- domain: number
device_class: temperature
translation_key: number_or_entity
.temperature_units: &temperature_units
- "°C"
- "°F"
.temperature_threshold_entity: &temperature_threshold_entity
- domain: input_number
unit_of_measurement: *temperature_units
- domain: sensor
device_class: temperature
- domain: number
device_class: temperature
.condition_unit_temperature: &condition_unit_temperature
required: false
selector:
select:
options:
- "°C"
- "°F"
is_off: *condition_common
is_on: *condition_common
@@ -49,24 +74,13 @@ target_humidity:
target: *condition_climate_target
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *humidity_threshold_entity
mode: is
number: *humidity_threshold_number
above: *number_or_entity_humidity
below: *number_or_entity_humidity
target_temperature:
target: *condition_climate_target
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *temperature_threshold_entity
mode: is
number:
mode: box
unit_of_measurement: *temperature_units
above: *number_or_entity_temperature
below: *number_or_entity_temperature
unit: *condition_unit_temperature

View File

@@ -11,8 +11,7 @@ set_preset_mode:
required: true
example: "away"
selector:
state:
attribute: preset_mode
text:
set_temperature:
target:
@@ -56,10 +55,16 @@ set_temperature:
mode: box
hvac_mode:
selector:
state:
hide_states:
- unavailable
- unknown
select:
options:
- "off"
- "auto"
- "cool"
- "dry"
- "fan_only"
- "heat_cool"
- "heat"
translation_key: hvac_mode
set_humidity:
target:
entity:
@@ -86,8 +91,7 @@ set_fan_mode:
required: true
example: "low"
selector:
state:
attribute: fan_mode
text:
set_hvac_mode:
target:
@@ -111,8 +115,7 @@ set_swing_mode:
required: true
example: "on"
selector:
state:
attribute: swing_mode
text:
set_swing_horizontal_mode:
target:
@@ -125,8 +128,7 @@ set_swing_horizontal_mode:
required: true
example: "on"
selector:
state:
attribute: swing_horizontal_mode
text:
turn_on:
target:

View File

@@ -2,13 +2,8 @@
"common": {
"condition_behavior_description": "How the state should match on the targeted climate-control devices.",
"condition_behavior_name": "Behavior",
"condition_threshold_description": "What to test for and threshold values.",
"condition_threshold_name": "Threshold configuration",
"trigger_behavior_description": "The behavior of the targeted climates to trigger on.",
"trigger_behavior_name": "Behavior",
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
"trigger_threshold_name": "Threshold configuration"
"trigger_behavior_name": "Behavior"
},
"conditions": {
"is_cooling": {
@@ -64,13 +59,17 @@
"target_humidity": {
"description": "Tests the humidity setpoint of one or more climate-control devices.",
"fields": {
"above": {
"description": "Require the target humidity to be above this value.",
"name": "Above"
},
"behavior": {
"description": "[%key:component::climate::common::condition_behavior_description%]",
"name": "[%key:component::climate::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::climate::common::condition_threshold_description%]",
"name": "[%key:component::climate::common::condition_threshold_name%]"
"below": {
"description": "Require the target humidity to be below this value.",
"name": "Below"
}
},
"name": "Climate-control device target humidity"
@@ -78,13 +77,21 @@
"target_temperature": {
"description": "Tests the temperature setpoint of one or more climate-control devices.",
"fields": {
"above": {
"description": "Require the target temperature to be above this value.",
"name": "Above"
},
"behavior": {
"description": "[%key:component::climate::common::condition_behavior_description%]",
"name": "[%key:component::climate::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::climate::common::condition_threshold_description%]",
"name": "[%key:component::climate::common::condition_threshold_name%]"
"below": {
"description": "Require the target temperature to be below this value.",
"name": "Below"
},
"unit": {
"description": "All values will be converted to this unit when evaluating the condition.",
"name": "Unit of measurement"
}
},
"name": "Climate-control device target temperature"
@@ -274,12 +281,37 @@
"any": "Any"
}
},
"hvac_mode": {
"options": {
"auto": "[%key:common::state::auto%]",
"cool": "Cool",
"dry": "Dry",
"fan_only": "Fan only",
"heat": "Heat",
"heat_cool": "Heat/cool",
"off": "[%key:common::state::off%]"
}
},
"number_or_entity": {
"choices": {
"entity": "Entity",
"number": "Number"
}
},
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
},
"trigger_threshold_type": {
"options": {
"above": "Above a value",
"below": "Below a value",
"between": "In a range",
"outside": "Outside a range"
}
}
},
"services": {
@@ -427,9 +459,13 @@
"target_humidity_changed": {
"description": "Triggers after the humidity setpoint of one or more climate-control devices changes.",
"fields": {
"threshold": {
"description": "[%key:component::climate::common::trigger_threshold_changed_description%]",
"name": "[%key:component::climate::common::trigger_threshold_name%]"
"above": {
"description": "Trigger when the target humidity is above this value.",
"name": "Above"
},
"below": {
"description": "Trigger when the target humidity is below this value.",
"name": "Below"
}
},
"name": "Climate-control device target humidity changed"
@@ -441,9 +477,17 @@
"description": "[%key:component::climate::common::trigger_behavior_description%]",
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::climate::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::climate::common::trigger_threshold_name%]"
"lower_limit": {
"description": "Lower threshold limit.",
"name": "Lower threshold"
},
"threshold_type": {
"description": "Type of threshold crossing to trigger on.",
"name": "Threshold type"
},
"upper_limit": {
"description": "Upper threshold limit.",
"name": "Upper threshold"
}
},
"name": "Climate-control device target humidity crossed threshold"
@@ -451,9 +495,17 @@
"target_temperature_changed": {
"description": "Triggers after the temperature setpoint of one or more climate-control devices changes.",
"fields": {
"threshold": {
"description": "[%key:component::climate::common::trigger_threshold_changed_description%]",
"name": "[%key:component::climate::common::trigger_threshold_name%]"
"above": {
"description": "Trigger when the target temperature is above this value.",
"name": "Above"
},
"below": {
"description": "Trigger when the target temperature is below this value.",
"name": "Below"
},
"unit": {
"description": "All values will be converted to this unit when evaluating the trigger.",
"name": "Unit of measurement"
}
},
"name": "Climate-control device target temperature changed"
@@ -465,9 +517,21 @@
"description": "[%key:component::climate::common::trigger_behavior_description%]",
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::climate::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::climate::common::trigger_threshold_name%]"
"lower_limit": {
"description": "Lower threshold limit.",
"name": "Lower threshold"
},
"threshold_type": {
"description": "Type of threshold crossing to trigger on.",
"name": "Threshold type"
},
"unit": {
"description": "[%key:component::climate::triggers::target_temperature_changed::fields::unit::description%]",
"name": "[%key:component::climate::triggers::target_temperature_changed::fields::unit::name%]"
},
"upper_limit": {
"description": "Upper threshold limit.",
"name": "Upper threshold"
}
},
"name": "Climate-control device target temperature crossed threshold"

View File

@@ -7,38 +7,74 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger
.humidity_threshold_entity: &humidity_threshold_entity
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: humidity
- domain: number
device_class: humidity
.number_or_entity_humidity: &number_or_entity_humidity
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "%"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: humidity
- domain: number
device_class: humidity
translation_key: number_or_entity
.humidity_threshold_number: &humidity_threshold_number
min: 0
max: 100
mode: box
unit_of_measurement: "%"
.number_or_entity_temperature: &number_or_entity_temperature
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement:
- "°C"
- "°F"
- domain: sensor
device_class: temperature
- domain: number
device_class: temperature
translation_key: number_or_entity
.temperature_units: &temperature_units
- "°C"
- "°F"
.trigger_unit_temperature: &trigger_unit_temperature
required: false
selector:
select:
options:
- "°C"
- "°F"
.temperature_threshold_entity: &temperature_threshold_entity
- domain: input_number
unit_of_measurement: *temperature_units
- domain: sensor
device_class: temperature
- domain: number
device_class: temperature
.trigger_threshold_type: &trigger_threshold_type
required: true
default: above
selector:
select:
options:
- above
- below
- between
- outside
translation_key: trigger_threshold_type
started_cooling: *trigger_common
started_drying: *trigger_common
@@ -64,49 +100,29 @@ hvac_mode_changed:
target_humidity_changed:
target: *trigger_climate_target
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *humidity_threshold_entity
mode: changed
number: *humidity_threshold_number
above: *number_or_entity_humidity
below: *number_or_entity_humidity
target_humidity_crossed_threshold:
target: *trigger_climate_target
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *humidity_threshold_entity
mode: crossed
number: *humidity_threshold_number
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_humidity
upper_limit: *number_or_entity_humidity
target_temperature_changed:
target: *trigger_climate_target
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *temperature_threshold_entity
mode: changed
number:
mode: box
unit_of_measurement: *temperature_units
above: *number_or_entity_temperature
below: *number_or_entity_temperature
unit: *trigger_unit_temperature
target_temperature_crossed_threshold:
target: *trigger_climate_target
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *temperature_threshold_entity
mode: crossed
number:
mode: box
unit_of_measurement: *temperature_units
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity_temperature
upper_limit: *number_or_entity_temperature
unit: *trigger_unit_temperature

View File

@@ -75,11 +75,11 @@
"services": {
"remote_connect": {
"description": "Makes the instance UI accessible from outside of the local network by enabling your Home Assistant Cloud connection.",
"name": "Enable Home Assistant Cloud remote access"
"name": "Enable remote access"
},
"remote_disconnect": {
"description": "Disconnects the instance UI from Home Assistant Cloud. This disables access to it from outside your local network.",
"name": "Disable Home Assistant Cloud remote access"
"name": "Disable remote access"
}
},
"system_health": {

View File

@@ -6,7 +6,7 @@
},
"services": {
"process": {
"description": "Sends text to a conversation agent for processing.",
"description": "Launches a conversation from a transcribed text.",
"fields": {
"agent_id": {
"description": "Conversation agent to process your request. The conversation agent is the brains of your assistant. It processes the incoming text commands.",
@@ -25,10 +25,10 @@
"name": "Text"
}
},
"name": "Process conversation"
"name": "Process"
},
"reload": {
"description": "Reloads the intent configuration of conversation agents.",
"description": "Reloads the intent configuration.",
"fields": {
"agent_id": {
"description": "Conversation agent to reload.",
@@ -39,7 +39,7 @@
"name": "[%key:common::config_flow::data::language%]"
}
},
"name": "Reload conversation agents"
"name": "[%key:common::action::reload%]"
}
},
"title": "Conversation"

View File

@@ -12,22 +12,5 @@
"set_value": {
"service": "mdi:counter"
}
},
"triggers": {
"decremented": {
"trigger": "mdi:numeric-negative-1"
},
"incremented": {
"trigger": "mdi:numeric-positive-1"
},
"maximum_reached": {
"trigger": "mdi:sort-numeric-ascending-variant"
},
"minimum_reached": {
"trigger": "mdi:sort-numeric-descending-variant"
},
"reset": {
"trigger": "mdi:refresh"
}
}
}

View File

@@ -1,8 +1,4 @@
{
"common": {
"trigger_behavior_description": "The behavior of the targeted counters to trigger on.",
"trigger_behavior_name": "Behavior"
},
"entity_component": {
"_": {
"name": "[%key:component::counter::title%]",
@@ -29,78 +25,29 @@
}
}
},
"selector": {
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"services": {
"decrement": {
"description": "Decrements a counter by its step size.",
"name": "Decrement counter"
"name": "Decrement"
},
"increment": {
"description": "Increments a counter by its step size.",
"name": "Increment counter"
"name": "Increment"
},
"reset": {
"description": "Resets a counter to its initial value.",
"name": "Reset counter"
"name": "Reset"
},
"set_value": {
"description": "Sets a counter to a specific value.",
"description": "Sets the counter to a specific value.",
"fields": {
"value": {
"description": "The new counter value the entity should be set to.",
"name": "Value"
}
},
"name": "Set counter value"
"name": "Set"
}
},
"title": "Counter",
"triggers": {
"decremented": {
"description": "Triggers after one or more counters decrement.",
"name": "Counter decremented"
},
"incremented": {
"description": "Triggers after one or more counters increment.",
"name": "Counter incremented"
},
"maximum_reached": {
"description": "Triggers after one or more counters reach their maximum value.",
"fields": {
"behavior": {
"description": "[%key:component::counter::common::trigger_behavior_description%]",
"name": "[%key:component::counter::common::trigger_behavior_name%]"
}
},
"name": "Counter reached maximum"
},
"minimum_reached": {
"description": "Triggers after one or more counters reach their minimum value.",
"fields": {
"behavior": {
"description": "[%key:component::counter::common::trigger_behavior_description%]",
"name": "[%key:component::counter::common::trigger_behavior_name%]"
}
},
"name": "Counter reached minimum"
},
"reset": {
"description": "Triggers after one or more counters are reset.",
"fields": {
"behavior": {
"description": "[%key:component::counter::common::trigger_behavior_description%]",
"name": "[%key:component::counter::common::trigger_behavior_name%]"
}
},
"name": "Counter reset"
}
}
"title": "Counter"
}

View File

@@ -1,113 +0,0 @@
"""Provides triggers for counters."""
from homeassistant.const import (
CONF_MAXIMUM,
CONF_MINIMUM,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.trigger import (
ENTITY_STATE_TRIGGER_SCHEMA,
EntityTriggerBase,
Trigger,
)
from . import CONF_INITIAL, DOMAIN
def _is_integer_state(state: State) -> bool:
"""Return True if the state's value can be interpreted as an integer."""
try:
int(state.state)
except TypeError, ValueError:
return False
return True
class CounterBaseIntegerTrigger(EntityTriggerBase):
"""Base trigger for valid counter integer states."""
_domain_specs = {DOMAIN: DomainSpec()}
_schema = ENTITY_STATE_TRIGGER_SCHEMA
def is_valid_state(self, state: State) -> bool:
"""Check if the new state is valid."""
return _is_integer_state(state)
class CounterDecrementedTrigger(CounterBaseIntegerTrigger):
"""Trigger for when a counter is decremented."""
def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state is valid and the state has changed."""
if from_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
return False
return int(from_state.state) > int(to_state.state)
class CounterIncrementedTrigger(CounterBaseIntegerTrigger):
"""Trigger for when a counter is incremented."""
def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state is valid and the state has changed."""
if from_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
return False
return int(from_state.state) < int(to_state.state)
class CounterValueBaseTrigger(EntityTriggerBase):
"""Base trigger for counter value changes."""
_domain_specs = {DOMAIN: DomainSpec()}
def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state is valid and the state has changed."""
if from_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
return False
return from_state.state != to_state.state
class CounterMaxReachedTrigger(CounterValueBaseTrigger):
"""Trigger for when a counter reaches its maximum value."""
def is_valid_state(self, state: State) -> bool:
"""Check if the new state matches the expected state(s)."""
if (max_value := state.attributes.get(CONF_MAXIMUM)) is None:
return False
return state.state == str(max_value)
class CounterMinReachedTrigger(CounterValueBaseTrigger):
"""Trigger for when a counter reaches its minimum value."""
def is_valid_state(self, state: State) -> bool:
"""Check if the new state matches the expected state(s)."""
if (min_value := state.attributes.get(CONF_MINIMUM)) is None:
return False
return state.state == str(min_value)
class CounterResetTrigger(CounterValueBaseTrigger):
"""Trigger for reset of counter entities."""
def is_valid_state(self, state: State) -> bool:
"""Check if the new state matches the expected state(s)."""
if (init_state := state.attributes.get(CONF_INITIAL)) is None:
return False
return state.state == str(init_state)
TRIGGERS: dict[str, type[Trigger]] = {
"decremented": CounterDecrementedTrigger,
"incremented": CounterIncrementedTrigger,
"maximum_reached": CounterMaxReachedTrigger,
"minimum_reached": CounterMinReachedTrigger,
"reset": CounterResetTrigger,
}
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for counters."""
return TRIGGERS

View File

@@ -1,27 +0,0 @@
.trigger_common: &trigger_common
target:
entity:
domain: counter
fields:
behavior:
required: true
default: any
selector:
select:
translation_key: trigger_behavior
options:
- first
- last
- any
incremented:
target:
entity:
domain: counter
decremented:
target:
entity:
domain: counter
maximum_reached: *trigger_common
minimum_reached: *trigger_common
reset: *trigger_common

View File

@@ -3,11 +3,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
awning_is_closed:
fields: *condition_common_fields

View File

@@ -3,12 +3,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger
awning_closed:
fields: *trigger_common_fields

View File

@@ -1,91 +1 @@
"""The Leviton Decora Wi-Fi integration."""
from __future__ import annotations
from contextlib import suppress
from dataclasses import dataclass
from decora_wifi import DecoraWiFiSession
from decora_wifi.models.iot_switch import IotSwitch
from decora_wifi.models.person import Person
from decora_wifi.models.residence import Residence
from decora_wifi.models.residential_account import ResidentialAccount
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_PASSWORD,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
PLATFORMS = [Platform.LIGHT]
type DecoraWifiConfigEntry = ConfigEntry[DecoraWifiData]
@dataclass
class DecoraWifiData:
"""Runtime data for the Decora Wi-Fi integration."""
session: DecoraWiFiSession
switches: list[IotSwitch]
def _login_and_get_switches(email: str, password: str) -> DecoraWifiData:
"""Log in and fetch all IoT switches. Runs in executor."""
session = DecoraWiFiSession()
success = session.login(email, password)
if success is None:
raise ConfigEntryAuthFailed("Invalid credentials for myLeviton account")
perms = session.user.get_residential_permissions()
all_switches: list[IotSwitch] = []
for permission in perms:
if permission.residentialAccountId is not None:
acct = ResidentialAccount(session, permission.residentialAccountId)
all_switches.extend(
switch
for residence in acct.get_residences()
for switch in residence.get_iot_switches()
)
elif permission.residenceId is not None:
residence = Residence(session, permission.residenceId)
all_switches.extend(residence.get_iot_switches())
return DecoraWifiData(session, all_switches)
async def async_setup_entry(hass: HomeAssistant, entry: DecoraWifiConfigEntry) -> bool:
"""Set up Leviton Decora Wi-Fi from a config entry."""
try:
data = await hass.async_add_executor_job(
_login_and_get_switches,
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
)
except ValueError as err:
raise ConfigEntryNotReady(
"Failed to communicate with myLeviton service"
) from err
entry.runtime_data = data
async def _logout(_: Event | None = None) -> None:
with suppress(ValueError):
await hass.async_add_executor_job(Person.logout, data.session)
entry.async_on_unload(hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _logout))
entry.async_on_unload(_logout)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: DecoraWifiConfigEntry) -> bool:
"""Unload a Decora Wi-Fi config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
"""The decora_wifi component."""

View File

@@ -1,104 +0,0 @@
"""Config flow for Leviton Decora Wi-Fi integration."""
from __future__ import annotations
import contextlib
from typing import Any
from decora_wifi import DecoraWiFiSession
from decora_wifi.models.person import Person
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)
from .const import DOMAIN
USER_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): TextSelector(),
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(type=TextSelectorType.PASSWORD)
),
}
)
def _try_login(email: str, password: str) -> str | None:
"""Attempt to log in, return the user ID, or None on auth failure."""
session = DecoraWiFiSession()
if session.login(email, password) is None:
return None
user_id = str(session.user._id) # noqa: SLF001
with contextlib.suppress(ValueError):
Person.logout(session)
return user_id
class DecoraWifiConfigFlow(ConfigFlow, domain=DOMAIN):
"""Leviton Decora Wi-Fi config flow."""
VERSION = 1
MINOR_VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
user_id = await self.hass.async_add_executor_job(
_try_login,
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
)
except ValueError:
errors["base"] = "cannot_connect"
else:
if user_id is None:
errors["base"] = "invalid_auth"
else:
await self.async_set_unique_id(user_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=user_input[CONF_USERNAME],
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=self.add_suggested_values_to_schema(USER_SCHEMA, user_input),
errors=errors,
)
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Handle import from YAML configuration."""
self._async_abort_entries_match({CONF_USERNAME: import_data[CONF_USERNAME]})
try:
user_id = await self.hass.async_add_executor_job(
_try_login,
import_data[CONF_USERNAME],
import_data[CONF_PASSWORD],
)
except ValueError:
return self.async_abort(reason="cannot_connect")
if user_id is None:
return self.async_abort(reason="invalid_auth")
await self.async_set_unique_id(user_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=import_data[CONF_USERNAME],
data=import_data,
)

View File

@@ -1,4 +0,0 @@
"""Constants for the Leviton Decora Wi-Fi integration."""
DOMAIN = "decora_wifi"
INTEGRATION_TITLE = "Leviton Decora Wi-Fi"

View File

@@ -6,8 +6,13 @@ from datetime import timedelta
import logging
from typing import Any
from decora_wifi import DecoraWiFiSession
from decora_wifi.models.person import Person
from decora_wifi.models.residence import Residence
from decora_wifi.models.residential_account import ResidentialAccount
import voluptuous as vol
from homeassistant.components import persistent_notification
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_TRANSITION,
@@ -16,21 +21,13 @@ from homeassistant.components.light import (
LightEntity,
LightEntityFeature,
)
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle
from . import DecoraWifiConfigEntry
from .const import DOMAIN, INTEGRATION_TITLE
_LOGGER = logging.getLogger(__name__)
# Validation of the user's configuration
@@ -38,65 +35,63 @@ PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string}
)
NOTIFICATION_ID = "leviton_notification"
NOTIFICATION_TITLE = "myLeviton Decora Setup"
async def async_setup_platform(
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Decora WiFi platform from YAML (deprecated)."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
"""Set up the Decora WiFi platform."""
if (
result.get("type") is FlowResultType.ABORT
and (reason := result.get("reason")) != "already_configured"
):
ir.async_create_issue(
hass,
DOMAIN,
f"deprecated_yaml_import_issue_{reason}",
breaks_in_ha_version="2026.10.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key=f"deprecated_yaml_import_issue_{reason}",
translation_placeholders={
"domain": DOMAIN,
"integration_title": INTEGRATION_TITLE,
},
)
return
email = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
session = DecoraWiFiSession()
ir.async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2026.10.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": INTEGRATION_TITLE,
},
)
try:
success = session.login(email, password)
# If login failed, notify user.
if success is None:
msg = "Failed to log into myLeviton Services. Check credentials."
_LOGGER.error(msg)
persistent_notification.create(
hass, msg, title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID
)
return
async def async_setup_entry(
hass: HomeAssistant,
entry: DecoraWifiConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Decora WiFi lights from a config entry."""
async_add_entities(
DecoraWifiLight(switch) for switch in entry.runtime_data.switches
)
# Gather all the available devices...
perms = session.user.get_residential_permissions()
all_switches: list = []
for permission in perms:
if permission.residentialAccountId is not None:
acct = ResidentialAccount(session, permission.residentialAccountId)
all_switches.extend(
switch
for residence in acct.get_residences()
for switch in residence.get_iot_switches()
)
elif permission.residenceId is not None:
residence = Residence(session, permission.residenceId)
all_switches.extend(residence.get_iot_switches())
add_entities(DecoraWifiLight(sw) for sw in all_switches)
except ValueError:
_LOGGER.error("Failed to communicate with myLeviton Service")
# Listen for the stop event and log out.
def logout(event):
"""Log out..."""
try:
if session is not None:
Person.logout(session)
except ValueError:
_LOGGER.error("Failed to log out of myLeviton Service")
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, logout)
class DecoraWifiLight(LightEntity):

View File

@@ -2,9 +2,7 @@
"domain": "decora_wifi",
"name": "Leviton Decora Wi-Fi",
"codeowners": [],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/decora_wifi",
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["decora_wifi"],
"quality_scale": "legacy",

View File

@@ -1,35 +0,0 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"step": {
"user": {
"data": {
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"password": "The password of your myLeviton account.",
"username": "The email address of your myLeviton account."
}
}
}
},
"issues": {
"deprecated_yaml_import_issue_cannot_connect": {
"description": "Importing the YAML configuration for {integration_title} failed because the myLeviton service could not be reached. Please check your network connectivity and then remove the `decora_wifi` YAML configuration from your `configuration.yaml` file and set up the integration again using the UI.",
"title": "The {integration_title} YAML configuration import failed"
},
"deprecated_yaml_import_issue_invalid_auth": {
"description": "Importing the YAML configuration for {integration_title} failed because the provided credentials are invalid. Please remove the `decora_wifi` YAML configuration from your `configuration.yaml` file and set up the integration again using the UI with correct credentials.",
"title": "The {integration_title} YAML configuration import failed"
}
}
}

View File

@@ -7,11 +7,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_home: *condition_common
is_not_home: *condition_common

View File

@@ -120,7 +120,7 @@
"name": "MAC address"
}
},
"name": "See device tracker"
"name": "See"
}
},
"title": "Device tracker",

View File

@@ -7,12 +7,9 @@
required: true
default: any
selector:
select:
options:
- first
- last
- any
automation_behavior:
translation_key: trigger_behavior
mode: trigger
entered_home: *trigger_common
left_home: *trigger_common

View File

@@ -3,11 +3,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_closed:
fields: *condition_common_fields

View File

@@ -3,12 +3,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger
closed:
fields: *trigger_common_fields

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
"requirements": ["py-sucks==0.9.11", "deebot-client==18.1.0"]
"requirements": ["py-sucks==0.9.11", "deebot-client==18.0.0"]
}

View File

@@ -7,11 +7,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_off: *condition_common
is_on: *condition_common

View File

@@ -10,8 +10,7 @@ set_preset_mode:
required: true
example: "auto"
selector:
state:
attribute: preset_mode
text:
set_percentage:
target:
@@ -50,8 +49,7 @@ turn_on:
supported_features:
- fan.FanEntityFeature.PRESET_MODE
selector:
state:
attribute: preset_mode
text:
turn_off:
target:

View File

@@ -7,12 +7,9 @@
required: true
default: any
selector:
select:
options:
- first
- last
- any
automation_behavior:
translation_key: trigger_behavior
mode: trigger
turned_on: *trigger_common
turned_off: *trigger_common

View File

@@ -20,7 +20,5 @@ async def async_get_solar_forecast(
"wh_hours": {
timestamp.isoformat(): val
for timestamp, val in entry.runtime_data.data.wh_period.items()
if val != 0
or (timestamp.hour, timestamp.minute, timestamp.second) != (0, 0, 0)
}
}

View File

@@ -21,5 +21,5 @@
"integration_type": "system",
"preview_features": { "winter_mode": {} },
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20260325.0"]
"requirements": ["home-assistant-frontend==20260312.1"]
}

View File

@@ -3,11 +3,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_closed:
fields: *condition_common_fields

View File

@@ -3,12 +3,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger
closed:
fields: *trigger_common_fields

View File

@@ -3,11 +3,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_closed:
fields: *condition_common_fields

View File

@@ -3,12 +3,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger
closed:
fields: *trigger_common_fields

View File

@@ -296,7 +296,7 @@
"services": {
"reload": {
"description": "Reloads group configuration, entities, and notify services from YAML-configuration.",
"name": "Reload groups"
"name": "[%key:common::action::reload%]"
},
"remove": {
"description": "Removes a group.",
@@ -306,10 +306,10 @@
"name": "[%key:component::group::services::set::fields::object_id::name%]"
}
},
"name": "Remove group"
"name": "Remove"
},
"set": {
"description": "Creates or updates a group.",
"description": "Creates/Updates a group.",
"fields": {
"add_entities": {
"description": "List of members to be added to the group. Cannot be used in combination with `Entities` or `Remove entities`.",
@@ -340,7 +340,7 @@
"name": "Remove entities"
}
},
"name": "Set group"
"name": "Set"
}
},
"title": "Group"

View File

@@ -87,26 +87,22 @@ def _get_coordinator(
return coordinators[serial_number]
def _parse_time_str(
time_str: str,
translation_key: str,
translation_placeholders: dict[str, str] | None = None,
) -> time:
def _parse_time_str(time_str: str, field_name: str) -> time:
"""Parse a time string (HH:MM or HH:MM:SS) to a datetime.time object."""
parts = time_str.split(":")
if len(parts) not in (2, 3):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key=translation_key,
translation_placeholders=translation_placeholders or {},
translation_key="invalid_time_format",
translation_placeholders={"field_name": field_name},
)
try:
return datetime.strptime(f"{parts[0]}:{parts[1]}", "%H:%M").time()
except (ValueError, IndexError) as err:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key=translation_key,
translation_placeholders=translation_placeholders or {},
translation_key="invalid_time_format",
translation_placeholders={"field_name": field_name},
) from err
@@ -146,8 +142,8 @@ def async_setup_services(hass: HomeAssistant) -> None:
)
batt_mode: int = valid_modes[batt_mode_str]
start_time = _parse_time_str(start_time_str, "invalid_time_format_start_time")
end_time = _parse_time_str(end_time_str, "invalid_time_format_end_time")
start_time = _parse_time_str(start_time_str, "start_time")
end_time = _parse_time_str(end_time_str, "end_time")
coordinator: GrowattCoordinator = _get_coordinator(hass, device_id, "min")
await coordinator.update_time_segment(
@@ -196,13 +192,11 @@ def async_setup_services(hass: HomeAssistant) -> None:
cached = current["periods"][i - 1]
start = _parse_time_str(
call.data.get(f"period_{i}_start", cached["start_time"]),
"invalid_time_format_period_start",
{"period": str(i)},
f"period_{i}_start",
)
end = _parse_time_str(
call.data.get(f"period_{i}_end", cached["end_time"]),
"invalid_time_format_period_end",
{"period": str(i)},
f"period_{i}_end",
)
enabled: bool = call.data.get(f"period_{i}_enabled", cached["enabled"])
periods.append({"start_time": start, "end_time": end, "enabled": enabled})
@@ -244,13 +238,11 @@ def async_setup_services(hass: HomeAssistant) -> None:
cached = current["periods"][i - 1]
start = _parse_time_str(
call.data.get(f"period_{i}_start", cached["start_time"]),
"invalid_time_format_period_start",
{"period": str(i)},
f"period_{i}_start",
)
end = _parse_time_str(
call.data.get(f"period_{i}_end", cached["end_time"]),
"invalid_time_format_period_end",
{"period": str(i)},
f"period_{i}_end",
)
enabled: bool = call.data.get(f"period_{i}_enabled", cached["enabled"])
periods.append({"start_time": start, "end_time": end, "enabled": enabled})

View File

@@ -579,7 +579,7 @@
"message": "Growatt API error: {error}"
},
"device_not_configured": {
"message": "{device_type} device {serial_number} is not configured for actions."
"message": "{device_type} device {serial_number} is not configured for services."
},
"device_not_found": {
"message": "Device {device_id} not found in the device registry."
@@ -591,31 +591,22 @@
"message": "{batt_mode} is not a valid battery mode. Allowed values: {allowed_modes}."
},
"invalid_charge_power": {
"message": "'Charge power' must be between 0 and 100, got {value}."
"message": "charge_power must be between 0 and 100, got {value}."
},
"invalid_charge_stop_soc": {
"message": "'Charge stop SOC' must be between 0 and 100, got {value}."
"message": "charge_stop_soc must be between 0 and 100, got {value}."
},
"invalid_discharge_power": {
"message": "'Discharge power' must be between 0 and 100, got {value}."
"message": "discharge_power must be between 0 and 100, got {value}."
},
"invalid_discharge_stop_soc": {
"message": "'Discharge stop SOC' must be between 0 and 100, got {value}."
"message": "discharge_stop_soc must be between 0 and 100, got {value}."
},
"invalid_segment_id": {
"message": "'Segment ID' must be between 1 and 9, got {segment_id}."
"message": "segment_id must be between 1 and 9, got {segment_id}."
},
"invalid_time_format_end_time": {
"message": "'End time' must be in HH:MM or HH:MM:SS format."
},
"invalid_time_format_period_end": {
"message": "'Period {period} end' must be in HH:MM or HH:MM:SS format."
},
"invalid_time_format_period_start": {
"message": "'Period {period} start' must be in HH:MM or HH:MM:SS format."
},
"invalid_time_format_start_time": {
"message": "'Start time' must be in HH:MM or HH:MM:SS format."
"invalid_time_format": {
"message": "{field_name} must be in HH:MM or HH:MM:SS format."
},
"no_devices_configured": {
"message": "No {device_type} devices with token authentication are configured. Actions require {device_type} devices with V1 API access."
@@ -645,27 +636,27 @@
},
"services": {
"read_ac_charge_times": {
"description": "Reads AC charge time periods from an SPH device.",
"description": "Read AC charge time periods from an SPH device.",
"fields": {
"device_id": {
"description": "[%key:component::growatt_server::services::read_time_segments::fields::device_id::description%]",
"name": "[%key:component::growatt_server::services::read_time_segments::fields::device_id::name%]"
"description": "The Growatt SPH device to read from.",
"name": "Device"
}
},
"name": "Read AC charge times"
},
"read_ac_discharge_times": {
"description": "Reads AC discharge time periods from an SPH device.",
"description": "Read AC discharge time periods from an SPH device.",
"fields": {
"device_id": {
"description": "[%key:component::growatt_server::services::read_time_segments::fields::device_id::description%]",
"name": "[%key:component::growatt_server::services::read_time_segments::fields::device_id::name%]"
"description": "[%key:component::growatt_server::services::read_ac_charge_times::fields::device_id::description%]",
"name": "[%key:component::growatt_server::services::read_ac_charge_times::fields::device_id::name%]"
}
},
"name": "Read AC discharge times"
},
"read_time_segments": {
"description": "Reads all time segments from a supported inverter.",
"description": "Read all time segments from a supported inverter.",
"fields": {
"device_id": {
"description": "The Growatt device to perform the action on.",
@@ -675,7 +666,7 @@
"name": "Read time segments"
},
"update_time_segment": {
"description": "Updates a time segment for supported inverters.",
"description": "Update a time segment for supported inverters.",
"fields": {
"batt_mode": {
"description": "Battery operation mode for this time segment.",
@@ -705,7 +696,7 @@
"name": "Update time segment"
},
"write_ac_charge_times": {
"description": "Writes AC charge time periods to an SPH device.",
"description": "Write AC charge time periods to an SPH device.",
"fields": {
"charge_power": {
"description": "Charge power limit (%).",
@@ -716,8 +707,8 @@
"name": "Charge stop SOC"
},
"device_id": {
"description": "[%key:component::growatt_server::services::read_time_segments::fields::device_id::description%]",
"name": "[%key:component::growatt_server::services::read_time_segments::fields::device_id::name%]"
"description": "[%key:component::growatt_server::services::read_ac_charge_times::fields::device_id::description%]",
"name": "[%key:component::growatt_server::services::read_ac_charge_times::fields::device_id::name%]"
},
"mains_enabled": {
"description": "Enable AC (mains) charging.",
@@ -763,11 +754,11 @@
"name": "Write AC charge times"
},
"write_ac_discharge_times": {
"description": "Writes AC discharge time periods to an SPH device.",
"description": "Write AC discharge time periods to an SPH device.",
"fields": {
"device_id": {
"description": "[%key:component::growatt_server::services::read_time_segments::fields::device_id::description%]",
"name": "[%key:component::growatt_server::services::read_time_segments::fields::device_id::name%]"
"description": "[%key:component::growatt_server::services::read_ac_charge_times::fields::device_id::description%]",
"name": "[%key:component::growatt_server::services::read_ac_charge_times::fields::device_id::name%]"
},
"discharge_power": {
"description": "Discharge power limit (%).",

View File

@@ -666,7 +666,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
# Init add-on ingress panels
panels_task = hass.async_create_task(
async_setup_addon_panel(hass), eager_start=True
async_setup_addon_panel(hass, hassio), eager_start=True
)
# Make sure to await the update_info task before

View File

@@ -2,23 +2,24 @@
from http import HTTPStatus
import logging
from typing import Any
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import IngressPanel
from aiohttp import web
from homeassistant.components import frontend
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import ATTR_ICON
from homeassistant.core import HomeAssistant
from .handler import get_supervisor_client
from .const import ATTR_ADMIN, ATTR_ENABLE, ATTR_PANELS, ATTR_TITLE
from .handler import HassIO, HassioAPIError
_LOGGER = logging.getLogger(__name__)
async def async_setup_addon_panel(hass: HomeAssistant) -> None:
async def async_setup_addon_panel(hass: HomeAssistant, hassio: HassIO) -> None:
"""Add-on Ingress Panel setup."""
hassio_addon_panel = HassIOAddonPanel(hass)
hassio_addon_panel = HassIOAddonPanel(hass, hassio)
hass.http.register_view(hassio_addon_panel)
# If panels are exists
@@ -27,8 +28,11 @@ async def async_setup_addon_panel(hass: HomeAssistant) -> None:
# Register available panels
for addon, data in panels.items():
if not data.enable:
if not data[ATTR_ENABLE]:
continue
# _register_panel never suspends and is only
# a coroutine because it would be a breaking change
# to make it a normal function
_register_panel(hass, addon, data)
@@ -38,22 +42,23 @@ class HassIOAddonPanel(HomeAssistantView):
name = "api:hassio_push:panel"
url = "/api/hassio_push/panel/{addon}"
def __init__(self, hass: HomeAssistant) -> None:
def __init__(self, hass: HomeAssistant, hassio: HassIO) -> None:
"""Initialize WebView."""
self.hass = hass
self.client = get_supervisor_client(hass)
self.hassio = hassio
async def post(self, request: web.Request, addon: str) -> web.Response:
"""Handle new add-on panel requests."""
panels = await self.get_panels()
# Panel exists for add-on slug
if addon not in panels or not panels[addon].enable:
_LOGGER.error("Panel is not enabled for %s", addon)
if addon not in panels or not panels[addon][ATTR_ENABLE]:
_LOGGER.error("Panel is not enable for %s", addon)
return web.Response(status=HTTPStatus.BAD_REQUEST)
data = panels[addon]
# Register panel
_register_panel(self.hass, addon, panels[addon])
_register_panel(self.hass, addon, data)
return web.Response()
async def delete(self, request: web.Request, addon: str) -> web.Response:
@@ -61,23 +66,24 @@ class HassIOAddonPanel(HomeAssistantView):
frontend.async_remove_panel(self.hass, addon)
return web.Response()
async def get_panels(self) -> dict[str, IngressPanel]:
async def get_panels(self) -> dict:
"""Return panels add-on info data."""
try:
return await self.client.ingress.panels()
except SupervisorError as err:
data = await self.hassio.get_ingress_panels()
return data[ATTR_PANELS]
except HassioAPIError as err:
_LOGGER.error("Can't read panel info: %s", err)
return {}
def _register_panel(hass: HomeAssistant, addon: str, data: IngressPanel):
"""Helper to register the panel."""
def _register_panel(hass: HomeAssistant, addon: str, data: dict[str, Any]):
"""Init coroutine to register the panel."""
frontend.async_register_built_in_panel(
hass,
"app",
frontend_url_path=addon,
sidebar_title=data.title,
sidebar_icon=data.icon,
require_admin=data.admin,
sidebar_title=data[ATTR_TITLE],
sidebar_icon=data[ATTR_ICON],
require_admin=data[ATTR_ADMIN],
config={"addon": addon},
)

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable, Coroutine
from http import HTTPStatus
import logging
import os
@@ -27,6 +28,21 @@ class HassioAPIError(RuntimeError):
"""Return if a API trow a error."""
def api_data[**_P](
funct: Callable[_P, Coroutine[Any, Any, dict[str, Any]]],
) -> Callable[_P, Coroutine[Any, Any, Any]]:
"""Return data of an api."""
async def _wrapper(*argv: _P.args, **kwargs: _P.kwargs) -> Any:
"""Wrap function."""
data = await funct(*argv, **kwargs)
if data["result"] == "ok":
return data["data"]
raise HassioAPIError(data["message"])
return _wrapper
class HassIO:
"""Small API wrapper for Hass.io."""
@@ -48,6 +64,14 @@ class HassIO:
"""Return base url for Supervisor."""
return self._base_url
@api_data
def get_ingress_panels(self) -> Coroutine:
"""Return data for Add-on ingress panels.
This method returns a coroutine.
"""
return self.send_command("/ingress/panels", method="get")
async def send_command(
self,
command: str,

View File

@@ -7,25 +7,31 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
.humidity_threshold_entity: &humidity_threshold_entity
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: humidity
- domain: number
device_class: humidity
.humidity_threshold_number: &humidity_threshold_number
min: 0
max: 100
mode: box
unit_of_measurement: "%"
.number_or_entity: &number_or_entity
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "%"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: humidity
- domain: number
device_class: humidity
translation_key: number_or_entity
is_off: *condition_common
is_on: *condition_common
@@ -36,10 +42,5 @@ is_target_humidity:
target: *condition_humidifier_target
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *humidity_threshold_entity
mode: is
number: *humidity_threshold_number
above: *number_or_entity
below: *number_or_entity

View File

@@ -11,8 +11,7 @@ set_mode:
required: true
example: "away"
selector:
state:
attribute: mode
text:
set_humidity:
target:

View File

@@ -2,8 +2,6 @@
"common": {
"condition_behavior_description": "How the state should match on the targeted humidifiers.",
"condition_behavior_name": "Behavior",
"condition_threshold_description": "What to test for and threshold values.",
"condition_threshold_name": "Threshold configuration",
"trigger_behavior_description": "The behavior of the targeted humidifiers to trigger on.",
"trigger_behavior_name": "Behavior"
},
@@ -51,13 +49,17 @@
"is_target_humidity": {
"description": "Tests the target humidity of one or more humidifiers.",
"fields": {
"above": {
"description": "Require the target humidity to be above this value.",
"name": "Above"
},
"behavior": {
"description": "[%key:component::humidifier::common::condition_behavior_description%]",
"name": "[%key:component::humidifier::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::humidifier::common::condition_threshold_description%]",
"name": "[%key:component::humidifier::common::condition_threshold_name%]"
"below": {
"description": "Require the target humidity to be below this value.",
"name": "Below"
}
},
"name": "Humidifier target humidity"
@@ -157,46 +159,60 @@
"any": "Any"
}
},
"number_or_entity": {
"choices": {
"entity": "Entity",
"number": "Number"
}
},
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
},
"trigger_threshold_type": {
"options": {
"above": "Above a value",
"below": "Below a value",
"between": "In a range",
"outside": "Outside a range"
}
}
},
"services": {
"set_humidity": {
"description": "Sets the target humidity of a humidifier.",
"description": "Sets the target humidity.",
"fields": {
"humidity": {
"description": "Target humidity.",
"name": "Humidity"
}
},
"name": "Set humidifier target humidity"
"name": "Set humidity"
},
"set_mode": {
"description": "Sets the mode of a humidifier.",
"description": "Sets the humidifier operation mode.",
"fields": {
"mode": {
"description": "Operation mode. For example, \"normal\", \"eco\", or \"away\". For a list of possible values, refer to the integration documentation.",
"name": "Mode"
}
},
"name": "Set humidifier mode"
"name": "Set mode"
},
"toggle": {
"description": "Toggles a humidifier on/off.",
"name": "Toggle humidifier"
"description": "Toggles the humidifier on/off.",
"name": "[%key:common::action::toggle%]"
},
"turn_off": {
"description": "Turns off a humidifier.",
"name": "Turn off humidifier"
"description": "Turns the humidifier off.",
"name": "[%key:common::action::turn_off%]"
},
"turn_on": {
"description": "Turns on a humidifier.",
"name": "Turn on humidifier"
"description": "Turns the humidifier on.",
"name": "[%key:common::action::turn_on%]"
}
},
"title": "Humidifier",

View File

@@ -7,12 +7,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger
started_drying: *trigger_common
started_humidifying: *trigger_common

View File

@@ -1,16 +1,24 @@
.humidity_threshold_entity: &humidity_threshold_entity
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: humidity
- domain: number
device_class: humidity
.humidity_threshold_number: &humidity_threshold_number
min: 0
max: 100
mode: box
unit_of_measurement: "%"
.number_or_entity: &number_or_entity
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "%"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "%"
- domain: number
device_class: humidity
- domain: sensor
device_class: humidity
translation_key: number_or_entity
is_value:
target:
@@ -26,15 +34,8 @@ is_value:
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
threshold:
required: true
selector:
numeric_threshold:
entity: *humidity_threshold_entity
mode: is
number: *humidity_threshold_number
mode: condition
above: *number_or_entity
below: *number_or_entity

View File

@@ -2,25 +2,24 @@
"common": {
"condition_behavior_description": "How the state should match on the targeted entities.",
"condition_behavior_name": "Behavior",
"condition_threshold_description": "What to test for and threshold values.",
"condition_threshold_name": "Threshold configuration",
"trigger_behavior_description": "The behavior of the targeted entities to trigger on.",
"trigger_behavior_name": "Behavior",
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
"trigger_threshold_name": "Threshold configuration"
"trigger_behavior_name": "Behavior"
},
"conditions": {
"is_value": {
"description": "Tests if a relative humidity value is above a threshold, below a threshold, or in a range of values.",
"fields": {
"above": {
"description": "Require the relative humidity to be above this value.",
"name": "Above"
},
"behavior": {
"description": "[%key:component::humidity::common::condition_behavior_description%]",
"name": "[%key:component::humidity::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::humidity::common::condition_threshold_description%]",
"name": "[%key:component::humidity::common::condition_threshold_name%]"
"below": {
"description": "Require the relative humidity to be below this value.",
"name": "Below"
}
},
"name": "Relative humidity"
@@ -33,12 +32,26 @@
"any": "Any"
}
},
"number_or_entity": {
"choices": {
"entity": "Entity",
"number": "Number"
}
},
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
},
"trigger_threshold_type": {
"options": {
"above": "Above",
"below": "Below",
"between": "Between",
"outside": "Outside"
}
}
},
"title": "Humidity",
@@ -46,9 +59,13 @@
"changed": {
"description": "Triggers after one or more relative humidity values change.",
"fields": {
"threshold": {
"description": "[%key:component::humidity::common::trigger_threshold_changed_description%]",
"name": "[%key:component::humidity::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when relative humidity is above this value.",
"name": "Above"
},
"below": {
"description": "Only trigger when relative humidity is below this value.",
"name": "Below"
}
},
"name": "Relative humidity changed"
@@ -60,9 +77,17 @@
"description": "[%key:component::humidity::common::trigger_behavior_description%]",
"name": "[%key:component::humidity::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::humidity::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::humidity::common::trigger_threshold_name%]"
"lower_limit": {
"description": "The lower limit of the threshold.",
"name": "Lower limit"
},
"threshold_type": {
"description": "The type of threshold to use.",
"name": "Threshold type"
},
"upper_limit": {
"description": "The upper limit of the threshold.",
"name": "Upper limit"
}
},
"name": "Relative humidity crossed threshold"

View File

@@ -3,26 +3,43 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger
.humidity_threshold_entity: &humidity_threshold_entity
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: humidity
- domain: number
device_class: humidity
.number_or_entity: &number_or_entity
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "%"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "%"
- domain: number
device_class: humidity
- domain: sensor
device_class: humidity
translation_key: number_or_entity
.humidity_threshold_number: &humidity_threshold_number
min: 0
max: 100
mode: box
unit_of_measurement: "%"
.trigger_threshold_type: &trigger_threshold_type
required: true
default: above
selector:
select:
options:
- above
- below
- between
- outside
translation_key: trigger_threshold_type
.trigger_target: &trigger_target
entity:
@@ -35,22 +52,13 @@
changed:
target: *trigger_target
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *humidity_threshold_entity
mode: changed
number: *humidity_threshold_number
above: *number_or_entity
below: *number_or_entity
crossed_threshold:
target: *trigger_target
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *humidity_threshold_entity
mode: crossed
number: *humidity_threshold_number
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity
upper_limit: *number_or_entity

View File

@@ -8,11 +8,30 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
.number_or_entity: &number_or_entity
required: false
selector:
choose:
choices:
number:
selector:
number:
unit_of_measurement: "lx"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "lx"
- domain: number
device_class: illuminance
- domain: sensor
device_class: illuminance
translation_key: number_or_entity
is_detected: *detected_condition_common
@@ -27,19 +46,5 @@ is_value:
device_class: illuminance
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity:
- domain: input_number
unit_of_measurement: "lx"
- domain: sensor
device_class: illuminance
- domain: number
device_class: illuminance
mode: is
number:
min: 0
mode: box
unit_of_measurement: "lx"
above: *number_or_entity
below: *number_or_entity

View File

@@ -2,13 +2,8 @@
"common": {
"condition_behavior_description": "How the state should match on the targeted entities.",
"condition_behavior_name": "Behavior",
"condition_threshold_description": "What to test for and threshold values.",
"condition_threshold_name": "Threshold configuration",
"trigger_behavior_description": "The behavior of the targeted entities to trigger on.",
"trigger_behavior_name": "Behavior",
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
"trigger_threshold_name": "Threshold configuration"
"trigger_behavior_name": "Behavior"
},
"conditions": {
"is_detected": {
@@ -34,13 +29,17 @@
"is_value": {
"description": "Tests the illuminance value.",
"fields": {
"above": {
"description": "Require the illuminance to be above this value.",
"name": "Above"
},
"behavior": {
"description": "[%key:component::illuminance::common::condition_behavior_description%]",
"name": "[%key:component::illuminance::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::illuminance::common::condition_threshold_description%]",
"name": "[%key:component::illuminance::common::condition_threshold_name%]"
"below": {
"description": "Require the illuminance to be below this value.",
"name": "Below"
}
},
"name": "Illuminance"
@@ -53,12 +52,26 @@
"any": "Any"
}
},
"number_or_entity": {
"choices": {
"entity": "Entity",
"number": "Number"
}
},
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
},
"trigger_threshold_type": {
"options": {
"above": "Above",
"below": "Below",
"between": "Between",
"outside": "Outside"
}
}
},
"title": "Illuminance",
@@ -66,9 +79,13 @@
"changed": {
"description": "Triggers after one or more illuminance values change.",
"fields": {
"threshold": {
"description": "[%key:component::illuminance::common::trigger_threshold_changed_description%]",
"name": "[%key:component::illuminance::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when illuminance is above this value.",
"name": "Above"
},
"below": {
"description": "Only trigger when illuminance is below this value.",
"name": "Below"
}
},
"name": "Illuminance changed"
@@ -90,9 +107,17 @@
"description": "[%key:component::illuminance::common::trigger_behavior_description%]",
"name": "[%key:component::illuminance::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::illuminance::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::illuminance::common::trigger_threshold_name%]"
"lower_limit": {
"description": "The lower limit of the threshold.",
"name": "Lower limit"
},
"threshold_type": {
"description": "The type of threshold to use.",
"name": "Threshold type"
},
"upper_limit": {
"description": "The upper limit of the threshold.",
"name": "Upper limit"
}
},
"name": "Illuminance crossed threshold"

View File

@@ -3,24 +3,43 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger
.illuminance_threshold_entity: &illuminance_threshold_entity
- domain: input_number
unit_of_measurement: "lx"
- domain: sensor
device_class: illuminance
- domain: number
device_class: illuminance
.number_or_entity: &number_or_entity
required: false
selector:
choose:
choices:
number:
selector:
number:
mode: box
unit_of_measurement: "lx"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "lx"
- domain: sensor
device_class: illuminance
- domain: number
device_class: illuminance
translation_key: number_or_entity
.illuminance_threshold_number: &illuminance_threshold_number
mode: box
unit_of_measurement: "lx"
.trigger_threshold_type: &trigger_threshold_type
required: true
default: above
selector:
select:
options:
- above
- below
- between
- outside
translation_key: trigger_threshold_type
.trigger_binary_target: &trigger_binary_target
entity:
@@ -45,22 +64,13 @@ cleared:
changed:
target: *trigger_numerical_target
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *illuminance_threshold_entity
mode: changed
number: *illuminance_threshold_number
above: *number_or_entity
below: *number_or_entity
crossed_threshold:
target: *trigger_numerical_target
fields:
behavior: *trigger_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *illuminance_threshold_entity
mode: crossed
number: *illuminance_threshold_number
threshold_type: *trigger_threshold_type
lower_limit: *number_or_entity
upper_limit: *number_or_entity

View File

@@ -13,7 +13,7 @@
"name": "Filename"
}
},
"name": "Take image snapshot"
"name": "Take snapshot"
}
},
"title": "Image"

View File

@@ -96,6 +96,7 @@ async def build_item_response(
hass: HomeAssistant,
client: JellyfinClient,
user_id: str,
media_content_type: str | None,
media_content_id: str,
) -> BrowseMedia:
"""Create response payload for the provided media query."""
@@ -104,7 +105,7 @@ async def build_item_response(
)
if title is None or media is None:
raise BrowseError(f"Media not found: {media_content_id}")
raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}")
children = await asyncio.gather(
*(item_payload(hass, client, user_id, media_item) for media_item in media)

View File

@@ -300,6 +300,7 @@ class JellyfinMediaPlayer(JellyfinClientEntity, MediaPlayerEntity):
self.hass,
self.coordinator.api_client,
self.coordinator.user_id,
media_content_type,
media_content_id,
)

View File

@@ -7,11 +7,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_docked: *condition_common
is_encountering_an_error: *condition_common

View File

@@ -7,12 +7,9 @@
required: true
default: any
selector:
select:
options:
- first
- last
- any
automation_behavior:
translation_key: trigger_behavior
mode: trigger
docked: *trigger_common
errored: *trigger_common

View File

@@ -7,11 +7,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_off: *condition_common
is_on: *condition_common

View File

@@ -225,8 +225,7 @@ turn_on:
supported_features:
- light.LightEntityFeature.EFFECT
selector:
state:
attribute: effect
text:
advanced_fields:
collapsed: true
fields:

View File

@@ -36,10 +36,7 @@
"field_xy_color_name": "XY-color",
"section_advanced_fields_name": "Advanced options",
"trigger_behavior_description": "The behavior of the targeted lights to trigger on.",
"trigger_behavior_name": "Behavior",
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
"trigger_threshold_name": "Threshold configuration"
"trigger_behavior_name": "Behavior"
},
"conditions": {
"is_off": {
@@ -314,6 +311,12 @@
"short": "Short"
}
},
"number_or_entity": {
"choices": {
"entity": "Entity",
"number": "Number"
}
},
"state": {
"options": {
"off": "[%key:common::state::off%]",
@@ -326,6 +329,14 @@
"first": "First",
"last": "Last"
}
},
"trigger_threshold_type": {
"options": {
"above": "Above a value",
"below": "Below a value",
"between": "In a range",
"outside": "Outside a range"
}
}
},
"services": {
@@ -496,9 +507,13 @@
"brightness_changed": {
"description": "Triggers after the brightness of one or more lights changes.",
"fields": {
"threshold": {
"description": "[%key:component::light::common::trigger_threshold_changed_description%]",
"name": "[%key:component::light::common::trigger_threshold_name%]"
"above": {
"description": "Trigger when the target brightness is above this value.",
"name": "Above"
},
"below": {
"description": "Trigger when the target brightness is below this value.",
"name": "Below"
}
},
"name": "Light brightness changed"
@@ -510,9 +525,17 @@
"description": "[%key:component::light::common::trigger_behavior_description%]",
"name": "[%key:component::light::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::light::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::light::common::trigger_threshold_name%]"
"lower_limit": {
"description": "Lower threshold limit.",
"name": "Lower threshold"
},
"threshold_type": {
"description": "Type of threshold crossing to trigger on.",
"name": "Threshold type"
},
"upper_limit": {
"description": "Upper threshold limit.",
"name": "Upper threshold"
}
},
"name": "Light brightness crossed threshold"

View File

@@ -7,26 +7,33 @@
required: true
default: any
selector:
select:
options:
- first
- last
- any
automation_behavior:
translation_key: trigger_behavior
mode: trigger
.brightness_threshold_entity: &brightness_threshold_entity
- domain: input_number
unit_of_measurement: "%"
- domain: number
unit_of_measurement: "%"
- domain: sensor
unit_of_measurement: "%"
.brightness_threshold_number: &brightness_threshold_number
min: 0
max: 100
mode: box
unit_of_measurement: "%"
.number_or_entity: &number_or_entity
required: false
selector:
choose:
choices:
number:
selector:
number:
max: 100
min: 0
mode: box
unit_of_measurement: "%"
entity:
selector:
entity:
filter:
- domain: input_number
unit_of_measurement: "%"
- domain: number
unit_of_measurement: "%"
- domain: sensor
unit_of_measurement: "%"
translation_key: number_or_entity
turned_on: *trigger_common
turned_off: *trigger_common
@@ -34,22 +41,23 @@ turned_off: *trigger_common
brightness_changed:
target: *trigger_light_target
fields:
threshold:
required: true
selector:
numeric_threshold:
entity: *brightness_threshold_entity
mode: changed
number: *brightness_threshold_number
above: *number_or_entity
below: *number_or_entity
brightness_crossed_threshold:
target: *trigger_light_target
fields:
behavior: *trigger_behavior
threshold:
threshold_type:
required: true
default: above
selector:
numeric_threshold:
entity: *brightness_threshold_entity
mode: crossed
number: *brightness_threshold_number
select:
options:
- above
- below
- between
- outside
translation_key: trigger_threshold_type
lower_limit: *number_or_entity
upper_limit: *number_or_entity

View File

@@ -16,5 +16,5 @@
"iot_class": "cloud_push",
"loggers": ["pylitterbot"],
"quality_scale": "platinum",
"requirements": ["pylitterbot==2025.2.0"]
"requirements": ["pylitterbot==2025.1.0"]
}

View File

@@ -7,11 +7,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_jammed: *condition_common
is_locked: *condition_common

View File

@@ -7,12 +7,9 @@
required: true
default: any
selector:
select:
options:
- first
- last
- any
automation_behavior:
translation_key: trigger_behavior
mode: trigger
jammed: *trigger_common
locked: *trigger_common

View File

@@ -20,11 +20,11 @@
"name": "Level"
}
},
"name": "Set logger default level"
"name": "Set default level"
},
"set_level": {
"description": "Sets the log level for one or more integrations.",
"name": "Set logger level"
"name": "Set level"
}
}
}

View File

@@ -13,7 +13,7 @@
"services": {
"reload_resources": {
"description": "Reloads dashboard resources from the YAML-configuration.",
"name": "Reload dashboard resources"
"name": "Reload resources"
}
},
"system_health": {

View File

@@ -7,11 +7,9 @@
required: true
default: any
selector:
select:
automation_behavior:
translation_key: condition_behavior
options:
- all
- any
mode: condition
is_off: *condition_common
is_on: *condition_common

View File

@@ -217,8 +217,7 @@ select_source:
required: true
example: "video1"
selector:
state:
attribute: source
text:
select_sound_mode:
target:
@@ -230,8 +229,7 @@ select_sound_mode:
sound_mode:
example: "Music"
selector:
state:
attribute: sound_mode
text:
clear_playlist:
target:

View File

@@ -7,9 +7,6 @@ stopped_playing:
required: true
default: any
selector:
select:
automation_behavior:
translation_key: trigger_behavior
options:
- first
- last
- any
mode: trigger

View File

@@ -49,7 +49,7 @@ from .const import (
STORAGE_KEY,
STORAGE_VERSION,
)
from .helpers import async_is_local_only_user, savable_state
from .helpers import savable_state
from .http_api import RegistrationsView
from .timers import async_handle_timer_event
from .util import async_create_cloud_hook, supports_push
@@ -107,14 +107,29 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
async def _async_setup_cloudhook(
hass: HomeAssistant,
entry: ConfigEntry,
user_id: str,
webhook_id: str,
) -> None:
"""Set up cloudhook forwarding for a mobile_app entry."""
local_only = await async_is_local_only_user(hass, user_id)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a mobile_app entry."""
registration = entry.data
webhook_id = registration[CONF_WEBHOOK_ID]
hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id] = entry
device_registry = dr.async_get(hass)
device = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, registration[ATTR_DEVICE_ID])},
manufacturer=registration[ATTR_MANUFACTURER],
model=registration[ATTR_MODEL],
name=registration[ATTR_DEVICE_NAME],
sw_version=registration[ATTR_OS_VERSION],
)
hass.data[DOMAIN][DATA_DEVICES][webhook_id] = device
registration_name = f"Mobile App: {registration[ATTR_DEVICE_NAME]}"
webhook_register(hass, DOMAIN, registration_name, webhook_id, handle_webhook)
def clean_cloudhook() -> None:
"""Clean up cloudhook from config entry."""
@@ -123,14 +138,6 @@ async def _async_setup_cloudhook(
data.pop(CONF_CLOUDHOOK_URL)
hass.config_entries.async_update_entry(entry, data=data)
if local_only:
# Local-only user should not have a cloudhook
if cloud.async_is_logged_in(hass) and CONF_CLOUDHOOK_URL in entry.data:
with suppress(cloud.CloudNotAvailable, ValueError):
await cloud.async_delete_cloudhook(hass, webhook_id)
clean_cloudhook()
return
def on_cloudhook_change(cloudhook: dict[str, Any] | None) -> None:
"""Handle cloudhook changes."""
if cloudhook:
@@ -173,33 +180,6 @@ async def _async_setup_cloudhook(
entry.async_on_unload(cloud.async_listen_connection_change(hass, manage_cloudhook))
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a mobile_app entry."""
registration = entry.data
webhook_id = registration[CONF_WEBHOOK_ID]
hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id] = entry
device_registry = dr.async_get(hass)
device = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, registration[ATTR_DEVICE_ID])},
manufacturer=registration[ATTR_MANUFACTURER],
model=registration[ATTR_MODEL],
name=registration[ATTR_DEVICE_NAME],
sw_version=registration[ATTR_OS_VERSION],
)
hass.data[DOMAIN][DATA_DEVICES][webhook_id] = device
registration_name = f"Mobile App: {registration[ATTR_DEVICE_NAME]}"
webhook_register(hass, DOMAIN, registration_name, webhook_id, handle_webhook)
await _async_setup_cloudhook(hass, entry, registration[CONF_USER_ID], webhook_id)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
if supports_push(hass, webhook_id):

View File

@@ -114,15 +114,6 @@ def decrypt_payload_legacy(key: str, ciphertext: bytes) -> JsonValueType | None:
)
async def async_is_local_only_user(hass: HomeAssistant, user_id: str) -> bool:
"""Return True if the user is local only."""
user = await hass.auth.async_get_user(user_id)
if user is None:
# Treat unknown/missing users as local-only to avoid exposing cloud URLs
return True
return user.local_only
def registration_context(registration: Mapping[str, Any]) -> Context:
"""Generate a context from a request."""
return Context(user_id=registration[CONF_USER_ID])

View File

@@ -68,13 +68,8 @@ class RegistrationsView(HomeAssistantView):
hass = request.app[KEY_HASS]
webhook_id = secrets.token_hex()
user = request["hass_user"]
if (
not user.local_only
and cloud.async_active_subscription(hass)
and cloud.async_is_connected(hass)
):
if cloud.async_active_subscription(hass) and cloud.async_is_connected(hass):
data[CONF_CLOUDHOOK_URL] = await async_create_cloud_hook(
hass, webhook_id, None
)
@@ -84,7 +79,7 @@ class RegistrationsView(HomeAssistantView):
if data[ATTR_SUPPORTS_ENCRYPTION]:
data[CONF_SECRET] = secrets.token_hex(SecretBox.KEY_SIZE)
data[CONF_USER_ID] = user.id
data[CONF_USER_ID] = request["hass_user"].id
# Fallback to DEVICE_ID if slug is empty.
if not slugify(data[ATTR_DEVICE_NAME], separator=""):
@@ -97,7 +92,7 @@ class RegistrationsView(HomeAssistantView):
)
remote_ui_url = None
if not user.local_only and cloud.async_active_subscription(hass):
if cloud.async_active_subscription(hass):
with suppress(cloud.CloudNotAvailable):
remote_ui_url = cloud.async_remote_ui_url(hass)

View File

@@ -94,7 +94,6 @@ from .const import (
CONF_CLOUDHOOK_URL,
CONF_REMOTE_UI_URL,
CONF_SECRET,
CONF_USER_ID,
DATA_CONFIG_ENTRIES,
DATA_DELETED_IDS,
DATA_DEVICES,
@@ -110,7 +109,6 @@ from .const import (
SIGNAL_SENSOR_UPDATE,
)
from .helpers import (
async_is_local_only_user,
decrypt_payload,
decrypt_payload_legacy,
empty_okay_response,
@@ -758,9 +756,7 @@ async def webhook_get_config(
"theme_color": MANIFEST_JSON["theme_color"],
}
if cloud.async_active_subscription(hass) and not await async_is_local_only_user(
hass, config_entry.data[CONF_USER_ID]
):
if cloud.async_active_subscription(hass):
if CONF_CLOUDHOOK_URL in config_entry.data:
resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL]
with suppress(cloud.CloudNotAvailable):

View File

@@ -1,4 +1,4 @@
"""Integration for moisture triggers and conditions."""
"""Integration for moisture triggers."""
from __future__ import annotations

View File

@@ -1,44 +0,0 @@
"""Provides conditions for moisture."""
from __future__ import annotations
from homeassistant.components.binary_sensor import (
DOMAIN as BINARY_SENSOR_DOMAIN,
BinarySensorDeviceClass,
)
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN, NumberDeviceClass
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass
from homeassistant.const import PERCENTAGE, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.condition import (
Condition,
make_entity_numerical_condition,
make_entity_state_condition,
)
_MOISTURE_BINARY_DOMAIN_SPECS = {
BINARY_SENSOR_DOMAIN: DomainSpec(
device_class=BinarySensorDeviceClass.MOISTURE,
)
}
_MOISTURE_NUMERICAL_DOMAIN_SPECS = {
SENSOR_DOMAIN: DomainSpec(device_class=SensorDeviceClass.MOISTURE),
NUMBER_DOMAIN: DomainSpec(device_class=NumberDeviceClass.MOISTURE),
}
CONDITIONS: dict[str, type[Condition]] = {
"is_detected": make_entity_state_condition(_MOISTURE_BINARY_DOMAIN_SPECS, STATE_ON),
"is_not_detected": make_entity_state_condition(
_MOISTURE_BINARY_DOMAIN_SPECS, STATE_OFF
),
"is_value": make_entity_numerical_condition(
_MOISTURE_NUMERICAL_DOMAIN_SPECS, PERCENTAGE
),
}
async def async_get_conditions(hass: HomeAssistant) -> dict[str, type[Condition]]:
"""Return the conditions for moisture."""
return CONDITIONS

View File

@@ -1,50 +0,0 @@
.detected_condition_common: &detected_condition_common
target:
entity:
- domain: binary_sensor
device_class: moisture
fields:
behavior: &condition_behavior
required: true
default: any
selector:
select:
translation_key: condition_behavior
options:
- all
- any
.moisture_threshold_entity: &moisture_threshold_entity
- domain: input_number
unit_of_measurement: "%"
- domain: sensor
device_class: moisture
- domain: number
device_class: moisture
.moisture_threshold_number: &moisture_threshold_number
min: 0
max: 100
mode: box
unit_of_measurement: "%"
is_detected: *detected_condition_common
is_not_detected: *detected_condition_common
is_value:
target:
entity:
- domain: sensor
device_class: moisture
- domain: number
device_class: moisture
fields:
behavior: *condition_behavior
threshold:
required: true
selector:
numeric_threshold:
entity: *moisture_threshold_entity
mode: is
number: *moisture_threshold_number

View File

@@ -1,15 +1,4 @@
{
"conditions": {
"is_detected": {
"condition": "mdi:water"
},
"is_not_detected": {
"condition": "mdi:water-off"
},
"is_value": {
"condition": "mdi:water-percent"
}
},
"triggers": {
"changed": {
"trigger": "mdi:water-percent"

View File

@@ -1,56 +1,13 @@
{
"common": {
"condition_behavior_description": "How the state should match on the targeted entities.",
"condition_behavior_name": "Behavior",
"condition_threshold_description": "What to test for and threshold values.",
"condition_threshold_name": "Threshold configuration",
"trigger_behavior_description": "The behavior of the targeted entities to trigger on.",
"trigger_behavior_name": "Behavior",
"trigger_threshold_changed_description": "Which changes to trigger on and threshold values.",
"trigger_threshold_crossed_description": "Which threshold crossing to trigger on and threshold values.",
"trigger_threshold_name": "Threshold configuration"
},
"conditions": {
"is_detected": {
"description": "Tests if one or more moisture sensors are detecting moisture.",
"fields": {
"behavior": {
"description": "[%key:component::moisture::common::condition_behavior_description%]",
"name": "[%key:component::moisture::common::condition_behavior_name%]"
}
},
"name": "Moisture is detected"
},
"is_not_detected": {
"description": "Tests if one or more moisture sensors are not detecting moisture.",
"fields": {
"behavior": {
"description": "[%key:component::moisture::common::condition_behavior_description%]",
"name": "[%key:component::moisture::common::condition_behavior_name%]"
}
},
"name": "Moisture is not detected"
},
"is_value": {
"description": "Tests the moisture level of one or more entities.",
"fields": {
"behavior": {
"description": "[%key:component::moisture::common::condition_behavior_description%]",
"name": "[%key:component::moisture::common::condition_behavior_name%]"
},
"threshold": {
"description": "[%key:component::moisture::common::condition_threshold_description%]",
"name": "[%key:component::moisture::common::condition_threshold_name%]"
}
},
"name": "Moisture level"
}
"trigger_behavior_name": "Behavior"
},
"selector": {
"condition_behavior": {
"options": {
"all": "All",
"any": "Any"
"number_or_entity": {
"choices": {
"entity": "Entity",
"number": "Number"
}
},
"trigger_behavior": {
@@ -59,6 +16,14 @@
"first": "First",
"last": "Last"
}
},
"trigger_threshold_type": {
"options": {
"above": "Above",
"below": "Below",
"between": "Between",
"outside": "Outside"
}
}
},
"title": "Moisture",
@@ -66,9 +31,13 @@
"changed": {
"description": "Triggers after one or more moisture content values change.",
"fields": {
"threshold": {
"description": "[%key:component::moisture::common::trigger_threshold_changed_description%]",
"name": "[%key:component::moisture::common::trigger_threshold_name%]"
"above": {
"description": "Only trigger when moisture content is above this value.",
"name": "Above"
},
"below": {
"description": "Only trigger when moisture content is below this value.",
"name": "Below"
}
},
"name": "Moisture content changed"
@@ -90,9 +59,17 @@
"description": "[%key:component::moisture::common::trigger_behavior_description%]",
"name": "[%key:component::moisture::common::trigger_behavior_name%]"
},
"threshold": {
"description": "[%key:component::moisture::common::trigger_threshold_crossed_description%]",
"name": "[%key:component::moisture::common::trigger_threshold_name%]"
"lower_limit": {
"description": "The lower limit of the threshold.",
"name": "Lower limit"
},
"threshold_type": {
"description": "The type of threshold to use.",
"name": "Threshold type"
},
"upper_limit": {
"description": "The upper limit of the threshold.",
"name": "Upper limit"
}
},
"name": "Moisture content crossed threshold"

Some files were not shown because too many files have changed in this diff Show More