Page 3 of 3

Re: Script for Airplanes.live api

Posted: Friday 28 June 2024 20:46
by janpep
I would not show your personal coordinates and remove the image. For me enough to know they are filled in.
In the URL I saw 0,0 so something strange happened there.
In the script the coordinates from settings are taken with the following selfexplaning lines:

Code: Select all

        -- Get location coordinates
        local lat = dz.settings.location.latitude
        local long = dz.settings.location.longitude
and in the URL these lat and long are filled in at the place where your 0,0 was.

Code: Select all

url = 'https://api.airplanes.live/v2/point/' .. lat .. '/' .. long ..'/' .. ap_maxRadiusMiles,
When you did not change one of these lines, I would not know the reason at this moment.
Workaround or try could be to fill in your latitude and longitude in stead of getting them the from the settings like:

Code: Select all

        -- Get location coordinates
        --local lat = dz.settings.location.latitude
        --local long = dz.settings.location.longitude
        local lat = yourlatitude
        local long = yourlongitude
See what happens and go from there.

Re: Script for Airplanes.live api

Posted: Friday 28 June 2024 21:49
by DiaDomo
Thx, I already hard coded the coordinates and now it's working.

Re: Script for Airplanes.live api

Posted: Friday 28 June 2024 23:07
by janpep
Updated t-Airplanes with added date to the (optional) CSV file export.
To keep it simple I use the system date and time when airplane is noticed.
When you have an existing csv file.... Maybe move/save that first. A new file will then be created automatically with new header line.
Otherwise new lines will be added with the extra field, not matching the old header line.

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'.
-- 28-06-2024 Added the date to the csv file export.

--- 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('airplanes') -- 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.
                        
                        -- Get date and time.
                        local ap_date = dz.time.dateToDate( dz.time.rawDate,'yyyy-mm-dd', 'dd-mm-yyyy' ) 
                        local ap_time = dz.time.rawTime
                        dz.log( 'ap_date: ' .. ap_date  .. ' and ap_time: ' .. ap_time, dz.LOG_DEBUG )
                        
                        --Now get remaining stuff from result_table.
                        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_date = ap_date
                                                _d.ap_table[i].ap_time = ap_time
                                                _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_date = ap_date,
                                                ap_time = ap_time,
                                                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_date .. '\t' .. _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_date\tap_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 --------------------------------------------------

Re: Script for Airplanes.live api

Posted: Saturday 29 June 2024 9:37
by HvdW
Hey @janpep, your baby has matured to an adult in quite a short time.
My compliments for all the love, care and devotion.

Re: Script for Airplanes.live api

Posted: Saturday 29 June 2024 10:12
by janpep
HvdW wrote: Saturday 29 June 2024 9:37 My compliments for all the love, care and devotion.
Thank you.
Next on my todo list is to look at better error handling (for all my scripts).
In this case, e.g. when fields are nil, I found that this can be the cause that the table can not be written and re-initialisation of the table is only solution.

Re: Script for Airplanes.live api

Posted: Saturday 29 June 2024 11:10
by janpep
DiaDomo wrote: Friday 28 June 2024 21:49 Thx, I already hard coded the coordinates and now it's working.
This is what I found in another topic from waltervl:

"Make sure that in the Security section in the settings (Setup > Settings > System > Local Networks (no username/password) you allow 127.0.0.1 (and / or ::1 when using IPv6 ) to not need a password. dzVents does use this port to get the location settings and to send certain commands to Domoticz."
Could this be the reason not getting the lat & long?

See: https://www.domoticz.com/forum/viewtopi ... 69#p291669

Re: Script for Airplanes.live api

Posted: Saturday 29 June 2024 11:34
by zenit
Hi Jan,

I am trying fiddeling with your sript to get it working.

Trying my lattitude en longtitude and Nm in "https://api.airplanes.live/v2/point..." is working. I'm getting back some json data.

But I'm keep getting this error in the log.

2024-06-29 11:31:45.266 Error: dzVents: Error: (3.1.8) Airplanes-: An error occurred when calling event handler Script #1
2024-06-29 11:31:45.266 Error: dzVents: Error: (3.1.8) Airplanes-: ...domoticz/scripts/dzVents/generated_scripts/Script #1.lua:217: attempt to index a nil value (local '_h')

Ant thoughts about this?

Kind regards,

Patrick

Re: Script for Airplanes.live api

Posted: Saturday 29 June 2024 11:48
by janpep
zenit wrote: Saturday 29 June 2024 11:34 ...domoticz/scripts/dzVents/generated_scripts/Script #1.lua:217: attempt to index a nil value (local '_h')
Ant thoughts about this?
In this line a function is called, that is expected to be found in global_data. I assume this is missing.

See: https://www.domoticz.com/forum/viewtopi ... 81#p317981

Re: Script for Airplanes.live api

Posted: Saturday 29 June 2024 12:05
by DiaDomo
Hi, it was working, but now I'm getting these errors in the log file:

Code: Select all

2024-06-29 11:48:30.861 Error: dzVents: Error: (3.1.8) Airplanes: An error occurred when calling event handler airplanes
2024-06-29 11:48:30.861 Error: dzVents: Error: (3.1.8) Airplanes: /home/pi/domoticz/scripts/dzVents/scripts/airplanes.lua:321: attempt to concatenate a nil value (field 'ap_desc')
2024-06-29 11:48:45.894 Error: dzVents: Error: (3.1.8) Airplanes: An error occurred when calling event handler airplanes
2024-06-29 11:48:45.894 Error: dzVents: Error: (3.1.8) Airplanes: /home/pi/domoticz/scripts/dzVents/scripts/airplanes.lua:321: attempt to concatenate a nil value (field 'ap_desc')
2024-06-29 11:49:00.889 Error: dzVents: Error: (3.1.8) Airplanes: An error occurred when calling event handler airplanes
2024-06-29 11:49:00.889 Error: dzVents: Error: (3.1.8) Airplanes: /home/pi/domoticz/scripts/dzVents/scripts/airplanes.lua:321: attempt to concatenate a nil value (field 'ap_desc')
How you any idea how to fix this?

Re: Script for Airplanes.live api

Posted: Saturday 29 June 2024 12:35
by janpep
This is what I meant earlier. See post at 10:12 hr.
Because the error, it fails to write. Then the row will remain in the table.
So re-initialize the table.

Code: Select all

domoticz.globalData.initialize('ap_table') -- To re-initialize the table
Or uncomment line 50 in t_Airplanes until it has run one time.

Code: Select all

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

Re: Script for Airplanes.live api

Posted: Saturday 29 June 2024 12:49
by janpep
It appears that a number of fields can be empty regularly and cause this problem. I will try to look into it more thoroughly next week.

Re: Script for Airplanes.live api

Posted: Monday 01 July 2024 10:58
by PieterS
Another minor problem:

I notice every morning a bar somewhere in the middle of the night. Sometimes in the 01:00, sometimes like tonight in the 03:00 hour bar..
Should not be allowed conform the standard rules of that airport.
There is no record in the textfile nor in the csv-file.

Image
Image
Image

I still not updated to last version with date in the csv-file :oops: No errors in log.

All this info Just to inform. Thanks for all the good work. This info will give a good view of the noise in this area.

Re: Script for Airplanes.live api

Posted: Monday 01 July 2024 11:47
by janpep
PieterS wrote: Monday 01 July 2024 10:58 All this info Just to inform. Thanks for all the good work. This info will give a good view of the noise in this area.
Thanks for reporting. I don't see an explanation for this yet. Both text~ and counterdevice are updated in the same 'if .. then' block. When there is no plane, there is nothing to count.
I didn't use the counter myself. I've added it now and will see if I can reproduce or explain it.

Re: Script for Airplanes.live api

Posted: Tuesday 02 July 2024 9:52
by janpep
janpep wrote: Monday 01 July 2024 11:47 I didn't use the counter myself. I've added it now and will see if I can reproduce or explain it.
I can reproduce the finding. It even goes both ways!
Measured here last night with low altitude and distance settings:
22 hrs. column -11 graph - 11 in text - 11 in csv.
00 hr. column - 2 in graph - 0 in text - 0 in csv.
06 hr. column - 1 in graph - 2 in text - 2 in csv.
07 hr. column - 0 in graph - 1 in text - 1 in csv.
In any case, text and CSV are always the same here.
Very strange. I can't explain it yet, because they are all written under the same conditions.

Re: Script for Airplanes.live api

Posted: Tuesday 02 July 2024 11:11
by PieterS
H Jan,

Since I updated yesterday to your latest version (with date in csv) the script gave errors.

Code: Select all

 Error: dzVents: Error: (3.1.8) Airplanes-: .../domoticz/userdata/scripts/dzVents/scripts/airplanes.lua:319: attempt to concatenate a nil value (field 'ap_flight')
So I reverted to the old script and it is broken too.. No more traffic noticed in the counter, csv and text. :roll:

This are the lines around line 319
Image

Building a clean install of Domoticz in Docker with new container, latest beta (build 16110) and different directory and port on the host to find out what is wrong. Keep you informed.

Re: Script for Airplanes.live api

Posted: Tuesday 02 July 2024 11:43
by janpep
PieterS wrote: Tuesday 02 July 2024 11:11 Since I updated yesterday to your latest version (with date in csv) the script gave errors.
This has nothing to do with update. It is mentioned two times before. Problem is in the empty field (nil). This gives an error.
Because the error, it fails to write. Then the row will remain in the table. It tries again and again and again. So re-initialize the table.
I was on vacation and with a very poor internet connection. So I could not do much. This week I will try to capture all these nil fields.
Maybe naive, but to be honest, I did not expect these empty fields for altitude, flight etc.
To be save, I guess I should better check them all.
It will take some time. Sometimes I have more to do.

Re: Script for Airplanes.live api

Posted: Tuesday 02 July 2024 11:47
by janpep
PieterS wrote: Tuesday 02 July 2024 11:11 Building a clean install of Domoticz in Docker with new container, latest beta (build 16110) and different directory and port on the host to find out what is wrong. Keep you informed.
This is quite rigorous for an issue that arises in a script and was mentioned before. :-)

Re: Script for Airplanes.live api

Posted: Tuesday 02 July 2024 12:15
by PieterS
janpep wrote: Tuesday 02 July 2024 11:43
PieterS wrote: Tuesday 02 July 2024 11:11 Since I updated yesterday to your latest version (with date in csv) the script gave errors.
This has nothing to do with update. It is mentioned two times before. Problem is in the empty field (nil). This gives an error.
Because the error, it fails to write. Then the row will remain in the table. It tries again and again and again. So re-initialize the table.
Ok, missed that.. I can understand about existing data in the table. But I am not used to edit the table in the Domoticz database. A bit hesitant to edit that file.. :(

I build a new Domoticz system in a seperate container with this result:

Image

So happy for now. :D

Re: Script for Airplanes.live api

Posted: Tuesday 02 July 2024 13:01
by janpep
PieterS wrote: Tuesday 02 July 2024 12:15 Ok, missed that.. I can understand about existing data in the table. But I am not used to edit the table in the Domoticz database. A bit hesitant to edit that file.. :(
I do not talk about editing domoticz tables, but the array that is kept by the script in the persistant data.
Explanation about cleaning this table is here.
https://www.domoticz.com/forum/viewtopi ... 94#p318094

Re: Script for Airplanes.live api

Posted: Sunday 14 July 2024 14:19
by janpep
Updated t-Airplanes.
janpep wrote: Saturday 29 June 2024 10:12 Next on my todo list is to look at better error handling (for all my scripts).
In this case, e.g. when fields are nil, I found that this can be the cause that the table can not be written and re-initialisation of the table is only solution.
So I had some work to do and I found a few more things to change. I had tree problems to overcome.

1. As mentioned earlier, it appears that certain fields are not always filled or contain a string where a numerical value is expected and used for calculations. This is now being captured as much as possible. A variable can be set now to give these fields the value 'unknown'.
I have not seen that the record cannot be written because of a nil field after this change.
I discovered a new way of declaring a variable where a second option is used when the field is nil. See the OR.

Code: Select all

ap_flight = result_table[i].flight or ap_unknown	-- If field is nil, or will evaluate to its second operand.
2. Flights were always inserted and updated in the table and were only written when no more flights were seen.
If the search radius and altitude are set very large, or if there is a lot of air traffic within the search area, so many planes could pass by that the script did not have time to write it. For that reason I have changed the order of some actions. That was not easy, because it has grown to be a huge script with consecutive tasks. That is why I have now moved some tasks to separate functions, which also makes it easier to read.

3. But now I had to find out when a plane is ready to be written and removed from the table.
If an aircraft is seen multiple times, the row in the table is updated if it came closer. If it is further, the row will be marked as 'leaving' as a sign to write. That works well, but is a problem for an aircraft that (fast passing or small search area) is only seen once, or is no longer seen once it travels further. So it will never be marked as 'leaving'.
A variable 'ap_forcewriteCounter' has now been set for this purpose. When this number is reached, the row will be written and deleted. The number is debatable. I've set it to 10 now. The last comment here is that it can lead to the situation that an aircraft, that was seen earlier, is being written after others. However, the time that is was seen is in the text and in the CSV file.

I have added field descriptions to the example fields of the result_table. Moved this as a reference to the end of the script.

I tested this with large numbers by increasing the search area so that Schiphol airport was within the radius and it seems to work well.

NB. The used table and functions placed in global_data script (mentioned earlier) are still needed, but remain unchanged.

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'.
-- 28-06-2024 Added the date to the csv file export.
-- 10-07-2024 Prevent nil or non numeric values that can not be read or calculated.
--  Added " or ap_unknown	--If variable is nil, 'or' will evaluate to the second operand.
--  Added field descriptions to the Example fields. Moved this as a reference to the end of the script.
-- 13-07-2024 Completely new code layout with separate functions.
--  Deleting rows from table now no longer waits until no more flights are seen. (could take to long).
--  A fast passer-by in a small area may be catched only once and not seen as leaving.
--  If flight is still found (with ap_forcewriteCounter) in the loop after arbitrary xx times, it is written.

--- 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 = 1		-- 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.
local ap_unknown = 'onbekend'	--Translate to mark nil values as unknown.
local ap_forcewriteCounter = 10	 --Force to write and remove from table when still seen in the loop x times.


----------------------------------------------------------------------------------
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

		-- Set location coordinate and distance variables.
		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.

		--Declare for airplane item variables.
		local result_table

		local ap_date
		local ap_time
		local ap_hex
		local ap_flight
		local ap_r
		local ap_t
		local ap_desc
		local ap_gs
		local ap_track
		local ap_direction
		local ap_lat
		local ap_long
		local ap_distance
		local ap_geom_rate
		local ap_geom_rateString
		local ap_altitude

		-- 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( 'Flight ' .. testValue .. ' is found in _d.ap_table', dz.LOG_DEBUG )
						return true
					end
				end
				-- Not found in table
				dz.log( 'Flight ' .. testValue .. ' is not found in _d.ap_table', dz.LOG_DEBUG )
				return false
			else
				-- Table not found.
				dz.log( 'I did not find _d.ap_table - Re-initialize.', dz.LOG_DEBUG )
				_d.initialize('airplanes') -- Re-initialize the table
				return false
			end
		end

		local function insertinAPtable()
			table.insert( _d.ap_table, {
				ap_date = ap_date,
				ap_time = ap_time,
				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,
				ap_scriptstatus = 'firstseen',
				ap_scriptcount = 1
			});
			dz.log( 'Created new row for flight ' .. ap_hex .. ' in _d.ap_table with distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
		end

		local function updateinAPtable()
			local tc = #_d.ap_table
			dz.log( '>>>>>> _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
						-- Update/Overwrite row with information of closest position.
						_d.ap_table[i].ap_date = ap_date
						_d.ap_table[i].ap_time = ap_time
						_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
						_d.ap_table[i].ap_scriptstatus = 'updated'
						--_d.ap_table[i].ap_scriptcount = _d.ap_table[i].ap_scriptcount + 1
						dz.log( 'Updated entire row for flight ' .. ap_flight .. ' with closer distance ' .. ap_distance .. ' km.', dz.LOG_DEBUG )
					else
						-- Moves away from current location.
						_d.ap_table[i].ap_scriptstatus = 'leaving'
						--_d.ap_table[i].ap_scriptcount = _d.ap_table[i].ap_scriptcount + 1
						dz.log( 'Updated row for flight ' .. ap_flight .. ' with greater distance ' .. ap_distance .. ' km as leaving.', dz.LOG_DEBUG )
					end
				end
			end
		end

		local function processResult_table()
			--Get values from resulttable to variables.

			-- Get date and time.
			ap_date = dz.time.dateToDate( dz.time.rawDate,'yyyy-mm-dd', 'dd-mm-yyyy' ) 
			ap_time = dz.time.rawTime
			dz.log( '>>>>>> ap_date: ' .. ap_date	.. ' and ap_time: ' .. ap_time, dz.LOG_DEBUG )

			--Now get remaining stuff from result_table.
			local tc = #result_table
			for i = 1, tc do
				ap_hex = result_table[i].hex				-- Required and always supposed to be present.
				ap_flight = result_table[i].flight or ap_unknown	-- If field is nil, or will evaluate to its second operand.
				ap_r = result_table[i].r or ap_unknown			-- When nil set to 'unknown'.
				ap_t = result_table[i].t or ap_unknown			-- When nil set to 'unknown'.
				ap_desc = result_table[i].desc or ap_unknown		-- When nil set to 'unknown'.
				if result_table[i].gs == nil then
					ap_gs = ap_unknown
				else
					ap_gs = _u.round( result_table[i].gs * 1.851999278976, 0 ) -- Calculate groundspeed knots to km.
				end
				ap_track = result_table[i].track or ap_unknown		-- Direction in degree.
				if ap_track ~= ap_unknown then
					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 )
				else
					ap_direction = ap_unknown
				end
				ap_lat = result_table[i].lat	-- Required and always supposed to be present.
				ap_long = result_table[i].lon	-- Required and always supposed to be present.
				dz.log( 'Flight ' .. ap_hex .. ' has Lat en Long: ' .. ap_lat .. ' - ' .. ap_long, dz.LOG_DEBUG )
				if ap_lat ~= ap_unknown and ap_long ~= ap_unknown then
					ap_distance = _u.round( _h.calculateDistance( lat, long, ap_lat, ap_long ), 1 )
				else
					ap_distance = ap_unknown
				end
				ap_geom_rateString = ''
				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
				ap_altitude = 0
					--The fieldalt_geom is regularly empty. Therefore we check for alternative.
					if tonumber( result_table[i].alt_geom ) ~= nil then
						dz.log( 'For flight ' .. ap_hex .. ' 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 tonumber( result_table[i].alt_baro ) ~= nil then 
						dz.log( 'For flight ' .. ap_hex .. ' 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( 'For flight ' .. ap_hex .. ' altitude is unknown.', dz.LOG_INFO )
						ap_altitude = ap_unknown
					end
					-- It is within the max. height.
					dz.log( 'Flight ' .. ap_hex .. ' has a distance of ' .. ap_distance .. ' km.', dz.LOG_DEBUG )

					if airplanesExist( ap_hex ) == true then
						-- This plane exist already in our table. check and update when needed.
						updateinAPtable()
					else
						-- This plane is Not in the table. So insert this new airplane.
						insertinAPtable()
					end
				else
					dz.log( 'Skip flight ' .. ap_hex .. ' that is passing at ' .. ap_altitude .. 'm. is higher then the set ' .. ap_maxAltitudeM .. ' m.', dz.LOG_DEBUG )
				end
			end
		end


		local function WritefromAPtable()
			if next( _d.ap_table ) ~= nil then
				dz.log( 'Check to write from _d.ap_table with ' .. #_d.ap_table .. ' rows.', dz.LOG_DEBUG )
				if type( _d.ap_table ) == "table" then
					-- For each table row
					local tc = #_d.ap_table
					for i = 1, tc do
						if _d.ap_table[i].ap_scriptstatus == 'leaving' or _d.ap_table[i].ap_scriptcount > ap_forcewriteCounter then
							-- This row can be written.
							-- Get the previous stored data from device to compare.
							local ap_lastdeviceText = dz.devices(ap_text_idx).text	-- Holds string of the previous round.
							-- Prepare the 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 flight ' .. _d.ap_table[i].ap_hex .. ' 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 flight ' .. _d.ap_table[i].ap_hex .. ' also to csvFile ' .. ap_csvFile, dz.LOG_DEBUG )
									local logString = _d.ap_table[i].ap_date .. '\t' .. _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_date\tap_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.
							_d.ap_table[i].ap_scriptstatus = 'tobedeleted'
							-- Mark the rownumber to be deleted.
							dz.log( 'Marked the row for flight ' .. _d.ap_table[i].ap_hex .. ' to be deleted.', dz.LOG_DEBUG )
						else
							_d.ap_table[i].ap_scriptcount = _d.ap_table[i].ap_scriptcount + 1
							dz.log( 'Counter for forced writing flight ' .. _d.ap_table[i].ap_hex .. ' = ' .. _d.ap_table[i].ap_scriptcount .. '.', dz.LOG_DEBUG )
						end
					end
				end
			else
				dz.log( 'Nothing left in _d.ap_table. Nothing left to do.', dz.LOG_DEBUG )
			end
		end

		local function RemovefromAPtable()
			-- We can remove during iteration by iterating from back to front.
			for i=#_d.ap_table,1,-1 do
				if _d.ap_table[i].ap_scriptstatus == 'tobedeleted' then
					--Row is marked for deletion.
					local flight = _d.ap_table[i].ap_hex
					table.remove( _d.ap_table, i )
					dz.log( 'Removed row for flight ' .. flight .. ' from _d.ap_table.', dz.LOG_DEBUG )
				end
			end
		end

		-- Now start to do something ============
		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.
				result_table = triggeredItem.json.ac
				if type(result_table) == "table" then
					dz.log( 'result_table: type = ' .. type(result_table), dz.LOG_DEBUG )
					if triggeredItem.json.total > 0 then
						-- We see at least one plane. Retreive thje data from result_table.
						processResult_table()
					else
						dz.log( 'Total flights found in response = 0.', dz.LOG_DEBUG )
					end
					-- See if we have something (left) in the table to write to device.
					WritefromAPtable()
					RemovefromAPtable()
				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
}
-----------------------------------------------------------------------
-- Example of the data we get in with description of the fields -------
--{
-- "ac": [
--	{
--	"hex": "485064",	-the 24-bit ICAO identifier of the aircraft, as 6 hex digits. The identifier may start with ‘~’, this means that the address is a non-ICAO address
--	"type": "adsb_icao",	-type of underlying messages / best source of current data for this position / aircraft: ( order of which data is preferentially used )
--	"flight": "KLM66C",	-callsign, the flight name or aircraft registration as 8 chars"
--	"r": "PH-EZY",		- aircraft registration pulled from database
--	"t": "E190",		-aircraft type pulled from database
--	"desc": "EMBRAER ERJ-190-100",
--	"ownOp": "Klm Cityhopper",
--	"alt_baro": 1875,	-the aircraft barometric altitude in feet as a number OR “ground” as a string
--	"alt_geom": 2200,	-geometric (GNSS / INS) altitude in feet referenced to the WGS84 ellipsoid
--	"gs": 254.5,		-ground speed in knots
--	"ias": 248,		-indicated air speed in knots
--	"tas": 256,		-true air speed in knots
--	"mach": 0.388,		-Mach number
--	"wd": 19,		-wind direction (and wind speed) are calculated from ground track, true heading, true airspeed and ground speed
--	"ws": 6,		-Windspeed
--	"oat": 6,		-outer/static air temperature (C) *(and total air temperature (C)) are calculated from mach number and true airspeed (typically somewhat inaccurate at lower altitudes / mach numbers below 0.5, calculation is inhibited for mach < 0.395)
--	"tat": 15,		-total air temperature (C)
--	"track": 93.6,		-true track over ground in degrees (0-359)
--	"track_rate": 0.09,	-Rate of change of track, degrees/second
--	"roll": -0.18,		-Roll, degrees, negative is left roll
--	"mag_heading": 90,	-Heading, degrees clockwise from magnetic north
--	"true_heading": 92.4,	-Heading, degrees clockwise from true north (usually only transmitted on ground, in the air usually derived from the magnetic heading using magnetic model WMM2020)
--	"baro_rate": -192,	-Rate of change of barometric altitude, feet/minute
--	"geom_rate": -128,	-Rate of change of geometric (GNSS / INS) altitude, feet/minute
--	"squawk": "7631",	-Mode A code (Squawk), encoded as 4 octal digits
--	"emergency": "none",	-ADS-B emergency/priority status, a superset of the 7×00 squawks (2.2.3.2.7.8.1.1) (none, general, lifeguard, minfuel, nordo, unlawful, downed, reserved)
--	"category": "A3",	-emitter category to identify particular aircraft or vehicle classes (values A0 – D7)
--	"nav_qnh": 1018.4,	-altimeter setting (QFE or QNH/QNE), hPa
--	"nav_altitude_mcp": 2016,	-selected altitude from the Mode Control Panel / Flight Control Unit (MCP/FCU) or equivalent equipment
--	"nav_altitude_fms": 2000,	-selected altitude from the Flight Manaagement System (FMS) (2.2.3.2.7.1.3.3)
--	"nav_modes": [			-set of engaged automation modes: ‘autopilot’, ‘vnav’, ‘althold’, ‘approach’, ‘lnav’, ‘tcas’
--		"autopilot",
--		"tcas"
--	],
--	"lat": 52.166748,	-the aircraft position in decimal degrees
--	"lon": 4.638977,	-the aircraft position in decimal degrees
--	"nic": 8,		-Navigation Integrity Category (2.2.3.2.7.2.6)
--	"rc": 186,		-Radius of Containment, meters; a measure of position integrity derived from NIC & supplementary bits. (2.2.3.2.7.2.6, Table 2-69)
--	"seen_pos": 0.179,	-how long ago (in seconds before “now”) the position was last updated
--	"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,		-ADS-B Version Number 0, 1, 2 (3-7 are reserved) (2.2.3.2.7.5)
--	"nic_baro": 1,		-Navigation Integrity Category for Barometric Altitude (2.2.5.1.35)
--	"nac_p": 10,		-Navigation Accuracy for Position (2.2.5.1.35)
--	"nac_v": 2,		-Navigation Accuracy for Velocity (2.2.5.1.19)
--	"sil": 3,		-Source Integity Level (2.2.5.1.40)
--	"sil_type": "perhour",	-interpretation of SIL: unknown, perhour, persample
--	"gva": 2,		-Geometric Vertical Accuracy (2.2.3.2.7.2.8)
--	"sda": 2,		-System Design Assurance (2.2.3.2.7.2.4.6)
--	"alert": 0,		-Flight status alert bit (2.2.3.2.3.2)
--	"spi": 0,		-Flight status special position identification bit (2.2.3.2.3.2)
--	"mlat": [],		-list of fields derived from MLAT data
--	"tisb": [],		-list of fields derived from TIS-B data
--	"messages": 119867,	-total number of Mode S messages received from this aircraft
--	"seen": 0.2,		-how long ago (in seconds before “now”) a message was last received from this aircraft
--	"rssi": -6.5,		-recent average RSSI (signal power), in dbFS; this will always be negative.
--	"dst": 1.883,
--	"dir": 318.9
--	}
-- ],
-- "msg": "No error",		-Shows if there is an error, default is “No error”.
-- "now": 1716574591594,	-The time this file was generated, in seconds since Jan 1 1970 00:00:00 GMT (the Unix epoch)
-- "total": 1,			-Total aircraft returned.
-- "ctime": 1716574591594,	-The time this file was cached, in seconds since Jan 1 1970 00:00:00 GMT (the Unix epoch).
-- "ptime": 0			uThe server processing time this request required in milliseconds. 
--}
-- That's All --------------------------------------------------