use std::collections::HashMap;
use std::convert::TryInto;
use std::error::Error;
use std::io::{stdin, Write};
use std::sync::mpsc::channel;
use std::thread;
use websocket::client::ClientBuilder;
use websocket::{CloseData, OwnedMessage, WebSocketError};
use ichen_openprotocol::Message;
use ichen_openprotocol::{Filters, JobCard};
struct Constants<'a> {
users: HashMap<&'a str, (u8, String)>,
jobs: Vec<JobCard<'a>>,
}
fn display_message(prefix: &str, msg: &Message) {
print!("{}", prefix);
match msg {
Message::Alive { options, .. } => println!("Alive({})", options.sequence),
Message::RequestControllersList { controller_id: None, options, .. } => {
println!("RequestControllersList({})", options.sequence)
}
Message::RequestControllersList { controller_id: Some(id), options, .. } => {
println!("RequestControllersList({}, {})", id, options.sequence)
}
Message::RequestJobCardsList { controller_id, options, .. } => {
println!("RequestJobCardsList({}, {})", controller_id, options.sequence)
}
Message::RequestMoldData { controller_id, options, .. } => {
println!("RequestMoldData({}, {})", controller_id, options.sequence)
}
Message::ReadMoldData { controller_id, field: None, options, .. } => {
println!("RequestMoldData({}, ALL, {})", controller_id, options.sequence)
}
Message::ReadMoldData { controller_id, field: Some(fld), options, .. } => {
println!("RequestMoldData({}, [{}], {})", controller_id, fld, options.sequence)
}
Message::ControllerAction { controller_id, action_id, options, .. } => {
println!("ControllerAction({}, [{}], {})", controller_id, action_id, options.sequence)
}
m => {
if prefix.is_empty() {
println!("{:#?}", m);
} else {
println!("\n{:#?}", m)
}
}
}
}
fn process_incoming_message<'a>(json: &'a str, builtin: &'a Constants<'a>) -> Option<Message<'a>> {
let message;
match Message::parse_from_json_str(json) {
Ok(m) => {
display_message(">>> ", &m);
message = m;
}
Err(err) => {
eprintln!("Error parsing message: {}", err);
return None;
}
}
match message {
Message::Alive { .. } => Some(Message::new_alive()),
Message::JoinResponse { result, .. } if result < 100 => {
eprintln!("Failed to JOIN: error code = {}", result);
None
}
Message::JoinResponse { .. } => Some(Message::RequestControllersList {
controller_id: None,
options: Default::default(),
}),
Message::LoginOperator { controller_id, password, .. } => {
if let Some((level, name)) = builtin.users.get(password) {
println!("User found: password=[{}], access level={}.", password, level);
Some(Message::OperatorInfo {
controller_id,
operator_id: Some((u32::from(*level) + 1).try_into().unwrap()), name,
password,
level: *level,
options: Default::default(),
})
} else {
println!("No user found with password: [{}].", password);
Some(Message::OperatorInfo {
controller_id,
operator_id: None,
name: "Not Allowed",
password,
level: 0,
options: Default::default(),
})
}
}
Message::RequestJobCardsList { controller_id, .. } => Some(Message::JobCardsList {
controller_id,
data: builtin.jobs.iter().map(|jc| (jc.job_card_id.as_ref(), jc.clone())).collect(), options: Default::default(),
}),
_ => None,
}
}
fn main() {
println!("iChen 4 Open Protocol Viewer");
println!();
print!("WebSocket URL (example: ws://x.x.x.x:port): ");
std::io::stdout().flush().expect("Failed to flush stdout.");
let mut input = String::new();
stdin().read_line(&mut input).expect("Failed to read line from stdin.");
let conn = input.trim();
if conn.is_empty() {
eprintln!("URL cannot be empty.");
return;
} else if conn.starts_with("wss://") {
eprintln!("This program is intended as a simple example for illustration purposes only.");
eprintln!("Due to added complexity, the wss: protocol is not supported by this program.");
return;
} else if !conn.starts_with("ws://") {
eprintln!("Invalid WebSocket URL format. Should be: ws://x.x.x.x:port");
return;
}
print!("Password: ");
std::io::stdout().flush().expect("Failed to flush stdout.");
let mut input = String::new();
stdin().read_line(&mut input).expect("Failed to read line from stdin.");
let password = input.trim();
if password.is_empty() {
eprintln!("Password cannot be empty.");
return;
}
println!("Connecting to iChen Server at {}...", conn);
let mut ws_builder;
match ClientBuilder::new(conn) {
Ok(b) => ws_builder = b,
Err(err) => {
eprintln!("Invalid URL: {}", err);
return;
}
}
let client;
match ws_builder.connect_insecure() {
Ok(c) => client = c,
Err(err) => {
eprintln!("Connect connect to server: {}", &err);
eprintln!(
"{}",
match &err {
WebSocketError::ProtocolError(e)
| WebSocketError::RequestError(e)
| WebSocketError::ResponseError(e)
| WebSocketError::DataFrameError(e) => e,
WebSocketError::IoError(e) => e.description(),
WebSocketError::HttpError(e) => e.description(),
WebSocketError::UrlError(e) => e.description(),
WebSocketError::TlsError(e) => e.description(),
WebSocketError::Utf8Error(e) => e.description(),
WebSocketError::WebSocketUrlError(e) => e.description(),
WebSocketError::StatusCodeError(_)
| WebSocketError::NoDataAvailable
| WebSocketError::TlsHandshakeFailure
| WebSocketError::TlsHandshakeInterruption => return,
}
);
return;
}
}
println!("Connection to iChen Server established.");
let builtin = Constants {
users: [
"000000", "111111", "222222", "333333", "444444", "555555", "666666", "777777",
"888888", "999999", "123456",
]
.iter()
.enumerate()
.map(|(index, value)| (*value, (index as u8, format!("MISUser{}", index))))
.collect(),
jobs: vec![
JobCard::new("JOB_CARD_1", "ABC-123", 0, 8000),
JobCard::new("JOB_CARD_2", "M002", 2000, 10000),
JobCard::new("JOB_CARD_3", "MOULD_003", 888, 3333),
JobCard::new("JOB_CARD_4", "MOULD_004", 123, 45678),
],
};
println!("=================================================");
println!("Built-in Users for Testing:");
builtin
.users
.iter()
.for_each(|(u, (a, n))| println!("> Name={}, Password={}, Level={}", n, u, a));
println!("=================================================");
println!("Built-in Job Cards for Testing:");
builtin.jobs.iter().for_each(|j| {
println!(
"> Name={}, Mold={}, Quantity={}/{}",
j.job_card_id, j.mold_id, j.progress, j.total
)
});
println!("=================================================");
println!("Press ENTER to quit...");
let (mut receiver, mut sender) = client.split().expect("Failed to split WebSocket channel.");
let (tx, rx) = channel();
let txx = tx.clone();
let receive_loop = thread::spawn(move || {
for message in receiver.incoming_messages() {
let message = match message {
Ok(msg) => msg,
Err(err) => {
eprintln!("Error receiving message: {}", err);
txx.send(OwnedMessage::Close(Some(CloseData::new(
1,
format!("Error receiving message: {}", err),
))))
.unwrap();
return;
}
};
match message {
OwnedMessage::Close(data) => {
if let Some(d) = data {
println!("WebSocket closed: ({}) {}", d.status_code, d.reason);
} else {
println!("WebSocket closed.");
}
return;
}
OwnedMessage::Ping(data) => txx.send(OwnedMessage::Pong(data)).unwrap(),
OwnedMessage::Text(json) => {
println!("Received ({}): {}", json.len(), json);
if let Some(msg) = process_incoming_message(&json, &builtin) {
match msg.to_json_str() {
Ok(resp) => {
txx.send(OwnedMessage::Text(resp)).unwrap();
display_message("<<< ", &msg);
}
Err(err) => eprintln!("Error serializing message: {}", err),
}
}
}
OwnedMessage::Binary(data) => {
println!("Received binary data: {} byte(s)", data.len())
}
_ => println!("Received: {:#?}", message),
}
}
});
let send_loop = thread::spawn(move || {
thread::sleep(std::time::Duration::from_secs(1));
for message in rx {
match sender.send_message(&message) {
Ok(()) => match message {
OwnedMessage::Close(data) => {
if let Some(d) = data {
println!(
"Closing WebSocket connection: ({}) {}",
d.status_code, d.reason
);
} else {
println!("Closing WebSocket connection...");
}
return;
}
OwnedMessage::Text(json) => println!("Sent ({}): {}", json.len(), json),
OwnedMessage::Binary(data) => println!("Sent data: {} byte(s)", data.len()),
_ => (),
},
Err(err) => {
eprintln!("Error sending message: {}", err);
let _ = sender.send_message(&websocket::Message::close());
println!("Closing WebSocket connection...");
return;
}
}
}
});
println!("Sending JOIN message...");
let msg = Message::new_join(password, Filters::All + Filters::JobCards + Filters::Operators);
match msg.to_json_str() {
Ok(m) => match tx.send(OwnedMessage::Text(m)) {
Ok(()) => (),
Err(err) => eprintln!("Error when sending JOIN message: {}", err),
},
Err(err) => eprintln!("Error in JOIN message: {}", err),
}
let mut input = String::new();
stdin().read_line(&mut input).expect("Failed to read line from stdin.");
let _ =
tx.send(OwnedMessage::Close(Some(<CloseData>::new(0, "Program termination.".to_string()))));
println!("Waiting for child threads to exit...");
let _ = send_loop.join();
let _ = receive_loop.join();
println!("Program terminated.");
}