generated from esd2-groupwork/template-repository
Compare commits
47 commits
Author | SHA1 | Date | |
---|---|---|---|
1ed406e929 | |||
78415feec3 | |||
099ffbcaca | |||
4a30d40d34 | |||
c12a476171 | |||
4455a96cbb | |||
e43c2c42cf | |||
d23894fba6 | |||
658ba709d1 | |||
41b95647a2 | |||
fd07adbb75 | |||
abb5263763 | |||
c5c62c5dbc | |||
57c514a289 | |||
0dff50269f | |||
57957e9285 | |||
1c3f60196c | |||
69fb508486 | |||
7f34d56ba3 | |||
6a78a80e85 | |||
0f03ec54ff | |||
4d4af9cf19 | |||
7197b71f0a | |||
eefaa646b1 | |||
572734ff0d | |||
445cf844c8 | |||
cdbe8e4659 | |||
67be087183 | |||
c72be40454 | |||
34e6699adf | |||
5bf7745ca6 | |||
2738513f3e | |||
a68d4efebc | |||
f8d6645690 | |||
5b0172b2b9 | |||
c01fd63211 | |||
1d775a5e19 | |||
bb6b883c4b | |||
e1b4dcafde | |||
509685ff0e | |||
fbd0ac4ca0 | |||
2f09bfb0f6 | |||
eec82cdfb5 | |||
beb0cd25dd | |||
0b367c019f | |||
23276e94d2 | |||
6f4ddf5e00 |
25 changed files with 24832 additions and 267 deletions
1
.cargo/config.toml
Normal file
1
.cargo/config.toml
Normal file
|
@ -0,0 +1 @@
|
|||
[build]
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
|||
target/
|
||||
logs/
|
||||
test.png
|
||||
*.png
|
||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "reference_material"]
|
||||
path = reference_material
|
||||
url = https://github.com/ravvenlabs/userspace-vdma-driver
|
2204
Cargo.lock
generated
2204
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "communication-layer"
|
||||
name = "comms"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
@ -9,7 +9,10 @@ edition = "2021"
|
|||
chrono = "0.4.35"
|
||||
fern = "0.6.2"
|
||||
image = { version = "0.25.0", default-features = false, features = ["png","ff","gif"] }
|
||||
imageproc = "0.24.0"
|
||||
ipnet = "2.9.0"
|
||||
local-ip-address = "0.6.1"
|
||||
log = "0.4.21"
|
||||
rand = "0.8.5"
|
||||
rocket = "0.5.0"
|
||||
rocket_cors = "0.6.0"
|
||||
|
|
BIN
Side_Ref.png
Normal file
BIN
Side_Ref.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 MiB |
BIN
Top_Ref.png
Normal file
BIN
Top_Ref.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 MiB |
1774
distributed/serve1.csv
Normal file
1774
distributed/serve1.csv
Normal file
File diff suppressed because it is too large
Load diff
1853
distributed/serve2.csv
Normal file
1853
distributed/serve2.csv
Normal file
File diff suppressed because it is too large
Load diff
1921
distributed/serve3.csv
Normal file
1921
distributed/serve3.csv
Normal file
File diff suppressed because it is too large
Load diff
2029
distributed/serve4.csv
Normal file
2029
distributed/serve4.csv
Normal file
File diff suppressed because it is too large
Load diff
1739
distributed/serve5.csv
Normal file
1739
distributed/serve5.csv
Normal file
File diff suppressed because it is too large
Load diff
1956
distributed/volley1.csv
Normal file
1956
distributed/volley1.csv
Normal file
File diff suppressed because it is too large
Load diff
1447
distributed/volley2.csv
Normal file
1447
distributed/volley2.csv
Normal file
File diff suppressed because it is too large
Load diff
4182
distributed/volley3.csv
Normal file
4182
distributed/volley3.csv
Normal file
File diff suppressed because it is too large
Load diff
2067
distributed/volley4.csv
Normal file
2067
distributed/volley4.csv
Normal file
File diff suppressed because it is too large
Load diff
1396
distributed/volley5.csv
Normal file
1396
distributed/volley5.csv
Normal file
File diff suppressed because it is too large
Load diff
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
0
generated/.gitkeep
Normal file
0
generated/.gitkeep
Normal file
1
reference_material
Submodule
1
reference_material
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit cfbb72d5ffac8d253ac6653e268efc76f7d0a3d5
|
1774
serve1.dat
Normal file
1774
serve1.dat
Normal file
File diff suppressed because it is too large
Load diff
453
src/cam_comm.rs
Normal file
453
src/cam_comm.rs
Normal file
|
@ -0,0 +1,453 @@
|
|||
use chrono::Utc;
|
||||
use imageproc::contours::find_contours;
|
||||
use local_ip_address::local_ip;
|
||||
use rocket::form::validate::Contains;
|
||||
use std::{ f64::consts::PI, fs::File, io::{stdin, BufReader, Bytes, Error, ErrorKind, Read, Write}, net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpStream}, thread::{self, JoinHandle}, time::Duration};
|
||||
use image::{io::Reader, DynamicImage, Rgb, RgbImage};
|
||||
use ipnet::{Ipv4Net,PrefixLenError};
|
||||
use log::{trace,debug,warn,error,info};
|
||||
//use crate::vdma_facade::feedthrough::VdmaHandle;
|
||||
|
||||
///RIT owns the subnet 129.21.0.0/16; probing by attempting to open thousands of TCP streams
|
||||
///simultaneously is unwise.
|
||||
const UNSAFE_SUBNET:Result<Ipv4Net,PrefixLenError> = Ipv4Net::new(Ipv4Addr::new(129,21,0,0),16);
|
||||
|
||||
///This subnet is universally considered the "local" subnet. This results in 256 addresses, and can
|
||||
///be probed both safely and efficiently, and as long as the world server on the LAN is running it
|
||||
///is functionally guaranteeed to be found.
|
||||
const HOME_SUBNET:Result<Ipv4Net,PrefixLenError> = Ipv4Net::new(Ipv4Addr::new(192,168,0,0),24);
|
||||
|
||||
///Current communications takes place on custom port.
|
||||
///Fix me: RTSP port is 554; prepare for real world situations
|
||||
const PORT_NUMBER:u16 = 55001;
|
||||
///Port probe timeout, for
|
||||
const PORT_PROBE_TIMEOUT:Duration = Duration::new(5,0);
|
||||
///The standard subnet is a /24
|
||||
const SUBNET_SIZE:u8 = 24;
|
||||
|
||||
const RADIANS_TO_DEGREES:f64 = 180.0 / PI;
|
||||
|
||||
///Storage of all world serve connection information
|
||||
pub struct WorldServerConnection{
|
||||
///Store all open TcpStreams
|
||||
port_list:Vec<(TcpStream,BufReader<TcpStream>)>,
|
||||
///Unique image identifier
|
||||
image_index:u64,
|
||||
//VDMA instance
|
||||
//vdma:Option<VdmaHandle>,
|
||||
}
|
||||
impl WorldServerConnection{
|
||||
pub fn new() -> Result<Self,Error>{
|
||||
//Import ref images
|
||||
let host_ip = local_ip().expect("No real IP address!");
|
||||
trace!("Host IP: {:?}",host_ip);
|
||||
let mut thread_list:Vec<JoinHandle<Result<TcpStream,Error>>> = Vec::new();
|
||||
let mut port_list:Vec<(TcpStream,BufReader<TcpStream>)> = Vec::new();
|
||||
if let IpAddr::V4(host_v4_ip) = host_ip{
|
||||
//Check current subnet
|
||||
let net = Ipv4Net::new(host_v4_ip,SUBNET_SIZE);
|
||||
|
||||
if let Ok(net) = net{
|
||||
//RIT owns 129.21.0.0/16; DO NOT SPAM THIS NETWORK
|
||||
if UNSAFE_SUBNET.unwrap().contains(&net){
|
||||
//Request world server address from user
|
||||
let address = manual_address_prompt();
|
||||
if let Ok(real_address) = address{
|
||||
let reader = BufReader::new(real_address.try_clone().expect("Failed clone"));
|
||||
port_list.push((real_address,reader));
|
||||
//thread_list won't be added to, so the thread for loop won't execute
|
||||
}
|
||||
} else if net.contains(&HOME_SUBNET.unwrap()){
|
||||
//Collect all Hosts on the current subnet
|
||||
info!("aggregating all IPs... This may take several minutes...");
|
||||
for net_address in net.hosts(){
|
||||
thread_list.push(
|
||||
thread::spawn(move ||{
|
||||
let ip_port = std::net::SocketAddr::V4(SocketAddrV4::new(net_address, PORT_NUMBER));
|
||||
//Attempt to open a TCP stream with this IP address, exit the thread
|
||||
//returning the open TcpStream, or an error
|
||||
return TcpStream::connect_timeout(&ip_port, PORT_PROBE_TIMEOUT);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Multithread the port-sniffing, only save sucessful connections
|
||||
for thread in thread_list{
|
||||
let output = thread.join().unwrap();
|
||||
if let Ok(real_output) = output{
|
||||
let reader = BufReader::new(real_output.try_clone().expect("Failed clone"));
|
||||
port_list.push((real_output,reader));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Self{port_list, image_index:0});// vdma:None});
|
||||
}
|
||||
|
||||
///Gets the next image, returns how long it took in milliseconds to process the image
|
||||
pub fn get_next_images(&mut self,input_name:&str) -> i64{
|
||||
let filename = "distributed/".to_string() + input_name + ".csv";
|
||||
debug!("Filename to be opened: {:?}", filename);
|
||||
let mut coords_file = File::open(filename).expect("bad filename!");
|
||||
let mut all_lines = String::default();
|
||||
let _ = coords_file.read_to_string(&mut all_lines);
|
||||
let linecount = all_lines.lines().count();
|
||||
let mut meta_finish_times = Vec::new();
|
||||
for line in 0..linecount{
|
||||
let mut finish_times = Vec::new();
|
||||
for (ref mut stream,ref mut reader) in self.port_list.iter_mut(){
|
||||
info!("Stream connected to address: {:?}",stream.peer_addr());
|
||||
send_processing_time(stream, line.try_into().expect("Number too big to cast!"));
|
||||
let start_time = Utc::now().time();
|
||||
|
||||
match input_name.to_lowercase(){
|
||||
name if name.contains("serve1") => _ = stream.write(&0u32.to_le_bytes()),
|
||||
name if name.contains("serve2") => _ = stream.write(&1u32.to_le_bytes()),
|
||||
name if name.contains("serve3") => _ = stream.write(&2u32.to_le_bytes()),
|
||||
name if name.contains("serve4") => _ = stream.write(&3u32.to_le_bytes()),
|
||||
name if name.contains("serve5") => _ = stream.write(&4u32.to_le_bytes()),
|
||||
name if name.contains("volley1") => _ = stream.write(&5u32.to_le_bytes()),
|
||||
name if name.contains("volley2") => _ = stream.write(&6u32.to_le_bytes()),
|
||||
name if name.contains("volley3") => _ = stream.write(&7u32.to_le_bytes()),
|
||||
name if name.contains("volley4") => _ = stream.write(&8u32.to_le_bytes()),
|
||||
name if name.contains("volley5") => _ = stream.write(&9u32.to_le_bytes()),
|
||||
_ => _ = stream.write(&0u32.to_le_bytes())
|
||||
}
|
||||
|
||||
//Storage for (x,y) vals for top and side;
|
||||
//how to define which is which?
|
||||
for _ in 0..2{
|
||||
let header_values = read_image_header(stream, reader, &mut self.image_index);
|
||||
let image = read_image(reader, header_values.3);
|
||||
let image_length = image.len();
|
||||
let centroids = Self::build_image(image,header_values);
|
||||
//TODO: Contour shenanigans to get the sole midpoint
|
||||
//info!("Centroids: {:?}", centroids);
|
||||
}
|
||||
//pixel_to_3d_coords() here
|
||||
//Dump coords to file (based off input name?) in `generated/`
|
||||
let end_time = Utc::now().time();
|
||||
finish_times.push((end_time - start_time).num_milliseconds());
|
||||
self.image_index += 1;
|
||||
}
|
||||
let mut final_result = 0;
|
||||
let val_count = finish_times.len();
|
||||
for val in finish_times{
|
||||
final_result += val;
|
||||
}
|
||||
final_result = ((final_result as usize) / val_count) as i64;
|
||||
meta_finish_times.push(final_result);
|
||||
}
|
||||
let mut final_result = 0;
|
||||
let val_count = meta_finish_times.len();
|
||||
for val in meta_finish_times{
|
||||
final_result += val;
|
||||
}
|
||||
final_result = ((final_result as usize) / val_count) as i64;
|
||||
info!("Processing time: {:?}ms", final_result);
|
||||
final_result
|
||||
}
|
||||
fn build_image(recieved_bytes:Vec<u8>, header_info:(u32,u32,u32,u64,String)) -> (u32,u32){
|
||||
let image_width = header_info.0;
|
||||
let image_height = header_info.1;
|
||||
let byte_depth = header_info.2;
|
||||
let image_name = header_info.4;
|
||||
|
||||
//Create a blank pane on which to store the read in pixels
|
||||
let mut img = RgbImage::new(image_width,image_height);
|
||||
|
||||
//Create a temporary storage point for u8s when generating pixels
|
||||
//Pixels are definitionally [u8;3], so this can be hardcoded to preserve space
|
||||
let mut temp:[u8;3] = [0;3];
|
||||
|
||||
//Iterate over bytes
|
||||
info!("Building image...");
|
||||
for i in 0..recieved_bytes.len(){
|
||||
let byte = recieved_bytes.get(i).expect("Index does not exist!");
|
||||
|
||||
//Each pixel is composed of [byte_depth] u8s
|
||||
if ((i as u32)+1) % byte_depth == 0{
|
||||
temp[2] = *byte;
|
||||
//Coordinate in the pane = i/3
|
||||
let pixel_number:u32 = ((i as u32) /byte_depth).try_into().unwrap();
|
||||
//Insert the pixel into the pane
|
||||
img.put_pixel(
|
||||
//Pixels coming directly from Unity are sent in reference to Unity; that is,
|
||||
//starting at the bottom left, and iterating horizontally, then vertically.
|
||||
//This line compensates and creates a properly-oriented image
|
||||
pixel_number%image_width, (image_height-1)-(pixel_number / image_width),
|
||||
//Extract image from temporary storage, ang generate pixel
|
||||
Rgb::<u8>{0:temp.try_into().expect("bad vector length!")}
|
||||
);
|
||||
//Clear temporary storage
|
||||
temp = [0;3];
|
||||
} else if ((i as u32)+1) % byte_depth == 2{
|
||||
temp[1] = *byte;
|
||||
} else if ((i as u32)+1) % byte_depth == 1{
|
||||
temp[0] = *byte;
|
||||
}
|
||||
}
|
||||
let a:RgbImage;
|
||||
let file_name:&str;
|
||||
if image_name.contains("Top") {
|
||||
file_name = "Top_Ref.png";
|
||||
} else if image_name.contains("Side") {
|
||||
file_name = "Side_Ref.png";
|
||||
} else {
|
||||
file_name = "Bad_name.png";
|
||||
}
|
||||
let temp = Reader::open(file_name).expect("Bad import of reference file!").decode();
|
||||
match temp {
|
||||
Ok(file) => {
|
||||
a = file.into_rgb8();
|
||||
},
|
||||
Err(error) => panic!("{:?}",error)
|
||||
}
|
||||
let centroids = Self::img_proc(&a, &img);
|
||||
info!("Image centroids: {:?}", centroids);
|
||||
//TODO: Figure out which centroid is which
|
||||
centroids[0]
|
||||
}
|
||||
|
||||
fn img_proc(a: &RgbImage, b: &RgbImage) -> Vec<(u32,u32)>{
|
||||
let width = a.dimensions().0;
|
||||
let height = a.dimensions().1;
|
||||
|
||||
let a_samples = a.as_flat_samples().samples;
|
||||
let b_samples = b.as_flat_samples().samples;
|
||||
|
||||
let mut diff_image = RgbImage::new(width, height);
|
||||
let mut x = 0;
|
||||
let mut y = 0;
|
||||
|
||||
for (a_pixel,b_pixel) in a_samples.chunks_exact(3).zip(b_samples.chunks_exact(3)){
|
||||
diff_image.put_pixel(x, y, Rgb::<u8>{0:[
|
||||
a_pixel[0].abs_diff(b_pixel[0]),
|
||||
a_pixel[1].abs_diff(b_pixel[1]),
|
||||
a_pixel[2].abs_diff(b_pixel[2])
|
||||
]});
|
||||
x += 1;
|
||||
if x >= width{
|
||||
x = 0;
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
let converted_image = DynamicImage::ImageRgb8(diff_image).to_luma8();
|
||||
let result= find_contours::<u32>(&converted_image);
|
||||
let mut return_val = Vec::new();
|
||||
for contour in result{
|
||||
let mut avg_x = 0;
|
||||
let mut avg_y = 0;
|
||||
for point in &contour.points{
|
||||
avg_x += point.x;
|
||||
avg_y += point.y;
|
||||
}
|
||||
avg_x = ((avg_x as usize) / contour.points.len()) as u32;
|
||||
avg_y = ((avg_y as usize) / contour.points.len()) as u32;
|
||||
return_val.push((avg_x, avg_y));
|
||||
}
|
||||
return_val
|
||||
}
|
||||
}
|
||||
///Borrows an iterator over the bytes in a buffered-reader of a TcpStream; reads the next 4 bytes,
|
||||
///outputs the generated u32
|
||||
fn bytes_to_u32(iter:&mut Bytes<&mut BufReader<TcpStream>>) -> u32{
|
||||
let mut array:[u8;4] = [0;4];
|
||||
for i in 0..4{
|
||||
let possible_result:Option<Result<u8, Error>> = iter.next();
|
||||
match possible_result{
|
||||
None => {},
|
||||
Some(result) => {
|
||||
match result{
|
||||
Ok(value) => {
|
||||
array[i] = value;
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Error converting byte to u32! {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return u32::from_le_bytes(array);
|
||||
}
|
||||
|
||||
fn bytes_to_u64(iter:&mut Bytes<&mut BufReader<TcpStream>>) -> u64{
|
||||
let mut array:[u8;8] = [0;8];
|
||||
for i in 0..8{
|
||||
let possible_result:Option<Result<u8, Error>> = iter.next();
|
||||
match possible_result{
|
||||
None => {},
|
||||
Some(result) => {
|
||||
match result{
|
||||
Ok(value) => {
|
||||
array[i] = value;
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Error converting byte to u32! {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return u64::from_le_bytes(array);
|
||||
}
|
||||
///Request the user to input the world server's address into stdin
|
||||
fn manual_address_prompt() -> Result<TcpStream,Error>{
|
||||
let return_val:Result<TcpStream,Error> = Err(Error::new(ErrorKind::AddrNotAvailable,"Invalid Address"));
|
||||
while return_val.is_err(){
|
||||
let mut user_input:String = String::default();
|
||||
match stdin().read_line(&mut user_input){
|
||||
Ok(_) => {
|
||||
match user_input.trim().parse::<SocketAddrV4>(){
|
||||
Ok(address) => {
|
||||
//Test if the address is accurate
|
||||
let stream = TcpStream::connect_timeout(&std::net::SocketAddr::V4(address), PORT_PROBE_TIMEOUT);
|
||||
match stream{
|
||||
Ok(stream) => return Ok(stream),
|
||||
Err(error) => {
|
||||
warn!("Address unavailable!");
|
||||
debug!("{:?}",error);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Unable to properly parse user input!");
|
||||
debug!("{:?}",error);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Unable to read user input!");
|
||||
debug!("{:?}",error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return return_val;
|
||||
}
|
||||
|
||||
//Used to send the calculated time value to the world server
|
||||
fn send_processing_time(stream:&mut TcpStream ,input_name:u32){
|
||||
debug!("Begin handshake; sending timestamp float...");
|
||||
//Write the f64 value, in bytes, to the stream, and ensure it has been sent
|
||||
_ = stream.write(&input_name.to_le_bytes());
|
||||
_ = stream.flush();
|
||||
debug!("Timestamp sent!");
|
||||
}
|
||||
|
||||
//Used to recieve images from the world server
|
||||
fn read_image_header(stream:&mut TcpStream, reader:&mut BufReader<TcpStream>, image_index:&mut u64) -> (u32,u32,u32,u64,String){
|
||||
//Create an iterator over the stream's bytes
|
||||
let mut iter = reader.bytes();
|
||||
|
||||
debug!("Continue handshake, read back image packet length...");
|
||||
//The first values sent will always be a u32 containing the length of the message (without the
|
||||
//length counted), followed by the image width and height
|
||||
let length = bytes_to_u64(&mut iter);
|
||||
debug!("Image packet length read as {:?}!",length);
|
||||
|
||||
debug!("Continue handshake, read back image width...");
|
||||
let image_width = bytes_to_u32(&mut iter);
|
||||
debug!("Image width read as {:?}!",image_width);
|
||||
|
||||
debug!("Continue handshake, read back image height...");
|
||||
let image_height = bytes_to_u32(&mut iter);
|
||||
debug!("Image height read as {:?}!",image_height);
|
||||
|
||||
//The length of the image is the total number of bytes
|
||||
let byte_depth = (length / ((image_width * image_height) as u64)) as u32;
|
||||
debug!("Image bitdepth calculated as {:?}!", byte_depth);
|
||||
|
||||
//Confirm to world server the correct number of bytes
|
||||
_ = stream.write(&byte_depth.to_le_bytes());
|
||||
_ = stream.flush();
|
||||
|
||||
debug!("Reading camera source info...");
|
||||
let camera_source = bytes_to_u32(&mut iter);
|
||||
let camera_name:&str;
|
||||
let top_cam_img = "./Top".to_owned() + &image_index.to_string() + ".png";
|
||||
let side_cam_img = "./Side".to_owned() + &image_index.to_string() + ".png";
|
||||
if camera_source == u32::MIN{
|
||||
debug!("Image source defined as \"Top\"!");
|
||||
camera_name = &top_cam_img;
|
||||
} else if camera_source == u32::MAX {
|
||||
debug!("Image source defined as \"Side\"!");
|
||||
camera_name = &side_cam_img;
|
||||
} else {
|
||||
error!("Unknown camera value!!!");
|
||||
camera_name = "./unknown.png";
|
||||
}
|
||||
|
||||
debug!("Writing {:?} to stream...", camera_source.to_le_bytes());
|
||||
_ = stream.write(&byte_depth.to_le_bytes());
|
||||
_ = stream.flush();
|
||||
(image_width,image_height,byte_depth,length,camera_name.to_string())
|
||||
}
|
||||
|
||||
fn read_image(reader:&mut BufReader<TcpStream>, image_length:u64)-> Vec<u8>{
|
||||
//Create a storage point for the bytes
|
||||
let mut recieved_bytes:Vec<u8> = Vec::new();
|
||||
//Create an iterator over the stream's bytes
|
||||
let mut iter = reader.bytes();
|
||||
//TODO: Remove assumption
|
||||
//Always assumes world server will send image
|
||||
info!("Start reading...");
|
||||
//Recieve the rest of the message
|
||||
for index in 0..image_length{
|
||||
let possible_result:Option<Result<u8, Error>> = iter.next();
|
||||
match possible_result{
|
||||
None => {},
|
||||
Some(result) => {
|
||||
match result{
|
||||
Ok(value) => {
|
||||
recieved_bytes.push(value);
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Error reading image from buffer! {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Image successfully recieved");
|
||||
|
||||
recieved_bytes
|
||||
}
|
||||
|
||||
|
||||
fn pixel_to_3d_coords(top_x:u32, top_y:u32, side_y:u32) -> (f64,f64,f64) {
|
||||
todo!();
|
||||
const TOP_CAM_FOV_H:f64 = 90.0;
|
||||
const TOP_CAM_FOV_V:f64 = 58.72538;
|
||||
const TOP_CAM_IMG_W:f64 = 3840.0;
|
||||
const TOP_CAM_IMG_H:f64 = 2160.0;
|
||||
const TOP_CAM_H:f64 = 13.0;
|
||||
|
||||
//const SIDE_CAM_FOV_H:f64 = 90.0;
|
||||
const SIDE_CAM_FOV_V:f64 = 120.0;
|
||||
//const SIDE_CAM_IMG_W:f64 = 3840.0;
|
||||
const SIDE_CAM_IMG_H:f64 = 2160.0;
|
||||
const SIDE_CAM_H:f64 = 0.0;
|
||||
const SIDE_CAM_Y:f64 = 9.5;
|
||||
|
||||
let theta_x:f64 = ((top_y as f64) - (TOP_CAM_IMG_W/2.0)) * (TOP_CAM_FOV_H / TOP_CAM_IMG_W);
|
||||
let x_ball = TOP_CAM_H * (theta_x.tan() * RADIANS_TO_DEGREES);
|
||||
|
||||
let theta_y:f64 = ((top_y as f64) - (TOP_CAM_IMG_H/2.0)) * (TOP_CAM_FOV_V / TOP_CAM_IMG_H);
|
||||
let y_ball = TOP_CAM_H * (theta_y.tan() * RADIANS_TO_DEGREES);
|
||||
|
||||
let theta_h:f64 = ((SIDE_CAM_IMG_H / 2.0) - (side_y as f64)) * (SIDE_CAM_FOV_V / SIDE_CAM_IMG_H);
|
||||
let mut h_ball = TOP_CAM_H * (theta_h.tan() * RADIANS_TO_DEGREES);
|
||||
|
||||
let x_comp:f64 = x_ball * (1.0 - (h_ball/TOP_CAM_H));
|
||||
let y_comp:f64 = y_ball * (1.0 - (h_ball/TOP_CAM_H));
|
||||
|
||||
let mut error = 0.38*h_ball;
|
||||
h_ball = h_ball + error;
|
||||
error = 0.15*h_ball;
|
||||
h_ball = h_ball + error;
|
||||
(x_comp,y_comp,h_ball)
|
||||
}
|
208
src/main.rs
208
src/main.rs
|
@ -1,211 +1,17 @@
|
|||
use std::{ fs, io::{Bytes, stdin, BufReader, Error, ErrorKind, Read, Write}, net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpStream}, path::Path, thread::{self, JoinHandle}, time::Duration};
|
||||
pub mod cam_comm;
|
||||
pub mod rest_api;
|
||||
use std::{ fs, path::Path};
|
||||
use chrono::{DateTime, Local};
|
||||
use image::{Rgb, RgbImage};
|
||||
use ipnet::{Ipv4Net,PrefixLenError};
|
||||
use local_ip_address::linux::local_ip;
|
||||
use log::{LevelFilter, trace,debug,warn,error,info};
|
||||
use log::LevelFilter;
|
||||
use fern::{log_file, Dispatch};
|
||||
|
||||
//RTSP port is 554; prepare for real world situations
|
||||
const PORT_NUMBER:u16 = 55001;
|
||||
const PORT_PROBE_TIMEOUT:Duration = Duration::new(5,0);
|
||||
const SUBNET_SIZE:u8 = 24;
|
||||
|
||||
//These subnets are well documented;
|
||||
///RIT owns the subnet 129.21.0.0/16; probing by attempting to open thousands of TCP streams
|
||||
///simultaneously is unwise.
|
||||
const UNSAFE_SUBNET:Result<Ipv4Net,PrefixLenError> = Ipv4Net::new(Ipv4Addr::new(129,21,0,0),16);
|
||||
|
||||
///This subnet is universally considered the "local" subnet. This results in 256 addresses, and can
|
||||
///be probed both safely and efficiently, and as long as the world server on the LAN is running it
|
||||
///is functionally guaranteeed to be found.
|
||||
const HOME_SUBNET:Result<Ipv4Net,PrefixLenError> = Ipv4Net::new(Ipv4Addr::new(192,168,0,0),24);
|
||||
|
||||
fn main() {
|
||||
#[rocket::main]
|
||||
async fn main() {
|
||||
setup_logs(&true);
|
||||
//Get host IP
|
||||
let host_ip = local_ip().expect("No real IP address!");
|
||||
trace!("Host IP: {:?}",host_ip);
|
||||
|
||||
let mut thread_list:Vec<JoinHandle<Result<TcpStream,Error>>> = Vec::new();
|
||||
let mut port_list:Vec<TcpStream> = Vec::new();
|
||||
if let IpAddr::V4(host_v4_ip) = host_ip{
|
||||
//Check current subnet
|
||||
let net = Ipv4Net::new(host_v4_ip,SUBNET_SIZE);
|
||||
|
||||
if let Ok(net) = net{
|
||||
//RIT owns 129.21.0.0/16; DO NOT SPAM THIS NETWORK
|
||||
if UNSAFE_SUBNET.unwrap().contains(&net){
|
||||
//Request world server address from user
|
||||
let address = manual_address_prompt();
|
||||
if address.is_ok(){
|
||||
port_list.push(address.unwrap());
|
||||
//thread_list won't be added to, so the thread for loop won't execute
|
||||
}
|
||||
} else if net.contains(&HOME_SUBNET.unwrap()){
|
||||
//Collect all Hosts on the current subnet
|
||||
info!("aggregating all IPs... This may take several minutes...");
|
||||
for net_address in net.hosts(){
|
||||
thread_list.push(
|
||||
thread::spawn(move ||{
|
||||
let ip_port = std::net::SocketAddr::V4(SocketAddrV4::new(net_address, PORT_NUMBER));
|
||||
//Attempt to open a TCP stream with this IP address, exit the thread
|
||||
//returning the open TcpStream, or an error
|
||||
return TcpStream::connect_timeout(&ip_port, PORT_PROBE_TIMEOUT);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Multithread the port-sniffing block, only save sucessful connections
|
||||
for thread in thread_list{
|
||||
let output = thread.join().unwrap();
|
||||
if let Ok(real_output) = output{
|
||||
port_list.push(real_output);
|
||||
}
|
||||
}
|
||||
|
||||
//For all available ports, try the custom communication protocol
|
||||
for mut stream in port_list{
|
||||
info!("Stream connected to address: {:?}",stream.peer_addr());
|
||||
send_processing_time(&mut stream, 2.7f64);
|
||||
read_response(&mut stream);
|
||||
}
|
||||
//TODO: Document/implement RTSP client-side comms
|
||||
//Currently using custom port and custom protocol, but if real-world cameras will
|
||||
//be in use, we will likely need to be portable to the RTSP protocol, or similar
|
||||
rest_api::start_rest_endpoint().await;
|
||||
}
|
||||
|
||||
//Used to send the calculated time value to the world server
|
||||
pub fn send_processing_time(stream:&mut TcpStream ,sent_value:f64){
|
||||
debug!("Begin handshake; sending timestamp float...");
|
||||
//Write the f64 value, in bytes, to the stream, and ensure it has been sent
|
||||
stream.write(&sent_value.to_le_bytes()).unwrap();
|
||||
stream.flush().unwrap();
|
||||
debug!("Timestamp sent!");
|
||||
}
|
||||
|
||||
//Used to recieve images from the world server
|
||||
pub fn read_response(stream:&mut TcpStream){
|
||||
//Buffer the incoming stream
|
||||
let reader = BufReader::new(stream.try_clone().expect("Failed clone"));
|
||||
//Create a storage point for the bytes
|
||||
let mut recieved_bytes:Vec<u8> = Vec::new();
|
||||
//Create an iterator over the stream's bytes
|
||||
let mut iter = reader.bytes();
|
||||
|
||||
debug!("Continue handshake, read back image packet length...");
|
||||
//The first values sent will always be a u32 containing the length of the message (without the
|
||||
//length counted), followed by the image width and height
|
||||
let length = bytes_to_u32(&mut iter);
|
||||
debug!("Image packet length read as {:?}!",length);
|
||||
|
||||
debug!("Continue handshake, read back image width...");
|
||||
let image_width = bytes_to_u32(&mut iter);
|
||||
debug!("Image width read as {:?}!",image_width);
|
||||
|
||||
debug!("Continue handshake, read back image height...");
|
||||
let image_height = bytes_to_u32(&mut iter);
|
||||
debug!("Image height read as {:?}!",image_height);
|
||||
|
||||
//The length of the image is the total number of bytes
|
||||
let byte_depth = length / (image_width * image_height);
|
||||
debug!("Image bitdepth calculated as {:?}!", byte_depth);
|
||||
|
||||
//Confirm to world server the correct number of bytes
|
||||
stream.write(&byte_depth.to_le_bytes()).unwrap();
|
||||
stream.flush().unwrap();
|
||||
|
||||
//TODO: Remove assumption
|
||||
//Always assumes world server will send image
|
||||
info!("Start reading...");
|
||||
//Recieve the rest of the message
|
||||
for _ in 0..length{
|
||||
recieved_bytes.push(iter.next().unwrap().unwrap_or(0));
|
||||
}
|
||||
|
||||
info!("Image successfully recieved");
|
||||
|
||||
//Create a blank pane on which to store the read in pixels
|
||||
let mut img = RgbImage::new(image_width,image_height);
|
||||
|
||||
//Create a temporary storage point for u8s when generating pixels
|
||||
let mut temp:Vec<u8> = Vec::new();
|
||||
|
||||
//Iterate over bytes
|
||||
info!("Building image...");
|
||||
for i in 0..recieved_bytes.len(){
|
||||
let byte = recieved_bytes.get(i).expect("Index does not exist!");
|
||||
|
||||
temp.push(byte.clone());
|
||||
//Each pixel is composed of [byte_depth] u8s
|
||||
if ((i as u32)+1) % byte_depth == 0{
|
||||
//Coordinate in the pane = i/3
|
||||
let pixel_number:u32 = (i as u32) /byte_depth;
|
||||
//Insert the pixel into the pane
|
||||
img.put_pixel(
|
||||
//Pixels coming directly from Unity are sent "upside down", that is,
|
||||
//starting at the bottom right, and iterating horizontally, then vertically.
|
||||
//This line compensates and creates a properly-oriented image
|
||||
(image_width-1)-(pixel_number%image_width), (image_height-1)-(pixel_number / image_width),
|
||||
//Extract image from temporary storage, ang generate pixel
|
||||
Rgb::<u8>{0:temp.clone().try_into().expect("bad vector length!")}
|
||||
);
|
||||
//Clear temporary storage
|
||||
temp.clear();
|
||||
}
|
||||
}
|
||||
//Dump image to file
|
||||
//ONLY FOR EXPERIMENTATION AND TESTING PURPOSES
|
||||
info!("Image built, saving to file...");
|
||||
_ = img.save("./test.png");
|
||||
info!("image saved!");
|
||||
}
|
||||
|
||||
///Borrows an iterator over the bytes in a buffered-reader of a TcpStream; reads the next 4 bytes,
|
||||
///outputs the generated u32
|
||||
fn bytes_to_u32(iter:&mut Bytes<BufReader<TcpStream>>) -> u32{
|
||||
let mut array:[u8;4] = [0;4];
|
||||
for i in 0..4{
|
||||
array[i] = iter.next().unwrap().unwrap();
|
||||
}
|
||||
return u32::from_le_bytes(array);
|
||||
}
|
||||
|
||||
///Request the user to input the world server's address into stdin
|
||||
fn manual_address_prompt() -> Result<TcpStream,Error>{
|
||||
let return_val:Result<TcpStream,Error> = Err(Error::new(ErrorKind::AddrNotAvailable,"Invalid Address"));
|
||||
while return_val.is_err(){
|
||||
let mut user_input:String = String::default();
|
||||
match stdin().read_line(&mut user_input){
|
||||
Ok(_) => {
|
||||
match user_input.trim().parse::<SocketAddrV4>(){
|
||||
Ok(address) => {
|
||||
//Test if the address is accurate
|
||||
let stream = TcpStream::connect_timeout(&std::net::SocketAddr::V4(address), PORT_PROBE_TIMEOUT);
|
||||
if stream.is_ok(){
|
||||
return stream;
|
||||
} else {
|
||||
warn!("Address unavailable!");
|
||||
debug!("{:?}",stream.unwrap_err());
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Unable to properly parse user input!");
|
||||
debug!("{:?}",error);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Unable to read user input!");
|
||||
debug!("{:?}",error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return return_val;
|
||||
}
|
||||
|
||||
///Set up logging macros to be used throughout the program
|
||||
fn setup_logs(debug:&bool){
|
||||
|
|
84
src/rest_api.rs
Normal file
84
src/rest_api.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use std::sync::Mutex;
|
||||
use crate::cam_comm::WorldServerConnection;
|
||||
use local_ip_address::local_ip;
|
||||
use rocket::{routes, get, post, figment::Figment, fs::NamedFile, http::Method, response::status, Config, State};
|
||||
use rocket_cors::{AllowedOrigins, CorsOptions};
|
||||
use log::error;
|
||||
|
||||
#[post("/serve", data = "<raw_index>")]
|
||||
fn start_serve(raw_index:String,cams:&State<Mutex<WorldServerConnection>>) -> status::Accepted<()> {
|
||||
println!("{:?}",raw_index);
|
||||
let parsed_index = raw_index.strip_prefix("file_name=").expect("bad data!");
|
||||
let cameras = cams.lock();
|
||||
match cameras{
|
||||
Ok(mut cameras) => {
|
||||
cameras.get_next_images(parsed_index);
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Error getting lock on camera state! {:?}",error);
|
||||
}
|
||||
}
|
||||
status::Accepted(())
|
||||
}
|
||||
|
||||
//Get most recently updated file
|
||||
#[get("/serve")]
|
||||
async fn get_serve_data() -> Option<NamedFile>{
|
||||
let last_modified_file = std::fs::read_dir("distributed") //Read in the current directory
|
||||
.expect("couldn't access local dir") //assume the directory is real
|
||||
.flatten() //Ignore all the files that have permissions issues
|
||||
.filter(|file| file.metadata().unwrap().is_file()) //Ignore files that aren't actually files
|
||||
.max_by_key(|x| x.metadata().unwrap().modified().unwrap()) //Find newest file
|
||||
.unwrap(); //Assume the newest file actually exists
|
||||
NamedFile::open(last_modified_file.path()).await.ok()
|
||||
}
|
||||
|
||||
///Get specific file, as defined by request
|
||||
#[get("/serve/<file>")]
|
||||
async fn get_data(file:String) -> Option<NamedFile>{
|
||||
let filename = "distributed/".to_string() + &file + ".csv";
|
||||
NamedFile::open(filename).await.ok()
|
||||
}
|
||||
|
||||
#[get("/ball-path/<file>")]
|
||||
async fn get_ball_path(file:String) -> Option<NamedFile>{
|
||||
let filename = "generated/".to_string() + &file + ".csv";
|
||||
NamedFile::open(filename).await.ok()
|
||||
}
|
||||
|
||||
#[get("/favicon.ico")]
|
||||
async fn favicon() -> Option<NamedFile>{ NamedFile::open("favicon.ico").await.ok() }
|
||||
|
||||
///Initialises the world server connection, spins Rocket into a unique process
|
||||
pub async fn start_rest_endpoint(){
|
||||
//Rocket defaults to using the localhost loopback address. This isn't ideal for prod usage, so
|
||||
//instead we bind to the host IP address.
|
||||
let host_ip = local_ip().expect("No real IP address!");
|
||||
let figment = Figment::from(Config::default())
|
||||
.merge(("address",host_ip));
|
||||
|
||||
//Allow cross-origin resource sharing
|
||||
let cors = CorsOptions::default()
|
||||
.allowed_origins(AllowedOrigins::all())
|
||||
.allowed_methods(
|
||||
vec![Method::Get, Method::Post]
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect(),
|
||||
).allow_credentials(true);
|
||||
|
||||
match WorldServerConnection::new(){
|
||||
Ok(world_server) => {
|
||||
let _rocket = rocket::custom(figment)
|
||||
.attach(cors.to_cors().expect("CORS init failure!"))
|
||||
.manage(Mutex::new(world_server))
|
||||
.mount("/", routes![favicon,get_ball_path,get_data,start_serve,get_serve_data])
|
||||
.launch().await;
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Cannot open connection to world server! {:?}", error);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
BIN
vdma_docs.pdf
Normal file
BIN
vdma_docs.pdf
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue