winIDEA SDK
data_recorder.py
1# This script is licensed under BSD License, see file LICENSE.txt.
2# (c) TASKING Germany GmbH, 2023
3#
4# This script demonstrates recording of watch expressions and variables
5# using slower but more flexible evaluator and fast batch access.
6# The recorded data is then written to CSV file. If the 'openpyxl'
7# module is installed, the data is also written to XLSX file.
8#
9# All the above functionality is grouped into functions, so you can
10# easily take out only part of the script. This script can also be
11# imported as a module to user's scripts, so it is easy to reuse
12# functions found here.
13#
14# See also data_recorder_with_daq.py
15
16import csv
17import time
18import isystem.connect as ic
19
20winidea_id = ''
21
22
23try:
24 import pylab as pl
25 isPyLabInstalled = True
26except ImportError as ex:
27 isPyLabInstalled = False
28
29
30def initTarget(cmgr):
31 """
32 This function initializes the target. Customize it according to
33 your needs.
34 """
35
36 debugCtrl = ic.CDebugFacade(cmgr)
37
38 debugCtrl.reset()
39 debugCtrl.runUntilFunction("main")
40 debugCtrl.waitUntilStopped()
41
42 debugCtrl.run()
43
44 return debugCtrl
45
46
47def recordWatchExpressions(debugCtrl,
48 watches,
49 recordingTimeInSeconds,
50 samplingInterval,
51 fileName,
52 isRecordToMemory,
53 isPrintToStdOut):
54 """
55 This function evaluates watch expressions and returns list of results. The
56 amount of data that can be recorded is limited by the amount of system
57 memory.
58
59 Parameters:
60
61 debugCtrl - iSYSTEM's debugCtrl controller, which provides access to target
62
63 watches - list of strings with watch expressions, for example
64 ['main_loop_counter,h', 'g_arrayInt[0]']
65
66 recordingTime - how long to record the expressions, in
67 seconds. Recording can also be terminated by
68 pressing the 'q' key
69
70 samplingInterval - defines how much time should pass between samples
71
72 isPrintToStdOut - if True, each row is printed to stdout during recording
73
74 Returns:
75 List of rows, where each row is a list containing time stamp and recorded
76 values in the same order as watch expressions were specified. Example for
77 three expressions:
78 [[0, 23, 45, 4.35],
79 [0.1, 24, -525, 1.78]
80 ]
81 """
82
83 startTime = time.time()
84 endTime = startTime + recordingTimeInSeconds
85
86 if isPrintToStdOut:
87 print(['Time'] + watches)
88
89 recordedData = []
90 sampleCounter = 0
91
92 while (time.time() < endTime):
93
94 currentTime = time.time() - startTime
95 row = [currentTime]
96
97 for expression in watches:
98
99 value = debugCtrl.evaluate(ic.IConnectDebug.fRealTime, expression)
100 # CValueType contains also other methods, if you want to get
101 # value as integer, float, ... See
102 # http://isystem.com/files/isystem.connect/api/classisys_1_1_c_value_type.html
103 row.append(value.getResult())
104
105 if isPrintToStdOut:
106 print(row)
107
108 if isRecordToMemory:
109 recordedData.append(row)
110
111 sampleCounter += 1
112 nextSamplingTime = startTime + samplingInterval * sampleCounter
113 sleepTime = nextSamplingTime - time.time()
114 if sleepTime > 0:
115 time.sleep(sleepTime)
116
117 return recordedData
118
119
120def recordWatchExpressionsToCSV(debugCtrl,
121 watches,
122 recordingTimeInSeconds,
123 samplingInterval,
124 fileName,
125 isRecordToMemory,
126 isPrintToStdOut):
127 """
128 This function evaluates watch expressions and writes them to CSV file.
129 If isRecordToMemory == False, the amount of data that can be recorded is
130 limited by the free disk size.
131
132 Parameters:
133
134 debugCtrl - iSYSTEM's debugCtrl controller, which provides access to target
135
136 watches - list of strings with watch expressions, for example
137 ['main_loop_counter,h', 'g_arrayInt[0]']
138
139 recordingTime - how long to record the expressions, in
140 seconds. Recording can also be terminated by
141 pressing the 'q' key
142
143 samplingInterval - defines how much time should pass between samples
144
145 fileName - name of the output CSV file.
146
147 isRecordToMemory - if True, then data is also stored into memory
148 array and returned as function return value. Be
149 aware of memory usage in this case. If false,
150 an empty list is returned.
151
152 isPrintToStdOut - if True, each row is printed to stdout during recording
153
154 Returns:
155 List of rows, where each row is a list containing time stamp and recorded
156 values in the same order as watch expressions were specified. Example for
157 three expressions:
158 [[0, 23, 45, 4.35],
159 [0.1, 24, -525, 1.78]
160 ]
161
162 If isRecordToMemory == False, an empty list is returned.
163 """
164
165 startTime = time.time()
166 endTime = startTime + recordingTimeInSeconds
167
168 recordedData = []
169
170 with open(fileName, 'w') as csvFile:
171
172 # add parameter dialect = 'excel' for Excel specific output
173 # See also http://docs.python.org/library/csv.html
174 # for configuring CSV output
175 csvWriter = csv.writer(csvFile)
176 # write header
177 header = ['Time'] + watches
178 print(header)
179 csvWriter.writerow(header)
180 sampleCounter = 0
181
182 while (time.time() < endTime):
183
184 currentTime = time.time() - startTime
185 row = [currentTime]
186
187 for expression in watches:
188
189 print('expression: ', expression)
190 value = debugCtrl.evaluate(ic.IConnectDebug.fRealTime, expression)
191 # CValueType contains also other methods, if you want to get
192 # value as integer, float, ... See
193 # http://isystem.com/files/isystem.connect/api/classisys_1_1_c_value_type.html
194 row.append(value.getResult())
195
196 csvWriter.writerow(row)
197
198 if isPrintToStdOut:
199 print(row)
200
201 if isRecordToMemory:
202 recordedData.append(row)
203
204 sampleCounter += 1
205 nextSamplingTime = startTime + samplingInterval * sampleCounter
206 sleepTime = nextSamplingTime - time.time()
207 if sleepTime > 0:
208 time.sleep(sleepTime)
209
210 return recordedData
211
212
213def recordBatch(cmgr,
214 dbgCtrl,
215 variables,
216 recordingTimeInSeconds,
217 samplingIntervalInSeconds):
218 """
219 This function runs batch access and returns recorded data as a list of
220 SBatchAccessItemResult structures.
221 The amount of data that can be recorded is limited by the amount of system
222 memory.
223
224 Parameters:
225
226 cmgr - iSYSTEM's connection manager, which provides connection to target
227 debugCtrl - iSYSTEM's debugCtrl controller, which provides access to target
228
229 variables - list of strings with variables, for example
230 ['main_loop_counter', 'g_baseStruct.i_base']
231
232 recordingTime - how long to record the expressions, in
233 seconds.
234
235 samplingInterval - defines how much time should pass between samples
236
237 Returns:
238 A list of SBatchAccessItemResult structures, one structure per variable
239 per time stamp.
240 """
241
242 numItems = len(variables)
243 numRuns = int(recordingTimeInSeconds / samplingIntervalInSeconds)
244
245 # Set parameters for batch access. If your target does not
246 # support real time access, use 'flStopResume' instead of
247 # 'flRealTime'.
248 header = ic.SBatchAccessHeader()
249 header.m_dwFlags = (ic.SBatchAccessHeader.flRealTime |
250 ic.SBatchAccessHeader.flWantTimeStamp)
251 header.m_dwNumItems = numItems
252 header.m_dwNumRuns = numRuns
253 header.m_qwStartAtTime = 0 # immediate start
254 header.m_qwRunInterval = int(samplingIntervalInSeconds * 1000000)
255
256 ba_items = ic.VectorBatchAccessItem()
257 itemSizes = []
258
259 for var in variables:
260 item = ic.SBatchAccessItem()
261
262 varInfo = dbgCtrl.getSymbolInfo(ic.IConnectDebug.fRealTime, var)
263
264 # Define item(s) to be accessed
265 item.m_byFlags = ic.SBatchAccessItem.flRead # it's a read
266 item.m_bySize = varInfo.getSizeMAUs() # var size
267 item.m_byMemArea = varInfo.getMemArea() # var memory space
268 item.m_aAddress = varInfo.getAddress() # var address
269 ba_items.push_back(item)
270 itemSizes.append(varInfo.getSizeMAUs())
271
272
273 ba_results = ic.VectorBatchAccessResult(numItems * numRuns)
274 dataCtrl = ic.CDataController(cmgr)
275
276 # Finally we record the data
277 dataCtrl.batchAccess(0, header, ba_items, ba_results)
278
279 return ba_results, itemSizes
280
281
282def bigEndian2Int(numBytes, cArray):
283 """ Converts big-endian seq. of bytes to integer. """
284
285 value = 0
286 for i in range(numBytes):
287 value <<= 8
288 value |= ic.CDataController.getByte(cArray, i)
289
290 return value
291
292
293def batchAccessResultToCSV(fileName, ba_results, itemSizes, variables):
294 """
295 Writes results of batchAccess() to CSV file. Uses big endian conversion.
296
297 Parameters:
298 fileName - then name of the output file
299 ba_results - results returned by function recordBatch()
300 itemSizes - results returned by function recordBatch()
301 """
302
303 numItems = len(itemSizes)
304 numRuns = int(ba_results.size() / numItems)
305
306 firstAccess = ba_results[0].m_qwTimeStamp
307
308 with open(fileName, 'w') as csvFile:
309
310 csvWriter = csv.writer(csvFile)
311
312 # write header
313 csvWriter.writerow(['Time'] + variables)
314
315 for runIdx in range(numRuns):
316
317 row = [(ba_results[runIdx * numItems].m_qwTimeStamp - firstAccess)/1000000.]
318
319 for varIdx in range(numItems):
320 result = ba_results[runIdx * numItems + varIdx]
321
322 if (result.m_byResult == ic.SBatchAccessItemResult.resOK):
323 value = bigEndian2Int(itemSizes[varIdx], result.m_abyData)
324 row.append(value)
325
326 elif (result.m_byResult == ic.SBatchAccessItemResult.resAccess):
327 row.append('Can not access. Check access flags in header!')
328 elif (result.m_byResult == ic.SBatchAccessItemResult.resTimeout):
329 row.append('Timeout! Probably the sampling rate is invalid!')
330 else:
331 row.append('Invalid error code: ' + str(result.m_byResult))
332
333 csvWriter.writerow(row)
334
335
336def batchAccessResultToList(ba_results, itemSizes):
337 """
338 Converts results of batchAccess() to Python list. Uses big endian conversion.
339
340 Parameters:
341 fileName - then name of the output file
342 ba_results - results returned by function recordBatch()
343 itemSizes - results returned by function recordBatch()
344 """
345
346 numItems = len(itemSizes)
347 numRuns = int(ba_results.size() / numItems)
348
349 firstAccess = ba_results[0].m_qwTimeStamp
350 recordedData = []
351
352 for runIdx in range(numRuns):
353
354 row = [(ba_results[runIdx * numItems].m_qwTimeStamp - firstAccess)/1000000.]
355
356 for varIdx in range(numItems):
357 result = ba_results[runIdx * numItems + varIdx]
358
359 if (result.m_byResult == ic.SBatchAccessItemResult.resOK):
360 value = bigEndian2Int(itemSizes[varIdx], result.m_abyData)
361 row.append(value)
362
363 elif (result.m_byResult == ic.SBatchAccessItemResult.resAccess):
364 row.append('Can not access. Check access flags in header!')
365 elif (result.m_byResult == ic.SBatchAccessItemResult.resTimeout):
366 row.append('Timeout! Probably the sampling rate is invalid!')
367 else:
368 row.append('Invalid error code: ' + str(result.m_byResult))
369
370 recordedData.append(row)
371
372 return recordedData
373
374
375def writeDatatoCSV(fileName, data, expressions):
376 with open(fileName, 'w', neline='') as csvFile:
377 csvWriter = csv.writer(csvFile)
378 # write header
379 csvWriter.writerow(['Time'] + expressions)
380
381 for row in data:
382 csvWriter.writerow(row)
383
384
385def main():
386 cmgr = ic.ConnectionMgr()
387 cmgr.connect(ic.CConnectionConfig().instanceId(winidea_id))
388
389 debugCtrl = initTarget(cmgr)
390 ideCtrl = ic.CIDEController(cmgr)
391
392 # required so that character codes are UTF-8 compliant
393 ideCtrl.setOption('/IDE/Debug.Symbols.Format.ANSI', True)
394
395 # watches can contain all expressions acceptable in winIDEA's Watch
396 # window. For example, to read input from IO module, one can
397 # evaluate expression: `DigitalIn.DIN0
398 # Modifiers 'd' and 'h' define hex or decimal values
399 watches = ['main_loop_counter,h', 'g_intArray1[0],d', 'g_complexStruct.m_struct.i_base,d']
400 recordingTimeInSeconds = 5
401 samplingIntervalInSeconds = 0.2
402 filePrefix = 'watches'
403 isRecordToMemory = True
404 isPrintToStdOut = True
405 print('Recording watch expressions:')
406 watchResults = recordWatchExpressionsToCSV(debugCtrl,
407 watches,
408 recordingTimeInSeconds,
409 samplingIntervalInSeconds,
410 filePrefix + '.csv',
411 isRecordToMemory,
412 isPrintToStdOut)
413
414 filePrefix = 'batch'
415 # Variables allocated in memory can be specified here
416 variables = ['main_loop_counter', 'g_intArray1[0]', 'g_complexStruct.m_struct.i_base']
417 # start from 0
418 debugCtrl.modify(ic.IConnectDebug.fRealTime, 'main_loop_counter', '0')
419 samplingIntervalInSeconds = 0.5
420
421 print('Recording with batch access...')
422 batchResults, itemSizes = recordBatch(cmgr,
423 debugCtrl,
424 variables,
425 recordingTimeInSeconds,
426 samplingIntervalInSeconds)
427
428 batchAccessResultToCSV(filePrefix + '.csv', batchResults,
429 itemSizes, variables)
430
431 batchData = batchAccessResultToList(batchResults, itemSizes)
432
433 if isPyLabInstalled:
434 data = pl.array(batchData)
435 times = data[:, 0] # the first column is time
436 pl.subplot(3, 1, 1)
437 pl.plot(times, data[:, 1])
438 pl.ylabel('main_loop_counter')
439
440 pl.subplot(3, 1, 2)
441 pl.plot(times, data[:, 2], 'g')
442 pl.ylabel('g_intArray1[0]')
443
444 pl.subplot(3, 1, 3)
445 pl.plot(times, data[:, 3], 'r')
446 pl.ylabel('g_complexStruct.m_struct.i_base')
447 pl.xlabel('t[s]')
448
449 pl.show()
450
451 print('Done!')
452
453
454if __name__ == '__main__':
455 main()