samkohn: @dadwyer since you proposed this idea I want to make sure you get the chance to look over the changes before I merge them in.
diff --git a/larpix/larpix.py b/larpix/larpix.py
index f1f7684..b337ec6 100644
--- a/larpix/larpix.py
+++ b/larpix/larpix.py
@@ -710,11 +710,31 @@ class Controller(object):
     '''
     Controls a collection of LArPix Chip objects.
 
+    Properties and attributes:
+
+    - ``chips``: the ``Chip`` objects that the controller controls
+    - ``all_chip``: all possible ``Chip`` objects (considering there are
+      a finite number of chip IDs), initialized on object construction
+    - ``port``: the path to the serial port, i.e. "/dev/(whatever)"
+      (default: ``'/dev/ttyUSB1'``)
+    - ``timeout``: the timeout used for serial commands, in seconds.
+      This can be changed between calls to the read and write commands.
+      (default: ``1``)
+    - ``reads``: list of all the PacketCollections that have been sent
+      back to this controller. PacketCollections are created by
+      ``run``, ``write_configuration``, and ``read_configuration``, but
+      not by any of the ``serial_*`` methods.
+    - ``use_all_chips``: if ``True``, look up chip objects in
+      ``self.all_chips``, else look up in ``self.chips`` (default:
+      ``False``)
+
     '''
     start_byte = b'\x73'
     stop_byte = b'\x71'
     def __init__(self, port='/dev/ttyUSB1'):
         self.chips = []
+        self.all_chips = self._init_chips()
+        self.use_all_chips = False
         self.reads = []
         self.nreads = 0
         self.port = port
@@ -723,15 +743,23 @@ class Controller(object):
         self.max_write = 8192
         self._serial = serial.Serial
 
-    def init_chips(self, nchips = 256, iochain = 0):
-        self.chips = [Chip(i, iochain) for i in range(256)]
+    def _init_chips(self, nchips = 256, iochain = 0):
+        '''
+        Return all possible chips.
+
+        '''
+        return [Chip(i, iochain) for i in range(256)]
 
     def get_chip(self, chip_id, io_chain):
-        for chip in self.chips:
+        if self.use_all_chips:
+            chip_list = self.all_chips
+        else:
+            chip_list = self.chips
+        for chip in chip_list:
             if chip.chip_id == chip_id and chip.io_chain == io_chain:
                 return chip
-        raise ValueError('Could not find chip (%d, %d)' % (chip_id,
-            io_chain))
+        raise ValueError('Could not find chip (%d, %d) (using all_chips'
+                '? %s)' % (chip_id, io_chain, self.use_all_chips))
 
     def serial_flush(self):
         with self._serial(self.port, baudrate=self.baudrate,
diff --git a/larpix/tasks.py b/larpix/tasks.py
index a01da07..abace8a 100644
--- a/larpix/tasks.py
+++ b/larpix/tasks.py
@@ -58,15 +58,13 @@ def get_chip_ids(**settings):
     logger.info('Executing get_chip_ids')
     if 'controller' in settings:
         controller = settings['controller']
-        if not controller.chips:
-            controller.init_chips()
     else:
         controller = larpix.Controller(settings['port'])
-        controller.init_chips()
+    controller.use_all_chips = True
     stored_timeout = controller.timeout
     controller.timeout=0.1
     chips = []
-    for chip in controller.chips:
+    for chip in controller.all_chips:
         controller.read_configuration(chip, 0, timeout=0.1)
         if len(chip.reads) == 0:
             print('Chip ID %d: Packet lost in black hole.  No connection?' %
@@ -81,6 +79,7 @@ def get_chip_ids(**settings):
             chips.append(chip)
             logger.info('Found chip %s' % chip)
     controller.timeout = stored_timeout
+    controller.use_all_chips = False
     return chips
 
 def simple_stats(**settings):

Separate the functionality of "init_chips" (broadcasting) from the list of known actual chips
Controller objects should know which chips actually exist and which are just there for command broadcasting. Proposed revision:

- `controller.chips` is for known chips
- `controller.all_chips` is for all possible chips (i.e. all chip IDs combined with all known daisy chains)
- `controller.init_chips` becomes `controller._init_chips` and is called automatically during initialization to initialize `controller.all_chips`
diff --git a/test/test_larpix.py b/test/test_larpix.py
index b22de84..af9e035 100644
--- a/test/test_larpix.py
+++ b/test/test_larpix.py
@@ -1461,9 +1461,8 @@ def test_configuration_from_dict_reg_reset_cycles():
 
 def test_controller_init_chips():
     controller = Controller(None)
-    controller.init_chips()
-    result = list(map(str, controller.chips))
-    expected = list(map(str, (Chip(i, 0) for i in range(256))))
+    result = list(map(repr, controller._init_chips()))
+    expected = list(map(repr, (Chip(i, 0) for i in range(256))))
     assert result == expected
 
 def test_controller_get_chip():
@@ -1472,6 +1471,13 @@ def test_controller_get_chip():
     controller.chips.append(chip)
     assert controller.get_chip(1, 3) == chip
 
+def test_controller_get_chip_all_chips():
+    controller = Controller(None)
+    controller.use_all_chips = True
+    result = controller.get_chip(5, 0)
+    expected = controller.all_chips[5]
+    assert result == expected
+
 def test_controller_get_chip_error():
     controller = Controller(None)
     chip = Chip(1, 3)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
bitarray==3.3.0
bitstring==4.3.1
exceptiongroup==1.2.2
iniconfig==2.1.0
-e git+https://github.com/samkohn/larpix-control.git@726cfac3948552f082bb645a6f7cdcf7d5c8cfee#egg=larpix_control
packaging==24.2
pluggy==1.5.0
pyserial==3.5
pytest==8.3.5
tomli==2.2.1

name: larpix-control
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - bitarray==3.3.0
      - bitstring==4.3.1
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - packaging==24.2
      - pluggy==1.5.0
      - pyserial==3.5
      - pytest==8.3.5
      - tomli==2.2.1
prefix: /opt/conda/envs/larpix-control

test/test_larpix.py::test_controller_init_chips test/test_larpix.py::test_controller_get_chip_all_chips
test/test_larpix.py::test_configuration_set_pixel_trim_thresholds_errors test/test_larpix.py::test_configuration_set_global_threshold_errors test/test_larpix.py::test_configuration_set_csa_gain_errors test/test_larpix.py::test_configuration_set_csa_bypass_errors test/test_larpix.py::test_configuration_set_internal_bypass_errors test/test_larpix.py::test_configuration_set_csa_bypass_select_errors test/test_larpix.py::test_configuration_set_csa_monitor_select_errors test/test_larpix.py::test_configuration_set_csa_testpulse_enable_errors test/test_larpix.py::test_configuration_set_csa_testpulse_dac_amplitude_errors test/test_larpix.py::test_configuration_set_test_mode_errors test/test_larpix.py::test_configuration_set_cross_trigger_mode_errors test/test_larpix.py::test_configuration_set_periodic_reset_errors test/test_larpix.py::test_configuration_set_fifo_diagnostic_errors test/test_larpix.py::test_configuration_set_test_burst_length_errors test/test_larpix.py::test_configuration_set_adc_burst_length_errors test/test_larpix.py::test_configuration_set_channel_mask_errors test/test_larpix.py::test_configuration_set_external_trigger_mask_errors test/test_larpix.py::test_configuration_set_reset_cycles_errors test/test_larpix.py::test_configuration_write_errors test/test_larpix.py::test_controller_get_chip_error test/test_larpix.py::test_controller_format_UART test/test_larpix.py::test_controller_format_bytestream test/test_larpix.py::test_controller_write_configuration test/test_larpix.py::test_controller_write_configuration_one_reg test/test_larpix.py::test_controller_write_configuration_write_read test/test_larpix.py::test_controller_get_configuration_bytestreams test/test_larpix.py::test_controller_parse_input test/test_larpix.py::test_controller_parse_input_dropped_data_byte test/test_larpix.py::test_controller_parse_input_dropped_start_byte test/test_larpix.py::test_controller_parse_input_dropped_stop_byte test/test_larpix.py::test_controller_parse_input_dropped_stopstart_bytes
test/test_larpix.py::test_FakeSerialPort_write test/test_larpix.py::test_FakeSerialPort_read test/test_larpix.py::test_FakeSerialPort_read_multi test/test_larpix.py::test_chip_str test/test_larpix.py::test_chip_get_configuration_packets test/test_larpix.py::test_chip_sync_configuration test/test_larpix.py::test_chip_export_reads test/test_larpix.py::test_chip_export_reads_no_new_reads test/test_larpix.py::test_chip_export_reads_all test/test_larpix.py::test_controller_save_output test/test_larpix.py::test_controller_load test/test_larpix.py::test_packet_bits_bytes test/test_larpix.py::test_packet_init_default test/test_larpix.py::test_packet_init_bytestream test/test_larpix.py::test_packet_bytes_zeros test/test_larpix.py::test_packet_bytes_custom test/test_larpix.py::test_packet_bytes_properties test/test_larpix.py::test_packet_export_test test/test_larpix.py::test_packet_export_data test/test_larpix.py::test_packet_export_config_read test/test_larpix.py::test_packet_export_config_write test/test_larpix.py::test_packet_set_packet_type test/test_larpix.py::test_packet_get_packet_type test/test_larpix.py::test_packet_set_chipid test/test_larpix.py::test_packet_get_chipid test/test_larpix.py::test_packet_set_parity_bit_value test/test_larpix.py::test_packet_get_parity_bit_value test/test_larpix.py::test_packet_compute_parity test/test_larpix.py::test_packet_assign_parity test/test_larpix.py::test_packet_has_valid_parity test/test_larpix.py::test_packet_set_channel_id test/test_larpix.py::test_packet_get_channel_id test/test_larpix.py::test_packet_set_timestamp test/test_larpix.py::test_packet_get_timestamp test/test_larpix.py::test_packet_set_dataword test/test_larpix.py::test_packet_get_dataword test/test_larpix.py::test_packet_get_dataword_ADC_bug test/test_larpix.py::test_packet_set_fifo_half_flag test/test_larpix.py::test_packet_get_fifo_half_flag test/test_larpix.py::test_packet_set_fifo_full_flag test/test_larpix.py::test_packet_get_fifo_full_flag test/test_larpix.py::test_packet_set_register_address test/test_larpix.py::test_packet_get_register_address test/test_larpix.py::test_packet_set_register_data test/test_larpix.py::test_packet_get_register_data test/test_larpix.py::test_packet_set_test_counter test/test_larpix.py::test_packet_get_test_counter test/test_larpix.py::test_configuration_get_nondefault_registers test/test_larpix.py::test_configuration_get_nondefault_registers_array test/test_larpix.py::test_configuration_get_nondefault_registers_many_changes test/test_larpix.py::test_configuration_set_pixel_trim_thresholds test/test_larpix.py::test_configuration_get_pixel_trim_thresholds test/test_larpix.py::test_configuration_set_global_threshold test/test_larpix.py::test_configuration_get_global_threshold test/test_larpix.py::test_configuration_set_csa_gain test/test_larpix.py::test_configuration_get_csa_gain test/test_larpix.py::test_configuration_set_csa_bypass test/test_larpix.py::test_configuration_get_csa_bypass test/test_larpix.py::test_configuration_set_internal_bypass test/test_larpix.py::test_configuration_get_internal_bypass test/test_larpix.py::test_configuration_set_csa_bypass_select test/test_larpix.py::test_configuration_get_csa_bypass_select test/test_larpix.py::test_configuration_set_csa_monitor_select test/test_larpix.py::test_configuration_get_csa_monitor_select test/test_larpix.py::test_configuration_set_csa_testpulse_enable test/test_larpix.py::test_configuration_get_csa_testpulse_enable test/test_larpix.py::test_configuration_set_csa_testpulse_dac_amplitude test/test_larpix.py::test_configuration_get_csa_testpulse_dac_amplitude test/test_larpix.py::test_configuration_set_test_mode test/test_larpix.py::test_configuration_get_test_mode test/test_larpix.py::test_configuration_set_cross_trigger_mode test/test_larpix.py::test_configuration_get_cross_trigger_mode test/test_larpix.py::test_configuration_set_periodic_reset test/test_larpix.py::test_configuration_get_periodic_reset test/test_larpix.py::test_configuration_set_fifo_diagnostic test/test_larpix.py::test_configuration_get_fifo_diagnostic test/test_larpix.py::test_configuration_set_test_burst_length test/test_larpix.py::test_configuration_get_test_burst_length test/test_larpix.py::test_configuration_set_adc_burst_length test/test_larpix.py::test_configuration_get_adc_burst_length test/test_larpix.py::test_configuration_set_channel_mask test/test_larpix.py::test_configuration_get_channel_mask test/test_larpix.py::test_configuration_set_external_trigger_mask test/test_larpix.py::test_configuration_get_external_trigger_mask test/test_larpix.py::test_configuration_set_reset_cycles test/test_larpix.py::test_configuration_get_reset_cycles test/test_larpix.py::test_configuration_disable_channels test/test_larpix.py::test_configuration_disable_channels_default test/test_larpix.py::test_configuration_enable_channels test/test_larpix.py::test_configuration_enable_channels_default test/test_larpix.py::test_configuration_enable_external_trigger test/test_larpix.py::test_configuration_enable_external_trigger_default test/test_larpix.py::test_configuration_disable_external_trigger test/test_larpix.py::test_configuration_enable_testpulse test/test_larpix.py::test_configuration_enable_testpulse_default test/test_larpix.py::test_configuration_disable_testpulse test/test_larpix.py::test_configuration_disable_testpulse_default test/test_larpix.py::test_configuration_enable_analog_monitor test/test_larpix.py::test_configuration_disable_analog_monitor test/test_larpix.py::test_configuration_trim_threshold_data test/test_larpix.py::test_configuration_global_threshold_data test/test_larpix.py::test_configuration_csa_gain_and_bypasses_data test/test_larpix.py::test_configuration_csa_bypass_select_data test/test_larpix.py::test_configuration_csa_monitor_select_data test/test_larpix.py::test_configuration_csa_testpulse_enable_data test/test_larpix.py::test_configuration_csa_testpulse_dac_amplitude_data test/test_larpix.py::test_configuration_test_mode_xtrig_reset_diag_data test/test_larpix.py::test_configuration_sample_cycles_data test/test_larpix.py::test_configuration_test_burst_length_data test/test_larpix.py::test_configuration_adc_burst_length_data test/test_larpix.py::test_configuration_channel_mask_data test/test_larpix.py::test_configuration_external_trigger_mask_data test/test_larpix.py::test_configuration_reset_cycles_data test/test_larpix.py::test_configuration_to_dict test/test_larpix.py::test_configuration_from_dict test/test_larpix.py::test_configuration_write test/test_larpix.py::test_configuration_write_force test/test_larpix.py::test_configuration_read_absolute test/test_larpix.py::test_configuration_read_default test/test_larpix.py::test_configuration_read_local test/test_larpix.py::test_configuration_from_dict_reg_pixel_trim test/test_larpix.py::test_configuration_from_dict_reg_global_threshold test/test_larpix.py::test_configuration_from_dict_reg_csa_gain test/test_larpix.py::test_configuration_from_dict_reg_csa_bypass test/test_larpix.py::test_configuration_from_dict_reg_internal_bypass test/test_larpix.py::test_configuration_from_dict_reg_csa_bypass_select test/test_larpix.py::test_configuration_from_dict_reg_csa_monitor_select test/test_larpix.py::test_configuration_from_dict_reg_csa_testpulse_enable test/test_larpix.py::test_configuration_from_dict_reg_csa_testpulse_dac_amplitude test/test_larpix.py::test_configuration_from_dict_reg_test_mode test/test_larpix.py::test_configuration_from_dict_reg_cross_trigger_mode test/test_larpix.py::test_configuration_from_dict_reg_periodic_reset test/test_larpix.py::test_configuration_from_dict_reg_fifo_diagnostic test/test_larpix.py::test_configuration_from_dict_reg_sample_cycles test/test_larpix.py::test_configuration_from_dict_reg_test_burst_length test/test_larpix.py::test_configuration_from_dict_reg_adc_burst_length test/test_larpix.py::test_configuration_from_dict_reg_channel_mask test/test_larpix.py::test_configuration_from_dict_reg_external_trigger_mask test/test_larpix.py::test_configuration_from_dict_reg_reset_cycles test/test_larpix.py::test_controller_get_chip test/test_larpix.py::test_controller_serial_read_mock test/test_larpix.py::test_controller_serial_write_mock test/test_larpix.py::test_controller_serial_write_read_mock test/test_larpix.py::test_packetcollection_getitem_int test/test_larpix.py::test_packetcollection_getitem_int_bits test/test_larpix.py::test_packetcollection_getitem_slice test/test_larpix.py::test_packetcollection_getitem_slice_bits test/test_larpix.py::test_packetcollection_origin test/test_larpix.py::test_packetcollection_to_dict test/test_larpix.py::test_packetcollection_from_dict
larpix/larpix.py larpix/tasks.py
larpix/larpix.py larpix/tasks.py