# Python: API Reference

Reference for the `synergy_logic` package. See {doc}`first-program` for a
tutorial introduction.

```python
import synergy_logic
```

---

## `Base`

The backplane. Constructing one signs on every module and builds the object
tree.

```python
synergy_logic.Base(*, zero_indexing=False, cs_pin=..., ack_pin=..., spi=..., en_pin=...)
```

All parameters are keyword-only and every one has a default that is correct for
SLM-RP4 hardware. In practice you write `Base()`.

| Parameter | Default | Purpose |
|---|---|---|
| `zero_indexing` | `False` | Number slots and channels from 0 instead of 1 |
| `cs_pin` | `board.D27` | Chip select |
| `ack_pin` | `board.D25` | Acknowledge |
| `spi` | `SPI(6, 0)` | The `/dev/spidev6.0` bus |
| `en_pin` | `board.D17` | Base enable |

### Indexing

```python
base[1]        # IO_Module for slot 1
```

### Methods

`init()`
: Re-run sign-on. Called automatically by the constructor.

`deinit()`
: Release the base controller.

`print_modules()`
: Print each slot and its module name.

`rollCall(module_names)`
: Compare a list of expected module names against those that signed on. Raises
  `ValueError` reporting each mismatch, the module expected and its position in
  the list. Call this at startup in any program that assumes a particular
  rail.

  The exception text reads `... in slot 0` but the number is the **zero-based
  index into the list you passed**, not a slot number. A mismatch on the module
  in slot 1 is reported as slot 0.

  ```python
  base.rollCall(["SLM-DCI-16NP", "SLM-DCO-15P", "SLM-AI-8-V"])
  ```

### Properties

`is_active`
: `True` if the base controller is initialised and responding.

### Watchdog

`config_watchdog(milliseconds, mode="TOGGLE")`
: Set the timeout. `mode` is `"TOGGLE"` (reset the controller and recover) or
  `"HOLD"` (hold in reset until 24 V is cycled).

`start_watchdog()` / `stop_watchdog()`
: Start and stop the timer.

`pet_watchdog()`
: Reset the timer manually. Any bus read or write does this implicitly.

### C-style API

An alternative to the object model, matching the form of the C++ library.

```python
base.writeDiscrete(data, slot, channel=None)
base.readDiscrete(slot, channel=None)
base.writeAnalog(data, slot, channel)
base.readAnalog(slot, channel)
base.readTemperature(slot, channel)
base.readStatus(slot)
```

Passing `channel=None` to the discrete calls operates on every channel of the
slot at once, as a bitmap.

---

## `IO_Module`

One module in one slot. Obtained from `base[slot]`.

### Indexing

```python
module[2]           # IO_Channel for channel 2
module[1:]          # every channel, as a list
```

Combo modules, which carry both inputs and outputs, raise `ValueError` on
both forms — a slice is as ambiguous as a single index. Use `module.inputs[n]`
and `module.outputs[n]`, which accept an index or a slice:

```python
combo.outputs[1]    # one channel
combo.inputs[1:]    # every input channel
```

### Value properties

| Property | Read | Write | Notes |
|---|---|---|---|
| `values` | ✓ | ✓ | List of every channel's value. Not valid on combo modules. |
| `reals` | ✓ | ✓ | Same, in engineering units. |
| `input_values` | ✓ | | Combo-safe |
| `output_values` | ✓ | ✓ | Combo-safe |
| `input_reals` | ✓ | | Combo-safe |
| `output_reals` | ✓ | ✓ | Combo-safe |

```python
module.values = [1, 0, 1, 0, 1, 0, 1, 0]
module.reals  = [5.0, 2.5, 0.0, 10.0]
```

### Bitmapped access

`do_bitmapped(states)`
: Write every discrete output from one integer. `0xAA` sets every even channel.

`di_bitmapped()`
: Read every discrete input as one integer.

`status_bitmapped()`
: Read the module's status bytes as one integer.

These are a single bus transaction, so they are markedly faster than looping
over channels.

### Status properties

| Property | Meaning |
|---|---|
| `missing24` | Field supply absent |
| `not_ready` | Module still initialising |
| `over_range(channel=None)` | Input above range |
| `under_range(channel=None)` | Input below range |
| `burnout(channel=None)` | Open circuit |

Omit `channel` to test whether *any* channel on the module reports the
condition.

### Configuration

`configure_module(config_data)`
: Send a configuration block. Factory defaults are applied automatically at
  sign-on; this overrides them.

  ```python
  module.configure_module((0x40, 0x03))   # channels 1-4 only
  ```

  Block layouts and field values: {doc}`../module-configuration`.

### Other

`print_state()`
: Print every channel's current reading.

  **Broken on combo modules.** It prints the slot header, then raises
  `AttributeError: 'NoneType' object has no attribute 'channel'`. Read
  `input_values` and `output_values` instead.

---

## `IO_Channel`

A single channel. Obtained from `module[channel]`.

### Properties

`value`
: The channel's value, read or written.

  - Discrete: `True` / `False`
  - Analog: raw counts at the module's native resolution, 0-8191 for a 13-bit
    input and 0-4095 for a 12-bit output.

`real`
: The value in engineering units, such as volts, milliamps or degrees. Writing
  converts to the nearest available count.

```python
channel.value = True
counts = channel.value

channel.real = 5.0
volts = channel.real
```

```{admonition} Scale against the module's own resolution
:class: important

Counts are reported at the resolution of the module, not at a fixed width.
{doc}`../../hardware/module-catalog` lists the resolution of every module.
```

### Status properties

`over_range`, `under_range` and `burnout`, per channel, read-only.

---

## `slm_rp4_helpers`

A small companion package for the SLM-RP4's own peripherals.

```python
from slm_rp4_helpers import read_switch_state
```

`read_switch_state()`
: Read the front-panel toggle switch (GPIO 13). Returns `0` or `1`.

See {doc}`../onboard-peripherals`.

---

## Exceptions

`ValueError`
: Raised by `rollCall()` on a mismatch, and by `values`, `reals` or plain
  indexing on a combo module.

`TypeError`
: Raised by `rollCall()` if not given a list or tuple of strings.

The library does **not** raise on a bus timeout. A failed transfer generally
appears as a stale or zero reading, so validate values that matter rather than
relying on an exception.
