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