From a3b48ea95d225adffad4999b1cd246f1ab8e7c33 Mon Sep 17 00:00:00 2001 From: stephen Date: Fri, 23 Feb 2024 17:16:50 +0100 Subject: [PATCH 1/8] WIP Tether Interface is struct --- build.rs | 7 ++- src/tether_interface.rs | 125 +++++++++++++++++++++++----------------- 2 files changed, 75 insertions(+), 57 deletions(-) diff --git a/build.rs b/build.rs index a524c0d..82e36fc 100644 --- a/build.rs +++ b/build.rs @@ -17,11 +17,11 @@ fn main() { for (index, fixture_path) in entries.iter().enumerate() { println!("Fixture file: {:?}", fixture_path); - match fs::read_to_string(&fixture_path) { + match fs::read_to_string(fixture_path) { Ok(d) => { entire_string.push_str(&d); if index < (entries.len() - 1) { - entire_string.push_str(","); + entire_string.push(','); } } Err(e) => { @@ -36,5 +36,6 @@ fn main() { entire_string.push_str("\n]"); let mut f = File::create("src/all_fixtures.json").expect("failed to create output file"); - f.write(entire_string.as_bytes()).expect("failed to write"); + f.write_all(entire_string.as_bytes()) + .expect("failed to write"); } diff --git a/src/tether_interface.rs b/src/tether_interface.rs index 97ac8bd..cf4352b 100644 --- a/src/tether_interface.rs +++ b/src/tether_interface.rs @@ -7,7 +7,14 @@ use std::{ use egui::Color32; use log::{debug, info}; use serde::{Deserialize, Serialize}; -use tether_agent::{PlugOptionsBuilder, TetherAgentOptionsBuilder}; +use tether_agent::{PlugOptionsBuilder, TetherAgent, TetherAgentOptionsBuilder}; + +pub struct TetherInterface { + tether_agent: TetherAgent, + tx: Sender, + rx_quit: Receiver<()>, + handle: JoinHandle<()>, +} #[derive(Serialize, Deserialize, Debug)] pub struct TetherNotePayload { @@ -64,68 +71,78 @@ pub enum RemoteControlMessage { SceneAnimation(RemoteSceneMessage), } -pub fn start_tether_thread( - tx: Sender, - rx_quit: Receiver<()>, -) -> JoinHandle<()> { - let tether_agent = TetherAgentOptionsBuilder::new("ArtnetController") - .build() - .expect("failed to init Tether Agent"); +impl TetherInterface { + pub fn new(tx: Sender, rx_quit: Receiver<()>) -> Self { + let tether_agent = TetherAgentOptionsBuilder::new("ArtnetController") + .build() + .expect("failed to init Tether Agent"); - let input_midi_cc = PlugOptionsBuilder::create_input("controlChange") - .build(&tether_agent) - .expect("failed to create Input Plug"); + let input_midi_cc = PlugOptionsBuilder::create_input("controlChange") + .build(&tether_agent) + .expect("failed to create Input Plug"); - let input_midi_notes = PlugOptionsBuilder::create_input("notesOn") - .build(&tether_agent) - .expect("failed to create Input Plug"); + let input_midi_notes = PlugOptionsBuilder::create_input("notesOn") + .build(&tether_agent) + .expect("failed to create Input Plug"); - let input_macros = PlugOptionsBuilder::create_input("macros") - .build(&tether_agent) - .expect("failed to create Input Plug"); + let input_macros = PlugOptionsBuilder::create_input("macros") + .build(&tether_agent) + .expect("failed to create Input Plug"); - let input_scenes = PlugOptionsBuilder::create_input("scenes") - .build(&tether_agent) - .expect("failed to create Input Plug"); + let input_scenes = PlugOptionsBuilder::create_input("scenes") + .build(&tether_agent) + .expect("failed to create Input Plug"); - let mut should_quit = false; + let mut should_quit = false; - spawn(move || { - while !should_quit { - while let Some((topic, message)) = tether_agent.check_messages() { - if input_midi_cc.matches(&topic) { - debug!("MIDI CC"); - let m = rmp_serde::from_slice::(message.payload()) - .unwrap(); - tx.send(RemoteControlMessage::Midi( - TetherMidiMessage::ControlChange(m), - )) - .expect("failed to send from Tether Interface thread") - } - if input_midi_notes.matches(&topic) { - debug!("MIDI Note"); - let m = rmp_serde::from_slice::(message.payload()).unwrap(); - tx.send(RemoteControlMessage::Midi(TetherMidiMessage::NoteOn(m))) + let handle = spawn(move || { + while !should_quit { + while let Some((topic, message)) = tether_agent.check_messages() { + if input_midi_cc.matches(&topic) { + debug!("MIDI CC"); + let m = + rmp_serde::from_slice::(message.payload()) + .unwrap(); + tx.send(RemoteControlMessage::Midi( + TetherMidiMessage::ControlChange(m), + )) .expect("failed to send from Tether Interface thread") + } + if input_midi_notes.matches(&topic) { + debug!("MIDI Note"); + let m = + rmp_serde::from_slice::(message.payload()).unwrap(); + tx.send(RemoteControlMessage::Midi(TetherMidiMessage::NoteOn(m))) + .expect("failed to send from Tether Interface thread") + } + if input_macros.matches(&topic) { + debug!("Macro (direct) control message"); + let m = + rmp_serde::from_slice::(message.payload()).unwrap(); + tx.send(RemoteControlMessage::MacroAnimation(m)) + .expect("failed to send from Tether Interface thread"); + } + if input_scenes.matches(&topic) { + debug!("Remote Scene message"); + let m = + rmp_serde::from_slice::(message.payload()).unwrap(); + tx.send(RemoteControlMessage::SceneAnimation(m)) + .expect("failed to send from Tether Interface thread"); + } } - if input_macros.matches(&topic) { - debug!("Macro (direct) control message"); - let m = rmp_serde::from_slice::(message.payload()).unwrap(); - tx.send(RemoteControlMessage::MacroAnimation(m)) - .expect("failed to send from Tether Interface thread"); - } - if input_scenes.matches(&topic) { - debug!("Remote Scene message"); - let m = rmp_serde::from_slice::(message.payload()).unwrap(); - tx.send(RemoteControlMessage::SceneAnimation(m)) - .expect("failed to send from Tether Interface thread"); + if rx_quit.try_recv().is_ok() { + info!("Tether thread got quit request"); + should_quit = true; } + sleep(Duration::from_millis(1)); } - if rx_quit.try_recv().is_ok() { - info!("Tether thread got quit request"); - should_quit = true; - } - sleep(Duration::from_millis(1)); + }); + + TetherInterface { + tether_agent, + tx, + rx_quit, + handle, } - }) + } } From e255633145e85ac3a10c93f7f869d3eb0d87e443 Mon Sep 17 00:00:00 2001 From: stephen Date: Fri, 23 Feb 2024 17:31:48 +0100 Subject: [PATCH 2/8] WIP need to resolve some ownership issues --- src/main.rs | 37 +++++++++++++++++-------------------- src/model.rs | 24 +++++++++++++++++------- src/tether_interface.rs | 14 +++++++------- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/main.rs b/src/main.rs index cbe7d0b..c907be8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ use crate::{ artnet::{ArtNetInterface, ArtNetMode}, model::Model, settings::Cli, - tether_interface::start_tether_thread, ui::SIMPLE_WIN_SIZE, }; @@ -33,13 +32,9 @@ fn main() { debug!("Started with settings: {:?}", cli); - let mut handles = Vec::new(); - - let (tether_tx, tether_rx) = mpsc::channel(); - let (quit_tether_tx, quit_tether_rx) = mpsc::channel(); - let tether_handle = start_tether_thread(tether_tx.clone(), quit_tether_rx); - - handles.push(tether_handle); + // let (tether_tx, tether_rx) = mpsc::channel(); + // let (quit_tether_tx, quit_tether_rx) = mpsc::channel(); + // let tether_handle = start_tether_thread(tether_tx.clone(), quit_tether_rx); let artnet = { if cli.artnet_broadcast { @@ -55,7 +50,7 @@ fn main() { } }; - let mut model = Model::new(tether_rx, cli.clone(), artnet); + let mut model = Model::new(cli.clone(), artnet); if cli.headless_mode { info!("Running in headless mode; Ctrl+C to quit"); @@ -93,19 +88,21 @@ fn main() { .expect("Failed to launch GUI"); info!("GUI ended; exit soon..."); } - quit_tether_tx + model + .quit_tether_tx .send(()) .expect("failed to send quit message via channel"); - for h in handles { - match h.join() { - Ok(()) => { - debug!("Thread join OK"); - } - Err(e) => { - error!("Thread joined with error, {:?}", e); - } - } - } + // let handles = model.handles_mut(); + // for h in handles { + // match h.join() { + // Ok(()) => { + // debug!("Thread join OK"); + // } + // Err(e) => { + // error!("Thread joined with error, {:?}", e); + // } + // } + // } std::thread::sleep(Duration::from_secs(1)); info!("...Exit now"); std::process::exit(0); diff --git a/src/model.rs b/src/model.rs index c62095e..64656de 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,5 +1,6 @@ use std::{ - sync::mpsc::Receiver, + sync::mpsc::{self, Receiver, Sender}, + thread::JoinHandle, time::{Duration, SystemTime}, }; @@ -14,7 +15,7 @@ use crate::{ settings::{Cli, CHANNELS_PER_UNIVERSE}, tether_interface::{ RemoteControlMessage, RemoteMacroMessage, RemoteMacroValue, RemoteSceneMessage, - TetherControlChangePayload, TetherMidiMessage, TetherNotePayload, + TetherControlChangePayload, TetherInterface, TetherMidiMessage, TetherNotePayload, }, ui::{render_gui, ViewMode}, }; @@ -27,9 +28,12 @@ pub enum BehaviourOnExit { } pub struct Model { + pub handles: Vec>, + pub quit_tether_tx: Sender<()>, pub channels_state: Vec, pub channels_assigned: Vec, pub tether_rx: Receiver, + pub tether_interface: Option, pub settings: Cli, pub artnet: ArtNetInterface, pub project: Project, @@ -57,11 +61,7 @@ impl eframe::App for Model { } impl Model { - pub fn new( - tether_rx: Receiver, - settings: Cli, - artnet: ArtNetInterface, - ) -> Model { + pub fn new(settings: Cli, artnet: ArtNetInterface) -> Model { let mut current_project_path = None; let project = match Project::load(&settings.project_path) { @@ -90,8 +90,14 @@ impl Model { } } + let (tether_tx, tether_rx) = mpsc::channel(); + let (quit_tether_tx, quit_tether_rx) = mpsc::channel(); + let mut model = Model { + quit_tether_tx, + handles: Vec::new(), tether_rx, + tether_interface: None, channels_state: Vec::new(), channels_assigned, settings, @@ -544,6 +550,10 @@ impl Model { std::thread::sleep(Duration::from_millis(500)); info!("...reset before quit done"); } + + pub fn handles_mut(&mut self) -> &mut [JoinHandle<()>] { + &mut self.handles + } } fn fixtures_list_contains(search_list: &Option>, label_search_string: &str) -> bool { diff --git a/src/tether_interface.rs b/src/tether_interface.rs index cf4352b..2c8e724 100644 --- a/src/tether_interface.rs +++ b/src/tether_interface.rs @@ -9,13 +9,6 @@ use log::{debug, info}; use serde::{Deserialize, Serialize}; use tether_agent::{PlugOptionsBuilder, TetherAgent, TetherAgentOptionsBuilder}; -pub struct TetherInterface { - tether_agent: TetherAgent, - tx: Sender, - rx_quit: Receiver<()>, - handle: JoinHandle<()>, -} - #[derive(Serialize, Deserialize, Debug)] pub struct TetherNotePayload { pub channel: u8, @@ -71,6 +64,13 @@ pub enum RemoteControlMessage { SceneAnimation(RemoteSceneMessage), } +pub struct TetherInterface { + tether_agent: TetherAgent, + pub tx: Sender, + rx_quit: Receiver<()>, + handle: JoinHandle<()>, +} + impl TetherInterface { pub fn new(tx: Sender, rx_quit: Receiver<()>) -> Self { let tether_agent = TetherAgentOptionsBuilder::new("ArtnetController") From d6fc76a845b338533b42706d353d137665826943 Mon Sep 17 00:00:00 2001 From: stephen Date: Mon, 26 Feb 2024 16:36:54 +0100 Subject: [PATCH 3/8] working proof-of-concept requiring manual Tether connect --- src/main.rs | 15 ++++-------- src/model.rs | 22 +++++++---------- src/tether_interface.rs | 52 ++++++++++++++++++++++++----------------- src/ui/mod.rs | 11 +++++++++ 4 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/main.rs b/src/main.rs index c907be8..cdcfa1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::{net::SocketAddr, sync::mpsc, time::Duration}; use env_logger::Env; -use log::{debug, error, info}; +use log::{debug, info}; use clap::Parser; @@ -32,10 +32,6 @@ fn main() { debug!("Started with settings: {:?}", cli); - // let (tether_tx, tether_rx) = mpsc::channel(); - // let (quit_tether_tx, quit_tether_rx) = mpsc::channel(); - // let tether_handle = start_tether_thread(tether_tx.clone(), quit_tether_rx); - let artnet = { if cli.artnet_broadcast { ArtNetInterface::new(ArtNetMode::Broadcast, cli.artnet_update_frequency) @@ -72,7 +68,6 @@ fn main() { std::thread::sleep(Duration::from_millis(1)); model.update(); } - model.reset_before_quit(); } else { info!("Running graphics mode; close the window to quit"); let options = eframe::NativeOptions { @@ -88,10 +83,10 @@ fn main() { .expect("Failed to launch GUI"); info!("GUI ended; exit soon..."); } - model - .quit_tether_tx - .send(()) - .expect("failed to send quit message via channel"); + // model + // .quit_tether_tx + // .send(()) + // .expect("failed to send quit message via channel"); // let handles = model.handles_mut(); // for h in handles { // match h.join() { diff --git a/src/model.rs b/src/model.rs index 64656de..3e02bdb 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,5 +1,5 @@ use std::{ - sync::mpsc::{self, Receiver, Sender}, + sync::{Arc, Mutex}, thread::JoinHandle, time::{Duration, SystemTime}, }; @@ -29,11 +29,9 @@ pub enum BehaviourOnExit { pub struct Model { pub handles: Vec>, - pub quit_tether_tx: Sender<()>, pub channels_state: Vec, pub channels_assigned: Vec, - pub tether_rx: Receiver, - pub tether_interface: Option, + pub tether_interface: TetherInterface, pub settings: Cli, pub artnet: ArtNetInterface, pub project: Project, @@ -47,6 +45,7 @@ pub struct Model { pub save_on_exit: bool, pub show_confirm_exit: bool, pub allowed_to_close: bool, + pub should_quit: Arc>, } impl eframe::App for Model { @@ -90,14 +89,11 @@ impl Model { } } - let (tether_tx, tether_rx) = mpsc::channel(); - let (quit_tether_tx, quit_tether_rx) = mpsc::channel(); + let tether_interface = TetherInterface::new(); let mut model = Model { - quit_tether_tx, handles: Vec::new(), - tether_rx, - tether_interface: None, + tether_interface, channels_state: Vec::new(), channels_assigned, settings, @@ -111,6 +107,7 @@ impl Model { save_on_exit: true, show_confirm_exit: false, allowed_to_close: false, + should_quit: Arc::new(Mutex::new(false)), }; model.apply_home_values(); @@ -121,7 +118,7 @@ impl Model { pub fn update(&mut self) { let mut work_done = false; - while let Ok(m) = self.tether_rx.try_recv() { + while let Ok(m) = self.tether_interface.message_rx.try_recv() { work_done = true; self.apply_macros = true; match m { @@ -519,6 +516,7 @@ impl Model { } pub fn reset_before_quit(&mut self) { + *self.should_quit.lock().unwrap() = true; if self.save_on_exit { info!("Save-on-exit enabled; will save current project if loaded..."); if let Some(existing_project_path) = &self.current_project_path { @@ -550,10 +548,6 @@ impl Model { std::thread::sleep(Duration::from_millis(500)); info!("...reset before quit done"); } - - pub fn handles_mut(&mut self) -> &mut [JoinHandle<()>] { - &mut self.handles - } } fn fixtures_list_contains(search_list: &Option>, label_search_string: &str) -> bool { diff --git a/src/tether_interface.rs b/src/tether_interface.rs index 2c8e724..261ede7 100644 --- a/src/tether_interface.rs +++ b/src/tether_interface.rs @@ -1,11 +1,15 @@ use std::{ - sync::mpsc::{Receiver, Sender}, + sync::{ + self, + mpsc::{Receiver, Sender}, + Arc, Mutex, + }, thread::{sleep, spawn, JoinHandle}, time::Duration, }; use egui::Color32; -use log::{debug, info}; +use log::{debug, error, info}; use serde::{Deserialize, Serialize}; use tether_agent::{PlugOptionsBuilder, TetherAgent, TetherAgentOptionsBuilder}; @@ -65,14 +69,27 @@ pub enum RemoteControlMessage { } pub struct TetherInterface { - tether_agent: TetherAgent, - pub tx: Sender, - rx_quit: Receiver<()>, - handle: JoinHandle<()>, + pub message_rx: Receiver, + pub quit_channel: (Sender<()>, Receiver<()>), + // --- + message_tx: Sender, } impl TetherInterface { - pub fn new(tx: Sender, rx_quit: Receiver<()>) -> Self { + pub fn new() -> Self { + let (message_tx, message_rx) = sync::mpsc::channel(); + let (quit_tx, quit_rx) = sync::mpsc::channel(); + + TetherInterface { + message_tx, + message_rx, + quit_channel: (quit_tx, quit_rx), + } + } + + pub fn connect(&mut self, should_quit: Arc>) { + info!("Attempt to connect Tether Agent..."); + let tether_agent = TetherAgentOptionsBuilder::new("ArtnetController") .build() .expect("failed to init Tether Agent"); @@ -93,10 +110,11 @@ impl TetherInterface { .build(&tether_agent) .expect("failed to create Input Plug"); - let mut should_quit = false; + let tx = self.message_tx.clone(); + let (quit_tx, quit_rx) = &self.quit_channel; - let handle = spawn(move || { - while !should_quit { + let handle = Some(spawn(move || { + while !*should_quit.lock().unwrap() { while let Some((topic, message)) = tether_agent.check_messages() { if input_midi_cc.matches(&topic) { debug!("MIDI CC"); @@ -130,19 +148,9 @@ impl TetherInterface { .expect("failed to send from Tether Interface thread"); } } - if rx_quit.try_recv().is_ok() { - info!("Tether thread got quit request"); - should_quit = true; - } sleep(Duration::from_millis(1)); } - }); - - TetherInterface { - tether_agent, - tx, - rx_quit, - handle, - } + info!("Tether Interface: Thread loop end"); + })); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 282249a..80e8c8d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -45,6 +45,7 @@ pub fn render_gui(model: &mut Model, ctx: &eframe::egui::Context, frame: &mut ef match model.view_mode { ViewMode::Advanced => { egui::SidePanel::left("LeftPanel").show(ctx, |ui| { + render_tether_controls(model, ui); render_macro_controls(model, ui); }); @@ -58,11 +59,13 @@ pub fn render_gui(model: &mut Model, ctx: &eframe::egui::Context, frame: &mut ef } ViewMode::Simple => { egui::CentralPanel::default().show(ctx, |ui| { + render_tether_controls(model, ui); render_macro_controls(model, ui); }); } ViewMode::Scenes => { egui::SidePanel::left("LeftPanel").show(ctx, |ui| { + render_tether_controls(model, ui); render_macro_controls(model, ui); }); egui::CentralPanel::default().show(ctx, |ui| { @@ -231,3 +234,11 @@ pub fn render_sliders(model: &mut Model, ui: &mut Ui) { }); }); } + +fn render_tether_controls(model: &mut Model, ui: &mut Ui) { + ui.group(|ui| { + if ui.button("Connect").clicked() { + model.tether_interface.connect(model.should_quit.clone()); + } + }); +} From 330b4688371c742057caaa6be76f30e7576d1dfc Mon Sep 17 00:00:00 2001 From: stephen Date: Mon, 26 Feb 2024 17:03:08 +0100 Subject: [PATCH 4/8] can connect, get error and reconnect --- src/model.rs | 8 +++ src/tether_interface.rs | 128 ++++++++++++++++++++------------------- src/ui/macro_controls.rs | 2 +- src/ui/mod.rs | 33 ++++++++-- 4 files changed, 104 insertions(+), 67 deletions(-) diff --git a/src/model.rs b/src/model.rs index 3e02bdb..b4fd09a 100644 --- a/src/model.rs +++ b/src/model.rs @@ -27,11 +27,18 @@ pub enum BehaviourOnExit { Zero, } +pub enum TetherStatus { + NotConnected, + Connected, + Errored(String), +} + pub struct Model { pub handles: Vec>, pub channels_state: Vec, pub channels_assigned: Vec, pub tether_interface: TetherInterface, + pub tether_status: TetherStatus, pub settings: Cli, pub artnet: ArtNetInterface, pub project: Project, @@ -92,6 +99,7 @@ impl Model { let tether_interface = TetherInterface::new(); let mut model = Model { + tether_status: TetherStatus::NotConnected, handles: Vec::new(), tether_interface, channels_state: Vec::new(), diff --git a/src/tether_interface.rs b/src/tether_interface.rs index 261ede7..e89b6a5 100644 --- a/src/tether_interface.rs +++ b/src/tether_interface.rs @@ -4,14 +4,15 @@ use std::{ mpsc::{Receiver, Sender}, Arc, Mutex, }, - thread::{sleep, spawn, JoinHandle}, + thread::{sleep, spawn}, time::Duration, }; +use anyhow::anyhow; use egui::Color32; use log::{debug, error, info}; use serde::{Deserialize, Serialize}; -use tether_agent::{PlugOptionsBuilder, TetherAgent, TetherAgentOptionsBuilder}; +use tether_agent::{PlugOptionsBuilder, TetherAgentOptionsBuilder}; #[derive(Serialize, Deserialize, Debug)] pub struct TetherNotePayload { @@ -87,70 +88,73 @@ impl TetherInterface { } } - pub fn connect(&mut self, should_quit: Arc>) { + pub fn connect(&mut self, should_quit: Arc>) -> Result<(), anyhow::Error> { info!("Attempt to connect Tether Agent..."); - let tether_agent = TetherAgentOptionsBuilder::new("ArtnetController") - .build() - .expect("failed to init Tether Agent"); - - let input_midi_cc = PlugOptionsBuilder::create_input("controlChange") - .build(&tether_agent) - .expect("failed to create Input Plug"); - - let input_midi_notes = PlugOptionsBuilder::create_input("notesOn") - .build(&tether_agent) - .expect("failed to create Input Plug"); - - let input_macros = PlugOptionsBuilder::create_input("macros") - .build(&tether_agent) - .expect("failed to create Input Plug"); - - let input_scenes = PlugOptionsBuilder::create_input("scenes") - .build(&tether_agent) - .expect("failed to create Input Plug"); - - let tx = self.message_tx.clone(); - let (quit_tx, quit_rx) = &self.quit_channel; - - let handle = Some(spawn(move || { - while !*should_quit.lock().unwrap() { - while let Some((topic, message)) = tether_agent.check_messages() { - if input_midi_cc.matches(&topic) { - debug!("MIDI CC"); - let m = - rmp_serde::from_slice::(message.payload()) - .unwrap(); - tx.send(RemoteControlMessage::Midi( - TetherMidiMessage::ControlChange(m), - )) - .expect("failed to send from Tether Interface thread") - } - if input_midi_notes.matches(&topic) { - debug!("MIDI Note"); - let m = - rmp_serde::from_slice::(message.payload()).unwrap(); - tx.send(RemoteControlMessage::Midi(TetherMidiMessage::NoteOn(m))) + if let Ok(tether_agent) = TetherAgentOptionsBuilder::new("ArtnetController").build() { + let input_midi_cc = PlugOptionsBuilder::create_input("controlChange") + .build(&tether_agent) + .expect("failed to create Input Plug"); + + let input_midi_notes = PlugOptionsBuilder::create_input("notesOn") + .build(&tether_agent) + .expect("failed to create Input Plug"); + + let input_macros = PlugOptionsBuilder::create_input("macros") + .build(&tether_agent) + .expect("failed to create Input Plug"); + + let input_scenes = PlugOptionsBuilder::create_input("scenes") + .build(&tether_agent) + .expect("failed to create Input Plug"); + + let tx = self.message_tx.clone(); + + spawn(move || { + while !*should_quit.lock().unwrap() { + while let Some((topic, message)) = tether_agent.check_messages() { + if input_midi_cc.matches(&topic) { + debug!("MIDI CC"); + let m = rmp_serde::from_slice::( + message.payload(), + ) + .unwrap(); + tx.send(RemoteControlMessage::Midi( + TetherMidiMessage::ControlChange(m), + )) .expect("failed to send from Tether Interface thread") + } + if input_midi_notes.matches(&topic) { + debug!("MIDI Note"); + let m = rmp_serde::from_slice::(message.payload()) + .unwrap(); + tx.send(RemoteControlMessage::Midi(TetherMidiMessage::NoteOn(m))) + .expect("failed to send from Tether Interface thread") + } + if input_macros.matches(&topic) { + debug!("Macro (direct) control message"); + let m = rmp_serde::from_slice::(message.payload()) + .unwrap(); + tx.send(RemoteControlMessage::MacroAnimation(m)) + .expect("failed to send from Tether Interface thread"); + } + if input_scenes.matches(&topic) { + debug!("Remote Scene message"); + let m = rmp_serde::from_slice::(message.payload()) + .unwrap(); + tx.send(RemoteControlMessage::SceneAnimation(m)) + .expect("failed to send from Tether Interface thread"); + } } - if input_macros.matches(&topic) { - debug!("Macro (direct) control message"); - let m = - rmp_serde::from_slice::(message.payload()).unwrap(); - tx.send(RemoteControlMessage::MacroAnimation(m)) - .expect("failed to send from Tether Interface thread"); - } - if input_scenes.matches(&topic) { - debug!("Remote Scene message"); - let m = - rmp_serde::from_slice::(message.payload()).unwrap(); - tx.send(RemoteControlMessage::SceneAnimation(m)) - .expect("failed to send from Tether Interface thread"); - } + sleep(Duration::from_millis(1)); } - sleep(Duration::from_millis(1)); - } - info!("Tether Interface: Thread loop end"); - })); + info!("Tether Interface: Thread loop end"); + }); + + Ok(()) + } else { + error!("Failed to connect Tether"); + Err(anyhow!("Tether failed to connect")) + } } } diff --git a/src/ui/macro_controls.rs b/src/ui/macro_controls.rs index 0bfbbda..5e4a22b 100644 --- a/src/ui/macro_controls.rs +++ b/src/ui/macro_controls.rs @@ -7,8 +7,8 @@ use crate::{ }; pub fn render_macro_controls(model: &mut Model, ui: &mut Ui) { - ui.heading("All"); ui.horizontal(|ui| { + ui.heading("All"); if ui.button("HOME").clicked() { model.apply_macros = false; model.apply_home_values(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 80e8c8d..84dcb34 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -2,7 +2,7 @@ use egui::{Color32, Grid, RichText, ScrollArea, Slider, Ui, Vec2}; use log::{error, info, warn}; use crate::{ - model::{BehaviourOnExit, Model}, + model::{BehaviourOnExit, Model, TetherStatus}, project::Project, settings::CHANNELS_PER_UNIVERSE, }; @@ -236,9 +236,34 @@ pub fn render_sliders(model: &mut Model, ui: &mut Ui) { } fn render_tether_controls(model: &mut Model, ui: &mut Ui) { - ui.group(|ui| { - if ui.button("Connect").clicked() { - model.tether_interface.connect(model.should_quit.clone()); + ui.horizontal(|ui| { + ui.heading("Tether"); + + match &model.tether_status { + TetherStatus::NotConnected => { + offer_tether_connect(model, ui); + } + TetherStatus::Connected => { + ui.label(RichText::new("Connected").color(Color32::LIGHT_GREEN)); + } + TetherStatus::Errored(msg) => { + ui.label(RichText::new(msg).color(Color32::RED)); + offer_tether_connect(model, ui); + } } }); + ui.separator(); +} + +fn offer_tether_connect(model: &mut Model, ui: &mut Ui) { + if ui.button("Connect").clicked() { + match model.tether_interface.connect(model.should_quit.clone()) { + Ok(_) => { + model.tether_status = TetherStatus::Connected; + } + Err(e) => { + model.tether_status = TetherStatus::Errored(format!("Error: {e}")); + } + } + } } From e43d82841567d857777e3cd5fc3b7844930c891c Mon Sep 17 00:00:00 2001 From: stephen Date: Mon, 26 Feb 2024 17:13:56 +0100 Subject: [PATCH 5/8] unless explicitly disabled, always try to auto connect Tether --- src/main.rs | 16 +--------------- src/model.rs | 13 +++++++++++-- src/settings.rs | 4 ++++ src/ui/mod.rs | 19 ++++++++++++------- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/main.rs b/src/main.rs index cdcfa1b..f49728a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,21 +83,7 @@ fn main() { .expect("Failed to launch GUI"); info!("GUI ended; exit soon..."); } - // model - // .quit_tether_tx - // .send(()) - // .expect("failed to send quit message via channel"); - // let handles = model.handles_mut(); - // for h in handles { - // match h.join() { - // Ok(()) => { - // debug!("Thread join OK"); - // } - // Err(e) => { - // error!("Thread joined with error, {:?}", e); - // } - // } - // } + std::thread::sleep(Duration::from_secs(1)); info!("...Exit now"); std::process::exit(0); diff --git a/src/model.rs b/src/model.rs index b4fd09a..3f463af 100644 --- a/src/model.rs +++ b/src/model.rs @@ -17,7 +17,7 @@ use crate::{ RemoteControlMessage, RemoteMacroMessage, RemoteMacroValue, RemoteSceneMessage, TetherControlChangePayload, TetherInterface, TetherMidiMessage, TetherNotePayload, }, - ui::{render_gui, ViewMode}, + ui::{attempt_connection, render_gui, ViewMode}, }; #[derive(PartialEq)] @@ -96,8 +96,12 @@ impl Model { } } + let should_quit = Arc::new(Mutex::new(false)); + let tether_interface = TetherInterface::new(); + let should_auto_connect = !settings.tether_disable_autoconnect; + let mut model = Model { tether_status: TetherStatus::NotConnected, handles: Vec::new(), @@ -115,9 +119,14 @@ impl Model { save_on_exit: true, show_confirm_exit: false, allowed_to_close: false, - should_quit: Arc::new(Mutex::new(false)), + should_quit, }; + if should_auto_connect { + info!("Auto connect Tether enabled; will attempt to connect now..."); + attempt_connection(&mut model) + } + model.apply_home_values(); model diff --git a/src/settings.rs b/src/settings.rs index efcfd79..f3451e2 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -45,4 +45,8 @@ pub struct Cli { #[arg(long = "auto.random")] pub auto_random: bool, + + /// Flag to disable Tether connect on start (GUI only) + #[arg(long = "tether.noAutoConnect")] + pub tether_disable_autoconnect: bool, } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 84dcb34..0360131 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -241,6 +241,7 @@ fn render_tether_controls(model: &mut Model, ui: &mut Ui) { match &model.tether_status { TetherStatus::NotConnected => { + ui.label(RichText::new("Not (yet) connected").color(Color32::YELLOW)); offer_tether_connect(model, ui); } TetherStatus::Connected => { @@ -257,13 +258,17 @@ fn render_tether_controls(model: &mut Model, ui: &mut Ui) { fn offer_tether_connect(model: &mut Model, ui: &mut Ui) { if ui.button("Connect").clicked() { - match model.tether_interface.connect(model.should_quit.clone()) { - Ok(_) => { - model.tether_status = TetherStatus::Connected; - } - Err(e) => { - model.tether_status = TetherStatus::Errored(format!("Error: {e}")); - } + attempt_connection(model); + } +} + +pub fn attempt_connection(model: &mut Model) { + match model.tether_interface.connect(model.should_quit.clone()) { + Ok(_) => { + model.tether_status = TetherStatus::Connected; + } + Err(e) => { + model.tether_status = TetherStatus::Errored(format!("Error: {e}")); } } } From 848c3a3c9efba0cb813501d8b8646e9c498e9df6 Mon Sep 17 00:00:00 2001 From: stephen Date: Mon, 26 Feb 2024 17:22:45 +0100 Subject: [PATCH 6/8] can specify Tether host (no other options for now) --- src/settings.rs | 4 ++++ src/tether_interface.rs | 11 +++++++++-- src/ui/mod.rs | 5 ++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index f3451e2..c9dd011 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -49,4 +49,8 @@ pub struct Cli { /// Flag to disable Tether connect on start (GUI only) #[arg(long = "tether.noAutoConnect")] pub tether_disable_autoconnect: bool, + + /// Host/IP for Tether MQTT Broker + #[arg(long = "tether.host")] + pub tether_host: Option, } diff --git a/src/tether_interface.rs b/src/tether_interface.rs index e89b6a5..cc1911e 100644 --- a/src/tether_interface.rs +++ b/src/tether_interface.rs @@ -88,10 +88,17 @@ impl TetherInterface { } } - pub fn connect(&mut self, should_quit: Arc>) -> Result<(), anyhow::Error> { + pub fn connect( + &mut self, + should_quit: Arc>, + tether_host: Option<&str>, + ) -> Result<(), anyhow::Error> { info!("Attempt to connect Tether Agent..."); - if let Ok(tether_agent) = TetherAgentOptionsBuilder::new("ArtnetController").build() { + if let Ok(tether_agent) = TetherAgentOptionsBuilder::new("ArtnetController") + .host(tether_host) + .build() + { let input_midi_cc = PlugOptionsBuilder::create_input("controlChange") .build(&tether_agent) .expect("failed to create Input Plug"); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 0360131..a3a7f74 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -263,7 +263,10 @@ fn offer_tether_connect(model: &mut Model, ui: &mut Ui) { } pub fn attempt_connection(model: &mut Model) { - match model.tether_interface.connect(model.should_quit.clone()) { + match model.tether_interface.connect( + model.should_quit.clone(), + model.settings.tether_host.as_deref(), + ) { Ok(_) => { model.tether_status = TetherStatus::Connected; } From e3e5dc5eee51b8cabbbcca8c43c524846bbb66fe Mon Sep 17 00:00:00 2001 From: stephen Date: Mon, 26 Feb 2024 17:23:56 +0100 Subject: [PATCH 7/8] todos updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82ad30f..3ff601d 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ If you have Tether Egui installed (`cargo install tether-egui`) then the easiest - [ ] Project JSON should save ArtNet configuration (but can override via CLI args) - [x] Colour conversions should be possible manually, e.g. RGB -> CMY - [x] With macros, add some visual indicators of state, e.g. Colour, Brightness and Pan/Tilt -- [ ] Allow the app to launch just fine without Tether +- [x] Allow the app to launch just fine without Tether - [ ] Allow the app to launch without any project file at all - [ ] Add 16-bit control, at least for macros (single slider adjusts the two channels as split between first and second 8-bit digits) - [ ] ArtNet on separate thread, with more precise timing; this might require some messaging back and forth and/or mutex From e4c11084fe70e7a999c78340306d5efb436cc76a Mon Sep 17 00:00:00 2001 From: stephen Date: Mon, 26 Feb 2024 17:24:31 +0100 Subject: [PATCH 8/8] v0.4.0 includes the Tether disconnection options --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9efbff1..364c691 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3101,7 +3101,7 @@ dependencies = [ [[package]] name = "tether-artnet-controller" -version = "0.3.3" +version = "0.4.0" dependencies = [ "anyhow", "artnet_protocol", diff --git a/Cargo.toml b/Cargo.toml index 7d717f3..b7655f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tether-artnet-controller" -version = "0.3.3" +version = "0.4.0" edition = "2021" repository = "https://github.com/RandomStudio/tether-artnet-controller" authors = ["Stephen Buchanan"]