chore: micodus_server rework #1
1783
Cargo.lock
generated
@ -9,6 +9,7 @@ bitvec = { version = "1.0" }
|
||||
chrono = { version = "0.4" }
|
||||
encoding_rs = { version = "0.8" }
|
||||
lazy_static = { version = "1.5" }
|
||||
libsql = { version = "0.9", features = ["replication"] }
|
||||
rand = { version = "0.9" }
|
||||
rusqlite = { version = "0.34", features = ["bundled"] }
|
||||
tokio = { version = "1.44", features = ["full", "sync"] }
|
||||
rusqlite = { version = "0.37", features = ["bundled"] }
|
||||
tokio = { version = "1.47", features = ["full", "sync"] }
|
||||
|
10
html/const.lua
Normal file
@ -0,0 +1,10 @@
|
||||
basepath = "/home/paul/git/micodus_server"
|
||||
dbfile = string.format("%s/data/tracker.db",basepath)
|
||||
query = [[
|
||||
SELECT time,latitude,longitude,height,speed,direction,serial
|
||||
FROM log
|
||||
ORDER BY id DESC, serial DESC
|
||||
LIMIT 1;
|
||||
]]
|
||||
|
||||
return {["basepath"]=basepath, ["dbfile"]=dbfile, ["query"]=query}
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
*/
|
||||
|
||||
let map;
|
||||
let point;
|
||||
|
||||
const arrows = ["↑", "↗", "→", "↘", "↓", "↙", "←", "↖"];
|
||||
|
||||
const socket = new WebSocket("wss://geo.paulbsd.com/ws");
|
||||
|
||||
function create_location(coords,text) {
|
||||
point = L.marker(coords, {
|
||||
color: 'red',
|
||||
fillColor: '#f03',
|
||||
fillOpacity: 0.5,
|
||||
radius: 50
|
||||
}).addTo(map).bindPopup(text);
|
||||
point.getPopup().autoPan=false;
|
||||
point.openPopup();
|
||||
}
|
||||
|
||||
function create_map(coords) {
|
||||
map = L.map('map', {
|
||||
center: coords,
|
||||
zoom: 15
|
||||
});
|
||||
L.tileLayer(`https://tile.openstreetmap.org/{z}/{x}/{y}.png`, {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> / <a href="https://www.paulbsd.com">PaulBSD</a>'
|
||||
}).addTo(map);
|
||||
}
|
||||
|
||||
function update(data) {
|
||||
const coords = [data.latitude,data.longitude];
|
||||
const speed = data.speed/10;
|
||||
let section = parseInt(data.direction/45 + 0.5);
|
||||
section = section % 8;
|
||||
console.log(arrows[section]);
|
||||
const text = `time: ${data.time}<br/>latitude: ${data.latitude}<br/>longitude: ${data.longitude}<br/>height: ${data.height}<br/>speed: ${speed}<br/>direction: ${arrows[section]} ${data.direction}`;
|
||||
if (!map) {
|
||||
create_map(coords);
|
||||
} else {
|
||||
map.setView(coords);
|
||||
map.flyTo(coords);
|
||||
}
|
||||
if (!point) {
|
||||
create_location(coords, text);
|
||||
} else {
|
||||
point.setLatLng(coords);
|
||||
point.setPopupContent(text);
|
||||
}
|
||||
}
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
update(data);
|
||||
});
|
||||
|
||||
socket.addEventListener("open", (event) => {
|
||||
socket.send("ping");
|
||||
});
|
BIN
html/favicon.ico
Normal file
After Width: | Height: | Size: 66 KiB |
@ -1,15 +1,30 @@
|
||||
<html>
|
||||
<title>PaulBSD geo</title>
|
||||
<link rel="icon" href="https://paulbsd.com/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="leaflet/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
||||
<link rel="stylesheet" href="style.css" crossorigin=""/>
|
||||
<script src="leaflet/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="">
|
||||
</script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black"/>
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#0313fc"/>
|
||||
<body>
|
||||
<div id="map" style="height: 100%;"></div>
|
||||
<script src="engine.js"></script>
|
||||
</body>
|
||||
<html lang="en">
|
||||
<title>Trackme</title>
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="static/style.css" crossorigin="">
|
||||
<link rel="stylesheet" href="static/leaflet/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="">
|
||||
<link rel="stylesheet" href="static/bootstrap/bootstrap.min.css" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="">
|
||||
<script src="static/leaflet/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#0313fc">
|
||||
<body>
|
||||
<div id="dash">
|
||||
<div id="map">
|
||||
</div>
|
||||
<div class="buttonscontainer">
|
||||
<button id="togglefollowbtn" class="btn btn-primary btn-lg btn-block">Unfollow tracker</button>
|
||||
<button id="upgradebtn" class="btn btn-secondary btn-lg btn-block">Upgrade</button>
|
||||
<button id="pingtrackerbtn" class="btn btn-secondary btn-lg btn-block">Ping tracker</button>
|
||||
</div>
|
||||
<div class="buttonscontainer">
|
||||
<button id="optionsbtn" class="btn btn-secondary btn-lg btn-block">Options</button>
|
||||
<button id="socketstatusbtn" class="btn btn-secondary btn-lg btn-block">WS Status</button>
|
||||
<button id="reloadwsbtn" class="btn btn-secondary btn-lg btn-block">Reload WS</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/bootstrap/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
||||
<script defer src="static/engine.js"></script>
|
||||
<script src="static/service.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
43
html/old/engine.js
Normal file
@ -0,0 +1,43 @@
|
||||
let map;
|
||||
let circle;
|
||||
|
||||
function update() {
|
||||
get_data().then(c=> {
|
||||
const d = [c.latitude,c.longitude];
|
||||
if (!map) {
|
||||
map = L.map('map', {
|
||||
center: d,
|
||||
zoom: 13
|
||||
});
|
||||
L.tileLayer(`https://tile.openstreetmap.org/{z}/{x}/{y}.png`, {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(map);
|
||||
}
|
||||
if (circle) {
|
||||
circle.remove();
|
||||
circle=null;
|
||||
}
|
||||
if (circle == null) {
|
||||
circle = L.circle(d, {
|
||||
color: 'red',
|
||||
fillColor: '#f03',
|
||||
fillOpacity: 0.5,
|
||||
radius: 50
|
||||
}).addTo(map).bindPopup('I\'m here.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function get_data() {
|
||||
const res = fetch("lastloc.json").then((a)=> {
|
||||
const b = a.json().then((j) => {
|
||||
return j;
|
||||
})
|
||||
return b;
|
||||
})
|
||||
return res;
|
||||
}
|
||||
|
||||
update();
|
||||
setInterval(update,10000);
|
1
html/old/lastloc.json
Normal file
@ -0,0 +1 @@
|
||||
{"latitude": 49.173069, "longitude": -0.342916}
|
21
html/old/lastloc.lua
Normal file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
local const = require('const')
|
||||
local json = require("json")
|
||||
local sqlite = require("lsqlite3")
|
||||
--local output = string.format("%s/html/lastloc.json",basepath)
|
||||
|
||||
function main()
|
||||
local db = sqlite.open(const.dbfile,sqlite.OPEN_READONLY)
|
||||
|
||||
local res, vm = db:nrows(const.query)
|
||||
for row in res, vm do
|
||||
local locstr = string.format("{\"latitude\": %s, \"longitude\": %s}", row.latitude, row.longitude)
|
||||
--f = io.open(output, "w")
|
||||
--f:write(locstr)
|
||||
ngx.say(locstr)
|
||||
end
|
||||
db:close()
|
||||
end
|
||||
|
||||
main()
|
6
html/old/test.html
Normal file
@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<script>
|
||||
window.navigator.vibrate(1000);
|
||||
document.write("<h1>"+window.navigator.userAgent+"</h1>");
|
||||
</script>
|
||||
</html>
|
@ -1,8 +1,8 @@
|
||||
// Create WebSocket connection.
|
||||
const socket = new WebSocket("wss://geo.paulbsd.com/ws");
|
||||
const socket = new WebSocket("wss://trackme.ovh/ws");
|
||||
|
||||
// Connection opened
|
||||
socket.addEventListener("open", (event) => {
|
||||
socket.addEventListener("open", (_event) => {
|
||||
socket.send("Hello Server!");
|
||||
});
|
||||
|
90
html/old/ws2.lua
Normal file
@ -0,0 +1,90 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
package.path = package.path..";/home/paul/git/micodus_server/html/?.lua"
|
||||
|
||||
local const = require("const")
|
||||
local json = require("json")
|
||||
local server = require("nginx.websocket.server")
|
||||
local sqlite = require("lsqlite3")
|
||||
|
||||
--ngx.shared.geo:set("last_time","")
|
||||
local db = sqlite.open(const.dbfile, sqlite.OPEN_READONLY)
|
||||
|
||||
function getdata()
|
||||
local res, vm = db:nrows(const.query)
|
||||
local data = {}
|
||||
for row in res, vm do
|
||||
data = {
|
||||
["time"] = row.time,
|
||||
["latitude"] = row.latitude,
|
||||
["longitude"] = row.longitude,
|
||||
["height"] = row.height,
|
||||
["speed"] = row.speed,
|
||||
["direction"] = row.direction,
|
||||
["serial"] = row.serial,
|
||||
}
|
||||
end
|
||||
-- db:close()
|
||||
return data
|
||||
end
|
||||
|
||||
function handle_ping(wb)
|
||||
local data, typ, err = wb:recv_frame()
|
||||
if data then
|
||||
ngx.log(ndx.ERR,data)
|
||||
end
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
function send_data(wb, last_time)
|
||||
while true do
|
||||
ngx.log(ndx.ERR,"test")
|
||||
local data = getdata()
|
||||
ngx.log(ndx.ERR,data)
|
||||
if data.time ~= last_time then
|
||||
local locstr = json.encode(data)
|
||||
local bytes, err = wb:send_text(locstr)
|
||||
ngx.log(ndx.ERR,bytes)
|
||||
if not bytes then
|
||||
ngx.log(ngx.ERR, "failed to send text: ", err)
|
||||
return ngx.exit(444)
|
||||
end
|
||||
last_time = data.time
|
||||
end
|
||||
ngx.sleep(0.5)
|
||||
coroutine.yield()
|
||||
end
|
||||
end
|
||||
|
||||
function geows()
|
||||
local locstr = nil
|
||||
local wb, err = server:new {
|
||||
timeout = 5000,
|
||||
max_payload_len = 65535
|
||||
}
|
||||
if not wb then
|
||||
ngx.log(ngx.ERR, "failed to new websocket: ", err)
|
||||
return ngx.exit(444)
|
||||
end
|
||||
local last_time = nil
|
||||
|
||||
local h = coroutine.create(handle_ping, wb)
|
||||
local s = coroutine.create(send_data, wb, last_time)
|
||||
|
||||
local i=0
|
||||
while true do
|
||||
ngx.log(ngx.ERR,i)
|
||||
coroutine.resume(h,wb)
|
||||
coroutine.resume(s,wb,last_time)
|
||||
if not wb then
|
||||
ngx.log(ngx.ERR, "failed to new websocket: ", err)
|
||||
return ngx.exit(444)
|
||||
end
|
||||
i=i+1
|
||||
ngx.sleep(0.5)
|
||||
end
|
||||
|
||||
wb:send_close()
|
||||
end
|
||||
|
||||
geows()
|
54
html/old/ws_test.lua
Normal file
@ -0,0 +1,54 @@
|
||||
local server = require "nginx.websocket.server"
|
||||
|
||||
function geows()
|
||||
local wb, err = server:new {
|
||||
timeout = 5000,
|
||||
max_payload_len = 65535
|
||||
}
|
||||
if not wb then
|
||||
ngx.log(ngx.ERR, "failed to new websocket: ", err)
|
||||
return ngx.exit(444)
|
||||
end
|
||||
|
||||
while true do
|
||||
local bytes, err = wb:send_text(string.format("%s haha!",data))
|
||||
if not bytes then
|
||||
ngx.log(ngx.ERR, "failed to send text: ", err)
|
||||
return ngx.exit(444)
|
||||
end
|
||||
ngx.sleep(1)
|
||||
end
|
||||
|
||||
--[[while true do
|
||||
local data, typ, err = wb:recv_frame()
|
||||
if wb.fatal then
|
||||
ngx.log(ngx.ERR, "failed to receive frame: ", err)
|
||||
return ngx.exit(444)
|
||||
end
|
||||
if not data then
|
||||
local bytes, err = wb:send_ping()
|
||||
if not bytes then
|
||||
ngx.log(ngx.ERR, "failed to send ping: ", err)
|
||||
return ngx.exit(444)
|
||||
end
|
||||
elseif typ == "close" then break
|
||||
elseif typ == "ping" then
|
||||
local bytes, err = wb:send_pong()
|
||||
if not bytes then
|
||||
ngx.log(ngx.ERR, "failed to send pong: ", err)
|
||||
return ngx.exit(444)
|
||||
end
|
||||
elseif typ == "pong" then
|
||||
ngx.log(ngx.INFO, "client ponged")
|
||||
elseif typ == "text" then
|
||||
local bytes, err = wb:send_text(string.format("%s haha!",data))
|
||||
if not bytes then
|
||||
ngx.log(ngx.ERR, "failed to send text: ", err)
|
||||
return ngx.exit(444)
|
||||
end
|
||||
end
|
||||
end--]]
|
||||
wb:send_close()
|
||||
end
|
||||
|
||||
geows()
|
7
html/static/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
6
html/static/bootstrap/bootstrap.min.css
vendored
Normal file
193
html/static/engine.js
Normal file
@ -0,0 +1,193 @@
|
||||
// engine.js
|
||||
|
||||
let socket = new WebSocket("wss://trackme.ovh/ws");
|
||||
const arrows = ["↑", "↗", "→", "↘", "↓", "↙", "←", "↖"];
|
||||
let togglefollowbtn;
|
||||
|
||||
class Position {
|
||||
constructor(type, iconUrl, iconRetinaUrl) {
|
||||
this.type = type;
|
||||
this.iconUrl = iconUrl;
|
||||
this.iconRetinaUrl = iconRetinaUrl;
|
||||
this.text = "";
|
||||
}
|
||||
|
||||
setData(obj) {
|
||||
this.data = obj;
|
||||
}
|
||||
|
||||
setDefaultData() {
|
||||
this.data = {latitude:49, longitude:0};
|
||||
}
|
||||
|
||||
static getMiddle(t, u) {
|
||||
const mid = {coords: [49, 0]};
|
||||
if (t.coords && u.coords) {
|
||||
for (const i in mid.coords) {
|
||||
mid.coords[i] = Math.min(t.coords[i], u.coords[i]) + (Math.abs(t.coords[i] - u.coords[i])/2);
|
||||
}
|
||||
} else mid.coords = t.coords;
|
||||
return mid.coords;
|
||||
}
|
||||
|
||||
createLocation() {
|
||||
const icon = new L.Icon.Default;
|
||||
icon.options.iconUrl = this.iconUrl;
|
||||
icon.options.iconRetinaUrl = this.iconRetinaUrl;
|
||||
|
||||
try {
|
||||
this.point = L.marker(this.coords, {
|
||||
color: "red",
|
||||
fillColor: "#f03",
|
||||
fillOpacity: 0.5,
|
||||
radius: 50,
|
||||
icon: icon
|
||||
}).addTo(map).bindPopup(this.text);
|
||||
|
||||
this.point.getPopup().autoPan = false;
|
||||
//this.point.openPopup();
|
||||
} catch (err) {
|
||||
console.log(`unable to create point (${err})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let map;
|
||||
let follow = true;
|
||||
const tracker = new Position("tracker", "marker-icon.png", "marker-icon-2x.png");
|
||||
const user = new Position("user", "marker-icon-alt.png", "marker-icon-2x-alt.png");
|
||||
|
||||
|
||||
function userGPSsuccess(pos) {
|
||||
user.data = {
|
||||
latitude: pos.coords.latitude,
|
||||
longitude: pos.coords.longitude,
|
||||
height: pos.coords.altitude,
|
||||
speed: pos.coords.speed,
|
||||
direction: pos.coords.heading,
|
||||
};
|
||||
user.coords = [pos.coords.latitude, pos.coords.longitude];
|
||||
}
|
||||
|
||||
function userGPSerror(err) {
|
||||
console.log(err);
|
||||
user.setDefaultData();
|
||||
}
|
||||
|
||||
function getPosition() {
|
||||
if (navigator.geolocation) {
|
||||
return new Promise((resolve, _reject) => {
|
||||
navigator.geolocation.getCurrentPosition(userGPSsuccess, userGPSerror);
|
||||
resolve("Position fetched");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createMap(coords) {
|
||||
map = L.map("map", {
|
||||
center: coords,
|
||||
zoom: 10
|
||||
});
|
||||
L.tileLayer(`https://tile.openstreetmap.org/{z}/{x}/{y}.png`, {
|
||||
maxZoom: 19,
|
||||
attribution: `© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> / <a href="https://www.paulbsd.com">PaulBSD</a>`
|
||||
}).addTo(map);
|
||||
map.zoomControl.setPosition('bottomright');
|
||||
map.addEventListener("zoomlevelschange", (_event) => {
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (tracker.data) {
|
||||
tracker.coords = [tracker.data.latitude, tracker.data.longitude];
|
||||
tracker.data.speed = tracker.data.speed/10;
|
||||
tracker.section = parseInt(tracker.data.direction/45 + 0.5) % 8;
|
||||
tracker.text = `<b>Tracker</b><br/>time: ${tracker.data.time}<br/>latitude: ${tracker.data.latitude}<br/>longitude: ${tracker.data.longitude}<br/>height: ${tracker.data.height}<br/>speed: ${tracker.data.speed}<br/>direction: ${arrows[tracker.section]} ${tracker.data.direction}°<br/>serial: ${tracker.data.serial}`;
|
||||
}
|
||||
|
||||
//user.text = `<b>You</b><br/>latitude: ${user.data.latitude}<br/>longitude: ${user.data.longitude}<br/>height: ${user.data.height}<br/>speed: ${user.data.speed}<br/>direction: ${arrows[user.section]} ${user.data.direction}°`;
|
||||
if (user.data) {
|
||||
user.text = `<b>You</b><br/>latitude: ${user.data.latitude}<br/>longitude: ${user.data.longitude}`;
|
||||
}
|
||||
|
||||
if (tracker.data) {
|
||||
const center = [tracker.data.latitude, tracker.data.longitude];
|
||||
if (!map) {
|
||||
createMap(center);
|
||||
}
|
||||
}
|
||||
|
||||
if (map && follow) {
|
||||
const coords = Position.getMiddle(tracker, user);
|
||||
try {
|
||||
map.setView(coords);
|
||||
map.flyTo(coords);
|
||||
map.fitBounds([tracker.coords, user.coords]);
|
||||
} catch (e) {
|
||||
console.log(e, coords);
|
||||
}
|
||||
}
|
||||
|
||||
if (!tracker.point && tracker.data) {
|
||||
tracker.createLocation();
|
||||
} else {
|
||||
if (tracker.coords) {
|
||||
tracker.point.setLatLng(tracker.coords);
|
||||
tracker.point.setPopupContent(tracker.text);
|
||||
}
|
||||
}
|
||||
|
||||
if (!user.point && user.data) {
|
||||
user.createLocation();
|
||||
} else {
|
||||
if (user.coords) {
|
||||
user.point.setLatLng(user.coords);
|
||||
user.point.setPopupContent(user.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ping() {
|
||||
try {
|
||||
if (socket.readyState > 1) {
|
||||
throw "closed socket";
|
||||
}
|
||||
socket.send("ping")
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
socket = new WebSocket("wss://trackme.ovh/ws");
|
||||
}
|
||||
}
|
||||
|
||||
function followButton() {
|
||||
if (follow) togglefollowbtn.innerHTML = "Unfollow tracker";
|
||||
else togglefollowbtn.innerHTML = "Follow tracker";
|
||||
}
|
||||
|
||||
function main() {
|
||||
togglefollowbtn = document.querySelector("#togglefollowbtn");
|
||||
follow = JSON.parse(localStorage.getItem("follow"));
|
||||
followButton();
|
||||
|
||||
togglefollowbtn.addEventListener("click", function() {
|
||||
follow = !follow;
|
||||
localStorage.setItem("follow", follow);
|
||||
followButton();
|
||||
});
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
tracker.data = JSON.parse(event.data);
|
||||
update();
|
||||
});
|
||||
|
||||
socket.addEventListener("open", (_event) => {
|
||||
ping();
|
||||
});
|
||||
|
||||
setInterval(getPosition, 1000);
|
||||
setInterval(ping, 1000);
|
||||
setInterval(update, 1000);
|
||||
}
|
||||
|
||||
main();
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 696 B |
BIN
html/static/leaflet/images/marker-icon-2x-alt.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
html/static/leaflet/images/marker-icon-alt.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 618 B After Width: | Height: | Size: 618 B |
32
html/static/service.js
Normal file
@ -0,0 +1,32 @@
|
||||
/* service worker */
|
||||
const CACHE_NAME = "trackme-v1";
|
||||
const urlsToCache = [
|
||||
"/",
|
||||
"/static/leaflet/leaflet.js",
|
||||
"/static/engine.js",
|
||||
"/static/style.css",
|
||||
];
|
||||
{
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("/static/service.js")
|
||||
.then((_reg) => {
|
||||
console.log("Registration successful");
|
||||
}).catch((error) => {
|
||||
console.log(`Registration error: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
self.addEventListener("install", function(event) {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then(function(cache) {
|
||||
console.log("Opened cache");
|
||||
return cache.addAll(urlsToCache);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", function(event) {
|
||||
console.log("fetch");
|
||||
});
|
||||
}
|
35
html/static/style.css
Normal file
@ -0,0 +1,35 @@
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.leaflet-popup-content {
|
||||
/*font-size: 13pt;*/
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#dash {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttonscontainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttonscontainer > button {
|
||||
margin: 3px;
|
||||
width: 100%;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.leaflet-popup-content {
|
||||
/*font-size: 13pt;*/
|
||||
}
|
35
html/ws.lua
@ -1,36 +1,29 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
package.path = package.path..";/home/paul/git/micodus_server/html/?.lua"
|
||||
|
||||
local const = require("const")
|
||||
local json = require("json")
|
||||
local server = require("nginx.websocket.server")
|
||||
local sqlite = require("lsqlite3")
|
||||
|
||||
local basepath = "/home/paul/git/micodus_server"
|
||||
local dbfile = string.format("%s/data/tracker.db",basepath)
|
||||
local query = [[
|
||||
SELECT time,latitude,longitude,height,speed,direction,serial
|
||||
FROM log
|
||||
ORDER BY time DESC
|
||||
LIMIT 1;
|
||||
]]
|
||||
|
||||
--ngx.shared.geo:set("last_time","")
|
||||
local db = sqlite.open(const.dbfile, sqlite.OPEN_READONLY)
|
||||
|
||||
function getdata()
|
||||
local db = sqlite.open(dbfile,sqlite3.OPEN_READONLY)
|
||||
local res, vm = db:nrows(query)
|
||||
local data
|
||||
local res, vm = db:nrows(const.query)
|
||||
local data = {}
|
||||
for row in res, vm do
|
||||
data = {
|
||||
["time"]=row.time,
|
||||
["latitude"]=row.latitude,
|
||||
["longitude"]=row.longitude,
|
||||
["height"]=row.height,
|
||||
["speed"]=row.speed,
|
||||
["direction"]=row.direction,
|
||||
["serial"]=row.serial,
|
||||
["time"] = row.time,
|
||||
["latitude"] = row.latitude,
|
||||
["longitude"] = row.longitude,
|
||||
["height"] = row.height,
|
||||
["speed"] = row.speed,
|
||||
["direction"] = row.direction,
|
||||
["serial"] = row.serial,
|
||||
}
|
||||
end
|
||||
db:close()
|
||||
return data
|
||||
end
|
||||
|
||||
@ -57,7 +50,7 @@ function geows()
|
||||
end
|
||||
last_time = data.time
|
||||
end
|
||||
ngx.sleep(1)
|
||||
ngx.sleep(0.5)
|
||||
end
|
||||
wb:send_close()
|
||||
end
|
||||
|
103
src/db.rs
@ -1,103 +0,0 @@
|
||||
use rusqlite::{types::*, *};
|
||||
|
||||
const DBPATH: &'static str = "data/tracker.db";
|
||||
const STATEMENTS: &'static [&str] = &[
|
||||
"CREATE TABLE log (
|
||||
id integer primary key autoincrement,
|
||||
time text,
|
||||
serial integer,
|
||||
latitude float,
|
||||
longitude float,
|
||||
speed integer,
|
||||
height integer,
|
||||
direction integer,
|
||||
is_satellite bool);",
|
||||
"CREATE INDEX idx_time ON log (time);",
|
||||
"CREATE INDEX idx_serial ON log (serial);",
|
||||
];
|
||||
const QUERY_INSERT: &'static str = "
|
||||
INSERT INTO log (
|
||||
time,
|
||||
latitude,
|
||||
longitude,
|
||||
speed,
|
||||
height,
|
||||
direction,
|
||||
serial,
|
||||
is_satellite
|
||||
)
|
||||
VALUES (
|
||||
:time,
|
||||
:latitude,
|
||||
:longitude,
|
||||
:speed,
|
||||
:height,
|
||||
:direction,
|
||||
:serial,
|
||||
:is_satellite
|
||||
)";
|
||||
|
||||
pub fn connectdb() -> Result<Connection> {
|
||||
let conn = Connection::open(DBPATH)?;
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
pub fn initdb(conn: &Connection) -> Result<()> {
|
||||
create_tables(&conn)?;
|
||||
set_pragmas(&conn)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_tables(conn: &Connection) -> Result<()> {
|
||||
for s in STATEMENTS {
|
||||
match conn.execute(s, ()) {
|
||||
Ok(_) => {}
|
||||
Err(err) => println!("update failed: {}", err),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_pragmas(conn: &Connection) -> Result<()> {
|
||||
conn.pragma_update(Some(DatabaseName::Main), "journal_mode", "WAL")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn prepare_insert(conn: &Connection) -> Statement {
|
||||
conn.prepare(QUERY_INSERT).unwrap()
|
||||
}
|
||||
|
||||
pub fn insert(conn: &Connection, dblog: &DbLog) -> Result<()> {
|
||||
let mut stmt = prepare_insert(&conn);
|
||||
match stmt.execute(params![
|
||||
dblog.time,
|
||||
dblog.latitude,
|
||||
dblog.longitude,
|
||||
dblog.speed,
|
||||
dblog.height,
|
||||
dblog.direction,
|
||||
dblog.serial,
|
||||
dblog.is_satellite,
|
||||
]) {
|
||||
Ok(i) => println!("{} rows were inserted", i),
|
||||
Err(err) => println!("insert failed: {}", err),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct DbLog {
|
||||
pub time: String,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub speed: u16,
|
||||
pub height: u16,
|
||||
pub direction: u16,
|
||||
pub serial: u16,
|
||||
pub is_satellite: bool,
|
||||
}
|
||||
|
||||
impl ToSql for DbLog {
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(self.time.to_sql().unwrap())
|
||||
}
|
||||
}
|
112
src/db/libsql_engine.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use libsql::*;
|
||||
|
||||
impl LibSQLEngine {
|
||||
pub const TABLE_STATEMENTS: &'static [&str] = &[
|
||||
"CREATE TABLE log (
|
||||
id integer primary key autoincrement,
|
||||
time text,
|
||||
serial integer,
|
||||
latitude float,
|
||||
longitude float,
|
||||
speed integer,
|
||||
height integer,
|
||||
direction integer,
|
||||
is_satellite bool
|
||||
);",
|
||||
"CREATE INDEX idx_time ON log (time);",
|
||||
"CREATE INDEX idx_serial ON log (serial);",
|
||||
];
|
||||
|
||||
pub const QUERY_INSERT: &'static str = "
|
||||
INSERT INTO log (
|
||||
time,
|
||||
latitude,
|
||||
longitude,
|
||||
speed,
|
||||
height,
|
||||
direction,
|
||||
serial,
|
||||
is_satellite
|
||||
)
|
||||
VALUES (
|
||||
:time,
|
||||
:latitude,
|
||||
:longitude,
|
||||
:speed,
|
||||
:height,
|
||||
:direction,
|
||||
:serial,
|
||||
:is_satellite
|
||||
);";
|
||||
|
||||
pub const DBPATH: &'static str = "data/tracker.db";
|
||||
|
||||
async fn initdb(&self) {
|
||||
self.create_tables().await;
|
||||
}
|
||||
|
||||
async fn create_tables(&self) -> Result<()> {
|
||||
for stmt in Self::TABLE_STATEMENTS {
|
||||
match self.conn.as_ref().unwrap().execute(stmt, ()).await {
|
||||
Ok(_) => {}
|
||||
Err(err) => println!("update failed: {}", err),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prepare_insert(&self) -> Statement {
|
||||
self.conn
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.prepare(Self::QUERY_INSERT)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Engine for LibSQLEngine {
|
||||
#[tokio::main]
|
||||
async fn connect(&mut self) {
|
||||
println!("{}", self.path.clone().unwrap_or("".to_string()));
|
||||
let path = match self.path.clone() {
|
||||
Some(o) => o,
|
||||
None => Self::DBPATH.to_string(),
|
||||
};
|
||||
let b = Builder::new_remote_replica(path, "".into(), "".into())
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
self.conn = Some(b.connect().unwrap());
|
||||
self.initdb().await
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn insert(&mut self, dblog: &DbLog) {
|
||||
let mut stmt = self.prepare_insert().await;
|
||||
match stmt
|
||||
.execute(params![
|
||||
dblog.time.clone(),
|
||||
dblog.latitude,
|
||||
dblog.longitude,
|
||||
dblog.speed,
|
||||
dblog.height,
|
||||
dblog.direction,
|
||||
dblog.serial,
|
||||
dblog.is_satellite,
|
||||
])
|
||||
.await
|
||||
{
|
||||
Ok(i) => println!("{} rows were inserted", i),
|
||||
Err(err) => println!("insert failed: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct LibSQLEngine {
|
||||
pub conn: Option<Connection>,
|
||||
pub path: Option<String>,
|
||||
pub url: Option<String>,
|
||||
pub token: Option<String>,
|
||||
}
|
77
src/db/mod.rs
Normal file
@ -0,0 +1,77 @@
|
||||
//pub mod libsql_engine;
|
||||
pub mod sqlite_engine;
|
||||
|
||||
pub enum SQLEngine {
|
||||
SQLite(sqlite_engine::SQLiteEngine),
|
||||
//LibSQL(libsql_engine::LibSQLEngine),
|
||||
}
|
||||
|
||||
pub trait Engine {
|
||||
fn connect(&mut self);
|
||||
fn init(&mut self);
|
||||
fn insert(&mut self, dblog: &DbLog);
|
||||
}
|
||||
|
||||
impl Engine for SQLEngine {
|
||||
fn connect(&mut self) {
|
||||
match self {
|
||||
Self::SQLite(engine) => engine.connect(),
|
||||
//Self::LibSQL(engine) => engine.connect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
match self {
|
||||
Self::SQLite(engine) => engine.init(),
|
||||
//Self::LibSQL(engine) => engine.connect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&mut self, dblog: &DbLog) {
|
||||
match self {
|
||||
Self::SQLite(engine) => engine.insert(&dblog),
|
||||
//Self::LibSQL(engine) => engine.connect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlite_engine::SQLiteEngine> for SQLEngine {
|
||||
fn from(store: sqlite_engine::SQLiteEngine) -> Self {
|
||||
Self::SQLite(store)
|
||||
}
|
||||
}
|
||||
|
||||
/*impl From<libsql_engine::LibSQLEngine> for SQLEngine {
|
||||
fn from(store: libsql_engine::LibSQLEngine) -> Self {
|
||||
Self::LibSQL(store)
|
||||
}
|
||||
}*/
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct DbLog {
|
||||
pub time: String,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub speed: u16,
|
||||
pub height: u16,
|
||||
pub direction: u16,
|
||||
pub serial: u16,
|
||||
pub is_satellite: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DbLog {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} {} {} {} {} {} {} {} ",
|
||||
self.time,
|
||||
self.latitude,
|
||||
self.longitude,
|
||||
self.speed,
|
||||
self.height,
|
||||
self.direction,
|
||||
self.serial,
|
||||
self.is_satellite,
|
||||
)
|
||||
}
|
||||
}
|
115
src/db/sqlite_engine.rs
Normal file
@ -0,0 +1,115 @@
|
||||
use super::DbLog;
|
||||
|
||||
use rusqlite::{types::*, *};
|
||||
|
||||
impl SQLiteEngine {
|
||||
pub const DBPATH: &'static str = "data/tracker.db";
|
||||
|
||||
pub const TABLE_STATEMENTS: &'static [&str] = &[
|
||||
"CREATE TABLE IF NOT EXISTS log (
|
||||
id integer primary key autoincrement,
|
||||
time text,
|
||||
serial integer,
|
||||
latitude float,
|
||||
longitude float,
|
||||
speed integer,
|
||||
height integer,
|
||||
direction integer,
|
||||
is_satellite bool
|
||||
);",
|
||||
"CREATE INDEX IF NOT EXISTS idx_time ON log (time);",
|
||||
"CREATE INDEX IF NOT EXISTS idx_serial ON log (serial);",
|
||||
];
|
||||
|
||||
pub const QUERY_INSERT: &'static str = "
|
||||
INSERT INTO log (
|
||||
time,
|
||||
latitude,
|
||||
longitude,
|
||||
speed,
|
||||
height,
|
||||
direction,
|
||||
serial,
|
||||
is_satellite
|
||||
)
|
||||
VALUES (
|
||||
:time,
|
||||
:latitude,
|
||||
:longitude,
|
||||
:speed,
|
||||
:height,
|
||||
:direction,
|
||||
:serial,
|
||||
:is_satellite
|
||||
);";
|
||||
|
||||
fn create_tables(&mut self) -> Result<()> {
|
||||
for s in Self::TABLE_STATEMENTS {
|
||||
match self.conn.as_ref().unwrap().execute(s, ()) {
|
||||
Ok(_) => {}
|
||||
Err(err) => println!("update failed: {}", err),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_pragmas(&self) -> Result<()> {
|
||||
self.conn
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.pragma_update(None, "journal_mode", "WAL")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_insert(&self) -> Statement {
|
||||
self.conn
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.prepare(Self::QUERY_INSERT)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for DbLog {
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||
Ok(self.time.to_sql().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Engine for SQLiteEngine {
|
||||
fn connect(&mut self) {
|
||||
let path = match self.path.clone() {
|
||||
Some(o) => o,
|
||||
None => Self::DBPATH.to_string(),
|
||||
};
|
||||
self.conn = Some(Connection::open(path).unwrap());
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.create_tables().unwrap();
|
||||
self.set_pragmas().unwrap();
|
||||
}
|
||||
|
||||
fn insert(&mut self, dblog: &DbLog) {
|
||||
let mut stmt = self.prepare_insert();
|
||||
match stmt.execute(params![
|
||||
dblog.time,
|
||||
dblog.latitude,
|
||||
dblog.longitude,
|
||||
dblog.speed,
|
||||
dblog.height,
|
||||
dblog.direction,
|
||||
dblog.serial,
|
||||
dblog.is_satellite,
|
||||
]) {
|
||||
Ok(_) => {}
|
||||
Err(err) => println!("insert failed: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SQLiteEngine {
|
||||
pub conn: Option<Connection>,
|
||||
pub path: Option<String>,
|
||||
}
|
0
src/lib.rs
Normal file
112
src/main.rs
@ -1,23 +1,25 @@
|
||||
mod db;
|
||||
mod parser;
|
||||
mod serve;
|
||||
|
||||
use crate::db::*;
|
||||
use crate::parser::*;
|
||||
use std::process::exit;
|
||||
|
||||
use std::io;
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
const ADDR: &'static str = "0.0.0.0";
|
||||
const PORT: u64 = 7701;
|
||||
const BUFSIZE: usize = 1024;
|
||||
use db::sqlite_engine::SQLiteEngine;
|
||||
use db::{SQLEngine::SQLite, *};
|
||||
use serve::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
//test();
|
||||
let conn = connectdb().unwrap();
|
||||
initdb(&conn).unwrap();
|
||||
apiserver().await.unwrap();
|
||||
println!(
|
||||
"starting micodus_server version {}",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
let mut s = SQLite(SQLiteEngine::default());
|
||||
s.connect();
|
||||
s.init();
|
||||
|
||||
let receiver = control_server().await;
|
||||
micodus_protocol_server(receiver).await;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -26,87 +28,5 @@ fn test() {
|
||||
let data: Vec<u8> = vec![0x36, 0x31, 0x33, 0x32, 0x31, 0x31, 0x38];
|
||||
let code = BcdNumber::try_from(&data as &[u8]).unwrap();
|
||||
println!("{code}");
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
async fn apiserver() -> io::Result<()> {
|
||||
let listener = match TcpListener::bind(format!("{}:{}", ADDR, PORT)).await {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
println!("error: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
let (socket, _remote_addr) = listener.accept().await?;
|
||||
tokio::spawn(async move {
|
||||
let mut buf = vec![0; BUFSIZE];
|
||||
'nest: loop {
|
||||
#[allow(unused_variables)]
|
||||
let terminal_id = 0;
|
||||
match socket.readable().await {
|
||||
Ok(_) => {
|
||||
match socket.try_read(&mut buf) {
|
||||
Ok(_) => match handle(&buf) {
|
||||
Some(o) => match socket.try_write(&o) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("error: {e}");
|
||||
break 'nest;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
break 'nest;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
println!("error read: {e}");
|
||||
if e.kind() == io::ErrorKind::WouldBlock {
|
||||
continue 'nest;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error socket readable: {e}");
|
||||
}
|
||||
}
|
||||
match socket.writable().await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("error socket writable: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(buf: &Vec<u8>) -> Option<Vec<u8>> {
|
||||
let mut rawdata = InboundDataWrapper::new(buf.to_vec());
|
||||
let reply = match parse_inbound_msg(&mut rawdata) {
|
||||
Ok(o) => {
|
||||
println!("query: {}", o);
|
||||
//println!("raw query: {:X?}", o.to_raw());
|
||||
Message::store(&o);
|
||||
Message::set_reply(o)
|
||||
}
|
||||
Err(e) => {
|
||||
println!("parse inbound message error: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
match reply {
|
||||
Some(o) => {
|
||||
println!("reply: {}", o);
|
||||
//println!("raw reply {:X?}", o.to_raw());
|
||||
println!("--------------");
|
||||
return Some(o.to_raw().into());
|
||||
}
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
51
src/old/misc.rs
Normal file
@ -0,0 +1,51 @@
|
||||
async fn serve_old(stream: &TcpStream, recv_clone: &Arc<RwLock<Receiver<u8>>>) {
|
||||
loop {
|
||||
let ready = stream
|
||||
.ready(Interest::READABLE | Interest::WRITABLE)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if ready.is_readable() {
|
||||
let mut buf = vec![0; BUFSIZE];
|
||||
match stream.try_read(&mut buf) {
|
||||
Ok(_) => match handle(&buf) {
|
||||
Some(o) => match stream.try_write(&o) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("error: {e}");
|
||||
break;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
println!("none");
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
println!("{e}");
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ready.is_writable() {
|
||||
let mut recv = recv_clone.write().await;
|
||||
if !recv.is_empty() {
|
||||
match recv.recv().await {
|
||||
Some(o) => match o {
|
||||
1 => {
|
||||
//socket.try_write(&[o, 0x01]).unwrap();
|
||||
println!("sent");
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -376,7 +376,6 @@ impl LocationInformationReport {
|
||||
|
||||
fn parse_time(&mut self, timeslice: [u8; 6]) {
|
||||
let code = BcdNumber::try_from(×lice as &[u8]).unwrap();
|
||||
println!("{}", code);
|
||||
let time = format!("{}", code.to_u64().unwrap());
|
||||
match NaiveDateTime::parse_from_str(time.as_str(), "%y%m%d%H%M%S") {
|
||||
Ok(o) => {
|
||||
@ -521,6 +520,26 @@ impl From<u32> for LocationInformationReportStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct LocationInformationQuery {}
|
||||
impl LocationInformationQuery {
|
||||
pub const ID: u16 = 0x8201;
|
||||
}
|
||||
|
||||
impl BodyMessage for LocationInformationQuery {
|
||||
fn parse(&mut self, rawbody: &Vec<u8>) {}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct LocationInformationQueryResponse {}
|
||||
impl LocationInformationQueryResponse {
|
||||
pub const ID: u16 = 0x0201;
|
||||
}
|
||||
|
||||
impl BodyMessage for LocationInformationQueryResponse {
|
||||
fn parse(&mut self, rawbody: &Vec<u8>) {}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct StartOfTrip {}
|
||||
impl StartOfTrip {
|
||||
@ -559,6 +578,7 @@ macro_rules! generate_impl {
|
||||
rawbody.into_iter()
|
||||
}
|
||||
}*/
|
||||
#[allow(unused)]
|
||||
impl $t {
|
||||
pub fn new(rawbody: &Vec<u8>) -> Self {
|
||||
let mut res = Self::default();
|
||||
@ -583,6 +603,8 @@ generate_impl!(
|
||||
QueryTerminalParameterResponse,
|
||||
TerminalControl,
|
||||
LocationInformationReport,
|
||||
LocationInformationQuery,
|
||||
LocationInformationQueryResponse,
|
||||
StartOfTrip,
|
||||
EndOfTrip
|
||||
);
|
||||
|
@ -1,5 +1,8 @@
|
||||
#![allow(unused_variables)]
|
||||
pub struct MessageError;
|
||||
pub enum MessageError {
|
||||
NotOurProtocolError,
|
||||
BasicError,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for MessageError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
@ -13,16 +16,23 @@ impl std::fmt::Display for MessageError {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NotOurProtocolError;
|
||||
macro_rules! errorgen {
|
||||
($t:ident,$msg:expr) => {
|
||||
pub struct $t;
|
||||
|
||||
impl std::fmt::Debug for NotOurProtocolError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "invalid protocol")
|
||||
}
|
||||
impl std::fmt::Debug for $t {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, $msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $t {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, $msg)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NotOurProtocolError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "invalid protocol")
|
||||
}
|
||||
}
|
||||
errorgen!(NotOurProtocolError, "invalid protocol");
|
||||
errorgen!(BasicError, "basic error");
|
||||
|
@ -89,7 +89,7 @@ impl std::fmt::Display for MessageHeader {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"id: {:X?}, length: {}, terminal id: {}, serial: {:X?}",
|
||||
"id: {:#06x}, length: {}, terminal id: {}, serial: {:X?}",
|
||||
self.id, self.bodylength, self.terminal_id, self.serial_number
|
||||
)
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use body::*;
|
||||
use error::*;
|
||||
use header::*;
|
||||
|
||||
use crate::db::*;
|
||||
use super::db::*;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
@ -20,7 +20,7 @@ pub fn parse_inbound_msg(
|
||||
let mut msg: Message = Message::default();
|
||||
|
||||
if rawdata.first_byte() != FLAG_DELIMITER {
|
||||
return Err(MessageError);
|
||||
return Err(MessageError::NotOurProtocolError);
|
||||
}
|
||||
|
||||
match msg.parse_header(rawdata) {
|
||||
@ -29,7 +29,7 @@ pub fn parse_inbound_msg(
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error parsing header {e}");
|
||||
return Err(MessageError);
|
||||
return Err(MessageError::BasicError);
|
||||
}
|
||||
};
|
||||
|
||||
@ -253,100 +253,91 @@ impl Message {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_reply(inmsg: Message) -> Option<Message> {
|
||||
pub fn send_reply(inmsg: Option<Message>) -> Option<Message> {
|
||||
let mut reply: Message = Message::default();
|
||||
let terminal_id = inmsg.header.get_raw_terminal_id().clone();
|
||||
match inmsg.content {
|
||||
MessageType::TerminalRegistration(t) => {
|
||||
let cnt = t.generate_reply(terminal_id.into(), inmsg.header.serial_number);
|
||||
reply.header.build(
|
||||
TerminalRegistrationReply::ID,
|
||||
cnt.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::TerminalRegistrationReply(cnt);
|
||||
}
|
||||
MessageType::TerminalAuthentication(t) => {
|
||||
let cnt = t.generate_reply(TerminalAuthentication::ID, inmsg.header.serial_number);
|
||||
reply.header.build(
|
||||
PlatformUniversalResponse::ID,
|
||||
cnt.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::PlatformUniversalResponse(cnt);
|
||||
}
|
||||
MessageType::LocationInformationReport(t) => {
|
||||
let cnt =
|
||||
t.generate_reply(LocationInformationReport::ID, inmsg.header.serial_number);
|
||||
reply.header.build(
|
||||
PlatformUniversalResponse::ID,
|
||||
cnt.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::PlatformUniversalResponse(cnt);
|
||||
}
|
||||
MessageType::TerminalHeartbeat(t) => {
|
||||
let cnt = t.generate_reply(TerminalHeartbeat::ID, inmsg.header.serial_number);
|
||||
reply.header.build(
|
||||
PlatformUniversalResponse::ID,
|
||||
cnt.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::PlatformUniversalResponse(cnt);
|
||||
}
|
||||
MessageType::TerminalLogout(t) => {
|
||||
let cnt = t.generate_reply(TerminalHeartbeat::ID, inmsg.header.serial_number);
|
||||
reply.header.build(
|
||||
PlatformUniversalResponse::ID,
|
||||
cnt.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::PlatformUniversalResponse(cnt);
|
||||
}
|
||||
_ => {
|
||||
println!("no type");
|
||||
return None;
|
||||
match inmsg {
|
||||
Some(inmsg) => {
|
||||
let terminal_id = inmsg.header.get_raw_terminal_id().clone();
|
||||
match inmsg.content {
|
||||
MessageType::TerminalRegistration(t) => {
|
||||
let content =
|
||||
t.generate_reply(terminal_id.into(), inmsg.header.serial_number);
|
||||
reply.header.build(
|
||||
TerminalRegistrationReply::ID,
|
||||
content.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::TerminalRegistrationReply(content);
|
||||
}
|
||||
MessageType::TerminalAuthentication(t) => {
|
||||
let content = t
|
||||
.generate_reply(TerminalAuthentication::ID, inmsg.header.serial_number);
|
||||
reply.header.build(
|
||||
PlatformUniversalResponse::ID,
|
||||
content.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::PlatformUniversalResponse(content);
|
||||
}
|
||||
MessageType::LocationInformationReport(t) => {
|
||||
let content = t.generate_reply(
|
||||
LocationInformationReport::ID,
|
||||
inmsg.header.serial_number,
|
||||
);
|
||||
reply.header.build(
|
||||
PlatformUniversalResponse::ID,
|
||||
content.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::PlatformUniversalResponse(content);
|
||||
}
|
||||
MessageType::TerminalHeartbeat(t) => {
|
||||
let content =
|
||||
t.generate_reply(TerminalHeartbeat::ID, inmsg.header.serial_number);
|
||||
reply.header.build(
|
||||
PlatformUniversalResponse::ID,
|
||||
content.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::PlatformUniversalResponse(content);
|
||||
}
|
||||
MessageType::TerminalLogout(t) => {
|
||||
let content =
|
||||
t.generate_reply(TerminalHeartbeat::ID, inmsg.header.serial_number);
|
||||
reply.header.build(
|
||||
PlatformUniversalResponse::ID,
|
||||
content.to_raw().len(),
|
||||
terminal_id,
|
||||
);
|
||||
reply.content = MessageType::PlatformUniversalResponse(content);
|
||||
}
|
||||
_ => {
|
||||
println!("no type");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
reply.outbound_finalize();
|
||||
Some(reply)
|
||||
}
|
||||
|
||||
pub fn store(inmsg: &Message) {
|
||||
match inmsg.content {
|
||||
MessageType::LocationInformationReport(ref t) => {
|
||||
/*{
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::prelude::*;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(true)
|
||||
.open("data/log.txt")
|
||||
.unwrap();
|
||||
|
||||
//if let Err(e) = writeln!(file, ) {
|
||||
// eprintln!("Couldn't write to file: {}", e);
|
||||
//}
|
||||
file.write(format!("{},{},{}\n", t.time, t.latitude, t.longitude).as_bytes())
|
||||
.unwrap();
|
||||
}*/
|
||||
|
||||
let conn = connectdb().unwrap();
|
||||
let dblog = crate::db::DbLog {
|
||||
serial: inmsg.header.serial_number,
|
||||
time: t.time.format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||
latitude: t.latitude,
|
||||
longitude: t.longitude,
|
||||
speed: t.speed,
|
||||
height: t.height,
|
||||
direction: t.direction,
|
||||
is_satellite: t.is_satellite(),
|
||||
};
|
||||
insert(&conn, &dblog).unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
pub fn store(inmsg: &Message) -> Option<DbLog> {
|
||||
let res = match inmsg.content {
|
||||
MessageType::LocationInformationReport(ref t) => Some(DbLog {
|
||||
serial: inmsg.header.serial_number,
|
||||
time: t.time.format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||
latitude: t.latitude,
|
||||
longitude: t.longitude,
|
||||
speed: t.speed,
|
||||
height: t.height,
|
||||
direction: t.direction,
|
||||
is_satellite: t.is_satellite(),
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
res
|
||||
}
|
||||
|
||||
pub fn outbound_finalize(&mut self) {
|
||||
@ -457,3 +448,22 @@ impl std::fmt::Display for MessageType {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::prelude::*;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(true)
|
||||
.open("data/log.txt")
|
||||
.unwrap();
|
||||
|
||||
//if let Err(e) = writeln!(file, ) {
|
||||
// eprintln!("Couldn't write to file: {}", e);
|
||||
//}
|
||||
file.write(format!("{},{},{}\n", t.time, t.latitude, t.longitude).as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
*/
|
||||
|
186
src/serve.rs
Normal file
@ -0,0 +1,186 @@
|
||||
use crate::db::sqlite_engine::SQLiteEngine;
|
||||
use crate::db::{SQLEngine::*, *};
|
||||
use crate::parser::*;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::process::exit;
|
||||
use std::{collections::HashMap, io, sync::Arc};
|
||||
|
||||
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
||||
use tokio::sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
RwLock,
|
||||
};
|
||||
|
||||
const MICODUS_ADDR: &'static str = "0.0.0.0";
|
||||
const MICODUS_PORT: u64 = 7701;
|
||||
const BUFSIZE: usize = 1024;
|
||||
|
||||
const CTRL_ADDR: &'static str = "127.0.0.1";
|
||||
const CTRL_PORT: u64 = 7702;
|
||||
|
||||
pub async fn control_server() -> Receiver<u8> {
|
||||
let listener = TcpListener::bind(format!("{}:{}", CTRL_ADDR, CTRL_PORT))
|
||||
.await
|
||||
.unwrap();
|
||||
let (sender, receiver): (Sender<u8>, Receiver<u8>) = channel(100);
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let (socket, _) = listener.accept().await.unwrap();
|
||||
socket.readable().await.unwrap();
|
||||
let mut buf = [0; 16];
|
||||
match socket.try_read(&mut buf) {
|
||||
Ok(0) => {}
|
||||
Ok(n) => {
|
||||
let mut data = str::from_utf8(&buf).unwrap();
|
||||
data = data.trim();
|
||||
if data.starts_with("test") {
|
||||
sender.send(1).await.unwrap();
|
||||
println!("ok");
|
||||
} else {
|
||||
println!("n: {n}, msg: '{data}'");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
receiver
|
||||
}
|
||||
|
||||
pub async fn micodus_protocol_server(receiver: Receiver<u8>) {
|
||||
let mut sql = SQLite(SQLiteEngine::default());
|
||||
sql.connect();
|
||||
|
||||
let recv = Arc::new(RwLock::new(receiver));
|
||||
let listen_addr_str = format!("{}:{}", MICODUS_ADDR, MICODUS_PORT);
|
||||
|
||||
let listen_addr: SocketAddr = listen_addr_str.parse().unwrap();
|
||||
|
||||
let socket = TcpSocket::new_v4().unwrap();
|
||||
socket.set_keepalive(true).unwrap();
|
||||
socket.set_reuseaddr(true).unwrap();
|
||||
socket.bind(listen_addr).unwrap();
|
||||
|
||||
let listener = match socket.listen(1024) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
println!("error: {e}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
let recv = Arc::clone(&recv);
|
||||
let (stream, _remote_addr) = listener.accept().await.unwrap();
|
||||
|
||||
println!("accept");
|
||||
tokio::spawn(async move {
|
||||
serve(&stream, &recv).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve(stream: &TcpStream, recv_clone: &Arc<RwLock<Receiver<u8>>>) {
|
||||
loop {
|
||||
let mut recv = recv_clone.write().await;
|
||||
tokio::select! {
|
||||
o = stream.readable() => {
|
||||
match o {
|
||||
Ok(_) => {
|
||||
let mut buf = vec![0; BUFSIZE];
|
||||
match stream.try_read(&mut buf) {
|
||||
Ok(_) => match handle(&buf) {
|
||||
Some(o) => match stream.try_write(&o) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("error: {e}");
|
||||
break;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
println!("none");
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{e}");
|
||||
break;
|
||||
}
|
||||
}}
|
||||
Err(e)=> { println!("error socket readable: {e}");}
|
||||
}
|
||||
},
|
||||
b = recv.recv() => {
|
||||
match b {
|
||||
Some(res) => {
|
||||
println!("test recv {res}");
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
},
|
||||
else => break,
|
||||
}
|
||||
|
||||
//if ready.is_writable() {
|
||||
// if !receiver.is_empty() {
|
||||
// println!("test2");
|
||||
// match receiver.recv().await {
|
||||
// Some(o) => match o {
|
||||
// 1 => {
|
||||
// //socket.try_write(&[o, 0x01]).unwrap();
|
||||
// println!("sent");
|
||||
// }
|
||||
// _ => {}
|
||||
// },
|
||||
// None => {}
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(buf: &Vec<u8>) -> Option<Vec<u8>> {
|
||||
let mut rawdata = InboundDataWrapper::new(buf.to_vec());
|
||||
//let sql = Arc::clone(s);
|
||||
let reply = match parse_inbound_msg(&mut rawdata) {
|
||||
Ok(o) => {
|
||||
println!("query: {}", o);
|
||||
//println!("raw query: {:X?}", o.to_raw());
|
||||
match Message::store(&o) {
|
||||
Some(log) => {
|
||||
let mut s = SQLite(SQLiteEngine::default());
|
||||
s.connect();
|
||||
s.insert(&log);
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
Message::send_reply(Some(o))
|
||||
}
|
||||
Err(e) => {
|
||||
println!("parse inbound message error: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
match reply {
|
||||
Some(o) => {
|
||||
println!("reply: {}", o);
|
||||
//println!("raw reply {:X?}", o.to_raw());
|
||||
println!("--------------");
|
||||
return Some(o.to_raw().into());
|
||||
}
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub struct ControllerMap(HashMap<String, (Sender<u8>, Receiver<u8>)>);
|
10
html/lastloc.lua → tools/sql.lua
Normal file → Executable file
@ -1,10 +1,9 @@
|
||||
#!/usr/bin/lua
|
||||
--ngx.say(_VERSION)
|
||||
|
||||
local sqlite = require("lsqlite3")
|
||||
local basepath = "/home/paul/git/micodus_server"
|
||||
local dbfile = string.format("%s/data/tracker.db",basepath)
|
||||
--local output = string.format("%s/html/lastloc.json",basepath)
|
||||
local output = string.format("%s/html/lastloc.json",basepath)
|
||||
local query = [[
|
||||
SELECT latitude,longitude
|
||||
FROM log
|
||||
@ -13,14 +12,13 @@ local query = [[
|
||||
]]
|
||||
|
||||
function main()
|
||||
local db = sqlite.open(dbfile,sqlite3.OPEN_READONLY)
|
||||
local db = sqlite.open(dbfile)
|
||||
|
||||
local res, vm = db:nrows(query)
|
||||
for row in res, vm do
|
||||
local locstr = string.format("{\"latitude\": %s, \"longitude\": %s}", row.latitude, row.longitude)
|
||||
--f = io.open(output, "w")
|
||||
--f:write(locstr)
|
||||
ngx.say(locstr)
|
||||
f = io.open(output, "w")
|
||||
f:write(locstr)
|
||||
end
|
||||
db:close()
|
||||
end
|
3
tools/systemd_run.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
sudo systemd-run --uid=paul --working-directory=/home/paul/git/micodus_server ./target/release/micodus_server
|