GIT repositories

Index page of all the GIT repositories that are clonable form this server via HTTPS. Übersichtsseite aller GIT-Repositories, die von diesem Server aus über git clone (HTTPS) erreichbar sind.

Services

A bunch of service scripts to convert, analyse and generate data. Ein paar Services zum Konvertieren, Analysieren und Generieren von Daten.

GNU octave web interface

A web interface for GNU Octave, which allows to run scientific calculations from netbooks, tables or smartphones. The interface provides a web form generator for Octave script parameters with pre-validation, automatic script list generation, as well presenting of output text, figures and files in a output HTML page. Ein Webinterface für GNU-Octave, mit dem wissenschaftliche Berechnungen von Netbooks, Tablets oder Smartphones aus durchgeführt werden können. Die Schnittstelle beinhaltet einen Formulargenerator für Octave-Scriptparameter, mit Einheiten und Einfabevalidierung. Textausgabe, Abbildungen und generierte Dateien werden abgefangen und in einer HTML-Seite dem Nutzer als Ergebnis zur Verfügung gestellt.

Bahnplaner in C

Trajectory generator in C

The simple trajectory generator described here was implemented for positioning purposes in educational context, and consists of only one platform independent C header/implementation file pair. It does not contain volatile structures nor system specific synchronisation. Instead, it works with one struct type intermediate variable (type trajgen_t) that can be allocated externally and is unchanged during the update process. This variable stores configuration, internal states, input data and position output data. Added/adapted features, such as position/velocity synchronisation shall make it suitable to be integrated in LinuxCNC (EMC) context. The brief feature list:

  • Axes: Main axes X, Y, Z, (translation) A, B, C (rotation), Auxiliary axes U, V, W (normally collinear to X/Y/Z).

  • Coordinated motion planer: Line motion, arc/circle/helix motion, (spline/nurbs in test), blending, synchronised I/O channels (required for EMC compatibility), velocity synchronisation (required for EMC), position synchronisation (required for EMC).

  • Manual operation: Planer for command velocities (e.g. from joystick), which includes kinematics.

  • Joint jogging: Planer for command positions for each joint without kinematics.

  • Main trajectory generator interface: Finite state machine based coordinator for the "sub planers".

  • Interpolation: Cubic joint position interpolators affecting the final output (command positions for the closed loop controllers) for all planers.

  • "Callback kinematics": The internal kinematic model is the "identity" model. Th.m. the pose axes directly correspond to the joint indices. Other kinematics can be defined by configuring function pointers to the forward/ inverse/reset functionalities.

  • Extensive compiling options: For optimisation, all planers and the interpolators can be static __inline__'d or exported. Integer and floating point types can be specified, features, such as interpolation, synchronisation, debugging, error stringification and the like can be switched off entirely to optimise memory space and the performance.

The implementation files shown below include not only the generator files (trajgen.c/.h), but also a test program with a C++ wrapper (trajgen.hh), build scripts, Makefile and GNU Octave analysis (stats.m).

Example x/y motion of the coordinated motion generator:

(Die gewünschten allgemeinen Erläuterungen zu Bahnplanern sind hier).

Der hier beschriebene Bahngenerator wurde für Positionierungszwecke im Lehr/Forschungsbereich implementiert und für die Integration in LinuxCNC erweitert. Er besteht aus nur einem plattformunabhängigen C Header/Implementation-Dateipaar und enthält keine systemspezifischen Echtzeitsynchronisationsmechanismen. Stattdessen arbeitet er mit einer Strukturvariablen (Typ trajgen_t), die extern alloziiert wird und als Zwischenspeicher dient, der während dem Rechenvorgang nicht geändert werden darf. Sie enthält die Konfiguration, interne Zustände, Eingabewerte und Ausgabewerte.

Die Features in aller Kürze:

  • Achsen: Hauptachsen X, Y, Z, (Translation) A, B, C (Rotation), Hilfsachsen U, V, W (normalerweise kollinear zu den kartesischen Hauptachsen).

  • Koordinierte Bewegungen: Linie, Kreis/Bogen/Helix, (Spline/Nurbs im Test), Überschleifen, Geschwindigkeits-/Positionssynchronisation, synchronisierte digitale Ausgänge.

  • Manueller Betrieb: Aus Geschwindigkeitseingaben (z.B. von Joystick) werden mit Kinematik neue Sollpositionen erzeugt.

  • Motor-Jogging: Aus Geschwindigkeitseingaben werden (ohne Kinematik) unabhängig neue Sollpositionen für die einzelnen Motorachen erzeugt.

  • Interpolation: Kubische Spline-Interpolation kann wahlweise zwischen die Ausgabepositionen der Planer und die Motor-Sollpositionen geschaltet werden.

  • "Callback"-Kinematik: Das interne Default-Modell der Gerätekinematik ist das "Identity"-Modell, d.h. die Werkzeug-Achspositionen entsprechen direkt den Motorachspositionen (joints). Über konfigurierbare Funktionspointer kann das Kinematische Modell extern implementiert und eingebunden werden.

  • Extensive Kompilierungseinsellungen: Um dem Compiler Raum für Optimierung zu geben können alle Planer, sowie die Interpolatoren entweder exportiert oder als static __inline__ kompiliert werden. Weiterhin können Integer- und Fließkommatypen angepasst und Funktionalitäten selektiv abgeschaltet werden (Interpolation, Synchronisation, Überschleifen, Fehlertexte usw.).

Neben den Hautdateien (trajgen.c/.h) ist weiter unten auch ein Testprogramm mit C++ Wrapper (trajgen.hh) GNU-Octave Auswertung (stats.m) angegeben.

Beispiel: X/Y Bahnbewegung des Planers für koordinierte Bewegungen:

x/y positions of the trajectory

trajectory velocity/acceleration

joint velocities/accelerations

Usage

(Ab hier geht es in die Programmierdetails, und da ist die "Amtssprache" Englisch).

In the very end I cannot deny that a close look into the source code is required to use all the features properly. The inline function/type documentaion helps you there. To get "booted" read the gist of it here.

Info

The function const char* trajgen_info(char* s, unsigned length); writes a string representations of its compilation settings into a string (s). This can be handy to validate parameters of the compiled program. That looks like e.g.:

"trajgen.c/h, version 1.0b, without-0pointer-checks, no-debug, with-interpolation, with-blending, with-syncing, with-abc, with-uvw, float-type=double, max-joints=9, queue-size=32, machine-resolution=1e-9, dimensional-resolution=1e-9, angle-resolution=1e-6, blending-max-angle=85deg, straight-blending-max-angle=1deg, ..."

Initialisation and configuration

To use the trajectory generator, copy the files trajgen.h and trajgen.c in your source tree and compile latter. Include trajgen.h and allocate a variable of the type trajgen_t (e.g. tg). Then configure the planers, either directly using tg.config.(settings) or by allocating an auxiliary configuration variable of type trajgen_config_t:

#include <trajgen.h>
 
trajgen_t tg;           /* The main trajectory generator data */
 
int init_trajgen()
{
  unsigned i;
 
  /* This is where we do our configuration. You can either use a separate
   * variable like this one or use tg.config directly. It makes no difference.*/
  trajgen_config_t cfg;
 
  /* That makes you safe! The trajgen checks null pointers.
   * Any configuration trouble will cause appropriate errors. */
  memset(&cfg, 0, sizeof(trajgen_config_t));
 
  /* 1st: How many axes do we use? Less axes -> better performance.
   * Here, let's say only x,y,z */
  cfg.number_of_used_joints = 3;
 
  /* Next: The sample rate. That is the sample rate which the axes closed loop
   * controllers want new command positions. Here we choose 4kHz ... */
  cfg.sample_interval = 1.0/4e3;  /* in seconds */
 
  /* ... but the planers only run with 1kHz, the interpolators do the positions
   * in between for each joint. */
  cfg.interpolation_rate = 4;
 
  /* Here you can specify your own kinematics, but we use NULL pointers here so
   * that the internal identity kinematics are used. Actually you don't need to
   * set these values at all if you used `memset(0)` before.
   */
  cfg.kinematics_functions.forward = 0; /* function(*joints, *pose) */
  cfg.kinematics_functions.inverse = 0; /* function(*pose, *joints) */
  cfg.kinematics_functions.reset = 0;   /* function(*joints, *pose) */
 
  /* Now about the restrictions for coordinated motions. We use 100mm/s and
   * 1000mm/ss here: */
  cfg.max_coord_velocity = 100e-3;
  cfg.max_coord_acceleration = 1000e-3;
 
  /* The settings for each independent joint planer. As those are often used for
   * initialisation purposes, we restrict them all to 10mm/s and 100mm/ss: */
  for(i=0; i<cfg.number_of_used_joints; i++) {
    cfg.joints[i].max_acceleration = 100e-3;
    cfg.joints[i].max_velocity = 10e-3;
  }
 
  /* And for the manual planer we have individual settings: 100mm/ss and
   * 50mm/s: */
  cfg.max_manual_accelerations.x = 100e-3;
  cfg.max_manual_accelerations.y = 100e-3;
  cfg.max_manual_accelerations.z = 100e-3;
  cfg.max_manual_velocities.x = 50e-3;
  cfg.max_manual_velocities.y = 50e-3;
  cfg.max_manual_velocities.z = 50e-3;
 
  /* Finally we call init */
  if(trajgen_initialize(&tg, cfg)) {
    char error_text[64];
    trajgen_errstr(tg.last_error, error_text, sizeof(error_text));
    rt_printf("Initialisation error: %s\n", error_text);
    return 1;
  } else {
    rt_printf("Initialisation successful\n");
    return 0;
  }
}

Cyclic call

Supposingly you use a realtime threading system with fifos or shared memory synchronisation. No matter how this framework looks like, you have to ensure that the function trajgen_tick() is called with the sample interval that you configured. The process for each cycle is:

/* This function has to be called with the configured 4kHz. */
void cycle()
{
  /* Here some generalised dummy code to see roughly where it points to ... */
  dispatch_fifo(&cmd);
  if(check_hardware() != OK) {
    trajgen_abort();
    rt_error_message(...);
  } else {
    switch(cmd.what) {
      case ABORT:
        trajgen_abort();
        break;
      case PAUSE:
        if(trajgen_pause() != TRAJ_ERROR_OK) {
          rt_error_message(...);
        }
        break;
      default:
        /* Something to get back to later */
    }
  }
 
  /* Call trajgen_tick() */
  if(trajgen_tick() != TRAJ_ERROR_OK) {
    /* Send the error via the used realtime messaging */
    rt_error_message(tg.last_error);
    /* Abort the generator (switch to disabled state) */
    trajgen_abort();
    /* Cutoff intermediate circuit */
    set_estop(1);
    /* We even don't update the axis command positions because it could be
     * a numerical error as well */
    return;
  }
 
  /* Transfer output data ... */
  for(i=0; i<tg.config.number_of_used_joints; i++) {
    axis_controller_set_command_position(tg.joints[i].position);
  }
  if(tg.state == TRAJ_STATE_COORDINATED) {
    /* Transfer sync I/O */
    digital_output_register_set(tg.coord_planer.sync_dio_current);
 
    /* Add commands */
    switch(cmd.what) {
      case MOVE_LINE:
        if(!trajgen_queue_full()) {
          if(trajgen_add_line(cmd.to_pose, cmd.speed, cmd.accel)) {
            trajgen_abort();
            rt_error_message(...);
          }
        } else {
          rt_notify(QUEUE_FULL);
        }
        break;
      case MOVE_ARC:
        ...
      case MOVE_SPLINE:
        ...
      case SWITCH:
        ...
    }
  } else {
    switch(cmd.what) {
      ...
    }
  }
}

States

If you use more than one of the integrated generators, you need handle state dependent date in the thread function. Handling and switching states is quite easy: The important functions and variable are:

The example shows the use of that without threading:

extern trajgen_t tg;
extern trajgen_config_t cfg;
extern void handle_error(trajgen_t* tg);
 
void sequential_example()
{
  unsigned i;
  trajgen_initialize(&_tg, cfg);
 
  if(trajgen_switch_state(TRAJ_STATE_COORDINATED)) handle_error(&tg);
 
  while(!tg.is_done) {
    if(trajgen_tick()) handle_error(&tg);
  }
 
  /* State is now  TRAJ_STATE_COORDINATED */
  for(i=0; i<10; i++) {
    /* Simply nothing will happen, even if it is selected the coord planer
     * is idle, and is_done === 1. */
    if(trajgen_tick()) handle_error(&tg);
  }
 
  if(trajgen_switch_state(TRAJ_STATE_MAN)) handle_error(&tg);
 
  while(!tg.is_done) {
    if(trajgen_tick()) handle_error(&tg);
  }
 
  /* State is now  TRAJ_STATE_MAN */
 
  ...
}

About errors

Simply said: All errors that the trajectory generator returns are critical and a reason to cut the intermediate circuit. The error type trajgen_error_t is generally used as function return, where all nonzero values indicate errors. The size is normed to uint16_t for compatibility. Additionally, the type traigen_error_details_t is a union-struct bit set with the same size, but it splits the error into a code part (12 bits) and a joint part (4 bits). Whenever an error is raised from interpolators or joint planers, the joint will be specified (value 0 to max joints). If the subsystem is not related to a joint, the value will be 0. You can get a string representation of the error with the function trajgen_errstr(). Take a look at the following example to get the gist of it:

#include "trajgen.h"
#include <stdio.h>
#include <string.h>
 
trajgen_t tg;
trajgen_config_t cfg;
trajgen_error_t erno;
char errmsg[64];
 
void try_initialize()
{
  if(!!(erno=trajgen_initialize(&tg, cfg))) {
    trajgen_errstr(tg.last_error.errno, errmsg, sizeof(errmsg));
    printf("Error: %s (%u)\n", errmsg, erno);
  } else {
    printf("Alright, init ok\n");
  }
}
 
int main()
{
  memset(&cfg, 0, sizeof(trajgen_config_t));
 
  /* Prints: "Error: [main trajgen] Null pointer" */
  if((erno=trajgen_initialize(NULL, cfg))) {
    trajgen_errstr(erno, errmsg, sizeof(errmsg));
    printf("Error: %s\n", errmsg);
  }
 
  try_initialize(); /* "Error: Invalid last used joint value" */
  cfg.number_of_used_joints = 1;
 
  try_initialize(); /* Error: [main trajgen] Invalid interpolation rate setting */
  cfg.interpolation_rate = 1;
 
  try_initialize(); /* "Error: [coord tg] Invalid acceleration" */
  cfg.max_coord_acceleration = 1000;
 
  try_initialize(); /* Error: [coord tg] Invalid velocity */
  cfg.max_coord_velocity = 100;
 
  try_initialize(); /* Error: [coord tg] Invalid sample interval */
  cfg.sample_interval = 1e-3;
 
  cfg.joints[0].max_acceleration = -5;
  try_initialize(); /* "Error: [joint tg] Invalid maximum acceleration[0]" */
 
  /* etc etc etc */
  return 0;
}

About reset

The function trajgen_reset() resets all planers to a state where they are disabled and flushed. Internal positions are set to current device positions if required. Note that reset really means reset. If you call trajgen_reset() or the reset function of any planer while the machine is moving you will cause an immediate freezing of the positions, which will lead to very high accelerations. Hence, resetting is only recommended in standstill state. When switching between the planers they will be implicitly reset in a safe situation.

Joint planers

The joint planers work with a very simple principle. They calcaulate a required velocity from the triangle ramp of the remaining distance to move and the configured axis acceleration, and truncate this velocity to the maximum configured or commanded velocity. Then they increase the current positions with s = v * t. You can change command velocity and command position any time. Complete usage:

Simple example:

#include <trajgen.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
 
trajgen_t tg;
trajgen_config_t cfg;
double t = 0;
 
static void tick()
{
  t += tg.config.sample_interval;
  if(trajgen_tick()) exit(3);
  printf("%8.3f, %12.6f, %12.6f, %12.6f\n",
    t, tg.joints[0].position, tg.joints[0].velocity, tg.joints[0].acceleration
  );
}
 
int main()
{
  memset(&cfg, 0, sizeof(trajgen_config_t));
  cfg.number_of_used_joints = 3;
  cfg.sample_interval = 1e-3;
  cfg.interpolation_rate = 1;
  cfg.max_coord_acceleration = 1000;
  cfg.max_coord_velocity = 100;
  cfg.joints[0].max_acceleration = 100;
  cfg.joints[0].max_velocity = 10;
  if(trajgen_initialize(&tg, cfg)) return 1;
 
  /* Output list header */
  printf("     t  ,        j0s  ,        j0v  ,        j0a\n");
 
  if(trajgen_switch_state(TRAJ_STATE_JOINT)) return 2;
  while(!tg.is_done) tick();
 
  /* Check command_velocity trimmed tp 10 */
  tg.joints[0].tg.command_velocity = 20;
  tg.joints[0].tg.command_position = 2.5;
  do { tick(); } while((!tg.is_done));
 
  /* Check command_velocity === 5 */
  tg.joints[0].tg.command_velocity = 5;
  tg.joints[0].tg.command_position = 5;
  do { tick(); } while((!tg.is_done));
 
  /* Abort behaviour */
  tg.joints[0].tg.command_velocity = 5;
  tg.joints[0].tg.command_position = 10;
  do {
    if(tg.joints[0].tg.is_enabled && tg.joints[0].position >= 7.5) {
      tg.joints[0].tg.is_enabled = 0;
    }
    tick();
  } while((!tg.is_done && t < 10));
 
  /* Jogging: Set command position to Inf/-Inf */
  tg.joints[0].tg.is_enabled = 1;
  tg.joints[0].tg.command_velocity = 10;
  tg.joints[0].tg.command_position = -INFINITY;
  do {
    if(tg.joints[0].tg.is_enabled && tg.joints[0].position <= 0) {
      tg.joints[0].tg.is_enabled = 0;
    }
    tick();
  } while((!tg.is_done && t < 10));
  return 0;
}

Manual planer

The manual planer is based on command velocities of the axes X, Y, Z, A, B, C, U, V and W. As manual operation is quite different from automatic coordinated motion, this planer has individual acceleration and speed settings for each axis. These settings should be set to appropriate values for human interaction - th.m. much slower. The input velocities can come from handwheels joysticks and the like. What it does is integrating the velocities for all axes according to their accelerations while scaling down all the current velocity increment so that all axes meet their acceleration restrictions, and finally integrating the positions from the truncated velocities. The resulting pose is pass through the kinematics to yield the joint positions.

Coordinated planer

The planer for queued automated coordinated motion (with kinematics). It basically determins the linear lengths of the segments when they are added to the queue, and generates each tick cycle a linear acceleration/velocity profile for the remaining length (to move) along the total segment length to obtain the current path progress. It then calculates the current pose (position of all axes) from the latter progress and feeds the result into the kinematics to calculate the joint command positions. See the functions trajgen_sg_tick() and trajgen_coord_tick(). All remaining functions are related to queue handling, precalculations of the segments, obtaining the pose from the progress for different motion types, modification of parameters to meet the configured or external restrictions, and error checks.

Usage:

Interpolators

The (cubic) spline interpolation is done every trajgen_tick() cycle, and work exclusively with joint positions. Each joint interpolator has a buffer of four positions from the planer, and calculate a new output position (and also velocity and acceleration) from their current progresses and the sample interval. When new position is almost exceeding the last buffer value, they request a new value from the planer, which will be invoked implicitly and push new values into the interpolator buffers. Usage notes:

Compiler switches

ToDo's, glitches, etc

About performance

Compiling and running the example of the output binary of test/test-coord.cc on a Raspberry PI (ARMV7 with 700MHz and floating point unit - the slowest I have that is not a micro controller/DSP) yields an average calculation time for trajgen_tick() of 16.3us. To reduce noise from the timing measurement, the statistics of 200 test program runs (with identical commands) was evaluated. All axes, blending, syncing, etc are switched on except interpolation, so that the planer has to recalculate completely every tick. The stats show that the most calculation times are below 25us, however, there are peaks that can mess up the realtime requirements. With a RPi and e.g. a Xenomai RT patch, the maximum recommended sample rate for the planer would be about 1kHz (without interpolation, with interpolation a factor 5 is unproblematic), which is still suitable for most of the applications.

Motion profile of the example

  line {x:40, y:0, z:0, v:10, a:1000, tol:0 }
  arc  {x:30, y:10, z:0, cx:40, cy:10, cz:0, nx:0, ny:0,nz:1, v:10, a:1000, tol:0 }
  arc  {x:5, y:15, z:0, cx:25, cy:15, cz:0, nx:0, ny:0,nz:1, v:10, a:1000, tol:1 }
  line {x:5, y:10, z:0, v:10, a:1000, tol:1 }
  line {x:-25, y:10, z:0, v:100, a:1000, tol:1 }
  line {x:-50, y:0, z:0, v:100, a:250, tol:1 }
  line {x:-60, y:50, z:0, v:100, a:250, tol:1 }
  line {x:-70, y:60, z:0, v:100, a:250, tol:1 }
  line {x:-70, y:70, z:0, v:100, a:250, tol:1 }
  line {x:-60, y:80, z:0, v:100, a:250, tol:1 }
  line {x:0, y:70, z:0, v:100, a:250, tol:1 }
  line {x:60, y:60, z:0, v:100, a:250, tol:1 }
  line {x:70, y:-10, z:0, v:100, a:250, tol:1 }
  line {x:50, y:-20, z:0, v:100, a:250, tol:5 }
  line {x:-25, y:-10, z:0, v:100, a:250, tol:5 }
  line {x:0, y:-5, z:0, v:100, a:250, tol:5 }
  line {x:0, y:0, z:0, v:100, a:250, tol:5 }

Performance

  Maximum calculation time: 156.0us
  Average calculation time: 16.3us
  StdDev  calculation time: 3.0us
  Ticks < 10% of max time : 63.9%
  Ticks < 15% of max time : 99.8%
  Ticks < 20% of max time : 99.9%

The figure shows the calculation times over the motion progress.

x/y positions of the trajectory

Header

/*******************************************************************************
 * @file trajgen.h
 *
 * Allows to move the machine tool tip to specified positions, velocities and
 * accelerations by modifying a virtual machine position every generator cycle.
 * The output is always an updated position for every joint. The closed loop
 * controllers of the joints follow this position or, if the cannot, raise a
 * trailing error fault.
 *
 * @author: Stefan Wilhelm (c erberos@atwillys.de)
 * @license: BSD (see below)
 * @version: 1.0b
 * @standard C99
 *
 * -----------------------------------------------------------------------------
 *
 * Coordinated motion (coord planer, manual) are related the planer pose (tool
 * tip position and direction), t.m. a new pose is calculated first, then the
 * joint position are determined using inverse kinematics. The joint planers
 * update the scalar position of every joint individually, the planer pose is
 * updated (for status or equivalence) using forward kinematics. After the new
 * joint positions are computed, they are used to feed the interpolators. The
 * output of the interpolatoes is the final output of the trajgen and can be
 * used as command position for the closed loop controllers.
 *
 * As wrapper for the different trajectory planers, the main trajgen organises
 * the initialisation, reset and synchronisation of the submodules. It is based
 * on a finite state machine. This means the generator can be only in  one defined
 * state, and switching between states causes leaving the current state (wait
 * for the current motion to be finished and cleanup after the sub modules), and
 * entering a new state (initialise and switch to module run state). Hence,
 * switching takes at least one tick (cycle period).
 *
 * Usage:
 *
 *  Variables:
 *
 *      All data of the trajgen are stored in one single data structure of the
 *      type trajgen_t. This structure contains the configuration, the current
 *      state, the (requested) state to switch to, the last error occurred,
 *      current pose, kinematic data, coordinated planer data, manual op data,
 *      as well as data for individual joints. Latter are joint data structures
 *      containing data of the interpolators, joint planers, and the
 *      joint command position outputs.
 *
 *      You can decide how and where this structure is located, either as part
 *      of a bigger data structure, global, local, stack, heap, etc. Only few
 *      aspects are important:
 *
 *          (1) The location must not change, as the pointer is saved in a
 *              static variable.
 *
 *          (2) There can be only one main trajgen (but multiple sub-planers
 *              e.g. for simulation purposes).
 *
 *  Synchronisation and threading
 *
 *      Thread safety is not implemented due to platform dependencies. You must
 *      take care yourself that no other thread or shared memory write process
 *      interferes with any function of the trajgen. It absolutely assumes that
 *      the data are not changed during a function call.
 *
 *  Configuration and initialisation:
 *
 *      The trajgen is configured once in the startup phase of the machine.
 *      Create a variable of type trajgen_config_t and pass it together with
 *      the pointer to the main trajgen structure to trajgen_initialize();
 *      The config will not be modified by any internal function.
 *
 *  Reset:
 *
 *      Calling trajgen_reset() will reset the internal state and internal
 *      variables of the trajgen and all submodules to a "clean" disabled state.
 *      The config will not be changed. The state will be 0 (TRAJ_STATE_DISABLED)
 *      and is_done will be 1 (true).
 *
 *  Cyclic function:
 *
 *      Call trajgen_tick() with the sample rate that the servo position
 *      need to be updated. The configuration variable "interpolation_rate"
 *      decides how frequently the trajectory needs to be recalculated. For a
 *      closed loop sample rate of 2KHz and an interpolation rate of 4, the
 *      trajectory will be recalculated with 500Hz, the missing cycles will be
 *      filled by the cubic interpolator of each joint. This means also that
 *      synchronous outputs and condition handling will be done with 500Hz.
 *
 *  State switching:
 *
 *      Use trajgen_switch_state() to switch between the submodules. After calling
 *      the function, is_done will be 0 and remain zero until the current planer
 *      stopped moving, is cleaned up and the new state is in running state.
 *      However, the state might remain zero if the newly selected generator
 *      started moving already.
 *
 *      States:
 *
 *          TRAJ_STATE_DISABLED:
 *
 *              The trajgen is idle and does not perform any calculations or
 *              position updates.
 *
 *          TRAJ_STATE_COORDINATED:
 *
 *              The coordinated motion planer is running and processes its
 *              queue.
 *
 *          TRAJ_STATE_MAN:
 *
 *              The (coordinated) teleoperation planer is running and processes
 *              according its given input velocities.
 *
 *          TRAJ_STATE_JOINT:
 *
 *              The joint individual planers are active.
 *
 *  Submodule access:
 *
 *      You have full access to the individual planners and get in touch with
 *      each you want to use if you define the export-enable macros described
 *      below.
 *
 *  Errors:
 *
 *      Each of the trajgen and submodule functions that can cause errors return
 *      an error code. The convention is: code 0 (zero) means OK, nonzero means
 *      error. You should always treat trajgen errors that are called cyclic in
 *      trajgen_coord_tick(), as well as initialisation errors, as fatal and cause
 *      an e-stop by opening the machine's intermediate circuit immediately.
 *
 *      Every generator has a variable last_error, where you can query the last
 *      error occurred. This values are only valid if a function returned nonzero
 *      before (the variable is not explicitly reset to 0 if a function returns
 *      OK). The main trajgen's last_error is a structure containing one single
 *      encoded 16bit error value (type trajgen_error_details_t). This error
 *      code contains the
 *
 *          (1) the code
 *          (2) the joint, which is only valid if an interpolator or joint planer
 *              failed.
 *
 *      This error code allows to identify quite accurately what went wrong using
 *      a single number that can be saved or passed to the user space easily.
 *
 * -----------------------------------------------------------------------------
 * License header: Copyright (c) 2008-2014, Stefan Wilhelm. All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met: (1) Redistributions
 * of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer. (2) Redistributions in binary form must reproduce
 * the above copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the distribution.
 * (3) Neither the name of atwillys.de nor the names of its contributors may be
 * used to endorse or promote products derived from this software without specific
 * prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 ******************************************************************************/
 
#ifndef SW_TRAJGEN_H
#define SW_TRAJGEN_H
#ifdef __cplusplus
extern "C" {
#endif
 
/* <editor-fold defaultstate="collapsed" desc="Build config: types and libraries"> */
 
#if defined __linux__
#include <stdint.h>
#include <sys/types.h>
#elif defined __MACH__
#include <stdint.h>
#include <sys/types.h>
#else
#error "Can you please define the rttypes for this platform and send a patch? :)"
/*
#define int8_t char
#define uint8_t unsigned char
#define int16_t short
#define uint16_t unsigned short
#define int32_t int
#define uint32_t unsigned int
#define int64_t long
#define uint64_t unsigned long
#define float32_t float
#define float64_t double
#define size_t unsigned int
 */
#endif
 
/**
 * Allows to specify 32 bit or 64 bot float, unfortunately "float_t" is already
 * reserved on many frames, so "real_t" is used instead. Can be also redefined
 * as typedef. The macro is used to be able to switch it easily using the pre
 * compiler.
 */
#ifndef real_t
#define real_t double
#endif
 
#ifndef float32_t
#define float32_t float
#endif
 
/**
 * Explicit declaration of a boolean type. Can be switched for memory optimizsation
 * (e.g. char) or best data type for the processor (e.g. int)
 */
#ifndef bool_t
#define bool_t unsigned
#endif
 
/**
 * Explicit declaration of a byte, no matter if signed or unsigned. Normally
 * used for bit operations or 8bit port i/o.
 */
#ifndef byte_t
#define byte_t uint8_t
#endif
 
/**
 * "Auto include" float.h
 * Note: This allows you to include float.h or a RT optimised one yourself.
 */
#ifndef FLT_MAX
#include <float.h>
#endif
 
/**
 * "Auto include" math.h, characteristic macro: M_PI
 * Note: This allows you to include math.h or a RT optimised one yourself.
 */
#ifndef M_PI
#include <math.h>
#endif
 
 
/*
 * Defines the maximum number of joints the program can handle.
 */
#ifndef JOINT_MAX_JOINTS
#define JOINT_MAX_JOINTS 9
#endif
 
/*
 * The maximum number of joints that the trajectory generator supports
 */
#ifndef TG_MAX_JOINTS
#define TG_MAX_JOINTS JOINT_MAX_JOINTS
#endif
 
/*
 * The maximum size if the coordinated motion generator queue (fixed allocated)
 * Note: This is an intermediate fifo with big data structures and should be small.
 * You are supposed to dispatch and push new values from a realtime fifo whenever
 * possible.
 */
#ifndef TG_QUEUE_SIZE
#define TG_QUEUE_SIZE 32
#endif
 
/*
 * Defines then threshold when the coordinated motion planer sees numbers as
 * zero or not.
 */
#ifndef TG_RESOLUTION
#define TG_RESOLUTION 1e-6
#endif
 
/*
 * Smallest floating point number that is not zero. (Epsilon)
 */
#ifndef FLOAT_EPS
#define FLOAT_EPS (sizeof(real_t) == sizeof(double) ? FLT_EPSILON : DBL_EPSILON)
#endif
 
/*
 * Spline/bezier iterations to find the length
 */
#ifndef TG_CURVE_ACC
#define TG_CURVE_ACC (16)
#endif
 
/*
 * Sin/cos, can be replaced platform dependent with the corresponding builtin.
 * #define SINCOS(x, sx, cx) do { *(sx)=sin((x)); *(cx)=cos((x)); } while(0);
 */
 
/*
 * You can set your own memory clear (set to 0) function. By default a static
 * inline function is used that is independent of any external library.
 * #define ZERO_MEMORY(ptr, size)
 */
 
/*
 * Define this to force the trajgen to check argument pointers
 * #define ARG_POINTER_CHECKS
 */
 
/*
 * Normally you like to define your own joint type and config for the machine,
 * but if you to it has to be compliant with the joint_t here.
 * #define JOINT_DECL_NONE
 */
 
/*
 * You can switch off the interpolator feature here. Any settings related to
 * it will be ignored. The interpolation rate must be set to 1.
 * #define NO_INTERPOLATION
 */
 
/*
 * You can switch off the trajectory position/velocity synchronisation using:
 * #define NO_TRAJECTORY_SYNC
 */
 
/*
 * You can optionally export the interpolators or leave them
 * inline static in the implementation file to improve code optimisation.
 * Interpolator types and error definitions are not affected by this setting,
 * they are always exported.
 * #define EXPORT_INTERPOLATORS
 */
 
/*
 * You can optionally not export the joint planers or leave them
 * inline static in the implementation file to improve code optimisation.
 * Types and error definitions are not affected by this setting, only functions.
 * #define EXPORT_JOINT_TG
 */
 
/*
 * You can optionally not export the manual/joystick generator or leave it
 * inline static in the implementation file to improve code optimisation.
 * Types and error definitions are not affected by this setting, only functions.
 * #define EXPORT_MANUAL_TG
 */
 
/*
 * You can optionally not export the coordinated motion generator or leave it
 * inline static in the implementation file to improve code optimisation.
 * Types and error definitions are not affected by this setting, only functions.
 * #define EXPORT_COORD_TG
 */
 
/*
 * You can switch off the interpolator feature here. Any settings related to
 * it will be ignored. The interpolation rate must be set to 1.
 * #define NO_MOTION_BLENDING
 */
 
/*
 * Defining this will enable stderr debug messages. Note: this can be quite
 * detailed. (command line option -DTG_DEBUG_LEVEL=<1,2,3>)
 * #define TG_DEBUG_LEVEL <0,1,2...> (default undefined)
 */
 
/*
 * Math "resolution", values define when a dimension or angle is treated
 * as zero.
 * #define TG_D_RES (default TG_RESOLUTION)
 * #define TG_A_RES (default TG_RESOLUTION)
 */
 
/* </editor-fold> */
 
/* <editor-fold defaultstate="collapsed" desc="pose_*: Robot pose types and operations"> */
 
/********************************************************************
 *
 * Data structure defining a position of logical axes:
 *
 *  Translation : x,z,y
 *  Rotation    : a,b,c
 *  Auxiliary translation: u,v,w
 *
 ********************************************************************/
 
/* Axis enumerators for double vector access */
typedef enum
{
  POSE_X_AXIS = 0, POSE_Y_AXIS, POSE_Z_AXIS, POSE_A_AXIS, POSE_B_AXIS,
  POSE_C_AXIS, POSE_U_AXIS, POSE_V_AXIS, POSE_W_AXIS
} pose_axis_t;
 
/* Pose by axes */
typedef struct { real_t x, y, z, a, b, c, u, v, w; } pose_t;
typedef struct { real_t x, y, z; } pose_vector_t;
#define pose_position_t pose_vector_t
 
/* Line definition */
typedef struct
{
  pose_position_t start, end; /* Start/end point */
  pose_vector_t u;        /* Unit vector of translation */
  real_t tm;              /* Magnitude /length */
} pose_line_t;
 
/* Circle definition */
typedef struct
{
  pose_position_t cp;     /* Circle centre point */
  pose_vector_t nv;       /* Circle normal vector */
  pose_vector_t rt,rp,rh; /* radius vectors: tangent, perpendicular, helix */
  real_t l, r, a, s;      /* Length, start radius, angle, parallel component coeff */
} pose_circle_t;
 
/* Curve (bezier/spline) definition */
typedef struct
{
  #ifdef WITH_CURVES
  pose_position_t start,end; /* End point */
  pose_vector_t suv, euv;    /* Start/end unit vectors */
  real_t len;                /* Length of the segment */
  real_t c[3][4];            /* Path component coefficients */
  real_t d[TG_CURVE_ACC];    /* Correction */
  #endif
} pose_curve_t;
 
typedef real_t pose_array_t[9];
 
/**
 * Inliners (These macros are not upper case)
 * Using byval for sources and by pointer for dst, so if somthing is switched
 * the compiler will raise an error.
 */
/* Assignment p=0 */
#define pose_set_zero(p) { \
  (p)->x=(p)->y=(p)->z=(p)->a=(p)->b=(p)->c=(p)->u=(p)->v=(p)->w= 0.0; \
}
 
#define pose_set_all(p, val) { \
  (p)->x=(p)->y=(p)->z=(p)->a=(p)->b=(p)->c=(p)->u=(p)->v=(p)->w=val; \
}
 
/**
 * Checks if all elements are valid numbers (not nan) and finite.
 */
#define pose_isfinite(p) ( \
  isfinite((p).x) && isfinite((p).y) && isfinite((p).z) && \
  isfinite((p).a) && isfinite((p).b) && isfinite((p).c) && \
  isfinite((p).u) && isfinite((p).v) && isfinite((p).w) \
)
 
/* Assignment dst = src */
/* stfwi: no memcpy, let the compiler handle optimisation */
#define pose_set(dst, src) { \
  (dst)->x = (src).x; (dst)->y = (src).y; (dst)->z = (src).z; \
  (dst)->a = (src).a; (dst)->b = (src).b; (dst)->c = (src).c; \
  (dst)->u = (src).u; (dst)->v = (src).v; (dst)->w = (src).w; \
}
 
/* Difference po = p1-p2 */
#define pose_diff(p1, p2, po) { \
  (po)->x = (p1).x-(p2).x; (po)->y = (p1).y-(p2).y; (po)->z=(p1).z - (p2).z; \
  (po)->a = (p1).a-(p2).a; (po)->b = (p1).b-(p2).b; (po)->c=(p1).c - (p2).c; \
  (po)->u = (p1).u-(p2).u; (po)->v = (p1).v-(p2).v; (po)->w=(p1).w - (p2).w; \
}
 
/* Sum po = p1+p2 */
#define pose_sum(p1, p2, po){ \
  (po)->x = (p1).x+(p2).x; (po)->y = (p1).y+(p2).y; (po)->z = (p1).z+(p2).z; \
  (po)->a = (p1).a+(p2).a; (po)->b = (p1).b+(p2).b; (po)->c = (p1).c+(p2).c; \
  (po)->u = (p1).u+(p2).u; (po)->v = (p1).v+(p2).v; (po)->w = (p1).w+(p2).w; \
}
 
/* Accumulate po += p2 */
#define pose_acc(po, p2){ \
  (po)->x += (p2).x; (po)->y += (p2).y; (po)->z += (p2).z; \
  (po)->a += (p2).a; (po)->b += (p2).b; (po)->c += (p2).c; \
  (po)->u += (p2).u; (po)->v += (p2).v; (po)->w += (p2).w; \
}
 
/* Substract po -= p2 */
#define pose_sub(po, p2){ \
  (po)->x -= (p2).x; (po)->y -= (p2).y; (po)->z -= (p2).z; \
  (po)->a -= (p2).a; (po)->b -= (p2).b; (po)->c -= (p2).c; \
  (po)->u -= (p2).u; (po)->v -= (p2).v; (po)->w -= (p2).w; \
}
 
/* Negation po = -po */
#define pose_neg(po){ \
  (po)->x = -(po)->x; (po)->y = -(po)->y; (po)->z = -(po)->z; \
  (po)->a = -(po)->a; (po)->b = -(po)->b; (po)->c = -(po)->c; \
  (po)->u = -(po)->u; (po)->v = -(po)->v; (po)->w = -(po)->w; \
}
 
/* Scalar multiply po *= (double) d */
#define pose_scale(po, d){ \
  (po)->x *= (d); (po)->y *= (d); (po)->z *= (d); \
  (po)->a *= (d); (po)->b *= (d); (po)->c *= (d); \
  (po)->u *= (d); (po)->v *= (d); (po)->w *= (d); \
}
 
/* Trim every axis to a maximum value (defined for every axis) */
#define pose_trim_all_upper(po, max) { \
  if ((po)->x > (max).x) (po)->x = (max).x; \
  if ((po)->y > (max).y) (po)->y = (max).y; \
  if ((po)->z > (max).z) (po)->z = (max).z; \
  if ((po)->a > (max).a) (po)->a = (max).a; \
  if ((po)->b > (max).b) (po)->b = (max).b; \
  if ((po)->c > (max).c) (po)->c = (max).c; \
  if ((po)->u > (max).u) (po)->u = (max).u; \
  if ((po)->v > (max).v) (po)->v = (max).v; \
  if ((po)->w > (max).w) (po)->w = (max).w; \
}
 
/* Trim every axis to a minimum value (defined for every axis) */
#define pose_trim_all_lower(po, min) { \
  if ((po)->x < (min).x) (po)->x = (min).x; \
  if ((po)->y < (min).y) (po)->y = (min).y; \
  if ((po)->z < (min).z) (po)->z = (min).z; \
  if ((po)->a < (min).a) (po)->a = (min).a; \
  if ((po)->b < (min).b) (po)->b = (min).b; \
  if ((po)->c < (min).c) (po)->c = (min).c; \
  if ((po)->u < (min).u) (po)->u = (min).u; \
  if ((po)->v < (min).v) (po)->v = (min).v; \
  if ((po)->w < (min).w) (po)->w = (min).w; \
}
 
/* Scalar multiply po *= (double) d */
/* StfWi: memcmp() with reference 0[9]? */
#define pose_is_zero(p) ( \
  ((p).x == 0.0 && (p).y == 0.0 && (p).z == 0.0 && \
  (p).a == 0.0 && (p).b == 0.0 && (p).c == 0.0 && (p).u == 0.0 && \
  (p).v == 0.0 && (p).w == 0.0) \
)
 
/* Scalar multiply po *= (double) d */
#define pose_is_all_greater_equal_zero(p) ( \
  ((p).x >= 0.0 && (p).y >= 0.0 && \
  (p).z >= 0.0 && (p).a >= 0.0 && (p).b >= 0.0 && (p).c >= 0.0 && \
  (p).u >= 0.0 && (p).v >= 0.0 && (p).w >= 0.0) \
)
 
/* </editor-fold> */
 
/* <editor-fold defaultstate="collapsed" desc="joint_*: Robot joint decls"> */
 
/*******************************************************************************
 *
 * Represents one joint of the machine, including configuration, status and
 * command transfer.
 *
 ******************************************************************************/
 
#ifdef JOINT_DECL_NONE
/*
 * NOTE: You have to define the joints compatible to the minimum settings
 */
#elif defined JOINT_DECL_FULL
 
/**
 * Type of joint
 */
typedef enum
{
  JOINT_TYPE_NONE = 0,    /* Not defined */
  JOINT_AXIS_TYPE_LINEAR, /* Linear axis, unit will be meters */
  JOINT_AXIS_TYPE_ROTARY  /* Polar axis, unit will be degrees */
} joint_axis_type_t;
 
/**
 * Feedback type of a joint
 */
typedef enum
{
  /* E.g. stepper without encoder */
  JOINT_POSITION_FEEDBACK_NONE = 0,
  /* Incremental encoder, requires homing for all except axis free motion */
  JOINT_POSITION_FEEDBACK_INCREMENTAL,
  /* Incremental encoder, requires no homing */
  JOINT_POSITION_FEEDBACK_ABSOLUTE
} joint_position_feedback_t;
 
/**
 * I/O logic of various joint inputs/outputs, e.g. limit switches, index ...
 */
typedef enum
{
  JOINT_LOGIC_ACTIVE_HIGH = 0x00,
  JOINT_LOGIC_ACTIVE_LOW  = 0x01, /* used with xor */
  JOINT_LOGIC_DISABLED
} joint_logic_t;
 
/**
 * Bit field of joint-related
 */
typedef union
{
  uint16_t all;
  struct
  {
    /* The joint/axis is enabled */
    unsigned enabled : 1;
    /* The amplifier is enabled */
    unsigned amplifier_enabled : 1;
    /* The joint is homed */
    unsigned homed : 1;
    /* The trajectory planer is at the goal position */
    unsigned trajectory_done : 1;
    /* The joint is in the position tolerance of the goal position */
    unsigned in_position : 1;
    /* The joint does not move (in standstill velocity tolerance) */
    unsigned standstill : 1;
    /* The joint amplifier has an error */
    unsigned amplifier_fault : 1;
    /* The joint encoder has an error */
    unsigned encoder_fault : 1;
    /* The home switch is activated (also depends on config) */
    unsigned home_index : 1;
    /* The positive limit switch is activated (depends also on config) */
    unsigned positive_limit : 1;
    /* The negative limit switch is activated (depends also on config) */
    unsigned negative_limit : 1;
    /* Fill up remaining bits */
    unsigned reserved : 5;
  } bits;
} joint_status_t;
 
typedef struct
{
  /* Joint type, linear or polar/rotary */
  joint_axis_type_t type;
  /* The type of feedback */
  joint_position_feedback_t feedback_type;
  /* The positive software limit in meters/degrees */
  real_t positive_software_limit;
  /* The negative software limit in meters/degrees */
  real_t negative_software_limit;
  /* Scales the axis position (normally increments) to meters/degrees */
  real_t position_to_meters_scaler;
  /* The maximum velocity the joint can do in m/s / degrees/s */
  real_t max_velocity;
  /* The maximum acceleration the joint can do in m/s / degrees/s */
  real_t max_acceleration;
  /* Max trailing error at speed=0 in meters/degrees */
  real_t max_trailing_error_min;
  /* Max trailing error at speed=max_velocity in meters/degrees */
  real_t max_trailing_error_max;
  /* The deceleration used for ESTOP full break. It can exceed the max_acceleration. */
  real_t estop_deceleration;
  /* Max position deviation at standstill to say the measured joint position is */
  /* accurate enough, in m/s / degrees/s */
  real_t in_position_tolerance;
  /* Max speed deviation at standstill to say the joint does not move anymore, */
  /* m/s / degrees/s */
  real_t standstill_tolerance;
  /* 1 = a limit switch exists and has to be respected */
  joint_logic_t positive_hardware_limit;
  /* 1 = a limit switch exists and has to be respected */
  joint_logic_t negative_hardware_limit;
  /* If not, a limit switch will be used OR, if not limit switch, the mechanical */
  /* joint endpoint (measured using trailing error) */
  joint_logic_t home_index;
} joint_config_t;
 
typedef struct
{
  /* Axis configuration */
  joint_config_t config;
  /* State of the joint, see joint_state_t */
  joint_status_t status;
  /* The interpolated position of the trajectory generator (for all kinds of trajgen) */
  real_t command_position;
  /* The interpolated velocity of the trajectory generator (for all kinds of trajgen) */
  real_t command_velocity;
  /* The interpolated acceleration of the trajectory generator (for all kinds of trajgen) */
  real_t command_acceleration;
  /* The scaled position measured with the encoders */
  real_t position;
  /* Position captured by a latch operation, e.g. homing index */
  real_t latched_position;
} joint_t;
 
#else
 
/**
 * Minimum joint definition required to use the trajgen
 */
typedef struct
{
  struct { real_t max_velocity, max_acceleration; } config;
  real_t command_position, command_velocity, command_acceleration, position;
} joint_t;
 
#endif
 
/* </editor-fold> */
 
/* <editor-fold defaultstate="collapsed" desc="trajgen_error_*: Trajgen error main decls"> */
 
/*******************************************************************************
 *
 *
 * Main trajectory generator error types and definitions.
 *
 *
 ******************************************************************************/
 
/**
 * Error declarations for the trajgen itself. However, because this is the "master"
 * File of the generator, it will return error codes defined in trajgen_error.h,
 * which includes joint, code and error source.
 */
enum
{
  /* Main controller */
  TRAJ_ERROR_OK = 0,
  TRAJ_ERROR_ERROR,
  TRAJ_ERROR_NULL_POINTER,
  TRAJ_ERROR_NUMERIC,
  TRAJ_ERROR_CONFIG_LAST_USED_JOINT_INVALID,
  TRAJ_ERROR_CONFIG_INTERPOLATION_RATE_INVALID,
  TRAJ_ERROR_CONFIG_OVERRIDE_INVALID,
  TRAJ_ERROR_CONFIG_COMPILE_SETTING_INVALID,
  TRAJ_ERROR_INVALID_STATE, /* The motion state is not valid (Internal problem) */
  TRAJ_ERROR_INVALID_SWITCHING_STATE, /* The operating mode to set is invalid */
  TRAJ_ERROR_INVALID_JOINT_NO,
 
  /* Kinematics */
  KINEMATICS_ERR_ERROR,
  KINEMATICS_ERR_NULL_POINTER,
  KINEMATICS_ERR_INIT_FUNCTION_NULL,
  KINEMATICS_ERR_FORWARD_FAILED,
  KINEMATICS_ERR_INVERSE_FAILED,
  KINEMATICS_ERR_RESET_FAILED,
 
  /* Interpolators */
  INTERPOLATOR_ERROR,
  INTERPOLATOR_ERROR_INIT_ARG_INVALID,
  INTERPOLATOR_ERROR_QUEUE_FULL,
  INTERPOLATOR_ERROR_OFFSET_IP_NULLPOINTER,
  INTERPOLATOR_ERROR_INTERPOLATE_ARG_NULLPOINTER,
  INTERPOLATOR_ERROR_NOT_RESET,
  INTERPOLATOR_ERROR_NULLPOINTER,
 
  /* Coordinated planer */
  TP_ERR_ERROR,
  TP_ERR_TP_NULL_POINTER,
  TP_ERR_ABORTING,
  TP_ERR_QUEUE_PUT_FAILED,
  TP_ERR_INVALID_PARAM,
  TP_ERR_QUEUE_FULL,
  TP_ERR_QUEUE_TO_MANY_ELEMENTS_TO_REMOVE,
  TP_ERR_INVALID_MOTION_TYPE,
  TP_ERR_INVALID_SPEED,
  TP_ERR_INVALID_ACCEL,
  TP_ERR_INVALID_POSE,
  TP_ERR_SEGMENT_LENGTH_ZERO,
  TP_ERR_INVALID_SAMPLE_INTERVAL,
  TP_ERR_ALREADY_MOVING,
  TP_ERR_UNIT_VECTOR_CALC_INVALID_TYPE,
  TP_ERR_REF_POSITION_INVALIDATED_DURING_MOTION,
 
  /* Joint planers */
  TRAJGEN_FREE_ERROR_ERROR,
  TRAJGEN_FREE_ERROR_INIT_NULLPOINTER,
  TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_ACCEL,
  TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_VELOCITY,
  TRAJGEN_FREE_ERROR_INIT_INVALID_SAMPLE_INTERVAL,
  TRAJGEN_FREE_ERROR_INVALID_ACCELERATION,
 
  /* Manual operation planer */
  TG_MAN_ERROR_ERROR,
  TG_MAN_ERROR_INIT_NULLPOINTER,
  TG_MAN_ERROR_INIT_INVALID_MAX_ACCEL,
  TG_MAN_ERROR_INIT_INVALID_MAX_VELOCITY,
  TG_MAN_ERROR_INIT_INVALID_SAMPLE_INTERVAL,
  TG_MAN_ERROR_INVALID_ACCELERATION
};
 
enum { KINEMATICS_ERR_OK=0 };
enum { INTERPOLATOR_OK=0 };
enum { TP_ERR_OK=0 };
enum { TRAJGEN_FREE_ERROR_OK=0 };
enum { TG_MAN_ERROR_OK=0 };
 
 
typedef uint16_t trajgen_error_t;
#define kinematics_error_t trajgen_error_t
#define trajgen_coord_error_t trajgen_error_t
#define interpolator_error_t trajgen_error_t
#define interpolator_error_t trajgen_error_t
#define trajgen_jointtg_error_t trajgen_error_t
#define trajgen_man_error_t trajgen_error_t
 
/**
 * Main error type
 */
typedef union
{
  int16_t errno;
  struct
  {
    unsigned code : 12;
    unsigned joint : 4;
  } sel;
} trajgen_error_details_t;
 
/**
 * Writes a text version of a trajgen error into *errstr
 * @param uint16_t errnum
 * @param char* errstr
 * @param unsigned length
 */
extern const char* trajgen_errstr(uint16_t errnum, char* errstr, unsigned length);
 
/* </editor-fold> */
 
/* <editor-fold defaultstate="collapsed" desc="interpolator_*: Interpolation"> */
 
/********************************************************************
 
 * Cyclic Cubic interpolation, used to interpolate joint command position for
 * the closed loop controllers between the way points produced by the trajectory
 * generators. This allows to enhance the system performance, especially when
 * high filter sample rates are required.
 *
 ********************************************************************/
 
/**
 * Cubic interpolator
 */
typedef struct
{
  uint16_t n;           /* Specifies number of queued points */
  real_t T;             /* The trajectory planer sample time */
  real_t Ti;            /* The time between two interpolated points */
  real_t t;             /* Current time, reset on push */
  real_t x0, x1, x2, x3;/* Buffer for the interpolation, used like a FIFO */
  real_t w0, w1;        /* Way points */
  real_t v0, v1;        /* Velocities at the way points */
  real_t a, b, c, d;    /* Coefficients polynomial, a*x^3 + b*x^2 + c*x + d */
} interpolator_t;
 
 
#ifdef EXPORT_INTERPOLATORS
/**
 * Initialise the interpolator.
 */
extern interpolator_error_t interpolator_init(interpolator_t*, real_t sample_interval,
    unsigned interpolation_rate);
 
/**
 * Reset the interpolator, clear the queue to the specified position value.
 */
extern interpolator_error_t interpolator_reset(interpolator_t*, real_t initial_position);
/**
 * Performs one interpolation tick. All pointer argument must refer to existing
 * variables (not NULL).
 */
extern interpolator_error_t interpolator_interpolate(interpolator_t*, real_t *x,
    real_t *v, real_t *a, real_t *j);
/**
 * Push a new position into the fifo. That is only allowed when the fifo is not full.
 */
extern interpolator_error_t interpolator_push(interpolator_t*, real_t point);
/**
 * Returns nonzero if the fifo is not full. You must push a value before the
 * next interpolation step, or the last value in the fifo will be pushed
 * automatically.
 */
extern bool_t interpolator_need_update(interpolator_t*);
 
#endif
/* </editor-fold> */
 
/* <editor-fold defaultstate="collapsed" desc="kinematics_*: Kinematics"> */
 
/*******************************************************************************
 *
 * Calculates the position and direction of the end effector (where the tool is
 * mounted) in three dimensional space based in the position of all relevant
 * machine joints and vice versa. Several kinematic machine models are available
 * and implementing own models is possible.
 *
 ******************************************************************************/
 
typedef struct
{
  /**
   * The reset kinematics function sets all its arguments to their proper
   * values at the known initial position. When called, these should be set,
   * when known, to initial values, e.g., from an INI file. If the home
   * kinematics can accept arbitrary starting points, these initial values
   * should be used.
   */
  kinematics_error_t(*reset)(pose_t *pose, real_t *joint_positions[]);
 
  /**
   * The forward kinematics take joint values and determine world coordinates,
   * given forward kinematics flags to resolve any ambiguities. The inverse
   * flags are set to indicate their value appropriate to the joint values
   * passed in.
   */
  kinematics_error_t(*forward)(real_t *joint_positions[], pose_t *pose);
 
  /**
   * The inverse kinematics take world coordinates and determine joint values,
   * given the inverse kinematics flags to resolve any ambiguities. The forward
   * are set to indicate their value appropriate to the world coordinates
   * passed in.
   */
  kinematics_error_t(*inverse)(pose_t *pose, real_t *joint_positions[]);
 
} kinematics_t;
 
extern kinematics_error_t kinematics_initialize(kinematics_t *kin, kinematics_t
    device_kinematics);
 
/* </editor-fold> */
 
/* <editor-fold defaultstate="collapsed" desc="trajgen_coord_*, trajgen_sg_*: Coordinated Motion"> */
 
/********************************************************************
 *
 * Planer for coordinated queued motion with blending, i/o synchronisation and
 * motion synchronisation.
 *
 ********************************************************************/
 
/**
 * Naming of motion task types (typedef is int'ed for bit packing)
 */
#define trajgen_sg_motion_type_t int
enum
{
  SG_INVALID  = 0x00,
  SG_LINEAR   = 0x01,
  SG_CIRCULAR = 0x02,
  SG_CURVE    = 0x03,
  SG_END_OF_MOTION
};
 
/**
 * Type naming of motion synchronisation (typedef is int'ed for bit packing)
 */
#define trajgen_coord_motion_sync_type_t int
enum
{
  TP_SYNC_NONE = 0x00,
  TP_SYNC_VELOCITY = 0x01,
  TP_SYNC_POSITION = 0x02
};
 
/**
 * Type for specification of synchronised motion (motion dependent on external
 * positions/velocities).
 */
typedef struct
{
  /**
   * Scales from the reference position/velocity (e.g. spindle position/speed)
   * to the scalar position/velocity of the trajectory when multiplied.
   */
  real_t scaler;
  /*
   * Position sync: The current (input) reference position
   * Velocity sync: The current (input) reference velocity
   */
  real_t reference;
  /* INTERNAL USE: Accumulates the position-synchronized segment lengths of
   * all surpassed segments for position sync.
   */
  real_t i_offset;
  /**
   * The type of synchronisation, for subsequent motion, default is TP_SYNC_NONE
   */
  trajgen_coord_motion_sync_type_t type: 4;
  /**
   * INTERNAL USE: Saves if the position offset does not require to be updated
   * before starting a position synced motion.
   */
  bool_t i_isset: 1;
} trajgen_coord_motion_sync_t;
 
/**
 * Coordinated planer configuration
 */
typedef struct
{
  /* Sample interval (cycle interval time) */
  real_t sample_interval;
  /* Max velocity allowed by machine constraints (normally set in a config file) */
  real_t max_velocity;
  /* Max acceleration allowed by machine constraints (normally set in a config file) */
  real_t max_acceleration;
} trajgen_coord_config_t;
 
/**
 * Mask type for synchronised digital I/O states (bit field)
 */
typedef uint16_t trajgen_coord_syncdio_t;
 
/**
 * Synchronised digital I/Os to set/reset when the next queued task is shifted.
 */
typedef struct
{
  /* the bits to be set to HIGH */
  trajgen_coord_syncdio_t set;
  /* the bits to be set to LOW */
  trajgen_coord_syncdio_t clear;
} trajgen_coord_syncdio_cmd_t;
 
/**
 * Task data specialisation for line motion
 */
typedef struct
{
  pose_line_t xyz, abc, uvw;
} trajgen_sg_line_t;
 
/**
 * Task data specialisation for circle motion
 */
typedef struct
{
  pose_circle_t xyz;
  pose_line_t abc, uvw;
} trajgen_sg_arc_t;
 
/**
 * Task data specialisation for curve motion
 */
typedef struct
{
  #ifdef WITH_CURVES
  pose_curve_t xyz;
  #else
  pose_line_t xyz;
  #endif
  pose_line_t abc, uvw;
} trajgen_sg_curve_t;
 
/**
 * Coordinated planer task task type, used in the queue.
 */
typedef struct
{
  union {
    trajgen_sg_line_t line;     /* Specific line data */
    trajgen_sg_arc_t arc;       /* Specific arc/circle/helix data */
    trajgen_sg_curve_t curve;  /* Specific curve/nurbs data */
  } coords;
 
  /**
   * segment's serial number
   */
  uint32_t id;
  /**
   * The scalar path length that the pose moved already in this segment, values
   * are from 0..target
   */
  real_t progress;
  /**
   * Scalar length of the path segment
   */
  real_t length;
  /**
   * Speed set by the user speed (requested speed)
   */
  real_t v;
  /**
   * Acceleration set by the user (requested acceleration)
   */
  real_t a;
  /**
   * Internally calculated (keep track of current step (vel * cycle_time) )
   */
  real_t current_velocity;
  /**
   * during the blend at the end of this move, stay within this distance from
   * the path
   */
  real_t blending_tolerance;
  /**
   * velocity below which we should start blending
   */
  real_t blending_velocity;
  /**
   * synched DIO's for this move. what to turn on/off
   */
  trajgen_coord_syncdio_cmd_t sync_dio;
  /**
   *  SG_LINEAR (coords.line) or
   */
  trajgen_sg_motion_type_t motion_type: 4;
  /**
   * The type of synchronisation, default is TP_SYNC_NONE
   */
  trajgen_coord_motion_sync_type_t sync_type: 4;
  /**
   * this motion is being executed
   */
  bool_t is_active: 1;
  /**
   * segment is being blended into following segment
   */
  bool_t is_blending: 1;
  /**
   * Feed scale, etc, enable bits for this move
   */
  bool_t override_enabled: 1;
} trajgen_sg_t;
 
typedef struct
{
  /* ptr to the tcs */
  trajgen_sg_t queue[TG_QUEUE_SIZE];
  /* size of queue */
  uint16_t size;
  /* number of tcs now in queue */
  uint16_t num_elements;
  /* indices to next to get, next to put */
  uint16_t start, end;
} trajgen_queue_t;
 
typedef struct
{
  real_t override;
  pose_t last_position;
} trajgen_coord_internals_t;
 
typedef struct
{
  /* Static configuration, set during initialisation */
  trajgen_coord_config_t config;
  /* Last error that occurred, only valid if a function does not return 0 */
  trajgen_coord_error_t last_error;
  /* The buffer containing queued segments */
  trajgen_queue_t queue;
  /* Defines for subsequent motion where spindle synchronisation is required */
  trajgen_coord_motion_sync_t sync;
  /* Will be added to (only) the next pushed segments and transferred to
   * sync_dio_current when the elements are processed */
  trajgen_coord_syncdio_cmd_t sync_dio_command;
  /* Contains the actual digital i/o word that has to be transferred to the HAL
   * or hardware port register */
  trajgen_coord_syncdio_t sync_dio_current;
  /* Internally used current values, DO NOT MODIFY */
  trajgen_coord_internals_t _i;
  /* number of motions blending */
  uint16_t num_queued_active;
  /* Id of the actual queued segment, can be used to identify the program line
   * in gnc programs */
  uint32_t sg_id;
  /* Id of the next segment that is pushed into the queue */
  uint32_t next_sg_id;
  /* Subsequent motions: stay within this distance of the programmed path during
   * blends */
  real_t blending_tolerance;
  /* Actual position/pose of the trajectory */
  pose_t pose;
  /* End position of the last queued segment */
  pose_t end_pose;
  /* Velocity scaling, normal value is 1.0 (means 100% = specified speed) */
  real_t override;
  /* Abort in progress */
  bool_t is_aborting;
  /* Motion paused */
  bool_t is_pausing;
  /* Queue empty and goal position reached */
  bool_t is_done;
  /* Subsequent motion: Flags defining which speed overrides are allowed */
  uint8_t override_enabled;
} trajgen_coord_t;
 
#ifdef EXPORT_COORD_TG
/**
 * Initialise the planer
 */
extern trajgen_coord_error_t trajgen_coord_init(trajgen_coord_t*, trajgen_coord_config_t config);
/**
 * Reset all mutable states, clear queue, but not the config.
 */
extern trajgen_coord_error_t trajgen_coord_reset(trajgen_coord_t*);
/**
 * Set the current pose. Use this for initialising or if axes moved manually.
 */
extern trajgen_coord_error_t trajgen_coord_set_position(trajgen_coord_t*, pose_t pos);
/**
 * Move a line
 */
extern trajgen_coord_error_t trajgen_coord_add_line(trajgen_coord_t*, pose_t end,
    real_t velocity, real_t acceleration);
/**
 * Move a circle
 */
extern trajgen_coord_error_t trajgen_coord_add_arc(trajgen_coord_t*, pose_t end,
    pose_position_t center, pose_vector_t normal, unsigned n_turns, real_t velocity,
    real_t acceleration);
/**
 * Move a curve/nurbs segment
 */
extern trajgen_coord_error_t trajgen_coord_add_curve(trajgen_coord_t* tp,
    pose_t end, pose_vector_t start_ctrl_point, pose_vector_t end_ctrl_point,
    real_t velocity, real_t acceleration);
/**
 * Pause motion
 */
extern trajgen_coord_error_t trajgen_coord_pause(trajgen_coord_t*);
/**
 * Resume from pause
 */
extern trajgen_coord_error_t trajgen_coord_resume(trajgen_coord_t*);
/**
 * Stop motion, clear the queue
 */
extern trajgen_coord_error_t trajgen_coord_abort(trajgen_coord_t*);
/**
 * Tick: has to be called with the configured sample frequency.
 */
extern trajgen_coord_error_t trajgen_coord_tick(trajgen_coord_t*);
/**
 * Returns nonzero if the queue is full, so you have to wait until pushing
 * next motion tasks.
 */
extern bool_t trajgen_coord_is_queue_full(trajgen_coord_t*);
/**
 * Returns nonzero if all motion has finished and the planer pose reached the
 * destination position.
 */
extern bool_t trajgen_coord_is_done(trajgen_coord_t*);
/**
 * Returns the current pose
 */
extern pose_t trajgen_coord_get_position(trajgen_coord_t*);
/**
 * Returns the ID of the actually processed task
 */
extern uint32_t trajgen_coord_get_current_id(trajgen_coord_t*);
/**
 * Returns the number of currently queued tasks.
 */
extern uint16_t trajgen_coord_get_num_queued(trajgen_coord_t*);
/**
 * Returns maximum size of the queue
 */
extern uint16_t trajgen_coord_get_queue_size(trajgen_coord_t*);
/**
 * Returns returns the number of currently processed tasks. That can be greater
 * one e.g. when blending.
 */
extern uint16_t trajgen_coord_get_num_queued_active(trajgen_coord_t*);
 
#endif
/* </editor-fold> */
 
/* <editor-fold defaultstate="collapsed" desc="trajgen_jointtg_*: Separate joint motion"> */
 
/********************************************************************
 *
 * Simple trajectory planner for one joint. When enabled, corrects the joint
 * position based on a given command position and command velocity (maximum
 * velocity when override == 1.0).
 *
 * Can be used for absolute motion, relative motion, position correction (
 * (e.g. mouse or jogwheel).
 *
 ********************************************************************/
 
typedef struct
{
  /* Maximum acceleration of this joint for the joint trajgen */
  real_t max_acceleration;
  /* Maximim positive and negative velocity */
  real_t max_velocity;
  /* Sample interval, seconds */
  real_t sample_interval;
} trajgen_jointtg_config_t;
 
typedef struct
{
  /* The last error that occurred, only valid if a function returns nonzero */
  trajgen_jointtg_error_t last_error;
  /* Configuration */
  trajgen_jointtg_config_t config;
  /* Internal actual speed */
  real_t velocity;
  /* Actual position (also returned by trajgen_jointtg_get_position()) */
  real_t position;
  /* The position where the tp shall move to */
  real_t command_position;
  /* The maximum velocity of this move */
  real_t command_velocity;
  /* Enabled flag */
  bool_t is_enabled;
  /* Pause flag */
  bool_t is_pause;
  /* Done flag */
  bool_t is_done;
} trajgen_jointtg_t;
 
#ifdef EXPORT_JOINT_TG
 
/**
 * Initializes the joint generator
 * @param trajgen_jointtg_t *tp
 * @param trajgen_jointtg_config_t config
 * @return trajgen_jointtg_error_t
 */
extern trajgen_jointtg_error_t trajgen_jointtg_initialize(trajgen_jointtg_t *tp,
    trajgen_jointtg_config_t config);
 
/**
 * Resets the manual operation generator except the configuration.
 * @return trajgen_jointtg_error_t
 */
extern trajgen_jointtg_error_t trajgen_jointtg_reset(trajgen_jointtg_t *tp);
 
/**
 * Aborts the motion (force velocity to 0.0)
 * @return
 */
extern trajgen_jointtg_error_t trajgen_jointtg_set_enabled(trajgen_jointtg_t *tp,
    bool_t enabled);
 
/**
 * Input a new command position for the joint
 * @return trajgen_jointtg_error_t
 */
extern trajgen_jointtg_error_t trajgen_jointtg_set_command_position(trajgen_jointtg_t *tp,
    real_t position);
 
/**
 * Input a new velocity for the joint
 * @return trajgen_jointtg_error_t
 */
extern trajgen_jointtg_error_t trajgen_jointtg_set_command_velocity(trajgen_jointtg_t *tp,
    real_t velocity);
 
/**
 * Returns the last generated pose (output)
 * @return pose_t
 */
extern real_t trajgen_jointtg_get_current_position(trajgen_jointtg_t *tp);
 
/**
 * Sets the internal position. Used to update when the amplifiers/closed loop
 * controls are off
 * @param pose_t position
 * @return pose_t
 */
extern trajgen_jointtg_error_t trajgen_jointtg_set_current_position(trajgen_jointtg_t *tp,
    real_t position);
 
/**
 * Calculates a new position from the command velocity
 * @return pose_t
 */
extern trajgen_jointtg_error_t trajgen_jointtg_tick(trajgen_jointtg_t *tp);
 
/**
 * Returns true (nonzero) if the command velocity and the actual total velocity
 * is zero.
 * @return bool_t
 */
extern bool_t trajgen_jointtg_is_done(trajgen_jointtg_t *tp);
 
#endif
/* </editor-fold> */
 
/* <editor-fold defaultstate="collapsed" desc="trajgen_man_*: Remote/manually operated motion"> */
 
/********************************************************************
 *
 * Trajectory generator coordinated motion based on input velocities
 * (e.g. Joystick). Output is a pose, which has to be converted to the individual
 * joint positions using inverse kinematics.
 *
 ********************************************************************/
 
typedef struct
{
  /* Maximum positive and negative velocity for all axes */
  pose_t max_velocity;
  /* Maximum acceleration for each axis */
  pose_t max_acceleration;
  /* Sample interval, seconds */
  real_t sample_interval;
} trajgen_man_config_t;
 
typedef struct
{
  /* The last error that occurred, only valid if a function returns nonzero */
  trajgen_man_error_t last_error;
  /* Configuration, used to init */
  trajgen_man_config_t config;
  /* The (input) velocities that the endeffector/axes shall move with */
  pose_t velocity;
  /* Internal actual speed */
  pose_t current_velocity;
  /* Actual position (also returned by trajgen_man_get_position()) */
  pose_t pose;
  /* Enabled flag */
  bool_t is_enabled;
} trajgen_man_t;
 
#ifdef EXPORT_MANUAL_TG
/**
 * Initializes the manual operation generator
 * @param trajgen_man_t *tp
 * @param trajgen_man_config_t config
 * @return trajgen_man_error_t
 */
extern trajgen_man_error_t trajgen_man_initialize(trajgen_man_t *mtp, trajgen_man_t *_tp,
    trajgen_man_config_t config);
 
/**
 * Resets the manual operation generator except the configuration.
 * @return trajgen_man_error_t
 */
extern trajgen_man_error_t trajgen_man_reset(trajgen_man_t *mtp);
 
/**
 * Aborts the motion (force velocity to 0.0)
 * @return
 */
extern trajgen_man_error_t trajgen_man_abort(trajgen_man_t *mtp);
 
/**
 * Input a new velocity for every axis
 * @return trajgen_man_error_t
 */
extern trajgen_man_error_t trajgen_man_set_velocity(trajgen_man_t *mtp, pose_t velocity);
 
/**
 * Returns the last generated pose (output)
 * @return pose_t
 */
extern pose_t trajgen_man_get_position(trajgen_man_t *mtp);
 
/**
 * Sets the internal position. Used to update when the amplifiers/closed loop
 * controls are off
 * @param pose_t position
 * @return pose_t
 */
extern trajgen_man_error_t trajgen_man_set_position(trajgen_man_t *mtp, pose_t position);
 
/**
 * Calculates a new position from the command velocity
 * @return pose_t
 */
extern trajgen_man_error_t trajgen_man_tick(trajgen_man_t *mtp);
 
/**
 * Returns true (nonzero) if the command velocity and the actual total velocity
 * is zero.
 * @return bool_t
 */
extern bool_t trajgen_man_is_done(trajgen_man_t *mtp);
 
#endif
/* </editor-fold> */
 
/* <editor-fold defaultstate="collapsed" desc="trajgen_*: Main trajectory generator"> */
 
/**
 * Operating mode state, including switching between modes
 */
typedef enum
{
  TRAJ_STATE_DISABLED = 0,  /* Controller disabled, implicitly stops all motion */
  TRAJ_STATE_JOINT,         /* Free joint motion */
  TRAJ_STATE_COORDINATED,   /* Coordinated motion, requires homing */
  TRAJ_STATE_MAN,           /* Manual operation, requires homing */
  TRAJ_______INTERNAL_STATES,
  TRAJ_STATE_OK_TO_SWITCH,
  TRAJ_STATE_DISABLING,
  TRAJ_STATE_JOINT_ENTER,
  TRAJ_STATE_JOINT_LEAVE,
  TRAJ_STATE_COORDINATED_ENTER,
  TRAJ_STATE_COORDINATED_LEAVE,
  TRAJ_STATE_TG_MAN_ENTER,
  TRAJ_STATE_TG_MAN_LEAVE
} trajgen_state_t;
 
/**
 * Configuration
 */
typedef struct
{
  /* Own machine kinematics or NULL for "identity" (x=x,y=y, ...) */
  kinematics_t kinematics_functions;
  /* Max (axes, not joints) accelerations for manual operation, but corresponds */
  /* to joints if the machine type is cartesian */
  pose_t max_manual_accelerations;
  /* Max (axes, not joints) velocities for manual operation, but corresponds to */
  /* joints if the machine type is cartesian */
  pose_t max_manual_velocities;
  /* Maximum trajectory velocity of the end effector ("tool tip"/"pose") */
  real_t max_coord_velocity;
  /* Maximum trajectory acceleration of the end effector ("tool tip"/"pose") */
  real_t max_coord_acceleration;
  /* The interval time of the positioning, equals 1/sample rate */
  real_t sample_interval;
  /* The number of used joints, NOTE: it corresponds to the LAST USED JOINT + 1) */
  /* if there are inactive joints between, they still need to be calculated. */
  uint8_t number_of_used_joints;
  /* The trajectory generators will run interpolation_rate times slower than the */
  /* positioning. Position between are interpolated. */
  uint8_t interpolation_rate;
  /* All configurations for the joint planers */
  trajgen_jointtg_config_t joints[TG_MAX_JOINTS];
} trajgen_config_t;
 
/**
 * Joint definition
 */
typedef struct
{
  /* Interpolator data */
  interpolator_t interpolator;
  /* The trajgen for this axis */
  trajgen_jointtg_t tg;
  /* Uninterpolated positions */
  real_t planer_position;
  /* The interpolated position */
  real_t position;
  /* The interpolated velocity */
  real_t velocity;
  /* The interpolated acceleration */
  real_t acceleration;
  /* The interpolated jerk */
  real_t jerk;
} trajgen_joint_t;
 
/**
 * Internal state variables
 */
typedef struct
{
  uint32_t tick;
  real_t* joint_positions[TG_MAX_JOINTS];
  real_t* planer_positions[TG_MAX_JOINTS];
} trajgen_internals_t;
 
/**
 * Main trajgen type
 */
typedef struct
{
  /* Configuration of the trajectory generator */
  trajgen_config_t config;
  /* The last error occurred, only valid if a function returns nonzero */
  trajgen_error_details_t last_error;
  /* The actual operating state of the trajectory generator */
  trajgen_state_t state;
  /* The externally requested */
  trajgen_state_t requested_state;
  /* The joint data that the planer needs */
  trajgen_joint_t joints[TG_MAX_JOINTS];
  /* Instance of the coordinated motion planer */
  trajgen_coord_t coord_planer;
  /* Instance of the manual operation planer */
  trajgen_man_t man_planer;
  /* Internal state variables, NO NOT MODIFY */
  trajgen_internals_t _internals;
  /* The kinematics data structure */
  kinematics_t kinematics;
  /* The pose of the last planer calculation, not the interpolated one */
  pose_t planer_pose;
  /* Specifies if the planed motion is done */
  bool_t is_done;
} trajgen_t;
 
 
/**
 * Initialises the trajectory generator and all sub systems. This function
 * MUST BE CALLED BEFORE ANY OTHER OPERATIONS with the trajgen, as internal
 * pointer require initialisation.
 * @param trajgen_t *trajectory_generator_data_location
 * @param trajgen_config_t config
 * @return trajgen_error_t
 */
extern trajgen_error_t trajgen_initialize(trajgen_t *trajectory_generator_data_location,
    trajgen_config_t config);
 
/**
 * Resets the internal state, except the configuration
 * @return trajgen_error_t
 */
extern trajgen_error_t trajgen_reset();
 
/**
 * Sets the new requested trajgen state.
 * @param trajgen_state_t state
 * @return trajgen_error_t
 */
extern trajgen_error_t trajgen_switch_state(trajgen_state_t state);
 
/**
 * Abort the current generator, decelerate to standstill. After aborting you
 * should wait for done state.
 * @return trajgen_error_t
 */
extern trajgen_error_t trajgen_abort();
 
/**
 * Called frequently with the sample period tg.config.sample_interval.
 * @return trajgen_error_t
 */
extern trajgen_error_t trajgen_tick();
 
/**
 * Returns the current tick counter of the TG, which is increased every time
 * trajgen_tick() is called.
 * @return uint32_t
 */
extern uint32_t trajgen_get_tick();
 
/**
 * Writes build information into the *s.
 * @param char* s
 * @param unsigned length
 * @return const char*
 */
extern const char* trajgen_info(char* s, unsigned length);
 
/**
 * Coordinated planer accessors
 * APPLY COORDINATED MOTION PLANER ONLY. See trajgen_coord functions for
 * documentation.
 */
extern bool_t trajgen_queue_full();
extern uint32_t trajgen_current_id();
extern uint16_t trajgen_num_queued();
extern uint16_t trajgen_queue_size();
extern trajgen_error_t trajgen_pause();
extern trajgen_error_t trajgen_resume();
extern trajgen_error_t trajgen_add_line(pose_t end, real_t velocity, real_t acceleration);
extern trajgen_error_t trajgen_add_arc(pose_t end, pose_position_t center, pose_vector_t
    normal, unsigned n_turns, real_t velocity, real_t acceleration);
extern trajgen_coord_error_t trajgen_add_curve(pose_t end, pose_vector_t start_ctrl_point,
    pose_vector_t end_ctrl_point, real_t velocity, real_t acceleration);
 
/* </editor-fold> */
 
#ifdef __cplusplus
}
#endif
#endif

Implementation

/*******************************************************************************
 * @file trajgen.c
 * @author: Stefan Wilhelm (c erberos@atwillys.de)
 * @license: BSD (see below)
 * @version: 1.0b
 * @standard C99
 *
 * Allows to move the machine tool tip to specified positions, velocities and
 * accelerations by modifying a virtual machine position every generator cycle.
 * The output is always an updated position for every joint. The closed loop
 * controllers of the joints follow this position or, if the cannot, raise a
 * trailing error fault.
 *
 * More details in the header file trajgen.h
 *
 * Annotations:
 *
 *  - This file contains static variables pointing to your trajgen structure.
 *    That means it is assumed that you have only one trajectory generator
 *    in one process. This means as well that you instance of trajgen_t
 *    shall NOT be moved in memory. This disadvantage is the cost of much
 *    easier handling of the this piece of software. All static variables
 *    are assigned when you call trajgen_init(). The normal usage is to
 *    have a statically or dynamically allocated trajgen_t in the process
 *    heap or shared memory and then work with this memory block until the
 *    process quits.
 *    Interpolators, coordinated, manual and joint planer functions have
 *    the pointer to "this" as first function argument.
 *
 *  - For performance reasons this file does not use volatile declarations,
 *    interfacing it to the realtime system is your part. The normal usage
 *    procedure is:
 *
 *      - (Realtime (timed) thread function start (for the current sample interval)
 *      - Do pre-checks (motor states/estop/intermediate circuit/sync flags ...)
 *          - Conditionally trajgen_abort() on error conditions.
 *      - trajgen_tick();
 *      - Do post checks with your trajgen_t structure variable (errors etc)
 *      - Transfer new joint command positions into the motor control registers.
 *      - Transfer new digital I/O bits into the corresponding registers.
 *      - Process the real time FIFO/shared memory commands, e.g. to switch
 *        states, adapt override, add new motion commands, etc.
 *      - Leave thread function / wait for next sample in thread loop.
 *
 * -----------------------------------------------------------------------------
 * License header: Copyright (c) 2008-2014, Stefan Wilhelm. All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met: (1) Redistributions
 * of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer. (2) Redistributions in binary form must reproduce
 * the above copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the distribution.
 * (3) Neither the name of atwillys.de nor the names of its contributors may be
 * used to endorse or promote products derived from this software without specific
 * prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 ******************************************************************************/
#include <stdio.h>
 
// <editor-fold defaultstate="collapsed" desc="Auxiliary functions/decls">
 
#include "trajgen.h"
 
/*******************************************************************************
 *
 * Auxiliary functions and declarations
 *
 ******************************************************************************/
 
// Version for info
#define TG_VERSION "1.0b"
 
// Definable memset(0)
#ifndef ZERO_MEMORY
#define ZERO_MEMORY zero_memory
static void zero_memory(void *dst, size_t length)
{
  char *p = (char*) dst;
  char *end = ((char*) p) + length - sizeof (long);
  while ((long*) p < (long*) end) {
    *((long*) p) = (long) 0;
    p += (unsigned long) sizeof (long);
  }
  end += sizeof (long);
  while (p < end) { *(char*) p = (char) 0; p++; }
}
#endif
 
// Definable sincos
#ifndef SINCOS
#define SINCOS(x, sx, cx) do { *(sx)=sin((x)); *(cx)=cos((x)); } while(0);
#endif
 
// Definable square root function
#ifndef TG_SQRT_FN
#define sqrtf sqrt
#else
#define sqrtf TG_SQRT_FN
#endif
 
// Pointer checks
#ifdef ARG_POINTER_CHECKS
#define CHK_PTR(X) if((X))
#else
#define CHK_PTR(X) if(0)
#endif
 
#ifndef NO_MOTION_BLENDING
#define NO_MOTION_BLENDING (0)
#else
#undef NO_MOTION_BLENDING
#define NO_MOTION_BLENDING (1)
#endif
 
/* Max angle between end->start vectors where blending is still allowed */
#ifndef MAX_ALLOWED_BLENDING_ANGLE_DEG
#define MAX_ALLOWED_BLENDING_ANGLE_DEG 85
#endif
 
#ifndef STRAIGHT_BLENDING_ANGLE
#define STRAIGHT_BLENDING_ANGLE 1
#endif
 
/* Define it if you don't like inlining */
#ifndef TG_INLINE
#define TG_INLINE __inline__
#endif
 
// stfwi: redo this a bit nicer
#ifdef TG_DEBUG_LEVEL
#if TG_DEBUG_LEVEL > 0
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
static void tg_debug(const char* fmt, ...)
{ va_list args; va_start (args, fmt); fflush(stdout);
  vfprintf(stderr, fmt, args); va_end(args); fflush(stderr); }
#define debug tg_debug
#else
#define debug  tg_nodebug
#endif
#if TG_DEBUG_LEVEL < 3
static void tg_nodebug(const char* pattern, ...) { ; }
#endif
#if TG_DEBUG_LEVEL > 1
#define debug2 tg_debug
#else
#define debug2 tg_nodebug
#endif
#if TG_DEBUG_LEVEL > 2
#define debug3 tg_debug
#else
#define debug3 tg_nodebug
#endif
#else
TG_INLINE static void tg_nodebug(const char* pattern, ...) { ; }
#define debug tg_nodebug
#define debug2 tg_nodebug
#define debug3 tg_nodebug
#endif
 
/*
 * Math "resolution", values define when a dimension or angle is treated
 * as zero.
 */
#ifndef TG_D_RES
#define TG_D_RES TG_RESOLUTION
#endif
#ifndef TG_A_RES
#define TG_A_RES TG_RESOLUTION
#endif
 
/*
 * Static switches, helps optimising the machine code
 */
#ifdef EXPORT_INTERPOLATORS
#define INTERPOLATION_STATIC
#else
#ifndef INTERPOLATION_STATIC
#define INTERPOLATION_STATIC static TG_INLINE
#endif
#endif
 
#ifdef EXPORT_JOINT_TG
#define JOINT_TG_STATIC
#else
#ifndef JOINT_TG_STATIC
#define JOINT_TG_STATIC static TG_INLINE
#endif
#endif
 
#ifdef EXPORT_MANUAL_TG
#define MANUAL_TG_STATIC
#else
#ifndef MANUAL_TG_STATIC
#define MANUAL_TG_STATIC static TG_INLINE
#endif
#endif
 
#ifdef EXPORT_COORD_TG
#define COORD_TG_STATIC
#define COORD_TG_STATICI
#else
#ifndef COORD_TG_STATIC
#define COORD_TG_STATIC static
#endif
#define COORD_TG_STATICI static TG_INLINE
#endif
 
static trajgen_error_t tg_err(unsigned code, unsigned joint);
 
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="Math/vector auxiliaries">
 
/********************************************************************
 *
 * Vector / robotic auxiliary functions
 *
 ********************************************************************/
 
#ifndef M_PI
#define M_PI (3.1415926535897932384626433832795029L)  /* pi */
#endif
#ifndef M_SQRT2
#define M_SQRT2 (sqrt(2.))  /* sqrt(2) */
#endif
 
#define sqr(x) ((x)*(x))
#define sqrtz(X) ((X)>0?sqrtf(X):((X)>-TG_RESOLUTION?0:NAN))
#define v_cpy(vs,vd) (*(vd)=(vs))
#define v_magnitude_sq(v) (sqr(v.x)+sqr(v.y)+sqr(v.z))
#define v_magnitude(v) (sqrtz(sqr(v.x)+sqr(v.y)+sqr(v.z)))
#define v_distance(v1,v2) sqrtz(sqr((v2).x-(v1).x)+sqr((v2).y-(v1).y)+sqr((v2).z-(v1).z))
#define v_dot(v1,v2) ((v1).x*(v2).x+(v1).y*(v2).y+(v1).z*(v2).z) /* "dot" scalar product */
#define v_err_t int
#define v_isfinite(p) (isfinite((p).x) && isfinite((p).y) && isfinite((p).z))
#define v_invalidate(p) { (p).x = (p).y = (p).z = NAN; }
#define v_setzero(p) { (p).x=(p).y=(p).z=0; }
 
#define v_pose_split(pose, xyz, abc, uvw) { \
  (xyz)->x=(pose).x; (xyz)->y=(pose).y; (xyz)->z=(pose).z; \
  (uvw)->x=(pose).u; (uvw)->y=(pose).v; (uvw)->z=(pose).w; \
  (abc)->x=(pose).a; (abc)->y=(pose).b; (abc)->z=(pose).c; \
}
#define v_pose_merge(xyz, abc, uvw, pose) { \
  (pose)->x=(xyz).x; (pose)->y=(xyz).y; (pose)->z=(xyz).z; \
  (pose)->u=(uvw).x; (pose)->v=(uvw).y; (pose)->w=(uvw).z; \
  (pose)->a=(abc).x; (pose)->b=(abc).y; (pose)->c=(abc).z; \
}
 
TG_INLINE static v_err_t v_scale(pose_vector_t v1, real_t d, pose_vector_t * r)
{ CHK_PTR(!r) return 1; r->x=v1.x*d; r->y=v1.y*d; r->z=v1.z*d; return 0; }
 
TG_INLINE static v_err_t v_sum(pose_vector_t v1, pose_vector_t v2, pose_vector_t * r)
{ CHK_PTR(!r) return 1; r->x=v1.x+v2.x; r->y=v1.y+v2.y; r->z=v1.z+v2.z; return 0; }
 
TG_INLINE static v_err_t v_sub(pose_vector_t v1, pose_vector_t v2, pose_vector_t * r)
{ CHK_PTR(!r) return 1; r->x=v1.x-v2.x; r->y=v1.y-v2.y; r->z=v1.z-v2.z; return 0; }
 
TG_INLINE static v_err_t v_cross(pose_vector_t v1, pose_vector_t v2, pose_vector_t * r)
{ CHK_PTR(!r) return 1; r->x=v1.y*v2.z-v1.z*v2.y; r->y=v1.z*v2.x-v1.x*v2.z;
  r->z=v1.x*v2.y-v1.y*v2.x; return 0; }
 
TG_INLINE static v_err_t v_unit(pose_vector_t v, pose_vector_t * r)
{ CHK_PTR(!r) return 1; real_t l; if ((l=v_magnitude(v)) == 0.0) {
  r->x=r->y=r->z=0; return 1; } r->x = v.x/l; r->y = v.y/l; r->z = v.z/l; return 0; }
 
TG_INLINE static v_err_t v_project(pose_vector_t v1, pose_vector_t v2, pose_vector_t *r)
{ CHK_PTR(!r) return 1; if(v_unit(v2, &v2)) return 1; v_scale(v2, v_dot(v1,v2), r);
  return 0; }
 
TG_INLINE static v_err_t v_plane_project(pose_vector_t v, pose_vector_t normal, pose_vector_t *r)
{ CHK_PTR(!r) return 1; pose_vector_t p; return v_project(v, normal, &p) || v_sub(v, p, r); }
 
/**
 * Defines a line, precalculates characteristic values.
 * @param pose_line_t *line
 * @param pose_position_t start
 * @param pose_position_t end
 * @return int
 */
static v_err_t v_lin_define(pose_line_t *line, pose_position_t start, pose_position_t end)
{
  CHK_PTR(!line) return 1;
  line->start = start;
  line->end = end;
  v_sub(end, start, &line->u);
  line->tm = v_magnitude(line->u);
  if (line->tm < TG_D_RES) {
    line->u.x = 1;
    line->tm = line->u.y = line->u.z = 0;
  } else if(v_unit(line->u, &line->u)) {
    line->tm = line->u.x = line->u.y = line->u.z = 0;
    return 1;
  }
  return 0;
}
 
/**
 * Calculates a point for a line motion from a progress (current one-dim position)
 * @param pose_line_t *line
 * @param real_t progress
 * @param pose_position_t *point
 * @return int
 */
static v_err_t v_lin_get(pose_line_t *line, real_t progress, pose_position_t *point)
{
  CHK_PTR(!line || !point) return 1;
  if (line->tm <= 0 || progress >= line->tm) {
    *point = line->end;
  } else {
    v_scale(line->u, progress, point);
    v_sum(line->start, *point, point);
  }
  return 0;
}
 
/**
 * Create a circle/arc/helix definition, precalculate characteristics.
 * @param pose_circle_t *circle
 * @param pose_position_t start
 * @param pose_position_t end
 * @param pose_vector_t center
 * @param pose_vector_t normal
 * @param int n_turns
 * @return int
 */
static v_err_t v_arc_define(pose_circle_t * circle, pose_position_t start,
    pose_position_t end, pose_vector_t center, pose_vector_t normal, unsigned n_turns)
{
  CHK_PTR(!circle) return 1;
  pose_vector_t v, e; // v:aux, e: rev. end vector
  real_t d;
  v_sub(start, center, &v); // determine projected centre point
  if(v_project(v, normal, &v)) return 1;
  v_sum(v, center, &circle->cp); // real center: point on normal in plane of the start point
  v_unit(normal, &circle->nv); // unit normal vector (below: negative turns -> other direction)
  circle->r = v_distance(start, circle->cp); // radius from start point
  if(circle->r < TG_D_RES) return 1; // start radius 0
  v_sub(end, center, &v); // determine projected centre point
  if(v_project(v, normal, &v)) return 1;
  v_sub(start, circle->cp, &circle->rt); // tangent normal vector
  v_cross(circle->nv, circle->rt, &circle->rp); // vector in helix direction
  v_sub(end, circle->cp, &circle->rh); // helix
  v_plane_project(circle->rh, circle->nv, &e);
  circle->s = v_magnitude(e) - circle->r;
  v_sub(circle->rh, e, &circle->rh);
  v_unit(e, &e);
  v_scale(e, circle->r, &e);
  if(v_magnitude(e) == 0) { v_scale(circle->nv, FLOAT_EPS, &v); v_sum(e, v, &e); }
  d = v_dot(circle->rt, e)/sqr(circle->r); // determine angle
  circle->a = d>1.0 ? 0.0 : (d<-1?M_PI : acos(d));
  v_cross(circle->rt, e, &v); // Angle correction (0->PI --> PI->2PI)
  if(v_dot(v, circle->nv) < 0) circle->a = (2.0*M_PI) - circle->a;
  if(circle->a > -(TG_A_RES) && circle->a < (TG_A_RES)) circle->a = 0;
  if(n_turns > 0) circle->a += n_turns * 2.0*M_PI; // add turns
  circle->l = sqrtz(sqr(circle->a * circle->r) + sqr(v_magnitude(circle->rh))); // Segment length
  return (circle->a == 0); // angle 0 -> error
}
 
/**
 * Returns a point on a circle at a given angle (progress).
 * @param pose_circle_t *circle
 * @param real_t angle
 * @param pose_position_t *point
 * @return int
 */
static v_err_t v_arc_get(pose_circle_t * circle, real_t progress, pose_position_t *point)
{
  CHK_PTR(!circle || !point) return 1;
  pose_vector_t p, s;
  real_t sx, cx;
  progress /= circle->l; // length --> 0..1
  SINCOS(progress * circle->a, &sx, &cx); // length --> angle
  v_scale(circle->rp, sx, &s);
  v_scale(circle->rt, cx, &p);
  v_sum(p, s, point);           // radius vector to center
  v_unit(*point, &p);    // add scaled radius vector
  v_scale(p, progress * circle->s, &p);// parallel component
  v_sum(*point, p, point);
  v_scale(circle->rh, progress, &s);   // perpendicular component
  v_sum(*point, s, point);
  v_sum(circle->cp, *point, point); // translate relative to center point
  return 0;
}
 
#ifdef WITH_CURVES
/* Experimental, not places this code yet. */
#include "curve.inc.c"
#endif
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="interpolator_*: Interpolator">
 
/*******************************************************************************
 *
 * Interpolator (cubic spline)
 *
 ******************************************************************************/
 
#ifndef NO_INTERPOLATION
 
INTERPOLATION_STATIC bool_t interpolator_need_update(interpolator_t *ci)
{
  CHK_PTR(!ci) return INTERPOLATOR_ERROR_NULLPOINTER;
  return ci->n<4;
}
 
INTERPOLATION_STATIC interpolator_error_t interpolator_push(interpolator_t * ip,
    real_t point)
{
  debug3("# ip: push %g\n", point);
  // TG_CC_FN_CALL(TG_CC_interpolator_push);
  CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
  if (ip->n >= 4) {
    return INTERPOLATOR_ERROR_QUEUE_FULL;
  } else if (ip->n == 0) {
    return INTERPOLATOR_ERROR_NOT_RESET;
  } else {
    ip->x0 = ip->x1;
    ip->x1 = ip->x2;
    ip->x2 = ip->x3;
    ip->x3 = point;
    ip->n++;
  }
  real_t T = ip->T;
  ip->w0 = (ip->x0 + 4.0*ip->x1 + ip->x2) / 6.0;
  ip->w1 = (ip->x1 + 4.0*ip->x2 + ip->x3) / 6.0;
  ip->v0 = ((ip->x2) - (ip->x0)) / (2.0 * (T));
  ip->v1 = ((ip->x3) - (ip->x1)) / (2.0 * (T));
  ip->d = ip->w0;
  ip->c = ip->v0;
  ip->b = 3 * (ip->w1 - ip->w0)/(T*T) - (2 * ip->v0 + ip->v1)/T;
  ip->a = (ip->v1 - ip->v0)/(3.0*(T*T)) - (2.0 * ip->b)/(3.0*T);
  ip->t = 0.0;
  return INTERPOLATOR_OK;
}
 
INTERPOLATION_STATIC interpolator_error_t interpolator_reset(interpolator_t * ip,
    real_t initial_position)
{
  // TG_CC_FN_CALL(TG_CC_interpolator_reset);
  debug3("# ip: reset\n");
  CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
  ip->n = 1;
  ip->t = 0.0;
  ip->x0 = ip->x1 = ip->x2 = ip->x3 = ip->w0 = ip->w1 = ip->d = initial_position;
  ip->v0 = ip->v1 = ip->a = ip->b = ip->c = 0.0;
  return interpolator_push(ip, initial_position);
}
 
INTERPOLATION_STATIC interpolator_error_t interpolator_init(interpolator_t * ip,
    real_t sample_interval, unsigned interpolation_rate)
{
  debug3("# ip: init\n");
  CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
  if (sample_interval <= 0.0 || interpolation_rate < 1) {
    return INTERPOLATOR_ERROR_INIT_ARG_INVALID;
  }
  ip->T = sample_interval;
  ip->Ti = ip->T / interpolation_rate;
  interpolator_reset(ip, 0);
  return INTERPOLATOR_OK;
}
 
INTERPOLATION_STATIC interpolator_error_t interpolator_interpolate(interpolator_t *ip,
    real_t *x, real_t *v, real_t *a, real_t *j)
{
  // TG_CC_FN_CALL(TG_CC_interpolator_interpolate);
  CHK_PTR(!ip||!x||!v||!a||!j) return INTERPOLATOR_ERROR_INTERPOLATE_ARG_NULLPOINTER;
  if (ip->n<4) interpolator_push(ip, ip->x3); // auto refill
  if(ip->x3 == ip->x2 && ip->x1 == ip->x3) {
    *x = ip->x3; *v = *a = *j = 0;
    debug3("# ip: interpolate: positions equal: %g (v-0, a=0)\n", ip->x3);
  } else {
    real_t t = ip->t; // let the compiler optimise all this
    *x = (((ip->a*t + ip->b)*t) + ip->c) * t + ip->d;
    *v = ((3.0 * ip->a*t) + 2.0 * ip->b) * t + ip->c;
    *a = 6.0 * ip->a*t + 2.0 * ip->b;
    *j = 6.0 * ip->a;
    debug3("# ip: interpolate: s%g v%g a%g\n", *x, *v, *a);
  }
  ip->t += ip->Ti;
  if (fabs(ip->T - ip->t) < 0.5 * ip->Ti) ip->n--;
  return INTERPOLATOR_OK;
}
 
#else
 
INTERPOLATION_STATIC bool_t interpolator_need_update(interpolator_t *ip)
{
  CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
  return ip->n<4;
}
 
INTERPOLATION_STATIC interpolator_error_t interpolator_push(interpolator_t * ip,
    real_t point)
{
  CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
  if (ip->n >= 4) {
    return INTERPOLATOR_ERROR_QUEUE_FULL;
  } else if (ip->n == 0) {
    return INTERPOLATOR_ERROR_NOT_RESET;
  } else {
    ip->x0 = ip->x1;
    ip->x1 = ip->x2;
    ip->x2 = ip->x3;
    ip->x3 = point;
    ip->n++;
  }
  return INTERPOLATOR_OK;
}
 
INTERPOLATION_STATIC interpolator_error_t interpolator_reset(interpolator_t * ip,
    real_t initial_position)
{
  CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
  ip->n = 2;
  ip->t = 0.0;
  ip->x0 = ip->x1 = ip->x2 = ip->x3 = ip->w0 = ip->w1 = ip->d = initial_position;
  ip->v0 = ip->v1 = ip->a = ip->b = ip->c = 0.0;
  return interpolator_push(ip, initial_position);
}
 
INTERPOLATION_STATIC interpolator_error_t interpolator_init(interpolator_t * ip,
    real_t sample_interval, unsigned interpolation_rate)
{
  CHK_PTR(!ip) return INTERPOLATOR_ERROR_NULLPOINTER;
  if (sample_interval <= 0.0 || interpolation_rate != 1) {
    return INTERPOLATOR_ERROR_INIT_ARG_INVALID;
  }
  ip->T = sample_interval;
  ip->Ti = 1.0/ip->T;
  interpolator_reset(ip, 0);
  return INTERPOLATOR_OK;
}
 
INTERPOLATION_STATIC interpolator_error_t interpolator_interpolate(interpolator_t *ip,
    real_t *x, real_t *v, real_t *a, real_t *j)
{
  CHK_PTR(!ip || !x || !v || !a || !j) return INTERPOLATOR_ERROR_INTERPOLATE_ARG_NULLPOINTER;
  if (ip->n<4) interpolator_push(ip, ip->x3);
  if(ip->x3 == ip->x2 && ip->x1 == ip->x3) {
    *x = ip->x3; *v = *a = *j = 0;
  } else {
    *x = ip->x3;
    *v = (ip->x3 - ip->x2) * ip->Ti;
    *a = (*v-ip->v0) * ip->Ti;
    *j = (*a-ip->v1) * ip->Ti;
    ip->v0 = *v;
    ip->v1 = *a;
  }
  ip->n--;
  return INTERPOLATOR_OK;
}
 
#endif
 
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="kinematics_*: Default identity kinematic model">
 
/*******************************************************************************
 *
 * Kinematics manager. Built-in default is an identity system.
 * See header file for documentation about how to set your own kinematics.
 *
 ******************************************************************************/
 
/**
 * Default passthrough forward kinematics
 * @param real_t *joint_positions
 * @param pose_t *world
 * @return kinematics_error_t
 */
static kinematics_error_t kinematics_identity_forward(real_t **joint_positions,
    pose_t *world)
{
  CHK_PTR(!joint_positions || !world) return KINEMATICS_ERR_NULL_POINTER;
  world->x = *(joint_positions[0]);
  world->y = *(joint_positions[1]);
  world->z = *(joint_positions[2]);
  world->a = *(joint_positions[3]);
  world->b = *(joint_positions[4]);
  world->c = *(joint_positions[5]);
  world->u = *(joint_positions[6]);
  world->v = *(joint_positions[7]);
  world->w = *(joint_positions[8]);
  return KINEMATICS_ERR_OK;
}
 
/**
 * Default passthrough inverse kinematics
 * @param pose_t *world
 * @param real_t *joint_positions
 * @return kinematics_error_t
 */
static kinematics_error_t kinematics_identity_inverse(pose_t *world, real_t **joint_positions)
{
  CHK_PTR(!joint_positions || !world) return KINEMATICS_ERR_NULL_POINTER;
  *(joint_positions[0]) = world->x;
  *(joint_positions[1]) = world->y;
  *(joint_positions[2]) = world->z;
  *(joint_positions[3]) = world->a;
  *(joint_positions[4]) = world->b;
  *(joint_positions[5]) = world->c;
  *(joint_positions[6]) = world->u;
  *(joint_positions[7]) = world->v;
  *(joint_positions[8]) = world->w;
  return KINEMATICS_ERR_OK;
}
 
/**
 * Default passthrough kinematics homeing
 * @param pose_t *world
 * @param real_t *joint_positions
 * @return kinematics_error_t
 */
static kinematics_error_t kinematics_identity_home(pose_t *world, real_t **joint_positions)
{
  CHK_PTR(!joint_positions || !world) return KINEMATICS_ERR_NULL_POINTER;
  return kinematics_identity_forward(joint_positions, world);
}
 
/**
 * Selects and initializes a machine type, sets the appropriate pointers to the
 * associated functions.
 * @param kinematics_t *kin
 * @param kinematics_t *device_kinematics
 * @return kinematics_error_t
 */
kinematics_error_t kinematics_initialize(kinematics_t *kin, kinematics_t device_kinematics)
{
  if (!kin) return KINEMATICS_ERR_NULL_POINTER;
  kin->forward = kinematics_identity_forward;
  kin->inverse = kinematics_identity_inverse;
  kin->reset = kinematics_identity_home;
  if(device_kinematics.forward) kin->forward = device_kinematics.forward;
  if(device_kinematics.inverse) kin->inverse = device_kinematics.inverse;
  if(device_kinematics.reset) kin->reset = device_kinematics.reset;
  if((!device_kinematics.forward) ^ (!device_kinematics.inverse)) {
    return KINEMATICS_ERR_INIT_FUNCTION_NULL;
  }
  return KINEMATICS_ERR_OK;
}
 
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="trajgen_coord_*: Coordinated motion planer">
 
/*******************************************************************************
 *
 *
 * Coordinated motion planner.
 *
 *
 ******************************************************************************/
 
/**
 * Error callback wrapper, passes through the error code without changing.
 * @param trajgen_coord_error_t e
 * @return trajgen_coord_error_t
 */
static TG_INLINE trajgen_coord_error_t trajgen_coord_raise_error(trajgen_coord_t* tp,
    trajgen_coord_error_t e)
{
  if (!tp) return e;
  tg_err(e, 0);
  return tp->last_error = e;
}
 
/**
 * Calculates a unit vector for starting a segment.
 * @param trajgen_sg_t *sg
 * @param pose_vector_t* result
 * @return trajgen_coord_error_t
 */
static trajgen_coord_error_t trajgen_sg_start_direction(trajgen_coord_t* tp,
    trajgen_sg_t *sg, pose_vector_t *result)
{
  CHK_PTR(!sg || !result) return TP_ERR_TP_NULL_POINTER;
  switch (sg->motion_type) {
    case SG_LINEAR:
    {
      v_sub(sg->coords.line.xyz.end, sg->coords.line.xyz.start, result);
      v_unit(*result, result);
      return TP_ERR_OK;
    }
    case SG_CIRCULAR:
    {
      pose_position_t startpoint;
      pose_vector_t radius, tan, perp;
      v_arc_get(&sg->coords.arc.xyz, 0.0, &startpoint);
      v_sub(startpoint, sg->coords.arc.xyz.cp, &radius);
      v_cross(sg->coords.arc.xyz.nv, radius, &tan);
      v_unit(tan, &tan);
      v_sub(sg->coords.arc.xyz.cp, startpoint, &perp);
      v_unit(perp, &perp);
      v_scale(tan, sg->a, &tan);
      v_scale(perp, sqr(0.5 * sg->v) / sg->coords.arc.xyz.r, &perp);
      v_sum(tan, perp, result);
      v_unit(*result, result);
      return TP_ERR_OK;
    }
    #ifdef WITH_CURVES
    case SG_CURVE:
    {
      v_cpy(sg->coords.curve.xyz.suv, result);
      return TP_ERR_OK;
    }
    #endif
  }
  return trajgen_coord_raise_error(tp, TP_ERR_INVALID_MOTION_TYPE);
}
 
/**
 * Calculates a unit vector at the end of a segment.
 * @param trajgen_sg_t *sg
 * @param pose_vector_t* result
 * @return trajgen_coord_error_t
 */
static trajgen_coord_error_t trajgen_sg_end_direction(trajgen_coord_t* tp,
    trajgen_sg_t *sg, pose_vector_t *result)
{
  CHK_PTR(!sg || !result) return TP_ERR_TP_NULL_POINTER;
  switch (sg->motion_type) {
    case SG_LINEAR:
    {
      v_sub(sg->coords.line.xyz.end, sg->coords.line.xyz.start, result);
      v_unit(*result, result);
      return TP_ERR_OK;
    }
    case SG_CIRCULAR:
    {
      pose_position_t endpoint;
      pose_vector_t radius;
      v_arc_get(&sg->coords.arc.xyz, sg->coords.arc.xyz.a, &endpoint);
      v_sub(endpoint, sg->coords.arc.xyz.cp, &radius);
      v_cross(sg->coords.arc.xyz.nv, radius, result);
      v_unit(*result, result);
      return TP_ERR_OK;
    }
    #ifdef WITH_CURVES
    case SG_CURVE:
    {
      v_cpy(sg->coords.curve.xyz.euv, result);
      return TP_ERR_OK;
    }
    #endif
  }
  return trajgen_coord_raise_error(tp, TP_ERR_INVALID_MOTION_TYPE);
}
 
/**
 * Push a new segment into the queue.
 */
static TG_INLINE trajgen_coord_error_t trajgen_coord_queue_push_back(trajgen_coord_t* tp,
    trajgen_sg_t *sg)
{
  CHK_PTR(sg) return trajgen_coord_raise_error(tp, TP_ERR_TP_NULL_POINTER);
  unsigned next = (tp->queue.end + 1) % tp->queue.size;
  if (next == tp->queue.start) return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_FULL);
  tp->queue.queue[tp->queue.end] = *sg;
  tp->queue.num_elements++;
  tp->queue.end = next;
  debug2("# tq: pushed segment %lu\n", (unsigned long)sg->id);
  return TP_ERR_OK;
}
 
/**
 * Removes n elements from the front of the queue
 * @return trajgen_coord_error_t
 */
static TG_INLINE trajgen_coord_error_t trajgen_coord_queue_pop_front(trajgen_coord_t* tp,
    int n)
{
  if (n <= 0) return TP_ERR_OK;
  if (n > tp->queue.num_elements) {
    return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_TO_MANY_ELEMENTS_TO_REMOVE);
  }
  debug2("# tq: popped segment %lu\n", (unsigned long)(tp->queue.queue[tp->queue.start].id));
  tp->queue.start = (tp->queue.start + n) % tp->queue.size;
  tp->queue.num_elements -= n;
  return TP_ERR_OK;
}
 
/**
 * Returns the head item of the queue
 * @return trajgen_sg_t *
 */
static TG_INLINE trajgen_sg_t *trajgen_coord_queue_front(trajgen_coord_t* tp)
{ return (tp->queue.num_elements==0) ? (trajgen_sg_t*) 0 :
  (&(tp->queue.queue[tp->queue.start])); }
 
/**
 * Returns a queue item by value without modifying the queue
 * @return trajgen_sg_t *
 */
static TG_INLINE trajgen_sg_t *trajgen_coord_queue_get(trajgen_coord_t* tp, unsigned n)
{ return (n>=tp->queue.num_elements) ? (trajgen_sg_t*) 0 :
  &(tp->queue.queue[(tp->queue.start + n) % tp->queue.size]); }
 
/**
 * Returns true if the queue is completely full. For margin checks, the function
 * trajgen_coord_get_num_queued() can be used in combination with the known size.
 * @return bool_t
 */
COORD_TG_STATICI bool_t trajgen_coord_is_queue_full(trajgen_coord_t* tp)
{ return tp->queue.num_elements >= tp->queue.size-1; }
//{ return (bool_t) (((tp->queue.end+1) % tp->queue.size) == tp->queue.start); }
 
/**
 * Returns the size (maximum length) of the queue
 * @return uint16_t
 */
COORD_TG_STATICI uint16_t trajgen_coord_get_queue_size(trajgen_coord_t* tp)
{ return tp->queue.size; }
 
/**
 * Resets the planner without overwriting the configuration and actual position
 * @return trajgen_coord_error_t
 */
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_reset(trajgen_coord_t* tp)
{
  CHK_PTR(!tp) return TP_ERR_TP_NULL_POINTER;
  tp->last_error = TP_ERR_OK;
  tp->end_pose = tp->_i.last_position = tp->pose;
  tp->queue.num_elements = tp->queue.start = tp->queue.end = 0;
  tp->sg_id = tp->next_sg_id = tp->num_queued_active = 0;
  tp->is_aborting = tp->is_pausing = 0;
  tp->sync_dio_current = tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
  tp->override = tp->_i.override = 1.0;
  tp->override_enabled = 1;
  tp->sync.type = TP_SYNC_NONE;
  tp->sync.reference = 0;
  tp->sync.i_offset = tp->sync.i_isset = 0;
  tp->is_done = 1;
  return TP_ERR_OK;
}
 
/**
 * Initializes the planner
 * @return trajgen_coord_error_t
 */
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_init(trajgen_coord_t *tp,
    trajgen_coord_config_t config)
{
  if (!tp) return TP_ERR_TP_NULL_POINTER;
  ZERO_MEMORY(tp, sizeof (trajgen_coord_t)); // Set all values and pointers to 0
  trajgen_coord_reset(tp);
  tp->queue.size = TG_QUEUE_SIZE;
  tp->config = config;
  if (config.max_acceleration <= 0.0) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_ACCEL);
  } else if (config.max_velocity <= 0.0) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SPEED);
  } else if (config.sample_interval <= 0.0) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SAMPLE_INTERVAL);
  } else {
    return TP_ERR_OK;
  }
}
 
/**
 * Set the current position of the planer. DO NOT TO THIS WHEN THE planer is
 * running (done!=1).
 * @param pose_t pos
 * @return trajgen_coord_error_t
 */
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_set_position(trajgen_coord_t* tp,
    pose_t p)
{ tp->pose = tp->end_pose = p; return TP_ERR_OK; }
 
/**
 * Pause motion
 * @return trajgen_coord_error_t
 */
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_pause(trajgen_coord_t* tp)
{ tp->is_pausing = 1; return TP_ERR_OK; }
 
/**
 * Resume from pause state
 * @return trajgen_coord_error_t
 */
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_resume(trajgen_coord_t* tp)
{ tp->is_pausing = 0; return TP_ERR_OK; }
 
/**
 * Abort all coordinated motion
 * @return trajgen_coord_error_t
 */
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_abort(trajgen_coord_t* tp)
{
  CHK_PTR(!tp) return TP_ERR_TP_NULL_POINTER;
  if (!tp->is_aborting) tp->is_pausing = tp->is_aborting = 1;
  tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
  return TP_ERR_OK;
}
 
/**
 * Returns the id of the last motion that is currently executing.
 * @return uint32_t
 */
COORD_TG_STATICI uint32_t trajgen_coord_get_current_id(trajgen_coord_t* tp)
{ return tp->sg_id; }
 
/**
 * Returns the actual output position of the generator
 * @return pose_t
 */
COORD_TG_STATICI pose_t trajgen_coord_get_position(trajgen_coord_t* tp)
{ return tp->pose; }
 
/**
 * Returns nonzero if the generator finished processing the queue and
 * reached the target position.
 * @return bool_t
 */
COORD_TG_STATICI bool_t trajgen_coord_is_done(trajgen_coord_t* tp)
{ return tp->is_done; }
 
/**
 * Returns the number of currently queued items
 * @return uint16_t
 */
COORD_TG_STATICI uint16_t trajgen_coord_get_num_queued(trajgen_coord_t* tp)
{ return tp->queue.num_elements; }
 
/**
 * Returns the number of currently processed items in the queue
 * @return uint16_t
 */
COORD_TG_STATICI uint16_t trajgen_coord_get_num_queued_active(trajgen_coord_t* tp)
{ return tp->num_queued_active; }
 
/**
 * Add a straight line into the queue. Current speed/accel/io settings apply.
 * @param pose_t end_point
 * @param real_t velocity
 * @param real_t acceleration
 * @return trajgen_coord_error_t
 */
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_add_line(trajgen_coord_t* tp,
    pose_t end_point, real_t velocity, real_t acceleration)
{
  trajgen_sg_t sg;
  //pose_line_t line_xyz, line_uvw, line_abc;
  pose_position_t start_xyz, end_xyz, start_uvw, end_uvw, start_abc, end_abc;
  sg.sync_dio = tp->sync_dio_command;
  tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
  if (tp->is_aborting) return trajgen_coord_raise_error(tp, TP_ERR_ABORTING);
  if (trajgen_coord_is_queue_full(tp)) return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_FULL);
  acceleration = acceleration == 0 ? tp->config.max_acceleration : acceleration;
  if (velocity < TG_RESOLUTION || velocity > tp->config.max_velocity) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SPEED);
  } else if (acceleration < TG_RESOLUTION || acceleration > tp->config.max_acceleration) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_ACCEL);
  } else if(!pose_isfinite(end_point)) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_POSE);
  }
  v_pose_split(tp->end_pose, &start_xyz, &start_abc, &start_uvw);
  v_pose_split(end_point, &end_xyz, &end_abc, &end_uvw);
  if(
    v_lin_define(&sg.coords.line.xyz, start_xyz, end_xyz)
    #ifndef NO_UVW
    || v_lin_define(&sg.coords.line.uvw, start_uvw, end_uvw)
    #endif
    #ifndef NO_ABC
    || v_lin_define(&sg.coords.line.abc, start_abc, end_abc)
    #endif
  ) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_PARAM);
  }
  #ifdef NO_ABC
  v_setzero(sg.coords.line.abc.start);
  v_setzero(sg.coords.line.abc.end);
  v_setzero(sg.coords.line.abc.u);
  sg.coords.line.abc.tm = 0;
  #endif
  #ifdef NO_UVW
  v_setzero(sg.coords.line.uvw.start);
  v_setzero(sg.coords.line.uvw.end);
  v_setzero(sg.coords.line.uvw.u);
  sg.coords.line.uvw.tm = 0;
  #endif
  sg.length = (sg.coords.line.xyz.tm>TG_D_RES) ? sg.coords.line.xyz.tm :
    (sg.coords.line.uvw.tm>TG_D_RES) ? (sg.coords.line.uvw.tm) : (sg.coords.line.abc.tm);
  if(sg.length < TG_RESOLUTION) {
    sg.length = 0;
    return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
  }
  sg.id = tp->next_sg_id;
  sg.is_active = 0;
  sg.v = velocity;
  sg.a = acceleration;
  sg.blending_tolerance = tp->blending_tolerance<TG_RESOLUTION ? 0:tp->blending_tolerance;
  sg.sync_type = tp->sync.type;
  sg.override_enabled = tp->override_enabled;
  sg.motion_type = SG_LINEAR;
  if (trajgen_coord_queue_push_back(tp, &sg) != 0) {
    return tp->last_error;
  }
  tp->end_pose = end_point; // remember the end of this move, as it's the start of the next one.
  tp->next_sg_id++;
  tp->is_done = 0;
  return TP_ERR_OK;
}
 
/**
 * Push an arc/circle/helix motion into the queue. Current speed/accel/io
 * settings apply.
 * @param pose_t end
 * @param pose_vector_t center
 * @param pose_vector_t normal
 * @param unsigned n_turns
 * @param real_t velocity
 * @param real_t acceleration
 * @return trajgen_coord_error_t
 */
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_add_arc(trajgen_coord_t* tp,
    pose_t end, pose_position_t center, pose_vector_t normal, unsigned n_turns,
    real_t velocity, real_t acceleration)
{
  trajgen_sg_t sg;
  pose_position_t start_xyz, end_xyz, start_uvw, end_uvw, start_abc, end_abc;
  sg.sync_dio = tp->sync_dio_command;
  tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
  sg.motion_type = SG_CIRCULAR;
  sg.id = tp->next_sg_id;
  sg.is_active = 0;
  sg.sync_type = tp->sync.type;
  sg.override_enabled = tp->override_enabled;
  sg.blending_tolerance = tp->blending_tolerance<TG_RESOLUTION ? 0:tp->blending_tolerance;
  if (tp->is_aborting) return trajgen_coord_raise_error(tp, TP_ERR_ABORTING);
  if (trajgen_coord_is_queue_full(tp)) return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_FULL);
  acceleration = acceleration == 0 ? tp->config.max_acceleration : acceleration;
  if (velocity < TG_RESOLUTION || velocity > tp->config.max_velocity) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SPEED);
  } else if (acceleration < TG_RESOLUTION || acceleration > tp->config.max_acceleration) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_ACCEL);
  } else if(!pose_isfinite(end) || !v_isfinite(center) || !v_isfinite(normal)) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_POSE);
  }
  sg.v = velocity;
  sg.a = acceleration;
  v_pose_split(tp->end_pose, &start_xyz, &start_abc, &start_uvw);
  v_pose_split(end, &end_xyz, &end_abc, &end_uvw);
 
  #ifdef NO_ABC
  v_setzero(sg.coords.arc.abc.start);
  v_setzero(sg.coords.arc.abc.end);
  v_setzero(sg.coords.arc.abc.u);
  sg.coords.arc.abc.tm = 0;
  #endif
  #ifdef NO_UVW
  v_setzero(sg.coords.arc.uvw.start);
  v_setzero(sg.coords.arc.uvw.end);
  v_setzero(sg.coords.arc.uvw.u);
  sg.coords.arc.uvw.tm = 0;
  #endif
  if(
    v_distance(start_xyz, center) < TG_RESOLUTION ||
    v_distance(end_xyz, center) < TG_RESOLUTION
  ) {
    debug("# tp: trajgen_coord_add_arc(): distance from center ~== 0\n");
    return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
  } else if(
    v_arc_define(&sg.coords.arc.xyz, start_xyz, end_xyz, center, normal, n_turns)
    #ifndef NO_ABC
    || v_lin_define(&sg.coords.arc.abc, start_abc, end_abc)
    #endif
    #ifndef NO_UVW
    || v_lin_define(&sg.coords.arc.uvw, start_uvw, end_uvw)
    #endif
  ) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_PARAM);
  } else if(sg.coords.arc.xyz.a == 0) {
    debug("# tp: trajgen_coord_add_arc(): arc.a===0\n");
    return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
  }
  sg.length = sg.coords.arc.xyz.l;
  if(sg.length < TG_RESOLUTION) {
    sg.length = 0;
    debug("# tp: trajgen_coord_add_arc(): sg.length~==0\n");
    return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
  }
  if (trajgen_coord_queue_push_back(tp, &sg) != 0) {
    return tp->last_error;
  }
  tp->end_pose = end;
  tp->next_sg_id++;
  tp->is_done = 0;
  return TP_ERR_OK;
}
 
/**
 * Push a curve/nurbs segment.
 * @param pose_t end
 * @param pose_vector_t start_handle
 * @param pose_vector_t end_handle
 * @param real_t velocity
 * @param real_t acceleration
 * @return trajgen_coord_error_t
 */
COORD_TG_STATICI trajgen_coord_error_t trajgen_coord_add_curve(trajgen_coord_t* tp,
    pose_t end, pose_vector_t start_ctrl_point, pose_vector_t end_ctrl_point,
    real_t velocity, real_t acceleration)
{
  #ifdef WITH_CURVES
  trajgen_sg_t sg;
  pose_position_t start_xyz, end_xyz, start_uvw, end_uvw, start_abc, end_abc;
  sg.sync_dio = tp->sync_dio_command;
  tp->sync_dio_command.clear = tp->sync_dio_command.set = 0;
  sg.motion_type = SG_CURVE;
  sg.id = tp->next_sg_id;
  sg.is_active = 0;
  sg.sync_type = tp->sync.type;
  sg.override_enabled = tp->override_enabled;
  sg.blending_tolerance = tp->blending_tolerance<TG_RESOLUTION ? 0:tp->blending_tolerance;
  if (tp->is_aborting) return trajgen_coord_raise_error(tp, TP_ERR_ABORTING);
  if (trajgen_coord_is_queue_full(tp)) return trajgen_coord_raise_error(tp, TP_ERR_QUEUE_FULL);
  acceleration = acceleration == 0 ? tp->config.max_acceleration : acceleration;
  if (velocity < TG_RESOLUTION || velocity > tp->config.max_velocity) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_SPEED);
  } else if (acceleration < TG_RESOLUTION || acceleration > tp->config.max_acceleration) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_ACCEL);
  } else if(!pose_isfinite(end) || !v_isfinite(start_ctrl_point) || !v_isfinite(end_ctrl_point)) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_POSE);
  }
  sg.v = velocity;
  sg.a = acceleration;
  v_pose_split(tp->end_pose, &start_xyz, &start_abc, &start_uvw);
  v_pose_split(end, &end_xyz, &end_abc, &end_uvw);
  #ifdef NO_ABC
  v_setzero(sg.coords.curve.abc.start);
  v_setzero(sg.coords.curve.abc.end);
  v_setzero(sg.coords.curve.abc.u);
  sg.coords.curve.abc.tm = 0;
  #endif
  #ifdef NO_UVW
  v_setzero(sg.coords.curve.uvw.start);
  v_setzero(sg.coords.curve.uvw.end);
  v_setzero(sg.coords.curve.uvw.u);
  sg.coords.curve.uvw.tm = 0;
  #endif
  if(
    v_distance(start_xyz, end_xyz) < TG_RESOLUTION
  ) {
    debug("# tp: trajgen_coord_add_curve(): segment length ~== 0\n");
    return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
  } else if(
    v_curve_define(&sg.coords.curve.xyz, start_xyz, end_xyz, start_ctrl_point, end_ctrl_point)
    #ifndef NO_ABC
    || v_lin_define(&sg.coords.curve.abc, start_abc, end_abc)
    #endif
    #ifndef NO_UVW
    || v_lin_define(&sg.coords.curve.uvw, start_uvw, end_uvw)
    #endif
  ) {
    return trajgen_coord_raise_error(tp, TP_ERR_INVALID_PARAM);
  }
  sg.length = sg.coords.curve.xyz.len;
  if(sg.length < TG_RESOLUTION) {
    sg.length = 0;
    debug("# tp: trajgen_coord_add_curve(): sg.length~==0\n");
    return trajgen_coord_raise_error(tp, TP_ERR_SEGMENT_LENGTH_ZERO);
  } else if (trajgen_coord_queue_push_back(tp, &sg) != 0) {
    return tp->last_error;
  }
  tp->end_pose = end;
  tp->next_sg_id++;
  tp->is_done = 0;
  return TP_ERR_OK;
  #else
  return TP_ERR_INVALID_MOTION_TYPE;
  #endif
}
 
/**
 * Initialises auxiliary variables of a segment in the queue.
 * @param trajgen_sg_t *sg
 * @return trajgen_coord_error_t
 */
static TG_INLINE trajgen_coord_error_t trajgen_sg_activate(trajgen_sg_t *sg)
{
  CHK_PTR(!sg) return TP_ERR_TP_NULL_POINTER;
  if(sg->is_active) return TP_ERR_OK;
  sg->is_active = 1;
  sg->is_blending = 0;
  sg->blending_velocity = 0.0;
  sg->progress = 0.0;
  sg->current_velocity = 0.0;
  return TP_ERR_OK;
}
 
/**
 * Writes the start/end position of a trajectory segment into pos.
 * @param trajgen_sg_t *sg
 * @param bool_t of_endpoint
 * @param pose_t* pos
 * @return trajgen_coord_error_t
 */
typedef enum { sg_of_curr=0, sg_of_end } trajgen_sg_get_pose_sel;
static trajgen_coord_error_t trajgen_sg_get_pose(trajgen_coord_t* tp, trajgen_sg_t *sg,
    trajgen_sg_get_pose_sel of_endpoint, pose_t* pos)
{
  CHK_PTR(!tp || !sg || !pos) return TP_ERR_TP_NULL_POINTER;
  pose_position_t xyz, abc, uvw;
  real_t progress = of_endpoint==sg_of_end ? sg->length : sg->progress;
  #ifdef NO_ABC
  v_setzero(abc);
  #endif
  #ifdef NO_UVW
  v_setzero(uvw);
  #endif
  switch (sg->motion_type) {
    case SG_LINEAR:
      if (sg->coords.line.xyz.tm > 0.) {
        v_lin_get(&sg->coords.line.xyz, progress, &xyz);
        #ifndef NO_ABC
        v_lin_get(&sg->coords.line.abc, progress * sg->coords.line.abc.tm/sg->length, &abc);
        #endif
        #ifndef NO_UVW
        v_lin_get(&sg->coords.line.uvw, progress * sg->coords.line.uvw.tm/sg->length, &uvw);
        #endif
      #ifndef NO_ABC
      } else if (sg->coords.line.abc.tm > 0.) {
        v_lin_get(&sg->coords.line.abc, progress, &abc);
        xyz = sg->coords.line.xyz.start;
        #ifndef NO_UVW
        uvw = sg->coords.line.uvw.start;
        #endif
      #endif
      #ifndef NO_UVW
      } else if (sg->coords.line.uvw.tm > 0.) {
        v_lin_get(&sg->coords.line.uvw, progress, &uvw);
        xyz = sg->coords.line.xyz.start;
        #ifndef NO_ABC
        abc = sg->coords.line.abc.start;
        #endif
      #endif
      } else {
        xyz = sg->coords.line.xyz.start;
        #ifndef NO_ABC
        abc = sg->coords.line.abc.start;
        #endif
        #ifndef NO_UVW
        uvw = sg->coords.line.uvw.start;
        #endif
      }
      break;
    case SG_CIRCULAR:
      // Args checked in add function
      //v_arc_get(&sg->coords.arc.xyz, progress * sg->coords.arc.xyz.a/sg->length, &xyz);
      v_arc_get(&sg->coords.arc.xyz, progress, &xyz);
      #ifndef NO_ABC
      v_lin_get(&sg->coords.arc.abc, progress * sg->coords.arc.abc.tm/sg->length, &abc);
      #endif
      #ifndef NO_UVW
      v_lin_get(&sg->coords.arc.uvw, progress * sg->coords.arc.uvw.tm/sg->length, &uvw);
      #endif
      break;
    case SG_CURVE:
      #ifdef WITH_CURVES
      // Curve length checked in add function
      v_curve_get(&sg->coords.curve.xyz, progress, &xyz);
      #ifndef NO_ABC
      v_lin_get(&sg->coords.curve.abc, progress * sg->coords.curve.abc.tm/sg->length, &abc);
      #endif
      #ifndef NO_UVW
      v_lin_get(&sg->coords.curve.uvw, progress * sg->coords.curve.uvw.tm/sg->length, &uvw);
      #endif
      break;
      #endif
    default:
      v_invalidate(xyz); v_invalidate(abc); v_invalidate(uvw);
      trajgen_coord_raise_error(tp, TP_ERR_INVALID_MOTION_TYPE);
  }
  v_pose_merge(xyz, abc, uvw, pos);
  return TP_ERR_OK;
}
 
/**
 * Calculates the next linearised position for a segment
 * v= new velocity, a=new acceleration, v0=velocity without override constrain.
 *
 * @param trajgen_sg_t *sg
 * @param real_t *overrun
 * @param real_t *v
 * @param real_t *a
 * @param real_t *v0
 * @return trajgen_coord_error_t
 */
static trajgen_coord_error_t trajgen_sg_tick(trajgen_coord_t* tp, trajgen_sg_t *sg,
    real_t *overrun, real_t *v, real_t *a, real_t *v0)
{
  CHK_PTR(!tp || !sg) return TP_ERR_TP_NULL_POINTER;
  real_t d=0, vn0=0, vn, an, T=tp->config.sample_interval;
  if ((sg->progress >= sg->length)
  || ((d = sg->current_velocity*T + 2.*(sg->progress-sg->length)) >= 0)
  || ((vn0=(sqrtz(T*T/4-d/sg->a) - T/2) * sg->a) < TG_RESOLUTION)
  ) {
    an = vn = vn0 = 0.0;
    d = sg->progress - sg->length;
    sg->progress = sg->length;
  } else {
    if (vn0 > tp->config.max_velocity) vn0 = tp->config.max_velocity;
    if (vn0 > sg->v) vn0 = sg->v;
    if ((vn=vn0) > sg->v * tp->_i.override) vn = sg->v * tp->_i.override;
    an = (vn - sg->current_velocity) / T;
    real_t aa = sg->a ;
    an = (an > aa) ? (aa) : ((an < -aa) ? (-aa) : (an));
    vn = sg->current_velocity + an * T;
    sg->progress += (vn + sg->current_velocity) * T/2;
    d = sg->progress - sg->length;
    if(d >= 0) { sg->progress = sg->length; vn = an = vn0 = 0; }
  }
  sg->current_velocity = vn;
  // if(next_sg && next_sg->progress == 0) next_sg->progress = d;
  if(overrun && d > 0) *overrun = d;
  if (v) *v = vn;
  if (a) *a = an;
  if (v0) *v0 = vn0;
  return TP_ERR_OK;
}
 
/**
 * Perform one planer interval tick.
 * @return trajgen_coord_error_t
 */
COORD_TG_STATIC trajgen_coord_error_t trajgen_coord_tick(trajgen_coord_t* tp)
{
  trajgen_coord_error_t err = TP_ERR_OK;
  trajgen_sg_t *sg, *nextsg;
 
  // Initialisation
  tp->_i.override = tp->override;
  tp->_i.last_position = tp->pose;
 
  // Read the queue front
  if (!(sg = trajgen_coord_queue_front(tp))) {
    // Queue empty, nothing to do
    tp->queue.num_elements = tp->queue.start = tp->queue.end = 0;
    tp->end_pose = tp->pose;
    tp->is_done = 1;
    tp->num_queued_active = tp->sg_id = tp->is_pausing = tp->is_aborting = 0;
    tp->sync.i_offset = tp->sync.i_isset = 0;
    return TP_ERR_OK;
  } else if (sg->progress == sg->length) {
    trajgen_coord_queue_pop_front(tp, 1);
    if (!(sg = trajgen_coord_queue_front(tp))) return TP_ERR_OK;
  }
 
  // Fetch next segment for blending
  // NO_MOTION_BLENDING: Compiler will subsequently remove blocks below as nextsg
  // evaluates to 0.
  nextsg = ((!NO_MOTION_BLENDING) && sg->blending_tolerance>0) ?
    trajgen_coord_queue_get(tp, 1) : (trajgen_sg_t*) 0;
 
  // Abort handling
  if (tp->is_aborting) {
    if (sg->current_velocity == 0.0 && (!nextsg ||
        (nextsg && nextsg->current_velocity == 0.0))) {
      tp->queue.num_elements = tp->queue.start = tp->queue.end = 0;
      tp->end_pose = tp->pose;
      tp->is_done = 1;
      tp->is_aborting = tp->is_pausing = tp->num_queued_active = tp->sg_id =
          tp->sync_dio_current = 0;
      return TP_ERR_OK; // v=0, return possible
    } else {
      sg->v = 0.0;
      sg->a = tp->config.max_acceleration;
      if (nextsg) {
        nextsg->v = 0.0;
        nextsg->a = tp->config.max_acceleration;
      }
    }
  }
 
  // Start of motion (first queue item read or if blending disabled)
  if (!sg->is_active) {
    trajgen_sg_activate(sg);
    tp->sg_id = sg->id;
    tp->num_queued_active = 1;
    tp->sync_dio_current = (tp->sync_dio_current | sg->sync_dio.set) &
        (~sg->sync_dio.clear);
    debug2("# tp: activated segment %lu ...\n", (unsigned long) sg->id);
    if(sg->motion_type != SG_CIRCULAR) debug2("# tp: segment target={%g, %g, %g}\n",
        sg->coords.line.xyz.end.x, sg->coords.line.xyz.end.y, sg->coords.line.xyz.end.z);
  }
 
  // Override initialisation, more override constrains may follow
  if(!sg->override_enabled) tp->_i.override = 1.0;
 
  // Handle next queued segment (only if blending enabled), on syncing
  // blending will be disabled, so this is block executed only once.
  if (nextsg && !nextsg->is_active) {
    #ifndef NO_TRAJECTORY_SYNC
    nextsg->sync_type = TP_SYNC_NONE; // give the compiler a reason to kick all this stuff
    #endif
    // Next segment processing needs to be postponed in case syncing ?
    if((nextsg->sync_type != TP_SYNC_NONE && !isfinite(tp->sync.reference*tp->sync.scaler))
    || (nextsg->sync_type != TP_SYNC_NONE && (tp->sync.scaler == 0))
    || (nextsg->sync_type == TP_SYNC_VELOCITY && tp->sync.reference <= 0)
    ) {
      sg->blending_tolerance = 0;
      nextsg = (trajgen_sg_t*) 0;
    } else {
      // Check if blending is possible, d evaluates to the angle between vectors.
      //
      // stfwi: Find way to optimise acos and cos later. Performance waste.
      //
      pose_vector_t v1, v2; real_t cos_phi, phi;
      if(trajgen_sg_end_direction(tp, sg, &v1) != TP_ERR_OK
      || trajgen_sg_start_direction(tp, nextsg, &v2) != TP_ERR_OK
      ) {
        return trajgen_coord_raise_error(tp, TP_ERR_UNIT_VECTOR_CALC_INVALID_TYPE);
      }
      if((cos_phi = v_dot(v1,v2)) < 0 // double check for erroneous calcs
      || (phi = acos(cos_phi)) > M_PI/180.*MAX_ALLOWED_BLENDING_ANGLE_DEG) {
        debug("# tp: disabled blending for %lu->%lu (angle=%.0fdeg > %.0fdeg)\n",
            sg->id, nextsg->id, phi*(180./M_PI), (real_t)MAX_ALLOWED_BLENDING_ANGLE_DEG);
        sg->blending_tolerance = 0;
        nextsg = (trajgen_sg_t*) 0;
      } else {
        // Blending allowed, precalculate blending activation velocity.
        // Blending rules:
        //  - Only from second half of the current segment to the first half of
        //    the next to prevent 3-segment race conditions.
        //  - Velocity constraints must apply.
        // Triangle speed ramp (without trimmed velocities) --> v^2=s/2 * a.
        //
        // Peak velocity check
        real_t v = sqrtz(nextsg->length/2 * (sg->a<nextsg->a ? sg->a : nextsg->a));
        if(cos_phi > 1) cos_phi = 1; // If unit vectors are a little bit too long
        if(cos_phi > 0 && phi > M_PI/180 * STRAIGHT_BLENDING_ANGLE) {
          real_t vv;
          // Additional tolerance dependent speed constrain
          vv = 2.0 * sqrtz(sg->a * sg->blending_tolerance / cos_phi);
          if(v > vv) v = vv;
          debug2("# tp: activated segment %lu for blending at speed %g...\n",
              (unsigned long) nextsg->id, v);
        } else {
          // Blending in same direction
          // StfWi: Check if more accurate cross product and arcsin is required
          // to validate this.
          sg->blending_tolerance = TG_RESOLUTION; // hint for straight blending
          debug2("# tp: activated segment %lu for 0-angle blending at speed %g...\n",
              (unsigned long) nextsg->id, v);
        }
        if (v > nextsg->v) v = nextsg->v;
        if (v > sg->v) v = sg->v;
        sg->blending_velocity = v;
        // Checkout/activate next segment
        trajgen_sg_activate(nextsg);
        tp->num_queued_active = 2;
        if(sg->motion_type != SG_CIRCULAR) debug2("# tp: segment target={%g, %g, %g}\n",
            nextsg->coords.line.xyz.end.x, nextsg->coords.line.xyz.end.y,
            nextsg->coords.line.xyz.end.z);
      }
    }
  }
 
  // Syncing (manipulates the override depending on an external position or velocity).
  #ifndef NO_TRAJECTORY_SYNC
  tp->sync.i_isset &= (sg->sync_type == TP_SYNC_POSITION);
  if (sg->sync_type == TP_SYNC_VELOCITY && !tp->is_aborting) {
    // Update calculated override depending on the reference speed:
    real_t v = tp->sync.scaler * tp->sync.reference;
    if (tp->is_pausing || !isfinite(v) || v < 0) v = 0;
    if (v < sg->v) tp->_i.override *= (v / sg->v);
  } else if (sg->sync_type == TP_SYNC_POSITION) {
    // Stfwi: CHECK: How's the abort behaviour supposed to be ?
    //  - Thread tapping: critical to stay in sync <-- vs. -->
    //  - Touch probe position tune-in: estop important.
    if (sg->progress >= sg->length) {
      // Segment overrun/finished, initialise sync flag for next one:
      tp->sync.i_isset = isfinite(tp->sync.reference*tp->sync.scaler) && tp->sync.scaler!=0;
      tp->sync.i_offset += tp->sync.i_isset ? sg->length : 0.0;
    } else if (!tp->sync.i_isset && isfinite(tp->sync.reference*tp->sync.scaler)
        && tp->sync.scaler!=0) {
      // Ref position now validated, initialise position offset:
      tp->sync.i_offset = (tp->sync.reference * tp->sync.scaler) - sg->progress;
      tp->sync.i_isset = 1;
    }
    // Determine the new override value for this cycle
    if (!tp->sync.i_isset) {
      tp->_i.override = 0;
    } else {
      // ds/dt = 1/2 a * dt + d^2s   ---(d^2s===dv)--->  dv = ds/dt - a * dt/2
      real_t ds = (tp->sync.reference * tp->sync.scaler) - tp->sync.i_offset
                    - sg->progress - (nextsg ? nextsg->progress : 0.0);
      if(!isfinite(ds) || tp->sync.scaler==0) {
        // Reference position was invalidated during the motion.
        err = trajgen_coord_raise_error(tp, TP_ERR_REF_POSITION_INVALIDATED_DURING_MOTION);
        trajgen_coord_abort(tp);
        tp->_i.override = 0;
      } else if (ds < 0) {
        // Trajectory is ahead of reference
        tp->_i.override = 0;
      } else {
        // Reference is ahead or matches
        real_t T = tp->config.sample_interval;
        real_t dv = ds / T - sg->a * T / 2.0;
        tp->_i.override = (sg->current_velocity + dv) / sg->v;
        tp->_i.override = (tp->_i.override < 0) ? 0 : ((tp->_i.override > 1) ? 1 : tp->_i.override);
      }
    }
  }
  else // else below macro end : End TP_SYNC_POSITION || (TP_SYNC_VELOCITY && !is_aborting)
  #endif
  if (tp->is_pausing) {
    tp->_i.override = 0;
  }
  if (!nextsg) {
    // No next --> no blending --> quickly calculate next pose and return.
    trajgen_sg_tick(tp, sg, 0,0,0,0);
    trajgen_sg_get_pose(tp, sg, sg_of_curr, &tp->pose);
    return err;
  } else if(!sg->is_blending) {
    // Check when to start blending
    real_t v, vn;
    trajgen_sg_tick(tp, sg, 0, &v, 0, &vn);
    trajgen_sg_get_pose(tp, sg, sg_of_curr, &tp->pose);
    if((sg->progress/sg->length > 0.5) && (vn < sg->blending_velocity)) {
      // Blending_velocity changes its meaning if blending=1:
      // sg->blending_velocity = sg->current_velocity;
      nextsg->current_velocity = sg->blending_velocity - sg->current_velocity;
      sg->is_blending = 1;
      debug2("# tp: start blending segment %lu -> %lu...\n", (unsigned long) sg->id,
          (unsigned long) nextsg->id);
    }
  } else {
    // Blending in progress
    real_t v;
    pose_t last_pose, curr_pose, nextsg_last_pose, nextsg_curr_pose;
    trajgen_sg_get_pose(tp, sg, sg_of_curr, &last_pose);
    trajgen_sg_tick(tp, sg, 0, &v, 0, 0);
    trajgen_sg_get_pose(tp, sg, sg_of_curr, &curr_pose);
    if (tp->sg_id != nextsg->id && sg->current_velocity < nextsg->current_velocity) {
      tp->sync_dio_current = (tp->sync_dio_current|nextsg->sync_dio.set)
          & (~nextsg->sync_dio.clear);
      tp->sg_id = nextsg->id;
      debug2("# tp: switched to segment %lu ...\n", (unsigned long) tp->sg_id);
    }
    trajgen_sg_get_pose(tp, nextsg, sg_of_curr, &nextsg_last_pose);
    real_t vv = nextsg->v;
    nextsg->v = sg->blending_velocity - v;
    nextsg->v = nextsg->v < 0.0 ? 0 : nextsg->v;
    trajgen_sg_tick(tp, nextsg, 0,0,0,0);
    nextsg->v = vv;
    trajgen_sg_get_pose(tp, nextsg, sg_of_curr, &nextsg_curr_pose);
    // Add increments of segment and nextsg to the position
    pose_acc(&tp->pose, curr_pose);
    pose_sub(&tp->pose, last_pose);
    pose_acc(&tp->pose, nextsg_curr_pose);
    pose_sub(&tp->pose, nextsg_last_pose);
  }
  return err;
}
 
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="trajgen_jointtg_*: Free single joint planer">
 
/********************************************************************
 *
 * Simple single axis trajectory planer, used for individual joints.
 *
 ********************************************************************/
 
static trajgen_jointtg_error_t trajgen_jointtg_err(trajgen_jointtg_t *tp, trajgen_jointtg_error_t e)
{ if (!tp) return e; return tp->last_error = e; }
 
trajgen_jointtg_error_t trajgen_jointtg_initialize(trajgen_jointtg_t *tp, trajgen_jointtg_config_t config)
{
  CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
  tp->config = config;
  tp->velocity = tp->position = 0.0;
  tp->is_enabled = 0;
  tp->last_error = TRAJGEN_FREE_ERROR_OK;
  if (config.max_acceleration < 0.0) {
    return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_ACCEL);
  } else if (config.max_velocity < 0.0) {
    return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_VELOCITY);
  } else if (config.sample_interval <= 0) {
    return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_INVALID_SAMPLE_INTERVAL);
  }
  return TRAJGEN_FREE_ERROR_OK;
}
 
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_reset(trajgen_jointtg_t *tp)
{
  CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
  tp->velocity = 0.0;
  tp->command_position = tp->position;
  tp->last_error = TRAJGEN_FREE_ERROR_OK;
  tp->is_pause = 0;
  tp->is_done = 1;
  return TRAJGEN_FREE_ERROR_OK;
}
 
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_set_enabled(trajgen_jointtg_t *tp,
    bool_t enabled)
{
  CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
  if (enabled == tp->is_enabled) {
    return TRAJGEN_FREE_ERROR_OK;
  } else if (!enabled) {
    tp->is_enabled = 0;
  } else {
    if (tp->is_done) trajgen_jointtg_reset(tp);
    tp->is_enabled = 1;
  }
  return TRAJGEN_FREE_ERROR_OK;
}
 
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_set_command_position(trajgen_jointtg_t *tp,
    real_t position)
{ tp->command_position = position; return TRAJGEN_FREE_ERROR_OK; }
 
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_set_command_velocity(trajgen_jointtg_t *tp,
    real_t velocity)
{
  CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
  if (velocity > tp->config.max_velocity) velocity = tp->config.max_velocity;
  else if (velocity < -tp->config.max_velocity) velocity = -tp->config.max_velocity;
  tp->command_velocity = velocity;
  return TRAJGEN_FREE_ERROR_OK;
}
 
JOINT_TG_STATIC real_t trajgen_jointtg_get_current_position(trajgen_jointtg_t *tp)
{ return tp->position; }
 
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_set_current_position(trajgen_jointtg_t *tp,
    real_t position)
{ tp->position = position; return TRAJGEN_FREE_ERROR_OK; }
 
JOINT_TG_STATIC bool_t trajgen_jointtg_is_done(trajgen_jointtg_t *tp)
{ return tp->is_done && tp->velocity == 0.0; }
 
JOINT_TG_STATIC trajgen_jointtg_error_t trajgen_jointtg_tick(trajgen_jointtg_t *tp)
{
  CHK_PTR(!tp) return trajgen_jointtg_err(tp, TRAJGEN_FREE_ERROR_INIT_NULLPOINTER);
  if(tp->command_velocity < 0) tp->is_enabled = 0; // Force disable
  if(!tp->is_enabled) tp->command_position = tp->position; // Force break
  if (tp->is_enabled || tp->velocity != 0 || tp->command_position != tp->position) {
    real_t T = tp->config.sample_interval;
    real_t dp = tp->command_position - tp->position;
    real_t vd = tp->config.max_acceleration * T;
    real_t dir = dp > 0 ? 1 : -1;
    real_t v;
    // Velocity required using s = 1/2 a t^2 + v0 * t + s0; v^2 = s*a
    dp *= dir; // Work with positive values
    if(dp < TG_RESOLUTION) dp = 0;
    v = (dp <= 0) ? 0 : (sqrtf(2.0 * tp->config.max_acceleration*dp + vd*vd)-vd);
    if(tp->is_pause) v = 0;
    if(v > tp->config.max_velocity && tp->config.max_velocity > 0) v = tp->config.max_velocity;
    if(v > tp->command_velocity && tp->command_velocity > 0) v = tp->command_velocity;
    if((v*=dir) > tp->velocity + vd) {
      tp->velocity += vd;
    } else if (v < tp->velocity - vd) {
      tp->velocity -= vd;
    } else {
      tp->velocity = v;
    }
    tp->position += tp->velocity * T;
    if (v < TG_RESOLUTION && fabs(tp->position - tp->command_position) < TG_RESOLUTION) {
      tp->position = tp->command_position;
      tp->velocity = 0.0;
    }
  }
  tp->is_done = (tp->velocity == 0.0 && tp->position == tp->command_position);
  return TRAJGEN_FREE_ERROR_OK;
}
 
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="trajgen_man_*: Manual operation planer (tele operation)">
 
/********************************************************************
 *
 *
 * Manual operation planer (tele operation)
 *
 *
 ********************************************************************/
 
static trajgen_man_error_t trajgen_man_err(trajgen_man_t *mtp, trajgen_man_error_t e)
{ if (!mtp) return e; return mtp->last_error = e; }
 
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_initialize(trajgen_man_t *_tp,
    trajgen_man_config_t config)
{
  CHK_PTR(!_tp) return TG_MAN_ERROR_INIT_NULLPOINTER;
  ZERO_MEMORY(_tp, sizeof (trajgen_man_t)); // Set all data here to 0
  if (!pose_is_all_greater_equal_zero(config.max_acceleration)) {
    return trajgen_man_err(_tp, TG_MAN_ERROR_INIT_INVALID_MAX_ACCEL);
  } else if (!pose_is_all_greater_equal_zero(config.max_velocity)) {
    return trajgen_man_err(_tp, TG_MAN_ERROR_INIT_INVALID_MAX_VELOCITY);
  } else if (config.sample_interval <= 0) {
    return trajgen_man_err(_tp, TG_MAN_ERROR_INIT_INVALID_SAMPLE_INTERVAL);
  }
  _tp->config = config;
  return TG_MAN_ERROR_OK;
}
 
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_reset(trajgen_man_t *mtp)
{
  pose_set_zero(&mtp->velocity);
  pose_set_zero(&mtp->current_velocity);
  mtp->is_enabled = 0;
  mtp->last_error = TG_MAN_ERROR_OK;
  return TG_MAN_ERROR_OK;
}
 
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_abort(trajgen_man_t *mtp)
{ mtp->is_enabled = 0; return TG_MAN_ERROR_OK; }
 
MANUAL_TG_STATIC bool_t trajgen_man_is_done(trajgen_man_t *mtp)
{ return pose_is_zero(mtp->velocity) && pose_is_zero(mtp->current_velocity); }
 
MANUAL_TG_STATIC pose_t trajgen_man_get_position(trajgen_man_t *mtp)
{ return mtp->pose; }
 
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_set_position(trajgen_man_t *mtp,
    pose_t position)
{ mtp->pose = position; return TG_MAN_ERROR_OK; }
 
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_set_velocity(trajgen_man_t *mtp,
    pose_t speed)
{
  pose_trim_all_upper(&speed, mtp->config.max_velocity);
  pose_neg(&speed);
  pose_trim_all_upper(&speed, mtp->config.max_velocity);
  pose_neg(&speed);
  mtp->velocity = speed;
  return TG_MAN_ERROR_OK;
}
 
MANUAL_TG_STATIC trajgen_man_error_t trajgen_man_tick(trajgen_man_t *mtp)
{
  pose_t dv; real_t T=mtp->config.sample_interval;
  if (!mtp->is_enabled) pose_set_zero(&mtp->velocity);
  // Determine maximum required acceleration to obtain the speed in one cycle,
  // including the polar axes
  pose_diff(mtp->velocity, mtp->current_velocity, &dv);
  if (!pose_is_zero(dv)) {
    real_t d, scl = 1.;
    // Something changed
    pose_scale(&dv, 1./T);
    // Accelerations for.x/y/z can be pre-checked with the trajectory acceleration
    if (dv.x != 0 && (d = fabs(mtp->config.max_acceleration.x / dv.x)) < scl) scl = d;
    if (dv.y != 0 && (d = fabs(mtp->config.max_acceleration.y / dv.y)) < scl) scl = d;
    if (dv.z != 0 && (d = fabs(mtp->config.max_acceleration.z / dv.z)) < scl) scl = d;
    #ifndef NO_ABC
    if (dv.a != 0 && (d = fabs(mtp->config.max_acceleration.a / dv.a)) < scl) scl = d;
    if (dv.b != 0 && (d = fabs(mtp->config.max_acceleration.b / dv.b)) < scl) scl = d;
    if (dv.c != 0 && (d = fabs(mtp->config.max_acceleration.c / dv.c)) < scl) scl = d;
    #else
    dv.a = dv.b = dv.c = 0;
    #endif
    #ifndef NO_UVW
    if (dv.u != 0 && (d = fabs(mtp->config.max_acceleration.u / dv.u)) < scl) scl = d;
    if (dv.v != 0 && (d = fabs(mtp->config.max_acceleration.v / dv.v)) < scl) scl = d;
    if (dv.w != 0 && (d = fabs(mtp->config.max_acceleration.w / dv.w)) < scl) scl = d;
    #else
    dv.u = dv.v = dv.w = 0;
    #endif
    if (scl < 1.0) {
      d = scl * T;       // Scale the speed down to the maximum acceleration allowed
      pose_scale(&dv, d); // Integrate the new velocity
      pose_acc(&mtp->current_velocity, dv);
    } else {
      // We reached the requested velocity in the actual sample period. accel
      // is now near zero. So we round to the requested velocity.
      mtp->current_velocity = mtp->velocity;
    }
  }
  dv = mtp->current_velocity;
  pose_scale(&dv, T); // Integrate position
  pose_acc(&mtp->pose, dv);
  return TG_MAN_ERROR_OK;
}
 
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="trajgen_*: Main trajectory generation coordinator">
 
/*******************************************************************************
 *
 * Main trajectory generation coordinator. Manages Free planers for all joints,
 * manual and coordinated planer, including errors and switching, interpolating,
 * and trajgen position updating.
 *
 * It is in the very end a finite state machine based organisation of the
 * available functionalities.
 *
 ******************************************************************************/
 
static trajgen_t *tg;
 
#define MTG (&tg->man_planer)
#define CTG (&tg->coord_planer)
 
/**
 * Generates the last_error variable and forwards the code passed as argument.
 * @param source
 * @param code
 * @param joint
 * @return
 */
static trajgen_error_t tg_err(unsigned code, unsigned joint)
{
  trajgen_error_details_t e;
  if (!tg) return (trajgen_error_t) code;
  e.errno = 0;
  e.sel.joint = joint;
  e.sel.code = code;
  tg->last_error = e;
  #ifdef TG_DEBUG_LEVEL
  if(code) {
    char s[128]; s[0]=s[127]='\0'; trajgen_errstr(tg->last_error.errno, s, 127);
    debug("# tg: trajgen_err(): ! Error: %s\n", s);
  }
  #endif
  return (trajgen_error_t) code;
}
 
/* not static but not exported in header, linker shall drop it if unused */
const char* trajgen_state_name(trajgen_state_t s)
{
  if(s<0 || s>TRAJ_STATE_TG_MAN_LEAVE) return "INVALID";
  static const char *names[] = {
    "disabled", "jointtg", "coordinated", "manual", "INVALID",
    "(ok-to-switch)", "(disabling)", "(jointtg enter)", "(jointtg leave)",
    "(coordinated enter)", "(coordinated leave)", "(manual enter)",
    "(manual leave)"
  };
  return names[s];
}
 
#define CHKERR(X) tg_err((X), 0)
 
/* Coord wrappers */
trajgen_error_t trajgen_pause()
{
  for(int i=0; i<tg->config.number_of_used_joints; i++) {
    tg->joints[i].tg.is_pause = 1;
  }
  return CHKERR(trajgen_coord_pause(CTG));
}
 
trajgen_error_t trajgen_resume()
{
  for(int i=0; i<tg->config.number_of_used_joints; i++) {
    tg->joints[i].tg.is_pause = 0;
  }
  return CHKERR(trajgen_coord_resume(CTG));
}
 
bool_t trajgen_queue_full()
{ return trajgen_coord_is_queue_full(CTG); }
 
uint32_t trajgen_current_id()
{ return trajgen_coord_get_current_id(CTG); }
 
uint16_t trajgen_num_queued()
{ return trajgen_coord_get_num_queued(CTG); }
 
uint16_t trajgen_queue_size()
{ return trajgen_coord_get_queue_size(CTG); }
 
trajgen_error_t trajgen_add_line(pose_t end, real_t velocity, real_t acceleration)
{ return CHKERR(trajgen_coord_add_line(CTG, end, velocity, acceleration)); }
 
trajgen_error_t trajgen_add_arc(pose_t end, pose_position_t center, pose_vector_t normal,
    unsigned n_turns, real_t velocity, real_t acceleration)
{ return CHKERR(trajgen_coord_add_arc(CTG, end, center, normal, n_turns, velocity,
    acceleration)); }
 
trajgen_coord_error_t trajgen_add_curve(pose_t end, pose_vector_t start_ctrl_point,
    pose_vector_t end_ctrl_point, real_t velocity, real_t acceleration)
{ return CHKERR(trajgen_coord_add_curve(CTG, end, start_ctrl_point, end_ctrl_point,
    velocity, acceleration)); }
 
/* Free wrappers */
trajgen_error_t trajgen_joint_command_velocity(unsigned joint, real_t v)
{
  if(joint > tg->config.number_of_used_joints) return CHKERR(TRAJ_ERROR_INVALID_JOINT_NO);
  return CHKERR(trajgen_jointtg_set_command_velocity(&tg->joints[joint].tg, v));
}
 
#undef CHKERR
 
/**
 * Returns the current tick counter of the TG
 * @return uint32_t
 */
uint32_t trajgen_get_tick()
{ return tg->_internals.tick; }
 
/**
 * Abort / stop (all planers)
 * @return trajgen_error_t
 */
trajgen_error_t trajgen_abort()
{
  debug("# tg: trajgen_abort()\n");
  unsigned i;
  trajgen_coord_abort(CTG);
  trajgen_man_abort(MTG);
  for (i=0; i < TG_MAX_JOINTS; i++) tg->joints[i].tg.is_enabled = 0;
  tg->is_done = 0;
  tg->requested_state = TRAJ_STATE_DISABLED;
  return TRAJ_ERROR_OK;
}
 
/**
 * Sets the new requested trajgen state.
 * @param trajgen_state_t state
 * @return trajgen_error_t
 */
trajgen_error_t trajgen_switch_state(trajgen_state_t state)
{
  if (state < TRAJ_STATE_DISABLED || state >= TRAJ_______INTERNAL_STATES) {
    return tg_err(TRAJ_ERROR_INVALID_SWITCHING_STATE, 0);
  }
  tg->is_done = 0;
  tg->requested_state = state;
  debug("# tg: trajgen_switch_state(%s)\n", trajgen_state_name(state));
  return TRAJ_ERROR_OK;
}
 
/**
 * Initializes the trajectory generator and all sub systems. This function
 * MUST BE CALLED BEFORE ANY OTHER OPERATIONS with the trajgen, as internal
 * pointer require initialization.
 * @param trajgen_t *trajectory_generator_data_location
 * @param trajgen_config_t config
 * @return trajgen_error_t
 */
trajgen_error_t trajgen_initialize(trajgen_t *trajgen_data_location, trajgen_config_t config)
{
  int i, err = 0;
  debug("# tg: trajgen_initialize(...)\n");
  // Assign basics
  tg = trajgen_data_location;
  if (!tg) return tg_err(TRAJ_ERROR_NULL_POINTER, 0);
  ZERO_MEMORY(tg, sizeof (trajgen_t));
 
  // Fixed configuration checks (in case the compiler does not raise errors before)
  if (FLOAT_EPS <= 0
  ||  TG_RESOLUTION <= FLOAT_EPS // stfwi: intentionally at least 2*FLOAT_EPS
  ||  TG_D_RES <= FLOAT_EPS
  ||  TG_A_RES <= FLOAT_EPS
  ||  TG_QUEUE_SIZE < 4
  ||  TG_MAX_JOINTS > JOINT_MAX_JOINTS
  ||  sizeof(real_t) < sizeof(float)
  ){
    return tg_err(TRAJ_ERROR_CONFIG_COMPILE_SETTING_INVALID, 0);
  }
  // Configuration checks
  if (config.number_of_used_joints > TG_MAX_JOINTS || !config.number_of_used_joints) {
    return tg_err(TRAJ_ERROR_CONFIG_LAST_USED_JOINT_INVALID, 0);
  }
  if(!config.interpolation_rate) {
    return tg_err(TRAJ_ERROR_CONFIG_INTERPOLATION_RATE_INVALID, 0);
  }
  // Coordinated planer initialisation
  {
    trajgen_coord_config_t trajgen_coord_config;
    trajgen_coord_config.sample_interval = config.sample_interval * config.interpolation_rate;
    trajgen_coord_config.max_velocity = config.max_coord_velocity;
    trajgen_coord_config.max_acceleration = config.max_coord_acceleration;
    if ((err = trajgen_coord_init(&(tg->coord_planer), trajgen_coord_config)) != 0) {
      return tg_err(err, 0);
    }
  }
  // Manual op planer initialisation
  {
    trajgen_man_config_t trajgen_man_config;
    trajgen_man_config.max_acceleration = config.max_manual_accelerations;
    trajgen_man_config.max_velocity = config.max_manual_velocities;
    trajgen_man_config.sample_interval = config.sample_interval * config.interpolation_rate;
    if ((err = trajgen_man_initialize(MTG, trajgen_man_config)) != 0) {
      return tg_err(err, 0);
    }
  }
  // Cubic interpolators and joints
  for (i = 0; i < TG_MAX_JOINTS; i++) {
    config.joints[i].sample_interval = config.sample_interval * config.interpolation_rate;
    tg->_internals.joint_positions[i] = &(tg->joints[i].position);
    tg->_internals.planer_positions[i] = &(tg->joints[i].planer_position);
    if (i >= config.number_of_used_joints) {
      config.joints[i].max_acceleration = config.joints[i].max_velocity = 0;
    }
    // Interpolators are all standard initialised
    if ((err = interpolator_init(&(tg->joints[i].interpolator), config.sample_interval
        *config.interpolation_rate, config.interpolation_rate)) != 0) {
      return tg_err(err, i);
    }
    // Free joint trajgens
    if ((err = trajgen_jointtg_initialize(&tg->joints[i].tg, config.joints[i])) != 0) {
      return tg_err(err, i);
    }
  }
  // Kinematics
  if ((err = kinematics_initialize(&(tg->kinematics), config.kinematics_functions)) != 0) {
    return tg_err(err, 0);
  }
  tg->config = config;
  // Reset state
  trajgen_reset();
  return TRAJ_ERROR_OK;
}
 
/**
 * Resets the whole trajgen
 * @return trajgen_error_t
 */
trajgen_error_t trajgen_reset()
{
  debug("# tg: trajgen_reset()\n");
 
  // Reset axes trajgens and interpolators
  unsigned i;
  for (i = 0; i < tg->config.number_of_used_joints; i++) {
    tg->_internals.planer_positions[i] = tg->_internals.joint_positions[i];
    tg->joints[i].tg.command_position = tg->joints[i].position;
    trajgen_jointtg_reset(&tg->joints[i].tg);
    interpolator_reset(&tg->joints[i].interpolator, tg->joints[i].planer_position);
  }
 
  // Update planer pose from planer position
  tg->kinematics.forward(tg->_internals.joint_positions, &tg->planer_pose);
  tg->_internals.tick = 0;
 
  // Reset coordinated trajgens
  trajgen_coord_reset(CTG);
  trajgen_man_reset(MTG);
 
  // Reset state and status
  tg->is_done = 1;
  tg->state = TRAJ_STATE_DISABLED;
  return TRAJ_ERROR_OK;
}
 
/**
 * Sets the planer poses and positions corresponding to the current joint
 * positions.
 * @return trajgen_error_t
 */
static TG_INLINE trajgen_error_t trajgen_reset_positions()
{
  int err, i;
  for (i=0; i < tg->config.number_of_used_joints; i++) {
    tg->joints[i].planer_position = tg->joints[i].tg.command_position =
        tg->joints[i].position;
    tg->joints[i].acceleration = tg->joints[i].velocity = tg->joints[i].jerk = 0;
    interpolator_reset(&tg->joints[i].interpolator, tg->joints[i].position);
  }
  if ((err = tg->kinematics.forward(tg->_internals.joint_positions, &tg->planer_pose)) != 0) {
    return tg_err(err, 0);
  }
  tg->kinematics.forward(tg->_internals.joint_positions, &tg->planer_pose);
  trajgen_coord_set_position(CTG, tg->planer_pose);
  trajgen_man_set_position(MTG, tg->planer_pose);
  return TRAJ_ERROR_OK;
}
 
/**
 * Called frequently with the sample period tg.config.sample_interval.
 * @return trajgen_error_t
 */
trajgen_error_t trajgen_tick()
{
  int err, i;
  #if TG_DEBUG_LEVEL > 1
  static trajgen_state_t _dbg_state = TRAJ_STATE_DISABLED;
  if(_dbg_state != tg->state) {
    debug2("# tg: trajgen_tick(): state change: %s --> %s\n", trajgen_state_name(_dbg_state),
        trajgen_state_name(tg->state));
    _dbg_state = tg->state;
  }
  #endif
 
  tg->_internals.tick++;
 
  // Commanded state switching
  if (tg->state != tg->requested_state) {
    switch (tg->state) {
      case TRAJ_STATE_DISABLED:
      case TRAJ_STATE_OK_TO_SWITCH:
        switch (tg->requested_state) {
          case TRAJ_STATE_COORDINATED:
            tg->state = TRAJ_STATE_COORDINATED_ENTER;
            tg->is_done = 0;
            break;
          case TRAJ_STATE_JOINT:
            tg->state = TRAJ_STATE_JOINT_ENTER;
            tg->is_done = 0;
            break;
          case TRAJ_STATE_MAN:
            tg->state = TRAJ_STATE_TG_MAN_ENTER;
            tg->is_done = 0;
            break;
          case TRAJ_STATE_DISABLED:
            // Prevent error below
            break;
          default:
            tg->requested_state = TRAJ_STATE_DISABLED;
            return tg_err(TRAJ_ERROR_INVALID_SWITCHING_STATE, 0);
        }
        break;
      case TRAJ_STATE_COORDINATED:
        tg->state = TRAJ_STATE_COORDINATED_LEAVE;
        break;
      case TRAJ_STATE_JOINT:
        tg->state = TRAJ_STATE_JOINT_LEAVE;
        break;
      case TRAJ_STATE_MAN:
        tg->state = TRAJ_STATE_TG_MAN_LEAVE;
        break;
      default:
        ; // This will be caught below
    }
  }
 
  // State execution and implicit switching
  switch (tg->state) {
    case TRAJ_STATE_COORDINATED_ENTER:
    {
      // Reset the coord planer position to the main generator position.
      // (if jogging/manual/axis independent motions were done before).
      // Update planer pose from joints
      trajgen_reset_positions();
      tg->state = TRAJ_STATE_COORDINATED;
      tg->is_done = 1;
      break;
    }
    case TRAJ_STATE_COORDINATED_LEAVE:
    {
      // Stop the motion, trajgen_coord_abort() can be called every cycle until
      // the motion is done.
      trajgen_coord_abort(CTG);
      // After finishing, switch to disabled preparation state
      // stfwi: CHECK: TIMEOUT TO PREVENT INTERNAL ERROR CASES ?
      if (tg->is_done) {
        tg->state = TRAJ_STATE_OK_TO_SWITCH;
      }
      // NO BREAK, the planer has to run until the motion is finished
    }
    case TRAJ_STATE_COORDINATED:
    {
      trajgen_joint_t *joint;
      // The trajectory is defined using the functions in trajgen_coordinated.h,
      // mainly trajgen_coord_add_line() and trajgen_coord_add_circle().
      // Generate new coarse joint positions for the interpolator
      if (interpolator_need_update(&(tg->joints[0].interpolator))) {
        if ((err = trajgen_coord_tick(CTG)) != 0) {
          return tg_err(err, 0);
        }
        tg->planer_pose = trajgen_coord_get_position(CTG);
        if ((err = tg->kinematics.inverse(&(tg->planer_pose), tg->_internals.planer_positions)) != 0) {
          return tg_err(err, 0);
        }
        for (i = 0; i < tg->config.number_of_used_joints; i++) {
          if ((err = interpolator_push(&(tg->joints[i].interpolator),
              *tg->_internals.planer_positions[i])) != 0) {
            return tg_err(err, i);
          }
        }
      }
      bool_t v0 = 1;
      if (!interpolator_need_update(&(tg->joints[0].interpolator))) {
        // Interpolate (every sample interval)
        for (i = 0; i < tg->config.number_of_used_joints; i++) {
          joint = &tg->joints[i];
          if ((err = interpolator_interpolate(&(joint->interpolator), &(joint->position),
              &(joint->velocity), &(joint->acceleration), &(joint->jerk))) != 0) {
            return tg_err(err, i);
          }
          v0 &= (joint->velocity < TG_RESOLUTION);
          if(!isfinite(joint->position)) {
            return tg_err(TRAJ_ERROR_NUMERIC, i);
          }
        }
      }
      // Transfer state variables
      tg->is_done = trajgen_coord_is_done(CTG) && v0;
      break;
    }
 
    ////////////////////////////////////////////////////////////////////////
 
    case TRAJ_STATE_JOINT_ENTER:
    {
      int i;
      trajgen_reset_positions();
      for (i = 0; i < tg->config.number_of_used_joints; i++) {
        if((err = trajgen_jointtg_reset(&tg->joints[i].tg))) {
          tg_err(err, i);
        } else {
          if(tg->config.joints[i].max_acceleration > 0
          && tg->config.joints[i].max_velocity > 0) {
            tg->joints[i].tg.is_enabled = 1;
            tg->joints[i].tg.is_done = 0;
          } else {
            tg->joints[i].tg.is_enabled = 0;
          }
        }
      }
      tg->state = TRAJ_STATE_JOINT;
      break;
    }
    case TRAJ_STATE_JOINT_LEAVE:
    {
      int i;
      for (i = 0; i < tg->config.number_of_used_joints; i++) {
        tg->joints[i].tg.is_enabled = 0;
      }
      if (tg->is_done) {
        tg->state = TRAJ_STATE_OK_TO_SWITCH;
      }
    }
    case TRAJ_STATE_JOINT:
    {
      int i, n_done = 0;
      trajgen_joint_t *joint;
      for (i = 0; i < tg->config.number_of_used_joints; i++) {
        joint = &tg->joints[i];
        if (interpolator_need_update(&(joint->interpolator))) {
          if ((err = trajgen_jointtg_tick(&joint->tg)) != 0) {
            return tg_err(err, i);
          }
          joint->planer_position = joint->tg.position;
          if (joint->tg.is_done) {
            n_done++;
          }
          if ((err = interpolator_push(&(joint->interpolator), joint->planer_position)) != 0) {
            return tg_err(err, i);
          }
        }
        if (interpolator_interpolate(&(joint->interpolator), &(joint->position),
            &(joint->velocity), &(joint->acceleration), &(joint->jerk)) != 0) {
          return tg_err(err, i);
        }
        if(!isfinite(joint->position)) {
          return tg_err(TRAJ_ERROR_NUMERIC, i);
        }
      }
      tg->is_done = n_done >= tg->config.number_of_used_joints;
      break;
    }
 
    ////////////////////////////////////////////////////////////////////////
 
    case TRAJ_STATE_TG_MAN_ENTER:
    {
      // Reset velocities/accelerations
      trajgen_man_reset(MTG);
      // Update planer pose
      trajgen_reset_positions();
      // Enable
      MTG->is_enabled = 1;
      // Switch state
      tg->state = TRAJ_STATE_MAN;
      break;
    }
    case TRAJ_STATE_TG_MAN_LEAVE:
    {
      // Force manual op command velocity to zero
      trajgen_man_abort(MTG);
      // If the axes are not moving anymore, switch state
      if (tg->is_done) {
        tg->state = TRAJ_STATE_OK_TO_SWITCH;
      }
      // NO BREAK HERE
    }
    case TRAJ_STATE_MAN:
    {
      int i;
      trajgen_joint_t *joint;
      // The pose_t variable tg->trajgen_man_planer,_planer->velocity shall
      // contain the new requested velocities. This variable shall be set every
      // before calling trajgen_tick(), e.g. transferring scaled joystick
      // inputs, velocity command from the computer keyboard etc.
      while (interpolator_need_update(&(tg->joints[0].interpolator))) {
        if ((err = trajgen_man_tick(MTG)) != 0) {
          return tg_err(err, 0);
        }
        tg->planer_pose = trajgen_man_get_position(MTG);
        if ((err = tg->kinematics.inverse(&(tg->planer_pose),
            tg->_internals.planer_positions)) != 0) {
          return tg_err(err, 0);
        }
        for (i = 0; i < tg->config.number_of_used_joints; i++) {
          if ((err = interpolator_push(&(tg->joints[i].interpolator),
              *tg->_internals.planer_positions[i])) != 0) {
            return tg_err(err, i);
          }
        }
      }
 
      // Interpolate (every sample interval)
      for (i = 0; i < tg->config.number_of_used_joints; i++) {
        joint = &tg->joints[i];
        interpolator_interpolate(&(joint->interpolator), &(joint->position),
            &(joint->velocity), &(joint->acceleration), &(joint->jerk));
        if(!isfinite(joint->position)) {
          return tg_err(TRAJ_ERROR_NUMERIC, i);
        }
      }
      // Transfer state variables
      tg->is_done = trajgen_man_is_done(MTG);
      break;
    }
 
    ////////////////////////////////////////////////////////////////////////
 
    case TRAJ_STATE_DISABLING:
    {
      // Reset the trajgen, which does not include the config or actual
      // joint values.
      trajgen_reset();
      // Proceed directly in DISABLED state
      tg->state = TRAJ_STATE_DISABLED;
      // NO BREAK
    }
    case TRAJ_STATE_DISABLED:
    {
      tg->is_done = 1;
      // State block reserved for updating states
    }
    case TRAJ_STATE_OK_TO_SWITCH:
    {
      // Planer position needs to be updated externally
      break;
    }
 
    ////////////////////////////////////////////////////////////////////////
 
    default:
    {
      tg->state = TRAJ_STATE_DISABLING;
      return tg_err(TRAJ_ERROR_INVALID_STATE, 0);
    }
  }
  return TRAJ_ERROR_OK;
}
 
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="trajgen_errstr/info: Error to string conversion">
 
/********************************************************************
 *
 * Trajectory generator error to string conversion (no standard libs required)
 * Trajectory generator info string
 *
 ********************************************************************/
 
#define sprinterr(_T) do {\
  if(_T) for(tp=_T; (*tp) && (errstr_p != errstr_e); errstr_p++, tp++) *errstr_p=*tp; \
} while(0);
 
#define add_joint() do {\
  if(errstr_p != errstr_e) { *errstr_p='['; errstr_p++; } \
  if(errstr_p != errstr_e) { *errstr_p='0'+(err.sel.joint%10); errstr_p++; } \
  if(errstr_p != errstr_e) { *errstr_p=']'; errstr_p++; } \
} while(0);
 
/**
 * Writes build information into the given string and returns latter as
 * const pointer.
 * @param char* s
 * @param unsigned length
 * @return const char*
 */
const char* trajgen_info(char* s, unsigned length) {
  if(!s || !length) return (const char*) s;
  char *errstr_p = s, *errstr_e = s+length;
  const char* tp;
  do { unsigned i; for(i=0; i<length; i++) s[i] = '\0'; } while(0);
  sprinterr("trajgen.c/h, version "); sprinterr(TG_VERSION);
  #ifdef ARG_POINTER_CHECKS
  sprinterr(", with-0pointer-checks");
  #else
  sprinterr(", without-0pointer-checks");
  #endif
  #ifdef TG_DEBUG_LEVEL
  #if TG_DEBUG_LEVEL > 1
  sprinterr(", debug2");
  #else
  sprinterr(", debug2");
  #endif
  #else
  sprinterr(", no-debug");
  #endif
  #ifdef NO_INTERPOLATION
  sprinterr(", without-interpolation");
  #else
  sprinterr(", with-interpolation");
  #endif
  #if NO_MOTION_BLENDING > 0
  sprinterr(", without-blending");
  #else
  sprinterr(", with-blending");
  #endif
  #ifdef WITH_CURVES
  sprinterr(", with-curves");
  #else
  sprinterr(", without-curves");
  #endif
  #ifdef NO_TRAJECTORY_SYNC
  sprinterr(", without-syncing");
  #else
  sprinterr(", with-syncing");
  #endif
  #ifdef NO_ABC
  sprinterr(", without-abc");
  #else
  sprinterr(", with-abc");
  #endif
  #ifdef NO_UVW
  sprinterr(", without-uvw");
  #else
  sprinterr(", with-uvw");
  #endif
  #ifdef EXPORT_INTERPOLATORS
  sprinterr(", exported-interpolators");
  #endif
  #ifdef EXPORT_JOINT_TG
  sprinterr(", exported-jointtg");
  #endif
  #ifdef EXPORT_MANUAL_TG
  sprinterr(", exported-manualtg");
  #endif
  #ifdef EXPORT_COORD_TG
  sprinterr(", exported-coordtg");
  #endif
  #define TG_STR(s) TG_STRINNER(s)
  #define TG_STRINNER(s) #s
  sprinterr((", float-type="  TG_STR(real_t)));
  sprinterr(", max-joints=" TG_STR(TG_MAX_JOINTS));
  sprinterr(", queue-size=" TG_STR(TG_QUEUE_SIZE));
  sprinterr(", machine-resolution=" TG_STR(TG_RESOLUTION));
  sprinterr(", dimensional-resolution=" TG_STR(TG_D_RES));
  sprinterr(", angle-resolution=" TG_STR(TG_A_RES));
  sprinterr(", blending-max-angle=" TG_STR(MAX_ALLOWED_BLENDING_ANGLE_DEG) "deg");
  sprinterr(", straight-blending-max-angle=" TG_STR(STRAIGHT_BLENDING_ANGLE) "deg");
  sprinterr(", stfwi. ");
  #undef TG_STR
  #undef TG_STRINNER
  return (const char*) s;
}
 
/**
 * Writes a text version of a trajgen error into *errstr
 * @param errnum
 * @param errstr
 * @param length
 */
const char* trajgen_errstr(uint16_t errnum, char* errstr, unsigned length)
{
  if(!errstr || !length) return (const char*) errstr;
  char *errstr_p = errstr, *errstr_e = errstr+length;
  const char* tp;
  trajgen_error_details_t err;
  err.errno = errnum;
  do { unsigned i; for(i=0; i<length; i++) errstr[i] = '\0'; } while(0);
  switch (err.sel.code) {
    case TRAJ_ERROR_OK:
      sprinterr("(ok)");
      break;
    case TRAJ_ERROR_ERROR:
      sprinterr("[main trajgen] General error");
      break;
    case TRAJ_ERROR_NULL_POINTER:
      sprinterr("[main trajgen] Null pointer");
      break;
    case TRAJ_ERROR_NUMERIC:
      sprinterr("[main trajgen] Null pointer");
      break;
    case TRAJ_ERROR_CONFIG_LAST_USED_JOINT_INVALID:
      sprinterr("[main trajgen] Invalid last used joint value");
      break;
    case TRAJ_ERROR_CONFIG_INTERPOLATION_RATE_INVALID:
      sprinterr("[main trajgen] Invalid interpolation rate setting");
      break;
    case TRAJ_ERROR_CONFIG_COMPILE_SETTING_INVALID:
      sprinterr("[main trajgen] Invalid compilation setting (preprocessor defined)");
      break;
    case TRAJ_ERROR_CONFIG_OVERRIDE_INVALID:
      sprinterr("[main trajgen] Invalid override");
      break;
    case TRAJ_ERROR_INVALID_STATE:
      sprinterr("[main trajgen] Invalid state");
      break;
    case TRAJ_ERROR_INVALID_SWITCHING_STATE:
      sprinterr("[main trajgen] Invalid switching state");
      break;
    case TRAJ_ERROR_INVALID_JOINT_NO:
      sprinterr("[main trajgen] Invalid joint index");
      break;
    case KINEMATICS_ERR_ERROR:
      sprinterr("[kinematics] General error");
      break;
    case KINEMATICS_ERR_NULL_POINTER:
      sprinterr("[kinematics] Null pointer");
      break;
    case KINEMATICS_ERR_INIT_FUNCTION_NULL:
      sprinterr("[kinematics] Invalid kinematics definition (contains function null-pointer)");
      break;
    case KINEMATICS_ERR_FORWARD_FAILED:
      sprinterr("[kinematics] Forward kinematics failed");
      break;
    case KINEMATICS_ERR_INVERSE_FAILED:
      sprinterr("[kinematics] Inverse kinematics failed");
      break;
    case KINEMATICS_ERR_RESET_FAILED:
      sprinterr("[kinematics] Reset setting failed");
      break;
    case INTERPOLATOR_ERROR:
      sprinterr("[interpolator] General error"); add_joint();
      break;
    case INTERPOLATOR_ERROR_INIT_ARG_INVALID:
      sprinterr("[interpolator] Invalid initialisation argument"); add_joint();
      break;
    case INTERPOLATOR_ERROR_QUEUE_FULL:
      sprinterr("[interpolator] Queue full"); add_joint();
      break;
    case INTERPOLATOR_ERROR_OFFSET_IP_NULLPOINTER:
      sprinterr("[interpolator] Offset CI null pointer"); add_joint();
      break;
    case INTERPOLATOR_ERROR_INTERPOLATE_ARG_NULLPOINTER:
      sprinterr("[interpolator] Interpolate: Null pointer"); add_joint();
      break;
    case INTERPOLATOR_ERROR_NOT_RESET:
      sprinterr("[interpolator] Not initialised"); add_joint();
      break;
    case TRAJGEN_FREE_ERROR_ERROR:
      sprinterr("[joint tg] General error"); add_joint();
      break;
    case TRAJGEN_FREE_ERROR_INIT_NULLPOINTER:
      sprinterr("[joint tg] Initialisation null pointer"); add_joint();
      break;
    case TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_ACCEL:
      sprinterr("[joint tg] Invalid maximum acceleration"); add_joint();
      break;
    case TRAJGEN_FREE_ERROR_INIT_INVALID_MAX_VELOCITY:
      sprinterr("[joint tg] Invalid maximum velocity"); add_joint();
      break;
    case TRAJGEN_FREE_ERROR_INIT_INVALID_SAMPLE_INTERVAL:
      sprinterr("[joint tg] Invalid sample interval"); add_joint();
      break;
    case TRAJGEN_FREE_ERROR_INVALID_ACCELERATION:
      sprinterr("[joint tg] Invalid acceleration"); add_joint();
      break;
    case TP_ERR_ERROR:
      sprinterr("[coord tg] General error");
      break;
    case TP_ERR_TP_NULL_POINTER:
      sprinterr("[coord tg] Null pointer");
      break;
    case TP_ERR_ABORTING:
      sprinterr("[coord tg] Aborting");
      break;
    case TP_ERR_QUEUE_PUT_FAILED:
      sprinterr("[coord tg] Queue push failed");
      break;
    case TP_ERR_INVALID_PARAM:
      sprinterr("[coord tg] Invalid argument");
      break;
    case TP_ERR_QUEUE_FULL:
      sprinterr("[coord tg] Queue full");
      break;
    case TP_ERR_QUEUE_TO_MANY_ELEMENTS_TO_REMOVE:
      sprinterr("[coord tg] Too many items to remove");
      break;
    case TP_ERR_INVALID_MOTION_TYPE:
      sprinterr("[coord tg] Invalid motion type");
      break;
    case TP_ERR_INVALID_SPEED:
      sprinterr("[coord tg] Invalid velocity");
      break;
    case TP_ERR_INVALID_ACCEL:
      sprinterr("[coord tg] Invalid acceleration");
      break;
    case TP_ERR_INVALID_POSE:
      sprinterr("[coord tg] Invalid argument pose");
      break;
    case TP_ERR_INVALID_SAMPLE_INTERVAL:
      sprinterr("[coord tg] Invalid sample interval");
      break;
    case TP_ERR_ALREADY_MOVING:
      sprinterr("[coord tg] Already moving");
      break;
    case TP_ERR_SEGMENT_LENGTH_ZERO:
      sprinterr("[coord tg] Segment to move has a length of zero");
      break;
    case TP_ERR_UNIT_VECTOR_CALC_INVALID_TYPE:
      sprinterr("[coord tg] Unit vector calculation failed");
      break;
    case TP_ERR_REF_POSITION_INVALIDATED_DURING_MOTION:
      sprinterr("[coord tg] Ref-position invalidated during motion");
      break;
    case TG_MAN_ERROR_ERROR:
      sprinterr("[manual tg] General error");
      break;
    case TG_MAN_ERROR_INIT_NULLPOINTER:
      sprinterr("[manual tg] Initialisation: null pointer");
      break;
    case TG_MAN_ERROR_INIT_INVALID_MAX_ACCEL:
      sprinterr("[manual tg] Invalid maximum acceleration");
      break;
    case TG_MAN_ERROR_INIT_INVALID_MAX_VELOCITY:
      sprinterr("[manual tg] Invalid maximum velocity");
      break;
    case TG_MAN_ERROR_INIT_INVALID_SAMPLE_INTERVAL:
      sprinterr("[manual tg] Invalid sample interval");
      break;
    case TG_MAN_ERROR_INVALID_ACCELERATION:
      sprinterr("[manual tg] Invalid acceleration");
      break;
    default:
      sprinterr("Unknown error code");
  }
  return (const char*) errstr;
}
 
#undef sprinterr
#undef add_joint
 
// </editor-fold>

Test program C++ wrapper

/**
 * Trajectory planer test c++ wrapper
 * @author stfwi
 * @file test/trajgen.cc
 */
#ifndef TRAJGEN_HH
#define TRAJGEN_HH
 
#include "../trajgen.h"
#include <string>
#include <sstream>
#include <iostream>
#include <cstring>
#include <cstdio>
 
template <typename flt_t=real_t>
class basic_trajgen
{
public:
 
  typedef enum {
    notg = TRAJ_STATE_DISABLED,
    coordtg = TRAJ_STATE_COORDINATED,
    freetg = TRAJ_STATE_JOINT,
    manualtg = TRAJ_STATE_MAN
  } selection_t;
 
public:
 
  static std::string dump_pose(const pose_t& p)
  {
    std::stringstream ss;
    ss << "{" << p.x << "," << p.y << "," << p.z;
    if(p.u!=0 && p.v!=0 && p.w!=0 && p.a!=0 && p.b!=0 && p.c!=0) {
      ss << ", " << p.a << "," << p.b << "," << p.c;
    }
    if(p.u!=0 && p.v!=0 && p.w!=0) {
      ss << ", " << p.u << "," << p.v << "," << p.w;
    }
    ss << "}";
    return ss.str();
  }
 
public:
 
  basic_trajgen() : _speed(0.1), _accel(1), _blending(0)
  {
    memset(_err_str, 0, sizeof(_err_str));
    memset(&_last_pose, 0, sizeof(pose_t));
    memset(&_tg, 0, sizeof(trajgen_t));
    memset(&_cfg, 0, sizeof(trajgen_config_t));
  }
 
  virtual ~basic_trajgen()
  { ; }
 
  basic_trajgen& init(flt_t max_speed, flt_t max_accel, flt_t sample_frequency,
          unsigned interpolation_rate, unsigned max_joints)
  {
    _n_joints = max_joints > TG_MAX_JOINTS ? TG_MAX_JOINTS : max_joints;
    memset(&_cfg, 0, sizeof(trajgen_config_t));
    _cfg.number_of_used_joints = _n_joints;
    _cfg.max_coord_velocity = max_speed;
    _cfg.max_coord_acceleration = max_accel;
    _cfg.sample_interval = 1.0/sample_frequency;
    _cfg.interpolation_rate = interpolation_rate;
    pose_set_all(&_cfg.max_manual_velocities, max_speed);
    pose_set_all(&_cfg.max_manual_accelerations, max_accel);
    for(unsigned i = 0; i < _n_joints; i++) {
      _cfg.joints[i].max_velocity = max_speed;
      _cfg.joints[i].max_acceleration = max_speed;
    }
    echk(trajgen_initialize(&_tg, _cfg));
    return *this;
  }
 
  inline const trajgen_t& tg() const
  { return _tg; }
 
  inline const trajgen_config_t& config() const
  { return _tg.config; }
 
  inline bool done() const
  { return _tg.is_done; }
 
  inline trajgen_state_t state() const
  { return _tg.state; }
 
  inline std::string info() {
    char s[4096];
    trajgen_info(s, 4096);
    return std::string(s);
  }
 
  inline unsigned current_tick() const
  { return trajgen_get_tick(); }
 
  inline flt_t current_tick_time() const
  { return (flt_t) trajgen_get_tick() * _tg.config.sample_interval; }
 
  inline bool queue_full() const
  { return trajgen_queue_full(); }
 
  inline unsigned num_queued() const
  { return trajgen_num_queued(); }
 
  inline unsigned queue_size() const
  { return trajgen_queue_size(); }
 
  inline basic_trajgen& select(selection_t s)
  {
    echk(trajgen_switch_state((trajgen_state_t)s));
    while(!_tg.is_done) echk(trajgen_tick());
    return *this;
  }
 
  inline pose_t pose() const
  { return _tg.planer_pose; }
 
  inline basic_trajgen& pose(pose_t p)
  {
    if(_tg.config.number_of_used_joints > 0) _tg.joints[0].position = p.x;
    if(_tg.config.number_of_used_joints > 1) _tg.joints[1].position = p.y;
    if(_tg.config.number_of_used_joints > 2) _tg.joints[2].position = p.z;
    if(_tg.config.number_of_used_joints > 3) _tg.joints[3].position = p.a;
    if(_tg.config.number_of_used_joints > 4) _tg.joints[4].position = p.b;
    if(_tg.config.number_of_used_joints > 5) _tg.joints[5].position = p.c;
    if(_tg.config.number_of_used_joints > 6) _tg.joints[6].position = p.u;
    if(_tg.config.number_of_used_joints > 7) _tg.joints[7].position = p.v;
    if(_tg.config.number_of_used_joints > 8) _tg.joints[8].position = p.w;
    _last_pose = p;
    trajgen_reset();
    return *this;
  }
 
 
  inline flt_t blending() const
  { return _tg.coord_planer.blending_tolerance; }
 
  inline basic_trajgen& blending(flt_t tol)
  { _blending = _tg.coord_planer.blending_tolerance = tol > 0 ? tol : 0; return *this; }
 
  inline flt_t speed() const
  { return _speed; }
 
  inline basic_trajgen& speed(flt_t v)
  { if(v <= 0) throw("Invalid speed"); _speed=v; return *this; }
 
  inline flt_t accel() const
  { return _accel; }
 
  inline basic_trajgen& accel(flt_t a)
  { if(a <= 0) throw("Invalid acceleration"); _accel=a; return *this; }
 
  inline flt_t override() const
  { return _tg.coord_planer.override; }
 
  inline basic_trajgen& override(flt_t o)
  { if(o<0||o>1) throw("Invalid override"); _tg.coord_planer.override=o; return *this; }
 
  inline unsigned num_joints() const
  { return _n_joints; }
 
  inline const trajgen_joint_t& joint(unsigned index) const
  { if(index>_n_joints) throw("Invalid joint index"); return _tg.joints[index]; }
 
  inline const trajgen_joint_t& operator [] (unsigned i) const
  { return joint(i); }
 
  inline unsigned long current_segment() const
  { return _tg.coord_planer.sg_id; }
 
  // Digital I/O synced motion of the coord planer
  inline const unsigned short dio_state() const
  { return _tg.coord_planer.sync_dio_current & 0xffff; }
 
  inline basic_trajgen& dio_set_next(unsigned short v)
  { _tg.coord_planer.sync_dio_command.set |= (v&0xffff); return *this; }
 
  inline basic_trajgen& dio_reset_next(unsigned short v)
  { _tg.coord_planer.sync_dio_command.clear |= (v&0xffff); return *this; }
 
  inline basic_trajgen& reset()
  { echk(trajgen_reset()); return *this; }
 
  inline basic_trajgen& pause()
  { echk(trajgen_pause()); return *this; }
 
  inline basic_trajgen& resume()
  { echk(trajgen_resume()); return *this; }
 
  inline basic_trajgen& abort()
  { echk(trajgen_abort()); return *this; }
 
  inline basic_trajgen& tick()
  { echk(trajgen_tick()); return *this; }
 
  basic_trajgen& line(double x=NAN, double y=NAN, double z=NAN, double a=NAN,
        double b=NAN, double c=NAN, double u=NAN, double v=NAN, double w=NAN)
  {
    pose_t p;
    p.x = isnan(x) ? _last_pose.x : x; p.y = isnan(y) ? _last_pose.y : y;
    p.z = isnan(z) ? _last_pose.z : z; p.a = isnan(a) ? _last_pose.a : a;
    p.b = isnan(b) ? _last_pose.b : b; p.c = isnan(c) ? _last_pose.c : c;
    p.u = isnan(u) ? _last_pose.u : u; p.v = isnan(v) ? _last_pose.v : v;
    p.w = isnan(w) ? _last_pose.w : w;
    _last_pose = p;
    if(isnan(a) && isnan(b) && isnan(c)) {
      fprintf(stderr, "# line {x:%.6g, y:%.6g, z:%.6g, v:%.6g, "
          "a:%.6g, tol:%.6g }\n", p.x, p.y, p.z, _speed, _accel, _blending);
    } else if(isnan(u) && isnan(v) && isnan(w)) {
      fprintf(stderr, "# line {x:%.6g, y:%.6g, z:%.6g, a:%.6g, b:%.6g, c:%.6g, v:%.6g, "
          "a:%.6g, tol:%.6g }\n", p.x, p.y, p.z, p.a, p.b, p.c, _speed, _accel, _blending);
    } else {
      fprintf(stderr, "# line {x:%.6g, y:%.6g, z:%.6g, a:%.6g, b:%.6g, c:%.6g, v:%.6g, u:%.6g, "
          "v:%.6g, w:%.6g,  a:%.6g, tol:%.6g }\n", p.x, p.y, p.z, p.a, p.b, p.c, p.u, p.v, p.w,
          _speed, _accel, _blending);
    }
    echk(trajgen_add_line(p, _speed, _accel));
    return *this;
  }
 
  basic_trajgen& arc(double xe, double ye, double ze, double xc, double yc, double zc,
      double xn, double yn, double zn, int turns=0)
  {
    pose_t p;
    pose_position_t c;
    pose_vector_t n;
    pose_set_zero(&p);
    p.x = xe; p.y = ye; p.z = ze;
    c.x = xc; c.y = yc; c.z = zc;
    n.x = xn; n.y = yn; n.z = zn;
    _last_pose.x = p.x;
    _last_pose.y = p.y;
    _last_pose.z = p.z;
    fprintf(stderr,"# arc {x:%.6g, y:%.6g, z:%.6g, cx:%.6g, cy:%.6g, cz:%.6g, nx:%.6g, ny:%.6g,"
           "nz:%.6g,  v:%.6g, a:%.6g, tol:%.6g }\n", p.x,p.y,p.z, c.x,c.y,c.z,
            n.x,n.y,n.z, _speed, _accel, _blending);
    echk(trajgen_add_arc(p,c,n,turns, _speed, _accel));
    return *this;
  }
 
  basic_trajgen& curve(
    double xe, double ye, double ze,
    double xscp, double yscp, double zscp,
    double xecp, double yecp, double zecp
  ){
    pose_t p;
    pose_position_t sh, eh;
    pose_set_zero(&p);
    p.x = xe; p.y = ye; p.z = ze;
    sh.x = xscp; sh.y = yscp; sh.z = zscp;
    eh.x = xecp; eh.y = yecp; eh.z = zecp;
    _last_pose.x = p.x;
    _last_pose.y = p.y;
    _last_pose.z = p.z;
    fprintf(stderr,"# curve {x:%.6g, y:%.6g, z:%.6g, cp1.x:%.6g, cp1.y:%.6g, cp1.z:%.6g, cp2.x:%.6g, cp2.y:%.6g,"
           "cp2.z:%.6g, v:%.6g, a:%.6g, tol:%.6g }\n", p.x,p.y,p.z, sh.x,sh.y,sh.z,
            eh.x,eh.y,eh.z, _speed, _accel, _blending);
    echk(trajgen_add_curve(p, sh, eh, _speed, _accel));
    return *this;
  }
 
private:
 
  void echk(unsigned errno)
  { if(errno) throw trajgen_errstr(_tg.last_error.errno, _err_str, 1024); }
 
private:
 
  flt_t _speed;
  flt_t _accel;
  flt_t _blending;
  unsigned _n_joints;
  trajgen_t _tg;
  trajgen_config_t _cfg;
  pose_t _last_pose;
  char _err_str[1024];
};
 
typedef basic_trajgen<real_t> trajgen;
 
#endif

Test program for the coordinated planer (C++)

/**
 * Example test file for the coordinated trajgen.
 *
 * @author stfwi
 * @file test/test-coord.cc
 */
#include "trajgen.hh"
#include <iostream>
#include <sstream>
#include <string>
#include <cmath>
#include <time.h>
#include <cstdlib>
#include <cstdio>
#include <stdarg.h>
 
using namespace std;
 
// <editor-fold defaultstate="collapsed" desc="Auxiliaries">
 
////////////////////////////////////////////////////////////////////////////////
// Auxiliaries
////////////////////////////////////////////////////////////////////////////////
 
#if defined (__linux__)
#include <sys/time.h>
inline double clk() {
  timespec ts;
  clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
  return (double) ((uint64_t)ts.tv_nsec + ((uint64_t)1e9) * (uint64_t)ts.tv_sec) / 1e3;
}
#elif defined (__MACH__)
#include <mach/mach_time.h>
inline double clk() {
  static double orwl_timebase = 0.0;
  static uint64_t orwl_timestart = 0;
  if (!orwl_timestart) {
    mach_timebase_info_data_t tb = {0};
    mach_timebase_info(&tb);
    orwl_timebase = tb.numer;
    orwl_timebase /= tb.denom;
    orwl_timestart = mach_absolute_time();
  }
  return (double)((mach_absolute_time() - orwl_timestart) * orwl_timebase) / 1e3;
}
#else
#define clk() 0
#endif
 
#if defined (__linux__) || defined (__MACH__)
std::string exec(std::string cmd) {
  FILE* pipe = popen((cmd+" 2>/dev/null").c_str(), "r");
  if(!pipe) return "(Error: Failed to invoke shell process)";
  char s[4096];
  s[4095] = '\0';
  std::string r = "";
  while(!feof(pipe)) if(fgets(s, 4095, pipe)) r += s;
  pclose(pipe);
  for(unsigned i=0; i<r.length(); i++) if(r[i]=='\n'||r[i]=='\r'||r[i]=='\t') r[i]=' ';
  while(r.length() && r[r.length()-1]==' ') r.resize(r.length()-1);
  while(r.length() && r[0]==' ') r.erase(0,1);
  return r;
}
std::string platform()
{
  std::stringstream ss;
  ss << "# Platform: "
     << "architecture '" << exec("arch") << "'";
  #if defined (__linux__)
  std::string s = exec("cat /proc/cpuinfo |grep -m1 -i 'mhz' |grep -woP '([^\\s]+)$'");
  if(s.length()) ss << ", cpu=" << s << "MHz";
  #elif defined (__APPLE__)
  std::string s = exec("sysctl -a machdep.cpu|grep 'machdep.cpu.brand_string'|grep -woP '([^\\s]+)$'");
  ss << ", cpu=" << s;
  #endif
  ss << ", kernel '" << exec("uname -s") << " " << exec("uname -r") << "'";
  ss << std::endl;
  return ss.str();
}
#else
std::string platform() { return std::string("(Windows maybe)"); }
#endif
 
// Platform independent float random
inline double rnd() {
  return ((double)rand())/RAND_MAX;
}
 
// Digit rounding (the q&d version)
inline double roundd(double v, unsigned digits=0) {
  for(unsigned i=0; i<digits; i++) v*=10.;
  v = round(v);
  for(unsigned i=0; i<digits; i++) v/=10.;
  return v;
}
 
// Random position, we use millimeter like scaling here
inline double randpos(double max) {
  return roundd(max*((rnd()*2)-1), 0);
}
 
// To ease some annoyances with iosbase formatting flags: the old way.
inline std::string fmt(const char* fmt, ...)
{ char s[512];
  va_list args;
  va_start(args, fmt);
  vsnprintf(s, 512, fmt, args);
  va_end(args);
  return s;
}
 
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="Test run / protocol generation">
 
////////////////////////////////////////////////////////////////////////////////
// Test run / protocol generation
////////////////////////////////////////////////////////////////////////////////
 
// Argument settings
 
typedef struct {
  unsigned p:1, k:1, t:1, j0:1, j1:1, j2:1, j3:1, j4:1, j5:1, j6:1, j7:1, j8:1,
  v:1, a:1, s:1, n:1, o:1, C:1, N:1, O:1, P:1, A:1, S:1,
  END:1;
} opts_t;
opts_t opts;
 
void run_tg(trajgen &tg) {
  const trajgen_coord_t& tp = tg.tg().coord_planer;
  bool joints[9];
  do {
    std::stringstream ss;
    std::string s;
    joints[0] = opts.j0 && (tg.num_joints() > 0);
    joints[1] = opts.j1 && (tg.num_joints() > 1);
    joints[2] = opts.j2 && (tg.num_joints() > 2);
    joints[3] = opts.j3 && (tg.num_joints() > 3);
    joints[4] = opts.j4 && (tg.num_joints() > 4);
    joints[5] = opts.j5 && (tg.num_joints() > 5);
    joints[6] = opts.j6 && (tg.num_joints() > 6);
    joints[7] = opts.j7 && (tg.num_joints() > 7);
    joints[8] = opts.j8 && (tg.num_joints() > 8);
 
    // CSV head
    ss.clear();
    ss << "  ";
    if(opts.p) ss << "t_calc_us, ";
    if(opts.k) ss << " tick, ";
    if(opts.t) ss << "    t  ,";
    if(opts.s) ss << "segment, ";
    for(unsigned i=0; i<tg.num_joints(); i++) {
      if(joints[i]) ss << "j" << i << "s,        ";
    }
    if(opts.v) {
      for(unsigned i=0; i<tg.num_joints(); i++) {
        if(joints[i]) ss << "j" << i << "v,       ";
      }
    }
    if(opts.a) {
      for(unsigned i=0; i<tg.num_joints(); i++) {
        if(joints[i]) ss << "j" << i << "a,       ";
      }
    }
    if(opts.C) ss << "v_check, a_check, ";
    if(opts.n) ss << "n_queued, ";
    if(opts.N) ss << "n_active, ";
    if(opts.o) ss << "io, ";
    if(opts.O) ss << "override, ";
    if(opts.P) ss << "pausing, ";
    if(opts.A) ss << "aborting, ";
    if(opts.S) ss << "s_c_process, s_c_length, s_c_vc, s_c_v, s_c_a, s_c_v_blend, "
                  << "s_c_blending, s_n_process, s_n_length, s_n_vc, s_n_v, s_n_a, ";
 
    s = ss.str();
    cout << s.substr(0, s.length()-2)  << endl;
  } while(0);
 
  // CSV body
  do {
    double c;
    std::stringstream ss;
    std::string s;
    if(opts.p) {
      c=clk(); tg.tick(); c=clk()-c;
      ss << fmt("%11.3f, ", c);
    } else {
      tg.tick();
    }
    if(opts.k) {
      ss << fmt("%5d, ", tg.current_tick());
    }
    if(opts.t) {
      ss << fmt("%7.3f, ", tg.current_tick_time());
    }
    if(opts.s) {
      ss << fmt("%2lu, ", tg.current_segment());
    }
    for (unsigned i=0; i<tg.num_joints(); i++) {
      if(joints[i]) ss << fmt("%+10.6f, ", tg[i].position);
    }
    if(opts.v) {
      for (unsigned i=0; i<tg.num_joints(); i++) {
        if(joints[i]) ss << fmt("%9.3f, ", tg[i].velocity);
      }
    }
    if(opts.a) {
      for (unsigned i=0; i<tg.num_joints(); i++) {
        if(joints[i]) ss << fmt("%9.3f, ", tg[i].acceleration);
      }
    }
    if(opts.C) {
      ss << fmt("%9.3f", sqrt(
        tg[0].velocity * tg[0].velocity +
        tg[1].velocity * tg[1].velocity +
        tg[2].velocity * tg[2].velocity
      )) << ", ";
      ss << fmt("%9.3f", sqrt(
        tg[0].acceleration * tg[0].acceleration +
        tg[1].acceleration * tg[1].acceleration +
        tg[2].acceleration * tg[2].acceleration
      )) << ", ";
    }
    if(opts.n) {
      ss << fmt("%2d, ", tg.num_queued());
    }
    if(opts.N) {
      ss << fmt("%1d, ", tp.num_queued_active);
    }
    if(opts.o) {
      ss << fmt("%2d, ", tg.dio_state());
    }
    if(opts.O) {
      ss << fmt("%4.2f, ", tp.override);
    }
    if(opts.P) {
      ss << fmt("%1d, ", tp.is_pausing);
    }
    if(opts.A) {
      ss << fmt("%1d, ", tp.is_aborting);
    }
 
    if(opts.S) {
      if(tp.queue.size >0 && tp.num_queued_active > 0) {
        const trajgen_sg_t& sg0 = tp.queue.queue[tp.queue.start];
        const trajgen_sg_t& sg1 = tp.queue.queue[(tp.queue.start+1)%tp.queue.size];
        if(tp.num_queued_active > 0) {
          ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, %10.4f, %1d, ", sg0.progress,
              sg0.length, sg0.current_velocity, sg0.v, sg0.a, sg0.blending_velocity,
              sg0.is_blending);
        }
        if(tp.num_queued_active >= 2) {
          ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, ", sg1.progress, sg1.length,
              sg1.current_velocity, sg1.v, sg1.a);
        } else {
          ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, ", 0,0,0,0,0);
        }
      } else {
        ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, %10.4f, %1d, ", 0,0,0,0,0,0,0);
        ss << fmt("%12.6f, %12.6f, %10.4f, %10.4f, %10.4f, ", 0,0,0,0,0);
      }
    }
    s = ss.str();
    cout << s.substr(0, s.length()-2) << endl;
  } while(!tg.done());
}
 
// </editor-fold>
 
// <editor-fold defaultstate="collapsed" desc="Main">
 
////////////////////////////////////////////////////////////////////////////////
// Main
////////////////////////////////////////////////////////////////////////////////
 
const string default_opts = "-txyz";
 
int usage()
{
  cerr << endl << "Usage: test-coord [-*|-ptksxyz012345678vaCnNoOPAS] task" << endl
       << "Where task one of: " << endl << endl
       << "  example, line, arc, curve, straight-blending, rand-straight-blending," << endl
       << "  rand-lines, rand-arcs, override, push-line, push-arc, sizes " << endl << endl
       << "Note: options must be all inline as the first argument, default is"
       << "'" << default_opts << "'. Meanings: " << endl
       << "   *   : Everything" << endl
       << "   p   : Performance (in csv: 't_calc_us')" << endl
       << "   k   : Tick index of trajgen_tick() (in csv: 'tick')" << endl
       << "   t   : Time (in csv: 't')" << endl
       << "   s   : Id of current segment (in csv: 'segment')" << endl
       << "   x,0 : Positions joint 0 (in csv: 'j0s')" << endl
       << "   y,1 : Positions joint 1 (in csv: 'j1s')" << endl
       << "   z,2 : Positions joint 2 (in csv: 'j2s')" << endl
       << "   3-8 : Positions joint N (in csv: 'jNs' -> a,b,c,u,v,w)" << endl
       << "   v   : Velocities (in csv: 'jNv')" << endl
       << "   a   : Accelerations (in csv: 'jNa')" << endl
       << "   C   : Recalculated trajectory speed/accel (in csv: 'v_check, a_check')" << endl
       << "   n   : Number of currently queued segments (in csv: 'n_queued')" << endl
       << "   N   : Number of active segments (in csv: 'n_active')" << endl
       << "   o   : Sync digital output state (in csv: 'io')" << endl
       << "   O   : Current override (in csv: 'override')" << endl
       << "   P   : Current 'pause' status (in csv: 'pausing')" << endl
       << "   A   : Current 'abort' status (in csv: 'aborting')" << endl
       << "   S   : Segment information (multiple entries)" << endl
       << endl;
  return 1;
}
 
int main(int argc, char** argv)
{
  string argopt, test_task;
  test_task = (argc==3) ? argv[2] : (argc==2 ? argv[1] : "");
  argopt = (argc==3) ? argv[1] : default_opts;
  if(argopt.length() > 0 && argopt.at(0) != '-') return usage();
  if(test_task.length() == 0 || test_task.at(0) == '-') return usage();
  memset(&opts, 0, sizeof(opts_t));
  opts.p = argopt.find('p') != string::npos;
  opts.k = argopt.find('k') != string::npos;
  opts.t = argopt.find('t') != string::npos;
  opts.s = argopt.find('s') != string::npos;
  opts.j0= argopt.find('0') != string::npos || argopt.find('x') != string::npos;
  opts.j1= argopt.find('2') != string::npos || argopt.find('y') != string::npos;
  opts.j2= argopt.find('2') != string::npos || argopt.find('z') != string::npos;
  opts.j3= argopt.find('3') != string::npos;
  opts.j4= argopt.find('4') != string::npos;
  opts.j5= argopt.find('5') != string::npos;
  opts.j6= argopt.find('6') != string::npos;
  opts.j7= argopt.find('7') != string::npos;
  opts.j8= argopt.find('8') != string::npos;
  opts.v = argopt.find('v') != string::npos;
  opts.a = argopt.find('a') != string::npos;
  opts.C = argopt.find('C') != string::npos;
  opts.n = argopt.find('n') != string::npos;
  opts.N = argopt.find('N') != string::npos;
  opts.o = argopt.find('o') != string::npos;
  opts.O = argopt.find('O') != string::npos;
  opts.P = argopt.find('P') != string::npos;
  opts.A = argopt.find('A') != string::npos;
  opts.S = argopt.find('S') != string::npos;
 
  if(argopt.find('*') != string::npos) memset(&opts, 0xff, sizeof(opts_t));
  srand(time(NULL));
  try {
    trajgen tg;
    if(argc >1) cout << "# Build: " << tg.info() << endl << platform();
    #ifndef NO_INTERPOLATION
    tg.init(500, 2000, 1000.0, 4, 3);
    #else
    tg.init(500, 2000, 1000.0, 1, 3);
    #endif
    if(test_task=="sizes") {
      cout << "#" << endl;
      cout << "# trajgen_t                    : " << (sizeof(trajgen_t)) << "b" << endl;
      cout << "#  trajgen_joint_t             : " << sizeof(trajgen_joint_t) << "b" << endl;
      cout << "#  trajgen_config_t            : " << sizeof(trajgen_config_t) << "b" << endl;
      cout << "#  trajgen_internals_t         : " << sizeof(trajgen_internals_t) << "b" << endl;
      cout << "#  trajgen_state_t             : " << sizeof(trajgen_state_t) << "b" << endl;
      cout << "#" << endl;
      cout << "#  trajgen_coord_t             : " << sizeof(trajgen_coord_t) << "b" << endl;
      cout << "#  trajgen_coord_internals_t   : " << sizeof(trajgen_coord_internals_t) << "b" << endl;
      cout << "#  trajgen_coord_config_t      : " << sizeof(trajgen_coord_config_t) << "b" << endl;
      cout << "#  trajgen_coord_syncdio_t     : " << sizeof(trajgen_coord_syncdio_t) << "b" << endl;
      cout << "#  trajgen_coord_syncdio_cmd_t : " << sizeof(trajgen_coord_syncdio_cmd_t) << "b" << endl;
      cout << "#   trajgen_queue_t            : " << (sizeof(trajgen_queue_t)) << "b" << endl;
      cout << "#    trajgen_sg_line_t         : " << sizeof(trajgen_sg_line_t) << "b" << endl;
      cout << "#    trajgen_sg_arc_t          : " << sizeof(trajgen_sg_arc_t) << "b" << endl;
      cout << "#    trajgen_sg_curve_t        : " << sizeof(trajgen_sg_curve_t) << "b" << endl;
      cout << "#    trajgen_sg_t              : " << sizeof(trajgen_sg_t) << "b" << endl;
      cout << "#     pose_vector_t            : " << sizeof(pose_vector_t) << "b" << endl;
      cout << "#     pose_line_t              : " << sizeof(pose_line_t) << "b" << endl;
      cout << "#     pose_circle_t            : " << sizeof(pose_circle_t) << "b" << endl;
      cout << "#     pose_curve_t             : " << sizeof(pose_curve_t) << "b" << endl;
      cout << "#      pose_t                  : " << sizeof(pose_t) << "b" << endl;
      cout << "#       real_t                 : " << sizeof(real_t) << "b" << endl;
      cout << "#" << endl;
      cout << "#  trajgen_jointtg_t           : " << sizeof(trajgen_jointtg_t) << "b" << endl;
      cout << "#   trajgen_jointtg_config_t   : " << sizeof(trajgen_jointtg_config_t) << "b" << endl;
      cout << "#" << endl;
      cout << "#  trajgen_man_t               : " << sizeof(trajgen_man_t) << "b" << endl;
      cout << "#   trajgen_man_config_t       : " << sizeof(trajgen_man_config_t) << "b" << endl;
      cout << "#" << endl;
      cout << "#  interpolator_t              : " << sizeof(interpolator_t) << "b" << endl;
      cout << "#  kinematics_t                : " << sizeof(kinematics_t) << "b" << endl;
      cout << "#  joint_t                     : " << sizeof(joint_t) << "b" << endl;
    } else if(test_task=="push-line") {
      cout << "t_calc_us" << endl;
      double c=clk();
      for(unsigned i=0; i<1000; i++) {
        if(tg.queue_full()) { tg.reset(); tg.select(trajgen::coordtg); }
        c=clk();
        tg.line(randpos(100), randpos(100), randpos(100));
        cout << (clk()-c) << endl;
      }
      return 0;
    } else if(test_task=="push-arc") {
      cout << "t_calc_us" << endl;
      double c=clk();
      for(unsigned i=0; i<1000; i++) {
        if(tg.num_queued() >  tg.queue_size()-3) { tg.reset(); tg.select(trajgen::coordtg); }
        double x = randpos(100);
        double y = randpos(100);
        tg.line(x, y);
        c=clk();
        tg.arc(x, y, 0, 0,0,0, 0,0,1, 1);
        cout << (clk()-c) << endl;
      }
      return 0;
    } else if(test_task=="rand-lines") {
      tg.select(trajgen::coordtg);
        while(!tg.queue_full()) {
          double speed = roundd(rnd()*250+10);
          double accel = roundd(rnd()*500+500);
          double blending = roundd(rnd()*13-3, 1);
          double x = randpos(100);
          double y = randpos(100);
          double z = randpos(100);
          blending = blending <0 ? 0:blending;
          tg.speed(speed).accel(accel).blending(blending).line(x,y,z);
        }
        run_tg(tg);
    } else if(test_task=="rand-arcs") {
      double x = randpos(100);
      double y = randpos(100);
      run_tg(tg
        .select(trajgen::coordtg)
        .speed(roundd(rnd()*250+10))
        .accel(roundd(rnd()*500+500))
        .blending(roundd(rnd()*6-1, 1))
        .line(x,y)
        .arc(x,y,0, 0,0,0, 0,0,1, 1)
      );
    } else if(test_task=="straight-blending") {
      run_tg(tg
        .select(trajgen::coordtg)
        .accel(1000.0)
        .speed(10.0)
        .blending(0.1)
        .line(2.5,0)
        .speed(100.0)
        .line(5,0)
      );
    } else if(test_task=="rand-straight-blending") {
      run_tg(tg
        .select(trajgen::coordtg)
        .accel(1000.0)
        .speed(rnd()*75+25)
        .blending(rnd() + TG_RESOLUTION)
        .line(2.5,0)
        .speed(rnd()*75+25)
        .line(5,0)
      );
 
    } else if(test_task=="line1") {
      run_tg(tg
        .select(trajgen::coordtg)
        .accel(1000.0)
        .speed(53.3144)
        .blending(rnd() + TG_RESOLUTION)
        .line(2.5,0)
        .speed(89.8083)
        .line(5,0)
      );
    } else if(test_task=="line") {
      run_tg(tg
        .select(trajgen::coordtg)
        .accel(100.0)
        .speed(10.0)
        .blending(0)
        .line(10,0)
      );
    } else if(test_task=="arc") {
      pose_t start = {10,0,0, 0,0,0, 0,0,0};
      run_tg(tg
        .pose(start)
        .select(trajgen::coordtg)
        .accel(100.0)
        .speed(10.0)
        .arc(10,0,0, 0,0,0, 0,0,1, 1)
      );
    } else if(test_task=="curve") {
      run_tg(tg
        .select(trajgen::coordtg)
        .accel(100.0)
        .speed(10.0)
        .blending(0)
        .curve(10,10,0, 10,2,0, 7,5,0)
      );
    } else if(test_task=="override") {
      run_tg(tg
        .select(trajgen::coordtg)
        .accel(100.0)
        .speed(10.0)
        .blending(0)
        .override(0.5)
        .line(5,0)
      );
    } else if(test_task=="example") {
      run_tg(tg
        .select(trajgen::coordtg)
        .accel(1000.0)
        .speed(10.0)
        .blending(0)
        .line(40,0)
        .arc(30,10,0, 40,10,0, 0,0,1, 0)
        .blending(1.0)
        .arc(20,10,0, 25,5,0, 0,0,1, 0)
        .line(5,10)
        .speed(100)
        .dio_set_next(0x0001)
        .line(-25,10)
        .dio_set_next(0x0002)
        .accel(250)
        .dio_set_next(0x0004)
        .line(-50,0)
        .dio_set_next(0x0008)
        .line(-60,50)
        .dio_reset_next(0x0008)
        .line(-70,60)
        .dio_reset_next(0x0004)
        .line(-70,70)
        .dio_reset_next(0x0002)
        .line(-60,80)
        .dio_reset_next(0x0001)
        .line(0,70)
        .line(60,60)
        .line(70,-10)
        .blending(5.0)
        .line(50,-20)
        .line(-25,-10)
        .line(0,-5)
        .line(0,0)
      );
    } else {
      return usage();
    }
  } catch (const char *s) {
    cerr << "Exception: " << s << endl;
    return 1;
  } catch (int n) {
    cerr << "Exception: " << n << endl;
    return n;
  } catch(...) {
    cerr << "Exception: (uncaught)" << endl;
    return 1;
  }
  return 0;
 
}
 
// </editor-fold>

Test Makefile

#CC=/usr/local/bin/gcc-4.9
CC=gcc
CCC=g++
AR=ar
AS=as
RANLIB=ranlib
CFLAGS= -Wall -std=c99 -pedantic -O3
CCFLAGS= -Wall -std=c++98 -pedantic -O3
LDFLAGS=
SHELL_INTERP=/bin/bash
OPTS=
ifeq ($(OS),Windows_NT)
  OPTS +=
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
LDFLAGS+=-lm -lrt
DISASM=objdump -d -M intel -S
endif
ifeq ($(UNAME_S),Darwin)
DISASM=otool -XVtd
LDFLAGS+=-lm
endif
endif

ifndef WITH_INTERPOLATION
OPTS+=-DNO_INTERPOLATION
endif
ifdef NO_SYNC
OPTS+=-DNO_TRAJECTORY_SYNC
endif
ifdef NO_BLEND
OPTS+=-DNO_MOTION_BLENDING
endif
ifdef NO_ABC
OPTS+=-DNO_ABC
endif
ifdef NO_UVW
OPTS+=-DNO_UVW
endif
ifdef DEBUG
OPTS+=-DTG_DEBUG_LEVEL=$(DEBUG)
endif
ifdef WITH_CURVES
OPTS+=-DWITH_CURVES
endif

.PHONY: default
default: coord

.PHONY: clean
clean:
    -@rm -f trajgen.o
    -@rm -rf stats
    -@rm -rf bin

.PHONY: all
all: clean test analyse show-analysis

.PHONY: test
test:
    @./test/build-run.sh run no

.PHONY: all-tests
all-tests:
    @./test/build-run.sh run-all no

.PHONY: example
example:
    @./test/build-run.sh example no
    @echo '--> The result is in directory "stats"'

.PHONY: error
error: test/test-error.c
    @mkdir -p bin
    @$(CC) $(CFLAGS) -o bin/test-error test/test-error.c trajgen.c $(OPTS) $(LDFLAGS)
    @./bin/error

.PHONY: test-error
test-error: error

.PHONY: joint
joint:
    @mkdir -p bin
    $(CC) $(CFLAGS) -o bin/joint test/test-jointtg.c trajgen.c $(OPTS) $(LDFLAGS)
    @./bin/joint

.PHONY: test-joint
test-joint: joint

.PHONY: coord
coord:
    @mkdir -p bin
    $(CCC) $(CCFLAGS) -o bin/coord test/test-coord.cc trajgen.c $(OPTS) $(LDFLAGS)

.PHONY: test-coord
test-coord: coord

.PHONY: sizes
sizes: coord
    @./bin/coord sizes

.PHONY: disasm
disasm:
    @mkdir -p bin
    $(CC) -c -g -fverbose-asm $(CFLAGS) -o bin/trajgen.o trajgen.c  $(OPTS)
    $(DISASM) bin/trajgen.o > bin/trajgen.lst

.PHONY: analyse
analyse:
    @./test/build-run.sh no analyse

.PHONY: analyze
analyze: analyse

.PHONY: analysis
analysis: analyse

.PHONY: stats
stats: analyse

.PHONY: sizes
sizes: analyse

.PHONY: show-analysis
show-analysis:
    -@$(SHELL_INTERP) "test/open-file.sh" "stats/index.html"

.PHONY: dev
dev: coord
    ./bin/coord -txyC curve

.PHONY: dev1
dev1:
    @mkdir -p bin
    @$(CC) $(CFLAGS) -o bin/curve curve.c $(OPTS) $(LDFLAGS)
    @./bin/curve

Test shell script

#!/bin/bash
#
# Trajectory planer test build/test script
# @author stfwi
# @file test/build-run.sh
#
OLDPWD=$(pwd)
ROOT=$(dirname "$( cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )")
PRG=$ROOT/bin/coord
OUT=$ROOT/stats
CURHOST=$(echo $HOSTNAME|grep -Pwo '^[^\.]+')
CURHOST="x"
MAKE_PDF=no
OCTAVE_ARG2=""
#OCTAVE_OPTS="--no-gui --no-history --no-window-system"
OCTAVE_OPTS="--no-history --no-window-system"
COMPILE_EXAMPLE_ONLY="no"
COMPILE_CALC_TIMES_ONLY="no"
PRGOPTS="-*"
 
if [ "$1" == "calc-times" ]; then
  DO_TESTS="yes"
  COMPILE_CALC_TIMES_ONLY="yes"
  COMPILE_EXAMPLE_ONLY="yes"
elif [ "$1" == "example" ]; then
  DO_TESTS="yes"
  COMPILE_EXAMPLE_ONLY="yes"
  DO_ALL_COMPILER_SWITCHES="no"
elif [ "$1" == "run-all" ]; then
  DO_TESTS="yes"
  COMPILE_EXAMPLE_ONLY="no"
  DO_ALL_COMPILER_SWITCHES="yes"
elif [ "$1" == "run" ]; then
  DO_TESTS="yes"
  COMPILE_EXAMPLE_ONLY="no"
  DO_ALL_COMPILER_SWITCHES="no"
elif [ "$1" == "no" ]; then
  DO_TESTS=""
  COMPILE_EXAMPLE_ONLY="no"
  DO_ALL_COMPILER_SWITCHES="no"
else
  echo "Usage: build-run.sh [no|run|run-all|example|calc-times] [no|analyse|analyse-timing|save-only]"
  echo ""
  echo "  1st argument: C compiling and run tests. Default is 'no'."
  echo "    run       : Run only tests with and without interpolation."
  echo "    run-all   : Run with more compiler flags: UVW,ABC off, blending off, etc."
  echo "    example   : Compile and run only the example trajectory."
  echo "    calc-times: 100 x compile example and force only timing output"
  echo ""
  echo "  2nd argument: Octave analysis. Default is 'no'."
  echo "    analyse: Analyse all data (positions, speeds, accels ...)."
  echo "    analyse-timing: Only performance statistics."
  echo "    save-only: Save a mat file for manual analysis."
  echo ""
  exit 1
fi
 
if [ "$2" == "save-only" ]; then
  DO_STATS="yes"
  OCTAVE_ARG2="save-only"
  OCTAVE_SAVE_ONLY="yes"
elif [ "$2" == "analyse-timing" ]; then
  DO_STATS="yes"
  OCTAVE_ARG2="timing-stats"
elif [ "$2" == "analyse" ]; then
  OCTAVE_ARG2=
  DO_STATS="yes"
fi
 
function do_test() {
  rm -f $PRG
  echo "Compile test $1 ..."
  cd $ROOT && make >/dev/null
  echo "Run test $1 ..."
  $PRG $PRGOPTS example > $OUT/$CURHOST-example--$1.txt 2>&1
  if [ "$COMPILE_EXAMPLE_ONLY" != "yes" ]; then
    #$PRG $PRGOPTS line > $OUT/$CURHOST-line--$1.txt 2>&1
    #$PRG $PRGOPTS arc > $OUT/$CURHOST-arc--$1.txt 2>&1
    $PRG $PRGOPTS curve > $OUT/$CURHOST-curve--$1.txt 2>&1
    #$PRG $PRGOPTS straight-blending > $OUT/$CURHOST-straight-blending--$1.txt 2>&1
    #$PRG $PRGOPTS push-line > $OUT/$CURHOST-push-line--$1.txt 2>&1
    #$PRG $PRGOPTS push-arc > $OUT/$CURHOST-push-arc--$1.txt 2>&1
    #$PRG $PRGOPTS rand-lines > $OUT/$CURHOST-rand-lines--$1.txt 2>&1
    #$PRG $PRGOPTS rand-arcs > $OUT/$CURHOST-rand-arcs--$1.txt 2>&1
  fi
  cd $OLDPWD
}
 
function do_tests() {
   do_test "noip"
  if [ "$DO_ALL_COMPILER_SWITCHES" == "yes" ]; then
    WITH_INTERPOLATION=1 do_test "all-on"
    NO_BLEND=1 do_test "noip-noblend"
    NO_MOTION_BLENDING=1 NO_SYNC=1 do_test "noip-noblend-nosync"
    NO_ABC=1 do_test "noip-noabc"
    NO_ABC=1 NO_UVW=1 do_test "noip-noabc-nouvw"
  fi
}
 
function do_stats() {
  OCTAVE="$(which octave)"
  if [ "$OCTAVE" != "" ]; then
    OCTAVE="$OCTAVE -qf $OCTAVE_OPTS"
    if [ "$OCTAVE_SAVE_ONLY" != "yes" ]; then
      rm -f $OUT/*.svg
      rm -f $OUT/*.png
      rm -f $OUT/*.html
    fi
    cd $OUT
    [ "$OCTAVE_SAVE_ONLY" != "yes" ] && cp $ROOT/test/stats.css $OUT/
    find . -type f -name '*.txt' -exec $OCTAVE $ROOT/test/stats.m {} "$OCTAVE_ARG2" \;
    if [ "$MAKE_PDF" == "yes" ]; then
      HTML2PDF="$(which wkhtmltopdf)"
      if [ "$HTML2PDF" != "" ]; then
        for F in *.html; do
          $HTML2PDF -q "$F" "$F.pdf"
        done
      fi
    fi
    if [ "$OCTAVE_SAVE_ONLY" != "yes" ]; then
      echo '<!doctype html><html><head>' > index.html
      echo '<link rel="stylesheet" type="css" href="stats.css"/>' >> index.html
      echo '</head><body><h1>Trajectory generator random motion test results</h1> <ul>' >> index.html
      for F in *.html; do
        if [ "$F" != "index.html" ]; then
          echo '<li><a href="'"$F"'">' "$F" '</a></li>' >> index.html
        fi
      done
      echo '</ul></body></html>' >> index.html
    fi
    cd $OLDPWD
  fi
}
 
[ "$DO_TESTS" == "yes" ] && rm -f $ROOT/bin/*
mkdir -p $ROOT/bin
mkdir -p $OUT
 
if [ "$COMPILE_CALC_TIMES_ONLY" == "yes" ]; then
  cd $ROOT && make >/dev/null
  cd $OUT || exit 1;
  for I in {1..25}; do
    $PRG -p example > "$I.txt" 2>/dev/null
  done
else
  [ "$DO_TESTS" == "yes" ] && do_tests
  [ "$DO_STATS" == "yes" ] && do_stats
fi
cd $OLDPWD

Test analysis (GNU Octave)

#!/usr/local/bin/octave -qf --no-gui --no-history --no-window-system
%
% Trajgen analysis script
%
% @author stfwi
% @file test/stats.m
%
global args;
for k=1:nargin, args{k}=argv(){k}; end;
clear k;
 
function f=infile()
  f = regexprep(argv(){1}, "^\.\/", "");
end
 
function f=infilebase()
  f = regexprep(regexprep(infile(), '\.[^\.]+$', ''), '.*?\/([^\/]+)', '$1');
end
 
function f=infilenoext()
  f= regexprep (infile(), '\.[^\.]+$', '');
end;
 
function f=arg(name)
  global args;
  f = length(args) < 2 || length(args{2})==0 || index(args{2}, name) > 0;
end;
 
function open_html()
  global html_fh;
  global scv_header;
  global build_info;
  global platform_info;
 
  html_fh = fopen([infilenoext() '.html'], 'w');
  write_html([
    '<!doctype html><html><head>' ...
    '<title>Results for ' infilebase() '</title>' ...
    '<link rel="stylesheet" type="text/css" href="stats.css"/>' ...
    '</head><body>' ...
  ]);
  write_html(['<h1>Results for ' infilebase() '</h1>']);
  write_html(['<p class="platform-info"><em>Platform:</em> ' platform_info '</p>']);
  write_html(['<p class="build-info"><em>Build information:</em> ' build_info '</p>']);
  write_html('<p class="csv-header"><em>Data header information: </em><pre>');
  for(k=1:length(scv_header))
    s = scv_header{k};
    s = regexprep(s, '\+([\d])', '$1');
    s = regexprep(s, '\.0+,', ',');
    s = regexprep(s, ':[\s]+', ':');
    s = regexprep(s, ',[\s]+', ', ');
    s = regexprep(s, ':0+,', ':0,');
    write_text(s);
  end
  write_html('</pre></p>');
end
 
function write_text(s)
  global html_fh;
  s = strrep(s, '&', '&amp;');
  s = strrep(s, '<', '&lt;');
  s = strrep(s, '>', '&gt;');
  fprintf(html_fh, "%s\n", s);
end
 
function write_html(s)
  global html_fh;
  fprintf(html_fh, "%s\n", s);
end
 
function close_html()
  global html_fh;
  write_html('</body></html>');
  fclose(html_fh);
end
 
set(0, 'defaultfigurevisible', 'off');
function savefig(tag, size)
  global html_fh;
  if(nargin <2), size='-S900,600'; end;
  file = sprintf('%s-%s.png', infilenoext(), tag);
  fig = gcf();
  if(isfigure(fig))
    warning ('off');
    print(file, [size '-F:20']);
    delete(fig);
  end
  fprintf(html_fh, '<p class="figure"><img src="%s" /></p>\n', file);
end
 
function stats = initargs()
  global args;
  global scv_header;
  global build_info;
  global platform_info;
  global timing_only;
  timing_only = 0;
  scv_header = {};
  build_info = '';
  platform_info = '';
  if(rows(args) < 1), error('Specify a file to analyse.'); end;
  filename = args{1};
  fh = fopen (filename, 'r');
  s0 = fgetl(fh);
  s1 = fgetl(fh);
  k=0;
  while(index(s1, '#') == 1),
    skk = strtrim(substr(s0, 2));
    if(index(skk, 'Platform:') > 0)
      platform_info = strtrim(strrep(skk,'Platform:',''));
    elseif(index(skk, 'Build:') > 0)
      build_info = strtrim(strrep(skk,'Build:',''));
    else
      k = k+1;
      scv_header{k} = skk;
    end
    s0=s1;
    s1=fgetl(fh);
  end;
  s0 =  strsplit(tolower(regexprep(strrep(s1, ' ', ''),'[^,_a-z0-9]', '')), ',');
  csv = csvread(fh);
  for k = 1:columns(s0), stats.(s0{k}) = csv(:,k); end;
  fclose (fh);
  clear csv s1 s0;
 
  %% Some fixes
  if(isfield(stats, 't'))
    %% Fit time
    dt = stats.t(end)-stats.t(end-1);
    stats.t = (0:length(stats.t)-1)' * dt;
  end
  assignin('base', 'stats', stats);
end
 
%% Timing (calculation time) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function time_stats(calctime)
  calctime = calctime(find(calctime > 0));
  calctime = calctime(find(calctime > min(calctime)));
  calctime = calctime(find(calctime < max(calctime)));
  cmin = min(calctime);
  cmax = max(calctime);
  cmean= mean(calctime);
  cmedian = median(calctime);
  cstd = std(calctime);
  num = rows(calctime);
  write_html('<p class="textout"><em>Performance:</em><pre>');
  write_text(sprintf('Maximum calculation time: %.1fus', cmax));
  write_text(sprintf('Average calculation time: %.1fus', cmean));
  write_text(sprintf('StdDev  calculation time: %.1fus', cstd));
  write_text(sprintf('Median  calculation time: %.1fus', cmedian));
  for(k=5:5:95)
    write_text(sprintf('Ticks < %2d%% of max time : %.1f%%', k, ...
        100*rows(find(calctime < cmax/100*k))/num));
  end
  write_html('</pre></p>');
  stats = calctime;
  n = rows(stats);
  x = (1:n).*(100/n);
  plot(x, stats, '-r', 'linewidth', 2);
  grid on;
  xlim([0,100]);
  ylim([0,cmax]);
  title(sprintf('Calculation times'));
  xlabel('Tick (iteration) / % of total tick count');
  ylabel('Calculation time / us');
  savefig('calc-time-stats');
  stats = sort(calctime);
  n = rows(stats);
  x = (1:n).*(100/n);
  plot(x, stats, '-r', 'linewidth', 2);
  grid on;
  xlim([0,100]);
  ylim([0,cmax]);
  title(sprintf('Sorted calculation times'));
  xlabel('Number of calculation cycles / % (sorted by calculation time)');
  ylabel('Calculation time / us');
  savefig('calc-time-stats-sorted');
end
 
%% Joint positions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function joint_positions(t,x,y,z)
  if(y==0),
    plot(t, x, '-k', 'linewidth', 2);
    grid on;
    title(sprintf('Joint position trace (x over time)'));
    xlabel('time');
    ylabel('x / 1');
  elseif(z == 0),
    plot(x, y, '-k', 'linewidth', 2);
    grid on;
    title(sprintf('Joint position trace (x/y)'));
    xlabel('x / 1');
    ylabel('y / 1');
    axis('equal');
  else
    plot3(x, y, z, '-k', 'linewidth', 2);
    grid on;
    title(sprintf('Joint position trace (x/y/z)'));
    xlabel('x / 1');
    ylabel('y / 1');
    zlabel('z / 1');
    axis('equal');
  end
  savefig('joint-positions');
end
 
%% Speeds/accels %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function joint_consistency(t,x,y,z)
  if(y==0), y=zeros(size(x)); end;
  if(z==0), z=zeros(size(z)); end;
  dt = t(end)-t(end-1);
  vx = diff(x)/dt; vx(1) = 0;
  vy = diff(y)/dt; vy(1) = 0;
  vz = diff(z)/dt; vz(1) = 0;
  v = sqrt(vx.*vx+vy.*vy+vz.*vz);
  ax = diff(vx)/dt; ax = [ ax; 0 ]; ax(1) = 0;
  ay = diff(vy)/dt; ay = [ ay; 0 ]; ay(1) = 0;
  az = diff(vz)/dt; az = [ az; 0 ]; az(1) = 0;
  a = sqrt(ax.*ax+ay.*ay+az.*az);
  t = ((1:rows(vx))') * dt;
  subplot(2, 1, 1);
  plot(t, v, '-k', 'linewidth', 2);
  grid on;
  title(sprintf('Trajectory velocity'));
  xlabel('time / 1');
  ylabel('velocity / 1');
  subplot(2, 1, 2);
  plot(t, a, '-k', 'linewidth', 2);
  grid on;
  title(sprintf('Trajectory acceleration'));
  xlabel('time / 1');
  ylabel('acceleration / 1');
  savefig('traj-velocity-acceleration', '-S1024,768');
  subplot(2, 1, 1);
  plot(t, vx, '-k', 'linewidth', 2);
  hold on;
  if(length(find(vy ~= 0))>0), plot(t, vy, '--k', 'linewidth', 2); end;
  if(length(find(vz ~= 0))>0), plot(t, vz,  ':k', 'linewidth', 2); end;
  grid on;
  title(sprintf('Joint velocities'));
  xlabel('time / 1');
  ylabel('velocity / 1');
  subplot(2, 1, 2);
  plot(t, ax, '-k', 'linewidth', 2);
  hold on;
  if(length(find(ay ~= 0))>0), plot(t, ay, '--k', 'linewidth', 2); end;
  if(length(find(az ~= 0))>0), plot(t, az,  ':k', 'linewidth', 2); end;
  grid on;
  title(sprintf('Joint accelerations'));
  xlabel('time / 1');
  ylabel('acceleration / 1');
  savefig('joint-velocity-acceleration');
end
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
disp(sprintf('Analyse %s', infilebase()));
initargs();
if(length(args)>1 && length(args{2})>0 && arg('save-only')),
  clear args;
  save([infilenoext() ".mat"]);
else
  with_timing = arg('timing') && isfield(stats, 't_calc_us');
  with_positions = arg('positions') && isfield(stats, 'j0s');
  with_speeds = (arg('speed') || arg('accel') || arg('velo') ) && isfield(stats, 'j0s');
  if(with_timing || with_positions || with_speeds)
    open_html();
    if(with_timing), time_stats(stats.t_calc_us); end;
    if(with_positions || with_speeds)
      if(!isfield(stats, 't')), t=(0:length(stats.j1s)-1)'; end;
      if(!isfield(stats, 'j1s')), stats.j1s = 0; end;
      if(!isfield(stats, 'j2s')), stats.j2s = 0; end;
      if(with_positions), joint_positions(stats.t, stats.j0s, stats.j1s, stats.j2s); end;
      if(with_speeds), joint_consistency(stats.t,stats.j0s,stats.j1s,stats.j2s); end;
    end
    close_html();
  end
end

Stylesheet of Test analysis

/**
 * Analysis output style sheet
 * @author stfwi
 * @file test/stats.css
 */
* { font-family: monospace; font-size: 11pt; }
html { margin: 0; width: 100%; padding: 0mm; }
body { margin: 0; padding: 0; width: 100%; }
div,img,p,pre,h1 {  position: relative; }
h1 { font-size: 13pt; width: 100%; text-align: center; }
pre { margin-top: 10pt; padding: 0; font-size: 10pt; padding-left: 10mm; }
p, h2, h3 { orphans: 3; widows: 3; }
p.csv-header + pre { font-size: 9pt; }
p { margin: 10pt 0 0 0; padding: 0; display: block; }
p.platform-info { font-size: 10pt; }
p.build-info { font-size: 10pt; }
p.figure { width: 100%; text-align: center; }
img { width: 90%; max-width: 95% !important; page-break-inside: avoid; }
a, a:visited { text-decoration: underline; color: #333399; }
h1, h2, h3 { page-break-after: avoid; }
 
@media screen {
  html { background: #fafafa; }
  body {
    width: 800px !important;
    min-height: 600px;
    padding: 42px 80px 42px 80px;
    margin: 10px;
    background: white;
    box-shadow: #444 2px 3px;
    border: solid 1px #aaa;
    border-radius: 2px;
  }
}
@media print {
  * {
    background: transparent !important;
    color: black !important;
    text-shadow: none !important;
    filter:none !important;
  }
  a[href]:after { content: " (" attr(href) ")"; }
  abbr[title]:after { content: " (" attr(title) ")"; }
  @page { margin: 5mm; }
}