AAMotion
AAMotion is a C# .NET API for advanced motion controller communication and control, designed for high-performance and flexible integration with industrial motion systems.
- 📚 API documentation: https://akribis-agito.github.io/Agito-Releases/aamotion/
- 🌐 Vendor: https://agito-akribis.com
Overview
AAMotion provides a comprehensive interface for connecting to, configuring, and controlling a wide range of motion controllers. The API supports both high-level and low-level operations, enabling users to perform tasks such as initialization, connection, axis control, IO operations, and advanced motion programming.
- Supported platforms: Windows 10 or later (Windows 7 SP1 or later on the .NET Framework 4.8 build)
- Supported frameworks: .NET Framework 4.8, .NET 8, .NET 10
- Documentation: API documentation is available via IntelliSense (XML doc comments included with the package).
System Requirements
- Microsoft Windows 10 or later; Windows 7 SP1 or later for the .NET Framework 4.8 build
- .NET Framework 4.8, .NET 8 or .NET 10
- Visual Studio 2022 or later (other IDEs may be used, but are not officially supported)
Installation
Install the NuGet package from nuget.org:
dotnet add package Agito.AAMotion
Or via the NuGet Package Manager in Visual Studio.
Getting Started
- Create a new C# project targeting .NET Framework 4.8, .NET 8 or .NET 10.
- Add the
Agito.AAMotionNuGet package. - Explore the API via IntelliSense — XML doc comments are included with the package.
Usage Example
Below is a minimal example demonstrating how to initialize a controller, connect to it, perform basic axis operations, and shut down:
using System;
using System.Diagnostics;
using AAMotion;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
const string ip = "172.1.1.101"; // the controller's factory default
MotionController controller = AAMotionAPI.Initialize(ControllerType.AGM800);
try
{
Console.WriteLine($"Connecting to {ip}");
controller.Init();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
bool isConnected = AAMotionAPI.Connect(controller, ip);
stopwatch.Stop();
Console.WriteLine($"Connect execution time: {stopwatch.ElapsedMilliseconds} ms");
if (isConnected)
{
Console.WriteLine("Connect successful");
AAMotionAPI.MotorOn(controller, AxisRef.B);
// Example: perform additional axis or IO operations here
}
else
{
Console.WriteLine("Connect failed");
}
}
finally
{
AAMotionAPI.MotorOff(controller, AxisRef.B);
Console.WriteLine($"MotorOff status for Axis B: {controller.GetAxis(AxisRef.B).MotorOn}");
// Release THIS controller's resources. The shared AACommServer keeps running
// for other clients; controller.Shutdown() would request a server shutdown,
// which the server honors only when this is its last client — call it only
// when you own the machine's comms lifecycle.
controller.Dispose();
}
}
}
}
Firmware simulation
The legacy file-based echo simulator (ConnectSimulator) was removed in 12.0.0. Local firmware
simulation now runs through AACommServer's managed ChannelType.Simulation channel: the server
spawns a firmware_sim.exe child, configures the motor, and bridges it over loopback.
The AAMotion package does not ship firmware_sim.exe. Provide the absolute path to a copy —
for example one extracted from a firmware release's AG300_MAS01_sim_<product>_v<version>.zip, or
one PCSuite imported under Documents\PCSuite\ApplicationData\Simulations\<product>\<version>\. A
sibling sim.info in that folder is used by PCSuite's version picker only; this API reads the exe
path alone.
controller.Init();
string simExe = @"C:\Users\me\Documents\PCSuite\ApplicationData\Simulations\AGD301\5.3.0\firmware_sim.exe";
bool ok = AAMotionAPI.ConnectSimulation(controller, simExe);
// Optional: pass catalog labels and a custom motor profile
// ok = AAMotionAPI.ConnectSimulation(controller, simExe, "AGD301", "5.0.0", "counts_per_rev=8000\npole_pairs=2\n");
if (ok)
{
// ... operate against the simulated controller ...
}
controller.Disconnect(); // reaps the firmware_sim child via AACommServer
controller.Dispose();
simProduct is forwarded to the server, and a value the product catalog resolves also declares the
session's controller type. That lets the connect verify the type rather than discover it, and
discovering it costs a probe that spawns and reaps a whole extra firmware_sim child before the
session's own. A declared type the sim disagrees with fails the connect — ConnectSimulation returns
false, and the underlying InvalidControllerType appears in the AAComm debug log.
What resolves is the ProductTypes catalog spelling: AGM800, AGD155, AGD155-EC,
AGD101-EC, AGD156-EC, DRV6-V3, Pulse-Sync, AGM800-EC and AGM400; the paired products under either half
of their name — AGD301 and AGD200, the Simulations\<product>\ folder prefixes (including the one
used above), or AGC301 and AGC300 — or their paired spelling (AGD301orAGC301,
AGD200orAGC300); and the older spellings of the products that have them (AGD301/AGC301,
AG300-CTL02, CiG1-MAS02, …). Passing myType.ToString() for a ControllerType resolves for
every member but the four EtherCAT ones — AGD155EC is catalogued AGD155-EC.
An unresolved name discovers the type by probe, exactly as omitting the argument does.
TryReconnect is Ethernet-only and cannot recover a simulation link. To reopen one, call
Disconnect() first and then ConnectSimulation(...). ConnectSimulation does not tear down a link
it finds open, and the connect short-circuits on one, so calling it alone on a wedged session returns
false without reopening anything — LastKnownAddress and AutoReconnectEnabled are recorded before
the attempt, so a failed call still updates them.
User Units
When the connected controller has Global User Units armed for an axis, AAMotion carries that axis's
scaled parameters in the controller's display units. The groups are position (main, auxiliary,
pulse-direction), velocity (the same three), acceleration and force: on an axis configured at
0.01 mm/count, Pos reads 24.68 rather than 2468, and an assignment is converted back to counts on
the way out. The conversion is applied by the communication layer, per parameter, from the group
membership and per-axis factor it reads at connect. A controller with no Global User Units armed is
unaffected, and so is one using the firmware's embedded user-units system — the firmware applies those
on the wire, before AAMotion sees the value.
To keep an integration on raw controller counts, opt the controller out:
controller.UseUserUnits = false; // before or after Connect — it is remembered either way
AAMotionAPI.Connect(controller, ip);
UseUserUnits defaults to true, and three things about it are worth knowing:
- It survives connect and reconnect. The communication layer clears its own opt-out on every
successful connect; the controller re-applies your setting once the connect succeeds, so configuring
it up front works. A direct write to
CommAPI.SkipUserUnitsis visible through the property but is not remembered — only a value assigned throughUseUserUnitsis re-applied. - It is instance-wide, not call-wide. It forwards to
CommAPI.SkipUserUnits, so two controllers sharing oneCommAPIshare the setting. To bypass the scaling for a single message, useAACommMessage.SkipUserUnitsinstead. - It does not unlock raw User Units configuration writes. A direct assignment to a
*UnitFctor*UnitUntparameter is rejected either way (error 4023) — change those through the API.
A multi-axis move requires its axes to agree on their units. AAMotionAPI.MoveAbs / MoveRel over
several axes apply one speed and one accel to the vector distance across them, which only means
something if the axes measure the same quantity: every axis must carry the same unit name in the
position, velocity and acceleration groups. Different scaling factors under one name are fine — two
encoder resolutions on the same gantry still measure millimetres — but a rad axis and a cm axis have
no common speed, and an axis with User Units off is on controller counts, which does not match a scaled
one. Axes that disagree make the call return false and command nothing, before any parameter is read
from or written to the controller; the reason arrives on MotionController.ErrorOccurred as
(-1, "<method>", <message naming the axes and the group>). Passing null as the controller does not
opt out — the units are read from the axes themselves, so the call still returns false; there is just
no channel to report it on. For the same reason the axes must be the SDK's own (controller.GetAxis(...)
/ controller.AxisList): a wrapper or mock of your own around ISingleAxis carries no axis identity, so
its units cannot be checked and the call is refused. Check an axis's configuration with
controller.CommAPI.GetUserUnitsEnabled('A') and
controller.CommAPI.MessagesContainer.TryGetUserUnitsGroup(group)?.AxisUnits('A') — both are keyed by
the axis letter, not by AxisRef — or opt out with UseUserUnits = false, which puts every axis back
on counts and therefore always matches.
The vector surface carries the same rule. IVector.VectorLinear (both overloads) and VectorArc
apply one VecSpeed/VecAccel/VecDecel across the axes named by the mask while each axis's AbsTrgt
— and, for an arc, its VecArcCenter — is in that axis's own units, so the same three groups must agree
and a mismatch is refused the same way, with the same (-1, "<method>", <message>) on
ErrorOccurred. The profile slots are written to the axis the call was made on, which is always one of the
members: the controller reads the profile from the axis that receives Begin, requires that axis to be the
mask's lowest member, and needs at least two members, all among A..H. A call whose initiating axis is not a
member of its mask, or not the lowest one, or that names fewer than two members or an axis past H, is
therefore refused before anything is written, with the same ErrorOccurred shape.
Which units a value is in
controller.GetUserUnits(axis, keyword) returns the unit name and factor that parameter reads and writes
in on that axis, as a UserUnitAxis: the controller's values when Global User Units are enabled on the
axis and the parameter belongs to one of its groups, otherwise an empty name and factor 1 — controller
counts. That identity answer is the counts pin: check Factor == 1 before treating a value as counts — a
scaled axis may carry no unit name, so the name alone is not the test. It is never null, and it is also
what an unknown keyword, an axis with units off, and a controller opted out through UseUserUnits = false
return. axis names one axis; a combined mask is an ArgumentException.
SetPosition takes counts, not display units
ISingleAxis.SetPosition writes a parameter the controller declares unscaled, so its argument is in
controller counts whether or not Global User Units are armed — not in the display units Pos reads in.
SetPosition(axis.Pos) is therefore not a round trip on a scaled axis: on the 0.01 mm/count axis
above it writes 24.68 as a count, rounded to 25. To set the current position from a display value,
divide it by that axis's position factor —
double factor = controller.GetUserUnits(AxisRef.A, eKeyword.Pos).Factor; // 1 when the axis is on counts
axis.SetPosition(displayValue / factor);
— or read the position in counts (controller.UseUserUnits = false) and pass that value straight back.
API Documentation
API documentation is available via IntelliSense. XML doc comments are included with the NuGet package.
Upgrading from 11.0.0
12.0.0 is a breaking release: download aamotion-12-change-disclosure.html, published with the 12.0.0 release, and open it in a browser for every break with the diagnostic it raises and compiled before/after samples — GitHub serves an .html release asset as a file rather than rendering it.
Release Notes
See CHANGELOG.md for detailed release notes and version history.
License
This software is licensed under the MIT License. See the NuGet package metadata for details.
For further information, consult the included documentation or contact Agito support.