# C++: API Reference

Reference for the `Synergy` C++ library. See {doc}`examples` for a tutorial
introduction.

```cpp
#include <Synergy.h>
```

The library creates a global instance named `SL`. You do not construct one.

---

## Initialisation

```cpp
uint8_t init();
```
Sign on the modules. Returns the number of slots that responded. The idiom is
`while (!SL.init());`, which blocks until the backplane is ready.

```cpp
void enableBaseController(bool state);
```
Enable or disable the base controller. `init()` calls this for you.

```cpp
bool isBaseActive();
```
`true` if the base controller is configured and responding. If it returns
`false`, call `init()` again.

```cpp
uint16_t rollCall(const char* moduleNames[], uint8_t numberOfModules);
```
Compare an array of expected module names against what signed on. Returns 0 when
every slot matches. Otherwise returns a bitmap of mismatched slots: bit 0 is
slot 1, so mismatches in slots 1 and 3 return `0x05`. Each mismatched slot is
disabled and no longer exchanges data.

```cpp
uint8_t checkConnection(uint8_t numberOfModules = 0);
```
Check whether any module has dropped off the bus. Returns the first missing
module, or 0 if all present.

```cpp
uint8_t printModules();
```
Print every signed-on module.

```cpp
uint32_t getFwVersion();
```
Base controller firmware version.

---

## Discrete I/O

```cpp
uint32_t readDiscrete(uint8_t slot, uint8_t channel = 0);
void     writeDiscrete(uint32_t data, uint8_t slot, uint8_t channel = 0);
```

Channel `0`, the default, operates on **every channel of the slot at once** as
a bitmap, in a single bus transaction. Pass a channel number to address one
channel.

```cpp
uint32_t all = SL.readDiscrete(1);        // whole slot as a bitmap
uint32_t ch3 = SL.readDiscrete(1, 3);     // channel 3 only

SL.writeDiscrete(0xFF, 2);                // first eight outputs on
SL.writeDiscrete(1, 2, 3);                // channel 3 on
```

```{admonition} Argument order
:class: caution

`writeDiscrete` and `writeAnalog` take the **data first**, then slot, then
channel. `readDiscrete` and `readAnalog` take slot then channel.

Transposing these is the most common mistake with this API, and it compiles
cleanly because everything is an integer.
```

---

## Analog I/O

```cpp
int   readAnalog(uint8_t slot, uint8_t channel);
void  writeAnalog(uint32_t data, uint8_t slot, uint8_t channel);
float readTemperature(uint8_t slot, uint8_t channel);
```

`readAnalog` returns counts at the module's native resolution, 0-8191 for a
13-bit input. `writeAnalog` expects the same, 0-4095 for a 12-bit output.
Values are masked on the base controller.

`readTemperature` returns a `float` in the module's configured unit. No scaling
needed.

```{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

```cpp
uint8_t checkUnderRange(uint8_t slot, uint8_t channel = 0);
uint8_t checkOverRange(uint8_t slot, uint8_t channel = 0);
uint8_t checkBurnout(uint8_t slot, uint8_t channel = 0);
uint8_t check24V(uint8_t slot);
```

Each returns 1 if the condition is present and the module supports reporting it.
Channel `0` tests whether *any* channel on the module reports it.

```cpp
char readStatus(int byteNum, int slot);
void readStatus(char buf[], uint8_t slot);
```

Raw status bytes: one byte, or all of them into a caller-supplied buffer.

---

## Configuration

```cpp
bool configureModule(char cfgData[], uint8_t slot);
bool configureModule(const char cfgData[], uint8_t slot);
void readModuleConfig(char cfgData[], uint8_t slot);
```

Send a configuration block to a module, or read back what is stored.
`readModuleConfig` reports the stored configuration, which is not a guarantee
that it has been applied.

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

---

## Watchdog

```cpp
void configWD(uint16_t milliseconds, uint8_t toggle);
void startWD();
void stopWD();
void petWD();
```

`toggle` selects the reset behaviour: reset and recover, or hold in reset until
24 V is cycled.

Any bus read or write pets the watchdog implicitly. Call `petWD()` explicitly
around code paths that might not touch the bus.

---

## Block transfers

```cpp
void readBlockData(char* buf, uint16_t len, uint16_t offset, uint8_t type);
void writeBlockData(char* buf, uint16_t len, uint16_t offset, uint8_t type);
```

Read or write the raw I/O image directly, bypassing per-channel access. This
is the fastest way to move a large number of points in one transaction, and
also the least forgiving, since you are responsible for the offsets and the byte
layout.

Use it where a large rail must be serviced within a tight cycle. See the
`examples/Utility/BlockDataTransfer` example on the device.

---

## Slot properties

```cpp
moduleProps readSlotProps(uint8_t slot);
```

Returns the properties of the module in the slot, including byte counts, data
sizes and capabilities. Useful for code that adapts to the rail rather than
assuming a fixed arrangement.

---

## Named channels

```cpp
struct channelLabel {
    uint8_t slot;
    uint8_t channel;
};
```

Every I/O function has an overload taking a `channelLabel` in place of separate
slot and channel arguments:

```cpp
uint32_t readDiscrete(channelLabel label);
void     writeDiscrete(uint32_t data, channelLabel label);
int      readAnalog(channelLabel label);
void     writeAnalog(uint32_t data, channelLabel label);
float    readTemperature(channelLabel label);
uint8_t  checkUnderRange(channelLabel label);
uint8_t  checkOverRange(channelLabel label);
uint8_t  checkBurnout(channelLabel label);
```

Declare labels once at the top of the file:

```cpp
const channelLabel StartButton = {1, 1};
const channelLabel MotorRun    = {2, 1};

SL.writeDiscrete(SL.readDiscrete(StartButton), MotorRun);
```

This is the recommended style for anything beyond a brief test. It is
self-documenting, and a change to the rail requires a single edit.

---

## Hardware constants

Defined in `RPi.h`:

```cpp
#define SPI_DEVPATH   "/dev/spidev6.0"
#define GPIO_DEVPATH  "/dev/gpiochip0"
```

The GPIO assignments are chip select 27, acknowledge 25 and base enable 17.
The OpenPLC Runtime drives the same pins, which is why only one of the two may
run at a time. See {doc}`../shared-hardware-rules`.
