//! Sample usage program.
//!
//! I use it to control a "ShowTec Compact par 7-3" on channel 1
//!
//! This is just to show how to use the library, and is not intended to be efficient,...
//!
//! The mini-command language *is* underpowered,...
use regex::Regex;
use std::error::Error;
use std::fs::File;
use std::io::BufRead;
use std::path::Path;
use std::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, io};
use libvm116::prelude::*;
fn get_epoch_ms() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis()
}
fn read_lines
(filename: P) -> io::Result>>
where
P: AsRef,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
/// Commands for the mini-language
#[derive(Debug)]
enum Command {
/// set a label, to which we will be able to return with 'loop'
Label { label: String },
/// loop a number of times to a label
Loop { label: String, i: u16, start: u16 },
/// wait some time
Wait { millis: u32 },
/// sets the light to rgb (on the current channel)
Rgb { r: u8, g: u8, b: u8 },
/// change the current channel
Channel { channel: u16 },
/// set all channels to 0
Reset {},
/// finish early
Exit {},
}
fn main() -> Result<(), Box> {
env_logger::init();
let mut commands = vec![];
let re_comment = Regex::new(r"(#.*)").unwrap();
let re_rgb = Regex::new(r"^(?i)rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$").unwrap();
let re_wait = Regex::new(r"^(?i)wait\((\d+)\)$").unwrap();
let re_loop = Regex::new(r"^(?i)loop\(([a-z]+),(\d+)\)$").unwrap();
let re_label = Regex::new(r"^(?i)label\(([a-z]+)\)$").unwrap();
let re_exit = Regex::new(r"^(?i)exit\(\)$").unwrap();
let re_reset = Regex::new(r"^(?i)reset\(\)$").unwrap();
let re_channel = Regex::new(r"^(?i)channel\((\d+)\)$").unwrap();
let args: Vec = env::args().collect();
log::debug!("args({}): {:?}", args.len(), args);
if args.len() == 2 {
if let Ok(lines) = read_lines(&args[1]) {
for mut line in lines {
if let Ok(line) = &mut line {
let line = re_comment.replace(line, "");
for expr in line.split(';') {
let expr = expr.trim();
if expr.is_empty() {
continue;
}
// allow multiple instructions per line
if let Some(cap) = re_label.captures(expr) {
commands.push(Command::Label {
label: cap[1].to_string(),
})
} else if let Some(cap) = re_loop.captures(expr) {
let start = cap[2].parse()?;
commands.push(Command::Loop {
label: cap[1].to_string(),
i: start,
start,
})
} else if let Some(cap) = re_rgb.captures(expr) {
commands.push(Command::Rgb {
r: cap[1].parse()?,
g: cap[2].parse()?,
b: cap[3].parse()?,
})
} else if let Some(cap) = re_wait.captures(expr) {
commands.push(Command::Wait {
millis: cap[1].parse()?,
})
} else if let Some(cap) = re_channel.captures(expr) {
commands.push(Command::Channel {
channel: cap[1].parse()?,
})
} else if re_exit.is_match(expr) {
commands.push(Command::Exit {})
} else if re_reset.is_match(expr) {
commands.push(Command::Reset {})
} else {
Err(format!("Invalid command: '{}'", expr))?;
}
}
}
}
}
} else {
let e = |n: usize| {
args.get(n)
.map(|s| s.parse().ok())
.flatten()
.unwrap_or(255u8)
};
let r = e(1);
let g = e(2);
let b = e(3);
commands.push(Command::Rgb { r, g, b });
commands.push(Command::Wait { millis: 0 });
}
log::debug!("Command: {:?}", commands);
let vm116 = Vm116::new()?; // vm116 - K8062
log::debug!("Opened ok");
let mut idx = 0;
let mut ref_time = get_epoch_ms();
let mut dmx_state = DmxState::new(512)?;
let mut base_channel = 1u16;
loop {
let mut goto_label = None;
if let Some(command) = commands.get_mut(idx) {
match command {
Command::Rgb { r, g, b } => {
dmx_state.set(0 + base_channel, *r)?;
dmx_state.set(1 + base_channel, *g)?;
dmx_state.set(2 + base_channel, *b)?;
}
Command::Wait { millis } => {
// we calculate until when we should wait, so that if we 'lost' time sending data,
// we will still be able to by synchronized (ie: 100 times 'wait(100)' will
// really wait until 10 sec is elapsed.
if *millis > 0 {
let wait_for = ref_time + *millis as u128;
let now = get_epoch_ms();
if wait_for > now {
let wait_millis = wait_for - now;
std::thread::sleep(Duration::from_millis(wait_millis as u64));
}
ref_time = wait_for;
}
let n_packets = vm116.send(&dmx_state)?;
log::debug!("Sent {n_packets} packets");
}
Command::Exit {} => {
let n_packets = vm116.send(&dmx_state)?;
log::debug!("Sent {n_packets} packets");
break;
}
Command::Label { label: _ } => {
// nothing to do
}
Command::Loop { label, i, start } => {
if *i == 0u16 {
// reset the loop counter
*i = *start;
} else {
goto_label = Some(label);
*i -= 1;
}
}
Command::Channel { channel } => {
base_channel = *channel;
}
Command::Reset { .. } => {
dmx_state.reset();
}
}
} else {
break;
}
if let Some(goto_label) = goto_label {
let gl = goto_label.clone();
if let Some(label_idx) = commands.iter().position(|c| {
if let Command::Label { label } = c {
label == &gl
} else {
false
}
}) {
idx = label_idx;
};
}
idx = idx + 1;
}
log::debug!("Finished");
Ok(())
}