# C++ API

`include/mscbxx.h` provides a header-only C++17 façade over the C MSCB library.
It is useful when an application operates on one node and wants typed variable
access without manually calling `mscb_info_variable()`, decoding byte widths,
or managing name-to-index mappings.

The API lives in namespace `midas` and has two public classes:

| Class | Represents |
|---|---|
| `midas::mscb` | A connection to one node through one submaster |
| `midas::u_mscb` | One cached node variable and its read/write conversion logic |

`mscbxx.h` contains the class implementation, but the application must still
link against the `mscb` library because the classes call the C functions in
`mscb.cxx`.

## Simple example

```cpp
#include <iostream>
#include <mscbxx.h>

int main()
{
    try {
        // Connect to node 10 through an Ethernet submaster.
        midas::mscb node("192.0.2.40", 10);

        std::cout << "Connected to " << node.get_node_name() << '\n';

        // Refresh all variables, then access one by name.
        if (node.read_range() != MSCB_SUCCESS) {
            std::cerr << "Could not read node variables\n";
            return 1;
        }

        const int input_index = node.idx("In0");
        const float input = node[input_index].get<float>();
        std::cout << "In0 = " << input
                  << ' ' << node.get_unit_short(input_index) << '\n';

        // Assignment immediately writes the value to the node.
        // Replace DAC0 with a writable variable available on your node.
        node["DAC0"] = 1.25f;
    }
    catch (const std::exception &e) {
        std::cerr << e.what() << '\n';
        return 1;
    }

    return 0;
}
```

Variable names and types are device-specific. Query or print the node before
using the example names:

```cpp
std::cout << node << std::endl;
```

## Building

The repository defines the CMake target `midas::mscb`. When MSCB is included
with `add_subdirectory()`, a program can link it as follows:

```cmake
cmake_minimum_required(VERSION 3.10)
project(mscbxx_example LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_subdirectory(path/to/mscb)
add_executable(mscbxx_example example.cxx)
find_package(Threads REQUIRED)
target_link_libraries(mscbxx_example PRIVATE midas::mscb Threads::Threads)

if(WIN32)
    target_link_libraries(mscbxx_example PRIVATE wsock32 ws2_32)
endif()
```

The target supplies the MSCB include directories. When linking a separately
built library, add `include/` to the compiler's include path and link `mscb`
plus the platform libraries required by that build.

## `midas::mscb`

### Constructor

```cpp
mscb(std::string submaster, int address,
     std::string password = "", int debug = 0);
```

Construction performs all setup synchronously:

1. opens and authenticates the submaster with `mscb_init()`;
2. reads general information for `address`, retrying after a ping if necessary;
3. reads node uptime;
4. reads metadata for every variable and builds the name-to-index map; and
5. calls `read_range()` to populate the variable cache.

Connection, authentication, protocol-version, and missing-node errors throw
`mexception`, which derives from `std::runtime_error`. Catching
`std::exception` is therefore sufficient. The exception text includes the
source location and, on supported systems, a stack trace.

### Node information

```cpp
std::string get_node_name();
std::string get_submaster();
int get_node_address();
int get_protocol_version();
int get_n_variables();
```

These values are cached during construction. Streaming the node prints the
submaster, node and group addresses, protocol version, revision, uptime, and
all cached variables:

```cpp
std::cout << node << std::endl;
```

Streaming does not refresh the node first. Call `read_range()` when fresh
values are required.

### Refresh variables

```cpp
int read_range(int first = 0, int last = 0);
```

Reads an inclusive range with `mscb_read_range()` and updates the corresponding
`u_mscb` cache entries. With the default arguments `(0, 0)`, it reads every
variable. The return value is an `MSCB_*` status.

The implementation uses a 1024-byte local receive buffer, so the packed range
must fit within 1024 bytes. Because `(0, 0)` means “all,” this method cannot be
used to request only variable zero; use the C API when that distinction is
needed.

### Access by index or name

```cpp
u_mscb &operator[](int index);
u_mscb &operator[](std::string variable_name);
int idx(const std::string &variable_name);
std::vector<u_mscb> vec(int first, int last);
```

`operator[]` returns a reference to the cached variable. Integer access checks
the range. Name access calls `idx()`, which throws when a name is not present.
Names are case-sensitive. The current map construction terminates names at
eight characters, so named access should use the first eight characters
reported for a variable.

`vec()` returns copies of an inclusive range of cached variables. It performs
no bounds checking; prefer checked `operator[]` access unless the indexes have
already been validated.

### Units

```cpp
std::string get_unit(int index);
std::string get_unit_short(int index);
```

Returns the variable's metric prefix and unit using the long or abbreviated
unit tables. Both methods validate the index and throw on an invalid value.

### Retry controls

```cpp
void set_max_retry(int count);
void set_max_eth_retry(int count);
```

`set_max_retry()` changes the library-wide MSCB operation retry count.
`set_max_eth_retry()` changes the Ethernet retry count for this object's
descriptor.

## `midas::u_mscb`

A `u_mscb` stores the descriptor, node address, variable index, inferred type,
width, and most recently read or assigned value. Applications normally obtain
one from `mscb::operator[]` rather than constructing it directly.

Its public low-level operations are:

```cpp
u_mscb(int fd, int address, int index, int tid, int size);
int get_tid();
void set_string(char *value);
void set_data(const unsigned char *data);
int write();
```

`set_string()` and `set_data()` update only the cache. `write()` sends the
cached value. These methods are used by `midas::mscb`; direct construction is
rarely necessary because callers would have to reproduce its metadata-to-type
mapping themselves.

### Reading cached values

```cpp
template<typename T> T get();
void get(std::string &value);
template<typename T> operator T();
operator std::string();
int get_tid();
```

`get<T>()` and the conversion operator convert the cached value to the
requested C++ type. They do not contact the node. The string overload formats
numeric and Boolean variables and returns string variables unchanged.

Examples:

```cpp
node.read_range(0, 7);

float temperature = node[2].get<float>();
int state = node[3];
std::string label = node["Label"];
std::cout << node[2] << '\n';
```

An incompatible conversion throws `mexception`.

### Writing through assignment

Assignments update the cache and immediately call `mscb_write()`:

```cpp
node[5] = 1.234f;
node["Enabled"] = true;
node["Label"] = std::string("ready");
```

Overloads exist for 8-, 16-, and 32-bit signed and unsigned integers, `bool`,
`float`, `double`, C strings, and `std::string`. Numeric assignments convert to
the target variable's inferred type and can narrow the supplied value.

The current `u_mscb::write()` returns `1` after calling `mscb_write()` and does
not propagate the underlying write status. Use the C API directly when the
application must confirm the acknowledgement or distinguish write failures.

## Type inference

The constructor maps MSCB metadata to MIDAS type identifiers:

| Variable metadata | Type identifier |
|---|---|
| Unit is `UNIT_STRING` | `TID_STRING` |
| `MSCBF_FLOAT` is set | `TID_FLOAT` |
| Signed, width 1/2/4 | `TID_INT8` / `TID_INT16` / `TID_INT32` |
| Unsigned, width 1/2/4 | `TID_UINT8` / `TID_UINT16` / `TID_UINT32` |

The header also defines `TID_BOOL`, `TID_DOUBLE`, and deprecated aliases such
as `TID_BYTE`, `TID_WORD`, and `TID_DWORD`. Automatic node discovery does not
map 3-byte or 8-byte integer variables to a usable `u_mscb` type. Use the
[C API](c-api.md) for variable layouts outside the table above.

## Lifetime and concurrency

The current `midas::mscb` class has no explicit `close()` method and no
destructor that calls `mscb_exit()`. It is best suited to an object that lives
for the duration of the process. Applications that repeatedly create and
destroy connections, or that require deterministic connection cleanup, should
manage the descriptor through the C API.

The underlying C library serializes access to a descriptor, but cached values
inside `midas::mscb` and `u_mscb` are not independently synchronized. Protect a
shared C++ object when several application threads may refresh or assign its
variables concurrently.
