From d384c6f70833c43eb94c7891b2e518807d67e7e5 Mon Sep 17 00:00:00 2001 From: Volkor <me@volkor.me> Date: Thu, 16 Jan 2025 04:11:11 +1100 Subject: [PATCH] add db handling --- Cargo.lock | 162 ++++++++ Cargo.toml | 7 +- README.md | 8 +- config.toml | 28 +- db/migrations/0001_initial.sql | 314 ++++++++++++++++ src/config.rs | 79 +++- src/db.rs | 651 +++++++++++++++++++++++++++++++++ src/main.rs | 93 +++-- src/packets.rs | 101 ++++- 9 files changed, 1384 insertions(+), 59 deletions(-) create mode 100644 db/migrations/0001_initial.sql create mode 100644 src/db.rs diff --git a/Cargo.lock b/Cargo.lock index 1e26c61..e7f6780 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,15 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -76,6 +85,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -153,6 +171,12 @@ dependencies = [ "windows", ] +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + [[package]] name = "byteorder" version = "1.5.0" @@ -324,6 +348,20 @@ dependencies = [ "log", ] +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -491,6 +529,12 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + [[package]] name = "io-kit-sys" version = "0.4.1" @@ -814,6 +858,29 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pear" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.95", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -870,6 +937,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", + "version_check", + "yansi", +] + [[package]] name = "prost" version = "0.11.9" @@ -1072,6 +1152,16 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rusqlite_migration" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923b42e802f7dc20a0a6b5e097ba7c83fe4289da07e49156fecf6af08aa9cd1c" +dependencies = [ + "log", + "rusqlite", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1156,6 +1246,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serialport" version = "4.6.1" @@ -1383,6 +1482,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.41" @@ -1440,6 +1573,15 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unescaper" version = "0.1.5" @@ -1673,6 +1815,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + [[package]] name = "xml-rs" version = "0.8.24" @@ -1683,16 +1834,27 @@ checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" name = "yamm" version = "0.1.0" dependencies = [ + "bincode", "fern", + "figment", "humantime", + "lazy_static", "log", "meshtastic", "rusqlite", + "rusqlite_migration", + "serde", "tokio", "tracing", "tracing-subscriber", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 87e8961..6ee6242 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,9 @@ meshtastic = "0.1.6" tokio = "1.42.0" tracing = "0.1.41" tracing-subscriber = "0.3.19" -rusqlite = { version = "0.32.0", features = ["bundled"] } \ No newline at end of file +rusqlite = { version = "0.32.0", features = ["bundled"] } +rusqlite_migration = "1.3.1" +lazy_static = "1.5.0" +figment = {version = "0.10.19", features = ["toml", "env"]} +serde = "1.0.217" +bincode = "1.3.3" diff --git a/README.md b/README.md index b9c2fe5..f9d7523 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ **Y**et **A**nother **M**eshtastic **M**ap -Inspired slightly by MeshInfo. +Inspired slightly by [MeshInfo](https://github.com/MeshAddicts/meshinfo). ## What it does -- Logs meshtastic broadcasts +- Logs meshtastic packets - Saves them to a database - Serves the data to a future GUI - Serves the data to a public webpage @@ -26,8 +26,8 @@ Inspired slightly by MeshInfo. ## What it doesn't - Runs meshtastic -- Had a GUI (yet) -- Lets you send messages (yet) +- Had a GUI (yet?) +- Lets you send messages (yet?) ## Installation diff --git a/config.toml b/config.toml index 8305c6f..f7875a7 100644 --- a/config.toml +++ b/config.toml @@ -1,18 +1,20 @@ -[config] +[server] +host = "0.0.0.0" +port = 8080 -# Connection Type -# Can be one of the following: Serial, Tcp, Mqtt -connection_type = "Tcp" +[database] +db_name = "yamm.db" -# Serial Port -## If connection_type is Serial, use this device to connect -serial_port = "/dev/something" +[logging] +level = "yamm=debug,rusqlite=debug,meshtastic=debug" -# IP Port -## If connection_type is Tcp, use this ip & port combo to connect -ip_port = "127.0.0.1:4403" +[meshconfig] +# How should we connect to the meshtastic node? (Tcp, Serial, Mqtt) +connection_type = "Tcp" -# MQTT -## If connection_type is MQTT, use this to connect. -### TODO! +## If connection_type is Serial, use this device to connect +serial_port = "Set this from whatever 'available ports' says." +## If connection_type is TCP or MQTT, use this ip & port combo to connect +ip = "192.168.1.128" +port = 4403 \ No newline at end of file diff --git a/db/migrations/0001_initial.sql b/db/migrations/0001_initial.sql new file mode 100644 index 0000000..3e73ef9 --- /dev/null +++ b/db/migrations/0001_initial.sql @@ -0,0 +1,314 @@ +CREATE TABLE MyNodeInfo ( + my_node_num INTEGER PRIMARY KEY, + reboot_count INTEGER NOT NULL, + min_app_version INTEGER NOT NULL +); + +CREATE TABLE Node ( + id INTEGER PRIMARY KEY, -- internal yamm packet numbering + num INT, -- "node number" wtf does that mean??? + user_id TEXT, -- the typical !c6744jd whatever code + user_lname TEXT, -- User's Long name + user_sname TEXT, -- User's Short name + -- mac_addr BLOB -- Deprecated in 2.1.x, esp32 only. + hw_model TEXT, + is_licensed INTEGER, + role TEXT, + latitude_i INTEGER, -- multiply by 1e-7 to get degrees in float. + longitude_i INTEGER, -- multiply by 1e-7 to get degrees in float. + altitude INTEGER, + -- time: INTEGER, -- Disabled, only used for syncing time to note. + location_source TEXT, -- Where does the location come from? + gps_timestamp INTEGER, -- What time does the gps say it is? + altitude_source TEXT, -- Where does the altitude come from? + pdop INTEGER, -- Horizontal, Vertical, and Position Dilution of precision, in 1/100 units. + hdop INTEGER, + vdop INTEGER, + gps_accuracy INTEGER, -- hardware constant in mm, used to calculate true accuracy (default to about 3m) + ground_speed INTEGER, -- in m/s + ground_track INTEGER, -- direction of motion + fix_quality INTEGER, -- GPS Fix (something called NMEA GxGSA Statement) + fix_type INTEGER, -- fix type, whatever that means + sats_in_view INTEGER, -- How many satellites we can see + sensor_id INTEGER, -- Just incase there's multiple gps devices on the node + next_update INTEGER, -- How long until we ask for GPS (in seconds) + seq_number INTEGER, -- Sequence number + precision_bits INTEGER, -- How accurate the position is + snr REAL, -- Signal to Noise ratio of this message, measured by receiver + last_heard INTEGER, -- Last time we heard the message + battery_level INTEGER, -- Battery level, if it has one + voltage REAL, -- Voltage from the power supply + channel_utilization REAL, -- Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise). + air_util_tx REAL, -- Percent of airtime for transmission used within the last hour. + channel INTEGER, -- The local channel we heard the node on. + via_mqtt INTEGER, -- Did we see this node over MQTT instead of LoRA? + hops_away INTEGER -- Number of hops away from us in the network. (0 is direct) +); + +CREATE TABLE Channel ( + channel_index INTEGER PRIMARY KEY, -- Index of the channel + psk BLOB, -- Encryption of the channel + name TEXT, -- <12 bytes channel name. + id INTEGER, -- used to create global unique channel name, (name.id) + uplink_enabled INTEGER, -- Are messages on the mesh sent to MQTT via other nodes? + downlink_enabled INTEGER, -- Are messages seen on the internet sent over LoRA? + position_precision INTEGER, -- How many bits of precision are sent in position packets? + role TEXT -- General 'status' of the channel +); + +CREATE TABLE DeviceConfig ( + role INTEGER, -- Node's Role + serial_enabled INTEGER, -- Disables the SerialConsole + debug_log_enabled INTEGER, -- defaults off after connection + button_gpio INTEGER, -- lets you change the buttons + buzzer_gpio INTEGER, -- pin for the buzzer + rebroadcast_mode INTEGER, -- role of the node + node_info_broadcast_secs INTEGER, + double_tap_as_button_press INTEGER, + is_managed INTEGER, -- if set, harder to config + disable_triple_click INTEGER +); +CREATE TABLE PositionConfig ( + position_broadcast_secs INTEGER, + position_broadcast_smart_enabled INTEGER, + fixed_position INTEGER, + gps_enabled INTEGER, + gps_update_interval INTEGER, + gps_attempt_time INTEGER, + position_flags INTEGER, + rx_gpio INTEGER, + tx_gpio INTEGER, + broadcast_smart_minimum_distance INTEGER, + broadcast_smart_minimum_interval_secs INTEGER, + gps_en_gpio INTEGER, + gps_mode INTEGER +); +CREATE TABLE PowerConfig ( + is_power_saving INTEGER, + on_battery_shutdown_after_secs INTEGER, + adc_multiplier_override REAL, + wait_bluetooth_secs INTEGER, + sds_secs INTEGER, + ls_secs INTEGER, + min_wake_secs INTEGER, + device_battery_ina_address INTEGER +); +CREATE TABLE NetworkConfig ( + wifi_enabled INTEGER, + wifi_ssid TEXT, + wifi_psk TEXT, + ntp_server TEXT, + eth_enabled INTEGER, + address_mode INTEGER, + ip INTEGER, + gateway INTEGER, + subnet INTEGER, + dns INTEGER, + rsyslog_server TEXT +); +CREATE TABLE DisplayConfig ( + screen_on_secs INTEGER, + gps_format INTEGER, + auto_screen_carousel_secs INTEGER, + compass_north_top INTEGER, + flip_screen INTEGER, + units INTEGER, + oled INTEGER, + displaymode INTEGER, + heading_bold INTEGER, + wake_on_tap_or_motion INTEGER +); +CREATE TABLE LoRaConfig ( + use_preset INTEGER, + modem_preset INTEGER, + bandwidth INTEGER, + spread_factor INTEGER, + coding_rate INTEGER, + frequency_offset REAL, + region INTEGER, + hop_limit INTEGER, + tx_enabled INTEGER, + tx_power INTEGER, + channel_num INTEGER, + override_duty_cycle INTEGER, + sx126x_rx_boosted_gain INTEGER, + override_frequency REAL, + ignore_incoming BLOB, + ignore_mqtt INTEGER +); +CREATE TABLE BluetoothConfig ( + enabled INTEGER, + mode INTEGER, + fixed_pin INTEGER +); + +-- Module Configs -- + +-- MQTT +CREATE TABLE MqttConfig ( + enabled INTEGER, + address TEXT, + username TEXT, + password TEXT, + encryption_enabled INTEGER, + json_enabled INTEGER, + tls_enabled INTEGER, + root TEXT, + proxy_to_client_enabled INTEGER, + map_reporting_enabled INTEGER, + publish_interval_secs INTEGER, + position_precision INTEGER +); + +-- Serial +CREATE TABLE SerialConfig ( + enabled INTEGER, + echo INTEGER, + rxd INTEGER, + txd INTEGER, + baud INTEGER, + timeout INTEGER, + mode INTEGER, + override_console_serial_port INTEGER +); + +-- External Notification +CREATE TABLE ExternalNotificationConfig ( + enabled INTEGER, + output_ms INTEGER, + output INTEGER, + output_vibra INTEGER, + output_buzzer INTEGER, + active INTEGER, + alert_message INTEGER, + alert_message_vibra INTEGER, + alert_message_buzzer INTEGER, + alert_bell INTEGER, + alert_bell_vibra INTEGER, + alert_bell_buzzer INTEGER, + use_pwm INTEGER, + nag_timeout INTEGER, + use_i2s_as_buzzer INTEGER +); + +-- Store Forward +CREATE TABLE StoreForwardConfig ( + enabled INTEGER, + heartbeat INTEGER, + records INTEGER, + history_return_max INTEGER, + history_return_window INTEGER +); + +-- Range Test +CREATE TABLE RangeTestConfig ( + enabled INTEGER, + sender INTEGER, + save INTEGER +); + +-- Telemetry +CREATE TABLE TelemetryConfig ( + device_update_interval INTEGER, + environment_update_interval INTEGER, + environment_measurement_enabled INTEGER, + environment_screen_enabled INTEGER, + environment_display_fahrenheit INTEGER, + air_quality_enabled INTEGER, + air_quality_interval INTEGER, + power_measurement_enabled INTEGER, + power_update_interval INTEGER, + power_screen_enabled INTEGER +); + +-- Canned Message +CREATE TABLE CannedMessageConfig ( + rotary1_enabled INTEGER, + inputbroker_pin_a INTEGER, + inputbroker_pin_b INTEGER, + inputbroker_pin_press INTEGER, + inputbroker_event_cw INTEGER, + inputbroker_event_ccw INTEGER, + inputbroker_event_press INTEGER, + updown1_enabled INTEGER, + enabled INTEGER, + allow_input_source TEXT, + send_bell INTEGER +); + +-- Audio +CREATE TABLE AudioConfig ( + codec2_enabled INTEGER, + ptt_pin INTEGER, + bitrate INTEGER, + i2s_ws INTEGER, + i2s_sd INTEGER, + i2s_din INTEGER, + i2s_sck INTEGER +); + +-- Remote Hardware +CREATE TABLE RemoteHardwareConfig ( + enabled INTEGER, + allow_undefined_pin_access INTEGER, + available_pins BLOB -- Use BLOB to store complex structures like a vector of structs +); + +-- NeighborInfo +CREATE TABLE NeighborInfoConfig ( + enabled INTEGER, + update_interval INTEGER +); + +-- AmbientLighting +CREATE TABLE AmbientLightingConfig ( + led_state INTEGER, + led_current INTEGER, + red INTEGER, + green INTEGER, + blue INTEGER +); + +-- Detection Sensor (any gpio pin alarm) +CREATE TABLE DetectionSensorConfig ( + enabled INTEGER, + minimum_broadcast_secs INTEGER, + state_broadcast_secs INTEGER, + send_bell INTEGER, + name TEXT, + monitor_pin INTEGER, + detection_triggered_high INTEGER, + use_pullup INTEGER +); + +-- Pax counter +CREATE TABLE PaxcounterConfig ( + enabled INTEGER, + paxcounter_update_interval INTEGER +); + +-- MeshPacket Main Table +CREATE TABLE MeshPacket ( + primary_id INTEGER PRIMARY KEY AUTOINCREMENT, + message_from INTEGER, + message_to INTEGER, + channel INTEGER, + id INTEGER, -- Unique ID for this packet, but not unique enough to use as a primary key, unfortunately. + rx_time INTEGER, + rx_snr REAL, + hop_limit INTEGER, + want_ack INTEGER, + priority INTEGER, + rx_rssi INTEGER, + delayed INTEGER, + via_mqtt INTEGER, + hop_start INTEGER, + variant_payload_id INTEGER, + FOREIGN KEY (variant_payload_id) REFERENCES VariantPayload(variant_id) +); + +-- VariantPayload Table +CREATE TABLE VariantPayload ( + variant_id INTEGER PRIMARY KEY AUTOINCREMENT, + +); diff --git a/src/config.rs b/src/config.rs index 48882af..5edec01 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,43 @@ -/// Full Config Structure to manage application settings -pub struct Config { +use serde::Deserialize; + +/// Holds the individual config segments. +#[derive(Debug, Clone, Deserialize, Default)] +pub struct AppConfig { + pub server: ServerConfig, + pub database: DatabaseConfig, + pub logging: LoggingConfig, + pub meshconfig: MeshConfig, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ServerConfig { + pub host: String, + pub port: u16, +} + +/// Holds the config for the database +#[derive(Debug, Clone, Deserialize)] +pub struct DatabaseConfig { + pub db_name: String, +} + +/// Holds the config for logging +#[derive(Debug, Clone, Deserialize)] +pub struct LoggingConfig { + pub level: String, +} + +/// Holds the config for the meshtastic node. +#[derive(Debug, Clone, Deserialize)] +pub struct MeshConfig { pub connection_type: ConnectionType, pub serial_port: Option<String>, - pub ip_port: Option<String>, + pub ip: String, + pub port: u16 } -/// Enum to define the connection type -#[derive(Default)] +/// Enum to define the connection type, defaulting to TCP +#[derive(Default, Debug, Clone, Deserialize)] pub enum ConnectionType { #[default] Tcp, @@ -14,3 +45,41 @@ pub enum ConnectionType { Mqtt, } + +// Defaults + +impl Default for ServerConfig { + fn default() -> Self { + Self { + host: "localhost".to_string(), + port: 8080, + } + } +} + +impl Default for DatabaseConfig { + fn default() -> Self { + Self { + db_name: "yamm.db".to_string(), + } + } +} + +impl Default for LoggingConfig { + fn default() -> Self { + Self { + level: "INFO".to_string(), + } + } +} + +impl Default for MeshConfig { + fn default() -> Self { + Self { + connection_type: ConnectionType::Tcp, + serial_port: None, + ip: "192.168.1.2".to_string(), + port: 4403, + } + } +} \ No newline at end of file diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..6d6b918 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,651 @@ +use std::path::{Path, PathBuf}; +use std::fs::File; +use std::io; + +use meshtastic::protobufs::config::{BluetoothConfig, DeviceConfig, DisplayConfig, LoRaConfig, NetworkConfig, PayloadVariant, PositionConfig, PowerConfig}; +use meshtastic::protobufs::module_config::{AmbientLightingConfig, AudioConfig, CannedMessageConfig, DetectionSensorConfig, ExternalNotificationConfig, MqttConfig, NeighborInfoConfig, PaxcounterConfig, RangeTestConfig, RemoteHardwareConfig, SerialConfig, StoreForwardConfig, TelemetryConfig}; +use rusqlite::{Connection, Result}; +use rusqlite_migration::{Migrations, M}; +use serde::{Deserialize, Serialize}; +use tracing::{debug, error, info}; +use lazy_static::lazy_static; +use meshtastic::protobufs::{Channel, MyNodeInfo, NodeInfo}; + +use crate::CONFIG; + +pub(crate) struct Database { + conn: Connection, +} + +// Migrations +lazy_static! { + static ref MIGRATIONS: Migrations<'static> = Migrations::new(vec![ + // Define your migrations here + M::up(include_str!("../db/migrations/0001_initial.sql")), + ]); +} + +fn u32_vec_to_u8_vec(vec: &Vec<u32>) -> Vec<u8> { + vec.iter() + .flat_map(|x| x.to_be_bytes().to_vec()) + .collect() +} + +#[derive(Serialize, Deserialize)] +struct RemoteHardwarePin { + gpio_pin: u32, + name: String, + r#type: i32, +} + +/// This contains all the code for reading and writing from the database. +/// Holy shit levels of boilerplate. +/// This is done like this instead of just saving BLOBs because I really want to use SQL itself to quickly query data. +/// Any parsing/indexing/long term statistics code I write isn't going to be even close to the level of optimisation +/// that rusqlite has. +impl Database { + /// Opens the database, running migrations and creating if it doesn't exist. + pub fn open() -> Result<Self> { + let db_path = PathBuf::from(&CONFIG.database.db_name); + // Pre-check, does the file exist? + if !db_path.exists() { + // If not, create the file. + match File::create(&db_path) { + Ok(_) => info!("Created database stub file."), + Err(e) => error!("Failed to create file: {:?}", e), + } + } + + // Open it up, and run migrations! + let mut conn = Connection::open(&db_path)?; + // Apply migrations + MIGRATIONS.to_latest(&mut conn).expect("Migration failed"); + Ok(Database { conn }) + } + + /// Updates the database with a new MyNodeInfo packet + pub fn update_mynodeinfo(my_node_info: &MyNodeInfo) -> Result<bool> { + // Open DB + let db = Database::open().expect("Failure to open database"); + + db.conn.execute( + "INSERT INTO MyNodeInfo (my_node_num, reboot_count, min_app_version) VALUES (?1, ?2, ?3)", + rusqlite::params![my_node_info.my_node_num, my_node_info.reboot_count, my_node_info.min_app_version], + )?; + debug!("Saved MyNodeInfo to database"); + Ok(true) + } + + /// Updates the database with a new NodeInfo packet + pub fn update_nodeinfo(node_info: &NodeInfo) -> Result<bool> { + // Open DB + let db = Database::open().expect("Failure to open database"); + + db.conn.execute( + "INSERT INTO Node ( + num, user_id, user_lname, user_sname, hw_model, is_licensed, role, + latitude_i, longitude_i, altitude, location_source, gps_timestamp, + altitude_source, pdop, hdop, vdop, gps_accuracy, ground_speed, + ground_track, fix_quality, fix_type, sats_in_view, sensor_id, + next_update, seq_number, precision_bits, snr, last_heard, + battery_level, voltage, channel_utilization, air_util_tx, + channel, via_mqtt, hops_away + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, + ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, + ?27, ?28, ?29, ?30, ?31, ?32, ?33, ?34, ?35 + )", + rusqlite::params![ + node_info.num, + // This is gibberish to me, so lets explain. + // We're mapping the value user.id as the value, but chucking a NULL in if it's a None type. + node_info.user.as_ref().map(|user| user.id.clone()), + node_info.user.as_ref().map(|user| user.long_name.clone()), + node_info.user.as_ref().map(|user| user.short_name.clone()), + node_info.user.as_ref().map(|user| user.hw_model), + node_info.user.as_ref().map(|user| user.is_licensed), + node_info.user.as_ref().map(|user| user.role), + node_info.position.as_ref().map(|position| position.latitude_i), + node_info.position.as_ref().map(|position| position.longitude_i), + node_info.position.as_ref().map(|position| position.altitude), + node_info.position.as_ref().map(|position| position.location_source), + node_info.position.as_ref().map(|position| position.timestamp), + node_info.position.as_ref().map(|position| position.altitude_source), + node_info.position.as_ref().map(|position| position.pdop), + node_info.position.as_ref().map(|position| position.hdop), + node_info.position.as_ref().map(|position| position.vdop), + node_info.position.as_ref().map(|position| position.gps_accuracy), + node_info.position.as_ref().map(|position| position.ground_speed), + node_info.position.as_ref().map(|position| position.ground_track), + node_info.position.as_ref().map(|position| position.fix_quality), + node_info.position.as_ref().map(|position| position.fix_type), + node_info.position.as_ref().map(|position| position.sats_in_view), + node_info.position.as_ref().map(|position| position.sensor_id), + node_info.position.as_ref().map(|position| position.next_update), + node_info.position.as_ref().map(|position| position.seq_number), + node_info.position.as_ref().map(|position| position.precision_bits), + node_info.snr, + node_info.last_heard, + node_info.device_metrics.as_ref().map(|device_metrics| device_metrics.battery_level), + node_info.device_metrics.as_ref().map(|device_metrics| device_metrics.voltage), + node_info.device_metrics.as_ref().map(|device_metrics| device_metrics.channel_utilization), + node_info.device_metrics.as_ref().map(|device_metrics| device_metrics.air_util_tx), + node_info.channel, + node_info.via_mqtt, + node_info.hops_away + ] + )?; + debug!("Saved NodeInfo to database"); + Ok(true) + } + + /// Updates the database with a new Channel packet + pub fn update_channel(channel: &Channel) -> Result<bool> { + // Open DB + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO Channel ( + channel_index, psk, name, id, uplink_enabled, downlink_enabled, position_precision, role + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8 + )", + rusqlite::params![ + channel.index, + channel.settings.as_ref().map(|settings| settings.psk.clone()), + channel.settings.as_ref().map(|settings| settings.name.clone()), + channel.settings.as_ref().map(|settings| settings.id), + channel.settings.as_ref().map(|settings| settings.uplink_enabled), + channel.settings.as_ref().map(|settings| settings.downlink_enabled), + channel.settings.as_ref().and_then(|settings| settings.module_settings.as_ref()).map(|module_settings| module_settings.position_precision), + channel.role + ] + )?; + debug!("Saved Channel to database"); + Ok(true) + } + + pub fn update_deviceconfig(config: &DeviceConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO DeviceConfig ( + role, serial_enabled, debug_log_enabled, button_gpio, buzzer_gpio, rebroadcast_mode, + node_info_broadcast_secs, double_tap_as_button_press, is_managed, disable_triple_click + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10 + )", + rusqlite::params![ + config.role, + config.serial_enabled, + config.debug_log_enabled, + config.button_gpio, + config.buzzer_gpio, + config.rebroadcast_mode, + config.node_info_broadcast_secs, + config.double_tap_as_button_press, + config.is_managed, + config.disable_triple_click + ] + )?; + debug!("Saved DeviceConfig to database"); + Ok(true) + } + + pub fn update_position_config(config: &PositionConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO PositionConfig ( + position_broadcast_secs, position_broadcast_smart_enabled, fixed_position, gps_enabled, + gps_update_interval, gps_attempt_time, position_flags, rx_gpio, tx_gpio, + broadcast_smart_minimum_distance, broadcast_smart_minimum_interval_secs, gps_en_gpio, + gps_mode + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13 + )", + rusqlite::params![ + config.position_broadcast_secs, + config.position_broadcast_smart_enabled, + config.fixed_position, + config.gps_enabled, + config.gps_update_interval, + config.gps_attempt_time, + config.position_flags, + config.rx_gpio, + config.tx_gpio, + config.broadcast_smart_minimum_distance, + config.broadcast_smart_minimum_interval_secs, + config.gps_en_gpio, + config.gps_mode + ] + )?; + debug!("Saved PositionConfig to database"); + Ok(true) + } + + pub fn update_power_config(config: &PowerConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO PowerConfig ( + is_power_saving, on_battery_shutdown_after_secs, adc_multiplier_override, wait_bluetooth_secs, + sds_secs, ls_secs, min_wake_secs, device_battery_ina_address + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8 + )", + rusqlite::params![ + config.is_power_saving, + config.on_battery_shutdown_after_secs, + config.adc_multiplier_override, + config.wait_bluetooth_secs, + config.sds_secs, + config.ls_secs, + config.min_wake_secs, + config.device_battery_ina_address + ] + )?; + debug!("Saved PowerConfig to database"); + Ok(true) + } + + pub fn update_network_config(config: &NetworkConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO NetworkConfig ( + wifi_enabled, wifi_ssid, wifi_psk, ntp_server, eth_enabled, address_mode, + ip, gateway, subnet, dns, rsyslog_server + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11 + )", + rusqlite::params![ + config.wifi_enabled, + config.wifi_ssid, + config.wifi_psk, + config.ntp_server, + config.eth_enabled, + config.address_mode, + config.ipv4_config.as_ref().map(|ipv4_config| ipv4_config.ip), + config.ipv4_config.as_ref().map(|ipv4_config| ipv4_config.gateway), + config.ipv4_config.as_ref().map(|ipv4_config| ipv4_config.subnet), + config.ipv4_config.as_ref().map(|ipv4_config| ipv4_config.dns), + config.rsyslog_server + ] + )?; + debug!("Saved NetworkConfig to database"); + Ok(true) + } + + + pub fn update_display_config(config: &DisplayConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO DisplayConfig ( + screen_on_secs, gps_format, auto_screen_carousel_secs, compass_north_top, + flip_screen, units, oled, displaymode, heading_bold, wake_on_tap_or_motion + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10 + )", + rusqlite::params![ + config.screen_on_secs, + config.gps_format, + config.auto_screen_carousel_secs, + config.compass_north_top, + config.flip_screen, + config.units, + config.oled, + config.displaymode, + config.heading_bold, + config.wake_on_tap_or_motion + ] + )?; + debug!("Saved DisplayConfig to database"); + Ok(true) + } + + pub fn update_lora_config(config: &LoRaConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO LoRaConfig ( + use_preset, modem_preset, bandwidth, spread_factor, coding_rate, + frequency_offset, region, hop_limit, tx_enabled, tx_power, + channel_num, override_duty_cycle, sx126x_rx_boosted_gain, + override_frequency, ignore_incoming, ignore_mqtt + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16 + )", + rusqlite::params![ + config.use_preset, + config.modem_preset, + config.bandwidth, + config.spread_factor, + config.coding_rate, + config.frequency_offset, + config.region, + config.hop_limit, + config.tx_enabled, + config.tx_power, + config.channel_num, + config.override_duty_cycle, + config.sx126x_rx_boosted_gain, + config.override_frequency, + // Rusqlite can't store a Vec<u32>, so we need to make it into Vec<u8> + u32_vec_to_u8_vec(&config.ignore_incoming), + config.ignore_mqtt + ] + )?; + debug!("Saved LoRaConfig to database"); + Ok(true) + } + + pub fn update_bluetooth_config(config: &BluetoothConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO BluetoothConfig ( + enabled, mode, fixed_pin + ) VALUES ( + ?1, ?2, ?3 + )", + rusqlite::params![ + config.enabled, + config.mode, + config.fixed_pin + ] + )?; + debug!("Saved BluetoothConfig to database"); + Ok(true) + } + + pub fn update_mqtt_config(config: &MqttConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO MqttConfig ( + enabled, address, username, password, encryption_enabled, json_enabled, + tls_enabled, root, proxy_to_client_enabled, map_reporting_enabled, + publish_interval_secs, position_precision + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12 + )", + rusqlite::params![ + config.enabled, + config.address, + config.username, + config.password, + config.encryption_enabled, + config.json_enabled, + config.tls_enabled, + config.root, + config.proxy_to_client_enabled, + config.map_reporting_enabled, + config.map_report_settings.as_ref().map(|map_report_settings| map_report_settings.publish_interval_secs), + config.map_report_settings.as_ref().map(|map_report_settings| map_report_settings.position_precision) + ] + )?; + debug!("Saved MqttConfig to database"); + Ok(true) + } + + pub fn update_serial_config(config: &SerialConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO SerialConfig ( + enabled, echo, rxd, txd, baud, timeout, mode, override_console_serial_port + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9 + )", + rusqlite::params![ + config.enabled, + config.echo, + config.rxd, + config.txd, + config.baud, + config.timeout, + config.mode, + config.override_console_serial_port + ] + )?; + debug!("Saved SerialConfig to database"); + Ok(true) + } + + pub fn update_externalnotification_config(config: &ExternalNotificationConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO ExternalNotificationConfig ( + enabled, output_ms, output, output_vibra, output_buzzer, active, alert_message, + alert_message_vibra, alert_message_buzzer, alert_bell, alert_bell_vibra, alert_bell_buzzer, + use_pwm, nag_timeout, use_i2c_as_buzzer + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15 + )", + rusqlite::params![ + config.enabled, + config.output_ms, + config.output_vibra, + config.output_buzzer, + config.active, + config.alert_message, + config.alert_message_vibra, + config.alert_message_buzzer, + config.alert_bell, + config.alert_bell_vibra, + config.alert_bell_buzzer, + config.use_pwm, + config.nag_timeout, + config.use_i2s_as_buzzer + ] + )?; + debug!("Saved ExternalNotificationConfig to database"); + Ok(true) + } + + pub fn update_storeforward_config(config: &StoreForwardConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO StoreForwardConfig ( + enabled, heartbeat, records, history_return_max, history_return_window + ) VALUES ( + ?1, ?2, ?3, ?4, ?5 + )", + rusqlite::params![ + config.enabled, + config.heartbeat, + config.records, + config.history_return_max, + config.history_return_window + ] + )?; + debug!("Saved StoreForwardConfig to database"); + Ok(true) + } + + pub fn update_rangetest_config(config: &RangeTestConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO RangeTestConfig ( + enabled, sender, save + ) VALUES ( + ?1, ?2, ?3 + )", + rusqlite::params![ + config.enabled, + config.sender, + config.save + ] + )?; + debug!("Saved RangeTestConfig to database"); + Ok(true) + } + + pub fn update_telemetry_config(config: &TelemetryConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO TelemetryConfig ( + device_update_interval, environment_update_interval, environment_measurement_enabled, + environment_screen_enabled, environment_display_fahrenheit, air_quality_enabled, + air_quality_interval, power_measurement_enabled, power_update_interval + power_screen_enabled + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10 + )", + rusqlite::params![ + config.device_update_interval, + config.environment_update_interval, + config.environment_measurement_enabled, + config.environment_screen_enabled, + config.environment_display_fahrenheit, + config.air_quality_enabled, + config.air_quality_interval, + config.power_measurement_enabled, + config.power_update_interval, + config.power_screen_enabled + ] + )?; + debug!("Saved TelemetryConfig to database"); + Ok(true) + } + + pub fn update_cannedmessage_config(config: &CannedMessageConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO CannedMessageConfig ( + rotary1_enabled, inputbroker_pin_a, inputbroker_pin_b, inputbroker_pin_press, + inputbroker_event_cw, inputbroker_event_ccw, inputbroker_event_press, + updown1_enabled, enabled, allow_input_source, send_bell + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11 + )", + rusqlite::params![ + config.rotary1_enabled, + config.inputbroker_pin_a, + config.inputbroker_pin_b, + config.inputbroker_pin_press, + config.inputbroker_event_cw, + config.inputbroker_event_ccw, + config.inputbroker_event_press, + config.updown1_enabled, + config.enabled, + config.allow_input_source, + config.send_bell + ] + )?; + debug!("Saved CannedMessageConfig to database"); + Ok(true) + } + + pub fn update_audio_config(config: &AudioConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO AudioConfig ( + codec2_enabled, ptt_pin, bitrate, i2s_ws, i2s_sd, i2s_din, i2c_sck + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7 + )", + rusqlite::params![ + config.codec2_enabled, + config.ptt_pin, + config.bitrate, + config.i2s_ws, + config.i2s_sd, + config.i2s_din, + config.i2s_sck + ] + )?; + debug!("Saved AudioConfig to database"); + Ok(true) + } + + pub fn update_remotehardware_config(config: &RemoteHardwareConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + let serialized_pins = bincode::serialize(&config.available_pins).unwrap_or(Vec::new()); + db.conn.execute( + "INSERT INTO RemoteHardwareConfig ( + enabled, allow_undefined_pin_access, available_pins + ) VALUES ( + ?1, ?2, ?3 + )", + rusqlite::params![ + config.enabled, + config.allow_undefined_pin_access, + serialized_pins + ] + )?; + debug!("Saved RemoteHardwareConfig to database"); + Ok(true) + } + + pub fn update_neighbourinfo_config(config: &NeighborInfoConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO NeighborInfoConfig ( + enabled, update_interval + ) VALUES ( + ?1, ?2 + )", + rusqlite::params![ + config.enabled, + config.update_interval + ] + )?; + debug!("Saved NeighborInfoConfig to database"); + Ok(true) + } + + pub fn update_ambientlighting_config(config: &AmbientLightingConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO AmbientLightingConfig ( + led_state, led_current, red, green, blue + ) VALUES ( + ?1, ?2, ?3, ?4, ?5 + )", + rusqlite::params![ + config.led_state, + config.current, + config.red, + config.green, + config.blue + ] + )?; + debug!("Saved AmbientLightingConfig to database"); + Ok(true) + } + + pub fn update_detectionsensor_config(config: &DetectionSensorConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO DetectionSensorConfig ( + enabled, minimum_broadcast_secs, state_broadcast_secs, send_bell, + name, monitor_pin, detection_triggered_high, use_pullup + + ) VALUES ( + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8 + )", + rusqlite::params![ + config.enabled, + config.minimum_broadcast_secs, + config.state_broadcast_secs, + config.send_bell, + config.name, + config.monitor_pin, + config.detection_triggered_high, + config.use_pullup + ] + )?; + debug!("Saved DetectionSensorConfig to database"); + Ok(true) + } + + pub fn update_paxcounter_config(config: &PaxcounterConfig) -> Result<bool> { + let db = Database::open().expect("Failure to open database"); + db.conn.execute( + "INSERT INTO PaxcounterConfig ( + enabled, paxcounter_update_interval + ) VALUES ( + ?1, ?2 + )", + rusqlite::params![ + config.enabled, + config.paxcounter_update_interval + ] + )?; + debug!("Saved PaxcounterConfig to database"); + Ok(true) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4cd35b6..cdc492f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,36 @@ +use figment::{Figment, providers::{Format, Toml, Env}}; -use std::io::{self, BufRead}; -use std::time::SystemTime; - -use meshtastic::api::StreamApi; +use lazy_static::lazy_static; +use meshtastic::api::{StreamApi, StreamHandle}; use meshtastic::utils; use meshtastic::protobufs::*; +use packets::parse; +use tokio::{net::TcpStream, sync::mpsc::UnboundedReceiver}; use tracing::{debug, error, info, Level}; -use tracing_subscriber::{fmt, FmtSubscriber}; -use tracing_subscriber::prelude::*; +use tracing_subscriber::FmtSubscriber; mod nodes; mod packets; +mod db; +mod config; +use crate::config::AppConfig; + +// Setup the config globally because I can't figure out how to pass it to functions I don't call directly. +// This is evaluated at runtime, and not compilation. \o/ +// Also, we try to merge configs. so envvars > config > defaults > error +lazy_static! { + pub static ref CONFIG: AppConfig = { + let config = Figment::new() + .merge(Toml::file("config.toml")) + .merge(Env::prefixed("yamm_")) + .extract() + .unwrap_or_default(); + tracing::info!("Loaded Config!"); + tracing::debug!("full config: {:?}", config); + config + }; +} #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { @@ -23,37 +42,53 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { tracing::subscriber::set_global_default(subscriber) .expect("setting default subscriber failed"); + // Hello World! info!("Y(et) A(nother) M(eshtastic) M(ap) - v0.1.0"); // set up new connection - // TODO: setup multiple types let stream_api = StreamApi::new(); - // Set Address and port - // TODO: read from config - let address = "127.0.0.1:4403".to_string(); - - // Connect over TCP - let tcp_stream = utils::stream::build_tcp_stream(address).await?; - let (mut decoded_listener, stream_api) = stream_api.connect(tcp_stream).await; - let config_id = utils::generate_rand_id(); - let stream_api = stream_api.configure(config_id).await?; - - // Read loop, exit with Ctrl+C or disconnecting node. - while let Some(decoded) = decoded_listener.recv().await { - // New Message! - debug!("Received: {:?}", decoded); - // Parse message - + // Different connection types have different setups. + let mut tcp_stream: StreamHandle<TcpStream>; + //let mut serial_stream: StreamHandle<SerialStream>; // SerialStream isn't a valid type??? + match &CONFIG.meshconfig.connection_type { + config::ConnectionType::Tcp => { + // Build the tcp stream object from config. + let address = &CONFIG.meshconfig.ip; + let port = &CONFIG.meshconfig.port.to_string(); + let tcp = format!("{}:{}", address, port); - // Save to DB + // Connect over TCP + let tcp_stream = utils::stream::build_tcp_stream(tcp).await?; + let (mut decoded_listener, stream_api) = stream_api.connect(tcp_stream).await; + let config_id = utils::generate_rand_id(); + let stream_api = stream_api.configure(config_id).await?; + + // Read loop, exit with Ctrl+C or disconnecting node. + while let Some(decoded) = decoded_listener.recv().await { + // New Message! + debug!("Received: {:?}", decoded); + // Parse message + parse(&decoded); + } + + // Note that in this specific example, this will only be called when + // the radio is disconnected, as the above loop will never exit. + // Typically you would allow the user to manually kill the loop, + // for example with tokio::select!. + let _stream_api = stream_api.disconnect().await?; + }, + config::ConnectionType::Serial => { + let available_ports = utils::stream::available_serial_ports()?; + info!("Available ports, please set in config.: {:?}", available_ports); + todo!("Not yet Implemented. See https://git.volkor.me/Volkor/yamm/-/issues/2") + }, + config::ConnectionType::Mqtt => { + todo!("Not yet Implemented. See https://git.volkor.me/Volkor/yamm/-/issues/3") + }, } - // Note that in this specific example, this will only be called when - // the radio is disconnected, as the above loop will never exit. - // Typically you would allow the user to manually kill the loop, - // for example with tokio::select!. - let _stream_api = stream_api.disconnect().await?; + Ok(()) } \ No newline at end of file diff --git a/src/packets.rs b/src/packets.rs index 78a4c36..eab690a 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -1,39 +1,51 @@ +use std::path::Path; + +use config::device_config; use from_radio::PayloadVariant; use meshtastic::protobufs::*; use tracing::{debug, error}; +use crate::db::Database; + /// Parses all incoming packets, handing them out to the correct handler. pub fn parse(message: &FromRadio) -> bool { - debug!("Parsing Message from radio"); + // debug!("Parsing Message from radio: {:?}", &message); // Handle all types of messages from the radio here. match message.payload_variant { // A normal message Some(PayloadVariant::Packet(ref packet)) => { + debug!("New Message Packet!"); parse_packet(packet); }, // Information about the local node Some(PayloadVariant::MyInfo(ref info)) => { + debug!("New MyNodeInfo Packet"); parse_myinfo(info); }, // Information about a remote node Some(PayloadVariant::NodeInfo(ref node_info)) => { + debug!("New NodeInfo Packet!"); parse_nodeinfo(node_info); }, // Information about the local node's config Some(PayloadVariant::Config(ref config)) => { + debug!("New Config Packet!"); parse_config(config); }, // Information about the local node's module config Some(PayloadVariant::ModuleConfig(ref module_config)) => { + debug!("New ModuleConfig Packet!"); parse_module_config(module_config); }, // Information about one of the 8 channels in (potential) use. Some(PayloadVariant::Channel(ref channel_info)) => { + debug!("New Channel Packet!"); parse_channel_info(channel_info); }, // Information about the queue (messages to send soon) Some(PayloadVariant::QueueStatus(ref queue_status)) => { + debug!("New Message Packet!"); parse_queue_info(queue_status); }, @@ -49,35 +61,110 @@ pub fn parse(message: &FromRadio) -> bool { fn parse_packet(packet: &MeshPacket) { // Handle packet data + debug!("Parsing MeshPacket Packet: {:#?}", &packet); todo!() } fn parse_myinfo(packet: &MyNodeInfo) { - // Check the last MyNodeInfo packet from the DB. If it's different, save it. - todo!() + debug!("Parsing MyNodeInfo Packet: {:#?}", &packet); + let _ = Database::update_mynodeinfo(packet); } fn parse_nodeinfo(node_info: &NodeInfo) { // Handle NodeInfo data - todo!() + debug!("Parsing MyNodeInfo Packet: {:#?}", &node_info); + let _ = Database::update_nodeinfo(node_info); } fn parse_config(config: &Config) { // Handle Config data - todo!() + debug!("Parsing Config Packet: {:#?}", &config); + + // Match and destructure the payload_variant + if let Some(payload_variant) = config.payload_variant.clone() { + match payload_variant { + config::PayloadVariant::Device(device_config) => { + let _ = Database::update_deviceconfig(&device_config); + }, + config::PayloadVariant::Position(position_config) => { + let _ = Database::update_position_config(&position_config); + }, + config::PayloadVariant::Power(power_config) => { + let _ = Database::update_power_config(&power_config); + }, + config::PayloadVariant::Network(network_config) => { + let _ = Database::update_network_config(&network_config); + }, + config::PayloadVariant::Display(display_config) => { + let _ = Database::update_display_config(&display_config); + }, + config::PayloadVariant::Lora(lora_config) => { + let _ = Database::update_lora_config(&lora_config); + }, + config::PayloadVariant::Bluetooth(bluetooth_config) => { + let _ = Database::update_bluetooth_config(&bluetooth_config); + }, + } + } } fn parse_module_config(module_config: &ModuleConfig) { + debug!("Parsing Module Packet: {:#?}", &module_config); // Handle ModuleConfig data - todo!() + // Match and destructure the payload_variant + if let Some(payload_variant) = module_config.payload_variant.clone() { + match payload_variant { + module_config::PayloadVariant::Mqtt(mqtt_config) => { + let _ = Database::update_mqtt_config(&mqtt_config); + }, + module_config::PayloadVariant::Serial(serial_config) => { + let _ = Database::update_serial_config(&serial_config); + }, + module_config::PayloadVariant::ExternalNotification(external_notification_config) => { + let _ = Database::update_externalnotification_config(&external_notification_config); + }, + module_config::PayloadVariant::StoreForward(store_forward_config) => { + let _ = Database::update_storeforward_config(&store_forward_config); + }, + module_config::PayloadVariant::RangeTest(range_test_config) => { + let _ = Database::update_rangetest_config(&range_test_config); + }, + module_config::PayloadVariant::Telemetry(telemetry_config) => { + let _ = Database::update_telemetry_config(&telemetry_config); + }, + module_config::PayloadVariant::CannedMessage(canned_message_config) => { + let _ = Database::update_cannedmessage_config(&canned_message_config); + }, + module_config::PayloadVariant::Audio(audio_config) => { + let _ = Database::update_audio_config(&audio_config); + }, + module_config::PayloadVariant::RemoteHardware(remote_hardware_config) => { + let _ = Database::update_remotehardware_config(&remote_hardware_config); + }, + module_config::PayloadVariant::NeighborInfo(neighbor_info_config) => { + let _ = Database::update_neighbourinfo_config(&neighbor_info_config); + }, + module_config::PayloadVariant::AmbientLighting(ambient_lighting_config) => { + let _ = Database::update_ambientlighting_config(&ambient_lighting_config); + }, + module_config::PayloadVariant::DetectionSensor(detection_sensor_config) => { + let _ = Database::update_detectionsensor_config(&detection_sensor_config); + }, + module_config::PayloadVariant::Paxcounter(paxcounter_config) => { + let _ = Database::update_paxcounter_config(&paxcounter_config); + }, + } + } } fn parse_channel_info(channel_info: &Channel) { + debug!("Parsing Channel Packet: {:#?}", &channel_info); // Handle ChannelInfo data - todo!() + let _ = Database::update_channel(channel_info); } fn parse_queue_info(queue_info: &QueueStatus) { + debug!("Parsing Queue Packet: {:#?}", &queue_info); // Handle QueueInfo data todo!() } \ No newline at end of file -- GitLab