Skip to content

Commit c211204

Browse files
authored
Adds Interactive Mode to Matlab agent (#24)
- Introduces interactive mode for bidirectional data exchange. The interactive mode requires stream_source for sending inputs from client to the Matlab agent. The API payloads and configuration gets updated. - Updates the Matlab Wrapper codes for stream and interactive simulation modes. - Adds new examples and expands documentation to describe all simulation modes.
1 parent 10feb6c commit c211204

File tree

64 files changed

+2641
-1416
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+2641
-1416
lines changed

.github/workflows/matlab-agent-ci.yml

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,22 @@ jobs:
5858
run: poetry run pylint matlab_agent --fail-under=9
5959

6060
# ───── 6. Tests ─────
61-
# - name: Run pytest with coverage
62-
# run: poetry run pytest
63-
64-
# - name: Upload coverage to Codecov
65-
# if: matrix.os == 'ubuntu-latest'
66-
# uses: codecov/codecov-action@v5
67-
# with:
68-
# files: coverage.xml
69-
# fail_ci_if_error: true
70-
# token: ${{ secrets.CODECOV_TOKEN }}
71-
72-
# - name: Run pytest (no coverage)
73-
# if: matrix.os != 'ubuntu-latest'
74-
# run: poetry run pytest -q
61+
- name: Run pytest (with coverage)
62+
if: matrix.os == 'ubuntu-latest'
63+
run: poetry run pytest --cov=matlab_agent --cov-report=xml
64+
65+
- name: Upload coverage to Codecov
66+
if: matrix.os == 'ubuntu-latest'
67+
uses: codecov/codecov-action@v5
68+
with:
69+
files: coverage.xml
70+
fail_ci_if_error: true
71+
token: ${{ secrets.CODECOV_TOKEN }}
72+
73+
# Windows/macOS → test without coverage
74+
- name: Run pytest (no coverage)
75+
if: matrix.os != 'ubuntu-latest'
76+
run: poetry run pytest -q
7577

7678
# ───── 7. Build + dist artefacts ─────
7779
- name: Install runtime dependencies

.gitignore

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,19 +198,20 @@ agents/matlab/dist/
198198
config*.yaml
199199
simulation*.yaml
200200
*.csv
201-
interactive.py
202-
*_interactive.py
203-
**/examples/interactive-simulation/*
204201
#SIMULATION BRIDGE
205202
/logs
206203
/certs
207204
*.pem
208205
*_use.yaml
209206
client/
207+
dist/
208+
/certs
209+
*.pem
210+
*_use.yaml
211+
client/
210212
client_ripetitive/
211213
dist/
212214
.venv*
213215
certs
214216
performance_log/
215217
performance_old/
216-
MagicMock/

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ _sim-bridge_ exposes a unified interface that enables seamless data exchange and
1919
- [Simulation Bridge](#simulation-bridge)
2020
- [Table of Contents](#table-of-contents)
2121
- [Key Features](#key-features)
22-
- [Agents](#agents)
23-
- [Modes of Simulation](#modes-of-simulation)
24-
- [Plug-in Protocol Adapters](#plug-in-protocol-adapters)
22+
- [Agents](#agents)
23+
- [Modes of Simulation](#modes-of-simulation)
24+
- [Plug-in Protocol Adapters](#plug-in-protocol-adapters)
2525
- [Documentation](#documentation)
2626
- [Simulation Bridge](#simulation-bridge-1)
2727
- [Matlab Agent](#matlab-agent)

agents/matlab/.pylintrc

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -577,12 +577,4 @@ max-returns=6
577577
max-statements=50
578578

579579
# Minimum number of public methods for a class (see R0903).
580-
min-public-methods=1
581-
582-
583-
[EXCEPTIONS]
584-
585-
# Exceptions that will emit a warning when being caught. Defaults to
586-
# "BaseException, Exception".
587-
overgeneral-exceptions=BaseException,
588-
Exception
580+
min-public-methods=1

agents/matlab/README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
The MATLAB Agent is a Python-based connector designed to interface with MATLAB simulations through various methods. It provides the following functionalities:
44

55
- **Batch Simulation**: Executes predefined MATLAB routines with specified input parameters, collecting the final results upon completion.
6-
- **Streaming Simulation (Agent-Based)**: Allows sending input once, with the output being received in real-time during the simulation.
6+
- **Streaming Simulation**: Executes MATLAB simulations where input parameters are sent once at the beginning, and results are transmitted continuously in real-time as the simulation progresses.
7+
- **Interactive Simulation**: Enables bidirectional communication with MATLAB during simulation execution, allowing continuous exchange of data, both input parameters and output results flow in real-time throughout the simulation process.
78

8-
The MATLAB Agent is primarily built to integrate with the Simulation Bridge but can also be utilized by external systems via RabbitMQ exchange methods. Communication parameters and other settings must be defined in the YAML-based configuration file.
9+
The MATLAB Agent is primarily built to integrate with the Simulation Bridge [_sim_bridge_](../../README.md) but can also be utilized by external systems via RabbitMQ exchange methods. Communication parameters and other settings must be defined in the YAML-based configuration file.
910

1011
<div align="center">
1112
<img src="matlab_agent/images/structure.png" alt="MATLAB Agent Structure" width="600" style="border: 1px solid #ddd; border-radius: 4px; padding: 5px;">
@@ -183,8 +184,9 @@ logging:
183184
file: logs/matlab_agent.log # The file path where logs will be stored.
184185

185186
tcp:
186-
host: localhost # The hostname or IP address for TCP communication.
187-
port: 5678 # The port number for TCP communication.
187+
host: localhost # Host for receiving input stream
188+
input_port: 5679 # Port for receiving input stream
189+
output_port: 5678 # Port for output stream
188190

189191
response_templates:
190192
success:
@@ -246,7 +248,8 @@ This command creates the following structure in your current directory (existing
246248
├── config.yaml # Agent configuration settings
247249
├── SimulationBatch.m # Template for batch simulations
248250
├── SimulationStreaming.m # Template for streaming simulations
249-
├── SimulationWrapper.m # MATLAB class for easy communication with the MATLAB Agent during streaming simulations
251+
├── SimulationWrapperStreaming.m # MATLAB class for streaming simulations
252+
├── SimulationWrapperInteractive.m # MATLAB class for interactive simulations
250253
└── client/
251254
├── simulation.yaml # Example simulation request payload
252255
├── use.yaml # Client configuration file
@@ -292,16 +295,16 @@ Example output:
292295

293296
```bash
294297
dist/
295-
├── matlab_agent-0.2.0-py3-none-any.whl
296-
└── matlab_agent-0.2.0.tar.gz
298+
├── matlab_agent-0.3.0-py3-none-any.whl
299+
└── matlab_agent-0.3.0.tar.gz
297300
```
298301

299302
### Verifying the Package (Optional but Recommended)
300303

301304
You can verify that the package works by installing it locally:
302305

303306
```bash
304-
pip install dist/matlab_agent-0.2.0-py3-none-any.whl
307+
pip install dist/matlab_agent-0.3.0-py3-none-any.whl
305308
```
306309

307310
Then, run the command defined in the script:
@@ -315,7 +318,7 @@ matlab-agent
315318
When you modify the code and want to release a new version, increment the version number in `pyproject.toml`:
316319

317320
```toml
318-
version = "0.3.0"
321+
version = "0.4.0"
319322
```
320323

321324
Then rebuild the package:
@@ -349,6 +352,7 @@ Inside this folder, you'll find:
349352
- `use.yaml` — Configuration file for the communication protocol (e.g., RabbitMQ settings)
350353
- `simulation.yaml` — The simulation request payload that will be sent to the MATLAB Agent
351354
- `use_matlab_agent.py` — Python script to send the request and receive the results
355+
- `use_matlab_agent_interactive.py` — Python script to interact with the MATLAB Agent in real-time
352356

353357
For detailed instructions on how to configure and use the client, refer to the [Use Matlab Agent](./matlab_agent/resources/README.md) in the `agents/matlab/matlab_agent/resources/` folder.
354358

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.2.0"
1+
__version__ = "1.0.0"

agents/matlab/matlab_agent/api/simulation.yaml.template

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@ simulation:
1414
# Options:
1515
# - 'batch': runs the simulation in batch mode, where results are returned only after the entire computation is complete.
1616
# - 'streaming': runs the simulation in streaming mode, providing real-time updates at each computation step.
17+
# - 'interactive': same as 'streaming', where you need to provide a continuous stream of inputs and outputs.
1718

1819
file: SimulationStreaming.m
1920
# The name of the MATLAB script or function file to execute for this simulation.
2021

2122
inputs:
2223
# Input variables to be passed to the simulation.
23-
# Customize these key-value pairs as needed for your specific simulation.
24+
# If the simulation type is 'interactive', it is mandatory to specify 'stream_source' to indicate the source of the input stream.
25+
# stream_source: The source of the input stream, e.g., RabbitMQ, which streams real-time input data to the simulation.
26+
# This is required for interactive simulations.
27+
stream_source: # Source of the input stream, typically RabbitMQ for streaming mode.
28+
2429
i1: ..
2530
i2: ..
2631
i3: ..

agents/matlab/matlab_agent/config/config.yaml.template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ performance:
3333

3434
tcp:
3535
host: localhost
36-
port: 5678
36+
input_port: 5679
37+
output_port: 5678
3738

3839
response_templates:
3940
success:

agents/matlab/matlab_agent/docs/README.md

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ An Streaming simulation is designed to receive a predefined input configuration
5858

5959
### Streaming Requirements
6060

61-
For this type of simulation, you must use the `SimulationWrapper` class, which should be placed in the same folder as the `Simulation.m` file. The `SimulationWrapper.m` handles the TCP/IP connection and communication with the MATLAB agent script.
61+
For this type of simulation, you must use the `SimulationWrapperStreaming` class, which should be placed in the same folder as the `SimulationStreaming.m` file. The `SimulationWrapperStreaming.m` handles the TCP/IP connection and communication with the MATLAB agent script.
6262

6363
The simulation logic must be entirely contained within the main function, defined at the top level as `Simulation()`. This function is responsible for managing both inputs and outputs in the following format:
6464

6565
```matlab
6666
function Simulation()
6767
% 🔌 Initialize the wrapper
68-
wrapper = SimulationWrapper();
68+
wrapper = SimulationWrapperStreaming();
6969
7070
% Receive input from the MATLAB agent (via JSON)
7171
inputs = wrapper.get_inputs();
@@ -94,3 +94,54 @@ These files provide reference implementations to help you structure your simulat
9494
#### Notes
9595

9696
No additional files are handled. All data is transmitted exclusively through the TCP socket.
97+
98+
---
99+
100+
## Interactive Simulation
101+
102+
An interactive simulation continuously exchanges data with the MATLAB Agent. The MATLAB code reacts to new input frames and can send updated outputs at any time during execution. Unlike batch or streaming modes, the simulation remains active while new frames arrive asynchronously.
103+
104+
Use the `SimulationWrapperInteractive` class located alongside your `InteractiveSimulation.m` file. This wrapper manages two TCP connections:
105+
106+
1. **Output stream** – sends simulation results to the MATLAB Agent.
107+
2. **Input stream** – receives new frames coming from the message broker.
108+
109+
### Interactive Flow
110+
111+
1. **Instantiate the wrapper**
112+
113+
```matlab
114+
wrapper = SimulationWrapperInteractive();
115+
```
116+
117+
2. **Retrieve initial parameters**
118+
Before entering the main loop the simulation can pull a first packet of inputs provided during the handshake.
119+
120+
```matlab
121+
init_data = wrapper.get_initial_inputs();
122+
% ... initialise simulation state using init_data ...
123+
```
124+
125+
3. **Main loop**
126+
Continuously poll for new frames, update the state and emit outputs.
127+
128+
```matlab
129+
while true
130+
data_in = wrapper.get_input();
131+
if ~isempty(data_in)
132+
% ... update simulation state ...
133+
wrapper.send_output(struct('inputs', data_in));
134+
end
135+
pause(0.01); % small delay to avoid busy waiting
136+
end
137+
```
138+
139+
4. **Finalisation**
140+
When the simulation ends, optionally call wrapper.send_completed() and clean up any resources.
141+
```matlab
142+
wrapper.send_completed();
143+
```
144+
145+
The corresponding API payload must specify `inputs.stream_source` with a RabbitMQ URL pointing to the topic where input frames will be published.
146+
147+
Refer to `examples/interactive-simulation` for a full example.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
function InteractiveSimulation()
2+
% INTERACTIVESIMULATION Interactive demo with frame validation.
3+
% It processes valid telemetry (t, x, y, vx, vy) and sends an error packet
4+
5+
wrapper = SimulationWrapperInteractive();
6+
init = wrapper.get_initial_inputs(); % handshake parameters
7+
8+
REQUIRED = init.REQUIRED; % required fields in the input frame
9+
PAUSE_IO = init.PAUSE_IO; % pause to avoid spin-lock (s)
10+
MAX_STEPS = init.MAX_STEPS; % number of iterations before termination
11+
12+
last_input = struct(); % cache of the last valid frame
13+
last_time = []; % timestamp of the last valid frame
14+
15+
step = 0; % iteration counter
16+
17+
%% ───── main loop
18+
while step < MAX_STEPS
19+
data_in = wrapper.get_input(); % receive data from the Python client
20+
21+
% 1️⃣ Frame validation
22+
disp('📥 Received frame:');
23+
disp(data_in); % log the incoming frame
24+
25+
invalid_reason = "";
26+
if isempty(data_in)
27+
invalid_reason = "empty frame";
28+
elseif ~isstruct(data_in)
29+
invalid_reason = "not a struct";
30+
elseif ~all(isfield(data_in, REQUIRED))
31+
missing = REQUIRED(~isfield(data_in, REQUIRED));
32+
invalid_reason = "missing fields: " + strjoin(missing, ",");
33+
end
34+
35+
% 2️⃣ Always generate an output
36+
if invalid_reason ~= ""
37+
disp(['❌ Invalid frame: ', invalid_reason]);
38+
err_out = struct( ...
39+
"status", "invalid", ...
40+
"reason", invalid_reason, ...
41+
"timestamp", posixtime(datetime("now")) ...
42+
);
43+
wrapper.send_output(err_out);
44+
else
45+
% 3️⃣ If the frame is valid and new, process it
46+
if isempty(last_input) || ~isequal(data_in, last_input)
47+
t = data_in.t; x = data_in.x; y = data_in.y;
48+
vx = data_in.vx; vy = data_in.vy;
49+
50+
if isempty(last_time)
51+
dt = 0;
52+
else
53+
dt = t - last_time;
54+
end
55+
56+
x_next = x + vx * dt;
57+
y_next = y + vy * dt;
58+
59+
ok_out = struct( ...
60+
"status", "ok", ...
61+
"predicted", struct("x_next", x_next, "y_next", y_next), ...
62+
"misc", struct( ...
63+
"distance_from_origin", hypot(x, y), ...
64+
"timestamp", posixtime(datetime("now")) ...
65+
) ...
66+
);
67+
68+
disp('📤 Output sent:');
69+
disp(ok_out);
70+
wrapper.send_output(ok_out);
71+
72+
last_input = data_in;
73+
last_time = t;
74+
end
75+
end
76+
77+
step = step + 1;
78+
pause(PAUSE_IO);
79+
end
80+
81+
wrapper.send_completed();
82+
end

0 commit comments

Comments
 (0)