feat: parse DMX on ESP32, relay colors via OSC on host
This commit is contained in:
parent
c6bb7aa1e3
commit
1138424c04
|
@ -64,6 +64,12 @@ version = "2.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -132,11 +138,21 @@ version = "0.8.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dmxusb"
|
name = "dmxusb"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"rosc",
|
||||||
"serialport",
|
"serialport",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -162,6 +178,12 @@ version = "1.70.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.172"
|
version = "0.2.172"
|
||||||
|
@ -197,6 +219,18 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.26.4"
|
version = "0.26.4"
|
||||||
|
@ -208,6 +242,22 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
|
@ -220,6 +270,12 @@ version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.95"
|
version = "1.0.95"
|
||||||
|
@ -238,12 +294,43 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rosc"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd37602e1513794e952274082d074e8d31aa7f64d047e3acb746c91db40600a5"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"nom",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serialport"
|
name = "serialport"
|
||||||
version = "4.7.2"
|
version = "4.7.2"
|
||||||
|
@ -300,6 +387,37 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unescaper"
|
name = "unescaper"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|
|
@ -5,4 +5,5 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.39", features = ["derive"] }
|
clap = { version = "4.5.39", features = ["derive"] }
|
||||||
|
rosc = "0.11.4"
|
||||||
serialport = "4.7.2"
|
serialport = "4.7.2"
|
||||||
|
|
103
src/main.rs
103
src/main.rs
|
@ -1,28 +1,37 @@
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits};
|
use rosc::encoder;
|
||||||
|
use rosc::{OscMessage, OscPacket, OscType};
|
||||||
|
|
||||||
use std::io::{self, ErrorKind};
|
use std::io::{self, Write};
|
||||||
|
use std::net::{IpAddr, Ipv6Addr, TcpListener};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
const BAUD_RATE: u32 = 250000;
|
const BAUD_RATE: u32 = 115_200;
|
||||||
|
|
||||||
const MAX_SLOTS: usize = 513;
|
const BREAK_MICROS: u64 = 88;
|
||||||
|
|
||||||
const MICROS_PER_SLOT: u64 = 44;
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
#[arg(short, long, conflicts_with = "port")]
|
#[arg(short, long, conflicts_with = "port")]
|
||||||
list_ports: bool,
|
list_ports: bool,
|
||||||
|
|
||||||
|
#[arg(short = 'h', long, default_value_t = IpAddr::V6(Ipv6Addr::LOCALHOST), conflicts_with = "list_ports")]
|
||||||
|
osc_host: IpAddr,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
short = 'p',
|
||||||
|
long,
|
||||||
|
default_value_t = 9996,
|
||||||
|
conflicts_with = "list_ports"
|
||||||
|
)]
|
||||||
|
osc_port: u16,
|
||||||
|
|
||||||
#[arg(required_unless_present = "list_ports")]
|
#[arg(required_unless_present = "list_ports")]
|
||||||
port: Option<String>,
|
port: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Packet = [u8; MAX_SLOTS];
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
|
@ -34,48 +43,60 @@ fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let listener = TcpListener::bind((args.osc_host, args.osc_port)).unwrap();
|
||||||
|
let (mut osc, _) = listener.accept().unwrap();
|
||||||
|
|
||||||
let mut serial = serialport::new(args.port.unwrap(), BAUD_RATE)
|
let mut serial = serialport::new(args.port.unwrap(), BAUD_RATE)
|
||||||
.data_bits(DataBits::Eight)
|
|
||||||
.flow_control(FlowControl::None)
|
|
||||||
.parity(Parity::None)
|
|
||||||
.stop_bits(StopBits::Two)
|
|
||||||
.timeout(Duration::from_micros(MICROS_PER_SLOT))
|
|
||||||
.open()
|
.open()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// ignore first (possibly incomplete) packet
|
let mut color = [0; 3];
|
||||||
let _ = read_packet(&mut serial).unwrap();
|
let mut strobe = false;
|
||||||
|
|
||||||
println!("{:#?}", read_packet(&mut serial).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_packet(serial: &mut Box<dyn SerialPort>) -> Result<Packet, io::Error> {
|
|
||||||
let mut buf = [0; MAX_SLOTS];
|
|
||||||
let mut pos = 0;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match serial.read(&mut buf[pos..]) {
|
match serial.read_exact(&mut color) {
|
||||||
Ok(bytes_read) => {
|
Ok(()) => {
|
||||||
pos += bytes_read;
|
if color[0] == 0 && color[1] == 0 && color[2] == 0 {
|
||||||
|
continue;
|
||||||
if pos == MAX_SLOTS {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
println!("{:#?}", color);
|
||||||
}
|
|
||||||
Err(err) if err.kind() == ErrorKind::Interrupted => continue,
|
|
||||||
Err(err) if err.kind() == ErrorKind::TimedOut => {
|
|
||||||
if pos > 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
thread::sleep(Duration::from_micros(MICROS_PER_SLOT));
|
let _ = send_value(&mut osc, "dmx_red".into(), color[0] as f32 / 255.0);
|
||||||
continue;
|
let _ = send_value(&mut osc, "dmx_green".into(), color[1] as f32 / 255.0);
|
||||||
|
let _ = send_value(&mut osc, "dmx_blue".into(), color[2] as f32 / 255.0);
|
||||||
|
|
||||||
|
if color[0] == 255 && color[1] == 255 && color[2] == 255 {
|
||||||
|
if !strobe {
|
||||||
|
let _ = send_value(&mut osc, "dmx_strobe".into(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
strobe = true;
|
||||||
|
} else {
|
||||||
|
if strobe {
|
||||||
|
let _ = send_value(&mut osc, "dmx_strobe".into(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
strobe = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
thread::sleep(Duration::from_micros(BREAK_MICROS));
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(buf)
|
|
||||||
|
fn send_value<W, T>(w: &mut W, addr: String, v: T) -> io::Result<()>
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
T: Into<OscType>,
|
||||||
|
{
|
||||||
|
let buf = encoder::encode_tcp(&OscPacket::Message(OscMessage {
|
||||||
|
addr,
|
||||||
|
args: vec![v.into()],
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
w.write_all(&buf[..])?;
|
||||||
|
w.flush()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
[build]
|
||||||
|
target = "riscv32imc-unknown-none-elf"
|
||||||
|
|
||||||
|
[target.riscv32imc-unknown-none-elf]
|
||||||
|
runner = "espflash flash"
|
||||||
|
rustflags = [
|
||||||
|
"-C", "link-arg=-Tlinkall.x",
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "uart-relay"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
esp-hal = { version = "0.23.1", features = ["esp32c3"] }
|
||||||
|
panic-halt = "1.0.0"
|
|
@ -0,0 +1,67 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use esp_hal::uart::{self, DataBits, Parity, StopBits, UartRx};
|
||||||
|
use esp_hal::usb_serial_jtag::UsbSerialJtag;
|
||||||
|
use esp_hal::{main, Async};
|
||||||
|
|
||||||
|
use panic_halt as _;
|
||||||
|
|
||||||
|
const BAUD_RATE: u32 = 250_000;
|
||||||
|
|
||||||
|
const MAX_SLOTS: usize = 513;
|
||||||
|
|
||||||
|
const START_CODE: u8 = 0;
|
||||||
|
|
||||||
|
type Packet = [u8; MAX_SLOTS];
|
||||||
|
|
||||||
|
#[main]
|
||||||
|
fn main() -> ! {
|
||||||
|
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||||
|
|
||||||
|
let mut rx = UartRx::new(
|
||||||
|
peripherals.UART0,
|
||||||
|
uart::Config::default()
|
||||||
|
.with_baudrate(BAUD_RATE)
|
||||||
|
.with_data_bits(DataBits::_8)
|
||||||
|
.with_parity(Parity::None)
|
||||||
|
.with_stop_bits(StopBits::_2)
|
||||||
|
.with_rx_timeout(1), // slots
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.with_rx(peripherals.GPIO3)
|
||||||
|
.into_async();
|
||||||
|
|
||||||
|
let mut usb_serial = UsbSerialJtag::new(peripherals.USB_DEVICE);
|
||||||
|
|
||||||
|
// ignore first (possibly incomplete) packet
|
||||||
|
let _ = read_packet(&mut rx).unwrap();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Ok(packet) = read_packet(&mut rx) {
|
||||||
|
if packet[0] == START_CODE {
|
||||||
|
let _ = usb_serial.write_bytes(&packet[1..4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_packet(rx: &mut UartRx<'_, Async>) -> Result<Packet, uart::Error> {
|
||||||
|
let mut buf = [0; MAX_SLOTS];
|
||||||
|
let mut pos = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match rx.read_buffered_bytes(&mut buf[pos..]) {
|
||||||
|
Ok(bytes_read) => {
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += bytes_read;
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
Loading…
Reference in New Issue