|
|
|
@ -59,13 +59,13 @@ enum GameState {
|
|
|
|
|
Scheduled,
|
|
|
|
|
Timeout,
|
|
|
|
|
Driver,
|
|
|
|
|
Driverdone,
|
|
|
|
|
DriverDone,
|
|
|
|
|
Autonomous,
|
|
|
|
|
AutonomousDone,
|
|
|
|
|
Abandoned,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
|
|
|
|
|
enum Round {
|
|
|
|
|
None = 0,
|
|
|
|
|
Practice = 1,
|
|
|
|
@ -102,7 +102,7 @@ fn int_to_round(round: i32) -> Round {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
|
|
|
|
|
struct MatchTuple {
|
|
|
|
|
division: i32,
|
|
|
|
|
round: Round,
|
|
|
|
@ -140,6 +140,20 @@ impl Event {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_match(self: &mut Event, tuple: MatchTuple) -> Option<&mut Match> {
|
|
|
|
|
match self.divisions.get_mut(&tuple.division) {
|
|
|
|
|
None => {},
|
|
|
|
|
Some(division) => {
|
|
|
|
|
for m in &mut division.matches {
|
|
|
|
|
if m.tuple == tuple {
|
|
|
|
|
return Some(m);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: remove extra entries instead of just adding new ones
|
|
|
|
|
fn parse_field_sets(self: &mut Event, sets: tm::FieldSetList) {
|
|
|
|
|
for set in sets.field_sets {
|
|
|
|
@ -170,19 +184,19 @@ impl Event {
|
|
|
|
|
fn parse_match_list(self: &mut Event, match_list: tm::MatchList) {
|
|
|
|
|
let mut matches: HashMap<i32, Vec<Match>> = HashMap::new();
|
|
|
|
|
for m in match_list.matches.iter() {
|
|
|
|
|
let match_tuple = MatchTuple{
|
|
|
|
|
let tuple = MatchTuple{
|
|
|
|
|
division: m.division.unwrap(),
|
|
|
|
|
round: int_to_round(m.round.unwrap()),
|
|
|
|
|
instance: m.instance.unwrap(),
|
|
|
|
|
match_num: m.r#match.unwrap(),
|
|
|
|
|
session: m.session.unwrap(),
|
|
|
|
|
};
|
|
|
|
|
match matches.get_mut(&match_tuple.division) {
|
|
|
|
|
match matches.get_mut(&tuple.division) {
|
|
|
|
|
Some(match_list) => {
|
|
|
|
|
match_list.push(Match{
|
|
|
|
|
state: None,
|
|
|
|
|
info: None,
|
|
|
|
|
match_tuple: match_tuple.clone(),
|
|
|
|
|
tuple: tuple.clone(),
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
None => {
|
|
|
|
@ -190,9 +204,9 @@ impl Event {
|
|
|
|
|
new_match_list.push(Match{
|
|
|
|
|
state: None,
|
|
|
|
|
info: None,
|
|
|
|
|
match_tuple: match_tuple.clone(),
|
|
|
|
|
tuple: tuple.clone(),
|
|
|
|
|
});
|
|
|
|
|
matches.insert(match_tuple.division, new_match_list);
|
|
|
|
|
matches.insert(tuple.division, new_match_list);
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -247,7 +261,7 @@ struct MatchInfo {
|
|
|
|
|
struct Match {
|
|
|
|
|
state: Option<MatchState>,
|
|
|
|
|
info: Option<MatchInfo>,
|
|
|
|
|
match_tuple: MatchTuple,
|
|
|
|
|
tuple: MatchTuple,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
@ -447,7 +461,7 @@ impl NoticeMsg {
|
|
|
|
|
|
|
|
|
|
struct TMClient {
|
|
|
|
|
stream: openssl::ssl::SslStream<TcpStream>,
|
|
|
|
|
notices: mpsc::Sender<Box<tm::Notice>>,
|
|
|
|
|
work_queue: mpsc::Sender<Work>,
|
|
|
|
|
responses: mpsc::Sender<Box<BackendMessage>>,
|
|
|
|
|
requests: mpsc::Receiver<Box<BackendMessage>>,
|
|
|
|
|
uuid: [u8; 16],
|
|
|
|
@ -461,7 +475,7 @@ struct TMClient {
|
|
|
|
|
const TCP_BUFFER_SIZE: usize = 10000;
|
|
|
|
|
impl TMClient {
|
|
|
|
|
fn new(uuid: [u8; 16], client_name: [u8; 32], password: String, username: [u8; 16]) -> (TMClient, TMConnection) {
|
|
|
|
|
let (notice_tx, notice_rx) = mpsc::channel();
|
|
|
|
|
let (work_tx, work_rx) = mpsc::channel();
|
|
|
|
|
let (response_tx, response_rx) = mpsc::channel();
|
|
|
|
|
let (request_tx, request_rx) = mpsc::channel();
|
|
|
|
|
|
|
|
|
@ -482,9 +496,10 @@ impl TMClient {
|
|
|
|
|
let stream = stream_config.connect("127.0.0.1", stream).unwrap();
|
|
|
|
|
stream.get_ref().set_read_timeout(Some(Duration::from_millis(100))).expect("Failed to set read timeout on socket");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (TMClient{
|
|
|
|
|
stream,
|
|
|
|
|
notices: notice_tx,
|
|
|
|
|
work_queue: work_tx.clone(),
|
|
|
|
|
responses: response_tx,
|
|
|
|
|
requests: request_rx,
|
|
|
|
|
uuid,
|
|
|
|
@ -495,8 +510,10 @@ impl TMClient {
|
|
|
|
|
connected: false,
|
|
|
|
|
},
|
|
|
|
|
TMConnection{
|
|
|
|
|
work_queuer: work_tx,
|
|
|
|
|
state_cancels: HashMap::new(),
|
|
|
|
|
requests: request_tx,
|
|
|
|
|
notices: notice_rx,
|
|
|
|
|
work_queue: work_rx,
|
|
|
|
|
responses: response_rx,
|
|
|
|
|
},);
|
|
|
|
|
}
|
|
|
|
@ -533,14 +550,14 @@ impl TMClient {
|
|
|
|
|
4 => {
|
|
|
|
|
match NoticeMsg::from_bytes(packet.data.clone()) {
|
|
|
|
|
Some(notice) => {
|
|
|
|
|
log::debug!("Received notice: {:?}", notice);
|
|
|
|
|
log::debug!("Received notice: {:#?}", notice);
|
|
|
|
|
let ack = BackendPacket::new(packet.header, packet.timestamp, 5, self.last_seq_num+1, notice.notice_id.to_le_bytes().to_vec());
|
|
|
|
|
self.last_seq_num += 1;
|
|
|
|
|
match self.stream.write(&ack.as_bytes()) {
|
|
|
|
|
Ok(_) => log::debug!("Sent ACK for notice {}", notice.notice_id),
|
|
|
|
|
Err(error) => log::error!("ACK error: {:?}", error),
|
|
|
|
|
}
|
|
|
|
|
match self.notices.send(Box::new(notice.notice)) {
|
|
|
|
|
match self.work_queue.send(Work::Notice(Box::new(notice.notice))) {
|
|
|
|
|
Ok(_) => log::debug!("Forwarded notice to callback engine"),
|
|
|
|
|
Err(error) => log::error!("Notice forward error {:?}", error),
|
|
|
|
|
}
|
|
|
|
@ -597,10 +614,23 @@ impl TMClient {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct StateChange {
|
|
|
|
|
next_state: MatchState,
|
|
|
|
|
tuple: MatchTuple,
|
|
|
|
|
field: FieldTuple,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct FieldTuple {
|
|
|
|
|
set: i32,
|
|
|
|
|
id: i32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct TMConnection {
|
|
|
|
|
notices: mpsc::Receiver<Box<tm::Notice>>,
|
|
|
|
|
work_queuer: mpsc::Sender<Work>,
|
|
|
|
|
work_queue: mpsc::Receiver<Work>,
|
|
|
|
|
responses: mpsc::Receiver<Box<BackendMessage>>,
|
|
|
|
|
requests: mpsc::Sender<Box<BackendMessage>>,
|
|
|
|
|
state_cancels: HashMap<FieldTuple, mpsc::Sender<()>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TMConnection {
|
|
|
|
@ -608,9 +638,24 @@ impl TMConnection {
|
|
|
|
|
self.requests.send(Box::new(BackendMessage::new(request_id, data))).unwrap();
|
|
|
|
|
return *self.responses.recv().unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn queue_state_change(self: &mut TMConnection, wait: Duration, state_change: StateChange) -> mpsc::Sender<()> {
|
|
|
|
|
let work_queuer = self.work_queuer.clone();
|
|
|
|
|
let (cancel_tx, cancel_rx) = mpsc::channel();
|
|
|
|
|
thread::spawn(move || {
|
|
|
|
|
match cancel_rx.recv_timeout(wait) {
|
|
|
|
|
Ok(_) => match work_queuer.send(Work::State(state_change)) {
|
|
|
|
|
Ok(_) => {},
|
|
|
|
|
Err(error) => log::error!("State change send error: {:?}", error),
|
|
|
|
|
},
|
|
|
|
|
Err(error) => log::error!("state change queue error: {:?}", error),
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return cancel_tx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type NoticeCallback = fn(tm::Notice, &mut Event, &TMConnection) -> Vec<MQTTMessage>;
|
|
|
|
|
type NoticeCallback = fn(tm::Notice, &mut Event, &mut TMConnection) -> Vec<MQTTMessage>;
|
|
|
|
|
|
|
|
|
|
fn get_affected_match(notice: &tm::Notice) -> Option<MatchTuple> {
|
|
|
|
|
match ¬ice.affected_match {
|
|
|
|
@ -751,12 +796,10 @@ fn get_game_score(notice: &tm::Notice) -> Option<GameScore> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_score_change(notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
fn on_score_change(notice: tm::Notice, event: &mut Event, _connection: &mut TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
match get_affected_match(¬ice) {
|
|
|
|
|
None => Vec::new(),
|
|
|
|
|
Some(tuple) => {
|
|
|
|
|
// Use `event` to figure out which arena topic to publish to
|
|
|
|
|
// Also add the match score topic based on the tuple
|
|
|
|
|
match get_game_score(¬ice) {
|
|
|
|
|
None => Vec::new(),
|
|
|
|
|
Some(score) => {
|
|
|
|
@ -765,52 +808,32 @@ fn on_score_change(notice: tm::Notice, event: &mut Event, _connection: &TMConnec
|
|
|
|
|
let mut out = Vec::new();
|
|
|
|
|
out.push(MQTTMessage{
|
|
|
|
|
topic: game_topic,
|
|
|
|
|
payload: serialized,
|
|
|
|
|
payload: serialized.clone(),
|
|
|
|
|
});
|
|
|
|
|
for (_, field_set) in &event.field_sets {
|
|
|
|
|
for field in &field_set.fields {
|
|
|
|
|
match field.last_known_match {
|
|
|
|
|
None => {},
|
|
|
|
|
Some(last_known_match) => {
|
|
|
|
|
if last_known_match == tuple {
|
|
|
|
|
let field_topic = format!("field/{}/score", field.id);
|
|
|
|
|
out.push(MQTTMessage{
|
|
|
|
|
topic: field_topic,
|
|
|
|
|
payload: serialized.clone(),
|
|
|
|
|
});
|
|
|
|
|
return out;
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_start(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_cancel(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_reset(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_assigned(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_active_field_changed(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_rankings_updated(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_event_status_updated(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
return out;
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_elim_alliance_update(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_elim_unavail_teams_update(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_list_update(_notice: tm::Notice, event: &mut Event, connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
fn on_match_list_update(_notice: tm::Notice, event: &mut Event, connection: &mut TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
let mut messages = Vec::new();
|
|
|
|
|
let match_list_resp = connection.request(1002, tm::BackendMessageData::default());
|
|
|
|
|
match match_list_resp.data.match_list {
|
|
|
|
@ -828,22 +851,30 @@ fn on_match_list_update(_notice: tm::Notice, event: &mut Event, connection: &TMC
|
|
|
|
|
return messages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_timer_start(notice: tm::Notice, _event: &mut Event, _connection: &mut TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
println!("State: {:#?}", notice.field_time.unwrap());
|
|
|
|
|
// 1) Find the state associated with the current block(driver or auton)
|
|
|
|
|
// 2) get the match tuple from the arena
|
|
|
|
|
// 3) add the mqtt messages for match & arena states
|
|
|
|
|
// 4) queue the state change to ${state}_done
|
|
|
|
|
// 5) add the cancel_send to the tmconnections map of timeout_sends
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Work {
|
|
|
|
|
Exit,
|
|
|
|
|
Notice(Box<tm::Notice>),
|
|
|
|
|
State(StateChange),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
env_logger::init();
|
|
|
|
|
|
|
|
|
|
let mut callbacks: HashMap<tm::NoticeId, NoticeCallback> = HashMap::new();
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeRealtimeScoreChanged, on_score_change);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeMatchScoreUpdated, on_score_change);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeFieldTimerStarted, on_match_start);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeFieldTimerStopped, on_match_cancel);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeFieldResetTimer, on_match_reset);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeFieldMatchAssigned, on_match_assigned);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeActiveFieldChanged, on_active_field_changed);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeRankingsUpdated, on_rankings_updated);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeEventStatusUpdated, on_event_status_updated);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeElimAllianceUpdated, on_elim_alliance_update);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeElimUnavailTeamsUpdated, on_elim_unavail_teams_update);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeMatchListUpdated, on_match_list_update);
|
|
|
|
|
callbacks.insert(tm::NoticeId::NoticeFieldTimerStarted, on_timer_start);
|
|
|
|
|
|
|
|
|
|
let mut mqttoptions = MqttOptions::new("vex-bridge", "localhost", 1883);
|
|
|
|
|
mqttoptions.set_keep_alive(Duration::from_secs(5));
|
|
|
|
@ -863,13 +894,13 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let running = true;
|
|
|
|
|
let mut running = true;
|
|
|
|
|
let mut uuid = [0u8; 16];
|
|
|
|
|
rand::thread_rng().fill_bytes(&mut uuid);
|
|
|
|
|
let mut client_name = [0u8;32];
|
|
|
|
|
rand::thread_rng().fill_bytes(&mut client_name);
|
|
|
|
|
let username: [u8;16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
|
let (mut tm_client, tm_connection) = TMClient::new(uuid, client_name, String::from(""), username);
|
|
|
|
|
let (mut tm_client, mut tm_connection) = TMClient::new(uuid, client_name, String::from(""), username);
|
|
|
|
|
let tm_thread = thread::spawn(move ||
|
|
|
|
|
while running {
|
|
|
|
|
tm_client.process();
|
|
|
|
@ -897,14 +928,14 @@ fn main() {
|
|
|
|
|
field_req.on_field_match = Some(field_data);
|
|
|
|
|
|
|
|
|
|
let field_resp = tm_connection.request(309, field_req);
|
|
|
|
|
println!("Field {}/{}: {:#?}", field_set_id, field.id, field_resp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while running {
|
|
|
|
|
thread::sleep(Duration::from_millis(1000));
|
|
|
|
|
match tm_connection.notices.recv() {
|
|
|
|
|
Ok(notice) => {
|
|
|
|
|
match tm_connection.work_queue.recv() {
|
|
|
|
|
Ok(work) => match work {
|
|
|
|
|
Work::Exit => running = false,
|
|
|
|
|
Work::Notice(notice) => {
|
|
|
|
|
let callback = callbacks.get(¬ice.id());
|
|
|
|
|
match callback {
|
|
|
|
|
None => {
|
|
|
|
@ -914,7 +945,7 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Some(callback) => {
|
|
|
|
|
let messages = callback(*notice, &mut event, &tm_connection);
|
|
|
|
|
let messages = callback(*notice, &mut event, &mut tm_connection);
|
|
|
|
|
for message in messages {
|
|
|
|
|
let result = client.publish(message.topic, QoS::AtMostOnce, true, message.payload);
|
|
|
|
|
match result {
|
|
|
|
@ -925,6 +956,15 @@ fn main() {
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Work::State(state_change) => {
|
|
|
|
|
match event.get_match(state_change.tuple) {
|
|
|
|
|
None => log::warn!("Received state change for unknown match {:#?}", state_change.tuple),
|
|
|
|
|
Some(m) => {
|
|
|
|
|
m.state = Some(state_change.next_state);
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Err(error) => log::error!("Notice recv error: {}", error),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|