diff --git a/src/pycbsdk/cbhw/device/base.py b/src/pycbsdk/cbhw/device/base.py index e6783f4..3a78149 100644 --- a/src/pycbsdk/cbhw/device/base.py +++ b/src/pycbsdk/cbhw/device/base.py @@ -29,8 +29,10 @@ def __init__(self, params: Params): lambda: CBChannelType.Any ), # Filled in upon receiving device config. "proc_chans": 0, + "instrument": -1, "channel_infos": {}, "group_infos": {}, + "group_nchans": {}, "sysfreq": None, # Should be 30_000 for legacy or 1e9 for Gemini PTP } # Init group_callbacks dict with an empty list for each supported smp grp (1-5:SMP; 6:RAW) diff --git a/src/pycbsdk/cbhw/device/nsp.py b/src/pycbsdk/cbhw/device/nsp.py index f028a19..97b12f2 100644 --- a/src/pycbsdk/cbhw/device/nsp.py +++ b/src/pycbsdk/cbhw/device/nsp.py @@ -386,6 +386,7 @@ def _handle_sysrep(self, pkt): if b_general or pkt.header.type == CBPacketType.SYSREPRUNLEV: self._config["runlevel"] = CBRunLevel(pkt.runlevel) self._config["sysfreq"] = pkt.sysfreq + self._config["instrument"] = pkt.header.instrument self._config_events["sysrep"].set() if b_general or pkt.header.type == CBPacketType.SYSREPRUNLEV: if self._config["runlevel"] == CBRunLevel.STANDBY: @@ -396,7 +397,10 @@ def _handle_sysrep(self, pkt): def _handle_chaninfo(self, pkt): # If this config packet is limited in scope then it might have some garbage data in its out-of-scope payload. # We should update our config, but only the parts that this REP packet is scoped to. - if pkt.header.type in [CBPacketType.CHANREP]: + if pkt.header.instrument != self._config["instrument"]: + # Gemini system returns channel info for all instruments. + pass + elif pkt.header.type in [CBPacketType.CHANREP]: # Full scope; overwrite our config. self._config["channel_infos"][pkt.chan] = copy.copy(pkt) self._config["channel_types"][pkt.chan] = get_chantype_from_chaninfo(pkt) @@ -457,6 +461,7 @@ def _handle_groupinfo(self, pkt): else: chan_list = set() self._config["group_infos"][pkt.group] = chan_list + self._config["group_nchans"][pkt.group] = len(chan_list) def _handle_configall(self, pkt): if pkt.header.dlen > 0: @@ -1017,6 +1022,7 @@ def get_config( self._config["channel_infos"] = {} # Do not clear sysfreq if we already have it as this cannot change. self._config["sysfreq"] = self._config.get("sysfreq", None) + # self._config["instrument"] = -1 time.sleep(0.1) pkt = self.packet_factory.make_packet( None, diff --git a/src/pycbsdk/cbhw/handler.py b/src/pycbsdk/cbhw/handler.py index 9bc4fbd..63fddff 100644 --- a/src/pycbsdk/cbhw/handler.py +++ b/src/pycbsdk/cbhw/handler.py @@ -82,11 +82,13 @@ def run(self) -> None: b_debug_unknown = True # If there are no callbacks and it's not a group or event packet, then debug. # See if we have any callbacks registered for this type of packet. + b_grp = False if chid & CBSpecialChan.CONFIGURATION: callbacks = self._device.config_callbacks[pkt_type] elif chid == CBSpecialChan.GROUP: # This is a sample group packet. The pkt_type is actually the sample group id (1-6) if pkt_type in self._device.group_callbacks: + b_grp = True callbacks = self._device.group_callbacks[pkt_type] else: # Known bug https://blackrockengineering.atlassian.net/browse/CSCI-95 @@ -106,6 +108,12 @@ def run(self) -> None: pkt = self._packet_factory.make_packet( data, chid=chid, pkt_type=pkt_type, chantype=chantype ) + if b_grp: + # Note: pkt.data length is always a multiple of 4 bytes = 32 bits = 2 channels * 16 bits. + n_chans = self._device.config["group_nchans"][pkt_type] + if n_chans % 2 != 0: + # Odd number of channels enabled then we have an extra 16 bits of data. + pkt.data = pkt.data[:n_chans] # Between the time the callbacks are grabbed above and the time we actually call them, # it's possible for the client to unregister the callback. # Thus it's very important that a callback is unregistered and some time is allowed to pass diff --git a/src/pycbsdk/examples/group_sample_intervals.py b/src/pycbsdk/examples/group_sample_intervals.py index e4ed538..7019080 100644 --- a/src/pycbsdk/examples/group_sample_intervals.py +++ b/src/pycbsdk/examples/group_sample_intervals.py @@ -12,17 +12,18 @@ class DummyApp: - def __init__(self, duration=21.0, t_step=1 / 30_000): + def __init__(self, nchans: int, duration=21.0, t_step=1 / 30_000): n_samples = int(np.ceil(duration * 30_000)) + self._nchans = nchans self._t_step = t_step - self._buffer = np.zeros((n_samples, 2), dtype=np.int16) + self._buffer = np.zeros((n_samples, nchans), dtype=np.int16) self._ts = np.zeros((n_samples,), dtype=np.int64) self._write_index = 0 self._last_time = 0 def handle_frame(self, pkt): if self._write_index < self._buffer.shape[0]: - self._buffer[self._write_index, :] = memoryview(pkt.data[:4]) + self._buffer[self._write_index, :] = memoryview(pkt.data[:self._nchans]) self._ts[self._write_index] = pkt.header.time self._write_index += 1 @@ -41,6 +42,7 @@ def finish(self): def main( duration: float = 11.0, smpgroup: int = 6, + nchans: int = 2, inst_addr: str = "", inst_port: int = 51002, client_addr: str = "", @@ -81,11 +83,12 @@ def main( for chtype in [CBChannelType.FrontEnd, CBChannelType.AnalogIn]: cbsdk.set_all_channels_disable(nsp_obj, chtype) - # Enable channel 1 at smpgroup. For smpgroup < 5, this also updates the smpfilter. - _ = cbsdk.set_channel_config(nsp_obj, 1, "smpgroup", smpgroup) + # Enable channels 1 & 2 at smpgroup. For smpgroup < 5, this also updates the smpfilter. + for ch in range(1, nchans + 1): + _ = cbsdk.set_channel_config(nsp_obj, ch, "smpgroup", smpgroup) # Create a dummy app. - app = DummyApp(duration=duration, t_step=1 / config["sysfreq"]) + app = DummyApp(nchans, duration=duration, t_step=1 / config["sysfreq"]) time.sleep(2.0)