Skip to content

Commit 0de2a16

Browse files
heindrichpaulCopilotgjohansson-STjoostlek
authored
Add binary sensor support and refactor NS sensor integration (home-assistant#154589)
Co-authored-by: Copilot <[email protected]> Co-authored-by: G Johansson <[email protected]> Co-authored-by: Joostlek <[email protected]>
1 parent c8c2413 commit 0de2a16

File tree

8 files changed

+536
-5
lines changed

8 files changed

+536
-5
lines changed

homeassistant/components/nederlandse_spoorwegen/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
_LOGGER = logging.getLogger(__name__)
1414

1515

16-
PLATFORMS = [Platform.SENSOR]
16+
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
1717

1818

1919
async def async_setup_entry(hass: HomeAssistant, entry: NSConfigEntry) -> bool:
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""Support for Nederlandse Spoorwegen public transport."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Callable
6+
from dataclasses import dataclass
7+
from datetime import datetime
8+
import logging
9+
10+
from ns_api import Trip
11+
12+
from homeassistant.components.binary_sensor import (
13+
BinarySensorEntity,
14+
BinarySensorEntityDescription,
15+
)
16+
from homeassistant.const import EntityCategory
17+
from homeassistant.core import HomeAssistant
18+
from homeassistant.helpers.device_registry import DeviceInfo
19+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
20+
from homeassistant.helpers.update_coordinator import CoordinatorEntity
21+
22+
from .const import DOMAIN, INTEGRATION_TITLE, ROUTE_MODEL
23+
from .coordinator import NSConfigEntry, NSDataUpdateCoordinator
24+
25+
_LOGGER = logging.getLogger(__name__)
26+
27+
PARALLEL_UPDATES = 0 # since we use coordinator pattern
28+
29+
30+
@dataclass(frozen=True, kw_only=True)
31+
class NSBinarySensorEntityDescription(BinarySensorEntityDescription):
32+
"""Describes Nederlandse Spoorwegen sensor entity."""
33+
34+
value_fn: Callable[[Trip], bool]
35+
36+
37+
def get_delay(planned: datetime | None, actual: datetime | None) -> bool:
38+
"""Return True if delay is present, False otherwise."""
39+
return bool(planned and actual and planned != actual)
40+
41+
42+
BINARY_SENSOR_DESCRIPTIONS = [
43+
NSBinarySensorEntityDescription(
44+
key="is_departure_delayed",
45+
translation_key="is_departure_delayed",
46+
entity_category=EntityCategory.DIAGNOSTIC,
47+
value_fn=lambda trip: get_delay(
48+
trip.departure_time_planned, trip.departure_time_actual
49+
),
50+
entity_registry_enabled_default=False,
51+
),
52+
NSBinarySensorEntityDescription(
53+
key="is_arrival_delayed",
54+
translation_key="is_arrival_delayed",
55+
entity_category=EntityCategory.DIAGNOSTIC,
56+
value_fn=lambda trip: get_delay(
57+
trip.arrival_time_planned, trip.arrival_time_actual
58+
),
59+
entity_registry_enabled_default=False,
60+
),
61+
NSBinarySensorEntityDescription(
62+
key="is_going",
63+
translation_key="is_going",
64+
entity_category=EntityCategory.DIAGNOSTIC,
65+
value_fn=lambda trip: trip.going,
66+
entity_registry_enabled_default=False,
67+
),
68+
]
69+
70+
71+
async def async_setup_entry(
72+
hass: HomeAssistant,
73+
config_entry: NSConfigEntry,
74+
async_add_entities: AddConfigEntryEntitiesCallback,
75+
) -> None:
76+
"""Set up the departure sensor from a config entry."""
77+
78+
coordinators = config_entry.runtime_data
79+
80+
for subentry_id, coordinator in coordinators.items():
81+
async_add_entities(
82+
(
83+
NSBinarySensor(coordinator, subentry_id, description)
84+
for description in BINARY_SENSOR_DESCRIPTIONS
85+
),
86+
config_subentry_id=subentry_id,
87+
)
88+
89+
90+
class NSBinarySensor(CoordinatorEntity[NSDataUpdateCoordinator], BinarySensorEntity):
91+
"""Generic NS binary sensor based on entity description."""
92+
93+
_attr_has_entity_name = True
94+
_attr_attribution = "Data provided by NS"
95+
entity_description: NSBinarySensorEntityDescription
96+
97+
def __init__(
98+
self,
99+
coordinator: NSDataUpdateCoordinator,
100+
subentry_id: str,
101+
description: NSBinarySensorEntityDescription,
102+
) -> None:
103+
"""Initialize the binary sensor."""
104+
super().__init__(coordinator)
105+
self.entity_description = description
106+
self._subentry_id = subentry_id
107+
self._attr_unique_id = f"{subentry_id}-{description.key}"
108+
self._attr_device_info = DeviceInfo(
109+
identifiers={(DOMAIN, subentry_id)},
110+
name=coordinator.name,
111+
manufacturer=INTEGRATION_TITLE,
112+
model=ROUTE_MODEL,
113+
)
114+
115+
@property
116+
def is_on(self) -> bool | None:
117+
"""Return true if the binary sensor is on."""
118+
if not (trip := self.coordinator.data.first_trip):
119+
return None
120+
return self.entity_description.value_fn(trip)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"entity": {
3+
"binary_sensor": {
4+
"is_arrival_delayed": {
5+
"default": "mdi:bell-alert-outline"
6+
},
7+
"is_departure_delayed": {
8+
"default": "mdi:bell-alert-outline"
9+
},
10+
"is_going": {
11+
"default": "mdi:bell-cancel-outline"
12+
}
13+
}
14+
}
15+
}

homeassistant/components/nederlandse_spoorwegen/sensor.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ async def async_setup_entry(
155155

156156

157157
class NSDepartureSensor(CoordinatorEntity[NSDataUpdateCoordinator], SensorEntity):
158-
"""Implementation of a NS Departure Sensor."""
158+
"""Implementation of a NS Departure Sensor (legacy)."""
159159

160160
_attr_device_class = SensorDeviceClass.TIMESTAMP
161161
_attr_attribution = "Data provided by NS"
@@ -202,6 +202,8 @@ def extra_state_attributes(self) -> dict[str, Any] | None:
202202
if not first_trip:
203203
return None
204204

205+
status = first_trip.status
206+
205207
return {
206208
"going": first_trip.going,
207209
"departure_time_planned": _get_time_str(first_trip.departure_time_planned),
@@ -221,7 +223,7 @@ def extra_state_attributes(self) -> dict[str, Any] | None:
221223
"arrival_platform_planned": first_trip.arrival_platform_planned,
222224
"arrival_platform_actual": first_trip.arrival_platform_actual,
223225
"next": _get_time_str(_get_departure_time(next_trip)),
224-
"status": first_trip.status.lower() if first_trip.status else None,
226+
"status": status.lower() if status else None,
225227
"transfers": first_trip.nr_transfers,
226228
"route": _get_route(first_trip),
227229
"remarks": None,

homeassistant/components/nederlandse_spoorwegen/strings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@
6464
}
6565
}
6666
},
67+
"entity": {
68+
"binary_sensor": {
69+
"is_arrival_delayed": {
70+
"name": "Arrival delayed"
71+
},
72+
"is_departure_delayed": {
73+
"name": "Departure delayed"
74+
},
75+
"is_going": {
76+
"name": "Going"
77+
}
78+
}
79+
},
6780
"issues": {
6881
"deprecated_yaml_import_issue_cannot_connect": {
6982
"description": "Configuring Nederlandse Spoorwegen using YAML sensor platform is deprecated.\n\nWhile importing your configuration, Home Assistant could not connect to the NS API. Please check your internet connection and the status of the NS API, then restart Home Assistant to try again, or remove the existing YAML configuration and set the integration up via the UI.",

0 commit comments

Comments
 (0)