-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathft_write_event.m
302 lines (263 loc) · 12 KB
/
ft_write_event.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
function ft_write_event(filename, event, varargin)
% FT_WRITE_EVENT writes an event structure to a file, a message daemon listening on a
% network socked, or to another computer connected through the serial port. Note that
% this function is mostly for real-time streaming of events. For most data files on
% disk the writing of events is done simultaneously with the header and data in
% FT_WRITE_DATA.
%
% Use as
% ft_write_event(filename, event, ...)
%
% The first argument is a string containing the filename. The second argument is a
% structure with the event. Multiple events can be represented as a structure array.
% Events are represented in the same format as those returned by FT_READ_EVENT.
% event.type = string
% event.sample = expressed in samples, the first sample of a recording is 1
% event.value = number or string
% event.offset = expressed in samples
% event.duration = expressed in samples
% event.timestamp = expressed in timestamp units, which vary over systems (optional)
%
% Additional options should be specified in key-value pairs and can be
% 'eventformat' = string, see below
% 'append' = boolean, not supported for all formats
%
% Events can be written to special communication streams by specifying the target as
% URI instead of a filename. Supported are
% buffer://<host>:<port>
% fifo://<filename>
% tcp://<host>:<port>
% udp://<host>:<port>
% mysql://<user>:<password>@<host>:<port>
% rfb://<password>@<host>:<port>
% serial:<port>?key1=value1&key2=value2&...
% rfb://<password>@<host>:<port>
%
% See also FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT, FT_WRITE_DATA
% Copyright (C) 2007-2020, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
% FieldTrip is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% FieldTrip is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$
global event_queue % for fcdc_global
global db_blob % for fcdc_mysql
if isempty(db_blob)
db_blob = 0;
end
if iscell(filename)
% use recursion to write to multiple event targets
for i=1:numel(filename)
ft_write_event(filename{i}, event, varargin);
end
return
end
% set the defaults
eventformat = ft_getopt(varargin, 'eventformat', ft_filetype(filename));
append = ft_getopt(varargin, 'append', 'yes');
maxqlength = ft_getopt(varargin, 'maxqlength', inf);
switch eventformat
case 'empty'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% just pretend that we are writing the events, this is only for debugging
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ft_info('Pretending to write %i events...\n', length(event));
case 'fcdc_global'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% store it in a global variable, this is only for debugging
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if isempty(event_queue) || ~isstruct(event_queue)
event_queue = event;
else
event_queue = appendstruct(event_queue, event);
end
case 'fcdc_rfb'
% remote frame buffer, i.e. VNC server on another computer
[password, host, port] = filetype_check_uri(filename);
rfbevent(sprintf('%s:%d', host, port), password, event.type, event.value);
case 'fcdc_buffer'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% write to a network transparent buffer for realtime analysis
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[host, port] = filetype_check_uri(filename);
if strcmp(append, 'no')
buffer('flush_evt', [], host, port); % flush the existing events
end
% the MEX file now can handle various MATLAB types directly and respects the fields
% sample, offset, duration
% -- these must all be numeric and non-empty (only first element is of interest)
% type, value
% -- these can be strings or any numeric type (double, single, [u]int[8-64])
% will be transmitted as if vectorised
% hack to fix empty event.offset and event.duration fields
% Maybe should track down why they're empty? But this works for now
% ES, 10-may-2019
if ~isempty(event)
for k = 1:numel(event)
if isfield(event(k), 'offset') && isempty(event(k).offset)
event(k).offset = 0;
end
if isfield(event(k), 'duration') && isempty(event(k).duration)
event(k).duration = 0;
end
end
end
buffer('put_evt', event, host, port); % append the new events
% SK: There was some code here for firing up a FieldTrip buffer locally,
% but this is very likely to be senseless because we have no proper header
% information here. Please explicitly use ft_create_buffer instead.
case 'fcdc_serial'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% write to a serial port
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
write_serial_event(filename, event);
case 'fcdc_mysql'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% write to a MySQL server listening somewhere else on the network
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% check that the required low-level toolbox is available
ft_hastoolbox('mysql', 1);
% write to a MySQL server listening somewhere else on the network
db_open(filename);
for i=1:length(event)
if db_blob
% insert the structure into the database table as a binary blob
db_insert_blob('fieldtrip.event', 'msg', event(i));
else
% make a structure with the same elements as the fields in the database table
s = struct;
% these fields also exist as elements in the table and as such can be used for filtering
if isa(event(i).type, 'char')
s.type = event(i).type;
end
if isa(event(i).value, 'numeric') && numel(event(i).value)==1
s.value = event(i).value;
end
if isa(event(i).sample, 'numeric') && numel(event(i).sample)==1
s.sample = event(i).sample;
end
if isa(event(i).sample, 'numeric') && numel(event(i).offset)==1
s.offset = event(i).offset;
end
if isa(event(i).sample, 'numeric') && numel(event(i).duration)==1
s.duration = event(i).duration;
end
% insert the structure into the database table
db_insert('fieldtrip.event', s);
end
end
case 'fcdc_fifo'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% write to a FIFO special file, which is a named pipe
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% these are opened in blocking mode, i.e. reading/writing will block until boths sides are connected
fifo = filetype_check_uri(filename);
if ~exist(fifo,'file')
ft_warning('the FIFO %s does not exist; attempting to create it', fifo);
system(sprintf('mkfifo -m 0666 %s',fifo));
end
fid = fopen_or_error(fifo, 'w');
for i=1:length(event)
try
% convert the event into a network message
msg = mxSerialize(event(i));
num = fwrite(fid, msg, 'uint8');
catch
ft_warning(lasterr);
end
if num~=length(msg)
ft_error('problem writing to FIFO %s', fifo);
end
end
fclose(fid);
case 'fcdc_tcp'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% serialize the events and write the message over a TCP network socket
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[host, port] = filetype_check_uri(filename);
con = pnet('tcpconnect', host, port);
pnet(con,'setwritetimeout',1);
if con~=-1
try % failsafe, don't give error when there is no connection
for i=1:length(event)
% convert the event into a network message
msg = mxSerialize(event(i));
% tell the message daemon that a message will be sent, and send it
pnet(con,'printf',num2str(msg));
pnet(con,'printf','\n');
end
catch
ft_warning(lasterr);
end % try
pnet(con, 'close');
end % if connected
case 'fcdc_udp'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% serialize the events and write the message over a UDP network socket
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[host, port] = filetype_check_uri(filename);
udp = pnet('udpsocket', port);
if udp~=-1
try % failsafe, don't give error when there is no connection
for i=1:length(event)
% convert the event into a network message
msg = mxSerialize(event(i));
% tell the message daemon that a message will be sent, and send it
pnet(udp,'write',uint8(msg),1000);
pnet(udp,'writepacket',host,port); % Send buffer as UDP packet to host
end
catch
ft_warning(lasterr);
end % try
pnet(udp,'close');
end % if connected
otherwise
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% write to file
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% since the file probably does not yet exist, determine its type by only looking at the extension
if filetype_check_extension(filename, '.mat')
% write the events to a MATLAB file
if exist(filename,'file') && strcmp(append, 'yes')
try
tmp = load(filename, 'event');
event = cat(1, tmp.event(:), event(:));
catch
event = event(:);
end
% optionally restric the length of the event queue to flush old events
if isfinite(maxqlength) && isreal(maxqlength) && (maxqlength>0) && (length(event)>maxqlength)
event = event(end-maxqlength+1:end);
% NOTE: this could be done using the filter event function, but
% then this is just a temporary solution that will probably be
% removed in a future versions of the code
end
save(filename, 'event', '-append', '-v6');
% NOTE: the -append option in this call to the save function does
% not actually do anything useful w.r.t. the event variable since the
% events are being appended in the code above and the the save function
% will just overwrite the existing event variable in the file.
% However, if there are other variables in the file (whatever) then the
% append option preservs them
else
save(filename, 'event', '-v6');
end
else
if ~isempty(evt)
ft_error('writing events to this fileformat is not supported here, please see FT_WRITE_DATA');
end
end % if file extension
end % switch eventformat