mirror of
https://github.com/Ysurac/openmptcprouter-feeds.git
synced 2025-03-09 15:40:03 +00:00
385 lines
12 KiB
Lua
385 lines
12 KiB
Lua
-------------------------------------------------------------
|
|
-- This module is designed to extract data from NMEA messages.
|
|
-- All data is combined into a table "GnssData".
|
|
-------------------------------------------------------------
|
|
-- Copyright 2021-2022 Vladislav Kadulin <spanky@yandex.ru>
|
|
-- Licensed to the GNU General Public License v3.0
|
|
|
|
local uci = require("luci.model.uci")
|
|
local serial = require("serial")
|
|
local checksum = require("checksum")
|
|
|
|
|
|
local nmea = {}
|
|
|
|
-- Table for navigation data
|
|
local function createGnssForm()
|
|
local GnssForm = {
|
|
warning = {
|
|
app = {true, ""},
|
|
gga = {true, ""},
|
|
rmc = {true, ""},
|
|
vtg = {true, ""},
|
|
gsa = {true, ""},
|
|
gp = {true, ""},
|
|
gns = {true, ""},
|
|
server = {true, ""},
|
|
locator = {true, ""}
|
|
},
|
|
gp = { longitude = "-", latitude = "-"},
|
|
gga = { longitude = "-", latitude = "-"}
|
|
}
|
|
return GnssForm
|
|
end
|
|
|
|
--Converting coordinates from the NMEA protocol to degrees
|
|
local function nmeaCoordinatesToDouble(coord)
|
|
local deg = math.floor(coord / 100)
|
|
return deg + (coord - 100 * deg) / 60
|
|
end
|
|
|
|
--We are looking for the desired data line in the line received from the device
|
|
local function findInResp(data, begin)
|
|
local err = true
|
|
local b = string.find(data, begin)
|
|
local e = string.find(data, "\r\n", b)
|
|
|
|
if b and e then
|
|
err = false
|
|
else
|
|
b, e = nil, nil
|
|
end
|
|
return err, b, e
|
|
end
|
|
|
|
-- message parsing, checksum checking
|
|
local function getCropData(data, msg)
|
|
local err, b, e = findInResp(data, msg)
|
|
if not err then
|
|
data = string.gsub(string.sub(data, b, e), '%c', "")
|
|
if checksum.crc8(data) then
|
|
data = string.gsub(data, msg, '', 1)
|
|
data = string.gsub(data, "*%d+%w+", '', 1)
|
|
err = {false, "OK"}
|
|
else
|
|
err = {true, "Checksum error"}
|
|
data = nil
|
|
end
|
|
else
|
|
err = {true, "No data found"}
|
|
data = nil
|
|
end
|
|
return err, data
|
|
end
|
|
|
|
-- Creating a table with data before adding data to a single space
|
|
function doTable(data, keys)
|
|
local parseData = {}
|
|
|
|
while string.find(data, ',,') do
|
|
data = string.gsub(data, ',,', ",-,")
|
|
end
|
|
|
|
if string.sub(data, 1, 1) == ',' then
|
|
data = '-' .. data
|
|
end
|
|
|
|
local i = 1
|
|
for val in string.gmatch(data, "[^,]+") do
|
|
parseData[keys[i]] = val
|
|
i = i + 1
|
|
end
|
|
return parseData
|
|
end
|
|
|
|
-- The function of searching the time zone by the received coordinates
|
|
local function findTimeZone(time, date, lon)
|
|
local datetime = { year,month,day,hour,min,sec }
|
|
local timeZone = uci:get("gpoint", "modem_settings", "timezone")
|
|
|
|
-- calculate the time zone by coordinates
|
|
if timeZone == nil or timeZone == "auto" then
|
|
timeZone = math.floor((tonumber(lon) + (7.5 * (tonumber(lon) > 0 and 1.0 or -1.0))) / 15.0)
|
|
end
|
|
|
|
datetime.hour, datetime.min, datetime.sec = string.match(time, "(%d%d)(%d%d)(%d%d)")
|
|
datetime.day, datetime.month, datetime.year = string.match(date,"(%d%d)(%d%d)(%d%d)")
|
|
datetime.year = "20" .. datetime.year -- Someone change this to 21 in the 2100 year
|
|
|
|
--we request the unix time and then add the time zone
|
|
local unix = os.time(datetime)
|
|
unix = unix + ((math.floor(tonumber(timeZone) * 100)) % 100) * 36
|
|
return unix + math.floor(tonumber(timeZone)) * 3600
|
|
end
|
|
|
|
-- Add 0 for the time and date values if < 10
|
|
local function addZero(val)
|
|
return tonumber(val) > 9 and tostring(val) or '0' .. tostring(val)
|
|
end
|
|
|
|
-- If there is no data, the default values of the table are dashed
|
|
local function addDash(data)
|
|
local dashData = {}
|
|
for i=1, #data do
|
|
dashData[data[i]] = '-'
|
|
end
|
|
return dashData
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------------
|
|
-- GGA - Global Positioning System Fix Data
|
|
local function getGGA(GnssData, resp)
|
|
GnssData.gga = {
|
|
"utc", -- UTC of this position report, hh is hours, mm is minutes, ss.ss is seconds.
|
|
"latitude", -- Latitude, dd is degrees, mm.mm is minutes
|
|
"ne", -- N or S (North or South)
|
|
"longitude", -- Longitude, dd is degrees, mm.mm is minutes
|
|
"ew", -- E or W (East or West)
|
|
"qual", -- GPS Quality Indicator (non null)
|
|
"sat", -- Number of satellites in use, 00 - 12
|
|
"hdp", -- Horizontal Dilution of precision (meters)
|
|
"alt", -- Antenna Altitude above/below mean-sea-level (geoid) (in meters)
|
|
"ualt", -- Units of antenna altitude, meters
|
|
"gsep", -- Geoidal separation, the difference between the WGS-84 earth ellipsoid and mean-sea-level
|
|
"ugsep", -- Units of geoidal separation, meters
|
|
"age", -- Age of differential GPS data, time in seconds since last SC104 type 1 or 9 update, null field when DGPS is not used
|
|
"drs" -- Differential reference station ID, 0000-1023
|
|
}
|
|
|
|
local err, gga = getCropData(resp, "$GPGGA,")
|
|
if not err[1] and string.gsub(gga, ',', '') ~= '0'
|
|
and string.sub(gga, string.find(gga, ',') + 1, string.find(gga, ',') + 1) ~= ',' then
|
|
GnssData.gga = doTable(gga, GnssData.gga)
|
|
GnssData.warning.gga = {false, "OK"}
|
|
else
|
|
GnssData.gga = addDash(GnssData.gga)
|
|
GnssData.warning.gga = err[1] and err or {true, "Bad GGA data"}
|
|
end
|
|
end
|
|
|
|
-- RMC - Recommended Minimum Navigation Information
|
|
local function getRMC(GnssData, resp)
|
|
GnssData.rmc = {
|
|
"utc", -- UTC of position fix, hh is hours, mm is minutes, ss.ss is seconds.
|
|
"valid", -- Status, A = Valid, V = Warning
|
|
"latitude", -- Latitude, dd is degrees. mm.mm is minutes.
|
|
"ns", -- N or S
|
|
"longitude", -- Longitude, ddd is degrees. mm.mm is minutes.
|
|
"ew", -- E or W
|
|
"knots", -- Speed over ground, knots
|
|
"tmgdt", -- Track made good, degrees true
|
|
"date", -- Date, ddmmyy
|
|
"mv", -- Magnetic Variation, degrees
|
|
"ewm", -- E or W
|
|
"nstat", -- Nav Status (NMEA 4.1 and later) A=autonomous, D=differential, E=Estimated, ->
|
|
-- M=Manual input mode N=not valid, S=Simulator, V = Valid
|
|
"sc" --checksum
|
|
}
|
|
|
|
local err, rmc = getCropData(resp, "$GPRMC,")
|
|
if not err[1] and string.find(rmc, ",A,") then
|
|
GnssData.rmc = doTable(rmc, GnssData.rmc)
|
|
GnssData.warning.rmc = {false, "OK"}
|
|
else
|
|
GnssData.rmc = addDash(GnssData.rmc)
|
|
GnssData.warning.rmc = err[1] and err or {true, "Bad RMC data"}
|
|
end
|
|
end
|
|
|
|
-- VTG - Track made good and Ground speed
|
|
local function getVTG(GnssData, resp)
|
|
GnssData.vtg = {
|
|
"course_t", -- Course over ground, degrees True
|
|
't', -- T = True
|
|
"course_m", -- Course over ground, degrees Magnetic
|
|
'm', -- M = Magnetic
|
|
"knots", -- Speed over ground, knots
|
|
'n', -- N = Knots
|
|
"speed", -- Speed over ground, km/hr
|
|
'k', -- K = Kilometers Per Hour
|
|
"faa" -- FAA mode indicator (NMEA 2.3 and later)
|
|
}
|
|
|
|
local err, vtg = getCropData(resp, "$GPVTG,")
|
|
if not err[1] and (string.find(vtg, 'A') or string.find(vtg, 'D')) then
|
|
GnssData.vtg = doTable(vtg, GnssData.vtg)
|
|
GnssData.warning.vtg = {false, "OK"}
|
|
else
|
|
GnssData.vtg = addDash(GnssData.vtg)
|
|
GnssData.warning.vtg = err[1] and err or {true, "Bad VTG data"}
|
|
end
|
|
end
|
|
|
|
--GSA - GPS DOP and active satellites
|
|
local function getGSA(GnssData, resp)
|
|
GnssData.gsa = {
|
|
"smode", -- Selection mode: M=Manual, forced to operate in 2D or 3D, A=Automatic, 2D/3D
|
|
"mode", -- Mode (1 = no fix, 2 = 2D fix, 3 = 3D fix)
|
|
"id1", -- ID of 1st satellite used for fix
|
|
"id2", -- ID of 2nd satellite used for fix
|
|
"id3", -- ID of 3rd satellite used for fix
|
|
"id4", -- ID of 4th satellite used for fix
|
|
"id5", -- ID of 5th satellite used for fix
|
|
"id6", -- ID of 6th satellite used for fix
|
|
"id7", -- ID of 7th satellite used for fix
|
|
"id8", -- ID of 8th satellite used for fix
|
|
"id9", -- ID of 9th satellite used for fix
|
|
"id10", -- ID of 10th satellite used for fix
|
|
"id11", -- ID of 11th satellite used for fix
|
|
"id12", -- ID of 12th satellite used for fix
|
|
"pdop", -- PDOP
|
|
"hdop", -- HDOP
|
|
"vdop", -- VDOP
|
|
"sc" -- checksum
|
|
}
|
|
|
|
local err, gsa = getCropData(resp, "$GPGSA,")
|
|
if not err[1] and string.find(gsa, '2') then
|
|
GnssData.gsa = doTable(gsa, GnssData.gsa)
|
|
GnssData.warning.gsa = {false, "OK"}
|
|
else
|
|
GnssData.gsa = addDash(GnssData.gsa)
|
|
GnssData.warning.gsa = err[1] and err or {true, "Bad GSA data"}
|
|
end
|
|
end
|
|
|
|
--GNS - GLONAS Fix data
|
|
local function getGNS(GnssData, resp)
|
|
GnssData.gns = {
|
|
"utc", -- UTC of this position report, hh is hours, mm is minutes, ss.ss is seconds.
|
|
"latitude", -- Latitude, dd is degrees, mm.mm is minutes
|
|
"ne", -- N or S (North or South)
|
|
"longitude", -- Longitude, dd is degrees, mm.mm is minutes
|
|
"ew", -- E or W (East or West)
|
|
"mi", -- Mode indicator (non-null)
|
|
"sat", -- Total number of satellites in use, 00-99
|
|
"hdp", -- Horizontal Dilution of Precision, HDOP
|
|
"alt", -- Antenna Altitude above/below mean-sea-level (geoid) (in meters)
|
|
"gsep", -- Goeidal separation meters
|
|
"age", -- Age of differential data
|
|
"drs", -- Differential reference station ID
|
|
"nstat" --Navigational status (optional) S = Safe C = Caution U = Unsafe V = Not valid for navigation
|
|
}
|
|
|
|
local err, gns = getCropData(resp, "$GNGNS,")
|
|
if not err[1] and string.gsub(gns, ',', '') ~= '0' and not string.find(gns, "NNN") then
|
|
GnssData.gns = doTable(gns, GnssData.gns)
|
|
GnssData.warning.gns = {false, "OK"}
|
|
else
|
|
GnssData.gns = addDash(GnssData.gns)
|
|
GnssData.warning.gns = err[1] and err or {true, "Bad GNS data"}
|
|
end
|
|
end
|
|
|
|
-- Prepares data for the web application (Some custom data)
|
|
local function getGPoint(GnssData, resp)
|
|
GnssData.gp = {
|
|
longitude = '-',
|
|
latitude = '-',
|
|
altitude = '-',
|
|
utc = '-',
|
|
date = '-',
|
|
nsat = '-',
|
|
hdop = '-',
|
|
cog = '-',
|
|
spkm = '-',
|
|
unix = '-'
|
|
}
|
|
|
|
local err = {true, ""}
|
|
local GpsOrGlonas = false
|
|
|
|
if not GnssData.warning.gga[1] then
|
|
GpsOrGlonas = GnssData.gga
|
|
elseif not GnssData.warning.gns[1] then
|
|
GpsOrGlonas = GnssData.gns
|
|
else
|
|
err[2] = "GGA: " .. GnssData.warning.gga[2] .. ' ' .. "GNS: " .. GnssData.warning.gns[2] .. ' '
|
|
end
|
|
|
|
if GpsOrGlonas then
|
|
GnssData.gp.latitude = string.format("%0.6f", nmeaCoordinatesToDouble(GpsOrGlonas.latitude))
|
|
GnssData.gp.longitude = string.format("%0.6f", nmeaCoordinatesToDouble(GpsOrGlonas.longitude))
|
|
GnssData.gp.altitude = GpsOrGlonas.alt
|
|
GnssData.gp.nsat = GpsOrGlonas.sat
|
|
GnssData.gp.hdop = GpsOrGlonas.hdp
|
|
end
|
|
|
|
if not GnssData.warning.vtg[1] then
|
|
GnssData.gp.cog = GnssData.vtg.course_t
|
|
GnssData.gp.spkm = GnssData.vtg.speed
|
|
else
|
|
err[2] = err[2] .. "VTG: " .. GnssData.warning.vtg[2] .. ' '
|
|
end
|
|
|
|
if not GnssData.warning.rmc[1] then
|
|
local unixTime = findTimeZone(GnssData.rmc.utc, GnssData.rmc.date, nmeaCoordinatesToDouble(GnssData.rmc.longitude))
|
|
local dateTime = os.date("*t", unixTime)
|
|
|
|
GnssData.gp.utc = string.format("%s:%s", addZero(dateTime.hour), addZero(dateTime.min))
|
|
GnssData.gp.date = string.format("%s.%s.%d", addZero(dateTime.day), addZero(dateTime.month), dateTime.year)
|
|
GnssData.gp.unix = unixTime
|
|
else
|
|
err[2] = err[2] .. "RMC: " .. GnssData.warning.rmc[2]
|
|
end
|
|
|
|
if GnssData.warning.gga[1] and GnssData.warning.gns[1] and GnssData.warning.vtg[1] and GnssData.warning.rmc[1] then
|
|
err = {false, "Updating data..."}
|
|
end
|
|
|
|
if err[2] == "" then
|
|
err = {false, "OK"}
|
|
end
|
|
|
|
GnssData.warning.gp = err
|
|
end
|
|
|
|
------------------------------------------------------
|
|
-- Get a certain kind of NMEA data (data parsing)
|
|
------------------------------------------------------
|
|
|
|
function nmea.getData(line, port)
|
|
GnssData = createGnssForm()
|
|
GnssData.warning.app, resp = serial.read(port)
|
|
|
|
if line == "GP" then
|
|
getGGA(GnssData, resp)
|
|
getGNS(GnssData, resp)
|
|
getRMC(GnssData, resp)
|
|
getVTG(GnssData, resp)
|
|
getGPoint(GnssData, resp)
|
|
elseif line == "GGA" then
|
|
getGGA(GnssData, resp)
|
|
elseif line == "GNS" then
|
|
getGNS(GnssData, resp)
|
|
elseif line == "RMC" then
|
|
getRMC(GnssData, resp)
|
|
elseif line == "VTG" then
|
|
getVTG(GnssData, resp)
|
|
elseif line == "GSA" then
|
|
getGSA(GnssData, resp)
|
|
else
|
|
GnssData.warning.app = {true, "Bad argument..."}
|
|
end
|
|
return GnssData
|
|
end
|
|
|
|
------------------------------------------------------
|
|
-- parsing all NMEA data
|
|
------------------------------------------------------
|
|
|
|
function nmea.getAllData(port)
|
|
GnssData = createGnssForm()
|
|
GnssData.warning.app, resp = serial.read(port)
|
|
|
|
getGGA(GnssData, resp)
|
|
getGNS(GnssData, resp)
|
|
getRMC(GnssData, resp)
|
|
getVTG(GnssData, resp)
|
|
getGSA(GnssData, resp) -- rarely used
|
|
getGPoint(GnssData, resp)
|
|
|
|
return GnssData
|
|
end
|
|
|
|
return nmea
|