> For the complete documentation index, see [llms.txt](https://delta-robotics.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://delta-robotics.gitbook.io/docs/software/thermoflex-python-api/api-reference.md).

# API reference

Complete reference for the ThermoFlex Python API, covering module functions, the Node and Muscle classes, control modes, exceptions, and firmware limits

This page documents the public interface of the ThermoFlex Python API, version 1.0.2. It is organized by workflow: module functions, sessions, the Node class, the Muscle class, logging, control modes, exceptions, and firmware compatibility.

The library is imported under the alias `tf` throughout.

```python
import thermoflex as tf
```

## Module functions

These functions live on the `tf` module. Use them to discover hardware, clean up, and control diagnostics.

| Function                                                      | Returns         | Description                                                                                                  |
| ------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------ |
| `discover(proid=[105])`                                       | `list[NodeNet]` | Scans USB ports for nodes matching the given product IDs. Raises `ImportError` if none are found.            |
| `get_usb_node(bus_id=105, timeout=5.0, poll_interval=0.1)`    | `Node`          | Returns the first active node on the USB bus. Raises `TimeoutError` if none respond within `timeout`.        |
| `endAll(timeout=10.0, force_timeout=2.0, exit_program=False)` | `bool`          | Disables all nodes, stops threads, and closes ports. Call this before your program exits.                    |
| `endsession(session)`                                         | `None`          | Ends and deletes a single session.                                                                           |
| `set_debug_level(level)`                                      | `None`          | Sets console verbosity. `level` is one of `"NONE"`, `"ERROR"`, `"WARNING"`, `"INFO"`, `"DEVICE"`, `"DEBUG"`. |
| `delay(time)`                                                 | `None`          | Deprecated. Use `time.sleep()` instead.                                                                      |

Most single-node programs need only two of these.

```python
node = tf.get_usb_node(timeout=5.0)   # Acquire a node
# ... command the node and its muscles ...
tf.endAll()                           # Required cleanup
```

## Session

A `Session` tracks the serial traffic for one or more networks and is created automatically when you discover hardware. You rarely construct one by hand. Active sessions are available as a list.

```python
sessions = tf.Session.sessionl   # All active sessions
first = sessions[0]
```

Sessions are closed for you by `tf.endAll()`, or individually with `tf.endsession(session)`. File logging built on top of sessions is covered under [Logging and sessions](#logging-and-sessions-experimental).

## Node

A `Node` represents one TF Node Controller. Obtain a node from `get_usb_node()` or from a network returned by `discover()`; do not construct `Node` directly.

### Lifecycle

Each node auto-creates two muscle objects, `node.muscle0` (port 0, M1) and `node.muscle1` (port 1, M2). Attach your own muscle with `attachMuscle`.

```python
muscle = tf.Muscle(_port=0)
node.attachMuscle(muscle, 0)   # (muscle, portNum)
```

### Commanding

| Method                                   | Description                                            |
| ---------------------------------------- | ------------------------------------------------------ |
| `setMode(conmode, device='all')`         | Sets the control mode for one or all muscles.          |
| `setSetpoint(setpoint, conmode, device)` | Sets the target for a muscle. Note the argument order. |
| `enable(muscle)` / `disable(muscle)`     | Enables or disables a single muscle object.            |
| `enableAll()` / `disableAll()`           | Enables or disables every attached muscle.             |
| `reset(device='node')`                   | Resets the node or a named device to defaults.         |
| `setLogmode(mode)`                       | Sets the node logging mode (`0` to `3`).               |

{% hint style="warning" %}
The argument order differs between the two `setSetpoint` methods. `Node.setSetpoint(setpoint, conmode, device)` takes the setpoint first. `Muscle.setSetpoint(conmode, setpoint)` takes the mode first. Passing them in the wrong order sends the wrong command.
{% endhint %}

### Status

Request status with `status(type, device='all')`, where `type` is `'compact'` or `'dump'` and `device` is `'all'`, `'node'`, `'portall'`, `'m1'`, or `'m2'`. Compact status carries live readings; dump status adds configuration and identity fields.

Typed accessors read the cached values. The availability column shows the smallest status type that populates each value.

| Accessor                   | Returns | Unit           | Availability |
| -------------------------- | ------- | -------------- | ------------ |
| `get_supply_voltage()`     | `float` | volts          | compact      |
| `get_uptime()`             | `int`   | milliseconds   | compact      |
| `get_error_code()`         | `int`   | error code     | compact      |
| `get_error_history()`      | `list`  | error codes    | compact      |
| `has_errors()`             | `bool`  | -              | compact      |
| `get_max_current()`        | `float` | amps           | compact      |
| `get_min_supply_voltage()` | `float` | volts          | compact      |
| `get_log_interval()`       | `int`   | milliseconds   | compact      |
| `get_firmware_version()`   | `str`   | version string | dump         |
| `get_board_version()`      | `str`   | version string | dump         |
| `get_can_id()`             | `int`   | CAN ID         | dump         |

```python
node.status('dump', device='node')
print(node.get_firmware_version(), node.get_supply_voltage())
```

### Configuration

`configure_node(config, save_to_flash=False)` applies node-level settings. Pass `save_to_flash=True` to persist them to non-volatile memory. Invalid values raise `ValueError`.

```python
node.configure_node({'heartbeat_enabled': True, 'can_id': 0x123}, save_to_flash=True)
```

## Muscle

A `Muscle` represents one SMA muscle on a node port. Construct it with the port number, then attach it to a node.

```python
muscle = tf.Muscle(_port=0, masternode=None)   # 0 = M1, 1 = M2
muscle.attach(node, 0)                          # or node.attachMuscle(muscle, 0)
```

### Modes and setpoints

| Method                           | Description                                                  |
| -------------------------------- | ------------------------------------------------------------ |
| `setMode(conmode, out=0)`        | Sets the control mode for this muscle.                       |
| `setSetpoint(conmode, setpoint)` | Sets the target. Raises `KeyError` if `setpoint` is omitted. |
| `setEnable(bool)`                | Enables (`True`) or disables (`False`) the muscle.           |
| `status(type='compact')`         | Requests status for this muscle only.                        |

```python
muscle.setMode("percent")
muscle.setSetpoint("percent", 0.5)   # (conmode, setpoint)
muscle.setEnable(True)
```

### Status accessors

| Accessor                 | Returns | Unit           | Availability |
| ------------------------ | ------- | -------------- | ------------ |
| `is_enabled()`           | `bool`  | -              | compact      |
| `get_mode()`             | `int`   | mode enum      | compact      |
| `get_setpoint()`         | `float` | mode-dependent | compact      |
| `get_output_pwm()`       | `float` | PWM output     | compact      |
| `get_current()`          | `float` | amps           | compact      |
| `get_voltage_drop()`     | `float` | volts          | compact      |
| `get_resistance()`       | `float` | milliohms      | compact      |
| `get_default_mode()`     | `int`   | mode enum      | dump         |
| `get_default_setpoint()` | `float` | mode-dependent | dump         |
| `get_pid_kp()`           | `float` | gain           | dump         |
| `get_pid_ki()`           | `float` | gain           | dump         |
| `get_pid_kd()`           | `float` | gain           | dump         |
| `get_sense_resistance()` | `float` | ohms           | dump         |
| `get_train_state()`      | `int`   | state enum     | dump         |

{% hint style="info" %}
`get_resistance()` returns milliohms, while `get_sense_resistance()` returns ohms. Convert before you compare them.
{% endhint %}

### PID configuration

`configure_pid(controller, kp=None, ki=None, kd=None)` sets gains for one controller. `controller` must be `'resistance'` or `'current'`; any other value raises `ValueError`. Only the gains you pass are updated. The muscle must be attached to a node first.

```python
muscle.configure_pid('resistance', kp=2.0, ki=0.1, kd=0.05)
```

`configure_muscle(config, save_to_flash=False)` sets other muscle settings, such as `default_mode` and `default_setpoint`. Invalid values raise `ValueError`.

## Logging and sessions (experimental)

{% hint style="warning" %}
The session and logging tooling is experimental. For production runs, consider adding your own logging in your script.
{% endhint %}

Three logging paths are available.

* **Node logging.** `node.setLogmode(mode)` sets continuous device logging. Modes are `0` (off), `1` (compact), `2` (dump), and `3` (readable dump).
* **File logging.** `session.enable_file_logging()` writes session data to a `./ThermoflexSessions/` folder in your working directory.
* **Analysis.** `SessionAnalyzer().load_session(path)` loads a saved session, and `extract_to_csv(path)` writes the parsed records to CSV.

```python
analyzer = tf.SessionAnalyzer()
analyzer.load_session('ThermoflexSessions/session1log/logdata/logdata.txt')
analyzer.extract_to_csv('session1.csv')
```

## Control modes

Set a mode with `setMode`, then a target with `setSetpoint`. The setpoint's meaning depends on the mode.

| Mode      | Setpoint meaning       | Units            |
| --------- | ---------------------- | ---------------- |
| `percent` | Fraction of full drive | 0.0 to 1.0       |
| `amps`    | Target current         | amps             |
| `volts`   | Target voltage         | volts            |
| `ohms`    | Target resistance      | ohms             |
| `train`   | Training routine       | firmware-defined |
| `count`   | Count-based target     | firmware-defined |

{% hint style="info" %}
`"voltage"` is accepted as an alias for `"volts"`. The `train` and `count` setpoint semantics are firmware-defined and are used in advanced workflows.
{% endhint %}

## Exceptions

| Exception         | Raised when                                                                        |
| ----------------- | ---------------------------------------------------------------------------------- |
| `ImportError`     | `discover()` finds no nodes on USB.                                                |
| `TimeoutError`    | `get_usb_node()` finds no node within the timeout.                                 |
| `KeyError`        | `Muscle.setSetpoint()` is called without a setpoint.                               |
| `ValueError`      | A configuration value is invalid, or `configure_pid()` gets a bad controller name. |
| `SerialException` | The serial port fails to open (raised by `pyserial`).                              |

## Firmware compatibility

The library targets TF Node Controller firmware from **1.1.1** (minimum) to **1.1.2** (maximum tested). On discovery it reads the node's firmware and prints a warning if the version falls outside that window. See [Firmware updates](/docs/software/firmware-updates.md) to bring a node into range.

## Related pages

* [ThermoFlex Python API](/docs/software/thermoflex-python-api.md): install and quickstart
* [Firmware updates](/docs/software/firmware-updates.md): update the TF Node Controller
* [First time flexing your muscles](/docs/tutorials/thermoflex/getting-started-with-our-evaluation-kit/first-time-flexing-your-muscles.md): a worked example
* [TF Node Controller](/docs/hardware/thermoflex/tf-node-controller.md): the hardware behind the API
* [Software roadmap](/docs/software/roadmap.md): tools in development
