Slow OneWire Bulk-Read Polling
===============================

This applies to :py:class:`herosdevices.core.bus.onewire.OneWireBusMaster` and
:py:class:`herosdevices.core.bus.onewire.W1ThermBusMaster`, which poll a onewire bus by triggering
one bulk conversion for every sensor at once (``therm_bulk_read``) instead of reading each sensor
individually. On a working bus, one poll cycle takes roughly one conversion time (about 750 ms at
12-bit resolution), no matter how many sensors are attached. If the kernel refuses the
bulk trigger, ``OneWireBusMaster`` falls back to reading each sensor individually so a poll cycle
still completes, but every sensor then pays its own conversion time.

Symptoms
--------

The ``poll_time`` observable (or the time a manual ``poll_once()`` takes) is close to::

    number_of_sensors * ~750 ms

For example, 13 sensors on one bus reporting a poll time around 9.5-10.5 s instead of roughly 1 s.

Diagnosis
---------

Check the kernel log for the bulk-read trigger being refused:

.. code-block:: bash

   dmesg | grep -iE "not registered|therm_bulk_read"

Two messages point at this problem:

``Family 28 for <rom-id> is not registered``
   Logged once per sensor, right when the bus master's first search runs.

``therm_bulk_read_store: unable to trigger a bulk read on the bus. err=-22``
   Logged every time a poll cycle tries to trigger a bulk conversion and the kernel rejects it.
   ``err=-22`` is ``EINVAL``.

If you see the first message on its own (only near boot) followed by the second message
persisting afterwards, this is a boot-ordering problem, described below. Also worth ruling out
before digging further:

.. code-block:: bash

   # do all sensors use the same power mode? a mix of parasite- and externally-powered
   # sensors on one bus also makes the kernel refuse the bulk trigger
   for d in $(cat /sys/bus/w1/devices/w1_bus_master1/w1_master_slaves); do
     echo -n "$d: "; cat /sys/bus/w1/devices/$d/ext_power
   done

Cause
-----

``w1-gpio``'s master driver runs its first bus search as soon as it comes up. If ``w1-therm``, the
driver that recognizes DS18B20-family sensors, has not registered yet at that exact moment, every
sensor found in that first search is added to the bus as an unrecognized ("not registered")
device. Once a sensor gets added this way, it stays ineligible for bulk-read for the rest of that
boot, even after ``w1-therm`` loads a moment later - the kernel does not retroactively upgrade it.

On a typical setup, ``w1-gpio`` and its dependency ``wire`` load early via the
``dtoverlay=w1-gpio`` device tree overlay, while ``w1-therm`` is only pulled in reactively once a
matching device is found. That ordering is exactly backwards for bulk-read: ``w1-therm`` needs to
already be loaded before the first search runs, not after.

.. tip::

   You can reproduce and confirm this by hand. Force a clean reload with the modules in the
   correct order and watch for the "not registered" message to disappear:

   .. code-block:: bash

      rmmod w1_therm 2>/dev/null
      rmmod w1_gpio
      rmmod wire
      modprobe wire
      modprobe w1_therm
      modprobe w1_gpio
      sleep 3
      dmesg | tail -50
      cat /sys/bus/w1/devices/w1_bus_master1/w1_master_slave_count

Fix
---

Make module loading order explicit instead of relying on the device tree overlay's reactive
autoloading. Add the following to ``/etc/modules`` (Raspberry Pi OS reads this via
``systemd-modules-load.service`` early in boot, before the overlay-triggered probe):

.. code-block:: text
   :caption: /etc/modules

   wire
   w1-therm
   w1-gpio

Leave ``dtoverlay=w1-gpio`` in ``config.txt`` as is - it is still needed for the GPIO pin
configuration itself. This change only makes sure ``w1-therm`` is ready before ``w1-gpio`` probes.

Verifying the fix
------------------

Reboot, then check that no sensor was added before ``w1-therm`` was ready:

.. code-block:: bash

   dmesg | grep -iE "not registered|therm_bulk_read"
   cat /sys/bus/w1/devices/w1_bus_master1/w1_master_slave_count

Both commands should come back clean: no matching log lines, and a slave count matching the
number of sensors you expect. A manual trigger should now settle quickly instead of erroring:

.. code-block:: bash

   echo trigger > /sys/bus/w1/devices/w1_bus_master1/therm_bulk_read
   sleep 1
   cat /sys/bus/w1/devices/w1_bus_master1/therm_bulk_read   # expect 1, not an error
   for d in $(cat /sys/bus/w1/devices/w1_bus_master1/w1_master_slaves); do
     echo -n "$d: "; cat /sys/bus/w1/devices/$d/temperature
   done
   cat /sys/bus/w1/devices/w1_bus_master1/therm_bulk_read   # expect 0 (drained)

.. note::

   ``OneWireBusMaster`` writes the trigger command with a trailing newline
   (``f"{action.trigger_value}\\n"`` in ``_drain_action``). On some kernel versions, ``w1-therm``
   rejects a trigger write missing that newline - same ``err=-22`` symptom, unrelated to boot
   order. If the fix above does not resolve the symptom, make sure you are running a
   ``herosdevices`` version that includes this trailing newline.
