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(); +}