FlightRadar24 planes overhead using Node Red

I’ve been feeding ADS-B data to FlightRadar24 since before I started using HA. I’ve always wanted to capture my local “planes overhead” data and show it on my dashboard, but have frankly been too lazy until now. I finally decided to get to work and use Node Red to my advantage, and it’s great!

Here’s how it works in a nutshell -

  1. Local API call to my Raspberry Pi running FlightRadar24’s feeder software.
  2. Parse JSON data and find the newest aircraft with “flight” info (this can be either a callsign or tail number).
  3. Use the Node Red integration to create an entity in HA with the “flight” info as the state.
  4. Add some other data as attributes (speed, lat, long, etc).
  5. Show the last flight overhead on Lovelace
  • (optional) Reboot the software every two days… it has a tendency to cease collecting data without warning.
  • (optional) Send a notification via Telegram if an aircraft is detected with an emergency squawk code.

Code for Node Red here:

[{"id":"06aeebb4b00fdca0","type":"tab","label":"Airshare","disabled":false,"info":""},{"id":"b3050475b9282833","type":"http request","z":"06aeebb4b00fdca0","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"http://IP_OF_YOUR_FR24_FEEDER:8080/data/aircraft.json","tls":"","persist":false,"proxy":"","authType":"","x":270,"y":140,"wires":[["787407227200ba2f"]]},{"id":"4525baba29804a36","type":"inject","z":"06aeebb4b00fdca0","name":"10s","props":[{"p":"payload"}],"repeat":"10","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"date","x":110,"y":140,"wires":[["b3050475b9282833"]]},{"id":"787407227200ba2f","type":"json","z":"06aeebb4b00fdca0","name":"","property":"payload","action":"","pretty":false,"x":430,"y":140,"wires":[["530db59b100a47d6"]]},{"id":"530db59b100a47d6","type":"function","z":"06aeebb4b00fdca0","name":"Plane data","func":"var planes = msg.payload.aircraft;\nvar zeroPlanes;\nvar arrayLength = planes.length;\nfor (var i = 0; i < arrayLength; i++) {\n    zeroPlanes = planes[i];\n    if(zeroPlanes.flight)\n    {\n        msg.hex = zeroPlanes.hex;\n        msg.squawk = zeroPlanes.squawk;\n        msg.lat = zeroPlanes.lat;\n        msg.long = zeroPlanes.lon;\n        msg.altitude = zeroPlanes.altitude;\n        msg.flight = zeroPlanes.flight;\n        msg.seen = zeroPlanes.seen;\n        msg.vert_rate = zeroPlanes.vert_rate;\n        msg.speed = zeroPlanes.speed;\n        msg.track = zeroPlanes.track;\n        msg.seen_pos = zeroPlanes.seen_pos;\n        msg.emergency = zeroPlanes.emergency;\n        break;\n    }\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":140,"wires":[["c801b9245b686ae8","08e92615957d75f8"]]},{"id":"c801b9245b686ae8","type":"switch","z":"06aeebb4b00fdca0","name":"Flight number?","property":"flight","propertyType":"msg","rules":[{"t":"istype","v":"undefined","vt":"undefined"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":460,"y":240,"wires":[[],["5b78c7805ed4eecc"]]},{"id":"007262ddef25853d","type":"http request","z":"06aeebb4b00fdca0","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"http://IP_OF_YOUR_FR24_FEEDER:8754/restart.html","tls":"","persist":false,"proxy":"","authType":"","x":370,"y":440,"wires":[[]]},{"id":"1b25d82a78478de4","type":"inject","z":"06aeebb4b00fdca0","name":"Tue/Thu/Sat at 4am","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"00 04 * * 2,4,6","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":160,"y":440,"wires":[["007262ddef25853d"]]},{"id":"1a8f929f4b64a21c","type":"comment","z":"06aeebb4b00fdca0","name":"Get nearby planes","info":"","x":150,"y":80,"wires":[]},{"id":"f01443e29f3f067a","type":"comment","z":"06aeebb4b00fdca0","name":"Reboot planespotter every few days","info":"","x":200,"y":380,"wires":[]},{"id":"08e92615957d75f8","type":"switch","z":"06aeebb4b00fdca0","name":"Emergency?","property":"emergency","propertyType":"msg","rules":[{"t":"istype","v":"undefined","vt":"undefined"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":450,"y":300,"wires":[[],["d189793fbe5c7bb0"]]},{"id":"d189793fbe5c7bb0","type":"function","z":"06aeebb4b00fdca0","name":"Message contents","func":"msg.payload = {\"type\": \"message\", \"content\": \"Aircraft detected with emergency! Flight: \" + msg.flight + \" Emergency: \" + msg.emergency, \"chatId\": YOUR_TELEGRAM_CHAT_ID}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":300,"wires":[["ba68262dc66b5324"]]},{"id":"ba68262dc66b5324","type":"function","z":"06aeebb4b00fdca0","name":"Your Telegram bot here!","func":"Replace this node with a \"Telegram\" node. You must install Telegram nodes in Node Red. ","outputs":1,"noerr":16,"initialize":"","finalize":"","libs":[],"x":910,"y":300,"wires":[[]]},{"id":"ce89da9c70785548","type":"comment","z":"06aeebb4b00fdca0","name":"Important note about the entity node","info":"You must install the Node Red HA integration!\n\nCheck the docs for this node. \n\nhttps://github.com/zachowj/hass-node-red","x":800,"y":200,"wires":[]},{"id":"5b78c7805ed4eecc","type":"ha-entity","z":"06aeebb4b00fdca0","name":"Planes","server":"95d0e223.bc05a","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":""},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""}],"state":"flight","stateType":"msg","attributes":[{"property":"Speed","value":"speed","valueType":"msg"},{"property":"Track","value":"track","valueType":"msg"},{"property":"Altitude","value":"altitude","valueType":"msg"},{"property":"Squawk","value":"squawk","valueType":"msg"},{"property":"Lat","value":"lat","valueType":"msg"},{"property":"Long","value":"long","valueType":"msg"},{"property":"Vertical rate","value":"vert_rate","valueType":"msg"},{"property":"Time since seen","value":"seen","valueType":"msg"},{"property":"Time since position change","value":"seen_pos","valueType":"msg"}],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":630,"y":240,"wires":[[]]},{"id":"95d0e223.bc05a","type":"server","name":"Home Assistant","version":1,"legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"credentials":{}}]
5 Likes

Hi Ben,

Many thanks for sharing this! I got it to work without errors and now I’m trying to understand how to use it. From what I see, this updates a dedicated node’s properties with the flight details of the flight closest to me. Is that correct? [edit: noticed after reading better that it is actually the newest detected flight] It would be great to have it create an entity for each flight with the flight number as a name, but I guess that was a lot more difficult? I did make one change; by renaming properties “lat” and “long” to “latitude” and “longitude”, the entity can be displayed on a map without any conversion.

Do you have any future plans for this? Would be happy to at least help with some ideas…

Hi Medivb,

So glad you like the Node Red flow! When posting here, my goal was for just one other person to find it cool/useful. I did make one change since posting which is adding the distance of the plane from my house (updated “Plane Data” function node code follows with a TODO item to add your house’s lat/long). Don’t forget to add a distance attribute to the entity node with a value of msg.distance.

I just changed lat and long to latitude and longitude and will check out the mapping! Thanks for the tip.

For an entity that tracks each flight, you have to install the Node Red integration. This is a requirement for the entity node to work. Once you complete that integration, your history will look something like the following and each color has an associated flight number!

I don’t currently have plans to make changes, but am 100% open to some ideas.

var planes = msg.payload.aircraft;
var zeroPlanes;
var arrayLength = planes.length;
for (var i = 0; i < arrayLength; i++) {
    zeroPlanes = planes[i];
    if(zeroPlanes.flight)
    {
        msg.hex = zeroPlanes.hex;
        msg.squawk = zeroPlanes.squawk;
        msg.lat = zeroPlanes.lat;
        msg.long = zeroPlanes.lon;
        // If lat and lon, get distance from home
        if(zeroPlanes.lat && zeroPlanes.lon){
**** TODO NEXT TWO LINES **** (remove this line)
            var homeLat = REPLACE_THIS_WITH_YOUR_HOME_LATITUDE_!!!!!!!!!!_!!!!!!!!!!;
            var homeLon = REPLACE_THIS_WITH_YOUR_HOME_LONGITUDE_!!!!!!!!!!_!!!!!!!!!!;
            var R = 6371; // km
            var dLat = (homeLat - zeroPlanes.lat) * Math.PI / 180;
            var dLon = (homeLon - zeroPlanes.lon) * Math.PI / 180;
            var lat1 = (zeroPlanes.lat) * Math.PI / 180;
            var lat2 = (homeLat) * Math.PI / 180;
            
            var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
            Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); 
            var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
            var d = R * c;
            var milesD = d / 1.609;
            milesD = Math.round(milesD * 10) / 10;
            msg.distance = milesD;
        }
        msg.altitude = zeroPlanes.altitude;
        msg.flight = zeroPlanes.flight;
        msg.seen = zeroPlanes.seen;
        msg.vert_rate = zeroPlanes.vert_rate;
        msg.speed = zeroPlanes.speed;
        msg.track = zeroPlanes.track;
        msg.seen_pos = zeroPlanes.seen_pos;
        msg.emergency = zeroPlanes.emergency;
        break;
    }
}

return msg;

Hi Ben,

I did install the add-on, so I am getting full benefits of the updating single sensor, which is already great. I added the distance and that seems to be working too, so awesome.

My ideal scenario would be that I would create a device for the receiver, and add entities for all actively detected planes, so I could create a radarmap with all planes on it inside HAS. I am trying to achieve that by adding an MQTT out node and having Mosquito take care of the receiving side and entity creation. This worked fine for sending all sorts of system sensor data from my PI’s, but I have not been able to get it to work for the aircraft data.

If I succeed, I will let you know!

Again, thanks for the jumpstart on this… Was really struggling to get this off the ground from scratch.

Medi

Ah I understand now, my mistake.

The only solution I’ve been able to think of is use x number of entity nodes and modify the “Plane Data” code to save info for multiple planes in the message object. Then send the info from plane 0 to the first entity node, info from plane 1 to the second entity node, and so on. The only problem with this if you are limited to x planes on the map; it’s not a dynamic solution.

I am trying to achieve that by adding an MQTT out node and having Mosquito take care of the receiving side and entity creation.

The quoted reply above seems like a good way to go.

Thanks @benblb for sharing!
Managed to get it going (after some updating of palettes and Node-Red it self).
I took your Node-Red code and then pasted in your updated Code to update the “Plane Data” node.

Now I need play around some more with mapping and how to “alert-list” certain callsigns and also plot it all on a map :slight_smile:
image

Awesome, so glad you got it working! Thanks for sharing :slight_smile:

@benblb

I am trying to get your updated code working with a 5 km radius, but it still gives me planes further away.

I have this now:

var planes = msg.payload.aircraft;
var zeroPlanes;
var arrayLength = planes.length;
for (var i = 0; i < arrayLength; i++) {
    zeroPlanes = planes[i];
    if (zeroPlanes.flight) {
        msg.hex = zeroPlanes.hex;
        msg.squawk = zeroPlanes.squawk;
        msg.lat = zeroPlanes.lat;
        msg.long = zeroPlanes.lon;
        // If lat and lon, get distance from home
        if (zeroPlanes.lat && zeroPlanes.lon) {
            var homeLat = 5.8428;
            var homeLon = 51.8449;
            var R = 5; // km
            var dLat = (homeLat - zeroPlanes.lat) * Math.PI / 180;
            var dLon = (homeLon - zeroPlanes.lon) * Math.PI / 180;
            var lat1 = (zeroPlanes.lat) * Math.PI / 180;
            var lat2 = (homeLat) * Math.PI / 180;

            var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
            var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
            var d = R * c;
            var milesD = d / 1.609;
            milesD = Math.round(milesD * 10) / 10;
            msg.distance = milesD;
        }
        msg.altitude = zeroPlanes.altitude;
        msg.flight = zeroPlanes.flight;
        msg.seen = zeroPlanes.seen;
        msg.vert_rate = zeroPlanes.vert_rate;
        msg.speed = zeroPlanes.speed;
        msg.track = zeroPlanes.track;
        msg.seen_pos = zeroPlanes.seen_pos;
        msg.emergency = zeroPlanes.emergency;
        break;
    }
}

return msg;

Am I doing something wrong? Or?

Hi @mr_Sjappie! Awesome to see that you want to do this as well. I can take a look at this in the next day or so, but wanted to quickly reply and say you should edit your post to remove the lat/long of your house.

I’ll get back to you shortly.

Do you know if there is any way to include planespotter images or maybe flight aware images in a telegram message?


https://www.planespotters.net/hex/C01A52

I’d like to have notifications for planes and helicopters that are flying over my house or current location that meet specific requirements such as altitude and speed.

Fllightradar uses jetphotos.com but they seem to have a much smaller image database.

If they have an API I think it would be doable, but I have not attempted. Cool idea though!

Really wishing I commented my code more now :stuck_out_tongue:

I suspect my line 16 where I set “var R” which is commented as //km should actually be meters. 6371m is pretty close to 4 miles. No idea why I didn’t use a closer value, but I don’t see anything else wrong with your code.

You may want to try increasing that value and see what happens.

It is my home city in the coordinates, not my direct home. :slight_smile:

I have tried increasing the R value, but still all planes from my receiver are displayed. It looks like it does not filter them out based on the location.

Is there something I can test or try? A debug?

I’ve added some checks. If the plane does not have a distance or the distance is father than 5KM, the distance will be set to 999. Simply create a switch node that checks msg.distance != 999.

I’ve also changed the distance to kilometers from miles for you.

var planes = msg.payload.aircraft;
var zeroPlanes;
var arrayLength = planes.length;
for (var i = 0; i < arrayLength; i++) {
    zeroPlanes = planes[i];
    if(zeroPlanes.flight)
    {
        msg.hex = zeroPlanes.hex;
        msg.squawk = zeroPlanes.squawk;
        msg.lat = zeroPlanes.lat;
        msg.long = zeroPlanes.lon;
        // If lat and lon, get distance from home
        if(zeroPlanes.lat && zeroPlanes.lon){
            var homeLat = 1234;
            var homeLon = -5678;
            var R = 6371; // km
            var dLat = (homeLat - zeroPlanes.lat) * Math.PI / 180;
            var dLon = (homeLon - zeroPlanes.lon) * Math.PI / 180;
            var lat1 = (zeroPlanes.lat) * Math.PI / 180;
            var lat2 = (homeLat) * Math.PI / 180;
            
            var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
            Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); 
            var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
            var d = R * c;
            var milesD = d / 1.609;
            milesD = Math.round(milesD * 10) / 10;
            var kmD = milesD * 1.60934;
            msg.distance = kmD;
            if (kmD > 5)
            {
                msg.distance = 999;
            }

        }
        else
        {
            msg.distance = 999;
        }
        msg.altitude = zeroPlanes.altitude;
        msg.flight = zeroPlanes.flight;
        msg.seen = zeroPlanes.seen;
        msg.vert_rate = zeroPlanes.vert_rate;
        msg.speed = zeroPlanes.speed;
        msg.track = zeroPlanes.track;
        msg.seen_pos = zeroPlanes.seen_pos;
        msg.emergency = zeroPlanes.emergency;
        break;
    }
}

return msg;

Going to test that today! :slight_smile:

It is now working better with the distance. Whoohoo…

I am new to using node red and I did some testing and poking around in the flow. And what I notice with debug node’s is that for example the airplane.json has 90 planes in it. Also the arraylength in your code gives has that same number. But the debug of the msg.flight after the plane data node gives only 1 plane back, I expected every 10 seconds a whole load of msg.flight messages. in this example 80+.

I did some searching for a way to see more messages or an error, but no luck yet. Looks to me that is missing planes this way…

Any idea’s?

To much array data? Or ?

I can upload my example or the airplane.json.

Sorry for all the questions… :wink:

Glad the distance is working for you!

The code I wrote is designed to only provide the most recent airplane with an identifiable callsign. It’s just so I can see which plane last passed by my house. If I want a record of all of the planes that passed by, I can view my “airplanes sensor” history for a list of all callsigns.

A couple of quick changes to the code will allow you to capture and output many planes worth of data. Simply append “i” in the for loop to the end of every variable name (i.e. so you end up with msg.flight1, msg.flight2, msg.flight3, etc), otherwise you’ll end up with the oldest plane in the JSON as your output instead of multiple planes.

You’ll also want to remove the “break” at the end of the code, since this is what boots out of the loop when we find our most recent match.

Lastly, if you want all planes, not just planes with known callsigns, remove the statement, “if(zeroPlanes.flight)” since this is what checks for a callsign.

Best of luck :slight_smile:

The solution with all the planes in separate msg.flight was not very handy for filtering. :frowning:
So I did a bit of trying and have now this code:

var planes = msg.payload.aircraft;
var zeroPlanes;
var arrayLength = planes.length;
var i;
for (i = 0; i < arrayLength; i++) {
    zeroPlanes = planes[i];
    if (zeroPlanes.flight) {
        msg.lat = zeroPlanes.lat;
        msg.long = zeroPlanes.lon;
        // If lat and lon, get distance from home
        if (zeroPlanes.lat && zeroPlanes.lon) {
            var homeLon = 5.8428;
            var homeLat = 51.8449;
            var R = 6373; // km
            var dLat = (zeroPlanes.lat - homeLat) * Math.PI / 180;
            var dLon = (zeroPlanes.lon - homeLon) * Math.PI / 180;
            var lat1 = (homeLat) * Math.PI / 180;
            var lat2 = (zeroPlanes.lat) * Math.PI / 180;
            var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
            var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
            var d = R * c;
            d = Math.round(d * 10) / 10;
            msg.distancekm = d;
            // Check if distance is less than 5 Km
            if (d < 5) {
                msg.distance = 5
                msg.flight = zeroPlanes.flight;
                msg.altitude = zeroPlanes.altitude;
                msg.seen = zeroPlanes.seen;
                msg.vert_rate = zeroPlanes.vert_rate;
                msg.speed = zeroPlanes.speed;
                msg.track = zeroPlanes.track;
                msg.seen_pos = zeroPlanes.seen_pos;
                msg.emergency = zeroPlanes.emergency;
                break;
            }
           }
        
       }
      
    }

return msg;

As I only wanted the planes in my direct neighborhood ( 5 Km) , my receiver pick’s up 50+ planes on average. Maybe my changes are not the correct way to do this filter, it gives some result. Not all planes in the 5 Km range are displayed all the time… so I am open to idea’s or changes,
I still am proud I figured this out already as a programming noob.

Also did a second filter for filtering out a specific callsign, that was simple changed the:
“if (zeroPlanes.flight) {”

to

"if (zeroPlanes.flight == "LIFELN3 “) {”

LIFELN3 is the callsign of the emergency helicopter here.

If you have an idea or suggestion on how to filter a list off callsign’s in one function node that would be helpful. otherwise I will create a few function node’s each filtering a specific callsign.

I am getting there step by step, and maybe it is of use for others.

Hi

I cam across this whilst doing something not with home assistant. I was trying to modify the code above to simply output a message for each aircraft seen, with a callsign, and to report just its distance and altitude in a message.

However its not my forte and i can’t make it work, is this something you could share how to do please?