diff --git a/docs/CANMODULE-UTILS.md b/docs/CANMODULE-UTILS.md index 5201ad81..89516bbc 100644 --- a/docs/CANMODULE-UTILS.md +++ b/docs/CANMODULE-UTILS.md @@ -50,6 +50,7 @@ high_speed timeout vcan sent_acknowledgement +operating_mode ``` Hyphenated key spelling is also accepted, for example `--set enable-termination=true`. diff --git a/src/include/CanDeviceConfiguration.h b/src/include/CanDeviceConfiguration.h index 5b005601..bfce027d 100644 --- a/src/include/CanDeviceConfiguration.h +++ b/src/include/CanDeviceConfiguration.h @@ -111,6 +111,21 @@ struct CanDeviceConfiguration { */ std::optional sent_acknowledgement; + /** + * @brief The operating mode of the CAN bus. + * + * This parameter is optional and only has effect for Anagate devices. It + * defaults to 0 if unset. The accepted values are: + * - 0 = default mode. + * - 1 = loop back mode: no telegrams are sent via the CAN bus, they are + * instead received as if they had been transmitted over CAN by a + * different CAN device. + * - 2 = listen mode: the device operates as a passive bus partner, meaning + * no telegrams are sent to the CAN bus (nor ACKs for incoming telegrams). + * - 3 = offline mode: no telegrams are sent or received on the CAN bus. + */ + std::optional operating_mode; + std::string to_string() const noexcept; /** @@ -153,6 +168,7 @@ inline const std::vector& fields() { {"timeout", &CanDeviceConfiguration::timeout}, {"vcan", &CanDeviceConfiguration::vcan}, {"sent_acknowledgement", &CanDeviceConfiguration::sent_acknowledgement}, + {"operating_mode", &CanDeviceConfiguration::operating_mode}, }; return kFields; } diff --git a/src/main/CanVendorAnagate.cpp b/src/main/CanVendorAnagate.cpp index c58c5ea1..d76b6c6c 100644 --- a/src/main/CanVendorAnagate.cpp +++ b/src/main/CanVendorAnagate.cpp @@ -20,7 +20,7 @@ const std::set CanVendorAnagate::accepted_parameters = { "bus_number", "host", "bitrate", "enable_termination", "high_speed", "sent_acknowledgement", - "timeout"}; + "timeout", "operating_mode"}; /** * @brief Callback function to handle incoming CAN frames from the AnaGate DLL. @@ -120,6 +120,9 @@ CanReturnCode CanVendorAnagate::vendor_open() noexcept { high_speed = args().config.high_speed.value(); } + operating_mode = + static_cast(args().config.operating_mode.value_or(0)); + // Set the modified configuration CANSetGlobals(m_handle, bitrate, operating_mode, enable_termination, high_speed, enable_timestamp); diff --git a/src/python/CanModule.cpp b/src/python/CanModule.cpp index a951b581..4ff1245d 100644 --- a/src/python/CanModule.cpp +++ b/src/python/CanModule.cpp @@ -92,6 +92,7 @@ PYBIND11_MODULE(canmodule, m) { .def_readwrite("timeout", &CanDeviceConfiguration::timeout) .def_readwrite("sent_acknowledgement", &CanDeviceConfiguration::sent_acknowledgement) + .def_readwrite("operating_mode", &CanDeviceConfiguration::operating_mode) .def("__str__", &CanDeviceConfiguration::to_string); py::class_(m, "CanDiagnostics") diff --git a/test/cpp/CanDeviceConfiguration_test.cpp b/test/cpp/CanDeviceConfiguration_test.cpp index f2fc19fc..8725c1ab 100644 --- a/test/cpp/CanDeviceConfiguration_test.cpp +++ b/test/cpp/CanDeviceConfiguration_test.cpp @@ -22,6 +22,7 @@ TEST_F(CanDeviceConfigurationTest, DefaultConstructorLeavesEverythingUnset) { ASSERT_FALSE(config.timeout.has_value()); ASSERT_FALSE(config.vcan.has_value()); ASSERT_FALSE(config.sent_acknowledgement.has_value()); + ASSERT_FALSE(config.operating_mode.has_value()); } TEST_F(CanDeviceConfigurationTest, AssignsEveryParameterOfTheMap) { @@ -35,6 +36,7 @@ TEST_F(CanDeviceConfigurationTest, AssignsEveryParameterOfTheMap) { {"timeout", "6000"}, {"vcan", "true"}, {"sent_acknowledgement", "1"}, + {"operating_mode", "2"}, }); ASSERT_EQ(config.bus_name.value(), "can0"); @@ -46,6 +48,7 @@ TEST_F(CanDeviceConfigurationTest, AssignsEveryParameterOfTheMap) { ASSERT_EQ(config.timeout.value(), 6000); ASSERT_TRUE(config.vcan.value()); ASSERT_EQ(config.sent_acknowledgement.value(), 1); + ASSERT_EQ(config.operating_mode.value(), 2); } TEST_F(CanDeviceConfigurationTest, LeavesTheAbsentParametersUnset) { @@ -61,11 +64,12 @@ TEST_F(CanDeviceConfigurationTest, LeavesTheAbsentParametersUnset) { ASSERT_FALSE(config.timeout.has_value()); ASSERT_FALSE(config.vcan.has_value()); ASSERT_FALSE(config.sent_acknowledgement.has_value()); + ASSERT_FALSE(config.operating_mode.has_value()); } TEST_F(CanDeviceConfigurationTest, AssignsEveryParameterPositionally) { const CanDeviceConfiguration config{"can0", 2, "127.0.0.1", 125000, true, - false, 6000, true, 1}; + false, 6000, true, 1, 2}; ASSERT_EQ(config.bus_name.value(), "can0"); ASSERT_EQ(config.bus_number.value(), 2); @@ -76,6 +80,7 @@ TEST_F(CanDeviceConfigurationTest, AssignsEveryParameterPositionally) { ASSERT_EQ(config.timeout.value(), 6000); ASSERT_TRUE(config.vcan.value()); ASSERT_EQ(config.sent_acknowledgement.value(), 1); + ASSERT_EQ(config.operating_mode.value(), 2); } TEST_F(CanDeviceConfigurationTest, LeavesTrailingPositionalParametersUnset) { @@ -90,6 +95,7 @@ TEST_F(CanDeviceConfigurationTest, LeavesTrailingPositionalParametersUnset) { ASSERT_FALSE(config.timeout.has_value()); ASSERT_FALSE(config.vcan.has_value()); ASSERT_FALSE(config.sent_acknowledgement.has_value()); + ASSERT_FALSE(config.operating_mode.has_value()); } TEST_F(CanDeviceConfigurationTest, RejectsUnknownParameters) { @@ -117,6 +123,8 @@ TEST_F(CanDeviceConfigurationTest, RejectsValuesOfTheWrongType) { {"high_speed", "TRUE"}, {"vcan", "1"}, {"sent_acknowledgement", "abc"}, + {"operating_mode", "-1"}, + {"operating_mode", "abc"}, }; for (const auto& [key, value] : invalid_values) { diff --git a/test/python/test_anagate.py b/test/python/test_anagate.py index 9f4350ce..5dda6f98 100644 --- a/test/python/test_anagate.py +++ b/test/python/test_anagate.py @@ -224,6 +224,31 @@ def test_anagate_diagnostics(): ) +def test_anagate_operating_mode(): + canDeviceConfig = CanDeviceConfiguration() + canDeviceConfig.host = DEVICE_ONE.host + canDeviceConfig.bus_number = DEVICE_ONE.bus_number + canDeviceConfig.bitrate = DEVICE_ONE.bitrate + canDeviceConfig.enable_termination = DEVICE_ONE.enable_termination + canDeviceConfig.high_speed = DEVICE_ONE.high_speed + canDeviceConfig.operating_mode = 1 + + myDevice1 = CanDevice.create("anagate", CanDeviceArguments(canDeviceConfig)) + try: + r = myDevice1.open() + assert r == CanReturnCode.success + + diag = myDevice1.diagnostics() + assert diag.mode == "LOOPBACK" + finally: + myDevice1.close() + + canDeviceConfig.operating_mode = 0 + myDevice1 = CanDevice.create("anagate", CanDeviceArguments(canDeviceConfig)) + myDevice1.open() + myDevice1.close() + + def test_anagate_bus_off_recovery(): received_frames_dev1 = [] myDevice1 = CanDevice.create( diff --git a/test/python/test_canmodule_utils_config.py b/test/python/test_canmodule_utils_config.py index 68c46f9d..5ca18213 100644 --- a/test/python/test_canmodule_utils_config.py +++ b/test/python/test_canmodule_utils_config.py @@ -164,6 +164,7 @@ def test_build_can_device_configuration_sets_all_supported_fields(): "high_speed": "false", "timeout": "6000", "sent_acknowledgement": "1", + "operating_mode": "2", } ) @@ -174,3 +175,4 @@ def test_build_can_device_configuration_sets_all_supported_fields(): assert configuration.high_speed is False assert configuration.timeout == 6000 assert configuration.sent_acknowledgement == 1 + assert configuration.operating_mode == 2