@@ -94,17 +94,39 @@ def _get_sigrok_prefix(self):
9494 return self .sigrok .command_prefix + prefix
9595
9696 @Driver .check_active
97- @step (title = 'call ' , args = ['args' ])
97+ @step (title = 'call_with_driver ' , args = ['args' ])
9898 def _call_with_driver (self , * args ):
9999 combined = self ._get_sigrok_prefix () + list (args )
100100 self .logger .debug ("Combined command: %s" , " " .join (combined ))
101101 self ._process = subprocess .Popen (
102102 combined ,
103103 stdout = subprocess .PIPE ,
104104 stdin = subprocess .PIPE ,
105- stderr = subprocess .PIPE
105+ stderr = subprocess .PIPE ,
106+ text = True
106107 )
107108
109+ @Driver .check_active
110+ @step (title = 'call_with_driver_blocking' , args = ['args' ])
111+ def _call_with_driver_blocking (self , * args , log_output = False ):
112+ combined = self ._get_sigrok_prefix () + list (args )
113+ self .logger .debug ("Combined command: %s" , " " .join (combined ))
114+ process = subprocess .Popen (
115+ combined ,
116+ stdout = subprocess .PIPE ,
117+ stdin = subprocess .PIPE ,
118+ stderr = subprocess .PIPE ,
119+ text = True
120+ )
121+ stdout , stderr = process .communicate ()
122+ if log_output :
123+ self .logger .debug ("stdout: %s" , stdout )
124+ self .logger .debug ("stderr: %s" , stderr )
125+ if process .returncode != 0 :
126+ raise OSError
127+ return stdout , stderr
128+
129+
108130 @Driver .check_active
109131 @step (title = 'call' , args = ['args' ])
110132 def _call (self , * args ):
@@ -137,8 +159,22 @@ class SigrokDriver(SigrokCommon):
137159
138160 @Driver .check_active
139161 def capture (self , filename , samplerate = "200k" ):
162+ """
163+ Starts to capture samples, in the background.
164+
165+ Args:
166+ filename: the path to the file where the capture is saved.
167+ samplerate: the sample-rate of the capture
168+
169+ Raises:
170+ RuntimeError() if a capture is already running.
171+ """
172+ if self ._running :
173+ raise RuntimeError ("capture is already running" )
174+
140175 self ._filename = filename
141176 self ._basename = os .path .basename (self ._filename )
177+ self ._validate_save_dir_exists ()
142178 self .logger .debug (
143179 "Saving to: %s with basename: %s" , self ._filename , self ._basename
144180 )
@@ -149,6 +185,7 @@ def capture(self, filename, samplerate="200k"):
149185 filename = os .path .join (self ._tmpdir , self ._basename )
150186 cmd .append (filename )
151187 self ._call_with_driver (* cmd )
188+
152189 args = self .sigrok .command_prefix + ['test' , '-e' , filename ]
153190
154191 while subprocess .call (args ):
@@ -161,60 +198,207 @@ def capture(self, filename, samplerate="200k"):
161198 self .logger .debug ("sigrok-cli call terminated prematurely with non-zero return-code" )
162199 self .logger .debug ("stdout: %s" , stdout )
163200 self .logger .debug ("stderr: %s" , stderr )
164- raise ExecutionError (f"sigrok-cli call terminated prematurely with return-code '{ ret } '." )
201+ raise ExecutionError (f"sigrok-cli call terminated prematurely, return-code '{ ret } '." )
165202 sleep (0.1 )
166203
167204 self ._running = True
168205
206+ @Driver .check_active
207+ def capture_for_time (self , filename , time_ms , samplerate = "200k" ):
208+ """
209+ Captures samples for a specified time (ms).
210+
211+ Blocks while capturing.
212+
213+ Args:
214+ filename: the path to the file where the capture is saved.
215+ time: time (in ms) for capture duration
216+ samplerate: the sample-rate of the capture
217+
218+ Returns:
219+ The capture as a list containing dict's with fields "Time"
220+ and the channel names
221+
222+ Raises:
223+ OSError() if the subprocess returned with non-zero return-code
224+ """
225+ return self ._capture_blocking (filename , ["--time" , str (time_ms )], samplerate )
226+
227+ @Driver .check_active
228+ def capture_samples (self , filename , samples , samplerate = "200k" ):
229+ """
230+ Captures a specified number of samples.
231+
232+ Blocks while capturing.
233+
234+ Args:
235+ filename: the path to the file where the capture is saved.
236+ samples: number of samples to capture
237+ samplerate: the sample-rate of the capture
238+
239+ Returns:
240+ The capture as a list containing dict's with fields "Time"
241+ and the channel names
242+ """
243+ return self ._capture_blocking (filename , ["--samples" , str (samples )], samplerate )
244+
169245 @Driver .check_active
170246 def stop (self ):
171- assert self ._running
247+ """
248+ Stops the capture and returns recorded samples.
249+
250+ Note that this method might block for several seconds because it needs
251+ to wait for output, parse the capture file and prepare the list
252+ containing the samples.
253+
254+ Returns:
255+ The capture as a list containing dict's with fields "Time"
256+ and the channel names
257+
258+ Raises:
259+ RuntimeError() if capture has not been started
260+ """
261+ if not self ._running :
262+ raise RuntimeError ("no capture started yet" )
172263 self ._running = False
173- fnames = ['time' ]
174- fnames .extend (self .sigrok .channels .split (',' ))
264+
175265 csv_filename = f'{ os .path .splitext (self ._basename )[0 ]} .csv'
176266
177267 # sigrok-cli can be quit through any keypress
178268 stdout , stderr = self ._process .communicate (input = b"q" )
179269 self .logger .debug ("stdout: %s" , stdout )
180270 self .logger .debug ("stderr: %s" , stderr )
181271
182- # Convert from .sr to .csv
272+ self ._convert_sr_csv (os .path .join (self ._tmpdir , self ._basename ),
273+ os .path .join (self ._tmpdir , csv_filename ))
274+ self ._transfer_tmp_file (csv_filename )
275+ return self ._process_csv (csv_filename )
276+
277+ def _capture_blocking (self , filename , capture_args , samplerate ):
278+ self ._filename = filename
279+ self ._basename = os .path .basename (self ._filename )
280+ self ._validate_save_dir_exists ()
281+ csv_filename = f'{ os .path .splitext (self ._basename )[0 ]} .csv'
282+ self .logger .debug (
283+ "Saving to: %s with basename: %s" , self ._filename , self ._basename
284+ )
285+ cmd = [
286+ "-l" , "4" , "--config" , f"samplerate={ samplerate } " ,
287+ * capture_args , "-o"
288+ ]
289+ filename = os .path .join (self ._tmpdir , self ._basename )
290+ cmd .append (filename )
291+ self ._call_with_driver_blocking (* cmd , log_output = True )
292+
293+ args = self .sigrok .command_prefix + ['test' , '-e' , filename ]
294+
295+ while subprocess .call (args ):
296+ sleep (0.1 )
297+
298+ self ._convert_sr_csv (os .path .join (self ._tmpdir , self ._basename ),
299+ os .path .join (self ._tmpdir , csv_filename ))
300+ self ._transfer_tmp_file (csv_filename )
301+ return self ._process_csv (csv_filename )
302+
303+ def _convert_sr_csv (self , file_path_sr , file_path_scv ):
183304 cmd = [
184305 '-i' ,
185- os . path . join ( self . _tmpdir , self . _basename ) , '-O' , 'csv:time=true' , '-o' ,
186- os . path . join ( self . _tmpdir , csv_filename )
306+ file_path_sr , '-O' , 'csv:time=true' , '-o' ,
307+ file_path_scv
187308 ]
188309 self ._call (* cmd )
189310 stdout , stderr = self ._process .communicate ()
190311 self .logger .debug ("stdout: %s" , stdout )
191312 self .logger .debug ("stderr: %s" , stderr )
313+
314+ def _validate_save_dir_exists (self ):
315+ if not os .path .exists (os .path .dirname (self ._filename )):
316+ raise ExecutionError (
317+ f"Won't be able to save capture file to target directory '{ os .path .dirname (self ._filename )} ', does not exist"
318+ )
319+
320+ def _transfer_tmp_file (self , csv_filename ):
192321 if isinstance (self .sigrok , NetworkSigrokUSBDevice ):
193- subprocess .call ([
194- 'scp' , f'{ self .sigrok .host } :{ os .path .join (self ._tmpdir , self ._basename )} ' ,
195- os .path .abspath (self ._filename )
196- ],
197- stdin = subprocess .DEVNULL ,
198- stdout = subprocess .DEVNULL ,
199- stderr = subprocess .DEVNULL )
322+ sr_remote_path = os .path .join (self ._tmpdir , self ._basename )
323+ sr_local_path = os .path .abspath (self ._filename )
324+ csv_remote_path = os .path .join (self ._tmpdir , csv_filename )
325+ csv_local_path = os .path .join (self ._local_tmpdir , csv_filename )
326+ self .logger .debug (
327+ "Transferring sigrok capture file from remote path '%s' to local path '%s'" ,
328+ sr_remote_path ,
329+ sr_local_path
330+ )
331+ # TODO: use NetworkResource ssh base options here too
332+ ret = subprocess .run ([
333+ 'scp' ,
334+ "-o" ,
335+ "UserKnownHostsFile=/dev/null" ,
336+ "-o" ,
337+ "StrictHostKeyChecking=no" ,
338+ f'{ self .sigrok .host } :{ sr_remote_path } ' ,
339+ sr_local_path ,
340+ ],
341+ stdin = subprocess .DEVNULL ,
342+ stdout = subprocess .PIPE ,
343+ stderr = subprocess .STDOUT
344+ )
345+ if ret .returncode != 0 :
346+ self .logger .error (
347+ "Transferring Sigrok capture file failed, return-code '%i : %s'" ,
348+ ret .returncode ,
349+ ret .stdout .decode ()
350+ )
351+ raise ExecutionError (
352+ f"Transferring Sigrok Capture file failed, return-code '{ ret .returncode } '" ,
353+ stdout = ret .stdout .decode ().split ("\n " )
354+ )
355+
200356 # get csv from remote host
201- subprocess .call ([
202- 'scp' , f'{ self .sigrok .host } :{ os .path .join (self ._tmpdir , csv_filename )} ' ,
203- os .path .join (self ._local_tmpdir , csv_filename )
204- ],
205- stdin = subprocess .DEVNULL ,
206- stdout = subprocess .DEVNULL ,
207- stderr = subprocess .DEVNULL )
357+ self .logger .debug (
358+ "Transferring CSV file from remote path '%s' to local path '%s'" ,
359+ csv_remote_path ,
360+ csv_local_path
361+ )
362+ # TODO: use NetworkResource ssh base options here too
363+ ret = subprocess .run ([
364+ 'scp' ,
365+ "-o" ,
366+ "UserKnownHostsFile=/dev/null" ,
367+ "-o" ,
368+ "StrictHostKeyChecking=no" ,
369+ f'{ self .sigrok .host } :{ csv_remote_path } ' ,
370+ csv_local_path
371+ ],
372+ stdin = subprocess .DEVNULL ,
373+ stdout = subprocess .PIPE ,
374+ stderr = subprocess .STDOUT )
375+ if ret .returncode != 0 :
376+ self .logger .error (
377+ "Transferring Sigrok CSV file failed, return-code '%i : %s'" ,
378+ ret .returncode ,
379+ ret .stdout .decode ()
380+ )
381+ raise ExecutionError (
382+ f"Transferring Sigrok CSV file failed, return-code '{ ret .returncode } '" ,
383+ stdout = ret .stdout .decode ().split ("\n " )
384+ )
385+ else :
386+ shutil .copyfile (
387+ os .path .join (self ._tmpdir , self ._basename ), self ._filename
388+ )
389+
390+ def _process_csv (self , csv_filename ):
391+ fnames = ['time' ]
392+ fnames .extend (self .sigrok .channels .split (',' ))
393+
394+ if isinstance (self .sigrok , NetworkSigrokUSBDevice ):
208395 with open (os .path .join (self ._local_tmpdir ,
209396 csv_filename )) as csv_file :
210397 # skip first 5 lines of the csv output, contains metadata and fieldnames
211398 for _ in range (0 , 5 ):
212399 next (csv_file )
213400 return list (csv .DictReader (csv_file , fieldnames = fnames ))
214401 else :
215- shutil .copyfile (
216- os .path .join (self ._tmpdir , self ._basename ), self ._filename
217- )
218402 with open (os .path .join (self ._tmpdir , csv_filename )) as csv_file :
219403 # skip first 5 lines of the csv output, contains metadata and fieldnames
220404 for _ in range (0 , 5 ):
@@ -223,6 +407,15 @@ def stop(self):
223407
224408 @Driver .check_active
225409 def analyze (self , args , filename = None ):
410+ """
411+ Analyzes captured data through `sigrok-cli`'s command line interface
412+
413+ Args:
414+ args: args to `sigrok-cli`
415+
416+ Returns:
417+ A dictionary containing the matched groups.
418+ """
226419 annotation_regex = re .compile (r'(?P<startnum>\d+)-(?P<endnum>\d+) (?P<decoder>[\w\-]+): (?P<annotation>[\w\-]+): (?P<data>".*)' ) # pylint: disable=line-too-long
227420 if not filename and self ._filename :
228421 filename = os .path .join (self ._tmpdir , self ._basename )
@@ -412,12 +605,15 @@ def stop(self):
412605
413606 Raises:
414607 RuntimeError() if capture has not been started
608+ OSError() if the subprocess returned with non-zero return-code
415609 """
416610 if not self ._running :
417611 raise RuntimeError ("no capture started yet" )
418612 while not self ._timeout .expired :
419613 if self ._process .poll () is not None :
420614 # process has finished. no need to wait for the timeout
615+ if self ._process .returncode != 0 :
616+ raise OSError
421617 break
422618 time .sleep (0.1 )
423619 else :
0 commit comments