Working with the Lua HTTP Example

The FAA US Airport data web service used in this example is no longer servicing http requests and will return an error when called. However, the example is being left up as it demonstrates how to call an http web service using Lua and how to process a returned XML data file in the system. The Lua script used in this example can be modified to work with other web services.

The Lua HTTP example imports external data into system:inmation via an HTTP web service. This highly flexible interface can be used easily with the aid of the scripting engine.

The steps shown in this example are:

  • calling an external HTTP web service for data

  • parsing the returned XML string

  • creating objects in the I/O model tree based on the returned data

  • updating existing objects value based on the returned data

Automatic Example Setup

You can then have the HTTP example automatically set up in your system:inmation, by running the following Lua script (which employs the esi-examples library) in the Console Display. All objects which are relevant for this example will be created in the 'Examples > LUA > HTTP Access' subtree underneath your Core object.

local exm = require('esi-examples')
exm:SETUP({{"usairportdata"}})

Calling a Web Service

The example contains a GenericItem called DataProcessing (created in the US Airport Data folder) which calls a free web service on the internet, in this case information from the United States Federal Aviation Agency.

--require the esi-lcurl-http-client library to make the client request
local https = require("esi-lcurl-http-client")

-- {code, {latitude, longitude}}
-- obviously, there would be ways to retrieve the following information dynamically
-- but for now we keep it static
local airport_codes = { ATL = {33.636719, -84.428067), LAX = (33.942536, -118.408075},
                        ORD = {41.978603, -87.904842), DFW = (32.896828, -97.037997),
                        JFK = {40.639751, -73.778925), DEN = (39.861656, -104.673178},
                        SF0 = {37.618972, -122.3748891,CLT = {35.214, -80.943139},
                        LAS = {36.080056, -115.15225), PHX = {33.434278, -112.011583},
                        IAH = {29.984433, -95.341442), MIA = (25.79325,  -80.290556},
                        SEA = {47.449, -122.309306}, EWR = (40.6925, -74.168667},
                        MCO = {28.429394, -81.308994), MSP = (44.881956, -93.221767),
                        DTW = {42.212444, -83.353389), 80S = (42.364347, -71.005181),
                        PHL = {39.871944, -75.241139), LGA = (40.777245, -73.872608))

for code, location in pairs(airport_codes) do

		-- Airport status data url and request xml response
		local url = "https://soa.smext.faa.gov/asws/api/airport/status/" .. code
		local headers = { accept = "application/xml" }

		-- request the XML stream
		local client = https.NEW({})
    	local inp = client:REQUEST('GET', url, headers)
...

In the above displayed script section the call of the web service is shown. Note that the esi-lcurl-http-client library of Lua is called using require (esi-lcurl-http-client is a helper library included in the system, see Lua API docs for more details). In the _for loop the script queries all the airports defined in the local table variable airport_codes. A new https client is opened and the request to the url is made using the REQUEST method. The request also includes the header that asks for the response to be in XML format. The result is saved in a local variable _inp_for parsing later on. We use inp.data to parse the body portion of the response.

Parsing XML

An example for parsing JSON data is available in Working with the JSON Example.

The parsing of the returned XML string happens by using some open source Lua functions that are stored in the HTTP Access folder object as a library.

Open Source XML Parser Function
Figure 1. Open Source XML Parser Function

These functions are used in the DataProcessing script:

SLAXML:parser
{
	startElement = function(name) current = name end,
	closeElement = function(name) current = nil end,
	text = function(txt)
        -- a condition will be hit only if the current element contains text
		-- when this happens, we set the value to the corresponding object
        if current == elem_city then
            syslib.setvalue(city:path(), txt, 0, syslib.currenttime())
        elseif current == elem_state then
            syslib.setvalue(state:path(), txt, 0, syslib.currenttime())
        elseif current == elem_name then
            syslib.setvalue(name:path(), txt, 0, syslib.currenttime())
        elseif current == elem_wind then
            syslib.setvalue(wind:path(), tonumber(parseWind(txt)), 0, syslib.currenttime())
        elseif current == elem_temp then
            -- parse the text for temperature and get the values in Fahrenheit and Celsius
            local f, c = parseTemp(txt)
            syslib.setvalue(tempF:path(), tonumber(f), 0, syslib.currenttime())
            syslib.setvalue(tempC:path(), tonumber(c), 0, syslib.currenttime())
    ...

When the parsing function identifies a valid element, it calls the syslib.setvalue() function to write the value to an object in the model tree.

Creating I/O Model Objects

The DataProcessing script generates the full object structure into the folder it resides in. All folders, subfolders and DataHolder items required to persist the data read from the web are created, if they do not exist.

-- if the folder for the current airport doesn't exist, we create the necessary objects
if not current_folder then
	current_folder = syslib.createobject(self:parent(), "MODEL_CLASS_GENFOLDER") -- create a generic folder
	current_folder.ObjectName = code  -- give a name to the object
	current_folder:commit() -- physically create the object

	city = syslib.createobject(current_folder, "MODEL_CLASS_HOLDERITEM")
	city.ObjectName = elem_city
	setLocation(city, location)
	city:commit()

	state = syslib.createobject(current_folder, "MODEL_CLA5S_HOLDERITEM")
	state.ObjectName = elem_state
	setLocation(state, location)
	state:commit()

	name = syslib.createobject(current_folder, "MODEL_CLASS_HOLDERITEM")
	name.ObjectName = elem_name
	setLocation(name, location)
	name:commit()

	weather_folder = syslib.createobject(current_folder, "MODEL_CLASS_GENFOLDER")
	weather_folder.ObjectName = 'Weather'
	weather_folder:commit()

	wind = syslib.createobject(weather_folder, "MODEL_CLASS_HOLDERITEM")
	wind.ObjectName = elem_wind
	wind.ObjectDescription = elem_wind   " " .. code   -- specify a desciption
	wind.OpcEngUnit = "mph"    -- specify an engineering unit
	wind.ArchiveOptions.ArchiveSelector = "ARC_PRODUCTION"
	wind.ArchiveOptions.5torageStrategy = "STORE_RAW_HISTORY"  -- historize the object
	wind.Limits.OpcRangelow = 0
	wind.Limits.OpcRangeHigh = 100
	--wind.Limits.OpcLimitLow = 0
	wind.Limits.OpcLimitHigh = 15
	setLocation(wind, location)
	wind:commit()
...

In the script above you can see how new objects can be created in the script (syslib.createobject), how their properties can be set, and how they get persisted in the system (:commit()_). For a detailed explanation about what objects are available in system:inmation, including their properties and valid values, please refer to the System Documentation.