1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter-feeds.git synced 2025-03-09 15:40:03 +00:00
This commit is contained in:
suyuan 2022-11-22 03:23:41 +08:00
parent f9dffa3b97
commit c84be29413
47 changed files with 8227 additions and 0 deletions

View 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

View 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

View 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

View file

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

View file

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

View file

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

View 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

View 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

View 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