chore: micodus_server rework

This commit is contained in:
Paul 2025-07-24 22:42:21 +02:00
parent cf283d3eab
commit feb09aea00
25 changed files with 2483 additions and 465 deletions

1770
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ bitvec = { version = "1.0" }
chrono = { version = "0.4" } chrono = { version = "0.4" }
encoding_rs = { version = "0.8" } encoding_rs = { version = "0.8" }
lazy_static = { version = "1.5" } lazy_static = { version = "1.5" }
libsql = { version = "0.9", features = ["replication"] }
rand = { version = "0.9" } rand = { version = "0.9" }
rusqlite = { version = "0.34", features = ["bundled"] } rusqlite = { version = "0.37", features = ["bundled"] }
tokio = { version = "1.44", features = ["full", "sync"] } tokio = { version = "1.46", features = ["full", "sync"] }

10
html/const.lua Normal file
View 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}

View File

@ -1,14 +1,12 @@
/* //engine.js
*/
let map; let map;
let point; let point;
const arrows = ["↑", "↗", "→", "↘", "↓", "↙", "←", "↖"]; const arrows = ["↑", "↗", "→", "↘", "↓", "↙", "←", "↖"];
const socket = new WebSocket("wss://trackme.ovh/ws");
const socket = new WebSocket("wss://geo.paulbsd.com/ws"); function create_location(coords, text) {
function create_location(coords,text) {
point = L.marker(coords, { point = L.marker(coords, {
color: 'red', color: 'red',
fillColor: '#f03', fillColor: '#f03',
@ -26,8 +24,9 @@ function create_map(coords) {
}); });
L.tileLayer(`https://tile.openstreetmap.org/{z}/{x}/{y}.png`, { L.tileLayer(`https://tile.openstreetmap.org/{z}/{x}/{y}.png`, {
maxZoom: 19, maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> / <a href="https://www.paulbsd.com">PaulBSD</a>' attribution: `&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> / <a href="https://www.paulbsd.com">PaulBSD</a>`
}).addTo(map); }).addTo(map);
map.zoomControl.setPosition('bottomright');
} }
function update(data) { function update(data) {
@ -35,27 +34,62 @@ function update(data) {
const speed = data.speed/10; const speed = data.speed/10;
let section = parseInt(data.direction/45 + 0.5); let section = parseInt(data.direction/45 + 0.5);
section = section % 8; 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}°<br/>serial: ${data.serial}`;
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) {
if (!map) {
create_map(coords);
} else {
map.setView(coords); map.setView(coords);
map.flyTo(coords); map.flyTo(coords);
}
if (!point) {
create_location(coords, text);
} else { } else {
create_map(coords);
}
if (point) {
point.setLatLng(coords); point.setLatLng(coords);
point.setPopupContent(text); point.setPopupContent(text);
} else {
create_location(coords, text);
} }
} }
function ping() {
socket.send("ping");
}
socket.addEventListener("message", (event) => { socket.addEventListener("message", (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
update(data); update(data);
}); });
socket.addEventListener("open", (event) => { socket.addEventListener("open", (event) => {
console.log(event);
socket.send("ping"); socket.send("ping");
}); });
setInterval(ping, 1000)
// service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/engine.js')
.then((reg) => {
console.log('Enregistrement réussi');
}).catch((error) => {
console.log('Erreur : ' + error);
});
}
const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
'/',
'/leaflet/leaflet.js',
'/engine.js',
'/style.css',
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});

BIN
html/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -1,6 +1,6 @@
<html> <html>
<title>PaulBSD geo</title> <title>Trackme</title>
<link rel="icon" href="https://paulbsd.com/favicon.ico" type="image/x-icon"> <link rel="icon" href="favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="leaflet/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/> <link rel="stylesheet" href="leaflet/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<link rel="stylesheet" href="style.css" crossorigin=""/> <link rel="stylesheet" href="style.css" crossorigin=""/>
<script src="leaflet/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""> <script src="leaflet/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="">

View File

@ -1,21 +1,14 @@
#!/usr/bin/lua #!/usr/bin/lua
--ngx.say(_VERSION)
local const = require('const')
local json = require("json")
local sqlite = require("lsqlite3") 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
ORDER BY time DESC
LIMIT 1;
]]
function main() function main()
local db = sqlite.open(dbfile,sqlite3.OPEN_READONLY) local db = sqlite.open(const.dbfile,sqlite.OPEN_READONLY)
local res, vm = db:nrows(query) local res, vm = db:nrows(const.query)
for row in res, vm do for row in res, vm do
local locstr = string.format("{\"latitude\": %s, \"longitude\": %s}", row.latitude, row.longitude) local locstr = string.format("{\"latitude\": %s, \"longitude\": %s}", row.latitude, row.longitude)
--f = io.open(output, "w") --f = io.open(output, "w")

43
html/old/engine.js Normal file
View 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: '&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);

1
html/old/lastloc.json Normal file
View File

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

6
html/old/test.html Normal file
View File

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

54
html/old/ws_test.lua Normal file
View 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()

View File

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

View File

@ -1,36 +1,30 @@
#!/usr/bin/lua #!/usr/bin/lua
package.path = package.path..";/home/paul/git/micodus_server/html/?.lua"
local const = require("const")
local json = require("json") local json = require("json")
local server = require("nginx.websocket.server") local server = require("nginx.websocket.server")
local sqlite = require("lsqlite3") 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","") --ngx.shared.geo:set("last_time","")
local db = sqlite.open(const.dbfile, sqlite.OPEN_READONLY)
function getdata() function getdata()
local db = sqlite.open(dbfile,sqlite3.OPEN_READONLY) local res, vm = db:nrows(const.query)
local res, vm = db:nrows(query) local data = {}
local data
for row in res, vm do for row in res, vm do
data = { data = {
["time"]=row.time, ["time"] = row.time,
["latitude"]=row.latitude, ["latitude"] = row.latitude,
["longitude"]=row.longitude, ["longitude"] = row.longitude,
["height"]=row.height, ["height"] = row.height,
["speed"]=row.speed, ["speed"] = row.speed,
["direction"]=row.direction, ["direction"] = row.direction,
["serial"]=row.serial, ["serial"] = row.serial,
} }
end end
db:close() -- db:close()
return data return data
end end
@ -49,6 +43,7 @@ function geows()
while true do while true do
local data = getdata() local data = getdata()
if data.time ~= last_time then if data.time ~= last_time then
--do
local locstr = json.encode(data) local locstr = json.encode(data)
local bytes, err = wb:send_text(locstr) local bytes, err = wb:send_text(locstr)
if not bytes then if not bytes then
@ -57,7 +52,7 @@ function geows()
end end
last_time = data.time last_time = data.time
end end
ngx.sleep(1) ngx.sleep(0.5)
end end
wb:send_close() wb:send_close()
end end

103
src/db.rs
View File

@ -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())
}
}

114
src/db/libsql_engine.rs Normal file
View File

@ -0,0 +1,114 @@
use super::*;
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
View 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
View File

@ -0,0 +1,115 @@
use super::*;
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
View File

View File

@ -1,23 +1,20 @@
mod db; pub mod db;
mod parser; pub mod parser;
pub mod serve;
use crate::db::*; //pub use db::libsql_engine::LibSQLEngine;
use crate::parser::*; pub use db::sqlite_engine::SQLiteEngine;
pub use db::*;
use std::io; pub use parser::*;
use tokio::net::TcpListener;
const ADDR: &'static str = "0.0.0.0";
const PORT: u64 = 7701;
const BUFSIZE: usize = 1024;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
//test(); let mut s: SQLEngine = SQLEngine::SQLite(SQLiteEngine::default());
let conn = connectdb().unwrap(); s.connect();
initdb(&conn).unwrap(); s.init();
apiserver().await.unwrap();
let receiver = serve::control_server().await;
serve::micodus_protocol_server(receiver).await;
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -28,85 +25,3 @@ fn test() {
println!("{code}"); println!("{code}");
std::process::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

@ -376,7 +376,6 @@ impl LocationInformationReport {
fn parse_time(&mut self, timeslice: [u8; 6]) { fn parse_time(&mut self, timeslice: [u8; 6]) {
let code = BcdNumber::try_from(&timeslice as &[u8]).unwrap(); let code = BcdNumber::try_from(&timeslice as &[u8]).unwrap();
println!("{}", code);
let time = format!("{}", code.to_u64().unwrap()); let time = format!("{}", code.to_u64().unwrap());
match NaiveDateTime::parse_from_str(time.as_str(), "%y%m%d%H%M%S") { match NaiveDateTime::parse_from_str(time.as_str(), "%y%m%d%H%M%S") {
Ok(o) => { 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)] #[derive(Default, Debug, Clone)]
pub struct StartOfTrip {} pub struct StartOfTrip {}
impl StartOfTrip { impl StartOfTrip {
@ -583,6 +602,8 @@ generate_impl!(
QueryTerminalParameterResponse, QueryTerminalParameterResponse,
TerminalControl, TerminalControl,
LocationInformationReport, LocationInformationReport,
LocationInformationQuery,
LocationInformationQueryResponse,
StartOfTrip, StartOfTrip,
EndOfTrip EndOfTrip
); );

View File

@ -89,7 +89,7 @@ impl std::fmt::Display for MessageHeader {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!( write!(
f, 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 self.id, self.bodylength, self.terminal_id, self.serial_number
) )
} }

View File

@ -1,13 +1,13 @@
#![allow(unused_variables)] #![allow(unused_variables)]
mod body; pub mod body;
mod error; pub mod error;
mod header; pub mod header;
use body::*; pub use body::*;
use error::*; pub use error::*;
use header::*; pub use header::*;
use crate::db::*; pub use super::db::*;
use std::collections::VecDeque; use std::collections::VecDeque;
@ -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 mut reply: Message = Message::default();
let terminal_id = inmsg.header.get_raw_terminal_id().clone(); match inmsg {
match inmsg.content { Some(inmsg) => {
MessageType::TerminalRegistration(t) => { let terminal_id = inmsg.header.get_raw_terminal_id().clone();
let cnt = t.generate_reply(terminal_id.into(), inmsg.header.serial_number); match inmsg.content {
reply.header.build( MessageType::TerminalRegistration(t) => {
TerminalRegistrationReply::ID, let content =
cnt.to_raw().len(), t.generate_reply(terminal_id.into(), inmsg.header.serial_number);
terminal_id, reply.header.build(
); TerminalRegistrationReply::ID,
reply.content = MessageType::TerminalRegistrationReply(cnt); content.to_raw().len(),
} terminal_id,
MessageType::TerminalAuthentication(t) => { );
let cnt = t.generate_reply(TerminalAuthentication::ID, inmsg.header.serial_number); reply.content = MessageType::TerminalRegistrationReply(content);
reply.header.build( }
PlatformUniversalResponse::ID, MessageType::TerminalAuthentication(t) => {
cnt.to_raw().len(), let content = t
terminal_id, .generate_reply(TerminalAuthentication::ID, inmsg.header.serial_number);
); reply.header.build(
reply.content = MessageType::PlatformUniversalResponse(cnt); PlatformUniversalResponse::ID,
} content.to_raw().len(),
MessageType::LocationInformationReport(t) => { terminal_id,
let cnt = );
t.generate_reply(LocationInformationReport::ID, inmsg.header.serial_number); reply.content = MessageType::PlatformUniversalResponse(content);
reply.header.build( }
PlatformUniversalResponse::ID, MessageType::LocationInformationReport(t) => {
cnt.to_raw().len(), let content = t.generate_reply(
terminal_id, LocationInformationReport::ID,
); inmsg.header.serial_number,
reply.content = MessageType::PlatformUniversalResponse(cnt); );
} reply.header.build(
MessageType::TerminalHeartbeat(t) => { PlatformUniversalResponse::ID,
let cnt = t.generate_reply(TerminalHeartbeat::ID, inmsg.header.serial_number); content.to_raw().len(),
reply.header.build( terminal_id,
PlatformUniversalResponse::ID, );
cnt.to_raw().len(), reply.content = MessageType::PlatformUniversalResponse(content);
terminal_id, }
); MessageType::TerminalHeartbeat(t) => {
reply.content = MessageType::PlatformUniversalResponse(cnt); let content =
} t.generate_reply(TerminalHeartbeat::ID, inmsg.header.serial_number);
MessageType::TerminalLogout(t) => { reply.header.build(
let cnt = t.generate_reply(TerminalHeartbeat::ID, inmsg.header.serial_number); PlatformUniversalResponse::ID,
reply.header.build( content.to_raw().len(),
PlatformUniversalResponse::ID, terminal_id,
cnt.to_raw().len(), );
terminal_id, reply.content = MessageType::PlatformUniversalResponse(content);
); }
reply.content = MessageType::PlatformUniversalResponse(cnt); MessageType::TerminalLogout(t) => {
} let content =
_ => { t.generate_reply(TerminalHeartbeat::ID, inmsg.header.serial_number);
println!("no type"); reply.header.build(
return None; PlatformUniversalResponse::ID,
content.to_raw().len(),
terminal_id,
);
reply.content = MessageType::PlatformUniversalResponse(content);
}
_ => {
println!("no type");
return None;
}
}
} }
None => {}
} }
reply.outbound_finalize(); reply.outbound_finalize();
Some(reply) Some(reply)
} }
pub fn store(inmsg: &Message) { pub fn store(inmsg: &Message) -> Option<DbLog> {
match inmsg.content { let res = match inmsg.content {
MessageType::LocationInformationReport(ref t) => { MessageType::LocationInformationReport(ref t) => Some(DbLog {
/*{ serial: inmsg.header.serial_number,
use std::fs::OpenOptions; time: t.time.format("%Y-%m-%d %H:%M:%S").to_string(),
use std::io::prelude::*; latitude: t.latitude,
longitude: t.longitude,
let mut file = OpenOptions::new() speed: t.speed,
.write(true) height: t.height,
.append(true) direction: t.direction,
.open("data/log.txt") is_satellite: t.is_satellite(),
.unwrap(); }),
_ => None,
//if let Err(e) = writeln!(file, ) { };
// eprintln!("Couldn't write to file: {}", e); res
//}
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) { pub fn outbound_finalize(&mut self) {
@ -457,3 +448,22 @@ impl std::fmt::Display for MessageType {
res 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();
}
*/

165
src/serve.rs Normal file
View File

@ -0,0 +1,165 @@
pub use crate::db::sqlite_engine::SQLiteEngine;
pub use crate::db::*;
pub use crate::parser::*;
use std::io;
use std::sync::Arc;
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::sync::RwLock;
const ADDR: &'static str = "0.0.0.0";
const PORT: u64 = 7701;
const BUFSIZE: usize = 1024;
pub async fn control_server() -> Receiver<u8> {
let listener = TcpListener::bind("127.0.0.1:7702").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: SQLEngine = SQLEngine::SQLite(SQLiteEngine::default());
sql.connect();
let arc_recv = Arc::new(RwLock::new(receiver));
let listener = match TcpListener::bind(format!("{}:{}", ADDR, PORT)).await {
Ok(o) => o,
Err(e) => {
println!("error: {e}");
std::process::exit(1);
}
};
//let sql_ref = Arc::clone(&arc_s);
loop {
let recv_clone = Arc::clone(&arc_recv);
let (socket, _remote_addr) = listener.accept().await.unwrap();
tokio::spawn(async move {
serve(&socket, &recv_clone).await;
});
}
}
async fn serve(socket: &TcpStream, a: &Arc<RwLock<Receiver<u8>>>) {
let mut buf = vec![0; BUFSIZE];
let aa = Arc::clone(&a);
loop {
let mut bb = aa.write().await;
//let terminal_id = 0;
tokio::select! {
readable = socket.readable() => {
match readable {
Ok(_) => {
match socket.try_read(&mut buf) {
Ok(_) => match handle(&buf) {
Some(o) => match socket.try_write(&o) {
Ok(_) => {}
Err(e) => {
println!("error: {e}");
}
},
None => {
println!("none");
}
},
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock {
println!("{e}");
}
}
}
}
Err(e) => {
println!("error socket readable: {e}");
}
}
}
send = bb.recv() => {
match socket.writable().await {
Ok(_) => {
match send {
Some(o) => match o {
1 => {
socket.try_write(&[o, 0x01]).unwrap();
println!("sent");
}
_ => {},
},
None => {
}
}
}
Err(e) => {
println!("error socket not writable: {e}")
}
}
}
}
}
}
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: SQLEngine = SQLEngine::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;
}
}
}

26
tools/sql.lua Executable file
View File

@ -0,0 +1,26 @@
#!/usr/bin/lua
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 query = [[
SELECT latitude,longitude
FROM log
ORDER BY time DESC
LIMIT 1;
]]
function main()
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)
end
db:close()
end
main()

3
tools/systemd_run.sh Normal file
View 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