mirror of
https://github.com/Ysurac/openmptcprouter-feeds.git
synced 2025-03-09 15:40:03 +00:00
add gps
This commit is contained in:
parent
f9dffa3b97
commit
c84be29413
47 changed files with 8227 additions and 0 deletions
95
luci-app-gpoint-main/root/usr/share/gpoint/lib/checksum.lua
Normal file
95
luci-app-gpoint-main/root/usr/share/gpoint/lib/checksum.lua
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
-------------------------------------------------------------------
|
||||
-- This module is designed to receive a checksum of GNSS messages,
|
||||
-- Such as crc8 and crc16 checksum.
|
||||
-------------------------------------------------------------------
|
||||
-- Copyright 2021-2022 Vladislav Kadulin <spanky@yandex.ru>
|
||||
-- Licensed to the GNU General Public License v3.0
|
||||
|
||||
checksum = {}
|
||||
|
||||
local function decimalToHex(num)
|
||||
if num == 0 then
|
||||
return '0'
|
||||
end
|
||||
local neg = false
|
||||
if num < 0 then
|
||||
neg = true
|
||||
num = num * -1
|
||||
end
|
||||
local hexstr = "0123456789ABCDEF"
|
||||
local result = ""
|
||||
while num > 0 do
|
||||
local n = math.mod(num, 16)
|
||||
result = string.sub(hexstr, n + 1, n + 1) .. result
|
||||
num = math.floor(num / 16)
|
||||
end
|
||||
if neg then
|
||||
result = '-' .. result
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function BitXOR(a, b)
|
||||
local p, c = 1, 0
|
||||
while a > 0 and b > 0 do
|
||||
local ra, rb = a % 2, b % 2
|
||||
if ra ~= rb then c = c + p end
|
||||
a, b, p = (a - ra) / 2, (b - rb) / 2, p * 2
|
||||
end
|
||||
|
||||
if a < b then a = b end
|
||||
while a > 0 do
|
||||
local ra = a % 2
|
||||
if ra > 0 then c = c + p end
|
||||
a, p = (a - ra) / 2, p * 2
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
local function BitAND(a, b)
|
||||
local p, c = 1,0
|
||||
while a > 0 and b > 0 do
|
||||
local ra, rb = a%2, b%2
|
||||
if ra + rb > 1 then c = c + p end
|
||||
a, b, p = (a - ra) / 2, (b - rb) / 2, p*2
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
local function rshift(x, by)
|
||||
return math.floor(x / 2 ^ by)
|
||||
end
|
||||
|
||||
|
||||
-- Checksum for NMEA data (CRC8)
|
||||
function checksum.crc8(data)
|
||||
local crc8 = string.sub(data, #data - 1)
|
||||
data = string.sub(data, 2, #data - 3)
|
||||
|
||||
local b_sum = string.byte(data, 1)
|
||||
for i = 2, #data do
|
||||
b_sum = BitXOR(b_sum, string.byte(data, i))
|
||||
end
|
||||
|
||||
return decimalToHex(b_sum) == crc8 and true or false
|
||||
end
|
||||
|
||||
-- Checksum for Wialone IPS (CRC16)
|
||||
function checksum.crc16(s)
|
||||
assert(type(s) == 'string')
|
||||
local crc16 = 0x0000
|
||||
for i = 1, #s do
|
||||
local c = s:byte(i)
|
||||
crc16 = BitXOR(crc16, c)
|
||||
for j = 1, 8 do
|
||||
local k = BitAND(crc16, 1)
|
||||
crc16 = rshift(crc16, 1)
|
||||
if k ~= 0 then
|
||||
crc16 = BitXOR(crc16, 0xA001)
|
||||
end
|
||||
end
|
||||
end
|
||||
return decimalToHex(crc16)
|
||||
end
|
||||
|
||||
return checksum
|
||||
225
luci-app-gpoint-main/root/usr/share/gpoint/lib/config.lua
Normal file
225
luci-app-gpoint-main/root/usr/share/gpoint/lib/config.lua
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
-------------------------------------------------------------------
|
||||
-- Module is used for point configuration and interaction with the UI
|
||||
-------------------------------------------------------------------
|
||||
-- Copyright 2021-2022 Vladislav Kadulin <spanky@yandex.ru>
|
||||
-- Licensed to the GNU General Public License v3.0
|
||||
|
||||
local uci = require("luci.model.uci")
|
||||
local sys = require("luci.sys")
|
||||
|
||||
config = {}
|
||||
|
||||
local CFG = uci:get_all("gpoint")
|
||||
|
||||
-- Status table
|
||||
local STATUS = {
|
||||
APP = {
|
||||
MODEM_OK = {false, "OK"},
|
||||
MODEM_ERROR = {true, "Modem error. Select modem in the settings!"},
|
||||
PORT_ERROR = {true, "Modem Port error. Select port in the settings!"}
|
||||
},
|
||||
SERVER = {
|
||||
SERVICE_ON = {false, "OK"},
|
||||
SERVICE_OFF = {true, "OFF"},
|
||||
IP_ERROR = {true, "Server address error. Enter the server address!"},
|
||||
PORT_ERROR = {true, "Server port error. Set the server port!"},
|
||||
LOGIN_ERROR = {true, "Login (ID) error. Specify the device login!"}
|
||||
},
|
||||
LOCATOR = {
|
||||
SERVICE_ON = {false, "OK"},
|
||||
SERVICE_OFF = {true, "OFF"},
|
||||
API_KEY_ERROR = {true, "Yandex Locator: API key not found!"},
|
||||
WIFI_IFACE_ERROR = {true, "Yandex Locator: Wi-Fi interface not found!"}
|
||||
},
|
||||
FILTER = {
|
||||
SERVICE_ON = {false, "OK"},
|
||||
SERVICE_OFF = {true, "OFF"}
|
||||
}
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- APP (Modem Settings)
|
||||
-- 1.Checking the configuration for the presence of the modem name
|
||||
-- 2.Checking the presence of the port in the configuration and whether
|
||||
-- it is in the list of devices, if the device is unavailable, we return a warning
|
||||
-- 3. return err + modem data (name modem and NMEA port modem)
|
||||
-----------------------------------------------------------------------------------
|
||||
function config.getModemData()
|
||||
|
||||
local err = {}
|
||||
local modem = {
|
||||
name = "-",
|
||||
port = "-"
|
||||
}
|
||||
|
||||
if not CFG.modem_settings.modem and CFG.modem_settings.modem == "mnf" then
|
||||
err = STATUS.APP.MODEM_ERROR
|
||||
elseif CFG.modem_settings.port and CFG.modem_settings.port == "pnf" then
|
||||
err = STATUS.APP.PORT_ERROR
|
||||
else
|
||||
err = STATUS.APP.MODEM_OK
|
||||
end
|
||||
|
||||
if not err[1] then
|
||||
modem.name = CFG.modem_settings.modem
|
||||
modem.port = CFG.modem_settings.port
|
||||
end
|
||||
return err, modem
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- Remote Server
|
||||
-- 1.We check whether the server service is enabled or not.
|
||||
-- 2.The correctness of the completed forms is checked such as address, login, port, etc ...
|
||||
-- 3.We return the absence of an error and the server configuration data otherwise an error, nil ...
|
||||
-----------------------------------------------------------------------------------
|
||||
function config.getServerData()
|
||||
|
||||
local err = {}
|
||||
local server = {
|
||||
address = "",
|
||||
port = "",
|
||||
protocol = "",
|
||||
login = "",
|
||||
password = "",
|
||||
frequency = "",
|
||||
blackbox = {
|
||||
enable = "",
|
||||
cycle = "",
|
||||
size = 0
|
||||
}
|
||||
}
|
||||
|
||||
if not CFG.server_settings.server_enable then
|
||||
err = STATUS.SERVER.SERVICE_OFF
|
||||
elseif not CFG.server_settings.server_ip then
|
||||
err = STATUS.SERVER.IP_ERROR
|
||||
elseif not CFG.server_settings.server_port then
|
||||
err = STATUS.SERVER.PORT_ERROR
|
||||
elseif not CFG.server_settings.server_login then
|
||||
err = STATUS.SERVER.LOGIN_ERROR
|
||||
else
|
||||
err = STATUS.SERVER.SERVICE_ON
|
||||
end
|
||||
|
||||
if not err[1] then
|
||||
server.address = CFG.server_settings.server_ip
|
||||
server.port = CFG.server_settings.server_port
|
||||
server.protocol = CFG.server_settings.proto
|
||||
server.login = CFG.server_settings.server_login
|
||||
|
||||
if server.protocol == "wialon" then
|
||||
server.password = CFG.server_settings.server_password or "NA"
|
||||
server.frequency = CFG.server_settings.server_frequency or 5
|
||||
server.blackbox.enable = CFG.server_settings.blackbox_enable and true or false
|
||||
server.blackbox.cycle = CFG.server_settings.blackbox_cycle and true or false
|
||||
server.blackbox.size = CFG.server_settings.blackbox_max_size or 1000
|
||||
elseif server.protocol == "traccar" then
|
||||
server.frequency = CFG.server_settings.server_frequency or 5
|
||||
end
|
||||
|
||||
return err, server
|
||||
else
|
||||
return err, nil
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- Yandex Locator
|
||||
-- 1.Check Yandex Locator service enable/disable
|
||||
-- 2.Check Yandex API key status enable/disable
|
||||
-- 3.Check Yandex Locator interface status enable/disable
|
||||
-----------------------------------------------------------------------------------
|
||||
function config.getLoctorData()
|
||||
|
||||
local err = {}
|
||||
local locator = {
|
||||
enable = false,
|
||||
iface = "",
|
||||
key = ""
|
||||
}
|
||||
|
||||
if not CFG.service_settings.ya_enable then
|
||||
err = STATUS.LOCATOR.SERVICE_OFF
|
||||
elseif not CFG.service_settings.ya_key then
|
||||
err = STATUS.LOCATOR.API_KEY_ERROR
|
||||
elseif not CFG.service_settings.ya_wifi and CFG.service_settings.ya_wifi == "wnf" then
|
||||
err = STATUS.LOCATOR.WIFI_IFACE_ERROR
|
||||
else
|
||||
err = STATUS.LOCATOR.SERVICE_ON
|
||||
end
|
||||
|
||||
if not err[1] then
|
||||
locator.iface = CFG.service_settings.ya_wifi
|
||||
locator.key = CFG.service_settings.ya_key
|
||||
return err, locator
|
||||
else
|
||||
return err, nil
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- GpointFilter
|
||||
-- 1. Checking for the filter library
|
||||
-- 2. Check GpointFilter service enable/disable
|
||||
-- 3. Make the settings, if there are none, then we apply the default settings
|
||||
-----------------------------------------------------------------------------------
|
||||
function config.getFilterData()
|
||||
|
||||
local err = {}
|
||||
local filter = {
|
||||
enable = false,
|
||||
changes = 0,
|
||||
hash ='0',
|
||||
speed = 0
|
||||
}
|
||||
|
||||
if not CFG.service_settings.filter_enable then
|
||||
err = STATUS.FILTER.SERVICE_OFF
|
||||
else
|
||||
err = STATUS.FILTER.SERVICE_ON
|
||||
filter.enable = true
|
||||
filter.changes = tonumber(CFG.service_settings.filter_changes or 3)
|
||||
filter.hash = tostring(CFG.service_settings.filter_hash or'7')
|
||||
filter.speed = tonumber(CFG.service_settings.filter_speed or 2)
|
||||
end
|
||||
|
||||
return err, filter
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- KalmanFilter
|
||||
-- 1. Checking for the kalman filter library
|
||||
-- 2. Check KalmanFilter service enable/disable
|
||||
-- 3. Make the settings, if there are none, then we apply the default settings
|
||||
-----------------------------------------------------------------------------------
|
||||
function config.getKalmanData()
|
||||
|
||||
local err = {}
|
||||
local filter = {
|
||||
enable = false,
|
||||
noise = 0
|
||||
}
|
||||
|
||||
if not CFG.service_settings.kalman_enable then
|
||||
err = STATUS.FILTER.SERVICE_OFF
|
||||
else
|
||||
err = STATUS.FILTER.SERVICE_ON
|
||||
filter.enable = true
|
||||
filter.noise = tonumber(CFG.service_settings.kalman_noise or 1.0)
|
||||
end
|
||||
|
||||
return err, filter
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
-- Session ID
|
||||
-- 1.When initializing the ubus, we write the session id to the uci to work with the UI
|
||||
-----------------------------------------------------------------------------------
|
||||
function config.setUbusSessionId(id)
|
||||
uci:set("gpoint", "service_settings", "sessionid", id)
|
||||
uci:save("gpoint")
|
||||
uci:commit("gpoint")
|
||||
end
|
||||
|
||||
return config
|
||||
147
luci-app-gpoint-main/root/usr/share/gpoint/lib/geohash.lua
Normal file
147
luci-app-gpoint-main/root/usr/share/gpoint/lib/geohash.lua
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
-- Geohash
|
||||
-- (c) 2015 Ivan Ribeiro Rocha (ivan.ribeiro@gmail.com)
|
||||
-- (c) 2022 modified by Vladislav Kadulin (spanky@yandex.ru)
|
||||
|
||||
local bit = require("bit32")
|
||||
|
||||
geohash = {}
|
||||
|
||||
local BITS = { 16, 8, 4, 2, 1 }
|
||||
local BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz"
|
||||
|
||||
local NEIGHBORS = { right = { even = "bc01fg45238967deuvhjyznpkmstqrwx" },
|
||||
left = { even = "238967debc01fg45kmstqrwxuvhjyznp" },
|
||||
top = { even = "p0r21436x8zb9dcf5h7kjnmqesgutwvy" },
|
||||
bottom = { even = "14365h7k9dcfesgujnmqp0r2twvyx8zb" } }
|
||||
|
||||
local BORDERS = { right = { even = "bcfguvyz" },
|
||||
left = { even = "0145hjnp" },
|
||||
top = { even = "prxz" },
|
||||
bottom = { even = "028b" } }
|
||||
|
||||
NEIGHBORS.bottom.odd = NEIGHBORS.left.even
|
||||
NEIGHBORS.top.odd = NEIGHBORS.right.even
|
||||
NEIGHBORS.left.odd = NEIGHBORS.bottom.even
|
||||
NEIGHBORS.right.odd = NEIGHBORS.top.even
|
||||
|
||||
BORDERS.bottom.odd = BORDERS.left.even
|
||||
BORDERS.top.odd = BORDERS.right.even
|
||||
BORDERS.left.odd = BORDERS.bottom.even
|
||||
BORDERS.right.odd = BORDERS.top.even
|
||||
|
||||
function geohash.decode(hash)
|
||||
local flip = true;
|
||||
local coords = { latitude = { -90.0, 90.0 },
|
||||
longitude = { -180.0, 180.0 } }
|
||||
|
||||
for i = 1, #hash do
|
||||
local c = hash:sub(i, i)
|
||||
local cd = BASE32:find(c) - 1
|
||||
for j = 1, 5 do
|
||||
mask = BITS[j]
|
||||
local tab = (flip and coords.longitude) or coords.latitude
|
||||
local idx = (bit.band(cd, mask) > 0) and 1 or 2
|
||||
tab[idx] = (tab[1] + tab[2]) / 2
|
||||
flip = not flip
|
||||
end
|
||||
end
|
||||
|
||||
for k, _ in pairs(coords) do
|
||||
coords[k][3] = (coords[k][1] + coords[k][2]) / 2
|
||||
end
|
||||
|
||||
return { lat = coords.latitude, lon = coords.longitude }
|
||||
|
||||
end
|
||||
|
||||
function geohash.encode(latitude, longitude, precision)
|
||||
local lat = { -90.0, 90.0 }
|
||||
local lon = { -180.0, 180.0 }
|
||||
local b, ch, flip = 0, 0, true
|
||||
local res = "";
|
||||
|
||||
latitude = tonumber(latitude)
|
||||
longitude = tonumber(longitude)
|
||||
precision = tonumber(precision)
|
||||
local precision = precision or 12
|
||||
|
||||
while #res < precision do
|
||||
local tab = flip and lon or lat
|
||||
local grd = flip and longitude or latitude
|
||||
|
||||
mid = (tab[1] + tab[2]) / 2
|
||||
|
||||
if grd > mid then
|
||||
ch = bit.bor(ch, BITS[b + 1])
|
||||
tab[1] = mid
|
||||
else
|
||||
tab[2] = mid
|
||||
end
|
||||
|
||||
flip = not flip;
|
||||
|
||||
if b < 4 then
|
||||
b = b + 1
|
||||
else
|
||||
res = res..BASE32:sub(ch + 1, ch + 1);
|
||||
b, ch = 0, 0
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function geohash.calculate_distance(lat1, lon1, lat2, lon2)
|
||||
local R = 6371000
|
||||
local r1, r2 = math.rad(lat1), math.rad(lat2)
|
||||
local dlat, dlon = math.rad((lat2-lat1)), math.rad((lon2-lon1))
|
||||
local a = math.sin(dlat/2) * math.sin(dlat/2) +
|
||||
math.cos(r1) * math.cos(r2) *
|
||||
math.sin(dlon/2) * math.sin(dlon/2)
|
||||
local c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
|
||||
return R * c
|
||||
end
|
||||
|
||||
function geohash.distance(hash1, hash2)
|
||||
local t1, t2 = decode(hash1), decode(hash2)
|
||||
return calculate_distance(coord(t1).lat, coord(t1).lon,
|
||||
coord(t2).lat, coord(t2).lon)
|
||||
end
|
||||
|
||||
function geohash.neighbor(hash, dir)
|
||||
hash = hash:lower()
|
||||
local len = #hash
|
||||
local last = hash:sub(len, len);
|
||||
local flip = ((math.mod(len,2) == 0) and 'even') or 'odd'
|
||||
local base = hash:sub(1, len - 1)
|
||||
if BORDERS[dir][flip]:find(last) then
|
||||
base = neighbor(base, dir)
|
||||
end
|
||||
local n = NEIGHBORS[dir][flip]:find(last)
|
||||
return base..BASE32:sub(n, n)
|
||||
end
|
||||
|
||||
function geohash.neighbors(hash)
|
||||
local neighbors = { top = neighbor(hash, 'top'),
|
||||
bottom = neighbor(hash, 'bottom'),
|
||||
right = neighbor(hash, 'right'),
|
||||
left = neighbor(hash, 'left') }
|
||||
neighbors.topleft = neighbor(neighbors.left, 'top');
|
||||
neighbors.topright = neighbor(neighbors.right, 'top');
|
||||
neighbors.bottomleft = neighbor(neighbors.left, 'bottom');
|
||||
neighbors.bottomright = neighbor(neighbors.right, 'bottom');
|
||||
return neighbors
|
||||
end
|
||||
|
||||
function geohash.coord(t)
|
||||
if type(t) == 'table' then
|
||||
return { lat = t.lat[3], lon = t.lon[3] }
|
||||
end
|
||||
return coord(decode(t))
|
||||
end
|
||||
|
||||
function geohash.coord_str(t)
|
||||
local t = coord(t)
|
||||
return string.format("lat: %s and lon: %s", tostring(t.lat), tostring(t.lon))
|
||||
end
|
||||
|
||||
return geohash
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
local kalman = require("kalman_lib")
|
||||
local matrix = require("matrix_lib")
|
||||
|
||||
gps_lib = {}
|
||||
|
||||
local PI = 3.14159265
|
||||
local EARTH_RADIUS_IN_MILES = 3963.1676
|
||||
|
||||
function gps_lib.set_seconds_per_timestep(kalman_filter, seconds_per_timestep)
|
||||
local unit_scaler = 0.001
|
||||
kalman_filter.state_transition[1][3] = unit_scaler * seconds_per_timestep
|
||||
kalman_filter.state_transition[2][4] = unit_scaler * seconds_per_timestep
|
||||
return kalman_filter
|
||||
end
|
||||
|
||||
function gps_lib.create_velocity2d(noise)
|
||||
local kalman_filter = kalman.create(4, 2)
|
||||
local v2p = 0.001
|
||||
|
||||
kalman_filter.state_transition = matrix.set_identity(kalman_filter.state_transition)
|
||||
kalman_filter = gps_lib.set_seconds_per_timestep(kalman_filter, 1.0)
|
||||
kalman_filter.observation_model = matrix.set(kalman_filter.observation_model,
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0)
|
||||
|
||||
local pos = 0.000001
|
||||
kalman_filter.process_noise_covariance = matrix.set(kalman_filter.process_noise_covariance,
|
||||
pos, 0.0, 0.0, 0.0,
|
||||
0.0, pos, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0)
|
||||
|
||||
kalman_filter.observation_noise_covariance = matrix.set(kalman_filter.observation_noise_covariance,
|
||||
pos * noise, 0.0,
|
||||
0.0, pos * noise)
|
||||
|
||||
kalman_filter.state_estimate = matrix.set(kalman_filter.state_estimate, 0.0, 0.0, 0.0, 0.0)
|
||||
kalman_filter.estimate_covariance = matrix.set_identity(kalman_filter.estimate_covariance)
|
||||
local trillion = 1000.0 * 1000.0 * 1000.0 * 1000.0
|
||||
kalman_filter.estimate_covariance = matrix.scale(kalman_filter.estimate_covariance, trillion)
|
||||
return kalman_filter
|
||||
end
|
||||
|
||||
function gps_lib.update_velocity2d(kalman_filter, lat, lon, seconds_since_last_timestep)
|
||||
kalman_filter = gps_lib.set_seconds_per_timestep(kalman_filter, seconds_since_last_timestep)
|
||||
kalman_filter.observation = matrix.set(kalman_filter.observation, lat * 1000.0, lon * 1000.0)
|
||||
kalman_filter = kalman.update(kalman_filter)
|
||||
return kalman_filter
|
||||
end
|
||||
|
||||
function gps_lib.get_lat_lon(kalman_filter)
|
||||
return string.format("%0.6f", kalman_filter.state_estimate[1][1] / 1000.0),
|
||||
string.format("%0.6f", kalman_filter.state_estimate[2][1] / 1000.0)
|
||||
end
|
||||
|
||||
function gps_lib.get_velocity(kalman_filter)
|
||||
return kalman_filter.state_estimate[3][1] / (1000.0 * 1000.0),
|
||||
kalman_filter.state_estimate[4][1] / (1000.0 * 1000.0)
|
||||
end
|
||||
|
||||
function gps_lib.get_bearing(kalman_filter)
|
||||
local lat, lon = gps_lib.get_lat_lon(kalman_filter)
|
||||
local delta_lat, delta_lon = gps_lib.get_velocity(kalman_filter)
|
||||
|
||||
local to_radians = PI / 180.0
|
||||
lat = lat * to_radians
|
||||
lon = lon * to_radians
|
||||
delta_lat = delta_lat * to_radians
|
||||
delta_lon = delta_lon * to_radians
|
||||
|
||||
local lat1 = lat - delta_lat
|
||||
local y = math.sin(delta_lon) * math.cos(lat)
|
||||
local x = math.cos(lat1) * math.sin(lat) - math.sin(lat1) * math.cos(lat) * math.cos(delta_lon)
|
||||
local bearing = math.atan2(y, x)
|
||||
|
||||
bearing = bearing / to_radians
|
||||
while bearing >= 360 do
|
||||
bearing = bearing - 360
|
||||
end
|
||||
while bearing < 0 do
|
||||
bearing = bearing + 360
|
||||
end
|
||||
return bearing
|
||||
end
|
||||
|
||||
function gps_lib.calculate_mph(lat, lon, delta_lat, delta_lon)
|
||||
local to_radians = PI / 180
|
||||
lat = lat * to_radians
|
||||
lon = lon * to_radians
|
||||
delta_lat = delta_lat * to_radians
|
||||
delta_lon = delta_lon * to_radians
|
||||
|
||||
local lat1 = lat - delta_lat
|
||||
local sin_half_dlat = math.sin(delta_lat / 2)
|
||||
local sin_half_dlon = math.sin(delta_lon / 2)
|
||||
|
||||
local a = sin_half_dlat * sin_half_dlat + math.cos(lat1) * math.cos(lat) * sin_half_dlon * sin_half_dlon
|
||||
local radians_per_second = 2 * math.atan2(1000 * math.sqrt(a), 1000 * math.sqrt(1.0 - a))
|
||||
|
||||
local miles_per_second = radians_per_second * EARTH_RADIUS_IN_MILES
|
||||
local miles_per_hour = miles_per_second * 60 * 60
|
||||
return miles_per_hour
|
||||
end
|
||||
|
||||
function gps_lib.get_mph(kalman_filter)
|
||||
local lat, lon = gps_lib.get_lat_lon(kalman_filter)
|
||||
local delta_lat, delta_lon = gps_lib.get_velocity(kalman_filter)
|
||||
return gps_lib.calculate_mph(lat, lon, delta_lat, delta_lon)
|
||||
end
|
||||
|
||||
return gps_lib
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
local matrix = require("matrix_lib")
|
||||
|
||||
kalman_lib = {}
|
||||
|
||||
function kalman_lib.create(state, observation)
|
||||
local kalman = {
|
||||
timestep = 0, -- K
|
||||
-- These parameters define the size of the matrices.
|
||||
state_dimension = state,
|
||||
observation_dimension = observation,
|
||||
|
||||
-- This group of matrices must be specified by the user.
|
||||
state_transition = matrix.create(state, state), -- F_k
|
||||
observation_model = matrix.create(observation, state), -- H_k
|
||||
process_noise_covariance = matrix.create(state, state), -- Q_k
|
||||
observation_noise_covariance = matrix.create(observation, observation), -- R_k
|
||||
|
||||
-- The observation is modified by the user before every time step.
|
||||
observation = matrix.create(observation, 1), -- z_k
|
||||
|
||||
-- This group of matrices are updated every time step by the filter.
|
||||
predicted_state = matrix.create(state, 1), -- x-hat_k|k-1
|
||||
predicted_estimate_covariance = matrix.create(state, state), -- P_k|k-1
|
||||
innovation = matrix.create(observation, 1), -- y-tilde_k
|
||||
innovation_covariance = matrix.create(observation, observation), -- S_k
|
||||
inverse_innovation_covariance = matrix.create(observation, observation), -- S_k^-1
|
||||
optimal_gain = matrix.create(state, observation), -- K_k
|
||||
state_estimate = matrix.create(state, 1), -- x-hat_k|k
|
||||
estimate_covariance = matrix.create(state, state), -- P_k|k
|
||||
|
||||
-- This group is used for meaningless intermediate calculations.
|
||||
vertical_scratch = matrix.create(state, observation),
|
||||
mall_square_scratch = matrix.create(observation, observation),
|
||||
big_square_scratch = matrix.create(state, state)
|
||||
}
|
||||
return kalman
|
||||
end
|
||||
|
||||
function kalman_lib.predict(kalman)
|
||||
kalman.timestep = kalman.timestep + 1
|
||||
-- Predict the state
|
||||
kalman.predicted_state = matrix.multiply(kalman.state_transition, kalman.state_estimate, kalman.predicted_state)
|
||||
-- Predict the state estimate covariance
|
||||
kalman.big_square_scratch = matrix.multiply(kalman.state_transition, kalman.estimate_covariance, kalman.big_square_scratch)
|
||||
kalman.predicted_estimate_covariance = matrix.multiply_by_transpose(kalman.big_square_scratch, kalman.state_transition, kalman.predicted_estimate_covariance)
|
||||
kalman.predicted_estimate_covariance = matrix.add(kalman.predicted_estimate_covariance, kalman.process_noise_covariance, kalman.predicted_estimate_covariance)
|
||||
return kalman
|
||||
end
|
||||
|
||||
function kalman_lib.estimate(kalman)
|
||||
-- Calculate innovation
|
||||
kalman.innovation = matrix.multiply(kalman.observation_model, kalman.predicted_state, kalman.innovation)
|
||||
kalman.innovation = matrix.subtract(kalman.observation, kalman.innovation, kalman.innovation)
|
||||
-- Calculate innovation covariance
|
||||
kalman.vertical_scratch = matrix.multiply_by_transpose(kalman.predicted_estimate_covariance, kalman.observation_model, kalman.vertical_scratch)
|
||||
kalman.innovation_covariance = matrix.multiply(kalman.observation_model, kalman.vertical_scratch, kalman.innovation_covariance)
|
||||
kalman.innovation_covariance = matrix.add(kalman.innovation_covariance, kalman.observation_noise_covariance, kalman.innovation_covariance)
|
||||
-- Invert the innovation covariance.
|
||||
-- Note: this destroys the innovation covariance.
|
||||
-- TODO: handle inversion failure intelligently.
|
||||
matrix.destructive_invert(kalman.innovation_covariance, kalman.inverse_innovation_covariance)
|
||||
-- Calculate the optimal Kalman gain.
|
||||
-- Note we still have a useful partial product in vertical scratch
|
||||
-- from the innovation covariance.
|
||||
kalman.optimal_gain = matrix.multiply(kalman.vertical_scratch, kalman.inverse_innovation_covariance, kalman.optimal_gain)
|
||||
-- Estimate the state
|
||||
kalman.state_estimate = matrix.multiply(kalman.optimal_gain, kalman.innovation, kalman.state_estimate)
|
||||
kalman.state_estimate = matrix.add(kalman.state_estimate, kalman.predicted_state, kalman.state_estimate)
|
||||
-- Estimate the state covariance
|
||||
kalman.big_square_scratch = matrix.multiply(kalman.optimal_gain, kalman.observation_model, kalman.big_square_scratch)
|
||||
kalman.big_square_scratch = matrix.subtract_from_identity(kalman.big_square_scratch)
|
||||
kalman.estimate_covariance = matrix.multiply(kalman.big_square_scratch, kalman.predicted_estimate_covariance, kalman.estimate_covariance)
|
||||
return kalman
|
||||
end
|
||||
|
||||
function kalman_lib.update(kalman)
|
||||
kalman = kalman_lib.predict(kalman)
|
||||
kalman = kalman_lib.estimate(kalman)
|
||||
return kalman
|
||||
end
|
||||
|
||||
return kalman_lib
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
matrix_lib = {}
|
||||
|
||||
function matrix_lib.create(rows, cols)
|
||||
local matrix = {}
|
||||
for i = 1,rows do
|
||||
matrix[i] = {}
|
||||
for j = 1,cols do
|
||||
matrix[i][j] = 0.0
|
||||
end
|
||||
end
|
||||
return matrix
|
||||
end
|
||||
|
||||
function matrix_lib.print(matrix)
|
||||
for i = 1, #matrix do
|
||||
for j = 1, #matrix[i] do
|
||||
io.write(matrix[i][j] .. " ")
|
||||
end
|
||||
io.write('\n')
|
||||
end
|
||||
end
|
||||
|
||||
function matrix_lib.set(matrix, ...)
|
||||
local k = 1
|
||||
for i = 1, #matrix do
|
||||
for j = 1, #matrix[i] do
|
||||
if arg[k] ~= nil then
|
||||
matrix[i][j] = arg[k]
|
||||
end
|
||||
k = k + 1
|
||||
end
|
||||
end
|
||||
return matrix
|
||||
end
|
||||
|
||||
function matrix_lib.set_identity(matrix)
|
||||
for i = 1, #matrix do
|
||||
for j = 1, #matrix[i] do
|
||||
matrix[i][j] = i == j and 1.0 or 0.0
|
||||
end
|
||||
end
|
||||
return matrix
|
||||
end
|
||||
|
||||
function matrix_lib.copy(matrix)
|
||||
local copy = {}
|
||||
for i = 1, #matrix do
|
||||
copy[i] = {}
|
||||
for j = 1, #matrix[i] do
|
||||
copy[i][j] = matrix[i][j]
|
||||
end
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
function matrix_lib.add(matrix_a, matrix_b, matrix_c)
|
||||
for i = 1, #matrix_a do
|
||||
for j = 1, #matrix_a[i] do
|
||||
matrix_c[i][j] = matrix_a[i][j] + matrix_b[i][j]
|
||||
end
|
||||
end
|
||||
return matrix_c
|
||||
end
|
||||
|
||||
function matrix_lib.subtract(matrix_a, matrix_b, matrix_c)
|
||||
for i = 1, #matrix_a do
|
||||
for j = 1, #matrix_a[i] do
|
||||
matrix_c[i][j] = matrix_a[i][j] - matrix_b[i][j]
|
||||
end
|
||||
end
|
||||
return matrix_c
|
||||
end
|
||||
|
||||
function matrix_lib.subtract_from_identity(matrix)
|
||||
for i = 1, #matrix do
|
||||
for j = 1, #matrix[i] do
|
||||
matrix[i][j] = i == j and (1.0 - matrix[i][j]) or (0.0 - matrix[i][j])
|
||||
end
|
||||
end
|
||||
return matrix
|
||||
end
|
||||
|
||||
function matrix_lib.multiply(matrix_a, matrix_b, matrix_c)
|
||||
for i = 1, #matrix_c do
|
||||
for j = 1, #matrix_c[i] do
|
||||
matrix_c[i][j] = 0.0
|
||||
for k = 1, #matrix_a[i] do
|
||||
matrix_c[i][j] = matrix_c[i][j] + (matrix_a[i][k] * matrix_b[k][j])
|
||||
end
|
||||
end
|
||||
end
|
||||
return matrix_c
|
||||
end
|
||||
|
||||
function matrix_lib.multiply_by_transpose(matrix_a, matrix_b, matrix_c)
|
||||
for i = 1, #matrix_c do
|
||||
for j = 1, #matrix_c[i] do
|
||||
matrix_c[i][j] = 0.0
|
||||
for k = 1, #matrix_a[1] do
|
||||
matrix_c[i][j] = matrix_c[i][j] + (matrix_a[i][k] * matrix_b[j][k])
|
||||
end
|
||||
end
|
||||
end
|
||||
return matrix_c
|
||||
end
|
||||
|
||||
function matrix_lib.transpose(matrix_input, matrix_output)
|
||||
for i = 1, #matrix_input do
|
||||
for j = 1, #matrix_input[i] do
|
||||
matrix_output[j][i] = matrix_input[i][j]
|
||||
end
|
||||
end
|
||||
return matrix_output
|
||||
end
|
||||
|
||||
function matrix_lib.equal(matrix_a, matrix_b, tolerance)
|
||||
for i = 1, #matrix_a do
|
||||
for j = 1, #matrix_a[i] do
|
||||
if math.abs(matrix_a[i][j] - matrix_b[i][j]) > tolerance then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function matrix_lib.scale(matrix, scalar)
|
||||
for i = 1, #matrix do
|
||||
for j = 1, #matrix[i] do
|
||||
matrix[i][j] = matrix[i][j] * scalar
|
||||
end
|
||||
end
|
||||
return matrix
|
||||
end
|
||||
|
||||
function matrix_lib.swap_rows(matrix, r1, r2)
|
||||
local tmp = matrix[r1]
|
||||
matrix[r1] = matrix[r2]
|
||||
matrix[r2] = tmp
|
||||
return matrix
|
||||
end
|
||||
|
||||
function matrix_lib.scale_row(matrix, r, scalar)
|
||||
for i = 1, #matrix do
|
||||
matrix[r][i] = matrix[r][i] * scalar
|
||||
end
|
||||
return matrix
|
||||
end
|
||||
|
||||
function matrix_lib.shear_row(matrix, r1, r2, scalar)
|
||||
for i = 1, #matrix do
|
||||
matrix[r1][i] = matrix[r1][i] + (scalar * matrix[r2][i])
|
||||
end
|
||||
return matrix
|
||||
end
|
||||
|
||||
function matrix_lib.destructive_invert(matrix_input, matrix_output)
|
||||
matrix_output = matrix_lib.set_identity(matrix_output)
|
||||
for i = 1, #matrix_input do
|
||||
if matrix_input[i][i] == 0.0 then
|
||||
local j
|
||||
for j = i + 1, #matrix_input do
|
||||
if matrix_input[r][i] ~= 0.0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if j == #matrix_input then
|
||||
return
|
||||
end
|
||||
|
||||
matrix_input = matrix_lib.swap_rows(matrix_input, i, j)
|
||||
matrix_output = matrix_lib.swap_rows(matrix_output, i, j)
|
||||
end
|
||||
|
||||
local scalar = 1.0 / matrix_input[i][i]
|
||||
matrix_input = matrix_lib.scale_row(matrix_input, i, scalar)
|
||||
matrix_output = matrix_lib.scale_row(matrix_output, i, scalar)
|
||||
|
||||
for r = 1, #matrix_input do
|
||||
if i ~= r then
|
||||
local shear_needed = -matrix_input[r][i]
|
||||
matrix_input = matrix_lib.shear_row(matrix_input, r, i, shear_needed)
|
||||
matrix_output = matrix_lib.shear_row(matrix_output, r, i, shear_needed)
|
||||
end
|
||||
end
|
||||
end
|
||||
return matrix_input, matrix_output
|
||||
end
|
||||
|
||||
return matrix_lib
|
||||
78
luci-app-gpoint-main/root/usr/share/gpoint/lib/locator.lua
Normal file
78
luci-app-gpoint-main/root/usr/share/gpoint/lib/locator.lua
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
-------------------------------------------------------------------
|
||||
-- Module is designed to work with the Yandex Locator API
|
||||
-- (WiFi is required!)
|
||||
-------------------------------------------------------------------
|
||||
-- Copyright 2021-2022 Vladislav Kadulin <spanky@yandex.ru>
|
||||
-- Licensed to the GNU General Public License v3.0
|
||||
|
||||
local json = require("luci.jsonc")
|
||||
local sys = require("luci.sys")
|
||||
local iwinfo = require("iwinfo")
|
||||
|
||||
locator = {}
|
||||
|
||||
local function configJSON(jsonData, iface, key)
|
||||
jsonData.common.api_key = key
|
||||
local inter = iwinfo.type(iface)
|
||||
local scanlist = iwinfo[inter].scanlist(iface)
|
||||
for _, v in pairs(scanlist) do
|
||||
v.bssid = string.gsub(v.bssid, ':', '')
|
||||
table.insert(jsonData.wifi_networks, {["mac"] = v.bssid, ["signal_strength"] = v.signal})
|
||||
end
|
||||
end
|
||||
|
||||
local function request(curl, jsonData)
|
||||
curl = curl .. json.stringify(jsonData) .. '\''
|
||||
local res = sys.exec(curl)
|
||||
if res == "" then
|
||||
res = "{\"error\": {\"message\":\"No internet connection\"}}"
|
||||
end
|
||||
return json.parse(res)
|
||||
end
|
||||
|
||||
-- Converter from degrees to NMEA data.
|
||||
function locator.degreesToNmea(coord)
|
||||
local degrees = math.floor(coord)
|
||||
coord = math.abs(coord) - degrees
|
||||
local sign = coord < 0 and "-" or ""
|
||||
return sign .. string.format("%02i%02.5f", degrees, coord * 60.00)
|
||||
end
|
||||
|
||||
-- Getting data coordinates via Yandex API
|
||||
function locator.getLocation(iface_name, api_key)
|
||||
local curl = "curl -X POST 'http://api.lbs.yandex.net/geolocation' -d 'json="
|
||||
local jsonData = {
|
||||
wifi_networks = {},
|
||||
common = {
|
||||
version = "1.0",
|
||||
api_key = ""
|
||||
}
|
||||
}
|
||||
|
||||
configJSON(jsonData, iface_name, api_key)
|
||||
local location = request(curl, jsonData)
|
||||
local err = {false, "OK"}
|
||||
local latitude = ""
|
||||
local longitude = ""
|
||||
local altitude = ""
|
||||
|
||||
if location.error then
|
||||
err = {true, location.error.message}
|
||||
end
|
||||
|
||||
if location.position then
|
||||
if tonumber(location.position.precision) >= 100000 then
|
||||
err = {true, "Bad precision"}
|
||||
else
|
||||
latitude = string.format("%0.8f", location.position.latitude)
|
||||
longitude = string.format("%0.8f", location.position.longitude)
|
||||
if latitude == "" or longitude == "" then
|
||||
err = {true, "Bad data..."}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return err, latitude, longitude
|
||||
end
|
||||
|
||||
return locator
|
||||
385
luci-app-gpoint-main/root/usr/share/gpoint/lib/nmea.lua
Normal file
385
luci-app-gpoint-main/root/usr/share/gpoint/lib/nmea.lua
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
-------------------------------------------------------------
|
||||
-- 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
|
||||
73
luci-app-gpoint-main/root/usr/share/gpoint/lib/serial.lua
Normal file
73
luci-app-gpoint-main/root/usr/share/gpoint/lib/serial.lua
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
-------------------------------------------------------------------
|
||||
-- Wrapper for working with a modem via serial port
|
||||
-------------------------------------------------------------------
|
||||
-- Copyright 2021-2022 Vladislav Kadulin <spanky@yandex.ru>
|
||||
-- Licensed to the GNU General Public License v3.0
|
||||
|
||||
local rs232 = require("luars232")
|
||||
|
||||
local serial = {}
|
||||
|
||||
local function configSerial(port)
|
||||
assert(port:set_baud_rate(rs232.RS232_BAUD_115200) == rs232.RS232_ERR_NOERROR)
|
||||
assert(port:set_parity(rs232.RS232_PARITY_NONE) == rs232.RS232_ERR_NOERROR)
|
||||
assert(port:set_data_bits(rs232.RS232_DATA_8) == rs232.RS232_ERR_NOERROR)
|
||||
assert(port:set_stop_bits(rs232.RS232_STOP_1) == rs232.RS232_ERR_NOERROR)
|
||||
assert(port:set_flow_control(rs232.RS232_FLOW_OFF) == rs232.RS232_ERR_NOERROR)
|
||||
end
|
||||
|
||||
-- write data from modem (AT PORT)
|
||||
function serial.write(serial_port, command)
|
||||
local err, port = rs232.open(serial_port)
|
||||
if err ~= rs232.RS232_ERR_NOERROR then
|
||||
err = {true, "Error opening AT port"}
|
||||
assert(port:close() == rs232.RS232_ERR_NOERROR)
|
||||
return err
|
||||
end
|
||||
configSerial(port)
|
||||
|
||||
local err, len_written = port:write(command .. "\r\n")
|
||||
if err ~= rs232.RS232_ERR_NOERROR then
|
||||
err = {true, "Error writing AT port"}
|
||||
assert(port:close() == rs232.RS232_ERR_NOERROR)
|
||||
return err
|
||||
end
|
||||
|
||||
err = {false, "OK"}
|
||||
assert(port:close() == rs232.RS232_ERR_NOERROR)
|
||||
return err
|
||||
end
|
||||
|
||||
-- read data from modem (GNSS PORT)
|
||||
function serial.read(serial_port)
|
||||
local err, port = rs232.open(serial_port)
|
||||
if err ~= rs232.RS232_ERR_NOERROR then
|
||||
err = {true, "Error opening GNSS port"}
|
||||
assert(port:close() == rs232.RS232_ERR_NOERROR)
|
||||
return err, ''
|
||||
end
|
||||
configSerial(port)
|
||||
|
||||
local READ_LEN = 1024 -- Read byte form GNSS port
|
||||
local TIMEOUT = 1000 -- Timeout reading in miliseconds
|
||||
|
||||
local serialData, err, read_data = {}, "", ""
|
||||
while READ_LEN > 0 do
|
||||
err, read_data = port:read(1, TIMEOUT)
|
||||
if err ~= rs232.RS232_ERR_NOERROR then
|
||||
err = {true, "Error reading GNSS port. Updating data or searching for satellites..."}
|
||||
assert(port:close() == rs232.RS232_ERR_NOERROR)
|
||||
return err, ""
|
||||
end
|
||||
if read_data ~= nil then
|
||||
table.insert(serialData, read_data)
|
||||
READ_LEN = READ_LEN - 1
|
||||
end
|
||||
end
|
||||
assert(port:close() == rs232.RS232_ERR_NOERROR)
|
||||
|
||||
err = {false, "OK"}
|
||||
return err, table.concat(serialData)
|
||||
end
|
||||
|
||||
return serial
|
||||
Loading…
Add table
Add a link
Reference in a new issue