Skip to content

Commit 6ab6533

Browse files
committed
new configuration file for Wrapper's
1 parent be11713 commit 6ab6533

File tree

4 files changed

+187
-102
lines changed

4 files changed

+187
-102
lines changed
Lines changed: 106 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,103 @@
1-
21
classdef SimulationWrapperInteractive < handle
2+
%SIMULATIONWRAPPERINTERACTIVE TCP wrapper for interactive
3+
%MATLAB simulations. Default parameters are taken from a YAML
4+
%configuration file, but each key can be overridden at construction
5+
%time with name–value pairs:
6+
%
7+
% w = SimulationWrapperInteractive(); % defaults
8+
% w = SimulationWrapperInteractive("config/dev.yaml"); % custom file
9+
% w = SimulationWrapperInteractive([], ...
10+
% "tcp.output_port", 6000, "tcp.retry_delay", 2); % overrides
11+
312
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
13+
out_client % tcpclient for outgoing data
14+
in_client % tcpclient for incoming data
15+
last_inputs % last inputs received (struct / array)
16+
cfg % configuration struct loaded from YAML
717
end
8-
18+
919
methods
10-
% Constructor for the SimulationWrapperInteractive class
11-
function obj = SimulationWrapperInteractive()
12-
% Default host and ports (modifiable)
13-
host = 'localhost';
14-
out_port = 5678;
15-
in_port = 5679;
16-
% Max retries for connecting to the server
17-
max_retries = 5;
18-
retry_delay = 1; % Delay between retries in seconds
20+
%% ----------------------------------------------------------------
21+
function obj = SimulationWrapperInteractive(cfgFile, varargin)
22+
% Constructor
23+
if nargin == 0 || isempty(cfgFile)
24+
cfgFile = fullfile(fileparts(mfilename("fullpath")), ...
25+
"..", "config", "default.yaml");
26+
end
27+
obj.cfg = obj.loadConfig(cfgFile, varargin{:});
28+
29+
% Shorthand vars
30+
Host = obj.cfg.tcp.host;
31+
outPort = obj.cfg.tcp.output_port;
32+
inPort = obj.cfg.tcp.input_port;
33+
maxRetries = obj.cfg.tcp.max_retries;
34+
retryDelay = obj.cfg.tcp.retry_delay;
1935

20-
% Try to connect to the server up to 'max_retries' times
21-
for retry = 1:max_retries
36+
% Attempt connection with retry policy
37+
for retry = 1:maxRetries
2238
try
23-
% Create a TCP client object to connect to Python server
24-
obj.out_client = tcpclient(host, out_port);
25-
obj.in_client = tcpclient(host, in_port);
39+
obj.out_client = tcpclient(Host, outPort);
40+
obj.in_client = tcpclient(Host, inPort);
2641
configureTerminator(obj.out_client, "LF");
27-
configureTerminator(obj.in_client, "LF");
28-
break; % Exit the loop if the connection is successful
42+
configureTerminator(obj.in_client, "LF");
43+
break; % connected
2944
catch ME
30-
% If connection fails, retry up to 'max_retries' times
31-
if retry == max_retries
32-
% If max retries reached, rethrow the exception
33-
rethrow(ME);
45+
if retry == maxRetries
46+
rethrow(ME); % give up
3447
end
35-
% Wait before retrying
36-
pause(retry_delay);
48+
pause(retryDelay);
3749
end
3850
end
3951

40-
% Receive the initial parameters in JSON format from Python
41-
data = readline(obj.out_client);
42-
% Decode the received JSON data and store it as 'last_inputs'
43-
obj.last_inputs = jsondecode(data);
52+
% Read first JSON line (blocking)
53+
firstLine = readline(obj.out_client);
54+
obj.last_inputs = jsondecode(firstLine);
4455
end
4556

46-
% Method to retrieve input parameters from the Python server
57+
%% ----------------------------------------------------------------
4758
function inputs = get_input(obj)
48-
% Single method to get input data
49-
% Reads new streaming data if available, otherwise returns last input
50-
51-
% Timeout or no data received for a while
52-
timeout_limit = 2; % Set a timeout limit (in seconds)
53-
time_start = tic;
59+
% Return most-recent inputs, refreshing from socket if available
60+
t0 = tic;
61+
timeoutLim = obj.cfg.tcp.timeout_limit;
5462

55-
new_data = obj.try_receive();
56-
while isempty(new_data)
57-
% No new data, check if the timeout limit is reached
58-
if toc(time_start) > timeout_limit
59-
disp('⏳ Timeout: No new input data received for a while.');
60-
break; % Exit the loop if timeout
63+
newData = obj.try_receive();
64+
while isempty(newData)
65+
if toc(t0) > timeoutLim
66+
disp("⏳ Timeout: No new input data received.");
67+
break;
6168
end
62-
% Retry reading if no new data
63-
new_data = obj.try_receive();
69+
pause(0.05); % yield CPU a bit
70+
newData = obj.try_receive();
6471
end
6572

66-
if ~isempty(new_data)
67-
obj.last_inputs = new_data;
73+
if ~isempty(newData)
74+
obj.last_inputs = newData;
75+
end
76+
inputs = obj.last_inputs;
77+
end
78+
79+
%% ----------------------------------------------------------------
80+
function send_output(obj, output_data)
81+
json_data = jsonencode(output_data);
82+
writeline(obj.out_client, json_data);
83+
end
84+
85+
%% ----------------------------------------------------------------
86+
function delete(obj)
87+
% Destructor – close clients safely
88+
if ~isempty(obj.out_client) && isvalid(obj.out_client)
89+
delete(obj.out_client);
90+
end
91+
if ~isempty(obj.in_client) && isvalid(obj.in_client)
92+
delete(obj.in_client);
6893
end
69-
inputs = obj.last_inputs; % Return the stored inputs
7094
end
95+
end % methods
7196

97+
methods (Access = private)
98+
%% ----------------------------------------------------------------
7299
function data_struct = try_receive(obj)
73-
% Non-blocking receive function to get data if available
100+
% Non-blocking read of a complete JSON line, if any
74101
data_struct = [];
75102
while obj.in_client.NumBytesAvailable > 0
76103
line = readline(obj.in_client);
@@ -79,24 +106,40 @@
79106
try
80107
data_struct = jsondecode(line);
81108
catch
82-
warning("JSON decode failed");
109+
warning("JSON decode failed, skipping line.");
83110
end
84111
end
85112
end
86113

87-
% Method to send output data to the Python server
88-
function send_output(obj, output_data)
89-
% Convert the output data to JSON format
90-
json_data = jsonencode(output_data);
91-
% Send the JSON-encoded data to Python server
92-
writeline(obj.out_client, json_data);
114+
%% ----------------------------------------------------------------
115+
function cfg = loadConfig(~, cfgFile, varargin)
116+
% Load YAML, then apply dotted-key overrides
117+
raw = yamlread(cfgFile); % returns struct
118+
cfg = raw; % immutable copy
119+
120+
% Apply overrides
121+
for k = 1:2:numel(varargin)
122+
path = split(varargin{k}, '.');
123+
val = varargin{k+1};
124+
cfg = SimulationWrapperInteractive.setDeep(cfg, path, val);
125+
end
93126
end
127+
end % private methods
94128

95-
% Destructor to clean up the TCP client object when the wrapper is deleted
96-
function delete(obj)
97-
% Close the TCP connection by deleting the client object
98-
delete(obj.out_client);
99-
delete(obj.in_client);
129+
methods (Static, Access = private)
130+
%% ----------------------------------------------------------------
131+
function s = setDeep(s, path, val)
132+
% Recursively set a dotted field in struct 's' to 'val'
133+
if numel(path) == 1
134+
s.(path{1}) = val;
135+
else
136+
field = path{1};
137+
if ~isfield(s, field) || ~isstruct(s.(field))
138+
s.(field) = struct();
139+
end
140+
s.(field) = SimulationWrapperInteractive.setDeep( ...
141+
s.(field), path(2:end), val);
142+
end
100143
end
101144
end
102-
end
145+
end
Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,87 @@
11
classdef SimulationWrapperStreaming < handle
2+
%SIMULATIONWRAPPERSTREAMING Wrapper for one-way output (streaming) simulations.
3+
%Reads default parameters from a YAML file, but any key can be overridden:
4+
%
5+
% w = SimulationWrapperStreaming(); % defaults
6+
% w = SimulationWrapperStreaming("config/dev.yaml"); % custom file
7+
% w = SimulationWrapperStreaming([], "tcp.output_port", 6000);
8+
29
properties (Access = private)
3-
tcp_client % TCP client object for communication with Python
4-
inputs % Store the inputs received from Python
10+
tcp_client % tcpclient for bidirectional stream
11+
inputs % first JSON payload from Python
12+
cfg % configuration struct
513
end
6-
14+
15+
%% ─────────────────────────────────────────────────────────────────────
716
methods
8-
% Constructor for the SimulationWrapperStreaming class
9-
function obj = SimulationWrapperStreaming()
10-
% Default port (modifiable)
11-
port = 5678;
17+
function obj = SimulationWrapperStreaming(cfgFile, varargin)
18+
% Constructor: load config, open socket, read first payload
19+
if nargin == 0 || isempty(cfgFile)
20+
cfgFile = fullfile(fileparts(mfilename("fullpath")), ...
21+
"..", "config", "default.yaml");
22+
end
23+
obj.cfg = obj.loadConfig(cfgFile, varargin{:});
1224

13-
% Max retries for connecting to the server
14-
max_retries = 5;
15-
retry_delay = 1; % Delay between retries in seconds
25+
host = obj.cfg.tcp.host;
26+
port = obj.cfg.tcp.output_port;
27+
maxRetries = obj.cfg.tcp.max_retries;
28+
retryDelay = obj.cfg.tcp.retry_delay;
1629

17-
% Try to connect to the server up to 'max_retries' times
18-
for retry = 1:max_retries
30+
for r = 1:maxRetries
1931
try
20-
% Create a TCP client object to connect to Python server
21-
obj.tcp_client = tcpclient('localhost', port);
22-
% Configure the TCP client to use LF as a terminator
32+
obj.tcp_client = tcpclient(host, port);
2333
configureTerminator(obj.tcp_client, "LF");
24-
break; % Exit the loop if the connection is successful
34+
break; % success
2535
catch ME
26-
% If connection fails, retry up to 'max_retries' times
27-
if retry == max_retries
28-
% If max retries reached, rethrow the exception
29-
rethrow(ME);
36+
if r == maxRetries
37+
rethrow(ME); % give up
3038
end
31-
% Wait before retrying
32-
pause(retry_delay);
39+
pause(retryDelay);
3340
end
3441
end
3542

36-
% Receive the initial parameters in JSON format from Python
37-
data = readline(obj.tcp_client);
38-
% Decode the received JSON data and store it as 'inputs'
39-
obj.inputs = jsondecode(data);
43+
firstLine = readline(obj.tcp_client);
44+
obj.inputs = jsondecode(firstLine);
4045
end
41-
42-
% Method to retrieve the input parameters from the Python server
46+
4347
function inputs = get_inputs(obj)
44-
inputs = obj.inputs; % Return the stored inputs
48+
inputs = obj.inputs;
4549
end
46-
47-
% Method to send output data to the Python server
50+
4851
function send_output(obj, output_data)
49-
% Convert the output data to JSON format
50-
json_data = jsonencode(output_data);
51-
% Send the JSON-encoded data to Python server
52-
writeline(obj.tcp_client, json_data);
52+
writeline(obj.tcp_client, jsonencode(output_data));
5353
end
54-
55-
% Destructor to clean up the TCP client object when the wrapper is deleted
54+
5655
function delete(obj)
57-
% Close the TCP connection by deleting the client object
58-
delete(obj.tcp_client);
56+
if ~isempty(obj.tcp_client) && isvalid(obj.tcp_client)
57+
delete(obj.tcp_client);
58+
end
59+
end
60+
end
61+
62+
%% ─────────────────────────────────────────────────────────────────────
63+
methods (Access = private)
64+
function cfg = loadConfig(~, cfgFile, varargin)
65+
cfg = yamlread(cfgFile); % struct
66+
for k = 1:2:numel(varargin) % dotted-key overrides
67+
path = split(varargin{k}, '.');
68+
val = varargin{k+1};
69+
cfg = SimulationWrapperStreaming.setDeep(cfg, path, val);
70+
end
71+
end
72+
end
73+
74+
methods (Static, Access = private)
75+
function s = setDeep(s, path, val) % recursive helper
76+
if numel(path) == 1
77+
s.(path{1}) = val;
78+
else
79+
f = path{1};
80+
if ~isfield(s, f) || ~isstruct(s.(f))
81+
s.(f) = struct();
82+
end
83+
s.(f) = SimulationWrapperStreaming.setDeep(s.(f), path(2:end), val);
84+
end
5985
end
6086
end
6187
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
tcp:
2+
host: "localhost"
3+
# ——— Incoming stream (Python → MATLAB) ———
4+
input_port: 5679
5+
6+
# ——— Outgoing stream (MATLAB → Python) ———
7+
output_port: 5678
8+
9+
# Connection / retry policy
10+
max_retries: 5 # attempts before giving up
11+
retry_delay: 1 # seconds between retries
12+
timeout_limit: 2 # seconds without data before warning

agents/matlab/matlab_agent/src/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ def generate_default_project():
111111
'client/simulation.yaml': ('matlab_agent.api',
112112
'simulation.yaml.template'),
113113
'client/README.md': (MATLAB_AGENT_RESOURCES, 'README.md'),
114+
'client/config/default.yaml': (MATLAB_AGENT_RESOURCES,
115+
'config/default.yaml.template'),
114116
}
115117

116118
# Descriptions for each file
@@ -127,6 +129,8 @@ def generate_default_project():
127129
'client/simulation.yaml':
128130
"Example API payload to communicate with the MATLAB agent",
129131
'client/README.md': "README file for the client directory",
132+
'client/config/default.yaml':
133+
"Default configuration file for the MATLAB agent client (Streaming & Interactive)",
130134
}
131135

132136
try:

0 commit comments

Comments
 (0)