parent
							
								
									9e72263af3
								
							
						
					
					
						commit
						4e6162aa88
					
				| @ -0,0 +1,350 @@ | ||||
| import gex | ||||
| from gex import TF, TF_Msg | ||||
| from gex.Client import EventReport | ||||
| 
 | ||||
| CMD_READ_RAW = 0 | ||||
| CMD_READ_SMOOTHED = 1 | ||||
| CMD_GET_ENABLED_CHANNELS = 10 | ||||
| CMD_SETUP_TRIGGER = 20 | ||||
| CMD_ARM = 21 | ||||
| CMD_DISARM = 22 | ||||
| CMD_ABORT = 23 | ||||
| CMD_FORCE_TRIGGER = 24 | ||||
| CMD_BLOCK_CAPTURE = 25 | ||||
| CMD_STREAM_START = 26 | ||||
| CMD_STREAM_STOP = 27 | ||||
| CMD_SET_SMOOTHING_FACTOR = 28 | ||||
| 
 | ||||
| EVT_CAPT_START = 0 | ||||
| EVT_CAPT_MORE = 1 | ||||
| EVT_CAPT_DONE = 2 | ||||
| 
 | ||||
| EVT_STREAM_START = 10 | ||||
| EVT_STREAM_DATA = 11 | ||||
| EVT_STREAM_END = 12 | ||||
| 
 | ||||
| class TriggerReport: | ||||
|     def __init__(self, buf, edge, pretrig, timestamp): | ||||
|         self.buf = buf | ||||
|         self.edge = edge | ||||
|         self.pretrig = pretrig | ||||
|         self.timestamp = timestamp | ||||
| 
 | ||||
| class ADC(gex.Unit): | ||||
|     """ | ||||
|     ADC device | ||||
|     """ | ||||
| 
 | ||||
|     def _type(self): | ||||
|         return 'ADC' | ||||
| 
 | ||||
|     def _init(self): | ||||
|         self._trig_buf = None | ||||
|         self._trig_edge = 0 # 1, 2, 3 | ||||
|         self._trig_pretrig_len = 0 | ||||
|         self._trig_next_id = 0 | ||||
|         self._trig_listener = None | ||||
|         self._trig_ts = 0 | ||||
| 
 | ||||
|         self._stream_next_id = 0 | ||||
|         self._stream_running = False | ||||
|         self._stream_listener = None | ||||
| 
 | ||||
|     def _on_trig_capt(self, msg:TF_Msg): | ||||
|         print("Capture") | ||||
|         pp = gex.PayloadParser(msg.data) | ||||
| 
 | ||||
|         if self._trig_buf is None: | ||||
|             raise Exception("Unexpected capture data frame") | ||||
| 
 | ||||
|         # All but the first trig capture frame are prefixed by a sequence number | ||||
|         if self._trig_next_id != 0: | ||||
|             idx = pp.u8() | ||||
|             if idx != self._trig_next_id: | ||||
|                 raise Exception("Lost capture data frame! Expected %d, got %d" % (self._bcap_next_id, idx)) | ||||
|         self._trig_next_id = (self._trig_next_id + 1) % 256 | ||||
| 
 | ||||
|         self._trig_buf.extend(pp.tail()) | ||||
| 
 | ||||
|         if msg.type == EVT_CAPT_DONE: | ||||
|             if self._trig_listener is not None: | ||||
|                 self._trig_listener(TriggerReport(buf=self._trig_buf, | ||||
|                                                   edge=self._trig_edge, | ||||
|                                                   pretrig=self._trig_pretrig_len, | ||||
|                                                   timestamp=self._trig_ts)) | ||||
| 
 | ||||
|             self._trig_buf = None | ||||
|             # We keep the trig listener | ||||
|             return TF.CLOSE | ||||
|         else: | ||||
|             return TF.STAY | ||||
| 
 | ||||
|     def _on_stream_capt(self, msg:TF_Msg): | ||||
|         print("Stream data frame") | ||||
|         pp = gex.PayloadParser(msg.data) | ||||
| 
 | ||||
|         if not self._stream_running: | ||||
|             raise Exception("Unexpected stream data frame") | ||||
| 
 | ||||
|         if msg.type == EVT_STREAM_END: | ||||
|             if self._stream_listener is not None: | ||||
|                 self._stream_listener(None) # Indicate it's closed | ||||
| 
 | ||||
|             # We keep the stream listener, so user doesnt have to set it before each stream | ||||
|             self._stream_running = False | ||||
|             return TF.CLOSE | ||||
|         else: | ||||
|             # All stream data frames are prefixed by a sequence number | ||||
|             idx = pp.u8() | ||||
|             if idx != self._stream_next_id: | ||||
|                 self._stream_running = False | ||||
|                 raise Exception("Lost stream data frame! Expected %d, got %d" % (self._bcap_next_id, idx)) | ||||
| 
 | ||||
|             self._stream_next_id = (self._stream_next_id + 1) % 256 | ||||
| 
 | ||||
|             tail = pp.tail() | ||||
| 
 | ||||
|             if self._stream_listener is not None: | ||||
|                 self._stream_listener(tail) | ||||
| 
 | ||||
|             return TF.STAY | ||||
| 
 | ||||
|     def _on_event(self, evt:EventReport): | ||||
|         """ | ||||
|         Handle a trigger or stream start event. | ||||
| 
 | ||||
|         - EVT_CAPT_START | ||||
|           First frame payload: edge:u8, pretrig_len:u16, payload:tail | ||||
| 
 | ||||
|           Following are plain TF frames with the same ID, each prefixed with a sequence number in 1 byte. | ||||
|           Type EVT_CAPT_MORE or EVT_CAPT_DONE indicate whether this is the last frame of the sequence, | ||||
|           after which the ID listener should be removed. | ||||
| 
 | ||||
|         - EVT_STREAM_START | ||||
|           regular GEX event format, payload is the first data block, INCLUDING a numeric prefix (0) - 1 byte | ||||
|           Following frames are plain TF frames with type EVT_STREAM_DATA or EVT_STREAM_END, also including the incrementing prefix. | ||||
| 
 | ||||
|         """ | ||||
|         print("ADC event %d" % evt.code) | ||||
| 
 | ||||
|         pp = gex.PayloadParser(evt.payload) | ||||
|         msg = evt.msg | ||||
| 
 | ||||
|         if evt.code == EVT_CAPT_START: | ||||
|             if self._trig_buf is not None: | ||||
|                 raise Exception("Unexpected start of capture") | ||||
| 
 | ||||
|             self._trig_ts = evt.timestamp | ||||
|             self._trig_buf = bytearray() | ||||
|             self._trig_edge = pp.u8() | ||||
|             self._trig_pretrig_len = pp.u16() | ||||
|             self._trig_next_id = 0 | ||||
|             msg.data = pp.tail() | ||||
|             self._on_trig_capt(msg) | ||||
|             self.client.tf.add_id_listener(msg.id, lambda tf,msg: self._on_trig_capt(msg)) | ||||
| 
 | ||||
|     def get_channels(self): | ||||
|         """ | ||||
|         Find enabled channel numbers. | ||||
|         Returns a list. | ||||
|         """ | ||||
|         msg = self._query(CMD_GET_ENABLED_CHANNELS) | ||||
|         return list(msg.data) | ||||
| 
 | ||||
|     def set_smoothing_factor(self, fac, confirm=True): | ||||
|         """ Set smoothing factor for read_smooth(), range 0-1.0 """ | ||||
|         pb = gex.PayloadBuilder() | ||||
|         pb.u16(round(fac*1000)) | ||||
|         self._send(CMD_SET_SMOOTHING_FACTOR, pld=pb.close(), confirm=confirm) | ||||
| 
 | ||||
|     def read_raw(self): | ||||
|         """ Read raw values. Returns a dict. """ | ||||
|         msg = self._query(CMD_READ_RAW) | ||||
|         pp = gex.PayloadParser(msg) | ||||
|         chs = dict() | ||||
|         while pp.length() > 0: | ||||
|             idx = pp.u8() | ||||
|             chs[idx] = pp.u16() | ||||
|         return chs | ||||
| 
 | ||||
|     def read_smooth(self): | ||||
|         """ Read smoothed values (floats). Returns a dict. """ | ||||
|         msg = self._query(CMD_READ_SMOOTHED) | ||||
|         pp = gex.PayloadParser(msg) | ||||
|         chs = dict() | ||||
|         while pp.length() > 0: | ||||
|             idx = pp.u8() | ||||
|             chs[idx] = pp.float() | ||||
|         return chs | ||||
| 
 | ||||
|     def on_trigger(self, lst): | ||||
|         """ Set the trigger handler """ | ||||
|         self._trig_listener = lst | ||||
| 
 | ||||
|     def off_trigger(self): | ||||
|         """ Remove the trigger handler """ | ||||
|         self.on_trigger(None) | ||||
| 
 | ||||
|     def setup_trigger(self, channel, level, count, edge='rising', pretrigger=0, holdoff=100, auto=False, confirm=True, handler=None): | ||||
|         """ | ||||
|         Configure a trigger. | ||||
| 
 | ||||
|         channel - 0-17 (16-tsense, 17-vrefint) | ||||
|         level   - triggering threshold, raw (0-4095) | ||||
|         count   - nbr of samples to capture after trigger | ||||
|         edge    - "rising", "falling" or "both" | ||||
|         pretrigger - nbr of samples to capture before the trigger occurred. Limited by the internal buffer. | ||||
|         holdoff - hold-off time (trigger also can't fire while the capture is ongoing, and if it's not armed) | ||||
|         auto    - auto re-arm after completing the capture. Normally the state switches to IDLE. | ||||
|         handler - attaches a callback handler for the received data | ||||
|         """ | ||||
| 
 | ||||
|         nedge = 0 | ||||
|         if edge == 'rising': | ||||
|             nedge = 1 | ||||
|         elif edge == 'falling': | ||||
|             nedge = 2 | ||||
|         elif edge == 'both': | ||||
|             nedge = 3 | ||||
|         else: | ||||
|             raise Exception("Bad edge arg") | ||||
| 
 | ||||
|         pb = gex.PayloadBuilder() | ||||
|         pb.u8(channel) | ||||
|         pb.u16(level) | ||||
|         pb.u8(nedge) | ||||
|         pb.u16(pretrigger) | ||||
|         pb.u32(count) | ||||
|         pb.u16(holdoff) | ||||
|         pb.bool(auto) | ||||
| 
 | ||||
|         self._send(cmd=CMD_SETUP_TRIGGER, pld=pb.close(), confirm=confirm) | ||||
| 
 | ||||
|         if handler is not None: | ||||
|             self._trig_listener = handler | ||||
| 
 | ||||
|     def arm(self, auto=None, confirm=True): | ||||
|         """ | ||||
|         ARM for trigger. | ||||
|         The trigger must be configured first. | ||||
| 
 | ||||
|         if auto is True or False, it sets the auto-rearm flag. | ||||
|         """ | ||||
| 
 | ||||
|         pb = gex.PayloadBuilder() | ||||
| 
 | ||||
|         if auto is None: | ||||
|             pb.u8(255) | ||||
|         else: | ||||
|             pb.u8(1 if auto else 0) | ||||
| 
 | ||||
|         self._send(cmd=CMD_ARM, pld=pb.close(), confirm=confirm) | ||||
| 
 | ||||
|     def disarm(self, confirm=True): | ||||
|         """ | ||||
|         DISARM. | ||||
|         No effect if not armed. | ||||
|         Always clears the auto-arm flag. | ||||
|         """ | ||||
|         self._send(cmd=CMD_DISARM, confirm=confirm) | ||||
| 
 | ||||
|     def abort(self, confirm=True): | ||||
|         """ | ||||
|         Abort any ongoing capture and dis-arm. | ||||
|         Also clears the auto-arm flag. | ||||
|         """ | ||||
|         self._send(cmd=CMD_ABORT, confirm=confirm) | ||||
| 
 | ||||
|     def force(self, handler=None, confirm=True): | ||||
|         """ | ||||
|         Force a trigger, including pre-trigger capture. | ||||
|         The device behavior is identical as if the trigger condition occurred naturally. | ||||
| 
 | ||||
|         The captured data is received asynchronously via an event. | ||||
|         """ | ||||
|         if handler is not None: | ||||
|             self.on_trigger(handler) | ||||
| 
 | ||||
|         self._send(cmd=CMD_FORCE_TRIGGER, confirm=confirm) | ||||
| 
 | ||||
|     def capture_in_progress(self): | ||||
|         return self._stream_running or self._trig_buf is not None | ||||
| 
 | ||||
|     def capture(self, count, timeout=5): | ||||
|         """ | ||||
|         Start a block capture. | ||||
|         This is similar to a forced trigger, but has custom size and doesn't include any pre-trigger. | ||||
| 
 | ||||
|         The captured data is received synchronously and returned. | ||||
|         """ | ||||
| 
 | ||||
|         if self.capture_in_progress(): | ||||
|             raise Exception("Another capture already in progress") | ||||
| 
 | ||||
|         pb = gex.PayloadBuilder() | ||||
|         pb.u32(count) | ||||
| 
 | ||||
|         buffer = bytearray() | ||||
|         self._bcap_next_id = 0 | ||||
|         self._bcap_done = False | ||||
|         self._stream_running = True # we use this flag to block any concurrent access | ||||
| 
 | ||||
|         def lst(frame): | ||||
|             pp = gex.PayloadParser(frame.data) | ||||
| 
 | ||||
|             index = pp.u8() | ||||
|             if index != self._bcap_next_id: | ||||
|                 self._bcap_done = True | ||||
|                 raise Exception("Lost capture data frame! Expected %d, got %d" % (self._bcap_next_id, index)) | ||||
|                 #return TF.CLOSE XXX | ||||
| 
 | ||||
|             self._bcap_next_id = (self._bcap_next_id + 1) % 256 | ||||
| 
 | ||||
|             buffer.extend(pp.tail()) | ||||
| 
 | ||||
|             if frame.type == EVT_CAPT_DONE: | ||||
|                 self._bcap_done = True | ||||
|                 return TF.CLOSE | ||||
|             return TF.STAY | ||||
| 
 | ||||
|         self._query_async(cmd=CMD_BLOCK_CAPTURE, pld=pb.close(), callback=lst) | ||||
| 
 | ||||
|         # wait with a timeout | ||||
|         self.client.transport.poll(timeout, lambda: self._bcap_done == True) | ||||
| 
 | ||||
|         self._stream_running = False | ||||
| 
 | ||||
|         if not self._bcap_done: | ||||
|             raise Exception("Capture not completed within timeout") | ||||
| 
 | ||||
|         return buffer | ||||
| 
 | ||||
|     def on_stream(self, lst): | ||||
|         self._stream_listener = lst | ||||
| 
 | ||||
|     def off_stream(self, lst): | ||||
|         self.on_stream(None) | ||||
| 
 | ||||
|     def stream_start(self, lst=None): | ||||
|         """ Start a capture stream """ | ||||
|         if self.capture_in_progress(): | ||||
|             raise Exception("Another capture already in progress") | ||||
| 
 | ||||
|         self._stream_next_id = 0 | ||||
|         self._stream_running = True | ||||
| 
 | ||||
|         if lst is not None: | ||||
|             self._stream_listener = lst | ||||
| 
 | ||||
|         def str_lst(tf, msg): | ||||
|             self._on_stream_capt(msg) | ||||
| 
 | ||||
|         self._query_async(cmd=CMD_STREAM_START, callback=str_lst) | ||||
| 
 | ||||
|     def stream_stop(self, lst, confirm=True): | ||||
|         """ Stop a stream """ | ||||
|         if not self._stream_running: | ||||
|             raise Exception("Not streaming") | ||||
| 
 | ||||
|         self._stream_listener = None | ||||
|         self._send(cmd=CMD_STREAM_STOP, confirm=confirm) | ||||
					Loading…
					
					
				
		Reference in new issue