Skip to content
Merged
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,8 +1,8 @@
cmake_minimum_required(VERSION 3.16)
project("AOG-TaskController")
set(PROJECT_VERSION_MAJOR 1)
set(PROJECT_VERSION_MINOR 2)
set(PROJECT_VERSION_PATCH 0)
set(PROJECT_VERSION_MINOR 3)
set(PROJECT_VERSION_PATCH 1)
set(PROJECT_VERSION
"${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}"
)
Expand Down
16 changes: 16 additions & 0 deletions include/settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ class Settings
*/
bool set_subnet(std::array<std::uint8_t, 3> subnet, bool save = true);

/**
* @brief Check if Tractor ECU is enabled
* @return True if TECU is enabled, false otherwise
*/
bool is_tecu_enabled() const;

/**
* @brief Set the Tractor ECU enabled state
* @param enabled Whether to enable the Tractor ECU
* @param save Whether or not to save the settings to file
* @return True if the setting was set successfully, false otherwise
*/
bool set_tecu_enabled(bool enabled, bool save = true);

/**
* @brief Get the absolute path to the settings file
* @param filename The filename to get the path for
Expand All @@ -57,5 +71,7 @@ class Settings

private:
constexpr static std::array<std::uint8_t, 3> DEFAULT_SUBNET = { 192, 168, 5 };
constexpr static bool DEFAULT_TECU_ENABLED = true;
std::array<std::uint8_t, 3> configuredSubnet = DEFAULT_SUBNET;
bool tecuEnabled = DEFAULT_TECU_ENABLED;
};
42 changes: 41 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,48 @@ Then, you can run the following commands:

```bash
mkdir build
cmake -S . -B build -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -Wno-dev
cmake -S . -B build -DBUILD_EXAMPLES=OFF -DCMAKE_POLICY_VERSION_MINIMUM="3.16" -DBUILD_TESTING=OFF -Wno-dev
cmake --build build --config Release --target package
```

The installer will be generated in the `build` directory.

## Configuration

AOG-TaskController reads its configuration from a `settings.json` file located in:

- **Windows:** `%APPDATA%\AOG-Taskcontroller\settings.json`

One of the available options is the `tecuEnabled` flag:

- **Key:** `tecuEnabled`
- **Default:** `true`
- **Description:** Enables communication with the Tractor ECU (TECU) over ISOBUS/CAN.
- **When to disable:** Set to `false` if your tractor or simulator does not provide TECU messages, if you do not need TECU-related data, or if you are troubleshooting TECU-related issues on the CAN bus.

Example `settings.json` snippet:

```json
{
"tecuEnabled": true
}
```

Before committing it's better to run these commands: (requires the LLVM project to be installed)
```powershell
git ls-files | Select-String '\.(c|cc|cpp|cxx|h|hh|hpp|hxx|proto)$' | ForEach-Object {
clang-format -i $_.ToString()
}
```

Install cmake-format with:
```powershell
python -m pip install --upgrade pip
pip install cmake-format pyyaml
```
Then execute with:
```powershell
Get-ChildItem -Recurse -Filter CMakeLists.txt | ForEach-Object {
python -m cmakelang.format -i $_.FullName
}
```
20 changes: 17 additions & 3 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ bool Application::initialize()
// 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
if (tcCF && settings->is_tecu_enabled())
{ // Only create TECU if TC was created and ECU is enabled
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;
Expand Down Expand Up @@ -125,7 +125,14 @@ bool Application::initialize()
}
else
{
std::cout << "[Warning] TECU Control Function not available, Speed/NMEA interfaces not created" << std::endl;
if (!settings->is_tecu_enabled())
{
std::cout << "[Info] Tractor ECU disabled in settings, skipping ECU initialization." << 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;
Expand Down Expand Up @@ -248,6 +255,13 @@ bool Application::update()
for (auto &client : tcServer->get_clients())
{
auto &state = client.second;

// Skip clients with no sections (e.g., tractors or non-implement devices)
if (state.get_number_of_sections() == 0)
{
continue;
}

std::vector<uint8_t> data = { state.is_section_control_enabled(), state.get_number_of_sections() };

std::uint8_t sectionIndex = 0;
Expand Down
33 changes: 33 additions & 0 deletions src/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,31 @@ bool Settings::load()
configuredSubnet = DEFAULT_SUBNET; // Key not found, use default
}

if (data.contains("tecuEnabled"))
{
try
{
tecuEnabled = data["tecuEnabled"].get<bool>();
}
catch (const nlohmann::json::exception &e)
{
std::cout << "Error parsing 'tecuEnabled': " << e.what() << std::endl;
tecuEnabled = DEFAULT_TECU_ENABLED; // Fallback to default
}
}
else
{
tecuEnabled = DEFAULT_TECU_ENABLED; // Key not found, use default
}

return true;
}

bool Settings::save() const
{
json data;
data["subnet"] = configuredSubnet;
data["tecuEnabled"] = tecuEnabled;

std::ofstream file(get_filename_path("settings.json"));
if (!file.is_open())
Expand Down Expand Up @@ -82,6 +100,21 @@ bool Settings::set_subnet(std::array<std::uint8_t, 3> subnet, bool save)
return true;
}

bool Settings::is_tecu_enabled() const
{
return tecuEnabled;
}

bool Settings::set_tecu_enabled(bool enabled, bool save)
{
tecuEnabled = enabled;
if (save)
{
return this->save();
}
return true;
}

std::string Settings::get_filename_path(std::string fileName)
{
char path[MAX_PATH];
Expand Down
20 changes: 19 additions & 1 deletion src/task_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ MyTCServer::MyTCServer(std::shared_ptr<isobus::InternalControlFunction> internal

bool MyTCServer::activate_object_pool(std::shared_ptr<isobus::ControlFunction> partnerCF, ObjectPoolActivationError &, ObjectPoolErrorCodes &, std::uint16_t &, std::uint16_t &)
{
std::cout << "[TC Server] Client " << partnerCF->get_NAME().get_full_name() << " requesting object pool activation" << std::endl;
// Safety check to make sure partnerCF has uploaded a DDOP
if (uploadedPools.find(partnerCF) == uploadedPools.end())
{
Expand Down Expand Up @@ -339,6 +340,8 @@ bool MyTCServer::activate_object_pool(std::shared_ptr<isobus::ControlFunction> p
}

clients[partnerCF] = state;
std::cout << "[TC Server] Client " << partnerCF->get_NAME().get_full_name() << " registered successfully with "
<< static_cast<int>(state.get_number_of_sections()) << " sections." << std::endl;
return true;
}

Expand Down Expand Up @@ -385,6 +388,7 @@ void MyTCServer::identify_task_controller(std::uint8_t)
void MyTCServer::on_client_timeout(std::shared_ptr<isobus::ControlFunction> partner)
{
// Cleanup the client state
std::cout << "[TC Server] Client " << partner->get_NAME().get_full_name() << " has timed out!" << std::endl;
clients.erase(partner);
}

Expand Down Expand Up @@ -452,6 +456,7 @@ bool MyTCServer::on_value_command(std::shared_ptr<isobus::ControlFunction> partn

bool MyTCServer::store_device_descriptor_object_pool(std::shared_ptr<isobus::ControlFunction> partnerCF, const std::vector<std::uint8_t> &binaryPool, bool appendToPool)
{
std::cout << "[TC Server] Client " << partnerCF->get_NAME().get_full_name() << " requesting object pool transfer of " << binaryPool.size() << " bytes" << std::endl;
if (uploadedPools.find(partnerCF) == uploadedPools.end())
{
uploadedPools[partnerCF] = std::queue<std::vector<std::uint8_t>>();
Expand Down Expand Up @@ -579,7 +584,13 @@ void MyTCServer::update_section_states(std::vector<bool> &sectionStates)
if (!state.is_section_control_enabled())
{
// According to standard, the section setpoint states should only be sent when in auto mode
return;
continue;
}

// Skip clients that don't have any sections configured (e.g., tractors or other non-implement devices)
if (state.get_number_of_sections() == 0)
{
continue;
}

bool requiresUpdate = false;
Expand Down Expand Up @@ -614,9 +625,16 @@ void MyTCServer::update_section_control_enabled(bool enabled)
{
for (auto &client : clients)
{
// Always update the local flag
if (client.second.is_section_control_enabled() != enabled)
{
client.second.set_section_control_enabled(enabled);
}

// Only send ISOBUS command to clients that support SectionControlState DDI and have sections
if (client.second.has_element_number_for_ddi(isobus::DataDescriptionIndex::SectionControlState) &&
client.second.get_number_of_sections() > 0)
{
send_section_control_state(client.first, enabled);
}
}
Expand Down
Loading