Initial commit: pure-Rust cross-platform Insta360 Link controller suite

This commit is contained in:
Lucas Oskorep committed 2026-10-19 17:08:35 -04:00
commit f468c15262
27 files changed
+22165

No files matched your search

File diff suppressed because it is too large. Load diff
+103
View File
@@ -0,0 +1,103 @@
use clap::{Parser, ValueEnum};
use insta360_link_protocol::{
CameraModel, Insta360CameraController, NusbTransport, UvcControlTransport,
};
use insta360_link_server::{build_router, new_mock_state, AppState, ServerTransport};
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
#[derive(Parser, Debug)]
#[command(
name = "insta360-link-server",
version,
about = "Cross-platform HTTP REST & WebSocket remote control server for Insta360 Link cameras"
)]
struct Cli {
/// Bind address for HTTP and WebSocket server
#[arg(long, default_value = "127.0.0.1:9360", value_name = "ADDR")]
bind: SocketAddr,
/// Run against an in-memory simulated camera model instead of physical USB
#[arg(long, value_enum, value_name = "MODEL")]
mock: Option<MockModelArg>,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum MockModelArg {
#[value(name = "link", alias = "link1")]
Link,
#[value(name = "link2")]
Link2,
#[value(name = "link2c")]
Link2c,
}
impl From<MockModelArg> for CameraModel {
fn from(val: MockModelArg) -> Self {
match val {
MockModelArg::Link => CameraModel::Link,
MockModelArg::Link2 => CameraModel::Link2,
MockModelArg::Link2c => CameraModel::Link2C,
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "insta360_link_server=info,tower_http=info".into()),
)
.init();
let cli = Cli::parse();
let state: AppState = match cli.mock {
Some(mock_arg) => {
let model: CameraModel = mock_arg.into();
tracing::info!(
"Initializing in-memory simulated camera: {} (PID: 0x{:04x})",
model.display_name(),
model.pid()
);
new_mock_state(model)
}
None => {
tracing::info!("Discovering and connecting to physical Insta360 Link camera via USB...");
match NusbTransport::open_first() {
Ok(usb) => {
let info = usb.camera_info().clone();
tracing::info!(
"Connected to physical camera: {} (Bus {} Addr {} • PID 0x{:04x})",
info.name,
info.bus_number,
info.device_address,
info.product_id
);
let ctrl = Insta360CameraController::new(ServerTransport::Usb(usb));
Arc::new(Mutex::new(ctrl))
}
Err(e) => {
tracing::warn!(
"Could not open physical Insta360 Link camera: {}. Falling back to simulated Link 2 camera. Pass '--mock <link|link2|link2c>' to select a specific model.",
e
);
new_mock_state(CameraModel::Link2)
}
}
}
};
let router = build_router(state);
let listener = tokio::net::TcpListener::bind(cli.bind).await?;
tracing::info!("Insta360 Link Server listening on http://{}", cli.bind);
tracing::info!("Web Remote UI: http://{}", cli.bind);
tracing::info!("WebSocket stream: ws://{}/ws", cli.bind);
tracing::info!("REST State API: http://{}/api/state", cli.bind);
axum::serve(listener, router).await?;
Ok(())
}