From b0dbae5fb183d013deff03eb7b84427673f1431f Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Fri, 30 May 2025 14:55:51 -0400 Subject: [PATCH 1/5] Typo fix (how has this never come up before?!) --- src/pycbsdk/cbhw/device/nsp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pycbsdk/cbhw/device/nsp.py b/src/pycbsdk/cbhw/device/nsp.py index 97b12f2..f03d4a4 100644 --- a/src/pycbsdk/cbhw/device/nsp.py +++ b/src/pycbsdk/cbhw/device/nsp.py @@ -430,8 +430,8 @@ def _handle_chaninfo(self, pkt): # self._config["channel_infos"][pkt.chan].union.a.moninst = pkt.moninst # self._config["channel_infos"][pkt.chan].union.a.monchan = pkt.monchan elif pkt.header.type == CBPacketType.CHANREPSCALE: - self._config["channel_infos"][pkt.chan].scalein = pkt.scalein - self._config["channel_infos"][pkt.chan].scaleout = pkt.scaleout + self._config["channel_infos"][pkt.chan].scalin = pkt.scalin + self._config["channel_infos"][pkt.chan].scalout = pkt.scalout elif pkt.header.type == CBPacketType.CHANREPDINP: # TODO: NOTE: Need extra check if this is for serial or digital? self._config["channel_infos"][pkt.chan].dinpopts = pkt.dinpopts From da1ef3f706ed1847893a19871d486c3b382dcfc3 Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Fri, 30 May 2025 15:50:05 -0400 Subject: [PATCH 2/5] sysfreq does not actually contain the timestamp resolution --- src/pycbsdk/examples/group_sample_intervals.py | 11 +++++++++-- src/pycbsdk/examples/print_rates.py | 9 ++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/pycbsdk/examples/group_sample_intervals.py b/src/pycbsdk/examples/group_sample_intervals.py index 7019080..56d50c2 100644 --- a/src/pycbsdk/examples/group_sample_intervals.py +++ b/src/pycbsdk/examples/group_sample_intervals.py @@ -83,12 +83,19 @@ def main( for chtype in [CBChannelType.FrontEnd, CBChannelType.AnalogIn]: cbsdk.set_all_channels_disable(nsp_obj, chtype) - # Enable channels 1 & 2 at smpgroup. For smpgroup < 5, this also updates the smpfilter. + # Enable first nchans 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) + # Calculate the clock step (I hate this) + if inst_addr and int(inst_addr.split(".")[-1]) in [200, 201, 202, 203, 203]: + # Note: This misses Gemini NSP! + t_step = 1 / 1e9 + else: + t_step = 1 / config["sysfreq"] + # Create a dummy app. - app = DummyApp(nchans, duration=duration, t_step=1 / config["sysfreq"]) + app = DummyApp(nchans, duration=duration, t_step=t_step) time.sleep(2.0) diff --git a/src/pycbsdk/examples/print_rates.py b/src/pycbsdk/examples/print_rates.py index b059057..8588280 100644 --- a/src/pycbsdk/examples/print_rates.py +++ b/src/pycbsdk/examples/print_rates.py @@ -177,8 +177,15 @@ def main( ] n_chans = sum(b_spk) + # Calculate the clock step (I hate this) + if inst_addr and int(inst_addr.split(".")[-1]) in [200, 201, 202, 203, 203]: + # Note: This misses Gemini NSP! + t_step = 1 / 1e9 + else: + t_step = 1 / config["sysfreq"] + # Create the dummy app. - app = DummyApp(n_chans, history=update_interval, tstep=1 / config["sysfreq"]) + app = DummyApp(n_chans, history=update_interval, tstep=t_step) # Register callbacks to update the app's state when appropriate packets are received. _ = cbsdk.register_spk_callback(nsp_obj, app.update_state) From 11c9deffcac762c561b5a88805c7d843b1702ab3 Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Fri, 30 May 2025 15:51:03 -0400 Subject: [PATCH 3/5] Explicitly ignore more packet types --- src/pycbsdk/cbhw/device/nsp.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/pycbsdk/cbhw/device/nsp.py b/src/pycbsdk/cbhw/device/nsp.py index f03d4a4..4fde09d 100644 --- a/src/pycbsdk/cbhw/device/nsp.py +++ b/src/pycbsdk/cbhw/device/nsp.py @@ -346,6 +346,9 @@ def _register_basic_callbacks(self): self.register_config_callback(CBPacketType.CHANREPAINP, self._handle_chaninfo) self.register_config_callback(CBPacketType.CHANREPSPKTHR, self._handle_chaninfo) + self.register_config_callback(CBPacketType.CHANREPNTRODEGROUP, self._handle_chaninfo) + self.register_config_callback(CBPacketType.CHANREPDISP, self._handle_chaninfo) + self.register_config_callback(CBPacketType.CHANREPUNITOVERRIDES, self._handle_chaninfo) self.register_config_callback(CBPacketType.GROUPREP, self._handle_groupinfo) self.register_config_callback(CBPacketType.PROCREP, self._handle_procinfo) @@ -443,14 +446,18 @@ def _handle_chaninfo(self, pkt): elif pkt.header.type == CBPacketType.CHANREPLABEL: self._config["channel_infos"][pkt.chan].label = pkt.label self._config["channel_infos"][pkt.chan].userflags = pkt.userflags - elif pkt.header.type == CBPacketType.CHANSETSPKTHR: - # TODO: from CHANREPSPKTHR, .spkthrlevel + elif pkt.header.type in [CBPacketType.CHANSETSPKTHR, CBPacketType.CHANREPSPKTHR]: self._config["channel_infos"][pkt.chan].spkthrlevel = pkt.spkthrlevel - + elif pkt.header.type == CBPacketType.CHANREPNTRODEGROUP: + # TODO: from use pkt.spkgroup + pass + elif pkt.header.type == CBPacketType.CHANREPDISP: + # TODO: Use .smpdispmin, .smpdispmax, .spkdispmax, .lncdispmax + pass + elif pkt.header.type == CBPacketType.CHANREPUNITOVERRIDES: + # TODO: Use .unitmapping + pass else: - # TODO: from CHANREPNTRODEGROUP, .spkgroup - # TODO: from CHANREPDISP, .smpdispmin, .smpdispmax, .spkdispmax, .lncdispmax - # TODO: from CHANREPUNITOVERRIDES, .unitmapping pass # print(f"handled chaninfo {pkt.chan} of type {hex(pkt.header.type)}") self._config_events["chaninfo"].set() From 392fae000988a0e217243c5aa18f5926913af542 Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Fri, 30 May 2025 15:53:16 -0400 Subject: [PATCH 4/5] Also drop chaninfo if chan id is beyond what is expected in proc_chans --- src/pycbsdk/cbhw/device/nsp.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pycbsdk/cbhw/device/nsp.py b/src/pycbsdk/cbhw/device/nsp.py index 4fde09d..ab9335a 100644 --- a/src/pycbsdk/cbhw/device/nsp.py +++ b/src/pycbsdk/cbhw/device/nsp.py @@ -400,8 +400,8 @@ 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.instrument != self._config["instrument"]: - # Gemini system returns channel info for all instruments. + if (pkt.header.instrument != self._config["instrument"]) or (pkt.chan > self._config["proc_chans"]): + # Drop channels that do not belong to this instrument pass elif pkt.header.type in [CBPacketType.CHANREP]: # Full scope; overwrite our config. @@ -486,6 +486,8 @@ def _handle_nplay(self, pkt): def _handle_procmon(self, pkt): arrival_time = time.time() + # Note: There's about 0.57 msec from when procmon is sent to when it is received. + # so we could make sys_time = arrival_time - 570_000e-9 update_interval = max(pkt.header.time - self._monitor_state["time"], 1) pkt_delta = self.pkts_received - self._monitor_state["pkts_received"] From df630e56d446c849740b95298ee233fa5e31a531 Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Fri, 30 May 2025 15:57:33 -0400 Subject: [PATCH 5/5] ruff format --- src/pycbsdk/cbhw/device/nsp.py | 19 ++++++++++++++----- src/pycbsdk/cbhw/packet/packets.py | 4 ++-- .../examples/group_sample_intervals.py | 4 ++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/pycbsdk/cbhw/device/nsp.py b/src/pycbsdk/cbhw/device/nsp.py index ab9335a..ca95b24 100644 --- a/src/pycbsdk/cbhw/device/nsp.py +++ b/src/pycbsdk/cbhw/device/nsp.py @@ -346,9 +346,13 @@ def _register_basic_callbacks(self): self.register_config_callback(CBPacketType.CHANREPAINP, self._handle_chaninfo) self.register_config_callback(CBPacketType.CHANREPSPKTHR, self._handle_chaninfo) - self.register_config_callback(CBPacketType.CHANREPNTRODEGROUP, self._handle_chaninfo) + self.register_config_callback( + CBPacketType.CHANREPNTRODEGROUP, self._handle_chaninfo + ) self.register_config_callback(CBPacketType.CHANREPDISP, self._handle_chaninfo) - self.register_config_callback(CBPacketType.CHANREPUNITOVERRIDES, self._handle_chaninfo) + self.register_config_callback( + CBPacketType.CHANREPUNITOVERRIDES, self._handle_chaninfo + ) self.register_config_callback(CBPacketType.GROUPREP, self._handle_groupinfo) self.register_config_callback(CBPacketType.PROCREP, self._handle_procinfo) @@ -400,7 +404,9 @@ 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.instrument != self._config["instrument"]) or (pkt.chan > self._config["proc_chans"]): + if (pkt.header.instrument != self._config["instrument"]) or ( + pkt.chan > self._config["proc_chans"] + ): # Drop channels that do not belong to this instrument pass elif pkt.header.type in [CBPacketType.CHANREP]: @@ -446,7 +452,10 @@ def _handle_chaninfo(self, pkt): elif pkt.header.type == CBPacketType.CHANREPLABEL: self._config["channel_infos"][pkt.chan].label = pkt.label self._config["channel_infos"][pkt.chan].userflags = pkt.userflags - elif pkt.header.type in [CBPacketType.CHANSETSPKTHR, CBPacketType.CHANREPSPKTHR]: + elif pkt.header.type in [ + CBPacketType.CHANSETSPKTHR, + CBPacketType.CHANREPSPKTHR, + ]: self._config["channel_infos"][pkt.chan].spkthrlevel = pkt.spkthrlevel elif pkt.header.type == CBPacketType.CHANREPNTRODEGROUP: # TODO: from use pkt.spkgroup @@ -502,7 +511,7 @@ def _handle_procmon(self, pkt): f";\tcounter - {pkt.counter if has_counter else 'N/A'}" f";\tdelta - {pkt_delta}" f";\tsent - {pkt.sentpkts}" - f";\trate (pkt/samp) - {pkt_delta/update_interval}" + f";\trate (pkt/samp) - {pkt_delta / update_interval}" ) self._monitor_state = { "counter": pkt.counter if has_counter else -1, diff --git a/src/pycbsdk/cbhw/packet/packets.py b/src/pycbsdk/cbhw/packet/packets.py index b0ffc65..9240f8e 100644 --- a/src/pycbsdk/cbhw/packet/packets.py +++ b/src/pycbsdk/cbhw/packet/packets.py @@ -530,7 +530,7 @@ def sizes(self) -> list[int]: # Convert coords from uint16 to half as many uint32 # TODO: Use numpy from buffer return struct.unpack( - f"<{len(self.coords)//2}L", + f"<{len(self.coords) // 2}L", struct.pack(f"<{len(self.coords)}H", self._array), ) @@ -540,7 +540,7 @@ def sizes(self, insizes: list[int]): assert n_elems <= (self.max_elements // 2) # TODO: Use numpy buffer self.coords = struct.unpack( - f"<{len(insizes)*2}H", struct.pack(f"<{len(insizes)}L", insizes) + f"<{len(insizes) * 2}H", struct.pack(f"<{len(insizes)}L", insizes) ) @property diff --git a/src/pycbsdk/examples/group_sample_intervals.py b/src/pycbsdk/examples/group_sample_intervals.py index 56d50c2..e2e0d61 100644 --- a/src/pycbsdk/examples/group_sample_intervals.py +++ b/src/pycbsdk/examples/group_sample_intervals.py @@ -23,7 +23,7 @@ def __init__(self, nchans: int, duration=21.0, t_step=1 / 30_000): def handle_frame(self, pkt): if self._write_index < self._buffer.shape[0]: - self._buffer[self._write_index, :] = memoryview(pkt.data[:self._nchans]) + self._buffer[self._write_index, :] = memoryview(pkt.data[: self._nchans]) self._ts[self._write_index] = pkt.header.time self._write_index += 1 @@ -35,7 +35,7 @@ def finish(self): s_elapsed = ts_elapsed * self._t_step n_samps = np.sum(b_ts) print( - f"Collected {n_samps} samples in {s_elapsed} s\t({n_samps/s_elapsed:.2f} Hz)." + f"Collected {n_samps} samples in {s_elapsed} s\t({n_samps / s_elapsed:.2f} Hz)." )