From 81a61c28619ef9f32e79e5698bc4162ca1ceb82d Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 16 Jan 2024 22:18:32 +0100 Subject: Add Destination to Send page --- Cargo.lock | 122 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + cats-radio-node.db | Bin 0 -> 16384 bytes src/db.rs | 16 ++++++- src/main.rs | 4 +- src/ui.rs | 36 +++++++++++--- static/main.js | 26 +++++++++- templates/dashboard.html | 5 ++ templates/send.html | 20 ++++++++ 9 files changed, 219 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ae0ba4..dff1ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,6 +36,21 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.79" @@ -333,6 +348,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "byteorder" version = "1.5.0" @@ -374,6 +395,7 @@ dependencies = [ "askama_axum", "async-stream", "axum 0.7.4", + "chrono", "futures", "futures-core", "half", @@ -407,6 +429,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.48.5", +] + [[package]] name = "colored" version = "2.1.0" @@ -1078,6 +1114,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -1129,6 +1188,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "labrador-ldpc" version = "1.1.1" @@ -2646,6 +2714,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + [[package]] name = "webpki-roots" version = "0.25.3" diff --git a/Cargo.toml b/Cargo.toml index 23ccf35..419374f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ anyhow = "1.0" askama = { version = "0.12", features = ["with-axum"] } askama_axum = "0.4" axum = "0.7" +chrono = "0.4" simple_logger = "4.3" log = "0.4" serde = { version = "1.0", features = ["derive"] } diff --git a/cats-radio-node.db b/cats-radio-node.db index e69de29..5cf7cb0 100644 Binary files a/cats-radio-node.db and b/cats-radio-node.db differ diff --git a/src/db.rs b/src/db.rs index 26f0c32..5d0f627 100644 --- a/src/db.rs +++ b/src/db.rs @@ -5,7 +5,8 @@ use sqlx::SqlitePool; #[derive(Clone)] pub struct Database { - pool : SqlitePool + pool : SqlitePool, + num_frames_received : u64, } #[derive(sqlx::FromRow, Debug)] @@ -25,7 +26,16 @@ impl Database { .await .expect("could not run SQLx migrations"); - Self { pool } + let num_frames_received : i64 = sqlx::query_scalar(r#"SELECT COUNT(id) FROM frames_received"#) + .fetch_one(&pool) + .await + .expect("could not count frames"); + + Self { pool, num_frames_received: num_frames_received.try_into().unwrap() } + } + + pub fn get_num_received_frames(&self) -> u64 { + self.num_frames_received } pub async fn store_packet(&mut self, packet: &[u8]) -> anyhow::Result<()> { @@ -41,6 +51,8 @@ impl Database { .await? .last_insert_rowid(); + self.num_frames_received += 1; + debug!("INSERTed row {id}"); Ok(()) } diff --git a/src/main.rs b/src/main.rs index fd4cc03..6b79581 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ struct AppState { conf : config::Config, db : db::Database, transmit_queue : mpsc::Sender>, + start_time : chrono::DateTime, } type SharedState = Arc>; @@ -59,7 +60,8 @@ async fn main() -> std::io::Result<()> { let shared_state = Arc::new(Mutex::new(AppState { conf : conf.clone(), db : db::Database::new().await, - transmit_queue: packet_send.clone(), + transmit_queue : packet_send.clone(), + start_time : chrono::Utc::now(), })); if conf.freq == 0 { diff --git a/src/ui.rs b/src/ui.rs index 2226a43..82c8b5c 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -16,7 +16,7 @@ use tower_http::services::ServeDir; use ham_cats::{ buffer::Buffer, - whisker::Identification, + whisker::{Identification, Destination}, }; use crate::{config, radio::MAX_PACKET_LEN}; @@ -51,6 +51,8 @@ struct DashboardTemplate<'a> { title: &'a str, page: ActivePage, conf: config::Config, + node_startup_time: String, + num_received_frames: u64, packets: Vec, } @@ -64,7 +66,10 @@ struct UIPacket { } async fn dashboard(State(state): State) -> DashboardTemplate<'static> { - let mut db = state.lock().unwrap().db.clone(); + let (conf, mut db, node_startup_time) = { + let st = state.lock().unwrap(); + (st.conf.clone(), st.db.clone(), st.start_time.clone()) + }; let packets = match db.get_most_recent_packets(10).await { Ok(v) => v, @@ -106,9 +111,11 @@ async fn dashboard(State(state): State) -> DashboardTemplate<'stati DashboardTemplate { title: "Dashboard", - conf: state.lock().unwrap().conf.clone(), + conf, page: ActivePage::Dashboard, - packets + num_received_frames : db.get_num_received_frames(), + node_startup_time : node_startup_time.format("%Y-%m-%d %H:%M:%S").to_string(), + packets, } } @@ -144,12 +151,19 @@ async fn send(State(state): State) -> SendTemplate<'static> { } } +#[derive(Deserialize, Debug)] +struct ApiSendPacketDestination { + callsign : String, + ssid : u8, +} + #[derive(Deserialize, Debug)] struct ApiSendPacket { + destinations : Vec, comment : Option, } -fn build_packet(config: config::Config, comment: Option) -> anyhow::Result> { +fn build_packet(config: config::Config, payload: ApiSendPacket) -> anyhow::Result> { let mut buf = [0; crate::radio::MAX_PACKET_LEN]; let mut pkt = ham_cats::packet::Packet::new(&mut buf); pkt.add_identification( @@ -158,11 +172,19 @@ fn build_packet(config: config::Config, comment: Option) -> anyhow::Resu ) .map_err(|e| anyhow!("Could not add identification to packet: {e}"))?; - if let Some(c) = comment { + if let Some(c) = payload.comment { pkt.add_comment(&c) .map_err(|e| anyhow!("Could not add comment to packet: {e}"))?; } + for dest in payload.destinations { + let dest = Destination::new(false, 0, &dest.callsign, dest.ssid) + .ok_or(anyhow!("Cound not create destination"))?; + + pkt.add_destination(dest) + .map_err(|e| anyhow!("Could not add destination to packet: {e}"))?; + } + let mut buf2 = [0; crate::radio::MAX_PACKET_LEN]; let mut data = Buffer::new_empty(&mut buf2); pkt.fully_encode(&mut data) @@ -179,7 +201,7 @@ async fn post_packet(State(state): State, Json(payload): Json { info!("Built packet of {} bytes", p.len()); match transmit_queue.send(p).await { diff --git a/static/main.js b/static/main.js index 8687dc1..ad50976 100644 --- a/static/main.js +++ b/static/main.js @@ -1,13 +1,37 @@ -async function btn_send_packet() { +async function btn_add_destination() { + const template = document.getElementById('destination_template'); + + let clon = template.content.cloneNode(true); + document.getElementById('destinations').appendChild(clon); +} + +async function btn_remove_destination(element_clicked) { + element_clicked.parentElement.remove() +} +async function btn_send_packet() { let data = { 'comment': null, + 'destinations': [], }; if (document.getElementById('with_comment').checked) { data.comment = document.getElementById('whisker_comment').value; } + const destinations = document.getElementById('destinations'); + const destList = destinations.querySelectorAll("p.destination"); + for (let i = 0; i < destList.length; i++) { + const dest_callsign = destList[i].querySelector("input.dest_callsign").value; + const dest_ssid_str = destList[i].querySelector("input.dest_ssid").value; + const dest_ssid = parseInt(dest_ssid_str, 10); + if (dest_ssid < 0 || dest_ssid > 255) { + alert("SSID must be between 0 and 255"); + return; + } + data.destinations.push({'callsign': dest_callsign, 'ssid': dest_ssid}); + } + await post('/api/send_packet', data); } diff --git a/templates/dashboard.html b/templates/dashboard.html index be176c4..fa899ba 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -1,6 +1,11 @@ {% include "head.html" %}

Dashboard

+
+

Statistics

+

This node is up since {{ node_startup_time }}

+

Database contains {{ num_received_frames }} received frames

+

Ten most recent packets

    diff --git a/templates/send.html b/templates/send.html index e287b18..2a669e4 100644 --- a/templates/send.html +++ b/templates/send.html @@ -1,8 +1,28 @@ {% include "head.html" %}

    Send a frame

    +

    + One main feature of CATS is that packets are constructed from Whiskers. + Each Whisker represents one possible attribute of data.

    +

    On this page you can select which whiskers to include in your packet.

    +

    Identification

    +

    {{ conf.callsign }}-{{ conf.ssid }}

    +

    Destination Whisker

    +

    CATS packets can optionally have one or more destinations. + This can be useful for e.g. sending a message to another amateur radio operator, + or for communicating with a service. + The destination consists of a UTF-8 callsign and an SSID byte.

    + +
    +

    Comment Whisker

    -- cgit v1.2.3