Skip to content

Commit

Permalink
Fixes and features
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandrErohin committed Dec 11, 2024
1 parent 3aaa094 commit 6a2dbc7
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 30 deletions.
4 changes: 4 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[flake8]

max-line-length = 120
exclude = .git,.github,docs,venv
13 changes: 13 additions & 0 deletions .github/workflows/codechecker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Code Checker
on:
pull_request:
push:
branches:
- main
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run flake8
uses: py-actions/flake8@v2
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ It allows you:
5. Create automations (example - [Automatically track a flight by your needs](#automation))
6. Add flights table to your [Home Assistant dashboard](https://www.home-assistant.io/dashboards/) by [Lovelace Card](#lovelace))
7. Track your flight as [Device Tracker](#device-tracker)
8. Get info for last flights which were in your area or get info about latest exited flight by creating [Last Flights History Sensor](#last-flights)

<img src="https://raw.githubusercontent.com/AlexandrErohin/home-assistant-flightradar24/master/docs/media/map.png" width="48%"><img src="https://raw.githubusercontent.com/AlexandrErohin/home-assistant-flightradar24/master/docs/media/sensors.png" width="48%">
<p align="center"><img src="https://raw.githubusercontent.com/AlexandrErohin/home-assistant-flightradar24/master/docs/media/lovelace.png" width="50%"></p>
Expand All @@ -34,7 +35,7 @@ It allows you:

### <a id="device-tracker">Device Tracker</a>
Track flights as device_tracker with flight information. To use it - you need to activate this feature
in [Edit Configuration](edit-configuration). When it is enabled - this integration creates device_tracker
in [Edit Configuration](#edit-configuration). When it is enabled - this integration creates device_tracker
for every additional tracked flight from `sensor.flightradar24_additional_tracked`.

To create device_tracker for a flight:
Expand All @@ -47,6 +48,7 @@ like `device_tracker.FLIGHT_NUMBER` or `device_tracker.CALL_SIGN`.
### Configuration
- Add to track
- Remove from track
- API data fetching - you may disable FlightRadar API calls when not needed to prevent unnecessary API calls and save bandwidth and server load.

Sensors shows how many flights in the given area, additional tracked, just have entered or exited it. All sensors have attribute `flights` with list of [flight object](#flight) contained a full information by every relevant flight for the sensor

Expand Down Expand Up @@ -174,6 +176,37 @@ automation:

This is an example to filter flights to track, change the conditions for your needs

### <a id="last-flights">Last Flights History Sensor</a>
You may get info for last flights which were in your area. Or get info about latest exited flight.
Here is an example for recording history for the last 5 flights.
The sensor has the same structure as `sensor.flighradar24_current_in_area` and so you can use the same markdown code.
Only the sensor state is different - it shows the latest exited flight.
You may change it for your needs.
Add following lines to your `configuration.yaml` file:
```yaml
template:
- trigger:
- platform: event
event_type: flightradar24_exit
sensor:
- unique_id: flightradar24_last_5_flights
name: "FlightRadar24 Last 5 Flights"
state: >-
{% set flight = trigger.event.data %}
{{ flight.flight_number }} - {{ flight.airline_short }} - {{ flight.aircraft_model }} ({{ flight.aircraft_registration }})
{{ flight.airport_origin_city }} > {{ flight.airport_destination_city }}
attributes:
flights: >-
{% set n = 5 %}
{% set m = this.attributes.flights | count | default(0) %}
{{ [ trigger.event.data ] +
( [] if m == 0 else
this.attributes.flights[0:n-1] )
}}
icon: mdi:airplane
```

### <a id="lovelace">Lovelace Card</a>
You can add flight table to your [Home Assistant dashboard](https://www.home-assistant.io/dashboards/)

Expand Down Expand Up @@ -276,6 +309,7 @@ recorder:
| latitude | Current latitude of the aircraft |
| longitude | Current longitude of the aircraft |
| altitude | Altitude (measurement: foot) |
| on_ground | Is the aircraft on ground (measurement: 0 - in the air; 1 - on ground) |
| distance | Distance between the aircraft and your point (measurement: kilometers) |
| ground_speed | Ground speed (measurement: knots) |
| squawk | Squawk code are what air traffic control (ATC) use to identify aircraft when they are flying **(for subscription only)** |
Expand Down
2 changes: 1 addition & 1 deletion custom_components/flightradar24/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
MAX_ALTITUDE,
)
from FlightRadar24 import FlightRadar24API, Entity
from .sensor import SENSOR_TYPES

PLATFORMS: list[Platform] = [
Platform.DEVICE_TRACKER,
Platform.SENSOR,
Platform.SWITCH,
Platform.TEXT,
]

Expand Down
8 changes: 4 additions & 4 deletions custom_components/flightradar24/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from __future__ import annotations
from logging import getLogger
import voluptuous as vol
from typing import Any
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
OptionsFlowWithConfigEntry,
OptionsFlow,
)
from .const import (
DOMAIN,
Expand Down Expand Up @@ -59,11 +59,11 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Flo

@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> config_entries.OptionsFlow:
return OptionsFlow(config_entry)
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
return FlightRadarOptionsFlow(config_entry)


class OptionsFlow(OptionsFlowWithConfigEntry):
class FlightRadarOptionsFlow(OptionsFlowWithConfigEntry):

async def async_step_init(self, user_input: dict[str, Any] | None = None) -> FlowResult:
errors = {}
Expand Down
53 changes: 32 additions & 21 deletions custom_components/flightradar24/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __init__(
self.max_altitude = max_altitude
self.point = point
self.enable_tracker: bool = False
self.scanning: bool = True
self.device_info = DeviceInfo(
configuration_url=URL,
identifiers={(DOMAIN, self.unique_id)},
Expand All @@ -69,6 +70,9 @@ def __init__(
)

async def add_track(self, number: str) -> None:
if not self.scanning:
self.logger.error('FlightRadar24: API data fetching if OFF')
return
current: dict[str, dict[str, Any]] = {}
await self._find_flight(current, number)
if not current:
Expand Down Expand Up @@ -118,6 +122,9 @@ def process_search_flight(objects: dict, search: str) -> dict | None:
self.logger.error(e)

async def remove_track(self, number: str) -> None:
if not self.scanning:
self.logger.error('FlightRadar24: API data fetching if OFF')
return
flight_id = None
for flight_id in self.tracked:
flight = self.tracked[flight_id]
Expand All @@ -127,6 +134,8 @@ async def remove_track(self, number: str) -> None:
del self.tracked[flight_id]

async def _async_update_data(self):
if not self.scanning:
return
try:
await self._update_flights_in_area()
await self._update_flights_tracked()
Expand Down Expand Up @@ -185,7 +194,6 @@ async def _update_flights_tracked(self) -> None:
async def _update_most_tracked(self) -> None:
if self.most_tracked is None:
return

flights = await self.hass.async_add_executor_job(self._client.get_most_tracked)
current: dict[int, dict[str, Any]] = {}
for obj in flights.get('data'):
Expand All @@ -201,8 +209,9 @@ async def _update_most_tracked(self) -> None:
'airport_destination_city': obj.get('to_city'),
'aircraft_code': obj.get('model'),
'aircraft_model': obj.get('type'),
'on_ground': obj.get('on_ground'),
}
entries = self.entered = [current[x] for x in (current.keys() - self.most_tracked.keys())]
entries = [current[x] for x in (current.keys() - self.most_tracked.keys())]
self.most_tracked = current
self._handle_boundary(EVENT_MOST_TRACKED_NEW, entries)

Expand All @@ -212,10 +221,10 @@ async def _update_flights_data(self,
tracked: dict[str, dict[str, Any]],
sensor_type: SensorType | None = None,
) -> None:
altitude = None
if tracked is not None and obj.id in tracked and self._is_valid(tracked[obj.id]):
last_position = tracked[obj.id].get('on_ground') if tracked is not None and obj.id in tracked else None
if (tracked is not None and obj.id in tracked and self._is_valid(tracked[obj.id])
and self._to_int(last_position) == obj.on_ground):
flight = tracked[obj.id]
altitude = flight.get('altitude')
else:
data = await self.hass.async_add_executor_job(
self._client.get_flight_details, obj
Expand All @@ -231,7 +240,8 @@ async def _update_flights_data(self,
flight['squawk'] = obj.squawk
flight['vertical_speed'] = obj.vertical_speed
flight['distance'] = obj.get_distance_from(self.point)
self._takeoff_and_landing(flight, altitude, obj.altitude, sensor_type)
flight['on_ground'] = obj.on_ground
self._takeoff_and_landing(flight, last_position, obj.on_ground, sensor_type)

def _handle_boundary(self, event: str, flights: list[dict[str, Any]]) -> None:
for flight in flights:
Expand All @@ -243,29 +253,30 @@ def _fire_event(self, event: str, flight: dict[str, Any]) -> None:

def _takeoff_and_landing(self,
flight: dict[str, Any],
altitude_old, altitude_new,
last_position, position,
sensor_type: SensorType | None) -> None:
def to_int(element: any) -> None | int:
if element is None:
return None
try:
return int(element)
except ValueError:
return None

altitude_old = to_int(altitude_old)
altitude_new = to_int(altitude_new)
if sensor_type is None or altitude_old is None or altitude_new is None:
last_position = self._to_int(last_position)
position = self._to_int(position)
if sensor_type is None or last_position is None or position is None or last_position == position:
return
if altitude_old < 10 and altitude_new >= 10:
if position == 0:
self._fire_event(EVENT_AREA_TOOK_OFF if SensorType.IN_AREA == sensor_type else EVENT_TRACKED_TOOK_OFF,
flight)
elif altitude_old > 0 and altitude_new <= 0:
else:
self._fire_event(EVENT_AREA_LANDED if SensorType.IN_AREA == sensor_type else EVENT_TRACKED_LANDED, flight)

@staticmethod
def _is_valid(flight: dict) -> bool:
return flight.get('flight_number') is not None and flight.get('time_scheduled_departure') is not None
return all(flight.get(f) is not None for f in ['flight_number', 'time_scheduled_departure'])

@staticmethod
def _to_int(element: any) -> None | int:
if element is None:
return None
try:
return int(element)
except ValueError:
return None

@staticmethod
def _get_value(dictionary: dict, keys: list) -> Any | None:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/flightradar24/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/AlexandrErohin/home-assistant-flightradar24/issues",
"requirements": ["FlightRadarAPI==1.3.34", "pycountry==23.12.11"],
"version": "1.22.0"
"version": "1.23.0"
}
2 changes: 1 addition & 1 deletion custom_components/flightradar24/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class FlightRadar24SensorEntityDescription(SensorEntityDescription, FlightRadar2
icon="mdi:airplane",
state_class=SensorStateClass.TOTAL,
value=lambda coord: len(coord.in_area) if coord.in_area is not None else 0,
attributes=lambda coord: {'flights': [coord.in_area[x] for x in coord.in_area]},
attributes=lambda coord: {'flights': [coord.in_area[x] for x in coord.in_area] if coord.in_area else {}},
),
FlightRadar24SensorEntityDescription(
key="entered",
Expand Down
52 changes: 52 additions & 0 deletions custom_components/flightradar24/switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from __future__ import annotations
from typing import Any
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .coordinator import FlightRadar24Coordinator


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([FlightRadar24ScanEntity(coordinator)], False)


class FlightRadar24ScanEntity(
CoordinatorEntity[FlightRadar24Coordinator], SwitchEntity
):
entity_description: SwitchEntityDescription

def __init__(self, coordinator: FlightRadar24Coordinator) -> None:
super().__init__(coordinator)

self._attr_device_info = coordinator.device_info
self.entity_description = SwitchEntityDescription(
key="scanning",
name="API data fetching",
icon="mdi:connection",
entity_category=EntityCategory.CONFIG,
)
self._attr_unique_id = f"{coordinator.unique_id}_{DOMAIN}_{self.entity_description.key}"

@property
def is_on(self) -> bool:
"""Return true if switch is on."""
return self.coordinator.scanning

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
self.coordinator.scanning = True
self.async_write_ha_state()

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
self.coordinator.scanning = False
self.async_write_ha_state()
2 changes: 1 addition & 1 deletion custom_components/flightradar24/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.components.text import TextEntity, TextEntityDescription
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
Expand Down

0 comments on commit 6a2dbc7

Please sign in to comment.