Skip to main content

NMotion Transport Library

Version:
Production Ready

Our library provides comprehensive support for communication between NMotion components and the main system. The library can communicate with the connected NLink Adapter via USB or directly with NMotion components via native CAN.

It features two distinct layers of abstraction: the Interface Layer, which manages all USB or native CAN interface operations, including methods and functionalities specific to communication protocols, and the Device Layer, which abstracts device-level functionalities, encompassing operations such as data handling and device management.

We offer both C/C++ and Python libraries. The core library is implemented in C++, while the Python library serves as a high-level wrapper that provides access to the C++ library.

info

Currently our library only supports components which are connected to PC via our own NLink Adapter

Basic Code Flow

Before using the library make sure all the NMotion components are properly connected to the CAN Bus and have been assigned unique Node IDs. The NMotion CLI tool can be used to configure each of these components easily.

While writing the code to control NMotion components using this library, start by creating an Interface object and then initializing it with a specified port — such as /dev/ttyACM0 for USB or can0 for native CAN bus. Once initialized, create a Device object using this interface object and the CAN Node ID of the device, which represents the connected NMotion device. After this, wait for sometime to make sure that the devices get connected to the interface. Then you can utilize the various methods provided by the Device object to interact with and control the components as required by your application. After calling each device method, it is suggested to add a delay so that the next command is executed only after the system completely executes the prior command. This delay is especially crucial for actuators and drivers with connected motors, as it allows time for their motion to complete before issuing another command. After completing operations with the Device object, ensure that the interface is closed to release resources and terminate communication properly.

Psuedocode
/* Create an Interface object based on the interface type */
interface = Interface();

/* Initialize the interface based on the interface port: */
// For USB the interface_port value would be something like /dev/ttyACM0 or COM5
// For native CAN the interface_port value would be can0
interface.initialize(interface_port);

/* Create a Device object, by using the can_node_id of the device and passing the Interface object */
device = Device(can_node_id, interface);

// wait for sometime to make sure that the device's get connected to the interface
wait();

/* Utilize the device's methods as needed */
device.method1();
// wait for method1 to be completed
wait();

device.method2();
// wait for method2 to be completed
wait();

/* Close the interface to release resources */
interface.close();

Library Usage

As per the above codeflow the first step is to declare an interface class object. For that we have implmented two classes which inherits the core Interface class:

  • USBInterface: For USB related interface; mainly used to connect with NLink Adapter.
  • CANInterface: For native CAN related interface; mainly used to directly connect with NMotion components on the native CAN port of your system.

After creating the interface object, we need to initialise it. To initialise, we need to pass the interface port to object's intitialise method. For USBInterface, the port will be the USB port onto which the NLink Adapter is connected (eg. /dev/ttyACM0, COM3...etc) and for CANInterface, the port will be the name of the native CAN port (eg. can0, can1...etc)

After this, we need to create the device class object while passing the Interface object onto it. Then we can call various device's methods as needed and finally close the interface to release the resources.

C/C++

Introduction

The core C++ library has abstractions for the interface (Interface parent class which is inherited by USBInterface and CANInterface) and the devices (like Actuator, NDrive Z1...etc).

The library has mainly two types of functions: one which sets the value on the device and another one which gets the value from the device.

  • The model for the function which sets the value on the device is straight-forward. You pass the value to the function and then it returns the status indicating whether the communication between the device and the system was successful.
ret_status_t status;  // variable to store return status
int config = 23; // value which needs to be set

// Pass config value into the device method and get the status as return value of the function
status = Device.setConfig(config);
  • Since C++ doesn't natively support returning multiple values from a function unless a custom struct or class object is used, the function that retrieves values from the device utilizes a call by reference approach. The memory address of the variable onto which the data needs to be written is passed to the function and it returns the status indicating whether the communication between the device and the system was successful.
ret_status_t status; // variable to store return status
int config; // variable to store the config value

// Pass the memory address of the config variable to the get function
// Function will fetch the value from device and write it to the passed memory address
// Function will return status of the communication
status = Device.getConfig(&config);

Usage

Setup

For the purpose of explaining the library usage, assume that the hardware setup includes an NLink Adapter connected via USB to the system's /dev/ttyACM0 port, and an actuator with CAN Node ID 23 connected to the NLink Adapter. To control this actuator, a main.cpp code will be written and built using the CMake program.

Download the headers and the shared library files from the Downloads page to ensure that necessary files are available for the build process. After extracting the downloaded zip file, you will find the headers and library files organized in the following directory structure:

nmotion_transport
├── include
| └── nmotion_transport
| ├── interface.hpp
| └── ... // Library header files
└── lib
└── libnmotion_transport.so // Shared Library suitable to your system's architechture

After extracting the files, create a code workspace that includes src and include folders, along with a CMakeLists.txt file. Move the extracted library files (the nmotion_transport folder) into the include folder of the code workspace.

Once the files have been copied, the directory structure of the code workspace will be as follows:

├── src
| └── main.cpp
├── include
| └── nmotion_transport
| └── <library> // Library files which were downloaded earlier
└── CMakeLists.txt
Code

The main.cpp file contains the code that uses the library to control the actuator. This code will move the actuator in position control mode and then retrieve the encoder count from the actuator's output encoder.

main.cpp
#include <iostream>
#include <thread>
#include <nmotion_transport/usb_interface.hpp>
#include <nmotion_transport/actuator.hpp>

int main(int argc, char const *argv[]) {

// Create an Interface object based on the interface type
USBInterface iface; // USB Interface

// Initialize the interface with port:
// For USB the interface_port value would be something like /dev/ttyACM0 or COM5
// For native CAN the interface_port value would be can0
// Currently we have connected the NLink adapter on /dev/ttyACM0
iface.initInterface("/dev/ttyACM0");

// Create a Device object by passing its CAN Node ID and the Interface object
// Currently we have an actuator connected to the NLink Adapter whose CAN Node ID is 23
Actuator act1(23, &iface);

// Wait for the device to get connected
std::this_thread::sleep_for(std::chrono::seconds(2));

// Utilize the device's methods as needed
// Set function: values are passed directly and the function returns status of communication
ret_status_t set_status = act1.setPositionControl(180, 100);

// Wait for the set method to finish
// Wait for the actuator to complete its motion
std::this_thread::sleep_for(std::chrono::seconds(2));

// Get function: address of the variable is passed and the function returns the status of the communication
int32_t count = 0;
ret_status_t get_status = act1.getMotorEncoderCount(&count);

// Wait is not required for the get function since we are only reading the value of whatever is there in the device

// Print the value
std::cout << "Encoder count for the actuator is:" << count << std::endl;

// Close the interface to release resources
iface.closeInterface();
return 0;
}

To build the program with the cmake tool, we will use the below CMakeLists.txt file. This file specifies the Nmotion Transport library files, ensuring that the code compiles and links against the necessary shared library and header files. Note that the library files vary depending on the platform, so this file must be adjusted accordingly.

CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(get_set_data_actuator)

include_directories(include)

# We are using modern ABI, some compilers require this
add_compile_options(-D_GLIBCXX_USE_CXX11_ABI=1)

# Add Nmotion Library
include_directories(include/nmotion_transport/include)
add_library(nmotion_transport SHARED IMPORTED)
set_target_properties(nmotion_transport PROPERTIES
IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/include/nmotion_transport/lib/libnmotion_transport.so
)

add_executable(get_set_data_actuator src/main.cpp)

# Link against Nmotion library
target_link_libraries(get_set_data_actuator nmotion_transport)
Building and Running the Program

The following commands and steps needs to be followed to build and run the program.

On Linux, the process is simple and can be done using the default terminal. Navigate to the root of the code workspace and run the following commands in the terminal.

$ mkdir build && cd build
$ cmake ..
$ make
$ ./get_set_data_actuator
tip

For C/C++ based development, we offer better support for Linux platforms, making them generally preferred over Windows. On Windows, using alternative Python libraries is often a better choice.

Python

Introduction

The Python library is a wrapper around the core C++ library, all the functions and objects which is available in the core library has been exposed using the wrapper.

The set and get methods in the python library is implemented as follows:

  • The model for the function which sets the value on the device is same as that of the C++ set methods. You pass the value to the function and it returns the status indicating whether the communication between the device and the system was successful or not.
config = 23 # value which needs to be set

# Pass the config value to the device method and get the status as the return value of the function
status = Device.setConfig(config)
  • The model for the function which gets the value is straight-forward, you call the function and it returns the status as well as the value together as a tuple.
# Call the get method directly and get the value and status inside a tuple
# The first value of the tuple will be the status followed by the get values.
(status, config) = Device.getConfig()

Usage

Setup

For the purpose of explaining the library usage, assume that the hardware setup includes an NLink Adapter connected via USB to the system's /dev/ttyACM0 port, and an actuator with CAN Node ID 23 connected to the NLink Adapter. To control this actuator, a main.py script will be written.

Download the Python library from the Downloads page, extract it, and place the files in the root directory of your project. This is where main.py will also reside. The resulting project directory structure will be as follows:

├── nmotion_transport
| └── <Python library files>
└── main.py

The Library is compatible with Python versions 3.8, 3.9 and 3.10.

Code

The main.py file contains the code that uses the library to control the actuator. This code will move the actuator in position control mode and then retrieve the encoder count from the actuator's output encoder.

from nmotion_transport import USBInterface, Actuator
import time

# Create an Interface object and initialize based on the interface type
# In case of Python we create and initialise the interface at the same time.
# For USB the interface_port value would be something like /dev/ttyACM0 or COM5
# For native CAN the interface_port value would be can0
# Currently we have connected the NLink adapter on /dev/ttyACM0
iface = USBInterface("/dev/ttyACM0") # USB Interface

# Create a Device object by passing its CAN Node ID and the Interface object
# Currently we have an actuator connected to the NLink Adapter whose CAN Node ID is 23
act1 = Actuator(23, iface)

# Wait for the device to get connected
time.sleep(2)

# Utilize the device's methods as needed
# Set function: values are passed directly and the function returns status of communication
set_status = act1.setPositionControl(180,100)

# Wait for the set method to finish
# Wait for the actuator to complete its motion
time.sleep(2)

# Get funciton: function is called directly and it returns the status of the communication and the get value
(get_status, count) = act1.getEncoderCount()

# Wait is not required for the get function since we are only reading the value of whatever is there in the device

# Print the value
print(f'Encoder count is: {count}')

# Delete the actuator object before closing interface
del act1

# Close the interface to release resources
iface.close()
Running the program

Run the python program in the project directory by using the command:

$ python main.py

In case of errors, ensure that a compatible version of Python is being used. In some cases, it is necessary to use python3 instead of python with the above command to use the compatible version.

Supported Devices & Versions

DeviceVersion
NDrive Z1Frimware Version = 0.3.2 , Hardware Version <= 1.1
Smart ActuatorsFirmware Version <= 0.3.1

C/C++ API Reference

Return Status Enum

typedef enum {
RETURN_OK,
RETURN_ERROR,
RETURN_TIMEOUT,
RETURN_INVALID_ID,
RETURN_DEVICE_NOT_CONNECTED,
RETURN_INTERFACE_NOT_UP,
RETURN_WRONG_ARGUMENT,
} ret_status_t;
Description

Used to return status after calling a device's method.

EnumeratorValueDescription
RETURN_OK0Status OK
RETURN_ERROR1Unclassified Error
RETURN_TIMEOUT2Communication Timeout
RETURN_INVALID_ID3Device ID is invalid
RETURN_DEVICE_NOT_CONNECTED4Device is not connected on the interface
RETURN_INTERFACE_NOT_UP5Interface is not running
RETURN_WRONG_ARGUMENT6Passed Argument is not valid

Interface Class

This is the parent class for USBInterface and CANInterface classes. The methods defined below will be available in child classes as well.

Interface.initInterface()

Interface::initInterface(std::string interface_name,
std::function<void(uint32_t, uint8_t)> on_new_dev_cb = nullptr,
std::function<void(uint32_t)> on_dev_disconnect_cb = nullptr)
Description

Invokes lower level code to initlialize the Interface

Parameters
DatatypeVariableDescription
std::stringinterface_nameInterface identifier string eg. can0, /dev/ttyACM0, COM3...etc
std::function<void(uint32_t, uint8_t)>on_new_dev_cbCallback function which will be called when a new device is connected.
The arguments of the function are:
• CAN Node ID of the device of type uint32_t
• Value of uint8_t type variable represents the type of the device which is connected.
  • 0 means that an actuator is connected.
  • 1 means that a motor driver is connected.
  • 2 means that an IMU is connected.
Return value of the function is void ie. it returns nothing.
std::function<void(uint32_t)>on_dev_disconnect_cbCallback function which will be called when a device is disconnected. The argument of this function is the CAN Node ID of the device of type uint32_t and return value of the function is void i.e. it returns nothing.

info

on_new_dev_cb and on_dev_disconnect_cb callback functions are optional and they will be assigned with null pointers if nothing is passed.

Interface.closeInterface()

uint8_t Interface::closeInterface()
Description

Invokes lower level code to close the Interface

Returns
DatatypeDescription
uint8_tReturns 1 for successful close or 0 for failure

Python API Reference

Python library is a wrapper around the core C++ library and hence all the relations/objects which exists in core library will exist here as well.

Interface Class

This is the parent class for USBInterface and CANInterface classes. The methods defined below will be available in child classes as well.

Interface()

Interface(interface_name)
Description

Constructor for Interface class, it creates the Interface object and also invokes lower level code to initlialize the Interface.

Parameters
DatatypeVariableDescription
strinterface_nameInterface identifier string eg. can0, /dev/ttyACM0, COM3...etc

Interface.close()

Interface.close()
Description

Invokes lower level code to close the Interface

  NMotionTM  is a sub-brand of Nawe Robotics
Copyright © 2025 Nawe Robotics Private Limited