<!-- SPDX-FileCopyrightText: Copyright 2026 Jetperch LLC
SPDX-License-Identifier: Apache-2.0

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->

<a id="device-selection"></a>

# Device selection

Driver.device_paths(), Driver.find_devices() and Driver.find_one_device()
return `DevicePath` instances, which are str instances that also
parse the device path fields.  The device specifications select devices by
device path, model, model-serial_number or serial number, such as
“u/js320/8W2A”, “js320”, “js320-8W2A” or “8W2A”.  The optional brand,
such as “Joulescope”, restricts the selection to that brand’s devices.

### *class* pyjoulescope_driver.device_path.DevicePath

A device path str, such as “u/js320/31NB”.

DevicePath is a str subclass, so it works anywhere a device path
string does.  The field properties return None for paths that do
not have the “{backend}/{model}/{serial_number}” form.

A device in bootloader mode has a “&” model prefix, such as
“u/&js220/000415”.  Its model and brand are the same as in
application mode, and [`is_bootloader`](#pyjoulescope_driver.device_path.DevicePath.is_bootloader) is True.

#### *property* backend

The backend, such as “u” for USB, or None.

#### *property* brand

The brand, such as “Joulescope”, or None if unknown.

#### *property* is_bootloader

True if this path is a device in bootloader mode.

#### match(spec)

Check if this path matches a user-provided device specification.

* **Parameters:**
  **spec** – 

  The device specification, which is one of:
  * the full device path, such as “u/js320/31NB”.
  * the backend and model, such as “u/js320” or “u/js320/”,
    which selects every JS320 on that interface.
  * the model and serial number, such as “js320/31NB”.
  * the model and serial number, such as “js320-31NB”.
  * the model, such as “js320”.
  * the serial number, such as “31NB”.

  Matching is case-insensitive, and serial numbers must match exactly.
  The model forms match a device in either application or bootloader
  mode.  Use the “&” model prefix, such as “&js220”, to only match
  devices in bootloader mode.
* **Returns:**
  True on a match, False otherwise.

#### *property* model

The lowercase model without the bootloader prefix, such as “js320”, or None.

#### *property* serial_number

The serial number with its original case, such as “31NB”, or None.

<a id="module-pyjoulescope_driver.device_path"></a>

A device path string that parses its own fields.

Device paths have the form “{backend}/{model}/{serial_number}”, such as
“u/js320/31NB”.  Driver.device_paths() returns DevicePath instances.
Paths from other sources, such as “@/!add” callbacks, command-line
arguments or configuration files, are plain str.  Use DevicePath(path)
to convert them.

### pyjoulescope_driver.device_path.BRANDS_TO_MODELS *= {'Joulescope': ('js110', 'js220', 'js320')}*

The lowercase models for each brand.

### pyjoulescope_driver.device_path.BRAND_ALIASES *= {'js': 'Joulescope'}*

Case-insensitive alternate names for brands in BRANDS_TO_MODELS.

### pyjoulescope_driver.device_path.brand_validate(brand)

Validate a brand name.

* **Parameters:**
  **brand** – The case-insensitive brand name or alias, such as
  “joulescope” or “js”.
* **Returns:**
  The brand name, such as “Joulescope”.
* **Raises:**
  * **TypeError** – If brand is not a str.
  * **ValueError** – If brand is not supported.

<a id="module-pyjoulescope_driver.device_filter"></a>

Select devices from the driver’s device paths.

The driver reports all devices, including Joulescope instruments and
other devices, such as a MiniBitty at “u/mb/{serial_number}”.  Use the
brand argument to select only Joulescope instruments, or a backend and
model specification such as “u/js320/” to select every JS320 on one
interface.  See `DevicePath` for the device path and specification
formats.

Use this module as a namespace:

```default
from pyjoulescope_driver import device_filter
device_path = device_filter.find_one(paths, '31NB', brand='joulescope')
```

Driver.device_paths() and Driver.find_one_device() provide the same
selection directly.

### *exception* pyjoulescope_driver.device_filter.DeviceFilterError(specs, brand, matches, available)

The device specifications did not match exactly one device.

* **Variables:**
  * **specs** – The list of device specifications, or None for all.
  * **brand** – The brand, such as “Joulescope”, or None for all.
  * **matches** – The list of matching device paths.
  * **available** – The list of all device paths for the brand.

### pyjoulescope_driver.device_filter.find(device_paths, specs=None, brand=None)

Find the device paths for the matching devices.

* **Parameters:**
  * **device_paths** – The list of device path strings, such as
    from Driver.device_paths().
  * **specs** – 

    The device specifications, which is one of:
    * None to match all devices.
    * a string containing one or more comma-separated device
      specifications, such as “js320” or “31NB, u/js220/000415”.
      Whitespace around each specification is ignored, and an
      empty string matches all devices.
    * a list of device specification strings.

    A device matches if it matches any specification.
    See `DevicePath.match()` for the device specification format.
  * **brand** – The case-insensitive brand, such as “Joulescope”,
    to only match devices of that brand.  None (default) matches
    all brands.
* **Returns:**
  The list of matching `DevicePath` instances, in order.
* **Raises:**
  * **TypeError** – If specs or brand has an invalid type.
  * **ValueError** – If brand is not supported.

### pyjoulescope_driver.device_filter.find_one(device_paths, specs=None, brand=None)

Find the device path for exactly one matching device.

* **Parameters:**
  * **device_paths** – The list of device path strings, such as
    from Driver.device_paths().
  * **specs** – The device specifications.  See [`find()`](#pyjoulescope_driver.device_filter.find).
  * **brand** – The brand.  See [`find()`](#pyjoulescope_driver.device_filter.find).
* **Returns:**
  The matching `DevicePath`.
* **Raises:**
  [**DeviceFilterError**](#pyjoulescope_driver.device_filter.DeviceFilterError) – If zero or multiple devices match.
