2022-04-07 16:23:53 +01:00
|
|
|
use ddc::Ddc;
|
2022-05-11 01:15:48 +01:00
|
|
|
use ddc_i2c::{I2cDeviceDdc, I2cDeviceEnumerator};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::error::Error;
|
2022-04-07 16:23:53 +01:00
|
|
|
use std::net::UdpSocket;
|
|
|
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
2022-05-11 01:15:48 +01:00
|
|
|
use std::thread;
|
2022-04-07 16:23:53 +01:00
|
|
|
use std::time::{Duration, Instant};
|
2022-05-11 01:15:48 +01:00
|
|
|
use rosc;
|
2022-04-07 16:23:53 +01:00
|
|
|
|
|
|
|
pub type StdError<T> = Result<T, Box<dyn Error>>;
|
|
|
|
|
|
|
|
enum Command {
|
|
|
|
Monitor((usize, MonitorCommand)),
|
|
|
|
}
|
|
|
|
|
|
|
|
enum MonitorCommand {
|
|
|
|
Brightness(u8),
|
2022-05-11 01:15:48 +01:00
|
|
|
Input(u8),
|
2022-04-07 16:23:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MonitorCommand {
|
|
|
|
fn cmd_str(&self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
Self::Brightness(_) => "brightness",
|
|
|
|
Self::Input(_) => "input",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn vcp(&self) -> u8 {
|
|
|
|
match self {
|
|
|
|
Self::Brightness(_) => 0x10,
|
|
|
|
Self::Input(_) => 0x60,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run an i2c command handler for a specific i2c device, rate limiting each type of command
|
|
|
|
fn run_i2c(mut dev: I2cDeviceDdc, command_channel: Receiver<MonitorCommand>) {
|
2022-05-11 01:15:48 +01:00
|
|
|
let mut last_sent_command: HashMap<&str, Option<Instant>> = HashMap::new();
|
2022-04-07 16:23:53 +01:00
|
|
|
loop {
|
|
|
|
let cmd = command_channel.recv().unwrap();
|
|
|
|
if let Some(last) = last_sent_command.entry(cmd.cmd_str()).or_insert(None) {
|
|
|
|
if (*last + Duration::from_millis(100)) > Instant::now() {
|
|
|
|
// rate limit
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
match cmd {
|
|
|
|
MonitorCommand::Brightness(b) => dev.set_vcp_feature(0x10, b.into()).unwrap(),
|
|
|
|
// Hack - add 15 to align with DELL monitors
|
2022-05-11 01:15:48 +01:00
|
|
|
MonitorCommand::Input(i) => dev.set_vcp_feature(0x60, (i + 15).into()).unwrap(),
|
2022-04-07 16:23:53 +01:00
|
|
|
}
|
|
|
|
last_sent_command.insert(cmd.cmd_str(), Some(Instant::now()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn main() -> StdError<()> {
|
|
|
|
let displays = I2cDeviceEnumerator::new().unwrap().collect::<Vec<_>>();
|
|
|
|
println!("Enumerated {} displays", displays.len());
|
2022-05-11 01:15:48 +01:00
|
|
|
let txes: Vec<_> = displays
|
|
|
|
.into_iter()
|
|
|
|
.map(|d| {
|
|
|
|
let (tx, rx) = channel();
|
|
|
|
thread::spawn(move || run_i2c(d, rx));
|
|
|
|
tx
|
|
|
|
})
|
|
|
|
.collect();
|
2022-04-07 16:23:53 +01:00
|
|
|
let sock = UdpSocket::bind("0.0.0.0:1234").unwrap();
|
|
|
|
let mut buf = [0u8; rosc::decoder::MTU];
|
|
|
|
|
|
|
|
loop {
|
2022-05-11 01:15:48 +01:00
|
|
|
let (size, addr) = sock.recv_from(&mut buf).unwrap();
|
2022-04-07 16:23:53 +01:00
|
|
|
println!("Got {} bytes from {}", size, addr);
|
|
|
|
let (_, pack) = rosc::decoder::decode_udp(&buf[..size]).unwrap();
|
|
|
|
match pack {
|
|
|
|
rosc::OscPacket::Message(msg) => {
|
|
|
|
match osc_message_to_command(msg) {
|
2022-05-11 01:15:48 +01:00
|
|
|
Ok(cmd) => {
|
|
|
|
if let Err(e) = handle_cmd(cmd, &txes) {
|
|
|
|
println!("Error handling command: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
2022-04-07 16:23:53 +01:00
|
|
|
Err(e) => println!("Unrecognised OSC command: {:?}", e),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
rosc::OscPacket::Bundle(bundle) => {
|
|
|
|
println!("OSC Bundle: {:?}", bundle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_cmd(cmd: Command, txes: &Vec<Sender<MonitorCommand>>) -> StdError<()> {
|
|
|
|
match cmd {
|
|
|
|
Command::Monitor((idx, c)) => txes.get(idx).ok_or("Bad monitor index")?.send(c)?,
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
fn osc_message_to_command(msg: rosc::OscMessage) -> StdError<Command> {
|
|
|
|
println!("OSC: {}, args: {:?}", msg.addr, msg.args);
|
|
|
|
let splitaddr: Vec<_> = msg.addr.split("/").collect();
|
|
|
|
match &splitaddr[1..] {
|
|
|
|
["monitor", idx, control] => {
|
|
|
|
println!("Monitor {}, control {}, args {:?}", idx, control, msg.args);
|
|
|
|
let command = match *control {
|
2022-05-11 01:15:48 +01:00
|
|
|
"brightness" => Some(MonitorCommand::Brightness(
|
|
|
|
(msg.args[0].clone().float().unwrap() * 100.0) as u8,
|
|
|
|
)),
|
|
|
|
"input" => Some(MonitorCommand::Input(
|
|
|
|
(msg.args[0].clone().int().unwrap()) as u8,
|
|
|
|
)),
|
2022-04-07 16:23:53 +01:00
|
|
|
_ => None,
|
2022-05-11 01:15:48 +01:00
|
|
|
}
|
|
|
|
.ok_or(format!("Unrecognised monitor control: {}", *control))?;
|
2022-04-07 16:23:53 +01:00
|
|
|
let idx = idx.parse::<usize>().or(Err("Bad monitor index"))?;
|
|
|
|
Ok(Command::Monitor((idx, command)))
|
2022-05-11 01:15:48 +01:00
|
|
|
}
|
2022-04-07 16:23:53 +01:00
|
|
|
_ => Err("Unsupported osc address, ignoring".into()),
|
|
|
|
}
|
|
|
|
}
|