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::FirmataControl
Commands to control Firmata output.
structSyntalos::FirmataData
Output data returned from a Firmata device.
structSyntalos::IntSignalBlock
A block of integer signal data from a data source.
structSyntalos::FloatSignalBlock
A block of floating-point signal data from an analog data source.

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
)
    BaseDataType::TypeId typeId() const override         \
    {                                                    \
        return BaseDataType::TypeName;                   \
    }                                                    \
    static constexpr BaseDataType::TypeId staticTypeId() \
    {                                                    \
        return BaseDataType::TypeName;                   \
    }

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 <charconv>
#include <cstdint>
#include <cassert>
#include <array>
#include <cmath>

#include "syclock.h"
#include "binarystream.h"
#include "eigenaux.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 : uint16_t {
    UNKNOWN,      
    INITIALIZING, 
    IDLE,         
    PREPARING,    
    DORMANT,      
    READY,        
    RUNNING,      
    ERROR         
};

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

    enum TypeId {
        Unknown,
        ControlCommand,
        TableRow,
        Frame,
        FirmataControl,
        FirmataData,
        IntSignalBlock,
        FloatSignalBlock,
        Last
    };

    static std::string typeIdToString(int value)
    {
        if (value < 1 || value >= TypeId::Last)
            return "<<unknown>>";
        return typeIdToString(static_cast<TypeId>(value));
    }

    static std::string typeIdToString(TypeId value)
    {
        switch (value) {
        case Unknown:
            return "Unknown";
        case ControlCommand:
            return "ControlCommand";
        case TableRow:
            return "TableRow";
        case Frame:
            return "Frame";
        case FirmataControl:
            return "FirmataControl";
        case FirmataData:
            return "FirmataData";
        case IntSignalBlock:
            return "IntSignalBlock";
        case FloatSignalBlock:
            return "FloatSignalBlock";
        default:
            return "<<unknown>>";
        }
    }

    static TypeId typeIdFromString(const std::string &str)
    {
        if (str == "Unknown")
            return TypeId::Unknown;
        if (str == "ControlCommand")
            return TypeId::ControlCommand;
        if (str == "TableRow")
            return TypeId::TableRow;
        if (str == "Frame")
            return TypeId::Frame;
        if (str == "FirmataControl")
            return TypeId::FirmataControl;
        if (str == "FirmataData")
            return TypeId::FirmataData;
        if (str == "IntSignalBlock")
            return TypeId::IntSignalBlock;
        if (str == "FloatSignalBlock")
            return TypeId::FloatSignalBlock;
        return TypeId::Unknown;
    }

    [[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)                    \
    BaseDataType::TypeId typeId() const override         \
    {                                                    \
        return BaseDataType::TypeName;                   \
    }                                                    \
    static constexpr BaseDataType::TypeId staticTypeId() \
    {                                                    \
        return BaseDataType::TypeName;                   \
    }

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
};

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 FirmataCommandKind {
    UNKNOWN,
    NEW_DIG_PIN,
    NEW_ANA_PIN,
    IO_MODE,
    WRITE_ANALOG,
    WRITE_DIGITAL,
    WRITE_DIGITAL_PULSE,
    SYSEX 
};

struct FirmataControl : BaseDataType {
    SY_DEFINE_DATA_TYPE(FirmataControl)

    FirmataCommandKind command;
    uint8_t pinId{0};
    std::string pinName;
    bool isOutput{false};
    bool isPullUp{false};
    uint16_t value;

    explicit FirmataControl()
        : command(FirmataCommandKind::UNKNOWN),
          value(0)
    {
    }

    explicit FirmataControl(FirmataCommandKind cmd)
        : command(cmd),
          isPullUp(false),
          value(0)
    {
    }

    FirmataControl(FirmataCommandKind kind, int pinId, std::string name = std::string())
        : command(kind),
          pinId(pinId),
          pinName(std::move(name)),
          isPullUp(false),
          value(0)
    {
    }

    FirmataControl(FirmataCommandKind kind, std::string name)
        : command(kind),
          pinName(std::move(name)),
          isPullUp(false),
          value(0)
    {
    }

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

        stream.write(command);
        stream.write(pinId);
        stream.write(pinName);
        stream.write(isOutput);
        stream.write(isPullUp);
        stream.write(value);

        return true;
    }

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

        stream.read(obj.command);
        stream.read(obj.pinId);
        stream.read(obj.pinName);
        stream.read(obj.isOutput);
        stream.read(obj.isPullUp);
        stream.read(obj.value);

        return obj;
    }
};

struct FirmataData : BaseDataType {
    SY_DEFINE_DATA_TYPE(FirmataData)

    uint8_t pinId;
    std::string pinName;
    uint16_t value;
    bool isDigital;
    microseconds_t time;

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

        stream.write(pinId);
        stream.write(pinName);
        stream.write(value);
        stream.write(isDigital);
        stream.write(static_cast<int64_t>(time.count()));

        return true;
    }

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

        int64_t timeUs;
        stream.read(obj.pinId);
        stream.read(obj.pinName);
        stream.read(obj.value);
        stream.read(obj.isDigital);
        stream.read(timeUs);
        obj.time = microseconds_t(timeUs);

        return obj;
    }
};

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

struct IntSignalBlock : BaseDataType {
    SY_DEFINE_DATA_TYPE(IntSignalBlock)

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

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

    size_t rows() const
    {
        return data.rows();
    }
    size_t cols() const
    {
        return data.cols();
    }

    VectorXul timestamps;
    MatrixXsi data;

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

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

        return true;
    }

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

        obj.timestamps = deserializeEigen<VectorXul>(stream);
        obj.data = deserializeEigen<MatrixXsi>(stream);

        return obj;
    }
};

struct FloatSignalBlock : BaseDataType {
    SY_DEFINE_DATA_TYPE(FloatSignalBlock)

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

    explicit FloatSignalBlock(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];
    }

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

    size_t rows() const
    {
        return data.rows();
    }
    size_t cols() const
    {
        return data.cols();
    }

    VectorXul timestamps;
    MatrixXd data;

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

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

        return true;
    }

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

        obj.timestamps = deserializeEigen<VectorXul>(stream);
        obj.data = deserializeEigen<MatrixXd>(stream);

        return obj;
    }
};

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-03-16 at 19:16:01 +0000