Initial commit. Sync connection, send SMS; read existing SMSs
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
commit
ff29c267a8
2 changed files with 326 additions and 0 deletions
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "gateway_mm"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Maxime “pep” Buquet <pep@bouah.net>"]
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
edition = "2021"
|
||||||
|
categories = ["gateway", "bridge", "transport", "modemmanager"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mmdbus = "1.18.6"
|
316
src/main.rs
Normal file
316
src/main.rs
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use mmdbus::{
|
||||||
|
dbus::{
|
||||||
|
arg::{PropMap, RefArg, Variant},
|
||||||
|
blocking::{Connection, stdintf::org_freedesktop_dbus::ObjectManager, Proxy},
|
||||||
|
// channel::MatchingReceiver,
|
||||||
|
// message::{MatchRule, Message},
|
||||||
|
strings::{Path as DBusPath}, // Interface, Member,
|
||||||
|
// Error as DBusError,
|
||||||
|
},
|
||||||
|
modem::Modem as ModemAccess,
|
||||||
|
modem_messaging::ModemMessaging,
|
||||||
|
sms::Sms as SmsAccess,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
const SMSC: &str = "+33609001390"; // SFR
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DBus {
|
||||||
|
pub conn: Arc<Connection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DBus {
|
||||||
|
pub fn connect() -> Result<Self, Box<dyn StdError>> {
|
||||||
|
Ok(Connection::new_system()
|
||||||
|
.map(Arc::new)
|
||||||
|
.map(|conn| Self { conn })?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn proxy<'a, 'b, P>(&'b self, path: P) -> Proxy<'a, &'b Connection>
|
||||||
|
where
|
||||||
|
P: Into<DBusPath<'a>>,
|
||||||
|
{
|
||||||
|
self.conn.with_proxy(MM_NAME, path, TIMEOUT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ModemManager {
|
||||||
|
dbus: DBus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModemManager {
|
||||||
|
pub fn connect() -> Result<Self, Box<dyn StdError>> {
|
||||||
|
Ok(DBus::connect().map(|dbus| Self { dbus })?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modems(&self) -> Result<Vec<Modem>, Box<dyn StdError>> {
|
||||||
|
let objects = self.dbus.proxy(MM_PATH).get_managed_objects()?;
|
||||||
|
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 {
|
||||||
|
let numbers = self.own_numbers().unwrap_or(vec![String::new()]);
|
||||||
|
write!(f, "Modem {{ {}, {:?}, .. }}", self.path, numbers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Modem {
|
||||||
|
pub fn enabled(&self) -> Result<bool, Box<dyn StdError>> {
|
||||||
|
let state: ModemState = ModemAccess::state(&self.dbus.proxy(&self.path)).map(Into::into)?;
|
||||||
|
Ok(match state {
|
||||||
|
ModemState::Enabling
|
||||||
|
| ModemState::Enabled
|
||||||
|
| ModemState::Searching
|
||||||
|
| ModemState::Registered
|
||||||
|
| ModemState::Connecting
|
||||||
|
| ModemState::Connected => true,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn model(&self) -> Result<String, Box<dyn StdError>> {
|
||||||
|
Ok(ModemAccess::model(&self.dbus.proxy(&self.path))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn manufacturer(&self) -> Result<String, Box<dyn StdError>> {
|
||||||
|
Ok(ModemAccess::manufacturer(&self.dbus.proxy(&self.path))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn own_numbers(&self) -> Result<Vec<String>, Box<dyn StdError>> {
|
||||||
|
Ok(ModemAccess::own_numbers(&self.dbus.proxy(&self.path))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_sms(&self) -> Result<Vec<Sms>, Box<dyn StdError>> {
|
||||||
|
Ok(ModemMessaging::list(&self.dbus.proxy(&self.path))?
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| Sms {
|
||||||
|
dbus: self.dbus.clone(),
|
||||||
|
modem: self.clone(),
|
||||||
|
path: p,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_sms(&self, number: String, text: String) -> Result<Sms, Box<dyn StdError>> {
|
||||||
|
Sms::new(self, number, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_sms(&self, path: DBusPath) -> Result<(), Box<dyn StdError>> {
|
||||||
|
Ok(ModemMessaging::delete(&self.dbus.proxy(&self.path), path)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// include/ModemManager-enums.h MMSmsState
|
||||||
|
/// State of a given SMS.
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SmsState {
|
||||||
|
/// State unknown or not reportable.
|
||||||
|
Unknown = 0,
|
||||||
|
/// The message has been neither received not yet sent.
|
||||||
|
Stored = 1,
|
||||||
|
/// The message is being received but is not yet complete.
|
||||||
|
Receiving = 2,
|
||||||
|
/// The message has been completely received.
|
||||||
|
Received = 3,
|
||||||
|
/// The message is queued for delivery.
|
||||||
|
Sending = 4,
|
||||||
|
/// The message was successfully sent.
|
||||||
|
Sent = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SmsState {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", match self {
|
||||||
|
SmsState::Unknown => "unknown",
|
||||||
|
SmsState::Stored => "stored",
|
||||||
|
SmsState::Receiving => "receiving",
|
||||||
|
SmsState::Received => "received",
|
||||||
|
SmsState::Sending => "sending",
|
||||||
|
SmsState::Sent => "sent",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for SmsState {
|
||||||
|
fn from(num: u32) -> Self {
|
||||||
|
if num > 5 {
|
||||||
|
Self::Unknown
|
||||||
|
} else {
|
||||||
|
unsafe { *(&num as *const u32 as *const Self) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sms {
|
||||||
|
dbus: DBus,
|
||||||
|
modem: Modem,
|
||||||
|
path: DBusPath<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sms {
|
||||||
|
pub fn new(modem: &Modem, number: String, text: String) -> Result<Sms, Box<dyn StdError>> {
|
||||||
|
let props: PropMap = {
|
||||||
|
let mut tmp = HashMap::new();
|
||||||
|
tmp.insert(
|
||||||
|
String::from("number"),
|
||||||
|
Variant(Box::new(number) as Box<dyn RefArg>),
|
||||||
|
);
|
||||||
|
tmp.insert(
|
||||||
|
String::from("text"),
|
||||||
|
Variant(Box::new(text) as Box<dyn RefArg>),
|
||||||
|
);
|
||||||
|
tmp.insert(
|
||||||
|
String::from("smsc"),
|
||||||
|
Variant(Box::new(String::from(SMSC)) as Box<dyn RefArg>),
|
||||||
|
);
|
||||||
|
tmp
|
||||||
|
};
|
||||||
|
let path = ModemMessaging::create(&modem.dbus.proxy(&modem.path), props)?;
|
||||||
|
Ok(Sms {
|
||||||
|
dbus: modem.dbus.clone(),
|
||||||
|
modem: modem.clone(),
|
||||||
|
path: path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> Result<SmsState, Box<dyn StdError>> {
|
||||||
|
Ok(SmsAccess::state(&self.dbus.proxy(&self.path)).map(Into::into)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self) -> Result<(), Box<dyn StdError>> {
|
||||||
|
Ok(SmsAccess::send(&self.dbus.proxy(&self.path))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn number(&self) -> Result<String, Box<dyn StdError>> {
|
||||||
|
Ok(SmsAccess::number(&self.dbus.proxy(&self.path))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(&self) -> Result<String, Box<dyn StdError>> {
|
||||||
|
Ok(SmsAccess::text(&self.dbus.proxy(&self.path))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Sms {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let state = self.state().unwrap_or(SmsState::Unknown);
|
||||||
|
write!(f, "Sms ({}) {{ {}, {:?}, .. }}", state, self.path, self.modem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Sms {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let state = self.state().unwrap_or(SmsState::Unknown);
|
||||||
|
let number = self.number().unwrap_or(String::new());
|
||||||
|
let text = self.text().unwrap_or(String::new());
|
||||||
|
write!(f, "Sms ({}) {{ number: {}, text: {} }}", state, number, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn StdError>> {
|
||||||
|
let modems: Vec<Modem> = ModemManager::connect()?.modems()?;
|
||||||
|
for modem in modems {
|
||||||
|
println!("Enabled: {:?}", modem.enabled());
|
||||||
|
println!("Model: {:?}", modem.model());
|
||||||
|
// modem.create_sms(String::from("0123456789"), String::from("Test 1 2 3"))?.send()?;
|
||||||
|
let messages = modem.list_sms()?;
|
||||||
|
for sms in messages {
|
||||||
|
println!("{}", sms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue