Implement output

This commit is contained in:
Kai Vogelgesang 2021-11-08 18:14:03 +01:00
parent 200946c642
commit c956ff08e5
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879
7 changed files with 658 additions and 25 deletions

View File

@ -2,6 +2,66 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "CoreFoundation-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
dependencies = [
"libc",
"mach 0.1.2",
]
[[package]]
name = "IOKit-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
dependencies = [
"CoreFoundation-sys",
"libc",
"mach 0.1.2",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -14,16 +74,66 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697c714f50560202b1f4e2e09cd50a421881c83e9025db75d15f276616f04f40" checksum = "697c714f50560202b1f4e2e09cd50a421881c83e9025db75d15f276616f04f40"
[[package]]
name = "libc"
version = "0.2.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
[[package]] [[package]]
name = "libloading" name = "libloading"
version = "0.6.7" version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"winapi", "winapi",
] ]
[[package]]
name = "libudev"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe"
dependencies = [
"libc",
"libudev-sys",
]
[[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 = "mach"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9"
dependencies = [
"libc",
]
[[package]]
name = "mach"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1"
dependencies = [
"libc",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]] [[package]]
name = "neon" name = "neon"
version = "0.9.1" version = "0.9.1"
@ -60,11 +170,106 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02662cd2e62b131937bdef85d0918b05bc3c204daf4c64af62845403eccb60f3" checksum = "02662cd2e62b131937bdef85d0918b05bc3c204daf4c64af62845403eccb60f3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"libloading", "libloading",
"smallvec", "smallvec",
] ]
[[package]]
name = "nix"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb"
dependencies = [
"bitflags",
"cc",
"cfg-if 0.1.10",
"libc",
"void",
]
[[package]]
name = "num"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "pkg-config"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.32" version = "1.0.32"
@ -83,11 +288,31 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]] [[package]]
name = "rust_native_module" name = "rust_native_module"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"neon", "neon",
"num",
"serialport",
] ]
[[package]] [[package]]
@ -105,6 +330,23 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serialport"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8cd7c0f22290ee2c01457009fa6fc1cae4153d5608a924e5dc423babc2c655"
dependencies = [
"CoreFoundation-sys",
"IOKit-sys",
"bitflags",
"cfg-if 0.1.10",
"libudev",
"mach 0.2.3",
"nix",
"regex",
"winapi",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.7.0" version = "1.7.0"
@ -128,6 +370,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -12,6 +12,9 @@ crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
serialport = "4.0.1"
anyhow = "1.0.45"
num = "0.4.0"
[dependencies.neon] [dependencies.neon]
version = "0.9" version = "0.9"

View File

@ -1,5 +1,31 @@
type Result<T> =
{ type: "success" } & T
| { type: "error", message: string };
type Brightness =
{ type: "off" }
| { type: "switch" }
| { type: "dimmer", value: number }
| { type: "strobe", value: number };
type MovingHeadState = {
start_address: number, // [0, 512]
pan: number, // [-3pi/2, 3pi/2]
tilt: number, // [-pi/2, pi/2]
brightness: Brightness,
rgbw: [number, number, number, number], // RGBW, [0, 255]
speed: number, // [255, 0]
}
type OutputHandle = {
set: (heads: [MovingHeadState, MovingHeadState, MovingHeadState, MovingHeadState]) => Result<{}>,
close: () => Result<{}>,
}
declare module rust_native_module { declare module rust_native_module {
function hello(): string; function listPorts(): Array<string>;
function openOutput(): Result<OutputHandle>;
} }
export = rust_native_module; export = rust_native_module;

View File

@ -1,41 +1,199 @@
pub mod output;
use std::cell::RefCell;
use output::controller::Controller;
use output::fixtures::{Brightness, DMXFixture, MovingHead};
type BoxedController = JsBox<RefCell<Controller>>;
impl Finalize for Controller {}
use neon::prelude::*; use neon::prelude::*;
fn hello(mut cx: FunctionContext) -> JsResult<JsString> { fn list_ports(mut cx: FunctionContext) -> JsResult<JsArray> {
Ok(cx.string("hello from rust 🦀")) let array = cx.empty_array();
}
struct MyStruct { if let Ok(ports) = serialport::available_ports() {
name: String, for (i, port) in ports.iter().enumerate() {
} let port_name = cx.string(&port.port_name);
array.set(&mut cx, i as u32, port_name)?;
impl MyStruct {
fn new(name: String) -> Self {
println!("NEW {}", &name);
Self { name }
} }
}
impl Drop for MyStruct {
fn drop(&mut self) {
println!("DROP {}", self.name);
} }
Ok(array)
} }
impl Finalize for MyStruct { fn open_output(mut cx: FunctionContext) -> JsResult<JsObject> {
fn finalize<'a, C: Context<'a>>(self, _: &mut C) { let path = cx.argument::<JsString>(0)?;
println!("FINALIZE {}", self.name); let path = path.value(&mut cx);
let obj = cx.empty_object();
let success_string;
match Controller::new(path) {
Ok(controller) => {
success_string = cx.string("success");
let boxed_controller = cx.boxed(RefCell::new(controller));
obj.set(&mut cx, "_rust_ptr", boxed_controller)?;
let set_output_function = JsFunction::new(&mut cx, set_output)?;
obj.set(&mut cx, "set", set_output_function)?;
let close_function = JsFunction::new(&mut cx, close_output)?;
obj.set(&mut cx, "close", close_function)?;
} }
Err(e) => {
success_string = cx.string("error");
let error_message = cx.string(e.to_string());
obj.set(&mut cx, "message", error_message)?;
}
}
obj.set(&mut cx, "type", success_string)?;
Ok(obj)
} }
fn test_box(mut cx: FunctionContext) -> JsResult<JsBox<MyStruct>> { fn set_output(mut cx: FunctionContext) -> JsResult<JsObject> {
let my_struct = MyStruct::new("Test Struct ayayay".to_string()); let arg = cx.argument::<JsArray>(0)?;
Ok(cx.boxed(my_struct)) let mut moving_heads = Vec::with_capacity(4);
for i in 0..4 {
// why isn't neon-serde maintained anymore? T_T
let head = arg
.get(&mut cx, i)?
.downcast_or_throw::<JsObject, _>(&mut cx)?;
moving_heads.push(MovingHead {
start_address: head
.get(&mut cx, "startAddress")?
.downcast_or_throw::<JsNumber, _>(&mut cx)?
.value(&mut cx) as usize,
pan: head
.get(&mut cx, "pan")?
.downcast_or_throw::<JsNumber, _>(&mut cx)?
.value(&mut cx),
tilt: head
.get(&mut cx, "tilt")?
.downcast_or_throw::<JsNumber, _>(&mut cx)?
.value(&mut cx),
brightness: {
let brightness = head
.get(&mut cx, "brightness")?
.downcast_or_throw::<JsObject, _>(&mut cx)?;
match brightness
.get(&mut cx, "type")?
.downcast_or_throw::<JsString, _>(&mut cx)?
.value(&mut cx)
.as_str()
{
"off" => Brightness::Off,
"dimmer" => Brightness::Dimmer(
brightness
.get(&mut cx, "value")?
.downcast_or_throw::<JsNumber, _>(&mut cx)?
.value(&mut cx),
),
"strobe" => Brightness::Strobe(
brightness
.get(&mut cx, "value")?
.downcast_or_throw::<JsNumber, _>(&mut cx)?
.value(&mut cx),
),
"switch" => Brightness::Switch,
s => {
let err = cx.string(format!("Invalid brightness type: {}", s));
return cx.throw(err);
}
}
},
rgbw: {
let rgbw = head
.get(&mut cx, "rgbw")?
.downcast_or_throw::<JsArray, _>(&mut cx)?;
let mut vec = Vec::new();
for i in 0..4 {
let v = rgbw
.get(&mut cx, i)?
.downcast_or_throw::<JsNumber, _>(&mut cx)?
.value(&mut cx) as u8;
vec.push(v);
}
(vec[0], vec[1], vec[2], vec[3])
},
speed: head
.get(&mut cx, "speed")?
.downcast_or_throw::<JsNumber, _>(&mut cx)?
.value(&mut cx) as u8,
})
}
let this = cx.this();
let boxed_controller = this
.get(&mut cx, "_rust_ptr")?
.downcast_or_throw::<BoxedController, _>(&mut cx)?;
let boxed_controller = boxed_controller.borrow_mut();
{
let mut state = boxed_controller.state.lock().unwrap();
for head in moving_heads {
head.render(&mut state.data);
}
}
let obj = cx.empty_object();
let success_text = cx.string("success".to_string());
obj.set(&mut cx, "type", success_text)?;
Ok(obj)
}
fn close_output(mut cx: FunctionContext) -> JsResult<JsObject> {
let this = cx.this();
let boxed_controller = this
.get(&mut cx, "_rust_ptr")?
.downcast_or_throw::<BoxedController, _>(&mut cx)?;
let obj = cx.empty_object();
let success_string;
match boxed_controller.borrow_mut().stop() {
Ok(()) => {
success_string = cx.string("success");
}
Err(e) => {
success_string = cx.string("error");
let error_message = cx.string(e.to_string());
obj.set(&mut cx, "message", error_message)?;
}
}
obj.set(&mut cx, "type", success_string)?;
Ok(obj)
} }
#[neon::main] #[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> { fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("hello", hello)?; cx.export_function("listPorts", list_ports)?;
cx.export_function("gib_box", test_box)?; cx.export_function("openOutput", open_output)?;
Ok(()) Ok(())
} }

View File

@ -0,0 +1,138 @@
use std::{
io,
sync::{Arc, Mutex},
thread::{self, JoinHandle},
time::{Duration, Instant},
};
use anyhow::{anyhow, Error, Result};
use serialport::SerialPort;
enum MCUResponse {
Sync,
Ack,
}
const FPS: u32 = 50;
fn poll_response(ser: &mut dyn SerialPort) -> Result<MCUResponse> {
let mut read_buffer = vec![0u8; 32];
let bytes_read;
loop {
match ser.read(read_buffer.as_mut_slice()) {
Ok(t) => {
bytes_read = t;
break;
}
Err(ref e) if e.kind() == io::ErrorKind::TimedOut => continue,
Err(e) => Err(e),
}?
}
let response = std::str::from_utf8(&read_buffer[..bytes_read])?;
match response.trim() {
"Sync." => Ok(MCUResponse::Sync),
"Ack." => Ok(MCUResponse::Ack),
s => Err(anyhow!("Unknown response: \"{}\"", s)),
}
}
pub struct Controller {
pub state: Arc<Mutex<SharedState>>,
thread: Option<JoinHandle<Result<(), Error>>>,
}
impl Controller {
pub fn new(path: String) -> Result<Self> {
let state = Arc::new(Mutex::new(SharedState {
running: true,
data: [0; 512],
}));
let ser = serialport::new(path, 500_000)
.timeout(Duration::from_millis(10))
.open()?;
let handle = {
let state = state.clone();
thread::spawn(move || controller_thread(ser, state))
};
Ok(Self {
state,
thread: Some(handle),
})
}
pub fn stop(&mut self) -> Result<()> {
{
let mut state = self.state.lock().unwrap();
state.running = false;
}
if self.thread.is_some() {
self.thread.take().unwrap().join().unwrap()
} else {
Err(anyhow!("thread was already closed"))
}
}
}
pub struct SharedState {
running: bool,
pub data: [u8; 512],
}
fn controller_thread(mut ser: Box<dyn SerialPort>, state: Arc<Mutex<SharedState>>) -> Result<()> {
let frame_time = Duration::from_secs_f64(1.0 / FPS as f64);
// wait for initial sync
loop {
match poll_response(&mut *ser) {
Ok(MCUResponse::Sync) => break,
_ => continue,
}
}
'main: loop {
{
let running = state.lock().unwrap().running;
if !running {
break Ok(());
}
}
let loop_start = Instant::now();
let write_result = {
let dmx_buffer = state.lock().unwrap().data;
ser.write(&dmx_buffer)
};
if write_result.is_err() {
loop {
match poll_response(&mut *ser) {
Ok(MCUResponse::Sync) => continue 'main,
_ => continue,
}
}
}
loop {
match poll_response(&mut *ser) {
Ok(MCUResponse::Ack) => break,
Ok(MCUResponse::Sync) => continue 'main,
Err(_) => continue, // Eventually the MCU will send a "Sync" again
}
}
let loop_time = loop_start.elapsed();
if loop_time < frame_time {
thread::sleep(frame_time - loop_time);
} else {
eprintln!("[DMX] loop took too long!");
}
}
}

View File

@ -0,0 +1,58 @@
use std::f64::consts::{FRAC_PI_2, PI};
use num::traits::float::Float;
pub fn rescale<T: Float>(x: T, from: (T, T), to: (T, T)) -> T {
let (a, b) = from;
let (c, d) = to;
c + (d - c) * (x - a) / (b - a)
}
pub enum Brightness {
Off,
Dimmer(f64), // 0 to 1
Strobe(f64), // 0 to 1
Switch, // No idea what this does
}
pub struct MovingHead {
pub start_address: usize,
pub pan: f64, // -3pi/2 to 3pi/2
pub tilt: f64, // -pi/2 to pi/2
pub brightness: Brightness,
pub rgbw: (u8, u8, u8, u8), // RGBW
pub speed: u8, // reversed
}
pub trait DMXFixture {
fn render(&self, dst: &mut [u8]);
}
impl DMXFixture for MovingHead {
fn render(&self, dst: &mut [u8]) {
let pan = rescale(self.pan, (-1.5 * PI, 1.5 * PI), (255.0, 0.0)) as u8;
let pan_fine = 0;
let tilt = rescale(self.tilt, (-1.0 * FRAC_PI_2, FRAC_PI_2), (0.0, 255.0)) as u8;
let tilt_fine = 0;
let dimmer = match self.brightness {
Brightness::Off => 0,
Brightness::Dimmer(dimmer) => rescale(dimmer, (0.0, 1.0), (8.0, 134.0)) as u8,
Brightness::Strobe(strobe) => rescale(strobe, (0.0, 1.0), (135.0, 239.0)) as u8,
Brightness::Switch => 255,
};
let (r, g, b, w) = self.rgbw;
let offset = self.start_address - 1;
let channels = [
pan, pan_fine, tilt, tilt_fine, self.speed, dimmer, r, g, b, w, 0, 0, 0, 0,
];
dst[offset..offset + channels.len()].copy_from_slice(&channels);
}
}

View File

@ -0,0 +1,2 @@
pub mod fixtures;
pub mod controller;