Skip to content

Commit 2f8a9e2

Browse files
committed
add stop logic interactive simulation, removed commands (NEXT PR), improved SimulationWrapperInteractive to send complete simulation status
1 parent ef904a4 commit 2f8a9e2

17 files changed

+330
-473
lines changed
Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
function InteractiveSimulation()
22
% INTERACTIVESIMULATION Interactive demo with frame validation.
33
% It processes valid telemetry (t, x, y, vx, vy) and sends an error packet
4-
% if the frame is missing required fields.
4+
% if the frame is missing required fields. Terminates after 100 steps.
55

66
%% ───── configuration
77
REQUIRED = ["t", "x", "y", "vx", "vy"]; % required fields
88
PAUSE_IO = 0.01; % pause to avoid spin-lock (s)
9+
MAX_STEPS = 100; % number of iterations before termination
910

1011
%% ───── initialization
1112

@@ -15,8 +16,10 @@ function InteractiveSimulation()
1516
last_input = struct(); % cache of the last valid frame
1617
last_time = []; % timestamp of the last valid frame
1718

19+
step = 0; % iteration counter
20+
1821
%% ───── main loop
19-
while true
22+
while step < MAX_STEPS
2023
data_in = wrapper.get_input(); % receive data from the Python client
2124

2225
% 1️⃣ Frame validation
@@ -25,42 +28,38 @@ function InteractiveSimulation()
2528

2629
invalid_reason = "";
2730
if isempty(data_in)
28-
invalid_reason = "empty frame"; % empty frame
31+
invalid_reason = "empty frame";
2932
elseif ~isstruct(data_in)
30-
invalid_reason = "not a struct"; % not a struct
33+
invalid_reason = "not a struct";
3134
elseif ~all(isfield(data_in, REQUIRED))
3235
missing = REQUIRED(~isfield(data_in, REQUIRED));
3336
invalid_reason = "missing fields: " + strjoin(missing, ",");
3437
end
3538

36-
% 2️⃣ Always generate an output, regardless of frame validity
39+
% 2️⃣ Always generate an output
3740
if invalid_reason ~= ""
38-
disp(['❌ Invalid frame: ', invalid_reason]); % log error
41+
disp(['❌ Invalid frame: ', invalid_reason]);
3942
err_out = struct( ...
4043
"status", "invalid", ...
4144
"reason", invalid_reason, ...
4245
"timestamp", posixtime(datetime("now")) ...
4346
);
44-
wrapper.send_output(err_out); % send error packet
47+
wrapper.send_output(err_out);
4548
else
4649
% 3️⃣ If the frame is valid and new, process it
4750
if isempty(last_input) || ~isequal(data_in, last_input)
48-
% Extract variables
4951
t = data_in.t; x = data_in.x; y = data_in.y;
5052
vx = data_in.vx; vy = data_in.vy;
5153

52-
% Δt (Euler)
5354
if isempty(last_time)
54-
dt = 0; % if there's no last time, set dt to 0
55+
dt = 0;
5556
else
56-
dt = t - last_time; % calculate the time difference
57+
dt = t - last_time;
5758
end
5859

59-
% Prediction
6060
x_next = x + vx * dt;
6161
y_next = y + vy * dt;
6262

63-
% Build output
6463
ok_out = struct( ...
6564
"status", "ok", ...
6665
"predicted", struct("x_next", x_next, "y_next", y_next), ...
@@ -71,17 +70,17 @@ function InteractiveSimulation()
7170
);
7271

7372
disp('📤 Output sent:');
74-
disp(ok_out); % log the output sent
75-
76-
% Send the valid output packet
73+
disp(ok_out);
7774
wrapper.send_output(ok_out);
7875

79-
% Cache the last frame and time
8076
last_input = data_in;
8177
last_time = t;
8278
end
8379
end
8480

85-
pause(PAUSE_IO); % pause to avoid continuous loop consumption
81+
step = step + 1;
82+
pause(PAUSE_IO);
8683
end
84+
85+
wrapper.send_completed();
8786
end
Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,93 @@
11
# Interactive Simulation
22

3-
This example showcases a basic "Hello world" interactive simulation.
3+
The `InteractiveSimulation` is a 'Hello world' MATLAB-based simulation that interacts with external clients, processes telemetry data, and returns predicted positions for a moving object. Both input and output are in streaming.
4+
The simulation operates in steps: it receives input, validates it, computes the next position using velocity and time, and sends the result back. This process repeats for up to 100 steps.
45

5-
The simulation relies on `SimulationWrapperInteractive.m` to manage TCP/IP communication with the MATLAB agent, allowing real-time data exchange between the simulation and the client.
6+
Telemetry data must include time (`t`), position (`x`, `y`), and velocity (`vx`, `vy`). The simulation uses the Euler method to predict the next position (`x_next`, `y_next`). If required data is missing or invalid, an error message is returned. After 100 steps, the simulation terminates.
7+
8+
Communication with the MATLAB agent is handled via TCP, using the `SimulationWrapperInteractive` object to manage data exchange.
69

710
## Table of Contents
811

912
- [Interactive Simulation](#interactive-simulation)
1013
- [Table of Contents](#table-of-contents)
1114
- [Usage](#usage)
15+
- [Simulation Steps and Flow](#simulation-steps-and-flow)
1216

1317
## Usage
1418

15-
Before running the simulation, you need to configure the Matlab agent by setting the simulation folder path in the `config.yaml` file under the simulation section:
19+
Run this simulation interactively with a Python client using the specified API payload.
1620

1721
```yaml
1822
simulation:
19-
path: <path_to_simulation_folder>
20-
```
23+
# Unique identifier for this simulation request. This ID is used to track and reference the simulation request.
24+
request_id: abcdef12345
2125

22-
This path should point to the directory `interactive-simulation` containing the simulation files
26+
# Identifier for the client that is sending the simulation request.
27+
client_id: dt
2328

24-
Once configured, you can initiate the simulation using the API as described below.
29+
# Specifies the simulator type. In this case, it indicates that the simulation is running in MATLAB.
30+
simulator: matlab
2531

26-
The simulation can be initiated via the API by submitting a YAML payload, a template of which is available in the file `api/simulation.yaml`
32+
# Type of simulation. Here, it indicates that the simulation is "interactive", meaning it involves continuous interaction.
33+
type: interactive
2734

28-
```yaml
29-
simulation:
30-
request_id: abcdef12345 # Unique identifier for this simulation request
31-
client_id: dt # Client identifier for tracking purposes
32-
simulator: matlab # Specifies MATLAB as the simulation engine
33-
type: interactive # Indicates this is an interactive simulation type
34-
file: InteractiveSimulation.m # Main MATLAB file to execute
35+
# Name of the MATLAB script that will handle the simulation. This script will process the input data and return the output.
36+
file: InteractiveSimulation.m
37+
38+
# The inputs section defines the data that the simulation will receive to process.
3539
inputs:
36-
stream_source: "rabbitmq://streaming.inputs.sim123" # RabbitMQ stream for input data
40+
# Specifies the RabbitMQ stream URL from which the simulation will receive telemetry data.
41+
# This is the source for continuous stream data to be processed in the simulation.
42+
stream_source: "rabbitmq://streaming.inputs.sim123"
43+
44+
# The outputs section defines the structure of the results that will be returned after processing the inputs.
3745
outputs:
46+
# Predicted values after the simulation process. These values represent the predicted next positions of the object.
3847
predicted:
39-
x_next: float # Next predicted X coordinate value
40-
y_next: float # Next predicted Y coordinate value
48+
# Predicted x-coordinate of the object in the next time step.
49+
x_next: float
50+
51+
# Predicted y-coordinate of the object in the next time step.
52+
y_next: float
53+
54+
# Miscellaneous data that could provide additional context or information about the simulation.
4155
misc:
42-
distance_from_origin: float # Calculated distance from origin point
43-
timestamp: float # Simulation timestamp in epoch seconds
56+
# The Euclidean distance from the origin (0, 0) to the current position of the object.
57+
# This can be used to measure how far the object has moved from its starting point.
58+
distance_from_origin: float
59+
60+
# The timestamp of the output data in epoch seconds. This helps to track when the output was generated.
61+
timestamp: float # epoch seconds
4462
```
4563
46-
Use the client `use_matlab_agent_interactive.py` with the CLI option `--api-payload` to specify the path to this YAML payload file and start the client.
64+
> **Note:** The stream_source field in the inputs section is mandatory for interactive simulations. This parameter specifies the RabbitMQ stream from which the simulation will receive real-time input data, and it must be included for the simulation to function correctly.
65+
66+
## Simulation Steps and Flow
67+
68+
1. **Initialization**
69+
70+
- Initialize `SimulationWrapperInteractive` for TCP communication.
71+
- Prepare to receive telemetry input.
72+
73+
2. **Main Loop (100 Steps)**
74+
75+
- Process telemetry frames from the Python client.
76+
- Each frame must include: `t`, `x`, `y`, `vx`, `vy`.
77+
78+
3. **Frame Validation**
79+
80+
- Check for all required fields.
81+
- If valid, compute next position using Euler method:
82+
- `x_next = x + vx * dt`
83+
- `y_next = y + vy * dt`
84+
- `dt` is the time difference between current and previous `t`.
85+
- If invalid, send error message.
86+
87+
4. **Output**
88+
89+
- Send predicted position and additional info (distance from origin, timestamp) to the client.
90+
- On error, send error packet.
91+
92+
5. **Completion**
93+
- After 100 steps, send a "completed" message.
Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,114 @@
1-
21
classdef SimulationWrapperInteractive < handle
2+
% SIMULATIONWRAPPERINTERACTIVE
3+
% Wrapper for interactive communication with a Python-based simulation client
4+
% via TCP. Handles input reception, output transmission, and finalization.
5+
36
properties (Access = private)
4-
out_client % TCP client object for outgoing data
5-
in_client % TCP client object for incoming data
6-
last_inputs % Store the last inputs received from Python
7+
out_client % TCP client for sending data to Python
8+
in_client % TCP client for receiving data from Python
9+
last_inputs % Cache of the most recent valid input frame
710
end
8-
11+
912
methods
10-
% Constructor for the SimulationWrapperInteractive class
13+
% Constructor: Establishes TCP connections to the Python client
1114
function obj = SimulationWrapperInteractive()
12-
% Default host and ports (modifiable)
15+
% Default connection settings
1316
out_host = 'localhost';
1417
out_port = 5678;
1518
in_host = 'localhost';
1619
in_port = 5679;
17-
% Max retries for connecting to the server
20+
21+
% Retry logic configuration
1822
max_retries = 5;
19-
retry_delay = 1; % Delay between retries in seconds
23+
retry_delay = 1; % in seconds
2024

21-
% Try to connect to the server up to 'max_retries' times
25+
% Attempt to connect to input and output ports with retry
2226
for retry = 1:max_retries
2327
try
24-
% Create a TCP client object to connect to Python server
2528
obj.out_client = tcpclient(out_host, out_port);
26-
obj.in_client = tcpclient(in_host, in_port);
29+
obj.in_client = tcpclient(in_host, in_port);
30+
2731
configureTerminator(obj.out_client, "LF");
2832
configureTerminator(obj.in_client, "LF");
29-
break; % Exit the loop if the connection is successful
33+
34+
break; % Exit loop upon successful connection
3035
catch ME
31-
% If connection fails, retry up to 'max_retries' times
3236
if retry == max_retries
33-
% If max retries reached, rethrow the exception
34-
rethrow(ME);
37+
rethrow(ME); % Rethrow error if max retries reached
3538
end
36-
% Wait before retrying
37-
pause(retry_delay);
39+
pause(retry_delay); % Wait before next retry
3840
end
3941
end
4042

41-
% Receive the initial parameters in JSON format from Python
43+
% Read initial configuration/handshake data from Python (JSON format)
4244
data = readline(obj.out_client);
43-
% Decode the received JSON data and store it as 'last_inputs'
44-
obj.last_inputs = jsondecode(data);
45+
obj.last_inputs = jsondecode(data); % Store parsed input
4546
end
4647

47-
% Method to retrieve input parameters from the Python server
48+
% Retrieve the latest input data frame (non-blocking with timeout)
4849
function inputs = get_input(obj)
49-
% Single method to get input data
50-
% Reads new streaming data if available, otherwise returns last input
51-
52-
% Timeout or no data received for a while
53-
timeout_limit = 2; % Set a timeout limit (in seconds)
50+
timeout_limit = 2; % Timeout threshold in seconds
5451
time_start = tic;
5552

5653
new_data = obj.try_receive();
54+
55+
% Retry receiving until timeout is reached
5756
while isempty(new_data)
58-
% No new data, check if the timeout limit is reached
5957
if toc(time_start) > timeout_limit
6058
disp('⏳ Timeout: No new input data received for a while.');
61-
break; % Exit the loop if timeout
59+
break;
6260
end
63-
% Retry reading if no new data
6461
new_data = obj.try_receive();
6562
end
6663

64+
% Update internal cache if new data was received
6765
if ~isempty(new_data)
6866
obj.last_inputs = new_data;
6967
end
70-
inputs = obj.last_inputs; % Return the stored inputs
68+
69+
% Return the most recent available inputs
70+
inputs = obj.last_inputs;
7171
end
7272

73+
% Try to receive a new input frame (non-blocking)
7374
function data_struct = try_receive(obj)
74-
% Non-blocking receive function to get data if available
7575
data_struct = [];
76+
77+
% Read available data lines from the input stream
7678
while obj.in_client.NumBytesAvailable > 0
7779
line = readline(obj.in_client);
7880
disp("📩 Received:");
7981
disp(line)
8082
try
81-
data_struct = jsondecode(line);
83+
data_struct = jsondecode(line); % Attempt to parse JSON
8284
catch
83-
warning("JSON decode failed");
85+
warning("JSON decode failed"); % Log failure but continue
8486
end
8587
end
8688
end
8789

88-
% Method to send output data to the Python server
90+
% Send a "simulation completed" packet to Python
91+
function send_completed(obj)
92+
completed_packet = struct( ...
93+
"status", "completed", ...
94+
"timestamp", posixtime(datetime("now")) ...
95+
);
96+
disp("✅ Simulation completed. Sending final packet:");
97+
disp(completed_packet);
98+
99+
obj.send_output(completed_packet);
100+
end
101+
102+
% Send an output frame to the Python client (encoded as JSON)
89103
function send_output(obj, output_data)
90-
% Convert the output data to JSON format
91-
json_data = jsonencode(output_data);
92-
% Send the JSON-encoded data to Python server
93-
writeline(obj.out_client, json_data);
104+
json_data = jsonencode(output_data); % Convert to JSON string
105+
writeline(obj.out_client, json_data); % Send via TCP
94106
end
95107

96-
% Destructor to clean up the TCP client object when the wrapper is deleted
108+
% Destructor: Gracefully close the TCP connections
97109
function delete(obj)
98-
% Close the TCP connection by deleting the client object
99110
delete(obj.out_client);
100111
delete(obj.in_client);
101112
end
102113
end
103-
end
114+
end

agents/matlab/matlab_agent/resources/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ Each example folder contains an `api/` subfolder with example simulation payload
118118
To run the batch simulation example, specify the full absolute path to the payload file when invoking the Python client:
119119

120120
```bash
121-
python use_matlab_agent.py --api-payload "/Users/foo/simulation-bridge/agents/matlab/matlab_agent/docs/examples/batch-simulation/api/simulation_batch.yaml.example"
121+
python use_matlab_agent_batch.py --api-payload "/Users/foo/simulation-bridge/agents/matlab/matlab_agent/docs/examples/batch-simulation/api/simulation_batch.yaml.example"
122122
```
123123

124124
## Control Commands

0 commit comments

Comments
 (0)