From 5baf218a31aaee2ed39fd36503686fdab580cb01 Mon Sep 17 00:00:00 2001
From: ihedvall <92839244+ihedvall@users.noreply.github.com>
Date: Wed, 24 Jan 2024 11:35:25 +0100
Subject: [PATCH] Initial check-in of the pydbc python module.
---
.github/workflows/build.yml | 22 +--
.idea/.gitignore | 8 -
.idea/dbclib.iml | 8 +-
CMakeLists.txt | 4 +-
dbcviewer/src/childframe.cpp | 22 +--
dbcviewer/src/dbcdocument.cpp | 6 +-
include/dbc/attribute.h | 2 +
include/dbc/dbcfile.h | 2 +-
include/dbc/dbcmessage.h | 3 +-
include/dbc/envvar.h | 2 +
include/dbc/isampleobserver.h | 7 +-
include/dbc/message.h | 11 +-
include/dbc/network.h | 2 +-
include/dbc/signal.h | 87 ++++++----
include/dbc/signalobserver.h | 21 ++-
python/CMakeLists.txt | 22 ++-
python/src/pyattribute.cpp | 98 +++++++++++
python/src/pydbc.cpp | 30 ++--
python/src/pydbc.h | 20 +++
python/src/pydbcfile.cpp | 74 +++++++++
python/src/pydbcfile.h | 24 ---
python/src/pydbcmessage.cpp | 77 ++++++++-
python/src/pydbcmessage.h | 19 ---
python/src/pyenvvar.cpp | 82 ++++++++++
python/src/pymessage.cpp | 170 +++++++++++++++++++
python/src/pynetwork.cpp | 200 ++++++++++++++++++++++
python/src/pynode.cpp | 60 +++++++
python/src/pysignal.cpp | 273 +++++++++++++++++++++++++++++++
python/src/pysignalgroup.cpp | 47 ++++++
python/src/pysignalobserver.cpp | 118 +++++++++++++
python/tests/test_attribute.py | 40 +++++
python/tests/test_dbc_message.py | 29 ++++
python/tests/test_envvar.py | 63 +++++++
python/tests/test_message.py | 89 ++++++++++
python/tests/test_node.py | 32 ++++
python/tests/test_pydbc.py | 7 +-
python/tests/test_read.py | 60 +++++++
python/tests/test_signal.py | 172 +++++++++++++++++++
src/dbcfile.cpp | 14 ++
src/signal.cpp | 53 +++---
src/signalobserver.cpp | 18 +-
41 files changed, 1917 insertions(+), 181 deletions(-)
delete mode 100644 .idea/.gitignore
create mode 100644 python/src/pyattribute.cpp
create mode 100644 python/src/pydbc.h
create mode 100644 python/src/pydbcfile.cpp
delete mode 100644 python/src/pydbcfile.h
delete mode 100644 python/src/pydbcmessage.h
create mode 100644 python/src/pyenvvar.cpp
create mode 100644 python/src/pymessage.cpp
create mode 100644 python/src/pynetwork.cpp
create mode 100644 python/src/pynode.cpp
create mode 100644 python/src/pysignal.cpp
create mode 100644 python/src/pysignalgroup.cpp
create mode 100644 python/src/pysignalobserver.cpp
create mode 100644 python/tests/test_attribute.py
create mode 100644 python/tests/test_dbc_message.py
create mode 100644 python/tests/test_envvar.py
create mode 100644 python/tests/test_message.py
create mode 100644 python/tests/test_node.py
create mode 100644 python/tests/test_read.py
create mode 100644 python/tests/test_signal.py
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5fe2554..d6df25c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -36,15 +36,15 @@ jobs:
# CXX: clang++
# pack: 1
#
-# - name: win64
-# os: windows-latest
-# ninja_platform: win
-# msvc_arch: x64
-# cmake_env:
-# CMAKE_RC_FLAGS: "/C 1252"
-# CC: clang
-# CXX: clang++
-# pack: 1
+ - name: win64
+ os: windows-latest
+ ninja_platform: win
+ msvc_arch: x64
+ cmake_env:
+ CMAKE_RC_FLAGS: "/C 1252"
+ CC: clang
+ CXX: clang++
+ pack: 1
#
# - name: win32
# os: windows-latest
@@ -60,7 +60,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v3
@@ -116,7 +116,7 @@ jobs:
run: |
mkdir -p build/release
cd build/release
- cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ${{ env.CMAKE_FLAGS }} ${{ matrix.env.cmake_flags }} ../..
+ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DDBC_PYTHON=ON ${{ env.CMAKE_FLAGS }} ${{ matrix.env.cmake_flags }} ../..
- name: Build
run: |
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/dbclib.iml b/.idea/dbclib.iml
index f08604b..a9b4f0a 100644
--- a/.idea/dbclib.iml
+++ b/.idea/dbclib.iml
@@ -1,2 +1,8 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c0b0be7..2bac379 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,8 +12,8 @@ endif()
option(BUILD_SHARED_LIBS "Static libraries are preferred" OFF)
option(DBC_DOC "If doxygen is installed, then build documentation in Release mode" OFF)
option(DBC_TOOLS "Building applications" OFF)
-option(DBC_TEST "Building unit test" ON)
-option(DBC_PYTHON "Building Python module" ON)
+option(DBC_TEST "Building unit test" OFF)
+option(DBC_PYTHON "Building Python module" OFF)
if(DBC_TOOLS AND USE_VCPKG)
list(APPEND VCPKG_MANIFEST_FEATURES "tools")
diff --git a/dbcviewer/src/childframe.cpp b/dbcviewer/src/childframe.cpp
index e348e6a..9334899 100644
--- a/dbcviewer/src/childframe.cpp
+++ b/dbcviewer/src/childframe.cpp
@@ -1241,8 +1241,9 @@ void ChildFrame::OnShowMessageData(wxCommandEvent &event) {
const auto& signal_list = message->Signals();
for (const auto& itr : signal_list) {
const auto& signal = itr.second;
- auto observer = std::make_unique(signal);
- observer_list->push_back(std::move(observer));
+ auto observer = SignalObserver::CreateSignalObserver(signal);
+ observer_list->emplace_back(observer);
+
}
file->ReparseMessageList();
@@ -1311,8 +1312,8 @@ void ChildFrame::OnShowSignalData(wxCommandEvent &event) {
title << "/" << file->Name();
auto observer_list = std::make_unique();
- auto observer = std::make_unique(*signal);
- observer_list->push_back(std::move(observer));
+ auto observer = SignalObserver::CreateSignalObserver(*signal);
+ observer_list->emplace_back(observer);
file->ReparseMessageList();
@@ -1385,18 +1386,19 @@ void ChildFrame::OnPlotSignalData(wxCommandEvent &event) {
}
auto& app = wxGetApp();
// Create the observer list
- auto observer_list = std::make_unique();
- auto observer = std::make_unique(*signal);
- observer_list->push_back(std::move(observer));
+ SignalObserverList observer_list;
+ auto observer = SignalObserver::CreateSignalObserver(*signal);
+ observer_list.emplace_back(observer);
+
file->ReparseMessageList();
- const auto* obs = observer_list->front().get();
+ const auto* obs = observer_list.front().get();
if (obs == nullptr || obs->NofValidSamples() < 2) {
wxMessageBox("There is not enough valid samples to plot");
return;
}
// Produce a CSV file with the data for later use with the gnuplot script
- auto csv_file = CreateCsvFile(*observer_list);
- auto gp_file = CreateGnuPlotFile(*observer_list, csv_file, title.str());
+ auto csv_file = CreateCsvFile(observer_list);
+ auto gp_file = CreateGnuPlotFile(observer_list, csv_file, title.str());
if (csv_file.empty() || gp_file.empty()) {
wxMessageBox("Failed to create CSV or GP files.\nMore information in log file.");
return;
diff --git a/dbcviewer/src/dbcdocument.cpp b/dbcviewer/src/dbcdocument.cpp
index 15214d3..2c92a23 100644
--- a/dbcviewer/src/dbcdocument.cpp
+++ b/dbcviewer/src/dbcdocument.cpp
@@ -101,7 +101,7 @@ void DbcDocument::OnImportCanMessageFile(wxCommandEvent &event) {
LOG_ERROR() << "No header block in file. File: "
<< reader.ShortName();
}
- const auto* data_group = reader.GetDataGroup(0);
+ auto* data_group = header->LastDataGroup();
if (data_group == nullptr) {
LOG_ERROR() << "No data in file. File: "
<< reader.ShortName();
@@ -177,8 +177,8 @@ void DbcDocument::OnImportCanMessageFile(wxCommandEvent &event) {
return;
}
size_t count_messages = 0;
- dbc_file_->SetMessageSize(0);
- dbc_file_->SetMessageSize(nof_messages);
+ dbc_file_->MessageSize(0);
+ dbc_file_->MessageSize(nof_messages);
for (size_t sample = 0; sample < nof_messages; ++sample) {
uint64_t time = 0;
const auto time_valid = rel_time->GetChannelValue(sample, time);
diff --git a/include/dbc/attribute.h b/include/dbc/attribute.h
index 3f97793..74a8672 100644
--- a/include/dbc/attribute.h
+++ b/include/dbc/attribute.h
@@ -48,6 +48,8 @@ enum class AttributeValueType : int {
*/
class Attribute {
public:
+ Attribute() = default; ///< Default constructor
+
/** \brief Constructor for an attribute or definition. */
Attribute( AttributeType type, const std::string& name );
diff --git a/include/dbc/dbcfile.h b/include/dbc/dbcfile.h
index 8d9acfa..cafd671 100644
--- a/include/dbc/dbcfile.h
+++ b/include/dbc/dbcfile.h
@@ -71,7 +71,7 @@ class DbcFile {
*
*/
void ReparseMessageList();
-
+ void ClearObserverList();
private:
/** \brief Parses standard CAN messages. */
bool ParseStandardCAN(const DbcMessage& message);
diff --git a/include/dbc/dbcmessage.h b/include/dbc/dbcmessage.h
index 744e001..b32f83a 100644
--- a/include/dbc/dbcmessage.h
+++ b/include/dbc/dbcmessage.h
@@ -31,7 +31,6 @@ class DbcMessage {
DbcMessage(uint64_t time, uint32_t can_id, std::vector data);
DbcMessage(const DbcMessage& message) = default; ///< Default destructor.
-
void Time(uint64_t ns1970) {time_ = ns1970;} ///< Sets the time.
[[nodiscard]] uint64_t Time() const {return time_;} ///< Message time.
@@ -39,7 +38,7 @@ class DbcMessage {
[[nodiscard]] uint32_t CanId() const {return can_id_;} ///< CAN ID.
/** \brief Sets the CAN data bytes. */
- void Data(const std::vector& data) {data_ = data;}
+ void Data(const std::vector& data) { data_ = data;}
/** \brief Returns the CAN data bytes. */
[[nodiscard]] const std::vector& Data() const {return data_;}
diff --git a/include/dbc/envvar.h b/include/dbc/envvar.h
index 8fd4db8..c4873ec 100644
--- a/include/dbc/envvar.h
+++ b/include/dbc/envvar.h
@@ -33,6 +33,8 @@ enum class AccessType : int {
/** \brief Wrapper around an environment DBC variable. */
class EnvVar {
public:
+ EnvVar() = default;
+
/** \brief Sets the name. */
void Name(const std::string& name) { name_ = name; }
/** \brief Retuns the name. */
diff --git a/include/dbc/isampleobserver.h b/include/dbc/isampleobserver.h
index b7fbe72..1f53393 100644
--- a/include/dbc/isampleobserver.h
+++ b/include/dbc/isampleobserver.h
@@ -12,10 +12,13 @@ namespace dbc {
*/
class ISampleObserver {
public:
- ISampleObserver() = default; ///< Default constructor.
+
virtual ~ISampleObserver() = default; ///< Default destructor.
+
virtual void OnSample() = 0; ///< Handle a sample.
- virtual void DetachObserver() = 0; ///< Detach the observer object.
+
+ protected:
+ ISampleObserver() = default; ///< Default constructor.
};
} // namespace dbc
diff --git a/include/dbc/message.h b/include/dbc/message.h
index 68cbd67..d546280 100644
--- a/include/dbc/message.h
+++ b/include/dbc/message.h
@@ -21,7 +21,14 @@ using SignalList = std::map;
/** \brief DBC message configuration object. */
class Message {
public:
- void Ident(uint64_t ident) { ///< Sets the message ID (29-bit)
+ /**
+ * \brief Sets the message ID.
+ *
+ * Sets the message ID. The extended CAN ID is max 29-bit. to indicate
+ * that the ID is extended, bit 32 should be set.
+ * @param ident Message ID
+ */
+ void Ident(uint64_t ident) {
ident_ = ident;
}
[[nodiscard]] uint64_t Ident() const { ///< Returns the message ID.
@@ -115,7 +122,7 @@ class Message {
/** \brief Reset the internal sequence counter. */
void ResetSequenceNumber() {sequence_number_ = 0;}
/** \brief Returns the next sequence number. */
- uint8_t NextSequenceNumber() const {return sequence_number_;}
+ [[nodiscard]] uint8_t NextSequenceNumber() const {return sequence_number_;}
void ResetSampleCounter() const; ///< Reset the sample counters.
/** \brief Increments the internal sample counters. */
diff --git a/include/dbc/network.h b/include/dbc/network.h
index 30a0218..e57d01a 100644
--- a/include/dbc/network.h
+++ b/include/dbc/network.h
@@ -72,7 +72,7 @@ class Network {
void Comment(const std::string& comment) {
comment_ = comment;
}
- /** \brief Returns the desciptive text. */
+ /** \brief Returns the descriptive text. */
[[nodiscard]] const std::string& Comment() const { return comment_; }
/** \brief Returns the node by its name. */
diff --git a/include/dbc/signal.h b/include/dbc/signal.h
index e3f99fb..5adbf77 100644
--- a/include/dbc/signal.h
+++ b/include/dbc/signal.h
@@ -97,6 +97,7 @@ class Signal {
[[nodiscard]] MuxType Mux() const { return mux_type_; }
/** \brief Returns the multiplexer type as text. */
[[nodiscard]] std::string MuxAsString() const;
+
/** \brief Sets the multiplexor value. */
void MuxValue(int value) { mux_value_ = value; }
/** \brief Returns the multiplexor value. */
@@ -139,6 +140,10 @@ class Signal {
/** \brief Return the receiver list. */
[[nodiscard]] const std::vector& Receivers() const;
+ /** \brief Sets the attribute list. */
+ void Attributes(const std::vector& attribute_list) {
+ attribute_list_ = attribute_list;
+ }
/** \brief Returns the attribute list. */
[[nodiscard]] const std::vector& Attributes() const {
return attribute_list_;
@@ -146,12 +151,26 @@ class Signal {
/** \brief Sets the signals message ID. */
void MessageId(uint64_t message_id) { message_id_ = message_id;}
+
/** \brief Returns the message ID that the signal belongs to. */
[[nodiscard]] uint64_t MessageId() const { return message_id_; }
[[nodiscard]] bool IsMultiplexed() const; ///< True if multiplexed signal.
+
+ /**
+ * \brief Return true if this signal should be treated as a string.
+ *
+ * This function checks if this signal may be treated as a string
+ * value. The condition is if nof bytes > 8 i.e. cannot be converted
+ * any CAN data type. In theory, it could be a byte array but this
+ * is not used with any CAN protocols so far.
+ * @return True if this signal should be treated as a text or byte
+ * array value.
+ */
+ [[nodiscard]] bool IsArrayValue() const;
/** \brief Creates an attribute. */
[[nodiscard]] Attribute& CreateAttribute(const Attribute& definition);
+
/** \brief Creates an extended multiplexor struct. */
[[nodiscard]] ExtendedMux& GetExtendedMux();
@@ -181,6 +200,16 @@ class Signal {
void Valid(bool valid) {valid_ = valid;} ///< Set to true if valid value.
[[nodiscard]] bool Valid() const {return valid_;} ///< Trie if value is valid.
+ /**
+ * \brief Returns a reference to the internal signal value-
+ *
+ * The function returns the internal signal value object which
+ * is the latest channel value.
+ * @return Reference to the internal signal value object.
+ */
+ [[nodiscard]] const SignalValue& GetSignalValue() const {
+ return channel_value_;
+ }
/** \brief Returns the channel value. */
template
bool ChannelValue( T& value ) const;
@@ -190,10 +219,11 @@ class Signal {
bool EngValue( T& value ) const;
/** \brief Attach a sample observer. */
- void AttachObserver(ISampleObserver* observer) const;
+ void AttachObserver(std::shared_ptr& observer) const;
/** \brief Detach a sample observer. */
- void DetachObserver(const ISampleObserver* observer) const;
- private:
+ void ClearObserverList() const;
+
+ protected:
std::string name_; ///< Signal nsame.
std::string comment_; ///< Signal description.
std::string unit_; ///< Signal unit.
@@ -223,7 +253,7 @@ class Signal {
uint64_t sample_time_ = 0; ///< Last sample time
uint32_t sample_can_id_ = 0; ///< Last Can ID
- mutable std::vector observer_list_; ///< Observer list.
+ mutable std::vector> observer_list_; ///< Observer list.
void FireOnSample(); ///< Fire OnSample event.
};
@@ -235,41 +265,25 @@ bool Signal::ChannelValue(T& value) const {
switch (data_type_) {
case SignalDataType::SignedData: {
- try {
- const auto temp = channel_value_.signed_value;
- value = static_cast(temp);
- } catch (const std::exception&) {
- valid = false;
- }
+ const auto temp = channel_value_.signed_value;
+ value = static_cast(temp);
break;
}
case SignalDataType::UnsignedData: {
- size_t bytes = bit_length_ / 8;
- if ((bit_length_ % 8) != 0) {
- ++bytes;
- }
- if (bytes > 8) {
+ if (IsArrayValue()) {
valid = false;
} else {
- try {
- const auto temp = channel_value_.unsigned_value;
- value = static_cast(temp);
- } catch (const std::exception&) {
- valid = false;
- }
+ const auto temp = channel_value_.unsigned_value;
+ value = static_cast(temp);
}
break;
}
case SignalDataType::DoubleData:
case SignalDataType::FloatData: {
- try {
- const auto temp = channel_value_.float_value;
- value = static_cast(temp);
- } catch (const std::exception&) {
- valid = false;
- }
+ const auto temp = channel_value_.float_value;
+ value = static_cast(temp);
break;
}
@@ -280,10 +294,27 @@ bool Signal::ChannelValue(T& value) const {
return valid;
}
-/** \brief Returns the signal value as a string */
+/**
+ * \brief Returns the value a text string.
+ * @param value Destination string.
+ * @return True if the value is valid.
+ */
template <>
bool Signal::ChannelValue(std::string& value) const;
+/**
+ * \brief Returns the channel value as a byte array.
+ *
+ * Returns the channel value as a byte array. No meaning if it
+ * is a standard value but if the number of bytes > 8, it
+ * normally indicates that the value id a text value but may also
+ * be a byte array.
+ * @param value Destination byte array.
+ * @return True if the value is valid.
+ */
+template <>
+bool Signal::ChannelValue(std::vector& value) const;
+
/** \brief Returns the signal value as a signal value */
template <>
bool Signal::ChannelValue(SignalValue& value) const;
diff --git a/include/dbc/signalobserver.h b/include/dbc/signalobserver.h
index a854391..94b48fc 100644
--- a/include/dbc/signalobserver.h
+++ b/include/dbc/signalobserver.h
@@ -22,10 +22,11 @@ namespace dbc {
*/
class SignalObserver : public ISampleObserver {
public:
- explicit SignalObserver(const Signal& signal); ///< Constructor
+
SignalObserver() = delete; ///< Default constructor.
~SignalObserver() override; ///< Default destructor.
+ static std::shared_ptr CreateSignalObserver(const Signal& signal);
/** \brief Sets the maximum number of samples. */
void MaxSamples(size_t max_nof_samples);
/** \brief Returns the max number of samples. */
@@ -79,10 +80,10 @@ class SignalObserver : public ISampleObserver {
/** \brief Sample time to internal index. */
[[nodiscard]] std::pair TimeToIndex(uint64_t time) const;
- void DetachObserver() override; ///< Detach an observer.
void OnSample() override; ///< On sample callback handler.
protected:
+ explicit SignalObserver(const Signal& signal); ///< Constructor
private:
/** \brief Sample value. */
struct ChannelSample {
@@ -100,7 +101,7 @@ class SignalObserver : public ISampleObserver {
size_t sample_index_ = 0; ///< Points on next index
size_t nof_samples_ = 0; ///< Number of samples.
- bool attached_ = false; ///< True if the observer is attached.
+
};
template
@@ -160,7 +161,17 @@ bool SignalObserver::ChannelValue(size_t index, uint64_t& ns1970,
}
return valid;
}
-/** \brief Returns the unscaled signal value as a string. */
+
+/**
+ * @brief Returns the unscaled signal value for a specific sample.
+ *
+ * This function retrieves the unscaled signal value for a specific sample at the given index.
+ *
+ * @param index The index of the sample to retrieve the value for.
+ * @param ns1970 Reference to store the sample time in nanoseconds since 1970.
+ * @param value Reference to store the sample value.
+ * @return True if the value is valid, false otherwise.
+ */
template <>
bool SignalObserver::ChannelValue(size_t index, uint64_t& ns1970,
std::string& value) const;
@@ -257,6 +268,6 @@ bool SignalObserver::EngValue(size_t index, uint64_t& ns1970,
std::string& value) const;
/** \brief List of observer. */
-using SignalObserverList = std::vector>;
+using SignalObserverList = std::vector>;
} // namespace dbc
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index 12e8f61..c608e34 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -27,9 +27,17 @@ project(pydbc_lib
pybind11_add_module( pydbc MODULE
- src/pydbcmessage.cpp src/pydbcmessage.h
- src/pydbcfile.h
- src/pydbc.cpp)
+ src/pydbcmessage.cpp
+ src/pyattribute.cpp
+ src/pyenvvar.cpp
+ src/pysignal.cpp
+ src/pymessage.cpp
+ src/pynode.cpp
+ src/pynetwork.cpp
+ src/pysignalgroup.cpp
+ src/pydbcfile.cpp
+ src/pysignalobserver.cpp
+ src/pydbc.cpp src/pydbc.h )
cmake_print_variables(CMAKE_SOURCE_DIR)
cmake_print_variables(CMAKE_CURRENT_SOURCE_DIR)
@@ -53,21 +61,23 @@ if (DBC_TEST)
# so the CLION can handle the pytest properly
file(GLOB module_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/pydbc.* )
- foreach (file ${module_files})
+ foreach (pyfile ${module_files})
add_custom_command(
TARGET pydbc
POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy
- ARGS ${file} "${CMAKE_SOURCE_DIR}/venv/Lib/site-packages"
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ ARGS ${pyfile} "${CMAKE_SOURCE_DIR}/venv/Lib/site-packages"
)
endforeach ()
+
# Add the test to the CTest. The pyd module is in the current binary dir,
# so the pytest should run.
enable_testing()
add_test(NAME pydbc_test
COMMAND ${Python3_EXECUTABLE} -m pytest --log-cli-level=0 ${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+
)
endif()
\ No newline at end of file
diff --git a/python/src/pyattribute.cpp b/python/src/pyattribute.cpp
new file mode 100644
index 0000000..373cd09
--- /dev/null
+++ b/python/src/pyattribute.cpp
@@ -0,0 +1,98 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+
+#include "dbc/attribute.h"
+#include
+#include
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace pydbc {
+
+void InitAttribute(pybind11::module& m) {
+ constexpr std::string_view enum_type_doc =
+ R"(Defines what type of network object the attribute is valid for.)";
+ auto enum_type = enum_(m,"AttributeType",
+ enum_type_doc.data());
+ enum_type.value("DbcNetwork", AttributeType::DbcNetwork);
+ enum_type.value("DbcNode", AttributeType::DbcNode);
+ enum_type.value("DbcMessage", AttributeType::DbcMessage);
+ enum_type.value("DbcSignal", AttributeType::DbcSignal);
+ enum_type.value("EnvironmentVariable", AttributeType::EnvironmentVariable);
+ enum_type.value("NodeSignalRelation", AttributeType::NodeSignalRelation);
+ enum_type.value("NodeMessageRelation", AttributeType::NodeMessageRelation);
+
+ constexpr std::string_view enum_value_type_doc =
+ R"(Data type of the attribute.)";
+ auto enum_value_type = enum_(m,"AttributeValueType",
+ enum_value_type_doc.data());
+ enum_value_type.value("IntegerValue", AttributeValueType::IntegerValue);
+ enum_value_type.value("FloatValue", AttributeValueType::FloatValue);
+ enum_value_type.value("StringValue", AttributeValueType::StringValue);
+ enum_value_type.value("EnumValue", AttributeValueType::EnumValue);
+ enum_value_type.value("HexValue", AttributeValueType::HexValue);
+
+ constexpr std::string_view attribute_doc = R"(
+Support class for handling attributes of network objects.
+
+The Attribute class is a support class for handling attributes attached
+to DBC objects.
+
+The DBC file first specify a definition of an attribute.
+This definition defines range and data type i.e. a sort of template.
+Then the DBC file define the attribute that references a definition and
+has a value (constant).
+)";
+ auto attr = class_(m, "Attribute", attribute_doc.data());
+
+ constexpr std::string_view attribute_init_doc = R"(
+Constructor for an attribute or definition.
+
+Attributes:
+ type: Type of attribute
+ name: Name of the attribute
+)";
+ attr.def(init<>());
+ attr.def(init(),
+ attribute_init_doc.data(), "type"_a, "name"_a);
+ attr.def_property("name",
+ [](Attribute& self) {return self.Name();},
+ [](Attribute& self,const std::string& name) {
+ self.Name(name);
+ });
+ attr.def_property("type",
+ [](Attribute& self) {return self.Type();},
+ [](Attribute& self, AttributeType type) {self.Type(type);});
+ attr.def_property("value_type",
+ [](Attribute& self) {return self.ValueType();},
+ [](Attribute& self, AttributeValueType type) {self.ValueType(type);});
+ attr.def_property("min",
+ [](Attribute& self) {return self.Min();},
+ [](Attribute& self, double min) {self.Min(min);});
+ attr.def_property("max",
+ [](Attribute& self) {return self.Max();},
+ [](Attribute& self, double max) {self.Max(max);});
+ attr.def_property("enumerate",
+ [](Attribute& self) {
+ pybind11::list temp_list = pybind11::cast(self.EnumList());
+ return temp_list;
+ },
+ [](Attribute& self, const list& enum_list) {
+ self.EnumList(enum_list.cast>());
+ });
+ attr.def_property("value",
+ [](Attribute& self) {return self.Value();},
+ [](Attribute& self, const std::string& value) {self.Value(value);});
+ attr.def_property("value",
+ [](Attribute& self) {return self.Value();},
+ [](Attribute& self, int value) {self.Value(value);});
+ attr.def_property("value",
+ [](Attribute& self) {return self.Value();},
+ [](Attribute& self, double value) {self.Value(value);});
+}
+
+
+} // namespace pydbc
\ No newline at end of file
diff --git a/python/src/pydbc.cpp b/python/src/pydbc.cpp
index c9d05d0..1aa89a9 100644
--- a/python/src/pydbc.cpp
+++ b/python/src/pydbc.cpp
@@ -1,28 +1,24 @@
/*
-* Copyright 2023 Ingemar Hedvall
+* Copyright 2024 Ingemar Hedvall
* SPDX-License-Identifier: MIT
*/
-#include "pydbcmessage.h"
-#include "pydbcfile.h"
+#include "pydbc.h"
+
#include
using namespace pydbc;
using namespace pybind11;
PYBIND11_MODULE(pydbc, m) {
- auto msg = class_(m, "DbcMessage");
- msg.def(init<>());
- msg.def_property("time", &PyDbcMessage::GetTime, &PyDbcMessage::SetTime);
-
- auto file = class_(m, "DbcFile");
- file.def(init<>());
- file.def_property("filename", &PyDbcFile::GetFilename, &PyDbcFile::SetFilename);
- file.def_property_readonly("name", &PyDbcFile::Name);
- file.def_property_readonly("last_error", &PyDbcFile::LastError);
- file.def_property("base_time", &PyDbcFile::GetBaseTime, &PyDbcFile::SetBaseTime);
- file.def_property("message_size", &PyDbcFile::GetMessageSize, &PyDbcFile::SetMessageSize);
-
- file.def("parse_file", &PyDbcFile::ParseFile);
-
+ InitDbcMessage(m);
+ InitAttribute(m);
+ InitEnvVar(m);
+ InitSignal(m);
+ InitMessage(m);
+ InitNode(m);
+ InitSignalGroup(m);
+ InitNetwork(m);
+ InitDbcFile(m);
+ InitSignalObserver(m);
}
\ No newline at end of file
diff --git a/python/src/pydbc.h b/python/src/pydbc.h
new file mode 100644
index 0000000..de48f7a
--- /dev/null
+++ b/python/src/pydbc.h
@@ -0,0 +1,20 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+#pragma once
+#include
+
+namespace pydbc {
+
+void InitDbcMessage(pybind11::module& m);
+void InitAttribute(pybind11::module& m);
+void InitEnvVar(pybind11::module& m);
+void InitSignal(pybind11::module& m);
+void InitMessage(pybind11::module& m);
+void InitNode(pybind11::module& m);
+void InitSignalGroup(pybind11::module& m);
+void InitNetwork(pybind11::module& m);
+void InitDbcFile(pybind11::module& m);
+void InitSignalObserver(pybind11::module& m);
+} // pydbc
diff --git a/python/src/pydbcfile.cpp b/python/src/pydbcfile.cpp
new file mode 100644
index 0000000..834bb18
--- /dev/null
+++ b/python/src/pydbcfile.cpp
@@ -0,0 +1,74 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include
+#include
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace pydbc {
+
+void InitDbcFile(pybind11::module& m) {
+ auto file = class_(m, "DbcFile");
+ file.def(init<>());
+
+ file.def_property("filename",
+ [](DbcFile& self) {return self.Filename();},
+ [](DbcFile& self, const std::string& filename) {
+ self.Filename(filename);
+ });
+
+ file.def_property_readonly("name", &DbcFile::Name,
+ "Returns the filename without any path and extension");
+
+ file.def_property_readonly("last_error", &DbcFile::LastError,
+ "Returns the last parser error.");
+
+ file.def("get_network",[](DbcFile& self) {
+ return cast(self.GetNetwork(), return_value_policy::reference);
+ });
+
+ file.def("parse_file", &DbcFile::ParseFile,
+ "Parses the DBC file and returns true if the parsing was successful.");
+
+ constexpr std::string_view base_time_doc = R"(
+Start (absolute) time of messages.
+
+The base time is the time of the first message. If not set, the
+add_message() function will set it to the first message. The time
+should be nano-seconds since 1970.
+)";
+ file.def_property("base_time",
+ [](DbcFile& self) {return self.BaseTime();},
+ [](DbcFile& self, uint64_t ns1970) {
+ self.BaseTime(ns1970);
+ }, base_time_doc.data());
+
+ file.def_property("message_size",
+ [](DbcFile& self) {return self.MessageSize();},
+ [](DbcFile& self, size_t nof_messages) {
+ self.MessageSize(nof_messages);
+ }, "Sets the maximum number of messages in the internal message queue.");
+
+ file.def("add_message", &DbcFile::AddMessage,
+ "Adds a message to the internal message queue.",
+ "index"_a, "message"_a);
+
+ file.def("parse_message", &DbcFile::ParseMessage,
+ "Parses a message i.e. converting tne message into signal values.",
+ "message"_a);
+
+ file.def("reset_sample_counter", &DbcFile::ResetSampleCounter,
+ "Resets the sample counter for all signals.");
+
+ file.def("reparse_messages", &DbcFile::ReparseMessageList,
+ "Reparse all messages in the internal message queue.");
+
+}
+
+} // End namespace pydbc
\ No newline at end of file
diff --git a/python/src/pydbcfile.h b/python/src/pydbcfile.h
deleted file mode 100644
index 59dd7dd..0000000
--- a/python/src/pydbcfile.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
-* Copyright 2024 Ingemar Hedvall
-* SPDX-License-Identifier: MIT
- */
-
-#pragma once
-
-#include
-namespace pydbc {
-
- class PyDbcFile : public dbc::DbcFile {
- public:
- void SetFilename(const std::string &filename) { Filename(filename); }
- [[nodiscard]] const std::string &GetFilename() const { return Filename(); }
-
- void SetBaseTime(uint64_t ns1970) { BaseTime(ns1970); }
- [[nodiscard]] uint64_t GetBaseTime() const { return BaseTime(); }
-
- void SetMessageSize(size_t nof_messages) { MessageSize(nof_messages); }
- [[nodiscard]] size_t GetMessageSize() const { return MessageSize(); }
-
-};
-
-} // End namespace pydbc
\ No newline at end of file
diff --git a/python/src/pydbcmessage.cpp b/python/src/pydbcmessage.cpp
index 59838aa..66b55f8 100644
--- a/python/src/pydbcmessage.cpp
+++ b/python/src/pydbcmessage.cpp
@@ -1,9 +1,80 @@
/*
-* Copyright 2022 Ingemar Hedvall
+* Copyright 2024 Ingemar Hedvall
* SPDX-License-Identifier: MIT
*/
-#include "pydbcmessage.h"
+#include "dbc/dbcmessage.h"
+#include
+#include
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace {
+DbcMessage CreateDbcMessage(uint64_t time,
+ uint32_t can_id,
+ const list& data) {
+ const auto temp_list = data.cast>();
+ return {time, can_id, temp_list};
+}
+
+}
namespace pydbc {
-} // pydbc
\ No newline at end of file
+
+void InitDbcMessage(pybind11::module& m) {
+ constexpr std::string_view msg_doc = R"(
+Wrapper class around a CAN message.
+
+Simple support class that holds a CAN message. CAN message has a time, ID
+and none or more data bytes. This sound simple but there are several
+protocols on top of CAN that treat the CAN ID and its data bytes
+differently.
+)";
+
+ auto msg = class_(m, "DbcMessage", msg_doc.data());
+ msg.def(init<>());
+
+ constexpr std::string_view msg_init_doc = R"(
+Constructor that wraps a CAN message.
+
+Constructor that wraps a CAN message.
+
+Attributes:
+ time: Nano-seconds since 1970.
+ can_id: 11/29-bit CAN ID.
+ data: CAN data bytes.
+)";
+ msg.def(init(&CreateDbcMessage),
+ msg_init_doc.data(), "ns1970"_a, "can_id"_a, "message"_a);
+
+ msg.def_property("time",
+ [] (DbcMessage& self) {return self.Time();},
+ [] (DbcMessage& self, uint64_t ns1970) {self.Time(ns1970);});
+ msg.def_property("can_id",
+ [] (DbcMessage& self) {return self.CanId();},
+ [] (DbcMessage& self, uint32_t can_id) {self.CanId(can_id);});
+ msg.def_property("data",
+ [] (DbcMessage& self) {
+ pybind11::list temp_list = pybind11::cast(self.Data());
+ return temp_list;
+ },
+ [] (DbcMessage& self, const pybind11::list& data_list) {
+ self.Data(data_list.cast>());
+ });
+ msg.def_property_readonly("pgn", &DbcMessage::Pgn);
+ msg.def_property_readonly("priority", &DbcMessage::Priority);
+ msg.def_property_readonly("extended_data_page",
+ &DbcMessage::ExtendedDataPage);
+ msg.def_property_readonly("data_page", &DbcMessage::DataPage);
+ msg.def_property_readonly("pdu_format", &DbcMessage::PduFormat);
+ msg.def_property_readonly("pdu_specific", &DbcMessage::PduSpecific);
+ msg.def_property_readonly("source", &DbcMessage::Source);
+}
+
+
+
+} // pydbc
+
+
+
diff --git a/python/src/pydbcmessage.h b/python/src/pydbcmessage.h
deleted file mode 100644
index ee69f40..0000000
--- a/python/src/pydbcmessage.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
-* Copyright 2022 Ingemar Hedvall
-* SPDX-License-Identifier: MIT
- */
-
-#pragma once
-#include
-
-namespace pydbc {
-
- class PyDbcMessage : public dbc::DbcMessage {
- public:
- void SetTime(uint64_t ns1970) { Time(ns1970); }
- [[nodiscard]] uint64_t GetTime() const { return Time(); }
-
-};
-
-} // pydbc
-
diff --git a/python/src/pyenvvar.cpp b/python/src/pyenvvar.cpp
new file mode 100644
index 0000000..d408b09
--- /dev/null
+++ b/python/src/pyenvvar.cpp
@@ -0,0 +1,82 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+
+#include "dbc/envvar.h"
+#include
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace pydbc {
+
+void InitEnvVar(pybind11::module& m) {
+
+ constexpr std::string_view enum_type_doc =
+ R"(Defines the data type of the environment variable.)";
+ auto enum_type = enum_(m,"EnvType", enum_type_doc.data());
+ enum_type.value("IntegerType", EnvType::IntegerType);
+ enum_type.value("FloatType", EnvType::FloatType);
+ enum_type.value("StringType", EnvType::StringType);
+ enum_type.value("DataType", EnvType::DataType);
+
+ constexpr std::string_view enum_access_doc =
+ R"(Defines the access of the environment variable.)";
+ auto enum_access = enum_(m,"AccessType", enum_access_doc.data());
+ enum_access.value("Unrestricted", AccessType::Unrestricted);
+ enum_access.value("ReadOnly", AccessType::ReadOnly);
+ enum_access.value("WriteOnly", AccessType::WriteOnly);
+ enum_access.value("ReadWrite", AccessType::ReadWrite);
+
+ constexpr std::string_view env_doc = R"(
+Wrapper around an DBC environment variable.
+)";
+ auto env = class_(m, "EnvVar", env_doc.data());
+ env.def(init<>());
+ env.def_property("ident",
+ [](EnvVar& self) {return self.Ident();},
+ [](EnvVar& self, uint64_t ident) {self.Ident(ident);});
+ env.def_property("name",
+ [](EnvVar& self) {return self.Name();},
+ [](EnvVar& self,const std::string& name) {self.Name(name);});
+ env.def_property("comment",
+ [](EnvVar& self) {return self.Comment();},
+ [](EnvVar& self,const std::string& comment) {self.Comment(comment);});
+ env.def_property("unit",
+ [](EnvVar& self) {return self.Unit();},
+ [](EnvVar& self,const std::string& unit) {self.Unit(unit);});
+ env.def_property("type",
+ [](EnvVar& self) {return self.Type();},
+ [](EnvVar& self, EnvType type) {self.Type(type);});
+ env.def_property("access",
+ [](EnvVar& self) {return self.Access();},
+ [](EnvVar& self, AccessType access) {self.Access(access);});
+ env.def_property("min",
+ [](EnvVar& self) {return self.Min();},
+ [](EnvVar& self, double min) {self.Min(min);});
+ env.def_property("max",
+ [](EnvVar& self) {return self.Max();},
+ [](EnvVar& self, double max) {self.Max(max);});
+ env.def_property("initial_value",
+ [](EnvVar& self) {return self.InitValue();},
+ [](EnvVar& self, double value) {self.InitValue(value);});
+ env.def_property("enumerate",
+ [](EnvVar& self) {
+ pybind11::dict temp_list = pybind11::cast(self.EnumList());
+ return temp_list;
+ },
+ [](EnvVar& self, const dict& enum_list) {
+ self.EnumList(enum_list.cast>());
+ });
+ env.def_property("nodes",
+ [](EnvVar& self) {
+ pybind11::list temp_list = pybind11::cast(self.NodeList());
+ return temp_list;
+ },
+ [](EnvVar& self, const list& node_list) {
+ self.NodeList(node_list.cast>());
+ });
+}
+
+} // namespace pydbc
\ No newline at end of file
diff --git a/python/src/pymessage.cpp b/python/src/pymessage.cpp
new file mode 100644
index 0000000..34cfe5f
--- /dev/null
+++ b/python/src/pymessage.cpp
@@ -0,0 +1,170 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+
+#include "dbc/message.h"
+#include
+#include "dbc/signal.h"
+#include "dbc/attribute.h"
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace pydbc {
+
+void InitMessage(pybind11::module& m) {
+ constexpr std::string_view msg_doc = R"(
+Support object that purpose is to hold one signal value.
+
+support object that is used when parsing CAN messages. The
+object hold the last parsed messages signal value.
+)";
+ auto msg = class_(m, "Message", msg_doc.data());
+ msg.def(init<>());
+ msg.def_property("message_id",
+ [](Message& self) {
+ return self.Ident();
+ },[](Message& self, uint64_t ident) {
+ self.Ident(ident);
+ });
+ msg.def_property_readonly("extended",&Message::IsExtended,
+ "Returns true if the message uses extended 29-bit address.");
+ msg.def_property_readonly("j1939",&Message::IsJ1939,
+ "Returns true if the message uses J1939 address logic.");
+ msg.def_property_readonly("can_id",&Message::CanId,
+ "Returns the message CAN ID.");
+ msg.def_property_readonly("priority",&Message::Priority,
+ "Returns the J1939 priority.");
+ msg.def_property_readonly("pgn",&Message::Pgn, "Returns the J1939 PGN.");
+ msg.def_property_readonly("source",&Message::Source, "Returns the J1939 source ID.");
+ msg.def_property_readonly("extended_data_page",&Message::ExtendedDataPage,
+ "Returns true if this is a J1939 extended data page.");
+ msg.def_property_readonly("data_page",&Message::DataPage,
+ "Returns true if this is a J1939 data page.");
+ msg.def_property_readonly("pdu_format",&Message::PduFormat,
+ "Returns the J1939 PDU format (PF).");
+ msg.def_property_readonly("pdu_specific",&Message::PduSpecific,
+ "Returns the J1939 PDU specific (PS).");
+ msg.def_property_readonly("pdu1",&Message::IsPdu1,
+ "Returns true if the message is of type PDU1.");
+ msg.def_property_readonly("pdu2",&Message::IsPdu2,
+ "Returns true if the message is of type PDU2.");
+
+ msg.def_property("name",
+ [](Message& self) {
+ return self.Name();
+ },[](Message& self, const std::string& name) {
+ self.Name(name);
+ });
+ msg.def_property("comment",
+ [](Message& self) {
+ return self.Comment();
+ },[](Message& self, const std::string& comment) {
+ self.Comment(comment);
+ });
+ msg.def_property("nof_bytes",
+ [](Message& self) {
+ return self.NofBytes();
+ },[](Message& self, size_t nof_bytes) {
+ self.NofBytes(nof_bytes);
+ });
+
+ msg.def("add_node", &Message::Node,
+ "Adds a node or sets the node name", "node"_a);
+ msg.def_property_readonly("node_name",&Message::NodeName,
+ "Returns the node name.");
+
+ msg.def("get_signal", [] (Message& self, const std::string& name) -> Signal* {
+ return self.GetSignal(name);
+ }, "Returns the signal object by its name", "name"_a,
+ return_value_policy::reference_internal);
+
+ msg.def("get_multiplexor", &Message::GetMultiplexor,
+ "Returns the signal object that is the multiplexor",
+ return_value_policy::reference_internal);
+ msg.def("get_attribute", &Message::GetAttribute,
+ "Returns the attribute object by its name", "name"_a,
+ return_value_policy::reference_internal);
+
+ msg.def_property_readonly("signals", [] (Message& self) -> dict {
+ const auto& signal_list = self.Signals();
+ dict temp_list;
+ for (const auto& itr : signal_list) {
+ const auto& name = itr.first;
+ const auto& signal = itr.second;
+ temp_list[name.c_str()] = cast(signal, return_value_policy::reference);
+ }
+ return temp_list;
+ }, "Returns a list of signal objects");
+
+ msg.def_property_readonly("senders", [] (Message& self) -> list {
+ const auto& sender_list = self.Senders();
+ list temp_list = cast(sender_list);
+ return temp_list;
+ }, "Returns a list of sender nodes for this message");
+
+ msg.def_property_readonly("attributes", [] (Message& self) -> list {
+ const auto& attribute_list = self.Attributes();
+ list temp_list(attribute_list.size());
+ for (size_t index = 0; index < attribute_list.size(); ++index) {
+ const auto& attribute = attribute_list[index];
+ temp_list[index] = cast(attribute, return_value_policy::reference);
+ }
+ return temp_list;
+ }, "Returns a list of attribute objects");
+
+ msg.def("node_sender",&Message::IsNodeSender,
+ "Returns true if the node name is a sender of this message.","node_name"_a);
+
+ msg.def_property_readonly("data", [] (Message& self) -> list {
+ const auto& data_list = self.Data();
+ list temp_list = cast(data_list);
+ return temp_list;
+ }, "Returns the last message data bytes");
+
+ constexpr std::string_view update_data_doc = R"(
+Update the internal data buffer.
+
+Sets the internal last message data buffer. Note that the input offset
+and destination offset is used if the message requires more than one
+CAN data message before it is complete.
+
+Args:
+ data_bytes: Message data.
+ offset: Offset in the message data buffer (input index).
+ index: Offset in the last message data buffer (dest index).
+
+Returns:
+ True if this was the last bytes (complete message).
+)";
+ msg.def("update_data", [] (Message& self, const list& data_bytes,
+ size_t offset, size_t index) -> bool {
+ const auto temp_list = data_bytes.cast>();
+ return self.UpdateData(temp_list, offset, index);
+ }, update_data_doc.data(), "data_bytes"_a, "offset"_a = 0, "index"_a = 0);
+
+ msg.def("parse_message", &Message::ParseMessage,"Parses a CAN message",
+ "ns1970"_a, "can_id"_a);
+ msg.def("reset_sequence_number", &Message::ResetSequenceNumber,
+ "Resets the internal sequence number that keep track of CAN messages > 8 bytes");
+ msg.def_property_readonly("sequence_number", &Message::NextSequenceNumber,
+ "Returns the next sequence number that keep track of CAN messages > 8 bytes");
+ msg.def("reset_sample_counter", &Message::ResetSampleCounter,
+ "Resets the internal sample counter.");
+ msg.def("step_sample_counter", &Message::StepSampleCounter,
+ "Increments the internal sample counter.");
+ msg.def_property_readonly("sample_counter", &Message::SampleCounter,
+ "Returns the internal sample counter.");
+
+ msg.def("create_attribute", &Message::CreateAttribute,
+ return_value_policy::reference_internal,
+ "Creates a new attribute to the message using the input attribute as template.",
+ "attribute"_a);
+ msg.def("create_signal", &Message::CreateSignal,
+ return_value_policy::reference_internal,
+ "Creates a new signal with the supplied name.",
+ "name"_a);
+}
+
+} // namespace pydbc
\ No newline at end of file
diff --git a/python/src/pynetwork.cpp b/python/src/pynetwork.cpp
new file mode 100644
index 0000000..4c8f64b
--- /dev/null
+++ b/python/src/pynetwork.cpp
@@ -0,0 +1,200 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+#include "dbc/network.h"
+#include
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace pydbc {
+void InitNetwork(pybind11::module& m) {
+
+ auto protocol_type = enum_(m,"ProtocolType",
+ "Type of protocol on the bus.");
+ protocol_type.value("StandardCAN", ProtocolType::StandardCAN);
+ protocol_type.value("J1939", ProtocolType::J1939);
+ protocol_type.value("NMEA2000", ProtocolType::NMEA2000);
+ protocol_type.value("OBD2", ProtocolType::OBD2);
+
+ auto bus_type = enum_(m,"BusType",
+ "Type of bus.");
+ bus_type.value("CAN", BusType::CAN);
+
+ auto network = class_(m, "Network", "Interface against DBC network.");
+ network.def_property("filename",
+ [](Network& self) {return self.Filename();},
+ [](Network& self,const std::string& filename) {
+ self.Filename(filename);
+ });
+ network.def_property("protocol",
+ [](Network& self) {return self.Protocol();},
+ [](Network& self,ProtocolType type) {
+ self.Protocol(type);
+ });
+ network.def_property("bus",
+ [](Network& self) {return self.Bus();},
+ [](Network& self, BusType type) {
+ self.Bus(type);
+ });
+ network.def_property("version",
+ [](Network& self) {return self.Version();},
+ [](Network& self,const std::string& version) {
+ self.Version(version);
+ });
+ network.def_property("comment",
+ [](Network& self) {return self.Comment();},
+ [](Network& self,const std::string& comment) {
+ self.Comment(comment);
+ });
+
+ network.def("get_node", [] (Network& self, const std::string& name) {
+ const auto* node = self.GetNode(name);
+ return cast(node, return_value_policy::reference_internal);
+ }, "Returns the DBC node by name.", "name"_a);
+ network.def("get_node", [] (Network& self, uint8_t source) {
+ const auto* node = self.GetNodeBySource(source);
+ return cast(node, return_value_policy::reference_internal);
+ }, "Returns the DBC node by source number.", "source"_a);
+
+ network.def_property("j1939",
+ [](Network& self) {return self.J1939();},
+ [](Network& self, bool j1939) {
+ self.J1939(j1939);
+ });
+
+ network.def("get_message_by_message_id", [] (Network& self, uint64_t message_id) {
+ const auto* message = self.GetMessage(message_id);
+ return cast(message, return_value_policy::reference_internal);
+ }, "Returns a DBC message by its message ID.", "message_id"_a);
+
+ network.def("get_message_by_can_id", [] (Network& self, uint32_t can_id) {
+ const auto* message = self.GetMessageByCanId(can_id);
+ return cast(message, return_value_policy::reference_internal);
+ }, "Returns a DBC message by its CAN ID (29-bit).", "can_id"_a);
+
+ network.def("get_message_by_pgn", [] (Network& self, uint32_t pgn) {
+ const auto* message = self.GetMessageByPgn(pgn);
+ return cast(message, return_value_policy::reference_internal);
+ }, "Returns a DBC message by its PGN.", "pgn"_a);
+
+ network.def("get_message_by_pgn_and_source", [] (Network& self, uint32_t pgn, uint8_t source) {
+ const auto* message = self.GetMessageByPgnAndSource(pgn, source);
+ return cast(message, return_value_policy::reference_internal);
+ }, "Returns a DBC message by its PGN and source number.", "pgn"_a, "source"_a);
+
+ network.def("get_signal_by_message_id_and_name", [] (Network& self, uint64_t message_id,
+ const std::string& name) {
+ const auto* signal = self.GetSignal(message_id, name);
+ return cast(signal, return_value_policy::reference_internal);
+ }, "Returns a DBC message by its message ID and name.", "message_id"_a, "name"_a);
+
+ network.def("get_signal_by_can_id_and_name", [] (Network& self, uint32_t can_id,
+ const std::string& name) {
+ const auto* signal = self.GetSignalByCanId(can_id, name);
+ return cast(signal, return_value_policy::reference_internal);
+ }, "Returns a DBC signal by its message ID and name.", "can_id"_a, "name"_a);
+
+ network.def("get_signal_by_name", [] (Network& self, const std::string& name) {
+ const auto* signal = self.GetSignalByName(name);
+ return cast(signal, return_value_policy::reference_internal);
+ }, "Returns a DBC signal by its name.", "name"_a);
+
+ network.def("get_signal_group", [] (Network& self, uint64_t message_id,
+ const std::string& name) {
+ const auto* group = self.GetSignalGroup(message_id, name);
+ return cast(group, return_value_policy::reference_internal);
+ }, "Returns a DBC signal group by its message ID and name.", "message_id"_a, "name"_a);
+
+ network.def("get_signal_group_by_name", [] (Network& self, const std::string& name) {
+ const auto* group = self.GetSignalGroupByName(name);
+ return cast(group, return_value_policy::reference_internal);
+ }, "Returns a DBC signal group by its name.", "name"_a);
+
+ network.def_property_readonly("envvars",
+ [](Network& self) {
+ const auto& var_list = self.EnvVars();
+ dict temp_list;
+ for ( const auto& itr : var_list) {
+ const std::string& name = itr.first;
+ const auto& obj = itr.second;
+ temp_list[name.c_str()] = cast(obj, return_value_policy::reference );
+ }
+ return temp_list;
+ });
+
+ network.def_property_readonly("messages",
+ [](Network& self) {
+ const auto& msg_list = self.Messages();
+ dict temp_list;
+ for ( const auto& itr : msg_list) {
+ uint64_t msg_id = itr.first;
+ const auto& data = itr.second;
+
+ temp_list[std::to_string(msg_id).c_str()] = cast(data);
+ }
+ return temp_list;
+ });
+
+ network.def_property_readonly("nodes",
+ [](Network& self) {
+ const auto& node_list = self.Nodes();
+ dict temp_list;
+ for ( const auto& itr : node_list) {
+ const std::string& name = itr.first;
+ const auto& obj = itr.second;
+ temp_list[name.c_str()] = cast(obj, return_value_policy::reference );
+ }
+ return temp_list;
+ });
+
+ network.def_property_readonly("signal_groups",
+ [](Network& self) {
+ const auto& group_list = self.SignalGroups();
+ list temp_list(group_list.size());
+ for (size_t index = 0; index < group_list.size(); ++index) {
+ const auto& group = group_list[index];
+ temp_list[index] = cast(group, return_value_policy::reference );
+ }
+ return temp_list;
+ });
+ network.def_property_readonly("enumerates",
+ [](Network& self) -> dict {
+ const auto& enum_list = self.Enums();
+ dict temp_list;
+ for ( const auto& itr : enum_list) {
+ const auto& enum_name = itr.first;
+ const auto& enum_map = itr.second;
+ const dict temp = cast(enum_map );
+ temp_list[enum_name.c_str()] = temp;
+ }
+ return temp_list;
+ });
+ network.def_property_readonly("attributes",
+ [](Network& self) {
+ const auto& attr_list = self.Attributes();
+ list temp_list(attr_list.size());
+ for (size_t index = 0; index < attr_list.size(); ++index) {
+ const auto& attr = attr_list[index];
+ temp_list[index] = cast(attr, return_value_policy::reference );
+ }
+ return temp_list;
+ });
+
+ network.def("get_attribute_by_name", [] (Network& self, const std::string& name) {
+ const auto* attr = self.GetAttribute(name);
+ return cast(attr, return_value_policy::reference_internal);
+ }, "Returns a DBC attribute by its name.", "name"_a);
+
+ network.def("add_enumerate", [] (Network& self, const std::string& name, const dict& enumerate)-> void {
+ const auto temp_list = enumerate.cast>();
+ self.AddValueTable(name, temp_list);
+ }, "Adds an enumerate to the enumerate list", "name"_a, "enumerate"_a);
+
+ network.def("add_signal_group", &Network::AddSignalGroup,
+ "Adds a signal group to the network object", "group"_a);
+
+}
+
+} // end namespace pydbc
\ No newline at end of file
diff --git a/python/src/pynode.cpp b/python/src/pynode.cpp
new file mode 100644
index 0000000..c27ec3d
--- /dev/null
+++ b/python/src/pynode.cpp
@@ -0,0 +1,60 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+#include "dbc/node.h"
+#include
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace pydbc {
+
+void InitNode(pybind11::module& m) {
+ constexpr std::string_view node_doc = R"(
+Interface against an DBC node object.
+)";
+ auto node = class_(m, "Node", node_doc.data());
+ node.def(init<>());
+
+ node.def_property("name",
+ [](Node& self) {
+ return self.Name();
+ },[](Node& self, const std::string& name) {
+ self.Name(name);
+ });
+ node.def_property("source",
+ [](Node& self) {
+ return self.Source();
+ },[](Node& self, uint8_t source) {
+ self.Source(source);
+ });
+ node.def_property("comment",
+ [](Node& self) {
+ return self.Comment();
+ },[](Node& self, const std::string& comment) {
+ self.Comment(comment);
+ });
+
+ node.def_property_readonly("attributes", [] (Node& self) -> list {
+ const auto& attribute_list = self.Attributes();
+ list temp_list(attribute_list.size());
+ for (size_t index = 0; index < attribute_list.size(); ++index) {
+ const auto& attribute = attribute_list[index];
+ temp_list[index] = cast(attribute, return_value_policy::reference);
+ }
+ return temp_list;
+ }, "Returns a list of attribute objects");
+
+ node.def("create_attribute", &Node::CreateAttribute,
+ return_value_policy::reference_internal,
+ "Creates a new attribute to the message using the input attribute as template.",
+ "attribute"_a);
+
+ node.def("get_attribute", &Node::GetAttribute,
+ "Returns the attribute object by its name", "name"_a,
+ return_value_policy::reference_internal);
+
+}
+
+} // end namespace pydbc
\ No newline at end of file
diff --git a/python/src/pysignal.cpp b/python/src/pysignal.cpp
new file mode 100644
index 0000000..dbb61a8
--- /dev/null
+++ b/python/src/pysignal.cpp
@@ -0,0 +1,273 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+
+#include "dbc/signal.h"
+#include
+
+#include "dbc/attribute.h"
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace pydbc {
+
+void InitSignal(pybind11::module& m) {
+
+ constexpr std::string_view signal_value_doc = R"(
+Support object that purpose is to hold one signal value.
+
+support object that is used when parsing CAN messages. The
+object hold the last parsed messages signal value.
+)";
+ auto value = class_(m,"SignalValue",
+ signal_value_doc.data());
+ value.def(init<>());
+ value.def_readwrite("valid", &SignalValue::valid);
+ value.def_readwrite("signed_value", &SignalValue::signed_value);
+ value.def_readwrite("unsigned_value", &SignalValue::unsigned_value);
+ value.def_readwrite("float_value", &SignalValue::float_value);
+ value.def_readwrite("array_value", &SignalValue::array_value,
+ "The array value is used for signal that have more than 8 bytes.");
+ value.def("clear", &SignalValue::Clear,
+ "Reset all buffered values");
+
+ auto type = enum_(m, "SignalDataType",
+ "Data type for signal values");
+ type.value("SignedData", SignalDataType::SignedData, "Signed integer.");
+ type.value("UnsignedData", SignalDataType::UnsignedData, "Unsigned integer.");
+ type.value("FloatData", SignalDataType::FloatData, "32-bit floating point value.");
+ type.value("DoubleData", SignalDataType::DoubleData, "64-bit floating point value.");
+
+ constexpr std::string_view enum_mux_doc =
+ R"(
+Multiplexer type.
+
+A signal may be multiplexed i.e. have different value depending on
+another multiplexor signal.Then someone invented the extended multiplexed
+value which makes every thing confusing.
+)";
+ auto enum_type = enum_(m,"MuxType", enum_mux_doc.data());
+ enum_type.value("NotMultiplexed", MuxType::NotMultiplexed,
+ "Standard non-multiplexed signal.");
+ enum_type.value("Multiplexor", MuxType::Multiplexor,
+ "The signal is the multiplexor value.");
+ enum_type.value("Multiplexed", MuxType::Multiplexed,
+ "The signal is the multiplexed value.");
+ enum_type.value("ExtendedMultiplexor", MuxType::ExtendedMultiplexor,
+ "The signal is the extended multiplexor value.");
+
+
+ auto extended_mux = class_(m,"ExtendedMux",
+ "Wrapper for extended multiplexer support.");
+ extended_mux.def(init<>());
+ extended_mux.def_readwrite("multiplexor",
+ &ExtendedMux::multiplexor,
+ "Signal name of the multiplexer.");
+ extended_mux.def_readwrite("range_list", &ExtendedMux::range_list,
+ "List of min/max tuples");
+ extended_mux.def("in_range", &ExtendedMux::InRange,
+ "Test if a specific value is in range for this multiplexor",
+ "value"_a);
+
+ auto signal = class_(m, "Signal",
+ "Interface against a DBC signal object." );
+ signal.def(init<>());
+ signal.def_property("name",
+ [](Signal& self) {return self.Name();},
+ [](Signal& self,const std::string& name) {self.Name(name);});
+ signal.def_property("unit",
+ [](Signal& self) {return self.Unit();},
+ [](Signal& self,const std::string& unit) {self.Unit(unit);});
+ signal.def_property("comment",
+ [](Signal& self) {return self.Comment();},
+ [](Signal& self,const std::string& comment) {self.Comment(comment);});
+ signal.def_property("data_type",
+ [](Signal& self) {return self.DataType();},
+ [](Signal& self, SignalDataType type) {self.DataType(type);});
+ signal.def_property("mux",
+ [](Signal& self) {return self.Mux();},
+ [](Signal& self, MuxType type) {self.Mux(type);},
+ "Specifies if and what type of multiplexer. Default is not multiplexed");
+ signal.def_property("mux_value",
+ [](Signal& self) {return self.MuxValue();},
+ [](Signal& self, int value) {self.MuxValue(value);});
+ signal.def_property("bit_start",
+ [](Signal& self) {return self.BitStart();},
+ [](Signal& self, size_t start) {self.BitStart(start);});
+ signal.def_property("bit_length",
+ [](Signal& self) {return self.BitLength();},
+ [](Signal& self, size_t length) {self.BitLength(length);});
+ signal.def_property("little_endian",
+ [](Signal& self) {return self.LittleEndian();},
+ [](Signal& self, bool endian) {self.LittleEndian(endian);},
+ "Defines the byte order.");
+ signal.def_property("scale",
+ [](Signal& self) {return self.Scale();},
+ [](Signal& self, double scale) {self.Scale(scale);});
+ signal.def_property("offset",
+ [](Signal& self) {return self.Offset();},
+ [](Signal& self, double offset) {self.Offset(offset);});
+ signal.def_property("min",
+ [](Signal& self) {return self.Min();},
+ [](Signal& self, double min) {self.Min(min);});
+ signal.def_property("max",
+ [](Signal& self) {return self.Max();},
+ [](Signal& self, double max) {self.Max(max);});
+ signal.def_property("enumerate",
+ [](Signal& self) {
+ dict temp_list = cast(self.EnumList());
+ return temp_list;
+ },
+ [](Signal& self, const dict& enum_list) {
+ self.EnumList(enum_list.cast>());
+ });
+ signal.def_property("receivers",
+ [](Signal& self) {
+ list temp_list = cast(self.Receivers());
+ return temp_list;
+ },
+ [](Signal& self, const list& receivers) {
+ self.Receivers(receivers.cast>());
+ });
+ signal.def_property("attributes",
+ [](Signal& self) {
+ const auto& attr_list = self.Attributes();
+ list temp_list(attr_list.size());
+ for (size_t index = 0; index < temp_list.size(); ++index) {
+ const auto& attribute = attr_list[index];
+ temp_list[index] = cast(attribute, return_value_policy::reference );
+ }
+ return temp_list;
+ },
+ [](Signal& self, const list& attributes) {
+ std::vector temp_list(attributes.size());
+ for (size_t index = 0; index < temp_list.size(); ++ index) {
+ const auto obj = attributes[index];
+ const auto temp = obj.cast();
+ temp_list[index] = temp;
+ }
+ self.Attributes(temp_list);
+ });
+ signal.def_property("message_id",
+ [](Signal& self) {return self.MessageId();},
+ [](Signal& self, uint64_t message_id ) {self.MessageId(message_id);},
+ "Specifies which CAN message the signal belongs to.");
+
+ signal.def_property_readonly("multiplexed", &Signal::IsMultiplexed,
+ "Returns true if this signal is multiplexed.");
+ signal.def_property_readonly("array_value", &Signal::IsArrayValue,
+ "Returns true if number of bytes > 8 i.e. cannot be handled as its specified data type");
+ signal.def("create_attribute", &Signal::CreateAttribute,
+ return_value_policy::reference_internal,
+ "Creates a new attribute to the signal using the input attribute as template.",
+ "attribute"_a);
+ signal.def_property_readonly("extended_mux", &Signal::GetExtendedMux,
+ return_value_policy::reference_internal,
+ "Returns a reference to the internal extended multiplex object");
+ signal.def("get_enum_string", &Signal::GetEnumString,
+ "Returns the enumerate text for a specific value.",
+ "index"_a);
+ signal.def("parse_message", [](Signal& self, const list& message,
+ uint64_t ns1970, uint32_t can_id) {
+ const auto temp_list = message.cast>();
+ self.ParseMessage(temp_list, ns1970, can_id); },
+ "Parses out the signal value, in a CAN message.",
+ "message"_a, "ns1970"_a, "can_id"_a);
+
+ signal.def("reset_sample_counter", &Signal::ResetSampleCounter );
+ signal.def("step_sample_counter", &Signal::StepSampleCounter );
+
+
+ signal.def_property_readonly("sample_counter", &Signal::SampleCounter);
+ signal.def_property("sample_time",
+ [](Signal& self) {return self.SampleTime();},
+ [](Signal& self, uint64_t ns1970) {self.SampleTime(ns1970);},
+ "The sample time. Note absolute time, nano-seconds since 1970");
+ signal.def_property("valid",
+ [](Signal& self) {return self.Valid();},
+ [](Signal& self, bool valid) {self.Valid(valid);});
+
+ signal.def_property_readonly("signal_value", &Signal::GetSignalValue,
+ return_value_policy::reference_internal,
+ "Returns a reference to the internal signal value object");
+
+ signal.def_property_readonly("channel_value", [] (Signal& self) -> tuple {
+
+ switch (self.DataType()) {
+ case SignalDataType::SignedData: {
+ std::pair value(false, 0);
+ value.first = self.ChannelValue(value.second);
+ return cast(value);
+ }
+
+ case SignalDataType::UnsignedData: {
+ if (self.IsArrayValue()) {
+ // This is a most likely a string value
+ std::pair value(false, "");
+ try {
+ value.first = self.ChannelValue(value.second);
+ } catch (const std::exception&) {
+ value.first = false;
+ }
+ return cast(value);
+ } else {
+ std::pair value(false, 0);
+ value.first = self.ChannelValue(value.second);
+ return cast(value);
+ }
+ }
+
+ case SignalDataType::FloatData: {
+ std::pair value(false, 0.0F);
+ value.first = self.ChannelValue(value.second);
+ return cast(value);
+ }
+
+ case SignalDataType::DoubleData: {
+ std::pair value(false, 0.0);
+ value.first = self.ChannelValue(value.second);
+ return cast(value);
+ }
+
+ default:
+ break;
+ }
+
+ const std::pair invalid = {false, 0};
+ return cast(invalid);
+
+ }, "Returns a tuple (valid,value) of the latest signal value.");
+
+ signal.def_property_readonly("channel_array_value", [](Signal& self) -> tuple {
+ std::vector temp_list;
+ const bool valid = self.ChannelValue(temp_list);
+ const list byte_array = cast(temp_list);
+ const std::pair value(valid,byte_array);
+ return cast(value);
+ }, "Returns a tuple (valid,list) of the latest array value.");
+
+ signal.def_property_readonly("channel_text_value", [](Signal& self) -> tuple {
+ std::string text;
+ const bool valid = self.ChannelValue(text);
+ const std::pair value(valid,text);
+ return cast(value);
+ }, "Returns a tuple (valid,str) of the latest value.");
+
+ signal.def_property_readonly("eng_value", [](Signal& self) -> tuple {
+ double temp = 0.0;
+ const bool valid = self.EngValue(temp);
+ const std::pair value(valid,temp);
+ return cast(value);
+ }, "Returns a tuple (valid,double) of the latest scaled value.");
+
+ signal.def_property_readonly("eng_text", [](Signal& self) -> tuple {
+ std::string text;
+ const bool valid = self.EngValue(text);
+ const std::pair value(valid,text);
+ return cast(value);
+ }, "Returns a tuple (valid,str) of the latest scaled value.");
+};
+
+} // namespace pydbc
\ No newline at end of file
diff --git a/python/src/pysignalgroup.cpp b/python/src/pysignalgroup.cpp
new file mode 100644
index 0000000..7dbe5df
--- /dev/null
+++ b/python/src/pysignalgroup.cpp
@@ -0,0 +1,47 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+
+#include "dbc/signalgroup.h"
+#include
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace pydbc {
+
+void InitSignalGroup(pybind11::module& m) {
+ auto group = class_(m, "SignalGroup",
+ "Interface against an DBC signal group");
+ group.def_property("message_id",
+ [](SignalGroup& self) {
+ return self.MessageId();
+ },[](SignalGroup& self, uint64_t message_id) {
+ self.MessageId(message_id);
+ });
+
+ group.def_property("name",
+ [](SignalGroup& self) {
+ return self.Name();
+ },[](SignalGroup& self, const std::string& name) {
+ self.Name(name);
+ });
+
+ group.def_property("repetition",
+ [](SignalGroup& self) {
+ return self.Repetition();
+ },[](SignalGroup& self, size_t repetition) {
+ self.Repetition(repetition);
+ });
+
+ group.def_property("signals",
+ [](SignalGroup& self) -> list {
+ return cast(self.Signals());
+ },[](SignalGroup& self, const list& signal_list) -> void {
+ self.Signals(signal_list.cast>());
+ });
+
+}
+
+} // end namespace pydbc
diff --git a/python/src/pysignalobserver.cpp b/python/src/pysignalobserver.cpp
new file mode 100644
index 0000000..0286964
--- /dev/null
+++ b/python/src/pysignalobserver.cpp
@@ -0,0 +1,118 @@
+/*
+* Copyright 2024 Ingemar Hedvall
+* SPDX-License-Identifier: MIT
+ */
+
+#include "dbc/signalobserver.h"
+#include
+#include
+
+using namespace pybind11;
+using namespace dbc;
+
+namespace pydbc {
+
+void InitSignalObserver(pybind11::module& m) {
+ constexpr std::string_view observer_class_doc = R"(
+The sample observer hold a number of samples.
+
+The observer holds signal values in a circular buffer of
+a maximum number of samples size. The user shall access the samples
+in normal sample order i.e the first sample is 0 while the internal circular
+index might be something else.
+)";
+
+ auto observer = class_>(m,
+ "SignalObserver",
+ observer_class_doc.data());
+
+ observer.def_static("create_observer", &SignalObserver::CreateSignalObserver,
+ "Creates a signal observer object", "signal"_a);
+
+ observer.def_property("max_samples",
+ [](const SignalObserver& self) { return self.MaxSamples();},
+ [](SignalObserver& self, size_t max_samples) {
+ self.MaxSamples(max_samples);
+ });
+
+ observer.def_property_readonly("signal", &SignalObserver::GetSignal,
+ "Returns a reference to the observers signal object.",
+ return_value_policy::reference_internal);
+
+ observer.def("time", &SignalObserver::Time,
+ "Returns the absolute time in nano-seconds since 1970 for a message.",
+ "index"_a);
+
+ observer.def("can_id", &SignalObserver::Time,
+ "Returns the CAN ID for a message.",
+ "index"_a);
+
+ observer.def("channel_value_as_int", [](const SignalObserver& self, size_t index) -> tuple {
+ uint64_t time = 0;
+ int64_t value = 0;
+ const auto valid = self.ChannelValue(index, time, value);
+ return make_tuple(valid, value, time);
+ },"Returns a tuple(valid,value,time) for a sample.", "index"_a);
+
+ observer.def("channel_value_as_uint", [](const SignalObserver& self, size_t index) -> tuple {
+ uint64_t time = 0;
+ uint64_t value = 0;
+ const auto valid = self.ChannelValue(index, time, value);
+ return make_tuple(valid, value, time);
+ },"Returns a tuple(valid,value,time) for a sample.", "index"_a);
+
+ observer.def("channel_value_as_double", [](const SignalObserver& self, size_t index) -> tuple {
+ uint64_t time = 0;
+ double value = 0;
+ const auto valid = self.ChannelValue(index, time, value);
+ return make_tuple(valid, value, time);
+ },"Returns a tuple(valid,value,time) for a sample.", "index"_a);
+
+ observer.def("channel_value_as_string", [](const SignalObserver& self, size_t index) -> tuple {
+ uint64_t time = 0;
+ std::string value;
+ const auto valid = self.ChannelValue(index, time, value);
+ return make_tuple(valid, cast(value), time);
+ },"Returns a tuple(valid,value,time) for a sample.", "index"_a);
+
+ observer.def("eng_value_as_int", [](const SignalObserver& self, size_t index) -> tuple {
+ uint64_t time = 0;
+ int64_t value = 0;
+ const auto valid = self.EngValue(index, time, value);
+ return make_tuple(valid, value, time);
+ },"Returns a tuple(valid,value,time) for a sample.", "index"_a);
+
+ observer.def("eng_value_as_uint", [](const SignalObserver& self, size_t index) -> tuple {
+ uint64_t time = 0;
+ uint64_t value = 0;
+ const auto valid = self.EngValue(index, time, value);
+ return make_tuple(valid, value, time);
+ },"Returns a tuple(valid,value,time) for a sample.", "index"_a);
+
+ observer.def("eng_value_as_double", [](const SignalObserver& self, size_t index) -> tuple {
+ uint64_t time = 0;
+ double value = 0;
+ const auto valid = self.EngValue(index, time, value);
+ return make_tuple(valid, value, time);
+ },"Returns a tuple(valid,value,time) for a sample.", "index"_a);
+
+ observer.def("eng_value_as_string", [](const SignalObserver& self, size_t index) -> tuple {
+ uint64_t time = 0;
+ std::string value;
+ const auto valid = self.ChannelValue(index, time, value);
+ return make_tuple(valid, cast(value), time);
+ },"Returns a tuple(valid,value,time) for a sample.", "index"_a);
+
+ observer.def("reset_sample_index", &SignalObserver::ResetSampleIndex);
+
+ observer.def_property_readonly("first_index", &SignalObserver::FirstIndex);
+ observer.def_property_readonly("last_index", &SignalObserver::FirstIndex);
+ observer.def_property_readonly("nof_samples", &SignalObserver::NofSamples);
+ observer.def_property_readonly("nof_valid_samples", &SignalObserver::NofValidSamples);
+ observer.def("sample_to_index", &SignalObserver::SampleToIndex,
+ "sample"_a);
+ observer.def("time_to_index", &SignalObserver::TimeToIndex,
+ "time"_a);
+}
+
+} // end namespace pydbc
\ No newline at end of file
diff --git a/python/tests/test_attribute.py b/python/tests/test_attribute.py
new file mode 100644
index 0000000..dbd9351
--- /dev/null
+++ b/python/tests/test_attribute.py
@@ -0,0 +1,40 @@
+import pydbc
+import logging
+
+
+def test_attribute():
+ logging.info("TESTING pydbc.Attribute")
+
+ attr = pydbc.Attribute()
+ assert attr is not None
+
+ orig_name = "Obi-Wan"
+ attr.name = orig_name
+ assert attr.name == orig_name
+ logging.debug("Name: " + attr.name)
+
+ orig_type = pydbc.AttributeType.DbcSignal
+ attr.type = orig_type
+ assert attr.type == orig_type
+
+ orig_value_type = pydbc.AttributeValueType.HexValue
+ attr.value_type = orig_value_type
+ assert attr.value_type == orig_value_type
+
+ orig_min = 11.0
+ attr.min = orig_min
+ assert attr.min == orig_min
+
+ orig_max = 22.0
+ attr.max = orig_max
+ assert attr.max == orig_max
+
+ orig_enum = ["Zero", "One", "Two"]
+ attr.enumerate = orig_enum
+ assert attr.enumerate == orig_enum
+
+ orig_value = 11
+ attr.value = 11
+ assert attr.value == orig_value
+
+ logging.info(help(pydbc.Signal))
diff --git a/python/tests/test_dbc_message.py b/python/tests/test_dbc_message.py
new file mode 100644
index 0000000..9911b6d
--- /dev/null
+++ b/python/tests/test_dbc_message.py
@@ -0,0 +1,29 @@
+import pydbc
+import logging
+import time
+
+
+def test_dbc_message():
+ logging.info("TESTING pydbc::DbcMessage")
+
+ msg = pydbc.DbcMessage()
+ assert msg is not None
+
+ orig_time = time.time_ns()
+ msg.time = orig_time
+ assert msg.time == orig_time
+
+ orig_can_id = 12345
+ msg.can_id = orig_can_id
+ assert msg.can_id == orig_can_id
+
+ orig_list = [1, 2, 3, 255]
+ msg.data = orig_list
+ assert msg.data == orig_list
+
+ msg1 = pydbc.DbcMessage(orig_time, orig_can_id, orig_list)
+ assert msg1 is not None
+ assert msg1.time == orig_time
+ assert msg1.can_id == orig_can_id
+ assert msg1.data == orig_list
+ logging.debug("Message 1: " + str(msg1.data))
\ No newline at end of file
diff --git a/python/tests/test_envvar.py b/python/tests/test_envvar.py
new file mode 100644
index 0000000..8bbf38d
--- /dev/null
+++ b/python/tests/test_envvar.py
@@ -0,0 +1,63 @@
+import pydbc
+import logging
+
+
+def test_envvar():
+ logging.info("TESTING pydbc.EnvVar")
+
+ env = pydbc.EnvVar()
+ assert env is not None
+
+ orig_name = "Obi-Wan"
+ env.name = orig_name
+ assert env.name == orig_name
+ logging.debug("Name: " + env.name)
+
+ orig_comment = "Canobi"
+ env.comment = orig_comment
+ assert env.comment == orig_comment
+ logging.debug("Comment: " + env.comment)
+
+ orig_type = pydbc.EnvType.FloatType
+ env.type = orig_type
+ assert env.type == orig_type
+
+ orig_min = 11.0
+ env.min = orig_min
+ assert env.min == orig_min
+
+ orig_max = 22.0
+ env.max = orig_max
+ assert env.max == orig_max
+
+ orig_unit = "mph"
+ env.unit = orig_unit
+ assert env.unit == orig_unit
+ logging.debug("Unit: " + env.unit)
+
+ orig_init = 33.0
+ env.initial_value = orig_init
+ assert env.initial_value == orig_init
+
+ orig_ident = 1234
+ env.ident = orig_ident
+ assert env.ident == orig_ident
+
+ orig_access = pydbc.AccessType.ReadOnly
+ env.access = orig_access
+ assert env.access == orig_access
+ logging.debug("Access: " + str(env.access))
+
+ orig_node = ["Node 1", "Node2", "Node3" ]
+ env.nodes = orig_node
+ assert env.nodes == orig_node
+ logging.debug("Node List: " + str(env.nodes))
+
+ orig_enum = {
+ 0: "Zero",
+ 1: "One",
+ 2: "Two"
+ }
+ env.enumerate = orig_enum
+ assert env.enumerate == orig_enum
+ logging.debug("Enumerate: " + str(env.enumerate))
diff --git a/python/tests/test_message.py b/python/tests/test_message.py
new file mode 100644
index 0000000..553af5d
--- /dev/null
+++ b/python/tests/test_message.py
@@ -0,0 +1,89 @@
+import pydbc
+import logging
+
+
+def test_message():
+ logging.info("TESTING pydbc.Message")
+
+ msg = pydbc.Message()
+ assert msg is not None
+
+ assert msg.message_id == 0
+ msg.message_id = 10123
+ assert msg.message_id == 10123
+ logging.debug("Message ID: " + str(msg.message_id))
+
+ orig_name = "James"
+ msg.name = orig_name
+ assert msg.name == orig_name
+ logging.debug("Name: " + str(msg.name))
+
+ orig_comment = "Kirk"
+ msg.comment = orig_comment
+ assert msg.comment == orig_comment
+ logging.debug("Comment: " + str(msg.comment))
+
+ templ = pydbc.Attribute()
+ assert templ is not None
+
+ attr_name = "Spock"
+ attr = msg.create_attribute(templ)
+ assert attr is not None
+ attr.name = attr_name
+ assert attr.name == attr_name
+ assert len(msg.attributes) == 1
+ assert msg.attributes[0].name == attr_name
+
+ attr1 = msg.get_attribute(attr_name)
+ assert attr is not None
+ assert attr1.name == attr_name
+
+ msg.nof_bytes = 7
+ assert msg.nof_bytes == 7
+
+ node_name = "Enterprise"
+ msg.add_node(node_name)
+ assert msg.node_name == node_name
+
+ invalid_signal = msg.get_signal("foo")
+ assert invalid_signal is None
+
+ signal_name = "Pike"
+ signal = msg.create_signal(signal_name)
+ assert signal is not None
+ assert signal.name == signal_name
+
+ signal1 = msg.get_signal(signal_name)
+ assert signal1 is not None
+ assert signal1.name == signal.name
+ assert len(msg.signals) == 1
+ assert msg.signals[signal_name].name == signal_name
+
+ multiplexor = msg.get_multiplexor()
+ assert multiplexor is None
+
+ assert len(msg.senders) == 1
+ assert msg.senders[0] == node_name
+ assert msg.node_sender(node_name)
+ assert not msg.node_sender("Chapel")
+
+ # Note that nof_bytes == 7 is set earlier
+ data_list_short = [0, 1]
+ assert not msg.update_data(data_list_short)
+ data_list = [0, 1, 2, 3, 4, 5, 6]
+ assert msg.update_data(data_list, 0, 0)
+ assert msg.data == data_list
+ logging.debug("Data: " + str(msg.data))
+
+ msg.parse_message(0, 10123)
+
+ msg.reset_sequence_number()
+ assert msg.sequence_number == 0
+
+ msg.reset_sample_counter()
+ assert msg.sample_counter == 0
+ msg.step_sample_counter()
+ assert msg.sample_counter == 1
+
+
+
diff --git a/python/tests/test_node.py b/python/tests/test_node.py
new file mode 100644
index 0000000..7981285
--- /dev/null
+++ b/python/tests/test_node.py
@@ -0,0 +1,32 @@
+import pydbc
+import logging
+
+
+def test_node():
+ logging.info("TESTING pydbc.Node")
+
+ node = pydbc.Node()
+ assert node is not None
+
+ orig_name = "Michael"
+ node.name = orig_name
+ assert node.name == orig_name
+ logging.debug("Name: " + str(node.name))
+
+ orig_comment = "Burnham"
+ node.comment = orig_comment
+ assert node.comment == orig_comment
+ logging.debug("Comment: " + str(node.comment))
+
+ node.source = 11
+ assert node.source == 11
+
+ assert len(node.attributes) == 0
+ templ = pydbc.Attribute()
+ attr_name = "Tilly"
+ attr = node.create_attribute(templ)
+ attr.name = attr_name
+ assert len(node.attributes) == 1
+ assert node.attributes[0].name == attr_name
+ attr1 = node.get_attribute(attr_name)
+ assert attr1.name == attr_name
diff --git a/python/tests/test_pydbc.py b/python/tests/test_pydbc.py
index 295a363..cdcd86d 100644
--- a/python/tests/test_pydbc.py
+++ b/python/tests/test_pydbc.py
@@ -1,11 +1,12 @@
import pydbc
-import pytest
import logging
import time
def test_dbc_file():
+ logging.info("pydbc::DbcFile TESTING")
+
dbc = pydbc.DbcFile()
assert dbc is not None
@@ -21,3 +22,7 @@ def test_dbc_file():
dbc.base_time = base_time
assert dbc.base_time == base_time
logging.debug(dbc.base_time)
+
+
+
+
diff --git a/python/tests/test_read.py b/python/tests/test_read.py
new file mode 100644
index 0000000..5c27315
--- /dev/null
+++ b/python/tests/test_read.py
@@ -0,0 +1,60 @@
+import unittest
+import os
+import logging
+import pydbc
+
+DBC_SOURCE_DIR = "k:/test/dbc"
+
+
+class TestDbcRead(unittest.TestCase):
+ dbc_list = []
+
+ def setUp(self) -> None:
+
+ self.dbc_list.clear()
+ if not os.path.exists(DBC_SOURCE_DIR):
+ return
+ for (dir_path, dirs, files) in os.walk(DBC_SOURCE_DIR):
+ for filename in files:
+ stem, ext = os.path.splitext(filename)
+ if ext == ".dbc":
+ full_name = os.path.join(dir_path, filename)
+ self.dbc_list.append(full_name)
+
+ def test_check_parser(self):
+ logging.info("TESTING pydbc::TestDbcRead::test_check_parser")
+ if len(self.dbc_list) == 0:
+ self.skipTest("No DBC files to test")
+ for file in self.dbc_list:
+ dbc_file = pydbc.DbcFile()
+ dbc_file.filename = file
+ parse = dbc_file.parse_file()
+ self.assertTrue(parse,"File: " + dbc_file.filename + ", Error: " + dbc_file.last_error )
+ logging.debug("File: " + dbc_file.filename )
+
+ def test_check_message(self):
+ logging.info("TESTING pydbc::TestDbcRead::test_check_message")
+ if len(self.dbc_list) == 0:
+ self.skipTest("No DBC files to test")
+ dbc_file = pydbc.DbcFile()
+ dbc_file.filename = self.dbc_list[0]
+ parse = dbc_file.parse_file()
+ self.assertTrue(parse, "File: " + dbc_file.filename + ", Error: " + dbc_file.last_error)
+
+ network = dbc_file.get_network()
+ self.assertIsNotNone(network, dbc_file.name)
+
+ messages = network.messages
+ for msg_id in network.messages:
+ msg = messages[msg_id]
+ logging.debug("CAN ID:" + str(msg.can_id) + ", Name: " + msg.name)
+ self.assertGreater(msg.nof_bytes, 0, msg.name)
+ for signal_name in msg.signals:
+ signal = msg.signals[signal_name]
+ signal1 = msg.get_signal(signal.name)
+ self.assertEqual(signal.name, signal1.name)
+ logging.debug("Signal Name: " + signal.name)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/tests/test_signal.py b/python/tests/test_signal.py
new file mode 100644
index 0000000..8c84161
--- /dev/null
+++ b/python/tests/test_signal.py
@@ -0,0 +1,172 @@
+import pydbc
+import logging
+
+
+def test_signal_value():
+ logging.info("TESTING pydbc.SignalValue")
+
+ value = pydbc.SignalValue()
+ assert value is not None
+
+ assert not value.valid
+ value.valid = True
+ assert value.valid
+
+ assert value.signed_value == 0
+ value.signed_value = -123
+ assert value.signed_value == -123
+
+ assert value.unsigned_value == 0
+ value.unsigned_value = 123
+ assert value.unsigned_value == 123
+
+ assert value.float_value == 0.0
+ value.float_value = 1.23
+ assert value.float_value == 1.23
+
+ assert value.array_value == []
+ orig_array = [1, 2, 3]
+ value.array_value = orig_array
+ assert value.array_value == orig_array
+
+ value.clear()
+ assert not value.valid
+ assert value.signed_value == 0
+ assert value.unsigned_value == 0
+ assert value.float_value == 0.0
+ assert value.array_value == []
+
+
+def test_extended_mux():
+ logging.info("TESTING pydbc.ExtendedMux")
+
+ mux = pydbc.ExtendedMux()
+ assert mux is not None
+
+ orig_multiplexor = "Yoda"
+ mux.multiplexor = orig_multiplexor
+ assert mux.multiplexor == orig_multiplexor
+
+ orig_range_list = [(0, 10), (100, 110)]
+ mux.range_list = orig_range_list
+ logging.debug("Range List:" + str(mux.range_list))
+ assert mux.range_list == orig_range_list
+ assert mux.in_range(0)
+ assert not mux.in_range(11)
+ assert mux.in_range(110)
+
+
+def test_signal():
+ logging.info("TESTING pydbc.Signal")
+
+ signal = pydbc.Signal()
+ assert signal is not None
+
+ orig_name = "Luke"
+ signal.name = orig_name
+ assert signal.name == orig_name
+
+ orig_unit = "km/h"
+ signal.unit = orig_unit
+ assert signal.unit == orig_unit
+
+ orig_comment = "km/h"
+ signal.comment = orig_comment
+ assert signal.comment == orig_comment
+
+ orig_type = pydbc.SignalDataType.UnsignedData
+ signal.data_type = orig_type
+ assert signal.data_type == orig_type
+
+ assert signal.mux == pydbc.MuxType.NotMultiplexed
+ orig_mux = pydbc.MuxType.Multiplexor
+ signal.mux = orig_mux
+ assert signal.mux == orig_mux
+
+ signal.mux_value = 1
+ assert signal.mux_value == 1
+
+ signal.bit_start = 4
+ assert signal.bit_start == 4
+
+ signal.bit_length = 24
+ assert signal.bit_length == 24
+
+ signal.little_endian = False
+ assert not signal.little_endian
+
+ signal.little_endian = True
+ assert signal.little_endian
+
+ assert signal.scale == 1
+ assert signal.offset == 0
+
+ signal.scale = 22
+ signal.offset = 5
+ assert signal.scale == 22
+ assert signal.offset == 5
+
+ signal.min = 4
+ assert signal.min == 4
+ signal.max = 20
+ assert signal.max == 20
+
+ orig_enum = {0: "Zero", 1: "One", 2: "Two"}
+ signal.enumerate = orig_enum
+ assert signal.enumerate == orig_enum
+
+ orig_receivers = ["EMS", "EDU"]
+ signal.receivers = orig_receivers
+ assert signal.receivers == orig_receivers
+
+ orig_attr = [pydbc.Attribute(pydbc.AttributeType.DbcSignal, "Trouble")]
+ signal.attributes = orig_attr
+ assert signal.attributes[0].name == orig_attr[0].name
+
+ signal.message_id = 234
+ assert signal.message_id == 234
+
+ assert not signal.multiplexed
+ assert not signal.array_value
+
+ attr = signal.create_attribute(orig_attr[0])
+ assert len(signal.attributes) == 2
+
+ ext_mux = signal.extended_mux
+ ext_mux.multiplexor = "Olle"
+ logging.debug("Multiplexor: " + str(signal.extended_mux.multiplexor))
+ assert signal.extended_mux.multiplexor == "Olle"
+
+ assert signal.get_enum_string(1) == "One"
+
+ assert signal.sample_counter == 0
+ signal.step_sample_counter()
+ assert signal.sample_counter == 1
+ signal.reset_sample_counter()
+ assert signal.sample_counter == 0
+
+ assert signal.sample_time == 0
+
+ signal_value = signal.signal_value
+ signal_value.unsigned_value = 15
+ logging.debug("Signal Value: " + str(signal.signal_value.unsigned_value))
+ assert signal.signal_value.unsigned_value == 15
+
+ signal.data_type = pydbc.SignalDataType.UnsignedData
+ signal.valid = True
+ signal.scale = 2.0
+ signal.offset = 10.0
+ signal.signal_value.valid = True
+ signal.signal_value.unsigned_value = 333
+ signal.enumerate = {}
+
+ lrv = signal.channel_value
+ assert lrv[0]
+ assert lrv[1] == 333
+ assert signal.channel_text_value[0]
+ assert signal.channel_text_value[1] == "333"
+
+ assert signal.eng_value[0]
+ assert signal.eng_value[1] == 676
+ assert signal.eng_text[0]
+ assert signal.eng_text[1] == "676"
diff --git a/src/dbcfile.cpp b/src/dbcfile.cpp
index 9bf2f74..2762b5d 100644
--- a/src/dbcfile.cpp
+++ b/src/dbcfile.cpp
@@ -22,6 +22,7 @@ bool DbcFile::ParseFile() {
if (!DbcHelper::FileExist(filename)) {
std::ostringstream error;
error << "The file doesn't exist. File: " << filename_;
+ last_error_ = error.str();
return false;
}
@@ -219,4 +220,17 @@ void DbcFile::ReparseMessageList() {
}
}
+void DbcFile::ClearObserverList() {
+ if (!network_) {
+ return;
+ }
+ for (auto& itrMsg : network_->Messages()) {
+ auto& message = itrMsg.second;
+ for (auto& itrSignal : message.Signals()) {
+ auto& signal = itrSignal.second;
+ signal.ClearObserverList();
+ }
+ }
+}
+
} // namespace dbc
\ No newline at end of file
diff --git a/src/signal.cpp b/src/signal.cpp
index 926aa74..3b9a70c 100644
--- a/src/signal.cpp
+++ b/src/signal.cpp
@@ -9,11 +9,7 @@
namespace dbc {
Signal::~Signal() {
- for (auto* observer : observer_list_) {
- if (observer != nullptr) {
- observer->DetachObserver();
- }
- }
+ observer_list_.clear();
}
Attribute& Signal::CreateAttribute(const Attribute& definition) {
@@ -64,13 +60,9 @@ void Signal::ParseMessage(const std::vector& message,
channel_value_.signed_value = temp;
break;
}
- channel_value_.valid = true;
+
case SignalDataType::UnsignedData: {
- size_t bytes = bit_length_ / 8;
- if ((bit_length_ % 8) != 0) {
- ++bytes;
- }
- if (bytes > 8) {
+ if (IsArrayValue()) {
auto temp = DbcHelper::RawToByteArray(bit_start_, bit_length_,
message.data());
channel_value_.array_value = temp;
@@ -132,11 +124,7 @@ bool Signal::ChannelValue(std::string& value) const {
}
case SignalDataType::UnsignedData: {
- size_t bytes = bit_length_ / 8;
- if ((bit_length_ % 8) != 0) {
- ++bytes;
- }
- if (bytes > 8) {
+ if (IsArrayValue()) {
try {
valid = channel_value_.valid && Valid();
const auto temp = channel_value_.array_value;
@@ -186,6 +174,13 @@ bool Signal::ChannelValue(std::string& value) const {
return valid;
}
+template <>
+bool Signal::ChannelValue(std::vector& value) const {
+ const bool valid = channel_value_.valid && Valid();
+ value = channel_value_.array_value;
+ return valid;
+}
+
template <>
bool Signal::ChannelValue(SignalValue& value) const {
bool valid = channel_value_.valid && Valid();
@@ -362,25 +357,29 @@ std::string Signal::MuxAsString() const {
return temp.str();
}
-void Signal::AttachObserver(ISampleObserver* observer) const {
- observer_list_.push_back(observer);
+void Signal::AttachObserver(std::shared_ptr& observer) const {
+ std::shared_ptr temp(observer);
+ observer_list_.push_back(std::move(temp));
}
-void Signal::DetachObserver(const ISampleObserver* observer) const {
- for (auto itr = observer_list_.begin(); itr != observer_list_.end(); ) {
- if ( *itr == observer) {
- itr = observer_list_.erase(itr);
- } else {
- ++itr;
- }
- }
+void Signal::ClearObserverList() const {
+ observer_list_.clear();
}
void Signal::FireOnSample() {
- for (auto* observer : observer_list_) {
+ for (auto& observer : observer_list_) {
if (observer != nullptr) {
observer->OnSample();
}
}
}
+
+bool Signal::IsArrayValue() const {
+ size_t bytes = BitLength() / 8;
+ if ((BitLength() % 8) != 0) { // This shouldn't happen
+ ++bytes;
+ }
+ return bytes > 8;
+}
+
} // namespace dbc
\ No newline at end of file
diff --git a/src/signalobserver.cpp b/src/signalobserver.cpp
index 5bd991b..19095b5 100644
--- a/src/signalobserver.cpp
+++ b/src/signalobserver.cpp
@@ -6,24 +6,24 @@
#include "dbc/signalobserver.h"
#include
+
namespace dbc {
SignalObserver::SignalObserver(const Signal& signal)
: signal_(signal) {
- signal_.AttachObserver(this);
- attached_ = true;
MaxSamples(signal.SampleCounter());
}
SignalObserver::~SignalObserver() {
- SignalObserver::DetachObserver();
+
}
-void SignalObserver::DetachObserver() {
- if (attached_) {
- signal_.DetachObserver(this);
- attached_ = false;
- }
+std::shared_ptr SignalObserver::CreateSignalObserver(
+ const Signal& signal) {
+ auto observer = std::shared_ptr(new SignalObserver(signal));
+ auto temp = std::shared_ptr(observer);
+ signal.AttachObserver(temp);
+ return observer;
}
void SignalObserver::MaxSamples(size_t max_nof_samples) {
@@ -110,6 +110,8 @@ size_t SignalObserver::NofValidSamples() const {
});
}
+
+
template <>
bool SignalObserver::ChannelValue(size_t index, uint64_t& ns1970,
std::string& value) const {