# C++: Examples

Complete programs. Build each as described in {doc}`building`, and stop the
PLC first. See {doc}`../shared-hardware-rules`.

Further examples are supplied on the device under
`~/Desktop/synergy_cpp/examples/`, in the `Basic`, `Utility` and `Watchdog`
folders.

## The shape of every program

```cpp
#include <Synergy.h>

int main() {
    while (!SL.init());     // wait for the modules to sign on

    while (true) {
        // your control loop
    }
}
```

`SL` is a global instance created by the library. You do not construct it.
`init()` returns the number of slots that signed on, so `while (!SL.init())`
blocks until the backplane is ready.

## Blink an output

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

int main() {
    while (!SL.init());

    while (true) {
        SL.writeDiscrete(1, 1, 2);   // slot 1, channel 2, on
        usleep(1 * 1000000);
        SL.writeDiscrete(0, 1, 2);   // off
        usleep(1 * 1000000);
    }
}
```

Argument order is `(data, slot, channel)`, with the value first. Transposing
these arguments is a common mistake, and it compiles cleanly because every
parameter is an integer.

## List the modules

```cpp
#include <Synergy.h>

int main() {
    while (!SL.init());
    SL.printModules();
    return 0;
}
```

## Read discrete inputs

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

int main() {
    while (!SL.init());

    while (true) {
        // One channel
        uint32_t ch1 = SL.readDiscrete(1, 1);

        // Every channel of the slot at once -- channel 0 means "all"
        uint32_t all = SL.readDiscrete(1, 0);

        printf("ch1=%u  all=0x%08X\n", ch1, all);
        usleep(500000);
    }
}
```

Passing channel `0` to the discrete calls operates on the whole slot as a
bitmap, in one bus transaction. Use it when you need several channels, as it
is considerably cheaper than one call per channel.

## Read an analog input

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

int main() {
    while (!SL.init());

    while (true) {
        int counts = SL.readAnalog(1, 1);          // slot 1, channel 1

        // SLM-AI-8-V: 13-bit native, 0-10 V
        float volts = counts / 8191.0f * 10.0f;

        printf("%5d counts  %6.3f V\n", counts, volts);
        usleep(500000);
    }
}
```

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

`readAnalog()` returns counts at the module's native resolution, which is
0-8191 for a 13-bit input. `writeAnalog()` expects the same, 0-4095 for a
12-bit output.

{doc}`../../hardware/module-catalog` lists the resolution of every module.
```

## Write an analog output

```cpp
// SLM-AO-8-V: 12-bit native, 0-10 V
float volts = 5.0f;
uint32_t counts = (uint32_t)(volts / 10.0f * 4095.0f);

SL.writeAnalog(counts, 1, 1);
```

## Read a temperature

```cpp
float temp = SL.readTemperature(1, 1);   // slot 1, channel 1
printf("%.2f degrees\n", temp);
```

The thermocouple module returns a `float` in the configured unit directly. No
scaling, no counts.

## Verify the rail at startup

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

const char* expected[] = {
    "SLM-DCI-16NP",
    "SLM-DCO-15P",
    "SLM-AI-8-V",
};

int main() {
    while (!SL.init());

    uint16_t mismatch = SL.rollCall(expected, 3);
    if (mismatch != 0) {
        for (int slot = 1; slot <= 3; slot++) {
            if (mismatch & (1 << (slot - 1))) {
                printf("backplane does not match: slot %d\n", slot);
            }
        }
        return 1;
    }

    // safe to proceed
}
```

Verifying the rail at startup turns a misconfigured stack into an explicit
failure rather than silently wrong readings.

## Named channels

Rather than tracking slot and channel numbers through the program, name them:

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

const channelLabel StartButton = {1, 1};   // slot 1, channel 1
const channelLabel MotorRun    = {2, 1};
const channelLabel TankLevel   = {3, 1};

int main() {
    while (!SL.init());

    while (true) {
        uint32_t start = SL.readDiscrete(StartButton);
        int level      = SL.readAnalog(TankLevel);

        SL.writeDiscrete(start && level < 6000, MotorRun);
        usleep(10000);
    }
}
```

Every I/O function has a `channelLabel` overload. Using them means a change to
the rail requires editing only the labels at the top of the file.

## Status checks

```cpp
if (SL.check24V(1))            printf("slot 1: field supply missing\n");
if (SL.checkOverRange(3, 1))   printf("slot 3 ch1: over range\n");
if (SL.checkUnderRange(3, 1))  printf("slot 3 ch1: under range\n");
if (SL.checkBurnout(4, 1))     printf("slot 4 ch1: burnout\n");

uint8_t missing = SL.checkConnection();
if (missing) printf("slot %u has dropped off the bus\n", missing);
```

`checkConnection()` returns the first missing module, or 0 if all present. Call
it periodically in any program that has to survive a module coming loose.

```{admonition} The argument is a count, not a slot
:class: caution

The signature is `checkConnection(uint8_t numberOfModules = 0)`. Every other
status call on this page takes a slot first, so `checkConnection(3)` looks like
"check slot 3" and is not — it means "expect three modules". Call it with no
argument to check everything that signed on.
```

## Watchdog

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

int main() {
    while (!SL.init());

    SL.configWD(1000, 1);    // 1000 ms timeout, toggle mode
    SL.startWD();

    while (true) {
        doTheControlWork();
        SL.petWD();
        usleep(100000);
    }
}
```

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

## A complete control loop

Everything together: verify the rail, run at a fixed period, handle signals, and
fail safe.

```cpp
#include <Synergy.h>
#include <csignal>
#include <cstdio>
#include <ctime>

const channelLabel LevelSensor = {1, 1};
const channelLabel ValveOutput = {2, 1};

const char* expected[] = { "SLM-AI-8-V", "SLM-AO-8-V" };

volatile sig_atomic_t running = 1;

void handleSignal(int) { running = 0; }

int main() {
    signal(SIGINT,  handleSignal);
    signal(SIGTERM, handleSignal);

    while (!SL.init());

    if (SL.rollCall(expected, 2) != 0) {
        fprintf(stderr, "backplane mismatch\n");
        return 1;
    }

    SL.configWD(500, 1);
    SL.startWD();

    // Absolute-deadline scheduling: the loop period does not drift
    // by however long the previous iteration happened to take.
    struct timespec next;
    clock_gettime(CLOCK_MONOTONIC, &next);
    const long periodNs = 10 * 1000 * 1000;   // 10 ms

    while (running) {
        int level = SL.readAnalog(LevelSensor);          // 0-8191
        uint32_t cmd = (uint32_t)(level / 8191.0f * 4095.0f);
        SL.writeAnalog(cmd, ValveOutput);

        next.tv_nsec += periodNs;
        if (next.tv_nsec >= 1000000000L) {
            next.tv_nsec -= 1000000000L;
            next.tv_sec++;
        }
        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, nullptr);
    }

    SL.writeAnalog(0, ValveOutput);   // fail closed
    SL.stopWD();
    printf("stopped, valve closed\n");
    return 0;
}
```

Two elements of this example apply to any program.

**Absolute-deadline timing.** `usleep(10000)` sleeps for 10 ms *after* the
work, so the real period becomes the execution time plus 10 ms, and it drifts.
Computing the next deadline and sleeping until it keeps the period fixed.

**A signal handler and a fail-safe exit.** A service manager sends `SIGTERM`
when stopping a program.
Without a handler the process dies immediately and the valve stays wherever it
was. This is the guarantee you give up by leaving the PLC runtime, and you have
to put it back yourself.

## Next

{doc}`api-reference`, for the complete API.
