diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a9aa393..049fedb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ if(BUILD_EXAMPLES) add_subdirectory("examples/virtual_terminal/aux_inputs") add_subdirectory("examples/task_controller_client") add_subdirectory("examples/guidance") + add_subdirectory("examples/seeder_example") endif() if(BUILD_TESTING) diff --git a/examples/seeder_example/BasePool.iop b/examples/seeder_example/BasePool.iop new file mode 100644 index 00000000..38af5e05 Binary files /dev/null and b/examples/seeder_example/BasePool.iop differ diff --git a/examples/seeder_example/CMakeLists.txt b/examples/seeder_example/CMakeLists.txt new file mode 100644 index 00000000..ac2e07f5 --- /dev/null +++ b/examples/seeder_example/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.16) +project(seeder_example) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT BUILD_EXAMPLES) + find_package(isobus REQUIRED) +endif() +find_package(Threads REQUIRED) + +add_executable( + SeederExample + main.cpp + console_logger.cpp + vt_application.cpp + vt_application.hpp + seeder.cpp + seeder.hpp + object_pool.hpp + section_control_implement_sim.hpp + section_control_implement_sim.cpp) +target_link_libraries( + SeederExample PRIVATE isobus::Isobus isobus::HardwareIntegration + Threads::Threads isobus::Utility) + +add_custom_command( + TARGET SeederExample + POST_BUILD + COMMENT "Copying BasePool.iop to build directory" + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/BasePool.iop + $/BasePool.iop) diff --git a/examples/seeder_example/console_logger.cpp b/examples/seeder_example/console_logger.cpp new file mode 100644 index 00000000..2eb71263 --- /dev/null +++ b/examples/seeder_example/console_logger.cpp @@ -0,0 +1,67 @@ +#include "isobus/isobus/can_stack_logger.hpp" + +#include + +// A log sink for the CAN stack +class CustomLogger : public isobus::CANStackLogger +{ +public: + void sink_CAN_stack_log(CANStackLogger::LoggingLevel level, const std::string &text) override + { + switch (level) + { + case LoggingLevel::Debug: + { + std::cout << "[" + << "\033[1;36m" + << "Debug" + << "\033[0m" + << "]"; + } + break; + + case LoggingLevel::Info: + { + std::cout << "[" + << "\033[1;32m" + << "Info" + << "\033[0m" + << "]"; + } + break; + + case LoggingLevel::Warning: + { + std::cout << "[" + << "\033[1;33m" + << "Warn" + << "\033[0m" + << "]"; + } + break; + + case LoggingLevel::Error: + { + std::cout << "[" + << "\033[1;31m" + << "Error" + << "\033[0m" + << "]"; + } + break; + + case LoggingLevel::Critical: + { + std::cout << "[" + << "\033[1;35m" + << "Critical" + << "\033[0m" + << "]"; + } + break; + } + std::cout << text << std::endl; // Write the text to stdout + } +}; + +static CustomLogger logger; diff --git a/examples/seeder_example/main.cpp b/examples/seeder_example/main.cpp new file mode 100644 index 00000000..7d3024df --- /dev/null +++ b/examples/seeder_example/main.cpp @@ -0,0 +1,42 @@ +//================================================================================================ +/// @file main.cpp +/// +/// @brief Defines `main` for the seeder example +/// @details This example is meant to use all the major protocols in a more "complete" application. +/// @author Adrian Del Grosso +/// +/// @copyright 2023 Adrian Del Grosso +//================================================================================================ +#include "seeder.hpp" + +#include +#include + +std::atomic_bool running = { true }; + +void signal_handler(int) +{ + running = false; +} + +int main() +{ + int retVal = 0; + Seeder seederExample; + std::signal(SIGINT, signal_handler); + + if (seederExample.initialize()) + { + while (running) + { + seederExample.update(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + seederExample.terminate(); + } + else + { + retVal = -1; // Something wasn't right, such as CAN interface was missing. + } + return retVal; +} diff --git a/examples/seeder_example/object_pool.hpp b/examples/seeder_example/object_pool.hpp new file mode 100644 index 00000000..a4c8044f --- /dev/null +++ b/examples/seeder_example/object_pool.hpp @@ -0,0 +1,222 @@ +//================================================================================================ +/// @file object_pool.hpp +/// +/// @brief Defines the object IDs in the Seeder Example object pool +/// @details This is a file that should be auto-generated by your object pool designer application. +/// @author Adrian Del Grosso +/// +/// @copyright 2023 Adrian Del Grosso +//================================================================================================ + +#ifndef OBJECT_POOL_HPP +#define OBJECT_POOL_HPP + +#define UNDEFINED 65535 //0xFFFF +#define example_WorkingSet 0 //0x0000 +#define mainRunscreen_DataMask 1000 //0x03E8 +#define statisticsRunscreen_DataMask 1001 //0x03E9 +#define settingsRunscreen_DataMask 1002 //0x03EA +#define alarmsRunscreen_DataMask 1003 //0x03EB +#define noSpeed_AlarmMask 2000 //0x07D0 +#define noTaskController_AlarmMask 2001 //0x07D1 +#define planterRunscreenStatus_Container 3000 //0x0BB8 +#define credits_Container 3001 //0x0BB9 +#define section1Switch_Container 3002 //0x0BBA +#define section2Switch_Container 3003 //0x0BBB +#define section3Switch_Container 3004 //0x0BBC +#define section4Switch_Container 3005 //0x0BBD +#define section5Switch_Container 3006 //0x0BBE +#define section6Switch_Container 3007 //0x0BBF +#define sectionButtons_Container 3008 //0x0BC0 +#define statisticsDropdown_Container 3009 //0x0BC1 +#define hamburgerMenu_Container 3010 //0x0BC2 +#define canStatistics_Container 3011 //0x0BC3 +#define manualMode_Container 3012 //0x0BC4 +#define readoutBusLoad_Container 3013 //0x0BC5 +#define readoutMyAddress_Container 3014 //0x0BC6 +#define utStatistics_Container 3015 //0x0BC7 +#define utVersion_Container 3016 //0x0BC8 +#define utAddress_Container 3017 //0x0BC9 +#define tcStatistics_Container 3018 //0x0BCA +#define tcVersion_Container 3019 //0x0BCB +#define tcAddress_Container 3020 //0x0BCC +#define tcNumberSupportedBooms_Container 3021 //0x0BCD +#define tcNumberSectionsSupported_Container 3022 //0x0BCE +#define tcControlChannels_Container 3023 //0x0BCF +#define autoManual_Container 3024 //0x0BD0 +#define autoMode_Container 3025 //0x0BD1 +#define speedReadout_Container 3026 //0x0BD2 +#define noTcAlarmLine_Container 3027 //0x0BD3 +#define noSpeedAlarmLine_Container 3028 //0x0BD4 +#define enableAlarms_Container 3029 //0x0BD5 +#define mainRunscreen_SoftKeyMask 4000 //0x0FA0 +#define alarm_SKeyMask 4001 //0x0FA1 +#define returnHome_SKeyMask 4002 //0x0FA2 +#define home_Key 5000 //0x1388 +#define acknowledgeAlarm_SoftKey 5001 //0x1389 +#define settings_Key 5002 //0x138A +#define alarms_Key 5003 //0x138B +#define statistics_Key 5004 //0x138C +#define section1Toggle_Button 6000 //0x1770 +#define section2Toggle_Button 6001 //0x1771 +#define section3Toggle_Button 6002 //0x1772 +#define section4Toggle_Button 6003 //0x1773 +#define section5Toggle_Button 6004 //0x1774 +#define section6Toggle_Button 6005 //0x1775 +#define autoManualToggle_Button 6006 //0x1776 +#define enableAlarms_InBool 7000 //0x1B58 +#define statistics_InList 10000 //0x2710 +#define Title_OutStr 11000 //0x2AF8 +#define onOffIconCredit_OutStr 11001 //0x2AF9 +#define speed_OutStr 11002 //0x2AFA +#define temp_OutStr_ID_11003 11003 //0x2AFB +#define noMachineSpeedTitle_OutStr 11004 //0x2AFC +#define longTest_OutStr 11005 //0x2AFD +#define credits_OutStr 11006 //0x2AFE +#define planterIconCredit_OutStr 11007 //0x2AFF +#define settingsHeader_OutStr 11008 //0x2B00 +#define homeIconCredit_OutStr 11009 //0x2B01 +#define statisticsHeader_OutStr 11010 //0x2B02 +#define busload_OutStr 11011 //0x2B03 +#define unitPercent_OutStr 11012 //0x2B04 +#define statisticsChoose_OutStr 11013 //0x2B05 +#define listItemTaskController_OutStr 11014 //0x2B06 +#define listItemCANBus_OutStr 11015 //0x2B07 +#define listItemUniversalTerminal_OutStr 11016 //0x2B08 +#define currentAddress_OutStr 11017 //0x2B09 +#define reportedVersion_OutStr 11018 //0x2B0A +#define utAddress_OutStr 11019 //0x2B0B +#define tcAddress_OutStr 11020 //0x2B0C +#define tcNumberSupportedBooms_OutStr 11021 //0x2B0D +#define tcNumberSectionsSupported_OutStr 11022 //0x2B0E +#define tcControlChannels_OutStr 11023 //0x2B0F +#define autoIconCredit_OutStr 11024 //0x2B10 +#define autoManual_OutStr 11025 //0x2B11 +#define infoIconCredit_OutStr 11026 //0x2B12 +#define statisticsIconCredit_OutStr 11027 //0x2B13 +#define listItemCredits_OutStr 11028 //0x2B14 +#define unitKph_OutStr 11029 //0x2B15 +#define unitMph_OutStr 11030 //0x2B16 +#define machineSpeedNotDetectedSummary_OutStr 11031 //0x2B17 +#define noTCTitle_OutStr 11032 //0x2B18 +#define TCNotConnectedSummary_OutStr 11033 //0x2B19 +#define currentAlarmsHeader_OutStr 11034 //0x2B1A +#define noActiveAlarms_OutStr 11035 //0x2B1B +#define NoTaskController_OutStr 11036 //0x2B1C +#define NoMachineSpeed_OutStr 11037 //0x2B1D +#define enableAlarms_OutStr 11038 //0x2B1E +#define busload_OutNum 12000 //0x2EE0 +#define canAddress_OutNum 12001 //0x2EE1 +#define utVersion_OutNum 12002 //0x2EE2 +#define utAddress_OutNum 12003 //0x2EE3 +#define tcVersion_OutNum 12004 //0x2EE4 +#define tcAddress_OutNum 12005 //0x2EE5 +#define tcNumberBoomsSupported_OutNum 12006 //0x2EE6 +#define tcSupportedSections_OutNum 12007 //0x2EE7 +#define tcControlChannels_OutNum 12008 //0x2EE8 +#define speed_OutNum 12009 //0x2EE9 +#define ten_OutNum 12010 //0x2EEA +#define zero_OutNum 12011 //0x2EEB +#define twenty_OutNum 12012 //0x2EEC +#define thirty_OutNum 12013 //0x2EED +#define headerBorder_OutLine 13000 //0x32C8 +#define Title_OutRect 14000 //0x36B0 +#define MainRunscreenBackground_OutRect 14001 //0x36B1 +#define section1Status_OutRect 14002 //0x36B2 +#define section2Status_OutRect 14003 //0x36B3 +#define section3Status_OutRect 14004 //0x36B4 +#define section4Status_OutRect 14005 //0x36B5 +#define section5Status_OutRect 14006 //0x36B6 +#define section6Status_OutRect 14007 //0x36B7 +#define dropdown_OutRect 14008 //0x36B8 +#define hamburgerLine_OutRect 14009 //0x36B9 +#define readoutGeneric_OutRect 14010 //0x36BA +#define buttonCover_OutRect 14011 //0x36BB +#define temp_OutEllipse 15000 //0x3A98 +#define currentSpeed_OutMeter 17000 //0x4268 +#define avatar_OutPict 20000 //0x4E20 +#define warningIcon_OutPict 20001 //0x4E21 +#define warning_OutPict 20002 //0x4E22 +#define greenCheck_OutPict 20003 //0x4E23 +#define gear_OutPict 20004 //0x4E24 +#define planter_OutPict 20005 //0x4E25 +#define offButtonSliderSmall_OutPict 20006 //0x4E26 +#define onButtonSliderSmall_OutPict 20007 //0x4E27 +#define home_OutPict 20008 //0x4E28 +#define auto_OutPict 20009 //0x4E29 +#define manual_OutPict 20010 //0x4E2A +#define info_OutPict 20011 //0x4E2B +#define stats_OutPict 20012 //0x4E2C +#define Copy1_info_OutPict 20013 //0x4E2D +#define ButtonExampleNumber_VarNum 21000 //0x5208 +#define statisticsSelection_VarNum 21001 //0x5209 +#define busload_VarNum 21002 //0x520A +#define canAddress_VarNum 21003 //0x520B +#define utVersion_VarNum 21004 //0x520C +#define utAddress_VarNum 21005 //0x520D +#define tcVersion_VarNum 21006 //0x520E +#define tcAddress_VarNum 21007 //0x520F +#define tcNumberBoomsSupported_VarNum 21008 //0x5210 +#define tcSupportedSections_VarNum 21009 //0x5211 +#define tcControlChannels_VarNum 21010 //0x5212 +#define currentSpeedMeter_VarNum 21011 //0x5213 +#define currentSpeedReadout_VarNum 21012 //0x5214 +#define enableAlarms_VarNum 21013 //0x5215 +#define title_OutStr 22000 //0x55F0 +#define onOffIconsCredit_VarStr 22001 //0x55F1 +#define alarmMaskTitle_VarStr 22002 //0x55F2 +#define ExampleAlarmMask_VarStr 22003 //0x55F3 +#define planterIconsCredit_VarStr 22004 //0x55F4 +#define credits_VarStr 22005 //0x55F5 +#define settingsHeader_VarStr 22006 //0x55F6 +#define statisticsHeader_VarStr 22007 //0x55F7 +#define homeIconsCredit_VarStr 22008 //0x55F8 +#define busload_VarStr 22009 //0x55F9 +#define statisticsChoose_VarStr 22010 //0x55FA +#define canBus_VarStr 22011 //0x55FB +#define taskController_VarStr 22012 //0x55FC +#define universalTerminal_VarStr 22013 //0x55FD +#define currentAddress_VarStr 22014 //0x55FE +#define utVersion_VarStr 22015 //0x55FF +#define utAddress_VarStr 22016 //0x5600 +#define tcAddress_VarStr 22017 //0x5601 +#define tcNumberBoomsSupported_VarStr 22018 //0x5602 +#define tcNumberSectionsSupported_VarStr 22019 //0x5603 +#define tcControlChannels_VarStr 22020 //0x5604 +#define managementIconsCredit_VarStr 22021 //0x5605 +#define autoManual_VarStr 22022 //0x5606 +#define infoIconsCredit_VarStr 22023 //0x5607 +#define statisticsIconsCredit_VarStr 22024 //0x5608 +#define speed_VarStr 22025 //0x5609 +#define machineSpeedNotDetectedSummary_VarStr 22026 //0x560A +#define noTaskController_VarStr 22027 //0x560B +#define noTCSummary_VarStr 22028 //0x560C +#define currentAlarms_VarStr 22029 //0x560D +#define noActiveAlarms_VarStr 22030 //0x560E +#define enableAlarms_VarStr 22031 //0x560F +#define temp_FontAttr_ID_23000 23000 //0x59D8 +#define temp_FontAttr_ID_23001 23001 //0x59D9 +#define black48x64_FontAttr 23002 //0x59DA +#define unitFont_FontAttr 23003 //0x59DB +#define listItem_FontAttr 23004 //0x59DC +#define blackBold24x32_FontAttr 23005 //0x59DD +#define solidBlack_LineAttr 24000 //0x5DC0 +#define suppressed_LineAttr 24001 //0x5DC1 +#define solidWhite_FillAttr 25000 //0x61A8 +#define solidRed_FillAttr 25001 //0x61A9 +#define solidGreen_FillAttr 25002 //0x61AA +#define solidYellow_FillAttr 25003 //0x61AB +#define solidBlack_FillAttr 25004 //0x61AC +#define speedUnits_ObjPtr 27000 //0x6978 +#define section1EnableState_ObjPtr 27001 //0x6979 +#define section2EnableState_ObjPtr 27002 //0x697A +#define section3EnableState_ObjPtr 27003 //0x697B +#define section4EnableState_ObjPtr 27004 //0x697C +#define section5EnableState_ObjPtr 27005 //0x697D +#define section6EnableState_ObjPtr 27006 //0x697E +#define selectedStatisticsContainer_ObjPtr 27007 //0x697F +#define autoManual_ObjPtr 27008 //0x6980 +#define currentAlarms1_ObjPtr 27009 //0x6981 +#define currentAlarms2_ObjPtr 27010 //0x6982 + +#endif // OBJECT_POOL_HPP diff --git a/examples/seeder_example/section_control_implement_sim.cpp b/examples/seeder_example/section_control_implement_sim.cpp new file mode 100644 index 00000000..0bd96532 --- /dev/null +++ b/examples/seeder_example/section_control_implement_sim.cpp @@ -0,0 +1,439 @@ +#include "section_control_implement_sim.hpp" +#include "isobus/isobus/can_constants.hpp" +#include "isobus/isobus/isobus_standard_data_description_indices.hpp" +#include "isobus/utility/system_timing.hpp" +#include "isobus/utility/to_string.hpp" + +#include + +SectionControlImplementSimulator::SectionControlImplementSimulator(std::uint8_t value) : + sectionSetpointStates(value, false), + sectionSwitchStates(value, false) +{ +} + +std::uint8_t SectionControlImplementSimulator::get_number_of_sections() const +{ + assert(sectionSwitchStates.size() == sectionSetpointStates.size()); + return static_cast(sectionSwitchStates.size()); +} + +bool SectionControlImplementSimulator::get_section_actual_state(std::uint8_t index) const +{ + // We currently are just simulating here. In a real implement, you would want to read the actual state from the implement. + if (isAutoMode) + { + return sectionSetpointStates.at(index); + } + else + { + return sectionSwitchStates.at(index); + } +} + +std::uint8_t SectionControlImplementSimulator::get_actual_number_of_sections_on() const +{ + std::uint8_t retVal = 0; + for (std::uint8_t i = 0; i < get_number_of_sections(); i++) + { + if (true == get_section_actual_state(i)) + { + retVal++; + } + } + return retVal; +} + +bool SectionControlImplementSimulator::get_section_setpoint_state(std::uint8_t index) const +{ + return sectionSetpointStates.at(index); +} + +void SectionControlImplementSimulator::set_section_switch_state(std::uint8_t index, bool value) +{ + sectionSwitchStates.at(index) = value; +} + +bool SectionControlImplementSimulator::get_section_switch_state(std::uint8_t index) const +{ + return sectionSwitchStates.at(index); +} + +std::uint32_t SectionControlImplementSimulator::get_actual_rate() const +{ + bool anySectionOn = get_actual_number_of_sections_on() > 0; + return targetRate * (anySectionOn ? 1 : 0); +} + +std::uint32_t SectionControlImplementSimulator::get_target_rate() const +{ + return targetRate; +} + +bool SectionControlImplementSimulator::get_setpoint_work_state() const +{ + return setpointWorkState; +} + +void SectionControlImplementSimulator::set_is_mode_auto(bool isAuto) +{ + isAutoMode = isAuto; +} + +bool SectionControlImplementSimulator::get_is_mode_auto() const +{ + return isAutoMode; +} + +std::uint32_t SectionControlImplementSimulator::get_prescription_control_state() const +{ + return static_cast(get_is_mode_auto()); +} + +std::uint32_t SectionControlImplementSimulator::get_section_control_state() const +{ + return static_cast(get_is_mode_auto()); +} + +bool SectionControlImplementSimulator::create_ddop(std::shared_ptr poolToPopulate, isobus::NAME clientName) const +{ + bool retVal = true; + std::uint16_t elementCounter = 0; + assert(0 != get_number_of_sections()); // You need at least 1 section for this example + const std::int32_t SECTION_WIDTH = (BOOM_WIDTH / get_number_of_sections()); + poolToPopulate->clear(); + + // English, decimal point, 12 hour time, ddmmyyyy, all units imperial + constexpr std::array localizationData = { 'e', 'n', 0b01010000, 0x00, 0b01010101, 0b01010101, 0xFF }; + + // Make a pool with 1 granular product + // Set up device and device element + retVal &= poolToPopulate->add_device("Isobus Seeder", "1.0.0", "123", "IS1.1", localizationData, std::vector(), clientName.get_full_name()); + retVal &= poolToPopulate->add_device_element("Seeder", elementCounter, 0, isobus::task_controller_object::DeviceElementObject::Type::Device, static_cast(ImplementDDOPObjectIDs::MainDeviceElement)); + retVal &= poolToPopulate->add_device_process_data("Actual Work State", static_cast(isobus::DataDescriptionIndex::ActualWorkState), isobus::NULL_OBJECT_ID, static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange), static_cast(ImplementDDOPObjectIDs::DeviceActualWorkState)); + retVal &= poolToPopulate->add_device_process_data("Request Default PD", static_cast(ImplementDDOPObjectIDs::RequestDefaultProcessData), isobus::NULL_OBJECT_ID, 0, static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::Total), static_cast(ImplementDDOPObjectIDs::RequestDefaultProcessData)); + retVal &= poolToPopulate->add_device_process_data("Total Time", static_cast(isobus::DataDescriptionIndex::EffectiveTotalTime), static_cast(ImplementDDOPObjectIDs::TimePresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::Total), static_cast(ImplementDDOPObjectIDs::DeviceTotalTime)); + elementCounter++; // Increment element number. Needs to be unique for each element. + + // Set up connector element + retVal &= poolToPopulate->add_device_element("Connector", elementCounter, static_cast(ImplementDDOPObjectIDs::MainDeviceElement), isobus::task_controller_object::DeviceElementObject::Type::Connector, static_cast(ImplementDDOPObjectIDs::Connector)); + retVal &= poolToPopulate->add_device_process_data("Connector X", static_cast(isobus::DataDescriptionIndex::DeviceElementOffsetX), static_cast(ImplementDDOPObjectIDs::ShortWidthPresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable), 0, static_cast(ImplementDDOPObjectIDs::ConnectorXOffset)); + retVal &= poolToPopulate->add_device_process_data("Connector Y", static_cast(isobus::DataDescriptionIndex::DeviceElementOffsetY), static_cast(ImplementDDOPObjectIDs::ShortWidthPresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable), 0, static_cast(ImplementDDOPObjectIDs::ConnectorYOffset)); + retVal &= poolToPopulate->add_device_property("Type", 9, static_cast(isobus::DataDescriptionIndex::ConnectorType), isobus::NULL_OBJECT_ID, static_cast(ImplementDDOPObjectIDs::ConnectorType)); + elementCounter++; // Increment element number. Needs to be unique for each element. + + // Set up Boom element + retVal &= poolToPopulate->add_device_element("AgIsoStack Example", elementCounter, static_cast(ImplementDDOPObjectIDs::MainDeviceElement), isobus::task_controller_object::DeviceElementObject::Type::Function, static_cast(ImplementDDOPObjectIDs::MainBoom)); + retVal &= poolToPopulate->add_device_property("Offset X", 0, static_cast(isobus::DataDescriptionIndex::DeviceElementOffsetX), static_cast(ImplementDDOPObjectIDs::ShortWidthPresentation), static_cast(ImplementDDOPObjectIDs::BoomXOffset)); + retVal &= poolToPopulate->add_device_property("Offset Y", 0, static_cast(isobus::DataDescriptionIndex::DeviceElementOffsetY), static_cast(ImplementDDOPObjectIDs::ShortWidthPresentation), static_cast(ImplementDDOPObjectIDs::BoomYOffset)); + retVal &= poolToPopulate->add_device_property("Offset Z", 0, static_cast(isobus::DataDescriptionIndex::DeviceElementOffsetZ), static_cast(ImplementDDOPObjectIDs::ShortWidthPresentation), static_cast(ImplementDDOPObjectIDs::BoomZOffset)); + retVal &= poolToPopulate->add_device_process_data("Actual Working Width", static_cast(isobus::DataDescriptionIndex::ActualWorkingWidth), static_cast(ImplementDDOPObjectIDs::LongWidthPresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange), static_cast(ImplementDDOPObjectIDs::ActualWorkingWidth)); + retVal &= poolToPopulate->add_device_process_data("Setpoint Work State", static_cast(isobus::DataDescriptionIndex::SetpointWorkState), isobus::NULL_OBJECT_ID, static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange), static_cast(ImplementDDOPObjectIDs::SetpointWorkState)); + retVal &= poolToPopulate->add_device_process_data("Area Total", static_cast(isobus::DataDescriptionIndex::TotalArea), static_cast(ImplementDDOPObjectIDs::AreaPresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::Total), static_cast(ImplementDDOPObjectIDs::AreaTotal)); + retVal &= poolToPopulate->add_device_process_data("Section Control State", static_cast(isobus::DataDescriptionIndex::SectionControlState), isobus::NULL_OBJECT_ID, static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::TimeInterval), static_cast(ImplementDDOPObjectIDs::SectionControlState)); + elementCounter++; // Increment element number. Needs to be unique for each element. + + // Set up bin/tank element + retVal &= poolToPopulate->add_device_element("Product", elementCounter, 9, isobus::task_controller_object::DeviceElementObject::Type::Bin, static_cast(ImplementDDOPObjectIDs::GranularProduct)); + retVal &= poolToPopulate->add_device_process_data("Bin Capacity", static_cast(isobus::DataDescriptionIndex::MaximumCountContent), static_cast(ImplementDDOPObjectIDs::CountPresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::TimeInterval), static_cast(ImplementDDOPObjectIDs::BinCapacity)); + retVal &= poolToPopulate->add_device_process_data("Bin Level", static_cast(isobus::DataDescriptionIndex::ActualCountContent), static_cast(ImplementDDOPObjectIDs::CountPresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::TimeInterval), static_cast(ImplementDDOPObjectIDs::BinLevel)); + retVal &= poolToPopulate->add_device_process_data("Lifetime Total Count", static_cast(isobus::DataDescriptionIndex::LifetimeApplicationTotalCount), static_cast(ImplementDDOPObjectIDs::CountPresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::Total), static_cast(ImplementDDOPObjectIDs::LifetimeApplicationCountTotal)); + retVal &= poolToPopulate->add_device_process_data("Rx Control State", static_cast(isobus::DataDescriptionIndex::PrescriptionControlState), isobus::NULL_OBJECT_ID, static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::TimeInterval), static_cast(ImplementDDOPObjectIDs::PrescriptionControlState)); + retVal &= poolToPopulate->add_device_process_data("Target Rate", static_cast(isobus::DataDescriptionIndex::SetpointCountPerAreaApplicationRate), static_cast(ImplementDDOPObjectIDs::CountPerAreaPresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange), static_cast(ImplementDDOPObjectIDs::TargetRate)); + retVal &= poolToPopulate->add_device_process_data("Actual Rate", static_cast(isobus::DataDescriptionIndex::ActualCountPerAreaApplicationRate), static_cast(ImplementDDOPObjectIDs::CountPerAreaPresentation), static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::TimeInterval), static_cast(ImplementDDOPObjectIDs::ActualRate)); + retVal &= poolToPopulate->add_device_property("Operation Type", 2, static_cast(isobus::DataDescriptionIndex::ActualCulturalPractice), isobus::NULL_OBJECT_ID, static_cast(ImplementDDOPObjectIDs::ActualCulturalPractice)); + elementCounter++; // Increment element number. Needs to be unique for each element. + + // Set up sections for section control + // Using 7 ft sections + for (std::uint_fast8_t i = 0; i < get_number_of_sections(); i++) + { + std::int32_t individualSectionWidth = BOOM_WIDTH / get_number_of_sections(); + retVal &= poolToPopulate->add_device_element("Section " + isobus::to_string(static_cast(i)), elementCounter, static_cast(ImplementDDOPObjectIDs::MainBoom), isobus::task_controller_object::DeviceElementObject::Type::Section, static_cast(ImplementDDOPObjectIDs::Section1) + i); + retVal &= poolToPopulate->add_device_property("Offset X", -20, static_cast(isobus::DataDescriptionIndex::DeviceElementOffsetX), static_cast(ImplementDDOPObjectIDs::LongWidthPresentation), static_cast(ImplementDDOPObjectIDs::Section1XOffset) + i); + retVal &= poolToPopulate->add_device_property("Offset Y", ((-BOOM_WIDTH) / 2) + (i * SECTION_WIDTH) + (SECTION_WIDTH / 2), static_cast(isobus::DataDescriptionIndex::DeviceElementOffsetY), static_cast(ImplementDDOPObjectIDs::LongWidthPresentation), static_cast(ImplementDDOPObjectIDs::Section1YOffset) + i); + retVal &= poolToPopulate->add_device_property("Width", individualSectionWidth, static_cast(isobus::DataDescriptionIndex::ActualWorkingWidth), static_cast(ImplementDDOPObjectIDs::LongWidthPresentation), static_cast(ImplementDDOPObjectIDs::Section1Width) + i); + auto section = std::static_pointer_cast(poolToPopulate->get_object_by_id(i + static_cast(ImplementDDOPObjectIDs::Section1))); + section->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::Section1YOffset) + i); + section->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::Section1XOffset) + i); + section->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::Section1Width) + i); + elementCounter++; // Increment element number. Needs to be unique for each element, and each section is its own element. + } + + std::uint16_t sectionCounter = 0; + while (sectionCounter < get_number_of_sections()) + { + retVal &= poolToPopulate->add_device_process_data("Actual Work State 1-16", static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState1_16) + (sectionCounter / NUMBER_SECTIONS_PER_CONDENSED_MESSAGE), isobus::NULL_OBJECT_ID, static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange), static_cast(ImplementDDOPObjectIDs::ActualCondensedWorkingState1To16) + (sectionCounter / NUMBER_SECTIONS_PER_CONDENSED_MESSAGE)); + retVal &= poolToPopulate->add_device_process_data("Setpoint Work State 1-16", static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState1_16) + (sectionCounter / NUMBER_SECTIONS_PER_CONDENSED_MESSAGE), isobus::NULL_OBJECT_ID, static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable) | static_cast(isobus::task_controller_object::DeviceProcessDataObject::PropertiesBit::MemberOfDefaultSet), static_cast(isobus::task_controller_object::DeviceProcessDataObject::AvailableTriggerMethods::OnChange), static_cast(ImplementDDOPObjectIDs::SetpointCondensedWorkingState1To16) + (sectionCounter / NUMBER_SECTIONS_PER_CONDENSED_MESSAGE)); + sectionCounter += NUMBER_SECTIONS_PER_CONDENSED_MESSAGE; + } + + // Set up presentations + retVal &= poolToPopulate->add_device_value_presentation("mm", 0, 1.0f, 0, static_cast(ImplementDDOPObjectIDs::ShortWidthPresentation)); + retVal &= poolToPopulate->add_device_value_presentation("m", 0, 0.001f, 0, static_cast(ImplementDDOPObjectIDs::LongWidthPresentation)); + retVal &= poolToPopulate->add_device_value_presentation("m^2", 0, 1.0f, 0, static_cast(ImplementDDOPObjectIDs::AreaPresentation)); + retVal &= poolToPopulate->add_device_value_presentation("seeds", 0, 1.0f, 0, static_cast(ImplementDDOPObjectIDs::CountPresentation)); + retVal &= poolToPopulate->add_device_value_presentation("minutes", 0, 1.0f, 1, static_cast(ImplementDDOPObjectIDs::TimePresentation)); + retVal &= poolToPopulate->add_device_value_presentation("seeds/ha", 0, 1.0f, 0, static_cast(ImplementDDOPObjectIDs::CountPerAreaPresentation)); + + // Add child linkages to device elements if all objects were added OK + if (retVal) + { + auto sprayer = std::static_pointer_cast(poolToPopulate->get_object_by_id(static_cast(ImplementDDOPObjectIDs::MainDeviceElement))); + auto connector = std::static_pointer_cast(poolToPopulate->get_object_by_id(static_cast(ImplementDDOPObjectIDs::Connector))); + auto boom = std::static_pointer_cast(poolToPopulate->get_object_by_id(static_cast(ImplementDDOPObjectIDs::MainBoom))); + auto product = std::static_pointer_cast(poolToPopulate->get_object_by_id(static_cast(ImplementDDOPObjectIDs::GranularProduct))); + + sprayer->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::DeviceActualWorkState)); + sprayer->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::DeviceTotalTime)); + + connector->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::ConnectorXOffset)); + connector->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::ConnectorYOffset)); + connector->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::ConnectorType)); + + boom->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::BoomXOffset)); + boom->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::BoomYOffset)); + boom->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::BoomZOffset)); + boom->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::ActualWorkingWidth)); + boom->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::SectionControlState)); + + sectionCounter = 0; + while (sectionCounter < get_number_of_sections()) + { + boom->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::ActualCondensedWorkingState1To16) + (sectionCounter / NUMBER_SECTIONS_PER_CONDENSED_MESSAGE)); + boom->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::SetpointCondensedWorkingState1To16) + (sectionCounter / NUMBER_SECTIONS_PER_CONDENSED_MESSAGE)); + sectionCounter += NUMBER_SECTIONS_PER_CONDENSED_MESSAGE; + } + + product->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::BinCapacity)); + product->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::BinLevel)); + product->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::LifetimeApplicationCountTotal)); + product->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::PrescriptionControlState)); + product->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::ActualCulturalPractice)); + product->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::TargetRate)); + product->add_reference_to_child_object(static_cast(ImplementDDOPObjectIDs::ActualRate)); + } + return retVal; +} + +bool SectionControlImplementSimulator::request_value_command_callback(std::uint16_t, + std::uint16_t DDI, + std::uint32_t &value, + void *parentPointer) +{ + if (nullptr != parentPointer) + { + auto sim = reinterpret_cast(parentPointer); + switch (DDI) + { + case static_cast(isobus::DataDescriptionIndex::MaximumCountContent): + { + value = 200000; // Arbitrary values... not sure what is a realistic count + } + break; + + case static_cast(isobus::DataDescriptionIndex::ActualCountContent): + { + value = 150000; + } + break; + + case static_cast(isobus::DataDescriptionIndex::SectionControlState): + { + value = sim->get_section_control_state(); + } + break; + + case static_cast(isobus::DataDescriptionIndex::PrescriptionControlState): + { + value = sim->get_prescription_control_state(); + } + break; + + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState1_16): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState17_32): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState33_48): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState49_64): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState65_80): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState81_96): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState97_112): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState113_128): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState129_144): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState145_160): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState161_176): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState177_192): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState193_208): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState209_224): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState225_240): + case static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState241_256): + { + std::uint8_t sectionIndexOffset = NUMBER_SECTIONS_PER_CONDENSED_MESSAGE * static_cast(DDI - static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState1_16)); + value = 0; + + for (std::uint_fast8_t i = 0; i < NUMBER_SECTIONS_PER_CONDENSED_MESSAGE; i++) + { + if ((i + sectionIndexOffset) < sim->get_number_of_sections()) + { + bool sectionState = sim->get_section_actual_state(i + sectionIndexOffset); + value |= static_cast(sectionState) << (2 * i); + } + else + { + value |= (static_cast(0x03) << (2 * i)); + } + } + } + break; + + case static_cast(isobus::DataDescriptionIndex::ActualCountPerAreaApplicationRate): + { + value = sim->get_actual_rate(); + } + break; + + case static_cast(isobus::DataDescriptionIndex::ActualWorkState): + { + value = sim->get_actual_number_of_sections_on() > 0 ? 1 : 0; + } + break; + + case static_cast(isobus::DataDescriptionIndex::DeviceElementOffsetX): + case static_cast(isobus::DataDescriptionIndex::DeviceElementOffsetY): + case static_cast(isobus::DataDescriptionIndex::RequestDefaultProcessData): + { + value = 0; + } + break; + + case static_cast(isobus::DataDescriptionIndex::ActualWorkingWidth): + { + value = BOOM_WIDTH; + } + break; + + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState1_16): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState17_32): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState33_48): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState49_64): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState65_80): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState81_96): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState97_112): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState113_128): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState129_144): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState145_160): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState161_176): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState177_192): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState193_208): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState209_224): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState225_240): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState241_256): + { + auto sectionIndexOffset = static_cast(NUMBER_SECTIONS_PER_CONDENSED_MESSAGE * (DDI - static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState1_16))); + + value = 0; + for (std::uint_fast8_t i = 0; i < NUMBER_SECTIONS_PER_CONDENSED_MESSAGE; i++) + { + if ((i + sectionIndexOffset) < sim->get_number_of_sections()) + { + std::uint8_t sectionState = sim->get_section_setpoint_state(i + sectionIndexOffset); + value |= sectionState << (2 * i); + } + else + { + value |= (static_cast(0x03) << (2 * i)); + } + } + } + break; + + case static_cast(isobus::DataDescriptionIndex::SetpointCountPerAreaApplicationRate): + { + value = sim->get_target_rate(); + } + break; + + default: + { + value = 0; + } + break; + } + } + // The actual use of the return value here is for the TC client to know if it needs to keep calling more callbacks to search + // for one that can satisfy the element number + DDI combination it needs. + // But in the example this is the only value command callback, so we always want to return true. + return true; +} + +bool SectionControlImplementSimulator::value_command_callback(std::uint16_t, + std::uint16_t DDI, + std::uint32_t processVariableValue, + void *parentPointer) +{ + if (nullptr != parentPointer) + { + auto sim = reinterpret_cast(parentPointer); + switch (DDI) + { + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState1_16): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState17_32): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState33_48): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState49_64): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState65_80): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState81_96): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState97_112): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState113_128): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState129_144): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState145_160): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState161_176): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState177_192): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState193_208): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState209_224): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState225_240): + case static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState241_256): + { + auto sectionIndexOffset = static_cast(NUMBER_SECTIONS_PER_CONDENSED_MESSAGE * (DDI - static_cast(isobus::DataDescriptionIndex::SetpointCondensedWorkState1_16))); + + for (std::uint_fast8_t i = 0; i < NUMBER_SECTIONS_PER_CONDENSED_MESSAGE; i++) + { + if ((i + sectionIndexOffset) < sim->get_number_of_sections()) + { + bool sectionState = (0x01 == (processVariableValue >> (2 * i) & 0x03)); + sim->sectionSetpointStates.at(sectionIndexOffset + i) = sectionState; + } + else + { + break; + } + } + } + break; + + case static_cast(isobus::DataDescriptionIndex::SetpointCountPerAreaApplicationRate): + { + sim->targetRate = processVariableValue; + } + break; + + case static_cast(isobus::DataDescriptionIndex::SetpointWorkState): + { + sim->setpointWorkState = (0x01 == processVariableValue); + } + break; + + case static_cast(isobus::DataDescriptionIndex::PrescriptionControlState): + case static_cast(isobus::DataDescriptionIndex::SectionControlState): + { + sim->set_is_mode_auto(processVariableValue); + } + break; + + default: + break; + } + } + // The actual use of the return value here is for the TC client to know if it needs to keep calling more callbacks to search + // for one that can satisfy the element number + DDI combination it needs. + // But in the example this is the only value command callback, so we always want to return true. + return true; +} diff --git a/examples/seeder_example/section_control_implement_sim.hpp b/examples/seeder_example/section_control_implement_sim.hpp new file mode 100644 index 00000000..668f3d55 --- /dev/null +++ b/examples/seeder_example/section_control_implement_sim.hpp @@ -0,0 +1,202 @@ +//================================================================================================ +/// @file section_control_implement_sim.hpp +/// +/// @brief Defines a class that emulates a section control capable ISO implement. +/// @author Adrian Del Grosso +/// +/// @copyright 2023 Adrian Del Grosso +//================================================================================================ +#ifndef SECTION_CONTROL_IMPLEMENT_SIM_HPP +#define SECTION_CONTROL_IMPLEMENT_SIM_HPP + +#include "isobus/isobus/can_NAME.hpp" +#include "isobus/isobus/can_message.hpp" +#include "isobus/isobus/isobus_device_descriptor_object_pool.hpp" + +/// @brief Simulates a planter rate controller with section control +/// @note This is just an example. A real rate controller will obviously need to control rate and section +/// states rather than just echoing them back to the task controller. +class SectionControlImplementSimulator +{ +public: + static constexpr std::uint16_t MAX_NUMBER_SECTIONS_SUPPORTED = 256; ///< The most sections any implement can support is 256 + + /// @brief Enumerates unique IDs in the implement's DDOP + enum class ImplementDDOPObjectIDs : std::uint16_t + { + Device = 0, ///< Represents the device itself + + MainDeviceElement, ///< The main device element + + DeviceActualWorkState, ///< The actual work state (on/off) for the device + RequestDefaultProcessData, ///< https://www.isobus.net/isobus/dDEntity/144 + DeviceTotalTime, ///< Accumulated Time in working position + + Connector, ///< Element that represents a connector to which the implement is attached + ConnectorXOffset, ///< The fore/aft offset of the connector + ConnectorYOffset, ///< The left/right offset of the connector + ConnectorType, ///< https://www.isobus.net/isobus/dDEntity/767 + + MainBoom, ///< Element object that represents the boom + ActualWorkState, ///< The actual on/off work state for the boom + ActualWorkingWidth, ///< This is the effective / active working width of the boom during operation. + AreaTotal, ///< An area accumulator that gets reported to the TC for the whole boom + SetpointWorkState, ///< A settable work state for the entire boom sub-tree of objects + SectionControlState, ///< If section control is on or off (auto/manual) modes + BoomXOffset, ///< The offset up/down from the connector where the boom reference point (center) is + BoomYOffset, ///< The offset left/right from the connector where the boom reference point is + BoomZOffset, ///< The offset up/down from the connector where the boom reference point is + + Section1, ///< Section 1's device element object + SectionMax = Section1 + (MAX_NUMBER_SECTIONS_SUPPORTED - 1), ///< Individual device elements for each section + Section1XOffset, ///< The first section's X (fore/aft) offset + SectionXOffsetMax = Section1XOffset + (MAX_NUMBER_SECTIONS_SUPPORTED - 1), ///< Individual X offsets (fore/aft) for each section + Section1YOffset, ///< The first section's Y offset (left/right) + SectionYOffsetMax = Section1YOffset + (MAX_NUMBER_SECTIONS_SUPPORTED - 1), ///< Individual Y offsets (L/R) for each section + Section1Width, ///< The first section's width + SectionWidthMax = Section1Width + (MAX_NUMBER_SECTIONS_SUPPORTED - 1), ///< Individual IDs for each section's width + ActualCondensedWorkingState1To16, ///< https://www.isobus.net/isobus/dDEntity/183 + ActualCondensedWorkingState17To32, ///< Condensed actual work state for sections 17 to 32 + ActualCondensedWorkingState33To48, ///< Condensed actual work state for sections 33 to 48 + ActualCondensedWorkingState49To64, ///< Condensed actual work state for sections 49 to 64 + ActualCondensedWorkingState65To80, ///< Condensed actual work state for sections 65 to 80 + ActualCondensedWorkingState81To96, ///< Condensed actual work state for sections 81 to 96 + ActualCondensedWorkingState97To112, ///< Condensed actual work state for sections 97 to 112 + ActualCondensedWorkingState113To128, ///< Condensed actual work state for sections 113 to 128 + ActualCondensedWorkingState129To144, ///< Condensed actual work state for sections 129 to 144 + ActualCondensedWorkingState145To160, ///< Condensed actual work state for sections 145 to 160 + ActualCondensedWorkingState161To176, ///< Condensed actual work state for sections 161 to 176 + ActualCondensedWorkingState177To192, ///< Condensed actual work state for sections 177 to 192 + ActualCondensedWorkingState193To208, ///< Condensed actual work state for sections 193 to 208 + ActualCondensedWorkingState209To224, ///< Condensed actual work state for sections 209 to 224 + ActualCondensedWorkingState225To240, ///< Condensed actual work state for sections 225 to 240 + ActualCondensedWorkingState241To256, ///< Condensed actual work state for sections 241 to 256 + SetpointCondensedWorkingState1To16, ///< https://www.isobus.net/isobus/dDEntity/345 + SetpointCondensedWorkingState17To32, ///< Condensed setpoint work state for sections 17 to 32 + SetpointCondensedWorkingState33To48, ///< Condensed setpoint work state for sections 33 to 48 + SetpointCondensedWorkingState49To64, ///< Condensed setpoint work state for sections 49 to 64 + SetpointCondensedWorkingState65To80, ///< Condensed setpoint work state for sections 65 to 80 + SetpointCondensedWorkingState81To96, ///< Condensed setpoint work state for sections 81 to 96 + SetpointCondensedWorkingState97To112, ///< Condensed setpoint work state for sections 97 to 112 + SetpointCondensedWorkingState113To128, ///< Condensed setpoint work state for sections 113 to 128 + SetpointCondensedWorkingState129To144, ///< Condensed setpoint work state for sections 129 to 144 + SetpointCondensedWorkingState145To160, ///< Condensed setpoint work state for sections 145 to 160 + SetpointCondensedWorkingState161To176, ///< Condensed setpoint work state for sections 161 to 176 + SetpointCondensedWorkingState177To192, ///< Condensed setpoint work state for sections 177 to 192 + SetpointCondensedWorkingState193To208, ///< Condensed setpoint work state for sections 193 to 208 + SetpointCondensedWorkingState209To224, ///< Condensed setpoint work state for sections 209 to 224 + SetpointCondensedWorkingState225To240, ///< Condensed setpoint work state for sections 225 to 240 + SetpointCondensedWorkingState241To256, ///< Condensed setpoint work state for sections 241 to 256 + + GranularProduct, ///< The main bin element that describes the main product + BinCapacity, ///< The max bin content for the product device element + BinLevel, ///< Actual Device Element Content specified as volume + LifetimeApplicationCountTotal, + PrescriptionControlState, ///< https://www.isobus.net/isobus/dDEntity/203 + ActualCulturalPractice, ///< https://www.isobus.net/isobus/dDEntity/205 + TargetRate, ///< The target rate for the rate controller main product + ActualRate, ///< The actual rate of the rate controller main product + + AreaPresentation, ///< Describes to the TC how to display area units + TimePresentation, ///< Describes to the TC how to display time units + ShortWidthPresentation, ///< Describes to the TC how to display small width units + LongWidthPresentation, ///< Describes to the TC how to display large width units + CountPresentation, ///< Describes to the TC how to display volume units + CountPerAreaPresentation ///< Describes to the TC how to display volume per area units + }; + + /// @brief Constructor for the simulator + /// @param[in] value The number of sections to track for section control + explicit SectionControlImplementSimulator(std::uint8_t value); + + /// @brief Returns the number of sections that the sim is configured for + /// @returns The number of sections the sim is configured for + std::uint8_t get_number_of_sections() const; + + /// @brief Returns the current section state by index + /// @param[in] index The index of the section to get the state for + bool get_section_actual_state(std::uint8_t index) const; + + /// @brief Returns the number of sections that are currently on + /// @returns The number of sections that are currently on + std::uint8_t get_actual_number_of_sections_on() const; + + /// @brief Returns the current section setpoint state by index + /// @param[in] index The index of the section to get the setpoint state for + bool get_section_setpoint_state(std::uint8_t index) const; + + /// @brief Sets the current section's switch state by index + /// @param[in] index The index of the switch to set + /// @param[in] value The new state for the switch + void set_section_switch_state(std::uint8_t index, bool value); + + /// @brief Returns the current section's switch state by index + /// @param[in] index The index of the switch to get the state for + bool get_section_switch_state(std::uint8_t index) const; + + /// @brief Returns the actual prescription rate currently being applied + /// @returns The actual rate in seeds per hectare + std::uint32_t get_actual_rate() const; + + /// @brief Returns the target prescription rate to be applied + /// @returns Current target rate in seeds per hectare + std::uint32_t get_target_rate() const; + + /// @brief Returns the work state desired + bool get_setpoint_work_state() const; + + /// @brief Sets the current auto/manual control mode + /// @param[in] isAuto Pass in true for auto mode, false for manual mode + void set_is_mode_auto(bool isAuto); + + /// @brief Returns the current auto/manual control mode + /// @returns The current mode, true for auto, false for manual + bool get_is_mode_auto() const; + + /// @brief Returns the current prescription control state + /// @returns The current prescription control state + std::uint32_t get_prescription_control_state() const; + + /// @brief Returns the current section control state + /// @returns The current section control state + std::uint32_t get_section_control_state() const; + + /// @brief Generates a DDOP to send to the TC + /// @param[in] poolToPopulate A pointer to the DDOP that will be populated + /// @param[in] clientName The ISO NAME to generate the DDOP for + /// @returns true if the DDOP was successfully created, otherwise false + bool create_ddop(std::shared_ptr poolToPopulate, isobus::NAME clientName) const; + + /// @brief A callback that will be used by the TC client to read values + /// @param[in] elementNumber The element number associated to the value being requested + /// @param[in] DDI The ddi of the value in the element being requested + /// @param[in,out] value The value to report back to the TC client + /// @param[in] parentPointer A pointer to the class instance this callback is for + static bool request_value_command_callback(std::uint16_t elementNumber, + std::uint16_t DDI, + std::uint32_t &value, + void *parentPointer); + + /// @brief A callback that will be used by the TC client to set values + /// @param[in] elementNumber The element number being commanded + /// @param[in] DDI The DDI being commanded for the specified element number + /// @param[in] processVariableValue The value being commanded + /// @param[in] parentPointer the pointer to the class instance the callback is for + /// @returns true + static bool value_command_callback(std::uint16_t elementNumber, + std::uint16_t DDI, + std::uint32_t processVariableValue, + void *parentPointer); + +private: + static constexpr std::uint8_t NUMBER_SECTIONS_PER_CONDENSED_MESSAGE = 16; ///< Number of section states in a condensed working state message + static constexpr std::int32_t BOOM_WIDTH = 9144; // 30ft expressed in mm + + std::vector sectionSetpointStates; ///< Stores the on/off state desired for each section (left to right) + std::vector sectionSwitchStates; ///< Stores the UT section switches (false = disabled, true = enabled) (left to right) + std::uint32_t targetRate = 12000; ///< The target rate, default of 12k seeds per hectare + bool setpointWorkState = true; ///< The overall work state desired + bool isAutoMode = true; ///< Stores auto vs manual mode setting +}; + +#endif // SECTION_CONTROL_IMPLEMENT_SIM_HPP diff --git a/examples/seeder_example/seeder.cpp b/examples/seeder_example/seeder.cpp new file mode 100644 index 00000000..e4ce262a --- /dev/null +++ b/examples/seeder_example/seeder.cpp @@ -0,0 +1,124 @@ +//================================================================================================ +/// @file seeder.cpp +/// +/// @brief This is the implementation of an example seeder application +/// @author Adrian Del Grosso +/// +/// @copyright 2023 Adrian Del Grosso +//================================================================================================ +#include "seeder.hpp" + +#include "isobus/hardware_integration/available_can_drivers.hpp" +#include "isobus/hardware_integration/can_hardware_interface.hpp" +#include "isobus/isobus/isobus_diagnostic_protocol.hpp" +#include "isobus/isobus/isobus_standard_data_description_indices.hpp" +#include "isobus/isobus/isobus_task_controller_client.hpp" + +#include "console_logger.cpp" + +#include + +bool Seeder::initialize() +{ + bool retVal = true; + + // Automatically load the desired CAN driver based on the available drivers + std::shared_ptr canDriver = nullptr; +#if defined(ISOBUS_SOCKETCAN_AVAILABLE) + canDriver = std::make_shared("can0"); +#elif defined(ISOBUS_WINDOWSPCANBASIC_AVAILABLE) + canDriver = std::make_shared(PCAN_USBBUS1); +#elif defined(ISOBUS_WINDOWSINNOMAKERUSB2CAN_AVAILABLE) + canDriver = std::make_shared(0); // CAN0 +#elif defined(ISOBUS_MACCANPCAN_AVAILABLE) + canDriver = std::make_shared(PCAN_USBBUS1); +#endif + if (nullptr == canDriver) + { + std::cout << "Unable to find a CAN driver. Please make sure you have one of the above drivers installed with the library." << std::endl; + std::cout << "If you want to use a different driver, please add it to the list above." << std::endl; + return false; + } + + isobus::CANStackLogger::set_can_stack_logger_sink(&logger); + isobus::CANStackLogger::set_log_level(isobus::CANStackLogger::LoggingLevel::Debug); // Change this to Debug to see more information + isobus::CANHardwareInterface::set_number_of_can_channels(1); + isobus::CANHardwareInterface::assign_can_channel_frame_handler(0, canDriver); + + if ((!isobus::CANHardwareInterface::start()) || (!canDriver->get_is_valid())) + { + std::cout << "Failed to start hardware interface. The CAN driver might be invalid." << std::endl; + return false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + isobus::NAME TestDeviceNAME(0); + + //! This is an example device that is using a manufacturer code that is currently unused at time of writing + TestDeviceNAME.set_arbitrary_address_capable(true); + TestDeviceNAME.set_industry_group(2); + TestDeviceNAME.set_device_class(4); + TestDeviceNAME.set_function_code(static_cast(isobus::NAME::Function::RateControl)); + TestDeviceNAME.set_identity_number(2); + TestDeviceNAME.set_ecu_instance(0); + TestDeviceNAME.set_function_instance(0); + TestDeviceNAME.set_device_class_instance(0); + TestDeviceNAME.set_manufacturer_code(64); + + const isobus::NAMEFilter filterVirtualTerminal(isobus::NAME::NAMEParameters::FunctionCode, static_cast(isobus::NAME::Function::VirtualTerminal)); + const isobus::NAMEFilter filterTaskController(isobus::NAME::NAMEParameters::FunctionCode, static_cast(isobus::NAME::Function::TaskController)); + const std::vector vtNameFilters = { filterVirtualTerminal }; + const std::vector tcNameFilters = { filterTaskController }; + auto InternalECU = isobus::InternalControlFunction::create(TestDeviceNAME, 0x81, 0); + auto PartnerVT = isobus::PartneredControlFunction::create(0, vtNameFilters); + auto PartnerTC = isobus::PartneredControlFunction::create(0, tcNameFilters); + + diagnosticProtocol = std::make_unique(InternalECU); + diagnosticProtocol->initialize(); + + diagnosticProtocol->set_product_identification_code("1234567890ABC"); + diagnosticProtocol->set_product_identification_brand("AgIsoStack++"); + diagnosticProtocol->set_product_identification_model("AgIsoStack++ Seeder Example"); + diagnosticProtocol->set_software_id_field(0, "Example 1.0.0"); + diagnosticProtocol->set_ecu_id_field(isobus::DiagnosticProtocol::ECUIdentificationFields::HardwareID, "1234"); + diagnosticProtocol->set_ecu_id_field(isobus::DiagnosticProtocol::ECUIdentificationFields::Location, "N/A"); + diagnosticProtocol->set_ecu_id_field(isobus::DiagnosticProtocol::ECUIdentificationFields::ManufacturerName, "Open-Agriculture"); + diagnosticProtocol->set_ecu_id_field(isobus::DiagnosticProtocol::ECUIdentificationFields::PartNumber, "1234"); + diagnosticProtocol->set_ecu_id_field(isobus::DiagnosticProtocol::ECUIdentificationFields::SerialNumber, "2"); + diagnosticProtocol->ControlFunctionFunctionalitiesMessageInterface.set_task_controller_geo_client_option(255); + diagnosticProtocol->ControlFunctionFunctionalitiesMessageInterface.set_task_controller_section_control_client_option_state(1, 255); + diagnosticProtocol->ControlFunctionFunctionalitiesMessageInterface.set_functionality_is_supported(isobus::ControlFunctionFunctionalities::Functionalities::MinimumControlFunction, 1, true); + diagnosticProtocol->ControlFunctionFunctionalitiesMessageInterface.set_functionality_is_supported(isobus::ControlFunctionFunctionalities::Functionalities::UniversalTerminalWorkingSet, 1, true); + diagnosticProtocol->ControlFunctionFunctionalitiesMessageInterface.set_functionality_is_supported(isobus::ControlFunctionFunctionalities::Functionalities::TaskControllerBasicClient, 1, true); + diagnosticProtocol->ControlFunctionFunctionalitiesMessageInterface.set_functionality_is_supported(isobus::ControlFunctionFunctionalities::Functionalities::TaskControllerGeoClient, 1, true); + diagnosticProtocol->ControlFunctionFunctionalitiesMessageInterface.set_functionality_is_supported(isobus::ControlFunctionFunctionalities::Functionalities::TaskControllerSectionControlClient, 1, true); + + VTApplication = std::make_unique(PartnerVT, PartnerTC, InternalECU); + VTApplication->initialize(); + + return retVal; +} + +void Seeder::terminate() +{ + if (nullptr != VTApplication) + { + VTApplication->VTClientInterface->terminate(); + VTApplication->TCClientInterface.terminate(); + } + if (nullptr != diagnosticProtocol) + { + diagnosticProtocol->terminate(); + } + isobus::CANHardwareInterface::stop(); +} + +void Seeder::update() +{ + if (nullptr != VTApplication) + { + VTApplication->update(); + diagnosticProtocol->update(); + } +} diff --git a/examples/seeder_example/seeder.hpp b/examples/seeder_example/seeder.hpp new file mode 100644 index 00000000..246a31d7 --- /dev/null +++ b/examples/seeder_example/seeder.hpp @@ -0,0 +1,31 @@ +//================================================================================================ +/// @file seeder.hpp +/// +/// @brief This is the definition of an example seeder application +/// @author Adrian Del Grosso +/// +/// @copyright 2023 Adrian Del Grosso +//================================================================================================ +#ifndef SEEDER_HPP +#define SEEDER_HPP + +#include "isobus/isobus/isobus_diagnostic_protocol.hpp" +#include "vt_application.hpp" + +class Seeder +{ +public: + Seeder() = default; + + bool initialize(); + + void terminate(); + + void update(); + +private: + std::unique_ptr VTApplication = nullptr; + std::unique_ptr diagnosticProtocol = nullptr; +}; + +#endif // SEEDER_HPP diff --git a/examples/seeder_example/vt_application.cpp b/examples/seeder_example/vt_application.cpp new file mode 100644 index 00000000..8eed1a20 --- /dev/null +++ b/examples/seeder_example/vt_application.cpp @@ -0,0 +1,798 @@ +//================================================================================================ +/// @file vt_application.cpp +/// +/// @brief This is the implementation of the VT portion of the seeder example +/// @author Adrian Del Grosso +/// @author Daan Steenbergen +/// +/// @copyright 2024 The Open-Agriculture Developers +//================================================================================================ +#include "vt_application.hpp" + +#include "isobus/isobus/can_network_manager.hpp" +#include "isobus/isobus/isobus_standard_data_description_indices.hpp" +#include "isobus/utility/iop_file_interface.hpp" +#include "isobus/utility/system_timing.hpp" +#include "object_pool.hpp" + +#include +#include + +SeederVtApplication::SeederVtApplication(std::shared_ptr VTPartner, std::shared_ptr TCPartner, std::shared_ptr source) : + TCClientInterface(TCPartner, source, nullptr), + VTClientInterface(std::make_shared(VTPartner, source)), + VTClientUpdateHelper(VTClientInterface), + sectionControl(NUMBER_ONSCREEN_SECTIONS), + speedMessages(source, false, false, false, false) +{ + alarms[AlarmType::NoMachineSpeed] = Alarm(10000); // 10 seconds + alarms[AlarmType::NoTaskController] = Alarm(30000); // 30 seconds, TC can take a while to connect +} + +bool SeederVtApplication::initialize() +{ + objectPool = isobus::IOPFileInterface::read_iop_file("BasePool.iop"); + + if (objectPool.empty()) + { + std::cout << "Failed to load object pool from BasePool.iop" << std::endl; + return false; + } + std::cout << "Loaded object pool from BasePool.iop" << std::endl; + + // Generate a unique version string for this object pool (this is optional, and is entirely application specific behavior) + std::string objectPoolHash = isobus::IOPFileInterface::hash_object_pool_to_version(objectPool); + + VTClientInterface->set_object_pool(0, objectPool.data(), static_cast(objectPool.size()), objectPoolHash); + VTClientInterface->get_vt_soft_key_event_dispatcher().add_listener([this](const isobus::VirtualTerminalClient::VTKeyEvent &event) { this->handle_vt_key_events(event); }); + VTClientInterface->get_vt_button_event_dispatcher().add_listener([this](const isobus::VirtualTerminalClient::VTKeyEvent &event) { this->handle_vt_key_events(event); }); + VTClientInterface->get_vt_change_numeric_value_event_dispatcher().add_listener([this](const isobus::VirtualTerminalClient::VTChangeNumericValueEvent &event) { this->handle_numeric_value_events(event); }); + VTClientInterface->initialize(true); + + // Track the numeric values we want to update + VTClientUpdateHelper.add_tracked_numeric_value(enableAlarms_VarNum, true); + VTClientUpdateHelper.add_tracked_numeric_value(autoManual_ObjPtr); + VTClientUpdateHelper.add_tracked_numeric_value(statisticsSelection_VarNum, 1); + VTClientUpdateHelper.add_tracked_numeric_value(selectedStatisticsContainer_ObjPtr, canStatistics_Container); + VTClientUpdateHelper.add_tracked_numeric_value(canAddress_VarNum); + VTClientUpdateHelper.add_tracked_numeric_value(utAddress_VarNum); + VTClientUpdateHelper.add_tracked_numeric_value(busload_VarNum); + VTClientUpdateHelper.add_tracked_numeric_value(speedUnits_ObjPtr, unitKph_OutStr); + VTClientUpdateHelper.add_tracked_numeric_value(tcAddress_VarNum); + VTClientUpdateHelper.add_tracked_numeric_value(tcNumberBoomsSupported_VarNum); + VTClientUpdateHelper.add_tracked_numeric_value(tcControlChannels_VarNum); + VTClientUpdateHelper.add_tracked_numeric_value(tcSupportedSections_VarNum); + VTClientUpdateHelper.add_tracked_numeric_value(tcVersion_VarNum); + VTClientUpdateHelper.add_tracked_numeric_value(section1EnableState_ObjPtr); + VTClientUpdateHelper.add_tracked_numeric_value(section2EnableState_ObjPtr); + VTClientUpdateHelper.add_tracked_numeric_value(section3EnableState_ObjPtr); + VTClientUpdateHelper.add_tracked_numeric_value(section4EnableState_ObjPtr); + VTClientUpdateHelper.add_tracked_numeric_value(section5EnableState_ObjPtr); + VTClientUpdateHelper.add_tracked_numeric_value(section6EnableState_ObjPtr); + VTClientUpdateHelper.add_tracked_numeric_value(currentSpeedMeter_VarNum, 16); + VTClientUpdateHelper.add_tracked_numeric_value(currentSpeedReadout_VarNum, 16); + VTClientUpdateHelper.add_tracked_numeric_value(utVersion_VarNum); + VTClientUpdateHelper.add_tracked_numeric_value(currentAlarms1_ObjPtr); + VTClientUpdateHelper.add_tracked_numeric_value(currentAlarms2_ObjPtr); + + // Track the attribute values we want to update + VTClientUpdateHelper.add_tracked_attribute(speed_OutNum, 8, 0.0036f); + VTClientUpdateHelper.add_tracked_attribute(section1Status_OutRect, 5, solidGreen_FillAttr); + VTClientUpdateHelper.add_tracked_attribute(section2Status_OutRect, 5, solidYellow_FillAttr); + VTClientUpdateHelper.add_tracked_attribute(section3Status_OutRect, 5, solidRed_FillAttr); + VTClientUpdateHelper.add_tracked_attribute(section4Status_OutRect, 5, solidRed_FillAttr); + VTClientUpdateHelper.add_tracked_attribute(section5Status_OutRect, 5, solidYellow_FillAttr); + VTClientUpdateHelper.add_tracked_attribute(section6Status_OutRect, 5, solidGreen_FillAttr); + + VTClientUpdateHelper.initialize(); + + // Update the objects to their initial state, we should try to minimize this + VTClientUpdateHelper.set_numeric_value(currentSpeedMeter_VarNum, 0); + VTClientUpdateHelper.set_numeric_value(currentSpeedReadout_VarNum, 0); + VTClientUpdateHelper.set_numeric_value(autoManual_ObjPtr, sectionControl.get_is_mode_auto() ? autoMode_Container : manualMode_Container); + for (std::uint8_t i = 0; i < NUMBER_ONSCREEN_SECTIONS; ++i) + { + update_section_objects(i); + } + + speedMessages.initialize(); + speedMessages.get_machine_selected_speed_data_event_publisher().add_listener([this](const std::shared_ptr mssData, bool changed) { this->handle_machine_selected_speed(mssData, changed); }); + speedMessages.get_ground_based_machine_speed_data_event_publisher().add_listener([this](const std::shared_ptr gbsData, bool changed) { this->handle_ground_based_speed(gbsData, changed); }); + speedMessages.get_wheel_based_machine_speed_data_event_publisher().add_listener([this](const std::shared_ptr wbsData, bool changed) { this->handle_wheel_based_speed(wbsData, changed); }); + + ddop = std::make_shared(3); + if (sectionControl.create_ddop(ddop, TCClientInterface.get_internal_control_function()->get_NAME())) + { + TCClientInterface.configure(ddop, 1, 255, 255, true, true, true, false, true); + TCClientInterface.add_request_value_callback(SectionControlImplementSimulator::request_value_command_callback, §ionControl); + TCClientInterface.add_value_command_callback(SectionControlImplementSimulator::value_command_callback, §ionControl); + TCClientInterface.initialize(true); + } + else + { + std::cout << "Failed generating DDOP. TC functionality will not work until the DDOP structure is fixed." << std::endl; + return false; + } + return true; +} + +void SeederVtApplication::handle_vt_key_events(const isobus::VirtualTerminalClient::VTKeyEvent &event) +{ + if (event.keyNumber == 0) + { + // We have the alarm ACK code, so let's check if an alarm is active + for (auto &alarm : alarms) + { + if (alarm.second.is_active()) + { + alarm.second.acknowledge(); + update_alarms(); + break; + } + } + } + + if (isobus::VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased == event.keyEvent) + { + switch (event.objectID) + { + case home_Key: + { + VTClientUpdateHelper.set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); + } + break; + + case settings_Key: + { + VTClientUpdateHelper.set_active_data_or_alarm_mask(example_WorkingSet, settingsRunscreen_DataMask); + } + break; + + case statistics_Key: + { + VTClientUpdateHelper.set_active_data_or_alarm_mask(example_WorkingSet, statisticsRunscreen_DataMask); + } + break; + + case alarms_Key: + { + VTClientUpdateHelper.set_active_data_or_alarm_mask(example_WorkingSet, alarmsRunscreen_DataMask); + } + break; + + case acknowledgeAlarm_SoftKey: + { + // Acknowledge the first active alarm + for (auto &alarm : alarms) + { + if (alarm.second.is_active()) + { + alarm.second.acknowledge(); + update_alarms(); + break; + } + } + break; + } + break; + + case autoManualToggle_Button: + { + sectionControl.set_is_mode_auto(!sectionControl.get_is_mode_auto()); + VTClientUpdateHelper.set_numeric_value(autoManual_ObjPtr, sectionControl.get_is_mode_auto() ? autoMode_Container : manualMode_Container); + for (std::uint8_t i = 0; i < NUMBER_ONSCREEN_SECTIONS; ++i) + { + update_section_objects(i); + } + } + break; + + case section1Toggle_Button: + { + toggle_section(0); + } + break; + + case section2Toggle_Button: + { + toggle_section(1); + } + break; + + case section3Toggle_Button: + { + toggle_section(2); + } + break; + + case section4Toggle_Button: + { + toggle_section(3); + } + break; + + case section5Toggle_Button: + { + toggle_section(4); + } + break; + + case section6Toggle_Button: + { + toggle_section(5); + } + break; + + default: + break; + } + } +} + +void SeederVtApplication::handle_numeric_value_events(const isobus::VirtualTerminalClient::VTChangeNumericValueEvent &event) +{ + switch (event.objectID) + { + case statisticsSelection_VarNum: + { + // Update the frame to show the newly selected statistic + std::uint16_t targetContainer; + switch (event.value) + { + case 1: + { + targetContainer = canStatistics_Container; + } + break; + + case 2: + { + targetContainer = utStatistics_Container; + } + break; + + case 3: + { + targetContainer = tcStatistics_Container; + } + break; + + case 4: + { + targetContainer = credits_Container; + } + break; + + default: + { + targetContainer = UNDEFINED; + } + break; + } + VTClientUpdateHelper.set_numeric_value(selectedStatisticsContainer_ObjPtr, targetContainer); + } + break; + + default: + break; + } +} + +void SeederVtApplication::handle_machine_selected_speed(const std::shared_ptr mssData, bool changed) +{ + if (changed) + { + process_new_speed(SpeedSources::MachineSelected, mssData->get_machine_speed()); + } +} + +void SeederVtApplication::handle_ground_based_speed(const std::shared_ptr gbsData, bool changed) +{ + if (changed) + { + process_new_speed(SpeedSources::GroundBased, gbsData->get_machine_speed()); + } +} + +void SeederVtApplication::handle_wheel_based_speed(const std::shared_ptr wbsData, bool changed) +{ + if (changed) + { + process_new_speed(SpeedSources::WheelBased, wbsData->get_machine_speed()); + } +} + +void SeederVtApplication::process_new_speed(SpeedSources source, std::uint32_t speed) +{ + bool shouldConsumeThisSpeed = false; + + if (source == SpeedSources::MachineSelected) + { + shouldConsumeThisSpeed = true; // Best speed source. + } + else if ((SpeedSources::GroundBased == source) && (0 == speedMessages.get_number_received_machine_selected_speed_sources())) + { + shouldConsumeThisSpeed = true; // Second best speed source. + } + else if ((SpeedSources::WheelBased == source) && + (0 == speedMessages.get_number_received_machine_selected_speed_sources()) && + (0 == speedMessages.get_number_received_ground_based_speed_sources())) + { + shouldConsumeThisSpeed = true; // Third best speed source. + } + + if (shouldConsumeThisSpeed) + { + update_speedometer_objects(speed); + } +} + +void SeederVtApplication::update() +{ + // Update some polled data or other things that don't need as frequent updates + if (isobus::SystemTiming::time_expired_ms(slowUpdateTimestamp_ms, 1000)) + { + auto VTClientControlFunction = VTClientInterface->get_internal_control_function(); + auto VTControlFunction = VTClientInterface->get_partner_control_function(); + auto TCControlFunction = TCClientInterface.get_partner_control_function(); + + if (nullptr != VTClientControlFunction) + { + // These are used for displaying to the user. Address is not really needed to be known. + VTClientUpdateHelper.set_numeric_value(canAddress_VarNum, VTClientControlFunction->get_address()); + VTClientUpdateHelper.set_numeric_value(utAddress_VarNum, VTControlFunction->get_address()); + if (!languageDataRequested) + { + languageDataRequested = VTClientInterface->languageCommandInterface.send_request_language_command(); + } + } + if (get_is_object_shown(busload_VarNum)) + { + auto busload = isobus::CANNetworkManager::CANNetwork.get_estimated_busload(0); + VTClientUpdateHelper.set_numeric_value(busload_VarNum, static_cast(busload * 100.0f)); + } + update_ut_version_objects(VTClientInterface->get_connected_vt_version()); + + if (isobus::LanguageCommandInterface::DistanceUnits::ImperialUS == VTClientInterface->languageCommandInterface.get_commanded_distance_units()) + { + VTClientUpdateHelper.set_attribute(speed_OutNum, 8, 0.0022369363f); // mm/s to mph + VTClientUpdateHelper.set_numeric_value(speedUnits_ObjPtr, unitMph_OutStr); + } + else + { + VTClientUpdateHelper.set_attribute(speed_OutNum, 8, 0.0036f); // mm/s to kph + VTClientUpdateHelper.set_numeric_value(speedUnits_ObjPtr, unitKph_OutStr); + } + VTClientUpdateHelper.set_numeric_value(tcAddress_VarNum, TCClientInterface.get_partner_control_function()->get_address()); + VTClientUpdateHelper.set_numeric_value(tcNumberBoomsSupported_VarNum, TCClientInterface.get_connected_tc_number_booms_supported()); + VTClientUpdateHelper.set_numeric_value(tcControlChannels_VarNum, TCClientInterface.get_connected_tc_number_channels_supported()); + VTClientUpdateHelper.set_numeric_value(tcSupportedSections_VarNum, TCClientInterface.get_connected_tc_number_sections_supported()); + VTClientUpdateHelper.set_numeric_value(tcVersion_VarNum, static_cast(TCClientInterface.get_connected_tc_version())); + + if ((0 == speedMessages.get_number_received_machine_selected_speed_command_sources()) && + (0 == speedMessages.get_number_received_ground_based_speed_sources()) && + (0 == speedMessages.get_number_received_wheel_based_speed_sources())) + { + update_speedometer_objects(0); + } + + update_alarms(); + slowUpdateTimestamp_ms = isobus::SystemTiming::get_timestamp_ms(); + } + speedMessages.update(); + for (std::uint8_t i = 0; i < NUMBER_ONSCREEN_SECTIONS; ++i) + { + update_section_objects(i); + } + VTClientUpdateHelper.set_numeric_value(autoManual_ObjPtr, sectionControl.get_is_mode_auto() ? autoMode_Container : manualMode_Container); +} + +void SeederVtApplication::toggle_section(std::uint8_t sectionIndex) +{ + sectionControl.set_section_switch_state(sectionIndex, !sectionControl.get_section_switch_state(sectionIndex)); + TCClientInterface.on_value_changed_trigger(2, static_cast(isobus::DataDescriptionIndex::ActualCondensedWorkState1_16)); + update_section_objects(sectionIndex); +} + +void SeederVtApplication::update_section_objects(std::uint8_t sectionIndex) +{ + std::uint16_t newObject = offButtonSliderSmall_OutPict; + if (sectionControl.get_section_switch_state(sectionIndex)) + { + newObject = onButtonSliderSmall_OutPict; + } + + std::uint32_t fillAttribute = solidRed_FillAttr; + if (sectionControl.get_section_actual_state(sectionIndex)) + { + fillAttribute = solidGreen_FillAttr; + } + else if (sectionControl.get_section_setpoint_state(sectionIndex)) + { + fillAttribute = solidYellow_FillAttr; + } + else + { + fillAttribute = solidRed_FillAttr; + } + + std::uint16_t switchPointerId = UNDEFINED; + std::uint16_t statusRectangleId = UNDEFINED; + switch (sectionIndex) + { + case 0: + { + switchPointerId = section1EnableState_ObjPtr; + statusRectangleId = section1Status_OutRect; + } + break; + + case 1: + { + switchPointerId = section2EnableState_ObjPtr; + statusRectangleId = section2Status_OutRect; + } + break; + + case 2: + { + switchPointerId = section3EnableState_ObjPtr; + statusRectangleId = section3Status_OutRect; + } + break; + + case 3: + { + switchPointerId = section4EnableState_ObjPtr; + statusRectangleId = section4Status_OutRect; + } + break; + + case 4: + { + switchPointerId = section5EnableState_ObjPtr; + statusRectangleId = section5Status_OutRect; + } + break; + + case 5: + { + switchPointerId = section6EnableState_ObjPtr; + statusRectangleId = section6Status_OutRect; + } + break; + + default: + break; + } + VTClientUpdateHelper.set_numeric_value(switchPointerId, newObject); + VTClientUpdateHelper.set_attribute(statusRectangleId, 5, fillAttribute); // 5 Is the attribute ID of the fill attribute +} + +void SeederVtApplication::update_speedometer_objects(std::uint32_t speed) +{ + if (get_is_object_shown(currentSpeedReadout_VarNum)) + { + VTClientUpdateHelper.set_numeric_value(currentSpeedReadout_VarNum, speed); + } + + // The meter uses a fixed max of "30", so we'll have to do some scaling ourselves + switch (VTClientInterface->languageCommandInterface.get_commanded_distance_units()) + { + case isobus::LanguageCommandInterface::DistanceUnits::Metric: + { + // Scale to KPH + speed = static_cast((speed * 0.001f) * 3.6f); // Converting mm/s to m/s, then mm/s to kph + } + break; + + case isobus::LanguageCommandInterface::DistanceUnits::ImperialUS: + { + // Scale to MPH + speed = static_cast((speed * 0.001f) * 2.23694f); // Converting mm/s to m/s, then mm/s to mph + } + break; + + default: + { + speed = 0; // Reserved or n/a? + } + break; + } + if (get_is_object_shown(currentSpeedMeter_VarNum)) + { + VTClientUpdateHelper.set_numeric_value(currentSpeedMeter_VarNum, speed); + } +} + +bool SeederVtApplication::get_is_object_shown(std::uint16_t objectID) const +{ + //! TODO: add this functionality to the VTClientStateTracker + + if (!VTClientUpdateHelper.is_working_set_active()) + { + return false; + } + bool retVal = false; + + switch (objectID) + { + case section1Status_OutRect: + case section2Status_OutRect: + case section3Status_OutRect: + case section4Status_OutRect: + case section5Status_OutRect: + case section6Status_OutRect: + case autoManual_Container: + case autoManual_ObjPtr: + case mainRunscreen_SoftKeyMask: + case Title_OutStr: + case planterRunscreenStatus_Container: + case planter_OutPict: + case sectionButtons_Container: + case section1Switch_Container: + case section2Switch_Container: + case section3Switch_Container: + case section4Switch_Container: + case section5Switch_Container: + case section6Switch_Container: + case speed_OutNum: + case speedReadout_Container: + case speedUnits_ObjPtr: + case currentSpeedReadout_VarNum: + case currentSpeedMeter_VarNum: + { + retVal = (VTClientUpdateHelper.get_active_mask() == mainRunscreen_DataMask); + } + break; + + case statisticsHeader_OutStr: + case statisticsDropdown_Container: + case statistics_InList: + case selectedStatisticsContainer_ObjPtr: + { + retVal = (VTClientUpdateHelper.get_active_mask() == statisticsRunscreen_DataMask); + } + break; + + case returnHome_SKeyMask: + { + retVal = (VTClientUpdateHelper.get_active_mask() != mainRunscreen_DataMask); + } + break; + + case busload_VarNum: + case canAddress_VarNum: + { + retVal = ((VTClientUpdateHelper.get_active_mask() == statisticsRunscreen_DataMask) && + (VTClientUpdateHelper.get_numeric_value(selectedStatisticsContainer_ObjPtr) == canStatistics_Container)); + } + break; + + case utAddress_VarNum: + case utVersion_VarNum: + { + retVal = ((VTClientUpdateHelper.get_active_mask() == statisticsRunscreen_DataMask) && + (VTClientUpdateHelper.get_numeric_value(selectedStatisticsContainer_ObjPtr) == utStatistics_Container)); + } + break; + + case tcVersion_VarNum: + case tcAddress_VarNum: + case tcNumberBoomsSupported_VarNum: + case tcSupportedSections_VarNum: + case tcControlChannels_VarNum: + { + retVal = ((VTClientUpdateHelper.get_active_mask() == statisticsRunscreen_DataMask) && + (VTClientUpdateHelper.get_numeric_value(selectedStatisticsContainer_ObjPtr) == tcStatistics_Container)); + } + break; + + case machineSpeedNotDetectedSummary_OutStr: + { + retVal = (VTClientUpdateHelper.get_active_mask() == noSpeed_AlarmMask); + } + break; + + case TCNotConnectedSummary_OutStr: + case noTCTitle_OutStr: + { + retVal = (VTClientUpdateHelper.get_active_mask() == noTaskController_AlarmMask); + } + break; + + case warning_OutPict: + case alarm_SKeyMask: + { + retVal = ((VTClientUpdateHelper.get_active_mask() == noSpeed_AlarmMask) || + (VTClientUpdateHelper.get_active_mask() == noTaskController_AlarmMask)); + } + break; + + case currentAlarms1_ObjPtr: + case currentAlarms2_ObjPtr: + case currentAlarmsHeader_OutStr: + { + retVal = (VTClientUpdateHelper.get_active_mask() == alarmsRunscreen_DataMask); + } + break; + + case enableAlarms_VarNum: + case enableAlarms_Container: + case enableAlarms_InBool: + case enableAlarms_OutStr: + { + retVal = (VTClientUpdateHelper.get_active_mask() == settingsRunscreen_DataMask); + } + break; + + default: + { + retVal = true; + } + break; + } + return retVal; +} + +void SeederVtApplication::revert_to_previous_data_mask() +{ + for (std::uint16_t maskId : VTClientUpdateHelper.get_mask_history()) + { + // Check if mask is a data mask and if it is not the current mask + if ((maskId != noSpeed_AlarmMask) && (maskId != noTaskController_AlarmMask) && (maskId != VTClientUpdateHelper.get_active_mask())) + { + VTClientUpdateHelper.set_active_data_or_alarm_mask(example_WorkingSet, maskId); + return; + } + } + // No previous data mask found, revert to main runscreen + VTClientUpdateHelper.set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); +} + +void SeederVtApplication::update_ut_version_objects(isobus::VirtualTerminalClient::VTVersion version) +{ + std::uint8_t integerVersion = 0xFF; + + switch (version) + { + case isobus::VirtualTerminalClient::VTVersion::Version2OrOlder: + { + integerVersion = 2; + } + break; + + case isobus::VirtualTerminalClient::VTVersion::Version3: + { + integerVersion = 3; + } + break; + + case isobus::VirtualTerminalClient::VTVersion::Version4: + { + integerVersion = 4; + } + break; + + case isobus::VirtualTerminalClient::VTVersion::Version5: + { + integerVersion = 5; + } + break; + + case isobus::VirtualTerminalClient::VTVersion::Version6: + { + integerVersion = 6; + } + break; + + default: + break; + } + VTClientUpdateHelper.set_numeric_value(utVersion_VarNum, integerVersion); +} + +void SeederVtApplication::update_alarms() +{ + if (VTClientInterface->get_is_connected() && VTClientUpdateHelper.get_numeric_value(enableAlarms_VarNum)) + { + // Check if we have a speed source + if ((0 == speedMessages.get_number_received_machine_selected_speed_command_sources()) && + (0 == speedMessages.get_number_received_ground_based_speed_sources()) && + (0 == speedMessages.get_number_received_wheel_based_speed_sources())) + { + alarms.at(AlarmType::NoMachineSpeed).trigger(); + } + else + { + alarms.at(AlarmType::NoMachineSpeed).reset(); + } + + // Check if we have a TC connected + if (false == TCClientInterface.get_is_connected()) + { + alarms.at(AlarmType::NoTaskController).trigger(); + } + else + { + alarms.at(AlarmType::NoTaskController).reset(); + } + + // Show the first alarm that is active (i.e. highest priority) + std::size_t activeAlarmsCount = 0; + for (auto const &alarm : alarms) + { + if (alarm.second.is_active()) + { + activeAlarmsCount++; + switch (alarm.first) + { + case AlarmType::NoMachineSpeed: + { + if (1 == activeAlarmsCount) + { + VTClientUpdateHelper.set_active_data_or_alarm_mask(example_WorkingSet, noSpeed_AlarmMask); + } + VTClientUpdateHelper.set_numeric_value(currentAlarms1_ObjPtr, NoMachineSpeed_OutStr); + } + break; + + case AlarmType::NoTaskController: + { + if (1 == activeAlarmsCount) + { + VTClientUpdateHelper.set_active_data_or_alarm_mask(example_WorkingSet, noTaskController_AlarmMask); + } + VTClientUpdateHelper.set_numeric_value(1 == activeAlarmsCount ? currentAlarms1_ObjPtr : currentAlarms2_ObjPtr, NoTaskController_OutStr); + } + break; + + default: + break; + } + } + } + + if ((0 == activeAlarmsCount) && ((VTClientUpdateHelper.get_active_mask() == noSpeed_AlarmMask) || (VTClientUpdateHelper.get_active_mask() == noTaskController_AlarmMask))) + { + // No alarms active, but we're showing the alarm screen. Clear it. + revert_to_previous_data_mask(); + } + + for (std::size_t i = activeAlarmsCount; i < static_cast(AlarmType::Count); ++i) + { + // Clear the remaining alarm slots + VTClientUpdateHelper.set_numeric_value(i == 0 ? currentAlarms1_ObjPtr : currentAlarms2_ObjPtr, UNDEFINED); + } + } +} + +SeederVtApplication::Alarm::Alarm(std::uint32_t activationDelay_ms) : + activationDelay_ms(activationDelay_ms) +{ +} + +bool SeederVtApplication::Alarm::is_active() const +{ + return (!acknowledged) && (timestampTriggered_ms != 0) && + isobus::SystemTiming::time_expired_ms(timestampTriggered_ms, activationDelay_ms); +} + +void SeederVtApplication::Alarm::trigger() +{ + if (timestampTriggered_ms == 0) + { + timestampTriggered_ms = isobus::SystemTiming::get_timestamp_ms(); + } +} + +void SeederVtApplication::Alarm::acknowledge() +{ + acknowledged = true; +} + +void SeederVtApplication::Alarm::reset() +{ + timestampTriggered_ms = 0; + acknowledged = false; +} diff --git a/examples/seeder_example/vt_application.hpp b/examples/seeder_example/vt_application.hpp new file mode 100644 index 00000000..a10ec9e8 --- /dev/null +++ b/examples/seeder_example/vt_application.hpp @@ -0,0 +1,144 @@ +//================================================================================================ +/// @file vt_application.hpp +/// +/// @brief This is the definition of a class that handles the main application logic for this +/// example application. +/// @author Adrian Del Grosso +/// @author Daan Steenbergen +/// +/// @copyright 2024 The Open-Agriculture Developers +//================================================================================================ +#ifndef VT_APPLICATION_HPP +#define VT_APPLICATION_HPP + +#include "object_pool.hpp" +#include "section_control_implement_sim.hpp" + +#include "isobus/isobus/isobus_speed_distance_messages.hpp" +#include "isobus/isobus/isobus_task_controller_client.hpp" +#include "isobus/isobus/isobus_virtual_terminal_client.hpp" +#include "isobus/isobus/isobus_virtual_terminal_client_update_helper.hpp" + +/// @brief A class that manages the main application logic for this example program. +class SeederVtApplication +{ +public: + /// @brief Constructor for a SeederVtApplication + SeederVtApplication(std::shared_ptr VTPartner, std::shared_ptr TCPartner, std::shared_ptr source); + + /// @brief Initializes the class. Should be called before update is called the first time + bool initialize(); + + isobus::TaskControllerClient TCClientInterface; ///< An instance of the application's task controller interface + std::shared_ptr VTClientInterface; ///< The application's universal/virtual terminal interface + isobus::VirtualTerminalClientUpdateHelper VTClientUpdateHelper; // A helper class for updating the state of the VT + + /// @brief Cyclically updates the application + void update(); + +private: + /// @brief Enumerates our tolerated speed sources + enum class SpeedSources + { + MachineSelected, + GroundBased, + WheelBased + }; + + /// @brief Lists the different alarm conditions that might exist + enum class AlarmType + { + NoMachineSpeed, ///< No MSS message, needed for section control + NoTaskController, ///< No TC, makes the demo less interesting + + Count ///< The number of alarm types + }; + + /// @brief Stores information associated to if an alarm mask should be shown + class Alarm + { + public: + /// @brief Constructor for an Alarm + /// @param[in] activationDelay_ms The delay before the alarm is shown after it is triggered (in milliseconds) + explicit Alarm(std::uint32_t activationDelay_ms = 10000); + + /// @brief Returns if the alarm is active + /// @returns True if the alarm is active, otherwise false + bool is_active() const; + + /// @brief Triggers the alarm if it is not already triggered + void trigger(); + + /// @brief Acknowledges the alarm + void acknowledge(); + + /// @brief Resets the alarm + void reset(); + + private: + std::uint32_t timestampTriggered_ms = 0; + std::uint32_t activationDelay_ms; + bool acknowledged = false; + }; + + /// @brief A callback for handling VT softkey events + /// @param[in] event The event data to process + void handle_vt_key_events(const isobus::VirtualTerminalClient::VTKeyEvent &event); + + /// @brief A callback for handling VT numeric value events (user enters a new value, for example) + /// @param[in] event The event data to process + void handle_numeric_value_events(const isobus::VirtualTerminalClient::VTChangeNumericValueEvent &event); + + /// @brief A callback for handling machine selected speed events, used to set appropriate VT flags + /// @param[in] event The event data to process + void handle_machine_selected_speed(const std::shared_ptr mssData, bool changed); + + /// @brief A callback for handling ground based speed events, used to set appropriate VT flags + /// @param[in] event The event data to process + void handle_ground_based_speed(const std::shared_ptr mssData, bool changed); + + /// @brief A callback for handling wheel based speed events, used to set appropriate VT flags + /// @param[in] event The event data to process + void handle_wheel_based_speed(const std::shared_ptr mssData, bool changed); + + /// @brief Aggregates speeds and decides which speed to use + void process_new_speed(SpeedSources source, std::uint32_t speed); + + /// @brief Toggle a section switch on or off + /// @param[in] sectionIndex The index of the section to toggle + void toggle_section(std::uint8_t sectionIndex); + + /// @brief Reflects the section state on the screen + void update_section_objects(std::uint8_t sectionIndex); + + /// @brief Reflects the speed on the screen + void update_speedometer_objects(std::uint32_t speed); + + /// @brief Reflects the UT version on the screen + void update_ut_version_objects(isobus::VirtualTerminalClient::VTVersion version); + + /// @brief Returns if the selected object ID is shown currently + /// @param[in] objectID The object ID of the object to check the shown state of + /// @returns True if the selected object ID is shown currently, otherwise false + bool get_is_object_shown(std::uint16_t objectID) const; + + /// @brief Reverts the current mask to the last previously active data mask + void revert_to_previous_data_mask(); + + /// @brief Called cyclically by the update routine, checks if any alarm masks need to be shown to the user + void update_alarms(); + + static constexpr std::uint8_t NUMBER_ONSCREEN_SECTIONS = 6; ///< The number of sections we can display on the screen + + SectionControlImplementSimulator sectionControl; ///< A class that manages section control + std::vector objectPool; ///< Stores our object pool + std::map alarms; ///< Tracks alarm conditions in priority order + isobus::SpeedMessagesInterface speedMessages; ///< Interface for reading speed from the bus + std::shared_ptr ddop = nullptr; ///< Stores our application's DDOP + std::uint32_t slowUpdateTimestamp_ms = 0; ///< A timestamp to limit some polled data to 1Hz update rate + std::uint32_t lastMachineSpeed = 0; ///< Used to help track speed source timeouts + bool languageDataRequested = false; ///< Stores if we've requested the current language data yet + bool alarmsEnabled = true; ///< Enables or disables showing alarms +}; + +#endif // VT_APPLICATION_HPP diff --git a/sonar-project.properties b/sonar-project.properties index e5871659..8bdce9c4 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -9,7 +9,7 @@ sonar.sources=isobus,examples,hardware_integration,utility sonar.tests=test # Exclude some files from the analysis. -sonar.exclusions=**/PCANBasic.h,**/libusb.h,**/InnoMakerUsb2CanLib.h,**/PCBUSB.h,**/canal.h,**/canal_a.h +sonar.exclusions=**/PCANBasic.h,**/libusb.h,**/InnoMakerUsb2CanLib.h,**/PCBUSB.h,**/canal.h,**/canal_a.h,**/object_pool.hpp,**/objectPoolObjects.h # Force the reported findings to be based on C++11 sonar.cfamily.reportingCppStandardOverride=c++11 diff --git a/utility/src/iop_file_interface.cpp b/utility/src/iop_file_interface.cpp index e6c71249..30dc4922 100644 --- a/utility/src/iop_file_interface.cpp +++ b/utility/src/iop_file_interface.cpp @@ -44,7 +44,7 @@ namespace isobus std::size_t seed = iopData.size(); std::stringstream stream; - for (auto x : iopData) + for (std::uint32_t x : iopData) { x = ((x >> 16) ^ x) * 0x45d9f3b; x = ((x >> 16) ^ x) * 0x45d9f3b;