So for those who are interested, the final answer to the puzzle to get the data into Home Assistant proved to be a little trickier than I thought.
I abandoned my my last attempt because there was always obsolete data left behind, by using a separate entity for each aircraft in my table.
So with a filter to remove incomplete data from the reception of aircraft
[{"id":"19341a3840fd970d","type":"function","z":"d41d834573c2bcff","g":"4f45e06be3158e59","name":"Filter","func":"if (typeof msg.payload.flight == 'undefined' ) {\n msg.payload.flight = \"none\" \n} else {\n}\n\nif (typeof msg.payload.lat == 'undefined' ) {\n msg.payload.lat = 0 \n} else {\n} \n\nif (typeof msg.payload.lon == 'undefined' ) {\n msg.payload.lon = 0 \n} else {\n} \nreturn msg; ","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":460,"wires":[["b68798ee8b45137c"]]},{"id":"b68798ee8b45137c","type":"switch","z":"d41d834573c2bcff","g":"4f45e06be3158e59","name":"FL?","property":"payload.flight","propertyType":"msg","rules":[{"t":"eq","v":"none","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":410,"y":460,"wires":[[],["1dd36fa4691bd8f3"]]},{"id":"1dd36fa4691bd8f3","type":"switch","z":"d41d834573c2bcff","g":"4f45e06be3158e59","name":"LA?","property":"payload.lat","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":530,"y":460,"wires":[[],["f34e695288054fde"]]},{"id":"f34e695288054fde","type":"switch","z":"d41d834573c2bcff","g":"4f45e06be3158e59","name":"LO?","property":"payload.lon","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":650,"y":460,"wires":[[],["805efb922b817f37"]]}]
I then collected the data as before and included a routine to determine azimuth, elevation and distance, based on my Lat, Lon and height ASL
[{"id":"841db51631d5025d","type":"function","z":"d41d834573c2bcff","g":"8607a103b47572b5","name":"abs pos","func":"//Credit: https://javascript.plainenglish.io/calculating-azimuth-distance-and-altitude-from-a-pair-of-gps-locations-36b4325d8ab0\n\n\n\nfunction ParseAngle(ang, limit) {\n var angle = parseFloat(ang);\n if (isNaN(angle) || (angle < -limit) || (angle > limit)) {\n node.error(\"Invalid angle value.\", msg);\n return null;\n } else {\n return angle;\n }\n}\n\nfunction ParseElevation(el) {\n var angle = parseFloat(el);\n if (isNaN(angle)) {\n node.error(\"Invalid elevation value.\", msg);\n return null;\n } else {\n return angle;\n }\n}\n\nfunction ParseLocation(pos) {\n var lat = ParseAngle(pos.lat, 90.0);\n var location = null;\n if (lat != null) {\n var lon = ParseAngle(pos.lon, 180.0);\n if (lon != null) {\n var alt = ParseElevation(pos.alt);\n if (alt != null) {\n location = { lat: lat, lon: lon, alt: alt };\n }\n }\n }\n return location;\n}\n\nfunction EarthRadiusInMeters(latitudeRadians) {\n // latitudeRadians is geodetic, i.e. that reported by GPS.\n // http://en.wikipedia.org/wiki/Earth_radius\n var a = 6378137.0; // equatorial radius in meters\n var b = 6356752.3; // polar radius in meters\n var cos = Math.cos(latitudeRadians);\n var sin = Math.sin(latitudeRadians);\n var t1 = a * a * cos;\n var t2 = b * b * sin;\n var t3 = a * cos;\n var t4 = b * sin;\n return Math.sqrt((t1 * t1 + t2 * t2) / (t3 * t3 + t4 * t4));\n}\n\nfunction GeocentricLatitude(lat) {\n // Convert geodetic latitude 'lat' to a geocentric latitude 'clat'.\n // Geodetic latitude is the latitude as given by GPS.\n // Geocentric latitude is the angle measured from center of Earth between a point and the equator.\n // https://en.wikipedia.org/wiki/Latitude#Geocentric_latitude\n var e2 = 0.00669437999014;\n var clat = Math.atan((1.0 - e2) * Math.tan(lat));\n return clat;\n}\n\nfunction LocationToPoint(c) {\n // Convert (lat, lon, alt) to (x, y, z).\n var lat = c.lat * Math.PI / 180.0;\n var lon = c.lon * Math.PI / 180.0;\n var radius = EarthRadiusInMeters(lat);\n var clat = GeocentricLatitude(lat);\n\n var cosLon = Math.cos(lon);\n var sinLon = Math.sin(lon);\n var cosLat = Math.cos(clat);\n var sinLat = Math.sin(clat);\n var x = radius * cosLon * cosLat;\n var y = radius * sinLon * cosLat;\n var z = radius * sinLat;\n\n // We used geocentric latitude to calculate (x,y,z) on the Earth's ellipsoid.\n // Now we use geodetic latitude to calculate normal vector from the surface, to correct for elevation.\n var cosGlat = Math.cos(lat);\n var sinGlat = Math.sin(lat);\n\n var nx = cosGlat * cosLon;\n var ny = cosGlat * sinLon;\n var nz = sinGlat;\n\n x += c.alt * nx;\n y += c.alt * ny;\n z += c.alt * nz;\n\n return { x: x, y: y, z: z, radius: radius, nx: nx, ny: ny, nz: nz };\n}\n\nfunction Distance(ap, bp) {\n var dx = ap.x - bp.x;\n var dy = ap.y - bp.y;\n var dz = ap.z - bp.z;\n return Math.sqrt(dx * dx + dy * dy + dz * dz);\n}\n\nfunction RotateGlobe(b, a, bradius, aradius) {\n // Get modified coordinates of 'b' by rotating the globe so that 'a' is at lat=0, lon=0.\n var br = { lat: b.lat, lon: (b.lon - a.lon), alt: b.alt };\n var brp = LocationToPoint(br);\n\n // Rotate brp cartesian coordinates around the z-axis by a.lon degrees,\n // then around the y-axis by a.lat degrees.\n // Though we are decreasing by a.lat degrees, as seen above the y-axis,\n // this is a positive (counterclockwise) rotation (if B's longitude is east of A's).\n // However, from this point of view the x-axis is pointing left.\n // So we will look the other way making the x-axis pointing right, the z-axis\n // pointing up, and the rotation treated as negative.\n\n var alat = GeocentricLatitude(-a.lat * Math.PI / 180.0);\n var acos = Math.cos(alat);\n var asin = Math.sin(alat);\n\n var bx = (brp.x * acos) - (brp.z * asin);\n var by = brp.y;\n var bz = (brp.x * asin) + (brp.z * acos);\n\n return { x: bx, y: by, z: bz, radius: bradius };\n}\n\nfunction NormalizeVectorDiff(b, a) {\n // Calculate norm(b-a), where norm divides a vector by its length to produce a unit vector.\n var dx = b.x - a.x;\n var dy = b.y - a.y;\n var dz = b.z - a.z;\n var dist2 = dx * dx + dy * dy + dz * dz;\n if (dist2 == 0) {\n return null;\n }\n var dist = Math.sqrt(dist2);\n return { x: (dx / dist), y: (dy / dist), z: (dz / dist), radius: 1.0 };\n}\n\nfunction Calculate(pos1, pos2) {\n \n var result = {\n from: pos1,\n to: pos2,\n };\n\n var a = ParseLocation(pos1);\n if (a != null) {\n var b = ParseLocation(pos2);\n if (b != null) {\n var ap = LocationToPoint(a);\n var bp = LocationToPoint(b);\n var distKm = 0.001 * Distance(ap, bp);\n distKm = distKm.toFixed(0);\n result.distanceKm = distKm;\n var distNm = distKm *0.539956803;\n distNm = distNm.toFixed(0);\n result.distanceNm = distNm;\n\n // Let's use a trick to calculate azimuth:\n // Rotate the globe so that point A looks like latitude 0, longitude 0.\n // We keep the actual radii calculated based on the oblate geoid,\n // but use angles based on subtraction.\n // Point A will be at x=radius, y=0, z=0.\n // Vector difference B-A will have dz = N/S component, dy = E/W component.\n var br = RotateGlobe(b, a, bp.radius, ap.radius);\n if (br.z * br.z + br.y * br.y > 1.0e-6) {\n var theta = Math.atan2(br.z, br.y) * 180.0 / Math.PI;\n var azimuth = 90.0 - theta;\n if (azimuth < 0.0) {\n azimuth += 360.0;\n }\n if (azimuth > 360.0) {\n azimuth -= 360.0;\n }\n azimuth = azimuth.toFixed(0);\n result.azimuth = azimuth;\n }\n\n var bma = NormalizeVectorDiff(bp, ap);\n if (bma != null) {\n // Calculate altitude, which is the angle above the horizon of B as seen from A.\n // Almost always, B will actually be below the horizon, so the altitude will be negative.\n // The dot product of bma and norm = cos(zenith_angle), and zenith_angle = (90 deg) - altitude.\n // So altitude = 90 - acos(dotprod).\n var altitude = 90.0 - (180.0 / Math.PI) * Math.acos(bma.x * ap.nx + bma.y * ap.ny + bma.z * ap.nz);\n altitude = altitude.toFixed(0);\n result.elev_angle = altitude;\n }\n }\n }\n return result;\n}\nmsg.payload = Calculate(msg.payload.from, msg.payload.to);\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":940,"y":840,"wires":[["7b789d08e5aabfcb"]]},{"id":"7b789d08e5aabfcb","type":"function","z":"d41d834573c2bcff","g":"8607a103b47572b5","name":"together","func":"//str to num\nvar distanceKm = (parseFloat(msg.payload.distanceKm));\nvar distanceNm = (parseFloat(msg.payload.distanceNm));\nvar azimuth = (parseFloat(msg.payload.azimuth));\nvar elev_angle = (parseFloat(msg.payload.elev_angle));\nvar alt = (parseFloat(msg.payload.to.alt));\nvar dir = Number(msg.payload.to.dir).toFixed(0);\ndir = (parseFloat(dir));\nvar flight = (msg.payload.to.flight).trim();\n\nmsg.payload = {\n hex: msg.payload.to.hex,\n flight: flight,\n //callsign: msg.payload.to.callsign,\n //iata: msg.payload.to.iata,\n //codes: msg.payload.to.codes,\n height: msg.payload.to.height,\n speed: msg.payload.to.speed.toFixed(0),\n dir: dir,\n lat: Number(msg.payload.to.lat).toFixed(2),\n lon: Number(msg.payload.to.lon).toFixed(2),\n alt: alt,\n km: distanceKm,\n nm: distanceNm,\n azimuth: azimuth,\n elev_angle: elev_angle,\n state: msg.payload.to.state,\n ts: msg.payload.to.ts,\n last_updated: msg.payload.to.last_updated,\n\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1080,"y":840,"wires":[["6d91f1a274d1f046"]]},{"id":"05dffa0d3f42da1e","type":"function","z":"d41d834573c2bcff","g":"8607a103b47572b5","name":"Set LOC","func":"\n//home position\nvar from = {\n lat: 50.2774255,\n lon: -3.7715363,\n alt: 22,\n}\n\nvar to = {\n hex: msg.to.hex,\n flight: msg.to.flight.trim(),\n callsign: msg.payload.callsign,\n //iata: msg.payload._airport_codes_iata,\n //codes: msg.payload.airport_codes,\n height: msg.to.height,\n alt: msg.to.height,\n speed: msg.to.speed,\n dir: msg.to.dir,\n lat: msg.to.lat,\n lon: msg.to.lon,\n state: msg.to.state,\n ts: msg.to.ts,\n last_updated: msg.to.last_updated,\n\n}\n\n\nmsg.payload = {\n from: from,\n to: to,\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":800,"y":840,"wires":[["841db51631d5025d"]]}]
but this time I posted a xxx.json file directly to the Home Assistant www. folder as an html resource (thanks to kbrown01 for his input.
[{"id":"8ba66f53f448bfc6","type":"join","z":"d41d834573c2bcff","g":"4f45e06be3158e59","name":"","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"key","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"1","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":270,"y":580,"wires":[["a8f8e3c8252c5065"]]},{"id":"91395230bdeaaa1b","type":"json","z":"d41d834573c2bcff","g":"4f45e06be3158e59","name":"","property":"payload","action":"str","pretty":false,"x":650,"y":580,"wires":[["2494bb6753a4da9d"]]},{"id":"a8f8e3c8252c5065","type":"change","z":"d41d834573c2bcff","g":"4f45e06be3158e59","name":"airplanes","rules":[{"t":"set","p":"payload","pt":"msg","to":"{ \"airplanes\" : payload }","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":580,"wires":[["91395230bdeaaa1b"]]},{"id":"2494bb6753a4da9d","type":"file","z":"d41d834573c2bcff","g":"4f45e06be3158e59","name":"Output","filename":"/config/www/community/skyware/airdata.json","filenameType":"str","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"setbymsg","x":790,"y":580,"wires":[["d5a167db2aeda143"]]}]
Using the REST platform I completed the operation in the config.yaml file with a sensor
- platform: rest
name: planes
resource: http://192.168.1.130:8123/local/community/skyware/airdata.json
value_template: >
{{ value_json.airplanes | length }}
method: GET
scan_interval: 60
json_attributes:
- "airplanes"
- "flight"
- "height"
- "speed"
- "dir"
- "lat"
- "lon"
- "alt"
- "km"
- "nm"
- "azimuth"
- "elev_angle"
Job done!!!
This opens up huge possibilities to me by including JSON files with attributes generated with Node-Red and placing the output directly into HA.
I hope you might be inspired by this enjoy