discotool 0.5.6
Discover, list, and use USB microcontoller boards.
License: MIT
Requires Python: >=3.6
Keywords: circuitpython, micropython

## DiscoTool
Module and tool to discover all boards connected to USB.

![(Pimoroni Tiny2040, Arduino Nano 2040, Adafruit Huzzah ESP32)](https://raw.githubusercontent.com/Neradoc/discotool/main/docs/list_tiny_nano_esp32.png)

## Installing
The `discotool` command line program and the `discotool` python module for python 3 can be installed with pip.
```
python3 -m pip install discotool
```

## Compatibility
It currently runs on MacOS, Linux and Windows. It should not require any installation outside of pip dependencies (no libusb, etc.). If it doesn't work for you, an issue with relevant OS information is welcome.

## discotool CLI tool

When running `discotool` while using a single board, it will be automatically selected for commands. These are the commands I use the most:

- **`discotool repl`**: to connect to the REPL.
- **`discotool install ...`**: to install modules via circup.
- **`discotool update`**: to update modules via circup.

I often have more than one board connected, and connect to the REPL using `discotool -n pico` for example, or using the drive (mount) name to give each board a personnal name `discotool -m picotwo`.

I also use a command to back up all connected boards.
```
alias cpbackups='discotool backup --date ~/Backups/CPBackups'
```

### Configuration

Define environment variables to override the default command line tools used by discotool. They are called by appending the relevant parameters at the end of the command, or replacing a placeholder.

- **`DISCOTOOL_SERIALTOOL`**: command to connect to the REPL (tio, picocom, etc.), adds the port at the end or replaces `{port}` if present. 
	- on windows `{portnum}` is the `6` in `COM6`, required by TeraTerm.
	- default values, using the first one found:
		- **TeraTermPro**, **PuTTY** on Windows.
		- **tio**, **screen** on linux/mac.
- **`DISCOTOOL_CIRCUP`**: (`circup`) command to call circup (`pip install` it for the default).
- **`DISCOTOOL_NOCOLOR`**: disables colors in the output if it evaluates to True.

### Command line options

- **`--wait`**: runs the scan every second until it detects a board.
- **`--nocolor`**: do not output colors in the terminal (overrides all else).
- **`--color`**: output colors in the terminal (overrides all else).
- **`--info`**: add more informations on Circuitpython boards by scanning the drive's content (might trigger the autoreload).

#### No Command

- if no filter is given, run the `list` command.
- if filters are given, run the `repl` command.

#### Filters
Filters select boards from the list of devices found to run a command on them. They are combined with OR logic: anything that matches any filter is selected. All filters are NOT case sensitive. Filters are simple text matches, they don't support wildcards.

- **`--auto`**: select the first board found.
- **`--name`**: search in the USB name/description field. Eg: "clue", "QT", "S2".
- **`--serial`**: search the serial number of the board.
- **`--mount`**: search the volume names of the board. Eg: "CIRCUITPY".

#### Commands

- **`list`**: lists the selected boards, with name, manufacturer, serial number. Lists the serial ports and file volumes. If the `--info` option was given, identifies circuitpython code files present, as well as CPY version. On windows the name of the drive is listed in addition to the drive letter.
- **`repl`**: connect to the REPL of the selected boards using the tool specified, screen by default, choosing the first serial port found if there is more than one.
- **`eject`**: eject all selected board drives, or all found if no filter given. (MacOS only for now)
- **`backup <destination dir> [<sub dir>]`**: copy the content of the selected boards drives into the destination dir or the optional sub dir (that will be created for you). Each board is put in a directory with its name and serial number.
	- **`--create`**: create the destination dir if it does not exist.
	- **`--date`**: use a time stamp as subdirectory name, or add to the supplied name.
- **`circup <options>`**: calls circup with its `--path` option to each selected board and passes all other options and commands to it.
- **`get <key>`**: print just the value for the key, for the selected devices. Can be used with backticks and such in a shell script. Includes special keys:
	- **`pid`**, **`vid`**, **`sn`**: shortcuts for product_id, vendor_id and serial_num.
	- **`volume`**: path to the (first) mounted drive of the device.
	- **`port`**: (first) serial port of the device.
	- **`repl`**: (first) REPL serial port of the device.
	- **`cdc`**: (first) non-REPL serial port of the device.
	- **`main`** or **`code.py`**: full path to the main file for circuitpython.
- **`json`**: print the output of usbinfo as json for all selected boards.
	- **`--pretty`** **`-p`**: pretty print it for human reading.


## Module
Exposes the `get_identified_devices(drive_info=False)` function. Find boards on the host's USB bus and tries to match them with serial ports and mounted drives, virtual or not. If `drive_info` is True, when a drive is found, it reads circuitpython information if available: CPY version number and main files in order of priority (code.py, etc.). This might trigger the board's autoreload.
```python
import discotool
devicesList = discotool.get_identified_devices(drive_info=True)
```
Devices list (MacOS)
```python
[{
	'manufacturer': 'Adafruit Industries LLC',
	'name': 'CLUE nRF52840 Express',
	'ports': [{
		"dev": "'/dev/cu.usbmodem144443111'",
		"iface": "CircuitPython CDC data"
	}],
	'product_id': 32882,
	'serial_num': 'F88EE0399C0E1FC6',
	'vendor_id': 9114,
	'version': '6.0.1',
	'volumes': [{
		'mains': ['code.py'],
		'mount_point': '/Volumes/CIRCUITPY',
		'name': 'CIRCUITPY'
	}],
	'usb_location': '0x14630000'
}]
```

### Other functions of the module

- **`get_devices_list(drive_info=False)`**: return a tuple of the devices list and the list of remaining unidentified serial ports `(devicesList, remainingPorts)`.
- **`get_identified_devices(drive_info=False)`**: only return the devices list.
- **`get_unidentified_ports()`**: only return the unidentified serial ports.
- **`devices_by_name(name)`**: only return the devices where the name contains the given string (not case sensitive).
- **`devices_by_drive(drive_name)`**: only return the devices where the drive name (by default CIRCUITPY) is the given string.
- **`devices_by_serial(serial_number)`**: only return the devices where the serial number is the given string (not case sensitive).

### The DeviceInfoDict class
The device list is actually a list of DeviceInfoDict, a subclass of dictionary that adds a few properties as shortcuts for paths in the dictionary, with a default value of `None`.

Dictionary entries:
- **`name`**: USB name of the board (str).
- **`manufacturer`**: USB manufacturer of the board (str).
- **`product_id`**: USB Product ID (int).
- **`vendor_id`**: USB Vendor ID (int).
- **`serial_num`**: USB Serial number of the board (str).
- **`version`**: Circuitpython version, if found.
- **`ports`**: list of serial ports found on the board, dictionaries.
	- **`dev`**: port device, used to connect to the port. COM port on windows, dev path on linux (str).
	- **`iface`**: interface name for the serial port (str).
- **`volumes`**: list of mounted drives from the board, dictionaries.
	- **`name`**: drive name, usually `CIRCUITPY` for Circuitpython or `*BOOT` for a UF2 bootloader drive (str).
	- **`mount_point`**: mount path of the drive, letter on windows (str).
	- **`mains`**: list of "main" files for Circuitpython (code.py etc.), by order of priority.
- **`usb_location`**: an identifier for the USB port the board is connected to, should be consistent across reboots and modes (bootloader/application). 

Shortcuts:
- **`repl`**: REPL port, if any.
- **`data`**: data port, if any.
- **`drive`** and **`volume`**: board drive path.
- **`volume_name`**: board drive name.
- **`vid`**: shortcut for `vendor_id`.
- **`pid`**: shortcut for `product_id`.


## Screenshots:

Using tio:  
`discotool -n clue`
![discotool repl](https://raw.githubusercontent.com/Neradoc/discotool/main/docs/repl_to_clue.png)

`discotool -n clue circup update`
![discotool circup](https://raw.githubusercontent.com/Neradoc/discotool/main/docs/circup_update_clue.png)

[Samples of what output you can expect from some boards.](https://github.com/Neradoc/discotool/blob/main/docs/examples.md)