Initial kinda working OSC->DDC server
This commit is contained in:
commit
3a997c0f7b
|
@ -0,0 +1,5 @@
|
|||
if ! has nix_direnv_version || ! nix_direnv_version 1.3.0; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/1.3.0/direnvrc" "sha256-1VWM1BnI1GvclYBky5f5Y9HqeThmQUwCWQbsFQM1Eu0="
|
||||
fi
|
||||
|
||||
use flake
|
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
.direnv
|
||||
result
|
|
@ -0,0 +1,172 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "ddc"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba69f2c53e320fc4abad17cb02bbbf04d1a36f18e9907f347589ec5991b3c6c5"
|
||||
dependencies = [
|
||||
"mccs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ddc-i2c"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66503057bd41fc21b532b3ebe33b2ec57e5d4971fcfc3844306ebcb499b6c8c2"
|
||||
dependencies = [
|
||||
"ddc",
|
||||
"i2c",
|
||||
"i2c-linux",
|
||||
"resize-slice",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ddcmqtt"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ddc",
|
||||
"ddc-i2c",
|
||||
"rosc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "i2c"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60c7b7bdd7b3a985fdcf94a0d7d98e7a47fde8b7f22fb55ce1a91cc104a2ce9a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "i2c-linux"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0268a871aaa071221d6c2875ebedcf64710e59b0d87c68c8faf5e98b87dd2a4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"i2c",
|
||||
"i2c-linux-sys",
|
||||
"resize-slice",
|
||||
"udev",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "i2c-linux-sys"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b70fd3d30157850c87f4c7c09c0857d9c2e8c996bcbe23ec1299126e0479ec"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
|
||||
|
||||
[[package]]
|
||||
name = "libudev-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mccs"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74366c6da4179141e0d4551a46799a7e667a68eda60561690d5048bd8e0f8422"
|
||||
dependencies = [
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
|
||||
|
||||
[[package]]
|
||||
name = "resize-slice"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a3cb2f74a9891e76958b9e0ccd269a25b466c3ae3bb3efd71db157248308c4a"
|
||||
dependencies = [
|
||||
"uninitialized",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosc"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d9177134b9f1158370b6df30dd4f711ba07a6b3f6bfbab35b3bbbf1bf58cbcc"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "udev"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47504d1a49b2ea1b133e7ddd1d9f0a83cf03feb9b440c2c470d06db4589cf301"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libudev-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uninitialized"
|
||||
version = "0.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74c1aa4511c38276c548406f0b1f5f8b793f000cfb51e18f278a102abd057e81"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "ddcmqtt"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ddc = "*"
|
||||
ddc-i2c = { version = "0.2.1", features = [ "with-linux-enumerate" ] }
|
||||
rosc = "*"
|
||||
|
||||
[[bin]]
|
||||
name = "ddcmqtt"
|
||||
path = "src/bin/ddcmqtt.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "setmon"
|
||||
path = "src/bin/setmon.rs"
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"nodes": {
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1647857022,
|
||||
"narHash": "sha256-Aw70NWLOIwKhT60MHDGjgWis3DP3faCzr6ap9CSayek=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "0a5ff74dacb9ea22614f64e61aeb3ca0bf0e7311",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1642700792,
|
||||
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1648297722,
|
||||
"narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1648544490,
|
||||
"narHash": "sha256-EoBDcccV70tfz2LAs5lK0BjC7en5mzUVlgLsd5E6DW4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "e30ef9a5ce9b3de8bb438f15829c50f9525ca730",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1643381941,
|
||||
"narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5efc8ca954272c4376ac929f4c5ffefcc20551d5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1641687203,
|
||||
"narHash": "sha256-W6Xrb/l1x+E+WMVLw4q5HUnNjt3x4WQFSYJtjJtopbU=",
|
||||
"path": "/nix/store/8hd2049cr4dz9myjcjjh6cjjapz1p107-source",
|
||||
"rev": "00acdb2aa817048fbe1f91ece18fe7de09762531",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1641687203,
|
||||
"narHash": "sha256-W6Xrb/l1x+E+WMVLw4q5HUnNjt3x4WQFSYJtjJtopbU=",
|
||||
"path": "/nix/store/8hd2049cr4dz9myjcjjh6cjjapz1p107-source",
|
||||
"rev": "00acdb2aa817048fbe1f91ece18fe7de09762531",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
description = "virtual environments";
|
||||
|
||||
inputs.devshell.url = "github:numtide/devshell";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.naersk.url = "github:nix-community/naersk";
|
||||
|
||||
outputs = { self, flake-utils, devshell, nixpkgs, naersk }:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = nixpkgs.legacyPackages."${system}";
|
||||
naersk-lib = naersk.lib."${system}";
|
||||
in {
|
||||
defaultPackage = naersk-lib.buildPackage {
|
||||
pname = "ddcmqtt";
|
||||
root = ./.;
|
||||
nativeBuildInputs = with pkgs; [ pkgconfig libudev ];
|
||||
};
|
||||
/* devshell currently not really working for stuff that needs pkgconfig/libs
|
||||
devShell =
|
||||
let pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
overlays = [ devshell.overlay ];
|
||||
};
|
||||
in
|
||||
pkgs.devshell.mkShell {
|
||||
packages = with pkgs; [ rustc cargo gcc ];
|
||||
nativeBuildInputs = with pkgs; [ libudev pkgconfig ];
|
||||
};
|
||||
*/
|
||||
devshell = pkgs.mkShell{
|
||||
nativeBuildInputs = with pkgs; [ rustc cargo libudev pkgconfig ];
|
||||
};
|
||||
});
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
use ::ddcmqtt::{main as main_, StdError};
|
||||
|
||||
|
||||
fn main() -> StdError<()> {
|
||||
main_()
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
use ddc_i2c::{I2cDeviceEnumerator, I2cDeviceDdc};
|
||||
use ddc::Ddc;
|
||||
|
||||
use ::ddcmqtt::{StdError};
|
||||
|
||||
fn main() -> StdError<()> {
|
||||
let args_: Vec<_> = std::env::args().skip(1).collect();
|
||||
let args: Vec<&str> = args_.iter().map(|s| &**s).collect();
|
||||
match args.as_slice() {
|
||||
["brightness", arg] => ddc_all(0x10, arg.parse::<u16>()?),
|
||||
["input", arg] => ddc_all(0x60, arg.parse::<u16>()?),
|
||||
_ => Err("Bad usage".into()),
|
||||
|
||||
}?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ddc_all(feat: u8, val: u16) -> StdError<()> {
|
||||
for mut display in I2cDeviceEnumerator::new()? {
|
||||
display.set_vcp_feature(feat, val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
#![allow(unreachable_code)]
|
||||
use ddc::Ddc;
|
||||
use ddc_i2c::{I2cDeviceEnumerator, I2cDeviceDdc};
|
||||
use rosc;
|
||||
use std::net::UdpSocket;
|
||||
use std::thread;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
pub type StdError<T> = Result<T, Box<dyn Error>>;
|
||||
|
||||
enum Command {
|
||||
Monitor((usize, MonitorCommand)),
|
||||
}
|
||||
|
||||
enum MonitorCommand {
|
||||
Brightness(u8),
|
||||
Input(u8)
|
||||
}
|
||||
|
||||
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>) {
|
||||
let mut last_sent_command: HashMap<&str, Option<Instant>> = HashMap::new();
|
||||
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
|
||||
MonitorCommand::Input(i) => dev.set_vcp_feature(0x60, (i+15).into()).unwrap(),
|
||||
}
|
||||
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());
|
||||
let txes: Vec<_> = displays.into_iter().map(|d| {
|
||||
let (tx, rx) = channel();
|
||||
thread::spawn(move|| run_i2c(d, rx));
|
||||
tx
|
||||
}).collect();
|
||||
let sock = UdpSocket::bind("0.0.0.0:1234").unwrap();
|
||||
let mut buf = [0u8; rosc::decoder::MTU];
|
||||
|
||||
loop {
|
||||
let (size, addr) = sock.recv_from(&mut buf).unwrap();
|
||||
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) {
|
||||
Ok(cmd) => if let Err(e) = handle_cmd(cmd, &txes) {
|
||||
println!("Error handling command: {:?}", e);
|
||||
},
|
||||
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 {
|
||||
"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)),
|
||||
_ => None,
|
||||
}.ok_or(format!("Unrecognised monitor control: {}", *control))?;
|
||||
let idx = idx.parse::<usize>().or(Err("Bad monitor index"))?;
|
||||
Ok(Command::Monitor((idx, command)))
|
||||
},
|
||||
_ => Err("Unsupported osc address, ignoring".into()),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue