datactl/datatypes.h

datactl/datatypes.h

datactl/datatypes.h

Namespaces

Name
Syntalos

Classes

Name
structSyntalos::supports_buffer_reuse
structSyntalos::supports_buffer_reuse< Frame >
structSyntalos::BaseDataType
Base interface for all data types.
structSyntalos::ControlCommand
A control command to a module.
structSyntalos::TableRow
A new row for a table.
structSyntalos::LineCommand
Command issued to a hardware signal line / channel / pin.
structSyntalos::LineReading
Scalar reading from a hardware signal line at a moment in time.
structSyntalos::SignalBlockI32
A block of 32-bit signed integer signal data with timestamps.
structSyntalos::SignalBlockU16
A block of 16-bit unsigned integer signal data with timestamps.
structSyntalos::SignalBlockF32
A block of 32-bit floating-point timestamped signal data.

Defines

Name
SY_DEFINE_DATA_TYPE(TypeName)
Helper macro to define a Syntalos stream data type.

Macros Documentation

define SY_DEFINE_DATA_TYPE

#define SY_DEFINE_DATA_TYPE(
    TypeName
)
public:                                                  \
    BaseDataType::TypeId typeId() const override         \
    {                                                    \
        return BaseDataType::TypeName;                   \
    }                                                    \
    static constexpr BaseDataType::TypeId staticTypeId() \
    {                                                    \
        return BaseDataType::TypeName;                   \
    }                                                    \
    static constexpr const char *staticTypeName()        \
    {                                                    \
        return #TypeName;                                \
    }                                                    \
                                                         \
private:                                                 \
    TypeName(const TypeName &) = default;                \
    TypeName &operator=(const TypeName &) = default;     \
                                                         \
public:                                                  \
    TypeName(TypeName &&) noexcept = default;            \
    TypeName &operator=(TypeName &&) noexcept = default; \
    [[nodiscard]] TypeName clone() const                 \
    {                                                    \
        return TypeName(*this);                          \
    }

Helper macro to define a Syntalos stream data type.

Source code

/*
 * Copyright (C) 2019-2026 Matthias Klumpp <matthias@tenstral.net>
 *
 * Licensed under the GNU Lesser General Public License Version 3
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the license, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <memory>
#include <string>
#include <vector>
#include <concepts>
#include <charconv>
#include <cstdint>
#include <cassert>
#include <array>
#include <cmath>
#include <type_traits>
#include <tuple>
#include <utility>

#include "datactl/syclock.h"
#include "datactl/binarystream.h"
#include "datactl/eigenaux.h"
#include "datactl/flags.h"

namespace Syntalos
{

template<typename T>
struct supports_buffer_reuse : std::false_type {
};

// Frame carries a cv::Mat whose pixel buffer can be reused
struct Frame;
template<>
struct supports_buffer_reuse<Frame> : std::true_type {
};

enum class ModuleState : uint8_t {
    UNKNOWN,      
    INITIALIZING, 
    IDLE,         
    PREPARING,    
    DORMANT,      
    READY,        
    RUNNING,      
    ERROR         
};

std::string toString(ModuleState state);

struct BaseDataType {
public:
    virtual ~BaseDataType() = default;

protected:
    BaseDataType() = default;
    BaseDataType(const BaseDataType &) = default;
    BaseDataType &operator=(const BaseDataType &) = default;
    BaseDataType(BaseDataType &&) noexcept = default;
    BaseDataType &operator=(BaseDataType &&) noexcept = default;

public:
    enum TypeId {
        Unknown,
        ControlCommand,
        TableRow,
        Frame,
        LineCommand,
        LineReading,
        SignalBlockI32,
        SignalBlockU16,
        SignalBlockF32,
        Last
    };

    static bool typeIdIsValid(int value)
    {
        return value >= 1 && value < TypeId::Last;
    }

    static std::string typeIdToString(int value);
    static std::string typeIdToString(TypeId value);
    static TypeId typeIdFromString(const std::string &str);

    [[nodiscard]] virtual TypeId typeId() const = 0;

    [[nodiscard]] virtual ssize_t memorySize() const
    {
        // Size is not known in advance
        return -1;
    }

    virtual bool writeToMemory(void *memory, ssize_t size = -1) const
    {
        return false;
    };

    virtual bool toBytes(ByteVector &output) const = 0;
};

#define SY_DEFINE_DATA_TYPE(TypeName)                    \
public:                                                  \
    BaseDataType::TypeId typeId() const override         \
    {                                                    \
        return BaseDataType::TypeName;                   \
    }                                                    \
    static constexpr BaseDataType::TypeId staticTypeId() \
    {                                                    \
        return BaseDataType::TypeName;                   \
    }                                                    \
    static constexpr const char *staticTypeName()        \
    {                                                    \
        return #TypeName;                                \
    }                                                    \
                                                         \
private:                                                 \
    TypeName(const TypeName &) = default;                \
    TypeName &operator=(const TypeName &) = default;     \
                                                         \
public:                                                  \
    TypeName(TypeName &&) noexcept = default;            \
    TypeName &operator=(TypeName &&) noexcept = default; \
    [[nodiscard]] TypeName clone() const                 \
    {                                                    \
        return TypeName(*this);                          \
    }

template<typename T>
concept supports_explicit_clone = requires(const T &obj) {
    { obj.clone() } -> std::same_as<T>;
};

template<typename T>
constexpr int syDataTypeId()
    requires std::is_base_of_v<BaseDataType, T>
{
    return T::staticTypeId();
}

template<typename T>
T deserializeFromMemory(const void *memory, size_t size)
    requires std::is_base_of_v<BaseDataType, T>
{
    return T::fromMemory(memory, size);
}

enum class ControlCommandKind {
    UNKNOWN,
    START, 
    PAUSE, 
    STOP,  
    STEP,  
    CUSTOM
};

// Forward declarations
struct Frame;
struct SignalBlockU16;

struct ControlCommand : BaseDataType {
    SY_DEFINE_DATA_TYPE(ControlCommand)

    ControlCommandKind kind{ControlCommandKind::UNKNOWN}; 
    milliseconds_t duration; 
    std::string command;     

    explicit ControlCommand()
        : duration(0)
    {
    }
    explicit ControlCommand(ControlCommandKind ckind)
        : kind(ckind),
          duration(0)
    {
    }

    void setDuration(ulong value)
    {
        duration = milliseconds_t(value);
    }

    [[nodiscard]] ulong getDurationAsInt() const
    {
        return duration.count();
    }

    bool toBytes(ByteVector &output) const override
    {
        BinaryStreamWriter stream(output);

        stream.write(kind);
        stream.write(static_cast<uint64_t>(duration.count()));
        stream.write(command);

        return true;
    }

    static ControlCommand fromMemory(const void *memory, size_t size)
    {
        ControlCommand obj;
        BinaryStreamReader stream(memory, size);

        uint64_t durationValue;
        stream.read(obj.kind);
        stream.read(durationValue);
        stream.read(obj.command);
        obj.duration = milliseconds_t(durationValue);

        return obj;
    }
};

struct TableRow : BaseDataType {
    SY_DEFINE_DATA_TYPE(TableRow)

    std::vector<std::string> data;

    explicit TableRow() = default;
    explicit TableRow(const std::vector<std::string> &row)
        : data(row)
    {
    }

    void reserve(int size)
    {
        data.reserve(size);
    }

    void append(const std::string &t)
    {
        data.push_back(t);
    }

    [[nodiscard]] int length() const
    {
        return data.size();
    }

    bool toBytes(ByteVector &output) const override
    {
        BinaryStreamWriter stream(output);

        stream.write(data);

        return true;
    }

    static TableRow fromMemory(const void *memory, size_t size)
    {
        TableRow obj;
        BinaryStreamReader stream(memory, size);

        stream.read(obj.data);

        return obj;
    }
};

enum class LineCommandKind {
    UNKNOWN,
    SET_MODE,            
    WRITE_DIGITAL,       
    WRITE_ANALOG,        
    WRITE_DIGITAL_PULSE, 
    WRITE_ANALOG_PULSE,  
    CUSTOM               
};

enum class LineModeFlag : uint32_t {
    NONE = 0,
    IS_INPUT = 1u << 0,  
    IS_OUTPUT = 1u << 1, 
    PULL_UP = 1u << 2    
};

using LineModeFlags = Flags<LineModeFlag>;

struct LineCommand : BaseDataType {
    SY_DEFINE_DATA_TYPE(LineCommand)

    LineCommandKind kind{LineCommandKind::UNKNOWN};
    uint16_t lineId{0};                      
    uint32_t value{0};                       
    microseconds_t duration{};               
    LineModeFlags flags{LineModeFlag::NONE}; 
    ByteVector extra;                        

    explicit LineCommand() = default;

    explicit LineCommand(LineCommandKind k)
        : kind(k)
    {
    }

    LineCommand(LineCommandKind k, uint16_t line)
        : kind(k),
          lineId(line)
    {
    }

    LineCommand(LineCommandKind k, uint16_t line, uint32_t v)
        : kind(k),
          lineId(line),
          value(v)
    {
    }

    bool toBytes(ByteVector &output) const override
    {
        BinaryStreamWriter stream(output);

        stream.write(kind);
        stream.write(lineId);
        stream.write(value);
        stream.write(static_cast<int64_t>(duration.count()));
        stream.write(flags.toInt());
        stream.write(extra);

        return true;
    }

    static LineCommand fromMemory(const void *memory, size_t size)
    {
        LineCommand obj;
        BinaryStreamReader stream(memory, size);

        int64_t durationUs;
        uint32_t flagsRaw;
        stream.read(obj.kind);
        stream.read(obj.lineId);
        stream.read(obj.value);
        stream.read(durationUs);
        stream.read(flagsRaw);
        stream.read(obj.extra);
        obj.duration = microseconds_t(durationUs);
        obj.flags = LineModeFlags(flagsRaw);

        return obj;
    }
};

struct LineReading : BaseDataType {
    SY_DEFINE_DATA_TYPE(LineReading)

    uint16_t lineId{0};
    uint32_t value{0};
    microseconds_t time{};

    explicit LineReading() = default;

    bool toBytes(ByteVector &output) const override
    {
        BinaryStreamWriter stream(output);

        stream.write(lineId);
        stream.write(value);
        stream.write(static_cast<int64_t>(time.count()));

        return true;
    }

    static LineReading fromMemory(const void *memory, size_t size)
    {
        LineReading obj;
        BinaryStreamReader stream(memory, size);

        int64_t timeUs;
        stream.read(obj.lineId);
        stream.read(obj.value);
        stream.read(timeUs);
        obj.time = microseconds_t(timeUs);

        return obj;
    }
};

enum class SignalDataType {
    Amplifier,
    AuxInput,
    SupplyVoltage,
    BoardAdc,
    BoardDigIn,
    BoardDigOut
};

struct SignalBlockI32 : BaseDataType {
    SY_DEFINE_DATA_TYPE(SignalBlockI32)

    VectorXu64 timestamps;
    MatrixXi32 data;

    explicit SignalBlockI32(uint sampleCount = 60, uint channelCount = 1)
    {
        assert(channelCount > 0);
        timestamps.resize(sampleCount);
        data.resize(sampleCount, channelCount);
    }

    explicit SignalBlockI32(struct SignalBlockU16 &&src);

    [[nodiscard]] size_t length() const
    {
        return timestamps.size();
    }

    [[nodiscard]] size_t rows() const
    {
        return data.rows();
    }

    [[nodiscard]] size_t cols() const
    {
        return data.cols();
    }

    bool toBytes(ByteVector &output) const override
    {
        BinaryStreamWriter stream(output);

        serializeEigen(stream, timestamps);
        serializeEigen(stream, data);

        return true;
    }

    static SignalBlockI32 fromMemory(const void *memory, size_t size)
    {
        SignalBlockI32 obj;
        BinaryStreamReader stream(memory, size);

        obj.timestamps = deserializeEigen<VectorXu64>(stream);
        obj.data = deserializeEigen<MatrixXi32>(stream);

        return obj;
    }
};

struct SignalBlockU16 : BaseDataType {
    SY_DEFINE_DATA_TYPE(SignalBlockU16)

    VectorXu64 timestamps;
    MatrixXu16 data;

    explicit SignalBlockU16(uint sampleCount = 60, uint channelCount = 1)
    {
        assert(channelCount > 0);
        timestamps.resize(sampleCount);
        data.resize(sampleCount, channelCount);
    }

    explicit SignalBlockU16(struct SignalBlockI32 &&src);

    [[nodiscard]] size_t length() const
    {
        return timestamps.size();
    }

    [[nodiscard]] size_t rows() const
    {
        return data.rows();
    }

    [[nodiscard]] size_t cols() const
    {
        return data.cols();
    }

    bool toBytes(ByteVector &output) const override
    {
        BinaryStreamWriter stream(output);

        serializeEigen(stream, timestamps);
        serializeEigen(stream, data);

        return true;
    }

    static SignalBlockU16 fromMemory(const void *memory, size_t size)
    {
        SignalBlockU16 obj;
        BinaryStreamReader stream(memory, size);

        obj.timestamps = deserializeEigen<VectorXu64>(stream);
        obj.data = deserializeEigen<MatrixXu16>(stream);

        return obj;
    }
};

struct SignalBlockF32 : BaseDataType {
    SY_DEFINE_DATA_TYPE(SignalBlockF32)

    VectorXu64 timestamps;
    MatrixXf data;

    explicit SignalBlockF32(uint sampleCount = 60, uint channelCount = 1)
    {
        assert(channelCount > 0);
        timestamps.resize(sampleCount);
        data.resize(sampleCount, channelCount);
    }

    explicit SignalBlockF32(const std::vector<float> &floatVec, uint timestamp)
    {
        timestamps.array() += timestamp;
        data.resize(1, floatVec.size());
        for (size_t i = 0; i < floatVec.size(); ++i)
            data(0, i) = floatVec[i];
    }

    [[nodiscard]] size_t length() const
    {
        return timestamps.size();
    }

    [[nodiscard]] size_t rows() const
    {
        return data.rows();
    }

    [[nodiscard]] size_t cols() const
    {
        return data.cols();
    }

    bool toBytes(ByteVector &output) const override
    {
        BinaryStreamWriter stream(output);

        serializeEigen(stream, timestamps);
        serializeEigen(stream, data);

        return true;
    }

    static SignalBlockF32 fromMemory(const void *memory, size_t size)
    {
        SignalBlockF32 obj;
        BinaryStreamReader stream(memory, size);

        obj.timestamps = deserializeEigen<VectorXu64>(stream);
        obj.data = deserializeEigen<MatrixXf>(stream);

        return obj;
    }
};

using StreamTypeList = std::
    tuple<ControlCommand, TableRow, Frame, LineCommand, LineReading, SignalBlockI32, SignalBlockU16, SignalBlockF32>;

template<typename Fn>
constexpr bool forEachStreamType(Fn &&fn)
{
    return [&]<size_t... Is>(std::index_sequence<Is...>) {
        return (fn(std::type_identity<std::tuple_element_t<Is, StreamTypeList>>{}) || ...);
    }(std::make_index_sequence<std::tuple_size_v<StreamTypeList>>{});
}

void registerStreamMetaTypes();

std::vector<std::pair<std::string, int>> streamTypeIdIndex();

template<typename T>
inline std::string numToString(T x)
    requires std::is_arithmetic_v<T>
{
    if constexpr (std::is_same_v<T, bool>) {
        return x ? "true" : "false";
    } else {
        // Handle floating-point special cases
        if constexpr (std::is_floating_point_v<T>) {
            if (std::isnan(x))
                return "nan";
            if (std::isinf(x))
                return std::signbit(x) ? "-inf" : "inf";
            if (x == 0.0)
                x = 0.0; // canonicalize -0 to +0
        }

        std::array<char, 128> buf{};
        std::to_chars_result result{};

        if constexpr (std::is_floating_point_v<T>) {
            result = std::to_chars(buf.data(), buf.data() + buf.size(), x, std::chars_format::general);
        } else {
            result = std::to_chars(buf.data(), buf.data() + buf.size(), x);
        }

        if (result.ec != std::errc{}) {
            assert(false);
            return "<<conversion error>>";
        }

        return {buf.data(), result.ptr};
    }
}

} // namespace Syntalos

Updated on 2026-05-20 at 12:42:23 +0000