gateway_mm/src/main.rs

246 lines
6.9 KiB
Rust

// Copyright (C) 2022 Maxime “pep” Buquet <pep@bouah.net>
//
// 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 <https://www.gnu.org/licenses/>.
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<SyncConnection>,
pub handle: Arc<JoinHandle<()>>,
}
impl DBus {
pub fn connect() -> Result<Self, Error> {
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<DBusPath<'a>>,
{
Proxy::new(MM_NAME, path, TIMEOUT, &*self.conn)
}
}
#[derive(Clone)]
pub struct ModemManager {
dbus: DBus,
}
impl ModemManager {
pub fn connect() -> Result<Self, Error> {
DBus::connect().map(|dbus| Self { dbus })
}
pub async fn modems(&self) -> Result<Vec<Modem>, 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<bool, Error> {
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<String, Error> {
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<i32> 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<String> = args().collect();
if args.len() != 3 {
println!("Usage: {} <jid> <password>", 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;
},
}
}
}