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