Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# .editorconfig file
[*]
trim_trailing_whitespace = true
11 changes: 6 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ repos:

- repo: local
hooks:
- id: check-license
name: check_license
entry: reuse lint
- id: reuse-annotate
name: REUSE Annotate
entry: reuse
args: [annotate, -c, "All contributors", -l, "GPL-2.0-or-later", --merge-copyrights ]
language: python
pass_filenames: false
additional_dependencies: ["reuse"]
pass_filenames: true
types_or: ["rust"]
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 71 additions & 0 deletions docs/WebSocket/WebSocket.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ These are primarily informations that either change often or it doesn't make sen
- [Sector finished](#sector-finished-broadcast)
- [Lap finished](#lap-finished-broadcast)
- [Current Laptime](#current-laptime-broadcast)
- [Current Session](#current-session)
- [GNSS Data /v1/gnss_data](#gnss-data-v1gnss_data)
- [Success](#success-1)
- [Events](#events-1)
Expand Down Expand Up @@ -103,6 +104,76 @@ Example JSON object:
"time": "00:50:456.789"
}
}

```
### Current Session
The current session event provides the complete data of the ongoing session.
It contains information about the track, laps, and log points recorded so far in the session.
This event is sent when a websocket connection is established to provide the current state of the session.
A client can use this data to synchronize its state with the ongoing session.
The client should not display any other data until this event is received to ensure consistency.

The data object in the event contains the full session data structured in the same as would be downloaded via the [REST API](https://github.com/mFlorianW/rapid-rusty/blob/main/docs/REST/Session.md#get-v1sessionsid).

Example JSON object:
```json
{
"event": "current_session",
"data": {
"session": {
"id": 0,
"date": "01.01.1970",
"time": "13:00:00.000",
"track": {
"name": "Oschersleben",
"startline": {
"latitude": 52.025833,
"longitude": 11.279166
},
"finishline": {
"latitude": 52.025833,
"longitude": 11.279166
},
"sectors": [
{
"latitude": 52.025833,
"longitude": 11.279166
},
{
"latitude": 52.025833,
"longitude": 11.279166
}
]
},
"laps": [
{
"sectors": [
"00:00:25.144",
"00:00:25.144",
"00:00:25.144",
"00:00:25.144"
],
"log_points": [
{
"velocity": 100,
"longitude": 11,
"latitude": 52,
"time": "00:00:00.000",
"date": "01.01.1970"
},
{
"velocity": 100,
"longitude": 11,
"latitude": 52,
"time": "00:00:00.000",
"date": "01.01.1970"
}
]
}
]
}
}
}
```

## GNSS Data /v1/gnss_data
Expand Down
16 changes: 15 additions & 1 deletion module_core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 All contributors
// SPDX-FileCopyrightText: 2025, 2026 All contributors
//
// SPDX-License-Identifier: GPL-2.0-or-later

Expand Down Expand Up @@ -53,13 +53,15 @@ impl Event {
EventKind::SaveSessionRequestEvent(req) => Some(req.id),
EventKind::LoadSessionRequestEvent(req) => Some(req.id),
EventKind::DeleteSessionRequestEvent(req) => Some(req.id),
EventKind::CurrentSessionRequestEvent(req) => Some(req.id),
EventKind::LoadStoredSessionIdsResponseEvent(res) => Some(res.id),
EventKind::SaveSessionResponseEvent(res) => Some(res.id),
EventKind::LoadSessionResponseEvent(res) => Some(res.id),
EventKind::DeleteSessionResponseEvent(res) => Some(res.id),
EventKind::LoadStoredTrackIdsResponseEvent(res) => Some(res.id),
EventKind::LoadAllStoredTracksResponseEvent(res) => Some(res.id),
EventKind::DetectTrackResponseEvent(res) => Some(res.id),
EventKind::CurrentSessionResponseEvent(res) => Some(res.id),
_ => None,
}
}
Expand All @@ -78,13 +80,15 @@ impl Event {
EventKind::LoadStoredTrackIdsRequest(req)
| EventKind::LoadAllStoredTracksRequestEvent(req)
| EventKind::DetectTrackRequestEvent(req) => Some(req.sender_addr),
EventKind::CurrentSessionRequestEvent(req) => Some(req.sender_addr),
EventKind::LoadStoredSessionIdsResponseEvent(res) => Some(res.receiver_addr),
EventKind::SaveSessionResponseEvent(res) => Some(res.receiver_addr),
EventKind::LoadSessionResponseEvent(res) => Some(res.receiver_addr),
EventKind::DeleteSessionResponseEvent(res) => Some(res.receiver_addr),
EventKind::LoadStoredTrackIdsResponseEvent(res) => Some(res.receiver_addr),
EventKind::LoadAllStoredTracksResponseEvent(res) => Some(res.receiver_addr),
EventKind::DetectTrackResponseEvent(res) => Some(res.receiver_addr),
EventKind::CurrentSessionResponseEvent(res) => Some(res.receiver_addr),
_ => None,
}
}
Expand Down Expand Up @@ -229,6 +233,9 @@ pub type LoadStoredTracksReponsePtr = Arc<Response<Vec<Track>>>;
/// A thread-safe shared pointer to a track detection request.
pub type TrackDetectionResponsePtr = Arc<Response<Vec<Track>>>;

/// A thread-safe shared pointer to a current session response.
pub type CurrentSessionResponsePtr = Arc<Response<Option<Arc<RwLock<Session>>>>>;

/// Generic helper macro to extract enum payloads
#[macro_export]
macro_rules! payload_ref {
Expand Down Expand Up @@ -337,6 +344,13 @@ pub enum EventKind {
/// Event emitted after track detection finishes.
/// Contains the `TrackDetectionResponsePtr` with detection results.
DetectTrackResponseEvent(TrackDetectionResponsePtr),

/// Event carrying a request to get the current session.
CurrentSessionRequestEvent(EmptyRequestPtr),

/// Event emitted in response to a current session request.
/// Contains the `CurrentSessionResponsePtr` with the session data.
CurrentSessionResponseEvent(CurrentSessionResponsePtr),
}

/// A simple asynchronous event bus for publishing and subscribing to [`Event`]s.
Expand Down
12 changes: 10 additions & 2 deletions modules/active_session/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: 2025 All contributors
// SPDX-FileCopyrightText: 2025, 2026 All contributors
//
// SPDX-License-Identifier: GPL-2.0-or-later

use async_trait::async_trait;
use chrono::Utc;
use common::{lap::Lap, position::GnssPosition, session::Session};
use module_core::{
DurationPtr, EventKind, Module, ModuleCtx, Request, SaveSessionRequestPtr,
DurationPtr, EventKind, Module, ModuleCtx, Request, Response, SaveSessionRequestPtr,
TrackDetectionResponsePtr,
};
use std::sync::{Arc, RwLock};
Expand Down Expand Up @@ -132,6 +132,14 @@ impl Module for ActiveSession {
EventKind::GnssPositionEvent(gnss_pos) => {
self.on_gnss_position(*gnss_pos);
}
EventKind::CurrentSessionRequestEvent(request) => {
let resp = Response {
id: request.id,
receiver_addr: request.sender_addr,
data: self.session.as_ref().map(|s| s.clone()),
};
let _ = self.ctx.publish_event(EventKind::CurrentSessionResponseEvent(resp.into()));
}
_ => (),
}
},
Expand Down
66 changes: 64 additions & 2 deletions modules/active_session/tests/tests_active_session.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-FileCopyrightText: 2025 All contributors
// SPDX-FileCopyrightText: 2025, 2026 All contributors
//
// SPDX-License-Identifier: GPL-2.0-or-later

use active_session::ActiveSession;
use common::{lap::Lap, position::GnssPosition, test_helper::track::get_track};
use module_core::{
Event, EventBus, EventKind, EventKindType, Module, Response, payload_ref,
Event, EventBus, EventKind, EventKindType, Module, Request, Response, payload_ref,
test_helper::{register_response_event, stop_module, wait_for_event},
};
use std::time::Duration;
Expand Down Expand Up @@ -163,3 +163,65 @@ async fn test_store_log_points() {

stop_module(&eb, &mut active_session).await;
}

#[tokio::test]
#[test_log::test]
async fn test_current_session_request_response() {
let eb = EventBus::default();
let mut active_session = create_module(&eb);

// Before emitting the lap start wait for the track detected event.
let _track_event = wait_for_event(
&mut eb.subscribe(),
Duration::from_millis(100),
EventKindType::DetectTrackResponseEvent,
)
.await;
eb.publish(&Event {
kind: EventKind::LapStartedEvent,
});
eb.publish(&Event {
kind: EventKind::LapFinishedEvent(std::time::Duration::from_secs_f32(30.750).into()),
});
debug!("Waiting for CurrentSessionRequestEvent...");
eb.publish(&Event {
kind: EventKind::CurrentSessionRequestEvent(
Request {
id: 20,
sender_addr: 200,
data: {},
}
.into(),
),
});
let current_session_event = wait_for_event(
&mut eb.subscribe(),
Duration::from_millis(100),
EventKindType::CurrentSessionResponseEvent,
)
.await;

//scope is needed to clear the rwlock at the end.
{
let session = match payload_ref!(
current_session_event.kind,
EventKind::CurrentSessionResponseEvent
) {
Some(response) => response.data.clone(),
None => {
panic!("Received session doesn't have a payload");
}
};
let session_lock = session.expect("Session data is None");
let session = session_lock.read().unwrap();
assert_eq!(session.laps.len(), 1);
let lap = Lap {
sectors: vec![],
log_points: vec![],
};
assert_eq!(session.laps[0], lap);
assert_eq!(session.track, get_track());
}

stop_module(&eb, &mut active_session).await;
}
1 change: 1 addition & 0 deletions modules/rest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ serde_json.workspace = true

rocket = { version = "~0.5", features = ["json"] }
rocket_ws = { version = "~0.1" }
rand ={ version = "~0.9" }

[dev-dependencies]
reqwest = { version = "~0.12", features = ["json"] }
Expand Down
51 changes: 50 additions & 1 deletion modules/rest/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 All contributors
// SPDX-FileCopyrightText: 2025, 2026 All contributors
//
// SPDX-License-Identifier: GPL-2.0-or-later

Expand All @@ -12,6 +12,7 @@ use rocket::{
serde::{Serialize, json::Json},
};
use std::{
collections::HashMap,
env,
net::Ipv4Addr,
sync::{Arc, RwLock},
Expand All @@ -37,6 +38,7 @@ pub(crate) struct RestCtx {
ctx: ModuleCtx,
module_addr: u64,
request_id: u64,
connections: HashMap<String, bool>,
}

impl RestCtx {
Expand All @@ -49,6 +51,52 @@ impl RestCtx {
self.request_id += 1;
id
}

/// Register a new connection in the internal registry.
///
/// Inserts the given connection ID with an initial state of `false`
/// (e.g., not yet synchronized). If the ID already exists, its value is
/// replaced.
///
/// - conn_id: Unique identifier for the client/connection.
pub fn register_connection(&mut self, conn_id: &str) {
self.connections.insert(conn_id.to_owned(), false);
}

/// Unregisters a connection from the internal registry.
///
/// Removes the entry associated with the given connection ID.
///
/// - conn_id: Unique identifier for the client/connection.
pub fn unregister_connection(&mut self, conn_id: &str) {
self.connections.remove(conn_id);
}

/// Checks if a connection is marked as synchronized.
///
/// Returns `true` if the connection with the given ID is marked as synchronized,
/// otherwise returns `false`.
///
/// - conn_id: Unique identifier for the client/connection.
pub fn is_connection_synced(&self, conn_id: &str) -> bool {
match self.connections.get(conn_id) {
Some(synced) => *synced,
None => false,
}
}

/// Sets the synchronization state of a connection.
///
/// Updates the internal state for the connection with the given ID
/// to reflect whether it is synchronized or not.
/// - conn_id: Unique identifier for the client/connection.
///
/// - synced: New synchronization state to set for the connection.
pub fn set_connection_synced(&mut self, conn_id: &str, synced: bool) {
if let Some(entry) = self.connections.get_mut(conn_id) {
*entry = synced;
}
}
}

impl Rest {
Expand All @@ -67,6 +115,7 @@ impl Rest {
ctx,
module_addr: 0xff,
request_id: 0,
connections: HashMap::new(),
})),
}
}
Expand Down
Loading