// Copyright (C) 2022 Maxime “pep” Buquet // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Affero General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License // for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . pub mod error; pub mod mm; use error::Error; use mm::modem::Modem as ModemAccess; use std::fmt; use std::sync::Arc; use std::str::FromStr; use std::env::args; use env_logger; use futures_util::StreamExt; use tokio::{ task::JoinHandle, time::Duration, }; use mmdbus::{ dbus::{ message::MatchRule, nonblock::{ SyncConnection, Proxy, stdintf::org_freedesktop_dbus::ObjectManager, }, strings::{Path as DBusPath}, }, }; use dbus_tokio::connection; use xmpp::{ClientBuilder, ClientType}; use xmpp_parsers::{ Jid, BareJid, message::MessageType, }; // The following source has been used as an example: // https://github.com/soerenmeier/linux-info/blob/master/src/network/modem_manager.rs const MM_NAME: &str = "org.freedesktop.ModemManager1"; const MM_PATH: &str = "/org/freedesktop/ModemManager1"; const TIMEOUT: Duration = Duration::from_secs(5); #[derive(Clone)] pub struct DBus { pub conn: Arc, pub handle: Arc>, } impl DBus { pub fn connect() -> Result { let (resource, conn) = connection::new_system_sync()?; let handle: JoinHandle<()> = tokio::spawn(async { let err = resource.await; panic!("Lost connection to D-Bus: {}", err); }); Ok(Self { conn, handle: Arc::new(handle) }) } pub fn proxy<'a, 'b, P>(&'b self, path: P) -> Proxy<'a, &'b SyncConnection> where P: Into>, { Proxy::new(MM_NAME, path, TIMEOUT, &*self.conn) } } #[derive(Clone)] pub struct ModemManager { dbus: DBus, } impl ModemManager { pub fn connect() -> Result { DBus::connect().map(|dbus| Self { dbus }) } pub async fn modems(&self) -> Result, Error> { let objects = self.dbus.proxy(MM_PATH).get_managed_objects().await?; let modems = objects .into_iter() .map(|(path, _)| { Modem { dbus: self.dbus.clone(), path, } }) .collect(); Ok(modems) } } #[derive(Clone)] pub struct Modem { dbus: DBus, pub path: DBusPath<'static>, } impl fmt::Debug for Modem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Modem {{ {}, {:?}, .. }}", self.path, "1234") } } impl Modem { pub async fn enabled(&self) -> Result { let state: ModemState = ModemAccess::state(&self.dbus.proxy(&self.path)).await.map(Into::into)?; Ok(match state { ModemState::Enabling | ModemState::Enabled | ModemState::Searching | ModemState::Registered | ModemState::Connecting | ModemState::Connected => true, _ => false, }) } pub async fn model(&self) -> Result { ModemAccess::model(&self.dbus.proxy(&self.path)).await.map_err(Error::DBusError) } } #[repr(i32)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ModemState { /// The modem is unusable. Failed = -1, /// State unknown or not reportable. Unknown = 0, /// The modem is currently being initialized. Initializing = 1, /// The modem needs to be unlocked. Locked = 2, /// The modem is not enabled and is powered down. Disabled = 3, /// The modem is currently transitioning to the MM_MODEM_STATE_DISABLED /// state. Disabling = 4, /// The modem is currently transitioning to the MM_MODEM_STATE_ENABLED /// state. Enabling = 5, /// The modem is enabled and powered on but not registered with a network /// provider and not available for data connections. Enabled = 6, /// The modem is searching for a network provider to register with. Searching = 7, /// The modem is registered with a network provider, and data connections /// and messaging may be available for use. Registered = 8, /// The modem is disconnecting and deactivating the last active packet data /// bearer. This state will not be entered if more than one packet data /// bearer is active and one of the active bearers is deactivated. Disconnecting = 9, /// The modem is activating and connecting the first packet data bearer. /// Subsequent bearer activations when another bearer is already active /// do not cause this state to be entered. Connecting = 10, /// One or more packet data bearers is active and connected. Connected = 11, } impl From for ModemState { fn from(num: i32) -> Self { if num < -1 || num > 11 { Self::Unknown } else { unsafe { *(&num as *const i32 as *const Self) } } } } async fn print_foo(mm: ModemManager) -> Result<(), Error> { let modems = mm.modems().await?; println!("Modems: {:?}", modems); for modem in modems { println!("Modem: {:?}", modem); println!("Enabled: {:?}", modem.enabled().await); println!("Model: {:?}", modem.model().await); } Ok(()) } #[tokio::main] pub async fn main() -> Result<(), Error> { env_logger::init(); let args: Vec = args().collect(); if args.len() != 3 { println!("Usage: {} ", args[0]); return Err(Error::InvalidArgs) } // ModemManager let dbus = DBus::connect()?; let mut interval = tokio::time::interval(Duration::from_secs(2)); let mr = MatchRule::new_signal("org.freedesktop.DBus.Properties", "PropertiesChanged"); // XMPP let jid_str = args[1].as_str(); let jid = Jid::Bare(BareJid::from_str(jid_str).unwrap()); let passwd = args[2].as_str(); // Client instance let mut xmpp = ClientBuilder::new(jid_str, passwd) .set_client(ClientType::Bot, "gateway_mm") .set_website("https://codeberg.org/pep./gateway_mm") .build() .unwrap(); loop { tokio::select! { events = xmpp.wait_for_events() => { println!("Events: {:?}", events); }, _ = interval.tick() => { let mm = ModemManager { dbus: dbus.clone() }; let _ = print_foo(mm).await; }, } } }