use std::io::prelude::*;
use std::io::{Error, ErrorKind};
use std::net::TcpStream;
extern crate md5;
const AUTH: char = '1';
const NOAUTH: char = '0';
static PORT: &'static str = "4352";
fn pjlink_error(error_msg: &str) -> Error {
match &error_msg[0..4] {
"ERR1" => Error::new(ErrorKind::InvalidData, "Undefined command".to_string()),
"ERR2" => Error::new(ErrorKind::InvalidData, "Invalid parameter".to_string()),
"ERR3" => Error::new(
ErrorKind::InvalidData,
"Unavaiable at this time".to_string(),
),
"ERR4" => Error::new(
ErrorKind::InvalidData,
"Projector/Display Failure".to_string(),
),
"ERRA" => Error::new(
ErrorKind::PermissionDenied,
"Authorization Error".to_string(),
),
_ => Error::new(
ErrorKind::InvalidData,
format!("Error reported from the projector {}", error_msg),
),
}
}
fn parse_response(response: &str) -> Result<PjlinkResponse, Error> {
let mut equals_sign: usize = 0;
let len = response.len();
for (i, c) in response.chars().enumerate() {
if c == '=' || c == ' ' {
equals_sign = i;
break;
}
}
let command = if &response[0..1] != "%" {
CommandType::PJLINK
} else {
match &response[2..equals_sign] {
"POWR" => CommandType::Power,
"INPT" => CommandType::Input,
"AVMT" => CommandType::AvMute,
"ERST" => CommandType::ErrorStatus,
"LAMP" => CommandType::Lamp,
"INST" => CommandType::InputList,
"NAME" => CommandType::Name,
"INF1" => CommandType::Manufacturer,
"INF2" => CommandType::ProductName,
"INFO" => CommandType::Information,
"CLSS" => CommandType::Class,
_ => {
return Err(Error::new(
ErrorKind::InvalidInput,
"Invalid command type returned.",
));
}
}
};
let value = &response[equals_sign + 1..len];
if value.len() == 4 && &value[0..3] == "ERR" {
return Err(pjlink_error(value));
}
Ok(PjlinkResponse {
action: command,
value: value.to_string(),
})
}
enum CommandType {
PJLINK,
Power,
Input,
AvMute,
ErrorStatus,
Lamp,
InputList,
Name,
Manufacturer,
ProductName,
Information,
Class,
}
pub enum PowerStatus {
Off,
On,
Cooling,
Warmup,
}
pub enum InputType {
RGB(u8),
Video(u8),
Digital(u8),
Storage(u8),
Network(u8),
}
pub enum ErrorType {
NoError,
Warning,
Error,
}
pub struct AvMute {
pub audio: bool,
pub video: bool,
}
pub struct Lamp {
pub hours: u16,
pub on: bool,
}
pub struct ErrorStatus {
pub fan_error: ErrorType,
pub lamp_error: ErrorType,
pub temperature_error: ErrorType,
pub cover_open_error: ErrorType,
pub filter_error: ErrorType,
pub other_error: ErrorType,
}
struct PjlinkResponse {
action: CommandType,
value: String,
}
pub struct PjlinkDevice {
pub host: String,
password: String,
}
impl PjlinkDevice {
pub fn new(host: &str) -> Result<PjlinkDevice, Error> {
let pwd = String::from("");
PjlinkDevice::new_with_password(host, &pwd)
}
pub fn new_with_password(host: &str, password: &str) -> Result<PjlinkDevice, Error> {
Ok(PjlinkDevice {
host: host.to_string(),
password: String::from(password),
})
}
pub fn send_command(&self, command: &str) -> Result<String, Error> {
let host_port = [&self.host, ":", PORT].concat();
let mut client_buffer = [0u8; 256];
let mut stream = try!(TcpStream::connect(host_port));
let _ = stream.read(&mut client_buffer);
let cmd: String = match client_buffer[7] as char {
AUTH => {
let rnd_num = String::from_utf8_lossy(&client_buffer[9..17]).to_string();
if &self.password != "" {
let pwd_str = format!("{}{}", rnd_num, &self.password);
let digest = md5::compute(pwd_str);
format!("{:x}%1{}\r", digest, command)
} else {
return Err(Error::new(
ErrorKind::InvalidInput,
"This device requires a password and one was not supplied.",
));
}
}
NOAUTH => {
format!("%1{}\r", command)
}
_ => {
return Err(Error::new(
ErrorKind::InvalidInput,
"Invalid response or is not a PJLink device",
));
}
};
let result = stream.write(cmd.as_bytes());
match result {
Ok(_) => (),
Err(e) => return Err(e),
};
let result = stream.read(&mut client_buffer);
let len = match result {
Ok(len) => len,
Err(e) => return Err(e),
};
let response = String::from_utf8_lossy(&client_buffer[0..len - 1]).to_string();
Ok(response)
}
fn send(&self, cmd: &str) -> Result<PjlinkResponse, Error> {
match self.send_command(cmd) {
Ok(send_result) => match parse_response(&send_result) {
Ok(parse_result) => Ok(parse_result),
Err(e) => Err(e),
},
Err(e) => Err(e),
}
}
pub fn get_power_status(&self) -> Result<PowerStatus, Error> {
match self.send("POWR ?") {
Ok(result) => {
match result.action {
CommandType::Power => {
match &result.value[0..1] {
"0" => Ok(PowerStatus::Off),
"1" => Ok(PowerStatus::On),
"2" => Ok(PowerStatus::Cooling),
"3" => Ok(PowerStatus::Warmup),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response: {}", result.value),
)),
}
}
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Got a response we didn't expect: {}", result.value),
)),
}
}
Err(e) => Err(e),
}
}
pub fn power_on(&self) -> Result<PowerStatus, Error> {
match self.send("POWR 1") {
Ok(result) => {
match result.action {
CommandType::Power => {
match &result.value[0..2] {
"OK" => match self.get_power_status() {
Ok(status) => Ok(status),
Err(e) => Err(e),
},
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response: {}", result.value),
)),
}
}
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Got a response we didn't expect: {}", result.value),
)),
}
}
Err(e) => Err(e),
}
}
pub fn power_off(&self) -> Result<PowerStatus, Error> {
match self.send("POWR 0") {
Ok(result) => {
match result.action {
CommandType::Power => {
match &result.value[0..2] {
"OK" => match self.get_power_status() {
Ok(status) => Ok(status),
Err(e) => Err(e),
},
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response: {}", result.value),
)),
}
}
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Got a response we didn't expect: {}", result.value),
)),
}
}
Err(e) => Err(e),
}
}
pub fn get_info(&self) -> Result<String, Error> {
match self.send("INFO ?") {
Ok(result) => match result.action {
CommandType::Information => Ok(result.value),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response:: {}", result.value),
)),
},
Err(e) => Err(e),
}
}
pub fn get_manufacturer(&self) -> Result<String, Error> {
match self.send("INF1 ?") {
Ok(result) => match result.action {
CommandType::Manufacturer => Ok(result.value),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response:: {}", result.value),
)),
},
Err(e) => Err(e),
}
}
pub fn get_product_name(&self) -> Result<String, Error> {
match self.send("INF2 ?") {
Ok(result) => match result.action {
CommandType::ProductName => Ok(result.value),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response:: {}", result.value),
)),
},
Err(e) => Err(e),
}
}
pub fn get_class(&self) -> Result<String, Error> {
match self.send("CLSS ?") {
Ok(result) => match result.action {
CommandType::Class => Ok(result.value),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response:: {}", result.value),
)),
},
Err(e) => Err(e),
}
}
pub fn get_device_name(&self) -> Result<String, Error> {
match self.send("NAME ?") {
Ok(result) => match result.action {
CommandType::Name => Ok(result.value),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response:: {}", result.value),
)),
},
Err(e) => Err(e),
}
}
pub fn get_input(&self) -> Result<InputType, Error> {
match self.send("INPT ?") {
Ok(result) => {
let input = result.value.parse::<u8>().unwrap();
match input {
11...19 => Ok(InputType::RGB(input - 10)),
21...29 => Ok(InputType::Video(input - 20)),
31...39 => Ok(InputType::Digital(input - 30)),
41...49 => Ok(InputType::Storage(input - 40)),
51...59 => Ok(InputType::Network(input - 50)),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid input:: {}", input),
)),
}
}
Err(e) => Err(e),
}
}
pub fn set_input(&self, input: InputType) -> Result<InputType, Error> {
let input_number: u8 = match input {
InputType::RGB(i_num) => i_num + 10,
InputType::Video(i_num) => i_num + 20,
InputType::Digital(i_num) => i_num + 30,
InputType::Storage(i_num) => i_num + 40,
InputType::Network(i_num) => i_num + 50,
};
let command = format!("INPT {}", input_number);
match self.send(&command) {
Ok(result) => {
match result.action {
CommandType::Input => {
match &result.value[0..2] {
"OK" => match self.get_input() {
Ok(status) => Ok(status),
Err(e) => Err(e),
},
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response: {}", result.value),
)),
}
}
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Got a response we didn't expect: {}", result.value),
)),
}
}
Err(e) => Err(e),
}
}
pub fn get_avmute(&self) -> Result<AvMute, Error> {
match self.send("AVMT ?") {
Ok(result) => {
let status = result.value.parse::<u8>().unwrap();
match status {
11 => Ok(AvMute {
audio: false,
video: true,
}),
21 => Ok(AvMute {
audio: true,
video: false,
}),
31 => Ok(AvMute {
audio: true,
video: true,
}),
30 => Ok(AvMute {
audio: false,
video: false,
}),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid result:: {}", status),
)),
}
}
Err(e) => Err(e),
}
}
pub fn set_avmute(&self, mute_status: AvMute) -> Result<AvMute, Error> {
let mutes: u8 = match mute_status {
AvMute {
video: true,
audio: false,
} => 11,
AvMute {
video: false,
audio: true,
} => 21,
AvMute {
video: true,
audio: true,
} => 31,
_ => 30,
};
let command = format!("AVMT {}", mutes);
match self.send(&command) {
Ok(result) => {
match result.action {
CommandType::AvMute => {
match &result.value[0..2] {
"OK" => match self.get_avmute() {
Ok(status) => Ok(status),
Err(e) => Err(e),
},
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Invalid Response: {}", result.value),
)),
}
}
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Got a response we didn't expect: {}", result.value),
)),
}
}
Err(e) => Err(e),
}
}
pub fn get_lamp(&self) -> Result<Vec<Lamp>, Error> {
match self.send("LAMP ?") {
Ok(result) => {
let mut status = result.value.split_whitespace();
let mut lamps = Vec::new();
while let Some(l) = status.next() {
let hours = l.parse::<u16>().unwrap();
let on = match status.next() {
Some(x) => x == "1",
None => false,
};
lamps.push(Lamp {
hours: hours,
on: on,
});
}
Ok(lamps)
}
Err(e) => Err(e),
}
}
pub fn get_error_status(&self) -> Result<ErrorStatus, Error> {
match self.send("ERST ?") {
Ok(result) => {
let mut status = result.value.chars();
Ok(ErrorStatus {
fan_error: match status.next() {
Some(e) => match e {
'0' => ErrorType::NoError,
'1' => ErrorType::Warning,
'2' => ErrorType::Error,
_ => ErrorType::NoError,
},
None => ErrorType::NoError,
},
lamp_error: match status.next() {
Some(e) => match e {
'0' => ErrorType::NoError,
'1' => ErrorType::Warning,
'2' => ErrorType::Error,
_ => ErrorType::NoError,
},
None => ErrorType::NoError,
},
temperature_error: match status.next() {
Some(e) => match e {
'0' => ErrorType::NoError,
'1' => ErrorType::Warning,
'2' => ErrorType::Error,
_ => ErrorType::NoError,
},
None => ErrorType::NoError,
},
cover_open_error: match status.next() {
Some(e) => match e {
'0' => ErrorType::NoError,
'1' => ErrorType::Warning,
'2' => ErrorType::Error,
_ => ErrorType::NoError,
},
None => ErrorType::NoError,
},
filter_error: match status.next() {
Some(e) => match e {
'0' => ErrorType::NoError,
'1' => ErrorType::Warning,
'2' => ErrorType::Error,
_ => ErrorType::NoError,
},
None => ErrorType::NoError,
},
other_error: match status.next() {
Some(e) => match e {
'0' => ErrorType::NoError,
'1' => ErrorType::Warning,
'2' => ErrorType::Error,
_ => ErrorType::NoError,
},
None => ErrorType::NoError,
},
})
}
Err(e) => Err(e),
}
}
}