Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.


[12.0.0] - 2026-10-30

⚠️ Breaking

  • The 11.0.0 → 12.0.0 upgrade page walks the whole release for a consumer — every break with the diagnostic it raises, the changes that raise none, and compiled before/after samples. It is published with the 12.0.0 release as aamotion-12-change-disclosure.html; download and open it in a browser, as GitHub serves an .html release asset as a file rather than rendering it.
  • Requires Agito.AAComm 15.x (see its changelog for the breaking communication-layer changes: the IIdentityContainer member, MessagesInBulk on a Simulation channel, the AllStatInterpreter input contract).
  • The AssemblyVersion is pinned to 12.0.0.0 for the whole 12.x line (package and file version keep the full number). .NET Framework consumers must recompile against 12.0.0 — a binary built against 11.0.0.0 does not bind, and no binding redirect bridges the identity change. .NET 8/10 consumers see no difference.
  • A type of yours that implements one of the seven retyped interfaces — ISingleAxis, IVector, IIOModule, IECAM, IGroupAxis, ICiGroupAxis or ICiSlave; a wrapper, an adapter, a test double — no longer satisfies it until every affected member carries the new type: 73 members lose dynamic, 59 properties move from int to double, 56 array members move from AAValueArray<int> to AAValueArray<double>, the four gantry gains become per-set AAValueArray<double> members and 34 methods widen their parameters — 226 members, none of them counted twice. The compiler reports each unsatisfied member at the class declaration, so this surfaces as one wall of errors on that type rather than at call sites.
  • Parameter values are now carried as double end to end. Every array property that was AAValueArray<int> — Identity, GenData, ErrLog, DInMode, HomingDef, LoggerParams, LoggerStatus, EventTable and the rest, on ISingleAxis, IIOModule, IGroupAxis, IVector, IECAM, ICiSlave, ICiGroupAxis and MotionController — is now AAValueArray<double>, and yields the parameter's exact value instead of a rounded int. Assigning an element into an int needs an explicit cast.
  • dynamic is gone from the AAMotion surface. Every dynamic member is now statically typed: the value properties (Pos, AuxPos, PosRef, Speed, Accel, Decel, MaxVel, MaxAcc, Force, MotorCurr, ContCL, Ia…Vq, the Vec* members on IVector, …) are double; the array-valued ones (ISingleAxis.Vel, ISingleAxis.AInGain, and IIOModule.AInPort / AInFilt / AInOffSet / AInDB / AInGain) are AADynamicValueArray; MotionController.GenDataD is AADoubleArray. Code that relied on late binding — calling a member the static type does not have, or letting a dynamic convert silently to int — no longer compiles, and a narrowing conversion now needs an explicit (int). In exchange the members are IntelliSense-visible and bind at compile time.
  • Quantity-carrying properties are double; modes, indices, counts, bitmasks and millisecond durations keep their integer type (int, or uint for the homing timeouts). The properties that moved from int to double are the positions and distances (MasterPos, PosErr, RelTrgt, MaxPosErr, InTargetTol, LockVal, ModRev, AuxModRev, ComtAng, EventNextPos, EventBegPos, EventEndPos, EventGap, SpeedChgPos, CurrPosTh, CurrPosErrTh, PosPosTh, ForcePosErrTh, RetractTarget, MapErrOffset, MapErrOffRamp, MapErrOnStep, CNCAPosRef, CNCAdPosRef, CNCBAbsTrgt, CNCBPosRef, CNCBdPosRef), the velocities and profile magnitudes (StuckVel, SpeedChgNew, RetractSpeed, CNCASpeed, CNCBSpeed, CNCBEndSpeed, CNCBAccel, CNCBDecel, CNCBJerk, CNCAEmrgDec, CNCBEmrgDec), the gains and force parameters (ForceGain, ForceKi, ForceKd, ForceFFW, ForceVelFFW, ForceRefFilt, MaxForceErr, MaxForceErrOL, ForceAInTh, CurrCurrTh, CurrAInTh), and the voltages and temperatures (VBus, VLogic, MinVBus, MaxVBus, MaxVBusAbs, RegenOn, RegenOff, BoardTemp, PwrTemp, MotorTemp, MaxPwrTemp). A caller that stored one in an int must widen it or add a cast; one reading a scaled User-Units axis now receives the fractional value instead of a truncated one. Status, mode and configuration properties (MotionStat, StatReg, ConFlt, LimitsStat, MotorOn, MotorType, EncType, EncRes, EncDir, UsrUnits, EmulRat, ProtectMask, InTargetTime, StuckTime, RptWait, MaxVBusTime, PeakTime, MaxPWM, and the *Flag / *Dir / *Src / *Type / *Stat / table-index members) keep int. The four gantry gains (GantryAccFFW, GantryPosGain, GantryVelGain, GantryVelKi) move from int to a per-set AAValueArray<double> instead; see the next entry.
  • ISingleAxis.GantryAccFFW, GantryPosGain, GantryVelGain and GantryVelKi are per-set AAValueArray<double> members, get-only. On firmware with gain scheduling — the AGM800 — each gain holds one value per gain-scheduling set, and the controller lists it as an array of five: read or write set n as axis.GantryPosGain[n], n from 1 to 5. There the member's .Value raises InvalidOperationException, as on every array member. On the AGD200 and AGD155-EC the gain is a scalar: read and write it through .Value. As int properties they sent the unindexed mnemonic, which the AGM800 refuses (ERR 24), so no set could be read or written there. Reading one into a number no longer compiles (CS0029), nor does assigning to the member (CS0200); an implementer of ISingleAxis must declare the four the same way. GantryVelGain and GantryVelKi are the velocity loop's proportional and integral gains; their documentation named the position loop.
  • The single-axis motion, homing, vector, PEG and force-control methods take double. MoveAbs, MoveRel and their AndWait / Repetitive / RepetitiveCycle / TwoSpeed variants, Jog, SetPosition, Home, HomeToHardStop, HomeToIndex, HomeToLimitThenIndex, HomeToHardStopThenIndex, SetSingleEventPEG, SetEventFixedGapPEG, SetEventTableValue, the ForceCurr_Set* family and IVector's VectorLinear / VectorArc / SetVectorParam carry their position, distance, speed, acceleration, deceleration, threshold, offset, arc-centre and gain arguments as double / double? — including the ones that were uint on the homing methods — and ForceCurr_SetForceCmdSource takes double[] for forceCurrSlope and forceCmdVal, VectorLinear params double[] pos. Millisecond timeouts and dwell times keep their integer type (uint for the homing timeouts, int elsewhere); cycle counts, directions, sources, polarities, table indices, command hold times and the vector smoothing factor (VecJerk) stay int. VecAccel / VecDecel / VecEmrgDec are float registers, so SetVectorParam(…, 1234.5, …) used to truncate on every controller, User Units or not. The same signatures change on AAMotionAPI and on the Java binding. Passing an int literal still compiles; passing an int? variable still compiles too (the nullable conversion lifts); an int[] into a widened double[] parameter does not — declare it double[]. The IGroupAxis / ICiGroupAxis CNC path builders and the IECAM table helpers are deliberately unchanged: their arguments are controller counts, unaffected by Global User Units — a CNC segment crosses as a raw SpecialBulk payload the scaling seam leaves alone, and an ECAM table is filled with the counts the controller consumes — not display values.
  • ISingleAxis.MoveAbs(dynamic position, dynamic speed = null, …) is now MoveAbs(double position, double? speed = null, double? accel = null, double? decel = null). The optional-argument call shape is unchanged: nullable value types replace the dynamic nulls rather than an overload set.
  • The multi-axis AAMotionAPI.MoveAbs / MoveRel overloads take double accel, double speed, double decel and double[] targets (double aPos, double bPos, … on the fixed-arity overloads). An int[] target array no longer binds — declare it double[].
  • ParameterTypeConverter.GetSmartValue and GetArray return double instead of dynamic, and AASmartArrayWrapper's indexer returns double. A 64-bit (long) parameter is still read with its own command rather than from the value slot; the controller's long parameters stay inside ±2^51, which a double holds exactly.
  • AAMotionConfig.UseNativeDataTypes is removed. It chose between a rounded int view and a native-type view of float/double/long parameters; the value now always arrives at full precision, so the switch has nothing left to select. Delete the assignment.
  • ParameterTypeConverter.ConvertStringToRawInt(eKeyword, string) is renamed ConvertStringToRawValue and returns double; ConvertValueForWrite(eKeyword, int) now takes a double.
  • FastReadCache.GetCachedValue / GetCachedArrayValue return double? instead of int?.
  • A multi-axis AAMotionAPI.MoveAbs / MoveRel across axes whose Global User Units differ now returns false and commands nothing. These overloads apply one speed and one accel to the vector distance over every axis, so the axes have to measure the same quantity: each must carry the same unit name in the position, velocity and acceleration groups. sqrt(dA² + dB²) over a radian and a centimetre is not a length, and the single speed the caller passes has no unit to give it — so instead of norming incommensurable deltas and running the move, the call is refused before any parameter is read from or written to the controller, and the reason arrives on MotionController.ErrorOccurred as (-1, "<method>", <message naming the axes and the group>). Different scaling factors under the same unit name still match, so two encoder resolutions on one gantry are unaffected; an axis with User Units switched off is on controller counts and does not match a scaled axis. Each axis's units come from the axis itself, so passing null as the controller does not opt out — the call still returns false, only the error event is lost — and the axes must be the SDK's own (GetAxis / AxisList): a consumer's own ISingleAxis wrapper or mock carries no axis identity, so its units cannot be checked and the call is refused. Check an axis with CommAPI.GetUserUnitsEnabled(axis) and CommAPI.MessagesContainer.TryGetUserUnitsGroup(group)?.AxisUnits(axis), or set MotionController.UseUserUnits = false to put every axis back on counts — which always matches and restores the counts-based behaviour. A machine with no Global User Units armed is unaffected.
  • A vector move across axes whose Global User Units differ now returns false and commands nothing. IVector.VectorLinear (both overloads) and VectorArc — and their AAMotionAPI equivalents — apply one VecSpeed/VecAccel/VecDecel across the whole move while each axis's AbsTrgt, and for an arc its VecArcCenter, is in that axis's own units. That only means something if the axes measure the same quantity, so the same rule the multi-axis MoveAbs/MoveRel overloads apply now covers the vector surface: every axis must carry the same unit name in the position, velocity and acceleration groups, different scaling factors under one name still match, and an axis with User Units off is on counts and does not match a scaled one. A mismatch is refused before any parameter is read from or written to the controller, and reported on MotionController.ErrorOccurred as (-1, "<method>", <message naming the axes and the group>). The profile slots are written to the axis the call was made on, which is one of the members, so it is checked as one. Set MotionController.UseUserUnits = false to put every axis back on counts, which always matches. A machine with no Global User Units armed is unaffected.
  • ISingleAxis.GantryYawRef and the eKeyword.GantryYawRef member are removed. Controller firmware deprecated the GantryYawRef keyword on 2023-11-30; every supported (post-deprecation) firmware rejects it, so the property fails against current controllers. There is no replacement — delete the calls. The sibling gantry parameters GantryFdbk and GantryOffset are unaffected.
  • ISingleAxis.ForceRelFOn / eKeyword.ForceRelFOn are renamed ForceRefFOn. The controller's mnemonic is ForceRefFOn (CAN 579); the misspelled members never resolved in the message table, so every read returned 0 and every write was rejected. Rename the call sites; the C-ABI constant becomes AAMOTION_KEYWORD_FORCE_REF_FON.
  • IIOModule.AOutGain is added beside IIOModule.AOutShifts, which is unchanged. They are two different parameters that share CAN 221 across firmware generations, not one parameter under two names. AOutShifts is an integer shift exponent — the output is scaled by 2^value — and is what firmware without float analog IO reports. AOutGain is a plain multiplier and is what firmware with it reports. So 2 means x4 on one and x2 on the other, and they are not interchangeable. Each resolves only on the firmware that carries it; on the other, reading or writing an output — AOutGain[i] with i from 1, on firmware that reports AOutShifts, or the reverse — raises IndexOutOfRangeException before anything is sent. PCSuite makes the same distinction, editing one as an integer and the other as a float.
  • IGroupAxis.CNCAEncRatio / eKeyword.CNCAEncRatio are replaced by read-only CNCACumPosRef (double). CAN 468 is the controller's CNCACumPosRef, not an encoder ratio. The C-ABI constant becomes AAMOTION_KEYWORD_CNCACUM_POS_REF.
  • MotionStat and StatReg are [Flags] enums, and ISingleAxis.MotionStatEnum / StatRegEnum return the whole register as a bit set. Every member is now the bit it names (1 << n; a None = 0 member is added), and a mirror reports every set bit — an unmapped one included — instead of the one member whose index happened to equal the register value, which mislabelled any multi-bit register (bits 1 and 2 read as the member numbered 6) and turned an unmatched value into InMotion / Commutation. Test a bit with a mask ((axis.MotionStatEnum & MotionStat.Accelerating) != 0), not with ==; code that used a member's numeric value as a bit index must use the member as the mask it now is. StatReg.StepperOpenLoopStall is bit 31, int.MinValue as an int. EnumPropertyHelper.GetStatRegDes(0) describes an empty register as "No status bits set" instead of "Commutation", bit 0's own label.
  • AAMotionAPI.ConnectSimulator, MotionController.ConnectSimulator and CommAPIInterface.ConnectSimulator (both overloads of each) are removed, together with the AllowSim and SimParamsFile user settings of AACommServer. They belonged to the file-based parameter-echo simulator that was deleted from AACommServer in June 2026; since then no shipped server could accept the connection, so every call returned false. Delete the calls. The managed firmware simulation (ChannelType.Simulation) is the replacement, and ConnectSimulation — added in this release, see Added below — is its entry point.
  • OpticalCouplingLightFinding.SpiralDir and SpiralConfig.SpiralDir are removed. They wrote AGenData[525], a spiral-direction slot the controller's light-finding process no longer defines — writing it left a value on a slot the firmware may later reuse for something else, and the direction it claimed to select had no effect. There is no replacement: delete the assignment, and drop the field from any SpiralConfig initializer. Every other spiral parameter (NumOfXAxis, NumOfYAxis, SpiralPitch, SpiralMaxRadius, SpiralAngleCoeff, SpiralScanSpeed, SpiralRtnSpeed, PowerTh) is unchanged.
  • SingleAxisConfig carries FlatZoneDecay, OptimalCoeff and FiberArrayWidth, and ApplySingleAxisConfig applies them — AGenData[509], [508] and [506], the flat-zone return gradient, the peak-versus-balance weighting and the fiber-array width. They are single-axis process parameters, so a config that leaves them at their default 0 now writes three zeros where it previously wrote nothing, which turns flat-zone return mode off rather than leaving whatever a previous run set. A caller that assigned the properties directly after applying a config should move the values into the config instead. The properties themselves are unchanged.
  • No compile error — the call now throws. HomeToIndex, HomeToLimitThenIndex and HomeToHardStopThenIndex validate their profile arguments before arming anything: a speed, acceleration or hard-stop threshold that is not a finite positive number, or a direction other than 1 or -1, raises ArgumentOutOfRangeException naming the argument, and no HomingDef slot is written. These arguments were uint before the double retype, so the type carried the non-negativity the docs state. Such a call used to return as if it had succeeded — the firmware silently rejected the out-of-range slot (its acceleration floor is 100.0 counts/s²) and the sequence ran on whatever profile the slots already held.
  • No compile error — a multi-axis call now writes nothing. WriteUserParam, UserParamWait, MultiWriteUserParam and MultiWriteUserParamWait refuse an AxisRef that does not name exactly one axis A..L: the call returns false and raises ErrorOccurred, and nothing is pushed. The segment's axis field is the port index the controller subscripts UserParam[] with, so a multi-axis mask composed a fractional field that no wire could carry. A single-axis call composes the same segment as before, with the index now an integer field.
  • No compile error — the same number now means a different duration. Homing timeouts are applied in milliseconds, the unit the API documents. Home, HomeToHardStop, HomeToIndex, HomeToLimitThenIndex and HomeToHardStopThenIndex wrote the HomingDef timeout slots as sampleRate * timeout, which treats the value as seconds — a documented 15000 ms timeout became 15000 s, so a stuck homing step effectively never gave up. The slots now carry round(timeout / 1000 * sampleRate) samples, the same conversion the PCSuite homing tool applies. A caller that compensated by passing seconds must switch to milliseconds.
  • An eKeyword member's number is an opaque identifier, not the controller's CAN code. It always was in practice — a keyword is resolved by its member name against the message table the connected controller reports, so the number never reaches the wire — but the enum documented the numbers as CAN codes and a few had drifted from any real one. A code is also product- and firmware-specific, which no single number can express: measured on three controllers, an AGM800 reports RptCounter as 732 where an AGD200 reports 714, and they agree on all 303 parameters they share. ModeSwitchPos moves to 438 and the RptMode / RptCycles / RptCounter trio to 730 / 731 / 732, the values the current AGM800 firmware uses, and PosKi is added at 714. AOutShifts moves to 2221, because 221 now names AOutGain, so a stored (eKeyword)221 means AOutGain. Managed behaviour is unchanged by all of this. A C consumer that stored the old numbers should recompile; one that treated a constant as a CAN code should read the real one from the controller with MotionController.GetKeywordCANCode.
  • MotionController.ProgHaltAll is removed. It exposed a controller command as a readable property, and reading one sends it — touching controller.ProgHaltAll.Value halted every active user program thread instead of reporting a number. ISingleAxis.ProgHaltAll() is the method that has always meant to do that, and it stays.
  • MotionController.IsConnected (and AAMotionAPI.IsConnected) no longer pings or reconnects. The getter used to actively probe the controller via ICMP on every read and, when auto-reconnect was armed, disconnect and reconnect the link on a failed probe — a property read could block for up to ~19 s, and a debugger watch window or a WPF binding could disconnect a healthy controller just by evaluating it. The getter is now a cheap, non-blocking read of the comm layer's own logical connection state. Call the new MotionController.CheckConnection() / AAMotionAPI.CheckConnection(controller) to actively probe reachability, and TryReconnect to recover a link that is actually down. enableAutoReconnect (Connect's parameter, AutoReconnectEnabled) is recorded but no longer has any automatic effect.
  • A typed read that fails now throws ControllerCommandException instead of answering 0. A parameter read through a typed property, through one of the Is* status helpers built on them, or through MotionController.GetValue, used to answer 0 when the controller refused the read or replied with something that is not a number — indistinguishable from the controller genuinely reporting 0, which is what a position, a velocity and a status word all report at rest. The exception carries the controller's own error code, the keyword and the exact command text. No signature changes, so the compiler reports nothing: a poll loop that read a status property over a dead link used to spin on an invented 0 and now leaves the loop at the first failed read. ISingleAxis.TryRead, ISingleAxis.TryReadArray and MotionController.TryGetValue are unchanged — they report the same failure as false and never throw — and LastReadError and ReadFailures record the failure on both routes, throwing and reporting alike. An AADoubleArray element read, such as MotionController.GenDataD[i], a direct ParameterTypeConverter.GetDoubleArray call, and the raw register reads ReadGenDataF, ReadGenDataD, ReadUserParam, ReadUserParamF and ReadUserParamD on MotionController and AAMotionAPI follow the same contract; they used to raise InvalidOperationException or FormatException, record nothing, and hand back a NaN or infinite reply as a value. A consumer catching InvalidOperationException around a GenDataD[i] or other AADoubleArray element read, FormatException / InvalidOperationException around a ParameterTypeConverter.GetDoubleArray call, or FormatException around one of those five register reads, must catch ControllerCommandException instead. Nothing new crosses the C ABI: every export already turns a managed throw into its failure status, so aamotion_get_value now reports false for a reply that is not a number where it used to hand back 0 as if it had been read.
  • A typed set the controller refuses now throws ControllerCommandException instead of doing nothing. Assigning to a typed property — axis.Speed = 1000, axis.AbsTrgt, axis.Vel[1], controller.GenData[3] — used to discard the controller's answer, so a refused write left the parameter holding its old value with nothing at the call site to say so, and only a subscriber to ErrorOccurred ever heard about it. The exception carries the controller's own error code, the keyword, and the exact command text including the assignment (ASpeed=1000), so the failure names the value that did not take. No signature changes, so the compiler reports nothing. The consequence to plan for is that a call which writes several parameters before it commands anything — MoveAbs, MoveRel, ConfigureSlowLogger — now stops at the first refusal instead of running to the end, so an axis no longer begins on a profile the controller never took. The optical-coupling configuration is the exception and is unchanged: its properties assign through MotionController.WriteGenData, which catches the refusal into a false the setters discard, so it still runs to the end and still reports through ErrorOccurred once per refused slot. MotionController.WriteGenData reports a refused slot write as false, which is what its documented contract always promised. A failed write is deliberately NOT recorded in LastReadError and NOT counted by ReadFailures — both are the read record, by name and by their own documentation, so a consumer polling them for stale reads keeps being told about reads and nothing else. There is no TrySet: catch the exception, or keep using ErrorOccurred, which is still raised for every refusal. The two array paths that reach the controller through AASmartArrayWrapper's setter rather than the changed-value event — a long-typed array element, and MotionController.GenDataD — raise the same type as everything else: they used to raise InvalidOperationException with a message carrying no error code, so catch (ControllerCommandException) missed them and the payload was reachable only as text. A consumer catching InvalidOperationException around a GenDataD[i] or long-array element write must catch ControllerCommandException instead. Nothing new crosses the C ABI — every write export already turns a managed throw into its failure status, and the status bytes are unchanged.
  • No compile error — .Value on an array member now throws. An array parameter has no single value. On any AAValueArray<double> member — MotionController.GenData, IIOModule.AInMode, ISingleAxis.HomingDef and the rest — whose parameter the connected controller lists as an array of one or more elements, .Value raises InvalidOperationException on get and on set, before anything is sent, with a message naming the parameter and the indexes it has (AInMode is an array parameter; read an element from index 1 to 4.). The getter used to answer the slot's cached first element without reading the controller — 0 before any element was read — and the setter sent nothing, so a write was dropped and the next .Value handed it back as though the controller held it. ParameterTypeConverter.GetSmartValue and SetSmartValue go through .Value, so they throw the same for such a keyword, except a 64-bit (long) one, which they still send unindexed with its own command; the controller refuses that (ERR 24), raised as ControllerCommandException. Read or write an element by its index instead, from StartIndex to EndIndex: io.AInMode[1], or MotionController.GetValue(axis, keyword, index), which already refused index 0 on an array parameter. Nothing crosses the C ABI: aamotion_get_value reads through GetValue.
  • IGroupAxis.IsCNCCompleted raises ControllerCommandException on a failed status read, on both groups. It is a poll-until-flips predicate, and a predicate has no answer for a link that did not reply, so it now raises rather than reporting a completion state it could not read. The Group B branch previously returned false, which left a while (!IsCNCCompleted(...)) loop spinning forever on a dead link; Group A already raised, because it reads through the value-answering path that 12.0.0 made throw. Wrap the poll, or read the status yourself with TryGetValue if a failed poll is an expected outcome. The C ABI is unchanged — aamotion_is_cnc_completed already caught and returned 0 either way. The Wait* family is deliberately untouched: a wait returns whether it succeeded, and a failed read means it did not, which is the abort behaviour 12.0.0 already introduced.

Added

  • Add MotionController.CreateEventTableClient(axis) for the shared explicit verified-table lifecycle; it does not commission, move or automatically arm an axis.
  • AAMotionAPI.DownloadParameters orders the lock/event writes for firmware with the LockEventMode 1/2 guards (Firmware-Main #1256). On such firmware the controller refuses a change to EncSinCosHWEn, EncDir, VEncSrc or VEncOn (and AuxEncDir with the aux source, VEncFact/VEncFactDen with the VEnc source) while LockEn or EventOn is on (ERR 403), and refuses LockEn/EventOn with a stale offset (ERR 404; ERR 335 still means LockEventInit never ran). A .par is written in file order, so a restore over an armed axis, or one that re-arms after changing the source, failed partway. When the controller's firmware has the guards (it declares LockEventMode up to 2) and an axis's rows touch a dependency or LockEventMode, the download now writes LockEn and EventOn = 0 first (a .par carries neither, as they are not flash parameters, so the restore leaves that axis's Lock and Event off), then the other rows in file order, then LockEventMode, then LockEventInit when the axis is in mode 1 at re-arm (the file's LockEventMode row, else the controller's current mode, which the download reads first; if that read fails nothing is written), then the file's own LockEn/EventOn rows in file order, so the last one requested is the state left. The axis letter is matched case-insensitively (a and A are one axis). The added rows are sent but not counted as written. A refused disarm counts in ErrorCount, is listed in Rejections marked as added, fails the download with a completion error, and nothing is committed. A refused LockEventInit (ERR 151 when the remote can't be reached, say) counts in ErrorCount and is listed in Rejections, marked to be run before enabling Lock or Event, and the download still commits: the firmware refuses to arm that axis (ERR 335 or 404) until LockEventInit succeeds. Firmware that declares LockEventMode only up to 1 has no such guards and is written in file order. Integrators who write these keywords themselves need the same order; EncFilt, AuxEncFilt and CurrDir are not dependencies.
  • AGM800-EC: DownloadParameters treats the drive like any other until its next enable (Firmware-Main #1258). The drive's EtherCAT enable forces LockEventMode 2 and learns the offset itself, but only at that enable, so a restore that leaves the axis in mode 1 still runs LockEventInit before re-arming. The disarm still comes first. On EtherCAT it also drops an armed touch probe, which the master re-arms by toggling the 0x60B8 enable bit.
  • MotionController.UserProgramPrintReceived / AAMotionAPI.SetUserProgramPrintHandler / ClearUserProgramPrintHandler — subscribe to user-program printf lines (FW v5+, AGM800 Ethernet) as UserProgramPrintMessage. Wire fields: TimeSec, TimeTick, Thread, Message. Console-friendly fields: Timestamp (wall clock from ATime boot + stamp at the current-loop sample rate) and DisplayText (strips a leading !R!/!G!/!Y!, trims trailing CR/LF, expands literal \n/\r/\t). ATime is queried once (async CommAPI.Send, same path as the PCSuite Console — not sync SendReceive, so a clock timeout cannot tear down a fresh connect) when the first handler is added while connected, or on connect / a successful TryReconnect that rebuilt the link when a handler is already subscribed; the first printf line seeds the boot if that query fails. Disconnect/Dispose stop delivery so a late push cannot reseed the clock; TryReconnect reopens delivery after a disconnect. Clearing a handler mid-packet stops further lines in that packet. Typical Console use: Console.WriteLine($"{m.Timestamp:yyyy-MM-dd HH:mm:ss.fff} [T{m.Thread}] {m.DisplayText}"). Delivery rides the existing AAComm push path (CommAPI.OnPushMessageReceived → AACommPushMsgUP); no second socket on port 50010. Other push sources are ignored.
  • ControllerCommandException — the exception a failed command raises, and the only one AAMotion defines. It carries ErrorCode (the controller's own code, or NoControllerCode when the SDK detected the failure itself — a reply that is not a number), Keyword, Command (the exact text that was sent, array subscript and assigned value included) and a Message composed from all three, so the one line a user pastes into a bug report names the command as well as the error.
  • MotionController.ConnectSimulation / AAMotionAPI.ConnectSimulation(controller, simExePath[, …]) — the managed firmware-simulation entry point over ChannelType.Simulation (#902). Pass the absolute path to a firmware_sim.exe on disk — a relative path is refused, because the SDK checks it against the calling process's working directory while AACommServer spawns it from its own, so a relative path that looks valid here can name a different file there. AACommServer spawns it, configures the motor, and bridges loopback. The SDK does not ship the binary and does not read a sibling sim.info — optional simProduct, simVersion and simMotorProfile parameters are forwarded when supplied. A simProduct the product catalog knows also declares the controller type (AGM800, AGD155, AGD155-EC, AGD101-EC, AGD156-EC, DRV6-V3, Pulse-Sync, AGM800-EC, AGD301, AGC301, AGD200, AGC300, the paired spellings AGD301orAGC301 and AGD200orAGC300, the deprecated AGM400, and the older spellings of the ones that have them), which roughly halves the connect: discovering it instead costs a probe that spawns and reaps a whole extra firmware_sim child before the session's own. A declared type is verified like any other connection's, so a simProduct that disagrees with what the sim reports fails the connect — ConnectSimulation returns false, and the underlying InvalidControllerType appears in the AAComm debug log — instead of connecting to whatever the sim actually is. A name the catalog does not resolve is discovered by that probe, exactly as omitting it is — among them the four EtherCAT ControllerType member names (AGD155EC is catalogued AGD155-EC). Disconnect reaps the child process. TryReconnect remains Ethernet-only. To reopen a simulation session call Disconnect() first, 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. On the C ABI the sequence has an extra step, because aamotion_disconnect also frees the handle: disconnect, then a new aamotion_initialize, then aamotion_connect_simulation. The native SDK exposes the same path as aamotion_connect_simulation.
  • MotionController.CheckConnection() / AAMotionAPI.CheckConnection(controller) actively probe physical reachability via ICMP, without the side effects IsConnected used to have — never disconnects or reconnects by itself. Call it from a background timer or a manual health check, not from a binding or a tight loop.
  • ControllerType.AGM800EC (the product id, 16) and the AGM800EC controller it builds. AAMotionAPI.Initialize(ControllerType.AGM800EC) returns an AGM800EC, which has the AGM800's shape — axes A–H, I/O modules IO_A–IO_L, and Group / GroupB.
  • ISingleAxis.TryRead(keyword, out value) / TryReadArray(keyword, index, out value) and MotionController.TryGetValue(axis, keyword, out value, index = 0) — a read that says whether it succeeded, rather than throwing. A typed property now throws ControllerCommandException when the controller replies with an error or the link is dead (see the Breaking entry above); the Try forms return false instead, with value 0, for an error reply and for a reply that is not a number. Use them where a failed read is an expected outcome — a poll, a probe, an optional parameter — and let the typed property throw everywhere else. They are always a controller round trip in the units the axis reads in, never the FastReadCache; TryGetValue applies GetValue's argument checks, and a command keyword is refused the same way. An implementer of ISingleAxis has two members more to satisfy — the same wall of errors 12.0.0 already raises on such a type.
  • MotionController.LastReadError and ReadFailures — the record ErrorOccurred never kept. LastReadError is the most recent failed read (a ReadError: the controller's error code, or -1 for a reply that was not a number; the keyword; the exact command sent, such as AGenData[3]; the formatted error text; and a UTC timestamp), or null when none has failed since the last successful connect; a later successful read does not clear it. ReadFailures counts every failed read since the last successful connect. Both cover every read the SDK makes for a value — the typed properties, GetValue, the Try forms, the raw register reads and the motion waits' own polls — so a loop that only checks ReadFailures before and after can tell a real 0 from an unanswered read. ErrorOccurred is raised, as before, for a read the controller refused, but not for a reply that is not a number.
  • A motion wait ends at the first failed poll. MoveAbsAndWait, MoveRelAndWait and their Repetitive / TwoSpeed variants poll InTargetStat live; on an error reply or a dead link the poll used to read 0 — "not in target" — and the wait spun to its full timeout (five minutes by default). It now returns false at once, and LastReadError names InTargetStat with the failure, which is how a caller tells that outcome from a timeout. The signatures are unchanged.
  • Sine PTP motion (MotionMode 20 / 21) — MotionMode.SinePTP and MotionMode.SinePTPRepeat, ISingleAxis.JerkInAcc / JerkInDec, and the optional pre-cruise phase PreCruAbsTrgt / PreCruRelTrgt / PreCruiseSpd. ISingleAxis.CommitMotion() replaces the profile of the Sine PTP motion running on that axis with the parameters written since; MotionController.CommitMotion(axes) and AAMotionAPI.CommitMotion(controller, axes) do it for several axes in one synchronised step (AxisRef.A | AxisRef.B), refused whole when any named axis is not in a Sine PTP motion. AAMotionAPI.SinePtpBegin / SinePtpCommit write a whole SinePtpMotionConfig and start or commit it. Needs firmware with the Sine PTP profiler (the AGM800); other controllers refuse MotionMode 20 and 21.
  • ISingleAxis.LockValBefMap and eKeyword.LockValBefMap — the lock capture before error mapping, read-only (Firmware-Main #1380). It is latched on the same lock edge as LockVal, but before error compensation: the map table, MapErrOffset and the MapType on/off ramp are excluded, as in PosBeforeMap, so it is in the same space as EventPos. It is read at the product's full position width, 64-bit on the AGM800 and AGM800-EC, where LockVal stays 32-bit, so there LockVal - LockValBefMap is the map correction only modulo 2^32. Only firmware that reports the LockValBefMap keyword carries it; on older firmware a read throws ControllerCommandException. It is not saved to flash, so UploadParameters leaves it out of a .par.
  • ISingleAxis.LockBMapTable and eKeyword.LockBMapTable (CAN 958) — the lock table before error mapping, read-only (Firmware-Main #1544): row i is the same capture as LockValTable[i], taken before error compensation as LockValBefMap is for LockVal. It is sized from the controller's table like LockValTable and indexed from 1, and it is not saved to flash, so UploadParameters leaves it out of a .par. Only firmware that reports the keyword carries it: CI-master builds (the AGM800) with CFG_CI_LOCK_FIFO=1. Like LockValTable, its entries are 32-bit, so on the AGM800 they hold LockValBefMap modulo 2^32. On other builds, where CAN 958 is a boot-stub row, and on older firmware an indexed read throws IndexOutOfRangeException and sends nothing.
  • ISingleAxis.LockValTabB, LockTimeTabB and LockBMapTabB with eKeyword.LockValTabB (478), LockTimeTabB (479) and LockBMapTabB (959): the second half of the lock tables, read-only. The firmware stores lock n in LockValTable[n] while n is within it, then in LockValTabB[n - LockValTable.EndIndex] (EndIndex = LOCK_TABLE_ARRAY_SIZE - 1), and stores none past twice that EndIndex, while LockCntr keeps counting. LockBMapTabB is the before-map companion, only on CI-master builds (the AGM800) with CFG_CI_LOCK_FIFO=1 (Firmware-Main #1544); its entries are 32-bit, like LockValTable, so on the AGM800 they hold LockValBefMap modulo 2^32. Each is sized from the controller's table and indexed from 1; on firmware without the keyword an indexed read throws IndexOutOfRangeException and sends nothing.
  • FIFO position tracking and PVAJ motion modes, and the PVAJ stop reasons — MotionMode.FIFOPositionTracking (19), MotionMode.PVAJ (22) and MotionMode.MultiAxisPVAJ (23), and MotionReason.PVAJMemberWasStopped, PVAJMemberWasAborted, PVAJMemberRLSFLS and PVAJMemberGotMotorOff (42–45): the axis stopped because another member of its PVAJ group was stopped, was aborted, hit a limit switch or went motor-off. ISingleAxis.MotionModeEnum and MotionReasonEnum name them, and EnumPropertyHelper.GetMotionModeDes / GetMotionReasonDes describe them instead of answering "Unknown motion mode" / "Unknown status". The PVAJ modes need firmware with the PVAJ profiler (AGM800 5.3.0). AAMotion has no members for the PVAJ list or its PVAJValidate / PVAJArm commands.
  • ConFlt names every controller fault the firmware reports (1055–1082) — ConFlt.PositionErrorExceedsLimitOpenLoop, VelocityErrorExceedsLimitOpenLoop, ForceErrorExceedsLimitOpenLoop (1055–1057), Undervoltage10v5Supply (1058), OverCurrentMotorC (1059), BoardTemperatureTooHigh (1060), OtherGantryMemberAxisMotorOff (1061), FaultInputControlledStop (1062), OtherVectorMemberAxisMotorOff (1063), StallDetectedMotorOff (1065), VirtualEncoderMaxPulsesExceeded (1066), AnomalyDetected (1067), AbsEncoderErrorBit, AbsEncoderCRCError, AbsEncoderDisconnected (1068–1070), UnstableCurrentLoop (1071), HighPIVNoise (1072), CollisionDetected (1073), LoadEstimatorNotReady (1074), NoPhasingDetected (1080), CPUBackgroundWatchdog (1081) and ExternalAmplifierFault (1082). ISingleAxis.ConFltEnum used to read ConFlt.NoFault for any of these, so an axis stopped by one of them looked fault-free; it now returns the fault, and EnumPropertyHelper.GetConFltDes describes it instead of answering "Unknown fault status". 1064 is not a member: the firmware no longer raises it.
  • ParamHandlingSummary.Rejections — every entry the controller refused during an upload or download, in send order, each carrying the parameter as it was sent and the controller's reply (ParamRejection); a download lists the rows it skipped before any of those. The summary previously reported only an ErrorCount, so which parameters failed, and why, could not be recovered from the callback at all.
  • ParamHandlingSummary.Conversions — every download row whose value was rewritten before it was sent, as read and as sent (ParamConversion.Parameter and ParamConversion.Sent), so a caller can compare a readback against what the controller was given. aacomm.cli params-download prints each as converted <row as read> -> <row as sent>.
  • MotionController.UseUserUnits — whether values cross this controller's communication layer with the Global User Units scaling applied. Reading it reports what the layer will actually do; a value assigned through it is remembered and re-applied at the next connect. Opting out does not unlock raw *UnitFct / *UnitUnt writes — those stay rejected either way.
  • MotionController.GetValue(axis, keyword, index = 0) — the value a parameter holds on an axis, read by keyword rather than through the member that exposes it, in the units that axis reads in (GetUserUnits names them). index 0 reads the parameter itself; 1 and up read one element of an array parameter. One axis A..L; a combined mask is an ArgumentException, as on GetUserUnits, and so is a keyword the message table flags as a controller command — reading one would send it.
  • MotionController.LoggerUpload(params object[] parameterSpecifications) — the logger upload resolved and scaled: pass the same specifications ConfigureSlowLogger took, in the same order, and get a LoggerDataContainer whose packets carry the packet id and the values in that order, in the units every other read uses. AAMotion keeps no record of the configuration, so the caller supplies it; an error reply raises ErrorOccurred and yields no packets. The parameterless LoggerUpload() is unchanged and returns the controller's raw text — no float resolution, no User Units scaling.
  • MotionController.GetUserUnits(axis, keyword) — the unit name and factor a parameter reads and writes in on an axis, as a UserUnitAxis: the controller's values when Global User Units are enabled on the axis and the parameter is in one of its groups, otherwise an empty name and factor 1 (controller counts) — which is also the answer for an unknown keyword, an axis with units off, and a controller opted out through UseUserUnits. Never null, so Factor == 1 is the check for counts.
  • ISingleAxis.GetHomingDefStep(int step) and the HomingDefStep view it returns. HomingDef is one flat array of 20 steps of 10 slots whose slot roles depend on the step type in slot 1; the view names them — Type plus Param1…Param9, or the [1..9] indexer — so a caller no longer computes (step - 1) * 10 + slot by hand. HomingDef itself stays as the escape hatch for a step type the SDK does not name.
  • ParameterTypeConverter.ToInt32Register(double) — the controller's 32-bit two's-complement view of a value slot, for bit arithmetic on a port or status word now that the slot is a double. A plain (int) cast of a value above 2,147,483,647 — a DOutPort reported as an unsigned full-width word, say — is unspecified in C# and differs between .NET Framework and .NET 8/10; this wraps modulo 2^32, the bits the controller holds, on every framework. GroupDOutSetBit / ClearBit / ToggleBit, their Ci twins and the CNC completion poll fold through it.

Changed

  • The digital-output bit API states its index conventions. DOutSetBit / DOutClearBit / DOutToggleBit and the group and Central-I twins take the 1-based output number the controller's DOutPortSBit[n] takes; GetDOutPort / GetDInPort read a 0-based register bit, so output n is GetDOutPort(n - 1). Documentation only — no member changes behavior.
  • A write carries the caller's value whole to AAComm, which divides it by the User Units factor and rounds a firmware-integral parameter in controller counts. AAMotion used to narrow the display value first — to an int for a 32-bit parameter, to a long for a 64-bit one — which quantised a scaled axis to whole units: at 0.01 mm/count, axis.AbsTrgt = 12.345 reached the controller as 1200 counts instead of 1234. The two setter families also disagreed, so the same fraction was rounded on AbsTrgt and passed through raw on MaxVBus. Both now behave the same, and the rounding — to-even, on the counts — happens once, in AAComm's transmit seam. An integral 64-bit assignment still crosses with its exact digits, so a .par round-trip above 2^53 is unaffected.
  • The commands that render a value straight into their command text — ISingleAxis.SetPosition, SetEventTableValue, and the VectorLinear / VectorArc target and arc-centre writes — render it with the invariant wire format. They were culture-safe only for as long as those arguments were int.
  • Connecting no longer retries with forced About data; a failed connect fails on the first attempt (AAComm now refreshes a stale About cache itself). The same applies to the auto-reconnect path.

Fixed

  • A .par for another product is refused. DownloadParameters read no product from the file, so a .par taken from an AGD200 wrote onto an AGM800. When the file's // Controller type: header names a product other than the connected controller's, nothing is sent and the completion callback gets an error naming both products. The controller's legacy names (AG300-CTL02 for an AGD301, say) and the name UploadParameters writes for it are accepted; a header name no product answers to is refused the same way, and a file with no header downloads as before.
  • An integer keyword takes the rounded value on every transport. DownloadParameters sent an integer keyword's 123.5 as 124 over Ethernet, where AAComm's binary form rounds it, and as text over RS-232, where firmware refuses a value with a fraction or an exponent. It now rewrites the value of an integer keyword that is not already an integer literal to the integer firmware's own rounding gives (half away from zero: 124.5 is 125, -2.5 is -3) before it is checked or sent, so both carry the same integer for a value within the keyword's range. A float keyword's value is sent as written.
  • Every row a parameter download skips is named. A line the reader dropped (no =, under 3 characters, an axis outside A-L) was neither counted nor named, and a row the connected controller does not support and the ProductSN row were counted in SkippedCount without a Rejections entry. Each now has an entry with a PC Suite: ERR reason and counts in TotalCount; they are the first SkippedCount entries of Rejections. aacomm.cli params-download prints every one as skipped <row> -> <reason>.
  • Two FastReadCache refreshes can no longer run at once. The cache read its in-progress flag and set it a line later, so a RefreshNow() and the refresh timer arriving together both got through: two bulk reads went out on one connection, RefreshStarted fired twice for the one refresh a caller expected, and both completions re-armed the timer. The flag is now claimed in a single atomic step and freed on every way out of the refresh — including a RefreshStarted handler that throws, which used to leave the cache refusing every later refresh for the life of the process; such a throw is now reported on RefreshError like any other refresh failure. IsRefreshing is unchanged.
  • A FastReadCache whose RefreshCompleted handler throws keeps refreshing. The refresh raised RefreshCompleted before it scheduled the next one, and the refresh timer fires once per schedule, so a throwing handler stopped the periodic refresh for good: IsRefreshing read false, no error was raised, and readers kept getting the last values read. The next refresh is now scheduled first, and the handler's exception is reported on RefreshError, as a RefreshStarted handler's already was; RefreshNow() no longer throws it at the caller. Because the next refresh is already scheduled, it can start before a slow RefreshCompleted handler returns.
  • A throwing UploadParameters / DownloadParameters completion callback is called once. A callback that threw was called again with its own exception ("Exception in upload completion: …", "Upload failed: …", "Failed reading .par file: …", "Exception in download completion: …"), and an upload then counted its rows a second time and wrote the .par again, reporting doubled SuccessCount / ErrorCount. Each upload and download now calls the callback exactly once, whatever the callback does; its exception is not reported back to it.
  • A parameter upload or download that stops partway reports the failure and saves nothing. When the bulk run ended with an error after some replies had arrived (a throwing onProgress callback is one way), UploadParameters reported success and wrote the .par with empty values for every row it never read, and DownloadParameters counted the unsent rows as written and, with calcAndSave, saved the partial transfer to flash. Both now report the run's error: the upload writes no file, and the download counts only the rows that were answered, never sends ASave / CalcFilters for a partial transfer, still resyncs User Units, and reports no final 100% progress. A throwing onProgress on the download's final 100% no longer leaves onDone uncalled: the exception's message is appended to the reported error.
  • A parameter download that fails before its first message reaches the wire now reports the failure. The bulk runner behind DownloadParameters reads the connection's bulk size and connected state at the start of a transfer, and an exception from either escaped onto the background task driving the download — OnDone never fired and the call went quiet. Both reads are now covered and the failure arrives on OnDone with the exception's message, and a download whose transfer sent nothing no longer reports every parameter as written: the completion treated an empty reply set as a clean sweep, counted the whole file as successful and then committed it to flash with ASave. It now reports the failure and commits nothing, as the upload path already did. The runner's one-at-a-time start guard is claimed atomically as well, so two starts on one runner cannot both proceed.
  • MotionController.EnsureAACommServerRunning(serverExePath) starts the server through the same contract every other entry point uses, and answers on the port. It span its own process and judged “running” by the process list, so it missed --headless, the hidden window, the zone-check opt-out that keeps a Mark-of-the-Web executable from stopping on a modal security prompt, the lock that serialises two spawns, and the readiness poll that notices a child which died on startup; it also answered true for a process that had not bound its port yet, and never recognised a running AACommServerNative. It now delegates to CommAPI.StartAACommServer and reports whether a server is serving. A null or empty path starts nothing and reports whether one is already serving — delegating it would auto-locate and could start the machine-wide install the caller never named. The signature and the bool contract are unchanged.
  • TryReconnect no longer tears down a healthy link on a single flaky ping. Its "already connected and reachable" check sent one ICMP probe; a switch or firewall that blocks or rate-limits ICMP could make a live link look dead and trigger an unnecessary disconnect/reconnect cycle. Two independent probes are now sent, and either succeeding is treated as reachable.
  • A parameter download no longer moves the controller off its own network address. DownloadParameters wrote EthernetIP and EthernetMAC like any other row, so a .par taken from one bench and written to another re-addressed the target controller and the operator lost it mid-restore. Both are the controller's identity rather than part of its configuration, so the download now withholds them whatever the file carries — the same pair PCSuite's Parameters Download window excludes. A withheld row counts as skipped, not as a failure, and is listed in ParamHandlingSummary.Rejections with a client-side reply naming the exclusion, so nothing is dropped silently. EthernetPort is not identity and is still written.
  • A parameter file round-trips: what a controller uploads, it accepts back. A .par holds raw controller values, but the transfer applied Global User Units scaling on the way out and could not write the User Units configuration rows at all — *UnitFct / *UnitUnt[] are refused over raw messaging, which is 1068 of the 1106 rejections a full AGM800 download still reported. Both legs now carry raw values and are permitted to write that configuration, and the scaling cache is resynced afterwards, which is the contract for that bypass. Measured on an AGM800: a self-produced .par downloads back with 36 rejections where it had 4110, and those 36 are a firmware defect fixed in FW 5.2.1.16-3 rather than anything the SDK does.
  • Keywords the firmware refuses to carry inside a bulk packet (ERR 184 — UserUnitsEn is one of a set) are re-sent one at a time after the batched pass, instead of being reported as rejections. The retry keys on the reply, so it covers every keyword in that set rather than a list that goes stale.
  • ProductSN is no longer attempted by a download. It is writable only with elevated permission and answers ERR 328 otherwise, so it counts as skipped — the same class the connected controller does not support — rather than as a failure.
  • A parameter download no longer abandons the rest of a bulk packet at the first refused row. DownloadParameters batches up to 50 assignments per packet, and a plain bulk packet stops executing at its first error — so one row the controller refused silently discarded up to 49 writes behind it, and the unanswered tail was counted as a rejection per slot. A full .par download reported thousands of errors where only a few rows were genuinely refused, and the parameters that never reached the controller were not reported at all. Both legs now use the continue-on-error bulk packet, so every assignment is attempted and every reply is the controller's own. Firmware that does not implement that packet kind (it answers ERR 160) is detected on the first batch and the transfer falls back to the previous behaviour.
  • A .par saved by UploadParameters names the controller by product name. The header's // Controller type: line carried a hex product id (0xA for an AGM800), which PCSuite's Parameters Download cannot resolve, so it skipped the Central-i master / standalone mismatch warning for every file AAMotion wrote. The line now carries the product name PCSuite writes there for every controller it supports, so that warning applies to AAMotion's files as it does to PCSuite's own.
  • A parameter download with calcAndSave — the default — commits to flash. DownloadParameters is meant to finish with ASave and a CalcFilters per axis, and it sent neither: the commit ran on the transfer's reply callback, where AAComm refuses a synchronous send, and looked for CalcFilters under an axis-prefixed name the message table never carries. The completion callback still reported success, so every parameter the download wrote was gone after the next power cycle. The commit now runs off that callback, and a failure in it arrives in onDone's error argument instead of being dropped.
  • The DataLogger CSV export (GenerateCsvFile) is readable on a comma-decimal host: every time, value and statistics field is written with a dot decimal separator. It rendered under the host culture, so on de-DE, fr-FR and similar locales each fractional value became two columns and no reader could recover the rows.
  • ConfigureSlowLogger accepts the documented forty parameters and clears the fortieth slot. LoggerParams is a 1-based table, and the configure walked it as if it were 0-based: the fortieth parameter was refused as a forty-first, and a stale code left in slot 40 by an earlier configuration kept being logged.
  • ConfigureSlowLogger refuses an array parameter given without its index, and an index given on a scalar, with an ArgumentException and one ErrorOccurred — the fast recorder's verdicts, on all three specification shapes (string, CAN code, SlowLoggerParameter). An un-indexed array used to be accepted and logged nothing.
  • Every vector entry point — VectorLinear (both overloads), VectorArc and their AAMotionAPI equivalents — refuses a call whose initiating axis is not a member of the move, or not its lowest member, before anything is written: the call returns false and raises ErrorOccurred. The controller reads the profile from the axis that receives Begin and rejects any other shape (errors 192 and 266), so b.VectorArc(C, H, …) never ran; it used to leave the profile and the members' targets on the controller before the controller refused Begin.
  • VectorLinear (both overloads) and VectorArc refuse a move that names an axis past H or fewer than two members, before anything is written. The controller's VecMemberAxes reaches H and a vector move needs two members (error 196), so such a call never ran; the refusal is now local and reported.
  • VectorLinear (mask overload) refuses a position array shorter than the mask's axis count before the first write. It used to notice the missing position only when its walk reached that axis, after the profile and the earlier axes — motors on — were already on the controller.
  • The four-axis Central-I LinearAbsolute(aPos, bPos, cPos, dPos, velCruise, velEnd) now sends the D position. It named D in the segment header but composed only three positions, so the controller refused every call with ERR 181 and no such move ever ran. ArcXT / ArcYT / ArcZT had the same slip - nine fields where the arc row takes ten - and are corrected with it.
  • GenDataWait and UserParamWait now push the wait-array segment type (12). They carried the write type (9) with a wait body, which the controller refuses with ERR 181 before pushing anything; the field order was already the wait segment's, so the arguments are unchanged.
  • Position-class values no longer read back as 0 on a controller with User Units enabled — the 64-bit parameters Pos and PosRef, which are re-read from the controller by their own path, included. A scaled reply for an integral parameter is a decimal number (2468 counts at a 0.01 mm/count factor is 24.68), and the client parsed every reply as an integer — 24.68 failed to parse and the value became 0, so a.Pos read zero and any move computed from it went to the wrong place. Replies are now read as decimal numbers and carried at full precision, so a User-Units axis reports its real position. An integration that wants the raw controller counts instead can opt out per controller with the new MotionController.UseUserUnits: set it to false at any point, before or after Connect, and the controller keeps it across connects and reconnects. The opt-out is instance-wide (two controllers sharing one CommAPI share it) and does not unlock raw *UnitFct / *UnitUnt writes. ISingleAxis.SetPosition sits outside the scaling: it writes a parameter the controller declares unscaled, so its argument is always in controller counts — SetPosition(axis.Pos) is not a round trip on a scaled axis; divide the display value by the axis position factor, or read the position with UseUserUnits = false and pass that count back.
  • Float- and double-typed parameters no longer come back as a re-interpreted bit pattern. ForceGain, ForceKi, ForceKd and their siblings return the value the controller reports, and writing one sends that value rather than the integer its bits happen to spell. The write is spelled as the shortest invariant text that reads back as the same value, so it no longer loses digits on .NET Framework, and the same value is spelled identically on every framework.
  • Multi-axis AAMotionAPI.MoveAbs / MoveRel no longer command Speed = 0 — a move that silently never runs — when a requested component speed exceeds an axis's MaxVel. The profile clamp divided two int operands, so the quotient truncated to zero and every axis was scaled to a standstill; where it did not truncate to zero it still dropped the fraction and under-drove the whole move. The profile is now computed in double precision, with one shared scaling factor equal to the tightest axis constraint, preserving the equal-duration (PTP) relation between the axes. The same clamp turned the shared factor negative when the constraining axis travelled backwards, commanding a negative speed on the axes travelling forwards; the factor is now always a magnitude. Accel / Decel were scaled by the same clamp and are corrected with it.
  • Multi-axis AAMotionAPI.MoveAbs / MoveRel no longer command a negative Speed, Accel and Decel on an axis travelling backwards. Each axis was written its signed component of the profile, so the controller rejected that axis's Accel/Decel with ERR 14 ("Assigned value out of the range defined for this keyword" — the keyword minimum is 100.0) and ran the move on whatever acceleration profile the axis happened to hold; the negative Speed, which the controller does accept, persisted as an axis parameter and then drove the next move on that axis that supplied no speed of its own — MoveAbs / MoveRel write Speed only when the argument is given, while Jog always writes its own. Every axis is now commanded the magnitude of its component — direction comes from the target position, as before.
  • Multi-axis AAMotionAPI.MoveRel no longer overwrites the caller's distance array with the absolute positions it computes. The array passed in is left untouched, so a caller that reuses it for a second move is no longer silently moving to the wrong place.
  • MotionController.ReadGenDataF / ReadGenDataD and ReadUserParamF / ReadUserParamD now read the controller's reply in the invariant culture. The single-argument float.Parse / double.Parse they used applied the process culture with its default styles — which accept a group separator, and on a comma-decimal locale that separator is . — so a reply of 1.5 came back as 15, a tenfold-wrong reading with no error. They now parse invariantly, matching the text AAComm emits.
  • MotionController.WriteGenDataF / WriteGenDataD and WriteUserParam / WriteUserParamF / WriteUserParamD now render the value they send in the invariant culture. They formatted it with the process culture, so on a comma-decimal locale WriteGenDataF(1, 0.25f) sent AGenDataF[1] = 0,25 — and because the controller reads an assignment invariantly and accepts a group separator, the slot silently ended up holding 25. The command was never rejected, so the wrong value went unnoticed; the slot now receives the value that was passed in, on every locale.
  • Multi-axis AAMotionAPI.MoveRel no longer truncates the axis's position reference when it converts a relative move to absolute. It accumulated aryTrg_Pos[i] + (int)aryAxes[i].PosRef into an int array, so a fractional PosRef — what a User-Units axis reports — was dropped before the target was computed and the move landed short by up to one unit. The conversion is now done in double.
  • Homing speeds, accelerations, offsets and thresholds are no longer truncated on their way into HomingDef. Home, HomeToHardStop, HomeToIndex, HomeToLimitThenIndex and HomeToHardStopThenIndex cast every profile argument to int before writing its slot; they now carry it whole.
  • ISingleAxis.Home(string filePath) reads the homing-definition file's values as decimal numbers. It parsed each HomingDef[i] = value line with an integer parse, so a fractional speed or acceleration in the file was skipped silently and the slot kept whatever it already held.
  • The ECAM table generators apply the requested range before they push the points, so a generated table lands at the startIndex the caller asked for. ECAM_CreateLinearTable and ECAM_CreateSinusoidalTable called ECAM_SetTableData first, and that method takes both its destination and its length cap from the range in force at the time — so on a table that had never been ranged the push failed with an IndexOutOfRangeException before a single point reached the controller, and on a table reused from an earlier profile the points landed at the previous start index, truncated to the previous length, under a range the data never filled.
  • ECAM_SetTableData writes the last point of a range that ends at the final GenData slot. Its loop was bounded by the array's element count on a 1-based array, so the highest slot was silently skipped — a table configured as 1022..1024 on a controller whose GenData ends at 1024 arrived one point short. A range that starts below GenData's first index is now rejected without writing anything, instead of throwing IndexOutOfRangeException on its first slot.
  • ECAM_CreateSinusoidalTable closes the sweep at any offset. The closing sample was computed from sin(2π), a tiny negative residue rather than zero, so with a non-zero offset the cast landed the last point one below the first — amplitude 1000, offset 500 over 5 points ended on 499 instead of 500 — and the cam profile stepped at the seam on every cycle. The closing sample is now the opening one exactly.
  • OpticalCouplingLightFinding.GetSingleAxisScanResult now returns the per-channel peak indices. SingleAxisScanResult.ChannelPeakPowerIndices was never filled, so it came back as null — a NullReferenceException for a caller reading it alongside the peak powers and positions it sits with. It now carries channels 1-4, from the same slots the Channel1..4PeakPowerIndex properties read.
  • The per-axis IO register reads keep the controller's bit pattern when bit 31 is set. IIOModule.DInPort, DInPortHigh, DInLog, DInLogHigh, DInFilt, DOutLog, DOutType, DOutPort and BiDirConfig narrowed the value slot with a plain cast, so a register reported as an unsigned full-width word — 4294967295, every output asserted — read back as -2147483648 on .NET Framework and .NET 8 and as 2147483647 on .NET 10, none of which is the register the controller holds. They now take the same 32-bit two's-complement view the group DOut bit helpers use, which is identical on every framework.
  • The analog outputs can be read and written again, one element per output. IIOModule.AOutGain, AOutShifts, AOutOffset, AOutMode and AOutPort were not sized from the controller's parameter table, so every element access — io.AOutGain[i] for any output, read or written, or MotionController.GetValue on one of them — threw IndexOutOfRangeException before anything was sent. AOutGain[i] reads and writes the i-th analog output, from 1 to the count the controller reports; read an output by its index, because on a controller that lists that parameter with one or more outputs the member's own .Value raises InvalidOperationException, as on every array member. A drive without analog outputs (AGD101-EC, AGD156-EC) lists these parameters with no elements; it connects as before, and reading or writing an output there raises IndexOutOfRangeException with nothing sent.
  • An array parameter the controller lists with a single element can be read and written. A parameter at max index 1 was sized as a scalar, so its element 1 threw (NullReferenceException, or InvalidOperationException through IIOModule.AInGain) and a write to it returned normally and sent nothing, losing the value. It is an array of one element, read and written as element 1 like any other array, and GetValue(axis, keyword, 1) reads it. The AGD200 (3.0.33) and AGD155-EC (1.5.0.20) tables list ISingleAxis.PosFiltOn and MapTableB to MapTableE this way, and the AGD101-EC and AGD156-EC can list the analog inputs so (IIOModule.AInGain, AInMode, AInFilt, AInOffSet, AInDB).
  • An element of a scalar parameter is refused instead of lost. On an AAValueArray<double> member whose parameter the connected controller lists as a scalar, such as ISingleAxis.GantryPosGain on an AGD200, its one index [0] and enumerating the member raise InvalidOperationException before anything is sent, naming the parameter (GantryPosGain is a scalar parameter; read it through Value.). A read of [0] used to raise NullReferenceException, and a write returned normally and sent nothing, and enumerating the member then handed the value back as though the controller held it. Read and write such a parameter through .Value.
  • An element of a 64-bit array parameter is read once. MotionController.GetValue(axis, keyword, index), and with it aamotion_get_value, composed the element read through the parameter slot and then again through the 64-bit path, so a successful read of an element of Vel, MapStartPos, ModeSwitchPos, ECAMMasterIni or CNCBVel — the 64-bit arrays the client sizes from the AGM800's table — cost two round trips instead of one. ISingleAxis.Vel[i] reads through that same path and doubled with it; the other four are read once through their own members. The value is unchanged, and so are the refusals: an index the parameter does not have is still refused before anything is sent.
  • MotionController.GetValue(axis, keyword, index) reads an element of the arrays the AGM800 lists for GantryPosGain, GantryAccFFW, GantryVelGain, GantryVelKi and PosKi (to 5), GenDataF (to 100), UserParamF and UserParamD (to 20). They were sized as scalars, so an element read raised IndexOutOfRangeException before anything was sent, while TryGetValue read it.
  • The CNC segment builders no longer corrupt a segment on a comma-decimal locale. IGroupAxis.SetMotionProfile's S-factor, and the axis index the WriteUserParam / UserParamWait / MultiWriteUserParam / MultiWriteUserParamWait builders derive from the AxisRef they are given, were rendered under the process culture; on de-DE, fr-FR or ru-RU the decimal comma became an extra separator inside the comma-delimited segment and shifted every field behind it into the wrong slot. Both now render in the invariant wire format, so a segment is spelled identically on every host.
  • Reading a controller command as though it were a parameter no longer runs it. ProgRun, ProgHalt, ProgHaltAll, ProgReset, StopECAM and LoggerUpload name functions the controller performs on receipt, not storage slots — so composing a read of one is the command: ParameterTypeConverter.GetSmartValue(axis, …, eKeyword.ProgRun) started the user program and reported its reply as a value, GetDoubleArray(axis, …, "ProgHalt", 1) sent AProgHalt[1] and halted user-program thread 1, and MotionController.RegisterCacheKeywords(axis, eKeyword.ProgRun) put the command in the fast-read cache's refresh bulk, re-sending it every millisecond for as long as the cache ran. All six are now refused with an ArgumentException on GetSmartValue, GetArray and GetDoubleArray — which covers the protected Node.GetSmartValue a derived controller inherits — and on RegisterCacheKeywords / FastReadCache.RegisterArrayElements, before anything reaches the wire; a registration naming one is refused whole, so no keyword beside it is registered. MotionController.GetValue already refused them. This is not a rule that a command never answers with data: many controller commands take a parameter and reply with a value, and MotionController.LoggerUpload() — which retrieves the slow-logger data by sending ALoggerUpload itself — is unaffected.
  • Expanding a MotionController, an axis or a parameter array in the debugger no longer hangs the evaluation. Every value property on them is a controller round trip, so a debugger expanding one sent a read per member while it held frozen the thread that delivers the reply, and hung on the first. The debugger now shows their identity only — the kind and axis, a controller's last known address, an array's keyword and index range — and sends nothing to the controller.

Removed

  • The MotionController.ProgHaltAll property. It exposed the ProgHaltAll command as a readable value slot, so evaluating it — in code, or in a debugger watch expanding the controller — sent AProgHaltAll and halted every user program. Halt them deliberately with ISingleAxis.ProgHaltAll() or AAMotionAPI.ProgHaltAll(controller, axis), which are unchanged.

[11.0.0] - 2026-08-26

⚠️ Breaking

  • AAMotion.dll is now strong-named (PublicKeyToken=043a10799a9b732b, the same key as Agito.AAComm 14.x), so strong-named consumers can reference it. .NET Framework consumers must recompile against 11.0.0 — a binary built against a 10.x AAMotion, PublicKeyToken=null reference does not bind to the signed assembly (see the Agito.AAComm 14.0.0 changelog for the details; the same identity change and AssemblyVersion pinning, here 11.0.0.0, apply). .NET 8/10 consumers see no difference.
  • Requires Agito.AAComm 14.x (see its changelog for the breaking communication-layer changes — long-operation events, binary IPC v3, protocol negotiation).
  • AAMotionAPI.WriteUserParam(controller, AxisRef, index, value) is renamed GroupWriteUserParam — it issues the CNC group-buffered write (command 9), not a direct register write, and the old name collided with the new direct WriteUserParam(controller, index, value) (see Added). The IGroupAxis.WriteUserParam member keeps its name (the receiver disambiguates); the Java binding follows the facade rename.
  • AAMotionAPI.OpendAACommServer() is renamed OpenAACommServer() (typo fix).
  • AAMotionAPI.ForceCurr_SetSlowApproache is renamed ForceCurr_SetSlowApproach (typo fix) — also on ISingleAxis, the native ABI export, and the Java binding.
  • MotionController.ErrorOccurred and CommAPIInterface.ErrorOccurred are now event members (were public delegate fields). +=/-= subscriptions recompile as-is; direct assignment or external invocation no longer compiles.

Fixed

  • Connect no longer returns false on a busy machine while the server is running. It gates on CommAPI.IsAACommServerRunning, whose loopback probe used to depend on ThreadPool scheduling and so reported a listening server as absent under CPU pressure — reported only through a Debug.WriteLine, making it look like a silent connect failure. Fixed in AAComm; see its changelog.
  • AAMotionAPI.AutoCorner: the cornerAxes argument was incorrectly passed to GetGroup(), whose dictionary keys are only AxisRef.A (or AxisRef.B on AGM800). Every call path was broken — GetGroup(axis) with axis = AxisRef.A resolved to a 1-axis group (rejected by the controller as CNC_FIFO_WRONG_NUM_OF_INVOLVED_AXES), and axis = AxisRef.A | AxisRef.B caused a KeyNotFoundException on the unguarded indexer. Now resolves the group via GetGroup(AxisRef.A) like all other CNC facade methods, and forwards the full mask to the wire segment. Source- and binary-compatible; no signature or ABI change.

Added

  • Native aamotion.* C-ABI SDK (Agito.AAMotion.Native): a NativeAOT shared library (aamotion.dll / libaamotion.so / libaamotion.dylib + aamotion.h) exposing the full flat motion surface (199 functions, each aamotion_ + the managed name in snake_case, constants AAMOTION_*) over the AAMotion client with no .NET runtime on the target, for C / C++ / Python (ctypes) consumers. Packaged as the Agito.AAMotion.Native NuGet (runtimes/<rid>/native/, for .NET P/Invoke) and an agito-aamotion-native-sdk-*.zip C/C++ archive, version-locked to Agito.AAMotion and published together on the public Software/AAMotion-Native release line at https://github.com/akribis-agito/Agito-Releases/releases — not on nuget.org, because the C ABI is exact-match gated pre-1.0. win-x64 (HW-proven on AGM800) and linux-x64 ship; osx pends its build runner.

  • AAMotionAPI.UploadParameters() / DownloadParameters(): New static methods on AAMotionAPI that upload controller parameters to a .par file or download parameters from a .par file to the controller. Follows the PCSuite "Parameters Upload/Download" convention: upload queries all supported parameters and writes them with a header (product type, firmware/FPGA version, date, checksum) to an ASCII .par file; download reads the .par file, validates parameter compatibility, then writes each as a SET command. Ethernet connections use Bulk messages (up to 50 per packet); other channels send one by one. After a successful download, optionally sends ASave plus per-axis CalcFilters to persist parameters to flash.

  • ISingleAxis.MoveAbsRepetitiveCycle() / AAMotionAPI.MoveAbsRepetitiveCycle(): New method on ISingleAxis / SingleAxis and a corresponding static wrapper on AAMotionAPI that moves the axis to an absolute position repeatedly for a finite number of cycles with a dwell time at each end point. rptCycles = 0 requests infinite cycles (controller default). The method sets RptMode = 0, RptCycles = {rptCycles}, RelTrgt = 0, AbsTrgt = {position}, RptWait = {msDwellTime}, MotionMode = 2, then issues Begin.

  • The package now also ships a net10.0 target (alongside net48 and net8.0) — matching Agito.AAComm 14.x; .NET 10 is the current LTS runtime.

  • Two-speed point-to-point moves: ISingleAxis/AAMotionAPI MoveAbsTwoSpeed(AndWait) / MoveRelTwoSpeed(AndWait) — the move runs at speed1 and switches to speed2 when crossing speedChangePosition (Abs) / after speedChangeDistance (Rel), for slow-approach flows (bonding, dispensing). The optional speedChgDir (0 = forward, 1 = reverse crossing) defaults to 0 on Abs and to the distance sign on Rel; the native ABI takes it as a plain int where a negative value selects that default.

  • Direct GenData/UserParam register I/O in float and double widths: MotionController/AAMotionAPI ReadGenDataF/ReadGenDataD, WriteGenDataF/WriteGenDataD, ReadUserParam/ReadUserParamF/ReadUserParamD, WriteUserParam/WriteUserParamF/WriteUserParamD (writes A<reg>[i]=v directly, complementing the int-typed ReadGenData/WriteGenData), backed by the new eKeyword.UserParam/UserParamF/UserParamD mappings.

  • Optical coupling light-finding API: new OpticalCouplingLightFinding class in AAMotion/Shared/OpticalCouplingLightFinding.cs providing a 2-step workflow (parameter configuration, then start) for optical alignment processes.

    • Supports spiral scan (ScanType.Spiral = 1) and single-axis scan (ScanType.SingleAxis = 5).
    • Exposes configuration properties mapped to controller AGenData registers (e.g. ScanCode, CouplingAxis, SpiralPitch, MaxScanSpeed, OptimalCoeff).
    • Provides typed result models: SpiralScanResult and SingleAxisScanResult with coupling result, error code, sampled positions, and channel power data.
    • Includes helper methods to start scans, apply configurations, read sampled arrays, and batch-sync AGenData via SyncAllGenData.

Fixed

  • AAMotionAPI.OpenAACommServer(string) and CommAPIInterface.OpenAACommServer(string) now spawn the server through the AAComm spawn contract (headless + redirected stdin, correct working directory, socket-ready polling with a child-death probe) instead of a bare Process.Start + fixed 1.5 s sleep — a slow cold start, a lost bind race, or an interactive native host no longer yields a false "started" (or a TUI taking over the consumer's terminal). AAMotionAPI.OpenAACommServer() no longer latches "started" after a FAILED start: the first failure (missing sidecar, blocked port) previously made every later call a silent no-op even after the environment was fixed.
  • The *AndWait motion waits (MoveAbsAndWait, MoveRelAndWait, the two-speed and repetitive variants) now poll the controller's in-target status live, never through the FastReadCache. With FastReadEnabled, a cached pre-move InTargetStat=4 (the axis parked on its previous target) — or a value stranded by a link failure, which never invalidates cache entries — could satisfy the wait instantly, reporting "target reached" for a move that never happened.
  • MotionController.TryReconnect no longer trusts the raw socket alone: a logically-connected link is verified by ping (where the address is a real IP) before the call short-circuits to success, so a pulled cable / network partition now triggers a real disconnect-and-reconnect instead of "reconnected" with a dead link — this also makes IsConnected's auto-reconnect path honest. With no requested address and no last known address the call now fails instead of silently targeting a hardcoded default IP (a reachable but unintended controller could be connected). A successful reconnect to an explicit new address now updates the last known address, so reachability probes and later reconnects track the live link, not the device the controller left behind.
  • SendBulkCommandString no longer reports success when an error rides in a non-first segment of the bulk reply (e.g. OK>OK>ERR 4010) — the whole-reply error flag misses embedded segments, so classification is now per-segment against the full reply taxonomy (ERR / BOOT ERR / PC Suite: ERR).
  • Controller replies carrying int/long values are now parsed culture-invariantly, matching the float/double path. On a machine whose culture uses a non-ASCII negative sign (several ICU cultures on modern .NET), a negative reply like -123 failed to parse and read as 0; parameter values written to the controller now also format invariantly on every path.
  • MotionController.IsConnected no longer reports false for healthy non-IP connections: the ICMP reachability probe now runs only when the controller was connected via a real IP address — a Simulator / serial / CAN connection reports the logical connection state (previously the unparseable address failed the ping and the property was permanently false), and when no address is known the property no longer pings a hardcoded default IP (another device's reachability was reported as this controller's).
  • Bulk parameter upload/download keeps request↔reply alignment: an empty reply segment no longer shifts every later reply onto the previous command's slot (misattributed values in the .par file / on the controller), and a short bulk reply pads the unanswered tail with the no-reply error filler instead of re-sending the boundary commands (a re-send could execute a parameter write twice).
  • BulkCommandParser(index, response) now honors its documented 1-based contract: index 1 returns the first segment (previously the second), the last index returns the last segment (previously threw IndexOutOfRangeException), and out-of-range indexes return null.
  • Tearing down one MotionController no longer harms the others in the process. A garbage-collected (undisposed) controller's finalizer called Shutdown() — which could take down the shared AACommServer — and its disconnect cleared the process-wide parameter-metadata cache under live sibling controllers, silently degrading their float parameter reads/writes to int handling until their next connect. The finalizer now only disconnects its own controller, Dispose() suppresses the finalizer, disconnect no longer clears the shared cache (each connect atomically rebuilds it), and only an explicit Shutdown() touches the server. The native aamotion SDK's Release/Disconnect likewise no longer shut the server down — the explicit Shutdown export is the only path that may.
  • Enabling the fast-read cache no longer risks tearing down the connection when any call slower than ~200 ms is in flight — the background cache refresh used a hardcoded 100 ms timeout, tighter than any other command's wire budget; it now uses the default message timeout (a slow reply just delays the next refresh while readers serve the cached value).
  • FastReadCache reads now honor validity: a registered-but-never-refreshed entry is a cache miss (falls back to a live read) instead of a fabricated 0, and a value whose refresh failed is not served stale. Disconnect invalidates all entries (no unbounded-stale values while no refresh can run) and the background refresh no-ops while disconnected instead of spamming an error event per interval — throttled to an idle poll (500 ms) so a 1 ms-interval cache no longer burns ~1000 no-op tasks per second while disconnected; it resumes the configured cadence by itself after a reconnect.
  • FastReadCache.Dispose() no longer throws ObjectDisposedException on any cache that was ever used (its own disposed-guard fired mid-dispose) — on .NET Framework a garbage-collected undisposed cache could take the process down from the finalizer thread.
  • UploadParameters() / DownloadParameters() summaries no longer count API-synthesized error replies (PC Suite: ERR …, BOOT … ERR, e.g. from a connection lost mid-transfer) as successes — error classification now matches the full AAComm reply taxonomy instead of only controller ERR … replies.

Removed

  • The C++/CLI AAMotionCPP (mixed-mode) and AGAAMotion (native shim) projects have been retired. Non-.NET (C/C++) consumers move to the new C-ABI SDK — aamotion.h + aamotion.dll/libaamotion.so/libaamotion.dylib (project AAMotionNative). The managed Agito.AAMotion API and the Java binding are unaffected.

[10.0.0] - 2026-05-25

Added

  • EnsureAACommServerRunning(): New method on IMotionController / MotionController that ensures the AACommServer is running and ready to accept connections. Launches the server if not already running, with proper path resolution.

Deprecated

  • AAMotionConfig.UseAsyncCommTransport: in AAComm 13.0.0 both flag values produce equivalent observable transport behavior for callers using a single path serially (false → CommAPI.SendReceive over the sync flow; true → CommAPI.Send + an internal wait-handle over the async flow — see issue #32 history). The flag is therefore a no-op for typical usage and will be removed in a future release.

Changed

  • DataLogger StartDataRecording: AGD155 and AGD155-EC now use ARecStart[1] command syntax (firmware v2.3+); other controllers continue to use ARecStart without index.
  • Refactored DataLogger and SlowLogger to route all communication through CommAPIInterface.SendAndRecieve() instead of directly calling CommAPI.SendReceive(). This ensures consistent behavior when AAMotionConfig.UseAsyncCommTransport is enabled (async) or disabled (sync).

[9.1.1] - 2026-04-17

Changed

  • Packaging: picks up AAComm 12.1.1 sidecar change. AACommServer sidecar is now selected by consumer OS rather than TFM: Windows consumers (net48 or net8.0) get the WPF AACommServer.exe; non-Windows net8.0 consumers get AACommServerNetConsole. AAMotion consumers inherit this transitively — no code or reference changes required.

[9.1.0] - 2026-04-15

Fixed

  • ScanController / ScanControllerDetailed: Replaced unreliable TCP probe (TcpPortReachable) with ARP-based discovery via ARPTools.GetSubnetActiveIPs. This uses the same multi-NIC ARP scan as PCSuite's Discover Connections, ensuring controllers on all Ethernet interfaces are reliably found.
    • Fixes: random discovery failures with multiple NICs on the same subnet (OS routing picked one interface only)
    • Fixes: NullReferenceException in TcpClient.EndConnect when scanning IPs with no controller (disposed socket from dangling ConnectAsync task)
    • Removed TcpPortReachable private method (no longer needed)

[9.0.0] - 2026-04-03

Changed

  • Decoupled nuget from direct AAComm dependency: AAComm is now resolved via nuget

[8.4.1] - 2026-03-27

Added

  • Added LockSrcZeroSupported() method to ISingleAxis / SingleAxis, exposing Identity[62] bit 0x10 (ControllerIdentityFeature_02.LockSrcZero_Supported). When lockSource = 0 is passed to the three multi-step homing methods on an unsupported controller, NotSupportedException is thrown.
  • Added ControllerIdentityFeature_02.IDENTITY_NEW_FEATURES_2_LOCKSRC_ZERO_SUPPORTED = 0x00000010 constant.
  • Added three extended multi-step homing methods to ISingleAxis / SingleAxis:
    • HomeToHardStopThenIndex — motor on → arm Lock → hard stop on high following error → jog to hardware index → move to Locked position → disarm Lock → wait → set position → end. Accel/emer decel auto-computed (×10 and ×100 of velocity); all timeouts fixed at 15000 ms. Populates HomingDef as PCSuite step types 8, 14, 10, 15, 16, 14, 7, 6, 0.
    • HomeToIndex — motor on → arm Lock → jog to hardware index → move to Locked position → disarm Lock → wait → set position → end. Accel/emer decel auto-computed; all timeouts fixed at 15000 ms. Populates HomingDef as PCSuite step types 8, 14, 15, 16, 14, 7, 6, 0.
    • HomeToLimitThenIndex — motor on → arm Lock → jog to limit switch → jog to hardware index → move to Locked position → disarm Lock → wait → set position → end. Accel/emer decel auto-computed; all timeouts fixed at 15000 ms. Populates HomingDef as PCSuite step types 8, 14, 1, 15, 16, 14, 7, 6, 0.

[8.4.0] - 2026-03-25

Added

  • Added FastReadCache high-performance cached read mechanism:
    • New FastReadCache class in Shared/FastReadCache.cs for low-latency parameter reads.
    • Supports caching both single-value keywords (e.g., Speed, Pos, MotionStat) and array elements (e.g., AInPort[1]).
    • Uses a background timer to batch-refresh all registered values via bulk commands at configurable intervals (default 1ms).
    • Built-in events: RefreshStarted, RefreshCompleted, RefreshError for monitoring.
    • Methods: Enable() / Disable() to activate/deactivate, RegisterKeywords() / RegisterArrayElements() to specify what to cache, GetCachedValue() / GetCachedArrayValue() to read, RefreshNow() for forced refresh, GetCacheAgeMs() to check staleness.

Changed

  • AAMotionConfig now exposes FastReadEnabled (global on/off switch) and FastReadCacheRefreshIntervalMs (default 1ms).
  • MotionController now exposes FastReadCache property, EnableFastReadCache(), DisableFastReadCache(), RegisterCacheKeywords(), RefreshCacheNow(), and GetCacheAgeMs().
  • CommAPIInterface.HandleSingleValueAccessed and HandleArrayValueAccessed now check the cache first when enabled, falling back to controller reads.
  • InitializeFastReadCacheIfEnabled() is automatically called after successful Connect() — registers A/B/C axes' common keywords ( Pos, MotionStat, InTargetStat) and AInPort[1-4], then enables the cache if AAMotionConfig.FastReadEnabled is true.

Added

  • Added CommAPI property on MotionController to expose the underlying AAComm.CommAPI instance for direct low-level communication with maximum performance.

[8.3.0] - 2026-03-17

-AACommServer + API: Ethernet packet sniffer for debugging communication issues - Auto-captures traffic on Ethernet channel open/close - Session snapshots saved to Error/User/Marked folders on disconnect - UI: status indicator, statistics, enable checkbox, mark capture button - Ring buffer with configurable retention policies


[8.2.2] - 2026-02-26

Added

  • Added GenerateCsvFile(DataLoggerResult result, string filePath = null) in DataLogger API to export recorded data to a CSV file:
    • Accepts a directory path or full file path; uses current directory when path is null or empty.
    • Generates timestamped file name when a directory is specified.
    • Writes UTF-8 with BOM for correct Chinese display in Excel.
    • Includes metadata (export time, record length, gap, parameter count, data point count), parameter information (name, CAN code, user units), data rows, and statistics (Min, Max, Average).

Changed

  • DataLogger GetDataRecordingStatus: AGD155 and AGD155-EC now use keyword ARecStat (no array index); other controllers continue to use ARecStat[1].

[8.2.1] - 2026-01-14

Added

  • Added GetDataRecordingCapacity method to DataLogger API for querying the maximum recording capacity of the controller.

Changed

  • Improved auto-reconnect logic in IsConnected property:
    • Enhanced reconnection mechanism to continuously retry when connection is lost
    • Added thread-safe reconnection with lock mechanism to prevent multiple threads from triggering reconnection simultaneously
    • Improved state tracking to ensure reconnection attempts continue even after initial failure
    • Reconnection will retry every 2 seconds if connection remains lost, up to 4 attempts per cycle

[8.2.0] - 2026-01-10

Added

  • Added DataLogger high-speed recording API on MotionController:
    • New partial implementation in Shared/DataLogger.cs wrapping AAComm RecData services.
    • DataLoggerConfiguration to configure duration (sec), gap (ms), parameters list, and trigger position.
    • DataLoggerTrigger to configure trigger type, source, value, mask and optional max value (for range triggers).
    • DataLoggerResult / DataLoggerVector containers exposing recorded data, time vector, CAN codes and user units.
    • High-level helper methods: ConfigureDataLogger, StartDataRecording, StopDataRecording, ForceDataLoggerTrigger, WaitForDataRecordingToEnd, UploadDataLoggerData, and one-shot RecordData.

Changed

  • Improved XML documentation for DataLogger trigger parameters with clearer English/Chinese descriptions to match PCSuite terminology (持续时间, 间隔, 触发器, 掩码, 最大触发值, 触发时刻).

[8.1.1] - 2026-01-09

Changed

  • Keyword filtering now relies solely on MessagesContainer (from AAbout command) instead of ControllerKeywordFilter
    • Removed ControllerKeywordFilter class and all firmware version-based filtering rules
    • Keyword availability is now determined directly from the controller's AAbout response, which contains the authoritative list of supported keywords
    • This approach automatically supports any firmware version without requiring manual filter rule updates
    • Keywords are filtered based on what the controller actually reports, ensuring perfect compatibility with any firmware version
    • Simplified KeywordRegistry constructor by removing controllerType and firmwareVersion parameters
    • Removed firmware version reading from AIdentity[26-28] during connection (no longer needed)

Removed

  • Removed ControllerKeywordFilter class and all keyword filtering rules
  • Removed firmware version detection code from CommAPIInterface.Connect() method

[8.1.0] - 2026-01-07

Change

  • Exclude the keywords of the CNCB group (except AGM800).

[8.0.9] - 2026-01-06

Fixed

  • Corrected CNC buffer clear routing so GroupA and GroupB send to their own CNC groups.

[8.0.8] - 2026-01-06

Added

  • Added CNCB (CNC Group B) keyword properties to ICiGroupAxis interface and CiGroupAxis implementation for AGM800 controller:
    • Read-only properties: CNCBSpeed, CNCBAccel, CNCBDecel, CNCBJerk, CNCBEndSpeed, CNCBAbsTrgt, CNCBPosRef, CNCBdPosRef, CNCBStatus , CNCBVel
    • Read/Write properties: CNCBEmrgDec, CNCBPercents , CNCBSpeedPer , CNCBPause
    • These properties allow full access to CNCB parameters and status, enabling complete control and monitoring of CNC Group B operations on AGM800 controllers.

[8.0.7] - 2026-01-06

Changed

  • IsCNCCompleted now supports AGM800's GroupB (AxisRef.B). For GroupB, it reads {axis}CNCBStatus[10] instead of CNCAStatus[10].
  • ControllerType enum values now reference ProductTypes constants to eliminate duplicate definitions and ensure single source of truth for product type IDs.

[8.0.6] - 2026-01-04

Added

  • Added CNC Begin(AxisRef axisRef) method to specify the axis for CNC operations.

[8.0.5] - 2026-01-04

Added

  • Added firmware version-based keyword filtering system in ControllerKeywordFilter:
    • Unified configuration dictionary KeywordFilterRules for all keyword filtering rules (controller type and firmware version dependent)
    • Automatic firmware version detection from AIdentity[26-30] during connection:
      • AIdentity[26] = Major version
      • AIdentity[27] = Minor version
      • AIdentity[28] = Patch version
      • Combined version format: Major * 1000 + Minor * 100 + Patch (e.g., 3.0.42.0.0 → 3042)
    • Support for firmware version-dependent keyword filtering:
      • Keywords can be configured to require a minimum firmware version
      • If firmware version is unknown, version-dependent keywords are skipped by default (safe default)
    • AGD301 controller: MapTableB, MapTableC, MapTableD, MapTableE keywords now require firmware version >= 3042 (3.0.42.0.0)
      • These keywords are automatically skipped for AGD301 controllers with firmware version < 3042
      • These keywords are available for AGD301 controllers with firmware version >= 3042

Changed

  • Refactored keyword filtering system to use unified configuration:
    • Consolidated all keyword filtering rules (always-skip and version-dependent) into a single KeywordFilterRules dictionary
    • Removed separate ControllerBlacklist dictionary in favor of unified configuration
    • Improved maintainability: all filtering rules are now in one place with clear version requirements
  • Enhanced KeywordRegistry to accept firmware version parameter for dynamic keyword filtering
  • Improved CommAPIInterface.Connect() method to read and parse firmware version from controller during connection

Fixed

  • Fixed keyword filtering to properly handle firmware version-dependent keywords for different controller types
  • Improved keyword availability detection based on actual controller firmware version

[8.0.4] - 2025-12-22

Fixed

  • EventPulseRes now reports the correct value (previously always 0).

Added

  • Added Commutated and IsCommutated APIs.

[8.0.3] - 2025-12-17

Fixed

  • Fixed vector motion Begin behavior in SingleAxis.Vector* helpers to only send one Begin command for the currently selected axis, ensuring correct behavior when multiple member axes participate in a vector move.

[8.0.2] - 2025-12-12

Added

  • Added controller discovery helpers in AAMotionAPI:
    • ScanController quickly probes a subnet for reachable controllers and returns their IP addresses.
    • ScanControllerDetailed returns full ConnectionData entries for discovered controllers using parallel probing.

[8.0.1] - 2025-12-02

Added

  • Added global communication transport switch AAMotionConfig.UseAsyncCommTransport to control how AAMotion sends commands to AAComm:
    • false (default): Legacy synchronous mode using CommAPI.SendReceive with internal locking for thread safety.
    • true: Asynchronous queue-based mode using CommAPI.Send plus per-call wait handles for high-concurrency scenarios.
  • Added SendViaAsyncQueue implementation in CommAPIInterface to wrap AAComm's async Send API into a synchronous-style call at AAMotion level.

Changed

  • Centralized communication error handling in CommAPIInterface via unified ErrorHandlers(AACommEventArgs status) helper:
    • All synchronous and asynchronous send paths now route errors through a single handler.
    • MotionController.SendCommandString and SendBulkCommandString rely on CommAPIInterface.ErrorOccurred instead of duplicating error formatting logic.
  • Improved thread-safety and debuggability of communication:
    • Synchronous mode uses an internal lock to serialize access to SendReceive, preventing "SendReceive is in progress" errors under multi-threading.
    • Asynchronous mode avoids mixing AAComm's synchronous and asynchronous APIs while still providing a synchronous programming model to AAMotion callers.

[8.0.0] - 2025-12-01

Added

  • Deprecate .NET 4.0 target framework. The minimum supported .NET framework is now 4.8

[7.6.2] - 2025-11-25

Added

  • Added SlowLogger functionality for data logging and monitoring
    • Added LoggerOn property to enable/disable slow logger (1 = enabled, 0 = disabled)
    • Added LoggerGap property to set sampling interval (16 samples per group, approximately 1.0 ms per group)
    • Added LoggerParams array property to configure up to 40 logger parameters (1-40, organized in 8 groups)
    • Added LoggerFullMod property to control FIFO full mode (0 = pause logging, 1 = overwrite oldest data)
    • Added LoggerStatus array property to monitor logger status and statistics
    • Added LoggerAbout array property to get controller slow logger metadata
    • Added convenience properties: LoggerFreeSpace, LoggerState, LoggerPushedPackets, LoggerLostPackets
    • Added ConfigureSlowLogger() method to configure logger parameters in one call with flexible parameter specification:
      • Supports string parameters (e.g., "CPos", "AVel[2]", "Pos")
      • Supports int CAN codes (direct assignment)
      • Supports SlowLoggerParameter struct for strongly-typed parameter description
      • Supports eKeyword enum values
    • Added LoggerUpload() method to upload slow logger data from the controller
    • Added SlowLoggerParameter struct for strongly-typed logger parameter specification (Axis/Keyword/Array index)
    • Added GetKeywordCANCode() method overloads to get CAN codes for keywords:
      • Base CAN code retrieval (without axis/array index)
      • Complete CAN code retrieval (with axis and optional array index)
      • Support for both eKeyword enum and string keyword names

[7.6.1] - 2025-11-17

Added

  • Added EnumPropertyHelper static class to provide enumeration value description functionality
    • Added GetDescription() extension method for all enum types to automatically retrieve friendly descriptions from resource files
    • Added description retrieval methods for specific enumeration types:
      • GetMotionStatDes() - Get description for MotionStat (bit flag enum)
      • GetMotionReasonDes() - Get description for MotionReason
      • GetStatRegDes() - Get description for StatReg (bit flag enum)
      • GetHomingStatDes() - Get description for HomingStat
      • GetInTargetStatDes() - Get description for InTargetStat
      • GetConFltDes() - Get description for ConFlt
      • GetEncTypeDes() - Get description for EncType
      • GetMotionModeDes() - Get description for MotionMode
      • GetMapTypeDes() - Get description for MapType
      • GetOperationModeDes() - Get description for OperationMode
      • GetMotorOnDes() - Get description for MotorOn
      • GetInjectTypeDes() - Get description for InjectType
    • Support for both bit flag enums and regular enums with automatic description combination for multiple set bits
    • Automatic fallback to "Unknown" descriptions when enum values are invalid or not found in resource files
    • All descriptions are retrieved from Descriptions.resx resource file using unified naming convention: {EnumName}_{EnumValue}

[7.6.0] - 2025-10-31

Added

  • Added global configuration class AAMotionConfig with UseNativeDataTypes property to control data type behavior
    • UseNativeDataTypes = false (default): Backward compatibility mode - float parameters return int (rounded), long parameters return int if within int range
    • UseNativeDataTypes = true: Native type mode - returns real float/double/long values with full precision
  • Added ParameterMetadataCache class for caching parameter type information to improve performance
  • Added ParameterTypeConverter static class to centralize type conversion logic for controller parameters
  • Added unified AASmartArrayWrapper class to automatically handle type conversion for array properties (int/float/double/long)
  • Added support for native float, double, and long data types with automatic type detection and conversion

Changed

  • Refactored parameter type conversion logic from SingleAxis.cs to centralized ParameterTypeConverter class
  • Enhanced array wrapper system to automatically detect and handle int/float/double/long types based on parameter metadata
  • Improved type conversion performance by caching parameter metadata on connection initialization
  • Refactored GetSmartValue and SetSmartValue methods in Node.cs to use centralized ParameterTypeConverter
  • Improved code organization in PropertyTypeSystem.cs with better method grouping and formatting
  • Enhanced backward compatibility: old code expecting int types continues to work without modification
  • Optimized KeywordMappings initialization in Connect() method to handle controller type detection failures gracefully

Fixed

  • Fixed type conversion errors when accessing float/double/long parameters in old code that expects int types
  • Fixed KeyNotFoundException when KeywordMappings was not initialized due to controller connection failures
  • Fixed missing error handling for controller type detection during connection

[7.5.5] - 2025-09-24

Changed

  • Add the retry reconnection mechanism. Method: TryReconnect()
  • Add the automatic connection mechanism. In the Connect() method, add the parameter enableAutoReconnect to enable/disable the automatic reconnection mechanism.
  • AGM800 supports linear and ARC movements for all axes.

[7.5.4] - 2025-09-12

Changed

  • Add CNC attributes :CNCASpeed, CNCAAccel, CNCADecel, CNCAJerk
  • Add ECAM attributes: ECAMStart, ECAMStartCyc, ECAMEndCyc, ECAMEnd, ECAMGap, ECAMCycles, ECAMMasterIni, ECAMCycCount, ECAMInterp, ECAMMaster, StopECAM, ECAMTableNum
  • Increase the setting for communication timeout
  • Enhance ECAM functions

[7.5.3] - 2025-08-26

Changed

  • Added a check for relative motion to disallow zero distance.
  • Added IO_B for AGD20.

[7.5.2] - 2025-08-08

Changed

  • Change "Commutated" to "AutoPhase". Change "IsCommutated" to "IsAutoPhase"
  • Add the keywords CNCAEncFactNu, CNCAEncFactDn, CNCASpeedPer, CNCAEndSegMod,AccelFact
  • Add methods BulkCommandParser and BulkCommandParserInt

[7.5.1] - 2025-08-04

Changed

  • Add WriteGenData and ReadGenData to read and write GenData
  • A dynamic keyword shielding mechanism based on controller type has been implemented, which has enhanced the system's compatibility and maintainability.
  • Initialization and connection exception information optimization

Fixed

  • The issue of the AGD101-EC controller connection failure has been resolved.

[7.4.12] - 2025-07-30

Changed

  • Add the keyword "EventPulseRes"
  • Override MoveAbs to achieve position override

[7.4.11] - 2025-07-22

Changed

  • Add methods SetEventTableBeginIndex, SetEventTableEndIndex, SetEventTableSelect, and SetEventTableValue to be compatible with older versions of PEG
  • Modify the return type of ClearConFlt to be bool

[7.4.10] - 2025-07-18

Changed

  • Add method RunCNCProgramFromFile (use the CNC file generated by PCSuite to perform interpolation movement)
  • Add method IsCNCCompleted (determine whether the CNC movement has been completed)

[7.4.9] - 2025-07-17

Changed

  • Add the MotorOn and InjectType enumeration types along with the English descriptions of the parameters.
  • Added methods MoveAbsAndWait, MoveAbsRepetitiveAndWait, MoveRelAndWait, MoveRelRepetitiveAndWait. Return true after movement and positioning.

[7.4.8] - 2025-07-04

Changed

  • Add enumeration types and English descriptions for parameters to enhance usability.
  • After the vector motion parameters are configured, the "Begin" command is automatically sent.


[7.4.7] - 2025-06-26

Fixed

  • Modify the array length of HomingDef.

[7.4.6] - 2025-06-16

Changed

  • The AAValueArray array starts from index 1.
  • Add the keywords MapTableB, MapTableC, MapTableD, and MapTableE.

[7.4.5] - 2025-06-11

Changed

  • Add the method ClearConFlt() to clear ConFlt.
  • Add the methods GetDOutPort and GetDInPort to obtain the IO status of the specified bit.

[7.4.4] - 2025-06-05

Changed

  • Add annotations to methods and attributes.

[7.4.3] - 2025-06-03

Changed

  • Add method and attribute annotations.

Fixed

  • Add the minimum axis judgment of the VectorLinear method of Vector.

[7.4.2] - 2025-05-30

Changed

  • Change IsHomed to IsHoming and determine whether the return to home is complete.

[7.4.1] - 2025-05-29

Changed

  • Add Home() to set HomingOn=1.

[7.4.0] - 2025-05-29

Changed

  • Add the Vector function.

[7.3.13] - 2025-05-23

Changed

  • OpenAACommServer increases the return value ConnectResult.

[7.3.12] - 2025-05-23

Changed

  • The Amotion API of CiGroup adds operations on GroupB.

[7.3.11] - 2025-05-21

Changed

  • The MoveRel and MoveAbs of the AAMotionAPI add MotionController (different controller instances can be used).

[7.3.10] - 2025-05-20

Changed

  • Commutation success added judgment 200: Not required, 300/400: Successfully completed (using Hall), 500: Successfully completed (parameters have been modified).
  • Adding a judgment to return to zero completes IsHoming().

[7.3.9] - 2025-05-06

Changed

  • Add GroupB to AGM800.

[7.3.8] - 2025-04-24

Changed

  • Add force control function.
  • Add attributes "RptMode", "RptCycles", and "RptCounter".

[7.3.7] - 2025-04-16

Changed

  • Added axis properties CurrDir, RevPLim, FwdPLim, MaxPosErr, MaxVelErr.

[7.3.6] - 2025-04-03

Changed

  • Adapt to AAComm.StartAACommServer() return type change from 'string' to 'ConnectResult'.

[7.3.5] - 2025-04-03

Changed

  • PEG and Lock do not need to specify an axis when using an axis object.

[7.3.4] - 2025-03-27

Changed

  • Increase the Commutate method commutate and determine whether commutation is complete.

[7.3.3] - 2025-03-27

Changed

  • The fourth parameter of SetMotionProfile is double.

[7.3.2] - 2025-03-21

Changed

  • Add the SetLock method to set the Lock function.

Fixed

  • Delete the attribute array VecPosFDef. This attribute is not in the dictionary.
  • Make four connection attempts while making a connection in AAmotion.

[7.3.1] - 2025-03-20

Changed

  • Repair Group can set the specified bit of the specified axis.
  • Add PEG methods, SetSingleEventPEG and SetEventFixedGapPEG.
  • Maintain the AAMotionAPI and do not need to specify an axis when using Group.

[7.3.0] - 2025-03-18

Changed

  • Implement AAValueArray's foreach method.
  • Add and implement the Java language call AAMotion project. AAMotionJava.

Fixed

  • Fixed VecPosFDef attribute being AAValueArray.
  • Adds the undefined attribute int UserPWMDiv. AAValueArray MapTable. int EventOn.

[7.2.4] - 2025-03-05

Changed

  • Add the method CiGroupDOutSetBit, CiGroupDOutClearBit, CiGroupDOutToggleBit. When using CNCGroup, can set, clear, and switch the specified axis (AAMotionAPI also add).
  • AAValueArray CNCAStatus, added to the registry.

Fixed

  • Fixed the return value error of MotionReason, ForceCmdValVecArcDir, VecSpeed, VecType, LoggerFullMod.

[7.2.3] - 2025-02-18

Changed

  • Add Home(filepath) to return to zero based on the zeroback file.

Fixed

  • Fix the Group API method using GetGroup.

[7.2.2] - 2025-02-12

Changed

  • Add the noAck parameter of Connect.
  • Deleted the directory "\AAMotion\Implementations\Central_I\Core".
  • Update yaml.net nuget version to 16.3.0.

[7.2.1] - 2025-02-11

Fixed

  • Fix AAMotion MoveAbs by adding Begin().
  • Fix MoveAbs() of AAMotionAPI; MoveAbs is called instead of MoveRel.

Changed

  • Improve the AAMotionAPI and add the API of IGroupAxis, ICiSlave, ICiGroupAxis and IGroupAxis interfaces.

[7.2.0] - 2025-02-10

Changed

  • Add the bit operation of Group on IO output (GroupDOutSetBit, GroupDOutClearBit, GroupDOutToggleBit).

[7.1.0] - 2025-02-07

Changed

  • Increase the usage methods of the keywords, ProgHalt, ProgRun, AutoExec, ProgHaltAll and ProgReset.
  • Reload connect and add the port number.

[7.0.5] - 2025-01-16

Fixed

  • If connection fail due to FwInfoMismatch, retry connection with ForceAboutData set to true.

[7.0.4] - 2025-01-16

Changed

  • Reduce AAMotionAPI overloading: use methods with optional parameters.
  • Add the calling method of the AAMotionAPI.

[7.0.3] - 2025-01-09

Fixed

  • Add a timeout mechanism to the initialization loop in the CommAPIInterface constructor to prevent infinite waiting. Set the maximum wait time to 3 seconds (30 ticks of 100ms).

[7.0.2] - 2025-01-09

Fixed

  • Make sure the dictionary's life cycle is permanent.

[7.0.1] - 2025-01-08

Fixed

  • Include controller parameters eKeyword.cs file.

[7.0.0] - 2024-12-30

⚠️ Major Release

  • Introduction of AAMotionAPI, a functional programming style API interface for users more familiar with C++ style programming. This interface is incomplete.
  • AAMotion infrastructural level has been cleaned and redesigned:
    • There are no more status updates.
    • There are no more settings updates.
    • There is a new type called AAValueArray which functions exactly like an array.
    • All keywords are now accessible as a property.
    • Some keywords are more like functions, in which case they are exposed via the API as methods.
    • Hooking onto errors is now done via EventOccurred action in MotionController class.
    • Documentations have currently all been removed, due to a large effort to clean the code base and remove code bloat, unify and to standardize the code style. Since there is now a direct link to keywords of the controller, it may be better to centralize efforts in documentations first.
    • All design considerations are in an effort to speed up the performance of the API usage, as well as to simplify the overall design. It is now extremely straightforward to add support for new keywords.

[6.0.4] - 2024-11-20

Fixed

  • Fixed indexing bug for ForceCurr_SetForceCmdSource() and ForceCurr_SetCurrCmdSource() methods.

[6.0.3] - 2024-07-01

Fixed

  • Setting and Querying the MotorOn keyword in PCSuite is 20 seconds, so here we do the same thing at the communicator interface.

[6.0.2] - 2024-06-16

Fixed

  • .NET 4.0 YamlDotNet reference 4.2.2.

[6.0.1] - 2024-05-31

Fixed

  • Set motion mode when MoveAbs is called.

[6.0.0] - 2024-05-08

Added

  • Reference AAComm 7.0.0.

[5.13.0] - 2023-12-20

Added

  • Added support for interpreting user program variable names via AdaptUserProgMessage.

[5.12.3] - 2023-11-30

Fixed

  • AllStatInterpreter now supports 12 axes to compliment changes in 5.10.0.
  • VectorMotion Pause Continue Fix, Added Enum for Vector Motion.

[5.12.2] - 2023-11-27

Changed

  • AACommServer: Allow "retrying" socket init. upon process start.

[5.12.1] - 2023-11-22

Changed

  • Added support for on-the-fly target/speed/accel change for MoveAbs().

[5.12.0] - 2023-10-31

Changed

  • Add .net 6.0 and 7.0 targets. (nuget 5.11.0 is wrongly built and deprecated).

[5.11.0] - 2023-09-08

Fixed

  • Fix the errcode of 'FailAboutData' when connecting.

[5.10.0] - 2023-08-04

Fixed

  • AllStatUpdate to query for all 12 axes (instead of 8) such that the auxiliary ports will be updated too.

[5.9.0] - 2023-08-03

Fixed

  • AllStatUpdate to include AGD155_EC_ID as well.

[5.8.0] - 2023-04-13

Fixed

  • CRITICAL! AAComm RecUpload and "continuous" messages related bug.

[5.7.0] - 2023-04-13

Fixed

  • Add YamlDotNet nuget dependency.

[5.6.10] - 2023-04-13

Changed

  • Add vector motion methods.

[5.6.9] - 2023-01-12

Changed

  • Added gcode analysis methods.

[5.6.8] - 2023-01-12

Changed

  • Added methods to cigroup-axis class to do 6-axes linear motion and write doutport value related to axis to buffer.

[5.6.6] - 2023-01-12

Changed

  • Move params and methods of event class to single axis class.

[5.6.5] - 2023-01-12

Changed

  • Added params of gantry.

[5.6.4] - 2023-01-12

Changed

  • Added params and methods of error map.

[5.6.3] - 2023-01-12

Changed

  • Added params of userpwm.

[5.6.2] - 2023-01-12

Changed

  • Added params and methods of group9(userparam).

[5.6.1] - 2023-01-11

Changed

  • Added params and methods of position lock.

[5.6.0] - 2023-01-03

Added

  • Added override for Home method to do homing based on sequence stored in memory.

[5.5.0] - 2022-08-17

Changed

  • Communicator interface, AllStat Instantiation is cleaned to used feature_1 integer directly.

[5.5.0] - 2022-08-03

Added

  • All the Move commands in Single axis motion is now updated to include speed, accel, decel parameters.

[5.4.13] - 2022-08-03

Changed

  • MotionStat is added to the end of each Move Command. This should eliminate the timing issue that we experienced with the Taiwan customer.

[5.4.12] - 2022-08-03

Fixed

  • Removing MotorCurrent, Fixing MotorCurr and CurrRef Statuses in ISingleAxis.

[5.4.11] - 2021-10-04

Changed

  • Modify all Properties related to Force/Current/Position Mode Switch feature to be based on InternalProperies.

[5.4.10] - 2021-09-13

Changed

  • If packet number in a SlowLogger UpLoad is zero, jump to next UpLoad without processing.

[5.4.9] - 2021-09-10

Changed

  • Slow logger API StopSlowLogData: check the ID of the packet that was last logged, remove unwanted packet ID and contents from the main log list.

[5.4.8] - 2021-09-09

Added

  • Slow logger API functions: StartSLowLogProcess and StopSlowLogData.

[5.4.7] - 2021-09-07

Fixed

  • BuffTime and BuffPos command format must be array type.

[5.4.6] - 2021-09-06

Added

  • Add properties and methods for Spline buffer motion in CiSingleAxis.

[5.4.5] - 2021-08-04

Fixed

  • HomeToLimitThenIndex, Jog to index - 4, the first parameter is speed, argu 1.

[5.4.4] - 2021-06-30

Changed

  • Turn on AGMotion.
  • Open the CloseAACommServer API to the user.

[5.4.3] - 2021-06-08

Changed

  • Hide AGMotion for interim version release.

[5.4.3] - 2021-05-24

Changed

  • Do some Clearance works.

[5.4.3] - 2021-05-23

Added

  • Add Force/Current/Position Mode Switch feature.

[5.4.2] - 2021-05-20

Added

  • Add new AGMotion feature.

Changed

  • Eliminate all compilation Warnings.
  • AGMotion as an individual namespace.

[5.4.1] - 2021-05-12

Fixed

  • Default set time out as 2 seconds when send out packet.

[5.4.0] - 2021-04-16

Added

  • Add an option to set time out when send out packet, convenient for the user to debug for breakpoint.

[5.3.2] - 2021-03-24

Fixed

  • Corrected tag for CNC Arc Motion.

[5.3.1] - 2021-03-04

Fixed

  • Fix the bug on AIO Output Readback value incorrect.

[5.2.10] - 2021-02-17

Changed

  • Move VBus, VLogic, PwrTemp, MotorTemp into IMotionController to be used for all controller class.

[5.2.9] - 2021-02-17

Changed

  • Extend AGM400 Axes, IO, GroupAxis, PositionEventEx, full range from A to F.
  • Extend AGM800 Axes, IO, GroupAxis, PositionEventEx, full range from A to L.

[5.2.8] - 2020-10-29

Changed

  • AllStat for different controllers.

[5.2.7] - 2020-09-29

Changed

  • Change 4.6.2 target frameworks to 4.8 (remove redundancy).

[5.2.6] - 2020-09-29

Fixed

  • Updated calls to GetAllAxisStat within Group.cs.

[5.2.5] - 2020-09-19

Added

  • And multiple target frameworks support: .net 4, 4.6, 4.6.2.

[5.1.5] - 2020-09-18

Changed

  • Updated definitions and bug fixes for CNC Motion.

[5.1.4] - 2020-09-18

Changed

  • Call GetAllAxisStat using a new parameter to decide if AllStatAxisData group 10 is included.
  • Add DInPortHigh in AllStatAxisData group 10.
  • Add BoardTemp in AllStatAxisData group 10.
  • Clearance in IOMoudle.cs.

[5.1.3] - 2020-09-17

Changed

  • Array Type parameter index start from 1 for AAMotion user.
  • Rename and add elements in enum ControllerType.

Fixed

  • Instantiate eEventSetting dictionary for read.

[5.1.2] - 2020-09-16

Added

  • Add BiDirConfig feature.

[5.1.1] - 2020-09-15

Changed

  • Rename Class AG100 to AGD155.
  • Rename Class MAS02 to AGM800.
  • Rename Class MAS01 to AGM400.
  • Rename Class AG300 to AGC300.
  • Rename Class AGD301 to AGC301.

Added

  • Add DInLogHigh feature.

[5.1.0] - 2020-09-10

Added

  • Add AGD301 class.
  • Add TimerHandler in TimerEvent for internal debug from AAMotion user source code program.

Changed

  • Change the commands of GetAllSettingsDIOAG300, GetAllSettingsAIOAG300 to dynamic contents, initialize them in the CommandList and rebuild later dynamically.

Fixed

  • Dynamic Command fix the AGD301 system down after connection.
  • Add eIOSetting.DInMode in UpdatePropertyListIOSettingArray.
  • NumOfAOut for output array-type keywords.

[5.0.1] - 2020-09-09

Fixed

  • FIX nuget package.

[5.0.0] - 2020-09-09

Changed

  • Merge from 4.21.8.
  • ParseDIOSettings, ParseAIOSettings don't trigger event to write into Array-Type IO variables.

[4.21.8] - 2020-09-04

Changed

  • Replace PosEvent with PosEvent_A, PosEvent_B, PosEvent_C in AG300 class.

[4.21.7] - 2020-09-04

Fixed

  • Take CiStatus from AllStat returning data structure.

[4.21.6] - 2020-08-31

Fixed

  • Make AllStat "Continuous" message type.

[4.21.5] - 2020-08-27

Fixed

  • Increase Axis to L.

[4.21.4] - 2020-08-27

Added

  • Add CiStatus feature (Modified). Temporarily use .PropertyList[Axis].CiSlaveSettingsArray[eCiSlaveSetting.CiStatus][1] to replace .PropertyList[Axis].Statuses.CiStatus_1.

[4.21.3] - 2020-08-27

Added

  • Add CiStatus feature.
  • Temporarily disable CiSlaveRefreshRequired since AllStat[255] cannot work set.
  • Increase the AxisRef to Axis K.
  • Define GetAllCiAxisStat to replace GetAllAxisStatExtended1.
  • Add CiIdentity, CiStatus into eCiSlaveSetting.

[4.21.2] - 2020-08-06

Added

  • Optimize Array Properties Access.
  • Add new command GetAllSettingDIOCiMaster, GetAllSettingAIOCiMaster.

[4.21.1] - 2020-07-24

Added

  • Add MAS02 (AGM800) class, initial version.

[4.20.1] - 2020-06-30

Added

  • Add AG100 class, initial version.

[4.19.2] - 2020-06-30

Changed

  • Rename the property arrayStorage.

Fixed

  • Refresh takes after the communication OK with the controller.
  • VBus, VLogic, PwrTemp, MotorTemp updated valid only after the communication OK with the controller.

[4.19.1] - 2020-06-26

Changed

  • Set a flag called "AxisInternalPropertyUpdatedFromController" to true after the connection is OK, which prevent intermittently read PropertyList after connection but InternalProperty have not been updated.

[4.19.0] - 2020-06-23

Changed

  • Optimize the IO array properties update event handler.
  • Property read must occur after connected, otherwise default as "unreasonable" values.

[4.18.0] - 2020-06-19

Added

  • Replace the Set methods on "Array-Like" IO Features with direct "Array" format Read/Write Properties. These are DInMode, DOutMode, DOutSelect, AInFilt, AInOffSet, AInDB, AInGain, AInMode, AOutOffset, AOutShifts, AOutMode, AOutPort.

[4.17.0] - 2020-06-17

Added

  • Add dynamic allocation array size of Digital and Analogue input/output Mode, Filt, Offset, DB, Gain.

[4.16.0] - 2020-06-12

Changed

  • Upgraded AAComm DLL is upgraded from v3.0.0.2 to v3.0.0.18.

[4.15.0] - 2020-04-30

Added

  • AOutPort has additional option to change to openloop mode.

Changed

  • Clean up namespaces to use only one AAMotion namespace.

[4.13.1] - 2019-01-10

Fixed

  • Put back bulk command method which was removed in v4.12.0.

[4.13.0] - 2019-01-07

Added

  • MultiController.MoveAbs/MoveRel: Basic (Cross-controller) Multi-axis Synchronized PTP Method.

[4.12.1] - 2019-01-06

Changed

  • Changed [UpdateInternalStatus()] to type boolean.

[4.12.0] - 2019-12-30

Added

  • New method [UpdateInternalStatus()] on all MotionControllers which allow user to explicitly update internal status.

[4.11.0] - 2019-10-17

Added

  • New Homing method, which can take in sequence file .hseq file which is created from PCSuite.

[4.10.2] - 2019-10-13

Fixed

  • Edited summary of SendBulkCommandString method.

[4.10.1] - 2019-10-10

Fixed

  • Differentiated between internal and external bulk messaging.

[4.10.0] - 2019-10-10

Added

  • Support for bulk messaging.

[4.9.0] - 2019-08-26

Added

  • Support PositionEvents from AG300 Controller.

[4.8.0] - 2019-08-26

Added

  • LinearAbsolute: Generic CNC Method.
  • MultiSegmentOn: Multi-segment Option.

[4.7.0] - 2019-08-26

Added

  • Added InTargetTol, Added InTargetTime Keywords.
  • Added Arc functions in Group Axis to include Additional Cycles Input Parameter.

Fixed

  • Arc functions in Group axis was missing 1 parameter.

[4.6.0] - 2019-08-26

Changed

  • AAComm is now taken from nuget package.

[4.5.0] - 2019-08-26

Added

  • AllStat: Extension to be backward compatible and for supported MotorTemp.

[4.4.0] - 2019-08-26

Added

  • Controller has new constructor overload to allow configurable internal update rate.

[4.3.0] - 2019-08-26

Changed

  • Whenever motion method is called from either SingleAxis or GroupAxis, AllStat command is called immediately afterwards to update the statuses of the internal variables.