From eae0253e1b640b2279cf8e60579b8c0698e13626 Mon Sep 17 00:00:00 2001 From: Adrian Del Grosso <10929341+ad3154@users.noreply.github.com> Date: Sat, 28 Oct 2023 20:52:20 -0500 Subject: [PATCH] [VT/TC]: Language Command Send and Setters Added the ability to respond to requests for language commands. Added setters for language command values. This functionality will be needed for VT and TC servers. --- .../isobus_language_command_interface.hpp | 90 +++++++- .../src/isobus_language_command_interface.cpp | 150 ++++++++++++- test/language_command_interface_tests.cpp | 210 ++++++++++++++++++ 3 files changed, 446 insertions(+), 4 deletions(-) diff --git a/isobus/include/isobus/isobus/isobus_language_command_interface.hpp b/isobus/include/isobus/isobus/isobus_language_command_interface.hpp index 338dd576..b8010a55 100644 --- a/isobus/include/isobus/isobus/isobus_language_command_interface.hpp +++ b/isobus/include/isobus/isobus/isobus_language_command_interface.hpp @@ -10,6 +10,7 @@ #ifndef ISOBUS_LANGUAGE_COMMAND_INTERFACE_HPP #define ISOBUS_LANGUAGE_COMMAND_INTERFACE_HPP +#include "isobus/isobus/can_callbacks.hpp" #include "isobus/isobus/can_message.hpp" #include @@ -138,8 +139,9 @@ namespace isobus /// @brief Constructor for a LanguageCommandInterface /// @details This constructor will make a version of the class that will accept the message from any source - /// @param sourceControlFunction The internal control function that the interface should communicate from - explicit LanguageCommandInterface(std::shared_ptr sourceControlFunction); + /// @param[in] sourceControlFunction The internal control function that the interface should communicate from + /// @param[in] shouldRespondToRequests Set this to true if you want this interface to respond to requests for the language command PGN (used in VT/TC servers) + LanguageCommandInterface(std::shared_ptr sourceControlFunction, bool shouldRespondToRequests = false); /// @brief Constructor for a LanguageCommandInterface /// @details This constructor will make a version of the class that will filter the message to be @@ -173,16 +175,32 @@ namespace isobus /// @return `true` if the message was sent, otherwise `false` bool send_request_language_command() const; + /// @brief Sends a language command based on the current content of this class as a broadcast. + /// @note This is only meant to be used by a VT server or TC/DL server + /// @return `true` if the message was sent, otherwise `false` + bool send_language_command() const; + /// @brief Returns the commanded country code parsed from the last language command specifying the operator's desired language dialect. /// @note ISO 11783 networks shall use the alpha-2 country codes in accordance with ISO 3166-1. /// @return The commanded country code, or an empty string if none specified. std::string get_country_code() const; + /// @brief Sets the country code specifying the operator's desired language dialect. + /// @attention This is meant for servers only + /// @note ISO 11783 networks shall use the alpha-2 country codes in accordance with ISO 3166-1. + /// @param[in] country The country code to set + void set_country_code(std::string country); + /// @brief Returns the commanded language code parsed from the last language command /// @note If you do not support the returned language, your default shall be used /// @return The commanded language code (usually 2 characters length) std::string get_language_code() const; + /// @brief Sets the language + /// @attention This is meant for servers only! + /// @param[in] language The language code to set + void set_language_code(std::string language); + /// @brief Returns a timestamp (in ms) corresponding to when the interface last received a language command /// @return timestamp in milliseconds corresponding to when the interface last received a language command std::uint32_t get_language_command_timestamp() const; @@ -191,46 +209,101 @@ namespace isobus /// @return The decimal symbol that was last commanded DecimalSymbols get_commanded_decimal_symbol() const; + /// @brief Sets the decimal symbol to be used. + /// @attention This is meant for servers only! + /// @param[in] decimals The decimal symbol that was last commanded + void set_commanded_decimal_symbol(DecimalSymbols decimals); + /// @brief Returns the commanded time format parsed from the last language command /// @return The time format that was last commanded TimeFormats get_commanded_time_format() const; + /// @brief Sets the commanded time format + /// @attention This is meant for servers only! + /// @param[in] format The time format to set + void set_commanded_time_format(TimeFormats format); + /// @brief Returns the commanded date format parsed from the last language command /// @return The date format that was last commanded DateFormats get_commanded_date_format() const; + /// @brief Sets the commanded date format + /// @attention This is meant for servers only! + /// @param[in] format The date format to set + void set_commanded_date_format(DateFormats format); + /// @brief Returns the commanded distance units parsed from the last language command /// @return The distance units that were last commanded DistanceUnits get_commanded_distance_units() const; + /// @brief Sets the commanded distance units + /// @attention This is meant for servers only! + /// @param[in] units The commanded distance units to set + void set_commanded_distance_units(DistanceUnits units); + /// @brief Returns the commanded area units parsed from the last received language command /// @return The area units that were last commanded AreaUnits get_commanded_area_units() const; + /// @brief Sets the commanded area units + /// @attention This is meant for servers only! + /// @param[in] units The area units to set + void set_commanded_area_units(AreaUnits units); + /// @brief Returns the commanded volume units parsed from the last received language command /// @return The volume units that were last commanded VolumeUnits get_commanded_volume_units() const; + /// @brief Sets the commanded volume units + /// @attention This is meant for servers only! + /// @param[in] units The commanded volume units + void set_commanded_volume_units(VolumeUnits units); + /// @brief Returns the commanded mass units parsed from the last received language command /// @return The mass units that were last commanded MassUnits get_commanded_mass_units() const; + /// @brief Sets the commanded mass units + /// @attention This is meant for servers only! + /// @param[in] units The commanded mass units + void set_commanded_mass_units(MassUnits units); + /// @brief Returns the commanded temperature units parsed from the last received language command /// @return The temperature units that were last commanded TemperatureUnits get_commanded_temperature_units() const; + /// @brief Sets the commanded temperature units + /// @attention This is meant for servers only! + /// @param[in] units The commanded temperature unit system + void set_commanded_temperature_units(TemperatureUnits units); + /// @brief Returns the commanded pressure units parsed from the last received language command /// @return The pressure units that were last commanded PressureUnits get_commanded_pressure_units() const; + /// @brief Sets the commanded pressure units + /// @attention This is meant for servers only! + /// @param[in] units The commanded pressure unit system to command + void set_commanded_pressure_units(PressureUnits units); + /// @brief Returns the commanded force units parsed from the last received language command /// @return The force units that were last commanded ForceUnits get_commanded_force_units() const; + /// @brief Sets the commanded force units + /// @attention This is meant for servers only! + /// @param[in] units The commanded force unit system to command + void set_commanded_force_units(ForceUnits units); + /// @brief Returns the commanded "unit system" generic value that was parsed from the last received language command /// @return The commanded unit system UnitSystem get_commanded_generic_units() const; + /// @brief Sets the commanded generic unit system + /// @attention This is meant for servers only! + /// @param[in] units The commanded generic unit system to command + void set_commanded_generic_units(UnitSystem units); + /// @brief Returns The raw bytes that comprise the current localization data as defined in ISO11783-7 /// @returns The raw bytes that comprise the current localization data const std::array get_localization_raw_data() const; @@ -241,6 +314,18 @@ namespace isobus static void process_rx_message(const CANMessage &message, void *parentPointer); private: + /// @brief This is a callback to handle clients requesting the content of our language data for things like VT/TC servers + /// @param[in] parameterGroupNumber The PGN to handle in the callback + /// @param[in] requestingControlFunction The control function that is requesting the PGN + /// @param[out] acknowledge Tells the stack if we want to send an ACK or NACK + /// @param[out] acknowledgeType Tells the stack exactly how we want to do an ACK + /// @param[in] parentPointer A generic context pointer to locate the specific instance of this class we want + static bool on_language_request(std::uint32_t parameterGroupNumber, + std::shared_ptr requestingControlFunction, + bool &acknowledge, + AcknowledgementType &acknowledgeType, + void *parentPointer); + std::shared_ptr myControlFunction; ///< The control function to send messages as std::shared_ptr myPartner; ///< The partner to talk to, or nullptr to listen to all CFs std::string countryCode; ///< The last received alpha-2 country code as specified by ISO 3166-1, such as "NL, FR, GB, US, DE". @@ -258,6 +343,7 @@ namespace isobus ForceUnits forceUnitSystem = ForceUnits::Metric; ///< The force units that were commanded by the last language command message UnitSystem genericUnitSystem = UnitSystem::Metric; ///< The "unit system" that was commanded by the last language command message bool initialized = false; ///< Tracks if initialize has been called yet for this interface + bool respondToRequests = false; ///< Stores if the class should respond to PGN requests for the language command }; } // namespace isobus diff --git a/isobus/src/isobus_language_command_interface.cpp b/isobus/src/isobus_language_command_interface.cpp index 51d9e0a0..f863a985 100644 --- a/isobus/src/isobus_language_command_interface.cpp +++ b/isobus/src/isobus_language_command_interface.cpp @@ -21,9 +21,10 @@ namespace isobus { - LanguageCommandInterface::LanguageCommandInterface(std::shared_ptr sourceControlFunction) : + LanguageCommandInterface::LanguageCommandInterface(std::shared_ptr sourceControlFunction, bool shouldRespondToRequests) : myControlFunction(sourceControlFunction), - myPartner(nullptr) + myPartner(nullptr), + respondToRequests(shouldRespondToRequests) { } @@ -38,6 +39,11 @@ namespace isobus if (initialized) { CANNetworkManager::CANNetwork.remove_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::LanguageCommand), process_rx_message, this); + + if (respondToRequests && (!myControlFunction->get_pgn_request_protocol().expired())) + { + myControlFunction->get_pgn_request_protocol().lock()->remove_pgn_request_callback(static_cast(CANLibParameterGroupNumber::LanguageCommand), on_language_request, this); + } } } @@ -48,6 +54,11 @@ namespace isobus if (nullptr != myControlFunction) { CANNetworkManager::CANNetwork.add_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::LanguageCommand), process_rx_message, this); + + if (respondToRequests && (!myControlFunction->get_pgn_request_protocol().expired())) + { + myControlFunction->get_pgn_request_protocol().lock()->register_pgn_request_callback(static_cast(CANLibParameterGroupNumber::LanguageCommand), on_language_request, this); + } initialized = true; } else @@ -87,16 +98,78 @@ namespace isobus return retVal; } + bool LanguageCommandInterface::send_language_command() const + { + std::array buffer{ + static_cast(languageCode[0]), + static_cast(languageCode[1]), + static_cast((static_cast(timeFormat) << 4) | + (static_cast(decimalSymbol) << 6)), + static_cast(dateFormat), + static_cast(static_cast(massUnitSystem) | + (static_cast(volumeUnitSystem) << 2) | + (static_cast(areaUnitSystem) << 4) | + (static_cast(distanceUnitSystem) << 6)), + static_cast(static_cast(genericUnitSystem) | + (static_cast(forceUnitSystem) << 2) | + (static_cast(pressureUnitSystem) << 4) | + (static_cast(temperatureUnitSystem) << 6)), + static_cast(countryCode[0]), + static_cast(countryCode[1]) + }; + return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::LanguageCommand), + buffer.data(), + buffer.size(), + myControlFunction, + nullptr); + } + std::string LanguageCommandInterface::get_country_code() const { return countryCode; } + void LanguageCommandInterface::set_country_code(std::string country) + { + if (country.length() > 2) + { + CANStackLogger::warn("[VT/TC]: Language command country code should not be more than 2 characters! It will be truncated."); + } + else if (country.length() < 2) + { + CANStackLogger::warn("[VT/TC]: Language command country code should not be less than 2 characters! It will be padded."); + } + + while (country.length() < 2) + { + country.push_back(' '); + } + countryCode = country; + } + std::string LanguageCommandInterface::get_language_code() const { return languageCode; } + void LanguageCommandInterface::set_language_code(std::string language) + { + if (language.length() > 2) + { + CANStackLogger::warn("[VT/TC]: Language command language code should not be more than 2 characters! It will be truncated."); + } + else if (language.length() < 2) + { + CANStackLogger::warn("[VT/TC]: Language command language code should not be less than 2 characters! It will be padded."); + } + + while (language.length() < 2) + { + language.push_back(' '); + } + languageCode = language; + } + std::uint32_t LanguageCommandInterface::get_language_command_timestamp() const { return languageCommandTimestamp_ms; @@ -107,56 +180,111 @@ namespace isobus return decimalSymbol; } + void LanguageCommandInterface::set_commanded_decimal_symbol(DecimalSymbols decimals) + { + decimalSymbol = decimals; + } + LanguageCommandInterface::TimeFormats LanguageCommandInterface::get_commanded_time_format() const { return timeFormat; } + void LanguageCommandInterface::set_commanded_time_format(TimeFormats format) + { + timeFormat = format; + } + LanguageCommandInterface::DateFormats LanguageCommandInterface::get_commanded_date_format() const { return dateFormat; } + void LanguageCommandInterface::set_commanded_date_format(DateFormats format) + { + dateFormat = format; + } + LanguageCommandInterface::DistanceUnits LanguageCommandInterface::get_commanded_distance_units() const { return distanceUnitSystem; } + void LanguageCommandInterface::set_commanded_distance_units(DistanceUnits units) + { + distanceUnitSystem = units; + } + LanguageCommandInterface::AreaUnits LanguageCommandInterface::get_commanded_area_units() const { return areaUnitSystem; } + void LanguageCommandInterface::set_commanded_area_units(AreaUnits units) + { + areaUnitSystem = units; + } + LanguageCommandInterface::VolumeUnits LanguageCommandInterface::get_commanded_volume_units() const { return volumeUnitSystem; } + void LanguageCommandInterface::set_commanded_volume_units(VolumeUnits units) + { + volumeUnitSystem = units; + } + LanguageCommandInterface::MassUnits LanguageCommandInterface::get_commanded_mass_units() const { return massUnitSystem; } + void LanguageCommandInterface::set_commanded_mass_units(MassUnits units) + { + massUnitSystem = units; + } + LanguageCommandInterface::TemperatureUnits LanguageCommandInterface::get_commanded_temperature_units() const { return temperatureUnitSystem; } + void LanguageCommandInterface::set_commanded_temperature_units(TemperatureUnits units) + { + temperatureUnitSystem = units; + } + LanguageCommandInterface::PressureUnits LanguageCommandInterface::get_commanded_pressure_units() const { return pressureUnitSystem; } + void LanguageCommandInterface::set_commanded_pressure_units(PressureUnits units) + { + pressureUnitSystem = units; + } + LanguageCommandInterface::ForceUnits LanguageCommandInterface::get_commanded_force_units() const { return forceUnitSystem; } + void LanguageCommandInterface::set_commanded_force_units(ForceUnits units) + { + forceUnitSystem = units; + } + LanguageCommandInterface::UnitSystem LanguageCommandInterface::get_commanded_generic_units() const { return genericUnitSystem; } + void LanguageCommandInterface::set_commanded_generic_units(UnitSystem units) + { + genericUnitSystem = units; + } + const std::array LanguageCommandInterface::get_localization_raw_data() const { std::array retVal = { 0 }; @@ -229,4 +357,22 @@ namespace isobus } } + bool LanguageCommandInterface::on_language_request(std::uint32_t parameterGroupNumber, + std::shared_ptr, + bool &acknowledge, + AcknowledgementType &acknowledgeType, + void *parentPointer) + { + bool retVal = false; + + if ((nullptr != parentPointer) && (static_cast(CANLibParameterGroupNumber::LanguageCommand) == parameterGroupNumber)) + { + auto targetInterface = static_cast(parentPointer); + acknowledge = false; + acknowledgeType = AcknowledgementType::Positive; + retVal = true; + targetInterface->send_language_command(); + } + return retVal; + } } diff --git a/test/language_command_interface_tests.cpp b/test/language_command_interface_tests.cpp index ac10d088..7b6fb8a1 100644 --- a/test/language_command_interface_tests.cpp +++ b/test/language_command_interface_tests.cpp @@ -1,5 +1,15 @@ +//================================================================================================ +/// @file language_command_interface_tests.cpp +/// +/// @brief Unit tests for the LanguageCommandInterface class +/// @author Adrian Del Grosso +/// +/// @copyright 2023 Adrian Del Grosso +//================================================================================================ #include +#include "isobus/hardware_integration/can_hardware_interface.hpp" +#include "isobus/hardware_integration/virtual_can_plugin.hpp" #include "isobus/isobus/can_NAME_filter.hpp" #include "isobus/isobus/can_internal_control_function.hpp" #include "isobus/isobus/can_parameter_group_number_request_protocol.hpp" @@ -174,3 +184,203 @@ TEST(LANGUAGE_COMMAND_INTERFACE_TESTS, MessageContentParsing) //! @todo try to reduce the reference count, such that that we don't use a control function after it is destroyed ASSERT_TRUE(internalECU->destroy(2)); } + +TEST(LANGUAGE_COMMAND_INTERFACE_TESTS, SettersAndTransmitting) +{ + VirtualCANPlugin testPlugin; + testPlugin.open(); + + CANHardwareInterface::set_number_of_can_channels(1); + CANHardwareInterface::assign_can_channel_frame_handler(0, std::make_shared()); + CANHardwareInterface::start(); + + isobus::NAME TestDeviceNAME(0); + TestDeviceNAME.set_arbitrary_address_capable(true); + TestDeviceNAME.set_industry_group(3); + TestDeviceNAME.set_device_class(4); + TestDeviceNAME.set_function_code(static_cast(isobus::NAME::Function::EnduranceBraking)); + TestDeviceNAME.set_identity_number(9); + TestDeviceNAME.set_ecu_instance(5); + TestDeviceNAME.set_function_instance(0); + TestDeviceNAME.set_device_class_instance(0); + TestDeviceNAME.set_manufacturer_code(64); + + auto testECU = isobus::InternalControlFunction::create(TestDeviceNAME, 0x49, 0); + std::uint32_t waitingTimestamp_ms = SystemTiming::get_timestamp_ms(); + + while ((!testECU->get_address_valid()) && + (!SystemTiming::time_expired_ms(waitingTimestamp_ms, 2000))) + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + ASSERT_TRUE(testECU->get_address_valid()); + + CANMessageFrame testFrame; + memset(&testFrame, 0, sizeof(testFrame)); + testFrame.isExtendedFrame = true; + + // Get the virtual CAN plugin back to a known state + while (!testPlugin.get_queue_empty()) + { + testPlugin.read_frame(testFrame); + } + ASSERT_TRUE(testPlugin.get_queue_empty()); + + LanguageCommandInterface interfaceUnderTest(testECU, true); + + interfaceUnderTest.initialize(); + + interfaceUnderTest.set_language_code("en"); + interfaceUnderTest.set_commanded_decimal_symbol(LanguageCommandInterface::DecimalSymbols::Comma); + interfaceUnderTest.set_commanded_time_format(LanguageCommandInterface::TimeFormats::TwentyFourHour); + interfaceUnderTest.set_commanded_date_format(LanguageCommandInterface::DateFormats::yyyymmdd); + interfaceUnderTest.set_commanded_distance_units(LanguageCommandInterface::DistanceUnits::ImperialUS); + interfaceUnderTest.set_commanded_area_units(LanguageCommandInterface::AreaUnits::ImperialUS); + interfaceUnderTest.set_commanded_volume_units(LanguageCommandInterface::VolumeUnits::US); + interfaceUnderTest.set_commanded_mass_units(LanguageCommandInterface::MassUnits::US); + interfaceUnderTest.set_commanded_temperature_units(LanguageCommandInterface::TemperatureUnits::ImperialUS); + interfaceUnderTest.set_commanded_pressure_units(LanguageCommandInterface::PressureUnits::ImperialUS); + interfaceUnderTest.set_commanded_force_units(LanguageCommandInterface::ForceUnits::ImperialUS); + interfaceUnderTest.set_commanded_generic_units(LanguageCommandInterface::UnitSystem::US); + interfaceUnderTest.set_country_code("US"); + + EXPECT_EQ("en", interfaceUnderTest.get_language_code()); + EXPECT_EQ(LanguageCommandInterface::DecimalSymbols::Comma, interfaceUnderTest.get_commanded_decimal_symbol()); + EXPECT_EQ(LanguageCommandInterface::TimeFormats::TwentyFourHour, interfaceUnderTest.get_commanded_time_format()); + EXPECT_EQ(LanguageCommandInterface::DateFormats::yyyymmdd, interfaceUnderTest.get_commanded_date_format()); + EXPECT_EQ(LanguageCommandInterface::DistanceUnits::ImperialUS, interfaceUnderTest.get_commanded_distance_units()); + EXPECT_EQ(LanguageCommandInterface::AreaUnits::ImperialUS, interfaceUnderTest.get_commanded_area_units()); + EXPECT_EQ(LanguageCommandInterface::VolumeUnits::US, interfaceUnderTest.get_commanded_volume_units()); + EXPECT_EQ(LanguageCommandInterface::MassUnits::US, interfaceUnderTest.get_commanded_mass_units()); + EXPECT_EQ(LanguageCommandInterface::TemperatureUnits::ImperialUS, interfaceUnderTest.get_commanded_temperature_units()); + EXPECT_EQ(LanguageCommandInterface::PressureUnits::ImperialUS, interfaceUnderTest.get_commanded_pressure_units()); + EXPECT_EQ(LanguageCommandInterface::ForceUnits::ImperialUS, interfaceUnderTest.get_commanded_force_units()); + EXPECT_EQ(LanguageCommandInterface::UnitSystem::US, interfaceUnderTest.get_commanded_generic_units()); + EXPECT_EQ("US", interfaceUnderTest.get_country_code()); + + interfaceUnderTest.set_language_code("de"); + interfaceUnderTest.set_commanded_decimal_symbol(LanguageCommandInterface::DecimalSymbols::Reserved); + interfaceUnderTest.set_commanded_time_format(LanguageCommandInterface::TimeFormats::TwelveHourAmPm); + interfaceUnderTest.set_commanded_date_format(LanguageCommandInterface::DateFormats::mmddyyyy); + interfaceUnderTest.set_commanded_distance_units(LanguageCommandInterface::DistanceUnits::Metric); + interfaceUnderTest.set_commanded_area_units(LanguageCommandInterface::AreaUnits::Metric); + interfaceUnderTest.set_commanded_volume_units(LanguageCommandInterface::VolumeUnits::Metric); + interfaceUnderTest.set_commanded_mass_units(LanguageCommandInterface::MassUnits::Metric); + interfaceUnderTest.set_commanded_temperature_units(LanguageCommandInterface::TemperatureUnits::Metric); + interfaceUnderTest.set_commanded_pressure_units(LanguageCommandInterface::PressureUnits::Metric); + interfaceUnderTest.set_commanded_force_units(LanguageCommandInterface::ForceUnits::Metric); + interfaceUnderTest.set_commanded_generic_units(LanguageCommandInterface::UnitSystem::Metric); + interfaceUnderTest.set_country_code("DE"); + + EXPECT_EQ("de", interfaceUnderTest.get_language_code()); + EXPECT_EQ(LanguageCommandInterface::DecimalSymbols::Reserved, interfaceUnderTest.get_commanded_decimal_symbol()); + EXPECT_EQ(LanguageCommandInterface::TimeFormats::TwelveHourAmPm, interfaceUnderTest.get_commanded_time_format()); + EXPECT_EQ(LanguageCommandInterface::DateFormats::mmddyyyy, interfaceUnderTest.get_commanded_date_format()); + EXPECT_EQ(LanguageCommandInterface::DistanceUnits::Metric, interfaceUnderTest.get_commanded_distance_units()); + EXPECT_EQ(LanguageCommandInterface::AreaUnits::Metric, interfaceUnderTest.get_commanded_area_units()); + EXPECT_EQ(LanguageCommandInterface::VolumeUnits::Metric, interfaceUnderTest.get_commanded_volume_units()); + EXPECT_EQ(LanguageCommandInterface::MassUnits::Metric, interfaceUnderTest.get_commanded_mass_units()); + EXPECT_EQ(LanguageCommandInterface::TemperatureUnits::Metric, interfaceUnderTest.get_commanded_temperature_units()); + EXPECT_EQ(LanguageCommandInterface::PressureUnits::Metric, interfaceUnderTest.get_commanded_pressure_units()); + EXPECT_EQ(LanguageCommandInterface::ForceUnits::Metric, interfaceUnderTest.get_commanded_force_units()); + EXPECT_EQ(LanguageCommandInterface::UnitSystem::Metric, interfaceUnderTest.get_commanded_generic_units()); + EXPECT_EQ("DE", interfaceUnderTest.get_country_code()); + + // Change settings back to the one that is trickier to encode/decode + interfaceUnderTest.set_language_code("en"); + interfaceUnderTest.set_commanded_decimal_symbol(LanguageCommandInterface::DecimalSymbols::Comma); + interfaceUnderTest.set_commanded_time_format(LanguageCommandInterface::TimeFormats::TwelveHourAmPm); + interfaceUnderTest.set_commanded_date_format(LanguageCommandInterface::DateFormats::yyyymmdd); + interfaceUnderTest.set_commanded_distance_units(LanguageCommandInterface::DistanceUnits::ImperialUS); + interfaceUnderTest.set_commanded_area_units(LanguageCommandInterface::AreaUnits::ImperialUS); + interfaceUnderTest.set_commanded_volume_units(LanguageCommandInterface::VolumeUnits::US); + interfaceUnderTest.set_commanded_mass_units(LanguageCommandInterface::MassUnits::US); + interfaceUnderTest.set_commanded_temperature_units(LanguageCommandInterface::TemperatureUnits::ImperialUS); + interfaceUnderTest.set_commanded_pressure_units(LanguageCommandInterface::PressureUnits::ImperialUS); + interfaceUnderTest.set_commanded_force_units(LanguageCommandInterface::ForceUnits::ImperialUS); + interfaceUnderTest.set_commanded_generic_units(LanguageCommandInterface::UnitSystem::US); + interfaceUnderTest.set_country_code("US"); + + ASSERT_TRUE(interfaceUnderTest.send_language_command()); + + testPlugin.read_frame(testFrame); + + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_TRUE(testFrame.isExtendedFrame); + EXPECT_EQ(0x18FE0F49, testFrame.identifier); + EXPECT_EQ('e', testFrame.data[0]); + EXPECT_EQ('n', testFrame.data[1]); + EXPECT_EQ(static_cast(LanguageCommandInterface::TimeFormats::TwelveHourAmPm), (testFrame.data[2] >> 4) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::DecimalSymbols::Comma), (testFrame.data[2] >> 6) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::DateFormats::yyyymmdd), testFrame.data[3]); + EXPECT_EQ(static_cast(LanguageCommandInterface::MassUnits::US), (testFrame.data[4]) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::VolumeUnits::US), (testFrame.data[4] >> 2) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::AreaUnits::ImperialUS), (testFrame.data[4] >> 4) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::DistanceUnits::ImperialUS), (testFrame.data[4] >> 6) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::UnitSystem::US), (testFrame.data[5]) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::ForceUnits::ImperialUS), (testFrame.data[5] >> 2) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::PressureUnits::ImperialUS), (testFrame.data[5] >> 4) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::TemperatureUnits::ImperialUS), (testFrame.data[5] >> 6) & 0x03); + EXPECT_EQ('U', testFrame.data[6]); + EXPECT_EQ('S', testFrame.data[7]); + + // Test bad values for country and language + interfaceUnderTest.set_language_code("r"); + interfaceUnderTest.set_country_code(""); + + ASSERT_TRUE(interfaceUnderTest.send_language_command()); + + testPlugin.read_frame(testFrame); + + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_TRUE(testFrame.isExtendedFrame); + EXPECT_EQ(0x18FE0F49, testFrame.identifier); + EXPECT_EQ('r', testFrame.data[0]); + EXPECT_EQ(' ', testFrame.data[1]); + EXPECT_EQ(static_cast(LanguageCommandInterface::TimeFormats::TwelveHourAmPm), (testFrame.data[2] >> 4) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::DecimalSymbols::Comma), (testFrame.data[2] >> 6) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::DateFormats::yyyymmdd), testFrame.data[3]); + EXPECT_EQ(static_cast(LanguageCommandInterface::MassUnits::US), (testFrame.data[4]) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::VolumeUnits::US), (testFrame.data[4] >> 2) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::AreaUnits::ImperialUS), (testFrame.data[4] >> 4) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::DistanceUnits::ImperialUS), (testFrame.data[4] >> 6) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::UnitSystem::US), (testFrame.data[5]) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::ForceUnits::ImperialUS), (testFrame.data[5] >> 2) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::PressureUnits::ImperialUS), (testFrame.data[5] >> 4) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::TemperatureUnits::ImperialUS), (testFrame.data[5] >> 6) & 0x03); + EXPECT_EQ(' ', testFrame.data[6]); + EXPECT_EQ(' ', testFrame.data[7]); + + interfaceUnderTest.set_language_code("ThisIsWayTooLong"); + interfaceUnderTest.set_country_code("AndShouldBeTruncatedWhenSent"); + + ASSERT_TRUE(interfaceUnderTest.send_language_command()); + + testPlugin.read_frame(testFrame); + + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_TRUE(testFrame.isExtendedFrame); + EXPECT_EQ(0x18FE0F49, testFrame.identifier); + EXPECT_EQ('T', testFrame.data[0]); + EXPECT_EQ('h', testFrame.data[1]); + EXPECT_EQ(static_cast(LanguageCommandInterface::TimeFormats::TwelveHourAmPm), (testFrame.data[2] >> 4) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::DecimalSymbols::Comma), (testFrame.data[2] >> 6) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::DateFormats::yyyymmdd), testFrame.data[3]); + EXPECT_EQ(static_cast(LanguageCommandInterface::MassUnits::US), (testFrame.data[4]) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::VolumeUnits::US), (testFrame.data[4] >> 2) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::AreaUnits::ImperialUS), (testFrame.data[4] >> 4) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::DistanceUnits::ImperialUS), (testFrame.data[4] >> 6) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::UnitSystem::US), (testFrame.data[5]) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::ForceUnits::ImperialUS), (testFrame.data[5] >> 2) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::PressureUnits::ImperialUS), (testFrame.data[5] >> 4) & 0x03); + EXPECT_EQ(static_cast(LanguageCommandInterface::TemperatureUnits::ImperialUS), (testFrame.data[5] >> 6) & 0x03); + EXPECT_EQ('A', testFrame.data[6]); + EXPECT_EQ('n', testFrame.data[7]); + + testPlugin.close(); + + //! @todo try to reduce the reference count, such that that we don't use a control function after it is destroyed + EXPECT_TRUE(testECU->destroy(2)); + CANHardwareInterface::stop(); +}