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 {