Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16)
project("AOG-TaskController")
set(PROJECT_VERSION_MAJOR 1)
set(PROJECT_VERSION_MINOR 2)
set(PROJECT_VERSION_MINOR 3)
set(PROJECT_VERSION_PATCH 0)
set(PROJECT_VERSION
"${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}"
Expand Down Expand Up @@ -29,7 +29,7 @@ FetchContent_MakeAvailable(Boost)
FetchContent_Declare(
isobus
GIT_REPOSITORY https://github.com/Open-Agriculture/AgIsoStack-plus-plus.git
GIT_TAG 495eba6653010449d6202165240da7623243f416
GIT_TAG 05e5fd73315d79a48c21c24f39350063a1556af6
DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
FetchContent_MakeAvailable(isobus)

Expand Down
1 change: 1 addition & 0 deletions include/app.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Application

std::shared_ptr<isobus::CANHardwarePlugin> canDriver;
std::shared_ptr<MyTCServer> tcServer;
std::shared_ptr<isobus::InternalControlFunction> tecuCF = nullptr;
std::unique_ptr<isobus::SpeedMessagesInterface> speedMessagesInterface;
std::unique_ptr<isobus::NMEA2000MessageInterface> nmea2000MessageInterface;
std::uint8_t nmea2000SequenceIdentifier = 0;
Expand Down
5 changes: 5 additions & 0 deletions include/task_controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#pragma once

#include "isobus/isobus/isobus_data_dictionary.hpp"
#include "isobus/isobus/isobus_device_descriptor_object_pool.hpp"
#include "isobus/isobus/isobus_standard_data_description_indices.hpp"
#include "isobus/isobus/isobus_task_controller_server.hpp"
Expand Down Expand Up @@ -48,6 +49,9 @@ class ClientState
void mark_measurement_commands_sent();
std::uint16_t get_element_number_for_ddi(isobus::DataDescriptionIndex ddi) const;
void set_element_number_for_ddi(isobus::DataDescriptionIndex ddi, std::uint16_t elementNumber);
// Element work state management these act like master / override for actual sections
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is missing proper capitalization at the start. It should be: "Element work state management: these act like master/override for actual sections" or split into a proper sentence structure.

Suggested change
// Element work state management these act like master / override for actual sections
// Element work state management: these act like master/override for actual sections

Copilot uses AI. Check for mistakes.
void set_element_work_state(std::uint16_t elementNumber, bool isWorking);
bool try_get_element_work_state(std::uint16_t elementNumber, bool &isWorking) const;

private:
isobus::DeviceDescriptorObjectPool pool; ///< The device descriptor object pool (DDOP) for the TC
Expand All @@ -59,6 +63,7 @@ class ClientState
std::vector<std::uint8_t> sectionActualStates; // 2 bits per section (0 = off, 1 = on, 2 = error, 3 = not installed)
bool setpointWorkState = false; ///< The overall work state desired
bool actualWorkState = false; ///< The overall work state actual
std::map<std::uint16_t, bool> elementWorkStates; ///< Work state per element (element number -> is working)
bool isSectionControlEnabled = false; ///< Stores auto vs manual mode setting
};

Expand Down
145 changes: 96 additions & 49 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,70 +41,109 @@ bool Application::initialize()
return false;
}

isobus::CANNetworkManager::CANNetwork.get_configuration().set_number_of_packets_per_cts_message(255);

isobus::NAME ourNAME(0);

//! Make sure you change these for your device!!!!
ourNAME.set_arbitrary_address_capable(true);
ourNAME.set_industry_group(2);
ourNAME.set_device_class(0);
ourNAME.set_function_code(static_cast<std::uint8_t>(isobus::NAME::Function::TaskController));
ourNAME.set_identity_number(20);
ourNAME.set_ecu_instance(0);
ourNAME.set_function_instance(0); // TC #1. If you want to change the TC number, change this.
ourNAME.set_device_class_instance(0);
ourNAME.set_manufacturer_code(1407);

auto serverCF = isobus::CANNetworkManager::CANNetwork.create_internal_control_function(ourNAME, 0, isobus::preferred_addresses::IndustryGroup2::TaskController_MappingComputer); // The preferred address for a TC is defined in ISO 11783
auto addressClaimedFuture = std::async(std::launch::async, [&serverCF]() {
while (!serverCF->get_address_valid())
std::this_thread::sleep_for(std::chrono::milliseconds(100)); });
isobus::NAME tcNAME = ourNAME;
tcNAME.set_function_code(static_cast<std::uint8_t>(isobus::NAME::Function::TaskController));

isobus::NAME tecuNAME = ourNAME;
tecuNAME.set_function_code(static_cast<std::uint8_t>(isobus::NAME::Function::TractorECU));
tecuNAME.set_arbitrary_address_capable(false); // TECU address is fixed
tecuNAME.set_ecu_instance(0);

std::cout << "[Init] Creating Task Controller control function..." << std::endl;
auto tcCF = isobus::CANNetworkManager::CANNetwork.create_internal_control_function(tcNAME, 0, isobus::preferred_addresses::IndustryGroup2::TaskController_MappingComputer); // The preferred address for a TC is defined in ISO 11783
auto tcAddressClaimedFuture = std::async(std::launch::async, [&tcCF]() {
while (!tcCF->get_address_valid())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
isobus::CANNetworkManager::CANNetwork.update();
}
});

// If this fails, probably the update thread is not started
addressClaimedFuture.wait_for(std::chrono::seconds(5));
if (!serverCF->get_address_valid())
tcAddressClaimedFuture.wait_for(std::chrono::seconds(5));
if (!tcCF->get_address_valid())
{
std::cout << "Failed to claim address for TC server. The control function might be invalid." << std::endl;
return false;
}

tcServer = std::make_shared<MyTCServer>(serverCF);
// Create TECU control function
// TODO: Should we wait between this and TC?
// TODO: If there's already a TECU on the bus we should not create ours
if (tcCF)
{ // Only create TECU if TC was created
std::cout << "[Init] Creating Tractor ECU control function..." << std::endl;
tecuCF = isobus::CANNetworkManager::CANNetwork.create_internal_control_function(tecuNAME, 0, isobus::preferred_addresses::IndustryGroup2::TractorECU);
std::cout << "[Init] Tractor ECU control function created, waiting 1.5 seconds..." << std::endl;

// Update the network manager to process TECU CF claiming
for (int i = 0; i < 15; i++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
isobus::CANNetworkManager::CANNetwork.update();
}
}

tcServer = std::make_shared<MyTCServer>(tcCF);
auto &languageInterface = tcServer->get_language_command_interface();
languageInterface.set_language_code("en"); // This is the default, but you can change it if you want
languageInterface.set_country_code("US"); // This is the default, but you can change it if you want
tcServer->initialize();
tcServer->set_task_totals_active(true); // TODO: make this dynamic based on status in AOG

// Initialize speed and distance messages
speedMessagesInterface = std::make_unique<isobus::SpeedMessagesInterface>(serverCF, true, true, true, false); //TODO: make configurable whether to send these messages
speedMessagesInterface->initialize();
nmea2000MessageInterface = std::make_unique<isobus::NMEA2000MessageInterface>(serverCF, false, false, false, false, false, false, false);
nmea2000MessageInterface->initialize();
nmea2000MessageInterface->set_enable_sending_cog_sog_cyclically(true); // TODO: make configurable whether to send these messages

speedMessagesInterface->wheelBasedSpeedTransmitData.set_implement_start_stop_operations_state(isobus::SpeedMessagesInterface::WheelBasedMachineSpeedData::ImplementStartStopOperations::NotAvailable);
speedMessagesInterface->wheelBasedSpeedTransmitData.set_key_switch_state(isobus::SpeedMessagesInterface::WheelBasedMachineSpeedData::KeySwitchState::NotAvailable);
speedMessagesInterface->wheelBasedSpeedTransmitData.set_operator_direction_reversed_state(isobus::SpeedMessagesInterface::WheelBasedMachineSpeedData::OperatorDirectionReversed::NotAvailable);
speedMessagesInterface->machineSelectedSpeedTransmitData.set_speed_source(isobus::SpeedMessagesInterface::MachineSelectedSpeedData::SpeedSource::NavigationBasedSpeed);
if (tecuCF)
{
std::cout << "[Init] Creating Speed Messages Interface on TECU..." << std::endl;
speedMessagesInterface = std::make_unique<isobus::SpeedMessagesInterface>(tecuCF, true, true, true, false); //TODO: make configurable whether to send these messages
speedMessagesInterface->initialize();
speedMessagesInterface->wheelBasedSpeedTransmitData.set_implement_start_stop_operations_state(isobus::SpeedMessagesInterface::WheelBasedMachineSpeedData::ImplementStartStopOperations::NotAvailable);
speedMessagesInterface->wheelBasedSpeedTransmitData.set_key_switch_state(isobus::SpeedMessagesInterface::WheelBasedMachineSpeedData::KeySwitchState::NotAvailable);
speedMessagesInterface->wheelBasedSpeedTransmitData.set_operator_direction_reversed_state(isobus::SpeedMessagesInterface::WheelBasedMachineSpeedData::OperatorDirectionReversed::NotAvailable);
speedMessagesInterface->machineSelectedSpeedTransmitData.set_speed_source(isobus::SpeedMessagesInterface::MachineSelectedSpeedData::SpeedSource::NavigationBasedSpeed);
std::cout << "[Init] Speed Messages Interface created and initialized." << std::endl;

std::cout << "[Init] Creating NMEA2000 Message Interface on TECU..." << std::endl;
nmea2000MessageInterface = std::make_unique<isobus::NMEA2000MessageInterface>(tecuCF, false, false, false, false, false, false, false);
nmea2000MessageInterface->initialize();
nmea2000MessageInterface->set_enable_sending_cog_sog_cyclically(true); // TODO: make configurable whether to send these messages
std::cout << "[Init] NMEA2000 Message Interface created and initialized." << std::endl;
}
else
{
std::cout << "[Warning] TECU Control Function not available, Speed/NMEA interfaces not created" << std::endl;
}

std::cout << "Task controller server started." << std::endl;

static std::uint8_t xteSid = 0;
static std::uint32_t lastXteTransmit = 0;

auto packetHandler = [this, serverCF](std::uint8_t src, std::uint8_t pgn, std::span<std::uint8_t> data) {
if (src == 0x7F && pgn == 0xFE) // 254 - Steer Data
auto packetHandler = [this, tcCF](std::uint8_t src, std::uint8_t pgn, std::span<std::uint8_t> data) {
if (src == 0x7F && pgn == 0xE5) // 229 - Section Data 1 to 64
{
// TODO: hack to get desired section states. probably want to make a new pgn later when we need more than 16 sections
std::vector<bool> sectionStates;
for (std::uint8_t i = 0; i < 8; i++)
{
sectionStates.push_back(data[6] & (1 << i));
}
for (std::uint8_t i = 0; i < 8; i++)
for (std::uint8_t scb = 0; scb < 8; scb++)
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's trailing whitespace after 'scb' on this line. Consider removing it for code cleanliness.

Suggested change
for (std::uint8_t scb = 0; scb < 8; scb++)
for (std::uint8_t scb = 0; scb < 8; scb++)

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name 'scb' is unclear and cryptic. Consider using a more descriptive name like 'byteIndex' or 'sectionByte' to improve code readability.

Copilot uses AI. Check for mistakes.
{
sectionStates.push_back(data[7] & (1 << i));
for (std::uint8_t i = 0; i < 8; i++)
{
sectionStates.push_back(data[scb] & (1 << i));
}
}

tcServer->update_section_states(sectionStates);
}
else if (src == 0x7F && pgn == 0xF1) // 241 - Section Control
Expand All @@ -122,23 +161,28 @@ bool Application::initialize()
{
std::uint16_t speed = std::abs(value);
auto direction = value < 0 ? isobus::SpeedMessagesInterface::MachineDirection::Reverse : isobus::SpeedMessagesInterface::MachineDirection::Forward;
speedMessagesInterface->groundBasedSpeedTransmitData.set_machine_direction_of_travel(direction);
speedMessagesInterface->wheelBasedSpeedTransmitData.set_machine_direction_of_travel(direction);
speedMessagesInterface->machineSelectedSpeedTransmitData.set_machine_direction_of_travel(direction);

speedMessagesInterface->groundBasedSpeedTransmitData.set_machine_speed(speed);
speedMessagesInterface->wheelBasedSpeedTransmitData.set_machine_speed(speed);
speedMessagesInterface->machineSelectedSpeedTransmitData.set_machine_speed(speed);

speedMessagesInterface->groundBasedSpeedTransmitData.set_machine_distance(0); // TODO: Implement distance
speedMessagesInterface->wheelBasedSpeedTransmitData.set_machine_distance(0); // TODO: Implement distance
speedMessagesInterface->machineSelectedSpeedTransmitData.set_machine_distance(0); // TODO: Implement distance

auto &cog_sog_message = nmea2000MessageInterface->get_cog_sog_transmit_message();
cog_sog_message.set_sequence_id(nmea2000SequenceIdentifier++);
cog_sog_message.set_speed_over_ground(speed);
cog_sog_message.set_course_over_ground(0); // TODO: Implement course
cog_sog_message.set_course_over_ground_reference(isobus::NMEA2000Messages::CourseOverGroundSpeedOverGroundRapidUpdate::CourseOverGroundReference::NotApplicableOrNull);
if (speedMessagesInterface)
{
speedMessagesInterface->groundBasedSpeedTransmitData.set_machine_direction_of_travel(direction);
speedMessagesInterface->wheelBasedSpeedTransmitData.set_machine_direction_of_travel(direction);
speedMessagesInterface->machineSelectedSpeedTransmitData.set_machine_direction_of_travel(direction);

speedMessagesInterface->groundBasedSpeedTransmitData.set_machine_speed(speed);
speedMessagesInterface->wheelBasedSpeedTransmitData.set_machine_speed(speed);
speedMessagesInterface->machineSelectedSpeedTransmitData.set_machine_speed(speed);

speedMessagesInterface->groundBasedSpeedTransmitData.set_machine_distance(0); // TODO: Implement distance
speedMessagesInterface->wheelBasedSpeedTransmitData.set_machine_distance(0); // TODO: Implement distance
speedMessagesInterface->machineSelectedSpeedTransmitData.set_machine_distance(0); // TODO: Implement distance
}
if (nmea2000MessageInterface)
{
auto &cog_sog_message = nmea2000MessageInterface->get_cog_sog_transmit_message();
cog_sog_message.set_sequence_id(nmea2000SequenceIdentifier++);
cog_sog_message.set_speed_over_ground(speed / 10);
cog_sog_message.set_course_over_ground(0); // TODO: Implement course
cog_sog_message.set_course_over_ground_reference(isobus::NMEA2000Messages::CourseOverGroundSpeedOverGroundRapidUpdate::CourseOverGroundReference::NotApplicableOrNull);
}
}
else if (identifier == isobus::DataDescriptionIndex::GuidanceLineDeviation)
{
Expand All @@ -160,13 +204,13 @@ bool Application::initialize()
};
if (isobus::SystemTiming::time_expired_ms(lastXteTransmit, 1000)) // Transmit every second
{
if (isobus::CANNetworkManager::CANNetwork.send_can_message(0x1F903, xteData.data(), xteData.size(), serverCF))
if (isobus::CANNetworkManager::CANNetwork.send_can_message(0x1F903, xteData.data(), xteData.size(), tcCF))
{
lastXteTransmit = isobus::SystemTiming::get_timestamp_ms();
}
}
}
else if (static_cast<std::uint16_t>(identifier) == 597 /*isobus::DataDescriptionIndex::TotalDistance*/)
else if (static_cast<std::uint16_t>(identifier) == 597 /*isobus::DataDescriptionIndex::TotalDistance*/ && speedMessagesInterface)
{
auto distance = static_cast<std::uint32_t>(value);
speedMessagesInterface->groundBasedSpeedTransmitData.set_machine_distance(distance);
Expand All @@ -186,14 +230,17 @@ bool Application::initialize()
bool Application::update()
{
static std::uint32_t lastHeartbeatTransmit = 0;
static std::uint32_t lastTECUStatusTransmit = 0;

udpConnections->handle_address_detection();
udpConnections->handle_incoming_packets();

tcServer->request_measurement_commands();
tcServer->update();
speedMessagesInterface->update();
nmea2000MessageInterface->update();
if (speedMessagesInterface)
speedMessagesInterface->update();
if (nmea2000MessageInterface)
nmea2000MessageInterface->update();

if (isobus::SystemTiming::time_expired_ms(lastHeartbeatTransmit, 100))
{
Expand Down
4 changes: 2 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ class ArgumentProcessor
std::cout << "Options:\n";
std::cout << " --help\t\tShow this help message\n";
std::cout << " --version\t\tShow the version of the application\n";
std::cout << " --adapter=<driver>\tSelect the CAN driver\n";
std::cout << " --channel=<channel>\tSelect the CAN channel\n";
std::cout << " --can_adapter=<driver>\tSelect the CAN driver\n";
std::cout << " --can_channel=<channel>\tSelect the CAN channel\n";
std::cout << " --log_level=<level>\tSet the log level (debug, info, warning, error, critical)\n";
std::cout << " --log2file\t\tLog to file\n";
exit(0);
Expand Down
Loading
Loading