Skip to content

Instantly share code, notes, and snippets.

@j-c-cook
Last active April 30, 2025 20:22
Show Gist options
  • Save j-c-cook/21793e9df45107c2641233420936eb6c to your computer and use it in GitHub Desktop.
Save j-c-cook/21793e9df45107c2641233420936eb6c to your computer and use it in GitHub Desktop.
Explanation of PCF callback unrelated trigger

Partnered Control Function Callabacks Triggering for Unrelated Messages

Open-Agriculture/AgIsoStack-plus-plus#579 Open-Agriculture/AgIsoStack-plus-plus#580

Setup CAN

sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

Build

mkdir build && cd build
cmake ..
make

Run

Run in two separate processes at the same time.

./sender
./receiver

Result

$ ./receiver
Successfully claimed address 0xD0
Normal. Received message from expected CF with ID 5
Warning: Received message from unexpected CF. Expected ID 10, got ID 5
cmake_minimum_required(VERSION 3.10)
project(agisostack_bug_test)
set(CMAKE_CXX_STANDARD 14)
# Add AgIsoStack library
include(FetchContent)
FetchContent_Declare(
AgIsoStack
GIT_REPOSITORY https://github.com/Open-Agriculture/AgIsoStack-plus-plus.git
GIT_TAG main
)
FetchContent_MakeAvailable(AgIsoStack)
add_executable(receiver receiver.cpp)
target_link_libraries(receiver PRIVATE isobus::Isobus isobus::HardwareIntegration isobus::Utility)
add_executable(sender sender.cpp)
target_link_libraries(sender PRIVATE isobus::Isobus isobus::HardwareIntegration isobus::Utility)
#include <isobus/hardware_integration/can_hardware_interface.hpp>
#include <isobus/hardware_integration/socket_can_interface.hpp>
#include <isobus/isobus/can_network_manager.hpp>
#include <isobus/isobus/can_parameter_group_number_request_protocol.hpp>
#include <iostream>
#include <csignal>
#include "shared_utils.hpp"
std::atomic<bool> running(true);
void signal_handler(int signal) {
running = false;
}
std::vector<isobus::NAMEFilter> create_partner_cf_mask(std::uint8_t identity) {
return {
isobus::NAMEFilter{isobus::NAME::NAMEParameters::IdentityNumber, identity}
};
}
struct PCFContainer {
std::shared_ptr<isobus::PartneredControlFunction> pcf;
uint8_t identity;
PCFContainer(std::shared_ptr<isobus::PartneredControlFunction> pcf, std::uint8_t identity) : pcf(pcf), identity(identity) {
}
~PCFContainer() = default;
};
void messageCallbackOne(const isobus::CANMessage& message, void* target) {
auto container = reinterpret_cast<PCFContainer*>(target);
auto sourceId = message.get_source_control_function()->get_NAME().get_identity_number();
auto expectedId = container->identity;
if (message.get_source_control_function() != container->pcf) {
printf("Warning: Received message from unexpected CF. Expected ID %d, got ID %d\n",
expectedId, sourceId);
} else {
printf("Normal. Received message from expected CF with ID %d\n", sourceId);
}
}
void messageCallbackTwo(const isobus::CANMessage& message, void* target) {
auto container = reinterpret_cast<PCFContainer*>(target);
auto sourceId = message.get_source_control_function()->get_NAME().get_identity_number();
auto expectedId = container->identity;
if (message.get_source_control_function() != container->pcf) {
printf("Warning: Received message from unexpected CF. Expected ID %d, got ID %d\n",
expectedId, sourceId);
} else {
printf("Normal. Received message from expected CF with ID %d\n", sourceId);
}
}
int main() {
signal(SIGINT, signal_handler);
// Set up two channels but both pointing to vcan0
std::shared_ptr<isobus::CANHardwarePlugin> canDriver0 = std::make_shared<isobus::SocketCANInterface>("vcan0");
// Configure for 2 channels
isobus::CANHardwareInterface::set_number_of_can_channels(1);
isobus::CANHardwareInterface::assign_can_channel_frame_handler(CH0, canDriver0);
if (!isobus::CANHardwareInterface::start() ||
!canDriver0->get_is_valid()) {
printf("Failed to start hardware interface\n");
return -1;
}
isobus::CANNetworkManager::CANNetwork.initialize();
// Setup OUR name
isobus::NAME ourName;
ourName.set_identity_number(1);
// Create our control functions
auto ourCF = isobus::CANNetworkManager::CANNetwork.create_internal_control_function(
ourName, CH0, 0xd0);
auto pcfFive = isobus::CANNetworkManager::CANNetwork.create_partnered_control_function(
CH0, create_partner_cf_mask(5));
PCFContainer pcfFiveContainer = PCFContainer(pcfFive, 5);
auto pcfTen = isobus::CANNetworkManager::CANNetwork.create_partnered_control_function(
CH0, create_partner_cf_mask(10));
PCFContainer pcfTenContainer = PCFContainer(pcfTen, 10);
pcfFive->add_parameter_group_number_callback(
static_cast<std::uint32_t>(isobus::CANLibParameterGroupNumber::ProprietaryA),
messageCallbackOne, &pcfFiveContainer, ourCF);
pcfTen->add_parameter_group_number_callback(
static_cast<std::uint32_t>(isobus::CANLibParameterGroupNumber::ProprietaryA),
messageCallbackTwo, &pcfTenContainer, ourCF);
make_address_claim(ourCF);
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
while (running) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return 0;
}
#include <isobus/hardware_integration/can_hardware_interface.hpp>
#include <isobus/hardware_integration/socket_can_interface.hpp>
#include <isobus/isobus/can_network_manager.hpp>
#include <isobus/isobus/can_parameter_group_number_request_protocol.hpp>
#include <iostream>
#include "shared_utils.hpp"
std::atomic<bool> running(true);
void signal_handler(int signal) {
running = false;
}
std::vector<isobus::NAMEFilter> create_partner_cf_mask(std::uint8_t identity) {
return {
isobus::NAMEFilter{isobus::NAME::NAMEParameters::IdentityNumber, identity}
};
}
int main() {
// Set up channel pointing to vcan0
std::shared_ptr<isobus::CANHardwarePlugin> canDriver = std::make_shared<isobus::SocketCANInterface>("vcan0");
// Configure for 1 channel
isobus::CANHardwareInterface::set_number_of_can_channels(1);
isobus::CANHardwareInterface::assign_can_channel_frame_handler(CH0, canDriver);
if (!isobus::CANHardwareInterface::start() || !canDriver->get_is_valid()) {
printf("Failed to start hardware interface\n");
return -1;
}
isobus::CANNetworkManager::CANNetwork.initialize();
// Create the sending control function with NAME ID 5
isobus::NAME senderName;
senderName.set_identity_number(5);
auto senderCF = isobus::CANNetworkManager::CANNetwork.create_internal_control_function(
senderName, 0, 0xd1);
// Create a partnered control function for the receiver (ID 1)
auto receiverPCF = isobus::CANNetworkManager::CANNetwork.create_partnered_control_function(
CH0, create_partner_cf_mask(1));
// Claim address
if (make_address_claim(senderCF) != 0) {
return -1;
}
// Give some time for the network to stabilize
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// Send message every second
while (running) {
std::array<std::uint8_t, isobus::CAN_DATA_LENGTH> buffer = { 0 };
bool messageSent = isobus::CANNetworkManager::CANNetwork.send_can_message(
static_cast<std::uint32_t>(isobus::CANLibParameterGroupNumber::ProprietaryA),
buffer.data(),
buffer.size(),
senderCF,
receiverPCF, // Send to receiver PCF instead of broadcast
isobus::CANIdentifier::CANPriority::PriorityDefault6
);
std::cout << "Message send initiated: " << (messageSent ? "yes" : "no") << std::endl;
// Update the network manager
isobus::CANNetworkManager::CANNetwork.update();
// Debug: Print control function status
auto cfs = isobus::CANNetworkManager::CANNetwork.get_control_functions(true);
for (const auto& cf : cfs) {
std::cout << "CF at address 0x" << std::hex << (int)cf->get_address()
<< " NAME ID: 0x" << cf->get_NAME().get_identity_number()
<< " Type: " << cf->get_type_string() << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
// shared_utils.hpp
#ifndef SHARED_UTILS_HPP
#define SHARED_UTILS_HPP
#include <isobus/isobus/can_network_manager.hpp>
#include <thread>
#include <iostream>
enum CanChannel : std::uint8_t {
CH0
};
inline int make_address_claim(std::shared_ptr<isobus::InternalControlFunction> cf) {
// First request address claims from all devices
isobus::CANNetworkManager::CANNetwork.send_request_for_address_claim(cf->get_can_port());
// Add a small delay to allow devices to respond
std::this_thread::sleep_for(std::chrono::milliseconds(250));
// Now try to claim our address
const uint32_t TIMEOUT_MS = 5000; // 5 second timeout
const uint32_t POLL_INTERVAL_MS = 100;
uint32_t elapsed_ms = 0;
while (!cf->get_address_valid() && elapsed_ms < TIMEOUT_MS) {
std::this_thread::sleep_for(std::chrono::milliseconds(POLL_INTERVAL_MS));
elapsed_ms += POLL_INTERVAL_MS;
if (cf->get_address() == isobus::NULL_CAN_ADDRESS) {
printf("Waiting for address claim... (%lu ms)\n", elapsed_ms);
}
}
if (!cf->get_address_valid()) {
printf("Address claiming failed after %lu ms\n", elapsed_ms);
return -1;
}
printf("Successfully claimed address 0x%02X\n", cf->get_address());
return 0;
}
#endif // SHARED_UTILS_HPP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment