Initial commit
This commit is contained in:
commit
86d912fd1f
7 changed files with 4165 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
config.json
|
3926
Cargo.lock
generated
Normal file
3926
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "matrix-meshtastic-bridge"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.89"
|
||||
matrix-sdk = { version = "0.7.1", features = ["anyhow"] }
|
||||
meshtastic = "0.1.6"
|
||||
serde = { version = "1.0.210", features = ["derive"]}
|
||||
serde_json = "1.0.128"
|
||||
tokio = "1.40.0"
|
33
src/config.rs
Normal file
33
src/config.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use std::{fs::File, io::BufReader};
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
pub(crate) struct Config {
|
||||
pub(crate) matrix: MatrixConfig,
|
||||
pub(crate) meshtastic: MeshtasticConfig,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
pub(crate) struct MatrixConfig {
|
||||
pub(crate) username: String,
|
||||
pub(crate) password: String,
|
||||
#[serde(default = "get_device_name")]
|
||||
pub(crate) device_name: String,
|
||||
pub(crate) room: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
pub(crate) struct MeshtasticConfig {
|
||||
pub(crate) device: String,
|
||||
}
|
||||
|
||||
fn get_device_name() -> String {
|
||||
"meshtastic-bridge".to_string()
|
||||
}
|
||||
|
||||
pub(crate) async fn read_config() -> Config {
|
||||
let file = File::open("config.json").expect("failed to read config.json");
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
// Read the JSON contents of the file as an instance of `User`.
|
||||
serde_json::from_reader(reader).expect("failed to parse config.json")
|
||||
}
|
38
src/main.rs
Normal file
38
src/main.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use matrix_sdk::{config::SyncSettings, crypto::types::events::room, ruma::RoomId};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
mod config;
|
||||
mod matrix;
|
||||
mod meshtastic;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let config = config::read_config().await;
|
||||
|
||||
let (matrix_tx, matrix_rx) = mpsc::channel(32);
|
||||
|
||||
let room_id = RoomId::parse(&config.matrix.room).expect("invalid room id");
|
||||
println!("matrix room: {:?}", room_id);
|
||||
|
||||
let matrix_client = matrix::build(config.matrix)
|
||||
.await
|
||||
.expect("error logging into matrix");
|
||||
|
||||
matrix_client.sync_once(SyncSettings::default()).await?;
|
||||
|
||||
let meshtastic_client = meshtastic::build(config.meshtastic)
|
||||
.await
|
||||
.expect("error connecting to meshtastic");
|
||||
|
||||
tokio::spawn(async {
|
||||
meshtastic_client.receive(matrix_tx).await.expect("error receiving message from meshtastic");
|
||||
});
|
||||
|
||||
tokio::spawn(matrix::sender(matrix_client.clone(), matrix_rx, room_id));
|
||||
|
||||
// Syncing is important to synchronize the client state with the server.
|
||||
// This method will never return unless there is an error.
|
||||
matrix_client.sync(SyncSettings::default()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
99
src/matrix.rs
Normal file
99
src/matrix.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use matrix_sdk::{
|
||||
ruma::{
|
||||
events::room::{
|
||||
member::StrippedRoomMemberEvent,
|
||||
message::{RoomMessageEventContent, SyncRoomMessageEvent},
|
||||
}, OwnedRoomId, RoomId, TransactionId, UserId
|
||||
},
|
||||
Client, Room,
|
||||
};
|
||||
use tokio::time::{sleep, Duration};
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use crate::config;
|
||||
|
||||
pub(crate) async fn build(config: config::MatrixConfig) -> Result<matrix_sdk::Client, anyhow::Error> {
|
||||
let user = UserId::parse(config.username)
|
||||
.expect("invalid matrix username - should be a full mxid with server");
|
||||
let client = Client::builder()
|
||||
.server_name(user.server_name())
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
client
|
||||
.matrix_auth()
|
||||
.login_username(user, &config.password)
|
||||
.initial_device_display_name(&config.device_name)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
client.add_event_handler(|ev: SyncRoomMessageEvent| async move {
|
||||
println!("[matrix] Received a message {:?}", ev);
|
||||
});
|
||||
|
||||
client.add_event_handler(on_stripped_state_member);
|
||||
|
||||
println!("connected to matrix");
|
||||
|
||||
return Ok(client);
|
||||
}
|
||||
|
||||
async fn on_stripped_state_member(
|
||||
room_member: StrippedRoomMemberEvent,
|
||||
client: Client,
|
||||
room: Room,
|
||||
) {
|
||||
if room_member.state_key != client.user_id().unwrap() {
|
||||
return;
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
println!("Joining room {}", room.room_id());
|
||||
let mut delay = 2;
|
||||
|
||||
while let Err(err) = room.join().await {
|
||||
// retry autojoin due to synapse sending invites, before the
|
||||
// invited user can join for more information see
|
||||
// https://github.com/matrix-org/synapse/issues/4345
|
||||
eprintln!(
|
||||
"Failed to join room {} ({err:?}), retrying in {delay}s",
|
||||
room.room_id()
|
||||
);
|
||||
|
||||
sleep(Duration::from_secs(delay)).await;
|
||||
delay *= 2;
|
||||
|
||||
if delay > 3600 {
|
||||
eprintln!("Can't join room {} ({err:?})", room.room_id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!("Successfully joined room {}", room.room_id());
|
||||
|
||||
let content = RoomMessageEventContent::text_plain("fuck you");
|
||||
let txn_id = TransactionId::new();
|
||||
|
||||
let result = room.send(content).with_transaction_id(&txn_id).await;
|
||||
match result {
|
||||
Ok(_) => eprintln!("sent hello message"),
|
||||
Err(err) => eprintln!(
|
||||
"Error sending hello message to room {}: {err:?}",
|
||||
room.room_id()
|
||||
),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
pub(crate) async fn sender(client: Client, mut rx: Receiver<RoomMessageEventContent>, room_id: OwnedRoomId) {
|
||||
let room = match client.get_room(&room_id) {
|
||||
Some(room) => room,
|
||||
None => panic!("requested matrix room not found")
|
||||
};
|
||||
|
||||
while let Some(msg) = rx.recv().await {
|
||||
match room.send(msg).await {
|
||||
Err(err) => println!("error sending message to matrix: {:?}", err),
|
||||
Ok(response) => println!("sent message to matrix: {:?}", response),
|
||||
}
|
||||
};
|
||||
}
|
55
src/meshtastic.rs
Normal file
55
src/meshtastic.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
|
||||
use meshtastic::api::state::Connected;
|
||||
use meshtastic::api::{ConnectedStreamApi, StreamApi};
|
||||
use meshtastic::protobufs::FromRadio;
|
||||
use meshtastic::utils;
|
||||
use tokio::sync::mpsc::{Sender, UnboundedReceiver};
|
||||
use tokio::time::Duration;
|
||||
|
||||
use crate::config;
|
||||
|
||||
pub struct MeshtasticClient {
|
||||
decoded_listener: UnboundedReceiver<FromRadio>,
|
||||
stream_api: ConnectedStreamApi<Connected>,
|
||||
}
|
||||
|
||||
pub(crate) async fn build(
|
||||
config: config::MeshtasticConfig,
|
||||
) -> Result<MeshtasticClient, Box<dyn std::error::Error>> {
|
||||
let stream_api = StreamApi::new();
|
||||
let serial_stream = utils::stream::build_serial_stream(config.device, None, None, None)?;
|
||||
let (decoded_listener, stream_api) = stream_api.connect(serial_stream).await;
|
||||
|
||||
println!("connected to meshtastic device");
|
||||
|
||||
Ok(MeshtasticClient {
|
||||
decoded_listener: decoded_listener,
|
||||
stream_api: stream_api,
|
||||
})
|
||||
}
|
||||
|
||||
impl MeshtasticClient {
|
||||
pub async fn receive(
|
||||
mut self: MeshtasticClient,
|
||||
matrix_sender: Sender<RoomMessageEventContent>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// let config_id = utils::generate_rand_id();
|
||||
// let stream_api = stream_api.configure(config_id).await?;
|
||||
|
||||
// This loop can be broken with ctrl+c, or by disconnecting
|
||||
// the attached serial port.
|
||||
|
||||
println!("listening for messages from meshtastic");
|
||||
while let Some(decoded) = self.decoded_listener.recv().await {
|
||||
println!("Received: {:?}", decoded);
|
||||
|
||||
let msg = RoomMessageEventContent::text_plain(format!("{:?}", decoded));
|
||||
|
||||
matrix_sender
|
||||
.send_timeout(msg, Duration::from_secs(10))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue