[SOLVED] Flightradar24 - aircraft position

Hi there,

anyone who implemented somting like an aircraft tracking (with map view) for specific flights?

Idea:

  1. Flight schedule entry in my calendar
  2. OH will check for flight number
  3. OH will request flight data from the web
  4. Display position, speed and delay on a map.
3 Likes

Work has me traveling more these days. Something like this would be cool for the kids to be able to see when i am flying.

That is exactly what I am after :slight_smile:

I’ve found this thread and it looks like we’re not getting any API from FR24 anytime soon.

There’s however libhomeradar (linked in same thread) which allow to 24 requests per day.

This might help you get it started :wink:
Don’t forget to post a tutorial afterwards!

I have been playing around a little bit with this. I have another trip coming up tuesday so I will be out of pocket again. But I do have a dev api access key setup for flightaware. the free one is giving me basic flight info (departure, arrival, percent of the trip)

I think to start with I am going to have a couple php files that I can hit via the http binding to get some status. Then maybe work on a binding.

Ill keep you posted as I make progress

1 Like

Any update / progress?
:blush:

Have you been able to proceed?
I just registered for the free basic access as well and wonder if you could share some of your findings.
Thanks in advance.

Not as much as I had hoped. But here is what I have.

Items

Switch flighttest
Switch Traveling_Switch
String FlightNumber_String "FlightNumber_String [%s]"
String FlightOrgAirport_String "FlightOrgAirport_String [%s]"
String FlightDstAirport_String "FlightDstAirport_String [%s]"
String FlightEstimatedDeparture_String "FlightEstimatedDeparture_String [%s]"
String FlightActualDepartureTime_String "FlightActualDepartureTime_String [%s]"
String FlightEstimatedArrival_Time "FlightEstimatedArrival_Time [%s]"
String FlightActual_Arrival_Time "FlightActual_Arrival_Time [%s]"
String FlightStatus "FlightStatus [%s]"
Number FlightProgress_Percent "FlightProgress_Percent [%s]"
String FlightAircraftType "FlightAircraftType [%s]"

Rules

rule "flight"
when
Time cron "0 * * * * ?"
then
logInfo("General","here")
if(Traveling_Switch.state == ON)
{
    //traveling is turned on, we need to check
    //lets see if we have a flight number
        logInfo("Travel","Flight:" + FlightNumber_String.toString)
if(FlightNumber_String.state.toString != "")
    {
        logInfo("Travel","Flight not empty")


var String FlightOriginAirPorttmp = sendHttpGetRequest("http://192.168.2.35/flightaware/test.php?flightnum=FlightNumber_String").toString
            FlightOrgAirport_String.postUpdate(transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].origin.airport_name",FlightOriginAirPorttmp).toString)
            FlightDstAirport_String.postUpdate(transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].destination.airport_name",FlightOriginAirPorttmp).toString)
            FlightEstimatedDeparture_String.postUpdate(transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].estimated_departure_time.time",FlightOriginAirPorttmp).toString)
            FlightActualDepartureTime_String.postUpdate(transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].actual_departure_time.epoch",FlightOriginAirPorttmp).toString)
            FlightEstimatedArrival_Time.postUpdate(transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].estimated_arrival_time.time",FlightOriginAirPorttmp).toString)
FlightActual_Arrival_Time.postUpdate(transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].actual_arrival_time.epoch",FlightOriginAirPorttmp).toString)
FlightStatus.postUpdate(transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].status",FlightOriginAirPorttmp).toString)
FlightProgress_Percent.postUpdate(transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].progress_percent",FlightOriginAirPorttmp).toString)
FlightAircraftType.postUpdate(transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].aircrafttype",FlightOriginAirPorttmp).toString)

}

}
else  {
 logInfo("General", "Cron check: Travel = Off")
}
end



rule "detected flight"
when
        Item flighttest changed
then

if (flighttest.state==ON)
        var url = transform("REGEX", ".*\(.*?\).*", "Flight to Atlanta (DL 1961)".toString())
        logInfo("Flight","Flight:" +  url.toString())
}
end

The php file i used for test is here.

{"FlightInfoStatusResult":{"next_offset":1,"flights":[{"ident":"UPS782","faFlightID":"UPS782-1501705398-0-0-15","airline":"UPS","flightnumber":"782","tailnumber":"N150UP","type":"Form_Airline","blocked":false,"diverted":false,"cancelled":false,"origin":{"code":"KSAT","city":"San Antonio, TX","alternate_ident":"","airport_name":"San Antonio Intl"},"destination":{"code":"KLRD","city":"Laredo, TX","alternate_ident":"","airport_name":"Laredo Intl"},"filed_ete":1680,"route":"MILET4 LRD","filed_altitude":200,"display_filed_altitude":"20,000 feet","filed_airspeed_kts":353,"distance_filed":150,"filed_departure_time":{"epoch":1501760160,"tz":"CDT","dow":"Thursday","time":"06:36AM","date":"08/03/2017","localtime":1501742160},"estimated_departure_time":{"epoch":1501760100,"tz":"CDT","dow":"Thursday","time":"06:35AM","date":"08/03/2017","localtime":1501742100},"actual_departure_time":{"epoch":0},"departure_delay":-60,"filed_arrival_time":{"epoch":1501761840,"tz":"CDT","dow":"Thursday","time":"07:04AM","date":"08/03/2017","localtime":1501743840},"estimated_arrival_time":{"epoch":1501761780,"tz":"CDT","dow":"Thursday","time":"07:03AM","date":"08/03/2017","localtime":1501743780},"actual_arrival_time":{"epoch":0},"arrival_delay":-60,"status":"Scheduled","progress_percent":-1,"aircrafttype":"A306","full_aircrafttype":"A306","inbound_faFlightID":"UPS782-1501690099-0-0-64","adhoc":false}]}}

That way I wasn’t burning up a bunch of my calls to flightware.

The real page is

<?php
// get flight status on flight
$options = array(
                 'trace' => true,
                 'exceptions' => 0,
                 'login' => 'apilogin',
                 'password' => 'apikey',
                 );
$client = new SoapClient('http://flightxml.flightaware.com/soap/FlightXML3/wsdl', $options);



$params = array(
  "ident" => "UPS782",
  "howMany" => 1,
  "offset" => 0 );

$result = $client->FlightInfoStatus($params);
print_r($result);

So there is some work left to do. In the index page you can see the “ident” is hard coded. That needs to be changed to most likely to the item “FlightNumber_String” and that would put your flight number in.

I was working on that part with the rule “detected flight”

What I was trying to do was do some regex that would search for flight number in a string, that string would be my appointments since google was putting my flight numbers on my calendar automatically.

However, if you do something like just put in a string where you could type in your flight number and the date you are flying you could have the cronjob match that string and turn on the “Traveling_Switch” and then the rest of the rules would fire.

There was another item type i never got around to doing.

String FlightAircraftImage

Then in the rule it would update like:

FlightAircraftType.postUpdate(transform("JSONPATH", "https://www.skybrary.aero/images/$.FlightInfoStatusResult.flights[0].aircrafttype",FlightOriginAirPorttmp).toString)

Or whatever the format is. Not actually doing this on my server. But it seems the site skybrary.aero has ALOT if not all the aircraft type images stored based on their names.

I stopped working on this as my job duties changed and I don’t travel much (if any) like I was.

I hope this helps get you further and would love to see any progress you make or add!!

2 Likes

As there are some trips upcoming, I would like to proceed with this (didn’t have enough time yet :frowning:

Did you succeed? Are you able to get the aircrafts position as well?

I started to use your code and this pushed me forward significantly.

I guess I will have it done soon.
However, I miss one function on flightaware:
Checking for the unique flight id (faFlightID) on a specific date.
the API v3 does not allow GetFlightID (yet)

Anyway, I will try to find a workaround and will post the results here.

1 Like

I wish I was still traveling like i was. but i haven’t had a flight in just over a year. Can’t wait to see what comes of it.

I can’t complain :wink:

Although the good part for me:
My wife is travelling long haul flights as well.

emougjh time to troubleshoot :wink:

1 Like

lol. that was something that made it weird, wasn’t able to work on it while in the air. which would have been so amazing!!

So i used to just pick random flight numbers lol of flightaware that were on cross country trips.

If you start doing any UI stuff like on the dashboards i would love to see. That was my hope, I wanted to have on one of the touchscreen tablets a habpanel button that would light up if it was a travel day. Then show all the details and import the picture of the type of plane ect for the kids.

Great idea!
I will consider this after getting the main points done.

I am almost done.
However, I did not find a link to retrieve the aircraft type pictures.
After the next iteration and check, I will post the stuff here.

Here it comes.
Please let me know if you have questions or better suggestions for improvments.

What it does:
Whenever one of my calendar entries changes and an entry is detected with “Flug” (=flight), the information is extracted and used for a cross check with flightaware. The nearest flight is used for further processing.
Because the flight number is not unique and will re-occurr every day, I cross check this with the departure time in my calendar.
Then I constantly monitor the actual time vs. the departure time and track the flight using flightaware during the flight.
The location will be used for a map display.

My items:

//*********************** Flight Aware *********************************************
String		Fl_API_Usr      "Flight Aware User [%s]"                    (G_jdbc)
String		Fl_API_Key      "Flight Aware API Key [%s]"                 (G_jdbc)
Switch      FlightReq       "Flight Request Trigger"                    (G_jdbc,Group_HabPanel_Dashboard)

String      FlNum           "FlightNumber [%s]"                         (G_jdbc,Group_HabPanel_Dashboard)
DateTime    FlNum_DT        "Start Flug [%1$td.%1$tm. - %1$tH:%1$tM]"   (G_jdbc,Group_HabPanel_Dashboard)
DateTime    FlNum_AT        "Ankunft Flug [%1$td.%1$tm. - %1$tH:%1$tM]" (G_jdbc,Group_HabPanel_Dashboard)
String      FlTrvlr         "Fluggast [%s]"                             (G_jdbc,Group_HabPanel_Dashboard)
Location    Loc_FlTrvlr     "Flug"                                      (G_Loc,Group_HabPanel_Dashboard)
String      Loc_FlTrvlr_Str "Flight Location Address [%s]"              (G_jdbc,Group_HabPanel_Dashboard)

String      FlOrgAirP       "OrgAirport [%s]"                           (G_jdbc,Group_HabPanel_Dashboard)
String      FlDstAirP       "DstAirport [%s]"                           (G_jdbc,Group_HabPanel_Dashboard)
String      FlAircraft      "Aircraft type [%s]"                        (G_jdbc,Group_HabPanel_Dashboard)
String      FlAircr_url     "Aircraft url [%s]"                         (G_jdbc,Group_HabPanel_Dashboard)
DateTime    FlEstDepD       "EstimatedDeparture Date [%s]"              (G_jdbc,Group_HabPanel_Dashboard)
String      FlEstDepT       "EstimatedDeparture Time [%s]"              (G_jdb,Group_HabPanel_Dashboardc)
String      FlActDepT       "ActualDeparture Time [%s]"                 (G_jdbc)
DateTime    FlEstArrD       "EstimatedArrival Date [%s]"                (G_jdbc,Group_HabPanel_Dashboard)
String      FlEstArrT       "EstimatedArrival Time [%s]"                (G_jdbc,Group_HabPanel_Dashboard)
String      FlActArrT       "Actual_Arrival Time [%s]"                  (G_jdbc)
String      FlStatus        "Status [%s]"                               (G_jdbc,Group_HabPanel_Dashboard)
Number      FlProgr         "Progress_Percent [%s]"                     (G_Num,Group_HabPanel_Dashboard)

My calendar check:

//********************** CALENDAR rules **********************
import org.eclipse.smarthome.model.script.ScriptServiceUtil
val action = getActions("telegram","telegram:telegramBot:MyBot")

rule "Check calendar for Flights"
when
    Member of G_CalN changed
then
    logInfo("CALENDAR:", "Calendar entries changed")
	var itemName = triggeringItem.name.toString
    var itemState = triggeringItem.state.toString
    logInfo("CALENDAR:", "itemName: >" + itemName + "<")
    logInfo("CALENDAR:", "itemState: >" + itemState + "<")
// check whether the calendar entry contains flight information
    if(itemState.contains("Flug")) {
    // who's flight is it?
        if(itemState.contains("NCO")) FlTrvlr.postUpdate("NCO")
        else if(itemState.contains("DNA")) FlTrvlr.postUpdate("DNA")
        else if(itemState.contains("Alle")) FlTrvlr.postUpdate("Alle")
    // check Cal entry for flight Number in brackets
        val String flight_tmp = transform("REGEX", ".*\\(([A-Z]{1}.*)\\).*", itemState)
   // Check  which calendar entry (CalName1-14) contains the Flight and get departure and arrival time
        val String calNum = itemName.replaceAll("CalName", "")
        val calStart = ScriptServiceUtil.getItemRegistry.getItem("CalStart" + calNum)
        val calEnd = ScriptServiceUtil.getItemRegistry.getItem("CalEnd" + calNum)
        logInfo("CALENDAR:", "flight_tmp: >" + flight_tmp + "<")
        logInfo("CALENDAR:", "calNum: >" + calNum + "<")
        logInfo("CALENDAR:", "calStart: >" + calStart + "<")
        logInfo("CALENDAR:", "calEnd: >" + calEnd + "<")
    // is there a known flight?
        if(FlNum.state != "-") {
            val Number old_depart = (FlNum_DT.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
            val Number new_depart = (calStart.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
        // update only if the new detected flight departs earlier
            if(new_depart <= old_depart) {
                FlNum.postUpdate(flight_tmp)
                FlNum_DT.postUpdate(calStart.state)
                FlNum_AT.postUpdate(calEnd.state)
                logInfo("CALENDAR:", "calStart State: >" + FlNum_DT.state.format("%1$td.%1$tm. - %1$tH:%1$tM") + "<")
                logInfo("CALENDAR:", "calEnd State: >" + FlNum_AT.state.format("%1$td.%1$tm. - %1$tH:%1$tM") + "<")
                logInfo("CALENDAR:", "New (earlier) Flight detected in Calendar (Entry No. " + calNum + "): " + FlTrvlr.state.toString + " on Flight number: " + FlNum.state.toString + " on: " + FlNum_DT.state.format("%1$td.%1$tm. - %1$tH:%1$tM"))
                action.sendTelegram("(FrĂĽhere) Flugreise von " + FlTrvlr.state.toString + " entdeckt.\nFlugnummer: " + FlNum.state.toString + "\nAm: " + FlNum_DT.state.format("%1$td.%1$tm. - %1$tH:%1$tM"))
            }
            else {
                logInfo("CALENDAR:", "New Flight detected in Calendar (Entry No. " + calNum + ", but it's later than the already known one.")
            }
        }
        else {
            FlNum.postUpdate(flight_tmp)
            FlNum_DT.postUpdate(calStart.state)
            FlNum_AT.postUpdate(calEnd.state)
            logInfo("CALENDAR:", "New (earlier) Flight detected in Calendar (Entry No. " + calNum + "): " + FlTrvlr.state.toString + " on Flight number: " + FlNum.state.toString + " on: " + FlNum_DT.state.format("%1$td.%1$tm. - %1$tH:%1$tM"))
            action.sendTelegram("Flugreise von " + FlTrvlr.state.toString + " entdeckt.\nFlugnummer: " + FlNum.state.toString + "\nAm: " + FlNum_DT.state.format("%1$td.%1$tm. - %1$tH:%1$tM"))
        }
    }
end

My Flight rules:

	//********************** FLIGHT rules *********************
val action = getActions("telegram","telegram:telegramBot:MyBot")


rule "upcoming flights check"
when
	Time cron "57 */15 * * * ?"
then
	if(FlNum.state.toString != "-") {
		logInfo("FLIGHT ***","*** Flight departure time comparison started.")
		val Number now_tmp = now.millis
		val Number depart = (FlNum_DT.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
		val Number arrive = (FlNum_AT.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
		
		// flight en route?	
		if(depart <= now_tmp && arrive >= now_tmp) {
			FlightReq.sendCommand(ON)
			logInfo("FLIGHT ***","*** Flight " + FlNum.state.toString + " en route - Triggering flight data request")
			action.sendTelegram("*** Flight " + FlNum.state.toString + " en route\nTriggering flight data request")
		}
		else {
			FlStatus.postUpdate("scheduled")	
		}
	}
end


// Reset Fliight data after the aircraft has landed
rule "upcoming flights check"
when
	Item FlStatus changed to landed // does this work without "" for a string?
then
// if flight has landed, reset the flight items
	if(FlStatus.state == "landed") {// or arrived???
		logInfo("FLIGHT ***","The Flight " + FlNum.state.toString + " has arrived in " + FlDstAirP.state.toString + " - FlightData will be reset")
		FlNum.postUpdate("-")
		FlNum_DT.postUpdate(NULL)
		FlNum_AT.postUpdate(NULL)
		FlOrgAirP.postUpdate("-")
		FlDstAirP.postUpdate("-")
		FlStatus.postUpdate("-")
		FlProgr.postUpdate("-")
		FlAircraft.postUpdate("-")
		FlTrvlr.postUpdate("-")
		Loc_FlTrvlr.postUpdate(NULL) 
	}
end


rule "Flight Request"
when
	Item FlightReq received command ON
then

// this rule will be triggered once the next flight is airborn
	val int num = 3 // number of flights to be requested
	val String fl_date = FlNum_DT.state.format("%1$tm/%1$td/%1$tY") // used to find the right flight in the flightaware response
	logInfo("FLIGHT ***","*** START Flight check of flight " + FlNum.state.toString + " on " + FlNum_DT.state.format("%1$td.%1$tm. - %1$tH:%1$tM") + " - Flight date: " + fl_date)
// check the departure time from calendar item - this might be used later iv APIv3 provides GetFlightID request. This woule ease the check for the right flight (vs. looking for the departure date within the response of FlightInfoStatus)
	val Number depart = (FlNum_DT.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli / 1000 // this is the right time format for GetFlightID (das gibts aber noch nicht bei API v3)
	logInfo("FLIGHT ***","depart (EPOCH) >" + depart + "<")	// should be of format 1582660800

// request data for the actual flight number. the right flight (today) will be extracted by comparing the departure date
	var String flight_tmp = sendHttpGetRequest("http://" + Fl_API_Usr.state.toString + ":" + Fl_API_Key.state.toString + "@flightxml.flightaware.com/json/FlightXML3/FlightInfoStatus?ident=" + FlNum.state.toString + "&howMany=" + num)
	if(flight_tmp.contains("No flights found")) {
		logInfo("FLIGHT ***","No Flights found for Flight number " + FlNum.state.toString)
		action.sendTelegram("Keinen Flug zur Flugnummer " + FlNum.state.toString + " gefunden")
		return; // failing fast
	}

	var int flID = 0 // ID of the returned flight
	// OPEN TASK: how to optimize this with a loop?
	var String depDate0 = transform("JSONPATH", "$.FlightInfoStatusResult.flights[0].estimated_departure_time.date",flight_tmp).toString
	var String depDate1 = transform("JSONPATH", "$.FlightInfoStatusResult.flights[1].estimated_departure_time.date",flight_tmp).toString
	var String depDate2 = transform("JSONPATH", "$.FlightInfoStatusResult.flights[2].estimated_departure_time.date",flight_tmp).toString
	var String depDate3 = transform("JSONPATH", "$.FlightInfoStatusResult.flights[3].estimated_departure_time.date",flight_tmp).toString
	if(depDate0 == fl_date) {
		flID = 0
	}
	else if(depDate1 == fl_date) {
		flID = 1
	}
	else if(depDate2 == fl_date) {
		flID = 2
	}
	else if(depDate3 == fl_date) {
		flID = 3
	}
	else {
		logInfo("FLIGHT ***","No matching departure date found for flight " + FlNum.state.toString)
	}
	logInfo("FLIGHT ***","Identified Flight ID ["  + flID + "] for Flight " + FlNum.state.toString)

	var String faFlID = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].faFlightID",flight_tmp).toString
	var String origin = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].origin.city",flight_tmp).toString
	var String dest = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].destination.city",flight_tmp).toString
	var String estDate = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].estimated_departure_time.date",flight_tmp).toString
	var String estTime = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].estimated_departure_time.time",flight_tmp).toString
	var String status = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].status",flight_tmp).toString
	var String progress = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].progress_percent",flight_tmp).toString
	var String aircraft = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].aircrafttype",flight_tmp).toString
	var String diverted = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].diverted",flight_tmp).toString
	var String cancelled = transform("JSONPATH", "$.FlightInfoStatusResult.flights[" + flID + "].cancelled",flight_tmp).toString
	FlOrgAirP.postUpdate(origin)
	FlDstAirP.postUpdate(dest)
	FlStatus.postUpdate(status)
	FlProgr.postUpdate(progress)
	FlAircraft.postUpdate(aircraft)
	FlAircr_url.postUpdate("url" + aircraft)
	
	if(diverted == "true" || cancelled == "true") {
		logInfo("FLIGHT ***","Flight: Problems detected on flight " + faFlID + ": diverted: " + diverted + " / cancelled: " + cancelled + " / status: " + status)
		action.sendTelegram("Flight: Probleme mit Flug " + faFlID + "\ndiverted: " + diverted + "\ncancelled: " + cancelled + "\nstatus: " + status)
	}

// requesting the flight data for the unique faFlID
	var String Status_tmp = sendHttpGetRequest("http://" + Fl_API_Usr.state.toString + ":" + Fl_API_Key.state.toString + "@flightxml.flightaware.com/json/FlightXML3/GetFlightTrack?ident=" + faFlID + "&include_position_estimates=true")
// Check if data is available at all (in case not departed yet?)	
	if(Status_tmp.contains("NO_DATA")) {
		logInfo("FLIGHT ***","No Flight DATA yet - estimated departure on " + estDate + " at " + estTime)
		action.sendTelegram("No Flight DATA yet - estimated departure on " + estDate + " at " + estTime)
		Loc_FlTrvlr.postUpdate(NULL)
	}
	else {
	// get location out of the response (check json from the end of the response => latest position update)
		var lat = transform("JSONPATH", "$.GetFlightTrackResult.tracks[-1:].latitude",Status_tmp).toString
		var lon = transform("JSONPATH", "$.GetFlightTrackResult.tracks[-1:].longitude",Status_tmp).toString
		logInfo("FLIGHT ***","Location lat >" + lat + "<")
		logInfo("FLIGHT ***","Location lon >" + lon + "<")
		val loc = new PointType(new DecimalType(lat), new DecimalType(lon))
		Loc_FlTrvlr.postUpdate(loc)
	// set the Location of the travelling family members to NULL
		if(FlTrvlr.state == "NCO") NCO_Loc.postUpdate(NULL)
		if(FlTrvlr.state == "DNA") DNA_Loc.postUpdate(NULL)
		if(FlTrvlr.state == "Alle") {
			NCO_Loc.postUpdate(NULL)
			DNA_Loc.postUpdate(NULL)
			PEP_Loc.postUpdate(NULL)
		}
		action.sendTelegram("Flight (" + FlTrvlr.state.toString + "): " + FlNum.state.toString + " departed on " + estDate + " at " + estTime)
	}
end

Please let me know if you have ideas for improvement.
Especially the part in the flights rules, where I check the departure time, would be possibly better handled by a loop - but I did not figure out how, yet.

And my Habpanel View

1 Like

I have another idea / need for improvement:
After a flight has landed, the next upcoming flight will not be used unless the calendar entry changes.

So, how to cycle through all the calendar entries (CalName1 to CalName14) to check for “Flug”?
Any suggestions are greatly appreciated.