├── .github ├── FUNDING.yml └── workflows │ └── linting.yml ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── SpeedMessages │ └── SpeedMessages.ino └── VirtualTerminal │ ├── ObjectPool.cpp │ └── VirtualTerminal.ino ├── library.properties └── src ├── AgIsoStack.hpp ├── FlexCAN_T4.hpp ├── FlexCAN_T4.tpp ├── FlexCAN_T4FD.tpp ├── FlexCAN_T4FDTimings.tpp ├── can_NAME.cpp ├── can_NAME.hpp ├── can_NAME_filter.cpp ├── can_NAME_filter.hpp ├── can_badge.hpp ├── can_callbacks.cpp ├── can_callbacks.hpp ├── can_constants.hpp ├── can_control_function.cpp ├── can_control_function.hpp ├── can_extended_transport_protocol.cpp ├── can_extended_transport_protocol.hpp ├── can_general_parameter_group_numbers.hpp ├── can_hardware_abstraction.hpp ├── can_hardware_interface_single_thread.cpp ├── can_hardware_interface_single_thread.hpp ├── can_hardware_plugin.hpp ├── can_identifier.cpp ├── can_identifier.hpp ├── can_internal_control_function.cpp ├── can_internal_control_function.hpp ├── can_message.cpp ├── can_message.hpp ├── can_message_data.cpp ├── can_message_data.hpp ├── can_message_frame.cpp ├── can_message_frame.hpp ├── can_network_configuration.cpp ├── can_network_configuration.hpp ├── can_network_manager.cpp ├── can_network_manager.hpp ├── can_parameter_group_number_request_protocol.cpp ├── can_parameter_group_number_request_protocol.hpp ├── can_partnered_control_function.cpp ├── can_partnered_control_function.hpp ├── can_stack_logger.cpp ├── can_stack_logger.hpp ├── can_transport_protocol.cpp ├── can_transport_protocol.hpp ├── can_transport_protocol_base.cpp ├── can_transport_protocol_base.hpp ├── circular_buffer.hpp ├── data_span.hpp ├── event_dispatcher.hpp ├── flex_can_t4_plugin.cpp ├── flex_can_t4_plugin.hpp ├── imxrt_flexcan.hpp ├── isobus_data_dictionary.cpp ├── isobus_data_dictionary.hpp ├── isobus_device_descriptor_object_pool.cpp ├── isobus_device_descriptor_object_pool.hpp ├── isobus_diagnostic_protocol.cpp ├── isobus_diagnostic_protocol.hpp ├── isobus_functionalities.cpp ├── isobus_functionalities.hpp ├── isobus_guidance_interface.cpp ├── isobus_guidance_interface.hpp ├── isobus_heartbeat.cpp ├── isobus_heartbeat.hpp ├── isobus_language_command_interface.cpp ├── isobus_language_command_interface.hpp ├── isobus_maintain_power_interface.cpp ├── isobus_maintain_power_interface.hpp ├── isobus_preferred_addresses.hpp ├── isobus_shortcut_button_interface.cpp ├── isobus_shortcut_button_interface.hpp ├── isobus_speed_distance_messages.cpp ├── isobus_speed_distance_messages.hpp ├── isobus_standard_data_description_indices.hpp ├── isobus_task_controller_client.cpp ├── isobus_task_controller_client.hpp ├── isobus_task_controller_client_objects.cpp ├── isobus_task_controller_client_objects.hpp ├── isobus_time_date_interface.cpp ├── isobus_time_date_interface.hpp ├── isobus_virtual_terminal_base.hpp ├── isobus_virtual_terminal_client.cpp ├── isobus_virtual_terminal_client.hpp ├── isobus_virtual_terminal_client_state_tracker.cpp ├── isobus_virtual_terminal_client_state_tracker.hpp ├── isobus_virtual_terminal_client_update_helper.cpp ├── isobus_virtual_terminal_client_update_helper.hpp ├── isobus_virtual_terminal_objects.cpp ├── isobus_virtual_terminal_objects.hpp ├── kinetis_flexcan.hpp ├── nmea2000_fast_packet_protocol.cpp ├── nmea2000_fast_packet_protocol.hpp ├── nmea2000_message_definitions.cpp ├── nmea2000_message_definitions.hpp ├── nmea2000_message_interface.cpp ├── nmea2000_message_interface.hpp ├── platform_endianness.cpp ├── platform_endianness.hpp ├── processing_flags.cpp ├── processing_flags.hpp ├── system_timing.cpp ├── system_timing.hpp ├── thread_synchronization.hpp └── to_string.hpp /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ad3154 2 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | jobs: 3 | lint: 4 | runs-on: ubuntu-latest 5 | steps: 6 | - uses: actions/checkout@v3 7 | - uses: arduino/arduino-lint-action@v1 8 | with: 9 | library-manager: update 10 | project-type: library 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Open-Agriculture 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AgIsoStack-Arduino 2 | AgIsoStack is a free ISOBUS (ISO11783) and SAE J1939 compatible CAN stack that makes communication on off-highway vehicle CAN networks easy. 3 | 4 | This library is based on the larger [AgIsoStack++ project](https://github.com/Open-Agriculture/AgIsoStack-plus-plus), which provides a CMake build system and additional supported CAN hardware. 5 | 6 | Currently this Arduino library is compatible with Teensy hardware only. ESP32 support will likely be added at some point, but for now PlatformIO + ESP-IDF is supported for ESP32 platforms via the [main repo](https://github.com/Open-Agriculture/AgIsoStack-plus-plus). 7 | 8 | ### Features 9 | 10 | - ISO11783/J1939 Address Claiming 11 | - ISO11783 Universal/Virtual Terminal Client 12 | - ISO11783 Task Controller Client 13 | - NMEA2000 Fast Packet 14 | - J1939 and ISO11783 Diagnostic Messages 15 | - Implement/Machine speed and guidance messaging 16 | - ISOBUS Shortcut Button (ISB) 17 | - ISO11783 Transport Protocol 18 | - ISO11783 Extended Transport Protocol 19 | - Designed to integrate with proprietary messaging as needed 20 | 21 | ### Example 22 | 23 | Examples are located [here](https://github.com/Open-Agriculture/AgIsoStack-Arduino/tree/main/examples). 24 | The virtual terminal example sketch is a good starting point, and loads a VT object pool to a virtual terminal, as long as your teensy is connected to an ISO11783 CAN network using the Teensy's CAN 1 pins and compatible CAN transceiver. 25 | 26 | ### Troubleshooting 27 | 28 | * My Teensy won't start up after loading this library, or otherwise does nothing 29 | * There might be a bug in the Teensy core, where compiling with non-default optimization causes this issue. 30 | * You can fix this by editing your startup.c file for your Teensy. 31 | * Navigate to your teensy core files located normally at `C:\Users\\AppData\Local\Arduino15\packages\teensy\hardware\avr\\cores\teensy4` 32 | * Open `startup.c` 33 | * Add this line after the `#include` directives: `#pragma GCC optimize ("O2")` 34 | * My Teensy starts fine, but no object pool is loaded! 35 | * Make sure you are using a [proper CAN transceiver](https://www.amazon.com/SN65HVD230-CAN-Board-Communication-Development/dp/B00KM6XMXO). You can't connect CTX1 and CRX1 directly to a CAN bus. 36 | * Make sure you don't have CAN-H and CAN-L reversed. 37 | * Make sure your CAN network is properly terminated. 38 | * Try and view the serial output from the Teensy to see if any errors are shown. 39 | * Make sure your object pool isn't in EXTMEM without first being initialized, otherwise your pool might be all zeros, or worse. 40 | * My program doesn't compile because my Teensy is out of RAM1 space 41 | * Make sure you are compiling with optimizations set to "Smallest Code". 42 | * I don't see Teensy as a board option in Arduino IDE 43 | * Ensure you have followed the steps [outlined here](https://www.pjrc.com/teensy/td_download.html) to add the Teensy boards. 44 | * Something else! 45 | * Open an issue or start a discussion here on GitHub and a maintainer will try to help if they can. 46 | 47 | ### Documentation 48 | 49 | View the [precompiled doxygen](https://delgrossoengineering.com/isobus-docs/) or visit our [tutorial site](https://isobus-plus-plus.readthedocs.io/en/latest/). 50 | 51 | Note that the documentation is for the full C++ library, so file paths and other minor differences might exist on Arduino, but they should be very minor. 52 | 53 | You may also want to view the main repo's [examples](https://github.com/Open-Agriculture/AgIsoStack-plus-plus/tree/main/examples). 54 | -------------------------------------------------------------------------------- /examples/SpeedMessages/SpeedMessages.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace isobus; 4 | 5 | auto can0 = std::make_shared(0); 6 | std::shared_ptr ISOBUSControlFunction = nullptr; 7 | std::shared_ptr ISOBUSDiagnostics = nullptr; 8 | std::shared_ptr ISOBUSSpeedInterface = nullptr; 9 | 10 | // A log sink for the CAN stack 11 | class CustomLogger : public CANStackLogger 12 | { 13 | public: 14 | void sink_CAN_stack_log(CANStackLogger::LoggingLevel level, const std::string &text) override 15 | { 16 | switch (level) 17 | { 18 | case LoggingLevel::Debug: 19 | { 20 | Serial.print("[Debug]: "); 21 | } 22 | break; 23 | 24 | case LoggingLevel::Info: 25 | { 26 | Serial.print("[Info]: "); 27 | } 28 | break; 29 | 30 | case LoggingLevel::Warning: 31 | { 32 | Serial.print("[Warning]: "); 33 | } 34 | break; 35 | 36 | case LoggingLevel::Error: 37 | { 38 | Serial.print("[Error]: "); 39 | } 40 | break; 41 | 42 | case LoggingLevel::Critical: 43 | { 44 | Serial.print("[Critical]: "); 45 | } 46 | break; 47 | } 48 | Serial.println(text.c_str()); 49 | } 50 | }; 51 | 52 | static CustomLogger logger; 53 | 54 | // These are optional callbacks you can use to do something as soon as new speed information is received. 55 | static void test_mss_callback(const std::shared_ptr machineSpeedData, bool) 56 | { 57 | if (nullptr != machineSpeedData) 58 | { 59 | Serial.write(("[MSS] " + isobus::to_string(machineSpeedData->get_machine_speed()) + "mm/s\n").c_str()); 60 | } 61 | } 62 | 63 | static void test_wbs_callback(const std::shared_ptr wheelSpeedData, bool) 64 | { 65 | if (nullptr != wheelSpeedData) 66 | { 67 | Serial.write(("[WBS] " + isobus::to_string(wheelSpeedData->get_machine_speed()) + "mm/s\n").c_str()); 68 | } 69 | } 70 | 71 | static void test_gbs_callback(const std::shared_ptr groundSpeedData, bool) 72 | { 73 | if (nullptr != groundSpeedData) 74 | { 75 | Serial.write(("[GBS] " + isobus::to_string(groundSpeedData->get_machine_speed()) + "mm/s\n").c_str()); 76 | } 77 | } 78 | 79 | void setup() { 80 | // put your setup code here, to run once: 81 | Serial.begin(115200); 82 | CANStackLogger::set_can_stack_logger_sink(&logger); 83 | CANStackLogger::set_log_level(isobus::CANStackLogger::LoggingLevel::Debug); 84 | // Optional, add delay() here to give you time to connect to the serial logger 85 | CANHardwareInterface::set_number_of_can_channels(1); 86 | CANHardwareInterface::assign_can_channel_frame_handler(0, can0); 87 | CANHardwareInterface::start(); 88 | CANHardwareInterface::update(); 89 | 90 | NAME deviceNAME(0); 91 | // Make sure you change these for your device 92 | // This is an example device that is using a manufacturer code that is currently unused at time of writing 93 | deviceNAME.set_arbitrary_address_capable(true); 94 | deviceNAME.set_industry_group(0); 95 | deviceNAME.set_device_class(0); 96 | deviceNAME.set_function_code(static_cast(isobus::NAME::Function::IOController)); 97 | deviceNAME.set_identity_number(2); 98 | deviceNAME.set_ecu_instance(0); 99 | deviceNAME.set_function_instance(0); 100 | deviceNAME.set_device_class_instance(0); 101 | deviceNAME.set_manufacturer_code(1407); // This is the Open-Agriculture manufacturer code. You are welcome to use it if you want. 102 | // If you want to set a preferred address, you can add another parameter below, like (deviceNAME, 0, 0x81), otherwise the CAN stack will choose one automatically. 103 | ISOBUSControlFunction = CANNetworkManager::CANNetwork.create_internal_control_function(deviceNAME, 0); 104 | ISOBUSDiagnostics = std::make_shared(ISOBUSControlFunction); 105 | ISOBUSDiagnostics->initialize(); 106 | 107 | // Change these to be specific to your device 108 | ISOBUSDiagnostics->set_product_identification_brand("Arduino"); 109 | ISOBUSDiagnostics->set_product_identification_code("123456789"); 110 | ISOBUSDiagnostics->set_product_identification_model("Example"); 111 | ISOBUSDiagnostics->set_software_id_field(0, "0.0.1"); 112 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::HardwareID, "Hardware ID"); 113 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::Location, "The Aether"); 114 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::ManufacturerName, "None"); 115 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::PartNumber, "1234"); 116 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::SerialNumber, "1"); 117 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::Type, "AgISOStack"); 118 | 119 | // Set up to receive speed messages. 120 | ISOBUSSpeedInterface = std::make_shared(nullptr); 121 | ISOBUSSpeedInterface->initialize(); 122 | ISOBUSSpeedInterface->get_machine_selected_speed_data_event_publisher().add_listener(test_mss_callback); 123 | ISOBUSSpeedInterface->get_ground_based_machine_speed_data_event_publisher().add_listener(test_gbs_callback); 124 | ISOBUSSpeedInterface->get_wheel_based_machine_speed_data_event_publisher().add_listener(test_wbs_callback); 125 | } 126 | 127 | void loop() { 128 | // put your main code here, to run repeatedly: 129 | static std::uint32_t bestSpeedPrintTimestamp = SystemTiming::get_timestamp_ms(); 130 | 131 | ISOBUSDiagnostics->update(); // Update diagnostics interface 132 | ISOBUSSpeedInterface->update(); 133 | CANHardwareInterface::update(); // Update CAN stack 134 | 135 | // Every 1 second, print the "best speed". 136 | if (SystemTiming::time_expired_ms(bestSpeedPrintTimestamp, 1000)) 137 | { 138 | std::uint32_t bestSpeed = 0; 139 | 140 | if (ISOBUSSpeedInterface->get_number_received_machine_selected_speed_sources() > 0) 141 | { 142 | bestSpeed = ISOBUSSpeedInterface->get_received_machine_selected_speed(0)->get_machine_speed(); 143 | Serial.write("Best speed is MSS: "); 144 | Serial.write((isobus::to_string(bestSpeed) + "mm/s\n").c_str()); 145 | } 146 | else if (ISOBUSSpeedInterface->get_number_received_ground_based_speed_sources() > 0) 147 | { 148 | bestSpeed = ISOBUSSpeedInterface->get_received_ground_based_speed(0)->get_machine_speed(); 149 | Serial.write("Best speed is GBS: "); 150 | Serial.write((isobus::to_string(bestSpeed) + "mm/s\n").c_str()); 151 | } 152 | else if (ISOBUSSpeedInterface->get_number_received_wheel_based_speed_sources() > 0) 153 | { 154 | bestSpeed = ISOBUSSpeedInterface->get_received_wheel_based_speed(0)->get_machine_speed(); 155 | Serial.write("Best speed is WBS: "); 156 | Serial.write((isobus::to_string(bestSpeed) + "mm/s\n").c_str()); 157 | } 158 | else 159 | { 160 | Serial.write("No valid speed sources.\n"); 161 | } 162 | bestSpeedPrintTimestamp = SystemTiming::get_timestamp_ms(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /examples/VirtualTerminal/VirtualTerminal.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ObjectPool.cpp" 4 | 5 | using namespace isobus; 6 | 7 | auto can0 = std::make_shared(0); 8 | std::shared_ptr ISOBUSControlFunction = nullptr; 9 | std::shared_ptr ISOBUSDiagnostics = nullptr; 10 | std::shared_ptr ExampleVirtualTerminalClient = nullptr; 11 | 12 | // A log sink for the CAN stack 13 | class CustomLogger : public CANStackLogger 14 | { 15 | public: 16 | void sink_CAN_stack_log(CANStackLogger::LoggingLevel level, const std::string &text) override 17 | { 18 | switch (level) 19 | { 20 | case LoggingLevel::Debug: 21 | { 22 | Serial.print("[Debug]: "); 23 | } 24 | break; 25 | 26 | case LoggingLevel::Info: 27 | { 28 | Serial.print("[Info]: "); 29 | } 30 | break; 31 | 32 | case LoggingLevel::Warning: 33 | { 34 | Serial.print("[Warning]: "); 35 | } 36 | break; 37 | 38 | case LoggingLevel::Error: 39 | { 40 | Serial.print("[Error]: "); 41 | } 42 | break; 43 | 44 | case LoggingLevel::Critical: 45 | { 46 | Serial.print("[Critical]: "); 47 | } 48 | break; 49 | } 50 | Serial.println(text.c_str()); 51 | } 52 | }; 53 | 54 | static CustomLogger logger; 55 | 56 | // This callback will provide us with event driven notifications of button presses from the stack 57 | void handleVTKeyEvents(const VirtualTerminalClient::VTKeyEvent &event) 58 | { 59 | 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. 60 | 61 | switch (event.keyEvent) 62 | { 63 | case VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased: 64 | { 65 | switch (event.objectID) 66 | { 67 | case Plus_Button: 68 | { 69 | ExampleVirtualTerminalClient->send_change_numeric_value(ButtonExampleNumber_VarNum, ++exampleNumberOutput); 70 | } 71 | break; 72 | 73 | case Minus_Button: 74 | { 75 | ExampleVirtualTerminalClient->send_change_numeric_value(ButtonExampleNumber_VarNum, --exampleNumberOutput); 76 | } 77 | break; 78 | 79 | case alarm_SoftKey: 80 | { 81 | ExampleVirtualTerminalClient->send_change_active_mask(example_WorkingSet, example_AlarmMask); 82 | } 83 | break; 84 | 85 | case acknowledgeAlarm_SoftKey: 86 | { 87 | ExampleVirtualTerminalClient->send_change_active_mask(example_WorkingSet, mainRunscreen_DataMask); 88 | } 89 | break; 90 | 91 | default: 92 | break; 93 | } 94 | } 95 | break; 96 | 97 | default: 98 | break; 99 | } 100 | } 101 | 102 | void setup() { 103 | // put your setup code here, to run once: 104 | Serial.begin(115200); 105 | CANStackLogger::set_can_stack_logger_sink(&logger); 106 | CANStackLogger::set_log_level(isobus::CANStackLogger::LoggingLevel::Debug); 107 | // Optional, add delay() here to give you time to connect to the serial logger 108 | CANHardwareInterface::set_number_of_can_channels(1); 109 | CANHardwareInterface::assign_can_channel_frame_handler(0, can0); 110 | CANHardwareInterface::start(); 111 | CANHardwareInterface::update(); 112 | 113 | NAME deviceNAME(0); 114 | // Make sure you change these for your device 115 | // This is an example device that is using a manufacturer code that is currently unused at time of writing 116 | deviceNAME.set_arbitrary_address_capable(true); 117 | deviceNAME.set_industry_group(0); 118 | deviceNAME.set_device_class(0); 119 | deviceNAME.set_function_code(static_cast(isobus::NAME::Function::SteeringControl)); 120 | deviceNAME.set_identity_number(2); 121 | deviceNAME.set_ecu_instance(0); 122 | deviceNAME.set_function_instance(0); 123 | deviceNAME.set_device_class_instance(0); 124 | deviceNAME.set_manufacturer_code(1407); // This is the Open-Agriculture manufacturer code. You are welcome to use it if you want. 125 | // If you want to set a preferred address, you can add another parameter below, like (deviceNAME, 0, 0x81), otherwise the CAN stack will choose one automatically. 126 | ISOBUSControlFunction = CANNetworkManager::CANNetwork.create_internal_control_function(deviceNAME, 0); 127 | ISOBUSDiagnostics = std::make_shared(ISOBUSControlFunction); 128 | ISOBUSDiagnostics->initialize(); 129 | 130 | // Change these to be specific to your device 131 | ISOBUSDiagnostics->set_product_identification_brand("Arduino"); 132 | ISOBUSDiagnostics->set_product_identification_code("123456789"); 133 | ISOBUSDiagnostics->set_product_identification_model("Example"); 134 | ISOBUSDiagnostics->set_software_id_field(0, "0.0.1"); 135 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::HardwareID, "Hardware ID"); 136 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::Location, "The Aether"); 137 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::ManufacturerName, "None"); 138 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::PartNumber, "1234"); 139 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::SerialNumber, "1"); 140 | ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::Type, "AgISOStack"); 141 | 142 | // Set up virtual terminal client 143 | const NAMEFilter filterVirtualTerminal(NAME::NAMEParameters::FunctionCode, static_cast(NAME::Function::VirtualTerminal)); 144 | const std::vector vtNameFilters = { filterVirtualTerminal }; 145 | auto TestPartnerVT = CANNetworkManager::CANNetwork.create_partnered_control_function(0, vtNameFilters); 146 | ExampleVirtualTerminalClient = std::make_shared(TestPartnerVT, ISOBUSControlFunction); 147 | ExampleVirtualTerminalClient->set_object_pool(0, VT3TestPool, sizeof(VT3TestPool), "AIS1"); 148 | ExampleVirtualTerminalClient->get_vt_button_event_dispatcher().add_listener(handleVTKeyEvents); 149 | ExampleVirtualTerminalClient->get_vt_soft_key_event_dispatcher().add_listener(handleVTKeyEvents); 150 | ExampleVirtualTerminalClient->initialize(false); 151 | } 152 | 153 | void loop() { 154 | // put your main code here, to run repeatedly: 155 | ISOBUSDiagnostics->update(); // Update diagnostics interface 156 | ExampleVirtualTerminalClient->update(); // Update VT client 157 | CANHardwareInterface::update(); // Update CAN stack 158 | } 159 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=AgIsoStack 2 | version=0.1.5 3 | license=MIT 4 | author=Adrian Del Grosso 5 | maintainer=Adrian Del Grosso 6 | sentence=A free ISOBUS (ISO11783) and J1939 CAN Stack for Teensy. 7 | paragraph=Includes ISOBUS virtual terminal client, task controller client, and transport layer functionality. Based on the CMake AgIsoStack++ at https://github.com/Open-Agriculture/AgIsoStack-plus-plus. 8 | category=Communication 9 | architectures=teensy 10 | includes=AgIsoStack.hpp 11 | url=https://github.com/Open-Agriculture/AgIsoStack-Arduino 12 | 13 | -------------------------------------------------------------------------------- /src/AgIsoStack.hpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | ** @file AgIsoStack.hpp 3 | ** @author Automatic Code Generation 4 | ** @date October 19, 2024 at 23:45:04 5 | ** @brief Includes all important files in the AgIsoStack library. 6 | ** 7 | ** Copyright 2024 The AgIsoStack++ Developers 8 | *******************************************************************************/ 9 | 10 | #ifndef AG_ISO_STACK_HPP 11 | #define AG_ISO_STACK_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | 72 | #endif // AG_ISO_STACK_HPP 73 | -------------------------------------------------------------------------------- /src/can_NAME.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_NAME.cpp 3 | /// 4 | /// @brief A class that represents a control function's NAME 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #include "can_NAME.hpp" 10 | #include "can_stack_logger.hpp" 11 | 12 | namespace isobus 13 | { 14 | NAME::NAME(std::uint64_t rawNAMEData) : 15 | rawName(rawNAMEData) 16 | { 17 | } 18 | 19 | bool NAME::operator==(const NAME &obj) const 20 | { 21 | return this->rawName == obj.rawName; 22 | } 23 | 24 | bool NAME::get_arbitrary_address_capable() const 25 | { 26 | return (0 != (rawName >> 63)); 27 | } 28 | 29 | void NAME::set_arbitrary_address_capable(bool value) 30 | { 31 | rawName &= ~(static_cast(1) << 63); 32 | rawName |= (static_cast(value) << 63); 33 | } 34 | 35 | std::uint8_t NAME::get_industry_group() const 36 | { 37 | return ((rawName >> 60) & 0x07); 38 | } 39 | 40 | void NAME::set_industry_group(std::uint8_t value) 41 | { 42 | if (value > 0x07) 43 | { 44 | LOG_ERROR("[NAME]: Industry group out of range, must be between 0 and 7"); 45 | } 46 | rawName &= ~static_cast(0x7000000000000000); 47 | rawName |= (static_cast(value & 0x07) << 60); 48 | } 49 | 50 | std::uint8_t NAME::get_device_class_instance() const 51 | { 52 | return ((rawName >> 56) & 0x0F); 53 | } 54 | 55 | void NAME::set_device_class_instance(std::uint8_t value) 56 | { 57 | if (value > 0x0F) 58 | { 59 | LOG_ERROR("[NAME]: Device class instance out of range, must be between 0 and 15"); 60 | } 61 | rawName &= ~static_cast(0xF00000000000000); 62 | rawName |= (static_cast(value & 0x0F) << 56); 63 | } 64 | 65 | std::uint8_t NAME::get_device_class() const 66 | { 67 | return ((rawName >> 49) & 0x7F); 68 | } 69 | 70 | void NAME::set_device_class(std::uint8_t value) 71 | { 72 | if (value > 0x7F) 73 | { 74 | LOG_ERROR("[NAME]: Device class out of range, must be between 0 and 127"); 75 | } 76 | rawName &= ~static_cast(0xFE000000000000); 77 | rawName |= (static_cast(value & 0x7F) << 49); 78 | } 79 | 80 | std::uint8_t NAME::get_function_code() const 81 | { 82 | return ((rawName >> 40) & 0xFF); 83 | } 84 | 85 | void NAME::set_function_code(std::uint8_t value) 86 | { 87 | rawName &= ~static_cast(0xFF0000000000); 88 | rawName |= (static_cast(value & 0xFF) << 40); 89 | } 90 | 91 | std::uint8_t NAME::get_function_instance() const 92 | { 93 | return ((rawName >> 35) & 0x1F); 94 | } 95 | 96 | void NAME::set_function_instance(std::uint8_t value) 97 | { 98 | if (value > 0x1F) 99 | { 100 | LOG_ERROR("[NAME]: Function instance out of range, must be between 0 and 31"); 101 | } 102 | rawName &= ~static_cast(0xF800000000); 103 | rawName |= (static_cast(value & 0x1F) << 35); 104 | } 105 | 106 | std::uint8_t NAME::get_ecu_instance() const 107 | { 108 | return ((rawName >> 32) & 0x07); 109 | } 110 | 111 | void NAME::set_ecu_instance(std::uint8_t value) 112 | { 113 | if (value > 0x07) 114 | { 115 | LOG_ERROR("[NAME]: ECU instance out of range, must be between 0 and 7"); 116 | } 117 | rawName &= ~static_cast(0x700000000); 118 | rawName |= (static_cast(value & 0x07) << 32); 119 | } 120 | 121 | std::uint16_t NAME::get_manufacturer_code() const 122 | { 123 | return ((rawName >> 21) & 0x07FF); 124 | } 125 | 126 | void NAME::set_manufacturer_code(std::uint16_t value) 127 | { 128 | if (value > 0x07FF) 129 | { 130 | LOG_ERROR("[NAME]: Manufacturer code out of range, must be between 0 and 2047"); 131 | } 132 | rawName &= ~static_cast(0xFFE00000); 133 | rawName |= (static_cast(value & 0x07FF) << 21); 134 | } 135 | 136 | std::uint32_t NAME::get_identity_number() const 137 | { 138 | return (rawName & 0x001FFFFF); 139 | } 140 | 141 | void NAME::set_identity_number(uint32_t value) 142 | { 143 | if (value > 0x001FFFFF) 144 | { 145 | LOG_ERROR("[NAME]: Identity number out of range, must be between 0 and 2097151"); 146 | } 147 | rawName &= ~static_cast(0x1FFFFF); 148 | rawName |= static_cast(value & 0x1FFFFF); 149 | } 150 | 151 | std::uint64_t NAME::get_full_name() const 152 | { 153 | return rawName; 154 | } 155 | 156 | void NAME::set_full_name(std::uint64_t value) 157 | { 158 | rawName = value; 159 | } 160 | 161 | } // namespace isobus 162 | -------------------------------------------------------------------------------- /src/can_NAME_filter.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_NAME_filter.cpp 3 | /// 4 | /// @brief Defines a filter value for an ISONAME component. Used to tell the stack what kind of 5 | /// ECU you want to talk to when creating a partnered control function. 6 | /// @author Adrian Del Grosso 7 | /// 8 | /// @copyright 2022 The Open-Agriculture Developers 9 | //================================================================================================ 10 | #include "can_NAME_filter.hpp" 11 | 12 | namespace isobus 13 | { 14 | NAMEFilter::NAMEFilter(NAME::NAMEParameters nameParameter, std::uint32_t parameterMatchValue) : 15 | parameter(nameParameter), 16 | value(parameterMatchValue) 17 | { 18 | } 19 | 20 | NAME::NAMEParameters NAMEFilter::get_parameter() const 21 | { 22 | return parameter; 23 | } 24 | 25 | std::uint32_t NAMEFilter::get_value() const 26 | { 27 | return value; 28 | } 29 | 30 | bool NAMEFilter::check_name_matches_filter(const NAME &nameToCompare) const 31 | { 32 | bool retVal = false; 33 | 34 | switch (parameter) 35 | { 36 | case NAME::NAMEParameters::IdentityNumber: 37 | { 38 | retVal = (nameToCompare.get_identity_number() == value); 39 | } 40 | break; 41 | 42 | case NAME::NAMEParameters::ManufacturerCode: 43 | { 44 | retVal = (nameToCompare.get_manufacturer_code() == value); 45 | } 46 | break; 47 | 48 | case NAME::NAMEParameters::EcuInstance: 49 | { 50 | retVal = (nameToCompare.get_ecu_instance() == value); 51 | } 52 | break; 53 | 54 | case NAME::NAMEParameters::FunctionInstance: 55 | { 56 | retVal = (nameToCompare.get_function_instance() == value); 57 | } 58 | break; 59 | 60 | case NAME::NAMEParameters::FunctionCode: 61 | { 62 | retVal = (nameToCompare.get_function_code() == value); 63 | } 64 | break; 65 | 66 | case NAME::NAMEParameters::DeviceClass: 67 | { 68 | retVal = (nameToCompare.get_device_class() == value); 69 | } 70 | break; 71 | 72 | case NAME::NAMEParameters::DeviceClassInstance: 73 | { 74 | retVal = (nameToCompare.get_device_class_instance() == value); 75 | } 76 | break; 77 | 78 | case NAME::NAMEParameters::IndustryGroup: 79 | { 80 | retVal = (nameToCompare.get_industry_group() == value); 81 | } 82 | break; 83 | 84 | case NAME::NAMEParameters::ArbitraryAddressCapable: 85 | { 86 | retVal = (nameToCompare.get_arbitrary_address_capable() == (0 != value)); 87 | } 88 | break; 89 | 90 | default: 91 | { 92 | } 93 | break; 94 | } 95 | return retVal; 96 | } 97 | 98 | } // namespace isobus 99 | -------------------------------------------------------------------------------- /src/can_NAME_filter.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_NAME_filter.hpp 3 | /// 4 | /// @brief Defines a filter value for an ISONAME component. Used to tell the stack what kind of 5 | /// ECU you want to talk to when creating a partnered control function. 6 | /// @author Adrian Del Grosso 7 | /// 8 | /// @copyright 2022 The Open-Agriculture Developers 9 | //================================================================================================ 10 | 11 | #ifndef CAN_NAME_FILTER_HPP 12 | #define CAN_NAME_FILTER_HPP 13 | 14 | #include "can_NAME.hpp" 15 | 16 | namespace isobus 17 | { 18 | //================================================================================================ 19 | /// @class NAMEFilter 20 | /// 21 | /// @brief A class that associates a NAME parameter with a value of that parameter. 22 | /// @details This class is used to match a partner control function with specific criteria that 23 | /// defines it. Use these to define what device you want to talk to. 24 | //================================================================================================ 25 | class NAMEFilter 26 | { 27 | public: 28 | /// @brief Constructor for the NAMEFilter 29 | /// @param[in] nameParameter The component of the NAME to filter on 30 | /// @param[in] parameterMatchValue The value to match with the nameParameter 31 | NAMEFilter(NAME::NAMEParameters nameParameter, std::uint32_t parameterMatchValue); 32 | 33 | /// @brief Returns the parameter data associated with this filter 34 | /// @returns The parameter/NAME component associated with this filter 35 | NAME::NAMEParameters get_parameter() const; 36 | 37 | /// @brief Returns the value associated with this filter 38 | /// @returns The data associated with this filter component 39 | std::uint32_t get_value() const; 40 | 41 | /// @brief Returns true if a NAME matches this filter class's components 42 | /// @param[in] nameToCompare A NAME to compare against this filter 43 | /// @returns true if a NAME matches this filter class's components 44 | bool check_name_matches_filter(const NAME &nameToCompare) const; 45 | 46 | private: 47 | NAME::NAMEParameters parameter; ///< The NAME component to filter against 48 | std::uint32_t value; ///< The value of the data associated with the filter component 49 | }; 50 | 51 | } // namespace isobus 52 | 53 | #endif // CAN_NAME_FILTER_HPP 54 | -------------------------------------------------------------------------------- /src/can_badge.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_badge.hpp 3 | /// 4 | /// @brief A way to only allow certain object types to access certain functions that is enforced 5 | /// at compile time. A neat trick from Serenity OS :^) 6 | /// @author Adrian Del Grosso 7 | /// 8 | /// @copyright 2022 The Open-Agriculture Developers 9 | //================================================================================================ 10 | 11 | #ifndef CAN_BADGE_HPP 12 | #define CAN_BADGE_HPP 13 | 14 | namespace isobus 15 | { 16 | //================================================================================================ 17 | /// @class CANLibBadge 18 | /// 19 | /// @brief This is a way to protect functions on public interfaces from being 20 | /// accessed by objects that shouldn't access them. 21 | /// 22 | /// @details This class was inspired from SerenityOS. It's a way to avoid 23 | /// friends. It protects functions on a class's public interface from being 24 | /// called by types that were not explicitly allowed in the function signature. 25 | //================================================================================================ 26 | 27 | //! @cond DoNotRaiseWarning 28 | template 29 | class CANLibBadge 30 | { 31 | private: 32 | friend T; ///< Our best friend, T 33 | //! @brief An empty function for our best friend 34 | CANLibBadge() = default; 35 | }; 36 | //! @endcond 37 | 38 | } // namespace isobus 39 | 40 | #endif // CAN_BADGE_HPP 41 | -------------------------------------------------------------------------------- /src/can_callbacks.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_callbacks.cpp 3 | /// 4 | /// @brief An object to represent common callbacks used within this CAN stack. 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #include "can_callbacks.hpp" 10 | 11 | namespace isobus 12 | { 13 | ParameterGroupNumberCallbackData::ParameterGroupNumberCallbackData(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parentPointer, std::shared_ptr internalControlFunction) : 14 | callback(callback), 15 | parameterGroupNumber(parameterGroupNumber), 16 | parent(parentPointer), 17 | internalControlFunctionFilter(internalControlFunction) 18 | { 19 | } 20 | 21 | bool ParameterGroupNumberCallbackData::operator==(const ParameterGroupNumberCallbackData &obj) const 22 | { 23 | return ((obj.callback == this->callback) && 24 | (obj.parameterGroupNumber == this->parameterGroupNumber) && 25 | (obj.parent == this->parent) && 26 | (obj.internalControlFunctionFilter == this->internalControlFunctionFilter)); 27 | } 28 | 29 | std::uint32_t ParameterGroupNumberCallbackData::get_parameter_group_number() const 30 | { 31 | return parameterGroupNumber; 32 | } 33 | 34 | CANLibCallback ParameterGroupNumberCallbackData::get_callback() const 35 | { 36 | return callback; 37 | } 38 | 39 | void *ParameterGroupNumberCallbackData::get_parent() const 40 | { 41 | return parent; 42 | } 43 | 44 | std::shared_ptr ParameterGroupNumberCallbackData::get_internal_control_function() const 45 | { 46 | return internalControlFunctionFilter; 47 | } 48 | } // namespace isobus 49 | -------------------------------------------------------------------------------- /src/can_callbacks.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_callbacks.hpp 3 | /// 4 | /// @brief An object to represent common callbacks used within this CAN stack. 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #ifndef CAN_CALLBACKS_HPP 11 | #define CAN_CALLBACKS_HPP 12 | 13 | #include 14 | #include "can_message.hpp" 15 | 16 | namespace isobus 17 | { 18 | // Forward declare some classes 19 | class InternalControlFunction; 20 | class ControlFunction; 21 | 22 | /// @brief The types of acknowledgement that can be sent in the Ack PGN 23 | enum class AcknowledgementType : std::uint8_t 24 | { 25 | Positive = 0, ///< "ACK" Indicates that the request was completed 26 | Negative = 1, ///< "NACK" Indicates the request was not completed or we do not support the PGN 27 | AccessDenied = 2, ///< Signals to the requestor that their CF is not allowed to request this PGN 28 | CannotRespond = 3 ///< Signals to the requestor that we are unable to accept the request for some reason 29 | }; 30 | 31 | /// @brief Enumerates the "online" states of a control function. 32 | enum class ControlFunctionState 33 | { 34 | Offline, ///< The CF's address claim state is not valid 35 | Online ///< The CF's address claim state is valid 36 | }; 37 | 38 | /// @brief A callback for control functions to get CAN messages 39 | using CANLibCallback = void (*)(const CANMessage &message, void *parentPointer); 40 | /// @brief A callback for communicating CAN messages 41 | using CANMessageCallback = std::function; 42 | /// @brief A callback for communicating CAN message frames 43 | using CANMessageFrameCallback = std::function sourceControlFunction, 46 | std::shared_ptr destinationControlFunction, 47 | CANIdentifier::CANPriority priority)>; ///< A callback for sending a CAN frame 48 | /// @brief A callback that can inform you when a control function changes state between online and offline 49 | using ControlFunctionStateCallback = void (*)(std::shared_ptr controlFunction, ControlFunctionState state); 50 | /// @brief A callback to get chunks of data for transfer by a protocol 51 | using DataChunkCallback = bool (*)(std::uint32_t callbackIndex, 52 | std::uint32_t bytesOffset, 53 | std::uint32_t numberOfBytesNeeded, 54 | std::uint8_t *chunkBuffer, 55 | void *parentPointer); 56 | /// @brief A callback for when a transmit is completed by the stack 57 | using TransmitCompleteCallback = void (*)(std::uint32_t parameterGroupNumber, 58 | std::uint32_t dataLength, 59 | std::shared_ptr sourceControlFunction, 60 | std::shared_ptr destinationControlFunction, 61 | bool successful, 62 | void *parentPointer); 63 | /// @brief A callback for handling a PGN request 64 | using PGNRequestCallback = bool (*)(std::uint32_t parameterGroupNumber, 65 | std::shared_ptr requestingControlFunction, 66 | bool &acknowledge, 67 | AcknowledgementType &acknowledgeType, 68 | void *parentPointer); 69 | /// @brief A callback for handling a request for repetition rate for a specific PGN 70 | using PGNRequestForRepetitionRateCallback = bool (*)(std::uint32_t parameterGroupNumber, 71 | std::shared_ptr requestingControlFunction, 72 | std::shared_ptr targetControlFunction, 73 | std::uint32_t repetitionRate, 74 | void *parentPointer); 75 | 76 | /// @brief A storage class to hold data about callbacks for a specific PGN 77 | class ParameterGroupNumberCallbackData 78 | { 79 | public: 80 | /// @brief A constructor for holding callback data 81 | /// @param[in] parameterGroupNumber The PGN you want to register a callback for 82 | /// @param[in] callback The function you want the stack to call when it gets receives a message with a matching PGN 83 | /// @param[in] parentPointer A generic variable that can provide context to which object the callback was meant for 84 | /// @param[in] internalControlFunction An internal control function to use as an additional filter for the callback 85 | ParameterGroupNumberCallbackData(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parentPointer, std::shared_ptr internalControlFunction); 86 | 87 | /// @brief Equality operator for this class 88 | /// @param[in] obj The object to check equality against 89 | /// @returns true if the objects have equivalent data 90 | bool operator==(const ParameterGroupNumberCallbackData &obj) const; 91 | 92 | /// @brief Returns the PGN associated with this callback data 93 | /// @returns The PGN associated with this callback data 94 | std::uint32_t get_parameter_group_number() const; 95 | 96 | /// @brief Returns the callback pointer for this data object 97 | /// @returns The callback pointer for this data object 98 | CANLibCallback get_callback() const; 99 | 100 | /// @brief Returns the parent pointer for this data object 101 | /// @returns The parent pointer for this data object 102 | void *get_parent() const; 103 | 104 | /// @brief Returns the ICF being used as a filter for this callback 105 | /// @returns A pointer to the ICF being used as a filter, or nullptr 106 | std::shared_ptr get_internal_control_function() const; 107 | 108 | private: 109 | CANLibCallback callback; ///< The callback that will get called when a matching PGN is received 110 | std::uint32_t parameterGroupNumber; ///< The PGN assocuiated with this callback 111 | void *parent; ///< A generic variable that can provide context to which object the callback was meant for 112 | std::shared_ptr internalControlFunctionFilter; ///< An optional way to filter callbacks based on the destination of messages from the partner 113 | }; 114 | } // namespace isobus 115 | 116 | #endif // CAN_CALLBACKS_HPP 117 | -------------------------------------------------------------------------------- /src/can_constants.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_constants.hpp 3 | /// 4 | /// @brief General constants used throughout this library 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #ifndef CAN_CONSTANTS_HPP 10 | #define CAN_CONSTANTS_HPP 11 | 12 | #include 13 | 14 | namespace isobus 15 | { 16 | constexpr std::uint64_t DEFAULT_NAME = 0xFFFFFFFFFFFFFFFF; ///< An invalid NAME used as a default 17 | constexpr std::uint32_t DEFAULT_IDENTIFIER = 0xFFFFFFFF; ///< An invalid identifier used as a default 18 | constexpr std::uint8_t NULL_CAN_ADDRESS = 0xFE; ///< The NULL CAN address defined by J1939 and ISO11783 19 | constexpr std::uint8_t BROADCAST_CAN_ADDRESS = 0xFF; ///< The global/broadcast CAN address 20 | constexpr std::uint8_t CAN_DATA_LENGTH = 8; ///< The length of a classical CAN frame 21 | constexpr std::uint32_t CAN_PORT_MAXIMUM = 4; ///< An arbitrary limit for memory consumption 22 | constexpr std::uint16_t NULL_OBJECT_ID = 65535; ///< Special ID used to indicate no object 23 | 24 | } 25 | 26 | #endif // CAN_CONSTANTS_HPP 27 | -------------------------------------------------------------------------------- /src/can_control_function.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_control_function.cpp 3 | /// 4 | /// @brief Defines a base class to represent a generic ISOBUS control function. 5 | /// @author Adrian Del Grosso 6 | /// @author Daan Steenbergen 7 | /// 8 | /// @copyright 2022 The Open-Agriculture Developers 9 | //================================================================================================ 10 | 11 | #include "can_control_function.hpp" 12 | 13 | #include "can_constants.hpp" 14 | 15 | #include 16 | 17 | namespace isobus 18 | { 19 | Mutex ControlFunction::controlFunctionProcessingMutex; 20 | 21 | isobus::ControlFunction::ControlFunction(NAME NAMEValue, std::uint8_t addressValue, std::uint8_t CANPort, Type type) : 22 | controlFunctionType(type), 23 | controlFunctionNAME(NAMEValue), 24 | address(addressValue), 25 | canPortIndex(CANPort) 26 | { 27 | } 28 | 29 | std::uint8_t ControlFunction::get_address() const 30 | { 31 | return address; 32 | } 33 | 34 | bool ControlFunction::get_address_valid() const 35 | { 36 | return ((BROADCAST_CAN_ADDRESS != address) && (NULL_CAN_ADDRESS != address)); 37 | } 38 | 39 | std::uint8_t ControlFunction::get_can_port() const 40 | { 41 | return canPortIndex; 42 | } 43 | 44 | NAME ControlFunction::get_NAME() const 45 | { 46 | return controlFunctionNAME; 47 | } 48 | 49 | ControlFunction::Type ControlFunction::get_type() const 50 | { 51 | return controlFunctionType; 52 | } 53 | 54 | std::string ControlFunction::get_type_string() const 55 | { 56 | switch (controlFunctionType) 57 | { 58 | case Type::Internal: 59 | return "Internal"; 60 | case Type::External: 61 | return "External"; 62 | case Type::Partnered: 63 | return "Partnered"; 64 | default: 65 | return "Unknown"; 66 | } 67 | } 68 | 69 | } // namespace isobus 70 | -------------------------------------------------------------------------------- /src/can_control_function.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_control_function.hpp 3 | /// 4 | /// @brief Defines a base class to represent a generic ISOBUS control function. 5 | /// @author Adrian Del Grosso 6 | /// @author Daan Steenbergen 7 | /// 8 | /// @copyright 2024 The Open-Agriculture Developers 9 | //================================================================================================ 10 | 11 | #ifndef CAN_CONTROL_FUNCTION_HPP 12 | #define CAN_CONTROL_FUNCTION_HPP 13 | 14 | #include "can_NAME.hpp" 15 | #include "thread_synchronization.hpp" 16 | 17 | #include 18 | #include 19 | 20 | namespace isobus 21 | { 22 | /// @brief A class that describes an ISO11783 control function, which includes a NAME and address. 23 | class ControlFunction 24 | { 25 | public: 26 | /// @brief The type of the control function 27 | enum class Type 28 | { 29 | Internal, ///< The control function is part of our stack and can address claim 30 | External, ///< The control function is some other device on the bus 31 | Partnered ///< An external control function that you explicitly want to talk to 32 | }; 33 | 34 | /// @brief The constructor of a control function. In most cases use `CANNetworkManager::create_internal_control_function()` or 35 | /// `CANNetworkManager::create_partnered_control_function()` instead, only use this constructor if you have advanced needs. 36 | /// @param[in] NAMEValue The NAME of the control function 37 | /// @param[in] addressValue The current address of the control function 38 | /// @param[in] CANPort The CAN channel index that the control function communicates on 39 | /// @param[in] type The 'Type' of control function to create 40 | ControlFunction(NAME NAMEValue, std::uint8_t addressValue, std::uint8_t CANPort, Type type = Type::External); 41 | 42 | virtual ~ControlFunction() = default; 43 | 44 | /// @brief Returns the current address of the control function 45 | /// @returns The current address of the control function 46 | std::uint8_t get_address() const; 47 | 48 | /// @brief Describes if the control function has a valid address (not NULL or global) 49 | /// @returns true if the address is < 0xFE 50 | bool get_address_valid() const; 51 | 52 | /// @brief Returns the CAN channel index the control function communicates on 53 | /// @returns The control function's CAN channel index 54 | std::uint8_t get_can_port() const; 55 | 56 | /// @brief Returns the NAME of the control function as described by its address claim message 57 | /// @returns The control function's NAME 58 | NAME get_NAME() const; 59 | 60 | /// @brief Returns the `Type` of the control function 61 | /// @returns The control function type 62 | Type get_type() const; 63 | 64 | ///@brief Returns the 'Type' of the control function as a string 65 | ///@returns The control function type as a string 66 | std::string get_type_string() const; 67 | 68 | protected: 69 | friend class CANNetworkManager; ///< The network manager needs access to the control function's internals 70 | static Mutex controlFunctionProcessingMutex; ///< Protects the control function tables 71 | const Type controlFunctionType; ///< The Type of the control function 72 | NAME controlFunctionNAME; ///< The NAME of the control function 73 | bool claimedAddressSinceLastAddressClaimRequest = false; ///< Used to mark CFs as stale if they don't claim within a certain time 74 | std::uint8_t address; ///< The address of the control function 75 | const std::uint8_t canPortIndex; ///< The CAN channel index of the control function 76 | }; 77 | 78 | } // namespace isobus 79 | 80 | #endif // CAN_CONTROL_FUNCTION_HPP 81 | -------------------------------------------------------------------------------- /src/can_general_parameter_group_numbers.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_general_parameter_group_numbers.hpp 3 | /// 4 | /// @brief Defines some PGNs that are used in the library or are very common 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #ifndef CAN_GENERAL_PARAMETER_GROUP_NUMBERS_HPP 11 | #define CAN_GENERAL_PARAMETER_GROUP_NUMBERS_HPP 12 | 13 | namespace isobus 14 | { 15 | /// @brief PGNs commonly used by the CAN stack 16 | enum class CANLibParameterGroupNumber 17 | { 18 | Any = 0x0000, 19 | AgriculturalGuidanceMachineInfo = 0xAC00, 20 | AgriculturalGuidanceSystemCommand = 0xAD00, 21 | DiagnosticMessage22 = 0xC300, 22 | ExtendedTransportProtocolDataTransfer = 0xC700, 23 | ExtendedTransportProtocolConnectionManagement = 0xC800, 24 | ProcessData = 0xCB00, 25 | RequestForRepetitionRate = 0xCC00, 26 | DiagnosticMessage13 = 0xDF00, 27 | VirtualTerminalToECU = 0xE600, 28 | ECUtoVirtualTerminal = 0xE700, 29 | Acknowledge = 0xE800, 30 | ParameterGroupNumberRequest = 0xEA00, 31 | TransportProtocolDataTransfer = 0xEB00, 32 | TransportProtocolConnectionManagement = 0xEC00, 33 | AddressClaim = 0xEE00, 34 | ProprietaryA = 0xEF00, 35 | MachineSelectedSpeed = 0xF022, 36 | HeartbeatMessage = 0xF0E4, 37 | ProductIdentification = 0xFC8D, 38 | ControlFunctionFunctionalities = 0xFC8E, 39 | DiagnosticProtocolIdentification = 0xFD32, 40 | MachineSelectedSpeedCommand = 0xFD43, 41 | WorkingSetMaster = 0xFE0D, 42 | LanguageCommand = 0xFE0F, 43 | MaintainPower = 0xFE47, 44 | WheelBasedSpeedAndDistance = 0xFE48, 45 | GroundBasedSpeedAndDistance = 0xFE49, 46 | ECUIdentificationInformation = 0xFDC5, 47 | DiagnosticMessage1 = 0xFECA, 48 | DiagnosticMessage2 = 0xFECB, 49 | DiagnosticMessage3 = 0xFECC, 50 | DiagnosticMessage11 = 0xFED3, 51 | CommandedAddress = 0xFED8, 52 | SoftwareIdentification = 0xFEDA, 53 | AllImplementsStopOperationsSwitchState = 0xFD02, 54 | TimeDate = 0xFEE6, 55 | VesselHeading = 0x1F112, 56 | RateOfTurn = 0x1F113, 57 | PositionRapidUpdate = 0x1F801, 58 | CourseOverGroundSpeedOverGroundRapidUpdate = 0x1F802, 59 | PositionDeltaHighPrecisionRapidUpdate = 0x1F803, 60 | GNSSPositionData = 0x1F805, 61 | Datum = 0x1F814 62 | }; 63 | 64 | } // namespace isobus 65 | 66 | #endif // CAN_GENERAL_PARAMETER_GROUP_NUMBERS_HPP 67 | -------------------------------------------------------------------------------- /src/can_hardware_abstraction.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_hardware_abstraction.hpp 3 | /// 4 | /// @brief An abstraction between this CAN stack and any hardware layer 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #ifndef CAN_HARDWARE_ABSTRACTION_HPP 11 | #define CAN_HARDWARE_ABSTRACTION_HPP 12 | 13 | #include "can_message_frame.hpp" 14 | 15 | #include 16 | 17 | namespace isobus 18 | { 19 | /// @brief The sending abstraction layer between the hardware and the stack 20 | /// @param[in] frame The frame to transmit from the hardware 21 | /// @returns true if the frame was successfully sent, false otherwise 22 | bool send_can_message_frame_to_hardware(const CANMessageFrame &frame); 23 | 24 | /// @brief The receiving abstraction layer between the hardware and the stack 25 | /// @param[in] frame The frame to receive from the hardware 26 | void receive_can_message_frame_from_hardware(const CANMessageFrame &frame); 27 | 28 | /// @brief Informs the network manager whenever messages are emitted on the bus 29 | /// @param[in] txFrame The CAN frame that was just emitted 30 | void on_transmit_can_message_frame_from_hardware(const CANMessageFrame &txFrame); 31 | 32 | /// @brief The periodic update abstraction layer between the hardware and the stack 33 | void periodic_update_from_hardware(); 34 | 35 | } // namespace isobus 36 | 37 | #endif // CAN_HARDWARE_ABSTRACTION_HPP 38 | -------------------------------------------------------------------------------- /src/can_hardware_interface_single_thread.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_hardware_interface_single_thread.cpp 3 | /// 4 | /// @brief The hardware abstraction layer that separates the stack from the underlying CAN driver 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2023 Adrian Del Grosso 8 | //================================================================================================ 9 | #include "can_hardware_interface_single_thread.hpp" 10 | #include "can_stack_logger.hpp" 11 | #include "system_timing.hpp" 12 | #include "to_string.hpp" 13 | 14 | #include 15 | #include 16 | 17 | namespace isobus 18 | { 19 | isobus::EventDispatcher CANHardwareInterface::frameReceivedEventDispatcher; 20 | isobus::EventDispatcher CANHardwareInterface::frameTransmittedEventDispatcher; 21 | 22 | std::vector> CANHardwareInterface::hardwareChannels; 23 | bool CANHardwareInterface::started = false; 24 | 25 | CANHardwareInterface CANHardwareInterface::SINGLETON; 26 | 27 | bool send_can_message_frame_to_hardware(const CANMessageFrame &frame) 28 | { 29 | return CANHardwareInterface::transmit_can_frame(frame); 30 | } 31 | 32 | bool CANHardwareInterface::set_number_of_can_channels(std::uint8_t value) 33 | { 34 | if (started) 35 | { 36 | isobus::CANStackLogger::error("[HardwareInterface] Cannot set number of channels after interface is started."); 37 | return false; 38 | } 39 | 40 | while (value > hardwareChannels.size()) 41 | { 42 | hardwareChannels.emplace_back(new CANHardware()); 43 | hardwareChannels.back()->frameHandler = nullptr; 44 | } 45 | while (value < hardwareChannels.size()) 46 | { 47 | hardwareChannels.pop_back(); 48 | } 49 | return true; 50 | } 51 | 52 | bool CANHardwareInterface::assign_can_channel_frame_handler(std::uint8_t channelIndex, std::shared_ptr driver) 53 | { 54 | if (started) 55 | { 56 | isobus::CANStackLogger::error("[HardwareInterface] Cannot assign frame handlers after interface is started."); 57 | return false; 58 | } 59 | 60 | if (channelIndex >= hardwareChannels.size()) 61 | { 62 | isobus::CANStackLogger::error("[HardwareInterface] Unable to set frame handler at channel " + isobus::to_string(channelIndex) + 63 | ", because there are only " + isobus::to_string(hardwareChannels.size()) + " channels set. " + 64 | "Use set_number_of_can_channels() to increase the number of channels before assigning frame handlers."); 65 | return false; 66 | } 67 | 68 | if (nullptr != hardwareChannels[channelIndex]->frameHandler) 69 | { 70 | isobus::CANStackLogger::error("[HardwareInterface] Unable to set frame handler at channel " + isobus::to_string(channelIndex) + ", because it is already assigned."); 71 | return false; 72 | } 73 | 74 | hardwareChannels[channelIndex]->frameHandler = driver; 75 | return true; 76 | } 77 | 78 | std::uint8_t CANHardwareInterface::get_number_of_can_channels() 79 | { 80 | return static_cast(hardwareChannels.size() & std::numeric_limits::max()); 81 | } 82 | 83 | bool CANHardwareInterface::unassign_can_channel_frame_handler(std::uint8_t channelIndex) 84 | { 85 | if (started) 86 | { 87 | isobus::CANStackLogger::error("[HardwareInterface] Cannot remove frame handlers after interface is started."); 88 | return false; 89 | } 90 | 91 | if (channelIndex >= hardwareChannels.size()) 92 | { 93 | isobus::CANStackLogger::error("[HardwareInterface] Unable to remove frame handler at channel " + isobus::to_string(channelIndex) + 94 | ", because there are only " + isobus::to_string(hardwareChannels.size()) + " channels set."); 95 | return false; 96 | } 97 | 98 | if (nullptr == hardwareChannels[channelIndex]->frameHandler) 99 | { 100 | isobus::CANStackLogger::error("[HardwareInterface] Unable to remove frame handler at channel " + isobus::to_string(channelIndex) + ", because it is not assigned."); 101 | return false; 102 | } 103 | 104 | hardwareChannels[channelIndex]->frameHandler = nullptr; 105 | return true; 106 | } 107 | 108 | bool CANHardwareInterface::start() 109 | { 110 | if (started) 111 | { 112 | isobus::CANStackLogger::error("[HardwareInterface] Cannot start interface more than once."); 113 | return false; 114 | } 115 | 116 | started = true; 117 | 118 | for (std::size_t i = 0; i < hardwareChannels.size(); i++) 119 | { 120 | if (nullptr != hardwareChannels[i]->frameHandler) 121 | { 122 | hardwareChannels[i]->frameHandler->open(); 123 | } 124 | } 125 | 126 | return true; 127 | } 128 | 129 | bool CANHardwareInterface::stop() 130 | { 131 | if (!started) 132 | { 133 | isobus::CANStackLogger::error("[HardwareInterface] Cannot stop interface before it is started."); 134 | return false; 135 | } 136 | 137 | std::for_each(hardwareChannels.begin(), hardwareChannels.end(), [](const std::unique_ptr &channel) { 138 | if (nullptr != channel->frameHandler) 139 | { 140 | channel->frameHandler = nullptr; 141 | } 142 | channel->messagesToBeTransmitted.clear(); 143 | channel->receivedMessages.clear(); 144 | }); 145 | return true; 146 | } 147 | bool CANHardwareInterface::is_running() 148 | { 149 | return started; 150 | } 151 | 152 | bool CANHardwareInterface::transmit_can_frame(const isobus::CANMessageFrame &frame) 153 | { 154 | if (!started) 155 | { 156 | isobus::CANStackLogger::error("[HardwareInterface] Cannot transmit message before interface is started."); 157 | return false; 158 | } 159 | 160 | if (frame.channel >= hardwareChannels.size()) 161 | { 162 | isobus::CANStackLogger::error("[HardwareInterface] Cannot transmit message on channel %u, because there are only %u channels set.", frame.channel, hardwareChannels.size()); 163 | return false; 164 | } 165 | 166 | const std::unique_ptr &channel = hardwareChannels[frame.channel]; 167 | if (nullptr == channel->frameHandler) 168 | { 169 | isobus::CANStackLogger::error("[HardwareInterface] Cannot transmit message on channel %u, because it is not assigned.", frame.channel); 170 | return false; 171 | } 172 | 173 | if (channel->frameHandler->get_is_valid()) 174 | { 175 | channel->messagesToBeTransmitted.push_back(frame); 176 | return true; 177 | } 178 | return false; 179 | } 180 | 181 | isobus::EventDispatcher &CANHardwareInterface::get_can_frame_received_event_dispatcher() 182 | { 183 | return frameReceivedEventDispatcher; 184 | } 185 | 186 | isobus::EventDispatcher &CANHardwareInterface::get_can_frame_transmitted_event_dispatcher() 187 | { 188 | return frameTransmittedEventDispatcher; 189 | } 190 | 191 | void CANHardwareInterface::update() 192 | { 193 | if (started) 194 | { 195 | // Stage 1 - Receiving messages from hardware 196 | for (std::size_t i = 0; i < hardwareChannels.size(); i++) 197 | { 198 | receive_can_frame(i); 199 | while (!hardwareChannels[i]->receivedMessages.empty()) 200 | { 201 | const auto &frame = hardwareChannels[i]->receivedMessages.front(); 202 | 203 | frameReceivedEventDispatcher.invoke(frame); 204 | isobus::receive_can_message_frame_from_hardware(frame); 205 | 206 | hardwareChannels[i]->receivedMessages.pop_front(); 207 | } 208 | } 209 | 210 | // Stage 2 - Sending messages 211 | isobus::periodic_update_from_hardware(); 212 | 213 | // Stage 3 - Transmitting messages to hardware 214 | std::for_each(hardwareChannels.begin(), hardwareChannels.end(), [](const std::unique_ptr &channel) { 215 | while (!channel->messagesToBeTransmitted.empty()) 216 | { 217 | const auto &frame = channel->messagesToBeTransmitted.front(); 218 | 219 | if (transmit_can_frame_from_buffer(frame)) 220 | { 221 | frameTransmittedEventDispatcher.invoke(frame); 222 | isobus::on_transmit_can_message_frame_from_hardware(frame); 223 | channel->messagesToBeTransmitted.pop_front(); 224 | } 225 | else 226 | { 227 | break; 228 | } 229 | } 230 | }); 231 | } 232 | } 233 | 234 | void CANHardwareInterface::receive_can_frame(std::uint8_t channelIndex) 235 | { 236 | isobus::CANMessageFrame frame; 237 | if (started && 238 | (nullptr != hardwareChannels[channelIndex]->frameHandler)) 239 | { 240 | if (hardwareChannels[channelIndex]->frameHandler->get_is_valid()) 241 | { 242 | // Socket or other hardware still open 243 | if (hardwareChannels[channelIndex]->frameHandler->read_frame(frame)) 244 | { 245 | frame.channel = channelIndex; 246 | hardwareChannels[channelIndex]->receivedMessages.push_back(frame); 247 | } 248 | } 249 | else 250 | { 251 | isobus::CANStackLogger::critical("[CAN Rx Thread]: CAN Channel " + isobus::to_string(channelIndex) + " appears to be invalid."); 252 | } 253 | } 254 | } 255 | 256 | bool CANHardwareInterface::transmit_can_frame_from_buffer(const isobus::CANMessageFrame &frame) 257 | { 258 | bool retVal = false; 259 | if (frame.channel < hardwareChannels.size()) 260 | { 261 | retVal = ((nullptr != hardwareChannels[frame.channel]->frameHandler) && 262 | (hardwareChannels[frame.channel]->frameHandler->write_frame(frame))); 263 | } 264 | return retVal; 265 | } 266 | } // namespace isobus 267 | -------------------------------------------------------------------------------- /src/can_hardware_interface_single_thread.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_hardware_interface_single_thread.hpp 3 | /// 4 | /// @brief The hardware abstraction layer that separates the stack from the underlying CAN driver 5 | /// @note This version of the CAN Hardware Interface is meant for systems without multi-threading. 6 | /// @author Adrian Del Grosso 7 | /// 8 | /// @copyright 2023 Adrian Del Grosso 9 | //================================================================================================ 10 | #ifndef CAN_HARDWARE_INTERFACE_SINGLE_THREAD_HPP 11 | #define CAN_HARDWARE_INTERFACE_SINGLE_THREAD_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "can_hardware_plugin.hpp" 19 | #include "can_hardware_abstraction.hpp" 20 | #include "can_message_frame.hpp" 21 | #include "event_dispatcher.hpp" 22 | 23 | namespace isobus 24 | { 25 | //================================================================================================ 26 | /// @class CANHardwareInterface 27 | /// 28 | /// @brief Provides a common queuing layer for running the CAN stack and all CAN drivers and is 29 | /// meant for systems that are single threaded or do not support std::thread and its friends. 30 | /// 31 | /// @details The `CANHardwareInterface` class was created to provide a common queuing 32 | /// layer for running the CAN stack and all CAN drivers to simplify integration and crucially to 33 | /// provide a consistent, safe order of operations for all the function calls needed to properly 34 | /// drive the stack. 35 | //================================================================================================ 36 | class CANHardwareInterface 37 | { 38 | public: 39 | /// @brief Returns the number of configured CAN channels that the class is managing 40 | /// @returns The number of configured CAN channels that the class is managing 41 | static std::uint8_t get_number_of_can_channels(); 42 | 43 | /// @brief Sets the number of CAN channels to manage 44 | /// @details Allocates the proper number of `CanHardware` objects to track 45 | /// each CAN channel's Tx and Rx message queues. If you pass in a smaller number than what was 46 | /// already configured, it will delete the unneeded `CanHardware` objects. 47 | /// @note The function will fail if the channel is already assigned to a driver or the interface is already started 48 | /// @param value The number of CAN channels to manage 49 | /// @returns `true` if the channel count was set, otherwise `false`. 50 | static bool set_number_of_can_channels(std::uint8_t value); 51 | 52 | /// @brief Assigns a CAN driver to a channel 53 | /// @param[in] channelIndex The channel to assign to 54 | /// @param[in] canDriver The driver to assign to the channel 55 | /// @note The function will fail if the channel is already assigned to a driver or the interface is already started 56 | /// @returns `true` if the driver was assigned to the channel, otherwise `false` 57 | static bool assign_can_channel_frame_handler(std::uint8_t channelIndex, std::shared_ptr canDriver); 58 | 59 | /// @brief Removes a CAN driver from a channel 60 | /// @param[in] channelIndex The channel to remove the driver from 61 | /// @note The function will fail if the channel is already assigned to a driver or the interface is already started 62 | /// @returns `true` if the driver was removed from the channel, otherwise `false` 63 | static bool unassign_can_channel_frame_handler(std::uint8_t channelIndex); 64 | 65 | /// @brief Initializes the hardware interface 66 | /// @returns `true` if the interface was initialized, otherwise false (perhaps you already called start) 67 | static bool start(); 68 | 69 | /// @brief Cleans up and discards all remaining messages in the Tx and Rx queues. 70 | /// @returns `true` if the interface was stopped, otherwise `false` 71 | static bool stop(); 72 | 73 | /// @brief Checks if the CAN stack and CAN drivers are running 74 | /// @returns `true` if the threads are running, otherwise `false` 75 | static bool is_running(); 76 | 77 | /// @brief Called externally, adds a message to a CAN channel's Tx queue 78 | /// @param[in] frame The frame to add to the Tx queue 79 | /// @returns `true` if the frame was accepted, otherwise `false` (maybe wrong channel assigned) 80 | static bool transmit_can_frame(const isobus::CANMessageFrame &frame); 81 | 82 | /// @brief Get the event dispatcher for when a CAN message frame is received from hardware event 83 | /// @returns The event dispatcher which can be used to register callbacks/listeners to 84 | static isobus::EventDispatcher &get_can_frame_received_event_dispatcher(); 85 | 86 | /// @brief Get the event dispatcher for when a CAN message frame will be send to hardware event 87 | /// @returns The event dispatcher which can be used to register callbacks/listeners to 88 | static isobus::EventDispatcher &get_can_frame_transmitted_event_dispatcher(); 89 | 90 | /// @brief Call this periodically. Does most of the work of this class. 91 | /// @note You must call this very often, such as least every millisecond to ensure CAN messages get retrieved from the hardware 92 | static void update(); 93 | 94 | private: 95 | /// @brief Stores the Tx/Rx queues, mutexes, and driver needed to run a single CAN channel 96 | struct CANHardware 97 | { 98 | std::deque messagesToBeTransmitted; ///< Tx message queue for a CAN channel 99 | std::deque receivedMessages; ///< Rx message queue for a CAN channel 100 | std::shared_ptr frameHandler; ///< The CAN driver to use for a CAN channel 101 | }; 102 | 103 | /// @brief Singleton instance of the CANHardwareInterface class 104 | /// @details This is a little hack that allows to have the destructor called 105 | static CANHardwareInterface SINGLETON; 106 | 107 | /// @brief Constructor for the CANHardwareInterface class 108 | CANHardwareInterface() = default; 109 | 110 | /// @brief Destructor for the CANHardwareInterface class 111 | ~CANHardwareInterface() = default; 112 | 113 | /// @brief The receive thread(s) execute this function 114 | /// @param[in] channelIndex The associated CAN channel for the thread 115 | static void receive_can_frame(std::uint8_t channelIndex); 116 | 117 | /// @brief Attempts to write a frame using the driver assigned to a frame's channel 118 | /// @param[in] frame The frame to try and write to the bus 119 | /// @returns `true` if the frame was transmitted, otherwise `false` 120 | static bool transmit_can_frame_from_buffer(const isobus::CANMessageFrame &frame); 121 | 122 | static isobus::EventDispatcher frameReceivedEventDispatcher; ///< The event dispatcher for when a CAN message frame is received from hardware event 123 | static isobus::EventDispatcher frameTransmittedEventDispatcher; ///< The event dispatcher for when a CAN message has been transmitted via hardware 124 | 125 | static std::vector> hardwareChannels; ///< A list of all CAN channel's metadata 126 | static bool started; ///< Stores if the threads have been started 127 | }; 128 | } 129 | #endif // CAN_HARDWARE_INTERFACE_SINGLE_THREAD_HPP 130 | -------------------------------------------------------------------------------- /src/can_hardware_plugin.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_hardware_plugin.hpp 3 | /// 4 | /// @brief A base class for a CAN driver. Can be derived into your platform's required interface. 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #ifndef CAN_HARDEWARE_PLUGIN_HPP 10 | #define CAN_HARDEWARE_PLUGIN_HPP 11 | 12 | #include "can_message_frame.hpp" 13 | 14 | namespace isobus 15 | { 16 | //================================================================================================ 17 | /// @class CANHardwarePlugin 18 | /// 19 | /// @brief An abstract base class for a CAN driver 20 | //================================================================================================ 21 | class CANHardwarePlugin 22 | { 23 | public: 24 | /// @brief Returns if the driver is ready and in a good state 25 | /// @details This should return `false` until `open` is called, and after `close` is called, or 26 | /// if anything happens that causes the driver to be invalid, like the hardware is disconnected. 27 | /// @returns `true` if the driver is good/connected, `false` if the driver is not usable 28 | virtual bool get_is_valid() const = 0; 29 | 30 | /// @brief Disconnects the driver from the hardware. 31 | virtual void close() = 0; 32 | 33 | /// @brief Connects the driver to the hardware. This will be called to initialize the driver 34 | /// and connect it to the hardware. 35 | virtual void open() = 0; 36 | 37 | /// @brief Reads one frame from the bus synchronously 38 | /// @param[in, out] canFrame The CAN frame that was read 39 | /// @returns `true` if a CAN frame was read, otherwise `false` 40 | virtual bool read_frame(isobus::CANMessageFrame &canFrame) = 0; 41 | 42 | /// @brief Writes a frame to the bus (synchronous) 43 | /// @param[in] canFrame The frame to write to the bus 44 | /// @returns `true` if the frame was written, otherwise `false` 45 | virtual bool write_frame(const isobus::CANMessageFrame &canFrame) = 0; 46 | }; 47 | } 48 | #endif // CAN_HARDEWARE_PLUGIN_HPP 49 | -------------------------------------------------------------------------------- /src/can_identifier.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_identifier.cpp 3 | /// 4 | /// @brief A representation of a classical CAN identifier with utility functions for ectracting 5 | /// values that are encoded inside, along with some helpful constants. 6 | /// @author Adrian Del Grosso 7 | /// 8 | /// @copyright 2022 The Open-Agriculture Developers 9 | //================================================================================================ 10 | #include "can_identifier.hpp" 11 | 12 | namespace isobus 13 | { 14 | CANIdentifier::CANIdentifier(std::uint32_t rawIdentifierData) : 15 | m_RawIdentifier(rawIdentifierData) 16 | { 17 | } 18 | 19 | CANIdentifier::CANIdentifier(Type identifierType, 20 | std::uint32_t pgn, 21 | CANPriority priority, 22 | std::uint8_t destinationAddress, 23 | std::uint8_t sourceAddress) : 24 | m_RawIdentifier(0) 25 | { 26 | if (Type::Extended == identifierType) 27 | { 28 | m_RawIdentifier = (static_cast(priority) << PRIORITY_DATA_BIT_OFFSET); 29 | 30 | if (((pgn << PARAMTER_GROUP_NUMBER_OFFSET) & PDU2_FORMAT_MASK) < PDU2_FORMAT_MASK) 31 | { 32 | pgn = (pgn & DESTINATION_SPECIFIC_PGN_MASK); 33 | pgn = (pgn << PARAMTER_GROUP_NUMBER_OFFSET); 34 | m_RawIdentifier |= pgn; 35 | m_RawIdentifier |= (static_cast(destinationAddress) << PARAMTER_GROUP_NUMBER_OFFSET); 36 | } 37 | else 38 | { 39 | m_RawIdentifier |= ((pgn & BROADCAST_PGN_MASK) << PARAMTER_GROUP_NUMBER_OFFSET); 40 | } 41 | } 42 | m_RawIdentifier |= static_cast(sourceAddress); 43 | } 44 | 45 | CANIdentifier::CANPriority CANIdentifier::get_priority() const 46 | { 47 | const std::uint8_t EXTENDED_IDENTIFIER_MASK = 0x07; 48 | 49 | CANPriority retVal; 50 | 51 | if (Type::Extended == get_identifier_type()) 52 | { 53 | retVal = static_cast((m_RawIdentifier >> PRIORITY_DATA_BIT_OFFSET) & EXTENDED_IDENTIFIER_MASK); 54 | } 55 | else 56 | { 57 | retVal = CANPriority::PriorityHighest0; 58 | } 59 | return retVal; 60 | } 61 | 62 | std::uint32_t CANIdentifier::get_identifier() const 63 | { 64 | return (m_RawIdentifier & (~IDENTIFIER_TYPE_BIT_MASK)); 65 | } 66 | 67 | CANIdentifier::Type CANIdentifier::get_identifier_type() const 68 | { 69 | const std::uint32_t STANDARD_ID_11_BIT_SIZE = 0x7FF; 70 | Type retVal; 71 | 72 | if (m_RawIdentifier <= STANDARD_ID_11_BIT_SIZE) 73 | { 74 | retVal = Type::Standard; 75 | } 76 | else 77 | { 78 | retVal = Type::Extended; 79 | } 80 | return retVal; 81 | } 82 | 83 | std::uint32_t CANIdentifier::get_parameter_group_number() const 84 | { 85 | std::uint32_t retVal = UNDEFINED_PARAMETER_GROUP_NUMBER; 86 | 87 | if (Type::Extended == get_identifier_type()) 88 | { 89 | if ((PDU2_FORMAT_MASK & m_RawIdentifier) < PDU2_FORMAT_MASK) 90 | { 91 | retVal = ((m_RawIdentifier >> PARAMTER_GROUP_NUMBER_OFFSET) & DESTINATION_SPECIFIC_PGN_MASK); 92 | } 93 | else 94 | { 95 | retVal = ((m_RawIdentifier >> PARAMTER_GROUP_NUMBER_OFFSET) & BROADCAST_PGN_MASK); 96 | } 97 | } 98 | return retVal; 99 | } 100 | 101 | std::uint8_t CANIdentifier::get_destination_address() const 102 | { 103 | const std::uint8_t ADDRESS_BITS_SIZE = 0xFF; 104 | const std::uint8_t ADDRESS_DATA_OFFSET = 8; 105 | 106 | std::uint8_t retVal = GLOBAL_ADDRESS; 107 | 108 | if ((CANIdentifier::Type::Extended == get_identifier_type()) && 109 | ((PDU2_FORMAT_MASK & m_RawIdentifier) < PDU2_FORMAT_MASK)) 110 | { 111 | retVal = ((m_RawIdentifier >> ADDRESS_DATA_OFFSET) & ADDRESS_BITS_SIZE); 112 | } 113 | return retVal; 114 | } 115 | 116 | std::uint8_t CANIdentifier::get_source_address() const 117 | { 118 | const std::uint8_t ADDRESS_BITS_SIZE = 0xFF; 119 | std::uint8_t retVal = CANIdentifier::GLOBAL_ADDRESS; 120 | 121 | if (CANIdentifier::Type::Extended == get_identifier_type()) 122 | { 123 | retVal = (m_RawIdentifier & ADDRESS_BITS_SIZE); 124 | } 125 | return retVal; 126 | } 127 | 128 | bool CANIdentifier::get_is_valid() const 129 | { 130 | const std::uint32_t EXTENDED_ID_29_BIT_SIZE = 0x1FFFFFFF; 131 | const std::uint32_t STANDARD_ID_11_BIT_SIZE = 0x7FF; 132 | bool retVal; 133 | 134 | if (Type::Extended == get_identifier_type()) 135 | { 136 | retVal = (m_RawIdentifier <= EXTENDED_ID_29_BIT_SIZE); 137 | } 138 | else 139 | { 140 | retVal = (m_RawIdentifier <= STANDARD_ID_11_BIT_SIZE); 141 | } 142 | return retVal; 143 | } 144 | 145 | } // namespace isobus 146 | -------------------------------------------------------------------------------- /src/can_identifier.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_identifier.hpp 3 | /// 4 | /// @brief A representation of a classical CAN identifier with utility functions for ectracting 5 | /// values that are encoded inside, along with some helpful constants. 6 | /// @author Adrian Del Grosso 7 | /// 8 | /// @copyright 2022 The Open-Agriculture Developers 9 | //================================================================================================ 10 | 11 | #ifndef CAN_IDENTIFIER_HPP 12 | #define CAN_IDENTIFIER_HPP 13 | 14 | #include 15 | 16 | namespace isobus 17 | { 18 | //================================================================================================ 19 | /// @class CANIdentifier 20 | /// 21 | /// @brief A utility class that allows easy interpretation of a 32 bit CAN identifier 22 | //================================================================================================ 23 | class CANIdentifier 24 | { 25 | public: 26 | /// @brief Defines all the CAN frame priorities that can be encoded in a frame ID 27 | enum class CANPriority 28 | { 29 | PriorityHighest0 = 0, ///< Highest CAN priority 30 | Priority1 = 1, ///< Priority highest - 1 31 | Priority2 = 2, ///< Priority highest - 2 32 | Priority3 = 3, ///< Priority highest - 3 (Control messages priority) 33 | Priority4 = 4, ///< Priority highest - 4 34 | Priority5 = 5, ///< Priority highest - 5 35 | PriorityDefault6 = 6, ///< The default priority 36 | PriorityLowest7 = 7 ///< The lowest priority 37 | }; 38 | 39 | /// @brief Defines if a frame is a standard (11 bit) or extended (29 bit) ID frame 40 | enum class Type 41 | { 42 | Standard = 0, ///< Frame is an 11bit ID standard (legacy) message with no PGN and highest priority 43 | Extended = 1 ///< Frame is a modern 29 bit ID CAN frame 44 | }; 45 | 46 | /// @brief Constructor for a CAN Identifier class based on a raw 32 bit ID 47 | /// @param[in] rawIdentifierData The raw 32 bit ID to interpret 48 | explicit CANIdentifier(std::uint32_t rawIdentifierData); 49 | 50 | /// @brief Constructor for a CAN Identifier class based on all discrete components 51 | /// @param[in] identifierType Type of frame, either standard 11 bit ID, or extended 29 bit ID 52 | /// @param[in] pgn The parameter group number encoded in the frame (extended only) 53 | /// @param[in] priority The priority of the frame (extended only) 54 | /// @param[in] destinationAddress The destination address of the frame 55 | /// @param[in] sourceAddress The source address of the frame 56 | CANIdentifier(Type identifierType, 57 | std::uint32_t pgn, 58 | CANPriority priority, 59 | std::uint8_t destinationAddress, 60 | std::uint8_t sourceAddress); 61 | 62 | /// @brief Destructor for the CANIdentifier 63 | ~CANIdentifier() = default; 64 | 65 | /// @brief Returns the raw encoded ID of the CAN identifier 66 | /// @returns The raw encoded ID of the CAN identifier 67 | std::uint32_t get_identifier() const; 68 | 69 | /// @brief Returns the identifier type (standard vs extended) 70 | /// @returns The identifier type (standard vs extended) 71 | Type get_identifier_type() const; 72 | 73 | /// @brief Returns the PGN encoded in the identifier 74 | /// @returns The PGN encoded in the identifier 75 | std::uint32_t get_parameter_group_number() const; 76 | 77 | /// @brief Returns the priority of the frame encoded in the identifier 78 | /// @returns The priority of the frame encoded in the identifier 79 | CANPriority get_priority() const; 80 | 81 | /// @brief Returns the destination address of the frame encoded in the identifier 82 | /// @returns The destination address of the frame encoded in the identifier 83 | std::uint8_t get_destination_address() const; 84 | 85 | /// @brief Returns the source address of the frame encoded in the identifier 86 | /// @returns The source address of the frame encoded in the identifier 87 | std::uint8_t get_source_address() const; 88 | 89 | /// @brief Returns if the ID is valid based on some range checking 90 | /// @returns Frame valid status 91 | bool get_is_valid() const; 92 | 93 | static constexpr std::uint32_t IDENTIFIER_TYPE_BIT_MASK = 0x80000000; ///< This bit denotes if the frame is standard or extended format 94 | static constexpr std::uint32_t UNDEFINED_PARAMETER_GROUP_NUMBER = 0xFFFFFFFF; ///< A fake PGN used internally to denote a NULL PGN 95 | static constexpr std::uint8_t GLOBAL_ADDRESS = 0xFF; ///< The broadcast CAN address 96 | static constexpr std::uint8_t NULL_ADDRESS = 0xFE; ///< The NULL CAN address as defined by ISO11783 97 | 98 | private: 99 | static constexpr std::uint32_t BROADCAST_PGN_MASK = 0x0003FFFF; ///< Broadcast PGNs don't mask off the bits used for destination in the PGN 100 | static constexpr std::uint32_t DESTINATION_SPECIFIC_PGN_MASK = 0x0003FF00; ///< Destination specific PGNs mask the destination out of the PGN itself 101 | static constexpr std::uint32_t PDU2_FORMAT_MASK = 0x00F00000; ///< Mask that denotes the ID as being PDU2 format 102 | static constexpr std::uint8_t PARAMTER_GROUP_NUMBER_OFFSET = 8; ///< PGN is offset 8 bits into the ID 103 | static constexpr std::uint8_t PRIORITY_DATA_BIT_OFFSET = 26; ///< Priority is offset 26 bits into the ID 104 | 105 | std::uint32_t m_RawIdentifier; ///< The raw encoded 29 bit ID 106 | }; 107 | 108 | } // namespace isobus 109 | 110 | #endif // CAN_IDENTIFIER_HPP 111 | -------------------------------------------------------------------------------- /src/can_internal_control_function.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_internal_control_function.hpp 3 | /// 4 | /// @brief A representation of an ISOBUS ECU that we can send from. Use this class 5 | /// when defining your own control functions that will claim an address within your program. 6 | /// @author Adrian Del Grosso 7 | /// @author Daan Steenbergen 8 | /// 9 | /// @copyright 2024 The Open-Agriculture Developers 10 | //================================================================================================ 11 | 12 | #ifndef CAN_INTERNAL_CONTROL_FUNCTION_HPP 13 | #define CAN_INTERNAL_CONTROL_FUNCTION_HPP 14 | 15 | #include "can_control_function.hpp" 16 | #include "can_message.hpp" 17 | #include "event_dispatcher.hpp" 18 | 19 | #include 20 | 21 | namespace isobus 22 | { 23 | class ParameterGroupNumberRequestProtocol; 24 | 25 | /// @brief Describes an internal ECU's NAME and address data. Used to send CAN messages. 26 | /// @details This class is used to define your own ECU's NAME, and is used to transmit messages. 27 | /// Each instance of this class will claim a unique address on the bus, and can be used to 28 | /// send messages. 29 | class InternalControlFunction : public ControlFunction 30 | { 31 | public: 32 | /// @brief Defines the states the internal control function can be in 33 | enum class State 34 | { 35 | None, ///< Initial state 36 | WaitForClaim, ///< Waiting for the random delay time to expire 37 | SendRequestForClaim, ///< Sending the request for address claim to the bus 38 | WaitForRequestContentionPeriod, ///< Waiting for the address claim contention period to expire 39 | SendPreferredAddressClaim, ///< Claiming the preferred address as our own 40 | ContendForPreferredAddress, ///< Contending the preferred address with another ECU 41 | SendArbitraryAddressClaim, ///< Claiming an arbitrary (not our preferred) address as our own 42 | SendReclaimAddressOnRequest, ///< An ECU requested address claim, inform the bus of our current address 43 | UnableToClaim, ///< Unable to claim an address 44 | AddressClaimingComplete ///< Address claiming is complete and we have an address 45 | }; 46 | 47 | /// @brief The constructor of an internal control function. 48 | /// In most cases use `CANNetworkManager::create_internal_control_function()` instead, 49 | /// only use this constructor if you have advanced needs. 50 | /// @param[in] desiredName The NAME for this control function to claim as 51 | /// @param[in] preferredAddress The preferred NAME for this control function 52 | /// @param[in] CANPort The CAN channel index for this control function to use 53 | InternalControlFunction(NAME desiredName, std::uint8_t preferredAddress, std::uint8_t CANPort); 54 | 55 | /// @brief Returns the current state of the internal control function 56 | /// @returns The current state 57 | State get_current_state() const; 58 | 59 | /// @brief Processes a CAN message for address claiming purposes 60 | /// @param[in] message The CAN message being received 61 | void process_rx_message_for_address_claiming(const CANMessage &message); 62 | 63 | /// @brief Updates the internal control function address claiming, will be called periodically by 64 | /// the network manager if the ICF is registered there. 65 | /// @returns true if the address of internal control function has changed, otherwise false 66 | bool update_address_claiming(); 67 | 68 | /// @brief Returns the preferred address of the internal control function 69 | /// @returns The preferred address 70 | std::uint8_t get_preferred_address() const; 71 | 72 | /// @brief Returns the event dispatcher for when an address is claimed. Use this to register a callback 73 | /// for when an address is claimed. 74 | /// @returns The event dispatcher for when an address is claimed 75 | EventDispatcher &get_address_claimed_event_dispatcher(); 76 | 77 | /// @brief Gets the PGN request protocol for this ICF 78 | /// @returns The PGN request protocol for this ICF 79 | std::weak_ptr get_pgn_request_protocol() const; 80 | 81 | /// @brief Validates that a CAN message has not caused an address violation for this ICF. 82 | /// If a violation is found, a re-claim will be executed for as is required by ISO 11783-5, 83 | /// and will attempt to activate a DTC that is defined in ISO 11783-5. 84 | /// This function is for advanced use cases only. Normally, the network manager will call this 85 | /// for every message received. 86 | /// @note Address violation occurs when two CFs are using the same source address. 87 | /// @param[in] message The message to process 88 | /// @returns true if the message caused an address violation, otherwise false 89 | bool process_rx_message_for_address_violation(const CANMessage &message); 90 | 91 | protected: 92 | friend class CANNetworkManager; ///< Allow the network manager to access the pgn request protocol 93 | std::shared_ptr pgnRequestProtocol; ///< The PGN request protocol for this ICF 94 | 95 | private: 96 | /// @brief Sends the PGN request for the address claim PGN 97 | /// @returns true if the message was sent, otherwise false 98 | bool send_request_to_claim() const; 99 | 100 | /// @brief Sends the address claim message to the bus 101 | /// @param[in] address The address to claim 102 | /// @returns true if the message was sent, otherwise false 103 | bool send_address_claim(std::uint8_t address); 104 | 105 | /// @brief Attempts to process a commanded address. 106 | /// @details If the state machine has claimed successfully before, 107 | /// this will attempt to move a NAME from the claimed address to the new, specified address. 108 | /// @param[in] commandedAddress The address to attempt to claim 109 | void process_commanded_address(std::uint8_t commandedAddress); 110 | 111 | /// @brief Setter for the state 112 | /// @param[in] value The new state 113 | void set_current_state(State value); 114 | 115 | static constexpr std::uint32_t ADDRESS_CONTENTION_TIME_MS = 250; ///< The time in milliseconds to wait for address contention 116 | 117 | State state = State::None; ///< The current state of the internal control function 118 | std::uint32_t stateChangeTimestamp_ms = 0; ///< A timestamp in milliseconds used for timing the address claiming process 119 | std::uint8_t preferredAddress; ///< The address we'd prefer to claim as (we may not get it) 120 | std::uint8_t randomClaimDelay_ms; ///< The random delay before claiming an address as required by the ISO11783 standard 121 | EventDispatcher addressClaimedDispatcher; ///< The event dispatcher for when an address is claimed 122 | }; 123 | 124 | } // namespace isobus 125 | 126 | #endif // CAN_INTERNAL_CONTROL_FUNCTION_HPP 127 | -------------------------------------------------------------------------------- /src/can_message_data.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_message_data.cpp 3 | /// 4 | /// @brief An class that represents a CAN message of arbitrary length being transported. 5 | /// @author Daan Steenbergen 6 | /// 7 | /// @copyright 2023 OpenAgriculture 8 | //================================================================================================ 9 | 10 | #include "can_message_data.hpp" 11 | 12 | #include 13 | 14 | namespace isobus 15 | { 16 | CANMessageDataVector::CANMessageDataVector(std::size_t size) 17 | { 18 | vector::resize(size); 19 | } 20 | 21 | CANMessageDataVector::CANMessageDataVector(const std::vector &data) 22 | { 23 | vector::assign(data.begin(), data.end()); 24 | } 25 | 26 | CANMessageDataVector::CANMessageDataVector(const std::uint8_t *data, std::size_t size) 27 | { 28 | vector::assign(data, data + size); 29 | } 30 | 31 | std::size_t CANMessageDataVector::size() const 32 | { 33 | return vector::size(); 34 | } 35 | 36 | std::uint8_t CANMessageDataVector::get_byte(std::size_t index) 37 | { 38 | return vector::at(index); 39 | } 40 | 41 | void CANMessageDataVector::set_byte(std::size_t index, std::uint8_t value) 42 | { 43 | vector::at(index) = value; 44 | } 45 | 46 | CANDataSpan CANMessageDataVector::data() const 47 | { 48 | return CANDataSpan(vector::data(), vector::size()); 49 | } 50 | 51 | std::unique_ptr CANMessageDataVector::copy_if_not_owned(std::unique_ptr self) const 52 | { 53 | // We know that a CANMessageDataVector is always owned by itself, so we can just return itself. 54 | return self; 55 | } 56 | 57 | CANMessageDataView::CANMessageDataView(const std::uint8_t *ptr, std::size_t len) : 58 | CANDataSpan(ptr, len) 59 | { 60 | } 61 | 62 | std::size_t CANMessageDataView::size() const 63 | { 64 | return DataSpan::size(); 65 | } 66 | 67 | std::uint8_t CANMessageDataView::get_byte(std::size_t index) 68 | { 69 | return DataSpan::operator[](index); 70 | } 71 | 72 | CANDataSpan CANMessageDataView::data() const 73 | { 74 | return CANDataSpan(DataSpan::begin(), DataSpan::size()); 75 | } 76 | 77 | std::unique_ptr CANMessageDataView::copy_if_not_owned(std::unique_ptr) const 78 | { 79 | // A view doesn't own the data, so we need to make a copy. 80 | return std::unique_ptr(new CANMessageDataVector(DataSpan::begin(), DataSpan::size())); 81 | } 82 | 83 | CANMessageDataCallback::CANMessageDataCallback(std::size_t size, 84 | DataChunkCallback callback, 85 | void *parentPointer, 86 | std::size_t chunkSize) : 87 | totalSize(size), 88 | callback(callback), 89 | parentPointer(parentPointer), 90 | buffer(chunkSize), 91 | bufferSize(chunkSize) 92 | { 93 | } 94 | 95 | std::size_t CANMessageDataCallback::size() const 96 | { 97 | return totalSize; 98 | } 99 | 100 | std::uint8_t CANMessageDataCallback::get_byte(std::size_t index) 101 | { 102 | if (index >= totalSize) 103 | { 104 | return 0; 105 | } 106 | 107 | if ((index >= dataOffset + bufferSize) || (index < dataOffset) || (!initialized)) 108 | { 109 | initialized = true; 110 | dataOffset = index; 111 | callback(0, dataOffset, std::min(totalSize - dataOffset, bufferSize), buffer.data(), parentPointer); 112 | } 113 | return buffer[index - dataOffset]; 114 | } 115 | 116 | std::unique_ptr CANMessageDataCallback::copy_if_not_owned(std::unique_ptr self) const 117 | { 118 | // A callback doesn't own it's data, but it does own the callback function, so we can just return itself. 119 | return self; 120 | } 121 | 122 | } // namespace isobus 123 | -------------------------------------------------------------------------------- /src/can_message_data.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_message_data.hpp 3 | /// 4 | /// @brief An interface class that represents data payload of a CAN message of arbitrary length. 5 | /// @author Daan Steenbergen 6 | /// 7 | /// @copyright 2023 - The OpenAgriculture Developers 8 | //================================================================================================ 9 | 10 | #ifndef CAN_MESSAGE_DATA_HPP 11 | #define CAN_MESSAGE_DATA_HPP 12 | 13 | #include "can_callbacks.hpp" 14 | #include "can_control_function.hpp" 15 | #include "can_message.hpp" 16 | #include "data_span.hpp" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | namespace isobus 23 | { 24 | /// @brief A interface class that represents data payload of a CAN message of arbitrary length. 25 | class CANMessageData 26 | { 27 | public: 28 | /// @brief Default destructor. 29 | virtual ~CANMessageData() = default; 30 | 31 | /// @brief Get the size of the data. 32 | /// @return The size of the data. 33 | virtual std::size_t size() const = 0; 34 | 35 | /// @brief Get the byte at the given index. 36 | /// @param[in] index The index of the byte to get. 37 | /// @return The byte at the given index. 38 | virtual std::uint8_t get_byte(std::size_t index) = 0; 39 | 40 | /// @brief If the data isn't owned by this class, make a copy of the data. 41 | /// @param[in] self A pointer to this object. 42 | /// @return A copy of the data if it isn't owned by this class, otherwise a moved pointer. 43 | virtual std::unique_ptr copy_if_not_owned(std::unique_ptr self) const = 0; 44 | }; 45 | 46 | /// @brief A class that represents data of a CAN message by holding a vector of bytes. 47 | class CANMessageDataVector : public CANMessageData 48 | , public std::vector 49 | { 50 | public: 51 | /// @brief Construct a new CANMessageDataVector object. 52 | /// @param[in] size The size of the data. 53 | explicit CANMessageDataVector(std::size_t size); 54 | 55 | /// @brief Construct a new CANMessageDataVector object. 56 | /// @param[in] data The data to copy. 57 | explicit CANMessageDataVector(const std::vector &data); 58 | 59 | /// @brief Construct a new CANMessageDataVector object. 60 | /// @param[in] data A pointer to the data to copy. 61 | /// @param[in] size The size of the data to copy. 62 | CANMessageDataVector(const std::uint8_t *data, std::size_t size); 63 | 64 | /// @brief Get the size of the data. 65 | /// @return The size of the data. 66 | std::size_t size() const override; 67 | 68 | /// @brief Get the byte at the given index. 69 | /// @param[in] index The index of the byte to get. 70 | /// @return The byte at the given index. 71 | std::uint8_t get_byte(std::size_t index) override; 72 | 73 | /// @brief Set the byte at the given index. 74 | /// @param[in] index The index of the byte to set. 75 | /// @param[in] value The value to set the byte to. 76 | void set_byte(std::size_t index, std::uint8_t value); 77 | 78 | /// @brief Get the data span. 79 | /// @return The data span. 80 | CANDataSpan data() const; 81 | 82 | /// @brief If the data isn't owned by this class, make a copy of the data. 83 | /// @param[in] self A pointer to this object. 84 | /// @return A copy of the data if it isn't owned by this class, otherwise it returns itself. 85 | std::unique_ptr copy_if_not_owned(std::unique_ptr self) const override; 86 | }; 87 | 88 | /// @brief A class that represents data of a CAN message by holding a view of an array of bytes. 89 | /// The view is not owned by this class, it is simply holding a pointer to the array of bytes. 90 | class CANMessageDataView : public CANMessageData 91 | , public CANDataSpan 92 | { 93 | public: 94 | /// @brief Construct a new CANMessageDataView object. 95 | /// @param[in] ptr The pointer to the array of bytes. 96 | /// @param[in] len The length of the array of bytes. 97 | CANMessageDataView(const std::uint8_t *ptr, std::size_t len); 98 | 99 | /// @brief Get the size of the data. 100 | /// @return The size of the data. 101 | std::size_t size() const override; 102 | 103 | /// @brief Get the byte at the given index. 104 | /// @param[in] index The index of the byte to get. 105 | /// @return The byte at the given index. 106 | std::uint8_t get_byte(std::size_t index) override; 107 | 108 | /// @brief Get the data span. 109 | /// @return The data span. 110 | CANDataSpan data() const; 111 | 112 | /// @brief If the data isn't owned by this class, make a copy of the data. 113 | /// @param[in] self A pointer to this object. 114 | /// @return A copy of the data if it isn't owned by this class, otherwise it returns itself. 115 | std::unique_ptr copy_if_not_owned(std::unique_ptr self) const override; 116 | }; 117 | 118 | /// @brief A class that represents data of a CAN message by using a callback function. 119 | class CANMessageDataCallback : public CANMessageData 120 | { 121 | public: 122 | /// @brief Constructor for transport data that uses a callback function. 123 | /// @param[in] size The size of the data. 124 | /// @param[in] callback The callback function to be called for each data chunk. 125 | /// @param[in] parentPointer The parent object that owns this callback (optional). 126 | /// @param[in] chunkSize The size of each data chunk (optional, default is 7). 127 | CANMessageDataCallback(std::size_t size, 128 | DataChunkCallback callback, 129 | void *parentPointer = nullptr, 130 | std::size_t chunkSize = 7); 131 | 132 | /// @brief Get the size of the data. 133 | /// @return The size of the data. 134 | std::size_t size() const override; 135 | 136 | /// @brief Get the byte at the given index. 137 | /// @param[in] index The index of the byte to get. 138 | /// @return The byte at the given index. 139 | std::uint8_t get_byte(std::size_t index) override; 140 | 141 | /// @brief If the data isn't owned by this class, make a copy of the data. 142 | /// @param[in] self A pointer to this object. 143 | /// @return A copy of the data if it isn't owned by this class, otherwise it returns itself. 144 | std::unique_ptr copy_if_not_owned(std::unique_ptr self) const override; 145 | 146 | private: 147 | std::size_t totalSize; ///< The total size of the data. 148 | DataChunkCallback callback; ///< The callback function to be called for each data chunk. 149 | void *parentPointer; ///< The parent object that gets passed to the callback function. 150 | std::vector buffer; ///< The buffer to store the data chunks. 151 | std::size_t bufferSize; ///< The size of the buffer. 152 | std::size_t dataOffset = 0; ///< The offset of the data in the buffer. 153 | bool initialized = false; ///< Whether the buffer has been initialized. 154 | }; 155 | } // namespace isobus 156 | 157 | #endif // CAN_MESSAGE_DATA_HPP 158 | -------------------------------------------------------------------------------- /src/can_message_frame.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_message_frame.cpp 3 | /// 4 | /// @brief Implements helper functions for CANMessageFrame 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2023 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #include "can_message_frame.hpp" 10 | #include "can_constants.hpp" 11 | #include "can_identifier.hpp" 12 | 13 | namespace isobus 14 | { 15 | std::uint32_t CANMessageFrame::get_number_bits_in_message() const 16 | { 17 | constexpr std::uint32_t MAX_CONSECUTIVE_SAME_BITS = 5; // After 5 consecutive bits, 6th will be opposite 18 | const std::uint32_t dataLengthBits = CAN_DATA_LENGTH * dataLength; 19 | std::uint32_t retVal = 0; 20 | 21 | if (isExtendedFrame) 22 | { 23 | constexpr std::uint32_t EXTENDED_ID_BEST_NON_DATA_LENGTH = 67; // SOF, ID, Control, CRC, ACK, EOF, and interframe space 24 | constexpr std::uint32_t EXTENDED_ID_WORST_NON_DATA_LENGTH = 78; 25 | retVal = ((dataLengthBits + EXTENDED_ID_BEST_NON_DATA_LENGTH) + (dataLengthBits + (dataLengthBits / MAX_CONSECUTIVE_SAME_BITS) + EXTENDED_ID_WORST_NON_DATA_LENGTH)); 26 | } 27 | else 28 | { 29 | constexpr std::uint32_t STANDARD_ID_BEST_NON_DATA_LENGTH = 47; // SOF, ID, Control, CRC, ACK, EOF, and interframe space 30 | constexpr std::uint32_t STANDARD_ID_WORST_NON_DATA_LENGTH = 54; 31 | retVal = ((dataLengthBits + STANDARD_ID_BEST_NON_DATA_LENGTH) + (dataLengthBits + (dataLengthBits / MAX_CONSECUTIVE_SAME_BITS) + STANDARD_ID_WORST_NON_DATA_LENGTH)); 32 | } 33 | return retVal / 2; 34 | } 35 | } // namespace isobus 36 | -------------------------------------------------------------------------------- /src/can_message_frame.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_message_frame.hpp 3 | /// 4 | /// @brief A classical CAN frame, with 8 data bytes 5 | /// @author Adrian Del Grosso 6 | /// @author Daan Steenbergen 7 | /// 8 | /// @copyright 2022 The Open-Agriculture Developers 9 | //================================================================================================ 10 | 11 | #ifndef CAN_MESSAGE_FRAME_HPP 12 | #define CAN_MESSAGE_FRAME_HPP 13 | 14 | #include 15 | 16 | namespace isobus 17 | { 18 | //================================================================================================ 19 | /// @class CANMessageFrame 20 | /// 21 | /// @brief A CAN frame for interfacing with a hardware layer, like socket CAN or other interface 22 | //================================================================================================ 23 | class CANMessageFrame 24 | { 25 | public: 26 | /// Returns the number of bits in a CAN message with averaged bit stuffing 27 | /// @returns The number of bits in the message (with average bit stuffing) 28 | std::uint32_t get_number_bits_in_message() const; 29 | 30 | std::uint64_t timestamp_us; ///< A microsecond timestamp 31 | std::uint32_t identifier; ///< The 32 bit identifier of the frame 32 | std::uint8_t channel; ///< The CAN channel index associated with the frame 33 | std::uint8_t data[8]; ///< The data payload of the frame 34 | std::uint8_t dataLength; ///< The length of the data used in the frame 35 | bool isExtendedFrame; ///< Denotes if the frame is extended format 36 | }; 37 | 38 | } // namespace isobus 39 | 40 | #endif // CAN_MESSAGE_FRAME_HPP 41 | -------------------------------------------------------------------------------- /src/can_network_configuration.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_network_configuration.cpp 3 | /// 4 | /// @brief This is a class for changing stack settings. 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #include "can_network_configuration.hpp" 11 | 12 | namespace isobus 13 | { 14 | void CANNetworkConfiguration::set_max_number_transport_protocol_sessions(std::uint32_t value) 15 | { 16 | maxNumberTransportProtocolSessions = value; 17 | } 18 | 19 | std::uint32_t CANNetworkConfiguration::get_max_number_transport_protocol_sessions() const 20 | { 21 | return maxNumberTransportProtocolSessions; 22 | } 23 | 24 | void CANNetworkConfiguration::set_minimum_time_between_transport_protocol_bam_frames(std::uint32_t value) 25 | { 26 | constexpr std::uint32_t MAX_BAM_FRAME_DELAY_MS = 200; 27 | constexpr std::uint32_t MIN_BAM_FRAME_DELAY_MS = 10; 28 | 29 | if ((value <= MAX_BAM_FRAME_DELAY_MS) && 30 | (value >= MIN_BAM_FRAME_DELAY_MS)) 31 | { 32 | minimumTimeBetweenTransportProtocolBAMFrames = value; 33 | } 34 | } 35 | 36 | std::uint32_t CANNetworkConfiguration::get_minimum_time_between_transport_protocol_bam_frames() const 37 | { 38 | return minimumTimeBetweenTransportProtocolBAMFrames; 39 | } 40 | 41 | void CANNetworkConfiguration::set_number_of_packets_per_dpo_message(std::uint8_t numberFrames) 42 | { 43 | numberOfPacketsPerDPOMessage = numberFrames; 44 | } 45 | 46 | std::uint8_t CANNetworkConfiguration::get_number_of_packets_per_dpo_message() const 47 | { 48 | return numberOfPacketsPerDPOMessage; 49 | } 50 | 51 | void CANNetworkConfiguration::set_max_number_of_network_manager_protocol_frames_per_update(std::uint8_t numberFrames) 52 | { 53 | networkManagerMaxFramesToSendPerUpdate = numberFrames; 54 | } 55 | 56 | std::uint8_t CANNetworkConfiguration::get_max_number_of_network_manager_protocol_frames_per_update() const 57 | { 58 | return networkManagerMaxFramesToSendPerUpdate; 59 | } 60 | 61 | void CANNetworkConfiguration::set_number_of_packets_per_cts_message(std::uint8_t numberFrames) 62 | { 63 | numberOfPacketsPerCTSMessage = numberFrames; 64 | } 65 | 66 | std::uint8_t CANNetworkConfiguration::get_number_of_packets_per_cts_message() const 67 | { 68 | return numberOfPacketsPerCTSMessage; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/can_network_configuration.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_network_configuration.hpp 3 | /// 4 | /// @brief This is a class for changing stack settings. 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #ifndef CAN_NETWORK_CONFIGURATION_HPP 11 | #define CAN_NETWORK_CONFIGURATION_HPP 12 | 13 | #include 14 | 15 | namespace isobus 16 | { 17 | //================================================================================================ 18 | /// @class CANNetworkConfiguration 19 | /// 20 | /// @brief A class that defines stack-wide configuration data. You can set the values in there 21 | /// to suit your specific memory constraints. 22 | //================================================================================================ 23 | class CANNetworkConfiguration 24 | { 25 | public: 26 | /// @brief The constructor for the configuration object 27 | CANNetworkConfiguration() = default; 28 | 29 | /// @brief The destructor for the configuration object 30 | ~CANNetworkConfiguration() = default; 31 | 32 | /// @brief Configures the max number of concurrent TP sessions to provide a RAM limit for TP sessions 33 | /// @param[in] value The max allowable number of TP sessions 34 | void set_max_number_transport_protocol_sessions(std::uint32_t value); 35 | 36 | /// @brief Returns the max number of concurrent TP sessions 37 | /// @returns The max number of concurrent TP sessions 38 | std::uint32_t get_max_number_transport_protocol_sessions() const; 39 | 40 | /// @brief Sets the minimum time to wait between sending BAM frames 41 | /// (default is 50 ms for maximum J1939 compatibility) 42 | /// @details The acceptable range as defined by ISO-11783 is 10 to 200 ms. 43 | /// This is a minumum time, so if you set it to some value, like 10 ms, the 44 | /// stack will attempt to transmit it as close to that time as it can, but it is 45 | /// not possible to 100% ensure it. 46 | /// @param[in] value The minimum time to wait between sending BAM frames 47 | void set_minimum_time_between_transport_protocol_bam_frames(std::uint32_t value); 48 | 49 | /// @brief Returns the minimum time to wait between sending BAM frames 50 | /// @returns The minimum time to wait between sending BAM frames 51 | std::uint32_t get_minimum_time_between_transport_protocol_bam_frames() const; 52 | 53 | /// @brief Sets the max number of data frames the stack will use when 54 | /// in an ETP session, between EDPO phases. The default is 16. 55 | /// Note that the sending control function may choose to use a lower number of frames. 56 | /// @param[in] numberFrames The max number of data frames to use 57 | void set_number_of_packets_per_dpo_message(std::uint8_t numberFrames); 58 | 59 | /// @brief Returns the max number of data frames the stack will use when 60 | /// in an ETP session, between EDPO phases. The default is 16. 61 | /// Note that the sending control function may choose to use a lower number of frames. 62 | /// @returns The number of data frames the stack will use when sending ETP messages between EDPOs 63 | std::uint8_t get_number_of_packets_per_dpo_message() const; 64 | 65 | /// @brief Sets the max number of data frames the stack will send from each 66 | /// transport layer protocol, per update. The default is 255, 67 | /// but decreasing it may reduce bus load at the expense of transfer time. 68 | /// @param[in] numberFrames The max number of frames to use 69 | void set_max_number_of_network_manager_protocol_frames_per_update(std::uint8_t numberFrames); 70 | 71 | /// @brief Returns the max number of data frames the stack will send from each 72 | /// transport layer protocol, per update. The default is 255, 73 | /// but decreasing it may reduce bus load at the expense of transfer time. 74 | /// @returns The max number of frames to use in transport protocols in each network manager update 75 | std::uint8_t get_max_number_of_network_manager_protocol_frames_per_update() const; 76 | 77 | /// @brief Set the the number of packets per CTS message for TP sessions. The default 78 | /// is 16. Note that the receiving control function may not support this limitation, or choose 79 | /// to ignore it and use a different number of packets per CTS packet. 80 | /// @param[in] numberPackets The number of packets per CTS packet for TP sessions. 81 | void set_number_of_packets_per_cts_message(std::uint8_t numberPackets); 82 | 83 | /// @brief Get the the number of packets per CTS packet for TP sessions. 84 | /// @returns The number of packets per CTS packet for TP sessions. 85 | std::uint8_t get_number_of_packets_per_cts_message() const; 86 | 87 | private: 88 | static constexpr std::uint8_t DEFAULT_BAM_PACKET_DELAY_TIME_MS = 50; ///< The default time between BAM frames, as defined by J1939 89 | 90 | std::uint32_t maxNumberTransportProtocolSessions = 4; ///< The max number of TP sessions allowed 91 | std::uint32_t minimumTimeBetweenTransportProtocolBAMFrames = DEFAULT_BAM_PACKET_DELAY_TIME_MS; ///< The configurable time between BAM frames 92 | std::uint8_t networkManagerMaxFramesToSendPerUpdate = 0xFF; ///< Used to control the max number of transport layer frames added to the driver queue per network manager update 93 | std::uint8_t numberOfPacketsPerDPOMessage = 16; ///< The number of packets per DPO message for ETP sessions 94 | std::uint8_t numberOfPacketsPerCTSMessage = 16; ///< The number of packets per CTS message for TP sessions 95 | }; 96 | } // namespace isobus 97 | 98 | #endif // CAN_NETWORK_CONFIGURATION_HPP 99 | -------------------------------------------------------------------------------- /src/can_partnered_control_function.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_partnered_control_function.cpp 3 | /// 4 | /// @brief A class that describes a control function on the bus that the stack should communicate 5 | /// with. Use these to describe ECUs you want to send messages to. 6 | /// @author Adrian Del Grosso 7 | /// @author Daan Steenbergen 8 | /// 9 | /// @copyright 2022 The Open-Agriculture Developers 10 | //================================================================================================ 11 | #include "can_partnered_control_function.hpp" 12 | 13 | #include "can_constants.hpp" 14 | 15 | #include 16 | #include 17 | 18 | namespace isobus 19 | { 20 | PartneredControlFunction::PartneredControlFunction(std::uint8_t CANPort, const std::vector NAMEFilters) : 21 | ControlFunction(NAME(0), NULL_CAN_ADDRESS, CANPort, Type::Partnered), 22 | NAMEFilterList(NAMEFilters) 23 | { 24 | auto &processingMutex = ControlFunction::controlFunctionProcessingMutex; 25 | LOCK_GUARD(Mutex, processingMutex); 26 | } 27 | 28 | void PartneredControlFunction::add_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr internalControlFunction) 29 | { 30 | parameterGroupNumberCallbacks.emplace_back(parameterGroupNumber, callback, parent, internalControlFunction); 31 | } 32 | 33 | void PartneredControlFunction::remove_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr internalControlFunction) 34 | { 35 | ParameterGroupNumberCallbackData tempObject(parameterGroupNumber, callback, parent, internalControlFunction); 36 | auto callbackLocation = std::find(parameterGroupNumberCallbacks.begin(), parameterGroupNumberCallbacks.end(), tempObject); 37 | if (parameterGroupNumberCallbacks.end() != callbackLocation) 38 | { 39 | parameterGroupNumberCallbacks.erase(callbackLocation); 40 | } 41 | } 42 | 43 | std::size_t PartneredControlFunction::get_number_parameter_group_number_callbacks() const 44 | { 45 | return parameterGroupNumberCallbacks.size(); 46 | } 47 | 48 | std::size_t PartneredControlFunction::get_number_name_filters() const 49 | { 50 | return NAMEFilterList.size(); 51 | } 52 | 53 | bool PartneredControlFunction::get_name_filter_parameter(std::size_t index, NAME::NAMEParameters ¶meter, std::uint32_t &filterValue) const 54 | { 55 | bool retVal = false; 56 | 57 | if (index < get_number_name_filters()) 58 | { 59 | parameter = NAMEFilterList[index].get_parameter(); 60 | filterValue = NAMEFilterList[index].get_value(); 61 | retVal = true; 62 | } 63 | return retVal; 64 | } 65 | 66 | std::size_t PartneredControlFunction::get_number_name_filters_with_parameter_type(NAME::NAMEParameters parameter) 67 | { 68 | std::size_t retVal = 0; 69 | 70 | for (std::size_t i = 0; i < NAMEFilterList.size(); i++) 71 | { 72 | if (parameter == NAMEFilterList[i].get_parameter()) 73 | { 74 | retVal++; 75 | } 76 | } 77 | return retVal; 78 | } 79 | 80 | bool PartneredControlFunction::check_matches_name(NAME NAMEToCheck) const 81 | { 82 | std::uint32_t currentFilterValue; 83 | NAME::NAMEParameters currentFilterParameter; 84 | bool retVal = true; 85 | 86 | if (0 == get_number_name_filters()) 87 | { 88 | retVal = false; 89 | } 90 | 91 | for (std::uint32_t i = 0; i < get_number_name_filters(); i++) 92 | { 93 | get_name_filter_parameter(i, currentFilterParameter, currentFilterValue); 94 | 95 | switch (currentFilterParameter) 96 | { 97 | case NAME::NAMEParameters::IdentityNumber: 98 | { 99 | retVal = (currentFilterValue == NAMEToCheck.get_identity_number()); 100 | } 101 | break; 102 | 103 | case NAME::NAMEParameters::ManufacturerCode: 104 | { 105 | retVal = (currentFilterValue == NAMEToCheck.get_manufacturer_code()); 106 | } 107 | break; 108 | 109 | case NAME::NAMEParameters::EcuInstance: 110 | { 111 | retVal = (currentFilterValue == NAMEToCheck.get_ecu_instance()); 112 | } 113 | break; 114 | 115 | case NAME::NAMEParameters::FunctionInstance: 116 | { 117 | retVal = (currentFilterValue == NAMEToCheck.get_function_instance()); 118 | } 119 | break; 120 | 121 | case NAME::NAMEParameters::FunctionCode: 122 | { 123 | retVal = (currentFilterValue == NAMEToCheck.get_function_code()); 124 | } 125 | break; 126 | 127 | case NAME::NAMEParameters::DeviceClass: 128 | { 129 | retVal = (currentFilterValue == NAMEToCheck.get_device_class()); 130 | } 131 | break; 132 | 133 | case NAME::NAMEParameters::DeviceClassInstance: 134 | { 135 | retVal = (currentFilterValue == NAMEToCheck.get_device_class_instance()); 136 | } 137 | break; 138 | 139 | case NAME::NAMEParameters::IndustryGroup: 140 | { 141 | retVal = (currentFilterValue == NAMEToCheck.get_industry_group()); 142 | } 143 | break; 144 | 145 | case NAME::NAMEParameters::ArbitraryAddressCapable: 146 | { 147 | retVal = ((0 != currentFilterValue) == NAMEToCheck.get_arbitrary_address_capable()); 148 | } 149 | break; 150 | 151 | default: 152 | { 153 | retVal = false; 154 | } 155 | break; 156 | } 157 | 158 | if (false == retVal) 159 | { 160 | break; 161 | } 162 | } 163 | return retVal; 164 | } 165 | 166 | ParameterGroupNumberCallbackData &PartneredControlFunction::get_parameter_group_number_callback(std::size_t index) 167 | { 168 | assert(index < get_number_parameter_group_number_callbacks()); 169 | return parameterGroupNumberCallbacks[index]; 170 | } 171 | 172 | } // namespace isobus 173 | -------------------------------------------------------------------------------- /src/can_partnered_control_function.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_partnered_control_function.hpp 3 | /// 4 | /// @brief A class that describes a control function on the bus that the stack should communicate 5 | /// with. Use these to describe ECUs you want to send messages to. 6 | /// @author Adrian Del Grosso 7 | /// @author Daan Steenbergen 8 | /// 9 | /// @copyright 2024 The Open-Agriculture Developers 10 | //================================================================================================ 11 | 12 | #ifndef CAN_PARTNERED_CONTROL_FUNCTION_HPP 13 | #define CAN_PARTNERED_CONTROL_FUNCTION_HPP 14 | 15 | #include "can_NAME_filter.hpp" 16 | #include "can_badge.hpp" 17 | #include "can_callbacks.hpp" 18 | #include "can_control_function.hpp" 19 | 20 | #include 21 | 22 | namespace isobus 23 | { 24 | class CANNetworkManager; 25 | class InternalControlFunction; 26 | 27 | /// @brief This represents any device on the bus you want to talk to. 28 | /// @details To communicate with a device on the bus, create one of these objects and tell it 29 | /// via the constructor what the identity of that device is using NAME fields like 30 | /// manufacturer code, function, and device class. The stack will take care of locating the 31 | /// device on the bus that matches that description, and will allow you to talk to it through 32 | /// passing this object to the appropriate send function in the network manager. 33 | class PartneredControlFunction : public ControlFunction 34 | { 35 | public: 36 | /// @brief the constructor for a PartneredControlFunction, which is called by the factory function 37 | /// @param[in] CANPort The CAN channel associated with this control function definition 38 | /// @param[in] NAMEFilters A list of filters that describe the identity of the CF based on NAME components 39 | PartneredControlFunction(std::uint8_t CANPort, const std::vector NAMEFilters); 40 | 41 | /// @brief Deleted copy constructor for PartneredControlFunction to avoid slicing 42 | PartneredControlFunction(PartneredControlFunction &) = delete; 43 | 44 | /// @brief This is how you get notified that this control function has sent you a destination specific message. 45 | /// @details Add a callback function here to be notified when this device has sent you a message with the specified PGN. 46 | /// You can also get callbacks for any/all PGNs if you pass in `CANLibParameterGroupNumber::Any` as the PGN. 47 | /// Optionally you can use the parent pointer to get context inside your callback as to what C++ object the callback is 48 | /// destined for. Whatever you pass in `parent` will be passed back to you in the callback. In theory, you could use 49 | /// that variable for passing any arbitrary data through the callback also. 50 | /// You can add as many callbacks as you want, and can use the same function for multiple PGNs if you want. 51 | /// @param[in] parameterGroupNumber The PGN you want to use to communicate, or `CANLibParameterGroupNumber::Any` 52 | /// @param[in] callback The function you want to get called when a message is received with parameterGroupNumber from this CF 53 | /// @param[in] parent A generic context variable that helps identify what object the callback was destined for 54 | /// @param[in] internalControlFunction An internal control function to filter based on. If you supply this 55 | /// parameter the callback will only be called when messages from the partner are received with the 56 | /// specified ICF as the destination. 57 | void add_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr internalControlFunction = nullptr); 58 | 59 | /// @brief Removes a callback matching *exactly* the parameters passed in 60 | /// @param[in] parameterGroupNumber The PGN associated with the callback being removed 61 | /// @param[in] callback The callback function being removed 62 | /// @param[in] parent A generic context variable that helps identify what object the callback was destined for 63 | /// @param[in] internalControlFunction The ICF being used to filter messages against 64 | void remove_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr internalControlFunction = nullptr); 65 | 66 | /// @brief Returns the number of parameter group number callbacks associated with this control function 67 | /// @returns The number of parameter group number callbacks associated with this control function 68 | std::size_t get_number_parameter_group_number_callbacks() const; 69 | 70 | /// @brief Returns the number of NAME filter objects that describe the identity of this control function 71 | /// @returns The number of NAME filter objects that describe the identity of this control function 72 | std::size_t get_number_name_filters() const; 73 | 74 | /// @brief Returns the number of NAME filters with a specific NAME parameter component, like manufacturer code 75 | /// @param[in] parameter The NAME parameter to check against 76 | /// @returns The number of NAME filters with a specific NAME parameter component 77 | std::size_t get_number_name_filters_with_parameter_type(NAME::NAMEParameters parameter); 78 | 79 | /// @brief Returns a NAME filter by index 80 | /// @param[in] index The index of the filter to get 81 | /// @param[out] parameter The returned parameter type 82 | /// @param[out] filterValue The raw value of the filter associated with the `parameter` 83 | /// @returns true if a filter was returned successfully, false if the index was out of range 84 | bool get_name_filter_parameter(std::size_t index, NAME::NAMEParameters ¶meter, std::uint32_t &filterValue) const; 85 | 86 | /// @brief Checks to see if a NAME matches this CF's NAME filters 87 | /// @param[in] NAMEToCheck The NAME to check against this control function's filters 88 | /// @returns true if this control function matches the NAME that was passed in, false otherwise 89 | bool check_matches_name(NAME NAMEToCheck) const; 90 | 91 | private: 92 | friend class CANNetworkManager; ///< Allows the network manager to use get_parameter_group_number_callback 93 | 94 | /// @brief Returns a parameter group number associated with this control function by index 95 | /// @param[in] index The index from which to get the PGN callback data object 96 | /// @returns A reference to the PGN callback data object at the index specified 97 | ParameterGroupNumberCallbackData &get_parameter_group_number_callback(std::size_t index); 98 | 99 | const std::vector NAMEFilterList; ///< A list of NAME parameters that describe this control function's identity 100 | std::vector parameterGroupNumberCallbacks; ///< A list of all parameter group number callbacks associated with this control function 101 | bool initialized = false; ///< A way to track if the network manager has processed this CF against existing CFs 102 | }; 103 | 104 | } // namespace isobus 105 | 106 | #endif // CAN_PARTNERED_CONTROL_FUNCTION 107 | -------------------------------------------------------------------------------- /src/can_stack_logger.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_stack_logger.cpp 3 | /// 4 | /// @brief A class that acts as a logging sink. The intent is that someone could make their own 5 | /// derived class of logger and inject it into the CAN stack to get helpful debug logging. 6 | /// @author Adrian Del Grosso 7 | /// 8 | /// @copyright 2022 The Open-Agriculture Developers 9 | //================================================================================================ 10 | #include "can_stack_logger.hpp" 11 | 12 | #include 13 | 14 | namespace isobus 15 | { 16 | CANStackLogger *CANStackLogger::logger = nullptr; 17 | CANStackLogger::LoggingLevel CANStackLogger::currentLogLevel = LoggingLevel::Info; 18 | Mutex CANStackLogger::loggerMutex; 19 | 20 | #ifndef DISABLE_CAN_STACK_LOGGER 21 | void CANStackLogger::CAN_stack_log(LoggingLevel level, const std::string &logText) 22 | { 23 | LOCK_GUARD(Mutex, loggerMutex); 24 | CANStackLogger *canStackLogger = nullptr; 25 | 26 | if ((get_can_stack_logger(canStackLogger)) && 27 | (level >= get_log_level())) 28 | { 29 | canStackLogger->sink_CAN_stack_log(level, logText); 30 | } 31 | } 32 | 33 | void CANStackLogger::debug(const std::string &logText) 34 | { 35 | CAN_stack_log(LoggingLevel::Debug, logText); 36 | } 37 | 38 | void CANStackLogger::info(const std::string &logText) 39 | { 40 | CAN_stack_log(LoggingLevel::Info, logText); 41 | } 42 | 43 | void CANStackLogger::warn(const std::string &logText) 44 | { 45 | CAN_stack_log(LoggingLevel::Warning, logText); 46 | } 47 | 48 | void CANStackLogger::error(const std::string &logText) 49 | { 50 | CAN_stack_log(LoggingLevel::Error, logText); 51 | } 52 | 53 | void CANStackLogger::critical(const std::string &logText) 54 | { 55 | CAN_stack_log(LoggingLevel::Critical, logText); 56 | } 57 | 58 | #endif // DISABLE_CAN_STACK_LOGGER 59 | 60 | void CANStackLogger::set_can_stack_logger_sink(CANStackLogger *logSink) 61 | { 62 | logger = logSink; 63 | } 64 | 65 | CANStackLogger::LoggingLevel CANStackLogger::get_log_level() 66 | { 67 | return currentLogLevel; 68 | } 69 | 70 | void CANStackLogger::set_log_level(LoggingLevel newLogLevel) 71 | { 72 | currentLogLevel = newLogLevel; 73 | } 74 | 75 | void CANStackLogger::sink_CAN_stack_log(LoggingLevel, const std::string &) 76 | { 77 | // Override this function to use the log sink 78 | } 79 | 80 | bool CANStackLogger::get_can_stack_logger(CANStackLogger *&canStackLogger) 81 | { 82 | canStackLogger = logger; 83 | return (nullptr != canStackLogger); 84 | } 85 | } // namespace isobus 86 | -------------------------------------------------------------------------------- /src/can_transport_protocol_base.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_transport_protocol.cpp 3 | /// 4 | /// @brief Abstract base class for CAN transport protocols. 5 | /// @author Daan Steenbergen 6 | /// 7 | /// @copyright 2023 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #include "can_transport_protocol_base.hpp" 11 | #include "can_internal_control_function.hpp" 12 | #include "system_timing.hpp" 13 | 14 | namespace isobus 15 | { 16 | TransportProtocolSessionBase::TransportProtocolSessionBase(TransportProtocolSessionBase::Direction direction, 17 | std::unique_ptr data, 18 | std::uint32_t parameterGroupNumber, 19 | std::uint32_t totalMessageSize, 20 | std::shared_ptr source, 21 | std::shared_ptr destination, 22 | TransmitCompleteCallback sessionCompleteCallback, 23 | void *parentPointer) : 24 | direction(direction), 25 | parameterGroupNumber(parameterGroupNumber), 26 | data(std::move(data)), 27 | source(source), 28 | destination(destination), 29 | totalMessageSize(totalMessageSize), 30 | sessionCompleteCallback(sessionCompleteCallback), 31 | parent(parentPointer) 32 | { 33 | } 34 | 35 | bool TransportProtocolSessionBase::operator==(const TransportProtocolSessionBase &obj) const 36 | { 37 | return ((source == obj.source) && (destination == obj.destination) && (parameterGroupNumber == obj.parameterGroupNumber)); 38 | } 39 | 40 | bool TransportProtocolSessionBase::matches(std::shared_ptr other_source, std::shared_ptr other_destination) const 41 | { 42 | return ((source == other_source) && (destination == other_destination)); 43 | } 44 | 45 | TransportProtocolSessionBase::Direction TransportProtocolSessionBase::get_direction() const 46 | { 47 | return direction; 48 | } 49 | 50 | CANMessageData &TransportProtocolSessionBase::get_data() const 51 | { 52 | return *data; 53 | } 54 | 55 | std::uint32_t TransportProtocolSessionBase::get_message_length() const 56 | { 57 | return totalMessageSize; 58 | } 59 | 60 | std::shared_ptr TransportProtocolSessionBase::get_source() const 61 | { 62 | return source; 63 | } 64 | 65 | float TransportProtocolSessionBase::get_percentage_bytes_transferred() const 66 | { 67 | if (0 == get_message_length()) 68 | { 69 | return 0.0f; 70 | } 71 | return static_cast(get_total_bytes_transferred()) / static_cast(get_message_length()) * 100.0f; 72 | } 73 | 74 | std::shared_ptr TransportProtocolSessionBase::get_destination() const 75 | { 76 | return destination; 77 | } 78 | 79 | std::uint32_t TransportProtocolSessionBase::get_parameter_group_number() const 80 | { 81 | return parameterGroupNumber; 82 | } 83 | 84 | void isobus::TransportProtocolSessionBase::update_timestamp() 85 | { 86 | timestamp_ms = SystemTiming::get_timestamp_ms(); 87 | } 88 | 89 | std::uint32_t TransportProtocolSessionBase::get_time_since_last_update() const 90 | { 91 | return SystemTiming::get_time_elapsed_ms(timestamp_ms); 92 | } 93 | 94 | void TransportProtocolSessionBase::complete(bool success) const 95 | { 96 | if ((nullptr != sessionCompleteCallback) && (Direction::Transmit == direction)) 97 | { 98 | sessionCompleteCallback(get_parameter_group_number(), 99 | get_message_length(), 100 | std::static_pointer_cast(source), 101 | get_destination(), 102 | success, 103 | parent); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/can_transport_protocol_base.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_transport_protocol_base.hpp 3 | /// 4 | /// @brief Abstract base class for CAN transport protocols. 5 | /// @author Daan Steenbergen 6 | /// 7 | /// @copyright 2023 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #ifndef CAN_TRANSPORT_PROTOCOL_BASE_HPP 11 | #define CAN_TRANSPORT_PROTOCOL_BASE_HPP 12 | 13 | #include "can_control_function.hpp" 14 | #include "can_message.hpp" 15 | #include "can_message_data.hpp" 16 | 17 | namespace isobus 18 | { 19 | /// @brief An object to keep track of session information internally 20 | class TransportProtocolSessionBase 21 | { 22 | public: 23 | /// @brief Enumerates the possible session directions 24 | enum class Direction 25 | { 26 | Transmit, ///< We are transmitting a message 27 | Receive ///< We are receiving a message 28 | }; 29 | 30 | /// @brief The constructor for a session 31 | /// @param[in] direction The direction of the session 32 | /// @param[in] data Data buffer (will be moved into the session) 33 | /// @param[in] parameterGroupNumber The PGN of the message 34 | /// @param[in] totalMessageSize The total size of the message in bytes 35 | /// @param[in] source The source control function 36 | /// @param[in] destination The destination control function 37 | /// @param[in] sessionCompleteCallback A callback for when the session completes 38 | /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks 39 | TransportProtocolSessionBase(TransportProtocolSessionBase::Direction direction, 40 | std::unique_ptr data, 41 | std::uint32_t parameterGroupNumber, 42 | std::uint32_t totalMessageSize, 43 | std::shared_ptr source, 44 | std::shared_ptr destination, 45 | TransmitCompleteCallback sessionCompleteCallback, 46 | void *parentPointer); 47 | 48 | /// @brief The move constructor 49 | /// @param[in] other The object to move 50 | TransportProtocolSessionBase(TransportProtocolSessionBase &&other) = default; 51 | 52 | /// @brief The move assignment operator 53 | /// @param[in] other The object to move 54 | /// @return A reference to the moved object 55 | TransportProtocolSessionBase &operator=(TransportProtocolSessionBase &&other) = default; 56 | 57 | /// @brief The destructor for a session 58 | virtual ~TransportProtocolSessionBase() = default; 59 | 60 | /// @brief Get the direction of the session 61 | /// @return The direction of the session 62 | Direction get_direction() const; 63 | 64 | /// @brief A useful way to compare session objects to each other for equality, 65 | /// @details A session is considered equal when the source and destination control functions 66 | /// and parameter group number match. Note that we don't compare the super class, 67 | /// so this should only be used to compare sessions of the same type. 68 | /// @param[in] obj The object to compare to 69 | /// @returns true if the objects are equal, false if not 70 | bool operator==(const TransportProtocolSessionBase &obj) const; 71 | 72 | /// @brief Checks if the source and destination control functions match the given control functions. 73 | /// @param[in] other_source The control function to compare with the source control function. 74 | /// @param[in] other_destination The control function to compare with the destination control function. 75 | /// @returns True if the source and destination control functions match the given control functions, false otherwise. 76 | bool matches(std::shared_ptr other_source, std::shared_ptr other_destination) const; 77 | 78 | /// @brief Get the data buffer for the session 79 | /// @return The data buffer for the session 80 | CANMessageData &get_data() const; 81 | 82 | /// @brief Get the total number of bytes that will be sent or received in this session 83 | /// @return The length of the message in number of bytes 84 | std::uint32_t get_message_length() const; 85 | 86 | /// @brief Get the number of bytes that have been sent or received in this session 87 | /// @return The number of bytes that have been sent or received 88 | virtual std::uint32_t get_total_bytes_transferred() const = 0; 89 | 90 | /// @brief Get the percentage of bytes that have been sent or received in this session 91 | /// @return The percentage of bytes that have been sent or received (between 0 and 100) 92 | float get_percentage_bytes_transferred() const; 93 | 94 | /// @brief Get the control function that is sending the message 95 | /// @return The source control function 96 | std::shared_ptr get_source() const; 97 | 98 | /// @brief Get the control function that is receiving the message 99 | /// @return The destination control function 100 | std::shared_ptr get_destination() const; 101 | 102 | /// @brief Get the parameter group number of the message 103 | /// @return The PGN of the message 104 | std::uint32_t get_parameter_group_number() const; 105 | 106 | protected: 107 | /// @brief Update the timestamp of the session 108 | void update_timestamp(); 109 | 110 | /// @brief Get the time that has passed since the last update of the timestamp 111 | /// @return The duration in milliseconds 112 | std::uint32_t get_time_since_last_update() const; 113 | 114 | /// @brief Complete the session 115 | /// @param[in] success True if the session was successful, false otherwise 116 | void complete(bool success) const; 117 | 118 | private: 119 | Direction direction; ///< The direction of the session 120 | std::uint32_t parameterGroupNumber; ///< The PGN of the message 121 | std::unique_ptr data; ///< The data buffer for the message 122 | std::shared_ptr source; ///< The source control function 123 | std::shared_ptr destination; ///< The destination control function 124 | std::uint32_t timestamp_ms = 0; ///< A timestamp used to track session timeouts 125 | 126 | std::uint32_t totalMessageSize; ///< The total size of the message in bytes (the maximum size of a message is from ETP and can fit in an uint32_t) 127 | 128 | TransmitCompleteCallback sessionCompleteCallback = nullptr; ///< A callback that is to be called when the session is completed 129 | void *parent = nullptr; ///< A generic context variable that helps identify what object callbacks are destined for. Can be nullptr 130 | }; 131 | } // namespace isobus 132 | 133 | #endif // CAN_TRANSPORT_PROTOCOL_BASE_HPP 134 | -------------------------------------------------------------------------------- /src/data_span.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file can_message_data.hpp 3 | /// 4 | /// @brief Contains common types and functions for working with an arbitrary amount of items. 5 | /// @author Daan Steenbergen 6 | /// 7 | /// @copyright 2023 Open Agriculture 8 | //================================================================================================ 9 | #ifndef DATA_SPAN_HPP 10 | #define DATA_SPAN_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace isobus 17 | { 18 | //================================================================================================ 19 | /// @class DataSpan 20 | /// 21 | /// @brief A class that represents a span of data of arbitrary length. 22 | //================================================================================================ 23 | template 24 | class DataSpan 25 | { 26 | public: 27 | /// @brief Construct a new DataSpan object of a writeable array. 28 | /// @param ptr pointer to the buffer to use. 29 | /// @param len The number of elements in the buffer. 30 | DataSpan(T *ptr, std::size_t size) : 31 | ptr(ptr), 32 | _size(size) 33 | { 34 | } 35 | 36 | /// @brief Get the element at the given index. 37 | /// @param index The index of the element to get. 38 | /// @return The element at the given index. 39 | T &operator[](std::size_t index) 40 | { 41 | return ptr[index * sizeof(T)]; 42 | } 43 | 44 | /// @brief Get the element at the given index. 45 | /// @param index The index of the element to get. 46 | /// @return The element at the given index. 47 | T const &operator[](std::size_t index) const 48 | { 49 | return ptr[index * sizeof(T)]; 50 | } 51 | 52 | /// @brief Get the size of the data span. 53 | /// @return The size of the data span. 54 | std::size_t size() const 55 | { 56 | return _size; 57 | } 58 | 59 | /// @brief Get the begin iterator. 60 | /// @return The begin iterator. 61 | T *begin() const 62 | { 63 | return ptr; 64 | } 65 | 66 | /// @brief Get the end iterator. 67 | /// @return The end iterator. 68 | T *end() const 69 | { 70 | return ptr + _size * sizeof(T); 71 | } 72 | 73 | private: 74 | T *ptr; 75 | std::size_t _size; 76 | bool _isConst; 77 | }; 78 | } 79 | #endif // DATA_SPAN_HPP 80 | -------------------------------------------------------------------------------- /src/event_dispatcher.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file event_dispatcher.hpp 3 | /// 4 | /// @brief An object to represent a dispatcher that can invoke callbacks in a thread-safe manner. 5 | /// @author Daan Steenbergen 6 | /// 7 | /// @copyright 2024 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #ifndef EVENT_DISPATCHER_HPP 10 | #define EVENT_DISPATCHER_HPP 11 | 12 | #include "thread_synchronization.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace isobus 21 | { 22 | using EventCallbackHandle = std::size_t; 23 | 24 | /// @brief A dispatcher that notifies listeners when an event is invoked. 25 | template 26 | class EventDispatcher 27 | { 28 | public: 29 | using Callback = std::function; 30 | 31 | /// @brief Register a callback to be invoked when the event is invoked. 32 | /// @param callback The callback to register. 33 | /// @return A unique identifier for the callback, which can be used to remove the listener. 34 | EventCallbackHandle add_listener(const Callback &callback) 35 | { 36 | EventCallbackHandle id = nextId; 37 | nextId += 1; 38 | 39 | LOCK_GUARD(Mutex, callbacksMutex); 40 | if (isExecuting) 41 | { 42 | modifications.push([this, id, callback]() { callbacks[id] = callback; }); 43 | } 44 | else 45 | { 46 | callbacks[id] = callback; 47 | } 48 | return id; 49 | } 50 | 51 | /// @brief Register a callback to be invoked when the event is invoked. 52 | /// @param callback The callback to register. 53 | /// @param context The context object to pass through to the callback. 54 | /// @return A unique identifier for the callback, which can be used to remove the listener. 55 | template 56 | EventCallbackHandle add_listener(const std::function)> &callback, std::weak_ptr context) 57 | { 58 | Callback callbackWrapper = [callback, context](const E &...args) { 59 | if (auto contextPtr = context.lock()) 60 | { 61 | callback(args..., contextPtr); 62 | } 63 | }; 64 | return add_listener(callbackWrapper); 65 | } 66 | 67 | /// @brief Register an unsafe callback to be invoked when the event is invoked. 68 | /// @param callback The callback to register. 69 | /// @param context The context object to pass through to the callback. 70 | /// @return A unique identifier for the callback, which can be used to remove the listener. 71 | template 72 | EventCallbackHandle add_unsafe_listener(const std::function &callback, C *context) 73 | { 74 | Callback callbackWrapper = [callback, context](const E &...args) { 75 | callback(args..., context); 76 | }; 77 | return add_listener(callbackWrapper); 78 | } 79 | 80 | /// @brief Remove a callback from the list of listeners. 81 | /// @param id The unique identifier of the callback to remove. 82 | void remove_listener(EventCallbackHandle id) noexcept 83 | { 84 | LOCK_GUARD(Mutex, callbacksMutex); 85 | if (isExecuting) 86 | { 87 | modifications.push([this, id]() { callbacks.erase(id); }); 88 | } 89 | else 90 | { 91 | callbacks.erase(id); 92 | } 93 | } 94 | 95 | /// @brief Remove all listeners from the event. 96 | void clear_listeners() noexcept 97 | { 98 | LOCK_GUARD(Mutex, callbacksMutex); 99 | if (isExecuting) 100 | { 101 | modifications.push([this]() { callbacks.clear(); }); 102 | } 103 | else 104 | { 105 | callbacks.clear(); 106 | } 107 | } 108 | 109 | /// @brief Get the number of listeners registered to this event. 110 | /// @return The number of listeners 111 | std::size_t get_listener_count() 112 | { 113 | LOCK_GUARD(Mutex, callbacksMutex); 114 | return callbacks.size(); 115 | } 116 | 117 | /// @brief Call and event with context that is forwarded to all listeners. 118 | /// @param args The event context to notify listeners with. 119 | /// @return True if the event was successfully invoked, false otherwise. 120 | void invoke(E &&...args) 121 | { 122 | call(args...); 123 | } 124 | 125 | /// @brief Call an event with existing context to notify all listeners. 126 | /// @param args The event context to notify listeners with. 127 | /// @return True if the event was successfully invoked, false otherwise. 128 | void call(const E &...args) 129 | { 130 | { 131 | // Set flag to indicate we will be reading the list of callbacks, and 132 | // prevent other threads from modifying the list directly during this time 133 | LOCK_GUARD(Mutex, callbacksMutex); 134 | isExecuting = true; 135 | } 136 | 137 | // Execute the callbacks 138 | for (const auto &callback : callbacks) 139 | { 140 | callback.second(args...); 141 | } 142 | 143 | { 144 | LOCK_GUARD(Mutex, callbacksMutex); 145 | isExecuting = false; 146 | 147 | // Apply pending modifications to the callbacks list 148 | while (!modifications.empty()) 149 | { 150 | modifications.front()(); 151 | modifications.pop(); 152 | } 153 | } 154 | } 155 | 156 | private: 157 | std::unordered_map callbacks; ///< The list of callbacks 158 | bool isExecuting = false; ///< Whether the dispatcher is currently executing an event 159 | std::queue> modifications; ///< The modifications to the callbacks list 160 | Mutex callbacksMutex; ///< The mutex to protect the object from (unwanted) concurrent access 161 | EventCallbackHandle nextId = 0; // Counter for generating unique IDs 162 | }; 163 | } // namespace isobus 164 | 165 | #endif // EVENT_DISPATCHER_HPP 166 | -------------------------------------------------------------------------------- /src/flex_can_t4_plugin.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file flex_can_t4_plugin.cpp 3 | /// 4 | /// @brief An interface for using Teensy4/4.1 CAN hardware 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2023 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #include "flex_can_t4_plugin.hpp" 11 | #include "FlexCAN_T4.hpp" 12 | #include "can_stack_logger.hpp" 13 | 14 | namespace isobus 15 | { 16 | #if defined(__IMXRT1062__) 17 | FlexCAN_T4 FlexCANT4Plugin::can0; 18 | FlexCAN_T4 FlexCANT4Plugin::can1; 19 | FlexCAN_T4 FlexCANT4Plugin::can2; 20 | #elif defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) 21 | FlexCAN_T4 FlexCANT4Plugin::can0; 22 | FlexCAN_T4 FlexCANT4Plugin::can1; 23 | #endif 24 | 25 | FlexCANT4Plugin::FlexCANT4Plugin(std::uint8_t channel) : 26 | selectedChannel(channel) 27 | { 28 | } 29 | 30 | bool FlexCANT4Plugin::get_is_valid() const 31 | { 32 | return isOpen; 33 | } 34 | 35 | void FlexCANT4Plugin::close() 36 | { 37 | // Flex CAN doesn't have a way to stop it... 38 | isOpen = false; 39 | } 40 | 41 | void FlexCANT4Plugin::open() 42 | { 43 | if (0 == selectedChannel) 44 | { 45 | can0.begin(); 46 | can0.setBaudRate(250000); 47 | isOpen = true; 48 | } 49 | else if (1 == selectedChannel) 50 | { 51 | can1.begin(); 52 | can1.setBaudRate(250000); 53 | isOpen = true; 54 | } 55 | #if defined(__IMXRT1062__) 56 | else if (2 == selectedChannel) 57 | { 58 | can2.begin(); 59 | can2.setBaudRate(250000); 60 | isOpen = true; 61 | } 62 | #endif 63 | else 64 | { 65 | LOG_CRITICAL("[FlexCAN]: Invalid Channel Selected"); 66 | } 67 | } 68 | 69 | bool FlexCANT4Plugin::read_frame(isobus::CANMessageFrame &canFrame) 70 | { 71 | CAN_message_t message; 72 | bool retVal = false; 73 | 74 | if (0 == selectedChannel) 75 | { 76 | retVal = can0.read(message); 77 | canFrame.channel = 0; 78 | } 79 | else if (1 == selectedChannel) 80 | { 81 | retVal = can1.read(message); 82 | canFrame.channel = 1; 83 | } 84 | #if defined(__IMXRT1062__) 85 | else if (2 == selectedChannel) 86 | { 87 | retVal = can2.read(message); 88 | canFrame.channel = 2; 89 | } 90 | #endif 91 | 92 | memcpy(canFrame.data, message.buf, 8); 93 | canFrame.identifier = message.id; 94 | canFrame.dataLength = message.len; 95 | canFrame.isExtendedFrame = message.flags.extended; 96 | return retVal; 97 | } 98 | 99 | bool FlexCANT4Plugin::write_frame(const isobus::CANMessageFrame &canFrame) 100 | { 101 | CAN_message_t message; 102 | bool retVal = false; 103 | 104 | message.id = canFrame.identifier; 105 | message.len = canFrame.dataLength; 106 | message.flags.extended = true; 107 | message.seq = true; // Ask for sequential transmission 108 | memcpy(message.buf, canFrame.data, canFrame.dataLength); 109 | 110 | if (0 == selectedChannel) 111 | { 112 | retVal = can0.write(message); 113 | } 114 | else if (1 == selectedChannel) 115 | { 116 | retVal = can1.write(message); 117 | } 118 | #if defined(__IMXRT1062__) 119 | else if (2 == selectedChannel) 120 | { 121 | retVal = can2.write(message); 122 | } 123 | #endif 124 | return retVal; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/flex_can_t4_plugin.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file flex_can_t4_plugin.hpp 3 | /// 4 | /// @brief An interface for using FlexCAN_T4 on a Teensy4/4.1 device. 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2023 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #ifndef FLEX_CAN_T4_PLUGIN_HPP 10 | #define FLEX_CAN_T4_PLUGIN_HPP 11 | 12 | #include "FlexCAN_T4.hpp" 13 | #include "can_hardware_plugin.hpp" 14 | #include "can_hardware_abstraction.hpp" 15 | #include "can_message_frame.hpp" 16 | 17 | namespace isobus 18 | { 19 | /// @brief An interface for using FlexCAN_T4 on a Teensy4/4.1 device 20 | class FlexCANT4Plugin : public CANHardwarePlugin 21 | { 22 | public: 23 | /// @brief Constructor for the FlexCANT4Plugin 24 | /// @param[in] channel The channel to use on the device 25 | explicit FlexCANT4Plugin(std::uint8_t channel); 26 | 27 | /// @brief The destructor for FlexCANT4Plugin 28 | virtual ~FlexCANT4Plugin() = default; 29 | 30 | /// @brief Returns if the connection with the hardware is valid 31 | /// @returns `true` if connected, `false` if not connected 32 | bool get_is_valid() const override; 33 | 34 | /// @brief Closes the connection to the hardware 35 | void close() override; 36 | 37 | /// @brief Connects to the hardware you specified in the constructor's channel argument 38 | void open() override; 39 | 40 | /// @brief Returns a frame from the hardware (synchronous), or `false` if no frame can be read. 41 | /// @param[in, out] canFrame The CAN frame that was read 42 | /// @returns `true` if a CAN frame was read, otherwise `false` 43 | bool read_frame(isobus::CANMessageFrame &canFrame) override; 44 | 45 | /// @brief Writes a frame to the bus (synchronous) 46 | /// @param[in] canFrame The frame to write to the bus 47 | /// @returns `true` if the frame was written, otherwise `false` 48 | bool write_frame(const isobus::CANMessageFrame &canFrame) override; 49 | 50 | private: 51 | #if defined(__IMXRT1062__) 52 | static FlexCAN_T4 can0; 53 | static FlexCAN_T4 can1; 54 | static FlexCAN_T4 can2; 55 | #elif defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) 56 | static FlexCAN_T4 can0; 57 | static FlexCAN_T4 can1; 58 | #endif 59 | std::uint8_t selectedChannel; ///< The channel that this plugin is assigned to 60 | bool isOpen = false; ///< Tracks if the connection is open/connected 61 | }; 62 | } 63 | #endif // FLEX_CAN_T4_PLUGIN_HPP 64 | -------------------------------------------------------------------------------- /src/isobus_data_dictionary.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file isobus_data_dictionary.hpp 3 | /// 4 | /// @brief This file contains the definition of an auto-generated lookup of all ISOBUS DDIs 5 | /// as defined in ISO11783-11, exported from isobus.net. 6 | /// This file was generated January 25, 2024. 7 | /// 8 | /// @author Adrian Del Grosso 9 | /// @copyright 2024 The Open-Agriculture Developers 10 | //================================================================================================ 11 | #ifndef ISOBUS_DATA_DICTIONARY_HPP 12 | #define ISOBUS_DATA_DICTIONARY_HPP 13 | 14 | #include 15 | #include 16 | 17 | namespace isobus 18 | { 19 | /// @brief This class contains the definition of an auto-generated lookup of all ISOBUS DDIs 20 | class DataDictionary 21 | { 22 | public: 23 | /// @brief A struct containing the information for a single DDI 24 | struct Entry 25 | { 26 | const std::uint16_t ddi; ///< The DDI number 27 | 28 | const std::string name; ///< The name of the DDI 29 | const std::string units; ///< The units of the DDI 30 | const float resolution; ///< The resolution of the DDI 31 | }; 32 | 33 | /// @brief Checks the ISO 11783-11 database for the given DDI number 34 | /// and returns the corresponding entry if found. 35 | /// @param dataDictionaryIdentifier The DDI number to look up 36 | /// @return The entry for the given DDI number, or a default entry if not found 37 | static const Entry &get_entry(std::uint16_t dataDictionaryIdentifier); 38 | 39 | private: 40 | #ifndef DISABLE_ISOBUS_DATA_DICTIONARY 41 | static const Entry DDI_ENTRIES[715]; ///< A lookup table of all DDI entries in ISO11783-11 42 | #endif 43 | static const Entry DEFAULT_ENTRY; ///< A default "unknown" DDI to return if a DDI is not in the database 44 | }; 45 | } // namespace isobus 46 | 47 | #endif // ISOBUS_DATA_DICTIONARY_HPP 48 | -------------------------------------------------------------------------------- /src/isobus_heartbeat.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file isobus_heartbeat.hpp 3 | /// 4 | /// @brief Defines an interface for sending and receiving ISOBUS heartbeats. 5 | /// The heartbeat message is used to determine the integrity of the communication of messages and 6 | /// parameters being transmitted by a control function. There may be multiple instances of the 7 | /// heartbeat message on the network, and CFs are required transmit the message on request. 8 | /// As long as the heartbeat message is transmitted at the regular 9 | /// time interval and the sequence number increases through the valid range, then the 10 | /// heartbeat message indicates that the data source CF is operational and provides 11 | /// correct data in all its messages. 12 | /// 13 | /// @author Adrian Del Grosso 14 | /// 15 | /// @copyright 2024 The Open-Agriculture Developers 16 | //================================================================================================ 17 | #ifndef ISOBUS_HEARTBEAT_HPP 18 | #define ISOBUS_HEARTBEAT_HPP 19 | 20 | #include "can_callbacks.hpp" 21 | #include "can_internal_control_function.hpp" 22 | #include "can_message.hpp" 23 | #include "event_dispatcher.hpp" 24 | 25 | #include 26 | 27 | namespace isobus 28 | { 29 | /// @brief This class is used to send and receive ISOBUS heartbeats. 30 | class HeartbeatInterface 31 | { 32 | public: 33 | /// @brief This enum is used to define the possible errors that can occur when receiving a heartbeat. 34 | enum class HeartBeatError 35 | { 36 | InvalidSequenceCounter, ///< The sequence counter is not valid 37 | TimedOut ///< The heartbeat message has not been received within the repetition rate 38 | }; 39 | 40 | /// @brief Constructor for a HeartbeatInterface 41 | /// @param[in] sendCANFrameCallback A callback used to send CAN frames 42 | HeartbeatInterface(const CANMessageFrameCallback &sendCANFrameCallback); 43 | 44 | /// @brief This can be used to disable or enable this heartbeat functionality. 45 | /// It's probably best to leave it enabled for most applications, but it's not 46 | /// strictly needed. 47 | /// @note The interface is enabled by default. 48 | /// @param[in] enable Set to true to enable the interface, or false to disable it. 49 | void set_enabled(bool enable); 50 | 51 | /// @brief Returns if the interface is currently enabled or not. 52 | /// @note The interface is enabled by default. 53 | /// @returns true if the interface is enabled, false if the interface is disabled 54 | bool is_enabled() const; 55 | 56 | /// @brief This method can be used to request that another control function on the bus 57 | /// start sending the heartbeat message. This does not mean the request will be honored. 58 | /// In order to know if your request was accepted, you will need to either 59 | /// register for timeout events, register for heartbeat events, or check to see if your 60 | /// destination control function ever responded at some later time using the various methods 61 | /// available to you on this class' public interface. 62 | /// @note CFs may take up to 250ms to begin sending the heartbeat. 63 | /// @param[in] sourceControlFunction The internal control function to use when sending the request 64 | /// @param[in] destinationControlFunction The destination for the request 65 | /// @returns true if the request was transmitted, otherwise false. 66 | bool request_heartbeat(std::shared_ptr sourceControlFunction, 67 | std::shared_ptr destinationControlFunction) const; 68 | 69 | /// @brief Called by the internal control function class when a new internal control function is added. 70 | /// This allows us to respond to requests for heartbeats from other control functions. 71 | /// @param[in] newControlFunction The new internal control function 72 | void on_new_internal_control_function(std::shared_ptr newControlFunction); 73 | 74 | /// @brief Called when an internal control function is deleted. Cleans up stale registrations with 75 | /// PGN request protocol. 76 | /// @param[in] destroyedControlFunction The destroyed internal control function 77 | void on_destroyed_internal_control_function(std::shared_ptr destroyedControlFunction); 78 | 79 | /// @brief Returns an event dispatcher which can be used to register for heartbeat errors. 80 | /// Heartbeat errors are generated when a heartbeat message is not received within the 81 | /// repetition rate, or when the sequence counter is not valid. 82 | /// The control function that generated the error is passed as an argument to the event. 83 | /// @returns An event dispatcher for heartbeat errors 84 | EventDispatcher> &get_heartbeat_error_event_dispatcher(); 85 | 86 | /// @brief Returns an event dispatcher which can be used to register for new tracked heartbeat events. 87 | /// An event will be generated when a new control function is added to the list of CFs sending heartbeats. 88 | /// The control function that generated the error is passed as an argument to the event. 89 | /// @returns An event dispatcher for new tracked heartbeat events 90 | EventDispatcher> &get_new_tracked_heartbeat_event_dispatcher(); 91 | 92 | /// @brief Processes a CAN message, called by the network manager. 93 | /// @param[in] message The CAN message being received 94 | void process_rx_message(const CANMessage &message); 95 | 96 | /// @brief Updates the interface. Called by the network manager, 97 | /// so there is no need for you to call it in your application. 98 | void update(); 99 | 100 | private: 101 | /// @brief This enum is used to define special values for the sequence counter. 102 | enum class SequenceCounterSpecialValue : std::uint8_t 103 | { 104 | Initial = 251, ///< The heartbeat sequence number value shall be set to 251 once upon initialization of a CF. 105 | Error = 254, ///< Sequence Number value 254 indicates an error condition. 106 | NotAvailable = 255 ///< This value shall be used when the transmitted CF is in a shutdown status and is gracefully disconnecting from the network. 107 | }; 108 | 109 | static constexpr std::uint32_t SEQUENCE_TIMEOUT_MS = 300; ///< If the repetition rate exceeds 300 ms an error in the communication is detected. 110 | static constexpr std::uint32_t SEQUENCE_INITIAL_RESPONSE_TIMEOUT_MS = 250; ///< When requesting a heartbeat from another device, If no response for the repetition rate has been received after 250 ms, the requester shall assume that the request was not accepted 111 | static constexpr std::uint32_t SEQUENCE_REPETITION_RATE_MS = 100; ///< A consuming CF shall send a Request for Repetition rate for the heart beat message with a repetition rate of 100 ms 112 | 113 | /// @brief This class is used to store information about a tracked heartbeat. 114 | class Heartbeat 115 | { 116 | public: 117 | /// @brief Constructor for a Heartbeat 118 | /// @param[in] sendingControlFunction The control function that is sending the heartbeat 119 | explicit Heartbeat(std::shared_ptr sendingControlFunction); 120 | 121 | /// @brief Transmits a heartbeat message (for internal control functions only). 122 | /// Updates the sequence counter and timestamp if needed. 123 | /// @param[in] parent The parent interface 124 | /// @returns True if the message is sent, otherwise false. 125 | bool send(const HeartbeatInterface &parent); 126 | 127 | std::shared_ptr controlFunction; ///< The CF that is sending the message 128 | std::uint32_t timestamp_ms; ///< The last time the message was sent by the associated control function 129 | std::uint32_t repetitionRate_ms = SEQUENCE_REPETITION_RATE_MS; ///< For internal control functions, this controls how often the heartbeat is sent. This should really stay at the standard 100ms defined in ISO11783-7. 130 | std::uint8_t sequenceCounter = static_cast(SequenceCounterSpecialValue::Initial); ///< The sequence counter used to validate the heartbeat. Counts from 0-250 normally. 131 | }; 132 | 133 | /// @brief Processes a PGN request for a heartbeat. 134 | /// @param[in] parameterGroupNumber The PGN being requested 135 | /// @param[in] requestingControlFunction The control function that is requesting the heartbeat 136 | /// @param[in] targetControlFunction The control function that is being requested to send the heartbeat 137 | /// @param[in] repetitionRate The repetition rate for the heartbeat 138 | /// @param[in] parentPointer A context variable to find the relevant instance of this class 139 | /// @returns True if the request was transmitted, otherwise false. 140 | static bool process_request_for_heartbeat(std::uint32_t parameterGroupNumber, 141 | std::shared_ptr requestingControlFunction, 142 | std::shared_ptr targetControlFunction, 143 | std::uint32_t repetitionRate, 144 | void *parentPointer); 145 | 146 | const CANMessageFrameCallback sendCANFrameCallback; ///< A callback for sending a CAN frame 147 | EventDispatcher> heartbeatErrorEventDispatcher; ///< Event dispatcher for heartbeat errors 148 | EventDispatcher> newTrackedHeartbeatEventDispatcher; ///< Event dispatcher for when a heartbeat message from another control function becomes tracked by this interface 149 | std::list trackedHeartbeats; ///< Store tracked heartbeat data, per CF 150 | bool enabled = true; ///< Attribute that specifies if this interface is enabled. When false, the interface does nothing. 151 | }; 152 | } // namespace isobus 153 | 154 | #endif 155 | -------------------------------------------------------------------------------- /src/isobus_preferred_addresses.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file isobus_preferred_addresses.hpp 3 | /// 4 | /// @brief This is a reference for control function's preferred addresses as defined by 5 | /// ISO 11783-11 and/or SAE. Preferred addresses are industry group specific. 6 | /// You should use these when your are creating a control function that is a well known 7 | /// function type, but if your control function doesn't arbitrate for that address, the 8 | /// stack will claim for you in the dynamic address range. 9 | /// 10 | /// @author Adrian Del Grosso 11 | /// 12 | /// @copyright 2024 The Open-Agriculture Developers 13 | //================================================================================================ 14 | #ifndef ISOBUS_PREFERRED_ADDRESSES_HPP 15 | #define ISOBUS_PREFERRED_ADDRESSES_HPP 16 | 17 | #include 18 | 19 | namespace isobus 20 | { 21 | /// @brief This namespace contains all defined preferred addresses for control functions, which 22 | /// should be used when creating a control function that is a well known function type. 23 | namespace preferred_addresses 24 | { 25 | /// @brief Industry Group 1 applies to on-highway equipment. 26 | namespace IndustryGroup1 27 | { 28 | /// This enumerates all preferred addresses for industry group 1. 29 | enum PreferredAddress : std::uint8_t 30 | { 31 | // 128-158 are reserved for future assignment by SAE but available for use by self configurable ECUs 32 | AutomatedDrivingController2 = 156, 33 | ElectricPropulsionControlUnit3 = 157, 34 | AutomatedDrivingController1 = 158, 35 | RoadwayInformationSystem = 159, 36 | AdvancedEmergencyBrakingSystem = 160, 37 | FifthWheelSmartSystems = 161, 38 | SlopeSensor = 162, 39 | CatalystFluidSensor = 163, 40 | OnBoardDiagnosticUnit2 = 164, 41 | RearSteeringAxleController2 = 165, 42 | RearSteeringAxleController3 = 166, 43 | InstrumentCluster2 = 167, 44 | Trailer5Bridge = 168, 45 | Trailer5LightingElectrical = 169, 46 | Trailer5BrakesABS_EBS = 170, 47 | Trailer5Reefer = 171, 48 | Trailer5Cargo = 172, 49 | Trailer5ChassisSuspension = 173, 50 | OtherTrailer5Devices = 174, 51 | OtherTrailer5Devices2 = 175, 52 | Trailer4Bridge = 176, 53 | Trailer4LightingElectrical = 177, 54 | Trailer4BrakesABS_EBS = 178, 55 | Trailer4Reefer = 179, 56 | Trailer4Cargo = 180, 57 | Trailer4ChassisSuspension = 181, 58 | OtherTrailer4Devices = 182, 59 | OtherTrailer4Devices2 = 183, 60 | Trailer3Bridge = 184, 61 | Trailer3LightingElectrical = 185, 62 | Trailer3BrakesABS_EBS = 186, 63 | Trailer3Reefer = 187, 64 | Trailer3Cargo = 188, 65 | Trailer3ChassisSuspension = 189, 66 | OtherTrailer3Devices = 190, 67 | OtherTrailer3Devices2 = 191, 68 | Trailer2Bridge = 192, 69 | Trailer2LightingElectrical = 193, 70 | Trailer2BrakesABS_EBS = 194, 71 | Trailer2Reefer = 195, 72 | Trailer2Cargo = 196, 73 | Trailer2ChassisSuspension = 197, 74 | OtherTrailer2Devices = 198, 75 | OtherTrailer2Devices2 = 199, 76 | Trailer1Bridge = 200, 77 | Trailer1LightingElectrical = 201, 78 | Trailer1BrakesABS_EBS = 202, 79 | Trailer1Reefer = 203, 80 | Trailer1Cargo = 204, 81 | Trailer1ChassisSuspension = 205, 82 | OtherTrailer1Devices = 206, 83 | OtherTrailer1Devices2 = 207, 84 | SteeringBodyUnit = 228, 85 | BodyController2 = 229, 86 | BodyToVehicleInterfaceControl = 230, 87 | ArticulationTurntableControl = 231, 88 | ForwardRoadImageProcessor = 232, 89 | DoorController3 = 233, 90 | DoorController4 = 234, 91 | TractorTrailerBridge2 = 235, 92 | DoorController1 = 236, 93 | DoorController2 = 237, 94 | Tachograph = 238, 95 | ElectricPropulsionControlUnit1 = 239, 96 | ElectricPropulsionControlUnit2 = 240, 97 | WWH_OBDTester = 241, 98 | ElectricPropulsionControlUnit4 = 242, 99 | BatteryPackMonitor1 = 243, 100 | BatteryPackMonitor2_APU4 = 244, 101 | BatteryPackMonitor3_APU3 = 245, 102 | BatteryPackMonitor4_APU2 = 246, 103 | AuxiliaryPowerUnit_APU1 = 247 104 | }; 105 | } // namespace IndustryGroup1 106 | 107 | /// @brief Industry Group 2 applies to agricultural and forestry equipment. 108 | namespace IndustryGroup2 109 | { 110 | /// This enumerates all preferred addresses for industry group 2. 111 | enum PreferredAddress : std::uint8_t 112 | { 113 | // 128-235 are reserved by ISO for the self-configurable address capability 114 | DataLogger = 236, 115 | TIMServer = 237, 116 | SequenceController = 238, 117 | PositionControl = 239, 118 | TractorECU = 240, 119 | TailingsMonitoring = 241, 120 | HeaderControl = 242, 121 | ProductLossMonitoring = 243, 122 | ProductMoistureSensing = 244, 123 | NonVirtualTerminalDisplay_ImplementBus = 245, 124 | OperatorControls_MachineSpecific = 246, 125 | TaskController_MappingComputer = 247 126 | }; 127 | } // namespace IndustryGroup1 128 | 129 | /// @brief Industry Group 3 applies to construction equipment. 130 | namespace IndustryGroup3 131 | { 132 | /// This enumerates all preferred addresses for industry group 3. 133 | enum PreferredAddress : std::uint8_t 134 | { 135 | /// 128 thru 207 are reserved for future assignment by SAE 136 | /// 208 thru 223 are reserved for future assignment 137 | RotationSensor = 224, 138 | LiftArmController = 225, 139 | SlopeSensor = 226, 140 | MainController_SkidSteerLoader = 227, 141 | LoaderControl = 228, 142 | LaserTracer = 229, 143 | LandLevelingSystemDisplay = 230, 144 | SingleLandLevelingSystemSupervisor = 231, 145 | LandLevelingElectricMast = 232, 146 | SingleLandLevelingSystemOperatorInterface = 233, 147 | LaserReceiver = 234, 148 | SupplementalSensorProcessingUnit1 = 235, 149 | SupplementalSensorProcessingUnit2 = 236, 150 | SupplementalSensorProcessingUnit3 = 237, 151 | SupplementalSensorProcessingUnit4 = 238, 152 | SupplementalSensorProcessingUnit5 = 239, 153 | SupplementalSensorProcessingUnit6 = 240, 154 | EngineMonitor1 = 241, 155 | EngineMonitor2 = 242, 156 | EngineMonitor3 = 243, 157 | EngineMonitor4 = 244, 158 | EngineMonitor5 = 245, 159 | EngineMonitor6 = 246, 160 | EngineMonitor7 = 247 161 | }; 162 | } // namespace IndustryGroup3 163 | 164 | /// @brief Industry Group 4 applies to marine equipment. 165 | namespace IndustryGroup4 166 | { 167 | /// This enumerates all preferred addresses for industry group 4. 168 | enum PreferredAddress : std::uint8_t 169 | { 170 | /// 128 thru 207 are reserved for future assignment by SAE 171 | /// 208 thru 227 are reserved for future assignment 172 | PropulsionSensorHubAndGateway1 = 228, 173 | PropulsionSensorHubAndGateway2 = 229, 174 | PropulsionSensorHubAndGateway3 = 230, 175 | PropulsionSensorHubAndGateway4 = 231, 176 | Transmission3 = 232, 177 | Transmission4 = 233, 178 | Transmission5 = 234, 179 | Transmission6 = 235, 180 | Display1forProtectionSystemforMarineEngines = 236, 181 | ProtectionSystemforMarineEngines = 237, 182 | AlarmSystemControl1forMarineEngines = 238, 183 | Engine3 = 239, 184 | Engine4 = 240, 185 | Engine5 = 241, 186 | MarineDisplay1 = 242, 187 | MarineDisplay2 = 243, 188 | MarineDisplay3 = 244, 189 | MarineDisplay4 = 245, 190 | MarineDisplay5 = 246, 191 | MarineDisplay6 = 247 192 | }; 193 | } // namespace IndustryGroup4 194 | 195 | /// @brief Industry Group 5 applies to industrial process control stationary equipment (Gen-sets). 196 | namespace IndustryGroup5 197 | { 198 | /// This enumerates all preferred addresses for industry group 5. 199 | enum PreferredAddress : std::uint8_t 200 | { 201 | /// 128 thru 207 are reserved for future assignment by SAE 202 | /// 208 thru 229 are reserved for future assignment 203 | GeneratorVoltageRegulator = 230, 204 | Engine3 = 231, 205 | Engine4 = 232, 206 | Engine5 = 233, 207 | GeneratorSetController = 234, 208 | SupplementalSensorProcessingUnit1 = 235, 209 | SupplementalSensorProcessingUnit2 = 236, 210 | SupplementalSensorProcessingUnit3 = 237, 211 | SupplementalSensorProcessingUnit4 = 238, 212 | SupplementalSensorProcessingUnit5 = 239, 213 | SupplementalSensorProcessingUnit6 = 240, 214 | EngineMonitor1 = 241, 215 | EngineMonitor2 = 242, 216 | EngineMonitor3 = 243, 217 | EngineMonitor4 = 244, 218 | EngineMonitor5 = 245, 219 | EngineMonitor6 = 246, 220 | EngineMonitor7 = 247 221 | }; 222 | } // namespace IndustryGroup5 223 | } // namespace preferred_addresses 224 | } // namespace isobus 225 | #endif // ISOBUS_PREFERRED_ADDRESSES_HPP 226 | -------------------------------------------------------------------------------- /src/isobus_shortcut_button_interface.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file isobus_shortcut_button_interface.cpp 3 | /// 4 | /// @brief Implements the interface for an ISOBUS shortcut button 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2023 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #include "isobus_shortcut_button_interface.hpp" 10 | 11 | #include "can_constants.hpp" 12 | #include "can_general_parameter_group_numbers.hpp" 13 | #include "can_network_manager.hpp" 14 | #include "can_stack_logger.hpp" 15 | #include "system_timing.hpp" 16 | 17 | #include 18 | #include 19 | 20 | namespace isobus 21 | { 22 | ShortcutButtonInterface::ShortcutButtonInterface(std::shared_ptr internalControlFunction, bool serverEnabled) : 23 | sourceControlFunction(internalControlFunction), 24 | txFlags(static_cast(TransmitFlags::NumberOfFlags), process_flags, this), 25 | actAsISBServer(serverEnabled) 26 | { 27 | assert(nullptr != sourceControlFunction); // You need an internal control function for the interface to function 28 | } 29 | 30 | ShortcutButtonInterface::~ShortcutButtonInterface() 31 | { 32 | if (initialized) 33 | { 34 | CANNetworkManager::CANNetwork.remove_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::AllImplementsStopOperationsSwitchState), process_rx_message, this); 35 | } 36 | } 37 | 38 | void ShortcutButtonInterface::initialize() 39 | { 40 | if (!initialized) 41 | { 42 | CANNetworkManager::CANNetwork.add_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::AllImplementsStopOperationsSwitchState), 43 | process_rx_message, 44 | this); 45 | initialized = true; 46 | } 47 | } 48 | 49 | bool ShortcutButtonInterface::get_is_initialized() const 50 | { 51 | return initialized; 52 | } 53 | 54 | EventDispatcher &ShortcutButtonInterface::get_stop_all_implement_operations_state_event_dispatcher() 55 | { 56 | return ISBEventDispatcher; 57 | } 58 | 59 | void ShortcutButtonInterface::set_stop_all_implement_operations_state(StopAllImplementOperationsState newState) 60 | { 61 | if (actAsISBServer) 62 | { 63 | if (newState != commandedState) 64 | { 65 | commandedState = newState; 66 | 67 | if (StopAllImplementOperationsState::StopImplementOperations == newState) 68 | { 69 | LOG_ERROR("[ISB]: All implement operations must stop. (Triggered internally)"); 70 | } 71 | else 72 | { 73 | LOG_INFO("[ISB]: Internal ISB state is now permitted."); 74 | } 75 | txFlags.set_flag(static_cast(TransmitFlags::SendStopAllImplementOperationsSwitchState)); 76 | } 77 | } 78 | else 79 | { 80 | LOG_ERROR("[ISB]: You are attempting to set the internal ISB state but the ISB interface is not configured as a server!"); 81 | } 82 | } 83 | 84 | ShortcutButtonInterface::StopAllImplementOperationsState ShortcutButtonInterface::get_state() const 85 | { 86 | StopAllImplementOperationsState retVal = StopAllImplementOperationsState::PermitAllImplementsToOperationOn; 87 | 88 | if (StopAllImplementOperationsState::StopImplementOperations == commandedState) 89 | { 90 | retVal = commandedState; 91 | } 92 | else 93 | { 94 | for (auto ISB = isobusShorcutButtonList.cbegin(); ISB != isobusShorcutButtonList.end(); ISB++) 95 | { 96 | if (StopAllImplementOperationsState::StopImplementOperations == ISB->commandedState) 97 | { 98 | retVal = ISB->commandedState; // Any stop condition will be returned. 99 | break; 100 | } 101 | } 102 | } 103 | return retVal; 104 | } 105 | 106 | void ShortcutButtonInterface::update() 107 | { 108 | if (SystemTiming::time_expired_ms(allImplementsStopOperationsSwitchStateTimestamp_ms, TRANSMISSION_RATE_MS)) 109 | { 110 | // Prune old ISBs 111 | isobusShorcutButtonList.erase(std::remove_if(isobusShorcutButtonList.begin(), isobusShorcutButtonList.end(), [](ISBServerData &isb) { 112 | return SystemTiming::time_expired_ms(isb.messageReceivedTimestamp_ms, TRANSMISSION_TIMEOUT_MS); 113 | }), 114 | isobusShorcutButtonList.end()); 115 | 116 | txFlags.set_flag(static_cast(TransmitFlags::SendStopAllImplementOperationsSwitchState)); 117 | } 118 | txFlags.process_all_flags(); 119 | } 120 | 121 | void ShortcutButtonInterface::process_rx_message(const CANMessage &message, void *parentPointer) 122 | { 123 | assert(nullptr != parentPointer); 124 | 125 | static_cast(parentPointer)->process_message(message); 126 | } 127 | 128 | void ShortcutButtonInterface::process_flags(std::uint32_t flag, void *parent) 129 | { 130 | auto myInterface = static_cast(parent); 131 | bool transmitSuccessful = true; 132 | assert(nullptr != parent); 133 | 134 | if (flag == static_cast(TransmitFlags::SendStopAllImplementOperationsSwitchState)) 135 | { 136 | transmitSuccessful = myInterface->send_stop_all_implement_operations_switch_state(); 137 | 138 | if (transmitSuccessful) 139 | { 140 | myInterface->stopAllImplementOperationsTransitionNumber++; 141 | } 142 | } 143 | 144 | if (!transmitSuccessful) 145 | { 146 | myInterface->txFlags.set_flag(flag); 147 | } 148 | } 149 | 150 | void ShortcutButtonInterface::process_message(const CANMessage &message) 151 | { 152 | if ((message.get_can_port_index() == sourceControlFunction->get_can_port()) && 153 | (static_cast(CANLibParameterGroupNumber::AllImplementsStopOperationsSwitchState) == message.get_identifier().get_parameter_group_number())) 154 | { 155 | if (CAN_DATA_LENGTH == message.get_data_length()) 156 | { 157 | auto messageNAME = message.get_source_control_function()->get_NAME(); 158 | auto matches_isoname = [messageNAME](ISBServerData &isb) { return isb.ISONAME == messageNAME; }; 159 | auto ISB = std::find_if(isobusShorcutButtonList.begin(), isobusShorcutButtonList.end(), matches_isoname); 160 | auto &messageData = message.get_data(); 161 | StopAllImplementOperationsState previousState = get_state(); 162 | 163 | if (isobusShorcutButtonList.end() == ISB) 164 | { 165 | ISBServerData newISB; 166 | 167 | LOG_DEBUG("[ISB]: New ISB detected at address %u", message.get_identifier().get_source_address()); 168 | newISB.ISONAME = messageNAME; 169 | isobusShorcutButtonList.emplace_back(newISB); 170 | ISB = std::prev(isobusShorcutButtonList.end()); 171 | } 172 | 173 | if (isobusShorcutButtonList.end() != ISB) 174 | { 175 | std::uint8_t newTransitionCount = messageData.at(6); 176 | 177 | if (((ISB->stopAllImplementOperationsTransitionNumber == 255) && 178 | (0 != newTransitionCount)) || 179 | ((ISB->stopAllImplementOperationsTransitionNumber < 255) && 180 | (newTransitionCount > ISB->stopAllImplementOperationsTransitionNumber + 1))) 181 | { 182 | // A Working Set shall consider an increase in the transitions without detecting a corresponding 183 | // transition of the Stop all implement operations state as an error and react accordingly. 184 | ISB->commandedState = StopAllImplementOperationsState::StopImplementOperations; 185 | LOG_ERROR("[ISB]: Missed an ISB transition from ISB at address %u", message.get_identifier().get_source_address()); 186 | } 187 | else 188 | { 189 | ISB->commandedState = static_cast(messageData.at(7) & 0x03); 190 | } 191 | ISB->messageReceivedTimestamp_ms = SystemTiming::get_timestamp_ms(); 192 | ISB->stopAllImplementOperationsTransitionNumber = messageData.at(6); 193 | 194 | auto newState = get_state(); 195 | if (previousState != newState) 196 | { 197 | if (StopAllImplementOperationsState::StopImplementOperations == newState) 198 | { 199 | LOG_ERROR("[ISB]: All implement operations must stop. (ISB at address %u has commanded it)", message.get_identifier().get_source_address()); 200 | } 201 | else 202 | { 203 | LOG_INFO("[ISB]: Implement operations now permitted."); 204 | } 205 | ISBEventDispatcher.call(newState); 206 | } 207 | } 208 | } 209 | else 210 | { 211 | LOG_WARNING("[ISB]: Received malformed All Implements Stop Operations Switch State. DLC must be 8."); 212 | } 213 | } 214 | } 215 | 216 | bool ShortcutButtonInterface::send_stop_all_implement_operations_switch_state() const 217 | { 218 | std::array buffer = { 219 | 0xFF, 220 | 0xFF, 221 | 0xFF, 222 | 0xFF, 223 | 0xFF, 224 | 0xFF, 225 | stopAllImplementOperationsTransitionNumber, 226 | static_cast(0xFC | static_cast(commandedState)) 227 | }; 228 | 229 | return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::AllImplementsStopOperationsSwitchState), 230 | buffer.data(), 231 | static_cast(buffer.size()), 232 | sourceControlFunction, 233 | nullptr, 234 | CANIdentifier::CANPriority::Priority3); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/isobus_time_date_interface.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file isobus_time_date_interface.hpp 3 | /// 4 | /// @brief Defines an interface for accessing or sending time and date information using 5 | /// the Time/Date (TD) PGN. Can be useful for interacting with an ISOBUS file server, 6 | /// or just for keeping track of time and date information as provided by some authoritative 7 | /// control function on the bus. Control functions which provide the message this interface 8 | /// manages are expected to have a real-time clock (RTC) or GPS time source. 9 | /// @author Adrian Del Grosso 10 | /// 11 | /// @copyright 2024 The Open-Agriculture Developers 12 | //================================================================================================ 13 | #ifndef ISOBUS_TIME_DATE_INTERFACE_HPP 14 | #define ISOBUS_TIME_DATE_INTERFACE_HPP 15 | 16 | #include "can_callbacks.hpp" 17 | #include "can_internal_control_function.hpp" 18 | #include "can_message.hpp" 19 | #include "event_dispatcher.hpp" 20 | 21 | namespace isobus 22 | { 23 | /// @brief An interface for sending and receiving time and date information using the Time/Date (TD) PGN, 0xFEE6. 24 | /// You may hear this time referred to as "ISOBUS Time" in some cases. It is normally provided by control functions with a 25 | /// real-time clock (RTC) or GPS source. This is not the same thing as the NMEA2000 time and date, which is PGN 129033 (0x1F809), and is 26 | /// backwards compatible with J1939 which uses this same PGN and message structure. 27 | class TimeDateInterface 28 | { 29 | public: 30 | /// @brief A struct to hold time and date information. 31 | /// This will generally be a UTC time and date, unless the local hour offset is 0, 32 | /// in which case it will be a local time and date. 33 | /// We store it slightly differently than the PGN to make it easier to work with. 34 | struct TimeAndDate 35 | { 36 | std::uint16_t milliseconds = 0; ///< Number of milliseconds. This has resolution of 0.25s, so it will be either 0, 250, 500, or 750 37 | std::uint8_t seconds = 0; ///< Number of seconds, range: 0 to 59s 38 | std::uint8_t minutes = 0; ///< Number of minutes, range: 0 to 59m 39 | std::uint8_t hours = 0; ///< Number of hours, range: 0 to 23h 40 | std::uint8_t quarterDays = 0; ///< Number of quarter days. This is a less precise version of "hours" that is used in some cases. Range: 0 to 3. 0 is midnight, 1 is 6am, 2 is noon, 3 is 6pm 41 | std::uint8_t day = 0; ///< Number of days, range 0 to 31 42 | std::uint8_t month = 0; ///< Number of months, range 1 to 12 43 | std::uint16_t year = 1985; ///< The year. Range: 1985 to 2235 44 | std::int8_t localMinuteOffset = 0; ///< Local minute offset is the number of minutes between the UTC time and date and a local time and date. This value is added to UTC time and date to determine the local time and date. The local offset is a positive value for times east of the Prime Meridian to the International Date Line. 45 | std::int8_t localHourOffset = 0; ///< Local hour offset is the number of hours between the UTC time and date and a local time and date. This value is added to UTC time and date to determine the local time and date. The local offset is a positive value for times east of the Prime Meridian to the International Date Line. 46 | }; 47 | 48 | /// @brief A struct to hold time and date information and the control function that sent it. 49 | /// Used by the event dispatcher to provide event driven access to time and date information. 50 | struct TimeAndDateInformation 51 | { 52 | TimeAndDate timeAndDate; ///< The time and date information 53 | std::shared_ptr controlFunction; ///< The control function that sent the time and date information 54 | }; 55 | 56 | /// @brief Constructor for the TimeDateInterface class, with no source control function. 57 | /// Receives time and date information from the bus, and does not transmit. 58 | /// This is generally the normal use case for this class. 59 | TimeDateInterface() = default; 60 | 61 | /// @brief Constructor for the TimeDateInterface class, used for when you want to also transmit the time/date. 62 | /// @param sourceControlFunction If you want to transmit the time and date information, you 63 | /// can pass a control function in this parameter to be used as the source of the information. 64 | /// @param timeAndDateCallback A callback that will be called when the interface needs you to tell it the current time and date. 65 | /// This is used to populate the time and date information that will be sent out on the bus. The function you use for this callback 66 | /// should be relatively quick as it will be called from the CAN stack's thread, and you don't want to delay the stack's update thread. 67 | /// The function should return "true" if the time and date information was successfully populated, and "false" if it was not. 68 | /// Note that if it returns false, the request will probably be NACKed, which is not ideal. 69 | TimeDateInterface(std::shared_ptr sourceControlFunction, std::function timeAndDateCallback); 70 | 71 | /// @brief Destructor for the TimeDateInterface class. 72 | ~TimeDateInterface(); 73 | 74 | /// @brief Deleted copy constructor for TimeDateInterface 75 | TimeDateInterface(TimeDateInterface &) = delete; 76 | 77 | /// @brief Initializes the interface. 78 | /// @details This needs to be called before the interface is usable. 79 | /// It registers its PGN callback and sets up the PGN request interface 80 | /// if needed. 81 | void initialize(); 82 | 83 | /// @brief Returns if initialize has been called yet 84 | /// @return `true` if initialize has been called, otherwise false 85 | bool is_initialized() const; 86 | 87 | /// @brief Returns the event dispatcher for time and date information. 88 | /// Use this to subscribe to event-driven time and date information events. 89 | /// @return The event dispatcher for time and date information 90 | EventDispatcher &get_event_dispatcher(); 91 | 92 | /// @brief Sends a time and date message (a broadcast message) as long as the interface 93 | /// has been initialized and a control function has been set. 94 | /// @param timeAndDateToSend The time and date information to send 95 | /// @return `true` if the message was sent, otherwise `false` 96 | bool send_time_and_date(const TimeAndDate &timeAndDateToSend) const; 97 | 98 | /// @brief Requests time and date information from a specific control function, or from all control functions to see if any respond. 99 | /// Responses can be monitored by using the event dispatcher. See get_event_dispatcher. 100 | /// This is really just a very thin wrapper around the PGN request interface for convenience. 101 | /// @param requestingControlFunction This control function will be used to send the request. 102 | /// @param optionalDestination If you want to request time and date information from a specific control function, you can pass it here, otherwise pass an empty pointer. 103 | /// @return `true` if the request was sent, otherwise `false` 104 | bool request_time_and_date(std::shared_ptr requestingControlFunction, std::shared_ptr optionalDestination = nullptr) const; 105 | 106 | /// @brief Returns the control function that is being used as the source of the time and date information if one was set. 107 | /// @return The control function that is being used as the source of the time and date information, or an empty pointer if one was not set. 108 | std::shared_ptr get_control_function() const; 109 | 110 | private: 111 | /// @brief Parses incoming CAN messages into usable unit and language settings 112 | /// @param message The CAN message to parse 113 | /// @param parentPointer A generic context variable, usually the `this` pointer for this interface instance 114 | static void process_rx_message(const CANMessage &message, void *parentPointer); 115 | 116 | /// @brief Processes a PGN request 117 | /// @param[in] parameterGroupNumber The PGN being requested 118 | /// @param[in] requestingControlFunction The control function that is requesting the PGN 119 | /// @param[in] acknowledge If the request should be acknowledged (will always be false for this interface) 120 | /// @param[in] acknowledgeType How to acknowledge the request (will always be NACK for this interface) 121 | /// @param[in] parentPointer A context variable to find the relevant instance of this class 122 | /// @returns True if the request was serviced, otherwise false. 123 | static bool process_request_for_time_date(std::uint32_t parameterGroupNumber, 124 | std::shared_ptr requestingControlFunction, 125 | bool &acknowledge, 126 | AcknowledgementType &acknowledgeType, 127 | void *parentPointer); 128 | 129 | std::shared_ptr myControlFunction; ///< The control function to send messages as, or an empty pointer if not sending 130 | std::function userTimeDateCallback; ///< The callback the user provided to get the time and date information at runtime to be transmitted 131 | EventDispatcher timeAndDateEventDispatcher; ///< The event dispatcher for time and date information 132 | bool initialized = false; ///< If the interface has been initialized yet 133 | }; 134 | } // namespace isobus 135 | #endif // ISOBUS_TIME_DATE_INTERFACE_HPP 136 | -------------------------------------------------------------------------------- /src/isobus_virtual_terminal_client_update_helper.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file isobus_virtual_terminal_client_update_helper.cpp 3 | /// 4 | /// @brief A helper class to update and track the state of an active working set. 5 | /// @author Daan Steenbergen 6 | /// 7 | /// @copyright 2023 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #include "isobus_virtual_terminal_client_update_helper.hpp" 11 | 12 | #include "can_stack_logger.hpp" 13 | 14 | namespace isobus 15 | { 16 | VirtualTerminalClientUpdateHelper::VirtualTerminalClientUpdateHelper(std::shared_ptr client) : 17 | VirtualTerminalClientStateTracker(nullptr == client ? nullptr : client->get_internal_control_function()), 18 | vtClient(client) 19 | { 20 | if (nullptr == client) 21 | { 22 | LOG_ERROR("[VTStateHelper] constructor: client is nullptr"); 23 | return; 24 | } 25 | numericValueChangeEventHandle = client->get_vt_change_numeric_value_event_dispatcher().add_listener( 26 | std::bind(&VirtualTerminalClientUpdateHelper::process_numeric_value_change_event, this, std::placeholders::_1)); 27 | } 28 | 29 | VirtualTerminalClientUpdateHelper::~VirtualTerminalClientUpdateHelper() 30 | { 31 | if (nullptr != vtClient) 32 | { 33 | vtClient->get_vt_change_numeric_value_event_dispatcher().remove_listener(numericValueChangeEventHandle); 34 | } 35 | } 36 | 37 | bool VirtualTerminalClientUpdateHelper::set_numeric_value(std::uint16_t object_id, std::uint32_t value) 38 | { 39 | if (nullptr == client) 40 | { 41 | LOG_ERROR("[VTStateHelper] set_numeric_value: client is nullptr"); 42 | return false; 43 | } 44 | if (numericValueStates.find(object_id) == numericValueStates.end()) 45 | { 46 | LOG_WARNING("[VTStateHelper] set_numeric_value: objectId %lu not tracked", object_id); 47 | return false; 48 | } 49 | if (numericValueStates.at(object_id) == value) 50 | { 51 | return true; 52 | } 53 | 54 | bool success = vtClient->send_change_numeric_value(object_id, value); 55 | if (success) 56 | { 57 | numericValueStates[object_id] = value; 58 | } 59 | return success; 60 | } 61 | 62 | bool VirtualTerminalClientUpdateHelper::increase_numeric_value(std::uint16_t object_id, std::uint32_t step) 63 | { 64 | return set_numeric_value(object_id, get_numeric_value(object_id) + step); 65 | } 66 | 67 | bool VirtualTerminalClientUpdateHelper::decrease_numeric_value(std::uint16_t object_id, std::uint32_t step) 68 | { 69 | return set_numeric_value(object_id, get_numeric_value(object_id) - step); 70 | } 71 | 72 | void VirtualTerminalClientUpdateHelper::set_callback_validate_numeric_value(const std::function &callback) 73 | { 74 | callbackValidateNumericValue = callback; 75 | } 76 | 77 | void VirtualTerminalClientUpdateHelper::process_numeric_value_change_event(const VirtualTerminalClient::VTChangeNumericValueEvent &event) 78 | { 79 | if (numericValueStates.find(event.objectID) == numericValueStates.end()) 80 | { 81 | // Only proccess numeric value changes for tracked objects. 82 | return; 83 | } 84 | 85 | if (numericValueStates.at(event.objectID) == event.value) 86 | { 87 | // Do not process the event if the value has not changed. 88 | return; 89 | } 90 | 91 | std::uint32_t targetValue = event.value; // Default to the value received in the event. 92 | if ((callbackValidateNumericValue != nullptr) && callbackValidateNumericValue(event.objectID, event.value)) 93 | { 94 | // If the callback function returns false, reject the change by sending the previous value. 95 | targetValue = numericValueStates.at(event.objectID); 96 | } 97 | vtClient->send_change_numeric_value(event.objectID, targetValue); 98 | } 99 | 100 | bool VirtualTerminalClientUpdateHelper::set_active_data_or_alarm_mask(std::uint16_t workingSetId, std::uint16_t dataOrAlarmMaskId) 101 | { 102 | if (nullptr == client) 103 | { 104 | LOG_ERROR("[VTStateHelper] set_active_data_or_alarm_mask: client is nullptr"); 105 | return false; 106 | } 107 | if (activeDataOrAlarmMask == dataOrAlarmMaskId) 108 | { 109 | return true; 110 | } 111 | 112 | bool success = vtClient->send_change_active_mask(workingSetId, dataOrAlarmMaskId); 113 | if (success) 114 | { 115 | activeDataOrAlarmMask = dataOrAlarmMaskId; 116 | } 117 | return success; 118 | } 119 | 120 | bool VirtualTerminalClientUpdateHelper::set_active_soft_key_mask(VirtualTerminalClient::MaskType maskType, std::uint16_t maskId, std::uint16_t softKeyMaskId) 121 | { 122 | if (nullptr == client) 123 | { 124 | LOG_ERROR("[VTStateHelper] set_active_soft_key_mask: client is nullptr"); 125 | return false; 126 | } 127 | if (softKeyMasks.find(maskId) == softKeyMasks.end()) 128 | { 129 | LOG_WARNING("[VTStateHelper] set_active_soft_key_mask: data/alarm mask '%lu' not tracked", maskId); 130 | return false; 131 | } 132 | if (softKeyMasks.at(maskId) == softKeyMaskId) 133 | { 134 | return true; 135 | } 136 | 137 | bool success = vtClient->send_change_softkey_mask(maskType, maskId, softKeyMaskId); 138 | if (success) 139 | { 140 | softKeyMasks[maskId] = softKeyMaskId; 141 | } 142 | return success; 143 | } 144 | 145 | bool VirtualTerminalClientUpdateHelper::set_attribute(std::uint16_t objectId, std::uint8_t attribute, std::uint32_t value) 146 | { 147 | if (nullptr == client) 148 | { 149 | LOG_ERROR("[VTStateHelper] set_attribute: client is nullptr"); 150 | return false; 151 | } 152 | if (attributeStates.find(objectId) == attributeStates.end()) 153 | { 154 | LOG_ERROR("[VTStateHelper] set_attribute: objectId %lu not tracked", objectId); 155 | return false; 156 | } 157 | if (attributeStates.at(objectId).find(attribute) == attributeStates.at(objectId).end()) 158 | { 159 | LOG_WARNING("[VTStateHelper] set_attribute: attribute %lu of objectId %lu not tracked", attribute, objectId); 160 | return false; 161 | } 162 | if (attributeStates.at(objectId).at(attribute) == value) 163 | { 164 | return true; 165 | } 166 | 167 | bool success = vtClient->send_change_attribute(objectId, attribute, value); 168 | if (success) 169 | { 170 | attributeStates[objectId][attribute] = value; 171 | } 172 | return success; 173 | } 174 | 175 | } // namespace isobus 176 | -------------------------------------------------------------------------------- /src/isobus_virtual_terminal_client_update_helper.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file isobus_virtual_terminal_client_update_helper.hpp 3 | /// 4 | /// @brief A helper class to update and track the state of an active working set. 5 | /// @author Daan Steenbergen 6 | /// 7 | /// @copyright 2023 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #ifndef ISOBUS_VIRTUAL_TERMINAL_CLIENT_UPDATE_HELPER_HPP 11 | #define ISOBUS_VIRTUAL_TERMINAL_CLIENT_UPDATE_HELPER_HPP 12 | 13 | #include "isobus_virtual_terminal_client.hpp" 14 | #include "isobus_virtual_terminal_client_state_tracker.hpp" 15 | 16 | namespace isobus 17 | { 18 | /// @brief A helper class to update and track the state of an active working set. 19 | class VirtualTerminalClientUpdateHelper : public VirtualTerminalClientStateTracker 20 | { 21 | public: 22 | /// @brief The constructor of class to help update the state of an active working set. 23 | /// @param[in] client The virtual terminal client that provides the active working set. 24 | explicit VirtualTerminalClientUpdateHelper(std::shared_ptr client); 25 | 26 | /// @brief The destructor of class to unregister event listeners. 27 | ~VirtualTerminalClientUpdateHelper(); 28 | 29 | /// @brief Sets the numeric value of a tracked object. 30 | /// @param[in] objectId The object id of the numeric value to set. 31 | /// @param[in] value The value to set the numeric value to. 32 | /// @return True if the value was set successfully, false otherwise. 33 | bool set_numeric_value(std::uint16_t objectId, std::uint32_t value); 34 | 35 | /// @brief Increases the numeric value of a tracked object. 36 | /// @param[in] objectId The object id of the numeric value to increase. 37 | /// @param[in] step The step size to increase the numeric value with. 38 | /// @return True if the value was increased successfully, false otherwise. 39 | bool increase_numeric_value(std::uint16_t objectId, std::uint32_t step = 1); 40 | 41 | /// @brief Decreases the numeric value of a tracked object. 42 | /// @param[in] objectId The object id of the numeric value to decrease. 43 | /// @param[in] step The step size to decrease the numeric value with. 44 | /// @return True if the value was decreased successfully, false otherwise. 45 | bool decrease_numeric_value(std::uint16_t objectId, std::uint32_t step = 1); 46 | 47 | /// @brief Register a callback function to validate a numeric value change of a tracked object. 48 | /// If the callback function returns true, the numeric value change will be acknowledged. 49 | /// Otherwise, if the callback function returns false, the numeric value change will 50 | /// be rejected by sending the current value back to the VT. 51 | /// @param[in] callback The callback function to register, or nullptr to unregister. 52 | void set_callback_validate_numeric_value(const std::function &callback); 53 | 54 | /// @brief Sets the active data/alarm mask. 55 | /// @param[in] workingSetId The working set to set the active data/alarm mask for. 56 | /// @param[in] dataOrAlarmMaskId The data/alarm mask to set active. 57 | /// @return True if the data/alarm mask was set active successfully, false otherwise. 58 | bool set_active_data_or_alarm_mask(std::uint16_t workingSetId, std::uint16_t dataOrAlarmMaskId); 59 | 60 | /// @brief Sets the active soft key mask. 61 | /// @param[in] maskType The type of mask to set the active soft key mask for. 62 | /// @param[in] maskId The mask to set the active soft key mask for. 63 | /// @param[in] softKeyMaskId The soft key mask to set active. 64 | /// @return True if the soft key mask was set active successfully, false otherwise. 65 | bool set_active_soft_key_mask(VirtualTerminalClient::MaskType maskType, std::uint16_t maskId, std::uint16_t softKeyMaskId); 66 | 67 | /// @brief Sets the value of an attribute of a tracked object. 68 | /// @note If the to be tracked working set consists of more than the master, 69 | /// this function is incompatible with a VT prior to version 4. For working sets consisting 70 | /// of only the master, this function is compatible with any VT version. 71 | /// @param[in] objectId The object id of the attribute to set. 72 | /// @param[in] attribute The attribute to set. 73 | /// @param[in] value The value to set the attribute to. 74 | /// @return True if the attribute was set successfully, false otherwise. 75 | bool set_attribute(std::uint16_t objectId, std::uint8_t attribute, std::uint32_t value); 76 | 77 | private: 78 | /// @brief Processes a numeric value change event 79 | /// @param[in] event The numeric value change event to process. 80 | void process_numeric_value_change_event(const VirtualTerminalClient::VTChangeNumericValueEvent &event); 81 | 82 | std::shared_ptr vtClient; ///< Holds the vt client. 83 | 84 | std::function callbackValidateNumericValue; ///< Holds the callback function to validate a numeric value change. 85 | EventCallbackHandle numericValueChangeEventHandle; ///< Holds the handle to the numeric value change event listener 86 | }; 87 | } // namespace isobus 88 | 89 | #endif // ISOBUS_VIRTUAL_TERMINAL_CLIENT_UPDATE_HELPER_HPP 90 | -------------------------------------------------------------------------------- /src/platform_endianness.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file platform_endianness.cpp 3 | /// 4 | /// @brief Provides a runtime way to determine platform endianness. 5 | /// Useful when trying to convert bytes in memory (like float*) to a specific endianness. 6 | /// @author Adrian Del Grosso 7 | /// 8 | /// @copyright 2023 The Open-Agriculture Developers 9 | //================================================================================================ 10 | #include "platform_endianness.hpp" 11 | 12 | #include 13 | 14 | namespace isobus 15 | { 16 | bool is_little_endian() 17 | { 18 | std::int32_t number = 1; 19 | auto numPtr = reinterpret_cast(&number); 20 | return (numPtr[0] == 1); 21 | } 22 | 23 | bool is_big_endian() 24 | { 25 | return (false == is_little_endian()); 26 | } 27 | } // namespace isobus 28 | -------------------------------------------------------------------------------- /src/platform_endianness.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file platform_endianness.hpp 3 | /// 4 | /// @brief Provides a runtime way to determine platform endianness. 5 | /// Useful when trying to convert bytes in memory (like float*) to a specific endianness. 6 | /// @author Adrian Del Grosso 7 | /// 8 | /// @copyright 2023 The Open-Agriculture Developers 9 | //================================================================================================ 10 | #ifndef PLATFORM_ENDIANNESS_HPP 11 | #define PLATFORM_ENDIANNESS_HPP 12 | 13 | namespace isobus 14 | { 15 | /// @brief Returns if the platform is little endian 16 | /// @returns `true` if the platform is little endian, otherwise false 17 | bool is_little_endian(); 18 | 19 | /// @brief Returns if the platform is big endian 20 | /// @returns `true` if the platform is big endian, otherwise false 21 | bool is_big_endian(); 22 | 23 | } // namespace isobus 24 | 25 | #endif //PLATFORM_ENDIANNESS_HPP 26 | -------------------------------------------------------------------------------- /src/processing_flags.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file processing_flags.cpp 3 | /// 4 | /// @brief A class that manages 1 bit flags. Handy as a retry machanism for sending CAN messages. 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #include "processing_flags.hpp" 11 | 12 | #include 13 | 14 | namespace isobus 15 | { 16 | ProcessingFlags::ProcessingFlags(std::uint32_t numberOfFlags, ProcessFlagsCallback processingCallback, void *parentPointer) : 17 | callback(processingCallback), 18 | maxFlag(numberOfFlags), 19 | flagBitfield(nullptr), 20 | parent(parentPointer) 21 | { 22 | const std::uint32_t numberBytes = (numberOfFlags / 8) + 1; 23 | flagBitfield = new std::uint8_t[numberBytes]; 24 | memset(flagBitfield, 0, numberBytes); 25 | } 26 | 27 | ProcessingFlags ::~ProcessingFlags() 28 | { 29 | delete[] flagBitfield; 30 | flagBitfield = nullptr; 31 | } 32 | 33 | void ProcessingFlags::set_flag(std::uint32_t flag) 34 | { 35 | if (flag <= maxFlag) 36 | { 37 | flagBitfield[(flag / 8)] |= (1 << (flag % 8)); 38 | } 39 | } 40 | 41 | void ProcessingFlags::process_all_flags() 42 | { 43 | const std::uint32_t numberBytes = (maxFlag / 8) + 1; 44 | 45 | for (std::uint32_t i = 0; i < numberBytes; i++) 46 | { 47 | if (flagBitfield[i]) 48 | { 49 | for (std::uint8_t j = 0; j < 8; j++) 50 | { 51 | std::uint8_t currentFlag = (flagBitfield[i] & (1 << j)); 52 | 53 | if (currentFlag) 54 | { 55 | flagBitfield[i] &= ~(currentFlag); 56 | callback((8 * i) + j, parent); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } // namespace isobus 63 | -------------------------------------------------------------------------------- /src/processing_flags.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file processing_flags.hpp 3 | /// 4 | /// @brief A class that manages 1 bit flags. Handy as a retry machanism for sending CAN messages. 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #ifndef PROCESSING_FLAGS_HPP 10 | #define PROCESSING_FLAGS_HPP 11 | 12 | #include 13 | 14 | namespace isobus 15 | { 16 | class ProcessingFlags 17 | { 18 | public: 19 | typedef void (*ProcessFlagsCallback)(std::uint32_t flag, void *parentPointer); 20 | 21 | ProcessingFlags(std::uint32_t numberOfFlags, ProcessFlagsCallback processingCallback, void *parentPointer); 22 | ~ProcessingFlags(); 23 | 24 | void set_flag(std::uint32_t flag); 25 | void process_all_flags(); 26 | 27 | private: 28 | ProcessFlagsCallback callback; 29 | const std::uint32_t maxFlag; 30 | std::uint8_t *flagBitfield; 31 | void *parent; 32 | }; 33 | } // namespace isobus 34 | 35 | #endif // PROCESSING_FLAGS_HPP 36 | -------------------------------------------------------------------------------- /src/system_timing.cpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file system_timing.cpp 3 | /// 4 | /// @brief Utility class for getting system time and handling u32 time rollover 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #include "system_timing.hpp" 11 | 12 | #include 13 | #include 14 | 15 | namespace isobus 16 | { 17 | std::uint64_t SystemTiming::s_timestamp_ms = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); 18 | std::uint64_t SystemTiming::s_timestamp_us = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); 19 | 20 | std::uint32_t SystemTiming::get_timestamp_ms() 21 | { 22 | return incrementing_difference(static_cast(static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()) & std::numeric_limits::max()), static_cast(s_timestamp_ms)); 23 | } 24 | 25 | std::uint64_t SystemTiming::get_timestamp_us() 26 | { 27 | return incrementing_difference(static_cast(static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()) & std::numeric_limits::max()), s_timestamp_us); 28 | } 29 | 30 | std::uint32_t SystemTiming::get_time_elapsed_ms(std::uint32_t timestamp_ms) 31 | { 32 | return (get_timestamp_ms() - timestamp_ms); 33 | } 34 | 35 | std::uint64_t SystemTiming::get_time_elapsed_us(std::uint64_t timestamp_us) 36 | { 37 | return (get_timestamp_us() - timestamp_us); 38 | } 39 | 40 | bool SystemTiming::time_expired_ms(std::uint32_t timestamp_ms, std::uint32_t timeout_ms) 41 | { 42 | return (get_time_elapsed_ms(timestamp_ms) >= timeout_ms); 43 | } 44 | 45 | bool SystemTiming::time_expired_us(std::uint64_t timestamp_us, std::uint64_t timeout_us) 46 | { 47 | return (get_time_elapsed_us(timestamp_us) >= timeout_us); 48 | } 49 | 50 | std::uint32_t SystemTiming::incrementing_difference(std::uint32_t currentValue, std::uint32_t previousValue) 51 | { 52 | std::uint32_t retVal; 53 | 54 | if (currentValue >= previousValue) 55 | { 56 | retVal = currentValue - previousValue; 57 | } 58 | else 59 | { 60 | retVal = (std::numeric_limits::max() - previousValue) + currentValue + 1; 61 | } 62 | return retVal; 63 | } 64 | 65 | std::uint64_t SystemTiming::incrementing_difference(std::uint64_t currentValue, std::uint64_t previousValue) 66 | { 67 | std::uint64_t retVal; 68 | 69 | if (currentValue >= previousValue) 70 | { 71 | retVal = currentValue - previousValue; 72 | } 73 | else 74 | { 75 | retVal = (std::numeric_limits::max() - previousValue) + currentValue + 1; 76 | } 77 | return retVal; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/system_timing.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file system_timing.cpp 3 | /// 4 | /// @brief Utility class for getting system time and handling u32 time rollover 5 | /// @author Adrian Del Grosso 6 | /// 7 | /// @copyright 2022 The Open-Agriculture Developers 8 | //================================================================================================ 9 | 10 | #include 11 | 12 | namespace isobus 13 | { 14 | class SystemTiming 15 | { 16 | public: 17 | static std::uint32_t get_timestamp_ms(); 18 | static std::uint64_t get_timestamp_us(); 19 | 20 | static std::uint32_t get_time_elapsed_ms(std::uint32_t timestamp_ms); 21 | static std::uint64_t get_time_elapsed_us(std::uint64_t timestamp_us); 22 | 23 | static bool time_expired_ms(std::uint32_t timestamp_ms, std::uint32_t timeout_ms); 24 | static bool time_expired_us(std::uint64_t timestamp_us, std::uint64_t timeout_us); 25 | 26 | private: 27 | static std::uint32_t incrementing_difference(std::uint32_t currentValue, std::uint32_t previousValue); 28 | static std::uint64_t incrementing_difference(std::uint64_t currentValue, std::uint64_t previousValue); 29 | static std::uint64_t s_timestamp_ms; 30 | static std::uint64_t s_timestamp_us; 31 | }; 32 | 33 | } // namespace isobus 34 | -------------------------------------------------------------------------------- /src/thread_synchronization.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file thread_synchronization.hpp 3 | /// 4 | /// @brief A single header file to automatically include the correct thread synchronization 5 | /// @author Daan Steenbergen 6 | /// 7 | /// @copyright 2024 The Open-Agriculture Developers 8 | //================================================================================================ 9 | #ifndef THREAD_SYNCHRONIZATION_HPP 10 | #define THREAD_SYNCHRONIZATION_HPP 11 | 12 | #if defined CAN_STACK_DISABLE_THREADS || defined ARDUINO 13 | #include 14 | 15 | namespace isobus 16 | { 17 | /// @brief A dummy mutex class when treading is disabled. 18 | class Mutex 19 | { 20 | }; 21 | /// @brief A dummy recursive mutex class when treading is disabled. 22 | class RecursiveMutex 23 | { 24 | }; 25 | } 26 | /// @brief Disabled LOCK_GUARD macro since threads are disabled. 27 | #define LOCK_GUARD(type, x) 28 | 29 | /// @brief A template class for a queue, since threads are disabled this is a simple queue. 30 | /// @tparam T The item type for the queue. 31 | template 32 | class LockFreeQueue 33 | { 34 | public: 35 | /// @brief Constructor for the lock free queue. 36 | explicit LockFreeQueue(std::size_t) {} 37 | 38 | /// @brief Push an item to the queue. 39 | /// @param item The item to push to the queue. 40 | /// @return Simply returns true, since this version of the queue is not limited in size. 41 | bool push(const T &item) 42 | { 43 | queue.push(item); 44 | return true; 45 | } 46 | 47 | /// @brief Peek at the next item in the queue. 48 | /// @param item The item to peek at in the queue. 49 | /// @return True if the item was peeked at in the queue, false if the queue is empty. 50 | bool peek(T &item) 51 | { 52 | if (queue.empty()) 53 | { 54 | return false; 55 | } 56 | 57 | item = queue.front(); 58 | return true; 59 | } 60 | 61 | /// @brief Pop an item from the queue. 62 | /// @return True if the item was popped from the queue, false if the queue is empty. 63 | bool pop() 64 | { 65 | if (queue.empty()) 66 | { 67 | return false; 68 | } 69 | 70 | queue.pop(); 71 | return true; 72 | } 73 | 74 | /// @brief Check if the queue is full. 75 | /// @return Always returns false, since this version of the queue is not limited in size. 76 | bool is_full() const 77 | { 78 | return false; 79 | } 80 | 81 | /// @brief Clear the queue. 82 | void clear() 83 | { 84 | queue = {}; 85 | } 86 | 87 | private: 88 | std::queue queue; ///< The queue 89 | }; 90 | 91 | #else 92 | 93 | #include 94 | #include 95 | #include 96 | #include 97 | namespace isobus 98 | { 99 | using Mutex = std::mutex; 100 | using RecursiveMutex = std::recursive_mutex; 101 | } 102 | /// @brief A macro to automatically lock a mutex and unlock it when the scope ends. 103 | /// @param type The type of the mutex. 104 | /// @param x The mutex to lock. 105 | #define LOCK_GUARD(type, x) const std::lock_guard x##Lock(x) 106 | 107 | /// @brief A template class for a lock free queue. 108 | /// @tparam T The item type for the queue. 109 | template 110 | class LockFreeQueue 111 | { 112 | public: 113 | /// @brief Constructor for the lock free queue. 114 | explicit LockFreeQueue(std::size_t size) : 115 | buffer(size), capacity(size) 116 | { 117 | // Validate the size of the queue, if assertion is disabled, set the size to 1. 118 | assert(size > 0 && "The size of the queue must be greater than 0."); 119 | if (size == 0) 120 | { 121 | size = 1; 122 | } 123 | } 124 | 125 | /// @brief Push an item to the queue. 126 | /// @param item The item to push to the queue. 127 | /// @return True if the item was pushed to the queue, false if the queue is full. 128 | bool push(const T &item) 129 | { 130 | const auto currentWriteIndex = writeIndex.load(std::memory_order_relaxed); 131 | const auto nextWriteIndex = nextIndex(currentWriteIndex); 132 | 133 | if (nextWriteIndex == readIndex.load(std::memory_order_acquire)) 134 | { 135 | // The buffer is full. 136 | return false; 137 | } 138 | 139 | buffer[currentWriteIndex] = item; 140 | writeIndex.store(nextWriteIndex, std::memory_order_release); 141 | return true; 142 | } 143 | 144 | /// @brief Peek at the next item in the queue. 145 | /// @param item The item to peek at in the queue. 146 | /// @return True if the item was peeked at in the queue, false if the queue is empty. 147 | bool peek(T &item) 148 | { 149 | const auto currentReadIndex = readIndex.load(std::memory_order_relaxed); 150 | if (currentReadIndex == writeIndex.load(std::memory_order_acquire)) 151 | { 152 | // The buffer is empty. 153 | return false; 154 | } 155 | 156 | item = buffer[currentReadIndex]; 157 | return true; 158 | } 159 | 160 | /// @brief Pop an item from the queue. 161 | /// @return True if the item was popped from the queue, false if the queue is empty. 162 | bool pop() 163 | { 164 | const auto currentReadIndex = readIndex.load(std::memory_order_relaxed); 165 | if (currentReadIndex == writeIndex.load(std::memory_order_acquire)) 166 | { 167 | // The buffer is empty. 168 | return false; 169 | } 170 | 171 | readIndex.store(nextIndex(currentReadIndex), std::memory_order_release); 172 | return true; 173 | } 174 | 175 | /// @brief Check if the queue is full. 176 | /// @return True if the queue is full, false if the queue is not full. 177 | bool is_full() const 178 | { 179 | return nextIndex(writeIndex.load(std::memory_order_acquire)) == readIndex.load(std::memory_order_acquire); 180 | } 181 | 182 | /// @brief Clear the queue. 183 | void clear() 184 | { 185 | // Simply move the read index to the write index. 186 | readIndex.store(writeIndex.load(std::memory_order_acquire), std::memory_order_release); 187 | } 188 | 189 | private: 190 | std::vector buffer; ///< The buffer for the circular buffer. 191 | std::atomic readIndex = { 0 }; ///< The read index for the circular buffer. 192 | std::atomic writeIndex = { 0 }; ///< The write index for the circular buffer. 193 | const std::size_t capacity; ///< The capacity of the circular buffer. 194 | 195 | /// @brief Get the next index in the circular buffer. 196 | /// @param current The current index. 197 | /// @return The next index in the circular buffer. 198 | std::size_t nextIndex(std::size_t current) const 199 | { 200 | return (current + 1) % capacity; 201 | } 202 | }; 203 | 204 | #endif 205 | 206 | #endif // THREAD_SYNCHRONIZATION_HPP 207 | -------------------------------------------------------------------------------- /src/to_string.hpp: -------------------------------------------------------------------------------- 1 | //================================================================================================ 2 | /// @file to_string.hpp 3 | /// 4 | /// @brief A compatibility template to replace `std::to_string` 5 | /// @details Some compilers don't support `std::to_string` so this file is meant to abstract it 6 | /// away with a workaround if it's not supported. This solution was inspired by Catch2's 7 | /// implementation. 8 | /// @author Adrian Del Grosso 9 | /// 10 | /// @copyright 2022 The Open-Agriculture Developers 11 | //================================================================================================ 12 | #ifndef TO_STRING_HPP 13 | #define TO_STRING_HPP 14 | 15 | #include 16 | #include 17 | 18 | namespace isobus 19 | { 20 | template 21 | /// @brief A replacement for std::to_string 22 | /// @tparam T The data type 23 | /// @param t The thing to convert to string 24 | /// @returns the string form of `t` 25 | std::string to_string(T const &t) 26 | { 27 | std::ostringstream oss; 28 | oss << t; 29 | return oss.str(); 30 | } 31 | } // namespace isobus_utils 32 | 33 | #endif // TO_STRING_HPP 34 | --------------------------------------------------------------------------------