Compare commits

...
Sign in to create a new pull request.

47 commits

Author SHA1 Message Date
1ed406e929
Actually use favicon function 2024-04-26 12:28:24 -04:00
78415feec3
Add favicon to reduce errors in webconsole 2024-04-26 12:27:22 -04:00
099ffbcaca
Add reference images 2024-04-26 12:23:52 -04:00
4a30d40d34
Rip out VDMA, process direct on SOC 2024-04-26 10:56:32 -04:00
c12a476171
Move CSV files, update REST endpoint, remove VDMA
REST calls no longer require file extension. generated folder will be
for CSVs generated from built data

VDMA pending removal, sudo dependency removed pre-emptively
2024-04-24 17:45:53 -04:00
4455a96cbb
Add timer to processing calculation 2024-04-24 09:45:46 -04:00
e43c2c42cf
Merge branch 'main' into devel 2024-04-22 17:37:21 -04:00
d23894fba6
Test implement algo function
Also, remove unnecessary lib.rs file. This is only needed if the thing
being developed is a LIBRARY, which since we have a main file, it isn't
2024-04-22 17:28:50 -04:00
658ba709d1
Remove unwrap calls where possible 2024-04-18 18:06:42 -04:00
41b95647a2
Integrate data only REST endpoint
Project now builds without compile-time issues due to
underscore-vs-hyphen conflicts (I assume that was the problem at least),
VDMA has rudamentary implementation.
2024-04-18 17:37:10 -04:00
fd07adbb75
Fix POST endpoint to run properly 2024-04-18 15:55:27 -04:00
abb5263763
Explain CORS call 2024-04-18 14:43:26 -04:00
c5c62c5dbc
Update to include CORS functionality 2024-04-18 13:52:45 -04:00
57c514a289
Update rest api to bind to real IP address 2024-04-18 13:14:38 -04:00
0dff50269f
Add rocket config file 2024-04-17 23:35:10 -04:00
57957e9285
remove blocker for build
Something for later me to fix
2024-04-17 23:22:08 -04:00
1c3f60196c
Start commenting; add VDMA to worldserver 2024-04-17 19:46:41 -04:00
69fb508486
Updated comments, insert VDMA docs
Docs courtesy of AMD; publicly accessible, and indexed by Google at time
of writing
2024-04-17 18:40:49 -04:00
7f34d56ba3
Comment rest api 2024-04-17 18:38:16 -04:00
6a78a80e85
Add all data; update rest endpoint
REST endpoint now responds to a blanket request to
`localhost:8000/serve` with the most recently updated file available to
the server
2024-04-17 18:34:23 -04:00
0f03ec54ff
Remove world-server requirement 2024-04-17 17:14:53 -04:00
4d4af9cf19
Finish first try at VDMA 2024-04-14 18:16:15 -04:00
7197b71f0a
Continue VDMA development 2024-04-10 17:17:31 -04:00
eefaa646b1
Continue VDMA coding
VDMA API is incredibly unstable; subject to change as development
continue
2024-04-08 17:17:23 -04:00
572734ff0d
Start VDMA Framework
Loosely based on Kaputa's design; using Rust tools to make the process
simpler/easier to read
2024-04-08 11:44:44 -04:00
445cf844c8
Add submodule for VDMA ref material 2024-04-07 16:49:56 -04:00
cdbe8e4659
Implement REST framework API calls 2024-04-07 15:11:44 -04:00
67be087183
Increase modularity
Camera calls are now a unique module; working on starting the REST
endpoint development. Will require researching previous projects.
2024-04-07 13:43:37 -04:00
c72be40454
Finish base communication
Communication with the world server is now complete. Starting work on
REST prototyping
2024-04-07 12:24:44 -04:00
34e6699adf
Include force build for ARM target 2024-03-31 12:54:57 -04:00
5bf7745ca6
Update readme 2024-03-28 16:34:37 -04:00
2738513f3e
Merge branch 'devel' of ssh://git.blizzard.systems:25/esd2-groupwork/communication-layer into devel 2024-03-28 16:16:29 -04:00
a68d4efebc
Merge branch 'main' into devel 2024-03-28 16:14:38 -04:00
f8d6645690
Continue making code generic 2024-03-25 21:06:49 -04:00
5b0172b2b9
Simplify design 2024-03-25 20:47:15 -04:00
c01fd63211
Rethink connection
Connection now forks depending on current host IP location
2024-03-25 20:23:22 -04:00
1d775a5e19
Create complete handshake
Handshake now consists of:
- processing timestamp (f64) sent SoC -> World-Server (WS)
- Length of image packet 	  SoC <- WS
- image width 			  SoC <- WS
- image height			  SoC <- WS
- calculated image bitdepth       SoC -> WS
- raw image bytes		  SoC <- WS

Also, increase verbosity of logs; add screenshot of logs
2024-03-25 13:33:10 -04:00
bb6b883c4b
Continue development of comms layer 2024-03-25 12:32:23 -04:00
e1b4dcafde
Force toolchain version 2024-03-25 10:32:36 -04:00
509685ff0e
Add notes 2024-03-25 10:32:11 -04:00
fbd0ac4ca0
Update Cargo config to build safely 2024-03-24 18:09:16 -04:00
2f09bfb0f6
Continue commenting 2024-03-21 15:30:19 -04:00
eec82cdfb5
Add logging for tie estimation purposes
Also, dynamically determine the IP of the world server, for more
realistic interactions
2024-03-21 14:54:02 -04:00
beb0cd25dd
Comment read layer 2024-03-21 12:58:37 -04:00
0b367c019f
First attempt at importing network images 2024-03-20 18:43:21 -04:00
23276e94d2
Static send variable for demonstration and testing 2024-03-20 15:18:40 -04:00
6f4ddf5e00
Start developing communication layer
Currently, in testing phase
2024-03-20 14:39:00 -04:00
25 changed files with 24832 additions and 267 deletions

1
.cargo/config.toml Normal file
View file

@ -0,0 +1 @@
[build]

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
target/
logs/
test.png
*.png

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "reference_material"]
path = reference_material
url = https://github.com/ravvenlabs/userspace-vdma-driver

2204
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 MiB

BIN
Top_Ref.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 MiB

1774
distributed/serve1.csv Normal file

File diff suppressed because it is too large Load diff

1853
distributed/serve2.csv Normal file

File diff suppressed because it is too large Load diff

1921
distributed/serve3.csv Normal file

File diff suppressed because it is too large Load diff

2029
distributed/serve4.csv Normal file

File diff suppressed because it is too large Load diff

1739
distributed/serve5.csv Normal file

File diff suppressed because it is too large Load diff

1956
distributed/volley1.csv Normal file

File diff suppressed because it is too large Load diff

1447
distributed/volley2.csv Normal file

File diff suppressed because it is too large Load diff

4182
distributed/volley3.csv Normal file

File diff suppressed because it is too large Load diff

2067
distributed/volley4.csv Normal file

File diff suppressed because it is too large Load diff

1396
distributed/volley5.csv Normal file

File diff suppressed because it is too large Load diff

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

0
generated/.gitkeep Normal file
View file

1
reference_material Submodule

@ -0,0 +1 @@
Subproject commit cfbb72d5ffac8d253ac6653e268efc76f7d0a3d5

1774
serve1.dat Normal file

File diff suppressed because it is too large Load diff

453
src/cam_comm.rs Normal file
View 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)
}

View file

@ -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
View 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

Binary file not shown.