Skip to content

Commit

Permalink
[Tutorials]: Add TC client documentation to tutorials
Browse files Browse the repository at this point in the history
  • Loading branch information
ad3154 committed May 19, 2024
1 parent aad9f0d commit b33937c
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//================================================================================================
/// @file can_transport_protocol_base.cpp
/// @file can_transport_protocol_base.hpp
///
/// @brief Abstract base class for CAN transport protocols.
/// @author Daan Steenbergen
Expand Down
3 changes: 2 additions & 1 deletion sphinx/source/Tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ Tutorials
Tutorials/PGN Requests
Tutorials/Debug Logging
Tutorials/ISOBUS Shortcut Button
Tutorials/ESP32 PlatformIO
Tutorials/Task Controller Basics
Tutorials/Task Controller Client
Tutorials/ESP32 PlatformIO
9 changes: 8 additions & 1 deletion sphinx/source/Tutorials/Task Controller Basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The DDOP is uploaded to the TC during the connection process of the implement to
In other words, the Device Descriptor Object Pool (DDOP) is a collection of objects that describe the capabilities of the implement, such as the number of sections, the width of each section, and the type of product being applied.
It is the authoritative source of implement geometry on the machine.

In AgIsoStack, if you use our TC client, you will have to create a DDOP that tells the TC abour your implement.
In AgIsoStack, if you use our TC client, you will have to create a DDOP that tells the TC about your implement.
Likewise, if you use the AgIsoStack TC Server, you will receive DDOPs from client implements, and you'll have to know what they mean.

Before we jump into some examples of well constructed DDOPs, let's go over some terminology first. Once you have some understanding of the things you can put in a DDOP, we'll show you how you can use AgIsoStack to easily create one.
Expand Down Expand Up @@ -179,3 +179,10 @@ Here's an example of what a DDOP might look like for a more complex implement, l
The more complex an implement is, the more objects will be in the DDOP to accurately provide the TC with the information it needs to control and monitor the implement.

For DDOPs this complex, a good resource to refer to is `our seeder example <https://github.com/Open-Agriculture/AgIsoStack-plus-plus/blob/main/examples/seeder_example/section_control_implement_sim.cpp#L98>`_, which contains the code to create a more complex DDOP for a seeder.

AgIsoDDOPGenerator
^^^^^^^^^^^^^^^^^^

Open-Agriculture has created a tool called `AgIsoDDOPGenerator <https://github.com/Open-Agriculture/AgIsoDDOPGenerator>`_, which can help create, view, and edit binary DDOPs for your implements. Using this tool to view the heierarchies of DDOP objects can be very helpful in understanding how they are structured.

DDOPs created with AgIsoDDOPGenerator can be used with AgIsoStack, and can be uploaded to a TC using the AgIsoStack TC client.
118 changes: 118 additions & 0 deletions sphinx/source/Tutorials/Task Controller Client.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
.. _TaskControllerClient:

Task Controller Client
=======================

.. toctree::
:hidden:
:glob:

.. contents:: Contents
:depth: 2
:local:

This tutorial will walk you through basic use of the Task Controller client in AgIsoStack. The Task Controller client is used to connect an implement to a Task Controller.

It's suggested that you read the Task Controller Basics tutorial before starting this one, as it will give you an understanding of what a Task Controller is, and what a DDOP is.

Creating the Task Controller Client
------------------------------------

In order to communicate with a task controller, first you should identify the capabilities of your implement. How many sections can it control? How many simultaneous VRA rates can it handle? Can it function correctly without TC provided position based (GNSS) control?

Next, you'll have to create a DDOP that describes your implement to the TC. This DDOP will be uploaded to the TC when AgIsoStack connects to it, and will tell the TC what your implement is capable of doing.

Check out `our seeder example <https://github.com/Open-Agriculture/AgIsoStack-plus-plus/blob/main/examples/seeder_example/section_control_implement_sim.cpp#L98>`_ to see how a DDOP can be created, or review the :doc:`task controller basics tutorial <./Task Controller Basics>`.

Once you know the answer to these questions and you have a DDOP, you can create a :code:`TaskControllerClient`, and configure it to match your implement's capabilities.

Here's an example of how you can create a :code:`TaskControllerClient` in AgIsoStack:

.. code-block:: c++

#include "isobus/isobus/isobus_task_controller_client.hpp"

// Create a TaskControllerClient
// The parameters are, in order:
// The control function for the TC
// The control function used to send messages to the TC
// The control function for a virtual terminal you're connected to with the VT client (optional - helps synchronize language and units in some cases)
isobus::TaskControllerClient OurTaskControllerClient(PartnerTC, InternalECU, nullptr);

// Configure the TaskControllerClient with our DDOP.
// The parameters are, in order:
// Includes support for 1 boom, 10 sections, 1 rate
// Supports documentation, not supporting TC-GEO without position based control
// Supports TC-GEO with position based control, not supporting peer control
// Supports TC-SC section control
OurTaskControllerClient.configure(myDDOP, 1, 10, 1, true, false, true, false, true);

Now that we have our TC client, we need to define some functions to handle what we do when the TC requests information from us, and when it commands us to do something.

First, we define a function to handle requests:

.. code-block:: c++

bool request_value_command_callback(std::uint16_t elementNumber,
std::uint16_t DDI,
std::int32_t &value,
void *parentPointer)
{
}

This function will be called by the TC client when the TC requests a value from the implement. You should fill in the function to return the requested value. Generally this means that you'll want to `switch` on the DDI and/or element number, and return the appropriate value based on what your implement is doing.

You'll want to return true if the TC provided a valid DDI and element number, and you gave it a value in return.
Returning false will cause the TC client to send an error message to the TC, indicating that the requested value was not available. A TC expects all DPD values in your DDOP to be available, so you should basically never return false.

See the `seeder example <https://github.com/Open-Agriculture/AgIsoStack-plus-plus/blob/main/examples/seeder_example/section_control_implement_sim.cpp#L219>`_ or the `TC client example <https://github.com/Open-Agriculture/AgIsoStack-plus-plus/blob/main/examples/task_controller_client/section_control_implement_sim.cpp>`_ for an example of how this function can be implemented.

Next, we define a function to handle commands:

.. code-block:: c++

bool command_value_command_callback(std::uint16_t elementNumber,
std::uint16_t DDI,
std::int32_t processVariableValue,
void *parentPointer)
{
}

This function will be called by the TC client when the TC sends a command to the implement. You should fill in the function to handle the command. Generally this means that you'll want to `switch` on the DDI and/or element number, and set the appropriate value based on what your implement is doing.

You'll want to return true if the TC provided a valid DDI and element number, and you executed the command successfully. If you return false, the TC client will send an error message to the TC, indicating that the command was not executed. A TC expects all "settable" DPD values in your DDOP to be writable, so you should basically never return false.

You may also then need to trigger sending a value back to the TC, to confirm that the command was received and executed. For example, if the TC sends a command to turn on a section, you probably set the section to on.
Then, if your section's "Actual Condensed Work State" has an on-change trigger, you need to send the new value of that information to the TC.

To accomplish this, and in fact, to send any value to the TC, you can call the function :code:`on_value_changed_trigger` on the :code:`TaskControllerClient` object. Which will cause the interface to call your previously defined :code:`request_value_command_callback` function with the appropriate DDI and element number and send a message to the TC.

Lastly, we need to tell the TC client to start running. We do this by calling :code:`initialize`.

.. code-block:: c++

OurTaskControllerClient.initialize(true); // The "true" parameter tells the TC client to start running in a separate thread. If you pass "false", the TC client will run in the same thread as your main program and you'll have to call `update` on it periodically.

This starts the TC client running. It will now handle messages from the TC, and call your :code:`request_value_command_callback` and :code:`command_value_command_callback` functions as needed. All CAN messaging is handled for you, so you don't need to worry about manually sending any process data messages.

Once you are done with your TC client, you should call :code:`terminate` on it to stop it from running.

.. code-block:: c++

OurTaskControllerClient.terminate();

Other Useful Features
---------------------

There are many other functions on the `TaskControllerClient` object that you can use to interact with the TC. You should review the `TaskControllerClient` class in the `AgIsoStack documentation <https://delgrossoengineering.com/isobus-docs/isobus__task__controller__client_8hpp_source>`_ to see what functions are available.

Some highlights include:

- :code:`reupload_device_descriptor_object_pool` Which will re-upload the DDOP to the TC. This can be useful if you need to change the DDOP after the TC client has already started running.
- :code:`request_task_controller_identification` Which will request the TC to display its TC "number" on its screen, if applicable. This can be useful if you need to know visually what TC you're connected to.
- :code:`get_is_connected` Which will return true if the TC client is connected to a TC, and false otherwise. Useful to know if you're connected to a TC or not.
- :code:`get_is_task_active` Which will return true if the TC indicates it is currently running a task, and false otherwise. Not all TCs use this value properly, so it may not be useful in all cases.

Be sure to check out our examples for more information on how to use the `TaskControllerClient` object.

0 comments on commit b33937c

Please sign in to comment.