diff --git a/examples/virtual_terminal/esp32_platformio_object_pool/src/main.cpp b/examples/virtual_terminal/esp32_platformio_object_pool/src/main.cpp index a342d3843..e3572040e 100644 --- a/examples/virtual_terminal/esp32_platformio_object_pool/src/main.cpp +++ b/examples/virtual_terminal/esp32_platformio_object_pool/src/main.cpp @@ -21,37 +21,63 @@ static std::shared_ptr virtualTerminalClient = nullptr; static std::shared_ptr virtualTerminalUpdateHelper = nullptr; -// This callback will provide us with event driven notifications of button presses from the stack -void handleVTKeyEvents(const isobus::VirtualTerminalClient::VTKeyEvent &event) +// This callback will provide us with event driven notifications of softkey presses from the stack +void handle_softkey_event(const isobus::VirtualTerminalClient::VTKeyEvent &event) { + if (event.keyNumber == 0) + { + // We have the alarm ACK code, so if we have an active alarm, acknowledge it by going back to the main runscreen + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); + } + switch (event.keyEvent) { case isobus::VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased: - case isobus::VirtualTerminalClient::KeyActivationCode::ButtonStillHeld: { switch (event.objectID) { - case Plus_Button: + case alarm_SoftKey: { - virtualTerminalUpdateHelper->increase_numeric_value(ButtonExampleNumber_VarNum); + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, example_AlarmMask); } break; - case Minus_Button: + case acknowledgeAlarm_SoftKey: { - virtualTerminalUpdateHelper->decrease_numeric_value(ButtonExampleNumber_VarNum); + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); } break; - case alarm_SoftKey: + default: + break; + } + } + break; + + default: + break; + } +} + +// This callback will provide us with event driven notifications of button presses from the stack +void handle_button_event(const isobus::VirtualTerminalClient::VTKeyEvent &event) +{ + switch (event.keyEvent) + { + case isobus::VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased: + case isobus::VirtualTerminalClient::KeyActivationCode::ButtonStillHeld: + { + switch (event.objectID) + { + case Plus_Button: { - virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, example_AlarmMask); + virtualTerminalUpdateHelper->increase_numeric_value(ButtonExampleNumber_VarNum); } break; - case acknowledgeAlarm_SoftKey: + case Minus_Button: { - virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); + virtualTerminalUpdateHelper->decrease_numeric_value(ButtonExampleNumber_VarNum); } break; @@ -110,8 +136,8 @@ extern "C" void app_main() virtualTerminalClient = std::make_shared(TestPartnerVT, TestInternalECU); virtualTerminalClient->set_object_pool(0, testPool, (object_pool_end - object_pool_start) - 1, "ais1"); - virtualTerminalClient->get_vt_soft_key_event_dispatcher().add_listener(handleVTKeyEvents); - virtualTerminalClient->get_vt_button_event_dispatcher().add_listener(handleVTKeyEvents); + virtualTerminalClient->get_vt_soft_key_event_dispatcher().add_listener(handle_softkey_event); + virtualTerminalClient->get_vt_button_event_dispatcher().add_listener(handle_button_event); virtualTerminalClient->initialize(true); virtualTerminalUpdateHelper = std::make_shared(virtualTerminalClient); diff --git a/examples/virtual_terminal/version3_object_pool/main.cpp b/examples/virtual_terminal/version3_object_pool/main.cpp index dde372bc4..9ebd0922d 100644 --- a/examples/virtual_terminal/version3_object_pool/main.cpp +++ b/examples/virtual_terminal/version3_object_pool/main.cpp @@ -12,9 +12,7 @@ #include #include -#include #include -#include //! It is discouraged to use global variables, but it is done here for simplicity. static std::shared_ptr virtualTerminalClient = nullptr; @@ -26,37 +24,63 @@ void signal_handler(int) running = false; } -// This callback will provide us with event driven notifications of button presses from the stack -void handleVTKeyEvents(const isobus::VirtualTerminalClient::VTKeyEvent &event) +// This callback will provide us with event driven notifications of softkey presses from the stack +void handle_softkey_event(const isobus::VirtualTerminalClient::VTKeyEvent &event) { + if (event.keyNumber == 0) + { + // We have the alarm ACK code, so if we have an active alarm, acknowledge it by going back to the main runscreen + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); + } + switch (event.keyEvent) { case isobus::VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased: - case isobus::VirtualTerminalClient::KeyActivationCode::ButtonStillHeld: { switch (event.objectID) { - case Plus_Button: + case alarm_SoftKey: { - virtualTerminalUpdateHelper->increase_numeric_value(ButtonExampleNumber_VarNum); + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, example_AlarmMask); } break; - case Minus_Button: + case acknowledgeAlarm_SoftKey: { - virtualTerminalUpdateHelper->decrease_numeric_value(ButtonExampleNumber_VarNum); + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); } break; - case alarm_SoftKey: + default: + break; + } + } + break; + + default: + break; + } +} + +// This callback will provide us with event driven notifications of button presses from the stack +void handle_button_event(const isobus::VirtualTerminalClient::VTKeyEvent &event) +{ + switch (event.keyEvent) + { + case isobus::VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased: + case isobus::VirtualTerminalClient::KeyActivationCode::ButtonStillHeld: + { + switch (event.objectID) + { + case Plus_Button: { - virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, example_AlarmMask); + virtualTerminalUpdateHelper->increase_numeric_value(ButtonExampleNumber_VarNum); } break; - case acknowledgeAlarm_SoftKey: + case Minus_Button: { - virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); + virtualTerminalUpdateHelper->decrease_numeric_value(ButtonExampleNumber_VarNum); } break; @@ -140,8 +164,8 @@ int main() virtualTerminalClient = std::make_shared(TestPartnerVT, TestInternalECU); virtualTerminalClient->set_object_pool(0, testPool.data(), testPool.size(), objectPoolHash); - virtualTerminalClient->get_vt_soft_key_event_dispatcher().add_listener(handleVTKeyEvents); - virtualTerminalClient->get_vt_button_event_dispatcher().add_listener(handleVTKeyEvents); + virtualTerminalClient->get_vt_soft_key_event_dispatcher().add_listener(handle_softkey_event); + virtualTerminalClient->get_vt_button_event_dispatcher().add_listener(handle_button_event); virtualTerminalClient->initialize(true); virtualTerminalUpdateHelper = std::make_shared(virtualTerminalClient); diff --git a/sphinx/README.md b/sphinx/README.md index 8b0a952c1..ad9fa0a3b 100644 --- a/sphinx/README.md +++ b/sphinx/README.md @@ -13,6 +13,7 @@ doxygen doxyfile ``` Install required python modules: + ```bash pip install -r requirements.txt ``` diff --git a/sphinx/images/install_espidf.png b/sphinx/images/install_espidf.png deleted file mode 100644 index 956a258ee..000000000 Binary files a/sphinx/images/install_espidf.png and /dev/null differ diff --git a/sphinx/images/install_espidf_2.png b/sphinx/images/install_espidf_2.png deleted file mode 100644 index 8b6f988d8..000000000 Binary files a/sphinx/images/install_espidf_2.png and /dev/null differ diff --git a/sphinx/images/install_espidf_3.png b/sphinx/images/install_espidf_3.png deleted file mode 100644 index 47823932f..000000000 Binary files a/sphinx/images/install_espidf_3.png and /dev/null differ diff --git a/sphinx/images/pthreads_settings.png b/sphinx/images/pthreads_settings.png index c9471a6a0..10fc387ad 100644 Binary files a/sphinx/images/pthreads_settings.png and b/sphinx/images/pthreads_settings.png differ diff --git a/sphinx/images/tick_rate.png b/sphinx/images/tick_rate.png index 52cf71538..a6d20282e 100644 Binary files a/sphinx/images/tick_rate.png and b/sphinx/images/tick_rate.png differ diff --git a/sphinx/source/Tutorials/ESP32 PlatformIO.rst b/sphinx/source/Tutorials/ESP32 PlatformIO.rst index e1fd6fb70..5f6b24e28 100644 --- a/sphinx/source/Tutorials/ESP32 PlatformIO.rst +++ b/sphinx/source/Tutorials/ESP32 PlatformIO.rst @@ -16,36 +16,16 @@ This tutorial covers how to get started using the library with an ESP32 and Plat Installation -------------- -1. Install `Visual Studio Code. `_ -2. Install `Git `_ -3. Install the ESP-IDF extension in VS Code +#. Install `Visual Studio Code. `_ +#. Install `Git `_ +#. Install the PlatformIO Extension in VS Code -- Search for ESP-IDF in the extension marketplace and click install + .. image:: ../../images/install_platformio.png + :width: 360 + :alt: Installing the PlatformIO extension - .. image:: ../../images/install_espidf.png - :width: 360 - :alt: Installing ESP-IDF with VS Code Marketplace - -- Select the newly installed extension, select "Express" - - .. image:: ../../images/install_espidf_2.png - :width: 360 - :alt: Selecting the Express installation method - -- Select the most recent version of ESP-IDF and click "install". This might take several minutes to complete. - - .. image:: ../../images/install_espidf_3.png - :width: 360 - :alt: Choosing the version of ESP-IDF to install - -4. Install the PlatformIO Extension in VS Code - -.. image:: ../../images/install_platformio.png - :width: 360 - :alt: Installing the PlatformIO extension - -5. Select the PlatformIO Extension and wait for it's installation to finish. This might take several minutes. -6. Once PlatformIO is done installing, it might ask you to restart VS Code, or to reload it. Do this if it asks. +#. Select the PlatformIO Extension and wait for it's installation to finish. This might take several minutes. +#. Once PlatformIO is done installing, it might ask you to restart VS Code, or to reload it. Do this if it asks. Everything you need should now be installed! @@ -54,205 +34,208 @@ Getting Started With a Blank Project To create a blank PlatformIO project that uses AgIsoStack, here are the basic steps. -1. Open VS Code, select the PlatformIO extension, click "Create New Project", and/or "New Project" - -.. image:: ../../images/new_platformio_project.png - :width: 420 - :alt: Creating a new Platform IO project - -2. Give your project a name, select your target ESP32 board, and make sure to select `Espidf` as your framework. +#. Open VS Code, select the PlatformIO extension, click "Create New Project", and/or "New Project" -.. image:: ../../images/project_settings.png - :width: 360 - :alt: Setting Platform IO project settings + .. image:: ../../images/new_platformio_project.png + :width: 420 + :alt: Creating a new Platform IO project -3. Wait while PlatformIO creates the project. This can take several minutes depending on how much data PlatformIO has to fetch from the internet. -4. VS Code may ask you to trust your workspace once the project is created. Click "yes", and optionally consider checking the box to always trust your projects folder. +#. Give your project a name, select your target ESP32 board, and make sure to select :code:`Espidf` as your framework. -.. image:: ../../images/vs_code_trust.png - :width: 360 - :alt: Setting Platform IO project settings + .. image:: ../../images/project_settings.png + :width: 360 + :alt: Setting Platform IO project settings + +#. Wait while PlatformIO creates the project. This can take several minutes depending on how much data PlatformIO has to fetch from the internet. +#. VS Code may ask you to trust your workspace once the project is created. Click "yes", and optionally consider checking the box to always trust your projects folder. -Now you've got a blank project, but we need to do a few more things to actually bring in the library and ensure it compiles. + .. image:: ../../images/vs_code_trust.png + :width: 360 + :alt: Setting Platform IO project settings -5. Rename your :code:`main.c` file to :code:`main.cpp`. +#. Now you've got a blank project. Try to build it as followed, you should be able to build it successfully at this point. +Now that you have a blank project, you can start adding the AgIsoStack library to it. The AgIsoStack++ library is a C++ library, so we need to compile our main file as a C++ file instead of a C file. +Also, Since the ESP-IDF framework expects app_main to have C-linkage and we have changed to a C++ file, we need to add :code:`extern "C"` decoration, otherwise you will get a linker error when you build the project. -6. Change the definition for :code:`app_main` to be :code:`extern "C" void app_main()`. +#. Rename your :code:`main.c` file to :code:`main.cpp`. +#. Change the definition for :code:`app_main` to be :code:`extern "C" void app_main()`. +#. Add the following line to your :code:`platformio.ini` file: ::code:`lib_deps = https://github.com/Open-Agriculture/AgIsoStack-plus-plus.git` + + This will tell PlatformIO to reach out to GitHub, download the library, and automatically integrate it via CMake. -Because we are changing to a :code:`.cpp` file, but the ESP-IDF framework expects app_main to have C-linkage, we need to add this decoration, or you may get a linker error when you build the project. + .. warning:: + + PlatformIO will not automatically update AgIsoStack. You can run :code:`pio pkg update` to update it manually from the PlatformIO core CLI. + It is important that you do this from time to time to ensure you have the latest features and bug fixes. -7. Add the following line to your :code:`platformio.ini` file: :code:`lib_deps = https://github.com/Open-Agriculture/AgIsoStack-plus-plus.git` +#. Optionally, add lines to your :code:`platformio.ini` file to allow the CAN stack to send logged errors and warning messages through the serial monitor, and to have backtraces parsed for you if your application crashes. -This will tell PlatformIO to reach out to GitHub, download the library, and automatically integrate it via CMake. + .. code-block:: ini -.. warning:: + build_type = debug + upload_protocol = esptool + monitor_speed = 115200 + monitor_rts = 0 + monitor_dtr = 0 + monitor_filters = esp32_exception_decoder - PlatformIO will never automatically update AgIsoStack. You must run :code:`pio pkg update` to update it periodically from the PlatformIO core CLI. - It is important that you do this from time to time to ensure you have the latest features and bug fixes. + Your :code:`platformio.ini` file should end up looking something like this, though your board and environment may be different: -8. Optionally, add lines to your :code:`platformio.ini` file to allow the CAN stack to send logged errors and warning messages through the serial monitor, and to have backtraces parsed for you if your application crashes. + .. code-block:: ini -.. code-block:: text - - build_type = debug - upload_protocol = esptool - monitor_speed = 115200 - monitor_rts = 0 - monitor_dtr = 0 - monitor_filters = esp32_exception_decoder + ; PlatformIO Project Configuration File + ; + ; Build options: build flags, source filter + ; Upload options: custom upload port, speed and extra flags + ; Library options: dependencies, extra library storages + ; Advanced options: extra scripting + ; + ; Please visit documentation for the other options and examples + ; https://docs.platformio.org/page/projectconf.html -Your :code:`platformio.ini` file should end up looking something like this, though your board and environment may be different: + [env:denky32] + platform = espressif32 + board = denky32 + framework = espidf -.. code-block:: text + lib_deps = https://github.com/Open-Agriculture/AgIsoStack-plus-plus.git - ; PlatformIO Project Configuration File - ; - ; Build options: build flags, source filter - ; Upload options: custom upload port, speed and extra flags - ; Library options: dependencies, extra library storages - ; Advanced options: extra scripting - ; - ; Please visit documentation for the other options and examples - ; https://docs.platformio.org/page/projectconf.html + build_type = debug + upload_protocol = esptool + monitor_speed = 115200 + monitor_rts = 0 + monitor_dtr = 0 + monitor_filters = esp32_exception_decoder - [env:denky32] - platform = espressif32 - board = denky32 - framework = espidf + Specifically, this configuration was created for the ESP-WROOM-32 (denky32), so if you have a different board, make sure to edit this appropriately. - lib_deps = https://github.com/Open-Agriculture/AgIsoStack-plus-plus.git +#. Include the header files you might need in :code:`main.cpp`. - build_type = debug - upload_protocol = esptool - monitor_speed = 115200 - monitor_rts = 0 - monitor_dtr = 0 - monitor_filters = esp32_exception_decoder + AgIsoStack++ is a somewhat large library. There are a lot of files, each separated by specific CAN or ISOBUS functionality. Check out the other tutorials, or the VT example below for what you might want to include. -Specifically, this configuration was created for the ESP-WROOM-32 (denky32), so if you have a different board, make sure to edit this appropriately. + At an absolute minimum, you'll need these files to send or receive anything. -9. Include the header files you might need in :code:`main.cpp`. + .. code-block:: c++ -AgIsoStack++ is a somewhat large library. There are a lot of files, each separated by specific CAN or ISOBUS functionality. Check out the other tutorials, or the VT example below for what you might want to include. + #include "isobus/hardware_integration/twai_plugin.hpp" + #include "isobus/hardware_integration/can_hardware_interface.hpp" + #include "isobus/isobus/can_network_manager.hpp" + #include "isobus/isobus/can_partnered_control_function.hpp" -At an absolute minimum, you'll need these files to send or receive anything. +#. Set up the ESP32's TWAI -.. code-block:: c++ + he ESP32 has what is essentially a built-in classic CAN 2.0 controller. Because of this, you can use the TWAI interface on your ESP32 instead of having to add a serial CAN controller like the MCP2515. - #include "isobus/hardware_integration/twai_plugin.hpp" - #include "isobus/hardware_integration/can_hardware_interface.hpp" - #include "isobus/isobus/can_network_manager.hpp" - #include "isobus/isobus/can_partnered_control_function.hpp" + .. note:: + + You will still require a CAN transceiver `like this one `_ to convert the low voltage outputs of the TWAI controller to actual CAN signaling voltages. + You cannot hook a CAN bus directly to the TWAI pins. -10. Set up the ESP32's TWAI + Adding the following code will configure the TWAI. -The ESP32 has what is essentially a built-in classic CAN 2.0 controller. Because of this, you can use the TWAI interface on your ESP32 instead of having to add a serial CAN controller like the MCP2515. + .. code-block:: c++ + + extern "C" void app_main() + { + twai_general_config_t twaiConfig = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_21, GPIO_NUM_22, TWAI_MODE_NORMAL); + twai_timing_config_t twaiTiming = TWAI_TIMING_CONFIG_250KBITS(); + twai_filter_config_t twaiFilter = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + std::shared_ptr canDriver = std::make_shared(&twaiConfig, &twaiTiming, &twaiFilter); + } -.. note:: + This code sets GPIO 21 and GPIO 22 to the be the TWAI transmit and receive pins respectively, and configures the default ISO11783/J1939 baud rate of 250k bits. + It also ensures that all messages on the bus are passed through to the stack, and none are filtered out by the hardware. + Lastly, it creates an instance of the AgIsoStack TWAI driver class, which will manage the TWAI for you. - You will still require a CAN transceiver `like this one `_ to convert the low voltage outputs of the TWAI controller to actual CAN signaling voltages. - You cannot hook a CAN bus directly to the TWAI pins. + You do not need to choose these same GPIO pins, but these are known to work well. -Adding the following code will configure the TWAI. +#. Set up our device's NAME, and start the CAN stack. -.. code-block:: c++ - - extern "C" void app_main() - { - twai_general_config_t twaiConfig = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_21, GPIO_NUM_22, TWAI_MODE_NORMAL); - twai_timing_config_t twaiTiming = TWAI_TIMING_CONFIG_250KBITS(); - twai_filter_config_t twaiFilter = TWAI_FILTER_CONFIG_ACCEPT_ALL(); - std::shared_ptr canDriver = std::make_shared(&twaiConfig, &twaiTiming, &twaiFilter); - } + This is boilerplate code that can be found in nearly every AgIsoStack project that sets up your device and starts the CAN stack, with slight modifications for ESP32. -This code sets GPIO 21 and GPIO 22 to the be the TWAI transmit and receive pins respectively, and configures the default ISO11783/J1939 baud rate of 250k bits. -It also ensures that all messages on the bus are passed through to the stack, and none are filtered out by the hardware. -Lastly, it creates an instance of the AgIsoStack TWAI driver class, which will manage the TWAI for you. + .. code-block:: c++ -You do not need to choose these same GPIO pins, but these are known to work well. + #include "esp_log.h" + #include "freertos/task.h" -11. Set up our device's NAME, and start the CAN stack. + extern "C" void app_main() + { + twai_general_config_t twaiConfig = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_21, GPIO_NUM_22, TWAI_MODE_NORMAL); + twai_timing_config_t twaiTiming = TWAI_TIMING_CONFIG_250KBITS(); + twai_filter_config_t twaiFilter = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + std::shared_ptr canDriver = std::make_shared(&twaiConfig, &twaiTiming, &twaiFilter); -This is boilerplate code that can be found in nearly every AgIsoStack project that sets up your device and starts the CAN stack, with slight modifications for ESP32. + isobus::CANHardwareInterface::set_number_of_can_channels(1); + isobus::CANHardwareInterface::assign_can_channel_frame_handler(0, canDriver); + isobus::CANHardwareInterface::set_periodic_update_interval(10); // Default is 4ms, but we need to adjust this for default ESP32 tick rate of 100Hz -.. code-block:: c++ + if (!isobus::CANHardwareInterface::start() || !canDriver->get_is_valid()) + { + ESP_LOGE("AgIsoStack", "Failed to start hardware interface, the CAN driver might be invalid"); + } - extern "C" void app_main() - { - twai_general_config_t twaiConfig = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_21, GPIO_NUM_22, TWAI_MODE_NORMAL); - twai_timing_config_t twaiTiming = TWAI_TIMING_CONFIG_250KBITS(); - twai_filter_config_t twaiFilter = TWAI_FILTER_CONFIG_ACCEPT_ALL(); - std::shared_ptr canDriver = std::make_shared(&twaiConfig, &twaiTiming, &twaiFilter); + isobus::NAME TestDeviceNAME(0); - isobus::CANHardwareInterface::set_number_of_can_channels(1); - isobus::CANHardwareInterface::assign_can_channel_frame_handler(0, canDriver); - isobus::CANHardwareInterface::set_periodic_update_interval(1); + //! Make sure you change these for your device!!!! + TestDeviceNAME.set_arbitrary_address_capable(true); + TestDeviceNAME.set_industry_group(1); + TestDeviceNAME.set_device_class(0); + 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(1407); + auto TestInternalECU = isobus::InternalControlFunction::create(TestDeviceNAME, 0x81, 0); - if (!isobus::CANHardwareInterface::start() || !canDriver->get_is_valid()) - { - ESP_LOGE("AgIsoStack", "Failed to start hardware interface, the CAN driver might be invalid"); - } + while (true) + { + // CAN stack runs in other threads. Do nothing forever. + vTaskDelay(10); + } - isobus::NAME TestDeviceNAME(0); + isobus::CANHardwareInterface::stop(); + } - //! Make sure you change these for your device!!!! - TestDeviceNAME.set_arbitrary_address_capable(true); - TestDeviceNAME.set_industry_group(1); - TestDeviceNAME.set_device_class(0); - 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(1407); - auto TestInternalECU = isobus::InternalControlFunction::create(TestDeviceNAME, 0x81, 0); + This is the absolute minimum for the stack to address claim for you, and for it to be ready to accept your messages. - while (true) - { - // CAN stack runs in other threads. Do nothing forever. - vTaskDelay(10); - } +#. Set up your ESP32's OS and PThread options - isobus::CANHardwareInterface::stop(); - } + As mentioned before, AgIsoStack is a fairly large multi-threaded library, so we need to adjust some platform settings to allow the library to run smoothly. Not doing this will probably cause your device to repeatedly crash at runtime! + More specifically, we need to adjust the default stack size, and the amount of stack allocated to the pthreads task. -This is the absolute minimum for the stack to address claim for you, and for it to be ready to accept your messages. + In PlatformIO, run `menuconfig` by either running :code:`pio run -t menuconfig` or by selecting the option from the PlatformIO extension. -11. Set up your ESP32's OS and PThread options + .. image:: ../../images/menuconfig.png + :width: 400 + :alt: Running menuconfig -As mentioned before, AgIsoStack is a fairly large multi-threaded library, so we need to adjust some platform settings to allow the library to run smoothly. Not doing this will probably cause your device to repeatedly crash at runtime! -More specifically, we need to adjust the default stack size, and the amount of stack allocated to the pthreads task. + .. warning:: -In PlatformIO, run `menuconfig` by either running :code:`pio run -t menuconfig` or by selecting the option from the PlatformIO extension. - -.. image:: ../../images/menuconfig.png - :width: 400 - :alt: Running menuconfig - -.. warning:: - - If you are experiencing an error running :code:`menuconfig`, you may need to comment out the following line(s) in your :code:`CMakeLists.txt` file inside the :code:`src/` folder if present. Make sure you un-comment the line once you are done with :code:`menuconfig`. - - - :code:`target_add_binary_data(${COMPONENT_TARGET} "object_pool/object_pool.iop" BINARY)` + If you are experiencing an error running :code:`menuconfig`, you may need to comment out the following line(s) in your :code:`CMakeLists.txt` file inside the :code:`src/` folder if present. Make sure you un-comment the line once you are done with the menuconfig. + + :code:`target_add_binary_data(${COMPONENT_TARGET} "object_pool/object_pool.iop" BINARY)` -Once menuconfig is running, navigate to :code:`Component config -> PThreads` and change the settings to match the following: + Once menuconfig is running, navigate to :code:`Component config -> PThreads` and change the stack size of pthreads to 8192 bytes, this should prevent the stack from running out of memory. + If you still experience crashes, you may need to increase this value further. -.. image:: ../../images/pthreads_settings.png - :width: 500 - :alt: Running menuconfig + .. image:: ../../images/pthreads_settings.png + :width: 500 + :alt: Running menuconfig -Last, but not least, we have to increase the FreeRTOS tick rate **or** decrease the tick rate of the stack for them both to match. For increasing tick rate of FreeRTOS, navigate to :code:`Component config -> FreeRTOS -> Kernel` and configure the :code:`configTICK_RATE_HZ`. A good value is to match the update period of the stack, which by default is 4ms. So a good value for :code:`configTICK_RATE_HZ` is 250Hz. + Last, but not least, we have to increase the FreeRTOS tick rate **or** decrease the tick rate of the stack for them both to match. + For increasing tick rate of FreeRTOS, navigate to :code:`Component config -> FreeRTOS -> Kernel` and configure the :code:`configTICK_RATE_HZ`. We suggest to match the update period of the stack, which by default is 4ms. So a good value for :code:`configTICK_RATE_HZ` is 250Hz. -.. image:: ../../images/tick_rate.png - :width: 500 - :alt: Running menuconfig + .. image:: ../../images/tick_rate.png + :width: 500 + :alt: Running menuconfig -For decreasing the update rate of the stack, set the update period to your desired value in your init/main function: :code:`isobus::CANHardwareInterface::set_can_driver_update_period(10)` for 10ms update period. This matches the default FreeRTOS tick rate of 100Hz. + For decreasing the update rate of the stack, set the update period to your desired value in your init/main function: :code:`isobus::CANHardwareInterface::set_can_driver_update_period(10)` for 10ms update period. This matches the default FreeRTOS tick rate of 100Hz. -12. Add your application code and build your project! Using the PlatformIO extension, click "build" to compile your project. +#. Close the menuconfig by pressing :code:`Q` on your keyboard, followed by :code:`Y` to save your changes. +#. Add your application code and build your project using the PlatformIO extension. That's it! You should now have a working AgIsoStack project on your ESP32. If you made it this far, but you're not sure how to use the library to make a functional application yet, or you are having trouble compiling, check out the VT Client example in the next section, and be sure to read the other tutorials. @@ -263,11 +246,11 @@ To build and run a minimal, but interactive project that will load an ISOBUS obj .. note:: - This example sets up the TWAI interface to run on GPIO 21 and 22. It does not require any external CAN controller, but does require a CAN transceiver `like this one `_. + This example sets up the TWAI interface to run on GPIO 21 and 22. It does not require any external CAN controller, but does require a CAN transceiver `like this one `_. .. note:: - The example's :code:`platformio.ini` file is configured for a WROOM/Denky-32 board, so you may need to edit it to match your board type. + The example's :code:`platformio.ini` file is configured for a WROOM/Denky-32 board, so you may need to edit it to match your board type. The Wiring diff --git a/sphinx/source/Tutorials/Virtual Terminal Basics.rst b/sphinx/source/Tutorials/Virtual Terminal Basics.rst index 87d46d3e4..c77c473d5 100644 --- a/sphinx/source/Tutorials/Virtual Terminal Basics.rst +++ b/sphinx/source/Tutorials/Virtual Terminal Basics.rst @@ -20,7 +20,7 @@ The Virtual Terminal Client The first step in communicating with a VT is creating an object called :code:`VirtualTerminalClient`. This object will act as your interface for all VT communication. -The client requires two things to instantiate, a :code:`PartneredControlFunction` and a :code:`InternalControlFunction`. +The client requires two things to instantiate: a :code:`PartneredControlFunction` and an :code:`InternalControlFunction`. This is so that it can send messages on your behalf needed to maintain the connection and simplify the API over sending raw CAN messages to the VT. Let's start our program fresh, with a folder containing only the CAN stack. @@ -37,15 +37,12 @@ Create the file `main.cpp` as shown below inside that folder with the requisite #include "isobus/hardware_integration/available_can_drivers.hpp" #include "isobus/hardware_integration/can_hardware_interface.hpp" - #include "isobus/isobus/can_general_parameter_group_numbers.hpp" #include "isobus/isobus/can_network_manager.hpp" #include "isobus/isobus/can_partnered_control_function.hpp" - #include "isobus/isobus/can_stack_logger.hpp" #include #include #include - #include //! It is discouraged to use global variables, but it is done here for simplicity. static std::atomic_bool running = { true }; @@ -55,11 +52,6 @@ Create the file `main.cpp` as shown below inside that folder with the requisite running = false; } - void update_CAN_network(void *) - { - isobus::CANNetworkManager::CANNetwork.update(); - } - int main() { std::signal(SIGINT, signal_handler); @@ -129,20 +121,23 @@ It's the same boilerplate we've done before, but note the following key things: * Our partner has been defined to be any device on the bus with a function code that identifies it as a VT (see `isobus::NAME::NAMEParameters::FunctionCode `_ if you want to see some other function code that exist.) -With those notes in mind, let's create our VT client: +With those notes in mind, let's create our VT client. We also introduce a helper class here to make updating the VT easier, but we'll go over that in a bit. .. code-block:: c++ #include "isobus/isobus/isobus_virtual_terminal_client.hpp" + #include "isobus/isobus/isobus_virtual_terminal_client_update_helper.hpp" //! It is discouraged to use global variables, but we have done it here for simplicity. static std::shared_ptr TestVirtualTerminalClient = nullptr; + static std::shared_ptr virtualTerminalUpdateHelper = nullptr; int main() { ... TestVirtualTerminalClient = std::make_shared(TestPartnerVT, TestInternalECU); + virtualTerminalUpdateHelper = std::make_shared(virtualTerminalClient); ... } @@ -152,8 +147,9 @@ Now, we've got our client created, and we need to configure it. More specificall Object Pools ------------- -An ISOBUS object pool is a serialized blob of objects defined in ISO 11783-6. Basically it's the data the comprises the GUIs you see on a VT when you connect an ISOBUS ECU. -In other words, it's a way for a "headless" device to explain to a VT how to draw it's UI in a very consistent way that will be portable across machines and platforms. +Imagine an ISOBUS object pool as a container filled with different visual components (objects). Each object has a specific role, defined by the ISO 11783-6 standard. +Combining these objects creates the user interface you see on a VT. +In more technical terms, it's a way for a "headless" device to explain to a VT how to draw its UI in a very consistent way that will be portable across machines and platforms. This data is essentially a long list of objects that represent things to a VT, such as: @@ -213,7 +209,7 @@ Now, let's add some code to our example to read in this IOP file, and give it to } -Note how :code:`testPool` is not static here. It is required that whatever pointer you pass into the VT client via :code:`set_object_pool` MUST remain valid (IE, not deleted or out of scope) during the object pool upload or your application may crash. +Note how :code:`testPool` is not static here. It is required that whatever pointer you pass into the VT client via :code:`set_object_pool` MUST remain valid (i.e., not deleted or out of scope) during the object pool upload, or your application may crash. Furthermore, a hash is generated for the object pool. This is a unique string that represents the object pool. It is used to tell the VT if the object pool has changed since the last time it was uploaded. If it has, the VT will request the new object pool from the ECU. If it hasn't, the VT will assume the object pool is the same as the last time it was uploaded and will not request it again but instead load it from their cache. @@ -222,7 +218,7 @@ This can be used to read from some external device if needed in segments or just You can also have the CAN stack automatically scale your object pool to match the dimensions of whatever VT it ends up loading to. This can be helpful if you designed your object pool for a certain data mask size, but need the pool to load on VTs with different resolutions or VTs that support different fonts than you designed your pool with. -To do this, just tell the client what sizes you used when creating your object pool with the :code:`set_object_pool_scaling` function. The documentation for that function can be found `in our api docs `_. +To do this, just tell the client what sizes you used when creating your object pool with the :code:`set_object_pool_scaling` function. The documentation for that function can be found `in our API docs `_. .. note:: @@ -250,37 +246,71 @@ To do this, let's create a callback that accepts :code:`VirtualTerminalClient::V .. code-block:: c++ // This callback will provide us with event driven notifications of button presses from the stack - void handleVTKeyEvents(const isobus::VirtualTerminalClient::VTKeyEvent &event) + void handle_button_event(const isobus::VirtualTerminalClient::VTKeyEvent &event) { - static std::uint32_t exampleNumberOutput = 214748364; // In the object pool the output number has an offset of -214748364 so we use this to represent 0. - switch (event.keyEvent) { case isobus::VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased: + case isobus::VirtualTerminalClient::KeyActivationCode::ButtonStillHeld: { switch (event.objectID) { case Plus_Button: { - TestVirtualTerminalClient->send_change_numeric_value(ButtonExampleNumber_VarNum, ++exampleNumberOutput); + virtualTerminalUpdateHelper->increase_numeric_value(ButtonExampleNumber_VarNum); } break; case Minus_Button: { - TestVirtualTerminalClient->send_change_numeric_value(ButtonExampleNumber_VarNum, --exampleNumberOutput); + virtualTerminalUpdateHelper->decrease_numeric_value(ButtonExampleNumber_VarNum); } break; + default: + break; + } + } + break; + + default: + break; + } + } + +We'll use this function as our callback. This function will first look at the `key event `_ the VT is reporting to decide what to do. +In this example, we're doing actions based on the :code:`ButtonUnlatchedOrReleased` and :code:`ButtonStillHeld` events. Meaning we're doing something when the button is released, and we're doing something when the button is held down. +The :code:`ButtonStillHeld` event is useful for things like incrementing a value when a button is held down, or for scrolling through a list when a button is held down, as it will be called every so often while the button is held down. +Next, we're looking at the :code:`objectID` to decide what to do. In this example, we're incrementing and decrementing the counter on the screen. + +We'll also want to add a listener for softkey events, as they are a common way to interact with the VT. Let's add a callback for that too. + +.. code-block:: c++ + + // This callback will provide us with event driven notifications of softkey presses from the stack + void handle_softkey_event(const isobus::VirtualTerminalClient::VTKeyEvent &event) + { + if (event.keyNumber == 0) + { + // We have the alarm ACK code, so if we have an active alarm, acknowledge it by going back to the main runscreen + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); + } + + switch (event.keyEvent) + { + case isobus::VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased: + { + switch (event.objectID) + { case alarm_SoftKey: { - TestVirtualTerminalClient->send_change_active_mask(example_WorkingSet, example_AlarmMask); + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, example_AlarmMask); } break; case acknowledgeAlarm_SoftKey: { - TestVirtualTerminalClient->send_change_active_mask(example_WorkingSet, mainRunscreen_DataMask); + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); } break; @@ -295,17 +325,8 @@ To do this, let's create a callback that accepts :code:`VirtualTerminalClient::V } } -We'll use this function as our callback. This function will first look at the `key event `_ the VT is reporting to decide what to do. In this example, we're doing all actions based on when a button or softkey is `released`. -Next, we switch on the object ID of the button, and from there we can decide what to do about the event. - -As an example, if someone presses the softkey :code:`alarm_SoftKey`, this function will be called with the :code:`KeyActivationCode::ButtonPressedOrLatched` event, which we will ignore. -Then, when the user releases the softkey, this function will be called with the :code:`KeyActivationCode::ButtonUnlatchedOrReleased`. If the user hold down the button, the function will be called many times with the :code:`KeyActivationCode::ButtonStillHeld` event. -The :code:`objectID` tells us which button or key was pressed, so putting it all together, when we get the :code:`KeyActivationCode::ButtonUnlatchedOrReleased` event with the object ID of :code:`alarm_SoftKey`, we will take some action. -In this example, the action we're taking is to change the active mask from the main runscreen data mask :code:`mainRunscreen_DataMask` to an alarm mask called :code:`example_AlarmMask`. This will cause a pop-up on the VT (and possibly some beeping if your VT has a speaker). - -Note, when we added handling for the :code:`Minus_Button` and :code:`Plus_Button` in the object pool, we are incrementing and decrementing a variable in the program so that we can keep track of the displayed value on the screen. -This variable starts out with a value of 214748364 because the output number in the object pool has an offset applied to it of -214748364. This essentially means that if we send the VT a value of 214748364, it will be shown as 0. If we subtract 1, it will be shown as -1, or if we add 1 it will be shown as 1. -This is how a VT handles negative numbers. +Here we're doing something similar to the button event callback, but we're also checking the :code:`keyNumber` to see if it's 0. That is the special softkey event that is sent when the user presses a proprietary button to acknowledge an alarm on the VT. +Furthermore, the actions we're taking is to change the active masks, more specifically, switching between the main runscreen data mask :code:`mainRunscreen_DataMask` and an alarm mask called :code:`example_AlarmMask`. This will cause a pop-up on the VT (and possibly some beeping if your VT has a speaker). Other Events ^^^^^^^^^^^^^ @@ -323,42 +344,58 @@ In this example, we're only using the button and softkey events, but be sure to With all those different events, you can get all kinds of context from the VT about what the user is doing. -Registering our Callbacks -^^^^^^^^^^^^^^^^^^^^^^^^^^ +Configuring the VT client +^^^^^^^^^^^^^^^^^^^^^^^^^ Now that we have our callback, we have to tell the VT client about it so that it knows to call it when appropriate. .. code-block:: c++ - auto softKeyListener = TestVirtualTerminalClient->add_vt_soft_key_event_listener(handleVTKeyEvents); - auto buttonListener = TestVirtualTerminalClient->add_vt_button_event_listener(handleVTKeyEvents); + virtualTerminalClient->get_vt_soft_key_event_dispatcher().add_listener(handle_softkey_event); + virtualTerminalClient->get_vt_button_event_dispatcher().add_listener(handle_button_event); + +This is how you add a listener to the VT client. You can add as many listeners as you want, and they will all be called when the appropriate event occurs. -You could in theory have separate callbacks for softkeys and buttons, but in this example we're just re-using the same callback for both. +Furthermore, we need to initialize the VT client. This is done by calling the :code:`initialize` function on the VT client. This will start the process of uploading the object pool to the VT, and will also start the process of the VT sending us the current active mask and other information about the VT. -Note, the :code:`add_vt_soft_key_event_listener` and :code:`add_vt_button_event_listener` functions return a shared pointer to the listener. This is so that the listener doesn't get destroyed until you're done with it. If you don't store the listener in a variable, it will be destroyed as soon as the function returns, and your callback will never be called. So make sure to keep it alive as long as you want to receive events. +And lastly, we need to configure our VT update helper. It's not strictly necessary to use it, but it makes it easier to update the VT, and it also provides some nice features like tracking numeric values and soft key masks. +We need to tell it which objects to track, and then call the :code:`initialize` function on it. + +.. code-block:: c++ + + virtualTerminalUpdateHelper->add_tracked_numeric_value(ButtonExampleNumber_VarNum, 214748364); // In the object pool the output number has an offset of -214748364 so we use this to represent 0. + virtualTerminalUpdateHelper->initialize(); + +.. note:: + when we added handling for the :code:`Minus_Button` and :code:`Plus_Button` in the object pool, we are incrementing and decrementing a variable in the program so that we can keep track of the displayed value on the screen. + This variable starts out with a value of 214748364 because the output number in the object pool has an offset applied to it of -214748364. This essentially means that if we send the VT a value of 214748364, it will be shown as 0. If we subtract 1, it will be shown as -1, or if we add 1 it will be shown as 1. + This is how a VT handles negative numbers. Other Actions ^^^^^^^^^^^^^^ -Of course, you have the ability to do a lot more than just react to events. The VT client exposes many functions that you can call to make the VT do things with your object pool. We've already used a few of them in our callback, such as: +Of course, you have the ability to do a lot more than just react to events. The VT client exposes many functions that you can call to make the VT directly do things with your object pool. -* Changing the active mask -* Changing a numeric value - -Check out the `API documentation `_ (or the header file isobus_virtual_terminal_client.hpp) for the full list of functionality available, but the most commonly used ones are listed below along with the name of the function to use on the API. +Check out the `API documentation `_ (or the header file :code:`isobus_virtual_terminal_client.hpp`) for the full list of functionality available, but the most commonly used ones are listed below along with the name of the function to use on the API. * Changing the active mask - * :code:`send_change_active_mask` + * :code:`virtualTerminalClient->send_change_active_mask` + * :code:`virtualTerminalUpdateHelper->set_active_data_or_alarm_mask` * Changing a numeric value - * :code:`send_change_numeric_value` + * :code:`virtualTerminalClient->send_change_numeric_value` + * :code:`virtualTerminalUpdateHelper->set_numeric_value` + * :code:`virtualTerminalUpdateHelper->increase_numeric_value` + * :code:`virtualTerminalUpdateHelper->decrease_numeric_value` * Changing a string value - * :code:`send_change_string_value` + * :code:`virtualTerminalClient->send_change_string_value` * Changing a soft key mask - * :code:`send_change_softkey_mask` + * :code:`virtualTerminalClient->send_change_softkey_mask` + * :code:`virtualTerminalUpdateHelper->set_active_soft_key_mask` * Changing a list item - * :code:`send_change_list_item` + * :code:`virtualTerminalClient->send_change_list_item` * Changing an attribute, such as hiding a container - * :code:`send_change_attribute` + * :code:`virtualTerminalClient->send_change_attribute` + * :code:`virtualTerminalUpdateHelper->set_attribute` Final Result -------------- @@ -369,23 +406,21 @@ Here's the final code for this example: #include "isobus/hardware_integration/available_can_drivers.hpp" #include "isobus/hardware_integration/can_hardware_interface.hpp" - #include "isobus/isobus/can_general_parameter_group_numbers.hpp" #include "isobus/isobus/can_network_manager.hpp" #include "isobus/isobus/can_partnered_control_function.hpp" - #include "isobus/isobus/can_stack_logger.hpp" #include "isobus/isobus/isobus_virtual_terminal_client.hpp" + #include "isobus/isobus/isobus_virtual_terminal_client_update_helper.hpp" #include "isobus/utility/iop_file_interface.hpp" #include "objectPoolObjects.h" #include #include - #include #include - #include //! It is discouraged to use global variables, but it is done here for simplicity. - static std::shared_ptr TestVirtualTerminalClient = nullptr; + static std::shared_ptr virtualTerminalClient = nullptr; + static std::shared_ptr virtualTerminalUpdateHelper = nullptr; static std::atomic_bool running = { true }; void signal_handler(int) @@ -393,20 +428,14 @@ Here's the final code for this example: running = false; } - void update_CAN_network(void *) + // This callback will provide us with event driven notifications of softkey presses from the stack + void handle_softkey_event(const isobus::VirtualTerminalClient::VTKeyEvent &event) { - isobus::CANNetworkManager::CANNetwork.update(); - } - - void raw_can_glue(isobus::CANMessageFrame &rawFrame, void *parentPointer) - { - isobus::CANNetworkManager::CANNetwork.can_lib_process_rx_message(rawFrame, parentPointer); - } - - // This callback will provide us with event driven notifications of button presses from the stack - void handleVTKeyEvents(const isobus::VirtualTerminalClient::VTKeyEvent &event) - { - static std::uint32_t exampleNumberOutput = 214748364; // In the object pool the output number has an offset of -214748364 so we use this to represent 0. + if (event.keyNumber == 0) + { + // We have the alarm ACK code, so if we have an active alarm, acknowledge it by going back to the main runscreen + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); + } switch (event.keyEvent) { @@ -414,27 +443,48 @@ Here's the final code for this example: { switch (event.objectID) { - case Plus_Button: + case alarm_SoftKey: { - TestVirtualTerminalClient->send_change_numeric_value(ButtonExampleNumber_VarNum, ++exampleNumberOutput); + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, example_AlarmMask); } break; - case Minus_Button: + case acknowledgeAlarm_SoftKey: { - TestVirtualTerminalClient->send_change_numeric_value(ButtonExampleNumber_VarNum, --exampleNumberOutput); + virtualTerminalUpdateHelper->set_active_data_or_alarm_mask(example_WorkingSet, mainRunscreen_DataMask); } break; - case alarm_SoftKey: + default: + break; + } + } + break; + + default: + break; + } + } + + // This callback will provide us with event driven notifications of button presses from the stack + void handle_button_event(const isobus::VirtualTerminalClient::VTKeyEvent &event) + { + switch (event.keyEvent) + { + case isobus::VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased: + case isobus::VirtualTerminalClient::KeyActivationCode::ButtonStillHeld: + { + switch (event.objectID) + { + case Plus_Button: { - TestVirtualTerminalClient->send_change_active_mask(example_WorkingSet, example_AlarmMask); + virtualTerminalUpdateHelper->increase_numeric_value(ButtonExampleNumber_VarNum); } break; - case acknowledgeAlarm_SoftKey: + case Minus_Button: { - TestVirtualTerminalClient->send_change_active_mask(example_WorkingSet, mainRunscreen_DataMask); + virtualTerminalUpdateHelper->decrease_numeric_value(ButtonExampleNumber_VarNum); } break; @@ -482,9 +532,6 @@ Here's the final code for this example: return -2; } - isobus::CANHardwareInterface::add_can_lib_update_callback(update_CAN_network, nullptr); - isobus::CANHardwareInterface::add_raw_can_message_rx_callback(raw_can_glue, nullptr); - std::this_thread::sleep_for(std::chrono::milliseconds(250)); isobus::NAME TestDeviceNAME(0); @@ -517,11 +564,15 @@ Here's the final code for this example: auto TestInternalECU = isobus::InternalControlFunction::create(TestDeviceNAME, 0x1C, 0); auto TestPartnerVT = isobus::PartneredControlFunction::create(0, vtNameFilters); - TestVirtualTerminalClient = std::make_shared(TestPartnerVT, TestInternalECU); - TestVirtualTerminalClient->set_object_pool(0, testPool.data(), testPool.size(), objectPoolHash); - auto softKeyListener = TestVirtualTerminalClient->add_vt_soft_key_event_listener(handleVTKeyEvents); - auto buttonListener = TestVirtualTerminalClient->add_vt_button_event_listener(handleVTKeyEvents); - TestVirtualTerminalClient->initialize(true); + virtualTerminalClient = std::make_shared(TestPartnerVT, TestInternalECU); + virtualTerminalClient->set_object_pool(0, testPool.data(), testPool.size(), objectPoolHash); + virtualTerminalClient->get_vt_soft_key_event_dispatcher().add_listener(handle_softkey_event); + virtualTerminalClient->get_vt_button_event_dispatcher().add_listener(handle_button_event); + virtualTerminalClient->initialize(true); + + virtualTerminalUpdateHelper = std::make_shared(virtualTerminalClient); + virtualTerminalUpdateHelper->add_tracked_numeric_value(ButtonExampleNumber_VarNum, 214748364); // In the object pool the output number has an offset of -214748364 so we use this to represent 0. + virtualTerminalUpdateHelper->initialize(); while (running) { @@ -529,7 +580,7 @@ Here's the final code for this example: std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } - TestVirtualTerminalClient->terminate(); + virtualTerminalClient->terminate(); isobus::CANHardwareInterface::stop(); return 0; } @@ -542,7 +593,7 @@ The CMake for this program is a bit more complex than the other examples. We'll start off like we did in "ISOBUS Hello World". -.. code-block:: text +.. code-block:: cmake cmake_minimum_required(VERSION 3.16) @@ -560,22 +611,22 @@ We'll start off like we did in "ISOBUS Hello World". add_executable(vt_example main.cpp) -Looking "ISOBUS Hello World" we had this next: +Looking at "ISOBUS Hello World", we had this next: -.. code-block:: c++ +.. code-block:: cmake target_link_libraries(isobus_hello_world PRIVATE isobus::Isobus isobus::HardwareIntegration Threads::Threads) But like we mentioned earlier, we're now using a function (the IOP file reader) in the isobus utility library called "Utility", so we need to link that too: -.. code-block:: c++ +.. code-block:: cmake target_link_libraries(isobus_hello_world PRIVATE isobus::Isobus isobus::HardwareIntegration isobus::Utility Threads::Threads) We also want to move our IOP file to be in the same folder as the executable after it's built, so that it can locate it. We can do that with this little handy bit of CMake: -.. code-block:: text +.. code-block:: cmake add_custom_command( TARGET vt_example @@ -585,7 +636,7 @@ We can do that with this little handy bit of CMake: Now you should be able to build and run the program! -.. code-block:: text +.. code-block:: bash cmake -S . -B build cmake --build build