Skip to content

Commit

Permalink
feat: add winddir_name entity (#998)
Browse files Browse the repository at this point in the history
* feat: add `winddir_name` entity

* add HASS entity description
  • Loading branch information
bachya committed Jun 4, 2024
1 parent 2d76594 commit 88cc736
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,8 @@ data exists:
- **[Wind Chill][wind-chill]:** how cold the air feels to the human body when factoring
in relative humidity, wind speed, etc. (applicable when the apparent temperature is
lower than the air temperature)
- **Wind Direction Name:** a conversion from degrees to a human-friendly label (e.g.,
"NNW")

(Special thanks to the excellent [`thermal_comfort` library][thermal-comfort-library] for
inspiration on many of these.)
Expand Down
1 change: 1 addition & 0 deletions ecowitt2mqtt/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
DATA_POINT_WH90BATT_PC: Final = "wh90battpc"
DATA_POINT_WH90CAP_VOLT: Final = "ws90cap_volt"
DATA_POINT_WINDCHILL: Final = "windchill"
DATA_POINT_WINDDIR_NAME: Final = "winddir_name"
DATA_POINT_WINDSPEED: Final = "windspeed"
DATA_POINT_WRAIN_PIEZO: Final = "wrain_piezo"
DATA_POINT_WS90_VER: Final = "ws90_ver"
Expand Down
4 changes: 4 additions & 0 deletions ecowitt2mqtt/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
DATA_POINT_THERMAL_PERCEPTION,
DATA_POINT_UV,
DATA_POINT_WINDCHILL,
DATA_POINT_WINDDIR_NAME,
LOGGER,
)
from ecowitt2mqtt.helpers.calculator import (
Expand Down Expand Up @@ -118,6 +119,7 @@
from ecowitt2mqtt.helpers.calculator.wind import (
BeaufortScaleCalculator,
WindDirCalculator,
WindDirNameCalculator,
WindSpeedCalculator,
)
from ecowitt2mqtt.helpers.device import Device, get_device_from_raw_payload
Expand Down Expand Up @@ -181,6 +183,7 @@
DATA_POINT_THERMAL_PERCEPTION: ThermalPerceptionCalculator,
DATA_POINT_UV: UVIndexCalculator,
DATA_POINT_WINDCHILL: WindChillCalculator,
DATA_POINT_WINDDIR_NAME: WindDirNameCalculator,
}

DEFAULT_KEYS_TO_IGNORE = [
Expand Down Expand Up @@ -286,6 +289,7 @@ class ProcessedData:
DATA_POINT_SOLARRADIATION_PERCEIVED,
DATA_POINT_THERMAL_PERCEPTION,
DATA_POINT_WINDCHILL,
DATA_POINT_WINDDIR_NAME,
)

config: Config
Expand Down
42 changes: 42 additions & 0 deletions ecowitt2mqtt/helpers/calculator/wind.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from ecowitt2mqtt.const import (
CONF_OUTPUT_UNIT_SPEED,
DATA_POINT_GLOB_WINDDIR,
DATA_POINT_WINDSPEED,
DEGREE,
UnitOfSpeed,
Expand All @@ -20,6 +21,26 @@
from ecowitt2mqtt.helpers.typing import PreCalculatedValueType
from ecowitt2mqtt.util.unit_conversion import SpeedConverter

WIND_DIR_NAMES = [
"N",
"NNE",
"NE",
"ENE",
"E",
"ESE",
"SE",
"SSE",
"S",
"SSW",
"SW",
"WSW",
"W",
"WNW",
"NW",
"NNW",
"N",
]


@dataclass
class BeaufortScaleRating: # pylint: disable=too-many-instance-attributes
Expand Down Expand Up @@ -271,6 +292,27 @@ def output_unit(self) -> str:
return DEGREE


class WindDirNameCalculator(Calculator):
"""Define a wind direction name calculator."""

@Calculator.requires_keys(DATA_POINT_GLOB_WINDDIR)
def calculate_from_payload(
self, payload: dict[str, PreCalculatedValueType]
) -> CalculatedDataPoint:
"""Perform the calculation.
Args:
payload: An Ecowitt data payload.
Returns:
A parsed CalculatedDataPoint object.
"""
wind_dir = float(payload[DATA_POINT_GLOB_WINDDIR])
return self.get_calculated_data_point(
WIND_DIR_NAMES[int((wind_dir + 11.25) % 360 / 22.5)]
)


class WindSpeedCalculator(Calculator):
"""Define a wind speed calculator."""

Expand Down
5 changes: 5 additions & 0 deletions ecowitt2mqtt/helpers/publisher/mqtt/hass.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
DATA_POINT_UV,
DATA_POINT_WEEKLY_RAIN,
DATA_POINT_WINDCHILL,
DATA_POINT_WINDDIR_NAME,
DATA_POINT_WRAIN_PIEZO,
DATA_POINT_WS90_VER,
DATA_POINT_YEARLY_RAIN,
Expand Down Expand Up @@ -420,6 +421,10 @@ class HassDiscoveryInfo:
device_class=DeviceClass.TEMPERATURE,
state_class=StateClass.MEASUREMENT,
),
DATA_POINT_WINDDIR_NAME: EntityDescription(
icon="mdi:compass",
state_class=StateClass.MEASUREMENT,
),
DATA_POINT_WS90_VER: EntityDescription(entity_category=EntityCategory.DIAGNOSTIC),
}

Expand Down
7 changes: 7 additions & 0 deletions tests/data/test_battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,13 @@ def test_unknown_battery(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="SE",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"playstationbattery1": CalculatedDataPoint(
"batt",
BooleanBatteryState.OFF,
Expand Down
70 changes: 70 additions & 0 deletions tests/data/test_data_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,13 @@ def test_missing_distance(device_data: dict[str, Any], ecowitt: Ecowitt) -> None
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="E",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
}


Expand Down Expand Up @@ -1038,6 +1045,13 @@ def test_nonnumeric_value(device_data: dict[str, Any], ecowitt: Ecowitt) -> None
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="SE",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"Random New Key": CalculatedDataPoint("Random New Key", "Some Value"),
}

Expand Down Expand Up @@ -1425,6 +1439,13 @@ def test_precision(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="SE",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
}


Expand Down Expand Up @@ -1809,6 +1830,13 @@ def test_precision(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="SE",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
},
),
(
Expand Down Expand Up @@ -2132,6 +2160,13 @@ def test_precision(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="NE",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
},
),
(
Expand Down Expand Up @@ -2928,6 +2963,13 @@ def test_precision(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="E",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
},
),
(
Expand Down Expand Up @@ -3707,6 +3749,13 @@ def test_precision(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="NNW",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
},
),
(
Expand Down Expand Up @@ -4138,6 +4187,13 @@ def test_precision(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="WNW",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
},
),
(
Expand Down Expand Up @@ -4506,6 +4562,13 @@ def test_precision(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="SW",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
},
),
(
Expand Down Expand Up @@ -4821,6 +4884,13 @@ def test_precision(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="W",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
},
),
],
Expand Down
28 changes: 28 additions & 0 deletions tests/data/test_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,13 @@ def test_output_units(device_data: dict[str, Any], ecowitt: Ecowitt) -> None:
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="SE",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
}


Expand Down Expand Up @@ -843,6 +850,13 @@ def test_unit_conversion_to_imperial(
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="SE",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
}


Expand Down Expand Up @@ -1230,6 +1244,13 @@ def test_unit_conversion_to_imperial(
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="SE",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
},
),
(
Expand Down Expand Up @@ -1553,6 +1574,13 @@ def test_unit_conversion_to_imperial(
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
"winddir_name": CalculatedDataPoint(
data_point_key="winddir_name",
value="NE",
unit=None,
attributes={},
data_type=DataPointType.NON_BOOLEAN,
),
},
),
],
Expand Down
2 changes: 1 addition & 1 deletion tests/helpers/publisher/mqtt/test_topic_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def test_publish_processed(
await publishers[0].async_publish(device_data)
mock_aiomqtt_client.publish.assert_awaited_with(
TEST_MQTT_TOPIC,
payload=b'{"runtime": 319206.0, "tempin": 79.52, "humidityin": 31.0, "baromrel": 24.74, "baromabs": 24.74, "temp": 93.2, "humidity": 64.0, "winddir": 139.0, "windspeed": 20.89, "windgust": 1.12, "maxdailygust": 8.05, "solarradiation": 264.61, "uv": 2.0, "rainrate": 0.0, "eventrain": 0.0, "hourlyrain": 0.0, "dailyrain": 0.0, "weeklyrain": 0.0, "monthlyrain": 2.177, "yearlyrain": 4.441, "lightning_num": 13.0, "lightning": 0.6213711922373341, "lightning_time": "2022-04-20T17:17:17+00:00", "wh65batt": "OFF", "beaufortscale": 5, "dewpoint": 79.19328776816637, "feelslike": 111.0553021896001, "frostpoint": 70.28882284994654, "frostrisk": "No risk", "heatindex": 111.0553021896001, "humidex": 48, "humidex_perception": "Dangerous", "humidityabs": 0.001501643470436062, "humidityabsin": 0.001501643470436062, "relative_strain_index": 0.54, "relative_strain_index_perception": "Extreme discomfort", "safe_exposure_time_skin_type_1": 83.3, "safe_exposure_time_skin_type_2": 100.0, "safe_exposure_time_skin_type_3": 133.3, "safe_exposure_time_skin_type_4": 166.7, "safe_exposure_time_skin_type_5": 266.7, "safe_exposure_time_skin_type_6": 433.3, "simmerindex": 113.90619200000002, "simmerzone": "Danger of heatstroke", "solarradiation_perceived": 90.49958322993245, "thermalperception": "Severely high", "windchill": null}', # noqa: E501
payload=b'{"runtime": 319206.0, "tempin": 79.52, "humidityin": 31.0, "baromrel": 24.74, "baromabs": 24.74, "temp": 93.2, "humidity": 64.0, "winddir": 139.0, "windspeed": 20.89, "windgust": 1.12, "maxdailygust": 8.05, "solarradiation": 264.61, "uv": 2.0, "rainrate": 0.0, "eventrain": 0.0, "hourlyrain": 0.0, "dailyrain": 0.0, "weeklyrain": 0.0, "monthlyrain": 2.177, "yearlyrain": 4.441, "lightning_num": 13.0, "lightning": 0.6213711922373341, "lightning_time": "2022-04-20T17:17:17+00:00", "wh65batt": "OFF", "beaufortscale": 5, "dewpoint": 79.19328776816637, "feelslike": 111.0553021896001, "frostpoint": 70.28882284994654, "frostrisk": "No risk", "heatindex": 111.0553021896001, "humidex": 48, "humidex_perception": "Dangerous", "humidityabs": 0.001501643470436062, "humidityabsin": 0.001501643470436062, "relative_strain_index": 0.54, "relative_strain_index_perception": "Extreme discomfort", "safe_exposure_time_skin_type_1": 83.3, "safe_exposure_time_skin_type_2": 100.0, "safe_exposure_time_skin_type_3": 133.3, "safe_exposure_time_skin_type_4": 166.7, "safe_exposure_time_skin_type_5": 266.7, "safe_exposure_time_skin_type_6": 433.3, "simmerindex": 113.90619200000002, "simmerzone": "Danger of heatstroke", "solarradiation_perceived": 90.49958322993245, "thermalperception": "Severely high", "windchill": null, "winddir_name": "SE"}', # noqa: E501
retain=False,
)

Expand Down
Loading

0 comments on commit 88cc736

Please sign in to comment.