Script for Airplanes.live api

In this subforum you can show projects you have made, or you are busy with. Please create your own topic.

Moderator: leecollings

janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Script for Airplanes.live api

Post by janpep »

In addition to switching devices and registering consumption, measurements, etc., I also use Domoticz for certain information provision in and around the house. With this project I now also get the information about airplanes flying over my house in Domoticz. The source is Airplanes.live.

I will start to show the result:
AirplanesLive-1-JanPep.png
AirplanesLive-1-JanPep.png (11.58 KiB) Viewed 2052 times
In the log of the device:
AirplanesLive-2-JanPep.png
AirplanesLive-2-JanPep.png (45.56 KiB) Viewed 2052 times
According to the website: "It leverages enthusiast receivers to capture ADS-B (Automatic Dependent Surveillance-Broadcast) and MLAT (Multilateration) data, providing real-time information about aircraft in your area." On the site you can see a live map of the the current flights in the airspace above you.
There is also an API with which you can request the data. No api-key is needed. Shortest poll frequency interval is 1 second.
"Access does not currently require a feeder. That might change in the future."
At the moment I am not a feeder, but that might change in the future :-).

The reason:
After routes have been shifted, I increasingly hear planes flying low south of Amsterdam (20 km from Schiphol airport). Sometimes I can count the windows.
I don't want to complain, but every once in a while I think it's worth reporting. I do not do it because I would not have registered the details. Sometimes I am just curious what the heigt is, when I see or hear an airplane.

The solution:
With the script I can request the data for a desired max. radius from my location.
With a frequency of 1 minute I sometimes caught an airplane several times. At different distances, because it is passing with high speed. I just want it one time AND with its coordinates as close as possible. Therefore I have now set the poll frequency every 15 seconds. I found this smart solution from waaren in https://www.domoticz.com/forum/viewtopi ... ds#p220418
This works very well.

So what it does:
  • I store the result in a global table and overwrite its row content when the next one is closer.
  • I write it to a text device when it is no longer reported within the specified radius.
  • I am not interested in planes passing very high and therefore I filter on a configurable max. altitude and skip them.
  • In the text device it provides the flight code (plus hyperlink to its flight on the live map) with details of the aircraft, altitude, speed and distance. The flight direction and the speed of ascent or descent are also displayed, as well as the time of the observation.
Some insight in this proces can be seen in (parts from) the log, that shows what happened with the airplane you see in the first image:

Code: Select all

--===============================================================
Every 5 minutes you see in the log that the OpenURL is scheduled (to run every 15 seconds)
2024-05-29 09:55:00.663  Status: dzVents: Debug: Airplanes-: OpenURL: url = https://api.airplanes.live/v2/point/52.14313121973369/4.672619005171448/1.86411357
2024-05-29 09:55:00.663  Status: dzVents: Debug: Airplanes-: OpenURL: method = GET
2024-05-29 09:55:00.663  Status: dzVents: Debug: Airplanes-: OpenURL: post data = nil
2024-05-29 09:55:00.663  Status: dzVents: Debug: Airplanes-: OpenURL: headers = nil
2024-05-29 09:55:00.663  Status: dzVents: Debug: Airplanes-: OpenURL: callback = airplanes
2024-05-29 09:55:00.663  Status: dzVents: Info: Airplanes-: ------ Finished t-Airplanes
--===============================================================

--Then it runs every 15 seconds. Several times nothing is found. Here an example.
2024-05-29 09:56:16.011  Status: dzVents: Info: Airplanes-: ------ Start internal script: t-Airplanes: HTTPResponse: "airplanes"
2024-05-29 09:56:16.011  Status: dzVents: Debug: Airplanes-: Item and JSON - OK
2024-05-29 09:56:16.012  Status: dzVents: Debug: Airplanes-: result_table: type = table
2024-05-29 09:56:16.012  Status: dzVents: Debug: Airplanes-: Total flights found in response =  0.
2024-05-29 09:56:16.012  Status: dzVents: Debug: Airplanes-: Nothing left in global _d.ap_table to write to device.
2024-05-29 09:56:16.012  Status: dzVents: Info: Airplanes-: ------ Finished t-Airplanes
--===============================================================

-- Example of an airplane passing higher that the max height that was set.
2024-05-29 09:56:31.051  Status: dzVents: Info: Airplanes-: ------ Start internal script: t-Airplanes: HTTPResponse: "airplanes"
2024-05-29 09:56:31.054  Status: dzVents: Debug: Airplanes-: Item and JSON - OK
2024-05-29 09:56:31.054  Status: dzVents: Debug: Airplanes-: result_table: type = table
2024-05-29 09:56:31.054  Status: dzVents: Debug: Airplanes-: Lat en Long: 52.116486 - 4.678879
2024-05-29 09:56:31.055  Status: dzVents: Debug: Airplanes-: Skip flight that is passing at 10729m. is higher then the set 3500 m.
2024-05-29 09:56:31.055  Status: dzVents: Info: Airplanes-: ------ Finished t-Airplanes
--===============================================================

-- In the next run it sees a airplane at 2.5 km.
2024-05-29 09:57:46.073  Status: dzVents: Info: Airplanes-: ------ Start internal script: t-Airplanes: HTTPResponse: "airplanes"
2024-05-29 09:57:46.075  Status: dzVents: Debug: Airplanes-: Item and JSON - OK
2024-05-29 09:57:46.075  Status: dzVents: Debug: Airplanes-: result_table: type = table
2024-05-29 09:57:46.075  Status: dzVents: Debug: Airplanes-: Lat en Long: 52.156615 - 4.702698
2024-05-29 09:57:46.076  Status: dzVents: Debug: Airplanes-: I see: 34654b with distance = 2.5 km.
2024-05-29 09:57:46.076  Status: dzVents: Debug: Airplanes-: 34654b is not found in _d.ap_table
2024-05-29 09:57:46.076  Status: dzVents: Debug: Airplanes-: New row created in global _d.ap_table with distance 2.5 km.
2024-05-29 09:57:46.076  Status: dzVents: Info: Airplanes-: ------ Finished t-Airplanes
--===============================================================

-- In the next run it sees the same airplane. >50% closer at 1.2 km. Find it in the table and update
2024-05-29 09:58:01.056  Status: dzVents: Info: Airplanes-: ------ Start internal script: t-Airplanes: HTTPResponse: "airplanes"
2024-05-29 09:58:01.059  Status: dzVents: Debug: Airplanes-: Item and JSON - OK
2024-05-29 09:58:01.059  Status: dzVents: Debug: Airplanes-: result_table: type = table
2024-05-29 09:58:01.059  Status: dzVents: Debug: Airplanes-: Lat en Long: 52.141067 - 4.690456
2024-05-29 09:58:01.059  Status: dzVents: Debug: Airplanes-: I see: 34654b with distance = 1.2 km.
2024-05-29 09:58:01.059  Status: dzVents: Debug: Airplanes-: 34654b is found in _d.ap_table
2024-05-29 09:58:01.059  Status: dzVents: Debug: Airplanes-: Global _d.ap_table exist with 1 rows.
2024-05-29 09:58:01.059  Status: dzVents: Debug: Airplanes-: Update row with closer distance 1.2 km.
2024-05-29 09:58:01.060  Status: dzVents: Info: Airplanes-: ------ Finished t-Airplanes
--===============================================================

-- In the next run it sees the same airplane again at 2.0 km. Find it in the table, but does not update, because it is further away.
2024-05-29 09:58:16.041  Status: dzVents: Info: Airplanes-: ------ Start internal script: t-Airplanes: HTTPResponse: "airplanes"
2024-05-29 09:58:16.045  Status: dzVents: Debug: Airplanes-: Item and JSON - OK
2024-05-29 09:58:16.046  Status: dzVents: Debug: Airplanes-: result_table: type = table
2024-05-29 09:58:16.047  Status: dzVents: Debug: Airplanes-: Lat en Long: 52.125229 - 4.67804
2024-05-29 09:58:16.047  Status: dzVents: Debug: Airplanes-: I see: 34654b with distance = 2.0 km.
2024-05-29 09:58:16.047  Status: dzVents: Debug: Airplanes-: 34654b is found in _d.ap_table
2024-05-29 09:58:16.047  Status: dzVents: Debug: Airplanes-: Global _d.ap_table exist with 1 rows.
2024-05-29 09:58:16.048  Status: dzVents: Info: Airplanes-: ------ Finished t-Airplanes
--===============================================================

-- In the next run it sees no airplane. Now time to check and write the row that is created to the device.
2024-05-29 09:58:31.032  Status: dzVents: Info: Airplanes-: ------ Start internal script: t-Airplanes: HTTPResponse: "airplanes"
2024-05-29 09:58:31.032  Status: dzVents: Debug: Airplanes-: Item and JSON - OK
2024-05-29 09:58:31.032  Status: dzVents: Debug: Airplanes-: result_table: type = table
2024-05-29 09:58:31.032  Status: dzVents: Debug: Airplanes-: Total flights found in response =  0.
2024-05-29 09:58:31.033  Status: dzVents: Debug: Airplanes-: Write and clear from global _d.ap_table, now no more flights are seen.
2024-05-29 09:58:31.045  Status: dzVents: Debug: Airplanes-: Processing device-adapter for Airplanes.Live: Text device
2024-05-29 09:58:31.045  Status: dzVents: Debug: Airplanes-: Airplane  = <a href="https://globe.airplanes.live/?icao=34654b" target="_blank"><span style="color: Blue">KLM29M  </span></a> - EC-NLJ - AIRBUS A-321<br>Hoogte: 2271 m. Snelheid: 471 km/u. Afstand: 1.2 km.<br>Richting: ZZW Stijgt 956 m/min. Tijd: 09:58:01
2024-05-29 09:58:31.045  Status: dzVents: Debug: Airplanes-: Saving row with distance 1.2 km.
2024-05-29 09:58:31.045  Status: dzVents: Debug: Airplanes-: Removed index 1 from global _d.ap_table.
2024-05-29 09:58:31.045  Status: dzVents: Info: Airplanes-: ------ Finished t-Airplanes
--===============================================================

-- Finaly it sees nothing and has nothing left to do.
2024-05-29 09:58:46.053  Status: dzVents: Info: Airplanes-: ------ Start internal script: t-Airplanes: HTTPResponse: "airplanes"
2024-05-29 09:58:46.054  Status: dzVents: Debug: Airplanes-: Item and JSON - OK
2024-05-29 09:58:46.054  Status: dzVents: Debug: Airplanes-: result_table: type = table
2024-05-29 09:58:46.054  Status: dzVents: Debug: Airplanes-: Total flights found in response =  0.
2024-05-29 09:58:46.054  Status: dzVents: Debug: Airplanes-: Nothing left in global _d.ap_table to write to device.
2024-05-29 09:58:46.054  Status: dzVents: Info: Airplanes-: ------ Finished t-Airplanes
--===============================================================
The script t-Airplanes:

Code: Select all

-- 24-05-2024 script by Jan Peppink, https://ict.peppink.nl
-- Goal: Aircraft 'noise pollution detector'.
-- https://api.airplanes.live/v2/point/<lat>/<long>/<distanceKM>
-- Get data of airplaines that pass close by with lower altitudes.
-- When you hear them, they are probably logged. :-)
-- 27-05-2024 Call every 15 seconds. Store playe in global data table.
--  Update this when same plane is closer with call
--  When no planes are catched write the table (if not empty) to device.

--- Your settings ----------------------------------------------------------------
-- First set the used device index numbers and variables you might want to change.
---#################################################################
local ap_text_idx = 99999    --idx of the custom Text device
local ap_maxRadiusKm = 3    -- Query max radius in KM around your coordinates.
local ap_maxAltitudeM = 3500   -- Save only < max ap_maxAltitude in Meters.

----------------------------------------------------------------------------------
return {
	on = {
		timer = { 
            'every 5 minutes', -- Below in a loop it is made to 15 seconds!
        },
        httpResponses = {
            'airplanes'       -- matches callback string below
        },
	},
	logging = {
        -- Level can be domoticz.LOG_INFO, domoicz.LOG_MODULE_EXEC_INFO, domoticz.LOG_DEBUG, domoticz.LOG_ERROR or domoticz.LOG_FORCE
        --level = domoticz.LOG_INFO,
        --level = domoticz.LOG_DEBUG,
		marker = 'Airplanes-',
	},
	execute = function(dz, triggeredItem)
        -- Set Local environment=================
        local _u = dz.utils       -- Holds subset of handy utilities.
        local _h = dz.helpers     -- Holds the global functions
        local _d = dz.globalData  -- Holds the global data

        --_d.initialize('ap_table') -- Re-initialize the table

        -- Get location coordinates
        local lat = dz.settings.location.latitude
        local long = dz.settings.location.longitude

        local ap_maxRadiusMiles = ap_maxRadiusKm * 0.62137119 -- Calculate given km in sdettings to miles.
        
        -- Use some color variables
--        local cGreen = '#008000;'
--        local cYellow = '#ffff00;'
--        local cOrange = '#ffa500;'
--        local cRed = '#ff0000;'	
--        local cWhite = '#FFFFFF;'
--        local cBlue = '#0000FF;'
--        local cGrey = '#8d8d8d;'

--        local htmlColor = '#3b3bc4;'
--        --Adjust lineheight
--        local lineHeight = 1.2

        -- Local Functions go here =============
        local function airplanesExist( testValue )
            -- check if this plane is already in _d.ap_table
            if type( _d.ap_table ) == "table" then
                for i = 1, #_d.ap_table do
                    if _d.ap_table[i].ap_hex == testValue then
                        -- Found in table
                        dz.log( testValue .. ' is found in _d.ap_table', dz.LOG_DEBUG )
                        return true
                    end
                end
                -- Not found in table
                dz.log( testValue .. ' is not found in _d.ap_table', dz.LOG_DEBUG )
                return false
            else
                -- Table not found.
                dz.log( 'Did not find _d.ap_table - Re-initialize.', dz.LOG_DEBUG )
                _d.initialize('ap_table') -- Re-initialize the table
                return false
            end
        end

        -- Now start to do something ============
        -- Get the data.
		if (triggeredItem.isTimer) then
            -- Retrieve the data every 15 second per 5 minutes.
            for seconds = 15, 285, 15 do -- start with 15 step 15 until 285
                dz.openURL({
				    url = 'https://api.airplanes.live/v2/point/' .. lat .. '/' .. long ..'/' .. ap_maxRadiusMiles,
				    method = 'GET',
				    callback = 'airplanes'
                }).afterSec(seconds)
            end
		end	

        if (triggeredItem.isHTTPResponse) then
            -- Process the obtained data.
            -- Check the response and process the data.
            if (triggeredItem.ok and triggeredItem.isJSON) then
                dz.log( 'Item and JSON - OK', dz.LOG_DEBUG )
				-- We have some result. Store in table.
				local result_table = triggeredItem.json.ac
                if type(result_table) == "table" then
                    dz.log( 'result_table: type = ' .. type(result_table), dz.LOG_DEBUG )
                    -- The data we get is:
					--{
					--  "ac": [
					--    {
					--      "hex": "485064",
					--      "type": "adsb_icao",
					--      "flight": "KLM66C  ",
					--      "r": "PH-EZY",
					--      "t": "E190",
					--      "desc": "EMBRAER ERJ-190-100",
					--      "ownOp": "Klm Cityhopper",
					--      "alt_baro": 1875,
					--      "alt_geom": 2200,
					--      "gs": 254.5,
					--      "ias": 248,
					--      "tas": 256,
					--      "mach": 0.388,
					--      "wd": 19,
					--      "ws": 6,
					--      "oat": 6,
					--      "tat": 15,
					--      "track": 93.6,
					--      "track_rate": 0.09,
					--      "roll": -0.18,
					--      "mag_heading": 90,
					--      "true_heading": 92.4,
					--      "baro_rate": -192,
					--      "geom_rate": -128,
					--      "squawk": "7631",
					--      "emergency": "none",
					--      "category": "A3",
					--      "nav_qnh": 1018.4,
					--      "nav_altitude_mcp": 2016,
					--      "nav_altitude_fms": 2000,
					--      "nav_modes": [
					--        "autopilot",
					--        "tcas"
					--      ],
					--      "lat": 52.166748,
					--      "lon": 4.638977,
					--      "nic": 8,
					--      "rc": 186,
					--      "seen_pos": 0.179,
					--      "recentReceiverIds": [
					--        "8a6d5aed-e6d2-4a79",
					--        "462a41f7-9808-5db2",
					--        "b29d8c19-8849-41bb",
					--        "8a4d3496-8f73-11ea",
					--        "c21ace72-6613-4a53",
					--        "4284e70f-e716-e5ab",
					--        "01122182-d7a1-11ec",
					--        "40d9183e-8c3f-47a1",
					--        "6293f6c0-1ecf-4ad2",
					--        "e74ec3c3-c566-e5e2",
					--        "cdf69800-c853-47b0",
					--        "f786031b-56b9-4b97",
					--        "7715abf6-175d-4d44"
					--      ],
					--      "version": 2,
					--      "nic_baro": 1,
					--      "nac_p": 10,
					--      "nac_v": 2,
					--      "sil": 3,
					--      "sil_type": "perhour",
					--      "gva": 2,
					--      "sda": 2,
					--      "alert": 0,
					--      "spi": 0,
					--      "mlat": [],
					--      "tisb": [],
					--      "messages": 119867,
					--      "seen": 0.2,
					--      "rssi": -6.5,
					--      "dst": 1.883,
					--      "dir": 318.9
					--    }
					--  ],
					--  "msg": "No error",
					--  "now": 1716574591594,
					--  "total": 1,
					--  "ctime": 1716574591594,
					--  "ptime": 0
					--}
                    -- Now loop through the resul_table
                    if triggeredItem.json.total > 0 then
                        -- We see at least one plane. Retreive its data.
                        local tc = #result_table
                        for i = 1, tc do
                            local ap_hex = result_table[i].hex
                            local ap_flight = result_table[i].flight
                            local ap_r = result_table[i].r
                            local ap_t = result_table[i].t
                            local ap_desc = result_table[i].desc 
                            local ap_gs = _u.round( result_table[i].gs * 1.851999278976, 0 ) -- Calculate groundspeed knots to km.
                            local ap_track = result_table[i].track -- direction in degree
                            local ap_direction = _h.getDirectionfromDegree( ap_track )
                                -- Translate abbreviation into Dutch abbreviation. E(ast) becomes O(ost) S(outh) becomes Z(uid).
                                ap_direction = string.gsub( ap_direction, "E", "O", 2 )
                                ap_direction = string.gsub( ap_direction, "S", "Z", 2 )
                            local ap_lat = result_table[i].lat
                            local ap_long = result_table[i].lon
                                dz.log( 'Lat en Long: ' ..  ap_lat .. ' - ' .. ap_long, dz.LOG_DEBUG )
                            local ap_distance = _u.round( _h.calculateDistance( lat, long, ap_lat, ap_long ), 1 )
                            local ap_geom_rateString = ''
                            local ap_geom_rate = result_table[i].geom_rate
                                if ap_geom_rate ~= nil and ap_geom_rate > 0 then
                                    -- stijgend
                                    ap_geom_rateString = ' Stijgt ' .. _u.round ( ap_geom_rate * 0.3048 , 0 ) .. ' m/min.'
                                elseif ap_geom_rate ~= nil and ap_geom_rate < 0 then
                                    --dalend
                                    ap_geom_rateString = ' Daalt ' .. _u.round ( math.abs( ap_geom_rate ) * 0.3048 , 0 ) .. ' m/min.'
                                end
                            local ap_alt_geom = _u.round ( result_table[i].alt_geom * 0.3048 , 0 ) --Calculate height feet to meters.

                            if ap_alt_geom <= ap_maxAltitudeM then 
                                -- It is within the max. height.
                                dz.log( 'I see: ' .. ap_hex .. ' with distance = ' .. ap_distance .. ' km.', dz.LOG_DEBUG )

                                if airplanesExist( ap_hex ) == true then
                                    -- This plane exist already in our table. check and update when needed.
                                    local tc = #_d.ap_table
                                    dz.log( 'Global _d.ap_table exist with ' .. tc .. ' rows.', dz.LOG_DEBUG )
                                    for i = 1, tc do
                                        if _d.ap_table[i].ap_hex == ap_hex then
                                            --We got the same airplane
                                            if ap_distance < _d.ap_table[i].ap_distance then
                                                dz.log( 'Update row with closer distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
                                                -- Update/Overwrite with the closest.
                                                _d.ap_table[i].ap_time = dz.time.rawTime
                                                _d.ap_table[i].ap_hex = ap_hex
                                                _d.ap_table[i].ap_flight = ap_flight
                                                _d.ap_table[i].ap_r = ap_r
                                                _d.ap_table[i].ap_t = ap_t
                                                _d.ap_table[i].ap_desc = ap_desc
                                                _d.ap_table[i].ap_gs = ap_gs
                                                _d.ap_table[i].ap_track = ap_track
                                                _d.ap_table[i].ap_direction = ap_direction
                                                _d.ap_table[i].ap_lat = ap_lat
                                                _d.ap_table[i].ap_long = ap_long
                                                _d.ap_table[i].ap_distance = ap_distance
                                                _d.ap_table[i].ap_geom_rate = ap_geom_rate
                                                _d.ap_table[i].ap_geom_rateString = ap_geom_rateString
                                                _d.ap_table[i].ap_alt_geom = ap_alt_geom
                                            end
                                        end
                                    end
                                else
                                    --Not in the table. So insert this new airplane
                                    dz.log( 'New row created in global _d.ap_table with distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
                                    table.insert( _d.ap_table, {
                                                ap_time = dz.time.rawTime,
                                                ap_hex = ap_hex,
                                                ap_flight = ap_flight,
                                                ap_r = ap_r,
                                                ap_t = ap_t,
                                                ap_desc = ap_desc,
                                                ap_gs = ap_gs,
                                                ap_track = ap_track,
                                                ap_direction = ap_direction,
                                                ap_lat = ap_lat,
                                                ap_long = ap_long,
                                                ap_distance = ap_distance,
                                                ap_geom_rate = ap_geom_rate,
                                                ap_geom_rateString = ap_geom_rateString,
                                                ap_alt_geom = ap_alt_geom
                                        });
                                end
                            else
                                dz.log( 'Skip flight that is passing at ' .. ap_alt_geom .. 'm. is higher then the set ' .. ap_maxAltitudeM .. ' m.', dz.LOG_DEBUG )
                            end
                        end
                    else
                        dz.log( 'Total flights found in response =  0.', dz.LOG_DEBUG )
                        -- See if we have something in the table to write to device.
                        if next( _d.ap_table ) ~= nil then
                            -- the Table exist
                            -- Store previous data from table if exist.
                            dz.log( 'Write and clear from global _d.ap_table, now no more flights are seen.', dz.LOG_DEBUG )
                            local linetoRemove = 0
                            if type( _d.ap_table ) == "table" then
                                --for eacht table row
                                tr = #_d.ap_table
                                for i = 1, tr do
                                    --Get the previous stored data from device to compare
                                    local ap_lastdeviceText = dz.devices(ap_text_idx).text   -- Holds string of the previous round.
        		                
                                    --Data we have in this row
                                    local airplaneText = '<a href="https://globe.airplanes.live/?icao=' .. _d.ap_table[i].ap_hex .. '" target="_blank"><span style="color: Blue">' .. _d.ap_table[i].ap_flight .. '</span></a>' .. ' - ' .. _d.ap_table[i].ap_r .. ' - ' .. _d.ap_table[i].ap_desc  .. 
        		                    '<br>Hoogte: ' .. _d.ap_table[i].ap_alt_geom .. ' m. Snelheid: ' .. _d.ap_table[i].ap_gs .. ' km/u. Afstand: ' .. _d.ap_table[i].ap_distance ..  ' km.' ..
        		                    '<br>Richting: ' .. _d.ap_table[i].ap_direction .. _d.ap_table[i].ap_geom_rateString .. ' Tijd: ' .. _d.ap_table[i].ap_time
                                    dz.log( 'Airplane  = ' .. airplaneText, dz.LOG_DEBUG )

                                    if airplaneText ~= ap_lastdeviceText then
                                        dz.log( 'Saving row with distance ' .. _d.ap_table[i].ap_distance .. ' km.', dz.LOG_DEBUG )
                                        -- Data is changed, so update the device.
                                        dz.devices(ap_text_idx).updateText( airplaneText )                                    
                                    end
                                    linetoRemove = i
                                    break
                                end
                                if linetoRemove > 0 then
                                    -- Remove the row.
                                    table.remove( _d.ap_table, linetoRemove )
                                    dz.log( 'Removed index ' .. linetoRemove ..  ' from global _d.ap_table.', dz.LOG_DEBUG )
                                end
                            end
                        else
                            dz.log( 'Nothing left in global _d.ap_table to write to device.', dz.LOG_DEBUG )
                        end
                    end
                else
                    dz.log( 'No result_table found', dz.LOG_ERROR )
                end
		    else
			    dz.log( 'Item or JSON - NOT OK', dz.LOG_DEBUG )
			end
	    end
	end
}
-- That's All --------------------------------------------------
NB. The script expects the presence of a text device and, as follows, a table and two functions in 'global_data'

The script uses a table that is stored in 'global_data' under data:

Code: Select all

	-- Used in t-Airplanes        
	ap_table = { initial = {} },
The script calls function 'getDirectionfromDegree' that is expected in 'global_data' under helpers:

Code: Select all

        -- Used in dt-OpenMeteo, t-Weerlive, t-Airplanes
        getDirectionfromDegree = function( degrees )
            -- To calculate the degrees.
            local directionString = ''
            -- When string is not in Enlish, the icon does not appear!
            if degrees >= 0 and degrees < 11.25 then directionString = 'N' end
            if degrees >= 11.25 and degrees < 33.75 then directionString = 'NNE' end
            if degrees >= 33.75 and degrees < 56.25 then directionString = 'NE' end
            if degrees >= 56.25 and degrees < 78.75 then directionString = 'ENE' end
            if degrees >= 78.75 and degrees < 101.25 then directionString = 'E' end
            if degrees >= 101.25 and degrees < 123.75 then directionString = 'ESE' end
            if degrees >= 123.75 and degrees < 146.25 then directionString = 'SE' end
            if degrees >= 146.25 and degrees < 168.75 then directionString = 'SSE' end
            if degrees >= 168.75 and degrees < 191.25 then directionString = 'S' end
            if degrees >= 191.25 and degrees < 213.75 then directionString = 'SSW' end
            if degrees >= 213.75 and degrees < 236.25 then directionString = 'SW' end
            if degrees >= 236.25 and degrees < 258.75 then directionString = 'WSW' end
            if degrees >= 258.75 and degrees < 281.25 then directionString = 'W' end
            if degrees >= 281.25 and degrees < 303.75 then directionString = 'WNW' end
            if degrees >= 303.75 and degrees < 326.25 then directionString = 'NW' end
            if degrees >= 326.25 and degrees < 348.75  then directionString = 'NNW' end
            if degrees >= 348.75 and degrees <= 360 then directionString = 'N' end
            return directionString
        end,
The script calls function 'calculateDistance' that is expected in 'global_data' under helpers:

Code: Select all

        -- Used in t-Earthquake-KNMI, t-Airplanes
		calculateDistance = function( lat1, lon1, lat2, lon2)
    		-- Calculate distance using Haversine formula
		    local R = 6371 -- Earth radius in kilometers
		    local dLat = math.rad(lat2 - lat1)
			local dLon = math.rad(lon2 - lon1)
			local a = math.sin(dLat / 2) * math.sin(dLat / 2) + math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) * math.sin(dLon / 2) * math.sin(dLon / 2)
			local c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
			local distance = R * c
			return distance
		end,
Finally, you may want to use the icon.
Icon_airplane.zip
(7.83 KiB) Downloaded 42 times
Last edited by janpep on Tuesday 21 January 2025 20:00, edited 1 time in total.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
User avatar
waltervl
Posts: 5408
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2024.7
Location: NL
Contact:

Re: Script for Airplanes.live api

Post by waltervl »

With all these text devices you are creating (also with other scripts like earthquake) be sure you have the switch history log (in menu setup - settings) set to a low value otherwise your database will grow quickly in size.
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

You are right.That was one reason for storing only the closest catch and not all of them.
Next to that, also because I am not interested in keeping the history, I run a sceduled 'dt-ClearLog' script to cleanup several of these text devices on a regular basis. That goes very well. For those who want to keep the data, you could also consider to add a function to store it in a csv file or something like that.

Edit:
In addition: Also a reason to comment out the logging when testing is done. My disk has enough space, but with every 15 seconds it has also a lot to write.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

I am testing an update already. The altitude is not always present. I will take the alt_baro als alternative then or set it to 'unknown' if that is also empty.
Also I added the optional log to csv file, that I am also testing now.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

Update of the script with a few enhancements.
1. It happened regularly that the altitude in alt_geom was empty and caused an error. Now, if it is empty, it takes the altitude from alt_baro. Might that also be empty, it wiil mark it as unknown.
2. When you want to clear the log for the text device on a regular basis, but want to keep the content, I added the option to save it as a tab delimited csv file.
You can set the variables under Your settings.

Code: Select all

local ap_useCSVfile = 0          -- 0 = NO csv file, or 1 = Save airplanes also in csv file.
local csvFile = '/path/to/domoticz/scripts/SavedAirplanes.csv'
For this it expects the presence of the function 'AppendStringToFile' in global_data.

The function 'AppendStringToFile' to put in global_data under helpers:

Code: Select all

	AppendStringToFile = function( dz, logfileName, logString )
		local file = io.open( logfileName, "a" )
		file:write( logString )
		file:close()            
	end,
When the file does not exist, it will be created and tab delimited header line will be inserted.

The result will look like:

Code: Select all

ap_time ap_hex  ap_flight       ap_r    ap_t    ap_desc ap_gs   ap_track        ap_direction    ap_lat  ap_long ap_distance     ap_geom_rateStringap_altitude
13:58:30        48504b  PHTDS           PH-TDS  DA40    DIAMOND DA-40 Club Star 176     284.0   WNW     52.1498 4.675148        0.8             328
14:01:15        4857b3  LIFELN1         PH-TTR  EC35    AIRBUS HELICOPTERS EC-135/635   238     24.37   NNO     52.150424       4.655849        1.4               290
15:08:30        485b2f  ZXP24           PH-PXX  A139    AGUSTA AW-139   274     60.83   ONO     52.140656       4.66774 0.4      Stijgt 20 m/min.259
15:14:01        491310  NJE799C         CS-DXP  C56X    CESSNA 560XL Citation XLS       525     237.82  WZW     52.161038       4.642038        2.9               2995
15:30:30        4d210c  VJT602          9H-ILA  CRJ2    BOMBARDIER Regional Jet CRJ-200 499     218.06  ZW      52.163365       4.663225        2.3        Stijgt 312 m/min.      1760
15:36:45        4843d2  PHKBA           PH-KBA  C172    CESSNA 172 Skyhawk      165     80.33   O       52.117232       4.654672        3.1      Stijgt 39 m/min. 290
15:40:15        4843d2  PHKBA           PH-KBA  C172    CESSNA 172 Skyhawk      192     83.9    O       52.127747       4.687653        2.0      Daalt 20 m/min.  282
16:43:46        c04a7d  NCG01           C-GCFK  DH8A    DE HAVILLAND DHC-8-100 Dash 8   312     101.98  OZO     52.172287       4.68956 3.4      358
The updated script t-Airplanes:

Code: Select all

-- 24-05-2024 script by Jan Peppink, https://ict.peppink.nl
-- Goal: Aircraft 'noise pollution detector'.
-- https://api.airplanes.live/v2/point/<lat>/<long>/<distanceKM>
-- Get data of airplaines that pass close by with lower altitudes.
-- When you hear them, they are probably logged. :-)
-- 27-05-2024 Call every 15 seconds. Store playe in global data table.
--  Update this when same plane is closer with call
--  When no planes are catched write the table (if not empty) to device.
-- 29-05-2024 alt_geom is sometimes empty. If so, take alt_baro. When this is also empty then set as 'unkown'.
-- 29-05-2024 Added optional saving to tab delimited csv file, with fieldnames as header.
---           Makes use of function 'AppendStringToFile' in global_data.

--- Your settings ----------------------------------------------------------------
-- First set the used device index numbers and variables you might want to change.
---#################################################################
local ap_text_idx = 99999    --idx of the custom Text device
local ap_maxRadiusKm = 3    -- Query max radius in KM around your coordinates.
local ap_maxAltitudeM = 3500    -- Save only < max ap_maxAltitude in Meters.
local ap_useCSVfile = 0          -- 0 = NO csv file, or 1 = Save airplanes also in csv file.
local csvFile = '/path/to/domoticz/scripts/SavedAirplanes.csv'

----------------------------------------------------------------------------------
return {
	on = {
		timer = { 
            'every 5 minutes', -- Below in a loop it is made to 15 seconds!
        },
        httpResponses = {
            'airplanes'       -- matches callback string below
        },
	},
	logging = {
        -- Level can be domoticz.LOG_INFO, domoicz.LOG_MODULE_EXEC_INFO, domoticz.LOG_DEBUG, domoticz.LOG_ERROR or domoticz.LOG_FORCE
        --level = domoticz.LOG_INFO,
        --level = domoticz.LOG_DEBUG,
		marker = 'Airplanes-',
	},
	execute = function(dz, triggeredItem)
        -- Set Local environment=================
        local _u = dz.utils       -- Holds subset of handy utilities.
        local _h = dz.helpers     -- Holds the global functions
        local _d = dz.globalData  -- Holds the global data

        --_d.initialize('ap_table') -- Re-initialize the table

        -- Get location coordinates
        local lat = dz.settings.location.latitude
        local long = dz.settings.location.longitude

        local ap_maxRadiusMiles = ap_maxRadiusKm * 0.62137119 -- Calculate given km in sdettings to miles.
        
        -- Use some color variables
--        local cGreen = '#008000;'
--        local cYellow = '#ffff00;'
--        local cOrange = '#ffa500;'
--        local cRed = '#ff0000;'	
--        local cWhite = '#FFFFFF;'
--        local cBlue = '#0000FF;'
--        local cGrey = '#8d8d8d;'

--        local htmlColor = '#3b3bc4;'
--        --Adjust lineheight
--        local lineHeight = 1.2

        -- Local Functions go here =============
        local function airplanesExist( testValue )
            -- check if this plane is already in _d.ap_table
            if type( _d.ap_table ) == "table" then
                for i = 1, #_d.ap_table do
                    if _d.ap_table[i].ap_hex == testValue then
                        -- Found in table
                        dz.log( testValue .. ' is found in _d.ap_table', dz.LOG_DEBUG )
                        return true
                    end
                end
                -- Not found in table
                dz.log( testValue .. ' is not found in _d.ap_table', dz.LOG_DEBUG )
                return false
            else
                -- Table not found.
                dz.log( 'Did not find _d.ap_table - Re-initialize.', dz.LOG_DEBUG )
                _d.initialize('ap_table') -- Re-initialize the table
                return false
            end
        end

        -- Now start to do something ============
        -- Get the data.
		if (triggeredItem.isTimer) then
            -- Retrieve the data every 15 second per 5 minutes.
            for seconds = 15, 285, 15 do -- start with 15 step 15 until 285
                dz.openURL({
				    url = 'https://api.airplanes.live/v2/point/' .. lat .. '/' .. long ..'/' .. ap_maxRadiusMiles,
				    method = 'GET',
				    callback = 'airplanes'
                }).afterSec(seconds)
            end
		end	

        if (triggeredItem.isHTTPResponse) then
            -- Process the obtained data.
            -- Check the response and process the data.
            if (triggeredItem.ok and triggeredItem.isJSON) then
                dz.log( 'Item and JSON - OK', dz.LOG_DEBUG )
				-- We have some result. Store in table.
				local result_table = triggeredItem.json.ac
                if type(result_table) == "table" then
                    dz.log( 'result_table: type = ' .. type(result_table), dz.LOG_DEBUG )
                    -- The data we get is:
					--{
					--  "ac": [
					--    {
					--      "hex": "485064",
					--      "type": "adsb_icao",
					--      "flight": "KLM66C  ",
					--      "r": "PH-EZY",
					--      "t": "E190",
					--      "desc": "EMBRAER ERJ-190-100",
					--      "ownOp": "Klm Cityhopper",
					--      "alt_baro": 1875,
					--      "alt_geom": 2200,
					--      "gs": 254.5,
					--      "ias": 248,
					--      "tas": 256,
					--      "mach": 0.388,
					--      "wd": 19,
					--      "ws": 6,
					--      "oat": 6,
					--      "tat": 15,
					--      "track": 93.6,
					--      "track_rate": 0.09,
					--      "roll": -0.18,
					--      "mag_heading": 90,
					--      "true_heading": 92.4,
					--      "baro_rate": -192,
					--      "geom_rate": -128,
					--      "squawk": "7631",
					--      "emergency": "none",
					--      "category": "A3",
					--      "nav_qnh": 1018.4,
					--      "nav_altitude_mcp": 2016,
					--      "nav_altitude_fms": 2000,
					--      "nav_modes": [
					--        "autopilot",
					--        "tcas"
					--      ],
					--      "lat": 52.166748,
					--      "lon": 4.638977,
					--      "nic": 8,
					--      "rc": 186,
					--      "seen_pos": 0.179,
					--      "recentReceiverIds": [
					--        "8a6d5aed-e6d2-4a79",
					--        "462a41f7-9808-5db2",
					--        "b29d8c19-8849-41bb",
					--        "8a4d3496-8f73-11ea",
					--        "c21ace72-6613-4a53",
					--        "4284e70f-e716-e5ab",
					--        "01122182-d7a1-11ec",
					--        "40d9183e-8c3f-47a1",
					--        "6293f6c0-1ecf-4ad2",
					--        "e74ec3c3-c566-e5e2",
					--        "cdf69800-c853-47b0",
					--        "f786031b-56b9-4b97",
					--        "7715abf6-175d-4d44"
					--      ],
					--      "version": 2,
					--      "nic_baro": 1,
					--      "nac_p": 10,
					--      "nac_v": 2,
					--      "sil": 3,
					--      "sil_type": "perhour",
					--      "gva": 2,
					--      "sda": 2,
					--      "alert": 0,
					--      "spi": 0,
					--      "mlat": [],
					--      "tisb": [],
					--      "messages": 119867,
					--      "seen": 0.2,
					--      "rssi": -6.5,
					--      "dst": 1.883,
					--      "dir": 318.9
					--    }
					--  ],
					--  "msg": "No error",
					--  "now": 1716574591594,
					--  "total": 1,
					--  "ctime": 1716574591594,
					--  "ptime": 0
					--}
                    -- Now loop through the resul_table
                    if triggeredItem.json.total > 0 then
                        -- We see at least one plane. Retreive its data.
                        local tc = #result_table
                        for i = 1, tc do
                            local ap_hex = result_table[i].hex
                            local ap_flight = result_table[i].flight
                            local ap_r = result_table[i].r
                            local ap_t = result_table[i].t
                            local ap_desc = result_table[i].desc 
                            local ap_gs = _u.round( result_table[i].gs * 1.851999278976, 0 ) -- Calculate groundspeed knots to km.
                            local ap_track = result_table[i].track -- direction in degree
                            local ap_direction = _h.getDirectionfromDegree( ap_track )
                                -- Translate abbreviation into Dutch abbreviation. E(ast) becomes O(ost) S(outh) becomes Z(uid).
                                ap_direction = string.gsub( ap_direction, "E", "O", 2 )
                                ap_direction = string.gsub( ap_direction, "S", "Z", 2 )
                            local ap_lat = result_table[i].lat
                            local ap_long = result_table[i].lon
                                dz.log( 'Lat en Long: ' ..  ap_lat .. ' - ' .. ap_long, dz.LOG_DEBUG )
                            local ap_distance = _u.round( _h.calculateDistance( lat, long, ap_lat, ap_long ), 1 )

                            local ap_geom_rateString = ''
                            local ap_geom_rate = result_table[i].geom_rate
                                if ap_geom_rate ~= nil and ap_geom_rate > 0 then
                                    -- Ascending.
                                    ap_geom_rateString = ' Stijgt ' .. _u.round ( ap_geom_rate * 0.3048 , 0 ) .. ' m/min.'
                                elseif ap_geom_rate ~= nil and ap_geom_rate < 0 then
                                    -- Descending.
                                    ap_geom_rateString = ' Daalt ' .. _u.round ( math.abs( ap_geom_rate ) * 0.3048 , 0 ) .. ' m/min.'
                                end

                            local ap_altitude = 0
                                --The fieldalt_geom is regularly empty. Therefore we check for alternative.
                                if result_table[i].alt_geom ~= nil then
                                    dz.log( 'alt_geom ' .. result_table[i].alt_geom .. ' is used.', dz.LOG_DEBUG)
                                    -- Preferred fot the height.
                                    ap_altitude = _u.round ( result_table[i].alt_geom * 0.3048 , 0 ) --Calculate height feet to meters.
                                elseif result_table[i].alt_baro ~= nil then 
                                    dz.log( 'alt_baro ' .. result_table[i].alt_baro .. ' is used.', dz.LOG_DEBUG)
                                    -- If alt_geom is not available take the alt_baro.
                                    ap_altitude = _u.round ( result_table[i].alt_baro * 0.3048 , 0 ) --Calculate height feet to meters.
                                end                                

                            if ap_altitude <= ap_maxAltitudeM then 
                                if ap_altitude == 0 then
                                    dz.log( 'altitude is unknown.', dz.LOG_DEBUG)
                                    ap_altitude = 'onbekend'
                                end
                                -- It is within the max. height.
                                dz.log( 'I see: ' .. ap_hex .. ' with distance = ' .. ap_distance .. ' km.', dz.LOG_DEBUG )

                                if airplanesExist( ap_hex ) == true then
                                    -- This plane exist already in our table. check and update when needed.
                                    local tc = #_d.ap_table
                                    dz.log( 'Global _d.ap_table exist with ' .. tc .. ' rows.', dz.LOG_DEBUG )
                                    for i = 1, tc do
                                        if _d.ap_table[i].ap_hex == ap_hex then
                                            -- We got the same airplane.
                                            if ap_distance < _d.ap_table[i].ap_distance then
                                                dz.log( 'Update row with closer distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
                                                -- Update/Overwrite with the closest.
                                                _d.ap_table[i].ap_time = dz.time.rawTime
                                                _d.ap_table[i].ap_hex = ap_hex
                                                _d.ap_table[i].ap_flight = ap_flight
                                                _d.ap_table[i].ap_r = ap_r
                                                _d.ap_table[i].ap_t = ap_t
                                                _d.ap_table[i].ap_desc = ap_desc
                                                _d.ap_table[i].ap_gs = ap_gs
                                                _d.ap_table[i].ap_track = ap_track
                                                _d.ap_table[i].ap_direction = ap_direction
                                                _d.ap_table[i].ap_lat = ap_lat
                                                _d.ap_table[i].ap_long = ap_long
                                                _d.ap_table[i].ap_distance = ap_distance
                                                _d.ap_table[i].ap_geom_rate = ap_geom_rate
                                                _d.ap_table[i].ap_geom_rateString = ap_geom_rateString
                                                _d.ap_table[i].ap_altitude = ap_altitude
                                            end
                                        end
                                    end
                                else
                                    -- This plane is Not in the table. So insert this new airplane.
                                    dz.log( 'New row created in global _d.ap_table with distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
                                    table.insert( _d.ap_table, {
                                                ap_time = dz.time.rawTime,
                                                ap_hex = ap_hex,
                                                ap_flight = ap_flight,
                                                ap_r = ap_r,
                                                ap_t = ap_t,
                                                ap_desc = ap_desc,
                                                ap_gs = ap_gs,
                                                ap_track = ap_track,
                                                ap_direction = ap_direction,
                                                ap_lat = ap_lat,
                                                ap_long = ap_long,
                                                ap_distance = ap_distance,
                                                ap_geom_rate = ap_geom_rate,
                                                ap_geom_rateString = ap_geom_rateString,
                                                ap_altitude = ap_altitude
                                        });
                                end
                            else
                                dz.log( 'Skip flight that is passing at ' .. ap_altitude .. 'm. is higher then the set ' .. ap_maxAltitudeM .. ' m.', dz.LOG_DEBUG )
                            end
                        end
                    else
                        dz.log( 'Total flights found in response =  0.', dz.LOG_DEBUG )
                        -- See if we have something in the table to write to device.
                        if next( _d.ap_table ) ~= nil then
                            -- The table exist.
                            -- Store previous data from table if exist.
                            dz.log( 'Write and clear from global _d.ap_table, now no more flights are seen.', dz.LOG_DEBUG )
                            local linetoRemove = 0
                            if type( _d.ap_table ) == "table" then
                                --for eacht table row
                                tr = #_d.ap_table
                                for i = 1, tr do
                                    -- Get the previous stored data from device to compare.
                                    local ap_lastdeviceText = dz.devices(ap_text_idx).text   -- Holds string of the previous round.
        		                
                                    --Data we have in this row
                                    local airplaneText = '<a href="https://globe.airplanes.live/?icao=' .. _d.ap_table[i].ap_hex .. '" target="_blank"><span style="color: Blue">' .. _d.ap_table[i].ap_flight .. '</span></a>' .. ' - ' .. _d.ap_table[i].ap_r .. ' - ' .. _d.ap_table[i].ap_desc  .. 
        		                    '<br>Hoogte: ' .. _d.ap_table[i].ap_altitude .. ' m. Snelheid: ' .. _d.ap_table[i].ap_gs .. ' km/u. Afstand: ' .. _d.ap_table[i].ap_distance ..  ' km.' ..
        		                    '<br>Richting: ' .. _d.ap_table[i].ap_direction .. _d.ap_table[i].ap_geom_rateString .. ' Tijd: ' .. _d.ap_table[i].ap_time
                                    dz.log( 'Airplane  = ' .. airplaneText, dz.LOG_DEBUG )

                                    if airplaneText ~= ap_lastdeviceText then
                                        dz.log( 'Saving row with distance ' .. _d.ap_table[i].ap_distance .. ' km.', dz.LOG_DEBUG )
                                        -- Data is changed, so update the device.
                                        dz.devices(ap_text_idx).updateText( airplaneText )     

                                        if ap_useCSVfile == 1 then
                                            dz.log( 'Saving row also to csvFile ' .. csvFile, dz.LOG_DEBUG )
                                            local logString = _d.ap_table[i].ap_time .. '\t' ..  _d.ap_table[i].ap_hex  .. '\t' ..  _d.ap_table[i].ap_flight  .. '\t' ..  _d.ap_table[i].ap_r  .. '\t' ..  _d.ap_table[i].ap_t  .. '\t' ..  _d.ap_table[i].ap_desc  .. '\t' ..  _d.ap_table[i].ap_gs  .. '\t' ..  _d.ap_table[i].ap_track  .. '\t' ..  _d.ap_table[i].ap_direction  .. '\t' ..  _d.ap_table[i].ap_lat  .. '\t' ..  _d.ap_table[i].ap_long  .. '\t' ..  _d.ap_table[i].ap_distance  .. '\t' ..   _d.ap_table[i].ap_geom_rateString  .. '\t' ..  _d.ap_table[i].ap_altitude
                                            if _u.fileExists( csvFile ) then
                                                -- File exist so append content.
                                                _h.AppendStringToFile( dz, csvFile, logString .. '\n' )
                                            else
                                                -- File does not exist. Start with header information.
                                                local headerString = 'ap_time\tap_hex\tap_flight\tap_r\tap_t\tap_desc\tap_gs\tap_track\tap_direction\tap_lat\tap_long\tap_distance\tap_geom_rateString\tap_altitude'
                                                _h.AppendStringToFile( dz, csvFile, headerString  .. '\n' )                                                
                                                _h.AppendStringToFile( dz, csvFile, logString  .. '\n' )
                                            end
                                        end
                                    end
                                    -- When it is stored, we can mark the row to be removed.
                                    linetoRemove = i
                                    break
                                end
                                if linetoRemove > 0 then
                                    -- Remove the row after exiting the loop.
                                    table.remove( _d.ap_table, linetoRemove )
                                    dz.log( 'Removed index ' .. linetoRemove ..  ' from global _d.ap_table.', dz.LOG_DEBUG )
                                end
                            end
                        else
                            dz.log( 'Nothing left in global _d.ap_table to write to device.', dz.LOG_DEBUG )
                        end
                    end
                else
                    dz.log( 'No result_table found', dz.LOG_ERROR )
                end
		    else
			    dz.log( 'Item or JSON - NOT OK', dz.LOG_DEBUG )
			end
	    end
	end
}
-- That's All --------------------------------------------------
Last edited by janpep on Tuesday 21 January 2025 20:02, edited 1 time in total.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
lost
Posts: 643
Joined: Thursday 10 November 2016 9:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Script for Airplanes.live api

Post by lost »

I discover this free API from this message...
Quite nice to be able to get data from a simple URL (no auth/token needed):
https://api.airplanes.live/v2/point/Lat ... RadiusInNM

And even a link from hex field unique ID retrieved from previous call to target a specific flight:
https://globe.airplanes.live/?icao=ap_hex

Looks there is a few misses compared to flight radar for instance (at least in my location in France with Orly airport at 12NM) but that's really a no hassle solution!

For this kind of stuff, I usually prefer to setup a simple custom page auto-generated using python-yattag, as this does not fill domoticz DB (and this custom page can be a link to a tmpfs in ram, so no SD card wear induced as well). Domoticz web server, as most such servers, cope well with un-closed tags (body, div...) so the page text/links can be added keeping yattag data, with only periodic page resets (time or nb of records driven).

Would not allow already existing ap_hex entries merge, but filtering low altitude over a 20NM radius allows lesser refresh rate. For this last point, quite surprising you're not kicked out continuously getting data each 15s? No access quota!?

I have several such use-case, but as separate python services that interface with Domoticz using HTTP/JSON API as plugin infrastructure was not present or quite new when I wrote those... This may be a motivation to write this as a plugin when I have time.
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

I looked at flightradar24 and several others. Some have more information like place of departure and arrival. But all what I have seen was OR a paid solution OR a they have a free solution with very few polls (like 100 per month or so). In that search I found and ended with the Airplanes.live API.

If you have a preference for python, custom pages or plugins, feel free to go ahead as you wish. There are always ways to do it differently. I just showed my project as the area says.

Of course you have to be careful of uncontrolled growth of the database, but notice we are only talking about a few text fields here! A book of 428 pages is 2000 kB. I know, because I wrote one :-).
I worked a lot with large databases, but here we are not talking about databases of TB, or GB and not even 100 MB, mine is only a fifth of that. So you don't have to be too afraid either. My database is 22 MB and I don't see any subjective signals of poor performance. In Dutch there is a saying which translates in "It is child's play" :-).

As for the poll frequency. I clearly mentioned this in the first post. You can do it up to once per second, but that seemed to me like a real overkill for the purpose I have.

I am now working on an adjustment where you can specify the poll frequency in seconds in the settings. Mainly because I thought it was a fun puzzle. Secondly, you have to change only one number and not the '''From - To - Step' in the loop.

To be continued.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
FlyingDomotic
Posts: 329
Joined: Saturday 27 February 2016 0:30
Target OS: Raspberry Pi / ODroid
Domoticz version: 2020.2
Contact:

Re: Script for Airplanes.live api

Post by FlyingDomotic »

FYI, it's possible to capture ADS-B data with a simple SDR dongle on a RPI, should you whish to get ride of external providers.

Full implementation is available at https://stratux.me. It does more than getting information on flights around, but you can easily isolate the ADS-B part if needed.

To be exhaustive, note that ADS-B is not mandatory for every planes, so small parts of (non commercial) traffic can be missing.
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

Yes, I already had found some information about ADS-B. On airplanes.live there is also information about that, and you can become a 'feeder' there as mentioned in the first post of this topic. For me it is a step further. At the moment I have no plans to start a new hobby.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

During all night I got this error:

Code: Select all

Error: dzVents: Error: (3.1.8) Airplanes-: HTTP/2 response: 522 ==>> No reason returned!
When I try to connect with the browser I get a Cloudflare error page saying:
"What happened: The initial connection between Cloudflare's network and the origin web server timed out. As a result, the web page can not be displayed."

Normally this can occur when Cloudflare tries to connect to the website and doesn't receive a response within a specific time.
Maybe there is something wrong with the Airplanes.live server. I hope that my IP connections (despite the rules) are not rejected with "522 No reason returned".
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

As mentioned I made a change in the script to be able to set the poll interval per seconds in 5 minutes under 'Your settings'.
This works quite well, except if the last period no longer fits within 5 minutes. Then it will jump once. With only 2 years of math at school 50 years ago it is too much of a challenge for me to take this further. :-)
If the multiple of the interval in seconds falls neatly to 300 seconds, there is no problem at all.

The updated script:

Code: Select all

-- 24-05-2024 script by Jan Peppink, https://ict.peppink.nl
-- Goal: Aircraft 'noise pollution detector'.
-- https://api.airplanes.live/v2/point/<lat>/<long>/<distanceKM>
-- Get data of airplaines that pass close by with lower altitudes.
-- When you hear them, they are probably logged. :-)
-- 27-05-2024 Call every 15 seconds. Store playe in global data table.
--  Update this when same plane is closer with call
--  When no planes are catched write the table (if not empty) to device.
-- 29-05-2024 alt_geom is sometimes empty. If so, take alt_baro. When this is also empty then set as 'unkown'.
-- 29-05-2024 Added optional saving to tab delimited csv file, with fieldnames as header.
---           Makes use of function 'AppendStringToFile' in global_data.
-- 31-05-2024 Added ap_pollFreq for setting the poll interval in seconds per 5 minutes.

--- Your settings ----------------------------------------------------------------
-- First set the used device index numbers and variables you might want to change.
---#################################################################
local ap_text_idx = 99999    --idx of the custom Text device
local ap_maxRadiusKm = 3    -- Query max radius in KM around your coordinates.
local ap_maxAltitudeM = 2500    -- Save only < max ap_maxAltitude in Meters.
local ap_useCSVfile = 0          -- 0 = NO csv file, or 1 = Save airplanes also in csv file.
local ap_csvFile = '/path/to/domoticz/scripts/SavedAirplanes.csv'
local ap_pollFreq = 60  --Poll frequncy in seconds. Default 15. Normally > 1 and < 60.

----------------------------------------------------------------------------------
return {
	on = {
		timer = { 
            'every 5 minutes', -->>>KEEP THIS at 5 minutes!<<<--Adjust ap_pollFreq in seconds in Your settings.
        },
        httpResponses = {
            'airplanes'       -- matches callback string below
        },
	},
	logging = {
        -- Level can be domoticz.LOG_INFO, domoicz.LOG_MODULE_EXEC_INFO, domoticz.LOG_DEBUG, domoticz.LOG_ERROR or domoticz.LOG_FORCE
        --level = domoticz.LOG_INFO,
        level = domoticz.LOG_DEBUG,
		marker = 'Airplanes-',
	},
	execute = function(dz, triggeredItem)
        -- Set Local environment=================
        local _u = dz.utils       -- Holds subset of handy utilities.
        local _h = dz.helpers     -- Holds the global functions
        local _d = dz.globalData  -- Holds the global data

        --_d.initialize('ap_table') -- Re-initialize the table

        -- Get location coordinates
        local lat = dz.settings.location.latitude
        local long = dz.settings.location.longitude

        local ap_maxRadiusMiles = ap_maxRadiusKm * 0.62137119 -- Calculate given km in sdettings to miles.
        
        -- Use some color variables
--        local cGreen = '#008000;'
--        local cYellow = '#ffff00;'
--        local cOrange = '#ffa500;'
--        local cRed = '#ff0000;'	
--        local cWhite = '#FFFFFF;'
--        local cBlue = '#0000FF;'
--        local cGrey = '#8d8d8d;'

--        local htmlColor = '#3b3bc4;'
--        --Adjust lineheight
--        local lineHeight = 1.2

        -- Local Functions go here =============
        local function airplanesExist( testValue )
            -- check if this plane is already in _d.ap_table
            if type( _d.ap_table ) == "table" then
                for i = 1, #_d.ap_table do
                    if _d.ap_table[i].ap_hex == testValue then
                        -- Found in table
                        dz.log( testValue .. ' is found in _d.ap_table', dz.LOG_DEBUG )
                        return true
                    end
                end
                -- Not found in table
                dz.log( testValue .. ' is not found in _d.ap_table', dz.LOG_DEBUG )
                return false
            else
                -- Table not found.
                dz.log( 'Did not find _d.ap_table - Re-initialize.', dz.LOG_DEBUG )
                _d.initialize('ap_table') -- Re-initialize the table
                return false
            end
        end

        -- Now start to do something ============
        -- Get the data.
		if (triggeredItem.isTimer) then
            -- Retrieve the data every ap_pollFreq second per 5 minutes.
            for seconds = ap_pollFreq, 300, ap_pollFreq do
                dz.openURL({
				    url = 'https://api.airplanes.live/v2/point/' .. lat .. '/' .. long ..'/' .. ap_maxRadiusMiles,
				    method = 'GET',
				    callback = 'airplanes'
                }).afterSec(seconds)
            end
		end	

        if (triggeredItem.isHTTPResponse) then
            -- Process the obtained data.
            -- Check the response and process the data.
            if (triggeredItem.ok and triggeredItem.isJSON) then
                dz.log( 'Item and JSON - OK', dz.LOG_DEBUG )
				-- We have some result. Store in table.
				local result_table = triggeredItem.json.ac
                if type(result_table) == "table" then
                    dz.log( 'result_table: type = ' .. type(result_table), dz.LOG_DEBUG )
                    -- The data we get is:
					--{
					--  "ac": [
					--    {
					--      "hex": "485064",
					--      "type": "adsb_icao",
					--      "flight": "KLM66C  ",
					--      "r": "PH-EZY",
					--      "t": "E190",
					--      "desc": "EMBRAER ERJ-190-100",
					--      "ownOp": "Klm Cityhopper",
					--      "alt_baro": 1875,
					--      "alt_geom": 2200,
					--      "gs": 254.5,
					--      "ias": 248,
					--      "tas": 256,
					--      "mach": 0.388,
					--      "wd": 19,
					--      "ws": 6,
					--      "oat": 6,
					--      "tat": 15,
					--      "track": 93.6,
					--      "track_rate": 0.09,
					--      "roll": -0.18,
					--      "mag_heading": 90,
					--      "true_heading": 92.4,
					--      "baro_rate": -192,
					--      "geom_rate": -128,
					--      "squawk": "7631",
					--      "emergency": "none",
					--      "category": "A3",
					--      "nav_qnh": 1018.4,
					--      "nav_altitude_mcp": 2016,
					--      "nav_altitude_fms": 2000,
					--      "nav_modes": [
					--        "autopilot",
					--        "tcas"
					--      ],
					--      "lat": 52.166748,
					--      "lon": 4.638977,
					--      "nic": 8,
					--      "rc": 186,
					--      "seen_pos": 0.179,
					--      "recentReceiverIds": [
					--        "8a6d5aed-e6d2-4a79",
					--        "462a41f7-9808-5db2",
					--        "b29d8c19-8849-41bb",
					--        "8a4d3496-8f73-11ea",
					--        "c21ace72-6613-4a53",
					--        "4284e70f-e716-e5ab",
					--        "01122182-d7a1-11ec",
					--        "40d9183e-8c3f-47a1",
					--        "6293f6c0-1ecf-4ad2",
					--        "e74ec3c3-c566-e5e2",
					--        "cdf69800-c853-47b0",
					--        "f786031b-56b9-4b97",
					--        "7715abf6-175d-4d44"
					--      ],
					--      "version": 2,
					--      "nic_baro": 1,
					--      "nac_p": 10,
					--      "nac_v": 2,
					--      "sil": 3,
					--      "sil_type": "perhour",
					--      "gva": 2,
					--      "sda": 2,
					--      "alert": 0,
					--      "spi": 0,
					--      "mlat": [],
					--      "tisb": [],
					--      "messages": 119867,
					--      "seen": 0.2,
					--      "rssi": -6.5,
					--      "dst": 1.883,
					--      "dir": 318.9
					--    }
					--  ],
					--  "msg": "No error",
					--  "now": 1716574591594,
					--  "total": 1,
					--  "ctime": 1716574591594,
					--  "ptime": 0
					--}
                    -- Now loop through the resul_table
                    if triggeredItem.json.total > 0 then
                        -- We see at least one plane. Retreive its data.
                        local tc = #result_table
                        for i = 1, tc do
                            local ap_hex = result_table[i].hex
                            local ap_flight = result_table[i].flight
                            local ap_r = result_table[i].r
                            local ap_t = result_table[i].t
                            local ap_desc = result_table[i].desc 
                            local ap_gs = _u.round( result_table[i].gs * 1.851999278976, 0 ) -- Calculate groundspeed knots to km.
                            local ap_track = result_table[i].track -- direction in degree
                            local ap_direction = _h.getDirectionfromDegree( ap_track )
                                -- Translate abbreviation into Dutch abbreviation. E(ast) becomes O(ost) S(outh) becomes Z(uid).
                                ap_direction = string.gsub( ap_direction, "E", "O", 2 )
                                ap_direction = string.gsub( ap_direction, "S", "Z", 2 )
                            local ap_lat = result_table[i].lat
                            local ap_long = result_table[i].lon
                                dz.log( 'Lat en Long: ' ..  ap_lat .. ' - ' .. ap_long, dz.LOG_DEBUG )
                            local ap_distance = _u.round( _h.calculateDistance( lat, long, ap_lat, ap_long ), 1 )

                            local ap_geom_rateString = ''
                            local ap_geom_rate = result_table[i].geom_rate
                                if ap_geom_rate ~= nil and ap_geom_rate > 0 then
                                    -- Ascending.
                                    ap_geom_rateString = ' Stijgt ' .. _u.round ( ap_geom_rate * 0.3048 , 0 ) .. ' m/min.'
                                elseif ap_geom_rate ~= nil and ap_geom_rate < 0 then
                                    -- Descending.
                                    ap_geom_rateString = ' Daalt ' .. _u.round ( math.abs( ap_geom_rate ) * 0.3048 , 0 ) .. ' m/min.'
                                end

                            local ap_altitude = 0
                                --The fieldalt_geom is regularly empty. Therefore we check for alternative.
                                if result_table[i].alt_geom ~= nil then
                                    dz.log( 'alt_geom ' .. result_table[i].alt_geom .. ' is used.', dz.LOG_INFO )
                                    -- Preferred fot the height.
                                    ap_altitude = _u.round ( result_table[i].alt_geom * 0.3048 , 0 ) --Calculate height feet to meters.
                                elseif result_table[i].alt_baro ~= nil then 
                                    dz.log( 'alt_baro ' .. result_table[i].alt_baro .. ' is used.', dz.LOG_INFO )
                                    -- If alt_geom is not available take the alt_baro.
                                    ap_altitude = _u.round ( result_table[i].alt_baro * 0.3048 , 0 ) --Calculate height feet to meters.
                                end                                

                            if ap_altitude <= ap_maxAltitudeM then 
                                if ap_altitude == 0 then
                                    dz.log( 'altitude is unknown.', dz.LOG_INFO )
                                    ap_altitude = 'onbekend'
                                end
                                -- It is within the max. height.
                                dz.log( 'I see: ' .. ap_hex .. ' with distance = ' .. ap_distance .. ' km.', dz.LOG_DEBUG )

                                if airplanesExist( ap_hex ) == true then
                                    -- This plane exist already in our table. check and update when needed.
                                    local tc = #_d.ap_table
                                    dz.log( 'Global _d.ap_table exist with ' .. tc .. ' rows.', dz.LOG_DEBUG )
                                    for i = 1, tc do
                                        if _d.ap_table[i].ap_hex == ap_hex then
                                            -- We got the same airplane.
                                            if ap_distance < _d.ap_table[i].ap_distance then
                                                dz.log( 'Update row with closer distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
                                                -- Update/Overwrite with the closest.
                                                _d.ap_table[i].ap_time = dz.time.rawTime
                                                _d.ap_table[i].ap_hex = ap_hex
                                                _d.ap_table[i].ap_flight = ap_flight
                                                _d.ap_table[i].ap_r = ap_r
                                                _d.ap_table[i].ap_t = ap_t
                                                _d.ap_table[i].ap_desc = ap_desc
                                                _d.ap_table[i].ap_gs = ap_gs
                                                _d.ap_table[i].ap_track = ap_track
                                                _d.ap_table[i].ap_direction = ap_direction
                                                _d.ap_table[i].ap_lat = ap_lat
                                                _d.ap_table[i].ap_long = ap_long
                                                _d.ap_table[i].ap_distance = ap_distance
                                                _d.ap_table[i].ap_geom_rate = ap_geom_rate
                                                _d.ap_table[i].ap_geom_rateString = ap_geom_rateString
                                                _d.ap_table[i].ap_altitude = ap_altitude
                                            end
                                        end
                                    end
                                else
                                    -- This plane is Not in the table. So insert this new airplane.
                                    dz.log( 'New row created in global _d.ap_table with distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
                                    table.insert( _d.ap_table, {
                                                ap_time = dz.time.rawTime,
                                                ap_hex = ap_hex,
                                                ap_flight = ap_flight,
                                                ap_r = ap_r,
                                                ap_t = ap_t,
                                                ap_desc = ap_desc,
                                                ap_gs = ap_gs,
                                                ap_track = ap_track,
                                                ap_direction = ap_direction,
                                                ap_lat = ap_lat,
                                                ap_long = ap_long,
                                                ap_distance = ap_distance,
                                                ap_geom_rate = ap_geom_rate,
                                                ap_geom_rateString = ap_geom_rateString,
                                                ap_altitude = ap_altitude
                                        });
                                end
                            else
                                dz.log( 'Skip flight that is passing at ' .. ap_altitude .. 'm. is higher then the set ' .. ap_maxAltitudeM .. ' m.', dz.LOG_DEBUG )
                            end
                        end
                    else
                        dz.log( 'Total flights found in response =  0.', dz.LOG_DEBUG )
                        -- See if we have something in the table to write to device.
                        if next( _d.ap_table ) ~= nil then
                            -- The table exist.
                            -- Store previous data from table if exist.
                            dz.log( 'Write and clear from global _d.ap_table, now no more flights are seen.', dz.LOG_DEBUG )
                            local linetoRemove = 0
                            if type( _d.ap_table ) == "table" then
                                --for eacht table row
                                tr = #_d.ap_table
                                for i = 1, tr do
                                    -- Get the previous stored data from device to compare.
                                    local ap_lastdeviceText = dz.devices(ap_text_idx).text   -- Holds string of the previous round.
        		                
                                    --Data we have in this row
                                    local airplaneText = '<a href="https://globe.airplanes.live/?icao=' .. _d.ap_table[i].ap_hex .. '" target="_blank"><span style="color: Blue">' .. _d.ap_table[i].ap_flight .. '</span></a>' .. ' - ' .. _d.ap_table[i].ap_r .. ' - ' .. _d.ap_table[i].ap_desc  .. 
        		                    '<br>Hoogte: ' .. _d.ap_table[i].ap_altitude .. ' m. Snelheid: ' .. _d.ap_table[i].ap_gs .. ' km/u. Afstand: ' .. _d.ap_table[i].ap_distance ..  ' km.' ..
        		                    '<br>Richting: ' .. _d.ap_table[i].ap_direction .. _d.ap_table[i].ap_geom_rateString .. ' Tijd: ' .. _d.ap_table[i].ap_time
                                    dz.log( 'Airplane  = ' .. airplaneText, dz.LOG_DEBUG )

                                    if airplaneText ~= ap_lastdeviceText then
                                        dz.log( 'Saving row with distance ' .. _d.ap_table[i].ap_distance .. ' km.', dz.LOG_DEBUG )
                                        -- Data is changed, so update the device.
                                        dz.devices(ap_text_idx).updateText( airplaneText )     

                                        if ap_useCSVfile == 1 then
                                            dz.log( 'Saving row also to csvFile ' .. ap_csvFile, dz.LOG_DEBUG )
                                            local logString = _d.ap_table[i].ap_time .. '\t' ..  _d.ap_table[i].ap_hex  .. '\t' ..  _d.ap_table[i].ap_flight  .. '\t' ..  _d.ap_table[i].ap_r  .. '\t' ..  _d.ap_table[i].ap_t  .. '\t' ..  _d.ap_table[i].ap_desc  .. '\t' ..  _d.ap_table[i].ap_gs  .. '\t' ..  _d.ap_table[i].ap_track  .. '\t' ..  _d.ap_table[i].ap_direction  .. '\t' ..  _d.ap_table[i].ap_lat  .. '\t' ..  _d.ap_table[i].ap_long  .. '\t' ..  _d.ap_table[i].ap_distance  .. '\t' ..   _d.ap_table[i].ap_geom_rateString  .. '\t' ..  _d.ap_table[i].ap_altitude
                                            if _u.fileExists( ap_csvFile ) then
                                                -- File exist so append content.
                                                _h.AppendStringToFile( dz, ap_csvFile, logString .. '\n' )
                                            else
                                                -- File does not exist. Start with header information.
                                                local headerString = 'ap_time\tap_hex\tap_flight\tap_r\tap_t\tap_desc\tap_gs\tap_track\tap_direction\tap_lat\tap_long\tap_distance\tap_geom_rateString\tap_altitude'
                                                _h.AppendStringToFile( dz, ap_csvFile, headerString  .. '\n' )                                                
                                                _h.AppendStringToFile( dz, ap_csvFile, logString  .. '\n' )
                                            end
                                        end
                                    end
                                    -- When it is stored, we can mark the row to be removed.
                                    linetoRemove = i
                                    break
                                end
                                if linetoRemove > 0 then
                                    -- Remove the row after exiting the loop.
                                    table.remove( _d.ap_table, linetoRemove )
                                    dz.log( 'Removed index ' .. linetoRemove ..  ' from global _d.ap_table.', dz.LOG_DEBUG )
                                end
                            end
                        else
                            dz.log( 'Nothing left in global _d.ap_table to write to device.', dz.LOG_DEBUG )
                        end
                    end
                else
                    dz.log( 'No result_table found', dz.LOG_ERROR )
                end
		    else
			    dz.log( 'Item or JSON - NOT OK', dz.LOG_DEBUG )
			end
	    end
	end
}
-- That's All --------------------------------------------------
Last edited by janpep on Tuesday 21 January 2025 20:03, edited 1 time in total.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

janpep wrote: Saturday 01 June 2024 9:34 During all night I got this error:
The connection has been restored, so the above must have been a temporary problem and not because I was refused and blocked.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

I received a suggestion from HvdW. Added an incremental counter in the script as an optional device. When created and configured, it counts the daily number of airplanes that go over your house.

Updated script t_Airplanes:

Code: Select all

-- 24-05-2024 script by Jan Peppink, https://ict.peppink.nl
-- Goal: Aircraft 'noise pollution detector'.
-- https://api.airplanes.live/v2/point/<lat>/<long>/<distanceKM>
-- Get data of airplaines that pass close by with lower altitudes.
-- When you hear them, they are probably logged. :-)
-- 27-05-2024 Call every 15 seconds. Store playe in global data table.
--  Update this when same plane is closer with call
--  When no planes are catched write the table (if not empty) to device.
-- 29-05-2024 alt_geom is sometimes empty. If so, take alt_baro. When this is also empty then set as 'unkown'.
-- 29-05-2024 Added optional saving to tab delimited csv file, with fieldnames as header.
---           Makes use of function 'AppendStringToFile' in global_data.
-- 31-05-2024 Added ap_pollFreq for setting the poll interval in seconds per 5 minutes.
-- 24-06-2024 Added optional incremental counter device 'ap_counter_idx'.

--- Your settings ----------------------------------------------------------------
-- First set the used device index numbers and variables you might want to change.
---#################################################################
local ap_text_idx = 99999        --idx of the custom Text device
local ap_counter_idx = 99999    --idx of the optional incremental counter device. (or 99999 if not confugured)

local ap_maxRadiusKm = 3    -- Query max radius in KM around your coordinates.
local ap_maxAltitudeM = 2500    -- Save only < max ap_maxAltitude in Meters.
local ap_useCSVfile = 0          -- 0 = NO csv file, or 1 = Save airplanes also in csv file.
local ap_csvFile = '/<pathtoyourfile>/SavedAirplanes.csv'
local ap_pollFreq = 15  --Poll frequncy in seconds. Default 15. Normally > 1 and < 60.

----------------------------------------------------------------------------------
return {
	on = {
		timer = { 
            'every 5 minutes', -->>>KEEP THIS at 5 minutes!<<<--Adjust ap_pollFreq in seconds in Your settings.
        },
        httpResponses = {
            'airplanes'       -- matches callback string below
        },
	},
	logging = {
        -- Level can be domoticz.LOG_INFO, domoicz.LOG_MODULE_EXEC_INFO, domoticz.LOG_DEBUG, domoticz.LOG_ERROR or domoticz.LOG_FORCE
        --level = domoticz.LOG_INFO,
        --level = domoticz.LOG_DEBUG,
		marker = 'Airplanes-',
	},
	execute = function(dz, triggeredItem)
        -- Set Local environment=================
        local _u = dz.utils       -- Holds subset of handy utilities.
        local _h = dz.helpers     -- Holds the global functions
        local _d = dz.globalData  -- Holds the global data

        --_d.initialize('ap_table') -- Re-initialize the table

        -- Get location coordinates
        local lat = dz.settings.location.latitude
        local long = dz.settings.location.longitude

        local ap_maxRadiusMiles = ap_maxRadiusKm * 0.62137119 -- Calculate given km in sdettings to miles.
        
        -- Use some color variables
--        local cGreen = '#008000;'
--        local cYellow = '#ffff00;'
--        local cOrange = '#ffa500;'
--        local cRed = '#ff0000;'	
--        local cWhite = '#FFFFFF;'
--        local cBlue = '#0000FF;'
--        local cGrey = '#8d8d8d;'

--        local htmlColor = '#3b3bc4;'
--        --Adjust lineheight
--        local lineHeight = 1.2

        -- Local Functions go here =============
        local function airplanesExist( testValue )
            -- check if this plane is already in _d.ap_table
            if type( _d.ap_table ) == "table" then
                for i = 1, #_d.ap_table do
                    if _d.ap_table[i].ap_hex == testValue then
                        -- Found in table
                        dz.log( testValue .. ' is found in _d.ap_table', dz.LOG_DEBUG )
                        return true
                    end
                end
                -- Not found in table
                dz.log( testValue .. ' is not found in _d.ap_table', dz.LOG_DEBUG )
                return false
            else
                -- Table not found.
                dz.log( 'Did not find _d.ap_table - Re-initialize.', dz.LOG_DEBUG )
                _d.initialize('ap_table') -- Re-initialize the table
                return false
            end
        end

        -- Now start to do something ============
        -- Get the data.
		if (triggeredItem.isTimer) then
            -- Retrieve the data every ap_pollFreq second per 5 minutes.
            for seconds = ap_pollFreq, 300, ap_pollFreq do
                dz.openURL({
				    url = 'https://api.airplanes.live/v2/point/' .. lat .. '/' .. long ..'/' .. ap_maxRadiusMiles,
				    method = 'GET',
				    callback = 'airplanes'
                }).afterSec(seconds)
            end
		end	

        if (triggeredItem.isHTTPResponse) then
            -- Process the obtained data.
            -- Check the response and process the data.
            if (triggeredItem.ok and triggeredItem.isJSON) then
                dz.log( 'Item and JSON - OK', dz.LOG_DEBUG )
				-- We have some result. Store in table.
				local result_table = triggeredItem.json.ac
                if type(result_table) == "table" then
                    dz.log( 'result_table: type = ' .. type(result_table), dz.LOG_DEBUG )
                    -- The data we get is:
					--{
					--  "ac": [
					--    {
					--      "hex": "485064",
					--      "type": "adsb_icao",
					--      "flight": "KLM66C  ",
					--      "r": "PH-EZY",
					--      "t": "E190",
					--      "desc": "EMBRAER ERJ-190-100",
					--      "ownOp": "Klm Cityhopper",
					--      "alt_baro": 1875,
					--      "alt_geom": 2200,
					--      "gs": 254.5,
					--      "ias": 248,
					--      "tas": 256,
					--      "mach": 0.388,
					--      "wd": 19,
					--      "ws": 6,
					--      "oat": 6,
					--      "tat": 15,
					--      "track": 93.6,
					--      "track_rate": 0.09,
					--      "roll": -0.18,
					--      "mag_heading": 90,
					--      "true_heading": 92.4,
					--      "baro_rate": -192,
					--      "geom_rate": -128,
					--      "squawk": "7631",
					--      "emergency": "none",
					--      "category": "A3",
					--      "nav_qnh": 1018.4,
					--      "nav_altitude_mcp": 2016,
					--      "nav_altitude_fms": 2000,
					--      "nav_modes": [
					--        "autopilot",
					--        "tcas"
					--      ],
					--      "lat": 52.166748,
					--      "lon": 4.638977,
					--      "nic": 8,
					--      "rc": 186,
					--      "seen_pos": 0.179,
					--      "recentReceiverIds": [
					--        "8a6d5aed-e6d2-4a79",
					--        "462a41f7-9808-5db2",
					--        "b29d8c19-8849-41bb",
					--        "8a4d3496-8f73-11ea",
					--        "c21ace72-6613-4a53",
					--        "4284e70f-e716-e5ab",
					--        "01122182-d7a1-11ec",
					--        "40d9183e-8c3f-47a1",
					--        "6293f6c0-1ecf-4ad2",
					--        "e74ec3c3-c566-e5e2",
					--        "cdf69800-c853-47b0",
					--        "f786031b-56b9-4b97",
					--        "7715abf6-175d-4d44"
					--      ],
					--      "version": 2,
					--      "nic_baro": 1,
					--      "nac_p": 10,
					--      "nac_v": 2,
					--      "sil": 3,
					--      "sil_type": "perhour",
					--      "gva": 2,
					--      "sda": 2,
					--      "alert": 0,
					--      "spi": 0,
					--      "mlat": [],
					--      "tisb": [],
					--      "messages": 119867,
					--      "seen": 0.2,
					--      "rssi": -6.5,
					--      "dst": 1.883,
					--      "dir": 318.9
					--    }
					--  ],
					--  "msg": "No error",
					--  "now": 1716574591594,
					--  "total": 1,
					--  "ctime": 1716574591594,
					--  "ptime": 0
					--}
                    -- Now loop through the resul_table
                    if triggeredItem.json.total > 0 then
                        -- We see at least one plane. Retreive its data.
                        local tc = #result_table
                        for i = 1, tc do
                            local ap_hex = result_table[i].hex
                            local ap_flight = result_table[i].flight
                            local ap_r = result_table[i].r
                            local ap_t = result_table[i].t
                            local ap_desc = result_table[i].desc 
                            local ap_gs = _u.round( result_table[i].gs * 1.851999278976, 0 ) -- Calculate groundspeed knots to km.
                            local ap_track = result_table[i].track -- direction in degree
                            local ap_direction = _h.getDirectionfromDegree( ap_track )
                                -- Translate abbreviation into Dutch abbreviation. E(ast) becomes O(ost) S(outh) becomes Z(uid).
                                ap_direction = string.gsub( ap_direction, "E", "O", 2 )
                                ap_direction = string.gsub( ap_direction, "S", "Z", 2 )
                            local ap_lat = result_table[i].lat
                            local ap_long = result_table[i].lon
                                dz.log( 'Lat en Long: ' ..  ap_lat .. ' - ' .. ap_long, dz.LOG_DEBUG )
                            local ap_distance = _u.round( _h.calculateDistance( lat, long, ap_lat, ap_long ), 1 )

                            local ap_geom_rateString = ''
                            local ap_geom_rate = result_table[i].geom_rate
                                if ap_geom_rate ~= nil and ap_geom_rate > 0 then
                                    -- Ascending.
                                    ap_geom_rateString = ' Stijgt ' .. _u.round ( ap_geom_rate * 0.3048 , 0 ) .. ' m/min.'
                                elseif ap_geom_rate ~= nil and ap_geom_rate < 0 then
                                    -- Descending.
                                    ap_geom_rateString = ' Daalt ' .. _u.round ( math.abs( ap_geom_rate ) * 0.3048 , 0 ) .. ' m/min.'
                                end

                            local ap_altitude = 0
                                --The fieldalt_geom is regularly empty. Therefore we check for alternative.
                                if result_table[i].alt_geom ~= nil then
                                    dz.log( 'alt_geom ' .. result_table[i].alt_geom .. ' is used.', dz.LOG_INFO )
                                    -- Preferred fot the height.
                                    ap_altitude = _u.round ( result_table[i].alt_geom * 0.3048 , 0 ) --Calculate height feet to meters.
                                elseif result_table[i].alt_baro ~= nil then 
                                    dz.log( 'alt_baro ' .. result_table[i].alt_baro .. ' is used.', dz.LOG_INFO )
                                    -- If alt_geom is not available take the alt_baro.
                                    ap_altitude = _u.round ( result_table[i].alt_baro * 0.3048 , 0 ) --Calculate height feet to meters.
                                end                                

                            if ap_altitude <= ap_maxAltitudeM then 
                                if ap_altitude == 0 then
                                    dz.log( 'altitude is unknown.', dz.LOG_INFO )
                                    ap_altitude = 'onbekend'
                                end
                                -- It is within the max. height.
                                dz.log( 'I see: ' .. ap_hex .. ' with distance = ' .. ap_distance .. ' km.', dz.LOG_DEBUG )

                                if airplanesExist( ap_hex ) == true then
                                    -- This plane exist already in our table. check and update when needed.
                                    local tc = #_d.ap_table
                                    dz.log( 'Global _d.ap_table exist with ' .. tc .. ' rows.', dz.LOG_DEBUG )
                                    for i = 1, tc do
                                        if _d.ap_table[i].ap_hex == ap_hex then
                                            -- We got the same airplane.
                                            if ap_distance < _d.ap_table[i].ap_distance then
                                                dz.log( 'Update row with closer distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
                                                -- Update/Overwrite with the closest.
                                                _d.ap_table[i].ap_time = dz.time.rawTime
                                                _d.ap_table[i].ap_hex = ap_hex
                                                _d.ap_table[i].ap_flight = ap_flight
                                                _d.ap_table[i].ap_r = ap_r
                                                _d.ap_table[i].ap_t = ap_t
                                                _d.ap_table[i].ap_desc = ap_desc
                                                _d.ap_table[i].ap_gs = ap_gs
                                                _d.ap_table[i].ap_track = ap_track
                                                _d.ap_table[i].ap_direction = ap_direction
                                                _d.ap_table[i].ap_lat = ap_lat
                                                _d.ap_table[i].ap_long = ap_long
                                                _d.ap_table[i].ap_distance = ap_distance
                                                _d.ap_table[i].ap_geom_rate = ap_geom_rate
                                                _d.ap_table[i].ap_geom_rateString = ap_geom_rateString
                                                _d.ap_table[i].ap_altitude = ap_altitude
                                            end
                                        end
                                    end
                                else
                                    -- This plane is Not in the table. So insert this new airplane.
                                    dz.log( 'New row created in global _d.ap_table with distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
                                    table.insert( _d.ap_table, {
                                                ap_time = dz.time.rawTime,
                                                ap_hex = ap_hex,
                                                ap_flight = ap_flight,
                                                ap_r = ap_r,
                                                ap_t = ap_t,
                                                ap_desc = ap_desc,
                                                ap_gs = ap_gs,
                                                ap_track = ap_track,
                                                ap_direction = ap_direction,
                                                ap_lat = ap_lat,
                                                ap_long = ap_long,
                                                ap_distance = ap_distance,
                                                ap_geom_rate = ap_geom_rate,
                                                ap_geom_rateString = ap_geom_rateString,
                                                ap_altitude = ap_altitude
                                        });
                                end
                            else
                                dz.log( 'Skip flight that is passing at ' .. ap_altitude .. 'm. is higher then the set ' .. ap_maxAltitudeM .. ' m.', dz.LOG_DEBUG )
                            end
                        end
                    else
                        dz.log( 'Total flights found in response =  0.', dz.LOG_DEBUG )
                        -- See if we have something in the table to write to device.
                        if next( _d.ap_table ) ~= nil then
                            -- The table exist.
                            -- Store previous data from table if exist.
                            dz.log( 'Write and clear from global _d.ap_table, now no more flights are seen.', dz.LOG_DEBUG )
                            local linetoRemove = 0
                            if type( _d.ap_table ) == "table" then
                                --for eacht table row
                                tr = #_d.ap_table
                                for i = 1, tr do
                                    -- Get the previous stored data from device to compare.
                                    local ap_lastdeviceText = dz.devices(ap_text_idx).text   -- Holds string of the previous round.
        		                
                                    --Data we have in this row
                                    local airplaneText = '<a href="https://globe.airplanes.live/?icao=' .. _d.ap_table[i].ap_hex .. '" target="_blank"><span style="color: Blue">' .. _d.ap_table[i].ap_flight .. '</span></a>' .. ' - ' .. _d.ap_table[i].ap_r .. ' - ' .. _d.ap_table[i].ap_desc  .. 
        		                    '<br>Hoogte: ' .. _d.ap_table[i].ap_altitude .. ' m. Snelheid: ' .. _d.ap_table[i].ap_gs .. ' km/u. Afstand: ' .. _d.ap_table[i].ap_distance ..  ' km.' ..
        		                    '<br>Richting: ' .. _d.ap_table[i].ap_direction .. _d.ap_table[i].ap_geom_rateString .. ' Tijd: ' .. _d.ap_table[i].ap_time
                                    dz.log( 'Airplane  = ' .. airplaneText, dz.LOG_DEBUG )

                                    if airplaneText ~= ap_lastdeviceText then
                                        dz.log( 'Saving row with distance ' .. _d.ap_table[i].ap_distance .. ' km.', dz.LOG_DEBUG )
                                        -- Data is changed, so update the device.
                                        dz.devices(ap_text_idx).updateText( airplaneText )
                                        if ap_counter_idx ~= 99999 then
                                            --Counter is configured, so update the incremental counter device
                                            dz.devices(ap_counter_idx).updateCounter(1)
                                        end

                                        if ap_useCSVfile == 1 then
                                            dz.log( 'Saving row also to csvFile ' .. ap_csvFile, dz.LOG_DEBUG )
                                            local logString = _d.ap_table[i].ap_time .. '\t' ..  _d.ap_table[i].ap_hex  .. '\t' ..  _d.ap_table[i].ap_flight  .. '\t' ..  _d.ap_table[i].ap_r  .. '\t' ..  _d.ap_table[i].ap_t  .. '\t' ..  _d.ap_table[i].ap_desc  .. '\t' ..  _d.ap_table[i].ap_gs  .. '\t' ..  _d.ap_table[i].ap_track  .. '\t' ..  _d.ap_table[i].ap_direction  .. '\t' ..  _d.ap_table[i].ap_lat  .. '\t' ..  _d.ap_table[i].ap_long  .. '\t' ..  _d.ap_table[i].ap_distance  .. '\t' ..   _d.ap_table[i].ap_geom_rateString  .. '\t' ..  _d.ap_table[i].ap_altitude
                                            if _u.fileExists( ap_csvFile ) then
                                                -- File exist so append content.
                                                _h.AppendStringToFile( dz, ap_csvFile, logString .. '\n' )
                                            else
                                                -- File does not exist. Start with header information.
                                                local headerString = 'ap_time\tap_hex\tap_flight\tap_r\tap_t\tap_desc\tap_gs\tap_track\tap_direction\tap_lat\tap_long\tap_distance\tap_geom_rateString\tap_altitude'
                                                _h.AppendStringToFile( dz, ap_csvFile, headerString  .. '\n' )                                                
                                                _h.AppendStringToFile( dz, ap_csvFile, logString  .. '\n' )
                                            end
                                        end
                                    end
                                    -- When it is stored, we can mark the row to be removed.
                                    linetoRemove = i
                                    break
                                end
                                if linetoRemove > 0 then
                                    -- Remove the row after exiting the loop.
                                    table.remove( _d.ap_table, linetoRemove )
                                    dz.log( 'Removed index ' .. linetoRemove ..  ' from global _d.ap_table.', dz.LOG_DEBUG )
                                end
                            end
                        else
                            dz.log( 'Nothing left in global _d.ap_table to write to device.', dz.LOG_DEBUG )
                        end
                    end
                else
                    dz.log( 'No result_table found', dz.LOG_ERROR )
                end
		    else
			    dz.log( 'Item or JSON - NOT OK', dz.LOG_DEBUG )
			end
	    end
	end
}
-- That's All --------------------------------------------------
Last edited by janpep on Tuesday 21 January 2025 20:03, edited 1 time in total.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
PieterS
Posts: 197
Joined: Wednesday 31 May 2017 16:06
Target OS: NAS (Synology & others)
Domoticz version: 2024.7
Location: NL
Contact:

Re: Script for Airplanes.live api

Post by PieterS »

Very interesting script for monitoring my environment. Copied the script of yesterday. But maybe I missed some info about configuring the settings?

In Domoticz:
I created 2 virtual devices:
General, subtype Counter Incremental
General, subtype Tekst

In the script:
If filled the number of the IDX in in the settingspart of the script.

Where do I mark down my location? Is that picked up from the Systemsettings, Location of Domoticz?

After that I saved the script and did a restart of Domoticz.

This is in the log:

Code: Select all

2024-06-25 11:33:31.139 Error: dzVents: Error: (3.1.8) Airplanes-: An error occurred when calling event handler airplanes
2024-06-25 11:33:31.139 Error: dzVents: Error: (3.1.8) Airplanes-: .../domoticz/userdata/scripts/dzVents/scripts/airplanes.lua:304: bad argument #1 to 'next' (table expected, got nil)
Running Domoticz Build 16089 in Docker container on Synology with DSM 7.1

Any help is appreciated.
Synology with Domoticz build (V2024.7) in Docker
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

PieterS wrote: Tuesday 25 June 2024 11:42 Copied the script of yesterday. But maybe I missed some info about configuring the setting
It looks like the table is missing. In the first post you see that this table is placed in 'global_data'. In this way Iam able to access it also from my "startup" script.
The same is for the used functions.

The location is indeed picked up from settings.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
PieterS
Posts: 197
Joined: Wednesday 31 May 2017 16:06
Target OS: NAS (Synology & others)
Domoticz version: 2024.7
Location: NL
Contact:

Re: Script for Airplanes.live api

Post by PieterS »

It looks like the table is missing. In the first post you see that this table is placed in 'global_data'. In this way Iam able to access it also from my "startup" script.
The same is for the used functions.
Thnx for reply. Yes, I saw a correlation to a missing table in that error.

I read your first post again but I could not find any instruction to configure the environment. I have no idea if is need for that maybe? Even when I read the Wiki https://www.domoticz.com/wiki/DzVents:_ ... _variables there is only an explanation about the process.
In the paragraph How does the storage stuff work? I read that there should show up a file related to airplanes.lua in the map \\X.X.X.X\docker\domoticz\scripts\dzVents\data but that map contains only a README.me from Domoticz.

Up till now I have no idea how to configure Domoticz so that it facilitates persistent data in Docker..

I put the file in the map scripts:
Image

There is no file airplanes.lua in generated_scripts either.. Even after reboot of the container Domoticz.

Image

Domoticz has RW-rights to those (sub-)directories.
Any suggestions to solve the problem?
Synology with Domoticz build (V2024.7) in Docker
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

The info about persisant data and helper functions is in: https://www.domoticz.com/wiki/DzVents:_ ... stent_data

Save the following code under Settings --> More options --> Events as 'global_data', OR.... when this already exist, add the parts to the existing script. That should do it.
NB. I am not familiar how and where this is saved under docker. I am working with a Virtual Ubuntu machine.

I removed parts that have no relation to the airplane script.

global_data

Code: Select all

-- 05-01-2023: Script created by Jan peppink, https://ict.peppink.nl
-- This scripts holds all the globally persistent variables and helper functions
-- See the documentation in the wiki
-- NOTE: THERE CAN BE ONLY ONE global_data SCRIPT in your Domoticz install.

----------------------------------------------------------------------------------
return {
    ------------------------------------------
	-- Global persistent data
	data = {
        -- Used in t-Airplanes, Reinitialized at start dz in dst-SetDagAvondNachtStand.
        ap_table = { initial = {} },
    },

    ------------------------------------------
    -- Global helper functions
	helpers = {
        ------------------------
        -- Used in t-Earthquake-KNMI, t-Airplanes,
		calculateDistance = function( lat1, lon1, lat2, lon2)
    		-- Calculate distance using Haversine formula
		    local R = 6371 -- Earth radius in kilometers
		    local dLat = math.rad(lat2 - lat1)
			local dLon = math.rad(lon2 - lon1)
			local a = math.sin(dLat / 2) * math.sin(dLat / 2) + math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) * math.sin(dLon / 2) * math.sin(dLon / 2)
			local c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
			local distance = R * c
			return distance
		end,
        ------------------------
        -- Used in dt-OpenMeteo, t-Weerlive, t-Airplanes,
        getDirectionfromDegree = function( degrees )
            -- To calculate the degrees.
            local directionString = ''
            -- When string is not in Enlish, the icon does not appear!
            if degrees >= 0 and degrees < 11.25 then directionString = 'N' end
            if degrees >= 11.25 and degrees < 33.75 then directionString = 'NNE' end
            if degrees >= 33.75 and degrees < 56.25 then directionString = 'NE' end
            if degrees >= 56.25 and degrees < 78.75 then directionString = 'ENE' end
            if degrees >= 78.75 and degrees < 101.25 then directionString = 'E' end
            if degrees >= 101.25 and degrees < 123.75 then directionString = 'ESE' end
            if degrees >= 123.75 and degrees < 146.25 then directionString = 'SE' end
            if degrees >= 146.25 and degrees < 168.75 then directionString = 'SSE' end
            if degrees >= 168.75 and degrees < 191.25 then directionString = 'S' end
            if degrees >= 191.25 and degrees < 213.75 then directionString = 'SSW' end
            if degrees >= 213.75 and degrees < 236.25 then directionString = 'SW' end
            if degrees >= 236.25 and degrees < 258.75 then directionString = 'WSW' end
            if degrees >= 258.75 and degrees < 281.25 then directionString = 'W' end
            if degrees >= 281.25 and degrees < 303.75 then directionString = 'WNW' end
            if degrees >= 303.75 and degrees < 326.25 then directionString = 'NW' end
            if degrees >= 326.25 and degrees < 348.75  then directionString = 'NNW' end
            if degrees >= 348.75 and degrees <= 360 then directionString = 'N' end
            return directionString
        end,
        ------------------------
        -- Used in t-Airplanes,
       AppendStringToFile = function( dz, logfileName, logString )
            local file = io.open( logfileName, "a" )
            file:write( logString )
			file:close()            
        end,
        ------------------------
	}
}
-- That's All --------------------------------------------------
Last edited by janpep on Saturday 29 June 2024 11:55, edited 1 time in total.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
PieterS
Posts: 197
Joined: Wednesday 31 May 2017 16:06
Target OS: NAS (Synology & others)
Domoticz version: 2024.7
Location: NL
Contact:

Re: Script for Airplanes.live api

Post by PieterS »

Thnx Jan for quick response!

I copied the above script via Setup, More Options, Events, tab +, dzVents, Global Data into a new script.
I deleted the default text and pasted your script.
Saved it as: global_data

After a restart of Domoticz there is in the log:

Code: Select all

2024-06-25 16:24:36.649 Status: EventSystem: reset all events...
2024-06-25 16:24:36.650 Status: dzVents: Write file: /opt/domoticz/userdata/scripts/dzVents/generated_scripts/Luftdaten.lua
2024-06-25 16:24:36.650 Status: dzVents: Write file: /opt/domoticz/userdata/scripts/dzVents/generated_scripts/Dead devices.lua
2024-06-25 16:24:36.650 Status: dzVents: Write file: /opt/domoticz/userdata/scripts/dzVents/generated_scripts/Uptime.lua
2024-06-25 16:24:36.650 Status: dzVents: Write file: /opt/domoticz/userdata/scripts/dzVents/generated_scripts/global_data.lua 
No errors any more:D

As the wind is East there are no planes today! 8-)Very curious to the results! As soon as there is traffic again I will have a look and let you know!

Many thanks for all the good work and support!
Synology with Domoticz build (V2024.7) in Docker
janpep
Posts: 241
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2024.7
Location: Netherlands
Contact:

Re: Script for Airplanes.live api

Post by janpep »

I'm glad it works.
Domoticz in Ubuntu virtual machine on Synology DS718+ behind FRITZ!Box.
Using: EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
PieterS
Posts: 197
Joined: Wednesday 31 May 2017 16:06
Target OS: NAS (Synology & others)
Domoticz version: 2024.7
Location: NL
Contact:

Re: Script for Airplanes.live api

Post by PieterS »

Suprise!!! :lol:
Image

Better than I thought!! I checked in Webtrak because I missed it.
Thnx again! :!:

Only next goal is export to a csv-file so that I can filter.
Synology with Domoticz build (V2024.7) in Docker
Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests