Compare commits

..

No commits in common. "develop" and "master" have entirely different histories.

48 changed files with 514 additions and 2907 deletions

1755
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@ 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.37", features = ["bundled"] }
tokio = { version = "1.47", features = ["full", "sync"] }
rusqlite = { version = "0.34", features = ["bundled"] }
tokio = { version = "1.44", features = ["full", "sync"] }

View File

@ -1,10 +0,0 @@
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}

61
html/engine.js Normal file
View File

@ -0,0 +1,61 @@
/*
*/
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: '&copy; <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");
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -1,30 +1,15 @@
<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>
<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>

10
tools/sql.lua → html/lastloc.lua Executable file → Normal file
View File

@ -1,9 +1,10 @@
#!/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
@ -12,13 +13,14 @@ local query = [[
]]
function main()
local db = sqlite.open(dbfile)
local db = sqlite.open(dbfile,sqlite3.OPEN_READONLY)
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)
--f = io.open(output, "w")
--f:write(locstr)
ngx.say(locstr)
end
db:close()
end

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 618 B

View File

@ -1,43 +0,0 @@
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: '&copy; <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);

View File

@ -1 +0,0 @@
{"latitude": 49.173069, "longitude": -0.342916}

View File

@ -1,21 +0,0 @@
#!/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()

View File

@ -1,6 +0,0 @@
<html>
<script>
window.navigator.vibrate(1000);
document.write("<h1>"+window.navigator.userAgent+"</h1>");
</script>
</html>

View File

@ -1,90 +0,0 @@
#!/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()

View File

@ -1,54 +0,0 @@
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()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,193 +0,0 @@
// 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: `&copy; <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();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,32 +0,0 @@
/* 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");
});
}

View File

@ -1,35 +0,0 @@
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%;
}

3
html/style.css Normal file
View File

@ -0,0 +1,3 @@
.leaflet-popup-content {
/*font-size: 13pt;*/
}

View File

@ -1,8 +1,8 @@
// Create WebSocket connection.
const socket = new WebSocket("wss://trackme.ovh/ws");
const socket = new WebSocket("wss://geo.paulbsd.com/ws");
// Connection opened
socket.addEventListener("open", (_event) => {
socket.addEventListener("open", (event) => {
socket.send("Hello Server!");
});

View File

@ -1,29 +1,36 @@
#!/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 res, vm = db:nrows(const.query)
local data = {}
local db = sqlite.open(dbfile,sqlite3.OPEN_READONLY)
local res, vm = db:nrows(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
@ -50,7 +57,7 @@ function geows()
end
last_time = data.time
end
ngx.sleep(0.5)
ngx.sleep(1)
end
wb:send_close()
end

103
src/db.rs Normal file
View File

@ -0,0 +1,103 @@
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())
}
}

View File

@ -1,112 +0,0 @@
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>,
}

View File

@ -1,77 +0,0 @@
//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,
)
}
}

View File

@ -1,115 +0,0 @@
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>,
}

View File

View File

@ -1,25 +1,23 @@
mod db;
mod parser;
mod serve;
use std::process::exit;
use crate::db::*;
use crate::parser::*;
use db::sqlite_engine::SQLiteEngine;
use db::{SQLEngine::SQLite, *};
use serve::*;
use std::io;
use tokio::net::TcpListener;
const ADDR: &'static str = "0.0.0.0";
const PORT: u64 = 7701;
const BUFSIZE: usize = 1024;
#[tokio::main]
async fn main() {
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;
//test();
let conn = connectdb().unwrap();
initdb(&conn).unwrap();
apiserver().await.unwrap();
}
#[allow(dead_code)]
@ -28,5 +26,87 @@ 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}");
exit(0);
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;
}
}
}

View File

@ -1,51 +0,0 @@
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 => {}
}
}
}
}
}

View File

@ -376,6 +376,7 @@ impl LocationInformationReport {
fn parse_time(&mut self, timeslice: [u8; 6]) {
let code = BcdNumber::try_from(&timeslice 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) => {
@ -520,26 +521,6 @@ 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 {
@ -578,7 +559,6 @@ macro_rules! generate_impl {
rawbody.into_iter()
}
}*/
#[allow(unused)]
impl $t {
pub fn new(rawbody: &Vec<u8>) -> Self {
let mut res = Self::default();
@ -603,8 +583,6 @@ generate_impl!(
QueryTerminalParameterResponse,
TerminalControl,
LocationInformationReport,
LocationInformationQuery,
LocationInformationQueryResponse,
StartOfTrip,
EndOfTrip
);

View File

@ -1,8 +1,5 @@
#![allow(unused_variables)]
pub enum MessageError {
NotOurProtocolError,
BasicError,
}
pub struct MessageError;
impl std::fmt::Debug for MessageError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
@ -16,23 +13,16 @@ impl std::fmt::Display for MessageError {
}
}
macro_rules! errorgen {
($t:ident,$msg:expr) => {
pub struct $t;
pub struct NotOurProtocolError;
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::Debug 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");
impl std::fmt::Display for NotOurProtocolError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "invalid protocol")
}
}

View File

@ -89,7 +89,7 @@ impl std::fmt::Display for MessageHeader {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"id: {:#06x}, length: {}, terminal id: {}, serial: {:X?}",
"id: {:X?}, length: {}, terminal id: {}, serial: {:X?}",
self.id, self.bodylength, self.terminal_id, self.serial_number
)
}

View File

@ -7,7 +7,7 @@ use body::*;
use error::*;
use header::*;
use super::db::*;
use crate::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::NotOurProtocolError);
return Err(MessageError);
}
match msg.parse_header(rawdata) {
@ -29,7 +29,7 @@ pub fn parse_inbound_msg(
}
Err(e) => {
println!("error parsing header {e}");
return Err(MessageError::BasicError);
return Err(MessageError);
}
};
@ -253,91 +253,100 @@ impl Message {
}
}
pub fn send_reply(inmsg: Option<Message>) -> Option<Message> {
pub fn set_reply(inmsg: Message) -> Option<Message> {
let mut reply: Message = Message::default();
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;
}
}
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;
}
None => {}
}
reply.outbound_finalize();
Some(reply)
}
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 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 outbound_finalize(&mut self) {
@ -448,22 +457,3 @@ 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();
}
*/

View File

@ -1,186 +0,0 @@
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>)>);

View File

@ -1,3 +0,0 @@
#!/usr/bin/env zsh
sudo systemd-run --uid=paul --working-directory=/home/paul/git/micodus_server ./target/release/micodus_server