Add ScorpionTrack ignition binary sensor (#177470)

This commit is contained in:
Anthony
2026-08-11 15:29:11 +02:00
committed by GitHub
parent 5dd3c92435
commit 0efc9601fe
5 changed files with 248 additions and 1 deletions
@@ -0,0 +1,52 @@
"""Binary sensor platform for ScorpionTrack."""
from typing import override
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ScorpionTrackConfigEntry, ScorpionTrackCoordinator
from .entity import ScorpionTrackEntity
PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
entry: ScorpionTrackConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up ScorpionTrack ignition binary sensors."""
coordinator = entry.runtime_data
async_add_entities(
ScorpionTrackIgnitionBinarySensor(coordinator, vehicle.id)
for vehicle in coordinator.data.vehicles
)
class ScorpionTrackIgnitionBinarySensor(ScorpionTrackEntity, BinarySensorEntity):
"""Represent the latest shared vehicle ignition state."""
_attr_translation_key = "ignition"
def __init__(self, coordinator: ScorpionTrackCoordinator, vehicle_id: int) -> None:
"""Initialize the ignition binary sensor."""
super().__init__(coordinator, vehicle_id)
self._attr_unique_id = f"{coordinator.data.id}_{vehicle_id}_ignition"
def _available_ignition(self) -> bool | None:
"""Return the ignition value."""
return self.get_vehicle().position.ignition
@property
@override
def available(self) -> bool:
"""Return if the ignition binary sensor is available."""
return super().available and self._available_ignition() is not None
@property
@override
def is_on(self) -> bool | None:
"""Return the ignition state."""
return self._available_ignition()
@@ -12,4 +12,8 @@ CONF_SHARE_TOKEN = "share_token"
DEFAULT_SCAN_INTERVAL = timedelta(minutes=2)
PLATFORMS: tuple[Platform, ...] = (Platform.DEVICE_TRACKER, Platform.SENSOR)
PLATFORMS: tuple[Platform, ...] = (
Platform.BINARY_SENSOR,
Platform.DEVICE_TRACKER,
Platform.SENSOR,
)
@@ -22,6 +22,13 @@
}
}
},
"entity": {
"binary_sensor": {
"ignition": {
"name": "Ignition"
}
}
},
"exceptions": {
"cannot_connect": {
"message": "The ScorpionTrack share could not be reached right now."
@@ -0,0 +1,51 @@
# serializer version: 1
# name: test_ignition_binary_sensor_snapshot[binary_sensor.ab12_cde_ignition-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.ab12_cde_ignition',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Ignition',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Ignition',
'platform': 'scorpiontrack',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'ignition',
'unique_id': '101_1_ignition',
'unit_of_measurement': None,
})
# ---
# name: test_ignition_binary_sensor_snapshot[binary_sensor.ab12_cde_ignition-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'AB12 CDE Ignition',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.ab12_cde_ignition',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
@@ -0,0 +1,133 @@
"""Test the ScorpionTrack binary sensor platform."""
from dataclasses import replace
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
from pyscorpiontrack import ScorpionTrackShare
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.scorpiontrack.const import DEFAULT_SCAN_INTERVAL
from homeassistant.const import (
ATTR_LATITUDE,
ATTR_LONGITUDE,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
ENTITY_ID = "binary_sensor.ab12_cde_ignition"
async def test_ignition_binary_sensor_state(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_scorpiontrack_client: AsyncMock,
) -> None:
"""Test the ignition binary sensor uses the coordinator snapshot."""
await setup_integration(hass, mock_config_entry)
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == STATE_ON
assert ATTR_LATITUDE not in state.attributes
assert ATTR_LONGITUDE not in state.attributes
mock_scorpiontrack_client.async_get_share.assert_awaited_once_with()
async def test_ignition_binary_sensor_snapshot(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the ignition binary sensor entity and state attributes."""
with patch(
"homeassistant.components.scorpiontrack.PLATFORMS", (Platform.BINARY_SENSOR,)
):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
("ignition", "expected_state"),
[
pytest.param(True, STATE_ON, id="on"),
pytest.param(False, STATE_OFF, id="off"),
pytest.param(None, STATE_UNAVAILABLE, id="missing"),
],
)
async def test_ignition_binary_sensor_availability(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_share: ScorpionTrackShare,
mock_scorpiontrack_client: AsyncMock,
ignition: bool | None,
expected_state: str,
) -> None:
"""Test on, off, and missing ignition values."""
vehicle = mock_share.vehicles[0]
mock_scorpiontrack_client.async_get_share.return_value = replace(
mock_share,
vehicles=(
replace(
vehicle,
position=replace(vehicle.position, ignition=ignition),
),
),
)
await setup_integration(hass, mock_config_entry)
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == expected_state
async def test_removed_vehicle_makes_ignition_binary_sensor_unavailable(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_config_entry: MockConfigEntry,
mock_share: ScorpionTrackShare,
mock_scorpiontrack_client: AsyncMock,
) -> None:
"""Test ignition becomes unavailable if its vehicle leaves the share."""
await setup_integration(hass, mock_config_entry)
mock_scorpiontrack_client.async_get_share.return_value = replace(
mock_share, vehicles=()
)
freezer.tick(DEFAULT_SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == STATE_UNAVAILABLE
async def test_ignition_binary_sensor_uses_existing_vehicle_device(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test ignition shares the vehicle device with the tracker."""
await setup_integration(hass, mock_config_entry)
ignition_entry = entity_registry.async_get(ENTITY_ID)
tracker_entry = entity_registry.async_get("device_tracker.ab12_cde")
assert ignition_entry is not None
assert tracker_entry is not None
assert ignition_entry.unique_id == "101_1_ignition"
assert ignition_entry.original_device_class is None
assert ignition_entry.device_id == tracker_entry.device_id
assert ignition_entry.device_id is not None