Skip to content

Commit

Permalink
Add support for Arduino, Teensy 4 and 4.1
Browse files Browse the repository at this point in the history
Added lots of changes to allow the stack to build without threading
under the Arduino IDE. Added CAN plugin for Teensy hardware using the
FlexCAN T4 api.

Added single threaded CANHardwareInterface option.
Changed dynamic casts to static casts to completely eliminate rtti,
which shouldn't be an issue since the type can be inferred in those
cases where we were using dynamic_pointer_cast.
Added preprocessor checks to optinally compile out all threading.
Added a script to automatically generate an arduino library from the
content of the repo.
Added Arduino example files.
  • Loading branch information
ad3154 committed Sep 10, 2023
1 parent 4bff6ff commit 8d6f172
Show file tree
Hide file tree
Showing 40 changed files with 17,581 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
# Some files can be excluded from the clang-format style check, like 3rd party driver files,
# as their licenses may not allow us to modify them, such as if they are LGPL licensed.
# The exclude-regex option can be used to exclude these files from the style check:
exclude-regex: ^.*(PCANBasic\.h|libusb\.h|InnoMakerUsb2CanLib\.h)$
exclude-regex: ^.*(PCANBasic\.h|libusb\.h|InnoMakerUsb2CanLib\.h|arduino_example\.ino)$
cmake-format:
name: cmake-format style
runs-on: ubuntu-latest
Expand Down
9,399 changes: 9,399 additions & 0 deletions examples/arduino_example/ObjectPool.cpp

Large diffs are not rendered by default.

160 changes: 160 additions & 0 deletions examples/arduino_example/arduino_example.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#include <isobus.hpp>

#include "ObjectPool.cpp"

using namespace isobus;

auto can0 = std::make_shared<FlexCANT4Plugin>(0);
std::shared_ptr<InternalControlFunction> ISOBUSControlFunction = nullptr;
std::shared_ptr<DiagnosticProtocol> ISOBUSDiagnostics = nullptr;
std::shared_ptr<VirtualTerminalClient> ExampleVirtualTerminalClient = nullptr;
std::shared_ptr<void> softKeyListener = nullptr;
std::shared_ptr<void> buttonListener = nullptr;

// A log sink for the CAN stack
class CustomLogger : public isobus::CANStackLogger
{
public:
void sink_CAN_stack_log(CANStackLogger::LoggingLevel level, const std::string &text) override
{
switch (level)
{
case LoggingLevel::Debug:
{
Serial.print("[Debug]: ");
}
break;

case LoggingLevel::Info:
{
Serial.print("[Info]: ");
}
break;

case LoggingLevel::Warning:
{
Serial.print("[Warning]: ");
}
break;

case LoggingLevel::Error:
{
Serial.print("[Error]: ");
}
break;

case LoggingLevel::Critical:
{
Serial.print("[Critical]: ");
}
break;
}
Serial.println(text.c_str());
}
};

static CustomLogger logger;

// This callback will provide us with event driven notifications of button presses from the stack
void handleVTKeyEvents(const VirtualTerminalClient::VTKeyEvent &event)
{
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.

switch (event.keyEvent)
{
case VirtualTerminalClient::KeyActivationCode::ButtonUnlatchedOrReleased:
{
switch (event.objectID)
{
case Plus_Button:
{
ExampleVirtualTerminalClient->send_change_numeric_value(ButtonExampleNumber_VarNum, ++exampleNumberOutput);
}
break;

case Minus_Button:
{
ExampleVirtualTerminalClient->send_change_numeric_value(ButtonExampleNumber_VarNum, --exampleNumberOutput);
}
break;

case alarm_SoftKey:
{
ExampleVirtualTerminalClient->send_change_active_mask(example_WorkingSet, example_AlarmMask);
}
break;

case acknowledgeAlarm_SoftKey:
{
ExampleVirtualTerminalClient->send_change_active_mask(example_WorkingSet, mainRunscreen_DataMask);
}
break;

default:
break;
}
}
break;

default:
break;
}
}

void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
CANStackLogger::set_can_stack_logger_sink(&logger);
CANStackLogger::set_log_level(isobus::CANStackLogger::LoggingLevel::Debug);
// Optional, add delay() here to give you time to connect to the serial logger
CANHardwareInterface::set_number_of_can_channels(1);
CANHardwareInterface::assign_can_channel_frame_handler(0, can0);
CANHardwareInterface::start();
CANHardwareInterface::update();

NAME deviceNAME(0);
// Make sure you change these for your device
// This is an example device that is using a manufacturer code that is currently unused at time of writing
deviceNAME.set_arbitrary_address_capable(true);
deviceNAME.set_industry_group(0);
deviceNAME.set_device_class(0);
deviceNAME.set_function_code(static_cast<std::uint8_t>(isobus::NAME::Function::SteeringControl));
deviceNAME.set_identity_number(2);
deviceNAME.set_ecu_instance(0);
deviceNAME.set_function_instance(0);
deviceNAME.set_device_class_instance(0);
deviceNAME.set_manufacturer_code(64);
// Change 0x81 to be your preferred address, but 0x81 is the base arbitrary address
ISOBUSControlFunction = InternalControlFunction::create(deviceNAME, 0x81, 0);
ISOBUSDiagnostics = std::make_shared<DiagnosticProtocol>(ISOBUSControlFunction);
ISOBUSDiagnostics->initialize();

// Change these to be specific to your device
ISOBUSDiagnostics->set_product_identification_brand("Arduino");
ISOBUSDiagnostics->set_product_identification_code("123456789");
ISOBUSDiagnostics->set_product_identification_model("Example");
ISOBUSDiagnostics->set_software_id_field(0, "0.0.1");
ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::HardwareID, "Hardware ID");
ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::Location, "The Aether");
ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::ManufacturerName, "None");
ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::PartNumber, "1234");
ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::SerialNumber, "1");
ISOBUSDiagnostics->set_ecu_id_field(DiagnosticProtocol::ECUIdentificationFields::Type, "AgISOStack");

// Set up virtual terminal client
const NAMEFilter filterVirtualTerminal(NAME::NAMEParameters::FunctionCode, static_cast<std::uint8_t>(NAME::Function::VirtualTerminal));
const std::vector<NAMEFilter> vtNameFilters = { filterVirtualTerminal };
auto TestPartnerVT = PartneredControlFunction::create(0, vtNameFilters);
ExampleVirtualTerminalClient = std::make_shared<VirtualTerminalClient>(TestPartnerVT, ISOBUSControlFunction);
ExampleVirtualTerminalClient->set_object_pool(0, VirtualTerminalClient::VTVersion::Version3, VT3TestPool, sizeof(VT3TestPool), "AIS1");
softKeyListener = ExampleVirtualTerminalClient->add_vt_soft_key_event_listener(handleVTKeyEvents);
buttonListener = ExampleVirtualTerminalClient->add_vt_button_event_listener(handleVTKeyEvents);
ExampleVirtualTerminalClient->initialize(false);
}

void loop() {
// put your main code here, to run repeatedly:
ISOBUSDiagnostics->update(); // Update diagnostics interface
ExampleVirtualTerminalClient->update(); // Update VT client
CANHardwareInterface::update(); // Update CAN stack
}
150 changes: 150 additions & 0 deletions generateArduinoLibrary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import os, shutil, fnmatch, glob, fileinput
from datetime import datetime

def copyfiles(srcdir, dstdir, filepattern):
def failed(exc):
raise exc

for dirpath, dirs, files in os.walk(srcdir, topdown=True, onerror=failed):
for file in fnmatch.filter(files, filepattern):
if "test" not in dirpath and "examples" not in dirpath and "CMakeFiles" not in dirpath:
shutil.copy2(os.path.join(dirpath, file), dstdir)
print("Copied ", file)

def write_isobus_hpp(includefiles, f):
f.write(
f"""/*******************************************************************************
** @file isobus.hpp
** @author Automatic Code Generation
** @date {datetime.today().strftime("%B %d, %Y")} at {datetime.now().strftime("%H:%M:%S")}
** @brief Includes all important files in the AgIsoStack library.
**
** Copyright {datetime.today().strftime("%Y")} The AgIsoStack++ Developers
*******************************************************************************/
#ifndef ISOBUS_HPP
#define ISOBUS_HPP
""")

for includefile in includefiles:
f.write(f"#include <{includefile}>\n")

f.write(f"\n");
f.write(f"#endif // ISOBUS_HPP\n")

def write_library_properties(f):
f.write(
f"""name=AgIsoStack-plus-plus
version=0.1.0
license=MIT
author=Adrian Del Grosso <[email protected]>
maintainer=Adrian Del Grosso <[email protected]>
sentence=A free ISOBUS (ISO11783) and J1939 CAN Stack for Teensy.
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.
category=Communication
architectures=teensy
includes=isobus.hpp
url=https://github.com/Open-Agriculture/AgIsoStack-plus-plus
""")

def fixup_header_paths(fileName):
f = open(fileName,'r')
filedata = f.read()
f.close()

newdata = filedata.replace("isobus/isobus/","")
newdata = newdata.replace("isobus/utility/","")
newdata = newdata.replace("isobus/hardware_integration/","")

f = open(fileName,'w')
f.write(newdata)
f.close()

arduinoLibPath = "arduino_library/"
sourceDir = "src"
sourcePath = arduinoLibPath + sourceDir

if os.path.exists(arduinoLibPath) and os.path.isdir(arduinoLibPath):
shutil.rmtree(arduinoLibPath)
print("Removed existing arduino library")

os.mkdir(arduinoLibPath)
print("Created directory ", arduinoLibPath)

os.mkdir(sourcePath)
print("Created directory ", sourcePath)

print("Copying source files to library")

copyfiles(".", sourcePath, "*.cpp")
copyfiles(".", sourcePath, "*.hpp")
copyfiles(".", sourcePath, "*.tpp")

print("Pruning unneeded files for Arduino platform")

filePruneList = [
"iop_file_interface.cpp",
"iop_file_interface.hpp",
"available_can_drivers.hpp",
"canal.h",
"canal_a.h",
"innomaker_usb2can_windows_plugin.hpp",
"InnoMakerUsb2CanLib.h",
"libusb.h",
"mac_can_pcan_plugin.hpp",
"mcp2515_can_interface.hpp",
"pcan_basic_windows_plugin.hpp",
"PCANBasic.h",
"PCBUSB.h",
"socket_can_interface.hpp",
"spi_hardware_plugin.hpp",
"spi_interface_esp.hpp",
"spi_transaction_frame.hpp",
"toucan_vscp_canal.hpp",
"twai_plugin.hpp",
"virtual_can_plugin.hpp",
"innomaker_usb2can_windows_plugin.cpp",
"mac_can_pcan_plugin.cpp",
"mcp2515_can_interface.cpp",
"pcan_basic_windows_plugin.cpp",
"socket_can_interface.cpp",
"spi_interface_esp.cpp",
"spi_transaction_frame.cpp",
"toucan_vscp_canal.cpp",
"twai_plugin.cpp",
"virtual_can_plugin.cpp",
"can_hardware_interface.hpp",
"can_hardware_interface.cpp",
"socketcand_windows_network_client.hpp",
"socketcand_windows_network_client.cpp",
"CMakeCXXCompilerId.cpp"]

for punableFile in filePruneList:
if os.path.exists(os.path.join(sourcePath, punableFile)) and os.path.isfile(os.path.join(sourcePath, punableFile)):
os.remove(os.path.join(sourcePath, punableFile))
print("Pruning file ", punableFile)

print("Generating isobus.hpp from files in " + "./" + sourcePath + "/*.hpp")
headers = [os.path.normpath(i) for i in glob.glob("./" + sourcePath + "/*.hpp")]
strippedHeaders = list(map(lambda x: x.replace('arduino_library\\src\\','').replace('arduino_library/src/',''),headers))
print("Headers ", strippedHeaders)
f = open(os.path.join(sourcePath, "isobus.hpp"), "w")
write_isobus_hpp(strippedHeaders, f)
f.close()

print("Generating library.properties")
f = open(os.path.join(arduinoLibPath, "library.properties"), "w")
write_library_properties(f)
f.close()

print("Patching header file paths")
for header in headers:
fixup_header_paths(header)
print("Patched ", header)

sources = [os.path.normpath(i) for i in glob.glob("./" + sourcePath + "/*.cpp")]
for source in sources:
fixup_header_paths(source)
print("Patched ", source)
20 changes: 16 additions & 4 deletions hardware_integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,24 @@ if(BUILD_TESTING AND NOT "VirtualCAN" IN_LIST CAN_DRIVER)
endif()

# Set the source files
set(HARDWARE_INTEGRATION_SRC "can_hardware_interface.cpp")
if(CAN_STACK_DISABLE_THREADS OR ARDUINO)
set(HARDWARE_INTEGRATION_SRC "can_hardware_interface_single_thread.cpp")
message(STATUS "CAN Stack is compiling in single-threaded mode.")
else()
set(HARDWARE_INTEGRATION_SRC "can_hardware_interface.cpp")
message(STATUS "CAN Stack is compiling in multi-threaded mode.")
endif()

# Set the include files
set(HARDWARE_INTEGRATION_INCLUDE
"can_hardware_interface.hpp" "can_hardware_plugin.hpp"
"available_can_drivers.hpp")
if(CAN_STACK_DISABLE_THREADS OR ARDUINO)
set(HARDWARE_INTEGRATION_INCLUDE
"can_hardware_interface_single_thread.hpp" "can_hardware_plugin.hpp"
"available_can_drivers.hpp")
else()
set(HARDWARE_INTEGRATION_INCLUDE
"can_hardware_interface.hpp" "can_hardware_plugin.hpp"
"available_can_drivers.hpp")
endif()

# Add the source/include files based on the CAN driver chosen
if("SocketCAN" IN_LIST CAN_DRIVER)
Expand Down
Loading

0 comments on commit 8d6f172

Please sign in to comment.