1
-
2
1
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
+
3
12
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
7
17
end
8
-
18
+
9
19
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 ;
19
35
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
22
38
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 );
26
41
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
29
44
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
34
47
end
35
- % Wait before retrying
36
- pause(retry_delay );
48
+ pause(retryDelay );
37
49
end
38
50
end
39
51
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 );
44
55
end
45
56
46
- % Method to retrieve input parameters from the Python server
57
+ %% ----------------------------------------------------------------
47
58
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 ;
54
62
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 ;
61
68
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();
64
71
end
65
72
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 );
68
93
end
69
- inputs = obj .last_inputs ; % Return the stored inputs
70
94
end
95
+ end % methods
71
96
97
+ methods (Access = private )
98
+ %% ----------------------------------------------------------------
72
99
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
74
101
data_struct = [];
75
102
while obj .in_client .NumBytesAvailable > 0
76
103
line = readline(obj .in_client );
79
106
try
80
107
data_struct = jsondecode(line );
81
108
catch
82
- warning(" JSON decode failed" );
109
+ warning(" JSON decode failed, skipping line. " );
83
110
end
84
111
end
85
112
end
86
113
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
93
126
end
127
+ end % private methods
94
128
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
100
143
end
101
144
end
102
- end
145
+ end
0 commit comments